agent-embassy 1.8.2 → 1.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/README.md +23 -3
  3. package/README.zh-CN.md +23 -3
  4. package/SECURITY.md +30 -6
  5. package/dist/src/gateway/cli-copy.en.d.ts +1 -1
  6. package/dist/src/gateway/cli-copy.en.js +8 -0
  7. package/dist/src/gateway/cli-copy.en.js.map +1 -1
  8. package/dist/src/gateway/cli-copy.zh-CN.d.ts +1 -1
  9. package/dist/src/gateway/cli-copy.zh-CN.js +8 -0
  10. package/dist/src/gateway/cli-copy.zh-CN.js.map +1 -1
  11. package/dist/src/gateway/cli.d.ts +8 -3
  12. package/dist/src/gateway/cli.js +191 -19
  13. package/dist/src/gateway/cli.js.map +1 -1
  14. package/dist/src/gateway/config.d.ts +2 -0
  15. package/dist/src/gateway/config.js +1 -1
  16. package/dist/src/gateway/config.js.map +1 -1
  17. package/dist/src/gateway/control.d.ts +79 -25
  18. package/dist/src/gateway/control.js +98 -18
  19. package/dist/src/gateway/control.js.map +1 -1
  20. package/dist/src/gateway/dashboard-copy.d.ts +1 -1
  21. package/dist/src/gateway/dashboard-copy.en.d.ts +2 -0
  22. package/dist/src/gateway/dashboard-copy.en.js +2 -0
  23. package/dist/src/gateway/dashboard-copy.en.js.map +1 -1
  24. package/dist/src/gateway/dashboard-copy.js +2 -0
  25. package/dist/src/gateway/dashboard-copy.js.map +1 -1
  26. package/dist/src/gateway/dashboard-copy.zh-CN.d.ts +2 -0
  27. package/dist/src/gateway/dashboard-copy.zh-CN.js +2 -0
  28. package/dist/src/gateway/dashboard-copy.zh-CN.js.map +1 -1
  29. package/dist/src/gateway/dashboard-model.d.ts +3 -0
  30. package/dist/src/gateway/dashboard-model.js +3 -0
  31. package/dist/src/gateway/dashboard-model.js.map +1 -1
  32. package/dist/src/gateway/federation-nodes.d.ts +18 -0
  33. package/dist/src/gateway/federation-nodes.js +142 -0
  34. package/dist/src/gateway/federation-nodes.js.map +1 -0
  35. package/dist/src/gateway/live-dashboard-app/app.js +5 -4
  36. package/dist/src/gateway/peer-client.d.ts +55 -0
  37. package/dist/src/gateway/peer-client.js +203 -0
  38. package/dist/src/gateway/peer-client.js.map +1 -0
  39. package/dist/src/gateway/peer-mailbox.d.ts +78 -0
  40. package/dist/src/gateway/peer-mailbox.js +168 -0
  41. package/dist/src/gateway/peer-mailbox.js.map +1 -0
  42. package/dist/src/gateway/peer-protocol.d.ts +107 -0
  43. package/dist/src/gateway/peer-protocol.js +72 -0
  44. package/dist/src/gateway/peer-protocol.js.map +1 -0
  45. package/dist/src/gateway/peer-stdio.d.ts +23 -0
  46. package/dist/src/gateway/peer-stdio.js +100 -0
  47. package/dist/src/gateway/peer-stdio.js.map +1 -0
  48. package/dist/src/gateway/provenance-envelope.js +6 -1
  49. package/dist/src/gateway/provenance-envelope.js.map +1 -1
  50. package/dist/src/gateway/providers.d.ts +5 -2
  51. package/dist/src/gateway/providers.js +10 -7
  52. package/dist/src/gateway/providers.js.map +1 -1
  53. package/dist/src/gateway/server.d.ts +4 -1
  54. package/dist/src/gateway/server.js +21 -8
  55. package/dist/src/gateway/server.js.map +1 -1
  56. package/dist/src/gateway/service.d.ts +18 -0
  57. package/dist/src/gateway/service.js +423 -37
  58. package/dist/src/gateway/service.js.map +1 -1
  59. package/dist/src/gateway/state-v2-to-v3.js +1 -0
  60. package/dist/src/gateway/state-v2-to-v3.js.map +1 -1
  61. package/dist/src/gateway/store.d.ts +9 -1
  62. package/dist/src/gateway/store.js +183 -43
  63. package/dist/src/gateway/store.js.map +1 -1
  64. package/dist/src/gateway/types.d.ts +7 -6
  65. package/dist/src/gateway/types.js +5 -10
  66. package/dist/src/gateway/types.js.map +1 -1
  67. package/docs/CONFIGURATION.md +23 -8
  68. package/docs/CONFIGURATION.zh-CN.md +12 -4
  69. package/docs/DASHBOARD.md +2 -2
  70. package/docs/DASHBOARD.zh-CN.md +2 -2
  71. package/docs/DELIVERY.md +3 -1
  72. package/docs/DELIVERY.zh-CN.md +3 -1
  73. package/docs/GATEWAY-ARCHITECTURE.md +6 -4
  74. package/package.json +1 -1
  75. package/skills/embassy-peer/SKILL.md +32 -4
  76. package/skills/embassy-peer/agents/openai.yaml +2 -2
package/CHANGELOG.md CHANGED
@@ -4,6 +4,14 @@ All notable changes to this project are documented in this file.
4
4
 
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
6
6
 
