agents-can-communicate 0.5.10 → 0.6.0

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 (63) hide show
  1. package/README.md +2 -1
  2. package/bin/acc-antigravity-relay.mjs +7 -0
  3. package/bin/entrypoints/acc-antigravity-relay.mjs +116 -0
  4. package/bin/entrypoints/acc-bootstrap.mjs +4 -0
  5. package/bin/entrypoints/acc-hook.mjs +11 -7
  6. package/bin/entrypoints/antigravity-relay-binding.mjs +43 -0
  7. package/docs/ADAPTER_AUTHORING.md +33 -0
  8. package/docs/ARCHITECTURE.md +5 -3
  9. package/docs/CAPABILITIES.md +22 -16
  10. package/docs/CONFIGURATION.md +1 -0
  11. package/docs/GETTING_STARTED.md +10 -4
  12. package/docs/HOW_IT_WORKS.md +7 -1
  13. package/docs/TROUBLESHOOTING.md +72 -0
  14. package/node_modules/@agents-can-communicate/adapter-antigravity/certification.json +91 -0
  15. package/node_modules/@agents-can-communicate/adapter-antigravity/fixtures/PreInvocation-1.2.7.json +11 -0
  16. package/node_modules/@agents-can-communicate/adapter-antigravity/fixtures/SessionStart-1.2.7.json +9 -0
  17. package/node_modules/@agents-can-communicate/adapter-antigravity/fixtures/agentapi-error-answers-1.2.7.json +19 -0
  18. package/node_modules/@agents-can-communicate/adapter-antigravity/fixtures/agentapi-live-push-1.2.7.json +31 -0
  19. package/node_modules/@agents-can-communicate/adapter-antigravity/fixtures/certification-provenance.json +217 -0
  20. package/node_modules/@agents-can-communicate/adapter-antigravity/fixtures/delivery/antigravity-cli-1.2.7-relay-product-evidence.json +132 -0
  21. package/node_modules/@agents-can-communicate/adapter-antigravity/fixtures/delivery/antigravity-cli-1.2.7-relay-product.json +29 -0
  22. package/node_modules/@agents-can-communicate/adapter-antigravity/fixtures/hooks-first-trust-no-workspace-1.2.7.json +37 -0
  23. package/node_modules/@agents-can-communicate/adapter-antigravity/fixtures/live-push-surfaces-1.2.7.json +38 -0
  24. package/node_modules/@agents-can-communicate/adapter-antigravity/package.json +28 -0
  25. package/node_modules/@agents-can-communicate/adapter-antigravity/plugin/plugin.json +5 -0
  26. package/node_modules/@agents-can-communicate/adapter-antigravity/plugin/skills/acc/SKILL.md +329 -0
  27. package/node_modules/@agents-can-communicate/adapter-antigravity/src/adapter.mjs +119 -0
  28. package/node_modules/@agents-can-communicate/adapter-antigravity/src/agentapi.mjs +98 -0
  29. package/node_modules/@agents-can-communicate/adapter-antigravity/src/hooks.mjs +201 -0
  30. package/node_modules/@agents-can-communicate/adapter-antigravity/src/install.mjs +708 -0
  31. package/node_modules/@agents-can-communicate/adapter-antigravity/src/native-delivery.mjs +170 -0
  32. package/node_modules/@agents-can-communicate/adapter-antigravity/src/relay-endpoint.mjs +116 -0
  33. package/node_modules/@agents-can-communicate/adapter-antigravity/src/relay-start.mjs +123 -0
  34. package/node_modules/@agents-can-communicate/adapter-antigravity/src/relay.mjs +195 -0
  35. package/node_modules/@agents-can-communicate/adapter-antigravity/src/relays.mjs +64 -0
  36. package/node_modules/@agents-can-communicate/adapter-claude-code/package.json +1 -1
  37. package/node_modules/@agents-can-communicate/adapter-codex/package.json +1 -1
  38. package/node_modules/@agents-can-communicate/adapter-gemini-cli/extension/gemini-extension.json +1 -1
  39. package/node_modules/@agents-can-communicate/adapter-gemini-cli/package.json +1 -1
  40. package/node_modules/@agents-can-communicate/adapter-grok/package.json +1 -1
  41. package/node_modules/@agents-can-communicate/adapter-kimi/package.json +1 -1
  42. package/node_modules/@agents-can-communicate/adapter-sdk/package.json +1 -1
  43. package/node_modules/@agents-can-communicate/adapter-sdk/src/capabilities.mjs +37 -1
  44. package/node_modules/@agents-can-communicate/adapter-sdk/src/certification.mjs +25 -5
  45. package/node_modules/@agents-can-communicate/adapter-sdk/src/hook-shim.mjs +1 -0
  46. package/node_modules/@agents-can-communicate/adapter-sdk/src/index.mjs +1 -1
  47. package/node_modules/@agents-can-communicate/adapter-sdk/src/native-attempt.mjs +1 -1
  48. package/node_modules/@agents-can-communicate/cli/package.json +1 -1
  49. package/node_modules/@agents-can-communicate/cli/src/install-command.mjs +17 -3
  50. package/node_modules/@agents-can-communicate/cli/src/managed-runtime/entry.mjs +2 -2
  51. package/node_modules/@agents-can-communicate/cli/src/managed-runtime/launchers.mjs +1 -0
  52. package/node_modules/@agents-can-communicate/core/package.json +1 -1
  53. package/node_modules/@agents-can-communicate/delivery-router/package.json +1 -1
  54. package/node_modules/@agents-can-communicate/hook-runner/package.json +1 -1
  55. package/node_modules/@agents-can-communicate/hook-runner/src/native-attempt.mjs +31 -0
  56. package/node_modules/@agents-can-communicate/hook-runner/src/runner.mjs +59 -9
  57. package/node_modules/@agents-can-communicate/installer/package.json +1 -1
  58. package/node_modules/@agents-can-communicate/installer/src/detect.mjs +4 -0
  59. package/node_modules/@agents-can-communicate/installer/src/plan.mjs +10 -0
  60. package/node_modules/@agents-can-communicate/mcp-server/package.json +1 -1
  61. package/node_modules/@agents-can-communicate/protocol/package.json +1 -1
  62. package/node_modules/@agents-can-communicate/storage-filesystem/package.json +1 -1
  63. package/package.json +3 -1
