agents-can-communicate 0.2.0 → 0.3.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 +17 -7
- package/bin/acc-bootstrap.mjs +56 -0
- package/bin/acc-claude-channel.mjs +177 -0
- package/bin/acc.mjs +7 -2
- package/docs/ADAPTER_AUTHORING.md +34 -2
- package/docs/CAPABILITIES.md +25 -10
- package/docs/CLI.md +10 -3
- package/docs/CONFIGURATION.md +4 -0
- package/docs/HOW_IT_WORKS.md +277 -0
- package/docs/PROTOCOL.md +1 -1
- package/docs/RELEASING.md +7 -1
- package/docs/TROUBLESHOOTING.md +7 -1
- package/docs/index.md +3 -1
- package/node_modules/@agents-can-communicate/adapter-claude-code/certification.json +196 -46
- package/node_modules/@agents-can-communicate/adapter-claude-code/fixtures/certification-provenance.json +237 -45
- package/node_modules/@agents-can-communicate/adapter-claude-code/fixtures/delivery/claude-code-2.1.252.json +3 -1
- package/node_modules/@agents-can-communicate/adapter-claude-code/fixtures/delivery/claude-code-2.1.258.json +23 -0
- package/node_modules/@agents-can-communicate/adapter-claude-code/fixtures/delivery/claude-code-2.1.260.json +23 -0
- package/node_modules/@agents-can-communicate/adapter-claude-code/package.json +5 -2
- package/node_modules/@agents-can-communicate/adapter-claude-code/plugin/.mcp.json +8 -0
- package/node_modules/@agents-can-communicate/adapter-claude-code/plugin/skills/acc/SKILL.md +4 -2
- package/node_modules/@agents-can-communicate/adapter-claude-code/src/adapter.mjs +34 -2
- package/node_modules/@agents-can-communicate/adapter-claude-code/src/channel.mjs +377 -0
- package/node_modules/@agents-can-communicate/adapter-claude-code/src/install.mjs +27 -7
- package/node_modules/@agents-can-communicate/adapter-claude-code/src/native-delivery.mjs +229 -0
- package/node_modules/@agents-can-communicate/adapter-codex/certification.json +38 -5
- package/node_modules/@agents-can-communicate/adapter-codex/fixtures/certification-provenance.json +171 -38
- package/node_modules/@agents-can-communicate/adapter-codex/fixtures/delivery/codex-cli-0.152.0.json +3 -1
- package/node_modules/@agents-can-communicate/adapter-codex/fixtures/delivery/codex-cli-0.152.1-remote-workspace.json +25 -0
- package/node_modules/@agents-can-communicate/adapter-codex/package.json +4 -2
- package/node_modules/@agents-can-communicate/adapter-codex/plugin/skills/acc/SKILL.md +4 -2
- package/node_modules/@agents-can-communicate/adapter-codex/src/adapter.mjs +42 -7
- package/node_modules/@agents-can-communicate/adapter-codex/src/app-server-client.mjs +121 -0
- package/node_modules/@agents-can-communicate/adapter-codex/src/native-delivery.mjs +151 -0
- package/node_modules/@agents-can-communicate/adapter-codex/src/ws-json-rpc.mjs +192 -0
- package/node_modules/@agents-can-communicate/adapter-gemini-cli/certification.json +44 -28
- 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 +4 -2
- package/node_modules/@agents-can-communicate/adapter-gemini-cli/fixtures/{BeforeAgent.json → BeforeAgent-0.57.0.json} +2 -2
- package/node_modules/@agents-can-communicate/adapter-gemini-cli/fixtures/{BeforeTool.json → BeforeTool-0.57.0.json} +2 -2
- package/node_modules/@agents-can-communicate/adapter-gemini-cli/fixtures/{BeforeTool-shell.json → BeforeTool-shell-0.57.0.json} +2 -2
- package/node_modules/@agents-can-communicate/adapter-gemini-cli/fixtures/{SessionEnd.json → SessionEnd-0.57.0.json} +2 -2
- package/node_modules/@agents-can-communicate/adapter-gemini-cli/fixtures/{SessionStart.json → SessionStart-0.57.0.json} +2 -2
- package/node_modules/@agents-can-communicate/adapter-gemini-cli/fixtures/certification-provenance.json +266 -39
- package/node_modules/@agents-can-communicate/adapter-gemini-cli/package.json +6 -6
- package/node_modules/@agents-can-communicate/adapter-gemini-cli/src/adapter.mjs +25 -11
- 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 +4 -2
- package/node_modules/@agents-can-communicate/adapter-grok/src/adapter.mjs +3 -1
- 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 +4 -2
- package/node_modules/@agents-can-communicate/adapter-sdk/package.json +1 -1
- package/node_modules/@agents-can-communicate/adapter-sdk/src/capabilities.mjs +18 -0
- package/node_modules/@agents-can-communicate/adapter-sdk/src/hook-shim.mjs +2 -0
- package/node_modules/@agents-can-communicate/adapter-sdk/src/index.mjs +6 -2
- package/node_modules/@agents-can-communicate/adapter-sdk/src/native-activation.mjs +76 -0
- package/node_modules/@agents-can-communicate/adapter-sdk/src/native-delivery.mjs +202 -0
- package/node_modules/@agents-can-communicate/adapter-sdk/src/native-vocabulary.mjs +101 -0
- package/node_modules/@agents-can-communicate/adapter-sdk/src/session-binding.mjs +17 -2
- package/node_modules/@agents-can-communicate/cli/package.json +1 -1
- package/node_modules/@agents-can-communicate/cli/src/args.mjs +2 -2
- package/node_modules/@agents-can-communicate/cli/src/doctor-command.mjs +67 -5
- package/node_modules/@agents-can-communicate/cli/src/install-command.mjs +103 -11
- package/node_modules/@agents-can-communicate/core/package.json +1 -1
- package/node_modules/@agents-can-communicate/core/src/delivery-bindings.mjs +52 -2
- package/node_modules/@agents-can-communicate/core/src/service.mjs +10 -0
- package/node_modules/@agents-can-communicate/delivery-router/package.json +1 -1
- package/node_modules/@agents-can-communicate/delivery-router/src/router.mjs +26 -12
- package/node_modules/@agents-can-communicate/hook-runner/package.json +4 -2
- package/node_modules/@agents-can-communicate/hook-runner/src/native-binding.mjs +90 -0
- package/node_modules/@agents-can-communicate/hook-runner/src/runner.mjs +123 -90
- package/node_modules/@agents-can-communicate/installer/package.json +1 -1
- package/node_modules/@agents-can-communicate/installer/src/apply.mjs +48 -4
- package/node_modules/@agents-can-communicate/installer/src/bootstrap-runtime.mjs +144 -0
- package/node_modules/@agents-can-communicate/installer/src/detect.mjs +72 -3
- package/node_modules/@agents-can-communicate/installer/src/index.mjs +7 -0
- package/node_modules/@agents-can-communicate/installer/src/native-activation.mjs +161 -0
- package/node_modules/@agents-can-communicate/installer/src/ownership.mjs +6 -2
- package/node_modules/@agents-can-communicate/installer/src/plan.mjs +41 -6
- package/node_modules/@agents-can-communicate/installer/src/shell-bootstrap.mjs +210 -0
- package/node_modules/@agents-can-communicate/mcp-server/package.json +1 -1
- package/node_modules/@agents-can-communicate/protocol/package.json +1 -1
- package/node_modules/@agents-can-communicate/protocol/src/fields.mjs +17 -0
- package/node_modules/@agents-can-communicate/protocol/src/schema.mjs +18 -3
- package/node_modules/@agents-can-communicate/storage-filesystem/package.json +1 -1
- package/package.json +2 -1
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
import { readFile, readdir, stat } from "node:fs/promises";
|
|
2
|
+
import net from "node:net";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
|
|
5
|
+
import { PROTOCOL_CONTRACT, endpointDir, isSocketSafe, readRegistration, routeAck, routeReply }
|
|
6
|
+
from "./channel.mjs";
|
|
7
|
+
|
|
8
|
+
// The five native-delivery adapter methods for Claude Code. They keep every
|
|
9
|
+
// Claude flag, protocol name, and endpoint detail inside this package and hand
|
|
10
|
+
// core only opaque facts: a probe verdict, an activation plan, a handshake with
|
|
11
|
+
// an opaque endpoint id, and an offer result. offerMessage is the sender-side
|
|
12
|
+
// half - it connects to the receiver's session endpoint and delivers one
|
|
13
|
+
// envelope; the Channel process itself turns acc_reply into a real ACC answer.
|
|
14
|
+
|
|
15
|
+
const MIN_VERSION = "2.1.258";
|
|
16
|
+
const CHANNEL_PLUGIN = "plugin:agents-can-communicate@acc-local";
|
|
17
|
+
const DEV_CHANNEL_FLAG = "--dangerously-load-development-channels";
|
|
18
|
+
// The bytes the installed 2.1.258 Mach-O carries for the Channel MCP protocol;
|
|
19
|
+
// a build without Channels has neither. Read-only, bounded.
|
|
20
|
+
const PROBE_NEEDLE = Buffer.from("notifications/claude/channel");
|
|
21
|
+
const PROBE_MAX_BYTES = 256 * 1024 * 1024;
|
|
22
|
+
const STABLE_VERSION = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:\+[0-9A-Za-z.-]+)?$/;
|
|
23
|
+
|
|
24
|
+
function compare(left, right) {
|
|
25
|
+
const a = left.split("+")[0].split(".").map(Number);
|
|
26
|
+
const b = right.split("+")[0].split(".").map(Number);
|
|
27
|
+
for (let index = 0; index < 3; index += 1) if (a[index] !== b[index]) return a[index] < b[index] ? -1 : 1;
|
|
28
|
+
return 0;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async function executableHasChannel(realExecutable) {
|
|
32
|
+
let handle;
|
|
33
|
+
try {
|
|
34
|
+
handle = await (await import("node:fs/promises")).open(realExecutable, "r");
|
|
35
|
+
} catch {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
try {
|
|
39
|
+
const chunk = Buffer.alloc(1024 * 1024);
|
|
40
|
+
let carry = Buffer.alloc(0);
|
|
41
|
+
let read = 0;
|
|
42
|
+
let position = 0;
|
|
43
|
+
do {
|
|
44
|
+
({ bytesRead: read } = await handle.read(chunk, 0, chunk.length, position));
|
|
45
|
+
if (read === 0) break;
|
|
46
|
+
const window = Buffer.concat([carry, chunk.subarray(0, read)]);
|
|
47
|
+
if (window.includes(PROBE_NEEDLE)) return true;
|
|
48
|
+
carry = window.subarray(Math.max(0, window.length - PROBE_NEEDLE.length));
|
|
49
|
+
position += read;
|
|
50
|
+
} while (position < PROBE_MAX_BYTES);
|
|
51
|
+
return false;
|
|
52
|
+
} finally {
|
|
53
|
+
await handle.close().catch(() => null);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Read-only: a stable version at or above the minimum whose executable carries
|
|
58
|
+
// the Channel protocol. Never launches the client.
|
|
59
|
+
export async function probeNativeDelivery({ realExecutable, timeoutMs = 750,
|
|
60
|
+
hasChannel = executableHasChannel, readVersion = defaultReadVersion } = {}) {
|
|
61
|
+
const unsupported = reasonCode => ({ supported: false, clientVersion: null,
|
|
62
|
+
protocolContract: PROTOCOL_CONTRACT, executableFingerprint: null, modes: [], reasonCode });
|
|
63
|
+
if (typeof realExecutable !== "string" || realExecutable === "") return unsupported("feature_probe_failed");
|
|
64
|
+
const clientVersion = await withTimeout(readVersion(realExecutable, timeoutMs), timeoutMs)
|
|
65
|
+
.catch(() => null);
|
|
66
|
+
if (clientVersion === null || !STABLE_VERSION.test(clientVersion)) {
|
|
67
|
+
return { ...unsupported("feature_probe_failed"), clientVersion };
|
|
68
|
+
}
|
|
69
|
+
if (compare(clientVersion, MIN_VERSION) < 0) {
|
|
70
|
+
return { ...unsupported("below_minimum_version"), clientVersion };
|
|
71
|
+
}
|
|
72
|
+
const present = await withTimeout(Promise.resolve(hasChannel(realExecutable)), timeoutMs)
|
|
73
|
+
.catch(() => false);
|
|
74
|
+
if (!present) return { ...unsupported("protocol_mismatch"), clientVersion };
|
|
75
|
+
return { supported: true, clientVersion, protocolContract: PROTOCOL_CONTRACT,
|
|
76
|
+
executableFingerprint: null, modes: ["livePush", "idleWake", "busyQueue", "replyRoute"],
|
|
77
|
+
reasonCode: null };
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function defaultReadVersion(realExecutable, timeoutMs) {
|
|
81
|
+
return new Promise(resolve => {
|
|
82
|
+
import("node:child_process").then(({ execFile }) => {
|
|
83
|
+
execFile(realExecutable, ["--version"], { timeout: timeoutMs, windowsHide: true },
|
|
84
|
+
(error, stdout, stderr) => {
|
|
85
|
+
if (error !== null) return resolve(null);
|
|
86
|
+
resolve(/(\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?)/.exec(`${stdout}${stderr}`)?.[1] ?? null);
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function withTimeout(work, ms) {
|
|
93
|
+
let timer = null;
|
|
94
|
+
const deadline = new Promise((_resolve, reject) => {
|
|
95
|
+
timer = setTimeout(() => reject(Object.assign(new Error("native probe timed out"),
|
|
96
|
+
{ code: "ETIMEDOUT" })), ms);
|
|
97
|
+
});
|
|
98
|
+
return Promise.race([work, deadline]).finally(() => clearTimeout(timer));
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// The shell bootstrap adds only the captured Channel flag; the .mcp.json the
|
|
102
|
+
// adapter's own install writes is the native-config artifact. ACC never
|
|
103
|
+
// suppresses Claude's experimental warning.
|
|
104
|
+
export function planNativeActivation({ detection, livePolicy }) {
|
|
105
|
+
if (detection?.realExecutable === undefined || detection.realExecutable === null) {
|
|
106
|
+
return { eligible: false, reasonCode: "feature_probe_failed", mechanisms: [] };
|
|
107
|
+
}
|
|
108
|
+
void livePolicy;
|
|
109
|
+
return { eligible: true, reasonCode: null, mechanisms: [
|
|
110
|
+
{ kind: "native-config", artifactIds: ["claude-channel-mcp"] },
|
|
111
|
+
{ kind: "shell-bootstrap", command: "claude", realExecutable: detection.realExecutable,
|
|
112
|
+
prefixArgs: [DEV_CHANNEL_FLAG, CHANNEL_PLUGIN] },
|
|
113
|
+
] };
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
async function currentRegistrations(runtimeDir) {
|
|
117
|
+
const dir = endpointDir(runtimeDir);
|
|
118
|
+
let names;
|
|
119
|
+
try { names = await readdir(dir); } catch { return []; }
|
|
120
|
+
const registrations = [];
|
|
121
|
+
for (const name of names) {
|
|
122
|
+
if (!name.endsWith(".json")) continue;
|
|
123
|
+
try {
|
|
124
|
+
const record = readRegistration(await readFile(path.join(dir, name), "utf8"));
|
|
125
|
+
registrations.push(record);
|
|
126
|
+
} catch { /* skip a malformed neighbour */ }
|
|
127
|
+
}
|
|
128
|
+
return registrations;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Bind the exact Channel the hook-resolved Claude process owns: match clientPid,
|
|
132
|
+
// verify the protocol, and return an opaque endpoint id. Never selects the first
|
|
133
|
+
// registration; two Claude sessions cannot receive each other's endpoint.
|
|
134
|
+
export async function bindNativeSession({ clientPid, clientVersion, runtimeDir, timeoutMs = 750,
|
|
135
|
+
intervalMs = 50, now = () => Date.now(),
|
|
136
|
+
sleep = ms => new Promise(resolve => setTimeout(resolve, ms)) }) {
|
|
137
|
+
const closed = reasonCode => ({ supported: false, clientVersion: clientVersion ?? null,
|
|
138
|
+
protocolContract: PROTOCOL_CONTRACT, modes: [], opaqueEndpointRef: null, leaseUntil: null,
|
|
139
|
+
reasonCode });
|
|
140
|
+
if (!Number.isInteger(clientPid) || clientPid <= 0) return closed("handshake_failed");
|
|
141
|
+
// Both sides of this handshake are started by SessionStart: the hook writes
|
|
142
|
+
// the session binding and then calls here, while the Channel is still waiting
|
|
143
|
+
// for that same binding before it can listen and register. So the endpoint
|
|
144
|
+
// reliably appears after this begins, and a single read gave up on a channel
|
|
145
|
+
// that was moments from ready - measured, a healthy endpoint landed on disk
|
|
146
|
+
// just after the hook had already recorded no binding at all.
|
|
147
|
+
//
|
|
148
|
+
// The budget was always passed in for this. It stops one interval short of
|
|
149
|
+
// the hook's own timer, which races the same budget: an answer that arrives
|
|
150
|
+
// together with that timer is a coin toss the session loses.
|
|
151
|
+
const deadline = now() + Math.max(0, timeoutMs - intervalMs);
|
|
152
|
+
for (;;) {
|
|
153
|
+
const matches = (await currentRegistrations(runtimeDir))
|
|
154
|
+
.filter(record => record.clientPid === clientPid
|
|
155
|
+
&& record.protocolContract === PROTOCOL_CONTRACT
|
|
156
|
+
&& Date.parse(record.leaseUntil) > Date.now());
|
|
157
|
+
// Exactly one, still: waiting must not relax the rule that two Claude
|
|
158
|
+
// sessions can never be handed each other's endpoint.
|
|
159
|
+
if (matches.length === 1 && isSocketSafe(matches[0].socketPath)) {
|
|
160
|
+
const [record] = matches;
|
|
161
|
+
return { supported: true, clientVersion: clientVersion ?? null,
|
|
162
|
+
protocolContract: PROTOCOL_CONTRACT, modes: [...record.modes],
|
|
163
|
+
opaqueEndpointRef: record.endpointId, leaseUntil: record.leaseUntil, reasonCode: null };
|
|
164
|
+
}
|
|
165
|
+
if (now() >= deadline) return closed("handshake_failed");
|
|
166
|
+
await sleep(intervalMs);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Sender side: resolve the endpoint id to its registration under this
|
|
171
|
+
// workspace's runtime dir, connect to the session-scoped socket, and deliver one
|
|
172
|
+
// envelope. Returns only accepted/duplicate plus the bound client version.
|
|
173
|
+
export async function offerMessage({ binding, message, runtimeDir, timeoutMs = 2_000,
|
|
174
|
+
connect = net.createConnection }) {
|
|
175
|
+
const rejected = safeErrorCode => ({ accepted: false, transport: "claude-channel",
|
|
176
|
+
clientVersion: binding?.clientVersion ?? null, safeErrorCode });
|
|
177
|
+
if (typeof runtimeDir !== "string" || binding?.opaqueEndpointRef === undefined) {
|
|
178
|
+
return rejected("recipient_unavailable");
|
|
179
|
+
}
|
|
180
|
+
const file = path.join(endpointDir(runtimeDir), `${binding.opaqueEndpointRef}.json`);
|
|
181
|
+
if (path.dirname(file) !== endpointDir(runtimeDir)) return rejected("recipient_unavailable");
|
|
182
|
+
let record;
|
|
183
|
+
try {
|
|
184
|
+
record = readRegistration(await readFile(file, "utf8"));
|
|
185
|
+
} catch {
|
|
186
|
+
return rejected("recipient_unavailable");
|
|
187
|
+
}
|
|
188
|
+
if (record.endpointId !== binding.opaqueEndpointRef
|
|
189
|
+
|| record.protocolContract !== PROTOCOL_CONTRACT
|
|
190
|
+
|| Date.parse(record.leaseUntil) <= Date.now()
|
|
191
|
+
|| !(await stat(record.socketPath).then(s => s.isSocket(), () => false))
|
|
192
|
+
|| !isSocketSafe(record.socketPath)) {
|
|
193
|
+
return rejected("recipient_unavailable");
|
|
194
|
+
}
|
|
195
|
+
return sendEnvelope({ record, message, binding, connect, timeoutMs, rejected });
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function sendEnvelope({ record, message, binding, connect, timeoutMs, rejected }) {
|
|
199
|
+
const envelope = { nonce: record.nonce, messageId: message.messageId, kind: message.kind,
|
|
200
|
+
subject: message.subject ?? "", body: message.body ?? "",
|
|
201
|
+
...(typeof message.inReplyTo === "string" ? { inReplyTo: message.inReplyTo } : {}) };
|
|
202
|
+
return new Promise(resolve => {
|
|
203
|
+
const socket = connect(record.socketPath);
|
|
204
|
+
let settled = false;
|
|
205
|
+
const finish = value => { if (settled) return; settled = true; clearTimeout(timer);
|
|
206
|
+
socket.destroy(); resolve(value); };
|
|
207
|
+
const timer = setTimeout(() => finish(rejected("transport_error")), timeoutMs);
|
|
208
|
+
socket.once("error", () => finish(rejected("recipient_unavailable")));
|
|
209
|
+
socket.once("connect", () => {
|
|
210
|
+
socket.write(`${JSON.stringify(envelope)}\n`);
|
|
211
|
+
let buffer = "";
|
|
212
|
+
socket.on("data", chunk => {
|
|
213
|
+
buffer += chunk.toString("utf8");
|
|
214
|
+
const newline = buffer.indexOf("\n");
|
|
215
|
+
if (newline === -1) return;
|
|
216
|
+
let response;
|
|
217
|
+
try { response = JSON.parse(buffer.slice(0, newline)); }
|
|
218
|
+
catch { return finish(rejected("transport_rejected")); }
|
|
219
|
+
if (response.accepted === true) {
|
|
220
|
+
return finish({ accepted: true, transport: "claude-channel",
|
|
221
|
+
clientVersion: binding.clientVersion });
|
|
222
|
+
}
|
|
223
|
+
finish(rejected("transport_rejected"));
|
|
224
|
+
});
|
|
225
|
+
});
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
export { MIN_VERSION, endpointDir, routeAck, routeReply };
|
|
@@ -12,7 +12,9 @@
|
|
|
12
12
|
"idleBehavior": "fires when a client session starts",
|
|
13
13
|
"busyBehavior": "fires before the first model turn",
|
|
14
14
|
"authorityLevel": "advisory",
|
|
15
|
-
"limitations": [
|
|
15
|
+
"limitations": [
|
|
16
|
+
"plugin hooks must be explicitly trusted by the user"
|
|
17
|
+
],
|
|
16
18
|
"result": "pass"
|
|
17
19
|
},
|
|
18
20
|
{
|
|
@@ -27,7 +29,9 @@
|
|
|
27
29
|
"idleBehavior": "fires when a client session exits",
|
|
28
30
|
"busyBehavior": "does not run until the session exits",
|
|
29
31
|
"authorityLevel": "advisory",
|
|
30
|
-
"limitations": [
|
|
32
|
+
"limitations": [
|
|
33
|
+
"plugin hooks must be explicitly trusted by the user"
|
|
34
|
+
],
|
|
31
35
|
"result": "pass"
|
|
32
36
|
},
|
|
33
37
|
{
|
|
@@ -42,7 +46,9 @@
|
|
|
42
46
|
"idleBehavior": "waits for the next user prompt",
|
|
43
47
|
"busyBehavior": "does not interrupt an in-progress turn",
|
|
44
48
|
"authorityLevel": "context",
|
|
45
|
-
"limitations": [
|
|
49
|
+
"limitations": [
|
|
50
|
+
"stdout arrives as an unwrapped developer-role message"
|
|
51
|
+
],
|
|
46
52
|
"result": "pass"
|
|
47
53
|
},
|
|
48
54
|
{
|
|
@@ -57,7 +63,9 @@
|
|
|
57
63
|
"idleBehavior": "no write exists to guard",
|
|
58
64
|
"busyBehavior": "blocks an apply_patch call before disk mutation",
|
|
59
65
|
"authorityLevel": "blocking",
|
|
60
|
-
"limitations": [
|
|
66
|
+
"limitations": [
|
|
67
|
+
"runtime and unrecognised shell writes can bypass the guard"
|
|
68
|
+
],
|
|
61
69
|
"result": "pass"
|
|
62
70
|
},
|
|
63
71
|
{
|
|
@@ -72,7 +80,9 @@
|
|
|
72
80
|
"idleBehavior": "offers complete peer messages at the next prompt",
|
|
73
81
|
"busyBehavior": "does not interrupt an in-progress turn",
|
|
74
82
|
"authorityLevel": "context",
|
|
75
|
-
"limitations": [
|
|
83
|
+
"limitations": [
|
|
84
|
+
"delivery requires the next normal user turn"
|
|
85
|
+
],
|
|
76
86
|
"result": "pass"
|
|
77
87
|
},
|
|
78
88
|
{
|
|
@@ -112,6 +122,29 @@
|
|
|
112
122
|
"idle, busy, reply, duplicate, and fallback branches were not observed"
|
|
113
123
|
],
|
|
114
124
|
"result": "fail"
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
"client": "codex-cli",
|
|
128
|
+
"version": "0.152.1",
|
|
129
|
+
"platform": "darwin-arm64",
|
|
130
|
+
"observedAt": "2026-09-03T04:45:00.000Z",
|
|
131
|
+
"capability": "delivery.livePush",
|
|
132
|
+
"fixture": "fixtures/delivery/codex-cli-0.152.1-remote-workspace.json",
|
|
133
|
+
"provenance": "fixtures/certification-provenance.json",
|
|
134
|
+
"provenanceId": "native-delivery-0-152-1-remote-workspace",
|
|
135
|
+
"idleBehavior": "unobserved",
|
|
136
|
+
"busyBehavior": "unobserved",
|
|
137
|
+
"authorityLevel": "experimental",
|
|
138
|
+
"limitations": [
|
|
139
|
+
"release capture on the installed tarball, with the vendor daemon already running from a directory other than the session's",
|
|
140
|
+
"native delivery requires codex --remote unix://, and in that mode the session runs inside the daemon",
|
|
141
|
+
"the SessionStart hook payload reported cwd as the daemon's directory rather than the client's: the client was working in a temporary capture project while the payload named the daemon's own checkout",
|
|
142
|
+
"the App Server's own thread/list recorded that same daemon directory for the live thread, so the session's workspace is not available from anything ACC can reach",
|
|
143
|
+
"ACC placed the session in an unrelated workspace and injected that workspace's peers into it",
|
|
144
|
+
"no delivery branch could be observed because the session was never addressable from the workspace it was actually in",
|
|
145
|
+
"the earlier 0.152.1 pass started the daemon itself in the session's own directory, so the two directories coincided and the substitution was invisible"
|
|
146
|
+
],
|
|
147
|
+
"result": "fail"
|
|
115
148
|
}
|
|
116
149
|
]
|
|
117
150
|
}
|
package/node_modules/@agents-can-communicate/adapter-codex/fixtures/certification-provenance.json
CHANGED
|
@@ -2,64 +2,197 @@
|
|
|
2
2
|
"schemaVersion": 1,
|
|
3
3
|
"captures": [
|
|
4
4
|
{
|
|
5
|
-
"id": "session-start",
|
|
6
|
-
"
|
|
5
|
+
"id": "session-start",
|
|
6
|
+
"client": "codex-cli",
|
|
7
|
+
"version": "0.147.0",
|
|
8
|
+
"platform": "darwin-arm64",
|
|
9
|
+
"observedAt": "2026-08-16",
|
|
7
10
|
"fixture": "fixtures/SessionStart.json",
|
|
8
11
|
"sha256": "1580157d6b48c10f6de5f155aa1d7c68d1695409a5a178eb36c85ce5e01de956",
|
|
9
|
-
"event": "SessionStart",
|
|
10
|
-
"
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
"
|
|
12
|
+
"event": "SessionStart",
|
|
13
|
+
"tool": null,
|
|
14
|
+
"claims": [
|
|
15
|
+
{
|
|
16
|
+
"capability": "lifecycle.sessionStart",
|
|
17
|
+
"result": "pass",
|
|
18
|
+
"outcome": {
|
|
19
|
+
"kind": "event-observed",
|
|
20
|
+
"idle": "fires when a client session starts",
|
|
21
|
+
"busy": "fires before the first model turn",
|
|
22
|
+
"authority": "advisory",
|
|
23
|
+
"limitations": [
|
|
24
|
+
"plugin hooks must be explicitly trusted by the user"
|
|
25
|
+
]
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
]
|
|
14
29
|
},
|
|
15
30
|
{
|
|
16
|
-
"id": "session-end",
|
|
17
|
-
"
|
|
31
|
+
"id": "session-end",
|
|
32
|
+
"client": "codex-cli",
|
|
33
|
+
"version": "0.147.0",
|
|
34
|
+
"platform": "darwin-arm64",
|
|
35
|
+
"observedAt": "2026-08-16",
|
|
18
36
|
"fixture": "fixtures/SessionEnd.json",
|
|
19
37
|
"sha256": "e1468c9d5cd2ff5408184158004e9877684f1ae2815239691bbaa386327fe58e",
|
|
20
|
-
"event": "SessionEnd",
|
|
21
|
-
"
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
"
|
|
38
|
+
"event": "SessionEnd",
|
|
39
|
+
"tool": null,
|
|
40
|
+
"claims": [
|
|
41
|
+
{
|
|
42
|
+
"capability": "lifecycle.sessionEnd",
|
|
43
|
+
"result": "pass",
|
|
44
|
+
"outcome": {
|
|
45
|
+
"kind": "event-observed",
|
|
46
|
+
"idle": "fires when a client session exits",
|
|
47
|
+
"busy": "does not run until the session exits",
|
|
48
|
+
"authority": "advisory",
|
|
49
|
+
"limitations": [
|
|
50
|
+
"plugin hooks must be explicitly trusted by the user"
|
|
51
|
+
]
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
]
|
|
25
55
|
},
|
|
26
56
|
{
|
|
27
|
-
"id": "user-prompt-submit",
|
|
28
|
-
"
|
|
57
|
+
"id": "user-prompt-submit",
|
|
58
|
+
"client": "codex-cli",
|
|
59
|
+
"version": "0.147.0",
|
|
60
|
+
"platform": "darwin-arm64",
|
|
61
|
+
"observedAt": "2026-08-16",
|
|
29
62
|
"fixture": "fixtures/UserPromptSubmit.json",
|
|
30
63
|
"sha256": "1774062b17ea917cf67d8adca3408525a928c5b9b88adda1ff6b92f3e89a459f",
|
|
31
|
-
"event": "UserPromptSubmit",
|
|
64
|
+
"event": "UserPromptSubmit",
|
|
65
|
+
"tool": null,
|
|
32
66
|
"claims": [
|
|
33
|
-
{
|
|
34
|
-
"
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
"busy": "does not interrupt an in-progress turn",
|
|
40
|
-
"
|
|
67
|
+
{
|
|
68
|
+
"capability": "context.beforeTurnInjection",
|
|
69
|
+
"result": "pass",
|
|
70
|
+
"outcome": {
|
|
71
|
+
"kind": "model-context-observed",
|
|
72
|
+
"idle": "waits for the next user prompt",
|
|
73
|
+
"busy": "does not interrupt an in-progress turn",
|
|
74
|
+
"authority": "context",
|
|
75
|
+
"limitations": [
|
|
76
|
+
"stdout arrives as an unwrapped developer-role message"
|
|
77
|
+
]
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
"capability": "delivery.nextTurn",
|
|
82
|
+
"result": "pass",
|
|
83
|
+
"outcome": {
|
|
84
|
+
"kind": "model-context-observed",
|
|
85
|
+
"idle": "offers complete peer messages at the next prompt",
|
|
86
|
+
"busy": "does not interrupt an in-progress turn",
|
|
87
|
+
"authority": "context",
|
|
88
|
+
"limitations": [
|
|
89
|
+
"delivery requires the next normal user turn"
|
|
90
|
+
]
|
|
91
|
+
}
|
|
92
|
+
}
|
|
41
93
|
]
|
|
42
94
|
},
|
|
43
95
|
{
|
|
44
|
-
"id": "pre-tool-use",
|
|
45
|
-
"
|
|
96
|
+
"id": "pre-tool-use",
|
|
97
|
+
"client": "codex-cli",
|
|
98
|
+
"version": "0.147.0",
|
|
99
|
+
"platform": "darwin-arm64",
|
|
100
|
+
"observedAt": "2026-08-16",
|
|
46
101
|
"fixture": "fixtures/PreToolUse.json",
|
|
47
102
|
"sha256": "8d3785cfcffbe9affef6c10c1dbbeaf5a0b7062eaed4414e4a66c073f0397a77",
|
|
48
|
-
"event": "PreToolUse",
|
|
49
|
-
"
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
"
|
|
103
|
+
"event": "PreToolUse",
|
|
104
|
+
"tool": "apply_patch",
|
|
105
|
+
"claims": [
|
|
106
|
+
{
|
|
107
|
+
"capability": "guards.beforeWrite",
|
|
108
|
+
"result": "pass",
|
|
109
|
+
"outcome": {
|
|
110
|
+
"kind": "tool-denied-before-mutation",
|
|
111
|
+
"idle": "no write exists to guard",
|
|
112
|
+
"busy": "blocks an apply_patch call before disk mutation",
|
|
113
|
+
"authority": "blocking",
|
|
114
|
+
"limitations": [
|
|
115
|
+
"runtime and unrecognised shell writes can bypass the guard"
|
|
116
|
+
]
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
]
|
|
53
120
|
},
|
|
54
121
|
{
|
|
55
|
-
"id": "native-delivery-0-152-0",
|
|
56
|
-
"
|
|
122
|
+
"id": "native-delivery-0-152-0",
|
|
123
|
+
"client": "codex-cli",
|
|
124
|
+
"version": "0.152.0",
|
|
125
|
+
"platform": "darwin-arm64",
|
|
126
|
+
"observedAt": "2026-09-01T16:20:40.386Z",
|
|
57
127
|
"fixture": "fixtures/delivery/codex-cli-0.152.0.json",
|
|
58
|
-
"sha256": "
|
|
59
|
-
"event": null,
|
|
128
|
+
"sha256": "1f3a57d5ab6fecf827fd54a1fd452c3c7a8d0ac199dd2af025f1954aa83b3cf8",
|
|
129
|
+
"event": null,
|
|
130
|
+
"tool": null,
|
|
131
|
+
"claims": [
|
|
132
|
+
{
|
|
133
|
+
"capability": "delivery.livePush",
|
|
134
|
+
"result": "fail",
|
|
135
|
+
"outcome": {
|
|
136
|
+
"kind": "native-capture-failed"
|
|
137
|
+
}
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
"capability": "delivery.replyRoute",
|
|
141
|
+
"result": "fail",
|
|
142
|
+
"outcome": {
|
|
143
|
+
"kind": "native-capture-failed"
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
]
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
"id": "native-delivery-0-152-1",
|
|
150
|
+
"client": "codex-cli",
|
|
151
|
+
"version": "0.152.1",
|
|
152
|
+
"platform": "darwin-arm64",
|
|
153
|
+
"observedAt": "2026-09-02T21:32:19.320Z",
|
|
154
|
+
"fixture": "fixtures/delivery/codex-cli-0.152.1.json",
|
|
155
|
+
"sha256": "8254ce1ec25926228109edfa20f970c0036d3c2e012a0dd58af610afc57f7a39",
|
|
156
|
+
"event": null,
|
|
157
|
+
"tool": null,
|
|
158
|
+
"claims": [
|
|
159
|
+
{
|
|
160
|
+
"capability": "delivery.livePush",
|
|
161
|
+
"result": "pass",
|
|
162
|
+
"outcome": {
|
|
163
|
+
"kind": "native-delivery-observed",
|
|
164
|
+
"idle": "offered",
|
|
165
|
+
"busy": "queued_after_turn",
|
|
166
|
+
"authority": "experimental",
|
|
167
|
+
"limitations": [
|
|
168
|
+
"captured on darwin-arm64 only; Linux and Windows remain uncaptured",
|
|
169
|
+
"no daemon pre-existed; codex app-server daemon start created it for this capture and daemon stop removed it",
|
|
170
|
+
"the daemon speaks JSON-RPC over WebSocket on its control Unix socket; a submission queued on the idle thread started a turn by itself, and one queued during a turn was presented after that turn as a user prompt",
|
|
171
|
+
"the model answered through the ACC CLI (acc reply), which proves the product loop but not a native reply route; delivery.replyRoute stays uncertified",
|
|
172
|
+
"thread/queue/add has no native idempotency once a submission has been consumed: a retried clientUserMessageId created a second submission and a short turn but no second ACC answer; idempotency holds while the submission is still queued, where thread/queue/list exposes it"
|
|
173
|
+
]
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
]
|
|
177
|
+
},
|
|
178
|
+
{
|
|
179
|
+
"id": "native-delivery-0-152-1-remote-workspace",
|
|
180
|
+
"client": "codex-cli",
|
|
181
|
+
"version": "0.152.1",
|
|
182
|
+
"platform": "darwin-arm64",
|
|
183
|
+
"observedAt": "2026-09-03T04:45:00.000Z",
|
|
184
|
+
"fixture": "fixtures/delivery/codex-cli-0.152.1-remote-workspace.json",
|
|
185
|
+
"sha256": "e4f419111d254223f3930eeee61c30c447bb68dd51a4faf81be2a33b144bbaca",
|
|
186
|
+
"event": null,
|
|
187
|
+
"tool": null,
|
|
60
188
|
"claims": [
|
|
61
|
-
{
|
|
62
|
-
|
|
189
|
+
{
|
|
190
|
+
"capability": "delivery.livePush",
|
|
191
|
+
"result": "fail",
|
|
192
|
+
"outcome": {
|
|
193
|
+
"kind": "native-capture-failed"
|
|
194
|
+
}
|
|
195
|
+
}
|
|
63
196
|
]
|
|
64
197
|
}
|
|
65
198
|
]
|
package/node_modules/@agents-can-communicate/adapter-codex/fixtures/delivery/codex-cli-0.152.0.json
CHANGED
|
@@ -3,9 +3,11 @@
|
|
|
3
3
|
"version": "0.152.0",
|
|
4
4
|
"platform": "darwin-arm64",
|
|
5
5
|
"observedAt": "2026-09-01T16:20:40.386Z",
|
|
6
|
-
"capability": "
|
|
6
|
+
"capability": "native_delivery",
|
|
7
7
|
"result": "fail",
|
|
8
8
|
"fixture": "codex-cli-0.152.0-existing-session",
|
|
9
|
+
"launchMode": "no-client-launched",
|
|
10
|
+
"protocolContract": "codex-app-server-turn-start-v0",
|
|
9
11
|
"idle": "unobserved",
|
|
10
12
|
"busy": "unobserved",
|
|
11
13
|
"reply": "unobserved",
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"client": "codex-cli",
|
|
3
|
+
"version": "0.152.1",
|
|
4
|
+
"platform": "darwin-arm64",
|
|
5
|
+
"observedAt": "2026-09-03T04:45:00.000Z",
|
|
6
|
+
"capability": "native_delivery",
|
|
7
|
+
"result": "fail",
|
|
8
|
+
"fixture": "codex-cli-0.152.1-remote-workspace",
|
|
9
|
+
"launchMode": "ordinary-command-with-install-time-bootstrap",
|
|
10
|
+
"protocolContract": "codex-app-server-thread-queue-v1",
|
|
11
|
+
"idle": "unobserved",
|
|
12
|
+
"busy": "unobserved",
|
|
13
|
+
"reply": "unobserved",
|
|
14
|
+
"duplicate": "unobserved",
|
|
15
|
+
"fallback": "unobserved",
|
|
16
|
+
"limitations": [
|
|
17
|
+
"release capture on the installed tarball, with the vendor daemon already running from a directory other than the session's",
|
|
18
|
+
"native delivery requires codex --remote unix://, and in that mode the session runs inside the daemon",
|
|
19
|
+
"the SessionStart hook payload reported cwd as the daemon's directory rather than the client's: the client was working in a temporary capture project while the payload named the daemon's own checkout",
|
|
20
|
+
"the App Server's own thread/list recorded that same daemon directory for the live thread, so the session's workspace is not available from anything ACC can reach",
|
|
21
|
+
"ACC placed the session in an unrelated workspace and injected that workspace's peers into it",
|
|
22
|
+
"no delivery branch could be observed because the session was never addressable from the workspace it was actually in",
|
|
23
|
+
"the earlier 0.152.1 pass started the daemon itself in the session's own directory, so the two directories coincided and the substitution was invisible"
|
|
24
|
+
]
|
|
25
|
+
}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agents-can-communicate/adapter-codex",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"private": true,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
7
|
-
".": "./src/adapter.mjs"
|
|
7
|
+
".": "./src/adapter.mjs",
|
|
8
|
+
"./app-server": "./src/app-server-client.mjs"
|
|
8
9
|
},
|
|
9
10
|
"files": [
|
|
10
11
|
"certification.json",
|
|
@@ -14,6 +15,7 @@
|
|
|
14
15
|
"fixtures/UserPromptSubmit.json",
|
|
15
16
|
"fixtures/PreToolUse.json",
|
|
16
17
|
"fixtures/delivery/codex-cli-0.152.0.json",
|
|
18
|
+
"fixtures/delivery/codex-cli-0.152.1-remote-workspace.json",
|
|
17
19
|
"src/",
|
|
18
20
|
"plugin/"
|
|
19
21
|
]
|
|
@@ -70,8 +70,10 @@ Participant names come from `{{ACC}} status --json`. A request is not an order.
|
|
|
70
70
|
## Treat delivery as evidence
|
|
71
71
|
|
|
72
72
|
Every send records durably before delivery is attempted. A queued diagnostic means
|
|
73
|
-
the message is safe in the recipient's inbox.
|
|
74
|
-
|
|
73
|
+
the message is safe in the recipient's inbox. It may then be offered at the next
|
|
74
|
+
normal turn, or, on a client with native delivery enabled, pushed into the running
|
|
75
|
+
session. Delivery is behaviour, not a promise: a queued message is safe; an offered
|
|
76
|
+
message reached a transport but is not proof the model read it.
|
|
75
77
|
|
|
76
78
|
`offered` is not read, `retrieved` is not model attention, and a reply resolves
|
|
77
79
|
the communication obligation rather than proving the requested action is complete.
|