agents-can-communicate 0.8.0 → 0.8.1

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 (57) hide show
  1. package/bin/entrypoints/acc-mcp.mjs +0 -1
  2. package/bin/entrypoints/acc.mjs +0 -1
  3. package/docs/ADAPTER_AUTHORING.md +36 -17
  4. package/docs/ARCHITECTURE.md +9 -6
  5. package/docs/CAPABILITIES.md +23 -13
  6. package/docs/CLI.md +6 -3
  7. package/docs/PROTOCOL.md +2 -2
  8. package/docs/RELEASING.md +6 -4
  9. package/docs/TROUBLESHOOTING.md +2 -2
  10. package/node_modules/@agents-can-communicate/adapter-antigravity/package.json +1 -1
  11. package/node_modules/@agents-can-communicate/adapter-antigravity/plugin/plugin.json +1 -1
  12. package/node_modules/@agents-can-communicate/adapter-antigravity/src/adapter.mjs +2 -3
  13. package/node_modules/@agents-can-communicate/adapter-claude-code/package.json +1 -1
  14. package/node_modules/@agents-can-communicate/adapter-claude-code/src/adapter.mjs +5 -4
  15. package/node_modules/@agents-can-communicate/adapter-claude-code/src/inbox-delivery.mjs +12 -17
  16. package/node_modules/@agents-can-communicate/adapter-claude-code/src/inbox-endpoint.mjs +2 -2
  17. package/node_modules/@agents-can-communicate/adapter-codex/package.json +1 -1
  18. package/node_modules/@agents-can-communicate/adapter-codex/src/adapter.mjs +3 -4
  19. package/node_modules/@agents-can-communicate/adapter-codex/src/app-server-client.mjs +10 -13
  20. package/node_modules/@agents-can-communicate/adapter-codex/src/live-permissions.mjs +5 -5
  21. package/node_modules/@agents-can-communicate/adapter-codex/src/maintenance-host.mjs +46 -10
  22. package/node_modules/@agents-can-communicate/adapter-codex/src/maintenance.mjs +5 -7
  23. package/node_modules/@agents-can-communicate/adapter-codex/src/native-delivery.mjs +16 -12
  24. package/node_modules/@agents-can-communicate/adapter-codex/src/native-endpoint.mjs +12 -2
  25. package/node_modules/@agents-can-communicate/adapter-codex/src/service-setup.mjs +26 -9
  26. package/node_modules/@agents-can-communicate/adapter-codex/src/ws-json-rpc.mjs +12 -0
  27. package/node_modules/@agents-can-communicate/adapter-gemini-cli/extension/gemini-extension.json +1 -1
  28. package/node_modules/@agents-can-communicate/adapter-gemini-cli/package.json +1 -1
  29. package/node_modules/@agents-can-communicate/adapter-grok/package.json +1 -1
  30. package/node_modules/@agents-can-communicate/adapter-kimi/package.json +1 -1
  31. package/node_modules/@agents-can-communicate/adapter-sdk/package.json +1 -1
  32. package/node_modules/@agents-can-communicate/adapter-sdk/src/certification.mjs +2 -2
  33. package/node_modules/@agents-can-communicate/adapter-sdk/src/index.mjs +3 -2
  34. package/node_modules/@agents-can-communicate/adapter-sdk/src/native-delivery.mjs +85 -77
  35. package/node_modules/@agents-can-communicate/adapter-sdk/src/native-vocabulary.mjs +4 -4
  36. package/node_modules/@agents-can-communicate/cli/package.json +1 -1
  37. package/node_modules/@agents-can-communicate/cli/src/main.mjs +5 -6
  38. package/node_modules/@agents-can-communicate/cli/src/managed-runtime/maintenance.mjs +9 -15
  39. package/node_modules/@agents-can-communicate/cli/src/managed-runtime/pins.mjs +13 -4
  40. package/node_modules/@agents-can-communicate/core/package.json +1 -1
  41. package/node_modules/@agents-can-communicate/core/src/receipts.mjs +17 -2
  42. package/node_modules/@agents-can-communicate/core/src/status.mjs +14 -2
  43. package/node_modules/@agents-can-communicate/delivery-router/package.json +1 -1
  44. package/node_modules/@agents-can-communicate/delivery-router/src/refresh-binding.mjs +2 -2
  45. package/node_modules/@agents-can-communicate/delivery-router/src/router.mjs +9 -41
  46. package/node_modules/@agents-can-communicate/hook-runner/package.json +1 -1
  47. package/node_modules/@agents-can-communicate/hook-runner/src/client-pid.mjs +30 -1
  48. package/node_modules/@agents-can-communicate/hook-runner/src/native-binding.mjs +3 -4
  49. package/node_modules/@agents-can-communicate/hook-runner/src/process-table.mjs +30 -14
  50. package/node_modules/@agents-can-communicate/hook-runner/src/runner.mjs +3 -1
  51. package/node_modules/@agents-can-communicate/installer/package.json +1 -1
  52. package/node_modules/@agents-can-communicate/installer/src/delivery-diagnostics.mjs +0 -2
  53. package/node_modules/@agents-can-communicate/installer/src/detect.mjs +3 -4
  54. package/node_modules/@agents-can-communicate/mcp-server/package.json +1 -1
  55. package/node_modules/@agents-can-communicate/protocol/package.json +1 -1
  56. package/node_modules/@agents-can-communicate/storage-filesystem/package.json +1 -1
  57. package/package.json +1 -1