7
+ ## [1.9.0] - 2026-08-17
8
+
9
+ ### Added
10
+
11
+ - **SSH broker federation.** Declare peers in `<stateDir>/nodes.json` (each token is an OpenSSH Host alias; your SSH config owns keys, users, and ports — Embassy opens no listener and adds no auth system). Brokers exchange a strict three-method protocol (`initialize`, `catalog/get`, `handoff`) over `ssh <node> embassy peer-stdio`; remote routes appear as `alias@host` mirrors behind opaque references, consent edges have exactly one owner broker, and a handoff is `delivered` when the destination durably accepts it — after which the destination owns provider delivery. Anything lost after authorization settles ambiguous and is never replayed. Same-provider directions across different hosts are now routable; one-hop mesh only, no forwarding.
12
+ - **Universal shell peer ingress.** Any harness that can run a shell can be a Embassy peer: `embassy register-peer` mints a token (only its hash is ever persisted), `embassy await` long-polls the peer's ordinary durable queue with flush-before-receipt settlement, and the alias+token principal (supplied via stdin per call) works even in harnesses that give every tool call a fresh shell. One new safe code: `PEER_NOT_AWAITING`.
13
+ - SECURITY.md now documents the configured-SSH networking doctrine: user-owned peers only, local attestation unchanged, body-free network projections, ambiguity never replays.
14
+
7
15
  ## [1.8.2] - 2026-08-17
8
16
 
9
17
  ### Fixed
