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.
- package/README.md +8 -5
- package/docs/ARCHITECTURE.md +6 -1
- package/docs/CLI.md +7 -4
- package/docs/CONFIGURATION.md +7 -3
- package/docs/GETTING_STARTED.md +12 -3
- package/docs/TROUBLESHOOTING.md +25 -9
- package/docs/UPGRADING.md +39 -10
- package/node_modules/@agents-can-communicate/adapter-claude-code/package.json +1 -1
- package/node_modules/@agents-can-communicate/adapter-codex/package.json +1 -1
- package/node_modules/@agents-can-communicate/adapter-codex/src/adapter.mjs +5 -2
- package/node_modules/@agents-can-communicate/adapter-codex/src/app-server-client.mjs +2 -1
- package/node_modules/@agents-can-communicate/adapter-codex/src/install.mjs +1 -1
- package/node_modules/@agents-can-communicate/adapter-codex/src/live-permissions.mjs +37 -12
- package/node_modules/@agents-can-communicate/adapter-codex/src/maintenance-host.mjs +1 -1
- package/node_modules/@agents-can-communicate/adapter-codex/src/native-delivery.mjs +2 -1
- package/node_modules/@agents-can-communicate/adapter-codex/src/service-setup.mjs +135 -0
- package/node_modules/@agents-can-communicate/adapter-gemini-cli/extension/gemini-extension.json +1 -1
- package/node_modules/@agents-can-communicate/adapter-gemini-cli/package.json +1 -1
- package/node_modules/@agents-can-communicate/adapter-grok/package.json +1 -1
- package/node_modules/@agents-can-communicate/adapter-kimi/package.json +1 -1
- package/node_modules/@agents-can-communicate/adapter-sdk/package.json +1 -1
- package/node_modules/@agents-can-communicate/cli/package.json +1 -1
- package/node_modules/@agents-can-communicate/cli/src/doctor-command.mjs +10 -5
- package/node_modules/@agents-can-communicate/cli/src/help.mjs +3 -0
- package/node_modules/@agents-can-communicate/cli/src/install-command.mjs +15 -71
- package/node_modules/@agents-can-communicate/cli/src/install-delivery-consent.mjs +152 -0
- package/node_modules/@agents-can-communicate/cli/src/managed-runtime/generation-files.mjs +25 -0
- package/node_modules/@agents-can-communicate/cli/src/managed-runtime/generation.mjs +1 -22
- package/node_modules/@agents-can-communicate/cli/src/managed-runtime/launchers.mjs +1 -1
- package/node_modules/@agents-can-communicate/cli/src/managed-runtime/leases.mjs +2 -2
- package/node_modules/@agents-can-communicate/cli/src/managed-runtime/refresh.mjs +6 -1
- package/node_modules/@agents-can-communicate/cli/src/managed-runtime/state.mjs +23 -1
- package/node_modules/@agents-can-communicate/cli/src/native-delivery-status.mjs +22 -5
- package/node_modules/@agents-can-communicate/core/package.json +1 -1
- package/node_modules/@agents-can-communicate/delivery-router/package.json +1 -1
- package/node_modules/@agents-can-communicate/hook-runner/package.json +1 -1
- package/node_modules/@agents-can-communicate/installer/package.json +1 -1
- package/node_modules/@agents-can-communicate/installer/src/apply.mjs +6 -2
- package/node_modules/@agents-can-communicate/installer/src/detect.mjs +11 -0
- package/node_modules/@agents-can-communicate/installer/src/ownership.mjs +6 -1
- package/node_modules/@agents-can-communicate/installer/src/plan.mjs +10 -2
- package/node_modules/@agents-can-communicate/installer/src/service-setup.mjs +30 -0
- package/node_modules/@agents-can-communicate/mcp-server/package.json +1 -1
- package/node_modules/@agents-can-communicate/protocol/package.json +1 -1
- package/node_modules/@agents-can-communicate/storage-filesystem/package.json +1 -1
- 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.
|
|
71
|
-
|
|
72
|
-
|
|
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
|
|
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.
|
|
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/ARCHITECTURE.md
CHANGED
|
@@ -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.
|
|
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
|
|
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.
|
|
263
|
-
|
|
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
|
|
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
|
package/docs/CONFIGURATION.md
CHANGED
|
@@ -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.
|
|
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.
|
|
169
|
-
|
|
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
|
|
package/docs/GETTING_STARTED.md
CHANGED
|
@@ -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,
|
|
131
|
-
|
|
132
|
-
command. A loaded daemon thread can receive
|
|
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`.
|
package/docs/TROUBLESHOOTING.md
CHANGED
|
@@ -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
|
|
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
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
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
|
|
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
|
|
242
|
-
|
|
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,
|
|
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.
|
|
41
|
-
|
|
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
|
|
57
|
-
|
|
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
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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
|
|
@@ -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.
|
|
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
|
-
+ "
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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
|
-
:
|
|
51
|
-
|
|
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 =
|
|
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
|
-
{
|
|
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
|
|
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
|
+
}
|