agents-can-communicate 0.5.3 → 0.5.5

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 (46) hide show
  1. package/README.md +8 -5
  2. package/docs/ARCHITECTURE.md +6 -1
  3. package/docs/CLI.md +7 -4
  4. package/docs/CONFIGURATION.md +7 -3
  5. package/docs/GETTING_STARTED.md +12 -3
  6. package/docs/TROUBLESHOOTING.md +25 -9
  7. package/docs/UPGRADING.md +39 -10
  8. package/node_modules/@agents-can-communicate/adapter-claude-code/package.json +1 -1
  9. package/node_modules/@agents-can-communicate/adapter-codex/package.json +1 -1
  10. package/node_modules/@agents-can-communicate/adapter-codex/src/adapter.mjs +5 -2
  11. package/node_modules/@agents-can-communicate/adapter-codex/src/app-server-client.mjs +2 -1
  12. package/node_modules/@agents-can-communicate/adapter-codex/src/install.mjs +1 -1
  13. package/node_modules/@agents-can-communicate/adapter-codex/src/live-permissions.mjs +37 -12
  14. package/node_modules/@agents-can-communicate/adapter-codex/src/maintenance-host.mjs +1 -1
  15. package/node_modules/@agents-can-communicate/adapter-codex/src/native-delivery.mjs +2 -1
  16. package/node_modules/@agents-can-communicate/adapter-codex/src/service-setup.mjs +135 -0
  17. package/node_modules/@agents-can-communicate/adapter-gemini-cli/extension/gemini-extension.json +1 -1
  18. package/node_modules/@agents-can-communicate/adapter-gemini-cli/package.json +1 -1
  19. package/node_modules/@agents-can-communicate/adapter-grok/package.json +1 -1
  20. package/node_modules/@agents-can-communicate/adapter-kimi/package.json +1 -1
  21. package/node_modules/@agents-can-communicate/adapter-sdk/package.json +1 -1
  22. package/node_modules/@agents-can-communicate/cli/package.json +1 -1
  23. package/node_modules/@agents-can-communicate/cli/src/doctor-command.mjs +10 -5
  24. package/node_modules/@agents-can-communicate/cli/src/help.mjs +3 -0
  25. package/node_modules/@agents-can-communicate/cli/src/install-command.mjs +15 -71
  26. package/node_modules/@agents-can-communicate/cli/src/install-delivery-consent.mjs +152 -0
  27. package/node_modules/@agents-can-communicate/cli/src/managed-runtime/generation-files.mjs +25 -0
  28. package/node_modules/@agents-can-communicate/cli/src/managed-runtime/generation.mjs +1 -22
  29. package/node_modules/@agents-can-communicate/cli/src/managed-runtime/launchers.mjs +1 -1
  30. package/node_modules/@agents-can-communicate/cli/src/managed-runtime/leases.mjs +2 -2
  31. package/node_modules/@agents-can-communicate/cli/src/managed-runtime/refresh.mjs +6 -1
  32. package/node_modules/@agents-can-communicate/cli/src/managed-runtime/state.mjs +23 -1
  33. package/node_modules/@agents-can-communicate/cli/src/native-delivery-status.mjs +22 -5
  34. package/node_modules/@agents-can-communicate/core/package.json +1 -1
  35. package/node_modules/@agents-can-communicate/delivery-router/package.json +1 -1
  36. package/node_modules/@agents-can-communicate/hook-runner/package.json +1 -1
  37. package/node_modules/@agents-can-communicate/installer/package.json +1 -1
  38. package/node_modules/@agents-can-communicate/installer/src/apply.mjs +6 -2
  39. package/node_modules/@agents-can-communicate/installer/src/detect.mjs +11 -0
  40. package/node_modules/@agents-can-communicate/installer/src/ownership.mjs +6 -1
  41. package/node_modules/@agents-can-communicate/installer/src/plan.mjs +10 -2
  42. package/node_modules/@agents-can-communicate/installer/src/service-setup.mjs +30 -0
  43. package/node_modules/@agents-can-communicate/mcp-server/package.json +1 -1
  44. package/node_modules/@agents-can-communicate/protocol/package.json +1 -1
  45. package/node_modules/@agents-can-communicate/storage-filesystem/package.json +1 -1
  46. package/package.json +1 -1
package/README.md CHANGED
@@ -67,9 +67,11 @@ review the current ACC definitions in `/hooks`; changed hooks may need fresh tru
67
67
 
68
68
  For Codex live delivery on Apple Silicon macOS, installation also configures outgoing
69
69
  local socket access on Codex 0.153.4 or newer when using default workspace permissions.
70
- Custom policies are preserved. If its local service is missing, doctor gives the command
71
- `codex app-server daemon start`; then open a new Codex session. Doctor reports receiving
72
- channel state and outgoing permission configuration separately.
70
+ Custom policies are preserved. One default-No choice covers all selected clients that need
71
+ live-delivery consent. On Codex 0.154.0 or newer, an accepted choice can start a missing
72
+ service when the managed standalone installation is present. The client still owns session
73
+ startup, hook trust, and Claude Channels approval. Doctor reports service infrastructure,
74
+ session binding, and outgoing permissions separately.
73
75
 
74
76
  Open two sessions and give them ordinary tasks, as above. Look for an agent discovering a
75
77
  peer, checking who is changing a file, asking about a shared dependency, or replying to a
@@ -100,14 +102,15 @@ mutations. It adds no automatic peer-message injection or idle delivery. A reloc
100
102
 
101
103
  **Optional live delivery can start a turn in an idle Codex or Claude Code session.** It is
