agents-can-communicate 0.8.4 → 0.9.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.
- package/README.md +11 -7
- package/bin/entrypoints/acc-antigravity-relay.mjs +2 -4
- package/bin/entrypoints/acc-hook.mjs +33 -19
- package/bin/entrypoints/hook-output.mjs +14 -5
- package/docs/ADAPTER_AUTHORING.md +32 -0
- package/docs/ARCHITECTURE.md +7 -4
- package/docs/CAPABILITIES.md +7 -6
- package/docs/CLI.md +5 -0
- package/docs/CONFIGURATION.md +15 -10
- package/docs/DESIGN_DECISIONS.md +0 -2
- package/docs/GETTING_STARTED.md +39 -6
- package/docs/HOW_IT_WORKS.md +13 -8
- package/docs/SECURITY_MODEL.md +26 -5
- package/docs/TROUBLESHOOTING.md +74 -12
- package/docs/UPGRADING.md +50 -5
- package/node_modules/@agents-can-communicate/adapter-antigravity/certification.json +78 -0
- package/node_modules/@agents-can-communicate/adapter-antigravity/fixtures/certification-provenance.json +111 -0
- package/node_modules/@agents-can-communicate/adapter-antigravity/fixtures/delivery/antigravity-desktop-2.19.1-product-evidence.json +156 -0
- package/node_modules/@agents-can-communicate/adapter-antigravity/fixtures/delivery/antigravity-desktop-2.19.1-product.json +25 -0
- package/node_modules/@agents-can-communicate/adapter-antigravity/fixtures/desktop-2.19.1.json +141 -0
- package/node_modules/@agents-can-communicate/adapter-antigravity/package.json +4 -1
- package/node_modules/@agents-can-communicate/adapter-antigravity/plugin/plugin.json +1 -1
- package/node_modules/@agents-can-communicate/adapter-antigravity/src/adapter.mjs +13 -2
- package/node_modules/@agents-can-communicate/adapter-antigravity/src/agentapi.mjs +7 -5
- package/node_modules/@agents-can-communicate/adapter-antigravity/src/allow-rule.mjs +98 -17
- package/node_modules/@agents-can-communicate/adapter-antigravity/src/client-process.mjs +64 -0
- package/node_modules/@agents-can-communicate/adapter-antigravity/src/desktop-allow-rule.mjs +140 -0
- package/node_modules/@agents-can-communicate/adapter-antigravity/src/desktop-delivery.mjs +110 -0
- package/node_modules/@agents-can-communicate/adapter-antigravity/src/desktop-endpoint.mjs +203 -0
- package/node_modules/@agents-can-communicate/adapter-antigravity/src/install.mjs +61 -30
- package/node_modules/@agents-can-communicate/adapter-antigravity/src/native-delivery.mjs +61 -24
- package/node_modules/@agents-can-communicate/adapter-antigravity/src/relay-endpoint.mjs +30 -20
- package/node_modules/@agents-can-communicate/adapter-antigravity/src/relay-start.mjs +2 -1
- package/node_modules/@agents-can-communicate/adapter-antigravity/src/relay.mjs +20 -12
- package/node_modules/@agents-can-communicate/adapter-antigravity/src/relays.mjs +18 -4
- package/node_modules/@agents-can-communicate/adapter-claude-code/package.json +1 -1
- package/node_modules/@agents-can-communicate/adapter-claude-code/src/adapter.mjs +6 -0
- package/node_modules/@agents-can-communicate/adapter-claude-code/src/inbox-delivery.mjs +50 -37
- package/node_modules/@agents-can-communicate/adapter-claude-code/src/inbox-endpoint.mjs +44 -31
- package/node_modules/@agents-can-communicate/adapter-claude-code/src/inbox-registry.mjs +99 -14
- package/node_modules/@agents-can-communicate/adapter-claude-code/src/inbox-settings.mjs +3 -7
- package/node_modules/@agents-can-communicate/adapter-claude-code/src/inbox-socket-directories.mjs +6 -4
- package/node_modules/@agents-can-communicate/adapter-claude-code/src/install.mjs +32 -7
- package/node_modules/@agents-can-communicate/adapter-codex/package.json +1 -1
- package/node_modules/@agents-can-communicate/adapter-codex/src/app-server-client.mjs +68 -3
- package/node_modules/@agents-can-communicate/adapter-codex/src/daemon-layouts.mjs +13 -0
- package/node_modules/@agents-can-communicate/adapter-codex/src/embedded-host.mjs +87 -13
- package/node_modules/@agents-can-communicate/adapter-codex/src/hooks.mjs +16 -6
- package/node_modules/@agents-can-communicate/adapter-codex/src/install.mjs +18 -9
- package/node_modules/@agents-can-communicate/adapter-codex/src/maintenance-host.mjs +68 -27
- package/node_modules/@agents-can-communicate/adapter-codex/src/native-delivery.mjs +66 -26
- package/node_modules/@agents-can-communicate/adapter-codex/src/native-endpoint.mjs +39 -16
- package/node_modules/@agents-can-communicate/adapter-codex/src/ws-json-rpc.mjs +4 -2
- package/node_modules/@agents-can-communicate/adapter-gemini-cli/extension/gemini-extension.json +1 -1
- package/node_modules/@agents-can-communicate/adapter-gemini-cli/package.json +1 -1
- package/node_modules/@agents-can-communicate/adapter-gemini-cli/src/adapter.mjs +2 -1
- package/node_modules/@agents-can-communicate/adapter-gemini-cli/src/install.mjs +15 -9
- package/node_modules/@agents-can-communicate/adapter-grok/package.json +1 -1
- package/node_modules/@agents-can-communicate/adapter-grok/src/install.mjs +25 -14
- package/node_modules/@agents-can-communicate/adapter-kimi/package.json +1 -1
- package/node_modules/@agents-can-communicate/adapter-kimi/src/adapter.mjs +2 -1
- package/node_modules/@agents-can-communicate/adapter-kimi/src/install.mjs +15 -10
- package/node_modules/@agents-can-communicate/adapter-sdk/package.json +1 -1
- package/node_modules/@agents-can-communicate/adapter-sdk/src/capabilities.mjs +50 -5
- package/node_modules/@agents-can-communicate/adapter-sdk/src/certification.mjs +11 -2
- package/node_modules/@agents-can-communicate/adapter-sdk/src/executables.mjs +149 -0
- package/node_modules/@agents-can-communicate/adapter-sdk/src/hook-shim.mjs +79 -12
- package/node_modules/@agents-can-communicate/adapter-sdk/src/index.mjs +11 -2
- package/node_modules/@agents-can-communicate/adapter-sdk/src/native-attempt.mjs +6 -2
- package/node_modules/@agents-can-communicate/adapter-sdk/src/native-delivery.mjs +82 -17
- package/node_modules/@agents-can-communicate/adapter-sdk/src/native-vocabulary.mjs +9 -2
- package/node_modules/@agents-can-communicate/adapter-sdk/src/private-files.mjs +35 -0
- package/node_modules/@agents-can-communicate/adapter-sdk/src/process-table.mjs +232 -0
- package/node_modules/@agents-can-communicate/adapter-sdk/src/session-binding.mjs +8 -1
- package/node_modules/@agents-can-communicate/adapter-sdk/src/windows-command.mjs +69 -0
- package/node_modules/@agents-can-communicate/adapter-sdk/src/windows-process-chain.wsf +45 -0
- package/node_modules/@agents-can-communicate/cli/package.json +2 -1
- package/node_modules/@agents-can-communicate/cli/src/config-command.mjs +2 -1
- package/node_modules/@agents-can-communicate/cli/src/doctor-command.mjs +60 -4
- package/node_modules/@agents-can-communicate/cli/src/hook-support.mjs +9 -0
- package/node_modules/@agents-can-communicate/cli/src/hook-workspace.mjs +12 -3
- package/node_modules/@agents-can-communicate/cli/src/main.mjs +2 -1
- package/node_modules/@agents-can-communicate/cli/src/managed-runtime/activation.mjs +3 -2
- package/node_modules/@agents-can-communicate/cli/src/managed-runtime/download.mjs +12 -3
- package/node_modules/@agents-can-communicate/cli/src/managed-runtime/entry.mjs +7 -5
- package/node_modules/@agents-can-communicate/cli/src/managed-runtime/generation.mjs +7 -7
- package/node_modules/@agents-can-communicate/cli/src/managed-runtime/launchers.mjs +8 -5
- package/node_modules/@agents-can-communicate/cli/src/managed-runtime/maintenance.mjs +1 -1
- package/node_modules/@agents-can-communicate/cli/src/managed-runtime/mutex.mjs +7 -6
- package/node_modules/@agents-can-communicate/cli/src/managed-runtime/portable-fs.mjs +165 -0
- package/node_modules/@agents-can-communicate/cli/src/managed-runtime/schedule.mjs +1 -1
- package/node_modules/@agents-can-communicate/cli/src/managed-runtime/state.mjs +18 -8
- package/node_modules/@agents-can-communicate/cli/src/managed-runtime/worker.mjs +18 -3
- package/node_modules/@agents-can-communicate/cli/src/native-session-diagnostics.mjs +15 -4
- package/node_modules/@agents-can-communicate/cli/src/platform-paths.mjs +15 -10
- package/node_modules/@agents-can-communicate/cli/src/runtime-paths.mjs +3 -2
- package/node_modules/@agents-can-communicate/cli/src/update-command.mjs +11 -1
- package/node_modules/@agents-can-communicate/cli/src/workspace-discovery.mjs +4 -2
- package/node_modules/@agents-can-communicate/core/package.json +1 -1
- package/node_modules/@agents-can-communicate/core/src/delivery-bindings.mjs +7 -0
- package/node_modules/@agents-can-communicate/core/src/materialisation.mjs +39 -1
- package/node_modules/@agents-can-communicate/core/src/session-opening.mjs +11 -0
- package/node_modules/@agents-can-communicate/core/src/sessions.mjs +8 -2
- package/node_modules/@agents-can-communicate/delivery-router/package.json +1 -1
- package/node_modules/@agents-can-communicate/delivery-router/src/native-reason.mjs +6 -2
- package/node_modules/@agents-can-communicate/delivery-router/src/router.mjs +9 -5
- package/node_modules/@agents-can-communicate/hook-runner/package.json +1 -1
- package/node_modules/@agents-can-communicate/hook-runner/src/client-pid.mjs +65 -6
- package/node_modules/@agents-can-communicate/hook-runner/src/client-version.mjs +80 -0
- package/node_modules/@agents-can-communicate/hook-runner/src/native-attempt.mjs +7 -2
- package/node_modules/@agents-can-communicate/hook-runner/src/native-binding.mjs +6 -3
- package/node_modules/@agents-can-communicate/hook-runner/src/owner-context.mjs +34 -6
- package/node_modules/@agents-can-communicate/hook-runner/src/process-table.mjs +3 -50
- package/node_modules/@agents-can-communicate/hook-runner/src/runner.mjs +167 -34
- package/node_modules/@agents-can-communicate/installer/package.json +3 -2
- package/node_modules/@agents-can-communicate/installer/src/delivery-diagnostics.mjs +11 -2
- package/node_modules/@agents-can-communicate/installer/src/detect.mjs +13 -3
- package/node_modules/@agents-can-communicate/installer/src/hook-support.mjs +5 -0
- package/node_modules/@agents-can-communicate/installer/src/native-activation.mjs +6 -14
- package/node_modules/@agents-can-communicate/installer/src/plan.mjs +14 -0
- package/node_modules/@agents-can-communicate/mcp-server/package.json +1 -1
- package/node_modules/@agents-can-communicate/protocol/package.json +1 -1
- package/node_modules/@agents-can-communicate/protocol/src/config.mjs +6 -2
- package/node_modules/@agents-can-communicate/protocol/src/resources.mjs +6 -2
- package/node_modules/@agents-can-communicate/storage-filesystem/package.json +1 -1
- package/node_modules/@agents-can-communicate/storage-filesystem/src/active-journal.mjs +5 -2
- package/node_modules/@agents-can-communicate/storage-filesystem/src/atomic-json.mjs +41 -23
- package/node_modules/@agents-can-communicate/storage-filesystem/src/doomed-directory.mjs +4 -12
- package/node_modules/@agents-can-communicate/storage-filesystem/src/identity.mjs +7 -3
- package/node_modules/@agents-can-communicate/storage-filesystem/src/index.mjs +1 -0
- package/node_modules/@agents-can-communicate/storage-filesystem/src/journal.mjs +24 -7
- package/node_modules/@agents-can-communicate/storage-filesystem/src/portable-fs.mjs +165 -0
- package/node_modules/@agents-can-communicate/storage-filesystem/src/publication-path.mjs +8 -0
- package/node_modules/@agents-can-communicate/storage-filesystem/src/reclaim.mjs +26 -17
- package/node_modules/@agents-can-communicate/storage-filesystem/src/recovery.mjs +7 -5
- package/node_modules/@agents-can-communicate/storage-filesystem/src/retention.mjs +7 -16
- package/node_modules/@agents-can-communicate/storage-filesystem/src/safe-directory.mjs +138 -24
- package/node_modules/@agents-can-communicate/storage-filesystem/src/safe-file.mjs +3 -2
- package/node_modules/@agents-can-communicate/storage-filesystem/src/stage-sweep.mjs +9 -6
- package/node_modules/@agents-can-communicate/storage-filesystem/src/store.mjs +30 -8
- package/node_modules/@agents-can-communicate/storage-filesystem/src/writer-mutex.mjs +102 -32
- package/package.json +1 -5
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { decisionBody } from "@agents-can-communicate/adapter-sdk";
|
|
2
|
+
|
|
3
|
+
import { createAgentApi } from "./agentapi.mjs";
|
|
4
|
+
import { DESKTOP_MODES, DESKTOP_PROTOCOL, httpPort, listDesktopEndpoints, newDesktopEndpointId,
|
|
5
|
+
readDesktopEndpoint, readDesktopServer, removeDesktopEndpoint, writeDesktopEndpoint }
|
|
6
|
+
from "./desktop-endpoint.mjs";
|
|
7
|
+
import { renderMessage } from "./relay.mjs";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Live delivery into an Antigravity 2.0 desktop conversation.
|
|
11
|
+
*
|
|
12
|
+
* No relay and nothing for the agent to run: the desktop's language server is
|
|
13
|
+
* the client process ACC's own hook ran under, and its command line carries the
|
|
14
|
+
* endpoint. Each bind finds the port that serves `agentapi` and proves it
|
|
15
|
+
* answers for this conversation; each offer reads the token again from the
|
|
16
|
+
* same, still-running server and hands it to one `agentapi` child in that
|
|
17
|
+
* child's environment, exactly as the relay does for Antigravity CLI.
|
|
18
|
+
*/
|
|
19
|
+
export const DESKTOP_TRANSPORT = "antigravity-desktop";
|
|
20
|
+
const LEASE_MS = 120_000;
|
|
21
|
+
const CODES = new Map([["recipient_unavailable", "recipient_unavailable"],
|
|
22
|
+
["transport_rejected", "transport_rejected"]]);
|
|
23
|
+
|
|
24
|
+
const closed = (clientVersion, reasonCode) => ({ supported: false, clientVersion: clientVersion ?? null,
|
|
25
|
+
protocolContract: DESKTOP_PROTOCOL, modes: [], opaqueEndpointRef: null, leaseUntil: null, reasonCode });
|
|
26
|
+
const handshake = record => ({ supported: true, clientVersion: record.clientVersion,
|
|
27
|
+
protocolContract: DESKTOP_PROTOCOL, modes: [...DESKTOP_MODES], opaqueEndpointRef: record.endpointId,
|
|
28
|
+
leaseUntil: record.leaseUntil, reasonCode: null });
|
|
29
|
+
|
|
30
|
+
const apiFor = ({ server, port, conversationId, runAgentApi, timeoutMs }) => createAgentApi({
|
|
31
|
+
endpoint: { lsAddress: `127.0.0.1:${port}`, csrfToken: server.token, conversationId },
|
|
32
|
+
command: server.executable, timeoutMs, ...(runAgentApi ? { run: runAgentApi } : {}) });
|
|
33
|
+
|
|
34
|
+
// The server the record names, if it is the same process still: same pid,
|
|
35
|
+
// same start time, still the desktop language server.
|
|
36
|
+
async function sameServer(record, readServer) {
|
|
37
|
+
const server = await readServer(record.serverPid);
|
|
38
|
+
return server !== null && server.startedAt === record.serverStartedAt ? server : null;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* The desktop bind, or null when the client process is not the desktop's
|
|
43
|
+
* language server - the caller then binds Antigravity CLI's relay instead.
|
|
44
|
+
*/
|
|
45
|
+
export async function bindDesktop({ event, clientPid, clientVersion, runtimeDir, timeoutMs = 750,
|
|
46
|
+
now = Date.now, readServer = readDesktopServer, findPort = httpPort, runAgentApi } = {}) {
|
|
47
|
+
const server = await readServer(clientPid);
|
|
48
|
+
if (server === null) return null;
|
|
49
|
+
const version = server.version ?? clientVersion;
|
|
50
|
+
if (typeof version !== "string" || !/^\d+\.\d+\.\d+$/.test(version)) {
|
|
51
|
+
return closed(clientVersion, "version_unavailable");
|
|
52
|
+
}
|
|
53
|
+
const conversationId = event?.sessionId;
|
|
54
|
+
if (typeof conversationId !== "string" || !/^[A-Za-z0-9-]{8,128}$/.test(conversationId)) {
|
|
55
|
+
return closed(version, "handshake_failed");
|
|
56
|
+
}
|
|
57
|
+
const port = await findPort(clientPid);
|
|
58
|
+
if (port === null) return closed(version, "native_endpoint_unavailable");
|
|
59
|
+
const answer = await apiFor({ server, port, conversationId, runAgentApi, timeoutMs })
|
|
60
|
+
.conversationMetadata();
|
|
61
|
+
if (!answer.ok) {
|
|
62
|
+
return closed(version, answer.reasonCode === "recipient_unavailable"
|
|
63
|
+
? "native_session_unavailable" : "handshake_failed");
|
|
64
|
+
}
|
|
65
|
+
// One record per conversation: the one this bind proved.
|
|
66
|
+
for (const old of await listDesktopEndpoints({ runtimeDir })) {
|
|
67
|
+
if (old.conversationId === conversationId) await removeDesktopEndpoint({ runtimeDir, endpointId: old.endpointId });
|
|
68
|
+
}
|
|
69
|
+
const record = { schemaVersion: 1, endpointId: newDesktopEndpointId(), conversationId,
|
|
70
|
+
serverPid: clientPid, serverStartedAt: server.startedAt, port, clientVersion: version,
|
|
71
|
+
protocolContract: DESKTOP_PROTOCOL, modes: [...DESKTOP_MODES],
|
|
72
|
+
leaseUntil: new Date(now() + LEASE_MS).toISOString() };
|
|
73
|
+
await writeDesktopEndpoint({ runtimeDir, record });
|
|
74
|
+
return handshake(record);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** The router's re-check before an offer on an expired lease. */
|
|
78
|
+
export async function refreshDesktop({ binding, runtimeDir, timeoutMs = 750, now = Date.now,
|
|
79
|
+
readServer = readDesktopServer, runAgentApi } = {}) {
|
|
80
|
+
const record = await readDesktopEndpoint({ runtimeDir, endpointId: binding?.opaqueEndpointRef });
|
|
81
|
+
if (record === null) return closed(binding?.clientVersion, "native_session_unavailable");
|
|
82
|
+
const server = await sameServer(record, readServer);
|
|
83
|
+
if (server === null) return closed(record.clientVersion, "native_session_unavailable");
|
|
84
|
+
const answer = await apiFor({ server, port: record.port, conversationId: record.conversationId,
|
|
85
|
+
runAgentApi, timeoutMs }).conversationMetadata();
|
|
86
|
+
if (!answer.ok) return closed(record.clientVersion, "handshake_failed");
|
|
87
|
+
const renewed = { ...record, leaseUntil: new Date(now() + LEASE_MS).toISOString() };
|
|
88
|
+
await writeDesktopEndpoint({ runtimeDir, record: renewed });
|
|
89
|
+
return handshake(renewed);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** One fenced ACC message into the conversation, rendered here, never by the sender. */
|
|
93
|
+
export async function offerDesktop({ binding, message, runtimeDir, timeoutMs = 8_000, now = Date.now,
|
|
94
|
+
readServer = readDesktopServer, runAgentApi, budgetBytes = 6_000 } = {}) {
|
|
95
|
+
const rejected = safeErrorCode => ({ accepted: false, transport: DESKTOP_TRANSPORT,
|
|
96
|
+
clientVersion: binding?.clientVersion ?? null, safeErrorCode });
|
|
97
|
+
const record = await readDesktopEndpoint({ runtimeDir, endpointId: binding?.opaqueEndpointRef });
|
|
98
|
+
if (record === null || Date.parse(record.leaseUntil) <= now()) return rejected("recipient_unavailable");
|
|
99
|
+
const server = await sameServer(record, readServer);
|
|
100
|
+
if (server === null) return rejected("recipient_unavailable");
|
|
101
|
+
const text = renderMessage({ messageId: message.messageId, kind: message.kind,
|
|
102
|
+
subject: message.subject ?? "", body: decisionBody(message),
|
|
103
|
+
...(typeof message.inReplyTo === "string" ? { inReplyTo: message.inReplyTo } : {}),
|
|
104
|
+
...(typeof message.fromParticipantId === "string" ? { fromParticipantId: message.fromParticipantId } : {}) },
|
|
105
|
+
{ budgetBytes });
|
|
106
|
+
const answer = await apiFor({ server, port: record.port, conversationId: record.conversationId,
|
|
107
|
+
runAgentApi, timeoutMs }).sendMessage(text);
|
|
108
|
+
return answer.ok ? { accepted: true, transport: DESKTOP_TRANSPORT, clientVersion: record.clientVersion }
|
|
109
|
+
: rejected(CODES.get(answer.reasonCode) ?? "transport_error");
|
|
110
|
+
}
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { randomBytes } from "node:crypto";
|
|
3
|
+
import { constants } from "node:fs";
|
|
4
|
+
import { access, lstat, mkdir, open, readdir, realpath, rename, rm } from "node:fs/promises";
|
|
5
|
+
import http from "node:http";
|
|
6
|
+
import path from "node:path";
|
|
7
|
+
|
|
8
|
+
import { closedTo, openRegularNoFollow } from "@agents-can-communicate/adapter-sdk";
|
|
9
|
+
|
|
10
|
+
import { desktopServer, flagValue } from "./client-process.mjs";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Where a desktop conversation's live endpoint is, and how it is reached.
|
|
14
|
+
*
|
|
15
|
+
* Antigravity 2.0 serves every conversation from one language server, which
|
|
16
|
+
* takes its CSRF token on its command line. ACC reads that command line when it
|
|
17
|
+
* binds and again each time it delivers, from the one process its own hook ran
|
|
18
|
+
* under, and never writes the token anywhere: the record below holds the
|
|
19
|
+
* server's pid, its start time - so a reused pid is not mistaken for it - and
|
|
20
|
+
* the loopback port that answered, nothing that would let anyone drive it.
|
|
21
|
+
*/
|
|
22
|
+
export const DESKTOP_PROTOCOL = "antigravity-desktop-agentapi-v1";
|
|
23
|
+
export const DESKTOP_MODES = Object.freeze(["livePush", "idleWake", "busyQueue"]);
|
|
24
|
+
|
|
25
|
+
const KEYS = ["schemaVersion", "endpointId", "conversationId", "serverPid", "serverStartedAt",
|
|
26
|
+
"port", "clientVersion", "protocolContract", "modes", "leaseUntil"];
|
|
27
|
+
const ENDPOINT_ID = /^antigravity_desktop_[a-f0-9]{32}$/;
|
|
28
|
+
const CONVERSATION = /^[A-Za-z0-9-]{8,128}$/;
|
|
29
|
+
const VERSION = /^\d+\.\d+\.\d+$/;
|
|
30
|
+
const LEASE = /^\d{4}-\d\d-\d\dT\d\d:\d\d:\d\d\.\d{3}Z$/;
|
|
31
|
+
// `ps -o lstart`: "Sun Oct 4 22:55:53 2026", always 24 characters.
|
|
32
|
+
const STARTED = /^[A-Z][a-z]{2} [A-Z][a-z]{2} [ \d]\d \d\d:\d\d:\d\d \d{4}$/;
|
|
33
|
+
const MAX_BYTES = 4_096;
|
|
34
|
+
const own = info => typeof process.getuid !== "function" || info.uid === process.getuid();
|
|
35
|
+
const isPid = value => Number.isInteger(value) && value > 0;
|
|
36
|
+
const invalid = () => new Error("invalid Antigravity desktop endpoint");
|
|
37
|
+
|
|
38
|
+
export const desktopDir = runtimeDir => path.join(runtimeDir, "native", "antigravity-desktop");
|
|
39
|
+
export const newDesktopEndpointId = () => `antigravity_desktop_${randomBytes(16).toString("hex")}`;
|
|
40
|
+
export const isDesktopEndpointId = value => typeof value === "string" && ENDPOINT_ID.test(value);
|
|
41
|
+
|
|
42
|
+
export function validDesktopEndpoint(record) {
|
|
43
|
+
return record !== null && typeof record === "object" && !Array.isArray(record)
|
|
44
|
+
&& Object.keys(record).length === KEYS.length && KEYS.every(key => Object.hasOwn(record, key))
|
|
45
|
+
&& record.schemaVersion === 1 && ENDPOINT_ID.test(record.endpointId)
|
|
46
|
+
&& CONVERSATION.test(record.conversationId) && isPid(record.serverPid)
|
|
47
|
+
&& typeof record.serverStartedAt === "string" && STARTED.test(record.serverStartedAt)
|
|
48
|
+
&& Number.isInteger(record.port) && record.port > 0 && record.port < 65_536
|
|
49
|
+
&& VERSION.test(record.clientVersion) && record.protocolContract === DESKTOP_PROTOCOL
|
|
50
|
+
&& Array.isArray(record.modes) && record.modes.every(mode => DESKTOP_MODES.includes(mode))
|
|
51
|
+
&& typeof record.leaseUntil === "string" && LEASE.test(record.leaseUntil)
|
|
52
|
+
&& Number.isFinite(Date.parse(record.leaseUntil));
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async function directory(runtimeDir, create = false) {
|
|
56
|
+
if (typeof runtimeDir !== "string" || !path.isAbsolute(runtimeDir)) throw invalid();
|
|
57
|
+
const dir = desktopDir(await realpath(runtimeDir));
|
|
58
|
+
if (create) await mkdir(dir, { recursive: true, mode: 0o700 });
|
|
59
|
+
const info = await lstat(dir);
|
|
60
|
+
if (!info.isDirectory() || info.isSymbolicLink() || !own(info) || !closedTo(info, 0o077)) throw invalid();
|
|
61
|
+
return dir;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export async function writeDesktopEndpoint({ runtimeDir, record }) {
|
|
65
|
+
if (!validDesktopEndpoint(record)) throw invalid();
|
|
66
|
+
const text = `${JSON.stringify(record)}\n`;
|
|
67
|
+
if (Buffer.byteLength(text) > MAX_BYTES) throw invalid();
|
|
68
|
+
const dir = await directory(runtimeDir, true);
|
|
69
|
+
const file = path.join(dir, `${record.endpointId}.json`);
|
|
70
|
+
const temporary = path.join(dir, `.${record.endpointId}.${randomBytes(8).toString("hex")}.tmp`);
|
|
71
|
+
let handle;
|
|
72
|
+
try {
|
|
73
|
+
handle = await open(temporary, "wx", 0o600);
|
|
74
|
+
await handle.writeFile(text, "utf8");
|
|
75
|
+
await handle.close(); handle = null;
|
|
76
|
+
await rename(temporary, file);
|
|
77
|
+
} finally {
|
|
78
|
+
await handle?.close().catch(() => null);
|
|
79
|
+
await rm(temporary, { force: true }).catch(() => null);
|
|
80
|
+
}
|
|
81
|
+
return file;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export async function readDesktopEndpoint({ runtimeDir, endpointId }) {
|
|
85
|
+
if (!isDesktopEndpointId(endpointId)) return null;
|
|
86
|
+
let handle;
|
|
87
|
+
try {
|
|
88
|
+
handle = await openRegularNoFollow(path.join(await directory(runtimeDir), `${endpointId}.json`));
|
|
89
|
+
const info = await handle.stat();
|
|
90
|
+
if (!info.isFile() || !own(info) || info.size > MAX_BYTES || !closedTo(info, 0o077)) return null;
|
|
91
|
+
const record = JSON.parse(await handle.readFile("utf8"));
|
|
92
|
+
return validDesktopEndpoint(record) && record.endpointId === endpointId ? record : null;
|
|
93
|
+
} catch {
|
|
94
|
+
return null;
|
|
95
|
+
} finally {
|
|
96
|
+
await handle?.close().catch(() => null);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export async function listDesktopEndpoints({ runtimeDir }) {
|
|
101
|
+
let names;
|
|
102
|
+
try {
|
|
103
|
+
names = await readdir(await directory(runtimeDir));
|
|
104
|
+
} catch {
|
|
105
|
+
return [];
|
|
106
|
+
}
|
|
107
|
+
const found = [];
|
|
108
|
+
for (const name of names) {
|
|
109
|
+
if (!name.endsWith(".json")) continue;
|
|
110
|
+
const record = await readDesktopEndpoint({ runtimeDir, endpointId: name.slice(0, -5) });
|
|
111
|
+
if (record !== null) found.push(record);
|
|
112
|
+
}
|
|
113
|
+
return found;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export async function removeDesktopEndpoint({ runtimeDir, endpointId }) {
|
|
117
|
+
if (!isDesktopEndpointId(endpointId)) return;
|
|
118
|
+
try {
|
|
119
|
+
await rm(path.join(await directory(runtimeDir), `${endpointId}.json`), { force: true });
|
|
120
|
+
} catch { /* a stale record is refused on read; removal is best effort */ }
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const runFile = (file, args, { timeout }) => new Promise(resolve => {
|
|
124
|
+
execFile(file, args, { timeout, killSignal: "SIGKILL", windowsHide: true, maxBuffer: 1024 * 1024 },
|
|
125
|
+
(error, stdout) => resolve({ ok: error === null, stdout: String(stdout ?? "") }));
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* The desktop language server running as `pid`, read from its command line
|
|
130
|
+
* now: when it started, the executable that serves `agentapi`, the version it
|
|
131
|
+
* names and its CSRF token. Null for any other process, a gone one, or a
|
|
132
|
+
* command line that carries no token. The token is returned to the caller for
|
|
133
|
+
* one `agentapi` call and is never stored.
|
|
134
|
+
*/
|
|
135
|
+
export async function readDesktopServer(pid, { run = runFile, timeoutMs = 1_000,
|
|
136
|
+
platform = process.platform } = {}) {
|
|
137
|
+
// Read through POSIX ps; a Windows desktop build is uncaptured.
|
|
138
|
+
if (!isPid(pid) || platform === "win32") return null;
|
|
139
|
+
const { ok, stdout } = await run("/bin/ps", ["-ww", "-p", String(pid), "-o", "lstart=,args="],
|
|
140
|
+
{ timeout: timeoutMs });
|
|
141
|
+
if (!ok) return null;
|
|
142
|
+
const line = /^(.{24})\s+(.+)$/.exec(stdout.trim());
|
|
143
|
+
if (line === null || !STARTED.test(line[1])) return null;
|
|
144
|
+
const server = desktopServer(line[2]);
|
|
145
|
+
if (server === null) return null;
|
|
146
|
+
const token = flagValue(server.words, "--csrf_token");
|
|
147
|
+
if (typeof token !== "string" || token === "" || token.startsWith("-")) return null;
|
|
148
|
+
return { startedAt: line[1], executable: server.executable, version: server.version, token };
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Where lsof lives: /usr/sbin on macOS, /usr/bin on Linux. Never PATH: a
|
|
152
|
+
// directory an operator can write to must not supply the tool that names the
|
|
153
|
+
// port a token is sent to.
|
|
154
|
+
const LSOF_CANDIDATES = Object.freeze(["/usr/sbin/lsof", "/usr/bin/lsof"]);
|
|
155
|
+
const LOOPBACK = new Set(["127.0.0.1", "localhost", "[::1]"]);
|
|
156
|
+
|
|
157
|
+
async function resolveLsof() {
|
|
158
|
+
for (const candidate of LSOF_CANDIDATES) {
|
|
159
|
+
try { await access(candidate, constants.X_OK); return candidate; } catch { /* next */ }
|
|
160
|
+
}
|
|
161
|
+
return null;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/** The loopback TCP ports `pid` listens on, in the order lsof lists them. */
|
|
165
|
+
export async function listeningPorts(pid, { run = runFile, lsof = resolveLsof, timeoutMs = 1_000 } = {}) {
|
|
166
|
+
const file = await lsof();
|
|
167
|
+
if (file === null || !isPid(pid)) return [];
|
|
168
|
+
const { stdout } = await run(file, ["-nP", "-a", "-p", String(pid), "-iTCP", "-sTCP:LISTEN", "-Fn"],
|
|
169
|
+
{ timeout: timeoutMs });
|
|
170
|
+
const ports = [];
|
|
171
|
+
for (const line of stdout.split("\n")) {
|
|
172
|
+
const match = /^n(.+):(\d+)$/.exec(line.trim());
|
|
173
|
+
if (match === null || !LOOPBACK.has(match[1])) continue;
|
|
174
|
+
const port = Number(match[2]);
|
|
175
|
+
if (port > 0 && port < 65_536 && !ports.includes(port)) ports.push(port);
|
|
176
|
+
}
|
|
177
|
+
return ports;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Whether a loopback port is the server's plain-HTTP endpoint, the one
|
|
182
|
+
* `agentapi` talks to. The server's other port speaks TLS and answers a plain
|
|
183
|
+
* request with 400; the HTTP one answers `/healthz` with 200 (2.19.1). No token
|
|
184
|
+
* is sent.
|
|
185
|
+
*/
|
|
186
|
+
export function answersHealth(port, { timeoutMs = 500 } = {}) {
|
|
187
|
+
return new Promise(resolve => {
|
|
188
|
+
const request = http.get({ host: "127.0.0.1", port, path: "/healthz", timeout: timeoutMs }, response => {
|
|
189
|
+
response.resume();
|
|
190
|
+
resolve(response.statusCode === 200);
|
|
191
|
+
});
|
|
192
|
+
request.on("timeout", () => { request.destroy(); resolve(false); });
|
|
193
|
+
request.on("error", () => resolve(false));
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/** The first of the server's ports that answers as its HTTP endpoint, or null. */
|
|
198
|
+
export async function httpPort(pid, { ports = listeningPorts, health = answersHealth } = {}) {
|
|
199
|
+
for (const port of await ports(pid)) {
|
|
200
|
+
if (await health(port)) return port;
|
|
201
|
+
}
|
|
202
|
+
return null;
|
|
203
|
+
}
|
|
@@ -5,14 +5,16 @@ import path from "node:path";
|
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
6
|
import { promisify } from "node:util";
|
|
7
7
|
|
|
8
|
-
import { bakeSkillCommand, blankJson, defaultAntigravityRelay, isShellWord, ownVersion,
|
|
9
|
-
removeIfEmpty,
|
|
8
|
+
import { bakeSkillCommand, blankJson, defaultAntigravityRelay, isShellWord, mergeEnv, ownVersion,
|
|
9
|
+
removeIfEmpty, shellQuote, shortPath, stampPluginVersion, windowsHookCommand, writeCliShim,
|
|
10
|
+
writeForeignJson, writeHookShim, writeNodeShim }
|
|
10
11
|
from "@agents-can-communicate/adapter-sdk";
|
|
11
12
|
import { AccError, EXIT } from "@agents-can-communicate/protocol";
|
|
12
13
|
|
|
13
|
-
import { agySettingsPath, cliWrapperPath, ensureAllowRule, inspectAllowRule,
|
|
14
|
+
import { agySettingsPath, cliWrapperPath, ensureAllowRule, inspectAllowRule, inspectRelayRule,
|
|
14
15
|
withdrawAllowRule } from "./allow-rule.mjs";
|
|
15
|
-
import {
|
|
16
|
+
import { ensureDesktopRule, inspectDesktopRule, withdrawDesktopRule } from "./desktop-allow-rule.mjs";
|
|
17
|
+
import { relayShimName, relayStartCommand, runningRelays, stopRelays } from "./relays.mjs";
|
|
16
18
|
|
|
17
19
|
const run = promisify(execFile);
|
|
18
20
|
|
|
@@ -181,9 +183,15 @@ const exists = async target => {
|
|
|
181
183
|
* Each command carries its own event name, because the payload does not. The
|
|
182
184
|
* same shim answers every event and is told which one it is running as.
|
|
183
185
|
*/
|
|
184
|
-
export function accHookConfig(shim) {
|
|
186
|
+
export function accHookConfig(shim, { platform = process.platform } = {}) {
|
|
187
|
+
// On Windows this client runs a hook through Go's `cmd /c`, which escapes an
|
|
188
|
+
// inner quote as `\"` - a form cmd does not read (google-antigravity/
|
|
189
|
+
// antigravity-cli#222). The command therefore carries no quotes at all.
|
|
190
|
+
const command = event => (platform === "win32"
|
|
191
|
+
? windowsHookCommand("unquoted", { node: process.execPath, shim, args: [event] })
|
|
192
|
+
: `sh ${shellQuote(shim)} ${event}`);
|
|
185
193
|
return { [ACC_NAMESPACE]: Object.fromEntries(ACC_REGISTERED_EVENTS.map(event =>
|
|
186
|
-
[event, [{ type: "command", command:
|
|
194
|
+
[event, [{ type: "command", command: command(event), timeout: 10 }]])) };
|
|
187
195
|
}
|
|
188
196
|
|
|
189
197
|
/**
|
|
@@ -224,7 +232,7 @@ export function registeredSource(readback) {
|
|
|
224
232
|
*/
|
|
225
233
|
async function runAgy(args, { home, env, timeoutMs = 60_000 } = {}) {
|
|
226
234
|
return run("agy", args, { timeout: timeoutMs,
|
|
227
|
-
env:
|
|
235
|
+
env: mergeEnv(process.env, { ...env, ...(typeof home === "string" ? { HOME: home } : {}) }) });
|
|
228
236
|
}
|
|
229
237
|
|
|
230
238
|
const agyFor = context => context.runAgy ?? runAgy;
|
|
@@ -308,8 +316,6 @@ const importedCopy = readback => (Array.isArray(readback?.skills) ? readback.ski
|
|
|
308
316
|
* directory, so it outlives the Gemini CLI integration this client otherwise
|
|
309
317
|
* borrows from.
|
|
310
318
|
*/
|
|
311
|
-
const shellQuote = value => `"${String(value).replace(/(["\\$`])/g, "\\$1")}"`;
|
|
312
|
-
|
|
313
319
|
/**
|
|
314
320
|
* The command ACC's context line and skill tell the agent to run once per
|
|
315
321
|
* conversation. It has to run in the agent's own shell - that is the only
|
|
@@ -317,8 +323,13 @@ const shellQuote = value => `"${String(value).replace(/(["\\$`])/g, "\\$1")}"`;
|
|
|
317
323
|
* not something ACC could start on its behalf. It never falls back to `acc`
|
|
318
324
|
* on the PATH the way the CLI shim does: `acc start` is not a command.
|
|
319
325
|
*/
|
|
320
|
-
async function writeRelayShim({ home, relay = defaultAntigravityRelay(), node = process.execPath
|
|
321
|
-
|
|
326
|
+
async function writeRelayShim({ home, relay = defaultAntigravityRelay(), node = process.execPath,
|
|
327
|
+
platform = process.platform }) {
|
|
328
|
+
const target = path.join(shimDir(home), relayShimName(platform));
|
|
329
|
+
if (platform === "win32") {
|
|
330
|
+
return writeNodeShim({ file: target, target: relay, node, goneExit: 0,
|
|
331
|
+
gone: "ACC: live delivery is not installed here; run acc install --adapter antigravity" });
|
|
332
|
+
}
|
|
322
333
|
await mkdir(path.dirname(target), { recursive: true });
|
|
323
334
|
await writeFile(target, [
|
|
324
335
|
"#!/bin/sh",
|
|
@@ -341,10 +352,10 @@ async function writeRelayShim({ home, relay = defaultAntigravityRelay(), node =
|
|
|
341
352
|
}
|
|
342
353
|
|
|
343
354
|
async function installSkillPlugin(context) {
|
|
344
|
-
const { home, cli, node } = context;
|
|
355
|
+
const { home, cli, node, hostPlatform = process.platform } = context;
|
|
345
356
|
const manifestExisted = await exists(vendorManifestPath(home));
|
|
346
|
-
const cliShim = await writeCliShim({ dir: shimDir(home), cli, node });
|
|
347
|
-
await writeRelayShim({ home, relay: context.antigravityRelay, node });
|
|
357
|
+
const cliShim = await writeCliShim({ dir: shimDir(home), cli, node, platform: hostPlatform });
|
|
358
|
+
await writeRelayShim({ home, relay: context.antigravityRelay, node, platform: hostPlatform });
|
|
348
359
|
const stage = await mkdtemp(path.join(tmpdir(), "acc-antigravity-plugin-"));
|
|
349
360
|
try {
|
|
350
361
|
const plugin = path.join(stage, ACC_PLUGIN_NAME);
|
|
@@ -353,7 +364,7 @@ async function installSkillPlugin(context) {
|
|
|
353
364
|
version: await ownVersion(import.meta.url), io: { readFile, writeFile } });
|
|
354
365
|
// Bare when the path allows it: 1.2.12 matched no allow rule against a
|
|
355
366
|
// command whose first word was quoted (issue #214).
|
|
356
|
-
await bakeSkillCommand({ root: plugin, cliShim, bareWhenSafe: true });
|
|
367
|
+
await bakeSkillCommand({ root: plugin, cliShim, bareWhenSafe: true, platform: hostPlatform });
|
|
357
368
|
await agyFor(context)(["plugin", "install", plugin], { home, env: context.env });
|
|
358
369
|
} finally {
|
|
359
370
|
await rm(stage, { recursive: true, force: true });
|
|
@@ -382,14 +393,21 @@ async function installSkillPlugin(context) {
|
|
|
382
393
|
* unverified - never as registered.
|
|
383
394
|
*/
|
|
384
395
|
export async function installAntigravity(context) {
|
|
385
|
-
const { home, runner, node } = context;
|
|
396
|
+
const { home, runner, node, hostPlatform = process.platform } = context;
|
|
386
397
|
const file = hooksPathFor(context);
|
|
387
398
|
const found = await readJson(file, null);
|
|
388
399
|
const existing = found ?? {};
|
|
389
400
|
|
|
390
|
-
const
|
|
391
|
-
runner, node });
|
|
392
|
-
|
|
401
|
+
const written = await writeHookShim({ dir: shimDir(home), adapterId: "antigravity",
|
|
402
|
+
runner, node, platform: hostPlatform });
|
|
403
|
+
// A profile path with a space ("C:\Users\First Last") goes in by its 8.3 name.
|
|
404
|
+
const shim = hostPlatform === "win32" ? await shortPath(written) : written;
|
|
405
|
+
if (hostPlatform === "win32" && /\s/.test(shim)) {
|
|
406
|
+
throw new AccError(EXIT.DATA, "Antigravity CLI cannot run a hook whose path has a space, and "
|
|
407
|
+
+ `this volume gives ${written} no 8.3 name; enable 8.3 names on it `
|
|
408
|
+
+ "(fsutil 8dot3name set <drive>: 0) and run acc install again", { shim: written });
|
|
409
|
+
}
|
|
410
|
+
const merged = { ...existing, ...accHookConfig(shim, { platform: hostPlatform }) };
|
|
393
411
|
await mkdir(path.dirname(file), { recursive: true });
|
|
394
412
|
await writeForeignJson(file, merged, { readFile, writeFile, mkdir });
|
|
395
413
|
// Recorded beside the shim, never inside the client's file. A file holding
|
|
@@ -405,11 +423,13 @@ export async function installAntigravity(context) {
|
|
|
405
423
|
await installSkillPlugin(context);
|
|
406
424
|
// After the skill, whose wrapper the rule names. Always on (#214, 2026-09-27).
|
|
407
425
|
const allow = await ensureAllowRule(context);
|
|
426
|
+
// Antigravity 2.0 keeps grants of its own, read only where it has run.
|
|
427
|
+
const desktop = await ensureDesktopRule(context);
|
|
408
428
|
const changes = [shimDir(home), file, pluginInstallPath(home), vendorManifestPath(home),
|
|
409
|
-
...allow.changes];
|
|
429
|
+
...allow.changes, ...desktop.changes];
|
|
410
430
|
|
|
411
|
-
const diagnostics = [...allow.diagnostics];
|
|
412
|
-
const needsAction = [...allow.needsAction];
|
|
431
|
+
const diagnostics = [...allow.diagnostics, ...desktop.diagnostics];
|
|
432
|
+
const needsAction = [...allow.needsAction, ...desktop.needsAction];
|
|
413
433
|
if (locationOf(context) === "workspace") {
|
|
414
434
|
diagnostics.push("a workspace registration loads only while this project is an open "
|
|
415
435
|
+ `Antigravity workspace; in print mode pass --add-dir ${context.antigravityWorkspace}`);
|
|
@@ -533,12 +553,14 @@ export async function uninstallAntigravity(context) {
|
|
|
533
553
|
|
|
534
554
|
// The allow rule ACC recorded adding, and nothing else in the operator's file.
|
|
535
555
|
const allow = await withdrawAllowRule(context);
|
|
536
|
-
|
|
537
|
-
|
|
556
|
+
const desktop = await withdrawDesktopRule(context);
|
|
557
|
+
changes.push(...allow.changes, ...desktop.changes);
|
|
558
|
+
diagnostics.push(...allow.diagnostics, ...desktop.diagnostics);
|
|
559
|
+
const needsAction = [...allow.needsAction, ...desktop.needsAction];
|
|
538
560
|
|
|
539
561
|
if (!keep.includes(shimDir(home))) await rm(shimDir(home), { recursive: true, force: true });
|
|
540
562
|
return { ok: true, changes, diagnostics,
|
|
541
|
-
...(
|
|
563
|
+
...(needsAction.length > 0 ? { needsAction } : {}) };
|
|
542
564
|
}
|
|
543
565
|
|
|
544
566
|
/**
|
|
@@ -576,9 +598,13 @@ export async function detectAntigravity(context) {
|
|
|
576
598
|
// Read from files only, so it is known for a client ACC did not run. It is
|
|
577
599
|
// the client's own approval control over what a woken session can answer.
|
|
578
600
|
const commandApproval = await inspectAllowRule(context);
|
|
579
|
-
const
|
|
601
|
+
const relayApproval = await inspectRelayRule(context);
|
|
602
|
+
const desktopApproval = await inspectDesktopRule(context);
|
|
603
|
+
const approval = { commandApproval, relayApproval,
|
|
580
604
|
inboundDelivery: { state: commandApproval.state, diagnostic: commandApproval.diagnostic } };
|
|
581
605
|
diagnostics.push(commandApproval.diagnostic);
|
|
606
|
+
if (relayApproval.state !== "unreadable") diagnostics.push(relayApproval.diagnostic);
|
|
607
|
+
if (desktopApproval.diagnostic !== null) diagnostics.push(desktopApproval.diagnostic);
|
|
582
608
|
// The installer runs every adapter's detect and passes a null version for a
|
|
583
609
|
// client it did not find. Asking agy then would spawn a binary the installer
|
|
584
610
|
// could not run a moment ago, or - where it does exist but did not answer -
|
|
@@ -644,7 +670,7 @@ export async function detectAntigravity(context) {
|
|
|
644
670
|
}
|
|
645
671
|
// Every install writes the rule, so where ACC's wrapper is on disk a missing
|
|
646
672
|
// or unreadable rule is an install to finish, whatever the delivery policy.
|
|
647
|
-
if (await exists(cliWrapperPath(context.home))) {
|
|
673
|
+
if (await exists(cliWrapperPath(context.home, context.hostPlatform))) {
|
|
648
674
|
if (commandApproval.state === "prompts") {
|
|
649
675
|
needsAction.push(`acc install --adapter antigravity # ${commandApproval.rule} is missing `
|
|
650
676
|
+ `from permissions.allow in ${commandApproval.file}, so each ACC command waits for `
|
|
@@ -652,6 +678,11 @@ export async function detectAntigravity(context) {
|
|
|
652
678
|
} else if (commandApproval.state === "unreadable") {
|
|
653
679
|
needsAction.push(commandApproval.diagnostic);
|
|
654
680
|
}
|
|
681
|
+
if (relayApproval.state === "prompts") {
|
|
682
|
+
needsAction.push(`acc install --adapter antigravity # ${relayApproval.rule} is missing `
|
|
683
|
+
+ `from permissions.allow in ${relayApproval.file}, so the agent asks before it starts `
|
|
684
|
+
+ "live delivery");
|
|
685
|
+
}
|
|
655
686
|
}
|
|
656
687
|
if (blocked !== null) {
|
|
657
688
|
diagnostics.push(blocked.reason);
|
|
@@ -684,8 +715,8 @@ export async function doctorAntigravity(context) {
|
|
|
684
715
|
if (locationOf(context) === "workspace") {
|
|
685
716
|
diagnostics.push("registered per workspace; a session opened anywhere else loads nothing");
|
|
686
717
|
}
|
|
687
|
-
diagnostics.push(`live delivery starts when the agent runs
|
|
688
|
-
|
|
718
|
+
diagnostics.push(`live delivery starts when the agent runs ${relayStartCommand(context.home,
|
|
719
|
+
context.hostPlatform)} once in a conversation; while none is serving, ACC asks for that in its context `
|
|
689
720
|
+ "line, up to three times, when the policy is on");
|
|
690
721
|
diagnostics.push(`antigravity relays running on this machine: ${await runningRelays({
|
|
691
722
|
dataHome: context.dataHome })}`);
|
|
@@ -713,7 +744,7 @@ export function planAntigravityInstall(context) {
|
|
|
713
744
|
// match: a wrapper path that needs quotes gets none.
|
|
714
745
|
const plugin = [{ path: pluginInstallPath(context.home), kind: "merge" },
|
|
715
746
|
{ path: vendorManifestPath(context.home), kind: "merge" },
|
|
716
|
-
...(isShellWord(cliWrapperPath(context.home))
|
|
747
|
+
...(isShellWord(cliWrapperPath(context.home, context.hostPlatform))
|
|
717
748
|
? [{ path: agySettingsPath(context.home), kind: "merge" }] : [])];
|
|
718
749
|
if (locationChoice(context) !== null) {
|
|
719
750
|
return [shim, ...plugin,
|