agents-can-communicate 0.5.9 → 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 (80) 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 +27 -4
  9. package/docs/CAPABILITIES.md +22 -16
  10. package/docs/CLI.md +21 -3
  11. package/docs/CONCEPTS.md +7 -0
  12. package/docs/CONFIGURATION.md +1 -0
  13. package/docs/GETTING_STARTED.md +10 -4
  14. package/docs/HOW_IT_WORKS.md +7 -1
  15. package/docs/TROUBLESHOOTING.md +99 -0
  16. package/node_modules/@agents-can-communicate/adapter-antigravity/certification.json +91 -0
  17. package/node_modules/@agents-can-communicate/adapter-antigravity/fixtures/PreInvocation-1.2.7.json +11 -0
  18. package/node_modules/@agents-can-communicate/adapter-antigravity/fixtures/SessionStart-1.2.7.json +9 -0
  19. package/node_modules/@agents-can-communicate/adapter-antigravity/fixtures/agentapi-error-answers-1.2.7.json +19 -0
  20. package/node_modules/@agents-can-communicate/adapter-antigravity/fixtures/agentapi-live-push-1.2.7.json +31 -0
  21. package/node_modules/@agents-can-communicate/adapter-antigravity/fixtures/certification-provenance.json +217 -0
  22. package/node_modules/@agents-can-communicate/adapter-antigravity/fixtures/delivery/antigravity-cli-1.2.7-relay-product-evidence.json +132 -0
  23. package/node_modules/@agents-can-communicate/adapter-antigravity/fixtures/delivery/antigravity-cli-1.2.7-relay-product.json +29 -0
  24. package/node_modules/@agents-can-communicate/adapter-antigravity/fixtures/hooks-first-trust-no-workspace-1.2.7.json +37 -0
  25. package/node_modules/@agents-can-communicate/adapter-antigravity/fixtures/live-push-surfaces-1.2.7.json +38 -0
  26. package/node_modules/@agents-can-communicate/adapter-antigravity/package.json +28 -0
  27. package/node_modules/@agents-can-communicate/adapter-antigravity/plugin/plugin.json +5 -0
  28. package/node_modules/@agents-can-communicate/adapter-antigravity/plugin/skills/acc/SKILL.md +329 -0
  29. package/node_modules/@agents-can-communicate/adapter-antigravity/src/adapter.mjs +119 -0
  30. package/node_modules/@agents-can-communicate/adapter-antigravity/src/agentapi.mjs +98 -0
  31. package/node_modules/@agents-can-communicate/adapter-antigravity/src/hooks.mjs +201 -0
  32. package/node_modules/@agents-can-communicate/adapter-antigravity/src/install.mjs +708 -0
  33. package/node_modules/@agents-can-communicate/adapter-antigravity/src/native-delivery.mjs +170 -0
  34. package/node_modules/@agents-can-communicate/adapter-antigravity/src/relay-endpoint.mjs +116 -0
  35. package/node_modules/@agents-can-communicate/adapter-antigravity/src/relay-start.mjs +123 -0
  36. package/node_modules/@agents-can-communicate/adapter-antigravity/src/relay.mjs +195 -0
  37. package/node_modules/@agents-can-communicate/adapter-antigravity/src/relays.mjs +64 -0
  38. package/node_modules/@agents-can-communicate/adapter-claude-code/package.json +1 -1
  39. package/node_modules/@agents-can-communicate/adapter-claude-code/plugin/skills/acc/SKILL.md +14 -2
  40. package/node_modules/@agents-can-communicate/adapter-claude-code/src/adapter.mjs +2 -1
  41. package/node_modules/@agents-can-communicate/adapter-claude-code/src/hooks.mjs +5 -0
  42. package/node_modules/@agents-can-communicate/adapter-codex/package.json +1 -1
  43. package/node_modules/@agents-can-communicate/adapter-codex/plugin/skills/acc/SKILL.md +14 -2
  44. package/node_modules/@agents-can-communicate/adapter-gemini-cli/extension/gemini-extension.json +1 -1
  45. package/node_modules/@agents-can-communicate/adapter-gemini-cli/extension/skills/acc/SKILL.md +14 -2
  46. package/node_modules/@agents-can-communicate/adapter-gemini-cli/package.json +1 -1
  47. package/node_modules/@agents-can-communicate/adapter-grok/package.json +1 -1
  48. package/node_modules/@agents-can-communicate/adapter-grok/plugin/skills/acc/SKILL.md +14 -2
  49. package/node_modules/@agents-can-communicate/adapter-kimi/package.json +1 -1
  50. package/node_modules/@agents-can-communicate/adapter-kimi/plugin/skills/acc/SKILL.md +14 -2
  51. package/node_modules/@agents-can-communicate/adapter-sdk/package.json +1 -1
  52. package/node_modules/@agents-can-communicate/adapter-sdk/src/capabilities.mjs +37 -1
  53. package/node_modules/@agents-can-communicate/adapter-sdk/src/certification.mjs +25 -5
  54. package/node_modules/@agents-can-communicate/adapter-sdk/src/hook-shim.mjs +1 -0
  55. package/node_modules/@agents-can-communicate/adapter-sdk/src/index.mjs +1 -1
  56. package/node_modules/@agents-can-communicate/adapter-sdk/src/native-attempt.mjs +1 -1
  57. package/node_modules/@agents-can-communicate/cli/package.json +1 -1
  58. package/node_modules/@agents-can-communicate/cli/src/args.mjs +6 -2
  59. package/node_modules/@agents-can-communicate/cli/src/help.mjs +1 -1
  60. package/node_modules/@agents-can-communicate/cli/src/hook-workspace.mjs +118 -0
  61. package/node_modules/@agents-can-communicate/cli/src/index.mjs +1 -0
  62. package/node_modules/@agents-can-communicate/cli/src/install-command.mjs +17 -3
  63. package/node_modules/@agents-can-communicate/cli/src/main.mjs +8 -4
  64. package/node_modules/@agents-can-communicate/cli/src/managed-runtime/command-prefix.mjs +26 -0
  65. package/node_modules/@agents-can-communicate/cli/src/managed-runtime/entry.mjs +6 -4
  66. package/node_modules/@agents-can-communicate/cli/src/managed-runtime/launchers.mjs +3 -1
  67. package/node_modules/@agents-can-communicate/cli/src/session-owner.mjs +27 -6
  68. package/node_modules/@agents-can-communicate/core/package.json +1 -1
  69. package/node_modules/@agents-can-communicate/delivery-router/package.json +1 -1
  70. package/node_modules/@agents-can-communicate/hook-runner/package.json +1 -1
  71. package/node_modules/@agents-can-communicate/hook-runner/src/native-attempt.mjs +31 -0
  72. package/node_modules/@agents-can-communicate/hook-runner/src/owner-context.mjs +16 -3
  73. package/node_modules/@agents-can-communicate/hook-runner/src/runner.mjs +70 -17
  74. package/node_modules/@agents-can-communicate/installer/package.json +1 -1
  75. package/node_modules/@agents-can-communicate/installer/src/detect.mjs +4 -0
  76. package/node_modules/@agents-can-communicate/installer/src/plan.mjs +10 -0
  77. package/node_modules/@agents-can-communicate/mcp-server/package.json +1 -1
  78. package/node_modules/@agents-can-communicate/protocol/package.json +1 -1
  79. package/node_modules/@agents-can-communicate/storage-filesystem/package.json +1 -1
  80. 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
 
