agents-can-communicate 0.8.1 → 0.8.3

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 (54) hide show
  1. package/docs/ADAPTER_AUTHORING.md +14 -0
  2. package/docs/CAPABILITIES.md +1 -1
  3. package/docs/CLI.md +16 -0
  4. package/docs/CONFIGURATION.md +73 -8
  5. package/docs/GETTING_STARTED.md +6 -3
  6. package/docs/HOW_IT_WORKS.md +3 -1
  7. package/docs/SECURITY_MODEL.md +12 -1
  8. package/docs/TROUBLESHOOTING.md +32 -5
  9. package/docs/UPGRADING.md +40 -1
  10. package/node_modules/@agents-can-communicate/adapter-antigravity/package.json +1 -1
  11. package/node_modules/@agents-can-communicate/adapter-antigravity/plugin/plugin.json +1 -1
  12. package/node_modules/@agents-can-communicate/adapter-antigravity/plugin/skills/acc/SKILL.md +7 -0
  13. package/node_modules/@agents-can-communicate/adapter-antigravity/src/adapter.mjs +3 -1
  14. package/node_modules/@agents-can-communicate/adapter-antigravity/src/allow-rule.mjs +204 -0
  15. package/node_modules/@agents-can-communicate/adapter-antigravity/src/install.mjs +45 -10
  16. package/node_modules/@agents-can-communicate/adapter-claude-code/package.json +1 -1
  17. package/node_modules/@agents-can-communicate/adapter-claude-code/src/adapter.mjs +9 -0
  18. package/node_modules/@agents-can-communicate/adapter-claude-code/src/inbox-endpoint.mjs +3 -1
  19. package/node_modules/@agents-can-communicate/adapter-claude-code/src/inbox-observed-directories.mjs +62 -0
  20. package/node_modules/@agents-can-communicate/adapter-claude-code/src/inbox-socket-directories.mjs +41 -0
  21. package/node_modules/@agents-can-communicate/adapter-codex/package.json +1 -1
  22. package/node_modules/@agents-can-communicate/adapter-codex/src/adapter.mjs +3 -0
  23. package/node_modules/@agents-can-communicate/adapter-codex/src/install.mjs +5 -4
  24. package/node_modules/@agents-can-communicate/adapter-codex/src/live-permissions.mjs +64 -12
  25. package/node_modules/@agents-can-communicate/adapter-gemini-cli/extension/gemini-extension.json +1 -1
  26. package/node_modules/@agents-can-communicate/adapter-gemini-cli/package.json +1 -1
  27. package/node_modules/@agents-can-communicate/adapter-grok/package.json +1 -1
  28. package/node_modules/@agents-can-communicate/adapter-kimi/package.json +1 -1
  29. package/node_modules/@agents-can-communicate/adapter-sdk/package.json +1 -1
  30. package/node_modules/@agents-can-communicate/adapter-sdk/src/capabilities.mjs +7 -0
  31. package/node_modules/@agents-can-communicate/adapter-sdk/src/hook-shim.mjs +16 -2
  32. package/node_modules/@agents-can-communicate/adapter-sdk/src/index.mjs +1 -1
  33. package/node_modules/@agents-can-communicate/cli/package.json +1 -1
  34. package/node_modules/@agents-can-communicate/cli/src/doctor-command.mjs +9 -4
  35. package/node_modules/@agents-can-communicate/cli/src/install-command.mjs +11 -4
  36. package/node_modules/@agents-can-communicate/cli/src/main.mjs +44 -6
  37. package/node_modules/@agents-can-communicate/cli/src/managed-runtime/download.mjs +3 -1
  38. package/node_modules/@agents-can-communicate/cli/src/native-session-diagnostics.mjs +20 -10
  39. package/node_modules/@agents-can-communicate/core/package.json +1 -1
  40. package/node_modules/@agents-can-communicate/core/src/status.mjs +12 -0
  41. package/node_modules/@agents-can-communicate/delivery-router/package.json +1 -1
  42. package/node_modules/@agents-can-communicate/hook-runner/package.json +1 -1
  43. package/node_modules/@agents-can-communicate/hook-runner/src/runner.mjs +9 -0
  44. package/node_modules/@agents-can-communicate/installer/package.json +1 -1
  45. package/node_modules/@agents-can-communicate/installer/src/detect.mjs +10 -3
  46. package/node_modules/@agents-can-communicate/installer/src/index.mjs +1 -0
  47. package/node_modules/@agents-can-communicate/installer/src/receiver-sockets.mjs +19 -0
  48. package/node_modules/@agents-can-communicate/mcp-server/package.json +1 -1
  49. package/node_modules/@agents-can-communicate/protocol/package.json +1 -1
  50. package/node_modules/@agents-can-communicate/storage-filesystem/package.json +1 -1
  51. package/node_modules/@agents-can-communicate/storage-filesystem/src/atomic-json.mjs +30 -4
  52. package/node_modules/@agents-can-communicate/storage-filesystem/src/safe-directory.mjs +56 -25
  53. package/node_modules/@agents-can-communicate/storage-filesystem/src/store.mjs +6 -2
  54. package/package.json +1 -1
