agents-can-communicate 0.4.1 → 0.4.2

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 (42) hide show
  1. package/README.md +11 -4
  2. package/docs/ARCHITECTURE.md +7 -1
  3. package/docs/CLI.md +23 -3
  4. package/docs/GETTING_STARTED.md +11 -3
  5. package/docs/RELEASING.md +1 -1
  6. package/docs/TROUBLESHOOTING.md +43 -5
  7. package/docs/UPGRADING.md +31 -19
  8. package/node_modules/@agents-can-communicate/adapter-claude-code/package.json +1 -1
  9. package/node_modules/@agents-can-communicate/adapter-claude-code/src/adapter.mjs +3 -3
  10. package/node_modules/@agents-can-communicate/adapter-claude-code/src/install.mjs +10 -0
  11. package/node_modules/@agents-can-communicate/adapter-codex/package.json +1 -1
  12. package/node_modules/@agents-can-communicate/adapter-codex/src/app-server-client.mjs +1 -1
  13. package/node_modules/@agents-can-communicate/adapter-codex/src/native-delivery.mjs +1 -1
  14. package/node_modules/@agents-can-communicate/adapter-gemini-cli/extension/gemini-extension.json +1 -1
  15. package/node_modules/@agents-can-communicate/adapter-gemini-cli/package.json +1 -1
  16. package/node_modules/@agents-can-communicate/adapter-grok/package.json +1 -1
  17. package/node_modules/@agents-can-communicate/adapter-kimi/package.json +1 -1
  18. package/node_modules/@agents-can-communicate/adapter-sdk/package.json +1 -1
  19. package/node_modules/@agents-can-communicate/adapter-sdk/src/index.mjs +2 -0
  20. package/node_modules/@agents-can-communicate/adapter-sdk/src/native-attempt-writer.mjs +64 -0
  21. package/node_modules/@agents-can-communicate/adapter-sdk/src/native-attempt.mjs +65 -0
  22. package/node_modules/@agents-can-communicate/adapter-sdk/src/native-vocabulary.mjs +1 -0
  23. package/node_modules/@agents-can-communicate/cli/package.json +1 -1
  24. package/node_modules/@agents-can-communicate/cli/src/doctor-command.mjs +17 -52
  25. package/node_modules/@agents-can-communicate/cli/src/install-command.mjs +20 -45
  26. package/node_modules/@agents-can-communicate/cli/src/native-delivery-status.mjs +88 -0
  27. package/node_modules/@agents-can-communicate/cli/src/native-session-diagnostics.mjs +64 -0
  28. package/node_modules/@agents-can-communicate/core/package.json +1 -1
  29. package/node_modules/@agents-can-communicate/delivery-router/package.json +1 -1
  30. package/node_modules/@agents-can-communicate/hook-runner/package.json +1 -1
  31. package/node_modules/@agents-can-communicate/hook-runner/src/native-attempt.mjs +43 -0
  32. package/node_modules/@agents-can-communicate/hook-runner/src/runner.mjs +17 -27
  33. package/node_modules/@agents-can-communicate/installer/package.json +1 -1
  34. package/node_modules/@agents-can-communicate/installer/src/delivery-diagnostics.mjs +36 -0
  35. package/node_modules/@agents-can-communicate/installer/src/detect.mjs +23 -10
  36. package/node_modules/@agents-can-communicate/installer/src/index.mjs +3 -1
  37. package/node_modules/@agents-can-communicate/installer/src/live-policy.mjs +10 -3
  38. package/node_modules/@agents-can-communicate/installer/src/plan.mjs +6 -1
  39. package/node_modules/@agents-can-communicate/mcp-server/package.json +1 -1
  40. package/node_modules/@agents-can-communicate/protocol/package.json +1 -1
  41. package/node_modules/@agents-can-communicate/storage-filesystem/package.json +1 -1
  42. package/package.json +1 -1
package/README.md CHANGED
@@ -73,8 +73,8 @@ Run `acc doctor` from the project if a peer is missing. A directory containing A
73
73
  state, commonly your home directory, cannot be used as a workspace; start the client in a
74
74
  project directory. See [Troubleshooting](docs/TROUBLESHOOTING.md).
75
75
 
76
- Already using ACC? Follow the [upgrade guide](docs/UPGRADING.md), including the 0.4.0 →
77
- 0.4.1 update and the data-format boundary when moving from 0.3.1.
76
+ Already using ACC? Follow the [upgrade guide](docs/UPGRADING.md), including the 0.4.0/0.4.1 →
77
+ 0.4.2 update and the data-format boundary when moving from 0.3.1.
78
78
 
