agents-can-communicate 0.7.0 → 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.
- package/README.md +3 -1
- package/bin/entrypoints/acc-bootstrap.mjs +6 -48
- package/bin/entrypoints/acc-claude-channel.mjs +47 -157
- package/docs/ADAPTER_AUTHORING.md +41 -10
- package/docs/ARCHITECTURE.md +28 -13
- package/docs/CAPABILITIES.md +19 -16
- package/docs/CLI.md +60 -14
- package/docs/CONCEPTS.md +12 -4
- package/docs/CONFIGURATION.md +18 -11
- package/docs/GETTING_STARTED.md +15 -12
- package/docs/GLOSSARY.md +2 -1
- package/docs/HOW_IT_WORKS.md +8 -4
- package/docs/PROTOCOL.md +56 -6
- package/docs/RELEASING.md +3 -2
- package/docs/SECURITY_MODEL.md +14 -1
- package/docs/TROUBLESHOOTING.md +64 -27
- package/docs/UPGRADING.md +49 -8
- package/docs/WHY_ACC.md +5 -3
- package/node_modules/@agents-can-communicate/adapter-antigravity/package.json +1 -1
- package/node_modules/@agents-can-communicate/adapter-antigravity/plugin/plugin.json +1 -1
- package/node_modules/@agents-can-communicate/adapter-antigravity/plugin/skills/acc/SKILL.md +8 -0
- package/node_modules/@agents-can-communicate/adapter-claude-code/certification.json +14 -73
- package/node_modules/@agents-can-communicate/adapter-claude-code/fixtures/certification-provenance.json +29 -71
- package/node_modules/@agents-can-communicate/adapter-claude-code/fixtures/delivery/claude-code-2.1.282-product-evidence.json +161 -0
- package/node_modules/@agents-can-communicate/adapter-claude-code/fixtures/delivery/claude-code-2.1.282.json +28 -0
- package/node_modules/@agents-can-communicate/adapter-claude-code/fixtures/inbox-wake-2.1.282.json +44 -0
- package/node_modules/@agents-can-communicate/adapter-claude-code/package.json +5 -2
- package/node_modules/@agents-can-communicate/adapter-claude-code/plugin/skills/acc/SKILL.md +8 -0
- package/node_modules/@agents-can-communicate/adapter-claude-code/src/adapter.mjs +25 -30
- package/node_modules/@agents-can-communicate/adapter-claude-code/src/hooks.mjs +6 -0
- package/node_modules/@agents-can-communicate/adapter-claude-code/src/inbox-contract.mjs +10 -0
- package/node_modules/@agents-can-communicate/adapter-claude-code/src/inbox-delivery.mjs +254 -0
- package/node_modules/@agents-can-communicate/adapter-claude-code/src/inbox-endpoint.mjs +166 -0
- package/node_modules/@agents-can-communicate/adapter-claude-code/src/inbox-registry.mjs +71 -0
- package/node_modules/@agents-can-communicate/adapter-claude-code/src/inbox-settings.mjs +109 -0
- package/node_modules/@agents-can-communicate/adapter-claude-code/src/install.mjs +25 -31
- package/node_modules/@agents-can-communicate/adapter-codex/package.json +1 -1
- package/node_modules/@agents-can-communicate/adapter-codex/plugin/skills/acc/SKILL.md +8 -0
- package/node_modules/@agents-can-communicate/adapter-gemini-cli/extension/gemini-extension.json +1 -1
- package/node_modules/@agents-can-communicate/adapter-gemini-cli/extension/skills/acc/SKILL.md +8 -0
- package/node_modules/@agents-can-communicate/adapter-gemini-cli/package.json +1 -1
- package/node_modules/@agents-can-communicate/adapter-grok/package.json +1 -1
- package/node_modules/@agents-can-communicate/adapter-grok/plugin/skills/acc/SKILL.md +8 -0
- package/node_modules/@agents-can-communicate/adapter-kimi/package.json +1 -1
- package/node_modules/@agents-can-communicate/adapter-kimi/plugin/skills/acc/SKILL.md +8 -0
- package/node_modules/@agents-can-communicate/adapter-sdk/package.json +1 -1
- package/node_modules/@agents-can-communicate/adapter-sdk/src/context-projector.mjs +15 -2
- package/node_modules/@agents-can-communicate/adapter-sdk/src/events.mjs +16 -3
- package/node_modules/@agents-can-communicate/adapter-sdk/src/hook-shim.mjs +4 -3
- package/node_modules/@agents-can-communicate/adapter-sdk/src/index.mjs +1 -1
- package/node_modules/@agents-can-communicate/adapter-sdk/src/native-activation.mjs +4 -14
- package/node_modules/@agents-can-communicate/adapter-sdk/src/native-delivery.mjs +23 -11
- package/node_modules/@agents-can-communicate/adapter-sdk/src/native-vocabulary.mjs +5 -1
- package/node_modules/@agents-can-communicate/cli/package.json +1 -1
- package/node_modules/@agents-can-communicate/cli/src/doctor-command.mjs +8 -6
- package/node_modules/@agents-can-communicate/cli/src/install-command.mjs +8 -23
- package/node_modules/@agents-can-communicate/cli/src/install-delivery-consent.mjs +3 -4
- package/node_modules/@agents-can-communicate/cli/src/main.mjs +39 -10
- package/node_modules/@agents-can-communicate/cli/src/managed-runtime/entry.mjs +7 -43
- package/node_modules/@agents-can-communicate/cli/src/managed-runtime/launchers.mjs +5 -3
- package/node_modules/@agents-can-communicate/cli/src/managed-runtime/refresh.mjs +3 -4
- package/node_modules/@agents-can-communicate/cli/src/native-delivery-status.mjs +2 -2
- package/node_modules/@agents-can-communicate/core/package.json +1 -1
- package/node_modules/@agents-can-communicate/core/src/conversations.mjs +2 -2
- package/node_modules/@agents-can-communicate/core/src/next-turn-delivery.mjs +21 -4
- package/node_modules/@agents-can-communicate/core/src/receipts.mjs +66 -10
- package/node_modules/@agents-can-communicate/core/src/sessions.mjs +25 -2
- package/node_modules/@agents-can-communicate/core/src/status.mjs +38 -1
- package/node_modules/@agents-can-communicate/core/src/sync.mjs +11 -2
- package/node_modules/@agents-can-communicate/delivery-router/package.json +1 -1
- package/node_modules/@agents-can-communicate/delivery-router/src/router.mjs +24 -7
- package/node_modules/@agents-can-communicate/hook-runner/package.json +1 -1
- package/node_modules/@agents-can-communicate/hook-runner/src/native-attempt.mjs +6 -8
- package/node_modules/@agents-can-communicate/hook-runner/src/native-binding.mjs +24 -11
- package/node_modules/@agents-can-communicate/hook-runner/src/runner.mjs +21 -12
- package/node_modules/@agents-can-communicate/installer/package.json +1 -1
- package/node_modules/@agents-can-communicate/installer/src/apply.mjs +19 -5
- package/node_modules/@agents-can-communicate/installer/src/detect.mjs +3 -7
- package/node_modules/@agents-can-communicate/installer/src/index.mjs +4 -7
- package/node_modules/@agents-can-communicate/installer/src/native-activation.mjs +23 -61
- package/node_modules/@agents-can-communicate/installer/src/ownership.mjs +7 -1
- package/node_modules/@agents-can-communicate/installer/src/plan.mjs +15 -5
- package/node_modules/@agents-can-communicate/installer/src/shell-bootstrap.mjs +10 -133
- package/node_modules/@agents-can-communicate/mcp-server/package.json +1 -1
- package/node_modules/@agents-can-communicate/mcp-server/src/tools.mjs +5 -3
- package/node_modules/@agents-can-communicate/protocol/package.json +1 -1
- package/node_modules/@agents-can-communicate/storage-filesystem/package.json +1 -1
- package/node_modules/@agents-can-communicate/storage-filesystem/src/atomic-json.mjs +6 -1
- package/package.json +1 -1
- package/bin/acc-bootstrap.mjs +0 -7
- package/bin/acc-claude-channel.mjs +0 -10
- package/bin/entrypoints/claude-channel-binding.mjs +0 -46
- package/bin/entrypoints/claude-channel-stdio.mjs +0 -29
- package/node_modules/@agents-can-communicate/adapter-claude-code/plugin/.mcp.json +0 -8
- package/node_modules/@agents-can-communicate/adapter-claude-code/src/channel.mjs +0 -381
- package/node_modules/@agents-can-communicate/adapter-claude-code/src/native-delivery.mjs +0 -232
- 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.
|
|
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
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
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
|
-
|
|
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
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
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
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
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
|
-
|
|
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.
|
|
152
|
-
anchors: [{ platform: "darwin-arm64", version: "2.1.
|
|
153
|
-
protocolContract: "claude-code-
|
|
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: ["
|
|
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`
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
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
|
|
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
|
|
package/docs/ARCHITECTURE.md
CHANGED
|
@@ -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
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
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
|
|
100
|
-
|
|
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
|
|
103
|
-
launch
|
|
104
|
-
|
|
105
|
-
and generic MCP use inbox polling.
|
|
106
|
-
a session only where the client reports an open
|
|
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
|
|
|
@@ -181,6 +191,11 @@ Adapters project peer bodies in an attributed untrusted frame. If a complete bod
|
|
|
181
191
|
fit, the projection keeps the message id and points to `acc inbox --message <id>` instead
|
|
182
192
|
of silently truncating it.
|
|
183
193
|
|
|
194
|
+
On a client that takes next-turn bodies, a live offer that is still `offered` 15 minutes later
|
|
195
|
+
is projected once more before a turn,
|
|
196
|
+
after the new bodies and marked as a repeat, and never by an end-of-turn continuation; see
|
|
197
|
+
[the protocol contract](PROTOCOL.md#one-repeat-of-a-live-offer).
|
|
198
|
+
|
|
184
199
|
For this participant's unresolved messages already `offered` or `retrieved`, the standard
|
|
185
200
|
projector combines reply and acknowledgement reminders into a compact count after new
|
|
186
201
|
message bodies. Queued obligations and claim conflicts remain individual attention items.
|
package/docs/CAPABILITIES.md
CHANGED
|
@@ -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
|
|
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`
|
|
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
|
|
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.
|
|
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
|
|
85
|
-
message while the target is busy and
|
|
86
|
-
|
|
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
|
|
94
|
-
experimental live delivery. Other adapters retain their separately
|
|
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.
|
|
119
|
-
record rereads
|
|
120
|
-
cannot retain permission through an old
|
|
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
|
-
|
|
123
|
-
|
|
124
|
-
|
|
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
|
|