@@ -54,7 +54,6 @@ await serve({
54
54
  context: {
55
55
  service,
56
56
  deliveryRouter: createDeliveryRouter({ service, adapters, clock,
57
- platform: `${process.platform}-${process.arch}`,
58
57
  readLivePolicy: ({ adapter }) => readInstalledLivePolicy({ dataHome,
59
58
  adapterId: adapter.id }) }),
60
59
  workspaceId: descriptor.id,
@@ -25,7 +25,6 @@ const runtime = {
25
25
  ids: { next: kind => createId(kind, randomBytes) },
26
26
  createDeliveryRouter: ({ service, clock, dataHome }) =>
27
27
  createDeliveryRouter({ service, adapters, clock,
28
- platform: `${process.platform}-${process.arch}`,
29
28
  readLivePolicy: ({ adapter }) => readInstalledLivePolicy({ dataHome,
30
29
  adapterId: adapter.id }) }),
31
30
  // Asked only by `acc config init`, and only when stdout is a terminal. There
@@ -81,10 +81,15 @@ is not evidence for compaction. Counts add no ids to `offeredMessageIds` or
81
81
 
82
82
  `client.command` does double duty. `detect.mjs` uses it as the version-probe binary, and
83
83
  presence liveness separately walks the hook's process ancestry for the first ancestor whose
84
- executable basename matches it, to learn the client's own pid. Declare the binary the client
85
- actually runs as — `command: "claude"` for a client that really runs as `node` resolves
86
- nothing, and the failure is silent: the session gets `pid: null` and falls back to reading
87
- presence by age alone, with nothing telling you why.
84
+ executable basename matches it, to learn the client's own pid. A client shipped as a node
85
+ script, such as Gemini CLI, runs as `node`; for a `node` ancestor the walk also matches the
86
+ script it runs: the first argument after any interpreter options, by basename, with a
87
+ `.js`, `.mjs` or `.cjs` extension ignored. A word right after an option written without `=`
88
+ may be that option's value (`--require ./preload.cjs`), so it is also tried and the script
89
+ after it still counts. Declare the name the client actually runs as. A
90
+ command that matches neither resolves nothing, and the failure is silent: the session gets
91
+ `pid: null`, falls back to reading presence by age alone, and is not pinned to its
92
+ generation, with nothing telling you why.
88
93
 
89
94
  ## Declare only proven capabilities
90
95
 
@@ -148,9 +153,8 @@ to its capabilities:
148
153
 
149
154
  ```js
150
155
  nativeDelivery: {
151
- minimumByPlatform: { "darwin-arm64": "2.1.282" },
152
- anchors: [{ platform: "darwin-arm64", version: "2.1.282",
153
- protocolContract: "claude-code-inbox-socket-v1" }],
156
+ minimum: "2.1.282",
157
+ anchors: [{ version: "2.1.282", protocolContract: "claude-code-inbox-socket-v1" }],
154
158
  knownBad: [],
155
159
  activationKinds: ["native-service"],
156
160
  offerKind: "wake",
@@ -159,17 +163,32 @@ nativeDelivery: {
159
163
 
160
164
  The rules `defineAdapter` enforces, and the ones the runtime applies:
161
165
 
162
- - A minimum is the **first passing capture** on that platform, never a guessed first vendor
163
- release. Every anchor must have passing `delivery.livePush` evidence for the same client,
164
- version, and platform, and each minimum must itself be an anchor.
165
- - There is intentionally **no maximum version**. A newer stable release is admitted only
166
- when a current read-only feature probe (`probeNativeDelivery()`) and a per-session
167
- handshake (`bindNativeSession()`) both report the anchored `protocolContract`;
168
- `evaluateNativeEligibility()` and `validateNativeHandshake()` are those two checks.
169
- - Prereleases require their own passing capture; they are `prerelease_not_captured` even
170
- when numerically newer. `knownBad` names exact versions or inclusive intervals.
171
- - Exact-version certification still governs every non-native capability;
166
+ - The minimum is the **first passing capture**, never a guessed first vendor release. Every
167
+ anchor must have passing `delivery.livePush` evidence for the same client and version, and
168
+ the minimum must itself be an anchor. The platform an evidence row names is where the
169
+ capture was taken; it is provenance, and the anchor names no platform.
170
+ - There is intentionally **no maximum version** and **no platform**. A newer release is
171
+ admitted, on every platform, only when a current read-only feature probe
172
+ (`probeNativeDelivery()`) and a per-session handshake (`bindNativeSession()`) both report
173
+ the anchored `protocolContract`; `evaluateNativeEligibility()` and
174
+ `validateNativeHandshake()` are those two checks.
175
+ - A prerelease is judged by its release triple, as hook capabilities are: `2.2.0-beta.1` is
176
+ `2.2.0` against the minimum and `knownBad`. `knownBad` names exact versions or inclusive
177
+ intervals. A version that cannot be read at all is `version_unavailable`.
178
+ - Certification evidence still governs every non-native capability;
172
179
  `effectiveCapabilities()` is unchanged. The native rule is used for live delivery alone.
180
+
181
+ What may refuse live delivery is exactly three things:
182
+
183
+ - the machine's own probe or per-session handshake failing;
184
+ - a client older than the first passing capture;
185
+ - a regression recorded in `knownBad`.
186
+
187
+ Nothing else: not the platform, not a prerelease suffix, not where the evidence was taken. A
188
+ transport that is technically different on some platform is the probe's fact to report, as
189
+ `native_delivery_unsupported`, never a gap in the captures. The 0.8.0 shape,
190
+ `minimumByPlatform` with a platform on each anchor, is refused by name; the design record is
191
+ `docs/design/2026-09-26-native-delivery-across-platforms.md` in the repository.
173
192
  - Native methods return closed facts (`validateNativeActivationPlan()` closes the
174
193
  activation plan) and never put vendor data - endpoints, sockets, raw errors - into core.
175
194
 
@@ -107,8 +107,9 @@ turn.
107
107
 
108
108
  The Claude Code inbox wake has installed-client evidence on 2.1.282. Codex LocalDaemon
109
109
  has it on 0.152.1 and 0.153.4. Antigravity CLI has it on 1.2.7 and later through a relay
110
- the agent starts in its own shell. All three use a captured platform minimum,
111
- current probe and exact session handshake. Claude Code and Codex preserve the ordinary
110
+ the agent starts in its own shell. All three use a captured minimum that applies on every
111
+ platform, a current probe and an exact session handshake; a prerelease is judged by its
112
+ release triple. Claude Code and Codex preserve the ordinary
112
113
  client launch. Claude Code verifies its session registry, and Codex verifies its registered
113
114
  thread and workspace. Native delivery owns no vendor daemon lifecycle. Gemini CLI and Kimi
114
115
  Code have exact-version next-turn evidence only. Grok and generic MCP use inbox polling.
@@ -226,10 +227,12 @@ modes. It preserves explicit declarations and does not rewrite live leases. Miss
226
227
  changed generation files and unknown native bindings remain unverified holds.
227
228
  A session is also pinned to the generation
228
229
  it started with: its hooks delegate to that generation while the pin declares the same
229
- store contract this one does, and run the active generation otherwise. A generation
230
- directory is reclaimed once no control pointer, live lease, live pin or staging hold names
231
- it. Parsed help, version, and update recovery remain available when workspace admission is
232
- unavailable.
230
+ store contract this one does, and run the active generation otherwise. Apart from
231
+ `SessionEnd`, only the client's confirmed death releases a pin, so a session whose client
232
+ process cannot be found is not pinned, and a pin that names no client is reaped. A
233
+ generation directory is reclaimed once no control pointer, live lease, live pin or staging
234
+ hold names it. Parsed help, version, and update recovery remain available when workspace
235
+ admission is unavailable.
233
236
 
234
237
  An independent worker downloads npm packages with lifecycle scripts disabled, checks exact
235
238
  stable package identity and the discovered integrity, and health-checks the staged runtime.
@@ -20,9 +20,9 @@ Capability honesty separates four questions that are easy to collapse:
20
20
  A source method or vendor documentation is not certification. A capture applies forward: from
21
21
  the version that recorded it until a later capture changes that capability, and to every
22
22
  platform until one of them records something of its own. A client older than every capture
23
- degrades to false, and so does a capability a capture recorded as failing. Native
24
- minimum-based eligibility is separate, and stays per-platform. No weaker session inherits a
25
- stronger peer's capability.
23
+ degrades to false, and so does a capability a capture recorded as failing. Native live
24
+ delivery reads its captured minimum the same way: on every platform, with a prerelease judged
25
+ by its release triple. No weaker session inherits a stronger peer's capability.
26
26
 
27
27
  Run `acc doctor` in the project when observed behavior differs from this page. It reports
28
28
  the installed client version, platform, effective capability, and fallback instead of
@@ -53,9 +53,18 @@ The native rows are `no` at the hook versions this matrix names.
53
53
  Separate installed-client captures establish Codex `livePush` on 0.152.1 and
54
54
  0.153.4, Claude Code `livePush` on 2.1.282 through the session inbox, and
55
55
  Antigravity CLI `livePush` on 1.2.7 and later through a relay the agent starts in its own shell.
56
- Native eligibility uses the captured platform minimum, a current feature probe,
57
- and an exact per-session handshake. It is experimental and requires recipient
58
- opt-in. Codex, Claude Code and Antigravity CLI reply through `acc reply`. Their native
56
+ Native eligibility uses the captured minimum, a current feature probe, and an exact
57
+ per-session handshake. It is experimental and requires recipient opt-in.
58
+
59
+ What may refuse live delivery is exactly three things:
60
+
61
+ - the machine's own probe or per-session handshake failing;
62
+ - a client older than the first passing capture;
63
+ - a regression recorded in `knownBad`.
64
+
65
+ Nothing else: not the platform, not a prerelease suffix, not where the evidence was taken.
66
+ A platform whose transport is technically different is refused by the probe as a fact about
67
+ that transport, which is what Claude Code's named pipe on native Windows is. Codex, Claude Code and Antigravity CLI reply through `acc reply`. Their native
59
68
  `replyRoute` remains false.
60
69
 
61
70
  The limitations belong next to the adapters they affect:
@@ -64,7 +73,7 @@ The limitations belong next to the adapters they affect:
64
73
  |---|---|
65
74
  | Antigravity CLI | 1.2.7 on darwin-arm64, captured in print mode, and every later release by the forward rule. Only `SessionStart`, `PreInvocation`, `PostInvocation` and `Stop` load; `SessionEnd`, `PreToolUse` and `PostToolUse` are accepted into the config file and silently dropped, so there is no tool guard and no session-end deregistration - a session goes offline by presence age or an explicit `acc finish`. Payloads carry no `hook_event_name`, so each registered command passes its own event name. The end-of-turn `Stop` continuation reaches the model and is a bounded nudge, not a gate: ACC continues a turn at most once and fails open, and the client caps consecutive continuations itself (vendor 1.1.9). `agy agentapi send-message` can wake an idle session - captured - but only with that session's language-server address and CSRF token, which exist in the agent's own shell and in no hook. ACC does not take that token, so live push and reply routing are false. A peer message that arrives while the model writes its last answer is carried by the `Stop` continuation instead. A write that parses can register nothing, so install and doctor read `agy -p "/hooks"` back instead of trusting the file. Live push (1.2.7, darwin-arm64, TUI only, experimental, recorded opt-in) runs through a relay the agent starts once per conversation from its own shell - the only process holding the session endpoint - after ACC's context asks it to, up to three times while none is serving; the operator approves that command at the client's permission prompt. An idle session wakes; a busy one sees the message after its running answer, or at the next model invocation when the turn waits on a tool. Print mode and the first session in a folder trusted at that launch get no relay. |
66
75
  | Codex | Next-turn context, captured on 0.147.0, requires plugin trust. The observed stock 0.153.4 upgrade from ACC 0.3.1 to 0.4 required fresh review of five modified hook definitions; a subsequent restart retained all five active (activation evidence, not new event certification). LocalDaemon native delivery was captured through the installed package on 0.152.1 and 0.153.4, darwin-arm64; minimum 0.152.1, recorded opt-in, current feature probe and exact thread/cwd/process/version/protocol checks are required. Ordinary launch preserves the receiver workspace without ACC arguments or daemon ownership. Embedded or unreachable sessions keep their inbox. Native `replyRoute` remains false. |
67
- | Claude Code | 2.1.233 next-turn delivery waits for the next user prompt. From 2.1.282 on darwin-arm64, `delivery.livePush` is a live capability behind the native contract (experimental, off until opted in). ACC wakes the session through the inbox socket that Claude Code opens for every session, with one line of fixed ACC text and the message id. An idle session starts a turn. A busy session takes the wake between two tool calls. Each delivered wake fires `UserPromptSubmit`, so the next-turn hook shows the body and records the receipt `offered` via `next-turn`. The model answers with `acc reply`, so `delivery.replyRoute` stays false. A session in `bypassPermissions` mode holds each wake for approval unless `crossSessionInbound` is `accept`, and `refuse` drops wakes. Native Windows keeps next-turn delivery: its inbox is a named pipe that requires an auth line, and it is uncaptured. Linux is uncaptured. |
76
+ | Claude Code | 2.1.233 next-turn delivery waits for the next user prompt. From 2.1.282, captured on darwin-arm64 and applied on every platform whose inbox is a Unix socket, `delivery.livePush` is a live capability behind the native contract (experimental, off until opted in). ACC wakes the session through the inbox socket that Claude Code opens for every session, with one line of fixed ACC text and the message id. An idle session starts a turn. A busy session takes the wake between two tool calls. Each delivered wake fires `UserPromptSubmit`, so the next-turn hook shows the body and records the receipt `offered` via `next-turn`. The model answers with `acc reply`, so `delivery.replyRoute` stays false. A session in `bypassPermissions` mode holds each wake for approval unless `crossSessionInbound` is `accept`, and `refuse` drops wakes. Native Windows keeps next-turn delivery: its inbox is a named pipe that requires an auth line, and it is uncaptured. Linux is uncaptured. |
68
77
  | Gemini CLI | Package-shipped next-turn certification starts at 0.57.0. Its TUI has no captured external wake or queue interface and `--acp` changes launch ownership, so native delivery is fallback-only; live push and reply routing remain false. |
69
78
  | Grok | The Grok 1.0.24 installed-client check observed own CLI arguments after a terminal result, followed by owned work, message, and finish calls. This identity-only path does not certify peer-context injection. Documentation-shaped payloads do not count as real captures. The public leader surface exposed no proven addressed injection into an ordinary TUI session, so native delivery is `awaiting_compatibility_capture`; all capabilities remain false. |
70
79
  | Kimi Code | Next-turn and guard evidence was captured on 0.36.1, plus a 60-second heartbeat. Its server/queue APIs do not prove a transparent binding to an independently opened session, so native delivery is fallback-only. |
@@ -93,10 +102,11 @@ demand without a client heartbeat. Retirement is final: neither route may revive
93
102
  retired binding, a closed session or an old generation. Delivery lease refresh does
94
103
  not renew participant presence.
95
104
 
96
- On darwin-arm64, Codex LocalDaemon, the Claude Code inbox wake and the Antigravity CLI
97
- relay have captured experimental live delivery. Other adapters retain their separately
98
- certified next-turn paths or inbox polling. Linux and other uncaptured platforms retain
99
- the durable inbox; these captures establish no new hook capabilities there.
105
+ Codex LocalDaemon, the Claude Code inbox wake and the Antigravity CLI relay have captured
106
+ experimental live delivery, on darwin-arm64. Those captures set the minimum that applies on
107
+ every platform; on a host they never named, the probe and the per-session handshake decide,
108
+ and a transport that does not answer keeps the durable inbox. Other adapters retain their
109
+ separately certified next-turn paths or inbox polling.
100
110
 
101
111
  Codex's 120-second delivery lease can refresh on demand for the same live endpoint;
102
112
  it does not extend the 24-hour participant presence limit. A TUI exit can leave its
@@ -116,8 +126,8 @@ policy:
116
126
  | `all` | every addressed message kind may use live push |
117
127
 
118
128
  The default is `off`. `acc install --delivery actionable|all` records recipient consent.
119
- Current activation remains off when the platform, captured minimum or feature probe
120
- does not qualify; that temporary failure does not erase the requested policy. Each
129
+ Current activation remains off when the captured minimum or the feature probe does not
130
+ qualify; that temporary failure does not erase the requested policy. Each
121
131
  session must pass its own generation-bound handshake. Every adapter reads consent from the
122
132
  installation record and rereads it at hooks and before offers, so a long-running vendor
123
133
  process cannot retain permission through an old binding snapshot.
package/docs/CLI.md CHANGED
@@ -104,9 +104,12 @@ got. Two sessions of one participant show the same numbers, so read the workspac
104
104
  `<n> offered, not retrieved` when that number is above zero. Offered is not read: a
105
105
  transport took the bytes, and the model may or may not have looked.
106
106
 
107
- A participant with no open session is left out, unless messages still wait for it. Then its
108
- latest session stays listed as `offline`, with `endReason` when the client said why the
109
- session ended, such as `"clear"` for Claude Code's `/clear`. The text line names each one:
107
+ A participant with no open session is left out, unless messages still wait for it: queued,
108
+ or offered by a live transport and not yet shown again, so that its next turn would show
109
+ them. A message the next-turn hook already showed does not wait, since nothing will reach the
110
+ participant for it again. A waiting participant's latest session stays listed as `offline`,
111
+ with that number as `waiting`, and with `endReason` when the client said why the session
112
+ ended, such as `"clear"` for Claude Code's `/clear`. The text line names each one:
110
113
  `waiting for a closed session: claude_code-b (cleared, 2)`. `acc status --all` lists every
111
114
  session. Default `sync` reads
112
115
  events after a 16-digit event cursor (100 by
package/docs/PROTOCOL.md CHANGED
@@ -316,8 +316,8 @@ The recipient owns `livePolicy` because native push may start a model turn:
316
316
 
317
317
  Default is `off`. Policy never creates a capability. The router requires one
318
318
  current eligible recipient generation, supported client evidence, a verified
319
- binding and adapter acceptance. The Claude Code inbox wake uses a captured 2.1.282
320
- minimum on darwin-arm64. Its binding requires the same pid and socket in Claude Code's
319
+ binding and adapter acceptance. The Claude Code inbox wake uses a 2.1.282 minimum,
320
+ captured on darwin-arm64 and applied on every platform. Its binding requires the same pid and socket in Claude Code's
321
321
  session registry, and every refresh and wake also requires the same session id. Codex LocalDaemon uses a 0.152.1 minimum, confirmed
322
322
  through installed-product captures on 0.152.1 and 0.153.4. Recorded consent and
323
323
  exact thread, cwd, process, version and protocol verification govern every Codex session.
package/docs/RELEASING.md CHANGED
@@ -1,8 +1,8 @@
1
1
  # Releasing
2
2
 
3
3
  Use this procedure to build one auditable npm artifact, verify that exact tarball, and keep
4
- its evidence tied to the commit that supplied its bytes. The package is currently `0.5.0`;
5
- the commands derive the version from `package.json` so the filename cannot drift.
4
+ its evidence tied to the commit that supplied its bytes. The commands derive the version
5
+ from `package.json` so the filename cannot drift.
6
6
 
7
7
  ```mermaid
8
8
  graph LR
@@ -118,8 +118,10 @@ work belongs under `Unreleased` and gets a new candidate record.
118
118
  ## Capture only capabilities you observed
119
119
 
120
120
  Run native real-client checks on the exact versions and platforms recorded in
121
- retained fixtures. Exact-version evidence governs normal-turn and guard behavior.
122
- The Claude Code inbox wake uses its captured macOS arm64 minimum plus a current probe and
121
+ retained fixtures. A capture records evidence, and it never gates beyond that floor: what it
122
+ proves applies from its version onward and on every platform, and what refuses a client is
123
+ the machine's own probe or handshake, an older client, or a recorded regression.
124
+ The Claude Code inbox wake uses its captured minimum plus a current probe and
123
125
  per-session handshake. Its passing capture is a real-client product run of the packed
124
126
  candidate, launched with the ordinary command and installed hooks. Codex LocalDaemon
125
127
  uses a separate 0.152.1 minimum with installed-product captures on 0.152.1 and
@@ -240,8 +240,8 @@ local socket permissions as well; a writable state directory alone is insufficie
240
240
  ## Gemini does not guard a write
241
241
 
242
242
  Default and `plan` modes expose no write tool to the model. `auto_edit` exposes edit tools;
243
- shell availability depends on approval mode. Only Gemini CLI 0.57.0 on `darwin-arm64` has
244
- package-shipped delivery certification; other versions still use inbox.
243
+ shell availability depends on approval mode. Gemini CLI next-turn delivery is certified from
244
+ 0.57.0 onward, on every platform; an older client still uses inbox.
245
245
 
246
246
  From 0.55 onwards there is a quieter cause with the same symptom: an untrusted folder. The
247
247
  client prints `Approval mode overridden to "default" because the current folder is not
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agents-can-communicate/adapter-antigravity",
3
- "version": "0.8.0",
3
+ "version": "0.8.1",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "exports": {
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "name": "acc",
3
- "version": "0.8.0",
3
+ "version": "0.8.1",
4
4
  "description": "Coordinate this Antigravity CLI session with other AI agent sessions working in the same workspace."
5
5
  }
@@ -76,9 +76,8 @@ export function createAntigravityAdapter() {
76
76
  // no retireNativeSession here: the hook runner retires and re-publishes the
77
77
  // binding on every turn, and must not take the relay with it.
78
78
  nativeDelivery: {
79
- minimumByPlatform: { "darwin-arm64": ANTIGRAVITY_CLI_VERSION },
80
- anchors: [{ platform: "darwin-arm64", version: ANTIGRAVITY_CLI_VERSION,
81
- protocolContract: PROTOCOL_CONTRACT }],
79
+ minimum: ANTIGRAVITY_CLI_VERSION,
80
+ anchors: [{ version: ANTIGRAVITY_CLI_VERSION, protocolContract: PROTOCOL_CONTRACT }],
82
81
  knownBad: [], activationKinds: ["native-config"], policySource: "installation-record",
83
82
  },
84
83
  probeNativeDelivery, planNativeActivation, bindNativeSession, refreshNativeSession,
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agents-can-communicate/adapter-claude-code",
3
- "version": "0.8.0",
3
+ "version": "0.8.1",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "exports": {
@@ -22,8 +22,9 @@ export const CLAUDE_DELIVERY_FALLBACK = Object.freeze({
22
22
  * and a parent/child mapping claimed without observation is the kind of thing
23
23
  * that quietly maps every child onto its parent. `startupInjection` and
24
24
  * `safePointInjection` were not exercised - only the before-turn path was.
25
- * Native live delivery is the inbox wake, certified from 2.1.282 on
26
- * darwin-arm64; reply routing through the transport is not claimed.
25
+ * Native live delivery is the inbox wake, certified from 2.1.282 (captured on
26
+ * darwin-arm64, applied on every platform the probe admits); reply routing
27
+ * through the transport is not claimed.
27
28
  */
28
29
  export function createClaudeCodeAdapter() {
29
30
  return defineAdapter({
@@ -52,8 +53,8 @@ export function createClaudeCodeAdapter() {
52
53
  // newer stable client is admitted by the probe and the per-session
53
54
  // handshake. The offer is a wake; see src/inbox-delivery.mjs.
54
55
  nativeDelivery: {
55
- minimumByPlatform: { "darwin-arm64": MIN_VERSION },
56
- anchors: [{ platform: "darwin-arm64", version: MIN_VERSION, protocolContract: PROTOCOL_CONTRACT }],
56
+ minimum: MIN_VERSION,
57
+ anchors: [{ version: MIN_VERSION, protocolContract: PROTOCOL_CONTRACT }],
57
58
  knownBad: [],
58
59
  activationKinds: ["native-service"],
59
60
  policySource: "installation-record",
@@ -1,4 +1,5 @@
1
1
  import net from "node:net";
2
+ import { compareVersionOrder, versionOrder } from "@agents-can-communicate/adapter-sdk";
2
3
 
3
4
  import { INBOX_MODES, MIN_VERSION, PROTOCOL_CONTRACT, TRANSPORT } from "./inbox-contract.mjs";
4
5
  import { claimWake, newEndpointId, readInboxEndpoint, releaseWake, removeInboxEndpoint,
@@ -26,7 +27,6 @@ const LEASE_MS = 120_000;
26
27
  const MESSAGE_ID = /^[A-Za-z0-9_-]{1,200}$/;
27
28
  const PROBE_NEEDLE = Buffer.from("messagingSocketPath");
28
29
  const PROBE_MAX_BYTES = 256 * 1024 * 1024;
29
- const STABLE_VERSION = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:\+[0-9A-Za-z.-]+)?$/;
30
30
 
31
31
  export { INBOX_MODES, MIN_VERSION, PROTOCOL_CONTRACT, TRANSPORT };
32
32
 
@@ -37,12 +37,11 @@ export const wakeText = messageId => `ACC: new peer message ${messageId} for thi
37
37
  + `acc inbox --message ${messageId}. Answer it through ACC with acc reply --message ${messageId}; `
38
38
  + "SendMessage cannot deliver to an ACC participant.";
39
39
 
40
- function compare(left, right) {
41
- const a = left.split("+")[0].split(".").map(Number);
42
- const b = right.split("+")[0].split(".").map(Number);
43
- for (let index = 0; index < 3; index += 1) if (a[index] !== b[index]) return a[index] < b[index] ? -1 : 1;
44
- return 0;
45
- }
40
+ // A version is judged by its release triple: a prerelease of a version above
41
+ // the minimum is not an older client, and the inbox in the executable is what
42
+ // admits it. An unreadable version orders as null.
43
+ const belowMinimum = clientVersion => compareVersionOrder(versionOrder(clientVersion),
44
+ versionOrder(MIN_VERSION)) < 0;
46
45
 
47
46
  // The registry field only a build with the inbox writes. Read-only, bounded.
48
47
  async function executableHasInbox(realExecutable) {
@@ -92,8 +91,8 @@ function withTimeout(work, ms) {
92
91
  }
93
92
 
94
93
  /**
95
- * Read-only: a stable version at or above the capture, a platform whose inbox
96
- * is a Unix socket, and an executable that writes the inbox into its session
94
+ * Read-only: a version at or above the capture, a platform whose inbox is a
95
+ * Unix socket, and an executable that writes the inbox into its session
97
96
  * registry. Never launches a session. Native Windows serves a named pipe that
98
97
  * demands an auth line; nothing there is captured, so it keeps hook delivery.
99
98
  */
@@ -105,10 +104,8 @@ export async function probeNativeDelivery({ realExecutable, timeoutMs = 750, pla
105
104
  if (typeof realExecutable !== "string" || realExecutable === "") return unsupported("feature_probe_failed");
106
105
  const clientVersion = await withTimeout(Promise.resolve(readVersion(realExecutable, timeoutMs)), timeoutMs)
107
106
  .catch(() => null);
108
- if (clientVersion === null || !STABLE_VERSION.test(clientVersion)) {
109
- return unsupported("feature_probe_failed", clientVersion);
110
- }
111
- if (compare(clientVersion, MIN_VERSION) < 0) return unsupported("below_minimum_version", clientVersion);
107
+ if (versionOrder(clientVersion) === null) return unsupported("feature_probe_failed", clientVersion);
108
+ if (belowMinimum(clientVersion)) return unsupported("below_minimum_version", clientVersion);
112
109
  const present = await withTimeout(Promise.resolve(hasInbox(realExecutable)), timeoutMs).catch(() => false);
113
110
  if (!present) return unsupported("protocol_mismatch", clientVersion);
114
111
  return { supported: true, clientVersion, protocolContract: PROTOCOL_CONTRACT,
@@ -145,13 +142,11 @@ export async function bindNativeSession({ event, clientPid, clientVersion, runti
145
142
  if (typeof event?.sessionId !== "string" || event.sessionId === "") {
146
143
  return closed(clientVersion, "handshake_failed");
147
144
  }
148
- if (typeof clientVersion !== "string" || !STABLE_VERSION.test(clientVersion)) {
149
- return closed(clientVersion, "version_unavailable");
150
- }
145
+ if (versionOrder(clientVersion) === null) return closed(clientVersion, "version_unavailable");
151
146
  // The contract refuses it anyway, after this returns. Refusing here keeps an
152
147
  // older client - which binds again on every turn - from writing an endpoint
153
148
  // record each time.
154
- if (compare(clientVersion, MIN_VERSION) < 0) return closed(clientVersion, "below_minimum_version");
149
+ if (belowMinimum(clientVersion)) return closed(clientVersion, "below_minimum_version");
155
150
  const socketPath = env?.CLAUDE_CODE_MESSAGING_SOCKET;
156
151
  if (typeof socketPath !== "string" || socketPath === "") {
157
152
  return closed(clientVersion, "native_endpoint_unavailable");
@@ -2,6 +2,7 @@ import { randomBytes } from "node:crypto";
2
2
  import { constants } from "node:fs";
3
3
  import { lstat, mkdir, open, readdir, realpath, rename, rm } from "node:fs/promises";
4
4
  import path from "node:path";
5
+ import { versionOrder } from "@agents-can-communicate/adapter-sdk";
5
6
 
6
7
  import { PROTOCOL_CONTRACT } from "./inbox-contract.mjs";
7
8
  import { inboxSocketIsSafe } from "./inbox-registry.mjs";
@@ -14,7 +15,6 @@ import { isReception } from "./inbox-settings.mjs";
14
15
  const KEYS = ["schemaVersion", "endpointId", "socketPath", "configDir", "clientPid", "sessionId",
15
16
  "clientVersion", "protocolContract", "leaseUntil", "reception"];
16
17
  const ENDPOINT = /^claude_inbox_[a-f0-9]{32}$/;
17
- const STABLE = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:\+[0-9A-Za-z.-]+)?$/;
18
18
  const MAX_BYTES = 8_192;
19
19
  const absolute = value => typeof value === "string" && path.isAbsolute(value) && !value.includes("\0");
20
20
  const own = info => typeof process.getuid !== "function" || info.uid === process.getuid();
@@ -29,7 +29,7 @@ function valid(record) {
29
29
  && absolute(record.socketPath) && absolute(record.configDir)
30
30
  && Number.isInteger(record.clientPid) && record.clientPid > 0
31
31
  && typeof record.sessionId === "string" && record.sessionId !== "" && record.sessionId.length <= 200
32
- && typeof record.clientVersion === "string" && STABLE.test(record.clientVersion)
32
+ && typeof record.clientVersion === "string" && versionOrder(record.clientVersion) !== null
33
33
  && record.protocolContract === PROTOCOL_CONTRACT
34
34
  && typeof record.leaseUntil === "string"
35
35
  && /^\d{4}-\d\d-\d\dT\d\d:\d\d:\d\d\.\d{3}Z$/.test(record.leaseUntil)
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agents-can-communicate/adapter-codex",
3
- "version": "0.8.0",
3
+ "version": "0.8.1",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "exports": {
@@ -18,7 +18,7 @@ import { planCodexInstall, detectCodex, installCodexPlugin, preflightCodexUninst
18
18
  export { CODEX_QUEUE_MINIMUM };
19
19
  export const CODEX_VERSION = "0.147.0";
20
20
  export const CODEX_DELIVERY_FALLBACK = Object.freeze({
21
- diagnostic: "Codex native delivery requires codex-cli 0.152.1 or newer on darwin-arm64, "
21
+ diagnostic: "Codex native delivery requires codex-cli 0.152.1 or newer, "
22
22
  + "recorded recipient consent and a reachable LocalDaemon session with exact thread, cwd, "
23
23
  + "process, version and protocol verification. Embedded or unreachable sessions retain "
24
24
  + "durable messages. Message delivery does not start, restart or stop the vendor daemon and adds no "
@@ -61,9 +61,8 @@ export function createCodexAdapter() {
61
61
  // Native acceleration is a separate, per-session eligibility decision.
62
62
 
63
63
  nativeDelivery: {
64
- minimumByPlatform: { "darwin-arm64": CODEX_QUEUE_MINIMUM },
65
- anchors: [{ platform: "darwin-arm64", version: CODEX_QUEUE_MINIMUM,
66
- protocolContract: PROTOCOL_CONTRACT }], knownBad: [],
64
+ minimum: CODEX_QUEUE_MINIMUM,
65
+ anchors: [{ version: CODEX_QUEUE_MINIMUM, protocolContract: PROTOCOL_CONTRACT }], knownBad: [],
67
66
  activationKinds: ["native-service"], policySource: "installation-record",
68
67
  },
69
68
  probeNativeDelivery, planNativeActivation, bindNativeSession,
@@ -2,6 +2,8 @@ import os from "node:os";
2
2
  import path from "node:path";
3
3
  import { realpath, stat } from "node:fs/promises";
4
4
 
5
+ import { compareVersionOrder, versionOrder } from "@agents-can-communicate/adapter-sdk";
6
+
5
7
  import { openWebSocketPeer } from "./ws-json-rpc.mjs";
6
8
 
7
9
  // The Codex App Server queue protocol, captured on codex-cli 0.152.1. Every
@@ -18,7 +20,6 @@ export const MINIMUM_VERSION = "0.152.1";
18
20
  export const CODEX_QUEUE_MINIMUM = MINIMUM_VERSION;
19
21
  export const QUEUE_MODES = Object.freeze(["livePush", "idleWake", "busyQueue"]);
20
22
  const CLIENT_INFO = Object.freeze({ name: "agents-can-communicate", version: "0.2.0" });
21
- const STABLE_VERSION = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/;
22
23
  const MAX_PAGES = 20;
23
24
  const METHOD_NOT_FOUND = -32601;
24
25
  const INVALID_REQUEST = -32600;
@@ -27,15 +28,11 @@ export const controlSocketPath = (env = process.env) =>
27
28
  path.join(env.CODEX_HOME ?? path.join(os.homedir(), ".codex"),
28
29
  "app-server-control", "app-server-control.sock");
29
30
 
30
- export function parseStableVersion(text) {
31
- return STABLE_VERSION.test(String(text ?? "")) ? String(text).split(".").map(Number) : null;
32
- }
33
- export function compareStableVersions(left, right) {
34
- const a = parseStableVersion(left);
35
- const b = parseStableVersion(right);
36
- for (let index = 0; index < 3; index += 1) if (a[index] !== b[index]) return a[index] < b[index] ? -1 : 1;
37
- return 0;
38
- }
31
+ // A version is judged by its release triple, the same order certification
32
+ // evidence uses: a prerelease daemon above the minimum is admitted by the queue
33
+ // method it answers with, not refused for its suffix. Unreadable orders as null.
34
+ export { versionOrder };
35
+ export const compareVersions = (left, right) => compareVersionOrder(versionOrder(left), versionOrder(right));
39
36
  export function serverVersionOf(userAgent) {
40
37
  return /^[^\s/]+\/(\d+\.\d+\.\d+(?:-[0-9A-Za-z.]+)?)(?=[\s(]|$)/
41
38
  .exec(String(userAgent ?? ""))?.[1] ?? null;
@@ -58,10 +55,10 @@ export async function initializeCodex(peer) {
58
55
 
59
56
  export async function probeCodexQueue(peer, { threadId, minimum = MINIMUM_VERSION } = {}) {
60
57
  const serverVersion = await initializeCodex(peer);
61
- if (serverVersion === null || parseStableVersion(serverVersion) === null) {
62
- return { supported: false, serverVersion, reasonCode: "prerelease_not_captured" };
58
+ if (versionOrder(serverVersion) === null) {
59
+ return { supported: false, serverVersion, reasonCode: "version_unavailable" };
63
60
  }
64
- if (compareStableVersions(serverVersion, minimum) < 0) {
61
+ if (compareVersions(serverVersion, minimum) < 0) {
65
62
  return { supported: false, serverVersion, reasonCode: "below_minimum_version" };
66
63
  }
67
64
  try {
@@ -1,6 +1,6 @@
1
1
  import path from "node:path";
2
2
  import { channelSocketDirectory, tomlString } from "@agents-can-communicate/adapter-sdk";
3
- import { compareStableVersions, parseStableVersion } from "./app-server-client.mjs";
3
+ import { compareVersions } from "./app-server-client.mjs";
4
4
  import { addPermissions, inspectPermissions } from "./permission-ownership.mjs";
5
5
  import { scanConfig } from "./toml-scan.mjs";
6
6
 
@@ -8,9 +8,9 @@ const PROFILE = "acc-workspace";
8
8
  const MINIMUM = "0.153.4";
9
9
  const equal = (keys, expected) => JSON.stringify(keys) === JSON.stringify(expected);
10
10
  const literal = value => { try { return JSON.parse(value); } catch { return null; } };
11
- const supported = context => context.platform === "darwin-arm64"
12
- && parseStableVersion(context.clientVersion) !== null
13
- && compareStableVersions(context.clientVersion, MINIMUM) >= 0;
11
+ // Captured on darwin-arm64 with 0.153.4; the capture's minimum applies on
12
+ // every platform, and a prerelease is judged by its release triple.
13
+ const supported = context => compareVersions(context.clientVersion, MINIMUM) >= 0;
14
14
  const hasPermissions = entries => entries.some(entry =>
15
15
  ["permissions", "default_permissions", "profile", "profiles"].includes(entry.keys[0]));
16
16
  const configured = (source, context) => {
@@ -69,7 +69,7 @@ export function outgoingStatus(source, context) {
69
69
  ? "outgoing live delivery: local socket permissions configured for new sessions; active session overrides remain unverified"
70
70
  : `outgoing live delivery: sender permissions unverified in ${context.file}; `
71
71
  + (reasonCode === "permission_configuration_uncaptured"
72
- ? `automatic setup requires Codex ${MINIMUM} or newer on darwin-arm64`
72
+ ? `automatic setup requires Codex ${MINIMUM} or newer`
73
73
  : custom
74
74
  ? "custom permission policy was preserved; manually allow ACC state and local sockets "
75
75
  + "through the network proxy in the effective workspace profile"