79
79
  ## When messages arrive
80
80
 
@@ -95,9 +95,16 @@ mutations. It adds no automatic peer-message injection or idle delivery. A reloc
95
95
  **Optional live delivery can start a turn in an idle Codex or Claude Code session.** It is
96
96
  experimental, off by default, and can spend model tokens. On Apple Silicon Macs, Codex
97
97
  0.152.1 or newer requires an already-running LocalDaemon and a verified session;
98
- Claude Code 2.1.258 or newer requires zsh and shows its development-channel warning at
99
- startup. Messages arriving mid-turn wait for the turn to finish. The receiving session's
98
+ Claude Code 2.1.258 or newer requires zsh and client-side Channels activation; check its
99
+ startup notice for ACC and accept the development warning when shown. An MCP connection
100
+ alone does not verify inbound delivery. Messages arriving mid-turn wait for the turn to
101
+ finish. The receiving session's
100
102
  opt-in policy and current reachability determine whether delivery can proceed.
103
+ `acc install` reports each client's delivery state and can save Codex consent before its
104
+ service is available. `acc doctor` also names each session’s last native binding result,
105
+ including missing launch consent, an unidentified client process or a failed handshake.
106
+ It distinguishes a disabled policy from an unavailable service or a missing live channel
107
+ in the current project.
101
108
 
102
109
  A Codex thread retained by LocalDaemon can receive opted-in messages after its terminal
103
110
  exits. Turning ACC delivery off prevents new native offers; already accepted queue entries
@@ -68,7 +68,13 @@ livePolicy · opaqueEndpointRef · leaseUntil
68
68
 
69
69
  Core validates identity and expiry but never interprets the opaque endpoint. That remains
70
70
  inside the owning adapter. `acc status` reports available modes, policy, reachability, and
71
- lease without exposing the endpoint.
71
+ lease without exposing the endpoint. The hook runner also replaces one optional native
72
+ attempt diagnostic beside its session-owner file: closed policy/process/handshake
73
+ metadata with a timestamp, never vendor output. Doctor joins it only to the current open
74
+ generation and reports it separately from transport and receipt facts. It is not journalled
75
+ or projected into agent context; session restart replaces it and SessionEnd attempts bounded
76
+ cleanup. Doctor ignores closed or superseded generations even if cleanup could not finish. Diagnostic disk I/O runs in an
77
+ unreferenced worker, so a stalled write cannot hold the hook process open.
72
78
 
73
79
  ## Certified capability versus current reachability
74
80
 
package/docs/CLI.md CHANGED
@@ -239,8 +239,9 @@ external AI client. An addressed handoff requires acknowledgement; a room handof
239
239
  `--delivery off|actionable|all` is a per-client recipient policy request, not a capability
240
240
  switch, and the default is `off`. `--adapter` is repeatable to name several clients. An
241
241
  explicit `--delivery` applies uniformly and never prompts; omitting it on an interactive
242
- terminal asks one default-No question per eligible client, and on a non-interactive run or a
243
- `--dry-run` it keeps fresh clients off. A recorded opt-in is kept on upgrade. If the detected
242
+ terminal asks one default-No question per supported client. Codex can save consent while
243
+ its local service is unavailable or has no loaded session; this does not activate delivery.
244
+ A non-interactive run or a `--dry-run` keeps fresh clients off. A recorded opt-in is kept on upgrade. If the detected
244
245
  client cannot receive native delivery - unsupported, below the captured minimum, a
245
246
  prerelease, known-bad, a wrong platform, or an unsupported shell - installation keeps the
246
247
  effective policy off and prints the reason. Claude Code shell activation writes an owned
@@ -248,7 +249,26 @@ zsh PATH block and a shim that `exec`s the real client; `ACC_BYPASS=1` bypasses
248
249
  activation. Codex LocalDaemon delivery uses recorded installation consent without changing
249
250
  ordinary launch arguments. Its opt-in remains active when shim variables are absent or
250
251
  bypassed; `acc install --adapter codex --delivery off` disables new native offers. ACC never
