agents-can-communicate 0.10.3 → 0.10.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 (29) hide show
  1. package/docs/TROUBLESHOOTING.md +10 -0
  2. package/docs/UPGRADING.md +16 -0
  3. package/node_modules/@agents-can-communicate/adapter-antigravity/package.json +1 -1
  4. package/node_modules/@agents-can-communicate/adapter-antigravity/plugin/plugin.json +1 -1
  5. package/node_modules/@agents-can-communicate/adapter-claude-code/package.json +1 -1
  6. package/node_modules/@agents-can-communicate/adapter-codex/package.json +1 -1
  7. package/node_modules/@agents-can-communicate/adapter-codex/plugin/skills/acc/SKILL.md +19 -0
  8. package/node_modules/@agents-can-communicate/adapter-codex/src/native-delivery.mjs +9 -6
  9. package/node_modules/@agents-can-communicate/adapter-codex/src/native-endpoint.mjs +16 -5
  10. package/node_modules/@agents-can-communicate/adapter-gemini-cli/extension/gemini-extension.json +1 -1
  11. package/node_modules/@agents-can-communicate/adapter-gemini-cli/package.json +1 -1
  12. package/node_modules/@agents-can-communicate/adapter-grok/package.json +1 -1
  13. package/node_modules/@agents-can-communicate/adapter-kimi/package.json +1 -1
  14. package/node_modules/@agents-can-communicate/adapter-sdk/package.json +1 -1
  15. package/node_modules/@agents-can-communicate/adapter-sdk/src/native-vocabulary.mjs +2 -0
  16. package/node_modules/@agents-can-communicate/cli/package.json +1 -1
  17. package/node_modules/@agents-can-communicate/cli/src/main.mjs +5 -1
  18. package/node_modules/@agents-can-communicate/cli/src/native-session-diagnostics.mjs +2 -0
  19. package/node_modules/@agents-can-communicate/core/package.json +1 -1
  20. package/node_modules/@agents-can-communicate/delivery-router/package.json +1 -1
  21. package/node_modules/@agents-can-communicate/delivery-router/src/refresh-binding.mjs +9 -6
  22. package/node_modules/@agents-can-communicate/delivery-router/src/router.mjs +6 -1
  23. package/node_modules/@agents-can-communicate/hook-runner/package.json +1 -1
  24. package/node_modules/@agents-can-communicate/installer/package.json +1 -1
  25. package/node_modules/@agents-can-communicate/installer/src/delivery-diagnostics.mjs +1 -0
  26. package/node_modules/@agents-can-communicate/mcp-server/package.json +1 -1
  27. package/node_modules/@agents-can-communicate/protocol/package.json +1 -1
  28. package/node_modules/@agents-can-communicate/storage-filesystem/package.json +1 -1
  29. package/package.json +1 -1
@@ -158,6 +158,16 @@ delivery, start Codex without it; put a setting you need in `config.toml` instea
158
158
  If a reply stays queued with `transport_permission_denied`, the sender's permissions
159
159
  blocked local transport. This is distinct from an unavailable recipient. The message was
160
160
  recorded successfully; the error does not mean it was read or acknowledged.
161
+ For Codex sends, use the exact installed ACC wrapper through the client's approved
162
+ command path (`exec_command` with `sandbox_permissions: "require_escalated"`). The
163
+ installed ACC prefix rule can authorize that command. Keep it separate from other
164
+ shell commands. If delivery was already attempted, retry the same arguments and
165
+ the original `--client-message-id` printed by the CLI. This retries the recorded
166
+ message instead of recording another one. An initial execution refusal records
167
+ nothing; a refused retry does not erase an earlier recorded message.
168
+ On Codex 0.162.0 on macOS, even an installed socket allowlist did not permit direct
169
+ access to the daemon socket from the sandbox. A configured profile alone therefore
170
+ does not prove that a sandboxed sender can reach another Codex session.
161
171
  On Codex 0.153.4 or newer, rerun `acc install --adapter codex` with existing
162
172
  live consent. Add `--delivery actionable` when new consent is required, then start a new session.
163
173
  With ACC 0.8.0 or 0.8.1, every live offer from a Codex session to Claude Code failed this way:
package/docs/UPGRADING.md CHANGED
@@ -1,5 +1,21 @@
1
1
  # Upgrading
2
2
 
