lib/openagents/tools/publish_changes.ex

58e6347eeb72 · 2 KB

defmodule OpenAgents.Tools.PublishChanges do
  @moduledoc "Publishes all chat workspace changes to an assigned Forge branch."

  @behaviour OpenAgents.Tools.Tool

  alias OpenAgents.Modules.Metadata
  alias OpenAgents.Tools.{ExecutionResult, Tool, WorkspacePublication}

  @impl true
  def specification do
    %Tool{
      module_id: "openagents.tool.publish_changes.v1",
      name: "publish_changes",
      version: 1,
      description:
        "Commit all current workspace changes and publish them to the chat run's assigned " <>
          "OpenAgents Forge branch. The server chooses the repository, remote, and branch.",
      input_schema: %{
        "type" => "object",
        "properties" => %{
          "message" => %{"type" => "string", "minLength" => 1, "maxLength" => 2_000},
          "expected_workspace_digest" => %{
            "type" => "string",
            "pattern" => "^[0-9a-f]{64}$"
          }
        },
        "required" => ["message"],
        "additionalProperties" => false
      },
      output_schema: %{"type" => "object", "properties" => %{}, "additionalProperties" => true},
      side_effect: :external_effect,
      required_scope: "browser_conversation",
      required_authority: "repository.write",
      executor: %{
        id: "openagents.forge.workspace_publication",
        disclosure: "the OpenAgents runtime, publishing an isolated chat branch to its own Forge"
      },
      maintainer: "OpenAgents",
      attribution: ["OpenAgentsInc/openagents.com"],
      policy_facets: %{
        "privacy" => "browser_conversation",
        "residency" => "host",
        "consent" => "explicit_publication"
      },
      module_metadata:
        Metadata.first_party("repository.write", "browser_conversation",
          effect: :external_effect,
          privacy: "browser_conversation",
          residency: "host",
          surfaces: ["text"],
          approval_class: "explicit_operator_approval",
          approval_enforcement: "host_receipt"
        ),
      timeout_ms: 60_000,
      maximum_input_bytes: 8_192,
      maximum_output_bytes: 32_768,
      implementation: __MODULE__
    }
  end

  @impl true
  def execute(%{"message" => message} = arguments, context) do
    case WorkspacePublication.publish(
           context,
           String.trim(message),
           Map.get(arguments, "expected_workspace_digest")
         ) do
      {:ok, result} ->
        receipt = result["receipt"]

        {:ok,
         %ExecutionResult{
           result: result,
           target_receipt_refs: [
             "repository-publication:#{result["publication_id"]}",
             "forge-commit:#{result["repository"]}:#{result["published_oid"]}",
             "forge-branch:#{result["repository"]}:#{result["branch"]}",
             "forge-push:#{result["repository"]}:#{receipt["wal_seq"]}"
           ]
         }}

      {:error, reason} ->
        {:error, reason}
    end
  end
end