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.
- package/README.md +8 -5
- package/docs/CLI.md +7 -4
- package/docs/CONFIGURATION.md +7 -3
- package/docs/GETTING_STARTED.md +12 -3
- package/docs/TROUBLESHOOTING.md +7 -7
- package/docs/UPGRADING.md +7 -4
- 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/refresh.mjs +6 -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/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
|
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,
|
|
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.
|
|
41
|
-
|
|
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.
|
|
@@ -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
|
+
}
|
|
@@ -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,
|
|
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,
|
|
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."],
|