@@ -115,10 +117,31 @@ its checkout and branch. Runtime state never lands inside those roots; the only
115
117
  file ACC writes is an optional `acc.workspace.json` explicitly requested through
116
118
  `acc config init`.
117
119
 
120
+ Native hooks persist the initial workspace directory and id, keyed by adapter and
121
+ native session id, under the platform data home's `acc/native-workspaces`. This
122
+ record carries routing only, never owner credentials. Subsequent hooks resolve the
123
+ saved directory before loading the workspace-local owner binding; their payload's
124
+ current cwd still resolves relative file targets. A nested repository cannot change
125
+ the room or bypass its claims. A linked worktree of the original repository keeps
126
+ repository-relative claim paths. Room publication is serialized before session
127
+ opening, and the record survives SessionEnd so native conversation resume keeps the
128
+ room. A changed initial workspace identity fails open with a diagnostic instead of
129
+ opening a replacement room. If SessionStart was missed, the first user-turn hook
130
+ establishes the room. A legacy session without this record establishes it on its
131
+ next startup or user-turn hook; its original launch directory cannot be inferred.
132
+ Every bound owner header includes a local `--workspace acc://<reference>` selector.
133
+ The CLI validates the exact named routing record in ACC's own data home, then resolves
134
+ the saved room through the same path as hooks. It does not scan for an owner or infer
135
+ credentials. This also keeps a header usable when Git availability changes between
136
+ the hook and the CLI command. Ordinary project-config validation remains unchanged.
137
+
118
138
  A lone session can remain ephemeral. Durable state materialises when a second live session
119
139
  appears or the first claim, message, or handoff is committed. Solo presence therefore
120
140
  does not require durable workspace history. Native turn hooks still supply the session's
121
- own CLI arguments, so a peer joining later in the same turn does not require reattachment.
141
+ own CLI arguments and workspace directory, so a peer joining later in the same turn
142
+ does not require reattachment and a changed shell directory cannot silently select another
143
+ workspace. Claude SessionStart also restores this owner header after compaction; it
144
+ does not project peer bodies or advance their receipts.
122
145
  Grok instead receives that own header after a terminal tool result through PreToolUse;