package/README.md CHANGED
@@ -88,7 +88,8 @@ context; it cannot recover details that were never saved.
88
88
 
89
89
  ## Client support
90
90
 
91
- Integrations are available for **Claude Code, Codex, Gemini CLI, Grok, and Kimi Code**. Other
91
+ Integrations are available for **Antigravity CLI, Claude Code, Codex, Gemini CLI, Grok, and
92
+ Kimi Code**. Other
92
93
  clients can connect through [MCP](docs/MCP.md) with their own configuration and coordination
93
94
  instructions.
94
95
 
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env node
2
+ import { fileURLToPath } from "node:url";
3
+ import { invokedDirectly, runEntry } from "@agents-can-communicate/cli/managed-entry";
4
+
5
+ if (invokedDirectly(import.meta.url)) await runEntry({
6
+ kind: "acc-antigravity-relay", packageRoot: fileURLToPath(new URL("..", import.meta.url)),
7
+ });
@@ -0,0 +1,116 @@
1
+ #!/usr/bin/env node
2
+ // The relay the agent starts: `start` in its tool shell, `run` detached from it.
3
+ // Fails open everywhere - `start` prints one line and exits 0; `run` answers its
4
+ // ready line and exits quietly when anything it needs is missing.
5
+ import { execFile } from "node:child_process";
6
+ import { randomBytes } from "node:crypto";
7
+ import { appendFile } from "node:fs/promises";
8
+ import path from "node:path";
9
+
10
+ import { agentApiCommand, createAgentApi } from "@agents-can-communicate/adapter-antigravity/agentapi";
11
+ import { createRelay } from "@agents-can-communicate/adapter-antigravity/relay";
12
+ import { listRegistrations, newRelayId, relayDir } from "@agents-can-communicate/adapter-antigravity/relay-endpoint";
13
+ import { findConversation, spawnRelay, startRelay } from "@agents-can-communicate/adapter-antigravity/relay-start";
14
+ import { loadSessionBinding } from "@agents-can-communicate/adapter-sdk";
15
+ import { platformDataHome } from "@agents-can-communicate/cli";
16
+ import { createCoordinationService } from "@agents-can-communicate/core";
17
+ import { resolveClientPid } from "@agents-can-communicate/hook-runner/client-pid";
18
+ import { readProcessTable } from "@agents-can-communicate/hook-runner/process-table";
19
+ import { readInstalledLivePolicy } from "@agents-can-communicate/installer";
20
+ import { createId } from "@agents-can-communicate/protocol";
21
+ import { openFilesystemStore } from "@agents-can-communicate/storage-filesystem";
22
+
23
+ import { activateAntigravityRelay } from "./antigravity-relay-binding.mjs";
24
+
25
+ const clock = { now: () => new Date().toISOString() };
26
+ const ids = { next: kind => createId(kind, randomBytes) };
27
+ const alive = pid => { try { process.kill(pid, 0); return true; } catch { return false; } };
28
+ const READY_MS = 10_000;
29
+
30
+ const argvOf = pid => new Promise(resolve => execFile("ps", ["-o", "args=", "-p", String(pid)],
31
+ { timeout: 1_000 }, (_error, out) => resolve(String(out ?? "").trim().split(/\s+/).filter(Boolean))));
32
+
33
+ // Through the launcher that ran `start`, never this module directly: an entry
34
+ // module does not run itself, and under the managed runtime the launcher takes
35
+ // the lease that keeps this generation installed for as long as the relay lives.
36
+ const spawnRun = ({ env, payload }) => spawnRelay({ command: process.execPath,
37
+ args: [process.argv[1], "run"], env, payload, readyMs: READY_MS });
38
+
39
+ async function start() {
40
+ const dataHome = platformDataHome({ platform: process.platform, env: process.env });
41
+ const line = await startRelay({ env: process.env, pid: process.pid,
42
+ readTable: () => readProcessTable(), argvOf,
43
+ resolveAgyPid: (table, from) => resolveClientPid({ table, from, command: "agy" }),
44
+ readPolicy: () => readInstalledLivePolicy({ dataHome, adapterId: "antigravity" }),
45
+ findConversation: ({ conversationId, agyPid }) => findConversation({ dataHome, conversationId, agyPid }),
46
+ liveRelayFor: async ({ runtimeDir, conversationId }) => (await listRegistrations({ runtimeDir }))
47
+ .some(record => record.conversationId === conversationId && alive(record.relayPid)),
48
+ spawnRun });
49
+ process.stdout.write(`${line}\n`);
50
+ }
51
+
52
+ async function readPayload() {
53
+ let text = "";
54
+ for await (const chunk of process.stdin) text += chunk;
55
+ return JSON.parse(text);
56
+ }
57
+
58
+ // Event names, message ids and closed reason codes only: never a body, the
59
+ // token, the address or the nonce.
60
+ const logger = file => entry => appendFile(file, `${JSON.stringify({ at: clock.now(), ...entry })}\n`,
61
+ { mode: 0o600 }).catch(() => {});
62
+
63
+ async function run() {
64
+ const ready = result => process.stdout.write(`${JSON.stringify(result)}\n`);
65
+ const payload = await readPayload();
66
+ const { runtimeDir, conversationId, agyPid } = payload;
67
+ const dataHome = platformDataHome({ platform: process.platform, env: process.env });
68
+ const store = await openFilesystemStore({ root: runtimeDir, clock, ids, workspaceId: payload.workspaceId });
69
+ const service = createCoordinationService({ store, clock, ids });
70
+ const current = () => loadSessionBinding({ runtimeDir, harnessSessionId: conversationId });
71
+ const bound = await current();
72
+ if (bound === null || bound.clientPid !== agyPid) {
73
+ ready({ ok: false, reason: "no ACC session for this conversation" });
74
+ return;
75
+ }
76
+ const endpointId = newRelayId();
77
+ const relay = createRelay({ runtimeDir, conversationId, agyPid, isAlive: alive, endpointId,
78
+ clientVersion: bound.clientVersion,
79
+ observe: logger(path.join(relayDir(runtimeDir), `${endpointId}.log`)),
80
+ api: createAgentApi({ endpoint: payload, baseEnv: process.env,
81
+ command: agentApiCommand(process.env) }),
82
+ // The conversation can reopen under a new ACC generation while this relay
83
+ // keeps serving it, so renew whichever binding is current, never a copy.
84
+ refreshBinding: async leaseUntil => {
85
+ const now = await current();
86
+ if (now?.clientPid !== agyPid) return;
87
+ await service.refreshDeliveryBinding({ sessionId: now.accSessionId, generation: now.generation,
88
+ leaseUntil });
89
+ },
90
+ onExit: async () => {
91
+ const now = await current().catch(() => null);
92
+ if (now !== null) {
93
+ await service.clearDeliveryBinding({ sessionId: now.accSessionId, generation: now.generation,
94
+ opaqueEndpointRef: endpointId }).catch(() => {});
95
+ }
96
+ process.exit(0);
97
+ } });
98
+ await relay.listen();
99
+ process.on("SIGTERM", () => { relay.close("terminated"); });
100
+ const activation = await activateAntigravityRelay({ session: { sessionId: bound.accSessionId,
101
+ generation: bound.generation, clientPid: bound.clientPid, harnessSessionId: conversationId },
102
+ service, runtimeDir, dataHome }).catch(() => null);
103
+ if (activation?.state === "active") {
104
+ ready({ ok: true });
105
+ return;
106
+ }
107
+ ready({ ok: false, reason: activation?.reasonCode ?? "binding was not published" });
108
+ await relay.close("not_activated");
109
+ }
110
+
111
+ export async function main() {
112
+ const [command] = process.argv.slice(2);
113
+ if (command === "run") await run().catch(() => process.exit(0));
114
+ else await start().catch(() => process.stdout.write(
115
+ "ACC: live delivery did not start; peers still reach this conversation at its next turn.\n"));
116
+ }
@@ -7,6 +7,7 @@
7
7
  // empty unless ACC_BOOTSTRAP_DEBUG=1 asks for one safe line.
