agents-can-communicate 0.5.3 → 0.5.4

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 (40) hide show
  1. package/README.md +8 -5
  2. package/docs/CLI.md +7 -4
  3. package/docs/CONFIGURATION.md +7 -3
  4. package/docs/GETTING_STARTED.md +12 -3
  5. package/docs/TROUBLESHOOTING.md +7 -7
  6. package/docs/UPGRADING.md +7 -4
  7. package/node_modules/@agents-can-communicate/adapter-claude-code/package.json +1 -1
  8. package/node_modules/@agents-can-communicate/adapter-codex/package.json +1 -1
  9. package/node_modules/@agents-can-communicate/adapter-codex/src/adapter.mjs +5 -2
  10. package/node_modules/@agents-can-communicate/adapter-codex/src/app-server-client.mjs +2 -1
  11. package/node_modules/@agents-can-communicate/adapter-codex/src/install.mjs +1 -1
  12. package/node_modules/@agents-can-communicate/adapter-codex/src/live-permissions.mjs +37 -12
  13. package/node_modules/@agents-can-communicate/adapter-codex/src/maintenance-host.mjs +1 -1
  14. package/node_modules/@agents-can-communicate/adapter-codex/src/native-delivery.mjs +2 -1
  15. package/node_modules/@agents-can-communicate/adapter-codex/src/service-setup.mjs +135 -0
  16. package/node_modules/@agents-can-communicate/adapter-gemini-cli/extension/gemini-extension.json +1 -1
  17. package/node_modules/@agents-can-communicate/adapter-gemini-cli/package.json +1 -1
  18. package/node_modules/@agents-can-communicate/adapter-grok/package.json +1 -1
  19. package/node_modules/@agents-can-communicate/adapter-kimi/package.json +1 -1
  20. package/node_modules/@agents-can-communicate/adapter-sdk/package.json +1 -1
  21. package/node_modules/@agents-can-communicate/cli/package.json +1 -1
  22. package/node_modules/@agents-can-communicate/cli/src/doctor-command.mjs +10 -5
  23. package/node_modules/@agents-can-communicate/cli/src/help.mjs +3 -0
  24. package/node_modules/@agents-can-communicate/cli/src/install-command.mjs +15 -71
  25. package/node_modules/@agents-can-communicate/cli/src/install-delivery-consent.mjs +152 -0
  26. package/node_modules/@agents-can-communicate/cli/src/managed-runtime/refresh.mjs +6 -1
  27. package/node_modules/@agents-can-communicate/cli/src/native-delivery-status.mjs +22 -5
  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/installer/package.json +1 -1
  32. package/node_modules/@agents-can-communicate/installer/src/apply.mjs +6 -2
  33. package/node_modules/@agents-can-communicate/installer/src/detect.mjs +11 -0
  34. package/node_modules/@agents-can-communicate/installer/src/ownership.mjs +6 -1
  35. package/node_modules/@agents-can-communicate/installer/src/plan.mjs +10 -2
  36. package/node_modules/@agents-can-communicate/installer/src/service-setup.mjs +30 -0
  37. package/node_modules/@agents-can-communicate/mcp-server/package.json +1 -1
  38. package/node_modules/@agents-can-communicate/protocol/package.json +1 -1
  39. package/node_modules/@agents-can-communicate/storage-filesystem/package.json +1 -1
  40. 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.
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
package/docs/UPGRADING.md CHANGED
@@ -23,8 +23,9 @@ clients again and complete any hook/trust review they request. With existing liv
23
23
  Codex 0.153.4 or newer on macOS arm64 receives local socket permissions when its workspace
24
24
  policy is default or contains only ACC's legacy state root. Custom policies are preserved
25
25
  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.
26
+ If doctor names a missing local service, follow its prerequisite or explicit install action
27
+ before opening the new session. A supported explicit install can prepare the service. It
28
+ does not make a session-bound channel ready.
28
29
 
29
30
  Run `acc doctor` in the project after the new clients have started (or submitted a normal
30
31
  turn). Its per-session lines now explain missing launch consent, an unidentified client
