agent-embassy 1.2.0 → 1.4.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 (109) hide show
  1. package/CHANGELOG.md +41 -0
  2. package/CONTRIBUTING.md +50 -16
  3. package/README.md +57 -25
  4. package/README.zh-CN.md +45 -23
  5. package/SECURITY.md +68 -48
  6. package/dist/src/gateway/claude-helper-protocol.d.ts +1 -0
  7. package/dist/src/gateway/claude-helper-protocol.js +4 -2
  8. package/dist/src/gateway/claude-helper-protocol.js.map +1 -1
  9. package/dist/src/gateway/claude-helper-supervisor.d.ts +1 -0
  10. package/dist/src/gateway/claude-helper-supervisor.js +3 -1
  11. package/dist/src/gateway/claude-helper-supervisor.js.map +1 -1
  12. package/dist/src/gateway/claude-runtime.d.ts +0 -2
  13. package/dist/src/gateway/claude-runtime.js +6 -17
  14. package/dist/src/gateway/claude-runtime.js.map +1 -1
  15. package/dist/src/gateway/cli-copy.d.ts +1 -1
  16. package/dist/src/gateway/cli-copy.en.d.ts +4 -1
  17. package/dist/src/gateway/cli-copy.en.js +7 -3
  18. package/dist/src/gateway/cli-copy.en.js.map +1 -1
  19. package/dist/src/gateway/cli-copy.js +3 -0
  20. package/dist/src/gateway/cli-copy.js.map +1 -1
  21. package/dist/src/gateway/cli-copy.zh-CN.d.ts +4 -1
  22. package/dist/src/gateway/cli-copy.zh-CN.js +7 -3
  23. package/dist/src/gateway/cli-copy.zh-CN.js.map +1 -1
  24. package/dist/src/gateway/cli.d.ts +2 -5
  25. package/dist/src/gateway/cli.js +41 -67
  26. package/dist/src/gateway/cli.js.map +1 -1
  27. package/dist/src/gateway/codex-app-server.d.ts +2 -8
  28. package/dist/src/gateway/codex-app-server.js +36 -62
  29. package/dist/src/gateway/codex-app-server.js.map +1 -1
  30. package/dist/src/gateway/codex-local-transport.d.ts +4 -10
  31. package/dist/src/gateway/codex-local-transport.js +13 -16
  32. package/dist/src/gateway/codex-local-transport.js.map +1 -1
  33. package/dist/src/gateway/compatibility.d.ts +0 -27
  34. package/dist/src/gateway/compatibility.js +1 -101
  35. package/dist/src/gateway/compatibility.js.map +1 -1
  36. package/dist/src/gateway/config.d.ts +1 -4
  37. package/dist/src/gateway/config.js +0 -10
  38. package/dist/src/gateway/config.js.map +1 -1
  39. package/dist/src/gateway/control.d.ts +2 -21
  40. package/dist/src/gateway/control.js +7 -36
  41. package/dist/src/gateway/control.js.map +1 -1
  42. package/dist/src/gateway/dashboard-copy.d.ts +1 -1
  43. package/dist/src/gateway/dashboard-copy.en.d.ts +21 -8
  44. package/dist/src/gateway/dashboard-copy.en.js +40 -27
  45. package/dist/src/gateway/dashboard-copy.en.js.map +1 -1
  46. package/dist/src/gateway/dashboard-copy.js +21 -8
  47. package/dist/src/gateway/dashboard-copy.js.map +1 -1
  48. package/dist/src/gateway/dashboard-copy.zh-CN.d.ts +21 -8
  49. package/dist/src/gateway/dashboard-copy.zh-CN.js +40 -27
  50. package/dist/src/gateway/dashboard-copy.zh-CN.js.map +1 -1
  51. package/dist/src/gateway/dashboard-model.d.ts +4 -9
  52. package/dist/src/gateway/dashboard-model.js +159 -46
  53. package/dist/src/gateway/dashboard-model.js.map +1 -1
  54. package/dist/src/gateway/dashboard.js +27 -16
  55. package/dist/src/gateway/dashboard.js.map +1 -1
  56. package/dist/src/gateway/instance-lease.d.ts +2 -3
  57. package/dist/src/gateway/instance-lease.js +17 -140
  58. package/dist/src/gateway/instance-lease.js.map +1 -1
  59. package/dist/src/gateway/live-dashboard-app/app.js +62 -63
  60. package/dist/src/gateway/live-dashboard-command.d.ts +5 -1
  61. package/dist/src/gateway/live-dashboard-command.js +8 -6
  62. package/dist/src/gateway/live-dashboard-command.js.map +1 -1
  63. package/dist/src/gateway/live-dashboard-http.d.ts +0 -4
  64. package/dist/src/gateway/live-dashboard-http.js +20 -94
  65. package/dist/src/gateway/live-dashboard-http.js.map +1 -1
  66. package/dist/src/gateway/live-dashboard-protocol.d.ts +1 -5
  67. package/dist/src/gateway/live-dashboard-protocol.js +0 -42
  68. package/dist/src/gateway/live-dashboard-protocol.js.map +1 -1
  69. package/dist/src/gateway/live-dashboard-server.d.ts +2 -2
  70. package/dist/src/gateway/live-dashboard-server.js +18 -4
  71. package/dist/src/gateway/live-dashboard-server.js.map +1 -1
  72. package/dist/src/gateway/live-dashboard.d.ts +3 -6
  73. package/dist/src/gateway/live-dashboard.js +6 -42
  74. package/dist/src/gateway/live-dashboard.js.map +1 -1
  75. package/dist/src/gateway/progress-watch-machine.d.ts +6 -5
  76. package/dist/src/gateway/progress-watch-machine.js +6 -6
  77. package/dist/src/gateway/progress-watch-machine.js.map +1 -1
  78. package/dist/src/gateway/provenance-envelope.d.ts +1 -0
  79. package/dist/src/gateway/provenance-envelope.js +11 -4
  80. package/dist/src/gateway/provenance-envelope.js.map +1 -1
  81. package/dist/src/gateway/providers.d.ts +2 -21
  82. package/dist/src/gateway/providers.js +21 -261
  83. package/dist/src/gateway/providers.js.map +1 -1
  84. package/dist/src/gateway/server.js +0 -3
  85. package/dist/src/gateway/server.js.map +1 -1
  86. package/dist/src/gateway/service.d.ts +5 -15
  87. package/dist/src/gateway/service.js +101 -265
  88. package/dist/src/gateway/service.js.map +1 -1
  89. package/dist/src/gateway/store.d.ts +9 -21
  90. package/dist/src/gateway/store.js +360 -140
  91. package/dist/src/gateway/store.js.map +1 -1
  92. package/dist/src/gateway/types.d.ts +1 -6
  93. package/dist/src/gateway/types.js.map +1 -1
  94. package/docs/CONFIGURATION.md +102 -0
  95. package/docs/CONFIGURATION.zh-CN.md +72 -0
  96. package/docs/DASHBOARD.md +103 -0
  97. package/docs/DASHBOARD.zh-CN.md +47 -0
  98. package/docs/DELIVERY.md +84 -0
  99. package/docs/DELIVERY.zh-CN.md +53 -0
  100. package/docs/GATEWAY-ARCHITECTURE.md +249 -153
  101. package/package.json +8 -4
  102. package/skills/embassy-peer/SKILL.md +25 -10
  103. package/dist/src/gateway/claude-compatibility-scratch.d.ts +0 -18
  104. package/dist/src/gateway/claude-compatibility-scratch.js +0 -158
  105. package/dist/src/gateway/claude-compatibility-scratch.js.map +0 -1
  106. package/dist/src/gateway/live-dashboard-bootstrap.d.ts +0 -36
  107. package/dist/src/gateway/live-dashboard-bootstrap.js +0 -176
  108. package/dist/src/gateway/live-dashboard-bootstrap.js.map +0 -1
  109. package/docs/DESIGN.md +0 -134
