agents-can-communicate 0.7.1 → 0.8.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 (85) hide show
  1. package/README.md +3 -1
  2. package/bin/entrypoints/acc-bootstrap.mjs +6 -48
  3. package/bin/entrypoints/acc-claude-channel.mjs +47 -157
  4. package/docs/ADAPTER_AUTHORING.md +41 -10
  5. package/docs/ARCHITECTURE.md +23 -13
  6. package/docs/CAPABILITIES.md +19 -16
  7. package/docs/CLI.md +48 -13
  8. package/docs/CONCEPTS.md +5 -4
  9. package/docs/CONFIGURATION.md +18 -11
  10. package/docs/GETTING_STARTED.md +15 -12
  11. package/docs/GLOSSARY.md +2 -1
  12. package/docs/HOW_IT_WORKS.md +8 -4
  13. package/docs/PROTOCOL.md +25 -6
  14. package/docs/RELEASING.md +3 -2
  15. package/docs/SECURITY_MODEL.md +14 -1
  16. package/docs/TROUBLESHOOTING.md +64 -27
  17. package/docs/UPGRADING.md +49 -8
  18. package/docs/WHY_ACC.md +5 -3
  19. package/node_modules/@agents-can-communicate/adapter-antigravity/package.json +1 -1
  20. package/node_modules/@agents-can-communicate/adapter-antigravity/plugin/plugin.json +1 -1
  21. package/node_modules/@agents-can-communicate/adapter-claude-code/certification.json +14 -73
  22. package/node_modules/@agents-can-communicate/adapter-claude-code/fixtures/certification-provenance.json +29 -71
  23. package/node_modules/@agents-can-communicate/adapter-claude-code/fixtures/delivery/claude-code-2.1.282-product-evidence.json +161 -0
  24. package/node_modules/@agents-can-communicate/adapter-claude-code/fixtures/delivery/claude-code-2.1.282.json +28 -0
  25. package/node_modules/@agents-can-communicate/adapter-claude-code/fixtures/inbox-wake-2.1.282.json +44 -0
  26. package/node_modules/@agents-can-communicate/adapter-claude-code/package.json +5 -2
  27. package/node_modules/@agents-can-communicate/adapter-claude-code/src/adapter.mjs +25 -30
  28. package/node_modules/@agents-can-communicate/adapter-claude-code/src/hooks.mjs +6 -0
  29. package/node_modules/@agents-can-communicate/adapter-claude-code/src/inbox-contract.mjs +10 -0
  30. package/node_modules/@agents-can-communicate/adapter-claude-code/src/inbox-delivery.mjs +254 -0
  31. package/node_modules/@agents-can-communicate/adapter-claude-code/src/inbox-endpoint.mjs +166 -0
  32. package/node_modules/@agents-can-communicate/adapter-claude-code/src/inbox-registry.mjs +71 -0
  33. package/node_modules/@agents-can-communicate/adapter-claude-code/src/inbox-settings.mjs +109 -0
  34. package/node_modules/@agents-can-communicate/adapter-claude-code/src/install.mjs +25 -31
  35. package/node_modules/@agents-can-communicate/adapter-codex/package.json +1 -1
  36. package/node_modules/@agents-can-communicate/adapter-gemini-cli/extension/gemini-extension.json +1 -1
  37. package/node_modules/@agents-can-communicate/adapter-gemini-cli/package.json +1 -1
  38. package/node_modules/@agents-can-communicate/adapter-grok/package.json +1 -1
  39. package/node_modules/@agents-can-communicate/adapter-kimi/package.json +1 -1
  40. package/node_modules/@agents-can-communicate/adapter-sdk/package.json +1 -1
  41. package/node_modules/@agents-can-communicate/adapter-sdk/src/events.mjs +16 -3
  42. package/node_modules/@agents-can-communicate/adapter-sdk/src/hook-shim.mjs +4 -3
  43. package/node_modules/@agents-can-communicate/adapter-sdk/src/index.mjs +1 -1
  44. package/node_modules/@agents-can-communicate/adapter-sdk/src/native-activation.mjs +4 -14
  45. package/node_modules/@agents-can-communicate/adapter-sdk/src/native-delivery.mjs +23 -11
  46. package/node_modules/@agents-can-communicate/adapter-sdk/src/native-vocabulary.mjs +5 -1
  47. package/node_modules/@agents-can-communicate/cli/package.json +1 -1
  48. package/node_modules/@agents-can-communicate/cli/src/doctor-command.mjs +8 -6
  49. package/node_modules/@agents-can-communicate/cli/src/install-command.mjs +8 -23
  50. package/node_modules/@agents-can-communicate/cli/src/install-delivery-consent.mjs +3 -4
  51. package/node_modules/@agents-can-communicate/cli/src/main.mjs +25 -3
  52. package/node_modules/@agents-can-communicate/cli/src/managed-runtime/entry.mjs +7 -43
  53. package/node_modules/@agents-can-communicate/cli/src/managed-runtime/launchers.mjs +5 -3
  54. package/node_modules/@agents-can-communicate/cli/src/managed-runtime/refresh.mjs +3 -4
  55. package/node_modules/@agents-can-communicate/cli/src/native-delivery-status.mjs +2 -2
  56. package/node_modules/@agents-can-communicate/core/package.json +1 -1
  57. package/node_modules/@agents-can-communicate/core/src/sessions.mjs +25 -2
  58. package/node_modules/@agents-can-communicate/core/src/status.mjs +19 -1
  59. package/node_modules/@agents-can-communicate/delivery-router/package.json +1 -1
  60. package/node_modules/@agents-can-communicate/delivery-router/src/router.mjs +24 -7
  61. package/node_modules/@agents-can-communicate/hook-runner/package.json +1 -1
  62. package/node_modules/@agents-can-communicate/hook-runner/src/native-attempt.mjs +6 -8
  63. package/node_modules/@agents-can-communicate/hook-runner/src/native-binding.mjs +24 -11
  64. package/node_modules/@agents-can-communicate/hook-runner/src/runner.mjs +1 -1
  65. package/node_modules/@agents-can-communicate/installer/package.json +1 -1
  66. package/node_modules/@agents-can-communicate/installer/src/apply.mjs +19 -5
  67. package/node_modules/@agents-can-communicate/installer/src/detect.mjs +3 -7
  68. package/node_modules/@agents-can-communicate/installer/src/index.mjs +4 -7
  69. package/node_modules/@agents-can-communicate/installer/src/native-activation.mjs +23 -61
  70. package/node_modules/@agents-can-communicate/installer/src/ownership.mjs +7 -1
  71. package/node_modules/@agents-can-communicate/installer/src/plan.mjs +15 -5
  72. package/node_modules/@agents-can-communicate/installer/src/shell-bootstrap.mjs +10 -133
  73. package/node_modules/@agents-can-communicate/mcp-server/package.json +1 -1
  74. package/node_modules/@agents-can-communicate/protocol/package.json +1 -1
  75. package/node_modules/@agents-can-communicate/storage-filesystem/package.json +1 -1
  76. package/node_modules/@agents-can-communicate/storage-filesystem/src/atomic-json.mjs +6 -1
  77. package/package.json +1 -1
  78. package/bin/acc-bootstrap.mjs +0 -7
  79. package/bin/acc-claude-channel.mjs +0 -10
  80. package/bin/entrypoints/claude-channel-binding.mjs +0 -46
  81. package/bin/entrypoints/claude-channel-stdio.mjs +0 -29
  82. package/node_modules/@agents-can-communicate/adapter-claude-code/plugin/.mcp.json +0 -8
  83. package/node_modules/@agents-can-communicate/adapter-claude-code/src/channel.mjs +0 -381
  84. package/node_modules/@agents-can-communicate/adapter-claude-code/src/native-delivery.mjs +0 -232
  85. package/node_modules/@agents-can-communicate/installer/src/bootstrap-runtime.mjs +0 -150