102
104
  experimental, off by default, and can spend model tokens. On Apple Silicon Macs, Codex
103
- 0.152.1 or newer requires an already-running LocalDaemon and a verified session;
105
+ 0.152.1 or newer requires LocalDaemon infrastructure and a verified session;
104
106
  Claude Code 2.1.258 or newer requires zsh and client-side Channels activation; check its
105
107
  startup notice for ACC and accept the development warning when shown. An MCP connection
106
108
  alone does not verify inbound delivery. Messages arriving mid-turn wait for the turn to
107
109
  finish. The receiving session's
108
110
  opt-in policy and current reachability determine whether delivery can proceed.
109
111
  `acc install` reports each client's delivery state and can save Codex consent before its
110
- service is available. `acc doctor` also names each session’s last native binding result,
112
+ service is available. Use `--delivery actionable|all` for explicit automation and
113
+ `--delivery off` to disable incoming automatic requests. `acc doctor` also names each session’s last native binding result,
111
114
  including missing launch consent, an unidentified client process or a failed handshake.
112
115
  It distinguishes a disabled policy from an unavailable service or a missing live channel
113
116
  in the current project.
@@ -174,7 +174,12 @@ The CLI owns installation generations under `<dataHome>/acc/runtime`, outside wo
174
174
  All five launch paths select a generation and publish an actual-process lease under one
175
175
  admission mutex before loading workspace-capable code. Immutable launcher modules and
176
176
  runtime directories preserve in-progress imports. Leases survive `main()` returning and
177
- are removed only after confirmed process death. A session is also pinned to the generation
177
+ are removed only after confirmed process death. Older updaters can omit the store contract
178
+ when publishing a newer generation. The reader recovers missing contracts only from the
179
+ referenced generation, after checking its package identity and content hash, including file
180
+ modes. It preserves explicit declarations and does not rewrite live leases. Missing or
181
+ changed generation files and unknown native bindings remain unverified holds.
182
+ A session is also pinned to the generation
178
183
  it started with: its hooks delegate to that generation while the pin declares the same
179
184
  store contract this one does, and run the active generation otherwise. A generation
180
185
  directory is reclaimed once no control pointer, live lease, live pin or staging hold names
package/docs/CLI.md CHANGED
@@ -250,7 +250,7 @@ before leaving, request the concrete continuation and obtain a substantive reply
250
250
  `--delivery off|actionable|all` is a per-client recipient policy request, not a capability
251
251
  switch, and the default is `off`. `--adapter` is repeatable to name several clients. An
252
252
  explicit `--delivery` applies uniformly and never prompts; omitting it on an interactive
253
- terminal asks one default-No question per supported client. Codex can save consent while
253
+ terminal asks one default-No question for all selected clients that need a decision. Codex can save consent while
254
254
  its local service is unavailable or has no loaded session; this does not activate delivery.
255
255
  A non-interactive run or a `--dry-run` keeps fresh clients off. A recorded opt-in is kept on upgrade. If the detected
256
256
  client cannot receive native delivery - unsupported, below the captured minimum, a
@@ -259,8 +259,10 @@ effective policy off and prints the reason. Claude Code shell activation writes
259
259
  zsh PATH block and a shim that `exec`s the real client; `ACC_BYPASS=1` bypasses that
260
260
  activation. Codex LocalDaemon delivery uses recorded installation consent without changing
261
261
  ordinary launch arguments. Its opt-in remains active when shim variables are absent or
262
- bypassed; `acc install --adapter codex --delivery off` disables new native offers. Installation
263
- and message delivery do not start or stop the vendor daemon. The install summary names each client's requested policy,
262
+ bypassed; `acc install --adapter codex --delivery off` disables new native offers. On a
263
+ supported explicit install, complete consent can prepare a missing Codex service from an
264
+ existing managed standalone installation. Dry runs, delivery off, automatic refresh, and
265
+ message delivery do not start it. Uninstall does not stop the shared vendor daemon. The install summary names each client's requested policy,
264
266
  activation state and verified fallback. `doctor` separates protocol readiness, recorded
265
267
  consent and a live channel in the current workspace, with a next step for missing activation.
266
268
  A supported version or an installed plugin alone is not an active delivery channel.
@@ -269,7 +271,8 @@ block inbound Channels messages while its MCP server and tools remain connected;
269
271
  doctor therefore also names the client-side startup check.
270
272
  In doctor JSON, `nativeDelivery.activation` distinguishes missing launch setup from a
271
273
  recorded setup (or `not_required` for a pre-existing service). `policy` is the installed
272
- choice; `sessionPolicy` describes a native binding when one is visible. Existing Claude
274
+ choice. `deliveryDecision` gives its known source and reports legacy provenance as unknown.
275
+ `nativeServiceSetup` reports service infrastructure separately. `sessionPolicy` describes a native binding when one is visible. Existing Claude
273
276
  sessions can retain their launch policy after a different choice is installed for new
274
277
  sessions; Codex checks current recorded consent before new offers.
275
278
  `nativeDelivery.sessions` lists each current session's identity, present transport state
@@ -139,7 +139,9 @@ reports degraded reachability; it does not rewrite the installed consent.
139
139
 
140
140
  Codex LocalDaemon delivery separately requires macOS arm64, Codex 0.152.1 or newer, a
141
141
  current feature probe, and exact thread, canonical cwd, process, version and protocol
