agents-can-communicate 0.8.5 → 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 +29 -0
- 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 +63 -8
- package/docs/UPGRADING.md +33 -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 +42 -11
- 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 +36 -18
- 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 +9 -1
- package/node_modules/@agents-can-communicate/adapter-sdk/src/native-delivery.mjs +65 -10
- 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/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/native-session-diagnostics.mjs +11 -3
- 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/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 +1 -1
- 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 +154 -28
- package/node_modules/@agents-can-communicate/installer/package.json +3 -2
- package/node_modules/@agents-can-communicate/installer/src/delivery-diagnostics.mjs +2 -1
- 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
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
|
-
import { execFile } from "node:child_process";
|
|
3
2
|
import { constants } from "node:fs";
|
|
4
|
-
import { lstat, mkdir, open, rm } from "node:fs/promises";
|
|
3
|
+
import { lstat, mkdir, open, readdir, rm } from "node:fs/promises";
|
|
5
4
|
import net from "node:net";
|
|
6
5
|
import path from "node:path";
|
|
7
6
|
|
|
8
|
-
import { decisionBody
|
|
7
|
+
import { decisionBody, isWindowsPlatform, readProcessArgs, resolveExecutable, runExecutable }
|
|
8
|
+
from "@agents-can-communicate/adapter-sdk";
|
|
9
9
|
|
|
10
|
-
import {
|
|
10
|
+
import { isAgy } from "./client-process.mjs";
|
|
11
|
+
import { bindDesktop, offerDesktop, refreshDesktop } from "./desktop-delivery.mjs";
|
|
12
|
+
import { isDesktopEndpointId } from "./desktop-endpoint.mjs";
|
|
13
|
+
import { relayStartCommand } from "./relays.mjs";
|
|
14
|
+
import { PROTOCOL_CONTRACT, RELAY_MODES, listRegistrations, readRegistration, relayDir, relayPipeName }
|
|
11
15
|
from "./relay-endpoint.mjs";
|
|
12
16
|
|
|
13
17
|
/**
|
|
@@ -18,19 +22,31 @@ import { PROTOCOL_CONTRACT, RELAY_MODES, listRegistrations, readRegistration, re
|
|
|
18
22
|
*/
|
|
19
23
|
const TRANSPORT = "antigravity-relay";
|
|
20
24
|
const defaultAlive = pid => { try { process.kill(pid, 0); return true; } catch { return false; } };
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
})
|
|
25
|
-
|
|
26
|
-
|
|
25
|
+
// agy is found the way the platform finds it, and a .cmd starts through cmd.exe.
|
|
26
|
+
const runDefault = async (command, args, { timeout }) => {
|
|
27
|
+
const file = await resolveExecutable(command) ?? command;
|
|
28
|
+
return runExecutable(file, args, { timeout }).then(
|
|
29
|
+
({ stdout, stderr }) => ({ stdout: `${stdout ?? ""}${stderr ?? ""}` }),
|
|
30
|
+
error => ({ stdout: `${error?.stdout ?? ""}${error?.stderr ?? ""}` }));
|
|
31
|
+
};
|
|
32
|
+
const argvDefault = async pid => await readProcessArgs(pid, { timeoutMs: 1_000 }) ?? [];
|
|
27
33
|
|
|
28
34
|
export function isPrintMode(argv) {
|
|
29
35
|
return argv.some(arg => arg === "-p" || arg === "--print" || arg.startsWith("--print=")
|
|
30
36
|
|| arg.startsWith("-p="));
|
|
31
37
|
}
|
|
32
38
|
|
|
33
|
-
|
|
39
|
+
const listPipesOfMachine = () => readdir("\\\\.\\pipe\\");
|
|
40
|
+
|
|
41
|
+
/** A relay socket only this user can open; on Windows a relay pipe the machine lists. */
|
|
42
|
+
export async function isSocketSafe(socketPath, { platform = process.platform,
|
|
43
|
+
listPipes = listPipesOfMachine } = {}) {
|
|
44
|
+
if (isWindowsPlatform(platform)) {
|
|
45
|
+
const name = relayPipeName(socketPath);
|
|
46
|
+
if (name === null) return false;
|
|
47
|
+
const pipes = await Promise.resolve().then(listPipes).catch(() => []);
|
|
48
|
+
return pipes.some(pipe => String(pipe).toLowerCase() === name);
|
|
49
|
+
}
|
|
34
50
|
try {
|
|
35
51
|
const info = await lstat(socketPath);
|
|
36
52
|
return info.isSocket() && (typeof process.getuid !== "function" || info.uid === process.getuid())
|
|
@@ -63,12 +79,12 @@ const closedHandshake = (clientVersion, reasonCode) => ({ supported: false,
|
|
|
63
79
|
clientVersion: clientVersion ?? null, protocolContract: PROTOCOL_CONTRACT, modes: [],
|
|
64
80
|
opaqueEndpointRef: null, leaseUntil: null, reasonCode });
|
|
65
81
|
|
|
66
|
-
async function serving(record, { timeoutMs, isAlive, clientPid }) {
|
|
82
|
+
async function serving(record, { timeoutMs, isAlive, clientPid, platform = process.platform }) {
|
|
67
83
|
if (record === null || record.protocolContract !== PROTOCOL_CONTRACT) return "native_session_unavailable";
|
|
68
84
|
if (clientPid !== undefined && record.agyPid !== clientPid) return "native_session_unavailable";
|
|
69
85
|
if (!isAlive(record.agyPid) || !isAlive(record.relayPid)) return "native_session_unavailable";
|
|
70
86
|
if (Date.parse(record.leaseUntil) <= Date.now()) return "native_session_unavailable";
|
|
71
|
-
if (!await isSocketSafe(record.socketPath)) return "native_session_unavailable";
|
|
87
|
+
if (!await isSocketSafe(record.socketPath, { platform })) return "native_session_unavailable";
|
|
72
88
|
try {
|
|
73
89
|
const pong = await askRelay(record.socketPath, { nonce: record.nonce, ping: true }, timeoutMs);
|
|
74
90
|
return pong?.accepted === true && pong.ping === true ? null : "handshake_failed";
|
|
@@ -81,7 +97,8 @@ const handshake = record => ({ supported: true, clientVersion: record.clientVers
|
|
|
81
97
|
protocolContract: PROTOCOL_CONTRACT, modes: [...RELAY_MODES],
|
|
82
98
|
opaqueEndpointRef: record.endpointId, leaseUntil: record.leaseUntil, reasonCode: null });
|
|
83
99
|
|
|
84
|
-
export async function probeNativeDelivery({ timeoutMs = 750, run = runDefault
|
|
100
|
+
export async function probeNativeDelivery({ timeoutMs = 750, run = runDefault,
|
|
101
|
+
platform = process.platform } = {}) {
|
|
85
102
|
const unsupported = (reasonCode, clientVersion = null) => ({ supported: false, clientVersion,
|
|
86
103
|
protocolContract: PROTOCOL_CONTRACT, executableFingerprint: null, modes: [], reasonCode });
|
|
87
104
|
try {
|
|
@@ -103,18 +120,22 @@ export function planNativeActivation() {
|
|
|
103
120
|
}
|
|
104
121
|
|
|
105
122
|
export async function bindNativeSession({ event, clientPid, clientVersion, runtimeDir,
|
|
106
|
-
timeoutMs = 750, isAlive = defaultAlive } = {}) {
|
|
123
|
+
timeoutMs = 750, isAlive = defaultAlive, platform = process.platform, desktop = bindDesktop } = {}) {
|
|
107
124
|
if (!Number.isInteger(clientPid) || clientPid <= 0) return closedHandshake(clientVersion, "client_process_unknown");
|
|
108
|
-
|
|
125
|
+
// The desktop app's own language server: no relay, the server is the endpoint.
|
|
126
|
+
const served = await desktop({ event, clientPid, clientVersion, runtimeDir, timeoutMs });
|
|
127
|
+
if (served !== null) return served;
|
|
128
|
+
const candidates = (await listRegistrations({ runtimeDir, platform }))
|
|
109
129
|
.filter(record => record.conversationId === event?.sessionId);
|
|
110
130
|
for (const record of candidates) {
|
|
111
|
-
if (await serving(record, { timeoutMs, isAlive, clientPid }) === null) return handshake(record);
|
|
131
|
+
if (await serving(record, { timeoutMs, isAlive, clientPid, platform }) === null) return handshake(record);
|
|
112
132
|
}
|
|
113
133
|
return closedHandshake(clientVersion, "native_session_unavailable");
|
|
114
134
|
}
|
|
115
135
|
|
|
116
136
|
export async function refreshNativeSession({ binding, runtimeDir, timeoutMs = 750,
|
|
117
137
|
isAlive = defaultAlive } = {}) {
|
|
138
|
+
if (isDesktopEndpointId(binding?.opaqueEndpointRef)) return refreshDesktop({ binding, runtimeDir, timeoutMs });
|
|
118
139
|
const record = await readRegistration({ runtimeDir, endpointId: binding?.opaqueEndpointRef });
|
|
119
140
|
const reasonCode = await serving(record, { timeoutMs, isAlive });
|
|
120
141
|
return reasonCode === null ? handshake(record) : closedHandshake(binding?.clientVersion, reasonCode);
|
|
@@ -124,6 +145,9 @@ const SAFE_CODES = new Map([["recipient_unavailable", "recipient_unavailable"],
|
|
|
124
145
|
["transport_rejected", "transport_rejected"], ["bad_nonce", "recipient_unavailable"]]);
|
|
125
146
|
|
|
126
147
|
export async function offerMessage({ binding, message, runtimeDir, timeoutMs = 8_000 } = {}) {
|
|
148
|
+
if (isDesktopEndpointId(binding?.opaqueEndpointRef)) {
|
|
149
|
+
return offerDesktop({ binding, message, runtimeDir, timeoutMs });
|
|
150
|
+
}
|
|
127
151
|
const rejected = safeErrorCode => ({ accepted: false, transport: TRANSPORT,
|
|
128
152
|
clientVersion: binding?.clientVersion ?? null, safeErrorCode });
|
|
129
153
|
const record = await readRegistration({ runtimeDir, endpointId: binding?.opaqueEndpointRef });
|
|
@@ -146,9 +170,8 @@ export async function offerMessage({ binding, message, runtimeDir, timeoutMs = 8
|
|
|
146
170
|
}
|
|
147
171
|
|
|
148
172
|
const ASK_LIMIT = 3;
|
|
149
|
-
const HINT = home => "ACC: live delivery is on but not running in this conversation.
|
|
150
|
-
+ `peers reach you while idle, run once:
|
|
151
|
-
"acc-relay.sh")}" start`;
|
|
173
|
+
const HINT = (home, platform) => "ACC: live delivery is on but not running in this conversation. "
|
|
174
|
+
+ `To let peers reach you while idle, run once: ${relayStartCommand(home, platform)}`;
|
|
152
175
|
|
|
153
176
|
// Each ask is its own file, created exclusively. A counter read and then
|
|
154
177
|
// rewritten lets overlapping calls all observe the same count and each return
|
|
@@ -191,15 +214,20 @@ async function relayAlreadyServing({ runtimeDir, conversationId, clientPid, isAl
|
|
|
191
214
|
}
|
|
192
215
|
|
|
193
216
|
export async function nativeActivationHint({ event, nativeBinding, runtimeDir, clientPid, env,
|
|
194
|
-
argvOf = argvDefault, isAlive = defaultAlive }) {
|
|
217
|
+
argvOf = argvDefault, isAlive = defaultAlive, platform = process.platform }) {
|
|
195
218
|
// Only a degraded binding can be helped by starting a relay: "off" means the
|
|
196
219
|
// live policy is off, "unsupported" that the contract does not admit this
|
|
197
220
|
// client, and "active" that a relay already serves it. Those calls spend
|
|
198
221
|
// none of the asks recorded below.
|
|
199
222
|
if (nativeBinding?.state !== "degraded") return null;
|
|
200
|
-
|
|
223
|
+
// Windows keeps the profile in USERPROFILE; HOME is set only by Git Bash.
|
|
224
|
+
const home = isWindowsPlatform(platform) ? env?.USERPROFILE ?? env?.HOME : env?.HOME;
|
|
201
225
|
if (typeof event?.sessionId !== "string" || typeof home !== "string" || home === "") return null;
|
|
202
|
-
if (!Number.isInteger(clientPid)
|
|
226
|
+
if (!Number.isInteger(clientPid)) return null;
|
|
227
|
+
// The relay is Antigravity CLI's: the desktop app runs no agy, and its
|
|
228
|
+
// sandbox lets no relay start. A print-mode agy ends with its turn.
|
|
229
|
+
const argv = await argvOf(clientPid).catch(() => []);
|
|
230
|
+
if (!isAgy(argv) || isPrintMode(argv)) return null;
|
|
203
231
|
// The runner asks only when its own handshake failed. A relay can still be
|
|
204
232
|
// serving under a stale degraded binding; telling the agent to start it again
|
|
205
233
|
// would be wrong, and the check does not spend an ask.
|
|
@@ -212,5 +240,14 @@ export async function nativeActivationHint({ event, nativeBinding, runtimeDir, c
|
|
|
212
240
|
// the number now; release gives it back when the runner does not deliver.
|
|
213
241
|
const slot = await claimAsk(asked, marker);
|
|
214
242
|
if (slot === null) return null;
|
|
215
|
-
|
|
243
|
+
let line;
|
|
244
|
+
try {
|
|
245
|
+
line = HINT(home, platform);
|
|
246
|
+
} catch {
|
|
247
|
+
// A profile path no shell reads alike gets no command to run; the message
|
|
248
|
+
// still arrives on the next turn.
|
|
249
|
+
await releaseAsk(slot);
|
|
250
|
+
return null;
|
|
251
|
+
}
|
|
252
|
+
return { line, release: () => releaseAsk(slot) };
|
|
216
253
|
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { randomBytes } from "node:crypto";
|
|
2
|
-
import { constants } from "node:fs";
|
|
3
2
|
import { lstat, mkdir, open, readdir, realpath, rename, rm } from "node:fs/promises";
|
|
4
3
|
import path from "node:path";
|
|
5
4
|
|
|
5
|
+
import { closedTo, isWindowsPlatform, openRegularNoFollow } from "@agents-can-communicate/adapter-sdk";
|
|
6
|
+
|
|
6
7
|
/**
|
|
7
8
|
* Where a running relay is found, and what it may be trusted with.
|
|
8
9
|
*
|
|
@@ -11,6 +12,10 @@ import path from "node:path";
|
|
|
11
12
|
* session endpoint - the language-server address and the CSRF token - is never
|
|
12
13
|
* part of it. A key outside the closed list below is refused, so a mistake that
|
|
13
14
|
* tried to persist one fails loudly instead of quietly writing a credential.
|
|
15
|
+
*
|
|
16
|
+
* On Windows the relay listens on a named pipe, `\\.\pipe\acc-relay-<32 hex>`,
|
|
17
|
+
* and the record and its directory carry no mode bits: the profile's ACL keeps
|
|
18
|
+
* them to this user.
|
|
14
19
|
*/
|
|
15
20
|
export const PROTOCOL_CONTRACT = "antigravity-agentapi-relay-v1";
|
|
16
21
|
export const RELAY_MODES = Object.freeze(["livePush", "idleWake", "busyQueue"]);
|
|
@@ -23,42 +28,46 @@ const NONCE = /^[a-f0-9]{64}$/;
|
|
|
23
28
|
const VERSION = /^\d+\.\d+\.\d+$/;
|
|
24
29
|
const LEASE = /^\d{4}-\d\d-\d\dT\d\d:\d\d:\d\d\.\d{3}Z$/;
|
|
25
30
|
const MAX_BYTES = 4_096;
|
|
31
|
+
const RELAY_PIPE = /^\\\\\.\\pipe\\(acc-relay-[0-9a-f]{32})$/;
|
|
26
32
|
const own = info => typeof process.getuid !== "function" || info.uid === process.getuid();
|
|
27
33
|
const pid = value => Number.isInteger(value) && value > 0;
|
|
28
34
|
const invalid = () => new Error("invalid Antigravity relay registration");
|
|
29
35
|
|
|
30
36
|
export const relayDir = runtimeDir => path.join(runtimeDir, "native", "antigravity");
|
|
31
37
|
export const newRelayId = () => `antigravity_relay_${randomBytes(16).toString("hex")}`;
|
|
38
|
+
/** The pipe name a relay pipe path carries, or null. */
|
|
39
|
+
export const relayPipeName = value => (typeof value === "string" ? RELAY_PIPE.exec(value)?.[1] ?? null : null);
|
|
40
|
+
const socketPathValid = (value, platform) => (isWindowsPlatform(platform) ? relayPipeName(value) !== null
|
|
41
|
+
: typeof value === "string" && path.isAbsolute(value) && Buffer.byteLength(value) < 104);
|
|
32
42
|
|
|
33
|
-
export function validRegistration(record) {
|
|
43
|
+
export function validRegistration(record, { platform = process.platform } = {}) {
|
|
34
44
|
return record !== null && typeof record === "object" && !Array.isArray(record)
|
|
35
45
|
&& Object.keys(record).length === KEYS.length && KEYS.every(key => Object.hasOwn(record, key))
|
|
36
46
|
&& record.schemaVersion === 1 && RELAY_ID.test(record.endpointId)
|
|
37
47
|
&& CONVERSATION.test(record.conversationId) && pid(record.agyPid) && pid(record.relayPid)
|
|
38
|
-
&&
|
|
39
|
-
&& Buffer.byteLength(record.socketPath) < 104 && NONCE.test(record.nonce)
|
|
48
|
+
&& socketPathValid(record.socketPath, platform) && NONCE.test(record.nonce)
|
|
40
49
|
&& VERSION.test(record.clientVersion) && record.protocolContract === PROTOCOL_CONTRACT
|
|
41
50
|
&& Array.isArray(record.modes) && record.modes.every(mode => RELAY_MODES.includes(mode))
|
|
42
51
|
&& typeof record.leaseUntil === "string" && LEASE.test(record.leaseUntil)
|
|
43
52
|
&& Number.isFinite(Date.parse(record.leaseUntil));
|
|
44
53
|
}
|
|
45
54
|
|
|
46
|
-
async function directory(runtimeDir, create = false) {
|
|
55
|
+
async function directory(runtimeDir, create = false, platform = process.platform) {
|
|
47
56
|
if (typeof runtimeDir !== "string" || !path.isAbsolute(runtimeDir)) throw invalid();
|
|
48
57
|
const dir = relayDir(await realpath(runtimeDir));
|
|
49
58
|
if (create) await mkdir(dir, { recursive: true, mode: 0o700 });
|
|
50
59
|
const info = await lstat(dir);
|
|
51
|
-
if (!info.isDirectory() || info.isSymbolicLink() || !own(info) || (info
|
|
60
|
+
if (!info.isDirectory() || info.isSymbolicLink() || !own(info) || !closedTo(info, 0o077, { platform })) {
|
|
52
61
|
throw invalid();
|
|
53
62
|
}
|
|
54
63
|
return dir;
|
|
55
64
|
}
|
|
56
65
|
|
|
57
|
-
export async function writeRegistration({ runtimeDir, record }) {
|
|
58
|
-
if (!validRegistration(record)) throw invalid();
|
|
66
|
+
export async function writeRegistration({ runtimeDir, record, platform = process.platform }) {
|
|
67
|
+
if (!validRegistration(record, { platform })) throw invalid();
|
|
59
68
|
const text = `${JSON.stringify(record)}\n`;
|
|
60
69
|
if (Buffer.byteLength(text) > MAX_BYTES) throw invalid();
|
|
61
|
-
const dir = await directory(runtimeDir, true);
|
|
70
|
+
const dir = await directory(runtimeDir, true, platform);
|
|
62
71
|
const file = path.join(dir, `${record.endpointId}.json`);
|
|
63
72
|
const temporary = path.join(dir, `.${record.endpointId}.${randomBytes(8).toString("hex")}.tmp`);
|
|
64
73
|
let handle;
|
|
@@ -74,17 +83,18 @@ export async function writeRegistration({ runtimeDir, record }) {
|
|
|
74
83
|
return file;
|
|
75
84
|
}
|
|
76
85
|
|
|
77
|
-
export async function readRegistration({ runtimeDir, endpointId }) {
|
|
86
|
+
export async function readRegistration({ runtimeDir, endpointId, platform = process.platform }) {
|
|
78
87
|
if (typeof endpointId !== "string" || !RELAY_ID.test(endpointId)) return null;
|
|
79
88
|
let handle;
|
|
80
89
|
try {
|
|
81
|
-
const dir = await directory(runtimeDir);
|
|
82
|
-
handle = await
|
|
83
|
-
constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK);
|
|
90
|
+
const dir = await directory(runtimeDir, false, platform);
|
|
91
|
+
handle = await openRegularNoFollow(path.join(dir, `${endpointId}.json`), undefined, { platform });
|
|
84
92
|
const info = await handle.stat();
|
|
85
|
-
if (!info.isFile() || !own(info) || info.size > MAX_BYTES || (info
|
|
93
|
+
if (!info.isFile() || !own(info) || info.size > MAX_BYTES || !closedTo(info, 0o077, { platform })) {
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
86
96
|
const record = JSON.parse(await handle.readFile("utf8"));
|
|
87
|
-
return validRegistration(record) && record.endpointId === endpointId ? record : null;
|
|
97
|
+
return validRegistration(record, { platform }) && record.endpointId === endpointId ? record : null;
|
|
88
98
|
} catch {
|
|
89
99
|
return null;
|
|
90
100
|
} finally {
|
|
@@ -92,25 +102,25 @@ export async function readRegistration({ runtimeDir, endpointId }) {
|
|
|
92
102
|
}
|
|
93
103
|
}
|
|
94
104
|
|
|
95
|
-
export async function listRegistrations({ runtimeDir }) {
|
|
105
|
+
export async function listRegistrations({ runtimeDir, platform = process.platform }) {
|
|
96
106
|
let names;
|
|
97
107
|
try {
|
|
98
|
-
names = await readdir(await directory(runtimeDir));
|
|
108
|
+
names = await readdir(await directory(runtimeDir, false, platform));
|
|
99
109
|
} catch {
|
|
100
110
|
return [];
|
|
101
111
|
}
|
|
102
112
|
const found = [];
|
|
103
113
|
for (const name of names) {
|
|
104
114
|
if (!name.endsWith(".json")) continue;
|
|
105
|
-
const record = await readRegistration({ runtimeDir, endpointId: name.slice(0, -5) });
|
|
115
|
+
const record = await readRegistration({ runtimeDir, endpointId: name.slice(0, -5), platform });
|
|
106
116
|
if (record !== null) found.push(record);
|
|
107
117
|
}
|
|
108
118
|
return found;
|
|
109
119
|
}
|
|
110
120
|
|
|
111
|
-
export async function removeRegistration({ runtimeDir, endpointId }) {
|
|
121
|
+
export async function removeRegistration({ runtimeDir, endpointId, platform = process.platform }) {
|
|
112
122
|
if (typeof endpointId !== "string" || !RELAY_ID.test(endpointId)) return;
|
|
113
123
|
try {
|
|
114
|
-
await rm(path.join(await directory(runtimeDir), `${endpointId}.json`), { force: true });
|
|
124
|
+
await rm(path.join(await directory(runtimeDir, false, platform), `${endpointId}.json`), { force: true });
|
|
115
125
|
} catch { /* retirement is already decided; cleanup is best effort */ }
|
|
116
126
|
}
|
|
@@ -86,7 +86,8 @@ export function spawnRelay({ command, args, env, payload, readyMs = 10_000, spaw
|
|
|
86
86
|
return new Promise(resolve => {
|
|
87
87
|
let child;
|
|
88
88
|
try {
|
|
89
|
-
child = spawn(command, args, { env, detached: true,
|
|
89
|
+
child = spawn(command, args, { env, detached: true, windowsHide: true,
|
|
90
|
+
stdio: ["pipe", "pipe", "ignore"] });
|
|
90
91
|
} catch {
|
|
91
92
|
resolve({ ok: false, reason: "could not start" });
|
|
92
93
|
return;
|
|
@@ -3,7 +3,7 @@ import { chmodSync, existsSync, mkdirSync, unlinkSync } from "node:fs";
|
|
|
3
3
|
import net from "node:net";
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
|
|
6
|
-
import { channelSocketDirectory } from "@agents-can-communicate/adapter-sdk";
|
|
6
|
+
import { channelSocketDirectory, isWindowsPlatform } from "@agents-can-communicate/adapter-sdk";
|
|
7
7
|
|
|
8
8
|
import { PROTOCOL_CONTRACT, RELAY_MODES, newRelayId, removeRegistration, writeRegistration }
|
|
9
9
|
from "./relay-endpoint.mjs";
|
|
@@ -52,13 +52,17 @@ export function createRelay({ runtimeDir, socketDir = channelSocketDirectory(),
|
|
|
52
52
|
leaseMs = 120_000,
|
|
53
53
|
renewMs = 40_000, pidCheckMs = 5_000, probeMs = 60_000, lifetimeMs = 86_400_000,
|
|
54
54
|
budgetBytes = 6_000, refreshBinding = null, onExit = () => {}, observe = () => {},
|
|
55
|
-
now = Date.now }) {
|
|
55
|
+
now = Date.now, platform = process.platform }) {
|
|
56
56
|
// The binary picks the id first, so its log file can be named before the
|
|
57
57
|
// relay exists; tests let the relay choose.
|
|
58
58
|
const endpointId = requestedId ?? newRelayId();
|
|
59
59
|
const nonce = randomBytes(32).toString("hex");
|
|
60
|
-
|
|
61
|
-
|
|
60
|
+
// Windows: a named pipe whose 128 random bits only the private registration
|
|
61
|
+
// records; libuv creates the first instance, so no one can take the name first.
|
|
62
|
+
const windows = isWindowsPlatform(platform);
|
|
63
|
+
const socketPath = windows ? `\\\\.\\pipe\\acc-relay-${randomBytes(16).toString("hex")}`
|
|
64
|
+
: path.join(socketDir, `r${randomBytes(6).toString("hex")}.sock`);
|
|
65
|
+
if (!windows && Buffer.byteLength(socketPath) >= 104) throw new Error("relay socket path is too long");
|
|
62
66
|
const seen = new Set();
|
|
63
67
|
const connections = new Set();
|
|
64
68
|
const timers = [];
|
|
@@ -140,7 +144,7 @@ export function createRelay({ runtimeDir, socketDir = channelSocketDirectory(),
|
|
|
140
144
|
async function renew() {
|
|
141
145
|
if (closed) return;
|
|
142
146
|
const next = record();
|
|
143
|
-
await writeRegistration({ runtimeDir, record: next });
|
|
147
|
+
await writeRegistration({ runtimeDir, record: next, platform });
|
|
144
148
|
if (refreshBinding !== null) await Promise.resolve(refreshBinding(next.leaseUntil)).catch(() => {});
|
|
145
149
|
}
|
|
146
150
|
|
|
@@ -150,8 +154,9 @@ export function createRelay({ runtimeDir, socketDir = channelSocketDirectory(),
|
|
|
150
154
|
for (const timer of timers) clearInterval(timer);
|
|
151
155
|
for (const socket of connections) socket.destroy();
|
|
152
156
|
if (server !== null) await new Promise(resolve => server.close(() => resolve()));
|
|
153
|
-
|
|
154
|
-
|
|
157
|
+
// A pipe goes with its server; a socket file stays behind until removed.
|
|
158
|
+
try { if (!windows && existsSync(socketPath)) unlinkSync(socketPath); } catch { /* already gone */ }
|
|
159
|
+
await removeRegistration({ runtimeDir, endpointId, platform });
|
|
155
160
|
observe({ event: "relay_closed", reasonCode: reason });
|
|
156
161
|
onExit(reason);
|
|
157
162
|
}
|
|
@@ -163,18 +168,21 @@ export function createRelay({ runtimeDir, socketDir = channelSocketDirectory(),
|
|
|
163
168
|
}
|
|
164
169
|
|
|
165
170
|
async function listen() {
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
171
|
+
if (!windows) {
|
|
172
|
+
mkdirSync(socketDir, { recursive: true, mode: 0o700 });
|
|
173
|
+
chmodSync(socketDir, 0o700);
|
|
174
|
+
if (existsSync(socketPath)) throw new Error("relay socket already exists");
|
|
175
|
+
}
|
|
169
176
|
server = net.createServer(handleConnection);
|
|
170
177
|
await new Promise((resolve, reject) => {
|
|
171
178
|
server.once("error", reject);
|
|
179
|
+
if (windows) { server.listen(socketPath, resolve); return; }
|
|
172
180
|
const previous = process.umask(0o177);
|
|
173
181
|
server.listen(socketPath, () => { process.umask(previous); resolve(); });
|
|
174
182
|
});
|
|
175
|
-
chmodSync(socketPath, 0o600);
|
|
183
|
+
if (!windows) chmodSync(socketPath, 0o600);
|
|
176
184
|
const first = record();
|
|
177
|
-
await writeRegistration({ runtimeDir, record: first });
|
|
185
|
+
await writeRegistration({ runtimeDir, record: first, platform });
|
|
178
186
|
const started = now();
|
|
179
187
|
every(renewMs, renew);
|
|
180
188
|
every(pidCheckMs, () => { if (!isAlive(agyPid)) return close("client_exited"); });
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
import { execFile } from "node:child_process";
|
|
2
1
|
import { readdir, rm } from "node:fs/promises";
|
|
3
2
|
import path from "node:path";
|
|
4
3
|
|
|
4
|
+
import { isWindowsPlatform, readProcessArgs, shellQuote, windowsHookCommand }
|
|
5
|
+
from "@agents-can-communicate/adapter-sdk";
|
|
6
|
+
|
|
5
7
|
import { listRegistrations, relayDir, removeRegistration } from "./relay-endpoint.mjs";
|
|
6
8
|
|
|
7
9
|
/**
|
|
@@ -16,10 +18,22 @@ import { listRegistrations, relayDir, removeRegistration } from "./relay-endpoin
|
|
|
16
18
|
const RELAY_MARK = "acc-antigravity-relay";
|
|
17
19
|
const RELAY_LOG = /^antigravity_relay_[a-f0-9]{32}\.log$/;
|
|
18
20
|
const defaultAlive = pid => { try { process.kill(pid, 0); return true; } catch { return false; } };
|
|
19
|
-
const psArgv = pid =>
|
|
20
|
-
|
|
21
|
+
const psArgv = async pid => await readProcessArgs(pid, { timeoutMs: 1_000 }) ?? [];
|
|
22
|
+
|
|
23
|
+
// Windows runs no `sh`: there the shim is a Node script, started with node on a
|
|
24
|
+
// forward-slash path that PowerShell, cmd and Git Bash read alike.
|
|
25
|
+
export const relayShimName = platform => (isWindowsPlatform(platform) ? "acc-relay.mjs" : "acc-relay.sh");
|
|
26
|
+
|
|
27
|
+
// The path in the platform's own spelling, for the command the agent is told to
|
|
28
|
+
// run. The file itself is written where this host spells the same path.
|
|
29
|
+
export const relayShimPath = (home, platform = process.platform) => (isWindowsPlatform(platform)
|
|
30
|
+
? path.win32.join(home, ".gemini", "config", "acc", relayShimName(platform))
|
|
31
|
+
: path.posix.join(home, ".gemini", "config", "acc", relayShimName(platform)));
|
|
21
32
|
|
|
22
|
-
|
|
33
|
+
/** The command the agent runs once per conversation to start live delivery. */
|
|
34
|
+
export const relayStartCommand = (home, platform = process.platform) => (isWindowsPlatform(platform)
|
|
35
|
+
? windowsHookCommand("portable", { node: "node", shim: relayShimPath(home, platform), args: ["start"] })
|
|
36
|
+
: `sh ${shellQuote(relayShimPath(home, platform))} start`);
|
|
23
37
|
|
|
24
38
|
async function workspaceDirs(dataHome, list = readdir) {
|
|
25
39
|
if (typeof dataHome !== "string" || dataHome === "") return [];
|
|
@@ -6,6 +6,7 @@ import { denyOutcome, injectOutcome, injectStartOwnerOutcome, normalizeClaudeHoo
|
|
|
6
6
|
import { MIN_VERSION, PROTOCOL_CONTRACT, bindNativeSession, offerMessage, planNativeActivation,
|
|
7
7
|
probeNativeDelivery, refreshNativeSession, retireNativeSession } from "./inbox-delivery.mjs";
|
|
8
8
|
import { observedInboxDirectories } from "./inbox-observed-directories.mjs";
|
|
9
|
+
import { claudeConfigDir, readSessionRecord } from "./inbox-registry.mjs";
|
|
9
10
|
import { inboxSocketDirectories } from "./inbox-socket-directories.mjs";
|
|
10
11
|
import { planClaudeInstall, detectClaude, installClaudePlugin, uninstallClaudePlugin } from "./install.mjs";
|
|
11
12
|
|
|
@@ -36,6 +37,11 @@ export function createClaudeCodeAdapter() {
|
|
|
36
37
|
// decide whether the client is on this machine, so it has to be the
|
|
37
38
|
// real command rather than the adapter id: `2.1.233 (Claude Code)`.
|
|
38
39
|
client: { command: "claude", certificationName: "claude-code", versionArgs: ["--version"] },
|
|
40
|
+
// The build the session runs. Claude Code updates itself under running
|
|
41
|
+
// sessions and Claude.app runs a build of its own, so the `claude` on PATH
|
|
42
|
+
// can name another version; each session's registry record names its own.
|
|
43
|
+
clientVersionOf: async ({ pid, env }) => (await readSessionRecord({
|
|
44
|
+
configDir: claudeConfigDir(env), clientPid: pid }))?.version ?? null,
|
|
39
45
|
certification,
|
|
40
46
|
deliveryFallback: CLAUDE_DELIVERY_FALLBACK,
|
|
41
47
|
capabilities: {
|