package/README.md CHANGED
@@ -98,7 +98,9 @@ provide messages at the next normal turn; other sessions read their ACC inbox ex
98
98
 
99
99
  Experimental live delivery can wake eligible Claude Code and Codex sessions on Apple Silicon
100
100
  macOS. It requires opt-in and an active verified connection, is off by default, and can spend
101
- model tokens. Messages for busy sessions queue until the current turn ends.
101
+ model tokens. Claude Code sessions wake through the inbox that Claude Code itself opens, so
102
+ you start Claude Code with your ordinary command. A busy Claude Code session gets the message
103
+ between two tool calls. A busy Codex session gets it when the current turn ends.
102
104
 
103
105
  See [client capabilities](docs/CAPABILITIES.md) for exact support. Run `acc doctor` from your
104
106
  project if a peer is missing or delivery differs from what you expect; see
@@ -1,51 +1,9 @@
1
1
  #!/usr/bin/env node
2
- // Internal entry point behind an ACC-owned shell shim: is this exact vendor
3
- // executable eligible for native delivery right now? Exit 0 means yes and the
4
- // shim exports its owned policy before `exec`; any other exit - including a
5
- // crash, a missing adapter, or a damaged install - means the shim launches the
6
- // vendor command untouched. Nothing is ever written to stdout, and stderr stays
7
- // empty unless ACC_BOOTSTRAP_DEBUG=1 asks for one safe line.
8
- import { checkNativeBootstrap } from "@agents-can-communicate/installer";
9
-
10
- import { createAntigravityAdapter } from "@agents-can-communicate/adapter-antigravity";
11
- import { createClaudeCodeAdapter } from "@agents-can-communicate/adapter-claude-code";
12
- import { createCodexAdapter } from "@agents-can-communicate/adapter-codex";
13
- import { createGeminiCliAdapter } from "@agents-can-communicate/adapter-gemini-cli";
14
- import { createGrokAdapter } from "@agents-can-communicate/adapter-grok";
15
- import { createKimiAdapter } from "@agents-can-communicate/adapter-kimi";
16
-
17
- import { parseBootstrapOptions } from "@agents-can-communicate/cli/managed-entry";
18
-
19
- function debug(line) {
20
- if (process.env.ACC_BOOTSTRAP_DEBUG === "1") process.stderr.write(`acc-bootstrap: ${line}\n`);
21
- }
22
-
2
+ // Kept only for installs made by ACC 0.7.x or earlier. A 0.7.x updater refuses a
3
+ // downloaded package without this file, and a `claude` shell shim written by
4
+ // 0.7.x still runs it through its launcher until `acc install` or `acc update`
5
+ // retires the shim. A shim reads any exit other than 0 as "launch the vendor
6
+ // command untouched", so this answers 1 and writes nothing.
23
7
  export async function main() {
24
- try {
25
- const options = parseBootstrapOptions(process.argv.slice(2));
26
- if (options === null) {
27
- debug("usage: --adapter <id> --real-executable <path> --data-home <path>");
28
- process.exit(2);
29
- }
30
- const registry = {
31
- claude_code: createClaudeCodeAdapter, codex: createCodexAdapter,
32
- gemini_cli: createGeminiCliAdapter, grok: createGrokAdapter, kimi: createKimiAdapter,
33
- // No native delivery contract: this reports the client unsupported rather
34
- // than unknown, which is a different and more useful thing to read.
35
- antigravity: createAntigravityAdapter,
36
- };
37
- const adapter = registry[options.adapter]?.();
38
- if (adapter === undefined) {
39
- debug(`${options.adapter}: unknown adapter`);
40
- process.exit(1);
41
- }
42
- const result = await checkNativeBootstrap({ adapter, realExecutable: options.realExecutable,
43
- platform: `${process.platform}-${process.arch}`, dataHome: options.dataHome, timeoutMs: 750 });
44
- debug(`${options.adapter}: ${result.supported ? "supported" : result.reasonCode}`);
45
- process.exit(result.supported ? 0 : 1);
46
- } catch (error) {
47
- debug(`failed: ${String(error?.code ?? error?.name ?? "error")}`);
48
- process.exit(1);
49
- }
50
-
8
+ process.exitCode = 1;
51
9
  }