142
- checks. Its daemon must already be running; installation and delivery do not start or stop it.
142
+ checks. Explicit complete setup can prepare a definitely absent service on Codex 0.154.0
143
+ or newer when the managed standalone installation is present. Message delivery does not
144
+ start or stop it.
143
145
  Explicit update maintenance can restart a verified service after separate confirmation. Codex reads
144
146
  consent from the installation record, not a shell-shim variable. Unavailable or ineligible
145
147
  sessions retain durable inbox fallback. Use `acc install --adapter codex --delivery off`
@@ -165,8 +167,10 @@ not merge arbitrary security policies or verify overrides in an already running
165
167
  For a custom policy, grant the same state and socket access through the proxy in the
166
168
  client's effective workspace profile; do not combine it with legacy sandbox settings.
167
169
 
168
- Restart Codex after changing permissions. Explicit delivery `off` or uninstall restores
169
- the previous configuration only while every generated permission component is unchanged.
170
+ Restart Codex after changing permissions. Disabling incoming delivery retains unchanged
171
+ ACC-owned outgoing grants that you approved earlier. A fresh `--delivery off` install does
172
+ not add live socket grants. Uninstall restores the previous configuration only while every
173
+ generated permission component is unchanged.
170
174
  If any component was edited or another setting depends on it, ACC preserves the entire
171
175
  bundle and reports it for review. Plugin registration can still be removed independently.
172
176
 
@@ -55,6 +55,14 @@ verified channel in the current session. Codex can save your choice even when it
55
55
  service is not running yet. Declining uses the reported fallback: `acc inbox`, or next-turn
56
56
  hooks only where the exact client version and platform are certified.
57
57
 
58
+ An interactive install asks one default-No question for all selected clients that need a
59
+ choice. Use `--delivery actionable|all` for explicit noninteractive consent. Use
60
+ `--delivery off` to disable incoming automatic requests. A dry run does not ask, write
61
+ configuration, or start a service. When supported Codex service preparation succeeds, it
62
+ means the infrastructure is ready. Start a Codex session and complete the client's hook and
63
+ trust review to establish a session-bound channel. If the managed standalone prerequisite
64
+ is missing, install reports that prerequisite instead of claiming readiness.
65
+
58
66
  If you opt into Claude Code's experimental idle delivery, check Claude's Channels startup
59
67
  notice for ACC and accept its development-channel warning when shown. If it reports Channels
60
68
  unavailable or blocked, a connected MCP server does not make inbound delivery work;
@@ -127,9 +135,10 @@ durable inbox instead.
127
135
 
128
136
  Codex LocalDaemon and Claude Code Channel offer optional native delivery on
129
137
  Apple Silicon macOS. They are experimental, can spend tokens, and queue messages
130
- until a running turn finishes. Codex requires 0.152.1 or newer, an already-running
131
- LocalDaemon, recorded opt-in and a verified session; start it with your normal
132
- command. A loaded daemon thread can receive messages after its terminal exits.
138
+ until a running turn finishes. Codex requires 0.152.1 or newer, LocalDaemon infrastructure,
139
+ recorded opt-in, and a verified session. Supported explicit setup can prepare a missing
140
+ service. Start the client with your normal command. A loaded daemon thread can receive
141
+ messages after its terminal exits.
133
142
  [Capabilities](CAPABILITIES.md) explains policy, versions and fallback.
134
143
 
135
144
  Delivery evidence is deliberately narrow: `queued -> offered -> retrieved -> acknowledged`.
@@ -91,24 +91,24 @@ supported platform, current probe and exact session binding. Under `actionable`,
91
91
  a `note` stays queued with `delivery_disabled`.
92
92
 
93
93
  For Codex, use your ordinary launch command with 0.152.1 or newer on Apple Silicon
94
- macOS. Its LocalDaemon must already be running, and trusted hooks must establish
94
+ macOS. Its LocalDaemon infrastructure 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
97
  `acc doctor` reports readiness, consent and the current workspace's live channel separately.
98
98
  `native_endpoint_unavailable` means the local service endpoint is missing or is not a safe socket;
99
99
  `native_session_unavailable` means the service answered but has no loaded thread to probe.
100
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
- On a supported Codex CLI, a missing daemon can be started with
104
- `codex app-server daemon start`; open a new Codex session afterward. Doctor includes this
105
- command when it detects the missing endpoint.
101
+ outage. On Codex 0.154.0 or newer, an explicit install with complete consent can prepare a
102
+ missing service when the managed standalone installation is present. If that prerequisite
103
+ is missing or unsafe, doctor names the vendor action and does not claim readiness. A ready
104
+ service is infrastructure only. Open a new Codex session afterward and complete the
105
+ client-owned hook and trust review.
106
106
 
107
107
  If a reply stays queued with `transport_permission_denied`, the sender's permissions
108
108
  blocked local transport. This is distinct from an unavailable recipient. The message was
109
109
  recorded successfully; the error does not mean it was read or acknowledged.
110
110
  On Codex 0.153.4 or newer on macOS arm64, rerun `acc install --adapter codex` with existing
111
- live consent (or add `--delivery actionable` to opt in), then start a new session.
111
+ live consent. Add `--delivery actionable` when new consent is required, then start a new session.
112
112
  Doctor's `outgoingDelivery` reports the installed permission configuration separately
113
113
  from `nativeDelivery.runtime`: an active receiving channel does not establish outgoing
114
114
  access. Custom policies and active-session overrides remain unverified; inspect the config
@@ -238,8 +238,9 @@ Next: [Getting started](GETTING_STARTED.md) · [Capabilities](CAPABILITIES.md)
238
238
 
239
239
  Run `acc doctor` to see the update policy and pending notice. A hold keeps an update