@@ -18,16 +18,21 @@ queued turn after idle, and delivered the exact final reply back to Claude.
18
18
  ## Purpose and boundary
19
19
 
20
20
  The gateway lets already-running Claude Code sessions and explicitly
21
- registered native Codex tasks address one another by short aliases. Outbound
22
- Codex-to-Claude sends require an explicitly selected Claude route; inbound
23
- native Claude messages may come from any exact compatible live same-UID
24
- session without making that session outbound-selected. It provides a single
25
- private operational view across the two products without rebuilding either
26
- agent runtime.
21
+ registered native Codex tasks address one another by short aliases. Both
22
+ directions require an explicit permission edge. The broker runs in `paired`
23
+ inbound mode by default, so an inbound native Claude message is admitted only
24
+ from a session that already holds a pair edge to the addressed Codex task;
25
+ `embassy serve --inbound open` is the explicit opt-out that restores any exact
26
+ compatible live same-UID session as an inbound sender without making it
27
+ outbound-selected. Outbound Codex-to-Claude sends likewise require the pair
28
+ edge, created by `pair` or the one-task `select-claude` shorthand. It provides
29
+ a single private operational view across the two products without rebuilding
30
+ either agent runtime.
27
31
 
28
32
  Its exact Claude Code 2.1.226 runtime/peer-protocol pin is fail-closed.
29
- Still-running 2.1.224 sessions remain compatible during a patch upgrade
30
- because their registry records use the same reviewed peer protocol 1 shape.
33
+ Still-running 2.1.224 and 2.1.225 sessions remain compatible during a patch
34
+ upgrade because their registry records use the same reviewed peer protocol 1
35
+ shape.
31
36
 
32
37
  It is deliberately:
33
38
 
@@ -40,8 +45,8 @@ Embassy uses one private same-user Unix-domain control socket for its thin
40
45
  clients and generates a private static dashboard page in each supported
41
46
  language. `embassy serve` does not add a TCP listener, HTTP server, or public
42
47
  API. The opt-in `embassy dashboard --live` companion is a separate foreground