@@ -1,166 +1,56 @@
1
1
  #!/usr/bin/env node
2
- // The ACC Channel binary Claude Code spawns as a stdio MCP child. It composes
3
- // the same workspace discovery, session binding, core service, and local
4
- // data-home rules as acc-hook, opens a session-scoped endpoint for the delivery
5
- // router, and turns the model's explicit acc_reply / acc_ack into real ACC
6
- // records. It never collects a transcript or runs a model, and it fails open:
7
- // any composition error leaves Claude a plain MCP server that simply offers no
8
- // tools rather than crashing the session.
9
- import { randomBytes } from "node:crypto";
10
-
11
- import { createClaudeCodeAdapter } from "@agents-can-communicate/adapter-claude-code";
12
- import { listSessionBindings } from "@agents-can-communicate/adapter-sdk";
13
- import { createCoordinationService } from "@agents-can-communicate/core";
14
- import { createDeliveryRouter } from "@agents-can-communicate/delivery-router";
15
- import { readInstalledLivePolicy } from "@agents-can-communicate/installer";
16
- import { recordAndOffer } from "@agents-can-communicate/mcp-server";
17
- import { resolveClientPid } from "@agents-can-communicate/hook-runner/client-pid";
18
- import { readProcessTable } from "@agents-can-communicate/hook-runner/process-table";
19
- import { createId } from "@agents-can-communicate/protocol";
20
- import { openFilesystemStore } from "@agents-can-communicate/storage-filesystem";
21
- import { ALL_ADAPTERS, createGitProbe, discoverWorkspace, platformDataHome, runtimePaths }
22
- from "@agents-can-communicate/cli";
23
-
24
- import { createAccChannel, endpointDir, routeAck, routeReply }
25
- from "@agents-can-communicate/adapter-claude-code/channel";
26
-
27
- import { pump, startInertChannel } from "./claude-channel-stdio.mjs";
28
- import { activateClaudeChannel } from "./claude-channel-binding.mjs";
29
-
30
- const clock = { now: () => new Date().toISOString() };
31
- const ids = { next: kind => createId(kind, randomBytes) };
32
-
33
- /**
34
- * The ACC session this Claude process opened - decided by which client process
35
- * this Channel is running under, never by how many sessions happen to be
36
- * visible.
37
- *
38
- * There used to be a fallback here: if exactly one session was live, it was
39
- * taken to be this one. On a single-session machine that looked safe, and with
40
- * two sessions it silently handed one client the other's identity. The Channel
41
- * starts while its own SessionStart hook is still writing its binding, so the
42
- * only binding a second client can see at that moment is the *first* client's -
43
- * and the wait that was added to survive that race then locks the wrong answer
44
- * in on the first attempt, because it is a complete, live, plausible binding.
45
- *
46
- * Measured on two real 2.1.259 sessions: both Channels registered an endpoint
47
- * under the first client's pid. `bindNativeSession` refuses two registrations
48
- * for one pid, so the session that had been receiving live messages dropped
49
- * back to the durable inbox - and an `acc_reply` from the second window would
50
- * have been recorded as the first session's answer, which is a false record
51
- * rather than a lost message.
52
- *
53
- * This process was spawned by its client, so its own ancestry names that
54
- * client, and no race can change that. Ownership is decided by it.
55
- */
56
- export async function resolveSession({ runtimeDir, service, env, ownClientPid,
57
- listBindings = listSessionBindings }) {
58
- if (!Number.isInteger(ownClientPid)) return null;
59
- const bindings = await listBindings({ runtimeDir });
60
- if (bindings.length === 0) return null;
61
- const status = await service.collectStatus({});
62
- const live = new Set(status.participants
63
- .filter(participant => participant.presence !== "offline")
64
- .map(participant => participant.sessionId));
65
- const mine = bindings.filter(binding => live.has(binding.accSessionId)
66
- && binding.clientPid === ownClientPid);
67
- // One client process can outlive a session and open another, so the pid alone
68
- // can still name two. The harness session id the client exports separates
69
- // them; with nothing to separate them the answer is nobody, as before.
70
- const exported = new Set(Object.values(env ?? {}).filter(value => typeof value === "string"));
71
- const matched = mine.filter(binding => exported.has(binding.harnessSessionId));
72
- const chosen = mine.length === 1 ? mine[0] : matched.length === 1 ? matched[0] : null;
73
- return chosen === null ? null
74
- : { sessionId: chosen.accSessionId, generation: chosen.generation, clientPid: chosen.clientPid,
75
- harnessSessionId: chosen.harnessSessionId };
76
- }
77
-
78
- /** The client process this Channel was spawned by, or null when nobody knows. */
79
- export async function ownClient({ table = null, from = process.pid,
80
- command = createClaudeCodeAdapter().client.command } = {}) {
81
- return resolveClientPid({ table: table ?? await readProcessTable(), from, command });
82
- }
83
-
84
- // How long the Channel will wait for the hook to publish this session's
85
- // binding. Claude spawns this child while SessionStart is still running - both
86
- // landed in the same second on a real 2.1.259 launch - and a single lookup
87
- // loses that race often enough to make native delivery activate or not per
88
- // launch. Bounded, because a session with no ACC binding must still be answered.
89
- const BINDING_WAIT_MS = 5_000;
90
- const BINDING_POLL_MS = 100;
91
-
92
- /**
93
- * The session binding, once it exists - or null when the wait runs out.
94
- *
95
- * A binding with no `clientPid` names no process, and the Channel binds an
96
- * exact one, so it does not count as resolved: the hook writes the identity
97
- * first and the certified facts after, and reading in between must keep waiting
98
- * rather than settle for the half-written answer.
99
- */
100
- export async function resolveWithin({ resolve, deadline, intervalMs = BINDING_POLL_MS,
101
- now = () => Date.now(), sleep = ms => new Promise(resolve_ => setTimeout(resolve_, ms)) }) {
102
- for (;;) {
103
- const session = await resolve();
104
- if (session !== null && Number.isInteger(session?.clientPid)) return session;
105
- if (now() >= deadline) return null;
106
- await sleep(intervalMs);
2
+ // Kept only for installs made by ACC 0.7.x or earlier, which wired a Claude Channel MCP
3
+ // server. A 0.7.x updater refuses a downloaded package without this file, and a
4
+ // plugin copy written by 0.7.x can still ask Claude Code to spawn it until
5
+ // `acc install` or `acc update` lays the plugin out again.
6
+ //
7
+ // A child that answers nothing is reported to the user as a server that failed
8
+ // to connect, so this finishes the MCP handshake, declares no Channel and
9
+ // offers no tools, and exits when Claude closes its stdin or stops it with a signal.
10
+
11
+ function answer(message) {
12
+ if (message.method === "initialize") {
13
+ return { protocolVersion: message.params?.protocolVersion, capabilities: { tools: {} },
14
+ serverInfo: { name: "agents-can-communicate", version: "0.2.0" } };
107
15
  }
16
+ if (message.method === "tools/list") return { tools: [] };
17
+ if (message.method === "ping") return {};
18
+ return undefined;
108
19
  }
109
20
 
110
- async function compose() {
111
- const descriptor = await discoverWorkspace({ cwd: process.cwd(), env: process.env,
112
- gitProbe: createGitProbe() });
113
- const dataHome = platformDataHome({ platform: process.platform, env: process.env });
114
- const paths = runtimePaths({ dataHome, workspaceId: descriptor.id, workspaceRoots: descriptor.roots });
115
- const store = await openFilesystemStore({ root: paths.root, clock, ids,
116
- workspaceId: descriptor.id });
117
- const service = createCoordinationService({ store, clock, ids });
118
- const write = payload => process.stdout.write(`${JSON.stringify(payload)}\n`);
119
- // Read once: the ancestry that decides ownership cannot change while this
120
- // process runs, and re-reading it on every poll would spawn `ps` in a loop.
121
- const clientPid = await ownClient();
122
- const session = await resolveWithin({
123
- resolve: () => resolveSession({ runtimeDir: paths.root, service, env: process.env,
124
- ownClientPid: clientPid }),
125
- deadline: Date.now() + BINDING_WAIT_MS });
126
- // No binding to serve - but Claude is already speaking MCP to this child, and
127
- // returning here left the event loop empty: the process exited in 75ms
128
- // without answering `initialize`, and Claude reported the server as failed to
129
- // connect on every session that enables the plugin without ACC's shim.
130
- if (session === null || !Number.isInteger(session.clientPid)) {
131
- return startInertChannel({ write });
21
+ function handle(line) {
22
+ let message;
23
+ try { message = JSON.parse(line); } catch {
24
+ return { jsonrpc: "2.0", error: { code: -32700, message: "parse error" } };
132
25
  }
133
-
134
- const deliveryRouter = createDeliveryRouter({ service, adapters: ALL_ADAPTERS(), clock,
135
- readLivePolicy: ({ adapter }) => readInstalledLivePolicy({ dataHome, adapterId: adapter.id }) });
136
- const channel = createAccChannel({
137
- endpointDir: endpointDir(paths.root),
138
- clientPid: session.clientPid,
139
- write,
140
- // The Channel tool records through its own bound session, then uses the
141
- // same durable-first offer as ordinary MCP. A successful reply record alone
142
- // does not deliver it to the peer, and a failed offer must not lose it.
143
- routeReply: ({ messageId, body }) => recordAndOffer({ router: deliveryRouter,
144
- selectMessage: value => value.reply,
145
- record: () => routeReply({ service, session, messageId, body }) }),
146
- routeAck: ({ messageId }) => routeAck({ service, session, messageId }),
147
- // This process is the only party that knows the endpoint is still being
148
- // served, and Claude publishes no heartbeat, so nothing else would move
149
- // ACC's lease while the session sits idle - which is when live delivery is
150
- // worth having.
151
- refreshBinding: leaseUntil => service.refreshDeliveryBinding({
152
- sessionId: session.sessionId, generation: session.generation, leaseUntil }),
153
- });
154
- await channel.listen();
155
- pump(channel.handleLine, () => { channel.close(); process.exit(0); });
156
- // Serve MCP immediately; a slow or failed ACC binding must not stop the
157
- // client connecting. The listening endpoint lets an in-flight hook finish
158
- // before this owner acquires the same lifecycle lock and completes startup.
159
- await activateClaudeChannel({ session, service, runtimeDir: paths.root, dataHome,
160
- env: process.env }).catch(() => {});
161
- return channel;
26
+ if (message.id === undefined || message.id === null) return null;
27
+ const result = answer(message);
28
+ return result === undefined
29
+ ? { jsonrpc: "2.0", id: message.id, error: { code: -32601, message: `unknown method: ${message.method}` } }
30
+ : { jsonrpc: "2.0", id: message.id, result };
162
31
  }
163
32
 
164
33
  export async function main() {
165
- await compose().catch(() => startInertChannel());
34
+ let buffer = "";
35
+ process.stdin.setEncoding("utf8");
36
+ process.stdin.on("data", chunk => {
37
+ buffer += chunk;
38
+ let newline = buffer.indexOf("\n");
39
+ while (newline !== -1) {
40
+ const line = buffer.slice(0, newline).trim();
41
+ buffer = buffer.slice(newline + 1);
42
+ newline = buffer.indexOf("\n");
43
+ const reply = line === "" ? null : handle(line);
44
+ if (reply !== null) process.stdout.write(`${JSON.stringify(reply)}\n`);
45
+ }
46
+ });
47
+ await new Promise(resolve => {
48
+ process.stdin.once("end", resolve);
49
+ process.once("SIGTERM", resolve);
50
+ process.once("SIGINT", resolve);
51
+ process.stdin.resume();
52
+ });
53
+ // A signal can arrive while Claude still holds the pipe open; a stdin left
54
+ // reading would keep this process alive after main() returns.
55
+ process.stdin.destroy();
166
56
  }
@@ -148,11 +148,12 @@ to its capabilities:
148
148
 
149
149
  ```js
150
150
  nativeDelivery: {
151
- minimumByPlatform: { "darwin-arm64": "2.1.258" },
152
- anchors: [{ platform: "darwin-arm64", version: "2.1.258",
153
- protocolContract: "claude-code-channel-mcp-v1" }],
151
+ minimumByPlatform: { "darwin-arm64": "2.1.282" },
152
+ anchors: [{ platform: "darwin-arm64", version: "2.1.282",
153
+ protocolContract: "claude-code-inbox-socket-v1" }],
154
154
  knownBad: [],
155
- activationKinds: ["shell-bootstrap"],
155
+ activationKinds: ["native-service"],
156
+ offerKind: "wake",
156
157
  }
157
158
  ```
158
159
 
@@ -172,11 +173,32 @@ The rules `defineAdapter` enforces, and the ones the runtime applies:
172
173
  - Native methods return closed facts (`validateNativeActivationPlan()` closes the
173
174
  activation plan) and never put vendor data - endpoints, sockets, raw errors - into core.
174
175
 
175
- `nativeDelivery.policySource` defaults to the session environment. An adapter may
176
- declare `"installation-record"` when hooks and senders must read current recorded
177
- recipient consent independently of the vendor process environment. Installation
178
- records the requested policy even when current activation is unavailable. An absent
179
- or invalid record means `off`; a previously published binding cannot override it.
176
+ `nativeDelivery.policySource` accepts only `"installation-record"`, which is also the
177
+ default. Hooks and senders read the current recorded recipient consent, independently of
178
+ the vendor process environment. Installation records the requested policy even when
179
+ current activation is unavailable. An absent or invalid record means `off`. A previously
180
+ published binding cannot override it.
181
+
182
+ `nativeDelivery.offerKind` says what an accepted offer puts in front of the model. It is
183
+ `"message"` (the default) or `"wake"`:
184
+
185
+ - `"message"` carries the body. The router records the receipt as `offered` with the
186
+ transport name and returns the outcome `offered`.
187
+ - `"wake"` carries only a notice that makes the client run a turn. The router records no
188
+ offer and returns the outcome `woken`. The receipt stays `queued`. The next-turn hook
189
+ records `offered` via `next-turn` after its output carried the body. Use `"wake"` only
190
+ where the turn that the wake starts runs the client's next-turn hook, as Claude Code's
191
+ `UserPromptSubmit` does. When the client takes the wake but holds it for its user's
192
+ approval, return `pendingApproval: true` with the acceptance; the outcome carries it so
193
+ the sender is told the truth.
194
+
195
+ A failed offer of either kind is recorded as a failed offer and leaves the receipt queued.
196
+
197
+ `nativeDelivery.activationKinds` names the mechanisms the adapter may plan:
198
+ `"native-config"` or `"native-service"`. A `native-service` mechanism with
199
+ `preExisting: true` and no commands describes a service that the client itself runs, such
200
+ as the Claude Code inbox. A `shell-bootstrap` mechanism can no longer be planned. Install
201
+ records of one stay readable, so that `acc install` and `acc update` can retire it.
180
202
 
181
203
  The optional `refreshNativeSession()` method may revalidate an expired lease before
182
204
  an offer. It returns the same closed handshake shape as `bindNativeSession()` and
@@ -204,7 +226,7 @@ earlier failures without duplicating a certification tuple.
204
226
 
205
227
  Installed hook wiring may reach tier 2, but each effective hook capability still
206
228
  requires exact client/version/platform evidence. Tier 3 has separate native
207
- contracts: Claude Code Channel from 2.1.258 and Codex LocalDaemon from 0.152.1 on
229
+ contracts: the Claude Code inbox wake from 2.1.282 and Codex LocalDaemon from 0.152.1 on
208
230
  macOS arm64, with current probes and exact session handshakes. Codex's earlier
209
231
  remote-wrapper failure remains historical evidence. Ordinary launch now preserves
210
232
  workspace identity; the full installed product matrix, including negative controls
@@ -220,9 +242,18 @@ return normalizedEvent({
220
242
  kind, sessionId, cwd, model, parentSessionId, tool,
221
243
  targets, // paths this call would WRITE. For a shell call, pass the command to
222
244
  // shellWriteTargets() — it reads write positions only, never reads.
245
+ permissionMode, // the client's own name for the session's permission mode, or null
246
+ endReason, // on sessionEnd, the client's own word for why, or null
223
247
  });
224
248
  ```
225
249
 
250
+ `permissionMode` and `endReason` are optional and default to `null`. Pass `permissionMode`
251
+ when the client's hook reports one: a native binding reads it to tell a sender whether the
252
+ client will hold a wake for approval. Claude Code reports it on most hooks but not on
253
+ `SessionStart`. Pass `endReason` on `sessionEnd` when the client says why, as Claude Code's
254
+ `reason` does (`"clear"` after `/clear`). ACC keeps it in the closed session record's
255
+ `extensions`, and a sender is told that the conversation was cleared.
256
+
226
257
  Refuse an unrecognised payload. Inventing a session attaches the wrong one, or a new one
227
258
  every hook, and looks like it is working.
228
259
 
@@ -48,6 +48,11 @@ The order is part of the public guarantee:
48
48
  4. ask one adapter to cross its transport boundary;
49
49
  5. only after acceptance, commit `offered` and an immutable success event.
50
50
 
51
+ An adapter whose contract declares `offerKind: "wake"` puts only a notice in front of the
52
+ client, never the body. The router records no offer for an accepted wake and returns the
53
+ outcome `woken`. The receipt stays `queued` until the recipient's next-turn hook output
54
+ carried the body. That hook then commits `offered` via `next-turn`.
55
+
51
56
  A failed offer records the selected recipient session id and generation with a safe
52
57
  diagnostic code, then leaves the receipt queued. Core validates that target provenance and
53
58
  derives the event actor from it. A crash after bytes cross the boundary but before the
@@ -78,12 +83,16 @@ or projected into agent context; session restart replaces it and SessionEnd atte
78
83
  cleanup. Doctor ignores closed or superseded generations even if cleanup could not finish. Diagnostic disk I/O runs in an
79
84
  unreferenced worker, so a stalled write cannot hold the hook process open.
80
85
 
81
- Claude's MCP process can become ready after the bounded SessionStart handshake
82
- has ended. Once its endpoint listens, the Channel completes the same validated
83
- binding without a user prompt. It acquires the session lifecycle lock, recovers
84
- any committed journal, and rechecks its exact owner and current consent before
85
- publication. The optional diagnostic records `channelReady`; MCP remains responsive
86
- if registration fails. This does not create a session or revive a closed one.
86
+ Claude Code's inbox binding runs inside ACC's own hooks, with no ACC process in the
87
+ session. Each turn hook reads the session's inbox socket from `CLAUDE_CODE_MESSAGING_SOCKET`
88
+ and verifies it against Claude Code's session registry `<config>/sessions/<pid>.json`. The
89
+ pid and the socket must match; the session id is checked from the first offer on, because
90
+ Claude Code rewrites the entry for a `/resume` only after SessionStart. The hook then writes
91
+ a private endpoint record and publishes the binding with a 120-second lease. The router refreshes an expired
92
+ lease with the same registry check, so an idle session stays reachable between turns. The
93
+ adapter repeats the check before each offer. A registry that disappeared, or that names
94
+ another session or socket, refuses the offer, so a wake never reaches a reused pid. This
95
+ does not create a session or revive a closed one.
87
96
 
88
97
  ## Certified capability versus current reachability
89
98
 
@@ -96,14 +105,15 @@ a third certification check. A binding says what that current generation
96
105
  exposes and whether its lease is current, while recipient policy says whether it may spend a
97
106
  turn.
98
107
 
99
- Claude Code Channel has installed-client evidence on 2.1.258 and 2.1.260; Codex
100
- LocalDaemon has it on 0.152.1 and 0.153.4; Antigravity CLI has it on 1.2.7 and later through a relay
108
+ The Claude Code inbox wake has installed-client evidence on 2.1.282. Codex LocalDaemon
109
+ has it on 0.152.1 and 0.153.4. Antigravity CLI has it on 1.2.7 and later through a relay
101
110
  the agent starts in its own shell. All three use a captured platform minimum,
102
- current probe and exact session handshake. Codex preserves the ordinary client
103
- launch and verifies its registered thread and workspace; native delivery owns no vendor
104
- daemon lifecycle. Gemini CLI and Kimi Code have exact-version next-turn evidence only; Grok
105
- and generic MCP use inbox polling. Antigravity CLI additionally attaches
106
- a session only where the client reports an open workspace.
111
+ current probe and exact session handshake. Claude Code and Codex preserve the ordinary
112
+ client launch. Claude Code verifies its session registry, and Codex verifies its registered
113
+ thread and workspace. Native delivery owns no vendor daemon lifecycle. Gemini CLI and Kimi
114
+ Code have exact-version next-turn evidence only. Grok and generic MCP use inbox polling.
115
+ Antigravity CLI additionally attaches a session only where the client reports an open
116
+ workspace.
107
117
 
108
118
  ## Storage and workspace identity
109
119
 
@@ -4,7 +4,8 @@ Use this page to set expectations after installation. Integration means ACC can
4
4
  peer awareness and coordination instructions; it does not guarantee what a model will do
5
5
  with them. Delivery also varies independently from awareness. The durable inbox works for
6
6
  every participant, a client at or after a captured version may add next-turn delivery, and the
7
- experimental Codex LocalDaemon and Claude Code Channel paths can deliver while a session is idle.
7
+ experimental Codex LocalDaemon, Claude Code inbox wake and Antigravity CLI relay paths can
8
+ deliver while a session is idle.
8
9
 
9
10
  Capability honesty separates four questions that are easy to collapse:
10
11
 
@@ -50,11 +51,12 @@ child sessions, startup or safe-point injection, and before-read guards.
50
51
 
51
52
  The native rows are `no` at the hook versions this matrix names.
52
53
  Separate installed-client captures establish Codex `livePush` on 0.152.1 and
53
- 0.153.4, Claude Code `livePush` plus `replyRoute` on 2.1.258 and 2.1.260, and
54
+ 0.153.4, Claude Code `livePush` on 2.1.282 through the session inbox, and
54
55
  Antigravity CLI `livePush` on 1.2.7 and later through a relay the agent starts in its own shell.
55
56
  Native eligibility uses the captured platform minimum, a current feature probe,
56
57
  and an exact per-session handshake. It is experimental and requires recipient
57
- opt-in. Codex and Antigravity CLI reply through `acc reply`; their native `replyRoute` remains false.
58
+ opt-in. Codex, Claude Code and Antigravity CLI reply through `acc reply`. Their native
59
+ `replyRoute` remains false.
58
60
 
59
61
  The limitations belong next to the adapters they affect:
60
62
 
@@ -62,7 +64,7 @@ The limitations belong next to the adapters they affect:
62
64
  |---|---|
63
65
  | 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. |
64
66
  | 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. |
65
- | Claude Code | 2.1.233 next-turn delivery waits for the next user prompt. A 2.1.258 Channel capture proved idle offer, busy queue-after-turn, explicit reply, duplicate suppression, and durable fallback, so `delivery.livePush` and `delivery.replyRoute` are live capabilities behind the native contract (experimental, off until opted in; Claude's development-channel warning is vendor-owned and visible). |
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. |
66
68
  | 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. |
67
69
  | 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. |
68
70
  | 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. |
@@ -81,18 +83,19 @@ with a computed `reachable` boolean while keeping the opaque endpoint private.
81
83
  The router requires exactly one current eligible generation. No binding, several live
82
84
  sessions for one participant, and an adapter refusal keep delivery on the durable
83
85
  fallback. An expired lease may be revalidated by an adapter's optional refresh method;
84
- failure leaves the message queued. Busy behavior belongs to the transport: Claude's Channel can accept a
85
- message while the target is busy and queue it until the current turn finishes; an adapter
86
- that instead refuses with `recipient_busy` leaves the message on the durable path.
86
+ failure leaves the message queued. Busy behavior belongs to the transport. Codex accepts a
87
+ message while the target is busy and holds it until the current turn finishes. Claude Code
88
+ takes a wake between two tool calls of the running turn. An adapter that instead refuses
89
+ with `recipient_busy` leaves the message on the durable path.
87
90
 
88
91
  An endpoint may renew its lease, or an adapter may revalidate that exact endpoint on
89
92
  demand without a client heartbeat. Retirement is final: neither route may revive a
90
93
  retired binding, a closed session or an old generation. Delivery lease refresh does
91
94
  not renew participant presence.
92
95
 
93
- On darwin-arm64, Codex LocalDaemon and Claude Code Channel have captured
94
- experimental live delivery. Other adapters retain their separately certified
95
- next-turn paths or inbox polling. Linux and other uncaptured platforms retain
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
96
99
  the durable inbox; these captures establish no new hook capabilities there.
97
100
 
98
101
  Codex's 120-second delivery lease can refresh on demand for the same live endpoint;
@@ -115,13 +118,13 @@ policy:
115
118
  The default is `off`. `acc install --delivery actionable|all` records recipient consent.
116
119
  Current activation remains off when the platform, captured minimum or feature probe
117
120
  does not qualify; that temporary failure does not erase the requested policy. Each
118
- session must pass its own generation-bound handshake. An adapter using the installation
119
- record rereads consent at hooks and before offers, so a long-running vendor process
120
- cannot retain permission through an old environment value or binding snapshot.
121
+ session must pass its own generation-bound handshake. Every adapter reads consent from the
122
+ installation record and rereads it at hooks and before offers, so a long-running vendor
123
+ process cannot retain permission through an old binding snapshot.
121
124
 
122
- For an adapter using recorded consent, `--delivery off` and uninstall prevent new
123
- native offers. They cannot withdraw a submission already accepted by the vendor queue. Ordinary hook capabilities still
124
- require exact-version certification. Room messages are never live-push candidates.
125
+ `--delivery off` and uninstall prevent new native offers. They cannot withdraw a submission
126
+ already accepted by the vendor queue. Ordinary hook capabilities still require exact-version
127
+ certification. Room messages are never live-push candidates.
125
128
 
126
129
  ## Fallback
127
130
 
package/docs/CLI.md CHANGED
@@ -102,7 +102,13 @@ the messages addressed to that participant that it has not fetched, split by how
102
102
  got. Two sessions of one participant show the same numbers, so read the workspace total from
103
103
  `counts.unretrieved` rather than adding rows, and the text line ends with
104
104
  `<n> offered, not retrieved` when that number is above zero. Offered is not read: a
105
- transport took the bytes, and the model may or may not have looked. Default `sync` reads
105
+ transport took the bytes, and the model may or may not have looked.
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:
110
+ `waiting for a closed session: claude_code-b (cleared, 2)`. `acc status --all` lists every
111
+ session. Default `sync` reads
106
112
  events after a 16-digit event cursor (100 by
107
113
  default, up to 500). `--scope history` discovers historical message headers and reads
108
114
  selected records; `--scope full` adds an unbounded snapshot for explicit workspace forensics.
@@ -208,6 +214,36 @@ Human output starts with `recorded message_x`. A transport failure after that co
208
214
  not change the command exit code. Reuse an explicit `--client-message-id` after an
209
215
  uncertain result to recover the same logical message.
210
216
 
217
+ A queued result names why no live offer was made. `no_live_transport` means the recipient
218
+ is online but its session has no live transport bound: a participant attached with the CLI,
219
+ or a client without live delivery. It reads the message from its inbox or next turn, and
220
+ human output says `models has no live transport; the message waits in its inbox`.
221
+ `recipient_offline` means the recipient has no open session; the message waits until one
222
+ starts or resumes. When the client said why its last session ended, the result carries it,
223
+ for example `"endReason": "clear"` after Claude Code's `/clear`, and human output says
224
+ `models's conversation was cleared (/clear); the message waits until that conversation
225
+ resumes`. `recipient_unavailable` means the recipient's session is open but its live
226
+ transport could not be reached.
227
+
228
+ The delivery outcome `woken` means ACC woke a Claude Code session through its inbox:
229
+
230
+ ```json
231
+ { "recipientParticipantId": "models", "outcome": "woken", "transport": "claude-inbox" }
232
+ ```
233
+
234
+ Human output prints it as `woke models via claude-inbox; the message arrives with its next turn`.
235
+ The wake carries only fixed ACC text and the message id. The receipt stays `queued`. The
236
+ session's next-turn hook then shows the body and records the receipt as `offered` via
237
+ `next-turn`.
238
+
239
+ A Claude Code session that bypasses permission prompts holds each wake for its user's
240
+ approval, unless its `crossSessionInbound` setting is `accept`. The outcome then carries
241
+ `"pendingApproval": true`, and human output says
242
+ `sent a wake to models via claude-inbox, which its session holds for approval because it
243
+ bypasses permission prompts; the message arrives with its next turn`. A session whose
244
+ `crossSessionInbound` is `refuse` gets no wake: the message stays `queued` with
245
+ `delivery_disabled`.
246
+
211
247
  `reply` additionally returns `receipt`: the original message id, the replying participant,
212
248
  and state `acknowledged`. Its `message` and `delivery` describe the outgoing answer.
213
249
  Human output distinguishes `recorded <reply-id>` from `acknowledged <original-id>`.
@@ -320,12 +356,12 @@ terminal asks one default-No question for all selected clients that need a decis
320
356
  its local service is unavailable or has no loaded session; this does not activate delivery.
321
357
  A non-interactive run or a `--dry-run` keeps fresh clients off. A recorded opt-in is kept on upgrade. If the detected
322
358
  client cannot receive native delivery - unsupported, below the captured minimum, a
323
- prerelease, known-bad, a wrong platform, or an unsupported shell - installation keeps the
324
- effective policy off and prints the reason. Claude Code shell activation writes an owned
325
- zsh PATH block and a shim that `exec`s the real client; `ACC_BYPASS=1` bypasses that
326
- activation. Codex LocalDaemon delivery uses recorded installation consent without changing
327
- ordinary launch arguments. Its opt-in remains active when shim variables are absent or
328
- bypassed; `acc install --adapter codex --delivery off` disables new native offers. On a
359
+ prerelease, known-bad, or a wrong platform - installation keeps the
360
+ effective policy off and prints the reason. Every adapter reads live-delivery consent from
361
+ the installation record. Claude Code live delivery uses the inbox that each Claude Code
362
+ session opens itself. ACC adds no launch argument, shell file or wrapper for it. Codex
363
+ LocalDaemon delivery also keeps ordinary launch arguments unchanged.
364
+ `acc install --adapter <adapter> --delivery off` disables new native offers for that client. On a
329
365
  supported explicit install, complete consent can prepare a missing Codex service. On macOS
330
366
  arm64 with Codex 0.154.0 or newer, the same choice includes downloading a missing standalone
331
367
  package from OpenAI. ACC selects the installed CLI version and verifies the official
@@ -341,15 +377,14 @@ The install summary names each client's requested policy,
341
377
  activation state and verified fallback. `doctor` separates protocol readiness, recorded
342
378
  consent and a live channel in the current workspace, with a next step for missing activation.
343
379
  A supported version or an installed plugin alone is not an active delivery channel.
344
- `runtime: active` means ACC has a reachable local transport binding. Claude can still
345
- block inbound Channels messages while its MCP server and tools remain connected;
346
- doctor therefore also names the client-side startup check.
380
+ `runtime: active` means ACC has a reachable local transport binding. Claude Code applies
381
+ its own inbound controls to each wake: a session in `bypassPermissions` mode holds the wake
382
+ for approval. See [delivery troubleshooting](TROUBLESHOOTING.md#a-claude-code-session-holds-or-drops-acc-wakes).
347
383
  In doctor JSON, `nativeDelivery.activation` distinguishes missing launch setup from a
348
384
  recorded setup (or `not_required` for a pre-existing service). `policy` is the installed
349
385
  choice. `deliveryDecision` gives its known source and reports legacy provenance as unknown.
350
- `nativeServiceSetup` reports service infrastructure separately. `sessionPolicy` describes a native binding when one is visible. Existing Claude
351
- sessions can retain their launch policy after a different choice is installed for new
352
- sessions; Codex checks current recorded consent before new offers.
386
+ `nativeServiceSetup` reports service infrastructure separately. `sessionPolicy` describes a native binding when one is visible.
387
+ Every adapter rereads the current recorded consent at hooks and before new offers.
353
388
  `nativeDelivery.sessions` lists each current session's identity, present transport state
354
389
  and `lastAttempt`: timestamp, startup/turn event, effective policy and its source, whether
355
390
  that policy was missing/invalid/off, whether a client process was identified, and the closed