3
+ ## From 0.10.3
4
+
5
+ Update as usual with `acc update`. Version 0.10.4 preserves sender permission
6
+ failures as `transport_permission_denied`, including when access to socket
7
+ metadata or an expired delivery binding is denied. The CLI prints the original
8
+ retry key so the same recorded message can be offered again without a duplicate.
9
+
10
+ The refreshed Codex skill uses the client's approved command path for outgoing
11
+ ACC messages. ACC's existing exact-wrapper rule can authorize those commands.
12
+ An initial execution refusal records no message; a refused retry preserves an
13
+ earlier durable message. See [sender permissions](TROUBLESHOOTING.md#i-enabled-live-delivery-but-got-fallback)
14
+ for the recovery procedure.
15
+
16
+ This release keeps store contract 7 and adds no data migration. The update
17
+ refreshes installed integrations while preserving their recorded delivery policy.
18
+
3
19
  ## Store contract 6 to 7
4
20
 
5
21
  ACC moves each workspace's store from contract 6 to 7 by itself, right after an update
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agents-can-communicate/adapter-antigravity",
3
- "version": "0.10.3",
3
+ "version": "0.10.4",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "exports": {
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "name": "acc",
3
- "version": "0.10.3",
3
+ "version": "0.10.4",
4
4
  "description": "Coordinate this Antigravity CLI session with other AI agent sessions working in the same workspace."
5
5
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agents-can-communicate/adapter-claude-code",
3
- "version": "0.10.3",
3
+ "version": "0.10.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.10.3",
3
+ "version": "0.10.4",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "exports": {
@@ -16,6 +16,25 @@ Use this skill when hook context reports peers or actionable attention, or
16
16
  the user asks for coordination between sessions. An `ACC CLI (append):` header
17
17
  by itself supplies identity for later use; continue the user's ordinary work.
18
18
 
19
+ ### Send through Codex's approved command path
20
+
21
+ Run `message`, `request`, `reply`, and `finish` with `exec_command` and
22
+ `sandbox_permissions: "require_escalated"`. Put the installed `{{ACC}}` wrapper
23
+ first in the command. ACC's installed prefix rule can then authorize that exact
24
+ local command. Do not combine it with other shell commands. These sends stay
25
+ on this machine and reach the user's own agent sessions.
26
+
27
+ Codex can deny its daemon socket inside the sandbox even when the ACC profile
28
+ allows the socket. The approved command path preserves that sandbox boundary.
29
+ An initial execution refusal records no message. If a retry is refused, the
30
+ earlier recorded message remains durable. Report which case occurred.
31
+
32
+ Choose a distinct `--client-message-id` for each new send. If delivery reports
33
+ `transport_permission_denied`, retry the same command through approved execution
34
+ with the same key and content. The CLI prints the original key when it generated
35
+ one. A retry uses the recorded message and offers it again only while queued.
36
+ Use normal sandboxed execution for local reads and claims.
37
+
19
38
  ## Use your own CLI credentials
20
39
 
21
40
  When this turn's ACC hook supplies `ACC CLI (append):`, append those exact
@@ -168,22 +168,25 @@ async function bindAttempt({ event, clientPid, clientVersion, runtimeDir,
168
168
  // what `codex --version` printed, and a daemon that had updated under its CLI
169
169
  // was refused here on that difference alone.
170
170
  async function receiverFor(binding, runtimeDir, platform = process.platform) {
171
- const endpoint = await readNativeEndpoint({ runtimeDir, endpointId: binding?.opaqueEndpointRef, platform });
172
- return endpoint !== null && await socketIsReady(endpoint.socketPath, { platform }) ? endpoint : null;
171
+ const endpoint = await readNativeEndpoint({ runtimeDir, endpointId: binding?.opaqueEndpointRef,
172
+ platform, throwPermissionErrors: true });
173
+ return endpoint !== null && await socketIsReady(endpoint.socketPath,
174
+ { platform, throwPermissionErrors: true }) ? endpoint : null;
173
175
  }
174
176
 
175
177
  export async function refreshNativeSession({ binding, runtimeDir, timeoutMs = 750,
176
178
  now = Date.now, open = openCodexAppServer, platform = process.platform } = {}) {
177
179
  const rejected = reason => closed(binding?.clientVersion, reason);
178
- const endpoint = await receiverFor(binding, runtimeDir, platform);
179
- if (endpoint === null) return rejected("handshake_failed");
180
180
  try {
181
+ const endpoint = await receiverFor(binding, runtimeDir, platform);
182
+ if (endpoint === null) return rejected("handshake_failed");
181
183
  return await usingPeer(endpoint.socketPath, timeoutMs, open, async peer => {
182
184
  const { reasonCode, servingVersion } = await verifyReceiver(peer, endpoint);
183
185
  return reasonCode === null
184
186
  ? handshake({ ...endpoint, clientVersion: servingVersion }, now) : rejected(reasonCode);
185
187
  });
186
188
  } catch (error) {
189
+ if (["EPERM", "EACCES"].includes(error?.code)) return rejected("transport_permission_denied");
187
190
  return rejected(error?.code === "ETIMEDOUT" ? "handshake_timeout" : "handshake_failed");
188
191
  }
189
192
  }
@@ -192,9 +195,9 @@ export async function offerMessage({ binding, message, runtimeDir, timeoutMs = 5
192
195
  open = openCodexAppServer, platform = process.platform } = {}) {
193
196
  const rejected = safeErrorCode => ({ accepted: false, transport: "codex-app-server",
194
197
  clientVersion: binding?.clientVersion ?? null, safeErrorCode });
195
- const endpoint = await receiverFor(binding, runtimeDir, platform);
196
- if (endpoint === null) return rejected("recipient_unavailable");
197
198
  try {
199
+ const endpoint = await receiverFor(binding, runtimeDir, platform);
200
+ if (endpoint === null) return rejected("recipient_unavailable");
198
201
  return await usingPeer(endpoint.socketPath, timeoutMs, open, async peer => {
199
202
  const { reasonCode, servingVersion } = await verifyReceiver(peer, endpoint);
200
203
  if (reasonCode !== null) return rejected(reasonCode === "handshake_timeout" ? "transport_error"
@@ -50,18 +50,25 @@ async function directory(runtimeDir, create = false, platform = process.platform
50
50
  * 0.159.3), so there the socket counts while its directory lists it; Codex keeps
51
51
  * that directory to its user.
52
52
  */
53
- export async function socketIsReady(socketPath, { platform = process.platform } = {}) {
53
+ export async function socketIsReady(socketPath, { platform = process.platform,
54
+ throwPermissionErrors = false } = {}) {
54
55
  if (isWindowsPlatform(platform)) {
55
56
  if (!absolute(socketPath)) return false;
56
57
  const name = path.basename(socketPath).toLowerCase();
57
- const names = await readdir(path.dirname(socketPath)).catch(() => []);
58
+ const names = await readdir(path.dirname(socketPath)).catch(error => {
59
+ if (throwPermissionErrors && ["EPERM", "EACCES"].includes(error.code)) throw error;
60
+ return [];
61
+ });
58
62
  return names.some(entry => entry.toLowerCase() === name);
59
63
  }
60
64
  if (!absolute(socketPath)) return false;
61
65
  try {
62
66
  const info = await lstat(socketPath);
63
67
  return info.isSocket() && !info.isSymbolicLink() && own(info);
64
- } catch { return false; }
68
+ } catch (error) {
69
+ if (throwPermissionErrors && ["EPERM", "EACCES"].includes(error.code)) throw error;
70
+ return false;
71
+ }
65
72
  }
66
73
 
67
74
  /** The socket a control socket path leads to, or null. Codex 0.157.1 keeps a
@@ -104,7 +111,8 @@ export async function writeNativeEndpoint({ runtimeDir, record, platform = proce
104
111
 
105
112
  // Expired records are metadata for revalidation, never proof of reachability.
106
113
  // Refuse links and oversized files before reading; no foreign error text escapes.
107
- export async function readNativeEndpoint({ runtimeDir, endpointId, platform = process.platform }) {
114
+ export async function readNativeEndpoint({ runtimeDir, endpointId, platform = process.platform,
115
+ throwPermissionErrors = false }) {
108
116
  if (typeof endpointId !== "string" || !ENDPOINT.test(endpointId)) return null;
109
117
  let handle;
110
118
  try {
@@ -116,7 +124,10 @@ export async function readNativeEndpoint({ runtimeDir, endpointId, platform = pr
116
124
  }
117
125
  const record = JSON.parse(await handle.readFile("utf8"));
118
126
  return valid(record) && record.endpointId === endpointId ? record : null;
119
- } catch { return null; }
127
+ } catch (error) {
128
+ if (throwPermissionErrors && ["EPERM", "EACCES"].includes(error.code)) throw error;
129
+ return null;
130
+ }
120
131
  finally { await handle?.close().catch(() => null); }
121
132
  }
122
133
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agents-can-communicate",
3
- "version": "0.10.3",
3
+ "version": "0.10.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.10.3",
3
+ "version": "0.10.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.10.3",
3
+ "version": "0.10.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.10.3",
3
+ "version": "0.10.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.10.3",
3
+ "version": "0.10.4",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "exports": {
@@ -29,6 +29,8 @@ export const NATIVE_REASON_CODES = Object.freeze([
29
29
  "prerelease_not_captured", "below_minimum_version", "known_bad_version",
30
30
  "feature_probe_failed", "probe_timeout", "probe_version_mismatch", "protocol_mismatch",
31
31
  "native_endpoint_unavailable", "native_session_unavailable",
32
+ // A caller can be denied access to a healthy receiver's local transport.
33
+ "transport_permission_denied",
32
34
  "handshake_failed", "handshake_timeout", "handshake_version_mismatch",
33
35
  "session_generation_stale", "client_process_unknown", "unsupported_shell",
34
36
  // The current session's canonical workspace identity could not be verified.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agents-can-communicate/cli",
3
- "version": "0.10.3",
3
+ "version": "0.10.4",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "exports": {
@@ -6,6 +6,7 @@ import { AccError, CONFIG_FILENAME, EXIT, GENERIC_MESSAGE_KINDS, VALID_OBLIGATIO
6
6
  failure, ok }
7
7
  from "@agents-can-communicate/protocol";
8
8
  import { createCoordinationService } from "@agents-can-communicate/core";
9
+ import { shellQuote } from "@agents-can-communicate/adapter-sdk";
9
10
  import { describeNativeReason } from "@agents-can-communicate/installer";
10
11
  import { openFilesystemStore } from "@agents-can-communicate/storage-filesystem";
11
12
 
@@ -162,7 +163,10 @@ export function recordedText(message, delivery) {
162
163
  ? `woke ${item.recipientParticipantId} via ${item.transport}; the message arrives with its next turn`
163
164
  : item.outcome === "queued"
164
165
  ? item.errorCode === "transport_permission_denied"
165
- ? "live offer blocked by sender permissions; message remains queued; run acc doctor in the sending session"
166
+ ? "live offer blocked by sender permissions; message remains queued; retry the same command "
167
+ + "through approved execution"
168
+ + (typeof message.clientMessageId === "string"
169
+ ? ` with --client-message-id ${shellQuote(message.clientMessageId)}` : " with its original retry key")
166
170
  : item.errorCode === "no_live_transport"
167
171
  ? `${item.recipientParticipantId} has no live transport`
168
172
  + `${item.nativeReasonCode ? ` (${describeNativeReason(item.nativeReasonCode,
@@ -97,6 +97,8 @@ export async function updateNativeSessions(adapters, { service, status, root, no
97
97
  }
98
98
 
99
99
  const failures = {
100
+ transport_permission_denied: "this command cannot access the local delivery channel; "
101
+ + "check the sender's command permissions",
100
102
  client_process_unknown: "the client process could not be identified; start a new client session",
101
103
  handshake_failed: "the session handshake failed; check the client's integration/channel setup",
102
104
  handshake_timeout: "the session handshake timed out; check the local channel and retry on the next turn",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agents-can-communicate/core",
3
- "version": "0.10.3",
3
+ "version": "0.10.4",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "exports": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agents-can-communicate/delivery-router",
3
- "version": "0.10.3",
3
+ "version": "0.10.4",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "exports": {
@@ -1,6 +1,7 @@
1
1
  import { validateNativeHandshake } from "@agents-can-communicate/adapter-sdk";
2
2
 
3
3
  const MAX_REFRESH_LEASE_MS = 120_000;
4
+ const refused = (errorCode = "recipient_unavailable") => ({ refreshed: false, errorCode });
4
5
 
5
6
  function sameBinding(current, expected, leaseUntil) {
6
7
  return current !== null && current !== undefined
@@ -15,24 +16,26 @@ function sameBinding(current, expected, leaseUntil) {
15
16
 
16
17
  export async function refreshExpiredBinding({ service, adapter, binding, runtimeDir,
17
18
  clock, timeoutMs = 750 }) {
18
- if (typeof adapter?.refreshNativeSession !== "function") return false;
19
+ if (typeof adapter?.refreshNativeSession !== "function") return refused();
19
20
  const now = Date.parse(clock.now());
20
- if (!Number.isFinite(now)) return false;
21
+ if (!Number.isFinite(now)) return refused();
21
22
  try {
22
23
  const handshake = await adapter.refreshNativeSession({ binding, runtimeDir, timeoutMs });
23
24
  const validated = validateNativeHandshake(adapter, {
24
25
  clientVersion: binding.clientVersion, handshake,
25
26
  });
26
- if (!validated.ok || validated.opaqueEndpointRef !== binding.opaqueEndpointRef) return false;
27
+ if (!validated.ok) return refused(validated.reasonCode === "transport_permission_denied"
28
+ ? "transport_permission_denied" : "recipient_unavailable");
29
+ if (validated.opaqueEndpointRef !== binding.opaqueEndpointRef) return refused();
27
30
  const requestedLease = Date.parse(validated.leaseUntil);
28
- if (!Number.isFinite(requestedLease) || requestedLease <= now) return false;
31
+ if (!Number.isFinite(requestedLease) || requestedLease <= now) return refused();
29
32
  const leaseUntil = new Date(Math.min(requestedLease,
30
33
  now + MAX_REFRESH_LEASE_MS)).toISOString();
31
34
  await service.refreshDeliveryBinding({ sessionId: binding.sessionId,
32
35
  generation: binding.generation, leaseUntil });
33
36
  const current = await service.store?.ephemeral?.get("deliveryBinding", binding.sessionId);
34
- return sameBinding(current, binding, leaseUntil);
37
+ return sameBinding(current, binding, leaseUntil) ? { refreshed: true, errorCode: null } : refused();
35
38
  } catch {
36
- return false;
39
+ return refused();
37
40
  }
38
41
  }
@@ -132,7 +132,12 @@ export function createDeliveryRouter({ service, adapters, clock, readLivePolicy,
132
132
  if (Date.parse(binding.leaseUntil) <= Date.parse(now)) {
133
133
  const refreshed = await refreshExpiredBinding({ service, adapter, binding,
134
134
  runtimeDir: service.store?.root, clock });
135
- if (!refreshed) return durable(participantId, "recipient_unavailable");
135
+ if (!refreshed.refreshed) {
136
+ if (refreshed.errorCode === "transport_permission_denied") {
137
+ await recordFailure(binding, message, participantId, "live-adapter", refreshed.errorCode);
138
+ }
139
+ return durable(participantId, refreshed.errorCode);
140
+ }
136
141
  const current = (await service.listDeliveryBindings({
137
142
  participantId, now: clock.now() })).filter(item => item.sessionId === binding.sessionId
138
143
  && item.generation === binding.generation
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agents-can-communicate/hook-runner",
3
- "version": "0.10.3",
3
+ "version": "0.10.4",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "exports": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agents-can-communicate/installer",
3
- "version": "0.10.3",
3
+ "version": "0.10.4",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "exports": {
@@ -10,6 +10,7 @@ const REASONS = Object.freeze({
10
10
  known_bad_version: "this client version has a known native delivery failure",
11
11
  native_endpoint_unavailable: "the client's local delivery service is unavailable",
12
12
  native_session_unavailable: "the local delivery service has no loaded client session",
13
+ transport_permission_denied: "the calling process lacks permission to access the local delivery channel",
13
14
  feature_probe_failed: "the native protocol probe did not succeed",
14
15
  probe_timeout: "the native protocol probe timed out",
15
16
  probe_version_mismatch: "the local service and CLI versions differ",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agents-can-communicate/mcp-server",
3
- "version": "0.10.3",
3
+ "version": "0.10.4",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "exports": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agents-can-communicate/protocol",
3
- "version": "0.10.3",
3
+ "version": "0.10.4",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "exports": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agents-can-communicate/storage-filesystem",
3
- "version": "0.10.3",
3
+ "version": "0.10.4",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "exports": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agents-can-communicate",
3
- "version": "0.10.3",
3
+ "version": "0.10.4",
4
4
  "accManagedUpdateProtocol": 2,
5
5
  "accStoreVersion": 7,
6
6
  "type": "module",