8
8
  import { checkNativeBootstrap } from "@agents-can-communicate/installer";
9
9
 
10
+ import { createAntigravityAdapter } from "@agents-can-communicate/adapter-antigravity";
10
11
  import { createClaudeCodeAdapter } from "@agents-can-communicate/adapter-claude-code";
11
12
  import { createCodexAdapter } from "@agents-can-communicate/adapter-codex";
12
13
  import { createGeminiCliAdapter } from "@agents-can-communicate/adapter-gemini-cli";
@@ -29,6 +30,9 @@ try {
29
30
  const registry = {
30
31
  claude_code: createClaudeCodeAdapter, codex: createCodexAdapter,
31
32
  gemini_cli: createGeminiCliAdapter, grok: createGrokAdapter, kimi: createKimiAdapter,
33
+ // No native delivery contract: this reports the client unsupported rather
34
+ // than unknown, which is a different and more useful thing to read.
35
+ antigravity: createAntigravityAdapter,
32
36
  };
33
37
  const adapter = registry[options.adapter]?.();
34
38
  if (adapter === undefined) {
@@ -12,6 +12,7 @@ import { createId } from "@agents-can-communicate/protocol";
12
12
  import { runHook } from "@agents-can-communicate/hook-runner";
13
13
  import { hookEntrypointFor, resolvePinnedGeneration } from "@agents-can-communicate/cli";
14
14
 
15
+ import { createAntigravityAdapter } from "@agents-can-communicate/adapter-antigravity";
15
16
  import { createClaudeCodeAdapter } from "@agents-can-communicate/adapter-claude-code";
16
17
  import { createCodexAdapter } from "@agents-can-communicate/adapter-codex";
17
18
  import { createGeminiCliAdapter } from "@agents-can-communicate/adapter-gemini-cli";
@@ -19,6 +20,9 @@ import { createGrokAdapter } from "@agents-can-communicate/adapter-grok";
19
20
  import { createKimiAdapter } from "@agents-can-communicate/adapter-kimi";
20
21
 
21
22
  const adapters = {
23
+ // Registered like any other client. Its hook commands carry an extra
24
+ // argument - the event name - because this client's payload has none.
25
+ antigravity: createAntigravityAdapter(),
22
26
  claude_code: createClaudeCodeAdapter(),
23
27
  codex: createCodexAdapter(),
24
28
  gemini_cli: createGeminiCliAdapter(),
@@ -46,9 +50,9 @@ import { completeHookOutput } from "./hook-output.mjs";
46
50
  // a hook that has no pin to look up pays nothing beyond it. Any adapter or
47
51
  // payload that will not yield a session id simply has no pin to find, same as
48
52
  // today's no-pin behaviour.
49
- async function harnessSessionIdFor(adapterId, payload) {
53
+ async function harnessSessionIdFor(adapterId, payload, args) {
50
54
  try {
51
- const event = await adapters[adapterId]?.normalizeHook(payload);
55
+ const event = await adapters[adapterId]?.normalizeHook(payload, { args });
52
56
  return typeof event?.sessionId === "string" ? event.sessionId : null;
53
57
  } catch {
54
58
  return null;
@@ -71,9 +75,9 @@ async function harnessSessionIdFor(adapterId, payload) {
71
75
  // mid-resolution by another hook for the same session, or simply wrong)
72
76
  // could say. Unbounded delegation would be a hang, worse than any of the
73
77
  // failures this file already falls open from.
74
- async function delegateToPin({ managerRoot, packageRoot, adapterId, payload, delegated }) {
78
+ async function delegateToPin({ managerRoot, packageRoot, adapterId, payload, args, delegated }) {
75
79
  if (delegated || managerRoot === null || packageRoot === null) return false;
76
- const harnessSessionId = await harnessSessionIdFor(adapterId, payload);
80
+ const harnessSessionId = await harnessSessionIdFor(adapterId, payload, args);
77
81
  if (harnessSessionId === null) return false;
78
82
  const pinned = await resolvePinnedGeneration({ root: managerRoot, harnessSessionId, active: packageRoot });
79
83
  if (pinned === null) return false;
@@ -87,7 +91,7 @@ async function delegateToPin({ managerRoot, packageRoot, adapterId, payload, del
87
91
  }
88
92
 
89
93
  export async function main({ managerRoot = null, packageRoot = null, payload, delegated = false } = {}) {
90
- const [adapterId] = process.argv.slice(2);
94
+ const [adapterId, ...args] = process.argv.slice(2);
91
95
 
92
96
  // A delegating caller already read stdin once for the whole process and
93
97
  // hands its parsed payload down; only the outermost call reads it here.
@@ -99,9 +103,9 @@ export async function main({ managerRoot = null, packageRoot = null, payload, de
99
103
  }
100
104
  }
101
105
 
102
- if (await delegateToPin({ managerRoot, packageRoot, adapterId, payload, delegated })) return;
106
+ if (await delegateToPin({ managerRoot, packageRoot, adapterId, payload, args, delegated })) return;
103
107
 
104
- const result = await runHook({ adapterId, payload, adapters,
108
+ const result = await runHook({ adapterId, payload, args, adapters,
105
109
  runtime: { clock: { now: () => new Date().toISOString() },
106
110
  ids: { next: kind => createId(kind, randomBytes) } },
107
111
  env: process.env });
@@ -0,0 +1,43 @@
1
+ import { createAntigravityAdapter } from "@agents-can-communicate/adapter-antigravity";
2
+ import { loadSessionBinding, storeNativeAttempt } from "@agents-can-communicate/adapter-sdk";
3
+ import { createCoordinationService } from "@agents-can-communicate/core";
4
+ import { establishNativeBinding } from "@agents-can-communicate/hook-runner/native-binding";
5
+ import { withSessionLifecycle } from "@agents-can-communicate/hook-runner/session-lifecycle";
6
+ import { readInstalledLivePolicyState } from "@agents-can-communicate/installer";
7
+ import { openFilesystemStore } from "@agents-can-communicate/storage-filesystem";
8
+
9
+ // No hook runs when the agent starts a relay mid-turn, and the next
10
+ // PreInvocation can be hours away. The relay publishes its own binding the way
11
+ // the Claude Code Channel does: under the session's lifecycle lock, after
12
+ // rereading ownership, so a queued start never publishes for a replaced or
13
+ // closed session.
14
+ export async function activateAntigravityRelay({ session, service, runtimeDir, dataHome,
15
+ adapter = createAntigravityAdapter(), deadlineAt = Date.now() + 5_000 }) {
16
+ if (typeof session.harnessSessionId !== "string") return null;
17
+ return withSessionLifecycle({ root: runtimeDir, sessionId: session.harnessSessionId,
18
+ clock: service.clock, deadlineAt }, async () => {
19
+ const { clock, ids } = service;
20
+ const store = await openFilesystemStore({ root: runtimeDir, clock, ids,
21
+ workspaceId: service.store.workspaceId, deadlineAt });
22
+ const current = createCoordinationService({ store, clock, ids });
23
+ const binding = await loadSessionBinding({ runtimeDir, harnessSessionId: session.harnessSessionId });
24
+ if (binding?.accSessionId !== session.sessionId || binding.generation !== session.generation
25
+ || binding.clientPid !== session.clientPid) return null;
26
+ const located = await current.locateSession(session.sessionId);
27
+ if (located?.record.state !== "open" || located.record.generation !== session.generation) return null;
28
+ const { policy, policyStatus } = await readInstalledLivePolicyState({ dataHome,
29
+ adapterId: adapter.id });
30
+ if (policy === "off" || Date.now() >= deadlineAt) return null;
31
+ const result = await establishNativeBinding({ adapter,
32
+ event: { kind: "relayReady", sessionId: session.harnessSessionId },
33
+ hookBinding: binding, clientVersion: binding.clientVersion, platform: binding.platform,
34
+ livePolicy: policy, service: current, runtimeDir, clock, env: {},
35
+ timeoutMs: Math.max(1, Math.min(750, deadlineAt - Date.now())) });
36
+ await storeNativeAttempt({ runtimeDir, harnessSessionId: session.harnessSessionId,
37
+ accSessionId: session.sessionId, generation: session.generation, deadlineAt,
38
+ nativeAttempt: { at: clock.now(), event: "relayReady", state: result.state,
39
+ reasonCode: result.reasonCode, policy, policySource: "installation-record", policyStatus,
40
+ clientProcess: "identified" } }).catch(() => {});
41
+ return result;
42
+ });
43
+ }
@@ -52,6 +52,17 @@ it cannot create a session or advance receipts. Grok uses it to supply CLI owner
52
52
  arguments after a terminal result. This does not certify general context or peer
53
53
  delivery, and a tool such as `finish` can close the owner before the line arrives.
54
54
 
55
+ `continueTurnOutcome({ reason, payload })` is optional. A client whose end-of-turn hook can
56
+ hold a turn open implements it, and the runner's `turnEnd` handler calls it with the projected
57
+ peer context when - and only when - a peer body no invocation has shown yet is waiting. It
58
+ returns `{ stdout, stderr?, exitCode? }` in the client's own continuation shape, and prints
59
+ nothing to let the turn end. `payload` is the raw hook payload handed back unread, so the
60
+ adapter can apply its own ceiling from a counter the client supplies; nothing in core reads it.
61
+ An empty `stdout` records no offer, so the body stays queued for the next invocation. A
62
+ continuation costs the operator a model invocation, which is why an owner header or an
63
+ attention count is never a reason to call it. Antigravity CLI uses it for `Stop`, bounded to
64
+ one continuation per turn by the client's own `executionNum`.
65
+
55
66
  `renderContextResult` is required wherever an adapter renders peer messages. It returns
56
67
  `{ text, offeredMessageIds, includedAttentionIds }`, and the [receipt
57
68
  lifecycle](PROTOCOL.md#receipt-lifecycle) advances only from those ids — never by searching
@@ -110,6 +121,20 @@ the false value and can never enable it.
110
121
  shape for the installed client. Only an exact passing version/platform match remains true.
111
122
  Unreadable, unknown, or mismatched clients degrade every uncertified row to false.
112
123
 
124
+ An adapter for a client that ships often may declare a floor per platform:
125
+
126
+ ```js
127
+ certificationFloor: { "darwin-arm64": "1.2.7" },
128
+ ```
129
+
130
+ A stable version at or above the floor, on that platform, is then judged by the floor
131
+ version's evidence for any capability it has no capture of its own for. A later capture
132
+ wins for its own version, capability by capability - a recorded failure turns that
133
+ capability off for that version and leaves the rest on the floor. Earlier versions,
134
+ prereleases and other platforms stay uncertified, and `defineAdapter` refuses a floor
135
+ that names a version with no passing evidence on its platform. Antigravity CLI declares
136
+ one; every other adapter certifies exact versions only.
137
+
113
138
  The backing methods for delivery are `renderContextResult()` for `nextTurn`,
114
139
  `offerMessage()` for `livePush`, and `routeReply()` for `replyRoute`.
115
140
 
@@ -198,6 +223,13 @@ return normalizedEvent({
198
223
  Refuse an unrecognised payload. Inventing a session attaches the wrong one, or a new one
199
224
  every hook, and looks like it is working.
200
225
 
226
+ `normalizeHook(payload, { args })` also receives the arguments the client's hook command
227
+ carried after the adapter id. Most clients name the event inside the payload and ignore this.
228
+ Antigravity CLI does not send one at all, and its `PreInvocation` and `PostInvocation` hand
229
+ over byte-identical envelopes - so for that client the registered command's own argument is
230
+ the only thing that knows which hook ran, and its install writes the event name into each
231
+ command. `args` is always an array; an adapter that does not need it may take one parameter.
232
+
201
233
  ## Measure response contracts
202
234
 
203
235
  Measure them. Every client differs, and a wrong shape fails **silently**:
@@ -207,6 +239,7 @@ Measure them. Every client differs, and a wrong shape fails **silently**:
207
239
  | Codex | exit 2 + stderr | plain stdout (`developer` message) |
208
240
  | Claude Code | `hookSpecificOutput.permissionDecision` | same envelope |
209
241
  | Gemini CLI | `{"decision":"block"}` | `hookSpecificOutput` envelope |
242
+ | Antigravity CLI | no tool event loads, so a deny is unreachable | `{"injectSteps":[{"ephemeralMessage"}]}` from `PreInvocation` |
210
243
  | Grok | `{"decision":"deny","reason"}` (documented; deny not yet captured) | UserPromptSubmit stdout discarded; own identity only via PreToolUse after a terminal result, observed on 1.0.24 |
211
244
  | Kimi Code | `hookSpecificOutput.permissionDecision` | plain stdout |
212
245
 
@@ -97,11 +97,13 @@ exposes and whether its lease is current, while recipient policy says whether it
97
97
  turn.
98
98
 
99
99
  Claude Code Channel has installed-client evidence on 2.1.258 and 2.1.260; Codex
100
- LocalDaemon has it on 0.152.1 and 0.153.4. Both use a captured platform minimum,
100
+ LocalDaemon has it on 0.152.1 and 0.153.4; Antigravity CLI has it on 1.2.7 and later through a relay
101
+ the agent starts in its own shell. All three use a captured platform minimum,
101
102
  current probe and exact session handshake. Codex preserves the ordinary client
102
103
  launch and verifies its registered thread and workspace; native delivery owns no vendor
103
- daemon lifecycle. Gemini CLI and Kimi Code have exact-version next-turn evidence only;
104
- Grok and generic MCP use inbox polling.
104
+ daemon lifecycle. Gemini CLI and Kimi Code have exact-version next-turn evidence only; Grok
105
+ and generic MCP use inbox polling. Antigravity CLI additionally attaches
106
+ a session only where the client reports an open workspace.
105
107
 
106
108
  ## Storage and workspace identity
107
109
 
@@ -17,7 +17,8 @@ Capability honesty separates four questions that are easy to collapse:
17
17
  4. **Fallback** — what durable path remains when any earlier answer is no?
18
18
 
19
19
  A source method or vendor documentation is not certification. Uncaptured hook versions and
20
- unsupported platforms degrade to false. Native minimum-based eligibility is separate. No weaker session inherits a stronger peer's capability.
20
+ unsupported platforms degrade to false, except that an adapter may declare a captured version
21
+ as a floor for later stable releases on the same platform - Antigravity CLI does. Native minimum-based eligibility is separate. No weaker session inherits a stronger peer's capability.
21
22
 
22
23
  Run `acc doctor` in the project when observed behavior differs from this page. It reports
23
24
  the installed client version, platform, effective capability, and fallback instead of
@@ -25,34 +26,39 @@ assuming that a newer or differently packaged client behaves like a captured one
25
26
 
26
27
  ## Certified support
27
28
 
28
- Passing evidence currently ships for these exact versions on `darwin-arm64`:
29
-
30
- | Capability | Codex 0.147.0 | Claude Code 2.1.233 | Gemini CLI 0.57.0 | Grok 1.0.13 | Kimi 0.36.1 |
31
- |---|---:|---:|---:|---:|---:|
32
- | `lifecycle.sessionStart` | yes | yes | yes | no | yes |
33
- | `lifecycle.sessionEnd` | yes | yes | yes | no | no |
34
- | `lifecycle.heartbeat` | no | no | no | no | yes |
35
- | `context.beforeTurnInjection` | yes | yes | yes | no | yes |
36
- | `guards.beforeWrite` | yes | yes | yes | no | yes |
37
- | `guards.beforeShell` | no | yes | yes | no | yes |
38
- | `delivery.nextTurn` | yes | yes | yes | no | yes |
39
- | `delivery.livePush` | no | no | no | no | no |
40
- | `delivery.replyRoute` | no | no | no | no | no |
29
+ Passing evidence currently ships for these exact versions on `darwin-arm64`. Antigravity
30
+ CLI's column also covers every later stable release there: its adapter declares 1.2.7 as a
31
+ certification floor, so a newer version is judged by the 1.2.7 captures until one of its
32
+ own says otherwise.
33
+
34
+ | Capability | Antigravity 1.2.7 | Codex 0.147.0 | Claude Code 2.1.233 | Gemini CLI 0.57.0 | Grok 1.0.13 | Kimi 0.36.1 |
35
+ |---|---:|---:|---:|---:|---:|---:|
36
+ | `lifecycle.sessionStart` | yes | yes | yes | yes | no | yes |
37
+ | `lifecycle.sessionEnd` | no | yes | yes | yes | no | no |
38
+ | `lifecycle.heartbeat` | no | no | no | no | no | yes |
39
+ | `context.beforeTurnInjection` | yes | yes | yes | yes | no | yes |
40
+ | `guards.beforeWrite` | no | yes | yes | yes | no | yes |
41
+ | `guards.beforeShell` | no | no | yes | yes | no | yes |
42
+ | `delivery.nextTurn` | yes | yes | yes | yes | no | yes |
43
+ | `delivery.livePush` | yes | no | no | no | no | no |
44
+ | `delivery.replyRoute` | no | no | no | no | no | no |
41
45
 
42
46
  Every other capability in the closed shape defaults to false, including session resume,
43
47
  child sessions, startup or safe-point injection, and before-read guards.
44
48
 
45
49
  The native rows remain `no` for the older exact hook versions in this matrix.
46
50
  Separate installed-client captures establish Codex `livePush` on 0.152.1 and
47
- 0.153.4, and Claude Code `livePush` plus `replyRoute` on 2.1.258 and 2.1.260.
51
+ 0.153.4, Claude Code `livePush` plus `replyRoute` on 2.1.258 and 2.1.260, and
52
+ Antigravity CLI `livePush` on 1.2.7 and later through a relay the agent starts in its own shell.
48
53
  Native eligibility uses the captured platform minimum, a current feature probe,
49
54
  and an exact per-session handshake. It is experimental and requires recipient
50
- opt-in. Codex replies through `acc reply`; its native `replyRoute` remains false.
55
+ opt-in. Codex and Antigravity CLI reply through `acc reply`; their native `replyRoute` remains false.
51
56
 
52
57
  The limitations belong next to the adapters they affect:
53
58
 
54
59
  | Adapter | Exact limitation and evidence |
55
60
  |---|---|
61
+ | Antigravity CLI | 1.2.7 on darwin-arm64, captured in print mode, and later stable releases by certification floor. 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; 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. |
56
62
  | Codex | Exact 0.147.0 next-turn context 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. |
57
63
  | Claude Code | 2.1.233 next-turn delivery waits for the next user prompt. A 2.1.258 Channel capture proved idle offer, busy queue-after-turn, explicit reply, duplicate suppression, and durable fallback, so `delivery.livePush` and `delivery.replyRoute` are live capabilities behind the native contract (experimental, off until opted in; Claude's development-channel warning is vendor-owned and visible). |
58
64
  | Gemini CLI | Only 0.57.0 has package-shipped next-turn certification. 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. |
@@ -194,6 +194,7 @@ that resolves inside a workspace.
194
194
  | `ACC_MCP_WORKSPACE` | The project `acc-mcp` joins. Without it the server takes the directory the client launched it in, which is rarely the project |
195
195
  | `ACC_NO_UPDATE_CHECK=1` | Disables update networking and background scheduling; manual recovery of an already downloaded update remains available |
196
196
  | `ACC_PROBE_TIMEOUT_MS` | How long to wait for a client to print its version. Three seconds by default: generous on an idle machine, and not always enough on a busy one, where a client that overruns it is reported as not installed |
197
+ | `ACC_ANTIGRAVITY_HOOKS` | Where `acc install` registers Antigravity CLI hooks. `global`, the default, writes `~/.gemini/config/hooks.json`, which always loads and applies to every Antigravity session on the machine. `workspace` writes `<project>/.agents/hooks.json`, which is scoped to the project the command ran in and loads only while that project is an open Antigravity workspace - in print mode, only when it is passed with `--add-dir`. Any other value is refused by name rather than replaced by the default, and that client is skipped. The two cannot be combined: both files use the namespace `acc`, and a name in both is kept only once |
197
198
  | `ACC_NATIVE_DELIVERY_POLICY` | Owned shell-bootstrap consent, currently used by Claude Code. The shim sets `off`, `actionable`, or `all`; missing/invalid values mean off for that route. Codex instead reads recorded installation consent, even when this variable is absent |
198
199
  | `ACC_BYPASS=1` | Bypasses owned shell activation, currently Claude Code: no bootstrap check/native flags, and shim policy is unset. It does not disable Codex recorded opt-in; use `acc install --adapter codex --delivery off` for new Codex offers |
199
200
  | `ACC_BOOTSTRAP_DEBUG=1` | Lets the internal `acc-bootstrap` check write one safe diagnostic line to stderr. Off, it is silent, and it never writes to stdout |
@@ -132,16 +132,22 @@ Every message is recorded before ACC attempts faster delivery. Every participant
132
132
  durable inbox, which is the universal recovery path.
133
133
 
134
134
  On exact client versions and platforms with captured support, Codex, Claude Code, Gemini
135
- CLI, and Kimi Code can receive a message at the next normal turn. That does not wake an idle
136
- session. Grok, generic MCP clients, unknown versions, and unsupported platforms use the
137
- durable inbox instead.
135
+ CLI, Kimi Code and Antigravity CLI can receive a message at the next normal turn. That does
136
+ not wake an idle session. Grok, generic MCP clients, unknown versions, and unsupported
137
+ platforms use the durable inbox instead.
138
+
139
+ Antigravity CLI carries one extra condition: its hooks are given a project directory only
140
+ when the session has an open workspace. A session started without one attaches nothing, and
141
+ the client shows no hook output to say so - `acc doctor` names it.
138
142
 
139
143
  Codex LocalDaemon and Claude Code Channel offer optional native delivery on
140
144
  Apple Silicon macOS. They are experimental, can spend tokens, and queue messages
141
145
  until a running turn finishes. Codex requires 0.152.1 or newer, LocalDaemon infrastructure,
142
146
  recorded opt-in, and a verified session. Supported explicit setup can prepare a missing
143
147
  service. Start the client with your normal command. A loaded daemon thread can receive
144
- messages after its terminal exits.
148
+ messages after its terminal exits. Antigravity CLI 1.2.7 and later has one too: with delivery enabled,
149
+ ACC's context asks the agent once per conversation to start a relay from its own shell, and
150
+ you approve that command at the client's prompt.
145
151
  [Capabilities](CAPABILITIES.md) explains policy, versions and fallback.
146
152
 
147
153
  Delivery evidence is deliberately narrow: `queued -> offered -> retrieved -> acknowledged`.
@@ -99,7 +99,7 @@ All recipients have the same durable record, but adapters expose different accel
99
99
  - **Durable inbox:** universal recovery. `acc inbox` discovers bounded message headers;
100
100
  `acc inbox --message <id>` retrieves one complete addressed message after selection
101
101
  or compaction.
102
- - **Next normal turn:** exact captured versions of Codex, Claude Code, Gemini CLI, and Kimi
102
+ - **Next normal turn:** exact captured versions of Codex, Claude Code, Gemini CLI, Antigravity CLI and Kimi
103
103
  Code can receive complete attributed peer context when the user next prompts that client.
104
104
  This does not wake an idle session.
105
105
  - **Optional Claude Code channel:** supported Claude Code versions on Apple Silicon macOS
@@ -115,6 +115,12 @@ All recipients have the same durable record, but adapters expose different accel
115
115
  and does not manage the vendor daemon during message delivery. Separately,
116
116
  [confirmed update maintenance](UPGRADING.md#confirmed-client-service-maintenance) can restart it.
117
117
 
118
+ - **Optional Antigravity CLI relay:** on Apple Silicon macOS with 1.2.7 or later, the session
119
+ endpoint exists only in the agent's own shell, so ACC's context asks the agent once per
120
+ conversation to start a relay; the user approves that command. The relay keeps the endpoint
121
+ in memory, wakes an idle session with a fenced peer message, holds one for a busy session,
122
+ and ends with the client. It is experimental, off by default, and can spend tokens.
123
+
118
124
  Grok, generic MCP, unsupported versions and uncaptured platforms use inbox polling.
119
125
  [Capabilities](CAPABILITIES.md) lists evidence and fallback.
120
126
 
@@ -214,6 +214,78 @@ trusted` and keeps going, and the default mode has no write or shell tool to gua
214
214
  guard never fires and the mode you passed appears to have been ignored. Trust the folder,
215
215
  or start the session somewhere trusted.
216
216
 
217
+ ## Antigravity is installed and nothing happens
218
+
219
+ The most likely cause is that the session has no open workspace. Antigravity CLI gives its
220
+ hooks a project directory only through `workspacePaths`, and that array is empty unless the
221
+ session has one. The interactive TUI started in a project directory has one; a print-mode
222
+ `agy -p "..."` started in the same directory does not. The
223
+ hook then has no workspace to join, fails open as every ACC hook must, and this client shows
224
+ no hook output, so nothing anywhere says why.
225
+
226
+ Open the project as an Antigravity workspace, or pass it explicitly:
227
+
228
+ ```
229
+ agy -p "..." --add-dir /path/to/project
230
+ ```
231
+
232
+ The first TUI session in a folder you trusted at that same launch has no workspace either,
233
+ even with `--add-dir`; start the TUI again once the folder is trusted.
234
+
235
+ `acc doctor` names this state. Nothing else in the payload can substitute for it:
236
+ `transcriptPath` and `artifactDirectoryPath` both point inside the client's own
237
+ per-conversation directory, and the hook process does not inherit the directory the client
238
+ was started in.
239
+
240
+ If the session does have a workspace and ACC is still absent, check that the registration
241
+ actually loaded rather than that the file exists:
242
+
243
+ ```
244
+ agy -p "/hooks" --output-format json
245
+ ```
246
+
247
+ This client accepts a hook configuration it will not load and reports nothing. A file on
248
+ disk is not a registration: the Gemini CLI hook shape loads nothing here, an unsupported
249
+ event name is dropped out of an otherwise valid namespace, and one top-level key that is not
250
+ an integration namespace drops the whole file. The command above is the only answer that
251
+ counts.
252
+
253
+ The agent may see two ACC skills: `acc:acc`, which `acc install` puts there, and
254
+ `agents-can-communicate:acc`. The second is this client's own copy of ACC's Gemini CLI
255
+ extension, made on its first authenticated run into `~/.gemini/antigravity-cli/plugins/`. Its
256
+ skill loads, but none of its hooks do, and every command in it runs the Gemini CLI extension's
257
+ shim - so it works only while Gemini CLI is wired. `acc:acc` is the one to rely on; check what
258
+ the client actually loaded with:
259
+
260
+ ```
261
+ agy -p "/skills" --output-format json
262
+ ```
263
+
264
+ `agy plugin list` is not that check: it lists only plugins installed through
265
+ `agy plugin install`, and reports none while the imported skill above is loaded.
266
+
267
+ An agent that receives a peer message in print mode (`agy -p`) cannot answer it on its own.
268
+ It forms the right `acc reply` command from the skill, and print mode denies every command
269
+ because it cannot ask for approval; the client says so on stderr. The TUI asks instead.
270
+ ACC does not grant itself that permission. To let headless agents answer, allow the ACC
271
+ command yourself under `permissions.allow` in the client's settings, in the `command(...)`
272
+ form its message names.
273
+
274
+ ## Antigravity never wakes while idle
275
+
276
+ Live delivery needs four things, and `acc doctor` shows each:
277
+
278
+ - the live policy is on: `acc install --adapter antigravity --delivery actionable` (or `all`);
279
+ - the session is interactive - print mode ends with its turn and never runs a relay;
280
+ - the session attached to ACC at all (see the section above);
281
+ - the agent started the relay. ACC's context asks once per conversation; if the command was
282
+ declined, or never approved at the permission prompt, nothing asks again. Ask the agent to
283
+ run `sh "~/.gemini/config/acc/acc-relay.sh" start`, or start a new conversation.
284
+
285
+ `acc doctor` reports how many relays are running and, per session, whether a live transport is
286
+ active. A relay ends with its client; nothing is left running after the TUI exits or after
287
+ `acc uninstall`.
288
+
217
289
  ## Grok shows no injected message
218
290
 
219
291
  Grok discards UserPromptSubmit context. On the observed 1.0.24 client, run public