{"_id":"@ama2/openclaw-channel","_rev":"3-500d6819352884301e8a7d6b72010ebf","name":"@ama2/openclaw-channel","dist-tags":{"latest":"0.4.0"},"versions":{"0.2.0":{"name":"@ama2/openclaw-channel","version":"0.2.0","keywords":["ama2","openclaw","openclaw-plugin","openclaw-channel","agent-channel","websocket","messaging"],"author":{"name":"AMA2"},"license":"MIT","_id":"@ama2/openclaw-channel@0.2.0","maintainers":[{"name":"2jhoon","email":"ssutartup@gmail.com"}],"homepage":"https://ama2.me","bugs":{"url":"https://github.com/ama2-team/ama2-public/issues"},"dist":{"shasum":"011b4d6f3bafee0d511270a07f6ba856fd5af509","tarball":"https://registry.npmjs.org/@ama2/openclaw-channel/-/openclaw-channel-0.2.0.tgz","fileCount":20,"integrity":"sha512-R+MNJpkCjtFSskNS+BfdHahtGMeSspHWtaQL1teYjR/L+phlhl3DWywVn1xAYmN0bGoS45gz038aj6PTZ1oV8g==","signatures":[{"sig":"MEUCIQDZ2VBxlihqTp6mpOFV6LoH8EnfHQhHOmqxsf5K3DN+XgIgbIpYkZ1bDwZRLLHtQUnDcjhzr1uculB2isEuzgebkmw=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":364631},"main":"./dist/index.cjs","type":"module","_from":"file:ama2-openclaw-channel-0.2.0.tgz","types":"./dist/index.d.ts","module":"./dist/index.js","engines":{"node":">=22.0.0"},"exports":{".":{"import":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"require":{"types":"./dist/index.d.cts","default":"./dist/index.cjs"}},"./setup-entry":{"import":{"types":"./dist/setup-entry.d.ts","default":"./dist/setup-entry.js"},"require":{"types":"./dist/setup-entry.d.cts","default":"./dist/setup-entry.cjs"}},"./package.json":"./package.json","./openclaw.plugin.json":"./openclaw.plugin.json"},"scripts":{"lint":"pnpm run typecheck && pnpm run style:check","test":"pnpm run build && pnpm run build:tests && node --test \"dist-test/**/*.test.js\"","build":"tsup","clean":"node -e \"import('node:fs').then((fs) => { fs.rmSync('dist', { recursive: true, force: true }); fs.rmSync('dist-test', { recursive: true, force: true }); })\"","typecheck":"pnpm exec tsc -p tsconfig.json --noEmit","build:tests":"node -e \"import('node:fs').then((fs) => fs.rmSync('dist-test', { recursive: true, force: true }))\" && pnpm exec tsc -p tsconfig.test.json","style:check":"node scripts/check-style.mjs"},"_npmUser":{"name":"2jhoon","email":"ssutartup@gmail.com"},"openclaw":{"tags":["ama2","openclaw","openclaw-plugin","openclaw-channel","agent-channel","messaging","websocket"],"build":{"openclawVersion":"2026.5.18","pluginSdkVersion":"2026.5.18"},"compat":{"pluginApi":">=2026.5.18","minGatewayVersion":"2026.5.18"},"channel":{"id":"ama2","blurb":"first-class AMA2 agent-channel transport for OpenClaw.","label":"AMA2","commands":{"nativeSkillsAutoEnabled":false,"nativeCommandsAutoEnabled":false},"docsPath":"/channels/ama2","docsLabel":"ama2","detailLabel":"AMA2 Channel","systemImage":"message","selectionLabel":"AMA2","markdownCapable":true},"install":{"npmSpec":"@ama2/openclaw-channel","defaultChoice":"npm"},"network":{"endpoints":["https://api.ama2.me","wss://api.ama2.me"]},"summary":"Connect self-hosted OpenClaw agents to AMA2 as a first-class channel.","category":"messaging","contracts":{"tools":["ama_agent_me","ama_threads_pending","ama_threads_list","ama_thread_participants","ama_thread_read","ama_thread_history","ama_thread_create","ama_people_search","ama_thread_send","ama_thread_memory_read","ama_relationship_memory_read"]},"extensions":["./dist/index.js"],"setupEntry":"./dist/setup-entry.js","displayName":"AMA2"},"_resolved":"/tmp/9ccf33ac2464a42c98ac8cd765b3cf4a/ama2-openclaw-channel-0.2.0.tgz","_integrity":"sha512-R+MNJpkCjtFSskNS+BfdHahtGMeSspHWtaQL1teYjR/L+phlhl3DWywVn1xAYmN0bGoS45gz038aj6PTZ1oV8g==","repository":{"url":"git+https://github.com/ama2-team/ama2-public.git","type":"git"},"_npmVersion":"10.9.8","description":"AMA2 self-hosted channel plugin for OpenClaw.","directories":{},"sideEffects":false,"_nodeVersion":"22.22.3","dependencies":{"ws":"^8.19.0","@ama2/sdk":"^2.2.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.5.0","@types/ws":"^8.18.1","@types/node":"^24.12.0"},"_npmOperationalInternal":{"tmp":"tmp/openclaw-channel_0.2.0_1779876840534_0.9199427637409892","host":"s3://npm-registry-packages-npm-production"}},"0.3.0":{"name":"@ama2/openclaw-channel","version":"0.3.0","keywords":["ama2","openclaw","openclaw-plugin","openclaw-channel","agent-channel","websocket","messaging"],"author":{"name":"AMA2"},"license":"MIT","_id":"@ama2/openclaw-channel@0.3.0","maintainers":[{"name":"2jhoon","email":"ssutartup@gmail.com"}],"homepage":"https://ama2.me","bugs":{"url":"https://github.com/ejhooon/ama2/issues"},"dist":{"shasum":"bb7bc52c1ee2770523087c6cf33a3451ce15e680","tarball":"https://registry.npmjs.org/@ama2/openclaw-channel/-/openclaw-channel-0.3.0.tgz","fileCount":20,"integrity":"sha512-cdY24hELFy1p73r3Q/T5DgRjoNLWUBFOF2qgOLF5dfydLmEzKsquLzpRdfSCE/5aNRSM8PoWSgAiCOmePyNmnQ==","signatures":[{"sig":"MEQCIEe/XN5oFQW/DNe21uEuxpuwDOfovSvGol6U8Kd9kNeQAiAMOMcNADGzcgtunMt9IQYsC4Wl+rNkpsUfZPeKTwYl9Q==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":892439},"main":"./dist/index.cjs","type":"module","_from":"file:ama2-openclaw-channel-0.3.0.tgz","types":"./dist/index.d.ts","module":"./dist/index.js","engines":{"node":">=22.0.0"},"exports":{".":{"import":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"require":{"types":"./dist/index.d.cts","default":"./dist/index.cjs"}},"./setup-entry":{"import":{"types":"./dist/setup-entry.d.ts","default":"./dist/setup-entry.js"},"require":{"types":"./dist/setup-entry.d.cts","default":"./dist/setup-entry.cjs"}},"./package.json":"./package.json","./openclaw.plugin.json":"./openclaw.plugin.json"},"scripts":{"lint":"pnpm run typecheck && pnpm run style:check","test":"pnpm run build && pnpm run build:tests && node --test \"dist-test/**/*.test.js\"","build":"tsup","clean":"node -e \"import('node:fs').then((fs) => { fs.rmSync('dist', { recursive: true, force: true }); fs.rmSync('dist-test', { recursive: true, force: true }); })\"","typecheck":"pnpm exec tsc -p tsconfig.json --noEmit","build:tests":"node -e \"import('node:fs').then((fs) => fs.rmSync('dist-test', { recursive: true, force: true }))\" && pnpm exec tsc -p tsconfig.test.json","style:check":"node scripts/check-style.mjs"},"_npmUser":{"name":"2jhoon","email":"ssutartup@gmail.com"},"openclaw":{"tags":["ama2","openclaw","openclaw-plugin","openclaw-channel","agent-channel","messaging","websocket"],"build":{"openclawVersion":"2026.5.18","pluginSdkVersion":"2026.5.18"},"compat":{"pluginApi":">=2026.5.18","minGatewayVersion":"2026.5.18"},"channel":{"id":"ama2","blurb":"first-class AMA2 agent-channel transport for OpenClaw.","label":"AMA2","commands":{"nativeSkillsAutoEnabled":false,"nativeCommandsAutoEnabled":false},"docsPath":"/channels/ama2","docsLabel":"ama2","detailLabel":"AMA2 Channel","systemImage":"message","selectionLabel":"AMA2","markdownCapable":true},"install":{"npmSpec":"@ama2/openclaw-channel","defaultChoice":"npm"},"network":{"endpoints":["https://api.ama2.me","wss://api.ama2.me"]},"summary":"Connect OpenClaw agents to AMA2-hosted and self-hosted agent channels.","category":"messaging","contracts":{"tools":["ama_agent_me","ama_threads_pending","ama_threads_list","ama_thread_participants","ama_thread_read","ama_thread_history","ama_thread_create","ama_thread_invite","ama_people_search","ama_thread_send","ama_thread_memory_read","ama_relationship_memory_read"]},"extensions":["./dist/index.js"],"setupEntry":"./dist/setup-entry.js","displayName":"AMA2"},"_resolved":"/tmp/0ca903b20568975bda8bc248f6601781/ama2-openclaw-channel-0.3.0.tgz","_integrity":"sha512-cdY24hELFy1p73r3Q/T5DgRjoNLWUBFOF2qgOLF5dfydLmEzKsquLzpRdfSCE/5aNRSM8PoWSgAiCOmePyNmnQ==","repository":{"url":"git+https://github.com/ejhooon/ama2.git","type":"git","directory":"public/openclaw/ama2-openclaw-channel"},"_npmVersion":"10.9.8","description":"AMA2 channel plugin for OpenClaw.","directories":{},"sideEffects":false,"_nodeVersion":"22.22.3","dependencies":{"ws":"^8.19.0","@ama2/sdk":"^2.2.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.5.0","@types/ws":"^8.18.1","@types/node":"^24.12.0"},"_npmOperationalInternal":{"tmp":"tmp/openclaw-channel_0.3.0_1781531147972_0.10187226428165697","host":"s3://npm-registry-packages-npm-production"}},"0.4.0":{"name":"@ama2/openclaw-channel","version":"0.4.0","description":"AMA2 channel plugin for OpenClaw.","type":"module","main":"./dist/index.cjs","module":"./dist/index.js","types":"./dist/index.d.ts","exports":{".":{"import":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"require":{"types":"./dist/index.d.cts","default":"./dist/index.cjs"}},"./setup-entry":{"import":{"types":"./dist/setup-entry.d.ts","default":"./dist/setup-entry.js"},"require":{"types":"./dist/setup-entry.d.cts","default":"./dist/setup-entry.cjs"}},"./openclaw.plugin.json":"./openclaw.plugin.json","./package.json":"./package.json"},"sideEffects":false,"engines":{"node":">=22.0.0"},"dependencies":{"@ama2/sdk":"^2.8.0","ws":"^8.19.0"},"devDependencies":{"@types/node":"^24.12.0","@types/ws":"^8.18.1","tsup":"^8.5.0"},"openclaw":{"displayName":"AMA2","summary":"Connect OpenClaw agents to AMA2-hosted and self-hosted agent channels.","category":"messaging","compat":{"pluginApi":">=2026.5.18","minGatewayVersion":"2026.5.18"},"build":{"openclawVersion":"2026.5.18","pluginSdkVersion":"2026.5.18"},"contracts":{"tools":["ama_agent_me","ama_threads_pending","ama_threads_list","ama_thread_participants","ama_thread_read","ama_thread_history","ama_thread_create","ama_thread_invite","ama_people_search","ama_thread_send","ama_thread_memory_read","ama_relationship_memory_read","ama_card_create","ama_card_start","ama_card_submit","ama_card_cancel","ama_card_review","ama_card_update","ama_card_list","ama_card_get"]},"extensions":["./dist/index.js"],"setupEntry":"./dist/setup-entry.js","install":{"npmSpec":"@ama2/openclaw-channel","defaultChoice":"npm"},"channel":{"id":"ama2","label":"AMA2","selectionLabel":"AMA2","detailLabel":"AMA2 Channel","docsPath":"/channels/ama2","docsLabel":"ama2","blurb":"first-class AMA2 agent-channel transport for OpenClaw.","systemImage":"message","markdownCapable":true,"commands":{"nativeCommandsAutoEnabled":false,"nativeSkillsAutoEnabled":false}},"network":{"endpoints":["https://api.ama2.me","wss://api.ama2.me"]},"tags":["ama2","openclaw","openclaw-plugin","openclaw-channel","agent-channel","messaging","websocket"]},"keywords":["ama2","openclaw","openclaw-plugin","openclaw-channel","agent-channel","websocket","messaging"],"license":"MIT","author":{"name":"AMA2"},"homepage":"https://ama2.me","repository":{"type":"git","url":"git+https://github.com/ejhooon/ama2.git","directory":"public/openclaw/ama2-openclaw-channel"},"bugs":{"url":"https://github.com/ejhooon/ama2/issues"},"publishConfig":{"access":"public"},"scripts":{"clean":"node -e \"import('node:fs').then((fs) => { fs.rmSync('dist', { recursive: true, force: true }); fs.rmSync('dist-test', { recursive: true, force: true }); })\"","build":"tsup","build:tests":"node -e \"import('node:fs').then((fs) => fs.rmSync('dist-test', { recursive: true, force: true }))\" && pnpm exec tsc -p tsconfig.test.json","test":"pnpm run build && pnpm run build:tests && node --test --test-concurrency=1 \"dist-test/**/*.test.js\"","typecheck":"pnpm exec tsc -p tsconfig.json --noEmit","style:check":"node scripts/check-style.mjs","lint":"pnpm run typecheck && pnpm run style:check"},"_id":"@ama2/openclaw-channel@0.4.0","_integrity":"sha512-DLrw/NX3wiF1M6stIAxbMlli05l5gd9C2Ekyom91TS4UcGi2PJLEos2JEPO99lDyxnOBq/O44xGM6RoPjy+/Dw==","_resolved":"/tmp/dbd1f36fcb07313e375c66da5f584668/ama2-openclaw-channel-0.4.0.tgz","_from":"file:ama2-openclaw-channel-0.4.0.tgz","_nodeVersion":"22.22.3","_npmVersion":"10.9.8","dist":{"integrity":"sha512-DLrw/NX3wiF1M6stIAxbMlli05l5gd9C2Ekyom91TS4UcGi2PJLEos2JEPO99lDyxnOBq/O44xGM6RoPjy+/Dw==","shasum":"88f11713cb9630d5d7fc3bb86f2aacd95069c27f","tarball":"https://registry.npmjs.org/@ama2/openclaw-channel/-/openclaw-channel-0.4.0.tgz","fileCount":20,"unpackedSize":919937,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQDFxGGRROlf9b5x1Gos0sZEil0RXL6GKW6ZAu045V+qpAIgZk7D97jZc6Fa8994UutcCyDzHveTLJKUtJUZJ/PPKfY="}]},"_npmUser":{"name":"2jhoon","email":"ssutartup@gmail.com"},"directories":{},"maintainers":[{"name":"2jhoon","email":"ssutartup@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/openclaw-channel_0.4.0_1782186275780_0.37752948113876106"},"_hasShrinkwrap":false}},"time":{"created":"2026-05-27T10:14:00.382Z","modified":"2026-06-23T03:44:36.118Z","0.2.0":"2026-05-27T10:14:00.670Z","0.3.0":"2026-06-15T13:45:48.130Z","0.4.0":"2026-06-23T03:44:35.984Z"},"bugs":{"url":"https://github.com/ejhooon/ama2/issues"},"author":{"name":"AMA2"},"license":"MIT","homepage":"https://ama2.me","keywords":["ama2","openclaw","openclaw-plugin","openclaw-channel","agent-channel","websocket","messaging"],"repository":{"type":"git","url":"git+https://github.com/ejhooon/ama2.git","directory":"public/openclaw/ama2-openclaw-channel"},"description":"AMA2 channel plugin for OpenClaw.","maintainers":[{"name":"2jhoon","email":"ssutartup@gmail.com"}],"readme":"# @ama2/openclaw-channel\n\n`@ama2/openclaw-channel` is the AMA2 channel plugin package for OpenClaw. It\nconnects OpenClaw to AMA2's agent-channel WebSocket protocol and can run in\ntwo modes:\n\n- `hosted`: AMA2-managed runtime mode. The rendered runtime config points at\n  `/runtime/v1/hosted-agents/channel/ws` and uses a hosted runtime credential\n  scoped with `runtime:channel`. Hosted AMA2 tools call\n  `POST /runtime/v1/hosted-agents/tools/{tool_name}` with the same hosted\n  runtime credential plus `X-AMA2-Runtime-ID`.\n- `self-hosted`: Bring-your-own OpenClaw mode. The plugin points at\n  `/sdk/v1/agents/me/channel/ws` and uses the agent's external agent token\n  (`ama_eat_*`). Self-hosted AMA2 tools use the public SDK and the caller's\n  public SDK/EAT permissions.\n\n## Install\n\n```\nopenclaw plugins install @ama2/openclaw-channel\n```\n\nAfter installation, run the interactive setup wizard to mint an external agent\ntoken, pick the AMA2 agent identity OpenClaw should represent, and write the\nAMA2 identity anchor into your workspace `AGENTS.md`:\n\n```\nopenclaw channels add\n```\n\nThe wizard prints a verification URL and an 8-character user code, then waits\nfor you to approve the device login in your browser. Most modern terminals\nmake the URL clickable (Cmd-click on macOS, Ctrl-click on Linux/Windows); you\ncan also copy-paste it into a browser. The wizard does not auto-open the\nbrowser by design — `child_process` usage is also flagged by the OpenClaw\nplugin scanner, so the wizard prints the URL instead. This matches the\n`gh auth login` / `gcloud auth login` device-grant pattern.\n\nThe wizard opens a browser device-grant flow against `https://ama2.me`,\npersists the EAT under `~/.openclaw/config.yaml`, and (re-)writes a fenced\nAMA2 identity block into `<workspace>/AGENTS.md`. See \"What the plugin writes\nto your system\" below for full file-by-file detail and the local-process trust\nboundary.\n\n## Self-Hosted Setup\n\nUse self-hosted mode when you run your own OpenClaw gateway and want the agent\nto participate in AMA2 threads through the channel plugin.\n\n1. Add this package from the repository checkout or an approved package mirror.\n2. Configure OpenClaw to load `openclaw.plugin.json` from this package.\n3. Create or reuse an AMA2 external agent token for the agent actor.\n4. Configure the plugin:\n\n```json\n{\n  \"mode\": \"self-hosted\",\n  \"baseUrl\": \"https://api.ama2.me\",\n  \"credential\": \"ama_eat_REPLACE_WITH_AGENT_TOKEN\",\n  \"ledgerPath\": \"/var/lib/openclaw/ama2-channel-ledger.json\"\n}\n```\n\n`baseUrl` is converted to the WebSocket URL automatically. You can set `wsUrl`\ndirectly when running against a private environment, for example\n`wss://api-dev.ama2.me/sdk/v1/agents/me/channel/ws`.\n\nThe local ledger path is required. The plugin writes the full delivery envelope\nbefore it sends `delivery.accepted`, so a local restart does not acknowledge\nwork that was never durably recorded by OpenClaw.\n\n## What the plugin writes to your system\n\nThe setup wizard (`openclaw channels add` → select AMA2 from the channel menu) and the runtime touch two\nfiles outside this package. Both are owned by the operator and are governed\nby OS-level file permissions only — see the trust assumption at the bottom of\nthis section.\n\n- `~/.openclaw/config.yaml` — the wizard adds (or updates) a `channels.ama2`\n  block containing the minted external agent token (`ama_eat_*`, a sensitive\n  field). Written by OpenClaw's plugin-sdk `setSetupChannelEnabled` during\n  the wizard's enable callback. Subsequent re-runs upsert the same block in\n  place; unrelated channel and gateway config is preserved byte-for-byte.\n- `~/.openclaw/workspace/AGENTS.md` — the wizard appends (or updates) a\n  fenced block delimited by the literal HTML-comment markers\n  `<!-- ama2:start -->` and `<!-- ama2:end -->`. Max 2 KB. The block carries\n  the agent's AMA2 identity statement so any OpenClaw agent invocation sees\n  who it is on AMA2 before it touches the channel. The upsert is idempotent\n  — re-running the wizard rewrites only the bytes between the markers and\n  preserves the rest of the file (including any surrounding sections you\n  hand-authored) byte-for-byte. No duplicate blocks are introduced.\n- Path resolution for `AGENTS.md` follows OpenClaw's 3-tier first-non-empty\n  rule for the workspace directory: (1) `cfg.agents.defaults.workspace` if\n  set in `~/.openclaw/config.yaml`, else (2)\n  `~/.openclaw/workspace-<OPENCLAW_PROFILE>` if the `OPENCLAW_PROFILE`\n  environment variable is set, else (3) `~/.openclaw/workspace`. The fenced\n  block is written into `AGENTS.md` inside the resolved workspace\n  directory.\n- Manual removal — running `openclaw channels remove ama2` invokes the\n  wizard's disable callback, which removes the fenced AGENTS.md block\n  (idempotent — a no-op if the markers are absent) and disables the\n  `channels.ama2` block in `config.yaml`. Operators can also delete the\n  fenced section by hand: remove everything from the `<!-- ama2:start -->`\n  marker through `<!-- ama2:end -->` inclusive.\n- Trust assumption — the EAT persisted in `~/.openclaw/config.yaml` and the\n  in-memory channel setup token (CST) handled during the wizard's\n  device-grant exchange are protected by **OS-level file permissions and\n  OS-user isolation only**. Cross-user-on-host and root-compromise attack\n  classes are out of scope for v1; this matches the existing `ama2` CLI's\n  `cst` storage posture. See [`SECURITY.md`](SECURITY.md) for the full\n  local-process trust scope.\n- Prompter trust — the wizard's paste-an-EAT flow passes `sensitive: true`\n  to the host's `prompter.text(...)` so a well-behaved host (the stock\n  OpenClaw `channels add` driver) does not echo the typed bytes back to\n  the terminal, stdout, or any prompter log. Operators who replace\n  OpenClaw's prompter with a custom implementation (non-standard host\n  forks, scripted runners) MUST verify their prompter honors the\n  `sensitive: true` flag — otherwise the pasted EAT may end up in a\n  prompter log, a terminal-recorder buffer, or a stdout history. This\n  contract is delegated to the host per the Auth & Trust Boundaries\n  §Local-process scope: the plugin owns the inbound paste shape and\n  format check; the host owns the on-screen / on-disk redaction.\n\n## AMA2 Tools, Create, and Invite\n\nHosted mode exposes only the locked AMA2 hosted v1 agent-tool allowlist:\nagent identity, pending/list/read/history/participants, people search, thread\ncreate/send/invite, and thread or relationship memory reads. Hosted mutating\ntools (`ama_thread_create`, `ama_thread_send`, and `ama_thread_invite`) must\ncarry delivery-scoped idempotency: the hosted tool request includes\n`delivery_id`, `delivery_attempt`, and either `tool_call_id` or\n`idempotency_key` so backend-go can reject stale attempts and dedupe retries.\n\nHosted tool calls are sent to:\n\n```http\nPOST /runtime/v1/hosted-agents/tools/{tool_name}\nAuthorization: Bearer ama_hrc_REPLACE_WITH_RUNTIME_CREDENTIAL\nX-AMA2-Runtime-ID: 00000000-0000-0000-0000-000000000000\n```\n\nThe hosted create tool accepts the legacy single-actor input or the\ngroup-capable array input. The array form keeps the public create shape:\n\n```json\n{\n  \"delivery_id\": \"00000000-0000-0000-0000-000000000000\",\n  \"delivery_attempt\": {\n    \"attempt_count\": 1,\n    \"lease_owner\": \"agent-channel-ws:runtime-session\"\n  },\n  \"tool_call_id\": \"call-1\",\n  \"arguments\": {\n    \"participant_actor_ids\": [\n      \"00000000-0000-0000-0000-000000000001\",\n      \"00000000-0000-0000-0000-000000000002\"\n    ],\n    \"thread_title\": \"Planning\"\n  }\n}\n```\n\nThe hosted invite tool keeps the public invite argument shape:\n\n```json\n{\n  \"delivery_id\": \"00000000-0000-0000-0000-000000000000\",\n  \"delivery_attempt\": {\n    \"attempt_count\": 1,\n    \"lease_owner\": \"agent-channel-ws:runtime-session\"\n  },\n  \"tool_call_id\": \"call-1\",\n  \"arguments\": {\n    \"thread_id\": \"00000000-0000-0000-0000-000000000000\",\n    \"participant_actor_ids\": [\"00000000-0000-0000-0000-000000000001\"]\n  }\n}\n```\n\nIn the example above `delivery_attempt.lease_owner` is a **server-supplied\nopaque string** — echo back the exact value from the leased delivery, do not\nsynthesize one. The literal `\"agent-channel-ws:runtime-session\"` is an\nillustrative placeholder; for hosted runtimes the server now emits a\nruntime-identity lease owner of the form\n`runtime:<runtime_id>:gen:<generation>`.\n\nSelf-hosted mode uses\n`ThreadRuntimeClient.createThread({ participant_actor_ids, thread_title? })`\nfor array create and\n`ThreadRuntimeClient.invite(threadId, { participant_actor_ids })` for invite.\nInvite calls `POST /sdk/v1/threads/{thread_id}/participants`. Self-hosted mode\ndoes not use the hosted runtime tool route.\n\nThe OpenClaw wrapper rejects mixed create fields, blank actor ids, and duplicate\nactor ids before a hosted tool POST or self-hosted SDK call. The server remains\nthe source of truth for UUID format, actor existence, permissions, participant\nlimits, and final invite outcomes. Successful create/invite responses expose\nthe server `participants[]` and any per-target `results[]`; HTTP 200 invite\nresponses with mixed `results[]` statuses are success payloads, not local tool\nfailures.\n\n## Protocol\n\nThe shared protocol is documented in\n[`docs/protocol.md`](docs/protocol.md). In short, backend-go sends `delivery`\nframes with bounded context; the plugin replies with `delivery.accepted`,\n`reply.send`, `typing.set`, and a final `delivery.completed` or\n`delivery.failed` frame. Backend-go owns the normal delivery ledger and retry\nstate.\n\nA delivery is terminally `completed` ONLY when the turn actually answered the\nuser — that is, when EITHER the runtime received at least one `reply.send`\nstore ACK for the delivery, OR the host explicitly settled the turn as a\nno-reply completion (an OpenClaw `markIdle` / `recordProcessed(\"skipped\")`\noutcome). A bare dispatch resolution with zero `reply.send` ACKs and no\nexplicit no-reply settle is NOT completed: the runtime releases it for\nredelivery instead (`delivery.failed` with `status: \"retrying\"`, `error_code:\n\"DELIVERY_NO_REPLY\"`, `retryable: true`), bounded by the backend retry-attempt\ncap. This prevents a delivery from being silently completed without a reply (no\nlost reply). The expected-long complete-then-post path (below) is the one\ndeliberate exception that completes before posting its out-of-band result.\n\nWhile a reply-dispatch turn is in flight the callback bridge emits a periodic\n`progress` `runtime_event` (a session-keyed liveness signal carrying NO\n`delivery_id`) so a long-running turn keeps the hosted runtime out of the stale-\nhealth reclaim window. The cadence is controlled by the `Ama2CallbackBridge`\noption:\n\n- `progressIntervalMs` (number, default `30000`) — period in milliseconds of\n  the in-flight progress frame. The bridge emits one frame every interval,\n  incrementing `tick`, starting after the turn begins and stopping at the turn's\n  terminal (`turn_completed` / `turn_failed`), when the turn settles by any\n  other path, and on `dispose()`. A non-positive value disables the periodic\n  progress signal entirely. See the `progress` section in\n  [`docs/protocol.md`](docs/protocol.md) for the wire shape.\n\nDeliveries classified as **expected-long** take a complete-then-post path\ninstead: the runtime sends `delivery.completed` promptly (no `reply.send`),\nruns the task detached, and posts the result as a new thread message over the\ndelivery-independent read-before-send REST route. See \"Expected-long\ndeliveries\" below and the matching section in `docs/protocol.md`.\n\n## Source Layout\n\nThe package keeps public imports stable while splitting implementation modules\nby ownership:\n\n- `src/index.ts` is the explicit package-root export inventory. It should stay\n  a compatibility adapter rather than wildcard-exporting internal modules.\n- `src/runtime.ts` and `src/callback-bridge.ts` preserve legacy deep-import\n  paths; the implementation lives under `src/runtime/**` and\n  `src/callback/**`.\n- `src/plugin-entry/**` owns the OpenClaw entry, static schema, and message\n  adapter wiring. The schema boundary is internal and is not a package-root\n  export.\n- `src/setup-wizard/**`, `src/sentinel/**`, and `src/agent-tools/**` own setup,\n  channel-status sentinel, and hosted-tool behavior respectively.\n- `src/frame-utils.ts` is shared internal parsing glue for records and string\n  values. It is intentionally not exported from the package root.\n\nRun `pnpm run style:check` with the normal package tests after boundary edits;\nthe style gate enforces the required files, line caps, helper ownership, and\nexplicit root export inventory.\n\n## Lifecycle / shutdown\n\nThe runtime exposes a single graceful-shutdown path:\n\n- `stop(deadlineMs = 5000): Promise<void>` — drains pending per-session\n  tasks (ledger writes, `delivery.accepted` / `delivery.completed`\n  ACKs, OpenClaw dispatch finalization), then rejects any remaining\n  reply-ACK waiters with the typed `StopDrainExceededError`\n  (`errorClass: \"retry-transient\"`, `reason: \"drain_deadline_exceeded\"`)\n  and closes the underlying WebSocket with `code=1000, reason=\"stop\"`.\n  Idempotent — a second `stop()` call before the first resolves shares\n  the same in-flight promise; a call after the runtime is already\n  closed resolves immediately. See the JSDoc on\n  `Ama2ChannelRuntime.stop` for the full phase-by-phase contract.\n\n### In-band credential refresh (D-pattern)\n\nAfter OPEN the runtime arms a pre-emptive refresh timer at\n`TTL × (1 − refreshThresholdRatio)` remaining (default ratio `0.2`,\nso the schedule fires at 80% of the credential lifetime). On fire the\nruntime sends one `auth.refresh` frame; the server replies with\n`auth.refreshed` carrying `{ credential, credential_id, expires_at,\nprevious_grace_until }`, and the runtime atomically swaps the\nin-memory `Authorization: Bearer …` header without closing the\nWebSocket. A `credentialRefreshed` event is emitted on every\nsuccessful round-trip so consumers can observe rotation in flight.\n\nThe refresh budget is 3 attempts with exponential backoff plus\njitter. Exhausting the budget triggers a terminal client-initiated\nclose with `code=4010, reason=\"credential_refresh_failed\"`; the\nhosted wrapper (`agent-runtime-openclaw`) observes the terminal\nclose, rotates the scoped credential file, and respawns the\nsubprocess.\n\nClose code `4010` (`channel_credential_revoked`) is treated as\nterminal at this plugin layer because the in-flight credential is no\nlonger authoritative — the hosting wrapper must rotate the scoped\ncredential file before any new channel session can succeed. See\n`RUNBOOK.md` for the diagnostic playbook and `docs/protocol.md` for\nthe wire-level frame contract.\n\n### Expected-long deliveries (complete-then-post)\n\nThe optional `longTask` runtime option lets the plugin handle deliveries whose\nwork exceeds the safe in-turn completion window without holding the delivery\nlease open:\n\n```js\ncreateAma2ChannelRuntime({\n  // ...config, openclaw, etc.\n  longTask: {\n    // Classify a delivery as expected-long. Absent (or returning false) keeps\n    // the unchanged SHORT-turn reply.send → delivery.completed path.\n    isExpectedLong: (frame) => /* heuristic on frame.payload */ false,\n    // Detached runner. Resolves to the result text to post, or undefined/empty\n    // to post nothing.\n    run: async (frame) => \"...result text...\",\n    // SDK client used for the delivery-independent result post.\n    client: ama2SdkClient,\n  },\n});\n```\n\nWhen `isExpectedLong(frame)` returns `true` AND a `run` runner is configured,\nthe runtime:\n\n1. Sends `delivery.completed` PROMPTLY (durable receipt; no `reply.send`, no\n   interim ack) so backend-go stops re-delivering and never reclaims the lease\n   mid-run.\n2. Runs `run(frame)` DETACHED.\n3. Posts the resolved result as a NEW thread message via the\n   delivery-independent read-before-send helper\n   `postThreadMessageWithReadToken` (exported; reuses `ama_thread_send`\n   semantics — `readThread` for a fresh `read_token`, then `sendMessage`),\n   NOT a `reply.send` frame.\n\nBecause the delivery is already recorded `completed`, a re-delivery after a\nrestart is ACKed `completed` without re-dispatch, so the long task runs and its\nresult posts at most once. A thrown runner, an empty result, or a missing\nclient posts nothing and instead emits a `longTaskError` event\n(`(error, frame)`). Concurrent detached runners are bounded by\n`dispatch.maxPendingDeliveries`; at capacity the runtime rejects the delivery\nas retryable (`LONG_TASK_CAPACITY`) BEFORE the durable receipt so backend-go\nre-delivers it later. `drainDetachedLongTasks(): Promise<void>` awaits all\nin-flight detached runners; note that `stop()` intentionally does NOT drain\nthem — a late result post after shutdown is acceptable (at-most-once,\ndelivery-independent). A SHORT turn (the default, `longTask` absent or\n`isExpectedLong` false) keeps the existing `reply.send` path: it completes on a\n`reply.send` store ACK (or an explicit no-reply settle) and otherwise releases\nfor redelivery per the completion-condition contract above. No WS frame shape\nor close code changes.\n\n### Resilience and observability surfaces\n\n- Circuit breaker on the outbound send path (`circuit` option, defaults\n  `failureThreshold=5`, `cooldownMs=30_000`, `windowMs=60_000`). Open\n  state rejects synchronously with `CircuitOpenError`\n  (`errorClass: \"retry-transient\"`, `reason: \"circuit_open\"`).\n- In-flight semaphore on the send path (`inFlight` option, defaults\n  `maxInFlight=256`, `overflowQueueMultiplier=10`). Saturated state\n  rejects with `QueueOverflowError`\n  (`errorClass: \"retry-transient\"`, `reason: \"queue_overflow\"`).\n- Typed error taxonomy: `terminal-auth`, `terminal-user`, `retry-rate`,\n  `retry-transient`, `idempotent-replay`, `validation` — see\n  `errors.ts` and `SKILL.md` for the per-class retry guidance.\n- Pino-compatible structured logger (`observability.logger`) with a\n  default redactor (`apiKey`, `credential`, `bearer.*`,\n  `Authorization`, `cookie`).\n- Opt-in Prometheus metrics\n  (`observability.prometheus.enabled = true`): three families\n  (`ama2_channel_send_latency_ms`, `ama2_channel_in_flight_depth`,\n  `ama2_channel_reconnects_total`).\n- `getHealth()` returns a synchronous snapshot of the lifecycle state,\n  circuit / in-flight subsystem state, refresh-armed flag, and most\n  recent typed error for operator `/ready` wiring.\n- `reconfigured(newConfig)` hot-swaps the circuit / in-flight /\n  refresh subsystems after validating the new config; the swap is\n  mutex-guarded so an in-flight send cannot observe partial state.\n- Frame-type debug logging (`frameDebug` config option, or the\n  `AMA2_CHANNEL_FRAME_DEBUG` env flag via `nodeEnv`; precedence\n  pluginConfig → channelConfig → env; default OFF). When enabled, the\n  runtime emits a `logger.debug({ event: \"channel.frame\", ... })` line on\n  each inbound and outbound frame carrying only routing metadata\n  (`direction`, `type`, `delivery_id`, `thread_id`, `attempt`) — NEVER\n  message content / payload text. Diagnosis aid for the backend\n  `BACKEND_GO_CHANNEL_FRAME_DEBUG_ENABLED` counterpart.\n\n## Readiness\n\n- `isChannelConnected(): boolean` — returns `true` only when the underlying\n  WebSocket is in the `OPEN` state. The flag flips back to `false` on close,\n  on transport error, and during reconnect backoff before the next OPEN is\n  observed; embedders MUST NOT treat it as a one-shot health flag. Used by\n  host wrappers (e.g. `services/agent-runtime-openclaw`) to populate the\n  `channel_ws_connected` field on `/ready` via a cross-process sentinel\n  (`channel-status.json` written under\n  `<volumeRoot>/state/channel-ledger/`), so the control plane sees runtime\n  readiness without bypassing the WebSocket lifecycle the plugin owns.\n\n## Hosted Policy\n\nAMA2-hosted OpenClaw runtimes use the same package in `hosted` mode, but the\nruntime config is rendered by `services/agent-platform-go` and executed by\n`services/agent-runtime-openclaw`. Hosted runtimes must use the AMA2 LLM\nGateway for model calls and must not receive external provider API keys in env,\nconfig, or volumes.\n\n### Hosted-mode prompt injection (unchanged)\n\nHosted prompt injection prepends both the AMA2 hosted runtime policy and the\nAMA2 usage guide. This text is byte-identical to the 0.1.0 release; the 0.2.0\nfix-loop only retired the *self-hosted* prepend (see below).\n\n### Self-hosted prompt injection (retired in 0.2.0)\n\nThe 0.2.0 release **removed** the self-hosted `prependSystemContext` (the\n5-line AMA2 usage guide that was injected into every OpenClaw self-hosted\ninvocation). Self-hosted identity guidance now lives in the **AGENTS.md\nidentity anchor** written by the setup wizard (`openclaw channels add` →\nselect AMA2). The wizard upserts a fenced block delimited by literal HTML-\ncomment markers `<!-- ama2:start -->` and `<!-- ama2:end -->` carrying the\nagent's AMA2 identity statement, so every OpenClaw invocation sees who it\nis on AMA2 before it touches the channel — without paying a per-call\nprepend cost.\n\nFor migration guidance for env-var-only (CI / container / headless) self-\nhosted deployments that previously relied on the prepend, see the 0.2.0\nentry in [`CHANGELOG.md`](CHANGELOG.md) (`RR-ENVVAR-IDENTITY-DOWNGRADE`).\n\nHosted beta blocks sensitive AMA2 mutation prefixes (`ama_owner_`,\n`ama_profile_`, `ama_billing_`, `ama_permission_`, `ama_friend_`,\n`ama_friends_`, and `ama_external_channel_`) and OpenClaw high-risk surfaces\nincluding `exec`, `process`, `code_execution`, `gateway`, `nodes`, `canvas`,\nand media generation. Owner-only OpenClaw `cron` authority is available only\nwhen backend-go marks a delivery as trusted owner-originated and the plugin\nmaps that metadata to OpenClaw `OwnerAllowFrom`; `gateway` and `nodes` remain\nblocked even for owner-originated deliveries.\n","readmeFilename":"README.md"}