package/README.md CHANGED
@@ -12,7 +12,7 @@
12
12
  [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
13
13
  [![Node ≥ 20](https://img.shields.io/badge/node-%E2%89%A520-43853d)](package.json)
14
14
 
15
- Your [Claude Code](https://code.claude.com) sessions, [Codex](https://chatgpt.com/codex) desktop tasks, local DeepSeek Harness, and Grok Build agent do not share one routing surface. Embassy is a small local broker that gives all four providers named routes and explicit consent edges — no plugins, no API keys handled by Embassy, and no cloud relay.
15
+ Your [Claude Code](https://code.claude.com) sessions, [Codex](https://chatgpt.com/codex) desktop tasks, local DeepSeek Harness, Grok Build agent, and shell harnesses do not share one routing surface. Embassy is a small local broker that gives all five providers named routes and explicit consent edges — no plugins, no API keys handled by Embassy, and no cloud relay.
16
16
 
17
17
  ```bash
18
18
  npm install -g agent-embassy
@@ -41,7 +41,7 @@ Embassy is built for one person, one macOS account, and agents you already trust
41
41
 
42
42
  ## Quickstart
43
43
 
44
- **Requirements:** macOS and Node.js 20+. Claude routes require peer protocol 1; Codex routes require Desktop configured to use its managed standalone App Server. DeepSeek is optional and launches from `DSH_HOME` (default `~/.dsh`) through the checkout's `demo:acp` script; Grok Build is optional and launches the release-pinned ACP package. The release-owned [support matrix](support/provider-support-matrix.json) records the exact artifacts and capabilities tested for all four providers; it is release evidence, never a runtime allowlist:
44
+ **Requirements:** macOS and Node.js 20+. Claude routes require peer protocol 1; Codex routes require Desktop configured to use its managed standalone App Server. DeepSeek is optional and launches from `DSH_HOME` (default `~/.dsh`) through the checkout's `demo:acp` script; Grok Build is optional and launches the release-pinned ACP package. A shell peer needs only the local CLI and its one-time token. The release-owned [support matrix](support/provider-support-matrix.json) records the exact tested provider artifacts and capabilities; it is release evidence, never a runtime allowlist:
45
45
 
46
46
  ```bash
47
47
  ~/.codex/packages/standalone/current/codex app-server daemon start
@@ -88,6 +88,24 @@ You should see `"accepted":true`. The `codex-` prefix is required for Claude dis
88
88
 
89
89
  Registration records the exact inherited task identity and performs no App Server I/O. Every delivery opens a fresh attested local transport, initializes it, resumes that exact task with history excluded, and authorizes the body write once. App Server and Desktop restarts therefore do not require re-registration or re-anchoring; a current unavailable or unobservable task keeps the logical route and consent edge while the attempt reports an exact safe code. Embassy never retargets by alias or replays an ambiguously written body.
90
90
 
91
+ ### Optional: register a universal shell peer
92
+
93
+ A local shell harness can join as a `peer-*` route without a plugin, stable shell, daemon, PID binding, token file, or Keychain entry:
94
+
95
+ ```bash
96
+ embassy register-peer --alias peer-reviewer@this-mac
97
+ ```
98
+
99
+ Registration prints the `peer_` token exactly once. Keep it in the agent's context and provide it on the first stdin line of every authenticated peer command; when a command also carries a message body, the remaining stdin bytes are the body. Never put the token in argv. For example, wait for inbound mail:
100
+
101
+ ```bash
102
+ embassy await --alias peer-reviewer@this-mac --token-stdin <<'TOKEN'
103
+ peer_<32-character-token>
104
+ TOKEN
105
+ ```
106
+
107
+ `await` performs bounded 30-second long polls until mail arrives or the caller stops it. Each registration may have one waiter and the broker permits 16 in total. Embassy writes the complete framed message to stdout, waits for stdout to flush, and only then acknowledges its private receipt. A missing receipt is `unconfirmed`; uncertainty after write authorization is `ambiguous`, and neither is replayed after restart. `register-peer --emit-env` is an optional convenience for harnesses that really do retain one stable shell; stdin is the universal path.
108
+
91
109
  ### 3. Select a Claude destination
92
110
 
93
111
  Pick one name from `availablePeers`:
@@ -185,7 +203,7 @@ The broker also publishes mode-0600 static snapshots as `gateway-dashboard.html`
185
203
  └───────────────────────────────────────────────────────────┘
186
204
  ```
187
205
 
188
- Embassy publishes each registered Codex task into Claude Code's live-session registry as its own `codex-*` peer. Claude sessions discover those tasks through `ListAgents`; Codex uses its managed App Server. DeepSeek and Grok Build are boot-registered ACP routes whose owned subprocess and one route-local session start lazily on first dispatch.
206
+ Embassy publishes each registered Codex task into Claude Code's live-session registry as its own `codex-*` peer. Claude sessions discover those tasks through `ListAgents`; Codex uses its managed App Server. DeepSeek and Grok Build are boot-registered ACP routes whose owned subprocess and one route-local session start lazily on first dispatch. Universal shell peers use `peer-*` aliases and a pull mailbox authenticated by an alias plus one-time-minted token.
189
207
 
190
208
  A pair is one explicit permission edge between two named routes from different providers, bounded at 128 edges by default. Every edge is created explicitly with generic `pair --from/--to`; `select-claude` remains the one-Codex-task shorthand for a Claude↔Codex edge. Nothing is implied. Without an edge, a sender settles terminally as `SENDER_NOT_PAIRED`. `embassy serve --inbound open` is the explicit opt-out for supported native inbound senders.
191
209
 
@@ -234,6 +252,8 @@ Codex tasks can then be prompted with `$embassy-peer`; Claude Code discovers it
234
252
  | `wait-delivery` | either provider | Wait for that tracker to settle, up to the delivery deadline |
235
253
  | `untrack` | either provider | Close one active progress watch: `embassy untrack --conversation conv_<token>` |
236
254
  | `register-codex` / `unregister-codex` | Codex task | Advertise or retire that exact task; both take `--alias <codex-alias>`, and `embassy register-codex --alias codex-successor@this-mac --succeeds codex-reviewer@this-mac` hands the registration to a different task |
255
+ | `register-peer` / `unregister-peer` | shell harness | Register or retire a `peer-*` route; registration emits its raw token once, while authenticated calls use `--token-stdin` (or the optional stable-shell env form) |
256
+ | `await` | registered shell peer | Long-poll the peer mailbox in bounded 30-second iterations; one waiter per route, 16 globally, with acknowledgement only after stdout flush |
237
257
  | `pair` / `unpair` | endpoint participant | Add or remove one cross-provider edge by naming both ends: `embassy pair --from advisor@this-mac --to grok-main@this-mac`; the inherited caller must belong to the edge |
238
258
  | `select-claude` / `unselect-claude` | operator or Codex task | One-task shorthand for `pair`/`unpair`, taking `--alias <name@host>` or `--session <uuid>`: resolves the Codex end only when it is unambiguous (inherited or sole registered task), otherwise fails closed |
239
259
  | `send-to-claude` | registered Codex task | Send one bounded message to a paired Claude session: `--from <codex-alias> --to <claude-alias>`, body on stdin, optional `--expects-reply` and `--track [--idle-minutes <n>]` |
package/README.zh-CN.md CHANGED
@@ -12,7 +12,7 @@
12
12
  [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
13
13
  [![Node ≥ 20](https://img.shields.io/badge/node-%E2%89%A520-43853d)](package.json)
14
14
 
15
- 你的 [Claude Code](https://code.claude.com) 会话、[Codex](https://chatgpt.com/codex) 桌面任务、本地 DeepSeek HarnessGrok Build 代理没有共同路由面。Embassy 是一个小型本地代理,为四种提供方提供具名路由与显式同意边——无需插件,Embassy 不处理 API 密钥,也无需云端中继。
15
+ 你的 [Claude Code](https://code.claude.com) 会话、[Codex](https://chatgpt.com/codex) 桌面任务、本地 DeepSeek HarnessGrok Build 代理与 shell harness 没有共同路由面。Embassy 是一个小型本地代理,为五种提供方提供具名路由与显式同意边——无需插件,Embassy 不处理 API 密钥,也无需云端中继。
16
16
 
17
17
  ```bash
18
18
  npm install -g agent-embassy
@@ -37,7 +37,7 @@ Embassy 专为单人、单一 macOS 账户以及你已信任以该用户身份
37
37
 
38
38
  ## 快速开始
39
39
 
40
- **前置要求:** macOS 与 Node.js 20+。Claude 路由要求对等协议 1;Codex 路由要求 Desktop 使用托管独立 App Server。DeepSeek 是可选提供方,通过 `DSH_HOME`(默认 `~/.dsh`)指向的本地 checkout 中 `demo:acp` 脚本启动;Grok Build 也是可选提供方,通过发布版固定的 ACP 包启动。发布版自有的[支持矩阵](support/provider-support-matrix.json)记录四种提供方已测试的精确构件与能力;它只是发布证据,绝不是运行时允许列表:
40
+ **前置要求:** macOS 与 Node.js 20+。Claude 路由要求对等协议 1;Codex 路由要求 Desktop 使用托管独立 App Server。DeepSeek 是可选提供方,通过 `DSH_HOME`(默认 `~/.dsh`)指向的本地 checkout 中 `demo:acp` 脚本启动;Grok Build 也是可选提供方,通过发布版固定的 ACP 包启动。Shell 对等方只需要本地 CLI 与其仅铸造一次的令牌。发布版自有的[支持矩阵](support/provider-support-matrix.json)记录精确已测的提供方构件与能力;它只是发布证据,绝不是运行时允许列表:
41
41
 
42
42
  ```bash
43
43
  ~/.codex/packages/standalone/current/codex app-server daemon start
@@ -81,6 +81,24 @@ embassy register-codex --alias codex-reviewer@this-mac
81
81
 
82
82
  注册会记录精确的继承任务身份,并且不执行 App Server I/O。每次投递都会打开并验证新的本地传输,初始化后在排除历史的前提下恢复精确任务,并仅授权一次正文写入。因此 App Server、Desktop 与 `embassy serve` 重启都不需要重新注册或重新锚定;当前任务不可用或无法观测时,尝试会返回精确安全代码,而逻辑路由与同意边保持不变。Embassy 绝不会按别名改投其他任务,也不会重放写入结果不明确的正文。
83
83
 
84
+ ### 可选:注册通用 shell 对等方
85
+
86
+ 本地 shell harness 可以作为 `peer-*` 路由加入,无需插件、稳定 shell、守护进程、PID 绑定、令牌文件或 Keychain 条目:
87
+
88
+ ```bash
89
+ embassy register-peer --alias peer-reviewer@this-mac
90
+ ```
91
+
92
+ 注册只打印一次 `peer_` 令牌。将它保留在代理上下文中,并在每个需要认证的 peer 命令的标准输入第一行提供;若命令还携带消息正文,则其余标准输入字节就是正文。绝不要把令牌放进 argv。例如,等待入站邮件:
93
+
94
+ ```bash
95
+ embassy await --alias peer-reviewer@this-mac --token-stdin <<'TOKEN'
96
+ peer_<32-character-token>
97
+ TOKEN
98
+ ```
99
+
100
+ `await` 会进行有界的 30 秒长轮询,直到收到邮件或调用方停止。每条注册路由只能有一个等待者,broker 全局最多允许 16 个。Embassy 将完整带框消息写入 stdout,等待 stdout 刷新完成,之后才确认其私有回执。缺失回执会结算为 `unconfirmed`;写入授权后的不确定性会结算为 `ambiguous`,两者在重启后都不会重放。`register-peer --emit-env` 只是为确实保留稳定 shell 的 harness 提供的可选便利;标准输入是通用路径。
101
+
84
102
  ### 3. 选择 Claude 目的地
85
103
 
86
104
  从 `availablePeers` 中选择一个名称:
@@ -158,7 +176,7 @@ Embassy 会在实际写入提供方之前,为双向路由消息添加一个由
158
176
  └───────────────────────────────────────────────────────────┘
159
177
  ```
160
178
 
161
- Embassy 将每个已注册的 Codex 任务以各自的 `codex-*` 对等方身份发布到 Claude Code 的实时会话注册表中。Claude 会话通过 `ListAgents` 发现这些任务;Codex 使用托管 App Server。DeepSeek 与 Grok Build 是启动时登记的 ACP 路由,其自有子进程与单个路由本地会话会在首次投递时惰性启动。
179
+ Embassy 将每个已注册的 Codex 任务以各自的 `codex-*` 对等方身份发布到 Claude Code 的实时会话注册表中。Claude 会话通过 `ListAgents` 发现这些任务;Codex 使用托管 App Server。DeepSeek 与 Grok Build 是启动时登记的 ACP 路由,其自有子进程与单个路由本地会话会在首次投递时惰性启动。通用 shell 对等方使用 `peer-*` 别名与一个由别名加仅铸造一次的令牌认证的拉取邮箱。
162
180
 
163
181
  配对是来自不同提供方的两条具名路由之间的单一显式权限边,默认上限 128 条。每条边都通过通用的 `pair --from/--to` 显式创建;`select-claude` 保留为单 Codex 任务建立 Claude↔Codex 边的简写。一切都不会被隐式推断。没有边时,发送方以 `SENDER_NOT_PAIRED` 终局结算。`embassy serve --inbound open` 是针对受支持原生入站发送方的显式退出选项。
164
182
 
@@ -205,6 +223,8 @@ cp -R "$(npm root -g)/agent-embassy/skills/embassy-peer" ~/.claude/skills/
205
223
  | `wait-delivery` | 任一提供方 | 等待该跟踪器结算,直至投递截止时间 |
206
224
  | `untrack` | 任一提供方 | 关闭一个活跃的进度监视:`embassy untrack --conversation conv_<token>` |
207
225
  | `register-codex` / `unregister-codex` | Codex 任务 | 通告或注销该任务;两者都需要 `--alias <codex-alias>`,而 `embassy register-codex --alias codex-successor@this-mac --succeeds codex-reviewer@this-mac` 会将注册转交给另一个任务 |
226
+ | `register-peer` / `unregister-peer` | shell harness | 注册或注销一条 `peer-*` 路由;注册只输出一次原始令牌,已认证调用使用 `--token-stdin`(也可选用稳定 shell 环境形式) |
227
+ | `await` | 已注册 shell 对等方 | 以有界 30 秒迭代长轮询 peer 邮箱;每条路由一个等待者、全局 16 个,且只在 stdout 刷新后确认回执 |
208
228
  | `pair` / `unpair` | 端点参与方 | 显式指定两端来添加或移除一条跨提供方边:`embassy pair --from advisor@this-mac --to grok-main@this-mac`;继承调用方必须属于该边 |
209
229
  | `select-claude` / `unselect-claude` | 操作员或 Codex 任务 | `pair`/`unpair` 的单任务简写,接受 `--alias <name@host>` 或 `--session <uuid>`:仅在 Codex 端无歧义(继承标识或唯一已注册任务)时解析,否则以关闭状态失败 |
210
230
  | `send-to-claude` | 已注册的 Codex 任务 | 向已配对的 Claude 会话发送一条有界消息:`--from <codex-alias> --to <claude-alias>`,正文从标准输入读取,可选 `--expects-reply` 与 `--track [--idle-minutes <n>]` |
package/SECURITY.md CHANGED
@@ -17,11 +17,13 @@ session or thread identifiers, or unredacted personal paths.
17
17
 
18
18
  ## Deployment boundary
19
19
 
20
- Embassy is personal, same-machine software for one macOS user. Run it only
21
- under an OS account that is yours alone and where you trust every process
22
- already running as that user. Do not expose its sockets or state directory on a
23
- network, host it as a service, or use it to share a Claude or Codex subscription
24
- between users.
20
+ Embassy is personal software for one macOS user. Each broker remains local to
21
+ one machine; an explicit private `nodes.json` may connect the user's own
22
+ SSH-reachable machines through the user's existing OpenSSH configuration. Run
23
+ every node only under an OS account that is yours alone and where you trust
24
+ every process already running as that user. Do not expose Embassy sockets or
25
+ state on a network, host it as a service, or use it to share a provider
26
+ subscription between users.
25
27
 
26
28
  The broker is local; the agents are not. Embassy does not call a provider API,
27
29
  but a delivered body becomes model input in the receiving product and may be
@@ -120,6 +122,12 @@ silently expand Embassy's claimed boundary.
120
122
 
121
123
  ## Routing and consent
122
124
 
125
+ - A universal shell peer explicitly registers one `peer-*` alias. Its principal
126
+ is that alias plus a `peer_` token minted and printed exactly once. The token
127
+ is supplied on stdin (or inherited only by a harness with a stable shell),
128
+ compared in constant time, and rechecked immediately before effects. This is
129
+ same-UID attribution, not authentication against other same-user software;
130
+ there is deliberately no PID binding, token file, Keychain entry, or daemon.
123
131
  - A Codex task must explicitly self-register with a `codex-*` alias before it
124
132
  can participate.
125
133
  - Codex-to-Claude delivery requires an explicit operator-created pair with a
@@ -167,7 +175,11 @@ broker.
167
175
 
168
176
  ## Process and protocol boundary
169
177
 
170
- - The v1 launcher is foreground, macOS-only, same-machine, and local-host-only.
178
+ - The launcher is foreground and macOS-only. Provider attestation and the
179
+ control plane remain machine-local. Configured federation owns only a fixed
180
+ outbound `ssh ... embassy peer-stdio` subprocess; SSH supplies transport
181
+ authentication, encryption, and liveness, and Embassy opens no federation
182
+ listener.
171
183
  - Before provider setup, the launcher acquires one host-wide macOS advisory
172
184
  lease. If its lease helper exits or the lease is otherwise lost
173
185
  unexpectedly, Embassy shuts down rather than continuing without singleton
@@ -214,6 +226,11 @@ broker.
214
226
  - Queues, frames, bodies, callbacks, deadlines, deduplication,
215
227
  rate limits, and transient conversations are bounded. Ambiguous writes are
216
228
  never retried automatically.
229
+ - A peer catalog contains only bounded, body-free local metadata. It never
230
+ exports imported rows, message or conversation tokens, native identifiers,
231
+ provider frames, sockets, paths, credentials, or raw diagnostics. Each
232
+ destination broker owns its durable queue; loss after a federated write is
233
+ UNKNOWN and is never replayed.
217
234
  - Raw-body classification and accounting happen before framing. In the
218
235
  untrusted body only, Embassy case-insensitively neutralizes boundary-shaped
219
236
  opening or closing copies of its reserved framing tags before composing the
@@ -276,6 +293,13 @@ a broker restart against its still-exact logical route and consent edge. An
276
293
  armed or accepted message at crash settles ambiguous or unconfirmed and is
277
294
  never replayed.
278
295
 
296
+ For a shell peer, durable route ownership stores only
297
+ `peer:<sha256(uid NUL alias NUL token)>`; the raw peer token and private mailbox
298
+ receipts never enter state, logs, snapshots, dashboards, or routed frames.
299
+ Pending waiters, acknowledgements, and the bounded exact-duplicate receipt
300
+ tombstone are memory-only. A restart therefore cannot falsely confirm a
301
+ stdout write whose acknowledgement was not observed.
302
+
279
303
  The full `conv_` token exposed to a CLI initiator or routed recipient travels
280
304
  only inside the accepted CLI result or transient provider payload. It is never
281
305
  persisted, journaled, logged, placed in a receipt, projected through public
@@ -1,6 +1,6 @@
1
1
  export declare const callerIdentityConflictHintEn = "both agent identities were inherited; the Codex App Server daemon may have been started inside an agent session. From a normal terminal, run: codex app-server daemon restart";
2
2
  export declare const cliCopyEn: {
3
- readonly "help.usage": "Embassy — local messaging for Claude Code and Codex\n\nUsage:\n embassy <command> [options] [--lang en|zh-CN]\n\nCommands:\n serve [--inbound open] Run the socket-only broker (paired inbound by default)\n health Check broker health\n status Read the public status snapshot\n doctor Diagnose Codex Desktop attachment\n convert-state-v2-to-v3 Convert stopped-broker state after making a backup\n refresh-dashboard Refresh discovery and publish both static dashboards\n dashboard --live [--port <n>]\n Open live status and bounded route consent\n register-codex Register or succeed a Codex task\n unregister-codex Unregister the current Codex task\n select-claude Select a discovered Claude session\n unselect-claude Clear the Claude selection\n pair [--from <alias> --to <alias>] Add one cross-provider consent edge\n unpair [--from <alias> --to <alias>] Remove one cross-provider consent edge\n send-to-claude Send stdin to the selected Claude route\n send-to-codex Send stdin to a registered Codex route\n reply Reply with a conversation token\n delivery-status Read a delivery token\n wait-delivery Wait for terminal delivery status\n untrack Close one active progress watch\n\nOptions:\n --lang en|zh-CN Localize user-facing text\n --port <n> Live dashboard port, 1024–65535 (default 41961)\n --version, -v Print the version\n --help, -h Show this help\n";
3
+ readonly "help.usage": "Embassy — local messaging for Claude Code and Codex\n\nUsage:\n embassy <command> [options] [--lang en|zh-CN]\n\nCommands:\n serve [--inbound open] Run the socket-only broker (paired inbound by default)\n health Check broker health\n status Read the public status snapshot\n doctor Diagnose Codex Desktop attachment\n convert-state-v2-to-v3 Convert stopped-broker state after making a backup\n refresh-dashboard Refresh discovery and publish both static dashboards\n dashboard --live [--port <n>]\n Open live status and bounded route consent\n register-codex Register or succeed a Codex task\n unregister-codex Unregister the current Codex task\n register-peer --alias <peer-alias> [--token-stdin|--emit-env]\n Register a universal shell peer\n unregister-peer --alias <peer-alias> [--token-stdin]\n Unregister a universal shell peer\n await --alias <peer-alias> [--token-stdin]\n Wait for one peer message and acknowledge stdout\n select-claude Select a discovered Claude session\n unselect-claude Clear the Claude selection\n pair [--from <alias> --to <alias>] Add one cross-provider consent edge\n unpair [--from <alias> --to <alias>] Remove one cross-provider consent edge\n send-to-claude Send stdin to the selected Claude route\n send-to-codex Send stdin to a registered Codex route\n reply Reply with a conversation token\n delivery-status Read a delivery token\n wait-delivery Wait for terminal delivery status\n untrack Close one active progress watch\n\nOptions:\n --lang en|zh-CN Localize user-facing text\n --token-stdin Read the peer token as the first LF-terminated stdin line\n --emit-env Print the first registration token as an export command\n --port <n> Live dashboard port, 1024–65535 (default 41961)\n --version, -v Print the version\n --help, -h Show this help\n";
4
4
  readonly "hint.dashboardLiveRequired": "dashboard requires --live; static files are published by serve and refresh-dashboard.";
5
5
  readonly "hint.dashboardPortInUse": "live dashboard port {port} is already in use; close the holding process or choose another with --port <n>.";
6
6
  readonly "hint.controlInvalidResponse": "client/broker version skew is likely; rebuild or repoint this client to the broker's Embassy installation, then retry.";
@@ -16,6 +16,12 @@ Commands:
16
16
  Open live status and bounded route consent
17
17
  register-codex Register or succeed a Codex task
18
18
  unregister-codex Unregister the current Codex task
19
+ register-peer --alias <peer-alias> [--token-stdin|--emit-env]
20
+ Register a universal shell peer
21
+ unregister-peer --alias <peer-alias> [--token-stdin]
22
+ Unregister a universal shell peer
23
+ await --alias <peer-alias> [--token-stdin]
24
+ Wait for one peer message and acknowledge stdout
19
25
  select-claude Select a discovered Claude session
20
26
  unselect-claude Clear the Claude selection
21
27
  pair [--from <alias> --to <alias>] Add one cross-provider consent edge
@@ -29,6 +35,8 @@ Commands:
29
35
 
30
36
  Options:
31
37
  --lang en|zh-CN Localize user-facing text
38
+ --token-stdin Read the peer token as the first LF-terminated stdin line
39
+ --emit-env Print the first registration token as an export command
32
40
  --port <n> Live dashboard port, 1024–65535 (default 41961)
33
41
  --version, -v Print the version
34
42
  --help, -h Show this help
@@ -1 +1 @@
1
- {"version":3,"file":"cli-copy.en.js","sourceRoot":"","sources":["../../../src/gateway/cli-copy.en.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,4BAA4B,GACvC,+KAA+K,CAAC;AAElL,MAAM,CAAC,MAAM,SAAS,GAAG;IACvB,YAAY,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAgCf;IACC,4BAA4B,EAC1B,uFAAuF;IACzF,yBAAyB,EACvB,4GAA4G;IAC9G,6BAA6B,EAC3B,wHAAwH;IAC1H,sBAAsB,EACpB,4GAA4G;IAC9G,iCAAiC,EAC/B,kJAAkJ;IACpJ,aAAa,EAAE,mBAAmB;IAClC,gBAAgB,EAAE,+BAA+B;IACjD,mBAAmB,EAAE,sBAAsB;IAC3C,iBAAiB,EAAE,gDAAgD;IACnE,eAAe,EAAE,iBAAiB;IAClC,cAAc,EACZ,iJAAiJ;IACnJ,oBAAoB,EAClB,+EAA+E;IACjF,uBAAuB,EACrB,iHAAiH;CACzF,CAAC"}
1
+ {"version":3,"file":"cli-copy.en.js","sourceRoot":"","sources":["../../../src/gateway/cli-copy.en.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,4BAA4B,GACvC,+KAA+K,CAAC;AAElL,MAAM,CAAC,MAAM,SAAS,GAAG;IACvB,YAAY,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAwCf;IACC,4BAA4B,EAC1B,uFAAuF;IACzF,yBAAyB,EACvB,4GAA4G;IAC9G,6BAA6B,EAC3B,wHAAwH;IAC1H,sBAAsB,EACpB,4GAA4G;IAC9G,iCAAiC,EAC/B,kJAAkJ;IACpJ,aAAa,EAAE,mBAAmB;IAClC,gBAAgB,EAAE,+BAA+B;IACjD,mBAAmB,EAAE,sBAAsB;IAC3C,iBAAiB,EAAE,gDAAgD;IACnE,eAAe,EAAE,iBAAiB;IAClC,cAAc,EACZ,iJAAiJ;IACnJ,oBAAoB,EAClB,+EAA+E;IACjF,uBAAuB,EACrB,iHAAiH;CACzF,CAAC"}
@@ -1,6 +1,6 @@
1
1
  export declare const callerIdentityConflictHintZhCn = "\u540C\u65F6\u7EE7\u627F\u4E86\u4E24\u79CD\u4EE3\u7406\u8EAB\u4EFD\uFF1BCodex App Server \u5B88\u62A4\u8FDB\u7A0B\u53EF\u80FD\u662F\u5728\u4EE3\u7406\u4F1A\u8BDD\u5185\u542F\u52A8\u7684\u3002\u8BF7\u5728\u666E\u901A\u7EC8\u7AEF\u4E2D\u8FD0\u884C\uFF1Acodex app-server daemon restart";
2
2
  export declare const cliCopyZhCn: {
3
- readonly "help.usage": "Embassy — Claude Code 与 Codex 的本地消息通道\n\n用法:\n embassy <command> [options] [--lang en|zh-CN]\n\n命令:\n serve [--inbound open] 在前台运行仅使用套接字的代理(默认配对入站)\n health 检查代理健康状态\n status 读取公开状态快照\n doctor 诊断 Codex 桌面应用连接状态\n convert-state-v2-to-v3 备份后转换已停止代理的状态\n refresh-dashboard 刷新发现结果并发布两份静态仪表盘\n dashboard --live [--port <n>]\n 打开实时状态与有限路由同意操作\n register-codex 注册或继任 Codex 任务\n unregister-codex 注销当前 Codex 任务\n select-claude 选择已发现的 Claude 会话\n unselect-claude 清除 Claude 选择\n pair [--from <别名> --to <别名>] 添加一条跨提供商同意边\n unpair [--from <别名> --to <别名>] 移除一条跨提供商同意边\n send-to-claude 将标准输入发送到所选 Claude 路由\n send-to-codex 将标准输入发送到已注册 Codex 路由\n reply 使用会话令牌回复\n delivery-status 读取投递令牌状态\n wait-delivery 等待终结投递状态\n untrack 关闭一个活跃的进度监视\n\n选项:\n --lang en|zh-CN 本地化面向用户的文本\n --port <n> 实时仪表盘端口,1024–65535(默认 41961)\n --version, -v 输出版本\n --help, -h 显示此帮助\n";
3
+ readonly "help.usage": "Embassy — Claude Code 与 Codex 的本地消息通道\n\n用法:\n embassy <command> [options] [--lang en|zh-CN]\n\n命令:\n serve [--inbound open] 在前台运行仅使用套接字的代理(默认配对入站)\n health 检查代理健康状态\n status 读取公开状态快照\n doctor 诊断 Codex 桌面应用连接状态\n convert-state-v2-to-v3 备份后转换已停止代理的状态\n refresh-dashboard 刷新发现结果并发布两份静态仪表盘\n dashboard --live [--port <n>]\n 打开实时状态与有限路由同意操作\n register-codex 注册或继任 Codex 任务\n unregister-codex 注销当前 Codex 任务\n register-peer --alias <对等别名> [--token-stdin|--emit-env]\n 注册通用 shell 对等方\n unregister-peer --alias <对等别名> [--token-stdin]\n 注销通用 shell 对等方\n await --alias <对等别名> [--token-stdin]\n 等待一条对等消息并在标准输出后确认\n select-claude 选择已发现的 Claude 会话\n unselect-claude 清除 Claude 选择\n pair [--from <别名> --to <别名>] 添加一条跨提供商同意边\n unpair [--from <别名> --to <别名>] 移除一条跨提供商同意边\n send-to-claude 将标准输入发送到所选 Claude 路由\n send-to-codex 将标准输入发送到已注册 Codex 路由\n reply 使用会话令牌回复\n delivery-status 读取投递令牌状态\n wait-delivery 等待终结投递状态\n untrack 关闭一个活跃的进度监视\n\n选项:\n --lang en|zh-CN 本地化面向用户的文本\n --token-stdin 从标准输入首个 LF 结尾行读取对等令牌\n --emit-env 将首次注册令牌输出为 export 命令\n --port <n> 实时仪表盘端口,1024–65535(默认 41961)\n --version, -v 输出版本\n --help, -h 显示此帮助\n";
4
4
  readonly "hint.dashboardLiveRequired": "dashboard 需要 --live;静态文件由 serve 和 refresh-dashboard 发布。";
5
5
  readonly "hint.dashboardPortInUse": "实时仪表盘端口 {port} 已被占用;请关闭占用进程,或使用 --port <n> 选择其他端口。";
6
6
  readonly "hint.controlInvalidResponse": "客户端与网关进程的版本可能不一致;请重新构建客户端,或将其重新指向网关进程所使用的 Embassy 安装,然后重试。";
@@ -16,6 +16,12 @@ export const cliCopyZhCn = {
16
16
  打开实时状态与有限路由同意操作
17
17
  register-codex 注册或继任 Codex 任务
18
18
  unregister-codex 注销当前 Codex 任务
19
+ register-peer --alias <对等别名> [--token-stdin|--emit-env]
20
+ 注册通用 shell 对等方
21
+ unregister-peer --alias <对等别名> [--token-stdin]
22
+ 注销通用 shell 对等方
23
+ await --alias <对等别名> [--token-stdin]
24
+ 等待一条对等消息并在标准输出后确认
19
25
  select-claude 选择已发现的 Claude 会话
20
26
  unselect-claude 清除 Claude 选择
21
27
  pair [--from <别名> --to <别名>] 添加一条跨提供商同意边
@@ -29,6 +35,8 @@ export const cliCopyZhCn = {
29
35
 
30
36
  选项:
31
37
  --lang en|zh-CN 本地化面向用户的文本
38
+ --token-stdin 从标准输入首个 LF 结尾行读取对等令牌
39
+ --emit-env 将首次注册令牌输出为 export 命令
32
40
  --port <n> 实时仪表盘端口,1024–65535(默认 41961)
33
41
  --version, -v 输出版本
34
42
  --help, -h 显示此帮助
@@ -1 +1 @@
1
- {"version":3,"file":"cli-copy.zh-CN.js","sourceRoot":"","sources":["../../../src/gateway/cli-copy.zh-CN.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,8BAA8B,GACzC,yFAAyF,CAAC;AAE5F,MAAM,CAAC,MAAM,WAAW,GAAG;IACzB,YAAY,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAgCf;IACC,4BAA4B,EAC1B,yDAAyD;IAC3D,yBAAyB,EACvB,oDAAoD;IACtD,6BAA6B,EAC3B,4DAA4D;IAC9D,sBAAsB,EACpB,kDAAkD;IACpD,iCAAiC,EAC/B,mFAAmF;IACrF,aAAa,EAAE,QAAQ;IACvB,gBAAgB,EAAE,WAAW;IAC7B,mBAAmB,EAAE,QAAQ;IAC7B,iBAAiB,EAAE,eAAe;IAClC,eAAe,EAAE,OAAO;IACxB,cAAc,EACZ,wDAAwD;IAC1D,oBAAoB,EAAE,8BAA8B;IACpD,uBAAuB,EACrB,oDAAoD;CAC5B,CAAC"}
1
+ {"version":3,"file":"cli-copy.zh-CN.js","sourceRoot":"","sources":["../../../src/gateway/cli-copy.zh-CN.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,8BAA8B,GACzC,yFAAyF,CAAC;AAE5F,MAAM,CAAC,MAAM,WAAW,GAAG;IACzB,YAAY,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAwCf;IACC,4BAA4B,EAC1B,yDAAyD;IAC3D,yBAAyB,EACvB,oDAAoD;IACtD,6BAA6B,EAC3B,4DAA4D;IAC9D,sBAAsB,EACpB,kDAAkD;IACpD,iCAAiC,EAC/B,mFAAmF;IACrF,aAAa,EAAE,QAAQ;IACvB,gBAAgB,EAAE,WAAW;IAC7B,mBAAmB,EAAE,QAAQ;IAC7B,iBAAiB,EAAE,eAAe;IAClC,eAAe,EAAE,OAAO;IACxB,cAAc,EACZ,wDAAwD;IAC1D,oBAAoB,EAAE,8BAA8B;IACpD,uBAAuB,EACrB,oDAAoD;CAC5B,CAAC"}
@@ -1,11 +1,13 @@
1
1
  #!/usr/bin/env node
2
2
  import { type GatewayControlMethod, type GatewayControlResponse, type SendGatewayControlRequestOptions } from "./control.js";
3
3
  import { type GatewayConfig } from "./config.js";
4
+ import { type GatewayNodeInventory } from "./federation-nodes.js";
4
5
  import { type LiveDashboardCommandOutcome, type LiveDashboardCommandOptions } from "./live-dashboard-command.js";
5
6
  import { type GatewayServerOptions } from "./server.js";
7
+ import { runPeerStdio, type PeerStdioSession } from "./peer-stdio.js";
6
8
  import { type GatewayStateV2ToV3Result } from "./state-v2-to-v3.js";
7
- export declare const EMBASSY_VERSION = "1.8.2";
8
- export declare const gatewayCliCommands: readonly ["serve", "health", "status", "doctor", "convert-state-v2-to-v3", "delivery-status", "wait-delivery", "untrack", "refresh-dashboard", "dashboard", "register-codex", "unregister-codex", "select-claude", "unselect-claude", "pair", "unpair", "send-to-claude", "send-to-codex", "reply"];
9
+ export declare const EMBASSY_VERSION = "1.9.0";
10
+ export declare const gatewayCliCommands: readonly ["serve", "health", "status", "doctor", "convert-state-v2-to-v3", "delivery-status", "wait-delivery", "untrack", "refresh-dashboard", "dashboard", "register-codex", "unregister-codex", "select-claude", "unselect-claude", "pair", "unpair", "send-to-claude", "send-to-codex", "reply", "register-peer", "unregister-peer", "await", "peer-stdio"];
9
11
  export type GatewayCliCommand = (typeof gatewayCliCommands)[number];
10
12
  export declare const gatewayCliExitCodes: Readonly<{
11
13
  readonly ok: 0;
@@ -16,7 +18,7 @@ export declare const gatewayCliExitCodes: Readonly<{
16
18
  readonly failure: 6;
17
19
  }>;
18
20
  type Writable = {
19
- write(chunk: string): unknown;
21
+ write(chunk: string, callback?: (error?: Error | null) => void): unknown;
20
22
  };
21
23
  type GatewayControlSender = <M extends GatewayControlMethod>(options: SendGatewayControlRequestOptions<M>) => Promise<GatewayControlResponse<M>>;
22
24
  type GatewayServerRunner = (options: GatewayServerOptions) => Promise<void>;
@@ -24,12 +26,14 @@ type LiveDashboardRunner = (options: LiveDashboardCommandOptions) => Promise<Liv
24
26
  type GatewayStateConverter = (options: Readonly<{
25
27
  stateDir: string;
26
28
  }>) => Promise<GatewayStateV2ToV3Result>;
29
+ type PeerStdioRunner = (options: Parameters<typeof runPeerStdio>[0]) => PeerStdioSession;
27
30
  export type GatewayCliDependencies = {
28
31
  env?: NodeJS.ProcessEnv;
29
32
  stdin?: AsyncIterable<unknown>;
30
33
  stdout?: Writable;
31
34
  stderr?: Writable;
32
35
  loadConfig?: (env: NodeJS.ProcessEnv) => GatewayConfig;
36
+ loadNodeInventory?: (stateDir: string) => Promise<GatewayNodeInventory>;
33
37
  sendRequest?: GatewayControlSender;
34
38
  runServer?: GatewayServerRunner;
35
39
  runLiveDashboard?: LiveDashboardRunner;
@@ -37,6 +41,7 @@ export type GatewayCliDependencies = {
37
41
  serverSignal?: AbortSignal;
38
42
  liveDashboardSignal?: AbortSignal;
39
43
  validateControlSocket?: (stateDir: string, socketPath: string) => Promise<void>;
44
+ runPeerStdio?: PeerStdioRunner;
40
45
  now?: () => number;
41
46
  delay?: (milliseconds: number) => Promise<void>;
42
47
  };