@@ -29,6 +29,7 @@ export function createExampleAdapter() {
29
29
  detect, install, uninstall, doctor,
30
30
  planInstall, // what install would write
31
31
  preflightUninstall, // optional read-only removal validation
32
+ inboundSockets, // optional: socket paths a live sender connects to
32
33
  normalizeHook, // client payload -> normalised event
33
34
  renderContext, // SyncResult -> text
34
35
  renderContextResult, // text + ids of complete rendered groups
@@ -227,6 +228,19 @@ the refreshed lease. Refresh is not a heartbeat and cannot extend presence beyon
227
228
  its own expiry. Optional `retireNativeSession()` cleans adapter-owned endpoint state
228
229
  after confirmed core retirement; it must remain bounded and fail open for hooks.
229
230
 
231
+ An adapter whose `offerMessage()` connects to a Unix socket in the recipient's session
232
+ declares where those sockets live with the optional `inboundSockets(context)`. It returns
233
+ absolute paths, directories or sockets, derived from `context.env`, `context.platform` and
234
+ the client homes in the context. It runs synchronously, once per install, doctor or refresh
235
+ command, and must never throw. It may read the adapter's own records under
236
+ `context.stateRoot` when a session can bind outside what the environment implies. Claude Code
237
+ reads its endpoint records there, and keeps each read bounded. The installer
238
+ composes every known adapter's declaration into `context.receiverSockets`. An adapter whose
239
+ client sandboxes the sender's shell allows exactly that list, and reports its outgoing
240
+ permissions configured only while every entry is allowed. It never names another vendor's
241
+ paths itself. Codex does this in its permission profile. Declare a new receiving transport
242
+ here, or every Codex sender gets `transport_permission_denied` against it.
243
+
230
244
  A package-shipped passing Codex installed-hook capture additionally references its
231
245
  complete real product matrix through the selected provenance record's
232
246
  `productEvidence: { fixture, sha256 }`. Package verification checks the actual file,
@@ -71,7 +71,7 @@ The limitations belong next to the adapters they affect:
71
71
 
72
72
  | Adapter | Exact limitation and evidence |
73
73
  |---|---|
74
- | Antigravity CLI | 1.2.7 on darwin-arm64, captured in print mode, and every later release by the forward rule. Only `SessionStart`, `PreInvocation`, `PostInvocation` and `Stop` load; `SessionEnd`, `PreToolUse` and `PostToolUse` are accepted into the config file and silently dropped, so there is no tool guard and no session-end deregistration - a session goes offline by presence age or an explicit `acc finish`. Payloads carry no `hook_event_name`, so each registered command passes its own event name. The end-of-turn `Stop` continuation reaches the model and is a bounded nudge, not a gate: ACC continues a turn at most once and fails open, and the client caps consecutive continuations itself (vendor 1.1.9). `agy agentapi send-message` can wake an idle session - captured - but only with that session's language-server address and CSRF token, which exist in the agent's own shell and in no hook. ACC does not take that token, so live push and reply routing are false. A peer message that arrives while the model writes its last answer is carried by the `Stop` continuation instead. A write that parses can register nothing, so install and doctor read `agy -p "/hooks"` back instead of trusting the file. Live push (1.2.7, darwin-arm64, TUI only, experimental, recorded opt-in) runs through a relay the agent starts once per conversation from its own shell - the only process holding the session endpoint - after ACC's context asks it to, up to three times while none is serving; the operator approves that command at the client's permission prompt. An idle session wakes; a busy one sees the message after its running answer, or at the next model invocation when the turn waits on a tool. Print mode and the first session in a folder trusted at that launch get no relay. |
74
+ | Antigravity CLI | 1.2.7 on darwin-arm64, captured in print mode, and every later release by the forward rule. Only `SessionStart`, `PreInvocation`, `PostInvocation` and `Stop` load; `SessionEnd`, `PreToolUse` and `PostToolUse` are accepted into the config file and silently dropped, so there is no tool guard and no session-end deregistration - a session goes offline by presence age or an explicit `acc finish`. Payloads carry no `hook_event_name`, so each registered command passes its own event name. The end-of-turn `Stop` continuation reaches the model and is a bounded nudge, not a gate: ACC continues a turn at most once and fails open, and the client caps consecutive continuations itself (vendor 1.1.9). `agy agentapi send-message` can wake an idle session - captured - but only with that session's language-server address and CSRF token, which exist in the agent's own shell and in no hook. ACC does not take that token, so live push and reply routing are false. A peer message that arrives while the model writes its last answer is carried by the `Stop` continuation instead. A write that parses can register nothing, so install and doctor read `agy -p "/hooks"` back instead of trusting the file. Live push (1.2.7, darwin-arm64, TUI only, experimental, recorded opt-in) runs through a relay the agent starts once per conversation from its own shell - the only process holding the session endpoint - after ACC's context asks it to, up to three times while none is serving; the operator approves that command at the client's permission prompt. An idle session wakes; a busy one sees the message after its running answer, or at the next model invocation when the turn waits on a tool. Print mode and the first session in a folder trusted at that launch get no relay. The client asks before every shell command (1.2.12), so install always adds the prefix rule `command(<wrapper>)` to the client's settings to let the woken agent's ACC commands run without that prompt. A woken session answered with no prompt under that rule on 1.2.12 (2026-09-28); print mode with it has not been captured. |
75
75
  | Codex | Next-turn context, captured on 0.147.0, requires plugin trust. The observed stock 0.153.4 upgrade from ACC 0.3.1 to 0.4 required fresh review of five modified hook definitions; a subsequent restart retained all five active (activation evidence, not new event certification). LocalDaemon native delivery was captured through the installed package on 0.152.1 and 0.153.4, darwin-arm64; minimum 0.152.1, recorded opt-in, current feature probe and exact thread/cwd/process/version/protocol checks are required. Ordinary launch preserves the receiver workspace without ACC arguments or daemon ownership. Embedded or unreachable sessions keep their inbox. Native `replyRoute` remains false. |
76
76
  | Claude Code | 2.1.233 next-turn delivery waits for the next user prompt. From 2.1.282, captured on darwin-arm64 and applied on every platform whose inbox is a Unix socket, `delivery.livePush` is a live capability behind the native contract (experimental, off until opted in). ACC wakes the session through the inbox socket that Claude Code opens for every session, with one line of fixed ACC text and the message id. An idle session starts a turn. A busy session takes the wake between two tool calls. Each delivered wake fires `UserPromptSubmit`, so the next-turn hook shows the body and records the receipt `offered` via `next-turn`. The model answers with `acc reply`, so `delivery.replyRoute` stays false. A session in `bypassPermissions` mode holds each wake for approval unless `crossSessionInbound` is `accept`, and `refuse` drops wakes. Native Windows keeps next-turn delivery: its inbox is a named pipe that requires an auth line, and it is uncaptured. Linux is uncaptured. |
77
77
  | Gemini CLI | Package-shipped next-turn certification starts at 0.57.0. Its TUI has no captured external wake or queue interface and `--acp` changes launch ownership, so native delivery is fallback-only; live push and reply routing remain false. |
package/docs/CLI.md CHANGED
@@ -104,6 +104,17 @@ got. Two sessions of one participant show the same numbers, so read the workspac
104
104
  `<n> offered, not retrieved` when that number is above zero. Offered is not read: a
105
105
  transport took the bytes, and the model may or may not have looked.
106
106
 
107
+ A session is `stale` when no turn has sent a heartbeat for three heartbeat cadences. Each
108
+ session row says what a message to such a session meets. `processTracked` is true when ACC
109
+ knows the client process. A dead process makes the session `offline`, so a stale session with
110
+ a tracked process is idle. `liveDelivery` is true when the session has a live delivery
111
+ binding, and `liveLeaseCurrent` says whether the binding's lease is still valid. A stale
112
+ session wakes on send when the lease is valid, or when its adapter can verify the receiver
113
+ again: the router does that for a lapsed lease before it offers. The text line counts stale
114
+ sessions by that result:
115
+ `5 live (2 idle, wake on send; 1 idle until next turn; 1 idle, inbox only)`. `not answering`
116
+ is kept for a stale session whose process ACC does not know.
117
+
107
118
  A participant with no open session is left out, unless messages still wait for it: queued,
108
119
  or offered by a live transport and not yet shown again, so that its next turn would show
109
120
  them. A message the next-turn hook already showed does not wait, since nothing will reach the
@@ -373,6 +384,11 @@ Your existing npm or Homebrew command and shell profiles remain unchanged.
373
384
  An older consent that covered service start receives one expanded choice for the download.
374
385
  An accepted or declined answer persists. To change a declined answer, use
375
386
  `acc install --adapter codex --delivery actionable` (or `all` for that policy).
387
+ Installing the Antigravity adapter always adds one prefix rule for ACC's wrapper to
388
+ `~/.gemini/antigravity-cli/settings.json`, so ACC's commands run there without an approval
389
+ prompt; nothing asks about it and the delivery policy does not change it. Uninstall removes
390
+ exactly that rule. See
391
+ [Antigravity CLI command approval](CONFIGURATION.md#antigravity-cli-command-approval).
376
392
  Explicit `--delivery actionable|all` also approves a required download on supported clients.
377
393
  Dry runs, delivery off, doctor, automatic refresh, and message delivery do not download
378
394
  Codex or start its service. Uninstall does not remove Codex or stop the shared vendor daemon.
@@ -131,8 +131,9 @@ The allowed values are `off`, `actionable`, and `all`; the default is `off`. Thi
131
131
  does not belong in `acc.workspace.json`, where a pull request could opt someone else into
132
132
  spending a turn. It also cannot create a capability. Every adapter reads live-delivery
133
133
  consent from this installation record, and only from it. Exact-version evidence governs
134
- ordinary hook features. Claude Code live delivery separately requires macOS arm64, version
135
- 2.1.282 or newer, and a current feature probe before installation applies the requested
134
+ ordinary hook features. Claude Code live delivery separately requires version 2.1.282 or
135
+ newer on macOS or Linux (on native Windows its inbox is a named pipe, so delivery stays
136
+ next-turn), and a current feature probe before installation applies the requested
136
137
  policy. If those install-time checks fail, effective policy remains `off` and the installer
137
138
  reports next-turn or inbox fallback. Each later session must also pass its own
138
139
  generation-bound handshake. A failed session handshake clears or refuses that binding and
@@ -146,7 +147,7 @@ each wake for your approval unless its `crossSessionInbound` setting is `accept`
146
147
  never overrides them. See
147
148
  [held or dropped wakes](TROUBLESHOOTING.md#a-claude-code-session-holds-or-drops-acc-wakes).
148
149
 
149
- Codex LocalDaemon delivery separately requires macOS arm64, Codex 0.152.1 or newer, a
150
+ Codex LocalDaemon delivery separately requires Codex 0.152.1 or newer, a
150
151
  current feature probe, and exact thread, canonical cwd, process, version and protocol
151
152
  checks. On Codex 0.154.0 or newer, explicit complete setup can download a missing matching
152
153
  standalone package and prepare a definitely absent service. The setup choice names the
@@ -160,15 +161,47 @@ Unavailable or ineligible sessions retain durable inbox fallback. Use
160
161
  ### Codex outgoing permissions
161
162
 
162
163
  Receiving a native message and sending from the agent's sandbox are separate operations.
163
- On Codex 0.153.4 or newer on macOS arm64, live opt-in also configures outgoing access
164
+ On Codex 0.153.4 or newer, live opt-in also configures outgoing access
164
165
  for default workspace permissions. This happens even when the daemon is not yet available.
165
166
  Installation, its preview, and the interactive consent question disclose this change.
166
167
 
167
168
  ACC selects a permission profile named `acc-workspace`, extending `:workspace`. It grants
168
- write access to the ACC state directory and allows only the local ACC channel directory
169
- (`/tmp/acc-ch-<uid>`) and the Codex home control socket. The profile enables networking
170
- through `features.network_proxy = true`, with no external domains allowed. The proxy and
171
- socket grants must stay together: enabling networking without the proxy changes its scope.
169
+ write access to the ACC state directory and allows only the Unix sockets ACC delivers to.
170
+ Each receiving adapter declares its own, and the profile lists all of them even when an
171
+ install names Codex alone:
172
+
173
+ | Receiver | Allowed path |
174
+ |---|---|
175
+ | Codex | the control socket, `<CODEX_HOME>/app-server-control/app-server-control.sock` |
176
+ | Antigravity CLI relay | the ACC channel directory, `/tmp/acc-ch-<uid>` on macOS |
177
+ | Claude Code inbox | `/tmp/cc-socks`, `/tmp/cc-socks-<uid>`, `$XDG_RUNTIME_DIR/cc-socks` or else `$CLAUDE_CODE_TMPDIR/cc-socks` when set, `/run/user/<uid>/cc-socks` on Linux, and the directory of every Claude Code inbox ACC has bound on this machine |
178
+
179
+ ACC writes each path with its existing parent resolved, so on macOS the entries read
180
+ `/private/tmp/...`. Codex resolves a granted path only if it exists when Codex builds the
181
+ sandbox policy, and the sandbox compares resolved paths: a `/tmp/cc-socks-<uid>` entry
182
+ written before that directory existed never matched. A socket that is a link, such as the
183
+ Codex control socket, keeps its own name.
184
+
185
+ A command in the Codex sandbox can therefore connect to any of your Claude Code sessions'
186
+ inbox sockets. Claude Code's own inbound settings still decide what a received frame may do,
187
+ and ACC checks Claude Code's session registry before each wake it sends.
188
+
189
+ The profile enables networking through `features.network_proxy = true`, with no external
190
+ domains allowed. The proxy and socket grants must stay together: enabling networking
191
+ without the proxy changes its scope.
192
+
193
+ Doctor reports outgoing delivery as configured only while the profile allows every socket
194
+ above. A profile that ACC 0.8.0 or 0.8.1 wrote lacks the Claude Code inbox directories;
195
+ doctor names the missing paths and `acc install --adapter codex`. That command and
196
+ `acc update` rewrite ACC's own profile under the recorded delivery policy, `off` included.
197
+ Start a new Codex session afterwards.
198
+
199
+ The environment variables above are read from the process that runs `acc install` or
200
+ `acc doctor`. A Claude Code session started with its own `CLAUDE_CODE_TMPDIR` or
201
+ `XDG_RUNTIME_DIR` binds its inbox elsewhere. ACC learns that directory when its hooks first
202
+ bind the session, from its own endpoint record. From then on doctor names the directory
203
+ until `acc install --adapter codex` or `acc update` adds it. Only a directory named as Claude
204
+ Code names its inboxes, `cc-socks` or `cc-socks-<uid>`, is taken from a record.
172
205
 
173
206
  An existing legacy workspace-write configuration is migrated only when its sole writable
174
207
  root is ACC's state directory. Custom permission/configuration profiles, additional roots,
@@ -184,6 +217,38 @@ generated permission component is unchanged.
184
217
  If any component was edited or another setting depends on it, ACC preserves the entire
185
218
  bundle and reports it for review. Plugin registration can still be removed independently.
186
219
 
220
+ ### Antigravity CLI command approval
221
+
222
+ Antigravity CLI asks for approval before each shell command, ACC's included. A session that
223
+ live delivery wakes while you are away would therefore stop at the agent's first ACC command,
224
+ and the peer would get no answer until someone approved it. So when `acc install` installs
225
+ the Antigravity adapter, it always adds one rule to the client's own settings:
226
+
227
+ ```text
228
+ ~/.gemini/antigravity-cli/settings.json
229
+ permissions.allow: command(<home>/.gemini/config/acc/acc-cli.sh)
230
+ ```
231
+
232
+ A one-word rule is a prefix: every command that starts with that path runs without asking.
233
+ The agent then runs every ACC command without asking you, including one a peer's message
234
+ prompts; every other command still asks. Install does not ask about the rule, and the
235
+ delivery policy does not change it: it is part of ACC's Antigravity integration, as its hooks
236
+ are.
237
+
238
+ ACC keeps every other key and rule in the file, leaves a rule you already had as yours, and
239
+ records what it added in its own data home. `acc uninstall` removes every copy of that rule (one
240
+ in the same words cannot be told from ACC's), and a
241
+ container or file ACC created only if nothing else is in it. A rule removed by hand comes back
242
+ at the next `acc install` or automatic refresh; uninstall is what keeps it out. A file that is
243
+ not valid JSON, or whose `permissions.allow` is not a list, is left as it is and reported. The
244
+ client reads the file when it starts, so restart `agy` after install.
245
+
246
+ The rule matches only a command whose first word is the path without quotes, so ACC's skill
247
+ for this client writes the path without quotes. When your home path contains a space or a
248
+ shell metacharacter, the path needs quotes, no rule can match it, and ACC adds none.
249
+ `acc doctor` reports the rule as present, absent, or unable to apply, and asks for
250
+ `acc install --adapter antigravity` when ACC's rule is missing.
251
+
187
252
  ## Override local paths and identity
188
253
 
189
254
  Nothing in `acc.workspace.json` says where state is stored, and nothing there can — that is
@@ -43,7 +43,7 @@ session after that review; changed hook definitions may require trust again.
43
43
 
44
44
  For Codex live delivery, follow the outgoing-permission and local-service instructions
45
45
  from install/doctor, then start a new session. Default workspace permissions can be
46
- configured automatically on Codex 0.153.4 or newer on macOS arm64. Custom settings are
46
+ configured automatically on Codex 0.153.4 or newer. Custom settings are
47
47
  preserved; see [outgoing permissions](CONFIGURATION.md#codex-outgoing-permissions).
48
48
  With legacy sandbox settings, the named ACC state directory must also be in
49
49
  `sandbox_workspace_write.writable_roots` in the config the installer identifies.
@@ -58,7 +58,7 @@ hooks wherever the client is at or after a captured version.
58
58
  An interactive install asks one default-No question for all selected clients that need a
59
59
  choice. Use `--delivery actionable|all` for explicit noninteractive consent. Use
60
60
  `--delivery off` to disable incoming automatic requests. A dry run does not ask, write
61
- configuration, download Codex, or start a service. On macOS arm64 with Codex 0.154.0 or newer,
61
+ configuration, download Codex, or start a service. On macOS or Linux with Codex 0.154.0 or newer,
62
62
  the choice includes downloading a missing standalone package from OpenAI. ACC selects the
63
63
  matching version and keeps your existing npm or Homebrew command and shell profiles.
64
64
  When supported Codex service preparation succeeds, it
@@ -150,7 +150,10 @@ infrastructure, recorded opt-in, and a verified session. Supported explicit setu
150
150
  prepare a missing service. Start the client with your normal command. A loaded daemon thread can receive
151
151
  messages after its terminal exits. Antigravity CLI 1.2.7 and later has one too: with delivery enabled,
152
152
  ACC's context asks the agent to start a relay from its own shell, up to three times while
153
- none is running, and you approve that command at the client's prompt.
153
+ none is running, and you approve that command at the client's prompt. Antigravity also asks
154
+ before each shell command, so install adds a rule that lets ACC's own commands run without
155
+ that prompt, and a woken session can answer while you are away; see
156
+ [Antigravity CLI command approval](CONFIGURATION.md#antigravity-cli-command-approval).
154
157
  [Capabilities](CAPABILITIES.md) explains policy, versions and fallback.
155
158
 
156
159
  Delivery evidence is deliberately narrow: `queued -> offered -> retrieved -> acknowledged`.
@@ -123,7 +123,9 @@ All recipients have the same durable record, but adapters expose different accel
123
123
  endpoint exists only in the agent's own shell, so ACC's context asks the agent once per
124
124
  conversation to start a relay; the user approves that command. The relay keeps the endpoint
125
125
  in memory, wakes an idle session with a fenced peer message, holds one for a busy session,
126
- and ends with the client. It is experimental, off by default, and can spend tokens.
126
+ and ends with the client. It is experimental, off by default, and can spend tokens. The
127
+ woken agent's ACC commands run without the client's approval prompt because install adds
128
+ one allow rule for ACC's wrapper.
127
129
 
128
130
  Grok, generic MCP, unsupported versions and uncaptured platforms use inbox polling.
129
131
  [Capabilities](CAPABILITIES.md) lists evidence and fallback.
@@ -94,12 +94,22 @@ user's: a session in `bypassPermissions` mode holds each wake for approval unles
94
94
  `crossSessionInbound` is `accept`, and `refuse` drops wakes.
95
95
 
96
96
  On captured Codex/macOS configurations, live opt-in also grants outgoing access to ACC's
97
- state and local Unix sockets through a deny-by-default network proxy. Custom permission
97
+ state and local Unix sockets through a deny-by-default network proxy. The sockets are the
98
+ ones ACC delivers to, which include the directories where Claude Code binds each session's
99
+ inbox: a command in the Codex sandbox can connect to those sockets, and Claude Code's own
100
+ inbound settings decide what a received frame may do. Custom permission
98
101
  policies remain user-owned. Selection, proxy and grants are restored together only while
99
102
  unchanged; edited or newly referenced settings remain intact. See
100
103
  [Codex outgoing permissions](CONFIGURATION.md#codex-outgoing-permissions). Configuration
101
104
  readiness does not prove that an active session's overrides permit a live offer.
102
105
 
106
+ Antigravity CLI asks before each shell command. Installing its adapter adds one prefix rule
107
+ to that client's `permissions.allow`, for ACC's own wrapper path only. The agent then runs
108
+ every ACC command without a prompt, including one that a peer's message prompts. Every other
109
+ command still asks. ACC records the one rule it added and uninstall removes only that rule.
110
+ It never writes a settings file that it cannot parse. See
111
+ [Antigravity CLI command approval](CONFIGURATION.md#antigravity-cli-command-approval).
112
+
103
113
  ## Claims
104
114
 
105
115
  CLI claims default to advisory. Guarded enforcement must be requested explicitly and
@@ -175,6 +185,7 @@ caller chooses to send. The sender remains responsible for that content.
175
185
  | Symlink or traversal escapes a managed root | Per-segment containment, no-follow reads, closed config schema | Host filesystem compromise is out of scope |
176
186
  | Claim denial of service | Leases, visible owner, explicit authority release | Deliberate abuse by a trusted local peer is social |
177
187
  | Installer removes user configuration | Content-hashed ownership and byte comparison | User must remove modified leftovers manually |
188
+ | Peer message leads an Antigravity agent to run ACC commands unasked | Allow rule scoped to ACC's wrapper; every other command still asks; uninstall removes it | A peer can still prompt ACC actions, such as a reply or a claim, that nobody approves one by one |
178
189
  | False delivery claim | Record-first order, transport-owned `offered`, recipient-owned retrieval/ack, mutation tests | Crash after offer before commit can cause duplicate display |
179
190
  | Native endpoint leaks | Ephemeral opaque reference, user-only local endpoint, status redaction | Same-user local processes are outside the trust boundary |
180
191
  | MCP client impersonates another session | Identity fixed by launch env, closed tool schemas | A compromised launch config already controls that client |
@@ -138,8 +138,16 @@ client-owned hook and trust review.
138
138
  If a reply stays queued with `transport_permission_denied`, the sender's permissions
139
139
  blocked local transport. This is distinct from an unavailable recipient. The message was
140
140
  recorded successfully; the error does not mean it was read or acknowledged.
141
- On Codex 0.153.4 or newer on macOS arm64, rerun `acc install --adapter codex` with existing
141
+ On Codex 0.153.4 or newer, rerun `acc install --adapter codex` with existing
142
142
  live consent. Add `--delivery actionable` when new consent is required, then start a new session.
143
+ With ACC 0.8.0 or 0.8.1, every live offer from a Codex session to Claude Code failed this way:
144
+ the Codex profile did not allow Claude Code's inbox directories, and doctor still reported it
145
+ configured. Doctor now names the missing paths. `acc install --adapter codex` or `acc update`
146
+ rewrites the profile; start a new Codex session so it runs under the new one.
147
+ A Claude Code session started with its own `CLAUDE_CODE_TMPDIR` or `XDG_RUNTIME_DIR` binds its
148
+ inbox outside the directories your shell's environment implies. After ACC first binds that
149
+ session, doctor names its directory. Run `acc install --adapter codex` again, then start a new
150
+ Codex session.
143
151
  Doctor's `outgoingDelivery` reports the installed permission configuration separately
144
152
  from `nativeDelivery.runtime`: an active receiving channel does not establish outgoing
145
153
  access. Custom policies and active-session overrides remain unverified; inspect the config
@@ -301,10 +309,11 @@ agy -p "/skills" --output-format json
301
309
 
302
310
  An agent that receives a peer message in print mode (`agy -p`) cannot answer it on its own.
303
311
  It forms the right `acc reply` command from the skill, and print mode denies every command
304
- because it cannot ask for approval; the client says so on stderr. The TUI asks instead.
305
- ACC does not grant itself that permission. To let headless agents answer, allow the ACC
306
- command yourself under `permissions.allow` in the client's settings, in the `command(...)`
307
- form its message names.
312
+ because it cannot ask for approval; the client says so on stderr and names
313
+ `permissions.allow` in its settings. The TUI asks instead. The one rule ACC's install adds
314
+ there is described in
315
+ [Antigravity CLI command approval](CONFIGURATION.md#antigravity-cli-command-approval); it was
316
+ captured in the TUI, and print mode with it has not been captured.
308
317
 
309
318
  ## Antigravity never wakes while idle
310
319
 
@@ -322,6 +331,24 @@ Live delivery needs four things, and `acc doctor` shows each:
322
331
  active. A relay ends with its client; nothing is left running after the TUI exits or after
323
332
  `acc uninstall`.
324
333
 
334
+ ## Antigravity wakes and never answers
335
+
336
+ A woken session that stops at an approval prompt answers only once someone approves its
337
+ first ACC command. `acc doctor` prints an `Antigravity CLI inbound:` line with what it found in
338
+ `~/.gemini/antigravity-cli/settings.json`:
339
+
340
+ - **No rule.** Each ACC command waits for approval. ACC's install writes the rule, so it was
341
+ removed since or the install did not reach this file: run `acc install --adapter antigravity`,
342
+ then restart `agy`. Doctor lists this under what to run next.
343
+ - **The rule is present.** The client reads it at startup, so a session started before it was
344
+ added still asks; restart `agy`. If a prompt still appears, check the command the agent
345
+ ran: a path in quotes, or a command wrapped in `sh -c`, matches no rule.
346
+ - **The rule cannot apply.** The path to ACC's wrapper contains a space or a shell
347
+ metacharacter, so it needs quotes, and a quoted first word matches no rule. ACC adds no
348
+ rule and approvals stay manual.
349
+ - **The file cannot be read.** ACC leaves an invalid `settings.json` as it is. Repair it, then
350
+ run `acc install --adapter antigravity` again.
351
+
325
352
  ## Grok shows no injected message
326
353
 
327
354
  Grok discards UserPromptSubmit context. On the observed 1.0.24 client, run public
package/docs/UPGRADING.md CHANGED
@@ -1,5 +1,44 @@
1
1
  # Upgrading to 0.5.0
2
2
 
3
+ ## From 0.8.x
4
+
5
+ Update as usual:
6
+
7
+ ```bash
8
+ acc update
9
+ acc version
10
+ acc doctor
11
+ ```
12
+
13
+ The update refreshes every installed integration with its recorded policy. Two of them change
14
+ a client's own settings:
15
+
16
+ - **Codex.** ACC's permission profile in `~/.codex/config.toml` also allows the Claude Code
17
+ inbox socket directories, so a Codex session can wake a Claude Code session live. Grants are
18
+ written in their resolved form, such as `/private/tmp/cc-socks` on macOS. A profile you
19
+ edited stays yours: doctor then names the missing grants and `acc install --adapter codex`.
20
+ Start a new Codex session after the update; a session already open keeps its old policy.
21
+ - **Antigravity CLI.** The install adds `command(<wrapper>)` to `permissions.allow` in
22
+ `~/.gemini/antigravity-cli/settings.json`, and the skill calls the wrapper without quotes, so
23
+ a woken session runs its ACC commands without an approval prompt. Restart `agy`: it reads
24
+ the file only at startup. `acc uninstall` removes every copy of that rule.
25
+
26
+ `acc doctor` and `acc status` describe an idle session by what a message sent to it meets
27
+ (`idle, wake on send`, `idle until next turn`, `idle, inbox only`), and each status row carries
28
+ `processTracked`, `liveDelivery` and `liveLeaseCurrent`. This release adds no store event type
29
+ or field, so a store it writes stays readable by 0.8.x.
30
+
31
+ The installed version performs the download. If `acc update` from 0.8.2 or earlier fails with
32
+ `ETARGET` and `No matching version found` right after a release, npm is answering from a
33
+ package document it cached before the registry listed the release. Run the update so that
34
+ npm asks the registry again:
35
+
36
+ ```bash
37
+ npm_config_prefer_online=true acc update
38
+ ```
39
+
40
+ From 0.8.3, the download does this itself.
41
+
3
42
  ## From 0.7.x
4
43
 
5
44
  Claude Code live delivery wakes each session through the inbox socket that Claude Code opens
@@ -165,7 +204,7 @@ keeps holding activation across an uninstall until it exits.
165
204
 
166
205
  ## Confirmed client service maintenance
167
206
 
168
- On macOS arm64, `acc update` can ask once to restart a verified Codex service that holds a
207
+ `acc update` can ask once to restart a verified Codex service that holds a
169
208
  native binding blocking activation, or that is serving a version outside the captured
170
209
  native-delivery contract. A serving version that satisfies that contract is left alone,
171
210
  even when the installed CLI has moved to a newer build. The captured maintenance contract
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agents-can-communicate/adapter-antigravity",
3
- "version": "0.8.1",
3
+ "version": "0.8.3",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "exports": {
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "name": "acc",
3
- "version": "0.8.1",
3
+ "version": "0.8.3",
4
4
  "description": "Coordinate this Antigravity CLI session with other AI agent sessions working in the same workspace."
5
5
  }
@@ -41,6 +41,13 @@ work. An MCP connection can have a different participant from the hook session;
41
41
  use inbox/reply only for the participant the message addresses. Do not borrow a
42
42
  peer's ID, read runtime bindings, or improvise credentials.
43
43
 
44
+ ### Run each command as written
45
+
46
+ Start every ACC command with `{{ACC}}` exactly as this skill writes it: add no
47
+ quotes around it and no `sh -c` wrapper. Antigravity CLI decides from a command's
48
+ first word whether it needs approval, and the rule that lets ACC commands run
49
+ without a prompt matches only that form.
50
+
44
51
  ## Start shared work once
45
52
 
46
53
  After understanding the request, publish one concise intent:
@@ -1,4 +1,4 @@
1
- import { defineAdapter, projectContext, projectContextResult }
1
+ import { channelSocketDirectory, defineAdapter, projectContext, projectContextResult }
2
2
  from "@agents-can-communicate/adapter-sdk";
3
3
  import certification from "../certification.json" with { type: "json" };
4
4
 
@@ -82,6 +82,8 @@ export function createAntigravityAdapter() {
82
82
  },
83
83
  probeNativeDelivery, planNativeActivation, bindNativeSession, refreshNativeSession,
84
84
  offerMessage,
85
+ // The relay binds in ACC's own channel directory; a sandboxed sender is allowed it.
86
+ inboundSockets: () => [channelSocketDirectory()],
85
87
  deliveryFallback: { diagnostic:
86
88
  `Antigravity CLI next-turn and live delivery are certified from ${ANTIGRAVITY_CLI_VERSION} `
87
89
  + "onward; live delivery also needs the agent to start ACC's relay once per conversation, "
@@ -0,0 +1,204 @@
1
+ import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
2
+ import path from "node:path";
3
+
4
+ import { blankJson, isShellWord, removeIfEmpty, writeForeignJson }
5
+ from "@agents-can-communicate/adapter-sdk";
6
+
7
+ /**
8
+ * The one allow rule ACC may add to Antigravity CLI's settings, with consent.
9
+ *
10
+ * Antigravity CLI asks before every shell command. On 1.2.12 a live push woke an
11
+ * idle session and its first ACC command waited at an approval prompt with
12
+ * nobody there, so the peer never got an answer (issue #214). A rule under
13
+ * `permissions.allow` removes the prompt. What 1.2.12 was captured doing:
14
+ *
15
+ * - The file is `~/.gemini/antigravity-cli/settings.json`, read once at startup.
16
+ * `~/.gemini/settings.json` belongs to Gemini CLI and is never touched here.
17
+ * - A one-word rule is a prefix: `command("<wrapper>")` let `<wrapper> help`
18
+ * run unasked.
19
+ * - A command whose first word is quoted matched no rule at all, quoted or not;
20
+ * quotes around an argument are fine. So the skill names the wrapper bare,
21
+ * and a wrapper path that only stays one word inside quotes cannot be allowed.
22
+ *
23
+ * ACC writes the bare form, `command(<wrapper>)`, on every install of this
24
+ * adapter: the user decided on 2026-09-27 that it is always on, with no
25
+ * question and no dependence on the delivery policy. The file is still the
26
+ * operator's: one that does not parse, or whose `permissions.allow` is not a
27
+ * list, is never rewritten. What ACC added is recorded in its own data home -
28
+ * the rule and the containers it had to create - and uninstall takes back
29
+ * exactly that, and always ends the claim, so the same words added later by
30
+ * the operator stay theirs.
31
+ */
32
+ export const agySettingsPath = home => path.join(home, ".gemini", "antigravity-cli",
33
+ "settings.json");
34
+ export const cliWrapperPath = home => path.join(home, ".gemini", "config", "acc", "acc-cli.sh");
35
+ export const accAllowRule = wrapper => `command(${wrapper})`;
36
+
37
+ // Forms that already allow every command starting with the bare wrapper: the
38
+ // one ACC writes, and the quoted one-word form 1.2.12 was captured matching.
39
+ const allowingForms = wrapper => [accAllowRule(wrapper), `command("${wrapper}")`];
40
+
41
+ // Beside the `created-*` markers, for the same reason: the installer removes the
42
+ // shim directory before uninstall runs. `acc doctor` passes the state root.
43
+ const claimDir = ({ dataHome, stateRoot, home }) => typeof dataHome === "string" && dataHome !== ""
44
+ ? path.join(dataHome, "acc", "adapter-antigravity")
45
+ : typeof stateRoot === "string" && stateRoot !== ""
46
+ ? path.join(stateRoot, "adapter-antigravity")
47
+ : path.join(home, ".gemini", "config", "acc");
48
+ const claimPath = (context, file) => path.join(claimDir(context),
49
+ `allow-rule-${Buffer.from(file).toString("base64url")}`);
50
+
51
+ const isObject = value => value !== null && typeof value === "object" && !Array.isArray(value);
52
+
53
+ async function readClaim(file) {
54
+ try {
55
+ const claim = JSON.parse(await readFile(file, "utf8"));
56
+ return typeof claim?.rule === "string" ? claim : null;
57
+ } catch {
58
+ return null;
59
+ }
60
+ }
61
+
62
+ /** The settings as the client would read them, or why ACC must not edit them. */
63
+ async function readSettings(file) {
64
+ let text;
65
+ try {
66
+ text = await readFile(file, "utf8");
67
+ } catch (error) {
68
+ if (error.code === "ENOENT") return { state: "missing", value: {}, allow: [] };
69
+ return { state: "unreadable", reason: `could not be read (${error.code ?? error.message})` };
70
+ }
71
+ let value;
72
+ try {
73
+ value = JSON.parse(text);
74
+ } catch {
75
+ return { state: "unreadable", reason: "is not valid JSON" };
76
+ }
77
+ if (!isObject(value)) return { state: "unreadable", reason: "does not hold a JSON object" };
78
+ if (value.permissions !== undefined && !isObject(value.permissions)) {
79
+ return { state: "unreadable", reason: "has a permissions value that is not an object" };
80
+ }
81
+ const allow = value.permissions?.allow;
82
+ if (allow !== undefined && !Array.isArray(allow)) {
83
+ return { state: "unreadable", reason: "has a permissions.allow value that is not a list" };
84
+ }
85
+ return { state: "read", value, allow: allow ?? [] };
86
+ }
87
+
88
+ const RESTART = "Antigravity CLI reads it at startup, so a session started before it was "
89
+ + "added still asks until agy restarts";
90
+
91
+ /** What the rule's state is on this machine, in words doctor can print. */
92
+ export async function inspectAllowRule(context) {
93
+ const file = agySettingsPath(context.home);
94
+ const wrapper = cliWrapperPath(context.home);
95
+ const rule = accAllowRule(wrapper);
96
+ const base = { file, wrapper, rule, owned: false };
97
+ if (!isShellWord(wrapper)) {
98
+ return { ...base, state: "unmatchable", diagnostic: "each ACC command waits for approval, "
99
+ + "so a live wake stops at the agent's first ACC command until someone answers the "
100
+ + `approval prompt. No allow rule can remove it here: the wrapper ${wrapper} stays one `
101
+ + "shell word only inside quotes, and Antigravity CLI matches no rule against a command "
102
+ + "whose first word is quoted. ACC adds the prefix rule to permissions.allow in "
103
+ + `${file} when the wrapper path holds only letters, digits and . _ - /` };
104
+ }
105
+ const settings = await readSettings(file);
106
+ if (settings.state === "unreadable") {
107
+ return { ...base, state: "unreadable", diagnostic: `${file} ${settings.reason}, so ACC `
108
+ + "leaves it as it is and cannot tell whether ACC commands wait for approval; a live "
109
+ + "wake stops at the agent's first ACC command while they do. Repair the file, then run "
110
+ + `acc install --adapter antigravity to add ${rule} to permissions.allow` };
111
+ }
112
+ const matched = settings.allow.find(entry => allowingForms(wrapper).includes(entry));
113
+ if (matched !== undefined) {
114
+ const claim = await readClaim(claimPath(context, file));
115
+ const owned = claim?.rule === matched;
116
+ return { ...base, state: "allowed", owned, diagnostic: "ACC commands run without an "
117
+ + `approval prompt: permissions.allow in ${file} holds ${matched}`
118
+ + `${owned ? ", added by ACC" : ""}. ${RESTART}` };
119
+ }
120
+ // Every install writes the rule, so its absence means one did not run here, or
121
+ // the rule was taken out since.
122
+ return { ...base, state: "prompts",
123
+ diagnostic: "each ACC command waits for approval, so a live wake stops at the agent's "
124
+ + "first ACC command until someone answers the approval prompt. ACC's install adds "
125
+ + `${rule} to permissions.allow in ${file}, and it is not there: run acc install `
126
+ + "--adapter antigravity, then restart agy" };
127
+ }
128
+
129
+ async function addRule(context, found) {
130
+ const settings = await readSettings(found.file);
131
+ const claimFile = claimPath(context, found.file);
132
+ // Only what this write creates. A file or container that is already there is
133
+ // the operator's as far as ACC can tell: one ACC made earlier cannot be told
134
+ // from one they put in its place, and carrying the old flags over deleted
135
+ // their file at uninstall (review of #218). The cost is an empty container
136
+ // ACC once made staying after uninstall, never the operator's file going.
137
+ const created = {
138
+ file: settings.state === "missing",
139
+ permissions: settings.value.permissions === undefined,
140
+ allow: settings.value.permissions?.allow === undefined,
141
+ };
142
+ // The claim first: a crash between the two leaves a claim with no rule, which
143
+ // uninstall drops, rather than a rule nobody can tell is ACC's.
144
+ await mkdir(path.dirname(claimFile), { recursive: true });
145
+ await writeFile(claimFile, `${JSON.stringify({ file: found.file, rule: found.rule,
146
+ created })}\n`);
147
+ await writeForeignJson(found.file, { ...settings.value, permissions: {
148
+ ...settings.value.permissions, allow: [...settings.allow, found.rule] } },
149
+ { readFile, writeFile, mkdir });
150
+ }
151
+
152
+ /**
153
+ * Take back the rule ACC recorded adding, and the containers it created if they
154
+ * are empty. Every copy of it goes: one in the same words cannot be told from
155
+ * ACC's, and the client itself has duplicated grants across reloads, so taking
156
+ * one back left ACC commands running unasked after uninstall (review of #218).
157
+ * Anything else in the file stays, and a rule the operator had before ACC came
158
+ * was never claimed.
159
+ */
160
+ export async function withdrawAllowRule(context) {
161
+ const file = agySettingsPath(context.home);
162
+ const claimFile = claimPath(context, file);
163
+ const claim = await readClaim(claimFile);
164
+ if (claim === null) return { changes: [], diagnostics: [], needsAction: [] };
165
+ const settings = await readSettings(file);
166
+ if (settings.state === "unreadable") {
167
+ // Kept, so the next install or uninstall can finish once the file reads.
168
+ return { changes: [], diagnostics: [], needsAction: [`${file} ${settings.reason}; ACC `
169
+ + `left it as it is and still has to remove its ${claim.rule} from permissions.allow`] };
170
+ }
171
+ if (!settings.allow.includes(claim.rule)) {
172
+ await rm(claimFile, { force: true });
173
+ return { changes: [], diagnostics: [], needsAction: [] };
174
+ }
175
+ const permissions = { ...settings.value.permissions,
176
+ allow: settings.allow.filter(rule => rule !== claim.rule) };
177
+ if (claim.created?.allow === true && permissions.allow.length === 0) delete permissions.allow;
178
+ const next = { ...settings.value, permissions };
179
+ if (claim.created?.permissions === true && Object.keys(permissions).length === 0) {
180
+ delete next.permissions;
181
+ }
182
+ await writeForeignJson(file, next, { readFile, writeFile, mkdir });
183
+ if (claim.created?.file === true) {
184
+ await removeIfEmpty(file, { readFile, rm, isEmpty: blankJson() });
185
+ }
186
+ await rm(claimFile, { force: true });
187
+ return { changes: [file], diagnostics: [`removed ACC's ${claim.rule} from ${file}`],
188
+ needsAction: [] };
189
+ }
190
+
191
+ /** Put ACC's rule in place, at every install. */
192
+ export async function ensureAllowRule(context) {
193
+ const found = await inspectAllowRule(context);
194
+ if (found.state === "unmatchable" || found.state === "unreadable") {
195
+ return { changes: [], diagnostics: [found.diagnostic], needsAction: [found.diagnostic] };
196
+ }
197
+ if (found.state === "allowed") {
198
+ return { changes: [], diagnostics: [found.diagnostic], needsAction: [] };
199
+ }
200
+ await addRule(context, found);
201
+ return { changes: [found.file], needsAction: [], diagnostics: [`added ${found.rule} to `
202
+ + `permissions.allow in ${found.file}: ACC commands run without an approval prompt in `
203
+ + "Antigravity sessions started from now on; restart agy for a session already open"] };
204
+ }