43
- process that binds an authenticated listener with four bounded route actions
44
- on `127.0.0.1`; it is
48
+ process that binds an unauthenticated stable-port listener with four bounded
49
+ route actions on exact `127.0.0.1`; it is
45
50
  described under [Live dashboard companion](#live-dashboard-companion).
46
51
 
47
52
  ### Why this uses the new feature, but is not skill-only
@@ -129,7 +134,7 @@ probed by this project.
129
134
  │ │ callback replies
130
135
  ▼ ▼
131
136
  ┌──────────────────────── local singleton gateway ───────────────────────┐
132
- │ private control UDS │ transient bodies │ metadata state │ static HTML │
137
+ │ private control UDS │ retained bodies │ metadata state │ static HTML │
133
138
  └──────────┬──────────┴──────────────────┴────────────────┴──────────────┘
134
139
 
135
140
  ├─ local Codex App Server ─ registered native local tasks
@@ -153,12 +158,12 @@ The status below is intentionally narrower than the target architecture.
153
158
 
154
159
  | Component | Current evidence |
155
160
  | --- | --- |
156
- | Neutral gateway types, metadata store, route fencing, bounded queues, dedupe, rate limits, and public projection | **Implemented**, deterministic tests; message bodies persist under bounded retention (CO #36) |
161
+ | Neutral gateway types, metadata store, route fencing, bounded queues, dedupe, rate limits, and public projection | **Implemented**, deterministic tests; message bodies persist under bounded retention |
157
162
  | Private JSONL control protocol over a controller-owned UDS | **Implemented**, deterministic synthetic tests; no provider connection required |
158
163
  | Static metadata-only dashboard renderer and atomic publisher | **Implemented**, deterministic security tests; the static renderer requires no browser or HTTP server |
159
- | Opt-in live dashboard companion (`embassy dashboard --live`) | **Implemented**, deterministic tests over the loopback listener, capability-to-cookie exchange, projection, and four bounded route actions; it is a separate foreground process, never part of `embassy serve` |
164
+ | Opt-in live dashboard companion (`embassy dashboard --live`) | **Implemented**, deterministic tests over the stable loopback listener, direct multi-browser access, projection, request guards, and four bounded route actions; it is a separate foreground process, never part of `embassy serve` |
160
165
  | Claude registry/peer adapter pinned to 2.1.226 / peer protocol 1 | **Implemented** and live-tested, including 2.1.224–2.1.226 patch-overlap discovery, print-session discovery, native status frames, cancellation, and accessible-workspace attestation |
161
- | Exact Claude 2.1.226 binary/runtime attestation | **Implemented**; executes only bounded `claude --version` with a scrubbed environment and derives but does not open provider roots |
166
+ | Automatic exact Claude 2.1.226 binary/runtime validation | **Implemented**; executes only bounded `claude --version` with a scrubbed environment and derives but does not open provider roots |
162
167
  | Allowlisted Codex App Server connector with bounded busy behavior | **Implemented** and live-tested against App Server 0.147.0 for external busy observation, registered-route reachability across settings changes, and an automatically started queued turn; exact `STEER:` boundary behavior is covered deterministically |
163
168
  | Attach-only local Codex proxy transport and exact-owned cleanup | **Implemented**, five deterministic tests; no live App Server connection in routine tests |
164
169
  | Local provider adapters | **Implemented**, focused synthetic tests cover genuine-interactive Claude discovery, exact send/callback/receipt settlement and post-dispatch refresh, plus exact opted-in Codex ownership, registered-route reachability, monitor-only fallback, and cleanup; remote adapters remain disabled |
@@ -210,13 +215,13 @@ connector positively observes that exact task on the current endpoint
210
215
  generation.
211
216
 
212
217
  An App Server endpoint-generation change is a fenced route transition, not a
213
- new registration. Embassy stops dispatch through the old connector, creates a
214
- replacement connector, and runs the existing bounded compatibility probe
215
- against that replacement. Only a compatible result may proceed to
216
- `thread/loaded/list`, where every retained private thread ID must occur exactly
217
- once. The connector resumes that exact task with history excluded, and the
218
- store atomically re-anchors the same alias, owner lease, and pair edges to the
219
- new endpoint generation. Each successful re-anchor is recorded in a bounded
218
+ new registration. Embassy stops dispatch through the old connector and creates
219
+ a monitor-only replacement connector. That exact generation must pass a fresh
220
+ automatic initialize and `thread/loaded/list` validation, where every retained
221
+ private thread ID must occur exactly once. The connector resumes that exact
222
+ task with history excluded; only then may the controller activate writes and
223
+ atomically re-anchor the same alias, owner lease, and pair edges to the new
224
+ endpoint generation. Each successful re-anchor is recorded in a bounded
220
225
  private journal; neither thread IDs nor endpoint generations enter public
221
226
  activity, diagnostics, logs, or CLI output. An incompatible endpoint, a
222
227
  missing or duplicate exact task, or an unclean transition leaves the route
@@ -269,7 +274,7 @@ The thin skill/CLI exposes the same safe alias list to either provider.
269
274
  thread identity, source alias, target Claude current name or session UUID,
270
275
  and bounded text.
271
276
  2. The gateway checks thread ownership, selector state, rate and size limits,
272
- deadline, hop count, and dedupe state.
277
+ deadline, and dedupe state.
273
278
  3. It requires the selector to match an explicitly selected live UUID,
274
279
  refreshes the UUID's current process/socket coordinates, and revalidates
275
280
  the selected Claude peer's canonical workspace access and exact generation
@@ -278,7 +283,9 @@ The thin skill/CLI exposes the same safe alias list to either provider.
278
283
  `cross-session-message` textual frame with bounded sender attribution and a
279
284
  first-child reply hint containing the full conversation token, exact aliases,
280
285
  and reply command. It then opens a short-lived connection and writes one
281
- version-pinned peer frame. A reply request carries the gateway's own
286
+ version-pinned peer frame immediately, regardless of whether the current
287
+ Claude registry observation says `idle`, `busy`, or `waiting`. A reply
288
+ request carries the gateway's own
282
289
  anonymous callback UDS as the transport reply address; that path is never
283
290
  exposed in the content frame.
284
291
  5. It records only normalized delivery metadata. It does not retry an
@@ -286,12 +293,13 @@ The thin skill/CLI exposes the same safe alias list to either provider.
286
293
  6. A reply received on the callback listener is correlated in memory and
287
294
  routed to the owning explicitly registered Codex task.
288
295
 
289
- A transport write is not proof of successful model completion. The adapter
290
- distinguishes transport state from any hold/release/denial receipt supported
291
- by the pinned protocol. `transport_written` and native `held` are adapter/native
292
- progress signals only, not public `delivery_status` states. The public tracker
293
- remains `queued` or `stalled` until a terminal state; neither signal proves
294
- terminal success or permits a retry.
296
+ A Claude-bound peer socket is a native mailbox, not an idle gate. Once the
297
+ pre-write route checks pass, Embassy attempts that mailbox write immediately;
298
+ an observed busy state never queues the body. `transport_written` proves the
299
+ mailbox write and is reduced to terminal `delivered` for this direction. That
300
+ still does not prove Claude read, consumed, or acted on the body. The adapter
301
+ distinguishes this transport boundary from Claude-to-Codex native `held`, which
302
+ is a progress signal only. Neither boundary permits a retry.
295
303
 
296
304
  ### Claude to Codex
297
305
 
@@ -309,9 +317,13 @@ final reply.
309
317
  text as untrusted user-role input. This inbound observation grants only a
310
318
  transient, in-memory capability for the correlated reply. It does not add a
311
319
  Claude route, flip `selected`, or authorize a later unsolicited send.
312
- In paired mode, a sender without the exact permission edge is refused before
313
- message admission with `SENDER_NOT_PAIRED`; because no message was accepted,
314
- that refusal is not added to the delivery journal.
320
+ In paired mode — the default — a sender without the exact permission edge is
321
+ refused before message admission with `SENDER_NOT_PAIRED`. No message is
322
+ accepted, so no delivery is created, but the refusal is not silent: the
323
+ broker records it as a `rejected` journal event carrying the direction, both
324
+ aliases, the byte count, and the safe error code, and increments the
325
+ rejected counters on the accounting, the source route, and any matching
326
+ pair.
315
327
  3. The Claude process's inherited messaging-socket value may be accepted as a
316
328
  transient reply address after strict validation. Claude Code exports
317
329
  `CLAUDE_CODE_MESSAGING_SOCKET` as a raw absolute socket path; the CLI
@@ -337,12 +349,15 @@ final reply.
337
349
  emit Claude's approval-specific native `held` control frame for ordinary
338
350
  queueing.
339
351
  8. In `merged` and `verbose` notice modes, if the delivery remains pending for
340
- exactly `floor(messageDeadlineMs / 2)`, the gateway may send the
341
- originating Claude session at most one nonterminal
352
+ exactly `min(floor(messageDeadlineMs / 2), 120_000)` milliseconds, the
353
+ gateway may send the originating Claude session at most one nonterminal
342
354
  `<gateway-delivery-stall>` user frame for that receipt. It contains only an
343
355
  allowlisted reason and a bounded `queued-for-ms` age; it is not a native
344
- `held` receipt and does not settle the delivery. `quiet` suppresses this
345
- gateway-authored frame without changing native status or dashboard state.
356
+ `held` receipt and does not settle the delivery. The two-minute ceiling is
357
+ deliberate: stall visibility must not scale with the deadline, so under the
358
+ default four-hour deadline the notice fires at two minutes, not two hours.
359
+ `quiet` suppresses this gateway-authored frame without changing native status
360
+ or dashboard state.
346
361
  9. When the task becomes idle, the connector refreshes the exact task state and
347
362
  starts the held message. A route retains at most three queued steering
348
363
  messages; accepting a fourth atomically cancels the oldest with safe code
@@ -387,19 +402,24 @@ terminally settles accepted work, joins its bounded receipt writes, and only
387
402
  then closes provider adapters. This orders `GATEWAY_SHUTDOWN` receipts ahead of
388
403
  listener teardown instead of silently dropping late admitted work.
389
404
 
390
- Claude's native peer socket is itself an inbox, so Codex replies may be
391
- written while the Claude route is busy. The gateway still serializes its own
392
- writes, but it does not wait for Claude to become idle and thereby deadlock a
393
- Claude turn that is waiting for the reply.
405
+ Claude's native peer socket is itself a mailbox, so every Claude-bound body,
406
+ including a correlated Codex reply, is written regardless of Claude's observed
407
+ busy or idle state. The gateway still serializes its own writes, but it never
408
+ waits for Claude to become idle and thereby deadlocks a Claude turn that is
409
+ waiting for the reply. This does not change the opposite direction: ordinary
410
+ Codex-bound bodies remain idle-gated, and exact leading `STEER:` bodies keep the
411
+ next-tool-call-boundary rules above.
394
412
 
395
413
  ### Provenance framing and conversation continuation
396
414
 
397
415
  The broker classifies `STEER:`, `TRACK:`, and `DONE:`, enforces raw-byte body
398
- limits, deduplicates, accounts hops, and queues before presentation framing.
399
- The store therefore retains only the raw transient body. A pure composer runs
400
- at the final semantic provider-write boundary so a clean retry produces the
401
- same bytes with exactly one authoritative outer wrapper. Compatibility probes,
402
- receipt frames, and diagnostics do not use this path.
416
+ limits, deduplicates, and queues before presentation framing.
417
+ The store therefore retains only the raw unframed body, never the composed
418
+ envelope. A pure composer runs at the final semantic provider-write boundary so
419
+ a clean retry produces the same bytes with exactly one authoritative outer
420
+ wrapper. Automatic provider
421
+ startup and endpoint-generation validation, receipt frames, and diagnostics do
422
+ not use this path.
403
423
 
404
424
  Both provider directions use Claude-compatible textual framing with a
405
425
  broker-owned `cross-session-message` outer element and an
@@ -416,17 +436,17 @@ broker-owned `cross-session-message` outer element and an
416
436
  - In either direction, the first hint carries the full token in `conversation`
417
437
  and the exact recipient alias in `reply-as`, followed by an exact stdin-based
418
438
  `embassy reply --conversation ... --alias ...` instruction and the statement
419
- that route and hop policy are rechecked.
439
+ that caller, conversation, and route policy are rechecked.
420
440
 
421
- Embassy does not synthesize `from`, `from-session`, `hop-chain`, or `from-mode`
422
- attributes: those names have provider-native meanings the broker cannot
423
- truthfully claim. Native socket addresses, Codex thread IDs, Claude session
424
- UUIDs, endpoint generations, and route handles never enter the content frame.
441
+ Embassy does not synthesize `from`, `from-session`, or `from-mode` attributes:
442
+ those names have provider-native meanings the broker cannot truthfully claim.
443
+ Native socket addresses, Codex thread IDs, Claude session UUIDs, endpoint
444
+ generations, and route handles never enter the content frame.
425
445
 
426
446
  The outer structure and hint come only from validated broker metadata. Before
427
447
  composition, the untrusted body case-insensitively neutralizes boundary-shaped
428
- opening or closing occurrences of `cross-session-message` and
429
- `embassy-reply-hint` by inserting `\` immediately after the leading `<`.
448
+ opening or closing occurrences of Embassy's reserved framing tags by inserting
449
+ `\` immediately after the leading `<`.
430
450
  Everything else remains raw text. This is not general XML, cryptographic
431
451
  authentication, or proof that the message content is trustworthy; it is a
432
452
  consistent structural provenance marker at the model input boundary. A native
@@ -435,8 +455,8 @@ beneath the Embassy wrapper.
435
455
 
436
456
  The full token delivered in the hint lets the recipient call `reply`, but it is
437
457
  only a participant-scoped conversation locator. The service still validates
438
- the inherited caller, current conversation membership, current route policy,
439
- and hop count. The full token remains confined to the accepted control result
458
+ the inherited caller, current conversation membership, and current route
459
+ policy. The full token remains confined to the accepted control result
440
460
  and transient provider payload, and is memory-only: it is never persisted,
441
461
  journaled, logged, snapshotted, rendered on a dashboard, placed in a receipt,
442
462
  or returned from suffix-only public correlation. Formatter,
@@ -505,16 +525,22 @@ never displaced to admit a new send. A pressure-evicted handle returns
505
525
 
506
526
  ### Replies and process restarts
507
527
 
508
- Conversation IDs correlate replies, but callback addresses and message bodies
509
- exist only in memory. After a gateway restart, previously queued or in-flight
510
- metadata is marked abandoned; bodies are not recoverable and are never
511
- replayed. The prior Claude binding remains stored but stale. After authorized
512
- live discovery, one exact UUID-bound selection may be reactivated under its
513
- latest name. No queued text, pending reply, callback, native receipt handle,
514
- delivery token/status tracker, or conversation capability survives the
515
- restart; a prior token therefore returns `found: false`. A stale or offline
516
- selection can be explicitly removed by its stored alias or a user-supplied UUID
517
- without requiring discovery first.
528
+ Conversation IDs correlate replies, and callback addresses exist only in
529
+ memory, but queued message bodies are durable. After a gateway restart, each
530
+ queued message is triaged: one already past its deadline settles `expired`; one
531
+ whose target authority was transient, or whose target route is gone, settles
532
+ `abandoned` with `CONTROLLER_RESTARTED`; every other queued body is restored and
533
+ re-armed for dispatch, then sent exactly once after its exact route is
534
+ re-observed. A message in flight at the moment of the restart or crash settles
535
+ `ambiguous` with `CONTROLLER_RESTARTED` and is never replayed.
536
+
537
+ The prior Claude binding remains stored but stale. After authorized live
538
+ discovery, one exact UUID-bound selection may be reactivated under its latest
539
+ name. No pending reply, callback, native receipt handle, delivery token/status
540
+ tracker, or conversation capability survives the restart; a prior token
541
+ therefore returns `found: false`. A stale or offline selection can be explicitly
542
+ removed by its stored alias or a user-supplied UUID without requiring discovery
543
+ first.
518
544
 
519
545
  ## Gateway control plane
520
546
 
@@ -523,33 +549,41 @@ controller-owned mode-0700 state directory. The socket and state files are
523
549
  mode 0600. Frames are size-bounded and closed against unknown keys, methods,
524
550
  versions, and enum values.
525
551
 
526
- The small version 1 method family covers:
552
+ The closed version 1 method family is exactly these sixteen methods:
527
553
 
528
- - health and a safe public snapshot;
529
- - a read-only `observe_snapshot` projection, which may settle already-due
530
- lifecycle deliveries before projecting and is the only method the live
531
- dashboard companion calls;
532
- - explicit Codex registration, succession, and unregister;
533
- - explicit Claude selection and unselection from the current sanitized
554
+ - `health` and `list_snapshot`, a safe public snapshot;
555
+ - `observe_snapshot`, a read-only projection that may settle already-due
556
+ lifecycle deliveries before projecting;
557
+ - `register_codex`, `unregister_codex`, and
558
+ `remove_stale_codex_registration` explicit Codex registration and
559
+ succession, unregister, and broker-guarded removal of a stale orphan whose
560
+ owning endpoint generation is dead;
561
+ - `select_claude` and `unselect_claude`, from the current sanitized
534
562
  available-peer inventory;
535
- - delivery-status lookup by an opaque, memory-only correlation handle;
536
- - provider-specific send operations;
537
- - a correlated reply operation; and
538
- - dashboard refresh.
539
-
540
- The installed binary is `embassy` (`claude-codex-gateway` is a one-release
541
- deprecated alias). Its implemented commands are
542
- `serve`, `health`, `status`, `delivery-status`, `wait-delivery`,
563
+ - `pair` and `unpair`, the two-endpoint permission edge;
564
+ - `delivery_status`, a lookup by opaque, memory-only correlation handle;
565
+ - `untrack`, which closes one active progress watch by conversation token;
566
+ - `send_to_claude` and `send_to_codex`, the provider-specific sends;
567
+ - `reply`, the correlated reply operation; and
568
+ - `refresh_dashboard`, which refreshes Claude discovery and republishes.
569
+
570
+ The live dashboard companion calls `observe_snapshot` for every read; its
571
+ mutation route additionally calls `pair`, `unpair`,
572
+ `remove_stale_codex_registration`, and `refresh_dashboard`, and nothing else.
573
+
574
+ The installed binary is `embassy`, and it is the only installed binary. Its
575
+ seventeen implemented commands are
576
+ `serve`, `health`, `status`, `delivery-status`, `wait-delivery`, `untrack`,
543
577
  `refresh-dashboard`, `dashboard`, `register-codex`, `unregister-codex`,
544
- `select-claude`, `unselect-claude`, `send-to-claude`, `send-to-codex`, and
545
- `reply`. `dashboard` requires `--live` and accepts an optional
546
- `--lang en|zh-CN`; it starts the companion process rather than issuing a single
547
- control request. Message bodies are non-empty UTF-8 from standard input only,
548
- with a 16 KiB ceiling; they are never accepted in an argument or file. The
549
- client emits one bounded normalized JSON line and never returns a thread ID,
550
- provider-native ID, path, address, or message body. These commands require the
551
- foreground broker, except that `serve` starts it in the current terminal. It
552
- never daemonizes itself.
578
+ `select-claude`, `unselect-claude`, `pair`, `unpair`, `send-to-claude`,
579
+ `send-to-codex`, and `reply`. `dashboard` requires `--live` and accepts an
580
+ optional `--lang en|zh-CN` and `--port <n>`; it starts the companion process
581
+ rather than issuing a single control request. Message bodies are non-empty
582
+ UTF-8 from standard input only, with a 16 KiB ceiling; they are never accepted
583
+ in an argument or file. The client emits one bounded normalized JSON line and
584
+ never returns a thread ID, provider-native ID, path, address, or message body.
585
+ These commands require the foreground broker, except that `serve` starts it in
586
+ the current terminal. It never daemonizes itself.
553
587
 
554
588
  `select-claude --alias <current-name@host>` and
555
589
  `select-claude --session <uuid>` select the same logical session.
@@ -562,9 +596,19 @@ Codex registration, unregister, and Codex-to-Claude send require only a valid
562
596
  `CODEX_THREAD_ID`; they fail if a non-empty Claude messaging socket is also
563
597
  inherited. Claude-to-Codex send requires only the raw inherited Claude socket
564
598
  path and fails if a non-empty Codex thread ID is also present. `reply` likewise
565
- fails with both identities or neither. The operator-only health, status,
566
- dashboard refresh, live dashboard, select, unselect, and serve commands ignore
567
- provider identities.
599
+ fails with both identities or neither. `pair` and `unpair` also require an
600
+ exclusive inherited `CODEX_THREAD_ID`: naming both endpoints does not make them
601
+ operator commands, and they fail `CODEX_IDENTITY_REQUIRED` from a plain terminal
602
+ or `CALLER_IDENTITY_CONFLICT` from inside a Claude session.
603
+
604
+ `select-claude` and `unselect-claude` are the operator-runnable shorthand.
605
+ They do not ignore provider identity — they consume an inherited
606
+ `CODEX_THREAD_ID` when one is present, to resolve the Codex end, and reject a
607
+ malformed one — but they tolerate its absence and then resolve the Codex end
608
+ from the sole registered task. Selection is not a weaker operation than
609
+ `pair`: it atomically creates the same permission edge. The health, status,
610
+ dashboard refresh, live dashboard, untrack, and serve commands are the ones
611
+ that genuinely ignore provider identities.
568
612
 
569
613
  The foreground command is:
570
614
 
@@ -572,21 +616,27 @@ The foreground command is:
572
616
  embassy serve
573
617
  ```
574
618
 
575
- Before provider attestation, listener creation, or App Server attachment, the
619
+ `--inbound open` is the one security-relevant option:
620
+
621
+ ```text
622
+ embassy serve --inbound open
623
+ ```
624
+
625
+ The default is `paired`. `open` is the explicit opt-out that lets any exact
626
+ compatible live same-UID Claude session send inbound without a pair edge; it is
627
+ never implied and cannot be set through an environment variable.
628
+
629
+ Before provider validation, listener creation, or App Server attachment, the
576
630
  launcher acquires one fixed host-wide crash-reclaimable owner lease under the
577
631
  verified login home. The lease is independent of `EMBASSY_STATE_DIR`, so two foreground
578
632
  controllers cannot be started for the same login account by choosing different
579
- state roots. During the one-release prototype transition, a recognized exact
580
- legacy default root is also controller-locked while Embassy runs. Any
581
- pre-existing legacy lock blocks startup as `GATEWAY_INSTANCE_IN_USE` and is
582
- preserved; the operator may remove that exact stale lock only after confirming
583
- no prototype process remains. Missing, unsafe, or unrecognized legacy roots
584
- are not created, imported, or mutated.
633
+ state roots. It is the only instance lock Embassy takes: the pre-rename
634
+ prototype state root is no longer read, locked, or mutated.
585
635
 
586
636
  It emits one normalized ready line, publishes the private dashboard, and
587
637
  holds the process until `SIGINT` or `SIGTERM`, when exact-owned resources are
588
- closed. Startup attests the pinned local Claude and Codex runtimes and binds
589
- controller-owned UDS listeners, but does not discover a Claude peer, write a
638
+ closed. Startup automatically validates the exact-pinned local Claude and Codex
639
+ runtimes and binds controller-owned UDS listeners, but does not discover a Claude peer, write a
590
640
  provider socket, start a model turn, or contact a remote host. Its ready result
591
641
  reports local host `this-mac`, dashboard filename `gateway-dashboard.html`, and
592
642
  `codexMode: "native_messaging"` without exposing paths.
@@ -599,6 +649,27 @@ Same-UID socket access is a local containment boundary, not proof of a trusted
599
649
  agent process. Every mutation additionally checks route ownership, exact
600
650
  thread/session generation, source alias, bounds, and conversation state.
601
651
 
652
+ ### Progress watches
653
+
654
+ `send-to-claude`, `send-to-codex`, and `reply` each accept an opt-in `--track`
655
+ flag that opens one progress watch over the resulting conversation, plus an
656
+ optional `--idle-minutes <n>` that sets how long the watched thread may sit idle
657
+ before the watch reports a stall. `n` is an integer from 1 through 1440 and
658
+ defaults to 5; supplying it without `--track` is an argument error. A body with
659
+ an exact leading `TRACK:` prefix opens the same watch at the default idle window
660
+ without the flag.
661
+
662
+ `untrack --conversation conv_<token>` closes an active watch explicitly, and a
663
+ body with an exact leading `DONE:` prefix closes it as completed; one message
664
+ may not both open and complete a watch (`PROGRESS_WATCH_SIGNAL_CONFLICT`). The
665
+ global `EMBASSY_TRACKING_ENABLED` switch is on by default and exact `0` disables
666
+ the surface; `EMBASSY_MAX_WATCHES` bounds concurrent watches at 32 by default
667
+ (hard cap 256).
668
+
669
+ A watch is independent evidence about thread activity, not delivery evidence. It
670
+ may outlive an opener whose own delivery expired, so check the opener's
671
+ `delivery-status` separately before assuming the assignment text arrived.
672
+
602
673
  ## Codex connectors and remote hosts
603
674
 
604
675
  In the target multi-host design, each allowlisted execution host has a separate
@@ -636,11 +707,12 @@ Missing, malformed, or nonempty turns fail closed and are never emitted or
636
707
  persisted. The capability does not add an experimental client method or change
637
708
  the closed RPC allowlist.
638
709
 
639
- Monitor compatibility and write compatibility are distinct gates. A connector
640
- may initialize, list, resume, and expose normalized monitor state after its
641
- schema compatibility is attested while still reporting its write gate as
642
- unavailable. No Claude-initiated turn can start until exact write compatibility
643
- and explicit route ownership are established.
710
+ Automatic generation validation and controller write activation are distinct
711
+ gates. A replacement connector may initialize, list, resume, and expose
712
+ normalized monitor state after its exact version and required schemas validate
713
+ while still reporting its write gate as unavailable. No Claude-initiated turn
714
+ can start until the controller activates that exact endpoint generation and
715
+ explicit route ownership is established.
644
716
 
645
717
  Registration resumes the exact task and establishes reachability. Embassy does
646
718
  not read or retain reported working-directory or policy fields. Before
@@ -720,8 +792,9 @@ The one Desktop restart needed for the local shared-App-Server feasibility
720
792
  test has already been completed. Building, running synthetic tests, starting
721
793
  the gateway, rendering the dashboard, and a future Claude peer-socket test do
722
794
  not themselves require another Desktop restart. A provider or Desktop upgrade
723
- may require a new compatibility probe and, if its attachment mode changes, a
724
- separately announced controlled restart.
795
+ outside this release's exact pins requires an updated, reviewed Embassy
796
+ adapter and, if its attachment mode changes, a separately announced controlled
797
+ restart.
725
798
 
726
799
  ## Dashboard
727
800
 
@@ -738,50 +811,62 @@ A static page is a point-in-time snapshot and never refreshes itself: it emits
738
811
  no meta refresh and the page tells the operator to re-run
739
812
  `embassy refresh-dashboard` and reload, or to use `embassy dashboard --live`.
740
813
 
741
- It shows only:
742
-
743
- - aggregate gateway and per-host connector health;
744
- - available/selected Claude aliases, registered Codex aliases, provider, host,
745
- compatibility, state, and queue depth;
746
- - normalized message direction and delivery state, timestamp, latency, byte
747
- count, hop count, and a short opaque message-ID suffix; and
748
- - allowlisted alerts such as stale route, protocol mismatch, queue full, or
749
- ambiguous delivery.
814
+ Each page assembles seven sections:
815
+
816
+ - **Exchange** — aggregate gateway health plus the pair graph: every explicit
817
+ Claude↔Codex edge and its per-edge counters.
818
+ - **Attention** — allowlisted alerts such as stale route, protocol mismatch,
819
+ queue full, or ambiguous delivery.
820
+ - **Transit** queued-message depth and bytes in flight.
821
+ - **Progress supervision** active progress watches and their state.
822
+ - **Operator activity** — the broker's bounded public journal of accepted
823
+ operator actions.
824
+ - **Sessions** — available/selected Claude aliases, registered Codex aliases,
825
+ provider, host, compatibility, state, and queue depth.
826
+ - **Diagnostics** — compatibility attestations per provider, per-host connector
827
+ health and protocol, deadline-pressure buckets, accounting totals, and the
828
+ omission counters below. Normalized message direction and delivery state,
829
+ timestamp, latency, byte count, and a short opaque message-ID suffix appear
830
+ with the delivery rows.
750
831
 
751
832
  It never shows message content, prompts, replies, transcripts, titles, working
752
833
  directories, native IDs, PIDs, socket paths, endpoint paths, tool data, raw
753
- events, stderr, credentials, or configuration contents. This is a
754
- controller-owned UI artifact, not a shared task file. The public snapshot has
755
- a 240 KiB projection budget and reports explicit omission counters if bounded
756
- connector, peer, route, message, or alert rows are truncated.
834
+ events, stderr, credentials, or configuration contents retained bodies reach
835
+ only the live companion, never these files. This is a controller-owned UI
836
+ artifact, not a shared task file. The public snapshot has a 240 KiB projection
837
+ budget and reports explicit omission counters whenever bounded rows are
838
+ truncated: connectors, available peers, routes, pairs, progress watches,
839
+ upstream and projected progress-watch events, upstream message events, message
840
+ groups, message events, upstream alerts, attention items, and upstream and
841
+ projected activity events.
757
842
 
758
843
  ### Live dashboard companion
759
844
 
760
- `embassy dashboard --live` is the opt-in browser view of the same projection.
845
+ `embassy dashboard --live` is the opt-in browser view of broker state,
846
+ including bounded retained bodies in delivery detail; the static dashboard's
847
+ public projection remains metadata-only.
761
848
  It is a separate foreground process, not a mode of `embassy serve`: it holds no
762
849
  provider capability, owns no registry record, and reaches the broker over the
763
850
  same private control socket every other client command uses, so it reports the
764
851
  gateway as unavailable when nothing is serving.
765
852
 
766
- - **Bind.** One `http.createServer` listener on `127.0.0.1` with an ephemeral
767
- port, under a random per-run instance path. No other interface is bound and
768
- no port is fixed or advertised.
769
- - **Bootstrap.** Startup mints one 256-bit (32-byte) capability, base64url
770
- encoded, carried only in the URL fragment. The bootstrap URL is written to a
771
- mode-0600 `bootstrap.html` inside a fresh mode-0700 `live-<random>` run
772
- directory under the private state root, opened `wx` and identity-checked, and
773
- both file and directory are removed when the companion exits.
774
- - **Exchange.** The capability is single-use. It is exchanged once for a
775
- path-scoped `HttpOnly` `SameSite=Strict` session cookie; the fragment never
776
- reaches the server as part of a request line.
853
+ - **Bind.** One `http.createServer` listener binds exact `127.0.0.1` on stable
854
+ port `41961` by default, or the integer from the per-invocation `--port <n>`
855
+ option in the closed range 1024 through 65535. The direct root URL is
856
+ `http://127.0.0.1:<port>/`. A collision fails with
857
+ `LIVE_DASHBOARD_PORT_IN_USE` and directs the operator to `--port`; there is no
858
+ ephemeral-port fallback. No other interface is bound.
859
+ - **Access.** The root URL is usable concurrently from multiple windows, tabs,
860
+ and browsers. There is no capability token, URL fragment, cookie, browser
861
+ session, random instance path, or bootstrap file.
777
862
  - **Request checks.** The exact Host header is validated on every request.
778
- Navigation GETs may omit Origin and carry no sentinel; non-navigation POSTs
779
- require the exact Origin plus `X-Embassy-Request`. There are no CORS headers,
780
- no cross-origin reads, and no routes outside the instance path.
863
+ Navigation GETs may omit Origin; every POST requires the exact Origin plus
864
+ `X-Embassy-Request: 1`. `OPTIONS` is not accepted, there are no CORS headers,
865
+ and cross-origin reads are unavailable.
781
866
  - **Projection and actions.** The companion observes through
782
867
  `observe_snapshot`. Its only mutations are exact two-endpoint `pair`,
783
868
  `unpair`, `refresh_dashboard`, and `remove_stale_codex_registration`
784
- control calls behind one closed authenticated `/action` route. The recovery
869
+ control calls behind one closed `/action` route. The recovery
785
870
  action carries only a canonical public `codex-*` alias and is accepted only
786
871
  after the broker revalidates that the route is stale and its endpoint
787
872
  generation is dead; native task and generation IDs never enter the browser
@@ -796,9 +881,11 @@ gateway as unavailable when nothing is serving.
796
881
  fresh observation. An observation may
797
882
  settle already-due lifecycle deliveries before projecting, which is a broker
798
883
  timer effect, not additional browser authority.
799
- - **Containment.** Authentication scopes the browser, not the machine. Any
800
- process running as the same OS user including root and browser extensions
801
- with local filesystem access can read what the browser can read.
884
+ - **Containment.** The loopback server deliberately performs no local-process
885
+ or UID authentication. It assumes a trusted single-user machine: any local
886
+ software that can reach or spoof loopback can read the live view and invoke
887
+ the bounded actions. Host, Origin, and sentinel checks constrain ambient
888
+ browser-origin requests; they do not authenticate local software.
802
889
 
803
890
  `--lang en|zh-CN` selects the companion's display language. It has no effect on
804
891
  the static pair, which is always written in both languages.
@@ -814,9 +901,12 @@ The private store may retain:
814
901
  accounting and dashboard projection;
815
902
  - timestamps, counters, dedupe/rate-limit records, and safe error codes.
816
903
 
817
- It must never retain message bodies, provider output, prompts, replies, tool
818
- input/output, raw App Server or Claude frames, stderr, histories, credentials,
819
- Claude registry payloads, or callback/socket paths. The public snapshot is a
904
+ It also retains message bodies under bounded caps the queued body of every
905
+ undelivered message and the recent delivery ledger's retained bodies, evicted
906
+ oldest-first against a 1 MiB budget by default. It must never retain provider
907
+ output, tool input/output, raw App Server or Claude frames, stderr, histories,
908
+ credentials, Claude registry payloads, or callback/socket paths. The public
909
+ snapshot is a
820
910
  strict projection that also removes private route handles and endpoint
821
911
  generations. The state directory is mode 0700 and binding state is mode 0600;
822
912
  provider-native identifiers never enter normalized events, public snapshots,
@@ -825,14 +915,15 @@ every restored route begins stale and unusable. An authorized discovery may
825
915
  reactivate only the byte-identical durable Claude UUID after the current
826
916
  provider endpoint, workspace, complete unique discovery, and ownership lease
827
917
  all revalidate. The public `selected` bit flips only after that atomic private
828
- rebind succeeds. No queued body, callback, receipt handle, conversation, or
829
- reply capability is restored, and no delivery token or status tracker is
830
- reconstructed.
918
+ rebind succeeds. Queued bodies are restored and re-armed for dispatch; no
919
+ callback, receipt handle, conversation, or reply capability is restored, and no
920
+ delivery token or status tracker is reconstructed.
831
921
 
832
922
  The delivery-token mapping, queryable status tracker, native receipt handle,
833
923
  and one-stall-notice state are always memory-only. Durable delivery metadata
834
- does not contain enough information to reconstruct any of those capabilities
835
- or replay a body after restart.
924
+ does not contain enough information to reconstruct any of those capabilities.
925
+ A restored body is re-sent exactly once and only into its own exact
926
+ re-observed route; it is never replayed into a reconstructed capability.
836
927
 
837
928
  ## Minimum filesystem and process access
838
929
 
@@ -858,20 +949,20 @@ and fake App Server transports.
858
949
 
859
950
  ### Exact default roots on macOS
860
951
 
861
- The runtime attestation code derives these paths from the current OS user's
952
+ The automatic exact-version validator derives these paths from the current OS user's
862
953
  verified home; it does not scan the home directory. These are the reviewed
863
954
  boundaries exercised by the live gateway; routine tests substitute synthetic
864
955
  paths, peers, and transports:
865
956
 
866
957
  | Path/capability | Minimum purpose |
867
958
  | --- | --- |
868
- | `~/.local/bin/claude` and derived expected target `~/.local/share/claude/versions/2.1.226` | Stat the owned launcher/path components and read/execute only the resolved pinned target for bounded `--version`; live launcher attestation succeeded |
959
+ | `~/.local/bin/claude` (or the absolute `EMBASSY_CLAUDE_BIN` override) and derived expected target `~/.local/share/claude/versions/2.1.226` | Stat the owned launcher/path components and read/execute only the resolved pinned target for bounded `--version`; `PATH` and interactive shell profiles are never searched; live launcher validation succeeded |
869
960
  | `~/.claude/sessions` | Read/enumerate only live registry JSON during the separately authorized passive-discovery gate |
870
961
  | `/tmp/cc-socks` | At foreground startup, validate the private directory and create/remove only `/tmp/cc-socks/<gateway-pid>.sock` after inode/generation checks; search/stat genuine peers at passive discovery and connect one validated target only at the separately authorized send gate |
871
962
  | `~/.local/state/agent-embassy/.agent-embassy-state` | Validate or establish the exact ownership marker before creating the fixed host lease; an existing non-empty unmarked root is rejected without mutation |
872
963
  | `/usr/bin/lockf` and `/bin/cat` | Hold one fixed, non-waiting macOS advisory lease for the foreground controller; the helper receives no shell text, provider data, or model-supplied argument |
964
+ | `/usr/bin/open` | Executed only by the opt-in `embassy dashboard --live` companion, to open the loopback dashboard URL in the operator's browser; no shell, a scrubbed fixed environment, and a bounded timeout and output cap |
873
965
  | `~/.local/state/agent-embassy/.gateway-host.lock` | Fixed per-login kernel-held lease acquired before provider setup; it remains here even when `EMBASSY_STATE_DIR` is overridden. Its bounded PID/token record is exact-cleanup metadata, not a path-only stale-lock authority; a crash releases the kernel lock and the next foreground process may acquire the existing file |
874
- | `~/.local/state/claude-agent-bridge/gateway/.claude-codex-gateway-state` and `.gateway-controller.lock` | For one release, bounded-read the exact legacy ownership marker and lock record; create and hold the lock only when absent, preserve any pre-existing lock, and read no other legacy state or message data |
875
966
  | `~/.local/state/agent-embassy` (or explicit `EMBASSY_STATE_DIR`) | Default controller-owned store, control UDS, state lock, and static dashboard; an explicit absolute configuration may replace only these state surfaces |
876
967
  | `~/.codex/packages/standalone` and `~/.codex/app-server-control/app-server-control.sock` | Resolve the pinned managed Codex binary and attach to the already-running private local App Server; never bootstrap or unlink it |
877
968
 
@@ -922,9 +1013,14 @@ the preferred least-context setup, but it is not mandatory.
922
1013
  fail-closed. Only exact retry is permitted until the old route is confirmed
923
1014
  unregistered and the controller is restarted.
924
1015
  - No ambiguous mutation is retried automatically.
925
- - No queued body survives process loss.
926
- - Version-specific compatibility evidence expires on a provider or Desktop
927
- update.
1016
+ - A queued body survives process loss under bounded retention and is re-sent
1017
+ exactly once after its exact route is re-observed. A message in flight when
1018
+ the process was lost settles `ambiguous` with `CONTROLLER_RESTARTED`; a
1019
+ message whose target authority was transient, or whose target route no longer
1020
+ exists, settles `abandoned` with the same code.
1021
+ - A provider or Desktop update outside the release's exact reviewed pins fails
1022
+ closed; retained state never admits an unreviewed version or activates an
1023
+ unvalidated replacement endpoint generation.
928
1024
 
929
1025
  ## Validation boundary
930
1026