@@ -37,8 +38,10 @@ Claude admitted inbound Channels messages. See [delivery troubleshooting](TROUBL
37
38
  Upgrading preserves delivery consent. If doctor reports native delivery off and you want
38
39
  automatic peer requests, use `acc install --adapter codex --delivery actionable` (or the
39
40
  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.
41
+ local service/session becomes available. Ordinary installation keeps durable inbox access
42
+ when no verified live channel is bound. Explicit complete setup can prepare a definitely
43
+ absent supported service. Automatic refresh, delivery off, dry runs, and uninstall do not
44
+ start or stop that shared service.
42
45
 
43
46
  In Grok, public status supplies the session's own CLI arguments through the next tool
44
47
  hook. Grok still uses explicit inbox reads; this patch adds no external wake or guards.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agents-can-communicate/adapter-claude-code",
3
- "version": "0.5.3",
3
+ "version": "0.5.4",
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.4",
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.4",
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.4",
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.5.3",
3
+ "version": "0.5.4",
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.5.3",
3
+ "version": "0.5.4",
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.5.3",
3
+ "version": "0.5.4",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "exports": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agents-can-communicate/cli",
3
- "version": "0.5.3",
3
+ "version": "0.5.4",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "exports": {
@@ -6,12 +6,12 @@ import { describeDeliveryFallback, detectInstallation, livePolicyOf, loadOwnersh
6
6
  from "@agents-can-communicate/installer";
7
7
  import { AccError, EXIT } from "@agents-can-communicate/protocol";
8
8
 
9
- import { describeNative, nativeRemediation, nativeState, updateNativeRuntime } from "./native-delivery-status.mjs";
9
+ import { describeDeliveryDecision, describeNative, nativeRemediation, nativeState,
10
+ updateNativeRuntime } from "./native-delivery-status.mjs";
10
11
  export { describeNative } from "./native-delivery-status.mjs";
11
-
12
12
  import { nativeSessionLines, updateNativeSessions } from "./native-session-diagnostics.mjs";
13
-
14
13
  import { ALL_ADAPTERS, clientContext, probeTimeout } from "./install-command.mjs";
14
+ import { decisionOf } from "./install-delivery-consent.mjs";
15
15
  import { describePresence } from "./main.mjs";
16
16
  import { platformPaths } from "./platform-paths.mjs";
17
17
  import { noticeUpdate } from "./update-check.mjs";
@@ -173,7 +173,9 @@ export async function diagnoseAdapters({ options, runtime, detect = detectInstal
173
173
  // is the acc that copied the skills and manifests into it. They diverge after
174
174
  // an npm upgrade with no `acc install`, and reporting both is what makes the
175
175
  // divergence legible rather than hidden behind a single reassuring number.
176
- return { ...entry, stale, wired, bundleVersion, owned: { modified: owned.modified,
176
+ return { ...entry, stale, wired, bundleVersion,
177
+ deliveryDecision: installed === undefined ? null : decisionOf(installed),
178
+ owned: { modified: owned.modified,
177
179
  missing: owned.missing, intact: owned.intact.length },
178
180
  nativeDelivery: nativeState(entry.nativeDelivery, livePolicyOf(installed), {
179
181
  contract: adapters.find(adapter => adapter.id === entry.adapterId)?.nativeDelivery,
@@ -241,6 +243,7 @@ export async function runDoctor({ options, context, runtime }) {
241
243
  registry: ALL_ADAPTERS() });
242
244
  for (const adapter of adapters) {
243
245
  adapter.remediation.push(...nativeRemediation(adapter));
246
+ adapter.remediation = [...new Set(adapter.remediation)];
244
247
  }
245
248
 
246
249
  const data = {
@@ -276,9 +279,11 @@ export async function runDoctor({ options, context, runtime }) {
276
279
  ...adapters.filter(adapter => adapter.present)
277
280
  .map(adapter => ` ${adapter.displayName} live delivery: `
278
281
  + `${describeNative(adapter.nativeDelivery, { clientVersion: adapter.version })}; `
282
+ + `${adapter.nativeDelivery.policy === "off" && adapter.deliveryDecision !== null
283
+ ? `decision: ${describeDeliveryDecision(adapter.deliveryDecision)}; ` : ""}`
279
284
  + `fallback: ${describeDeliveryFallback(adapter)}`),
280
285
  ...nativeSessionLines(adapters),
281
- ...adapters.filter(adapter => adapter.present && adapter.outgoingDelivery)
286
+ ...adapters.filter(adapter => adapter.present && adapter.outgoingDelivery?.state === "configured")
282
287
  .map(adapter => ` ${adapter.displayName} ${adapter.outgoingDelivery.diagnostic}`),
283
288
  ...(manager === null ? [] : [` automatic updates ${manager.auto ? "on" : "off"}; ACC ${manager.active.version}`
284
289
  + (manager.pin ? `; pinned to ${manager.pin}` : ""), ...(update.notice ? [` ${update.notice}`] : [])]),
@@ -76,6 +76,9 @@ const CHOICES = Object.freeze({
76
76
  });
77
77
 
78
78
  const NOTES = Object.freeze({
79
+ install: ["Without --delivery, one default-No question covers all selected clients that need a decision.",
80
+ "--delivery actionable|all gives explicit complete-setup consent; --delivery off disables incoming automatic requests.",
81
+ "A dry run does not prompt, write configuration, or start a service. Service readiness does not establish a session binding."],
79
82
  attach: ["Native hooks already manage their own session. Attach only for a manual CLI session.",
80
83
  "Keep the returned session and generation together for subsequent calls.",
81
84
  "--cadence is a positive interval in milliseconds."],