123
146
  the first public status call makes it available for subsequent owned commands.
124
147
  Without relevant coordination context, that identity header is the only projected content.
@@ -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. |
package/docs/CLI.md CHANGED
@@ -4,7 +4,12 @@ Use `acc` to install and diagnose integrations, or to inspect the same communica
4
4
  operations that installed skills use on an agent's behalf. Setup commands are for a
5
5
  person; communication commands are the smaller agent-facing vocabulary. Every command
6
6
  accepts `--json` and `--cwd <path>`. `--workspace <config>` selects an explicit workspace
7
- config where supported by the common boundary.
7
+ config where supported by the common boundary. These three global options work before
8
+ or after the command: `acc --cwd /project status` and `acc status --cwd /project`
9
+ select the same workspace. A native hook also supplies `--workspace acc://<reference>`
10
+ to select its saved room directly. This reference is local to ACC's data home, is
11
+ not a network URL, and does not establish session ownership. Ordinary config paths
12
+ retain their existing validation.
8
13
 
9
14
  <!-- test:command -->
10
15
  ```bash
@@ -44,7 +49,15 @@ option value remains data: `--body --help` sends the literal body `--help`.
44
49
  Owner flags are `--session` and `--generation`; both are needed. The CLI also accepts the
45
50
  pair explicitly configured as `ACC_SESSION` and `ACC_GENERATION`. When an active turn hook
46
51
  runs, its `ACC CLI (append):` header supplies the current
47
- session's pair. The installed skill tells the agent to append it to its own commands,
52
+ session's pair, a shell-quoted `--cwd`, and its saved `--workspace` room reference.
53
+ Append the complete header even after changing the shell directory. Claude Code also
54
+ restores this header on `SessionStart`, including compaction, without requiring another prompt.
55
+ Native hooks retain the initial room even when later hook payloads have another
56
+ `cwd`, including a nested Git repository. The header continues to name that initial
57
+ directory and room even if Git later becomes available or unavailable. Standalone CLI
58
+ commands without these arguments still discover their workspace from their own cwd;
59
+ the CLI does not guess a native caller's identity.
60
+ The installed skill tells the agent to append it to its own commands,
48
61
  without a manual attach. Hooks do not export credentials to child processes. Native
49
62
  client IDs, a shared checkout, and a public session ID from
50
63
  `status` cannot establish ownership: a nested client can inherit its parent's environment.
@@ -54,7 +67,7 @@ it to peers or child agents. A later hook after a session restart can supply a n
54
67
  the old generation remains invalid. Solo turns receive only the owner header when there
55
68
  is no coordination context to show. This lets a session use its own inbox if a peer joins
56
69
  later in the same turn. The header alone is not a peer notice or a request to coordinate.
57
- If the context budget cannot hold the complete pair, the hook reports that limitation
70
+ If the context budget cannot hold the complete header, the hook reports that limitation
58
71
  on stderr and keeps any recovery text within budget. Missing or untrusted hooks cannot
59
72
  supply the pair; without it, the CLI still refuses owner operations.
60
73
 
@@ -69,6 +82,11 @@ Without a pair, mutations and `inbox` fail with exit `2` and
69
82
  inferred personal attention. Session-bound [MCP tools](MCP.md) manage their own identity;
70
83
  a generic MCP connection does not inherit a hook participant's inbox.
71
84
 
85
+ An explicit session selector absent from the selected workspace produces exit `5`
86
+ with `caller_workspace_mismatch`, rather than a successful empty status. Restore the
87
+ complete header or the manual attachment directory. A supplied generation must also
88
+ match. This diagnostic does not search other workspaces or recover credentials.
89
+
72
90
  ### Presence and intent
73
91
 
74
92
  ```bash
package/docs/CONCEPTS.md CHANGED
@@ -24,6 +24,13 @@ scoped to one machine and operating-system user. Git worktrees of one repository
24
24
  the same ACC workspace, but their checkout files remain separate. In a plain directory, the
25
25
  directory supplies identity unless optional configuration says otherwise. Git is optional.
26
26
 
27
+ A native session keeps the room selected at startup. Moving into subdirectories,
28
+ nested repositories, or other checkouts does not move that conversation to another
29
+ room. Sessions launched from the same parent directory therefore stay together as
30
+ their agents work in different child repositories. A separate session launched in
31
+ a child repository selects its own initial room. Compaction and resuming the same
32
+ native conversation retain its original room.
33
+
27
34
  A **participant** is the address for communication. A stable participant id can recover
28
35
  messages sent before a restart. A **session** is one current opening of that participant,
29
36
  with a generation token preventing an old process from changing its replacement's state.
@@ -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