240
240
  pending only while the store contract it declares differs from the incoming version's or
241
- is unknown; records written before 0.5.0 declare none, so the first update after upgrading
242
- still waits for all of them. `acc update` names each remaining hold with its process and
241
+ is unknown. ACC can recover a missing contract from the process's unchanged managed
242
+ generation when that generation declares one. A generation that predates the declaration
243
+ still needs its processes to exit. `acc update` names each remaining hold with its process and
243
244
  its declared contract. ACC process leases, including persistent MCP servers, end on
244
245
  confirmed process exit. Native bindings clear on observed SessionEnd or confirmed process
245
246
  death; a vendor daemon may remain alive after SessionEnd. Unknown PIDs remain holds until
@@ -250,6 +251,21 @@ unrelated holds still require lifecycle cleanup or confirmed process exit; safet
250
251
  do not expire merely by elapsed time. See
251
252
  [maintenance and recovery](UPGRADING.md#confirmed-client-service-maintenance).
252
253
 
254
+ If ACC 0.5.3 or 0.5.4 reports `acc-claude-channel; contract unknown` after an earlier
255
+ 0.4.x upgrade, its active pointer may have lost the contract field. This can affect newly
256
+ opened Channels too. A pending old updater retries its downloaded release without looking
257
+ for a newer fix. Install the current global CLI, then use that CLI to complete the update:
258
+
259
+ ```bash
260
+ npm install -g agents-can-communicate@latest
261
+ acc update
262
+ acc doctor
263
+ ```
264
+
265
+ The new reader can activate a compatible release while those Channels remain open.
266
+ It still waits for different contracts, unknown native bindings, or generation files it
267
+ cannot verify. See [legacy contract recovery](UPGRADING.md#recover-missing-runtime-contracts).
268
+
253
269
  Use `acc update` to retry a failed download or finish an interrupted integration refresh.
254
270
  A download failure keeps the working version. A partial integration refresh blocks
255
271
  workspace commands until recovery completes, while hooks let the client continue without
package/docs/UPGRADING.md CHANGED
@@ -1,5 +1,31 @@
1
1
  # Upgrading to 0.5.0
2
2
 
3
+ ## Recover missing runtime contracts
4
+
5
+ ACC 0.5.5 fixes an upgrade defect that can leave 0.5.3 or 0.5.4 waiting for
6
+ `acc-claude-channel` processes with `contract unknown`. The 0.4.x updater can activate
7
+ a newer generation without copying its store contract into the active pointer. Channels
8
+ started afterward copy that incomplete pointer even though their runtime declares a contract.
9
+
10
+ For an installation already waiting on an older pending release, run:
11
+
12
+ ```bash
13
+ npm install -g agents-can-communicate@latest
14
+ acc update
15
+ acc version
16
+ acc doctor
17
+ ```
18
+
19
+ The global CLI supplies the new management reader before runtime activation. This step is
20
+ needed because the older updater retries its pending release before checking npm for another
21
+ version. If a version pin selects an older release, clear it or select 0.5.5 first.
22
+
23
+ The reader verifies the referenced generation's package identity, complete contents and
24
+ file modes before recovering a missing contract. Compatible Channels can keep running on
25
+ their original generation during activation. Explicit differing contracts and unknown
26
+ native bindings still block. A generation without a contract, missing files, or changed
27
+ contents remains unknown. ACC does not terminate these processes or edit their lease records.
28
+
3
29
  ## From 0.4.4, 0.4.3, 0.4.2, 0.4.1 or 0.4.0
4
30
 
5
31
  This release keeps the existing workspace data format. It changes when an update is
@@ -23,8 +49,9 @@ clients again and complete any hook/trust review they request. With existing liv
23
49
  Codex 0.153.4 or newer on macOS arm64 receives local socket permissions when its workspace
24
50
  policy is default or contains only ACC's legacy state root. Custom policies are preserved
25
51
  and reported as unverified. See [outgoing permissions](CONFIGURATION.md#codex-outgoing-permissions).
26
- If doctor names a missing local service, run `codex app-server daemon start` before opening
27
- the new session.
52
+ If doctor names a missing local service, follow its prerequisite or explicit install action
53
+ before opening the new session. A supported explicit install can prepare the service. It
54
+ does not make a session-bound channel ready.
28
55
 
29
56
  Run `acc doctor` in the project after the new clients have started (or submitted a normal
30
57
  turn). Its per-session lines now explain missing launch consent, an unidentified client
@@ -37,8 +64,10 @@ Claude admitted inbound Channels messages. See [delivery troubleshooting](TROUBL
37
64
  Upgrading preserves delivery consent. If doctor reports native delivery off and you want
38
65
  automatic peer requests, use `acc install --adapter codex --delivery actionable` (or the
39
66
  relevant adapter); this can spend model tokens. Codex can save that consent before its
40
- local service/session becomes available. Installation does not start the vendor daemon; ACC
41
- keeps durable inbox access when no verified live channel is bound.
67
+ local service/session becomes available. Ordinary installation keeps durable inbox access
68
+ when no verified live channel is bound. Explicit complete setup can prepare a definitely
69
+ absent supported service. Automatic refresh, delivery off, dry runs, and uninstall do not
70
+ start or stop that shared service.
42
71
 
43
72
  In Grok, public status supplies the session's own CLI arguments through the next tool
44
73
  hook. Grok still uses explicit inbox reads; this patch adds no external wake or guards.
@@ -53,17 +82,17 @@ only two kinds keep an update pending:
53
82
  - A hold whose declared store contract differs from the incoming version's. This is the
54
83
  case the gate exists for, and a release that changes the store contract still needs
55
84
  every live process to exit.
56
- - A hold that declares no contract, which includes every record written by a release
57
- before this one. Unknown cannot be compared, so it stays a conservative wait.
85
+ - A hold with no verifiable contract. Older native bindings and generations that predate
86
+ the declaration remain unknown. A missing field can be recovered when the referenced
87
+ managed generation declares its contract and passes integrity verification.
58
88
 
59
89
  A hold declaring the same store contract as the incoming version proceeds. Several open
60
90
  Claude Code sessions, a Codex daemon and an idle ACC MCP server no longer have to be
61
91
  closed together to move between releases that share a contract.
62
92
 
63
- The first update after installing this release still waits for every process, because the
64
- holds it has to judge were written before the contract field existed. Close the relevant
65
- clients and persistent ACC processes once, or accept the eligible Codex service
66
- maintenance offer described below. Updates after that do not need it.
93
+ Processes still running a generation from before the contract field existed must exit once.
94
+ Close the relevant clients and persistent ACC processes, or accept the eligible Codex service
95
+ maintenance offer described below. Later compatible runtimes do not require that restart.
67
96
 
68
97
  `acc update` names each remaining hold with its process and its declared contract, so a
69
98
  wait now states its reason rather than only the PID. `acc doctor` reports the same
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agents-can-communicate/adapter-claude-code",
3
- "version": "0.5.3",
3
+ "version": "0.5.5",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "exports": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agents-can-communicate/adapter-codex",
3
- "version": "0.5.3",
3
+ "version": "0.5.5",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "exports": {
@@ -2,6 +2,7 @@ import { defineAdapter, projectContext, projectContextResult }
2
2
  from "@agents-can-communicate/adapter-sdk";
3
3
  import { probeNativeDelivery, planNativeActivation, bindNativeSession, refreshNativeSession, retireNativeSession, offerMessage } from "./native-delivery.mjs";
4
4
  import certification from "../certification.json" with { type: "json" };
5
+ import { createCodexServiceSetup } from "./service-setup.mjs";
5
6
  import { createCodexMaintenance } from "./maintenance.mjs";
6
7
  export { sameMaintenanceIdentity } from "./maintenance.mjs";
7
8
 
@@ -20,9 +21,10 @@ export const CODEX_DELIVERY_FALLBACK = Object.freeze({
20
21
  diagnostic: "Codex native delivery requires codex-cli 0.152.1 or newer on darwin-arm64, "
21
22
  + "recorded recipient consent and a reachable LocalDaemon session with exact thread, cwd, "
22
23
  + "process, version and protocol verification. Embedded or unreachable sessions retain "
23
- + "durable messages. Native delivery does not start, restart or stop the vendor daemon and adds no "
24
+ + "durable messages. Message delivery does not start, restart or stop the vendor daemon and adds no "
24
25
  + "launch arguments; fallback is exact-certified next-turn delivery or acc inbox. "
25
- + "Explicitly confirmed acc update maintenance has a separate daemon restart check",
26
+ + "Explicit install with complete setup consent can prepare a missing supported service; "
27
+ + "explicitly confirmed acc update maintenance has a separate daemon restart check",
26
28
  });
27
29
 
28
30
  /**
@@ -67,6 +69,7 @@ export function createCodexAdapter() {
67
69
  probeNativeDelivery, planNativeActivation, bindNativeSession,
68
70
  refreshNativeSession, retireNativeSession, offerMessage,
69
71
  ...createCodexMaintenance(),
72
+ ...createCodexServiceSetup(),
70
73
  startSession: async () => ({ ok: true, changes: [], diagnostics: [] }),
71
74
  endSession: async () => ({ ok: true, changes: [], diagnostics: [] }),
72
75
  guardWrite: async () => ({ ok: true, changes: [], diagnostics: [] }),
@@ -67,7 +67,8 @@ export async function probeCodexQueue(peer, { threadId, minimum = MINIMUM_VERSIO
67
67
  try {
68
68
  if (threadId === undefined) {
69
69
  const loaded = await pageAll(peer, "thread/loaded/list", {});
70
- threadId = loaded.find(id => typeof id === "string" && id !== "");
70
+ if (loaded.some(id => typeof id !== "string" || id === "")) throw protocolError();
71
+ threadId = loaded[0];
71
72
  if (threadId === undefined) return { supported: false, serverVersion,
72
73
  reasonCode: "native_session_unavailable" };
73
74
  }
@@ -350,7 +350,7 @@ export async function detectCodex({ home, agentsHome = home,
350
350
  // detection neither invents its hash algorithm nor starts a client service.
351
351
  return { ok: true, changes: [], outgoingDelivery,
352
352
  nativeSetup: nativeDelivery?.reasonCode === "native_endpoint_unavailable"
353
- ? "Codex CLI: run codex app-server daemon start, then open a new Codex session; ACC never starts or restarts the daemon"
353
+ ? "Codex CLI: run codex app-server daemon start, then open a new Codex session; explicit ACC install with complete setup consent can prepare a missing supported service"
354
354
  : null,
355
355
  diagnostics: [
356
356
  published ? "acc plugin published in the marketplace" : "acc plugin not registered",
@@ -26,6 +26,28 @@ const configured = (source, context) => {
26
26
  && sockets(context).every(socket => literal(value(["permissions", profile, "network", "unix_sockets", socket])) === "allow");
27
27
  };
28
28
 
29
+ const hasCustomPolicy = (source, context) => {
30
+ const ownership = inspectPermissions(source);
31
+ if (ownership.state === "customized") return true;
32
+ if (ownership.state === "owned") return false;
33
+ const entries = scanConfig(source);
34
+ const find = keys => entries.filter(entry => equal(entry.keys, keys));
35
+ const modes = find(["sandbox_mode"]), defaults = find(["default_permissions"]);
36
+ const legacy = entries.filter(entry => entry.keys[0] === "sandbox_workspace_write");
37
+ const features = entries.filter(entry => entry.keys[0] === "features");
38
+ const featureTable = find(["features"]);
39
+ const proxies = find(["features", "network_proxy"]);
40
+ return entries.some(entry => ["permissions", "profile", "profiles"].includes(entry.keys[0]))
41
+ || modes.length > 1 || defaults.length > 1
42
+ || modes.some(entry => entry.header || literal(entry.value) !== "workspace-write")
43
+ || defaults.some(entry => entry.header || literal(entry.value) !== ":workspace")
44
+ || (legacy.length > 0 && (legacy.length !== 2 || !legacy[0].header || legacy[0].array
45
+ || !equal(legacy[1].keys, ["sandbox_workspace_write", "writable_roots"])
46
+ || JSON.stringify(literal(legacy[1].value)) !== JSON.stringify([context.stateRoot])))
47
+ || (features.length > 0 && (featureTable.length !== 1 || !featureTable[0].header || featureTable[0].array))
48
+ || proxies.length > 1 || proxies.some(entry => entry.header || !["true", "false"].includes(entry.value));
49
+ };
50
+
29
51
  // Match the channel's per-user temporary directory and Codex's actual home.
30
52
  // Grants cover only ACC's local channel namespace, never arbitrary Unix sockets.
31
53
  const sockets = context => [channelSocketDirectory(),
@@ -33,8 +55,9 @@ const sockets = context => [channelSocketDirectory(),
33
55
 
34
56
  export function outgoingStatus(source, context) {
35
57
  const state = inspectPermissions(source).state;
58
+ const custom = hasCustomPolicy(source, context);
36
59
  const ready = state === "owned" && supported(context) && configured(source, context);
37
- const reasonCode = state === "customized" ? "permission_configuration_modified"
60
+ const reasonCode = custom ? "permission_configuration_modified"
38
61
  : !supported(context) ? "permission_configuration_uncaptured"
39
62
  : ready ? null : "sender_permissions_unverified";
40
63
  return { state: reasonCode === null ? "configured" : "unverified", reasonCode,
@@ -47,14 +70,24 @@ export function outgoingStatus(source, context) {
47
70
  : `outgoing live delivery: sender permissions unverified in ${context.file}; `
48
71
  + (reasonCode === "permission_configuration_uncaptured"
49
72
  ? `automatic setup requires Codex ${MINIMUM} or newer on darwin-arm64`
50
- : "existing permissions were preserved; run acc install --adapter codex, then start a new session; "
51
- + "custom permission policies must allow ACC state and local sockets through the network proxy") };
73
+ : custom
74
+ ? "custom permission policy was preserved; manually allow ACC state and local sockets "
75
+ + "through the network proxy in the effective workspace profile"
76
+ : "ACC outgoing grants are absent; run acc install --adapter codex --delivery actionable, "
77
+ + "then start a new session") };
52
78
  }
53
79
 
54
80
  export function prepareLivePermissions(source, context) {
55
81
  const owned = inspectPermissions(source);
56
82
  const requested = (context.requestedLivePolicy ?? context.livePolicy ?? "off") !== "off";
57
83
  if (owned.state === "customized") return { source, skipLegacy: true, status: outgoingStatus(source, context) };
84
+ // Incoming automatic requests and outgoing local access are separate choices.
85
+ // Once ACC owns a valid outgoing profile, turning incoming requests off must
86
+ // not revoke the already approved ability to use ACC from the sandbox. A
87
+ // fresh off install still creates no grants because it has no owned unit.
88
+ if (!requested && owned.state === "owned") {
89
+ return { source, skipLegacy: true, status: outgoingStatus(source, context) };
90
+ }
58
91
  if (!requested) return { source: owned.source, skipLegacy: hasPermissions(scanConfig(owned.source)) };
59
92
  if (!supported(context) || !context.stateRoot) {
60
93
  return { source, skipLegacy: hasPermissions(scanConfig(source)), status: outgoingStatus(source, context) };
@@ -70,15 +103,7 @@ export function prepareLivePermissions(source, context) {
70
103
  const features = entries.filter(entry => entry.keys[0] === "features");
71
104
  const featureTable = find(["features"]);
72
105
  const proxies = find(["features", "network_proxy"]);
73
- const customized = entries.some(entry => ["permissions", "profile", "profiles"].includes(entry.keys[0]))
74
- || modes.length > 1 || defaults.length > 1
75
- || modes.some(entry => entry.header || literal(entry.value) !== "workspace-write")
76
- || defaults.some(entry => entry.header || literal(entry.value) !== ":workspace")
77
- || (legacy.length > 0 && (legacy.length !== 2 || !legacy[0].header || legacy[0].array
78
- || !equal(legacy[1].keys, ["sandbox_workspace_write", "writable_roots"])
79
- || JSON.stringify(literal(legacy[1].value)) !== JSON.stringify([context.stateRoot])))
80
- || (features.length > 0 && (featureTable.length !== 1 || !featureTable[0].header || featureTable[0].array))
81
- || proxies.length > 1 || proxies.some(entry => entry.header || !["true", "false"].includes(entry.value));
106
+ const customized = hasCustomPolicy(source, context);
82
107
  if (customized) return { source, skipLegacy: hasPermissions(entries), status: outgoingStatus(source, context) };
83
108
  const edits = [{ id: "selection", start: 0, end: 0, body: `default_permissions = "${PROFILE}"\n` }];
84
109
  for (const [id, values] of [["mode", modes], ["default", defaults]]) {
@@ -17,7 +17,7 @@ export const managedExecutablePaths = codexHome => ["bin/codex", "codex"].map(na
17
17
 
18
18
  export function runMaintenanceCommand(command, args, options) {
19
19
  return new Promise(resolve => execFile(command, args,
20
- { ...options, timeout: 20_000, maxBuffer: 131_072, windowsHide: true },
20
+ { timeout: 20_000, ...options, maxBuffer: 131_072, windowsHide: true },
21
21
  (error, stdout, stderr) => resolve({ status: error ?
22
22
  (typeof error.code === "number" ? error.code : null) : 0, stdout, stderr })));
23
23
  }
@@ -51,7 +51,8 @@ export async function probeNativeDelivery({ timeoutMs = 750, env = process.env,
51
51
  }
52
52
 
53
53
  // Ordinary Codex chooses its own cwd and launch mode; ACC only reuses a
54
- // verified pre-existing service and never starts a daemon or rewrites argv.
54
+ // verified service here and never rewrites argv. Explicit installer service
55
+ // preparation has its own consent and identity checks in service-setup.mjs.
55
56
  export function planNativeActivation({ detection }) {
56
57
  const realExecutable = detection?.realExecutable;
57
58
  if (typeof realExecutable !== "string" || realExecutable === "") {
@@ -0,0 +1,135 @@
1
+ import { lstat, realpath } from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { socketIsReady } from "./native-endpoint.mjs";
4
+ import { probeNativeDelivery } from "./native-delivery.mjs";
5
+ import { failMaintenance, maintenanceContext, probeMaintenanceInstall,
6
+ readMaintenancePid, runMaintenanceCommand, verifyMaintenanceProcess } from "./maintenance-host.mjs";
7
+
8
+ const keys = ["home", "codexHome", "cliPath", "cliVersion", "managedPath", "managedVersion",
9
+ "managedRealPath", "cliIdentity", "managedIdentity", "socketPath", "pidPath", "platform"];
10
+ const same = (a, b, fields = keys) => fields.every(key => a?.[key] === b?.[key]);
11
+ const own = info => typeof process.getuid !== "function" || info.uid === process.getuid();
12
+ const sessionNeeded = "Codex service ready; open a new Codex session to establish its delivery binding and review client hooks and permissions";
13
+ const diagnostics = {
14
+ native_endpoint_unavailable: "Prepare the missing Codex service with codex app-server daemon start, then open a new Codex session",
15
+ managed_install_missing: "Codex service requires the managed standalone installation; install it with curl -fsSL https://chatgpt.com/codex/install.sh | sh using the selected HOME and CODEX_HOME, then retry acc install",
16
+ managed_binary_mismatch: "Codex CLI and managed standalone versions differ; repair the vendor installation, then retry acc install",
17
+ maintenance_platform_unsupported: "Codex service preparation is captured only on darwin-arm64; configure the vendor service manually",
18
+ maintenance_cli_unsupported: "Codex cold service preparation requires codex-cli 0.154.0 or newer with daemon commands; update the vendor installation, then retry acc install",
19
+ };
20
+ function report(state, reasonCode, facts = {}) {
21
+ return { ...facts, state, reasonCode, diagnostic: state === "ready"
22
+ ? reasonCode === "native_session_unavailable" ? sessionNeeded
23
+ : "Codex service infrastructure is ready; current session binding is reported separately"
24
+ : diagnostics[reasonCode] ?? `Codex service preparation could not verify ${reasonCode}; inspect the vendor service and retry acc install` };
25
+ }
26
+
27
+ export function createCodexServiceSetup({ run = runMaintenanceCommand, probe = probeNativeDelivery,
28
+ fs = { lstat, realpath }, contextPaths = maintenanceContext } = {}) {
29
+ async function info(file) {
30
+ try { return await fs.lstat(file); }
31
+ catch (error) { if (error.code === "ENOENT") return null; throw error; }
32
+ }
33
+ async function safeDirectories(paths) {
34
+ for (const dir of [paths.codexHome, path.dirname(paths.socketPath), path.dirname(paths.pidPath)]) {
35
+ const stat = await info(dir);
36
+ if (stat && (!stat.isDirectory() || stat.isSymbolicLink() || !own(stat) || (stat.mode & 0o022))) {
37
+ failMaintenance("unsafe_service_directory");
38
+ }
39
+ }
40
+ }
41
+ async function executableIdentity(file) {
42
+ const resolved = await fs.realpath(file), stat = await fs.lstat(resolved);
43
+ if (!stat.isFile() || !own(stat) || (stat.mode & 0o022)) failMaintenance("unsafe_service_executable");
44
+ return { resolved, identity: `${stat.dev}:${stat.ino}:${stat.size}:${stat.mtimeMs}:${stat.ctimeMs}` };
45
+ }
46
+ async function installed(paths) {
47
+ await safeDirectories(paths);
48
+ if (!await info(paths.managedPath)) failMaintenance("managed_install_missing");
49
+ const installation = await probeMaintenanceInstall(paths, run);
50
+ const cli = await executableIdentity(installation.cliPath);
51
+ const managed = await executableIdentity(paths.managedPath);
52
+ return { home: paths.options.env.HOME, codexHome: paths.codexHome,
53
+ managedPath: paths.managedPath, managedRealPath: managed.resolved,
54
+ cliIdentity: cli.identity, managedIdentity: managed.identity,
55
+ socketPath: paths.socketPath, pidPath: paths.pidPath, platform: paths.platform, ...installation };
56
+ }
57
+ async function verify(paths, facts) {
58
+ if (!await socketIsReady(paths.socketPath)) failMaintenance("daemon_socket_unproven");
59
+ const version = await run(facts.cliPath, ["app-server", "daemon", "version"], paths.options);
60
+ if (version.status !== 0) failMaintenance("daemon_version_unavailable");
61
+ const observed = JSON.parse(version.stdout);
62
+ if (observed.status !== "running" || observed.backend !== "pid") failMaintenance("maintenance_backend_unsupported");
63
+ if (observed.managedCodexPath !== facts.managedPath || observed.socketPath !== facts.socketPath
64
+ || observed.cliVersion !== facts.cliVersion || observed.managedCodexVersion !== facts.managedVersion
65
+ || observed.appServerVersion !== facts.cliVersion) failMaintenance("daemon_identity_unavailable");
66
+ const pid = await readMaintenancePid(paths.pidPath);
67
+ await verifyMaintenanceProcess(pid, paths, run);
68
+ const native = await probe({ env: paths.options.env, timeoutMs: 1_500 });
69
+ // native_session_unavailable is emitted only after initialize and a valid,
70
+ // empty loaded-thread list. It proves infrastructure, never a bound recipient.
71
+ if ((!native.supported && native.reasonCode !== "native_session_unavailable")
72
+ || native.clientVersion !== facts.cliVersion) failMaintenance("daemon_protocol_unverified");
73
+ if (!same(pid, await readMaintenancePid(paths.pidPath), ["pid", "processStartTime"])) {
74
+ failMaintenance("service_identity_changed");
75
+ }
76
+ await verifyMaintenanceProcess(pid, paths, run);
77
+ return report("ready", native.reasonCode ?? null, { ...facts, ...pid });
78
+ }
79
+ async function inspect(context, { strict = false } = {}) {
80
+ try {
81
+ const paths = await contextPaths(context);
82
+ if (paths.platform !== "darwin-arm64") failMaintenance("maintenance_platform_unsupported");
83
+ if (!strict) {
84
+ const native = await probe({ env: paths.options.env, timeoutMs: 1_500 });
85
+ // Preserve the existing delivery support matrix: a healthy older
86
+ // service needs no new managed-install or cold-start prerequisite.
87
+ if (native.supported) return report("ready", native.reasonCode ?? null);
88
+ }
89
+ const facts = await installed(paths);
90
+ if (!await info(paths.socketPath) && !await info(paths.pidPath)) {
91
+ return report("needed", "native_endpoint_unavailable", facts);
92
+ }
93
+ return await verify(paths, facts);
94
+ } catch (error) {
95
+ const reason = error.reasonCode ?? "service_inspection_failed";
96
+ return report(reason.endsWith("_unsupported") ? "unsupported" : "blocked", reason);
97
+ }
98
+ }
99
+ async function prepareNativeServiceSetup({ context, plan } = {}) {
100
+ let started = false;
101
+ try {
102
+ if (plan?.state === "ready") {
103
+ const current = await inspect(context);
104
+ return { ...current, state: current.state === "ready" ? "ready" : "blocked", started };
105
+ }
106
+ if (plan?.state !== "needed" || !keys.every(key => typeof plan[key] === "string")) {
107
+ return { ...report("blocked", "invalid_service_setup_plan"), started };
108
+ }
109
+ // Do not substitute approved paths into context: detect changed HOME,
110
+ // CODEX_HOME, PATH or managed-current targets before any start command.
111
+ const current = await inspect(context, { strict: true });
112
+ if (!same(plan, current)) return { ...report("blocked", "service_identity_changed"), started };
113
+ if (current.state === "ready") return { ...current, started };
114
+ if (current.state !== "needed") return { ...current, state: "blocked", started };
115
+ const paths = await contextPaths(context);
116
+ if (!same(plan, await installed(paths))) failMaintenance("service_identity_changed");
117
+ // Last check immediately before the only mutating command. Never delete
118
+ // stale metadata or stop/restart a process on this path.
119
+ if (await info(paths.socketPath) || await info(paths.pidPath)) {
120
+ return { ...await verify(paths, current), started };
121
+ }
122
+ started = true;
123
+ const result = await run(plan.cliPath, ["app-server", "daemon", "start"], {
124
+ cwd: plan.codexHome, env: { ...(context?.env ?? process.env), HOME: plan.home, CODEX_HOME: plan.codexHome },
125
+ timeout: 15_000,
126
+ });
127
+ if (result.status !== 0) failMaintenance("daemon_start_failed");
128
+ if (!same(plan, await installed(paths))) failMaintenance("service_identity_changed");
129
+ return { ...await verify(paths, current), started };
130
+ } catch (error) {
131
+ return { ...report(started ? "failed" : "blocked", error.reasonCode ?? "daemon_start_failed"), started };
132
+ }
133
+ }
134
+ return { inspectNativeServiceSetup: context => inspect(context), prepareNativeServiceSetup };
135
+ }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agents-can-communicate",
3
- "version": "0.5.3",
3
+ "version": "0.5.5",
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.5.3",
3
+ "version": "0.5.5",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "exports": {