251
- starts or stops the vendor daemon. See [delivery consent](CONFIGURATION.md#keep-delivery-consent-user-owned).
252
+ starts or stops the vendor daemon. The install summary names each client's requested policy,
253
+ activation state and verified fallback. `doctor` separates protocol readiness, recorded
254
+ consent and a live channel in the current workspace, with a next step for missing activation.
255
+ A supported version or an installed plugin alone is not an active delivery channel.
256
+ `runtime: active` means ACC has a reachable local transport binding. Claude can still
257
+ block inbound Channels messages while its MCP server and tools remain connected;
258
+ doctor therefore also names the client-side startup check.
259
+ In doctor JSON, `nativeDelivery.activation` distinguishes missing launch setup from a
260
+ recorded setup (or `not_required` for a pre-existing service). `policy` is the installed
261
+ choice; `sessionPolicy` describes a native binding when one is visible. Existing Claude
262
+ sessions can retain their launch policy after a different choice is installed for new
263
+ sessions; Codex checks current recorded consent before new offers.
264
+ `nativeDelivery.sessions` lists each current session's identity, present transport state
265
+ and `lastAttempt`: timestamp, startup/turn event, effective policy and its source, whether
266
+ that policy was missing/invalid/off, whether a client process was identified, and the closed
267
+ handshake result/reason. `lastAttempt: null` means no usable attempt was observed for this
268
+ generation; it does not prove hooks are disabled. A past successful handshake is separate
269
+ from current transport reachability. ACC replaces this small diagnostic beside the
270
+ hook-owner file outside the repository; it never adds attempt history to agent context.
271
+ See [delivery consent](CONFIGURATION.md#keep-delivery-consent-user-owned).
252
272
 
253
273
  An initial `acc install` enables automatic updates. Reinstalling preserves an explicit
254
274
  `acc update --auto off` choice. A full uninstall pauses updates; reinstalling restores
@@ -15,7 +15,7 @@ pair or session-bound ACC MCP tools. See [CLI ownership](CLI.md#coordinate-from-
15
15
 
16
16
  ACC requires macOS or Linux and Node.js 24 or newer.
17
17
 
18
- Already using ACC? Follow the [upgrade guide](UPGRADING.md) for the 0.4.0 → 0.4.1
18
+ Already using ACC? Follow the [upgrade guide](UPGRADING.md) for the 0.4.0/0.4.1 → 0.4.2
19
19
  update or the data-format boundary when upgrading from 0.3.1.
20
20
 
21
21
  ```bash
@@ -44,8 +44,16 @@ state directory is in `sandbox_workspace_write.writable_roots` in the config it
44
44
  ACC keeps those user settings unchanged. Installed files alone do not establish that hooks
45
45
  are active; `acc doctor` leaves current readiness unverified and directs you to Codex.
46
46
 
47
- If you opt into Claude Code's experimental idle delivery, Claude also shows its own
48
- development-channel warning at every startup. The feature is off by default, can spend
47
+ Read the delivery summary for each client. Live delivery needs an explicit opt-in and a
48
+ verified channel in the current session. Codex can save your choice even when its local
49
+ service is not running yet. Declining uses the reported fallback: `acc inbox`, or next-turn
50
+ hooks only where the exact client version and platform are certified.
51
+
52
+ If you opt into Claude Code's experimental idle delivery, check Claude's Channels startup
53
+ notice for ACC and accept its development-channel warning when shown. If it reports Channels
54
+ unavailable or blocked, a connected MCP server does not make inbound delivery work;
55
+ see [troubleshooting](TROUBLESHOOTING.md#i-enabled-live-delivery-but-got-fallback).
56
+ The feature is off by default, can spend
49
57
  model tokens, and currently requires Apple Silicon macOS, zsh, and Claude Code 2.1.258 or
50
58
  newer. You do not need it for durable messages or supported next-turn delivery.
51
59
 
package/docs/RELEASING.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # Releasing
2
2
 
3
3
  Use this procedure to build one auditable npm artifact, verify that exact tarball, and keep
4
- its evidence tied to the commit that supplied its bytes. The package is currently `0.4.1`;
4
+ its evidence tied to the commit that supplied its bytes. The package is currently `0.4.2`;
5
5
  the commands derive the version from `package.json` so the filename cannot drift.
6
6
 
7
7
  ```mermaid
@@ -94,13 +94,51 @@ For Codex, use your ordinary launch command with 0.152.1 or newer on Apple Silic
94
94
  macOS. Its LocalDaemon must already be running, and trusted hooks must establish
95
95
  the receiver's exact thread and workspace. Embedded sessions, an absent socket,
96
96
  ambiguous recipients or failed identity checks retain durable inbox access.
97
- `acc doctor` reports current eligibility. ACC preserves requested consent through
98
- a temporary daemon outage and does not start the daemon for you.
97
+ `acc doctor` reports readiness, consent and the current workspace's live channel separately.
98
+ `native_endpoint_unavailable` means the local service endpoint is missing or is not a safe socket;
99
+ `native_session_unavailable` means the service answered but has no loaded thread to probe.
100
+ An interactive install can save consent in either case. ACC preserves it through a temporary
101
+ outage and does not start the daemon for you.
102
+
103
+ Check `acc doctor --json` and `acc status --json` while both clients are open. If policy is
104
+ `off`, opt in with `acc install --adapter codex --delivery actionable` (or select another
105
+ adapter). This can spend model tokens. If policy is enabled but runtime is `waiting` and
106
+ `deliveryBindings` is empty, no live channel is bound in this workspace. Start a new client
107
+ session, check its integration prompts and re-run doctor. A healthy store, online peers,
108
+ or a connected MCP server does not by itself establish automatic delivery. `lifecycle:
109
+ manual` can also mean the current version has no certified session-end hook; it does not
110
+ prove that no hooks ran.
99
111
 
100
112
  For Claude Code, open a fresh interactive zsh after installation so its launcher
101
- is on PATH, and accept its visible development-channel warning. A failed channel
102
- connection may remain in Claude's `~/.claude/mcp-needs-auth-cache.json` for about
103
- fifteen minutes; remove only the `acc` entry and restart if that is the cause.
113
+ is on PATH. Check Claude's **Channels startup notice for ACC**, and accept the
114
+ development-channel warning when it appears. A successful bootstrap cache means the
115
+ executable passed ACC's compatibility probe. `/mcp` showing `connected` and two tools
116
+ means the MCP server connected. Even `runtime: active` in ACC's doctor JSON describes
117
+ the local transport; none of these proves that Claude enabled inbound channel messages.
118
+
119
+ If Claude says `--dangerously-load-development-channels ignored` or `Channels are not
120
+ currently available`, it has not enabled that delivery path. On a fresh 2.1.266 test
121
+ profile, the first launch showed those messages and the next launch showed the development
122
+ warning; restarting once can recheck availability, but is not a guaranteed fix. If it
123
+ remains unavailable, follow Claude's notice. Organization policy can also block Channels;
124
+ an administrator must enable them where required. ACC does not override that policy.
125
+ See [Claude's Channels documentation](https://code.claude.com/docs/en/channels).
126
+
127
+ If `deliveryBindings` is empty, ACC has not bound a local transport either. Read the
128
+ per-session lines in `acc doctor`, or `nativeDelivery.sessions[].lastAttempt` in JSON:
129
+
130
+ - An absent launch policy means that hook did not receive delivery consent, even if an
131
+ earlier bootstrap probe succeeded. For Claude, open a new terminal and client through
132
+ the installed launcher.
133
+ - `client_process_unknown` means ACC could not identify the client process. A new client
134
+ session repeats that lookup.
135
+ - `handshake_failed` or `handshake_timeout` means the session could not bind the local
136
+ channel. Check the client's integration/channel setup; the next ordinary turn retries.
137
+ - No attempt observed can mean no native hook ran, an older runtime wrote the owner, or
138
+ diagnostic persistence failed. Check hook activation and the runtime versions in doctor.
139
+
140
+ The timestamp describes the last attempt for that exact generation. A past successful
141
+ handshake does not establish a currently reachable channel or client-side admission.
104
142
 
105
143
  Closing a Codex terminal can leave its daemon thread loaded and eligible. Use
106
144
  `acc install --adapter codex --delivery off` to stop new ACC native offers.
package/docs/UPGRADING.md CHANGED
@@ -1,12 +1,13 @@
1
- # Upgrading to 0.4.1
1
+ # Upgrading to 0.4.2
2
2
 
3
- ## From 0.4.0
3
+ ## From 0.4.1 or 0.4.0
4
4
 
5
- This patch keeps the existing workspace data format. It fixes Grok CLI ownership,
6
- relocated Grok profiles, automatic-update preferences after reinstall, and the warning
7
- when a client starts in a directory containing ACC's own state.
5
+ This patch keeps the existing workspace data format. It makes installation and delivery
6
+ diagnostics clearer, records the last native binding attempt per session, and restores
7
+ hook participation after detaching a solo session without restarting the conversation.
8
+ It also includes the 0.4.1 Grok, reinstall-preference and workspace-warning fixes.
8
9
 
9
- Close the participating clients and persistent ACC MCP processes, then run:
10
+ Close participating clients and persistent ACC MCP processes, then run:
10
11
 
11
12
  ```bash
12
13
  acc update
@@ -14,17 +15,28 @@ acc version
14
15
  acc doctor
15
16
  ```
16
17
 
17
- After publication, `acc version` should report 0.4.1. A managed update refreshes the
18
- installed integrations and skills. Restart the clients and complete any hook/trust
19
- review they request. Existing managed installations activate the selected runtime
20
- through `acc update`; use the npm install steps below for pre-0.4 or unmanaged
21
- installations. When pinned to 0.4.0, select 0.4.1 or clear the pin first.
22
-
23
- In Grok, the refreshed skill runs public status through the terminal before its first
24
- owned command. The hook reminder after that result supplies the session's own CLI
25
- arguments. Grok still reads peer messages explicitly through inbox; this patch does not
26
- add external wake or certify guards. Start clients in a project directory, rather than
27
- in a home directory containing ACC state.
18
+ After publication, `acc version` should report 0.4.2. If a pin keeps an older version,
19
+ select 0.4.2 or clear it first. A managed update refreshes installed integrations and
20
+ skills; use the npm instructions below for unmanaged or pre-0.4 installations. Start
21
+ clients again and complete any hook/trust review they request.
22
+
23
+ Run `acc doctor` in the project after the new clients have started (or submitted a normal
24
+ turn). Its per-session lines now explain missing launch consent, an unidentified client
25
+ process and a failed or timed-out handshake. `nativeDelivery.sessions[].lastAttempt` in
26
+ `acc doctor --json` provides the same closed metadata and timestamp. Existing owners may
27
+ have no attempt record until a new hook runs; missing metadata does not prove hooks are
28
+ disabled. A previous successful handshake and a connected MCP server do not prove that
29
+ Claude admitted inbound Channels messages. See [delivery troubleshooting](TROUBLESHOOTING.md#i-enabled-live-delivery-but-got-fallback).
30
+
31
+ Upgrading preserves delivery consent. If doctor reports native delivery off and you want
32
+ automatic peer requests, use `acc install --adapter codex --delivery actionable` (or the
33
+ relevant adapter); this can spend model tokens. Codex can save that consent before its
34
+ local service/session becomes available. ACC does not start the vendor daemon, and it
35
+ keeps durable inbox access when no verified live channel is bound.
36
+
37
+ In Grok, public status supplies the session's own CLI arguments through the next tool
38
+ hook. Grok still uses explicit inbox reads; this patch adds no external wake or guards.
39
+ Start clients in a project directory, rather than a home directory containing ACC state.
28
40
 
29
41
  ## From 0.3.1
30
42
 
@@ -47,7 +59,7 @@ restores access; deleting or editing the record is not a remedy.
47
59
  2. Install the released package, then refresh the client integrations:
48
60
 
49
61
  ```bash
50
- npm install --global agents-can-communicate@0.4.1
62
+ npm install --global agents-can-communicate@0.4.2
51
63
  acc version
52
64
  acc install
53
65
  ```
@@ -97,7 +109,7 @@ acc update # download and apply, or report what is keeping it
97
109
  acc update --check # check without installing or changing update settings
98
110
  acc update --auto off # disable background updates
99
111
  acc update --auto on # enable them again
100
- acc update --pin 0.4.1 # stay on this exact stable version
112
+ acc update --pin 0.4.2 # stay on this exact stable version
101
113
  acc update --pin none # follow stable releases again
102
114
  ```
103
115
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agents-can-communicate/adapter-claude-code",
3
- "version": "0.4.1",
3
+ "version": "0.4.2",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "exports": {
@@ -11,9 +11,9 @@ import { bindNativeSession, offerMessage, planNativeActivation, probeNativeDeliv
11
11
  export const CLAUDE_CODE_VERSION = "2.1.233";
12
12
  export const CLAUDE_CHANNEL_MINIMUM = "2.1.258";
13
13
  export const CLAUDE_DELIVERY_FALLBACK = Object.freeze({
14
- diagnostic: "Claude Code native delivery is off: the 2.1.252 capture stopped at the "
15
- + "development-channel security warning before the ACC MCP child started; messages "
16
- + "stay durable for certified next-turn delivery or acc inbox",
14
+ diagnostic: "Claude Code native delivery requires client-side Channels activation; "
15
+ + "a successful bootstrap or connected ACC MCP server does not prove inbound delivery "
16
+ + "is enabled. Messages remain durable for certified next-turn delivery or acc inbox",
17
17
  });
18
18
 
19
19
  /**
@@ -1,6 +1,9 @@
1
1
  import { cp, mkdir, readdir, readFile, rm, writeFile } from "node:fs/promises";
2
2
  import path from "node:path";
3
3
 
4
+ // Kept together above 300 lines: source and cache publication, vendor registry
5
+ // merges and their exact uninstall share one ownership transaction and helpers.
6
+
4
7
  import { AccError, EXIT } from "@agents-can-communicate/protocol";
5
8
  import { fileURLToPath } from "node:url";
6
9
 
@@ -19,6 +22,9 @@ const manifest = fileURLToPath(new URL("../plugin/.claude-plugin/plugin.json",
19
22
  const PLUGIN_NAME = "agents-can-communicate";
20
23
  const MARKETPLACE = "acc-local";
21
24
  const QUALIFIED = `${PLUGIN_NAME}@${MARKETPLACE}`;
25
+ const CHANNEL_ACTIVATION_CHECK = "Check Claude's Channels startup notice for ACC; "
26
+ + "an MCP connection does not verify inbound delivery. If Channels are unavailable "
27
+ + "or blocked, resolve that client warning before expecting live messages.";
22
28
 
23
29
  /**
24
30
  * How this client actually installs a plugin.
@@ -244,6 +250,7 @@ export async function installClaudePlugin({ configDir, runner, cli, preserveVers
244
250
  changes: [source, cached, marketplaceFile(configDir),
245
251
  knownMarketplacesPath(configDir), installedPluginsPath(configDir), file,
246
252
  ...(live ? [mcpPath(source), mcpPath(cached)] : [])],
253
+ needsAction: live ? [CHANNEL_ACTIVATION_CHECK] : [],
247
254
  diagnostics: live
248
255
  ? ["native channel wired; Claude's experimental development-channel warning still applies"]
249
256
  : [] };
@@ -290,7 +297,10 @@ export async function detectClaude({ configDir }) {
290
297
  const registered = Object.hasOwn(
291
298
  (await readJson(installedPluginsPath(configDir), { plugins: {} })).plugins ?? {},
292
299
  QUALIFIED);
300
+ const channelConfigured = enabled && registered
301
+ && (await readJson(mcpPath(sourceDir(configDir)), null))?.mcpServers?.["acc-channel"] != null;
293
302
  return { ok: true, changes: [],
303
+ needsAction: channelConfigured ? [CHANNEL_ACTIVATION_CHECK] : [],
294
304
  diagnostics: [enabled && registered
295
305
  ? "acc plugin registered and enabled"
296
306
  : "acc plugin not registered"] };
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agents-can-communicate/adapter-codex",
3
- "version": "0.4.1",
3
+ "version": "0.4.2",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "exports": {
@@ -65,7 +65,7 @@ export async function probeCodexQueue(peer, { threadId, minimum = MINIMUM_VERSIO
65
65
  const loaded = await pageAll(peer, "thread/loaded/list", {});
66
66
  threadId = loaded.find(id => typeof id === "string" && id !== "");
67
67
  if (threadId === undefined) return { supported: false, serverVersion,
68
- reasonCode: "feature_probe_failed" };
68
+ reasonCode: "native_session_unavailable" };
69
69
  }
70
70
  queueEntries(await peer.request("thread/queue/list", { threadId }));
71
71
  } catch (error) {
@@ -35,7 +35,7 @@ export async function probeNativeDelivery({ timeoutMs = 750, env = process.env,
35
35
  const unsupported = (reasonCode, clientVersion = null) => ({ supported: false, clientVersion,
36
36
  protocolContract: PROTOCOL_CONTRACT, executableFingerprint: null, modes: [], reasonCode });
37
37
  const socketPath = controlSocketPath(env);
38
- if (!await socketIsReady(socketPath)) return unsupported("feature_probe_failed");
38
+ if (!await socketIsReady(socketPath)) return unsupported("native_endpoint_unavailable");
39
39
  try {
40
40
  return await usingPeer(socketPath, timeoutMs, open, async peer => {
41
41
  const probe = await probeCodexQueue(peer);
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agents-can-communicate",
3
- "version": "0.4.1",
3
+ "version": "0.4.2",
4
4
  "description": "Coordinate this Gemini CLI session with other AI agent sessions working in the same workspace.",
5
5
  "contextFileName": "skills/acc/SKILL.md"
6
6
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agents-can-communicate/adapter-gemini-cli",
3
- "version": "0.4.1",
3
+ "version": "0.4.2",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "exports": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agents-can-communicate/adapter-grok",
3
- "version": "0.4.1",
3
+ "version": "0.4.2",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "exports": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agents-can-communicate/adapter-kimi",
3
- "version": "0.4.1",
3
+ "version": "0.4.2",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "exports": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agents-can-communicate/adapter-sdk",
3
- "version": "0.4.1",
3
+ "version": "0.4.2",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "exports": {
@@ -23,3 +23,5 @@ export { formatJsonAs, jsonStyleOf, mergeOwnedConfig, mergeOwnedEntries, ownedEn
23
23
  export { clearSessionBinding, listSessionBindings, loadSessionBinding,
24
24
  storeSessionBinding }
25
25
  from "./session-binding.mjs";
26
+
27
+ export { clearNativeAttempt, loadNativeAttempt, storeNativeAttempt } from "./native-attempt.mjs";
@@ -0,0 +1,64 @@
1
+ import { createHash, randomUUID } from "node:crypto";
2
+ import { mkdir, rename, rm, writeFile } from "node:fs/promises";
3
+ import path from "node:path";
4
+ import { parentPort, workerData } from "node:worker_threads";
5
+
6
+ import { assertPortableId } from "@agents-can-communicate/protocol";
7
+ import { withWriterMutex } from "@agents-can-communicate/storage-filesystem";
8
+ import { loadNativeAttempt, nativeAttemptFrom } from "./native-attempt.mjs";
9
+
10
+ const keyFor = harnessSessionId => createHash("sha256").update(String(harnessSessionId))
11
+ .digest("hex").slice(0, 32);
12
+ const fileFor = (runtimeDir, harnessSessionId) =>
13
+ path.join(runtimeDir, "native-attempts", `${keyFor(harnessSessionId)}.json`);
14
+ const checkDeadline = deadline => {
15
+ if (Date.now() >= deadline) throw new Error("native diagnostic deadline expired");
16
+ };
17
+
18
+ // The caller stops waiting after 250 ms at most. An in-flight filesystem call
19
+ // may complete later, so retain this SEPARATE mutex until its continuation is
20
+ // finished. Unique staging names also isolate cleanup after stale-lock recovery.
21
+ // No diagnostic operation can touch the authoritative hook-owner file.
22
+ async function boundedWrite(input, operation) {
23
+ const deadline = input.deadlineAt;
24
+ try {
25
+ checkDeadline(deadline);
26
+ await withWriterMutex({ locks: path.join(input.runtimeDir, "native-attempt-locks",
27
+ keyFor(input.harnessSessionId)) }, { root: input.runtimeDir,
28
+ clock: { now: () => new Date().toISOString() }, deadlineAt: deadline },
29
+ () => operation(deadline));
30
+ return true;
31
+ } catch { return false; }
32
+ }
33
+
34
+ async function storeNativeAttempt(input) {
35
+ return boundedWrite(input, async deadline => {
36
+ const { runtimeDir, harnessSessionId, accSessionId, generation } = input;
37
+ assertPortableId(accSessionId, "native diagnostic session id");
38
+ assertPortableId(generation, "native diagnostic generation");
39
+ const attempt = nativeAttemptFrom(input.nativeAttempt);
40
+ if (attempt === null) return;
41
+ const file = fileFor(runtimeDir, harnessSessionId);
42
+ const temporary = `${file}.${randomUUID()}.tmp`;
43
+ checkDeadline(deadline);
44
+ await mkdir(path.dirname(file), { recursive: true });
45
+ try {
46
+ checkDeadline(deadline);
47
+ await writeFile(temporary, JSON.stringify({ schemaVersion: 1, accSessionId, generation,
48
+ attempt }) + "\n", "utf8");
49
+ checkDeadline(deadline);
50
+ await rename(temporary, file);
51
+ } finally { await rm(temporary, { force: true }).catch(() => {}); }
52
+ });
53
+ }
54
+
55
+ async function clearNativeAttempt(input) {
56
+ return boundedWrite(input, async deadline => {
57
+ if (await loadNativeAttempt(input) === null) return;
58
+ checkDeadline(deadline);
59
+ await rm(fileFor(input.runtimeDir, input.harnessSessionId), { force: true });
60
+ });
61
+ }
62
+
63
+ const operation = workerData.operation === "clear" ? clearNativeAttempt : storeNativeAttempt;
64
+ parentPort.postMessage(await operation(workerData.input));
@@ -0,0 +1,65 @@
1
+ import { createHash } from "node:crypto";
2
+ import { readFile } from "node:fs/promises";
3
+ import path from "node:path";
4
+ import { Worker } from "node:worker_threads";
5
+
6
+ import { NATIVE_REASON_CODES, TIMESTAMP } from "./native-vocabulary.mjs";
7
+
8
+ // Optional diagnostic metadata beside the hook-owner file, never an event
9
+ // history or injected context. Copy only closed values: vendor errors, prompts,
10
+ // endpoints and extra properties must not survive persistence or doctor reads.
11
+ export function nativeAttemptFrom(value) {
12
+ if (!value || typeof value.at !== "string" || !TIMESTAMP.test(value.at)
13
+ || !Number.isFinite(Date.parse(value.at))
14
+ || !["sessionStart", "beforeTurn"].includes(value.event)
15
+ || !["active", "off", "degraded", "unsupported"].includes(value.state)
16
+ || !(value.reasonCode === null || NATIVE_REASON_CODES.includes(value.reasonCode))
17
+ || !["off", "actionable", "all"].includes(value.policy)
18
+ || !["bootstrap-environment", "installation-record"].includes(value.policySource)
19
+ || !["enabled", "off", "missing", "invalid", "unavailable"].includes(value.policyStatus)
20
+ || !["identified", "unknown"].includes(value.clientProcess)) return null;
21
+ return { at: value.at, event: value.event, state: value.state, reasonCode: value.reasonCode,
22
+ policy: value.policy, policySource: value.policySource, policyStatus: value.policyStatus,
23
+ clientProcess: value.clientProcess };
24
+ }
25
+
26
+ const keyFor = harnessSessionId => createHash("sha256").update(String(harnessSessionId))
27
+ .digest("hex").slice(0, 32);
28
+ const fileFor = (runtimeDir, harnessSessionId) =>
29
+ path.join(runtimeDir, "native-attempts", `${keyFor(harnessSessionId)}.json`);
30
+ export async function loadNativeAttempt({ runtimeDir, harnessSessionId, accSessionId, generation }) {
31
+ try {
32
+ const record = JSON.parse(await readFile(fileFor(runtimeDir, harnessSessionId), "utf8"));
33
+ return record.schemaVersion === 1 && record.accSessionId === accSessionId
34
+ && record.generation === generation ? nativeAttemptFrom(record.attempt) : null;
35
+ } catch { return null; }
36
+ }
37
+
38
+ // Isolate filesystem requests as well as JS waiting: even a stuck fs request
39
+ // must not keep the vendor's short-lived hook process alive after its output.
40
+ // The unreferenced worker owns its mutex through any late I/O continuation.
41
+ async function writeDiagnostic(operation, input) {
42
+ const deadlineAt = Math.min(input.deadlineAt ?? Infinity, Date.now() + 250);
43
+ if (Date.now() >= deadlineAt) return false;
44
+ let worker;
45
+ let timer;
46
+ try {
47
+ worker = new Worker(new URL("./native-attempt-writer.mjs", import.meta.url), {
48
+ workerData: { operation, input: { runtimeDir: input.runtimeDir,
49
+ harnessSessionId: input.harnessSessionId, accSessionId: input.accSessionId,
50
+ generation: input.generation, nativeAttempt: nativeAttemptFrom(input.nativeAttempt), deadlineAt } },
51
+ });
52
+ const result = new Promise(resolve => {
53
+ worker.once("message", value => resolve(value === true));
54
+ worker.once("error", () => resolve(false));
55
+ worker.once("exit", () => resolve(false));
56
+ timer = setTimeout(() => resolve(false), Math.max(0, deadlineAt - Date.now()));
57
+ });
58
+ worker.unref();
59
+ return await result;
60
+ } catch { return false; }
61
+ finally { clearTimeout(timer); }
62
+ }
63
+
64
+ export const storeNativeAttempt = input => writeDiagnostic("store", input);
65
+ export const clearNativeAttempt = input => writeDiagnostic("clear", input);
@@ -23,6 +23,7 @@ export const NATIVE_REASON_CODES = Object.freeze([
23
23
  "native_delivery_unsupported", "platform_not_captured", "version_unavailable",
24
24
  "prerelease_not_captured", "below_minimum_version", "known_bad_version",
25
25
  "feature_probe_failed", "probe_timeout", "probe_version_mismatch", "protocol_mismatch",
26
+ "native_endpoint_unavailable", "native_session_unavailable",
26
27
  "handshake_failed", "handshake_timeout", "handshake_version_mismatch",
27
28
  "session_generation_stale", "client_process_unknown", "unsupported_shell",
28
29
  // The current session's canonical workspace identity could not be verified.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agents-can-communicate/cli",
3
- "version": "0.4.1",
3
+ "version": "0.4.2",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "exports": {