@minhspark/codex-mcp-bridge 1.25.2 → 1.26.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/.codex-plugin/plugin.json +1 -1
- package/CHANGELOG.md +58 -0
- package/README.md +81 -32
- package/docs/new-macos-setup.md +47 -21
- package/docs/new-windows-setup.md +34 -9
- package/docs/project-onboarding.md +21 -8
- package/package.json +2 -1
- package/plugin.json +1 -1
- package/scripts/background-relay-hidden.vbs +11 -0
- package/scripts/install-antigravity-bridge.mjs +65 -23
- package/scripts/install-background-relay.mjs +428 -0
- package/scripts/install-claude-code.mjs +2 -2
- package/scripts/install-claude-desktop.mjs +12 -7
- package/scripts/install-native-relay.mjs +2 -2
- package/scripts/setup-background-relay.mjs +58 -0
- package/scripts/setup.mjs +3 -3
- package/scripts/worker-relay.mjs +41 -0
- package/src/antigravity-bridge.mjs +96 -27
- package/src/antigravity-cli.mjs +13 -2
- package/src/antigravity-deliveries.mjs +32 -8
- package/src/antigravity-ipc.mjs +18 -9
- package/src/antigravity-outbound-bridge.mjs +71 -23
- package/src/antigravity-outbound-targets.mjs +100 -10
- package/src/background-antigravity-worker.mjs +192 -0
- package/src/background-claude-worker.mjs +240 -0
- package/src/background-codex-worker.mjs +344 -0
- package/src/background-relay-client.mjs +107 -0
- package/src/background-relay-config.mjs +98 -0
- package/src/background-relay-daemon.mjs +60 -0
- package/src/background-relay-mcp.mjs +45 -0
- package/src/background-relay-protocol.mjs +54 -0
- package/src/background-relay-registration.mjs +198 -0
- package/src/background-relay-service.mjs +311 -0
- package/src/background-relay-storage.mjs +203 -0
- package/src/background-relay-storage.ps1 +64 -0
- package/src/background-relay.mjs +373 -0
- package/src/bridge-readiness.mjs +2 -2
- package/src/claude-bridge.mjs +43 -25
- package/src/claude-code-registration.mjs +2 -1
- package/src/claude-session-router.mjs +8 -15
- package/src/index.mjs +39 -22
- package/src/mcp-supervisor.mjs +26 -16
- package/src/native-relay-companion.mjs +4 -1
- package/src/native-relay.mjs +5 -1
- package/src/peer-protocol.mjs +9 -6
- package/src/recipient-preflight.mjs +81 -0
- package/src/reload-control.mjs +2 -2
- package/src/setup.mjs +62 -25
- package/src/thread-delivery.mjs +10 -5
- package/src/windows-pipe-acl.ps1 +1 -1
|
@@ -7,32 +7,68 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
|
|
|
7
7
|
import { z } from "zod";
|
|
8
8
|
import { resolveAntigravitySenderContext } from "./antigravity-sender-context.mjs";
|
|
9
9
|
import { createAntigravityOutboundTargets } from "./antigravity-outbound-targets.mjs";
|
|
10
|
-
import { AntigravityDeliveries } from "./antigravity-deliveries.mjs";
|
|
10
|
+
import { AntigravityDeliveries, antigravityDeliveryError } from "./antigravity-deliveries.mjs";
|
|
11
11
|
import { antigravityBridgeDirectory } from "./antigravity-ipc.mjs";
|
|
12
12
|
import { createHardenedRootPolicy } from "./hardened-root-policy.mjs";
|
|
13
13
|
import { stopProcessInspectors } from "./claude-sender-context.mjs";
|
|
14
14
|
import { exitForVersionRequest } from "./cli-version.mjs";
|
|
15
|
+
import { BACKGROUND_WORKER_GUIDANCE, registerBackgroundTools } from "./background-relay-mcp.mjs";
|
|
15
16
|
import { assertAccountIdentity } from "./bridge-account-context.mjs";
|
|
17
|
+
import { bridgeDiagnostic, withBridgeDiagnostic, preflightFailure } from "./recipient-preflight.mjs";
|
|
18
|
+
import { createReloadControl, exitWhenClientCloses } from "./reload-control.mjs";
|
|
16
19
|
|
|
17
20
|
const { version } = JSON.parse(fs.readFileSync(new URL("../package.json", import.meta.url), "utf8"));
|
|
18
21
|
const result = (value) => ({ content: [{ type: "text", text: JSON.stringify(value, null, 2) }] });
|
|
19
22
|
const targetSchema = { target: z.enum(["codex", "claude"]) };
|
|
20
23
|
const taskSchema = { ...targetSchema, conversationId: z.string().uuid(), taskId: z.string().min(1).max(256).optional() };
|
|
21
24
|
|
|
22
|
-
export function createAntigravityOutboundBridge({ resolveSender = resolveAntigravitySenderContext, targets = createAntigravityOutboundTargets(), deliveries = new AntigravityDeliveries(path.join(antigravityBridgeDirectory(), "outbound-deliveries")), rootPolicy = createHardenedRootPolicy() } = {}) {
|
|
23
|
-
const
|
|
25
|
+
export function createAntigravityOutboundBridge({ resolveSender = resolveAntigravitySenderContext, targets = createAntigravityOutboundTargets(), deliveries = new AntigravityDeliveries(path.join(antigravityBridgeDirectory(), "outbound-deliveries")), rootPolicy = createHardenedRootPolicy(), env = process.env, channel = process } = {}) {
|
|
26
|
+
const reload = createReloadControl({
|
|
27
|
+
entry: "antigravity-outbound-bridge.mjs", env, channel,
|
|
28
|
+
inspect: () => deliveries.active.size ? "Antigravity outbound deliveries are still active" : targets.reloadReason?.() ?? null,
|
|
29
|
+
quiesce: () => targets.quiesce(),
|
|
30
|
+
exportState: () => ({ directory: deliveries.directory, targets: targets.exportReloadState() }),
|
|
31
|
+
restore: (state) => {
|
|
32
|
+
if (typeof state.directory !== "string" || path.relative(state.directory, deliveries.directory)) throw new Error("Antigravity durable receipt directory changed during reload");
|
|
33
|
+
targets.restoreReloadState(state.targets);
|
|
34
|
+
},
|
|
35
|
+
activate: () => targets.activate(),
|
|
36
|
+
});
|
|
37
|
+
const server = new McpServer({ name: "agent-bridge", version }, { instructions: "Send user-authorized work FROM the current Antigravity Desktop conversation to an exact existing Codex Desktop task or Claude Desktop Code session in the same project. Use list_agent_tasks first, verify title and identity, then send_to_agent_task with a fresh requestId. Keep requestId after uncertainty and read_agent_delivery; never automatically resend. Native permissions and sender identity are verified on every call. Do not launch a CLI substitute for a native task, impersonate Codex/Claude, or change permissions to overcome a rejection. Native acceptance is not task completion; read_agent_task for Codex or read_agent_delivery for a Claude reply. Independent new native work requires a separate native conversation; native handoff tools only continue existing tasks. Never use Computer Use, open an old chat, or create an empty, new, or error chat to load or recover a relay. Report the structured diagnostic when a native relay or session is unavailable. Agent-authored handoffs must be in English and name Antigravity as the author. " + BACKGROUND_WORKER_GUIDANCE });
|
|
38
|
+
|
|
39
|
+
registerBackgroundTools((name, config, handler) => server.registerTool(name, config, async (args, extra) => {
|
|
40
|
+
try { return await reload.run(() => handler(args, extra)); }
|
|
41
|
+
catch (error) {
|
|
42
|
+
return withBridgeDiagnostic({ isError: true, content: [] }, error, { component: "agent-bridge", operation: name, requestId: args.requestId, readOnly: config.annotations?.readOnlyHint === true });
|
|
43
|
+
}
|
|
44
|
+
}), { env });
|
|
24
45
|
|
|
25
46
|
function scope(expected, actual) {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
47
|
+
try {
|
|
48
|
+
if (!path.isAbsolute(expected) || !path.isAbsolute(actual)) throw new Error("An absolute project directory is required");
|
|
49
|
+
const canonical = fs.realpathSync.native(expected);
|
|
50
|
+
if (path.relative(canonical, fs.realpathSync.native(actual))) throw new Error("Destination must match the verified Antigravity sender's project");
|
|
51
|
+
rootPolicy.assert(canonical, "Antigravity sender project");
|
|
52
|
+
return canonical;
|
|
53
|
+
} catch (error) {
|
|
54
|
+
error.code ??= "ANTIGRAVITY_PROJECT_SCOPE_INVALID";
|
|
55
|
+
error.phase ??= "scope";
|
|
56
|
+
error.deliveryState = "not_sent";
|
|
57
|
+
throw error;
|
|
58
|
+
}
|
|
31
59
|
}
|
|
32
60
|
|
|
33
61
|
async function sender(extra) {
|
|
34
|
-
|
|
35
|
-
|
|
62
|
+
let identity;
|
|
63
|
+
try {
|
|
64
|
+
identity = await resolveSender(extra?._meta);
|
|
65
|
+
if (identity.status !== "verified" || identity.provider !== "antigravity" || !identity.id || !["bypass", "prompting"].includes(identity.mode)) throw new Error(identity.reason ?? "Antigravity sender is not verified");
|
|
66
|
+
} catch (error) {
|
|
67
|
+
error.code ??= "ANTIGRAVITY_SENDER_UNVERIFIED";
|
|
68
|
+
error.phase ??= "sender";
|
|
69
|
+
error.deliveryState = "not_sent";
|
|
70
|
+
throw error;
|
|
71
|
+
}
|
|
36
72
|
scope(identity.cwd, identity.cwd);
|
|
37
73
|
return identity;
|
|
38
74
|
}
|
|
@@ -40,44 +76,54 @@ export function createAntigravityOutboundBridge({ resolveSender = resolveAntigra
|
|
|
40
76
|
function recheck(identity, extra) {
|
|
41
77
|
return async () => {
|
|
42
78
|
const current = await sender(extra);
|
|
43
|
-
if (JSON.stringify(current) !== JSON.stringify(identity)) throw
|
|
79
|
+
if (JSON.stringify(current) !== JSON.stringify(identity)) throw preflightFailure("ANTIGRAVITY_SENDER_CHANGED", "Antigravity sender identity or permissions changed before delivery");
|
|
44
80
|
};
|
|
45
81
|
}
|
|
46
82
|
|
|
47
83
|
function register(name, description, inputSchema, readOnly, handler) {
|
|
48
84
|
server.registerTool(name, { description, inputSchema, annotations: { readOnlyHint: readOnly, destructiveHint: !readOnly, idempotentHint: true, openWorldHint: !readOnly } }, async (args, extra) => {
|
|
49
|
-
|
|
50
|
-
|
|
85
|
+
const options = { component: "agent-bridge", operation: name, requestId: args.requestId, conversationId: args.conversationId, readOnly };
|
|
86
|
+
try {
|
|
87
|
+
const value = await reload.run(() => handler(args, extra));
|
|
88
|
+
const failure = antigravityDeliveryError(value);
|
|
89
|
+
if (failure) return withBridgeDiagnostic({ ...result(value), isError: true }, failure, { ...options, readOnly: false, msgId: value.result?.msgId });
|
|
90
|
+
if (value?.diagnostic && (value.nativeReceipt === null || value.sender?.status !== "verified")) return withBridgeDiagnostic({ ...result(value), isError: true }, { diagnostic: value.diagnostic }, options);
|
|
91
|
+
return result(value);
|
|
92
|
+
} catch (error) {
|
|
93
|
+
const diagnostic = bridgeDiagnostic(error, options);
|
|
94
|
+
return withBridgeDiagnostic({ ...result({ error: diagnostic.reason, code: diagnostic.code, deliveryState: error.deliveryState ?? diagnostic.deliveryState }), isError: true }, { diagnostic }, options);
|
|
95
|
+
}
|
|
51
96
|
});
|
|
52
97
|
}
|
|
53
98
|
|
|
54
99
|
register("agent_bridge_status", "Check this Antigravity caller's native identity and outbound bridge capabilities without sending work.", {}, true, async (_, extra) => {
|
|
55
100
|
const identity = await resolveSender(extra?._meta);
|
|
56
101
|
const metadataFields = Object.fromEntries(Object.entries(extra?._meta ?? {}).filter(([key]) => key.startsWith("antigravity.google/")).map(([key, value]) => [key, Array.isArray(value) ? "array" : typeof value]));
|
|
57
|
-
|
|
102
|
+
const diagnostic = identity.status === "verified" ? null : bridgeDiagnostic(Object.assign(new Error(identity.reason ?? "Antigravity sender is not verified"), { code: "ANTIGRAVITY_SENDER_UNVERIFIED", phase: "sender" }), { component: "agent-bridge", operation: "agent_bridge_status", readOnly: true });
|
|
103
|
+
return { version, sender: identity, metadataFields, targets: ["codex-desktop", "claude-desktop"], permissions: "native-unchanged", createsConversations: false, ...(diagnostic ? { diagnostic } : {}) };
|
|
58
104
|
});
|
|
59
105
|
|
|
60
|
-
register("list_agent_tasks", "List exact existing native tasks in this Antigravity conversation's project before selecting a destination.", targetSchema, true, async (args, extra) => {
|
|
106
|
+
register("list_agent_tasks", "List exact existing native tasks in this Antigravity conversation's project before selecting a destination. Dormant Claude tasks remain unavailable until their native session is already connected.", targetSchema, true, async (args, extra) => {
|
|
61
107
|
const identity = await sender(extra);
|
|
62
108
|
return targets.list({ ...args, sender: identity, beforeSend: recheck(identity, extra) });
|
|
63
109
|
});
|
|
64
110
|
|
|
65
|
-
register("send_to_agent_task", "Continue an exact existing Codex or Claude Desktop task with authorized work. Preserve requestId across retries; inspect the durable receipt.", { ...taskSchema, requestId: z.string().uuid(), cwd: z.string().min(1), prompt: z.string().min(1).max(30000) }, false, async (args, extra) => {
|
|
111
|
+
register("send_to_agent_task", "Continue an exact existing Codex or connected Claude Desktop task with authorized work. Unavailable native sessions produce a diagnostic without opening chats; native permissions remain unchanged. Preserve requestId across retries; inspect the durable receipt.", { ...taskSchema, requestId: z.string().uuid(), cwd: z.string().min(1), prompt: z.string().min(1).max(30000) }, false, async (args, extra) => {
|
|
66
112
|
const identity = await sender(extra);
|
|
67
113
|
const cwd = scope(identity.cwd, args.cwd);
|
|
68
|
-
if (args.prompt.includes("\0")) throw
|
|
114
|
+
if (args.prompt.includes("\0")) throw preflightFailure("ANTIGRAVITY_PROMPT_INVALID", "Prompt must not contain NUL");
|
|
69
115
|
const prompt = `[From Antigravity Desktop · ${path.basename(cwd)} · Authorized task handoff]\n[Source conversation: ${identity.id}]\n\n${args.prompt}`;
|
|
70
116
|
return deliveries.dispatch({ ...args, cwd, surface: args.target, sender: { provider: identity.provider, id: identity.id }, prompt }, async () => {
|
|
71
117
|
await recheck(identity, extra)();
|
|
72
118
|
const sent = await targets.send({ ...args, cwd, prompt, sender: identity, ...(args.target === "codex" ? { threadId: args.conversationId } : { sessionId: args.conversationId }), beforeSend: recheck(identity, extra) });
|
|
73
119
|
return { ...sent, completed: false };
|
|
74
|
-
});
|
|
120
|
+
}, { component: "agent-bridge", operation: "send_to_agent_task" });
|
|
75
121
|
});
|
|
76
122
|
|
|
77
123
|
register("read_agent_delivery", "Read an owned delivery without resending. Accepted or pending does not prove a completed response.", { requestId: z.string().uuid() }, true, async ({ requestId }, extra) => {
|
|
78
124
|
const identity = await sender(extra);
|
|
79
125
|
const record = deliveries.read(requestId);
|
|
80
|
-
if (!record || record.sender?.provider !== "antigravity" || record.sender.id !== identity.id) throw
|
|
126
|
+
if (!record || record.sender?.provider !== "antigravity" || record.sender.id !== identity.id) throw preflightFailure("ANTIGRAVITY_RECEIPT_OWNER_MISMATCH", "Delivery does not belong to this Antigravity conversation");
|
|
81
127
|
scope(identity.cwd, record.cwd);
|
|
82
128
|
if (record.result?.accountContext) assertAccountIdentity(record.result.accountContext);
|
|
83
129
|
if (record.surface === "claude" && record.result?.msgId) {
|
|
@@ -85,7 +131,8 @@ export function createAntigravityOutboundBridge({ resolveSender = resolveAntigra
|
|
|
85
131
|
return { ...record, nativeReceipt: await targets.read({ target: "claude", msgId: record.result.msgId, sender: identity, beforeSend: recheck(identity, extra) }) };
|
|
86
132
|
} catch (error) {
|
|
87
133
|
if (error.code !== "ANTIGRAVITY_NATIVE_RECEIPT_UNAVAILABLE") throw error;
|
|
88
|
-
|
|
134
|
+
const diagnostic = bridgeDiagnostic(error, { component: "agent-bridge", operation: "read_agent_delivery", phase: "receipt", readOnly: true, requestId, msgId: record.result.msgId, conversationId: record.conversationId });
|
|
135
|
+
return { ...record, nativeReceipt: null, recovery: "The original native receipt is unavailable. Report the diagnostic before further action; do not resend or open a chat to load the relay.", receiptError: diagnostic.reason, diagnostic: record.diagnostic ?? diagnostic, receiptDiagnostic: diagnostic };
|
|
89
136
|
}
|
|
90
137
|
}
|
|
91
138
|
return record;
|
|
@@ -96,7 +143,8 @@ export function createAntigravityOutboundBridge({ resolveSender = resolveAntigra
|
|
|
96
143
|
return targets.read({ ...args, cwd: identity.cwd, sender: identity, ...(args.target === "codex" ? { threadId: args.conversationId } : { sessionId: args.conversationId }), beforeSend: recheck(identity, extra) });
|
|
97
144
|
});
|
|
98
145
|
|
|
99
|
-
|
|
146
|
+
reload.listen();
|
|
147
|
+
return { server, reload, busy: () => deliveries.active.size ? "Antigravity outbound deliveries are still active" : null, close: async () => { await targets.close(); await server.close(); } };
|
|
100
148
|
}
|
|
101
149
|
|
|
102
150
|
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
|
|
@@ -109,7 +157,7 @@ if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href)
|
|
|
109
157
|
await bridge.close();
|
|
110
158
|
await stopProcessInspectors();
|
|
111
159
|
};
|
|
112
|
-
for (const signal of ["SIGINT", "SIGTERM"]) process.once(signal, () => { void stop(); });
|
|
113
|
-
process.stdin.once("end", () => { void stop(); });
|
|
114
160
|
await bridge.server.connect(new StdioServerTransport());
|
|
161
|
+
const shutdown = exitWhenClientCloses({ busy: bridge.busy, stop });
|
|
162
|
+
for (const signal of ["SIGINT", "SIGTERM"]) process.once(signal, () => { void shutdown.close(signal); });
|
|
115
163
|
}
|
|
@@ -3,12 +3,13 @@ import path from "node:path";
|
|
|
3
3
|
import { NativeDesktopRelay, desktopTaskSocketPath, accountRelaySocketPath } from "./native-relay.mjs";
|
|
4
4
|
import { readBridgeAccounts, requireBridgeAccounts, assertAccountIdentity } from "./bridge-account-context.mjs";
|
|
5
5
|
import { PeerEndpoint, listClaudeSessions, assertClaudeSessionProcess } from "./peer-protocol.mjs";
|
|
6
|
-
import { readClaudeDesktopContext } from "./claude-desktop-context.mjs";
|
|
6
|
+
import { readClaudeDesktopContext, readClaudeDesktopTasks } from "./claude-desktop-context.mjs";
|
|
7
7
|
import { readClaudeInboundPolicy } from "./claude-inbound-policy.mjs";
|
|
8
8
|
import { assertRecipientClass } from "./recipient-preflight.mjs";
|
|
9
|
-
import { sameClaudeDesktopRecipient } from "./claude-session-router.mjs";
|
|
9
|
+
import { recoverClaudeDesktopSession, sameClaudeDesktopRecipient } from "./claude-session-router.mjs";
|
|
10
10
|
import { createHardenedRootPolicy } from "./hardened-root-policy.mjs";
|
|
11
11
|
import { protectCurrentUserPipe } from "./windows-pipe-acl.mjs";
|
|
12
|
+
import { cloneReloadState } from "./reload-control.mjs";
|
|
12
13
|
|
|
13
14
|
const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
14
15
|
|
|
@@ -42,6 +43,7 @@ export function createAntigravityOutboundTargets(options = {}) {
|
|
|
42
43
|
const readAccounts = options.readAccounts ?? readBridgeAccounts;
|
|
43
44
|
const assertAccounts = options.assertAccounts ?? ((identity) => assertAccountIdentity(identity, readAccounts()));
|
|
44
45
|
const listSessions = options.listSessions ?? listClaudeSessions;
|
|
46
|
+
const listTasks = options.listTasks ?? ((account) => readClaudeDesktopTasks({ account }));
|
|
45
47
|
const readContext = options.readContext ?? ((session, account) => readClaudeDesktopContext(session, { account }));
|
|
46
48
|
const readInbound = options.readInbound ?? readClaudeInboundPolicy;
|
|
47
49
|
const assertProcess = options.assertProcess ?? assertClaudeSessionProcess;
|
|
@@ -49,10 +51,12 @@ export function createAntigravityOutboundTargets(options = {}) {
|
|
|
49
51
|
const peerFactory = options.peerFactory ?? ((settings) => new PeerEndpoint(settings));
|
|
50
52
|
const receipts = new Map();
|
|
51
53
|
let peer;
|
|
54
|
+
let peerCwd;
|
|
55
|
+
let paused = false;
|
|
52
56
|
let closed = false;
|
|
53
57
|
|
|
54
58
|
function operation(sender, beforeSend, required = false) {
|
|
55
|
-
if (closed) throw blocked("Antigravity outbound targets
|
|
59
|
+
if (closed || paused) throw blocked("Antigravity outbound targets are closed or quiesced");
|
|
56
60
|
if (required && typeof beforeSend !== "function") throw blocked("A live Antigravity sender revalidation callback is required");
|
|
57
61
|
const identity = senderIdentity(sender);
|
|
58
62
|
const cwd = captureDirectory(identity.cwd);
|
|
@@ -60,7 +64,7 @@ export function createAntigravityOutboundTargets(options = {}) {
|
|
|
60
64
|
const accounts = readAccounts();
|
|
61
65
|
const accountContext = requireBridgeAccounts(accounts);
|
|
62
66
|
const check = async () => {
|
|
63
|
-
if (closed) throw blocked("Antigravity outbound targets
|
|
67
|
+
if (closed || paused) throw blocked("Antigravity outbound targets are closed or quiesced");
|
|
64
68
|
assertAccounts(accountContext);
|
|
65
69
|
roots.recheck(root, "Antigravity sender project");
|
|
66
70
|
const current = captureDirectory(identity.cwd);
|
|
@@ -122,6 +126,29 @@ export function createAntigravityOutboundTargets(options = {}) {
|
|
|
122
126
|
return { ...session, desktop, inbound: readInbound(session.cwd) };
|
|
123
127
|
}
|
|
124
128
|
|
|
129
|
+
function savedClaudeTask(context, tasks, sessionId, taskId) {
|
|
130
|
+
if (!Array.isArray(tasks) || tasks.length > 8192) throw blocked("Saved Claude Desktop tasks cannot be verified");
|
|
131
|
+
const matches = tasks.filter((task) => task.taskId === taskId);
|
|
132
|
+
const task = matches[0];
|
|
133
|
+
if (matches.length !== 1 || typeof taskId !== "string" || !taskId.startsWith("local_") || !UUID.test(taskId.slice(6)) || task.fileTaskId !== taskId || task.isArchived !== false
|
|
134
|
+
|| !UUID.test(sessionId ?? "") || task.cliSessionId !== sessionId || !sameDirectory(captureDirectory(task.cwd).path, context.cwd)
|
|
135
|
+
|| typeof task.title !== "string" || !task.title.trim() || task.title.length > 1024 || /[\u0000-\u001f\u007f]/u.test(task.title)) throw blocked("Exact saved Claude Desktop task and session identity cannot be verified");
|
|
136
|
+
return { target: "claude", sessionId, taskId, cwd: context.cwd, title: task.title, status: "dormant", recoverable: false, code: "CLAUDE_SESSION_NOT_READY" };
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
async function recoverClaudeRecipient(context, sessionId, taskId) {
|
|
140
|
+
if (listSessions().some((session) => session.sessionId === sessionId && session.alive)) return claudeRecipient(context, sessionId, taskId);
|
|
141
|
+
savedClaudeTask(context, await listTasks(context.accounts.claude), sessionId, taskId);
|
|
142
|
+
await context.check();
|
|
143
|
+
const resolved = await recoverClaudeDesktopSession({
|
|
144
|
+
target: "auto", expectedCwd: context.cwd, expectedTaskId: taskId, sessions: listSessions(), account: context.accounts.claude, readContext,
|
|
145
|
+
}, {
|
|
146
|
+
listTasks: () => listTasks(context.accounts.claude), listSessions, beforeInspect: context.check,
|
|
147
|
+
});
|
|
148
|
+
await context.check();
|
|
149
|
+
return claudeRecipient(context, resolved.session.sessionId, taskId);
|
|
150
|
+
}
|
|
151
|
+
|
|
125
152
|
async function list({ target, sender, beforeSend } = {}) {
|
|
126
153
|
const context = operation(sender, beforeSend);
|
|
127
154
|
await context.check();
|
|
@@ -148,9 +175,15 @@ export function createAntigravityOutboundTargets(options = {}) {
|
|
|
148
175
|
try {
|
|
149
176
|
const desktop = readContext(session, context.accounts.claude);
|
|
150
177
|
const verified = claudeRecipient(context, session.sessionId, desktop?.taskId);
|
|
151
|
-
rows.push({ target: "claude", sessionId: verified.sessionId, taskId: verified.desktop.taskId, cwd: context.cwd, title: verified.desktop.title ?? null, permissionClass: verified.desktop.permissionClass ?? null, inboundPolicy: verified.inbound?.value ?? null });
|
|
178
|
+
rows.push({ target: "claude", sessionId: verified.sessionId, taskId: verified.desktop.taskId, cwd: context.cwd, title: verified.desktop.title ?? null, permissionClass: verified.desktop.permissionClass ?? null, inboundPolicy: verified.inbound?.value ?? null, status: "ready" });
|
|
152
179
|
} catch {}
|
|
153
180
|
}
|
|
181
|
+
const tasks = await listTasks(context.accounts.claude);
|
|
182
|
+
if (!Array.isArray(tasks) || tasks.length > 8192) throw blocked("Saved Claude Desktop tasks cannot be verified");
|
|
183
|
+
for (const task of tasks) {
|
|
184
|
+
if (rows.some((row) => row.taskId === task.taskId) || listSessions().some((session) => session.sessionId === task.cliSessionId && session.alive)) continue;
|
|
185
|
+
try { rows.push(savedClaudeTask(context, tasks, task.cliSessionId, task.taskId)); } catch {}
|
|
186
|
+
}
|
|
154
187
|
await context.check();
|
|
155
188
|
return rows;
|
|
156
189
|
}
|
|
@@ -178,9 +211,12 @@ export function createAntigravityOutboundTargets(options = {}) {
|
|
|
178
211
|
}
|
|
179
212
|
}
|
|
180
213
|
if (target !== "claude") throw blocked("Target must be codex or claude");
|
|
181
|
-
const recipient =
|
|
214
|
+
const recipient = await recoverClaudeRecipient(context, sessionId, taskId);
|
|
182
215
|
assertRecipientClass(recipient, context.identity);
|
|
183
|
-
if (!peer)
|
|
216
|
+
if (!peer) {
|
|
217
|
+
peerCwd = context.cwd;
|
|
218
|
+
peer = peerFactory({ name: `antigravity-${process.pid}`, cwd: peerCwd, signalExit: false, env, protectSocket: protectCurrentUserPipe });
|
|
219
|
+
}
|
|
184
220
|
await peer.start();
|
|
185
221
|
let msgId;
|
|
186
222
|
try {
|
|
@@ -189,7 +225,7 @@ export function createAntigravityOutboundTargets(options = {}) {
|
|
|
189
225
|
recipient: { permissionMode: recipient.desktop.permissionMode ?? null, permissionClass: recipient.desktop.permissionClass ?? null, inboundPolicy: recipient.inbound?.value ?? null },
|
|
190
226
|
beforeSend: async () => {
|
|
191
227
|
await context.check();
|
|
192
|
-
const current = claudeRecipient(context, sessionId, taskId);
|
|
228
|
+
const current = claudeRecipient(context, recipient.sessionId, taskId);
|
|
193
229
|
if (!sameClaudeDesktopRecipient(recipient, current)) throw blocked("Claude session process or socket changed before delivery");
|
|
194
230
|
assertRecipientClass(current, context.identity);
|
|
195
231
|
await context.check();
|
|
@@ -198,7 +234,7 @@ export function createAntigravityOutboundTargets(options = {}) {
|
|
|
198
234
|
msgId = outcome.msgId;
|
|
199
235
|
receipts.set(msgId, { sender: context.identity, accountContext: context.accountContext, cwd: context.cwd });
|
|
200
236
|
await context.check();
|
|
201
|
-
return { target, sessionId, taskId, cwd: context.cwd, msgId, status: outcome.delivery?.status ?? "sent_unconfirmed", completed: false, accountContext: context.accountContext };
|
|
237
|
+
return { target, sessionId: recipient.sessionId, taskId, cwd: context.cwd, msgId, status: outcome.delivery?.status ?? "sent_unconfirmed", completed: false, accountContext: context.accountContext };
|
|
202
238
|
} catch (error) {
|
|
203
239
|
if (error.msgId) receipts.set(error.msgId, { sender: context.identity, accountContext: context.accountContext, cwd: context.cwd });
|
|
204
240
|
if (msgId) error.msgId = msgId;
|
|
@@ -221,6 +257,11 @@ export function createAntigravityOutboundTargets(options = {}) {
|
|
|
221
257
|
}
|
|
222
258
|
if (target !== undefined && target !== "claude") throw blocked("Target must be codex or claude");
|
|
223
259
|
if (msgId === undefined) {
|
|
260
|
+
if (!listSessions().some((session) => session.sessionId === sessionId && session.alive)) {
|
|
261
|
+
const task = savedClaudeTask(context, await listTasks(context.accounts.claude), sessionId, taskId);
|
|
262
|
+
await context.check();
|
|
263
|
+
return task;
|
|
264
|
+
}
|
|
224
265
|
const recipient = claudeRecipient(context, sessionId, taskId);
|
|
225
266
|
await context.check();
|
|
226
267
|
return { target: "claude", sessionId, taskId, cwd: context.cwd, title: recipient.desktop.title ?? null, permissionClass: recipient.desktop.permissionClass ?? null, inboundPolicy: recipient.inbound?.value ?? null };
|
|
@@ -240,5 +281,54 @@ export function createAntigravityOutboundTargets(options = {}) {
|
|
|
240
281
|
await peer?.closed;
|
|
241
282
|
}
|
|
242
283
|
|
|
243
|
-
|
|
284
|
+
const reloadReason = () => peer?.reloadReason() ?? null;
|
|
285
|
+
|
|
286
|
+
async function quiesce() {
|
|
287
|
+
const reason = reloadReason();
|
|
288
|
+
if (reason) throw new Error(reason);
|
|
289
|
+
paused = true;
|
|
290
|
+
await peer?.quiesce();
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
function exportReloadState() {
|
|
294
|
+
if (!paused || reloadReason()) throw new Error("Quiesce Antigravity targets before exporting reload state");
|
|
295
|
+
return cloneReloadState({ receipts: [...receipts], peer: peer ? { cwd: peerCwd, state: peer.exportReloadState() } : null });
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
function restoreReloadState(input) {
|
|
299
|
+
if (peer || receipts.size || closed) throw new Error("Antigravity reload state requires empty targets");
|
|
300
|
+
const state = cloneReloadState(input);
|
|
301
|
+
if (!state || !Array.isArray(state.receipts) || state.receipts.length > 100000
|
|
302
|
+
|| state.peer !== null && (!state.peer || typeof state.peer.cwd !== "string" || !path.isAbsolute(state.peer.cwd))) {
|
|
303
|
+
throw new Error("Invalid Antigravity target reload state");
|
|
304
|
+
}
|
|
305
|
+
const restored = new Map();
|
|
306
|
+
for (const pair of state.receipts) {
|
|
307
|
+
if (!Array.isArray(pair) || pair.length !== 2 || typeof pair[0] !== "string" || !pair[0] || restored.has(pair[0])) throw new Error("Invalid Antigravity receipt reload record");
|
|
308
|
+
const record = pair[1];
|
|
309
|
+
const identity = senderIdentity(record?.sender);
|
|
310
|
+
if (typeof record.cwd !== "string" || !path.isAbsolute(record.cwd) || typeof identity.cwd !== "string" || !sameDirectory(record.cwd, identity.cwd)
|
|
311
|
+
|| !record.accountContext || typeof record.accountContext.codex !== "string" || !record.accountContext.codex
|
|
312
|
+
|| typeof record.accountContext.claude !== "string" || !record.accountContext.claude) throw new Error("Invalid Antigravity receipt ownership reload record");
|
|
313
|
+
restored.set(pair[0], { ...record, sender: identity });
|
|
314
|
+
}
|
|
315
|
+
if (!state.peer && restored.size) throw new Error("Antigravity receipts require their native peer state");
|
|
316
|
+
if (state.peer) {
|
|
317
|
+
const nativeMessages = state.peer.state?.sentMessages;
|
|
318
|
+
if (!Array.isArray(nativeMessages) || nativeMessages.length !== restored.size || nativeMessages.some(([id]) => !restored.has(id))) throw new Error("Antigravity receipt ownership does not match native peer state");
|
|
319
|
+
const candidate = peerFactory({ name: `antigravity-${process.pid}`, cwd: state.peer.cwd, signalExit: false, env, protectSocket: protectCurrentUserPipe });
|
|
320
|
+
candidate.restoreReloadState(state.peer.state);
|
|
321
|
+
peer = candidate;
|
|
322
|
+
peerCwd = state.peer.cwd;
|
|
323
|
+
}
|
|
324
|
+
for (const [id, record] of restored) receipts.set(id, record);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
async function activate() {
|
|
328
|
+
if (closed) throw new Error("Antigravity targets are closed");
|
|
329
|
+
await peer?.resume();
|
|
330
|
+
paused = false;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
return { list, send, read, close, reloadReason, quiesce, exportReloadState, restoreReloadState, activate };
|
|
244
334
|
}
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { promisify } from "node:util";
|
|
3
|
+
import { AntigravityCliSession, resolveAntigravityCliBinary } from "./antigravity-cli.mjs";
|
|
4
|
+
|
|
5
|
+
const runFile = promisify(execFile);
|
|
6
|
+
const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
7
|
+
const RELAY_TOOLS = ["worker_status", "list_workers", "send_to_worker", "read_worker_delivery"];
|
|
8
|
+
const RELAY_ENV = ["AGENT_RELAY_ENDPOINT", "AGENT_RELAY_TOKEN", "AGENT_RELAY_WORKER_ID", "AGENT_RELAY_REQUEST_ID"];
|
|
9
|
+
const MAX_STDERR_BYTES = 65536;
|
|
10
|
+
|
|
11
|
+
function failure(code, reason, phase, deliveryState = "not_sent") {
|
|
12
|
+
return Object.assign(new Error(reason), { code, phase, deliveryState, sent: deliveryState === "sent" ? true : deliveryState === "not_sent" ? false : null });
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export async function inspectAntigravityWorker({ env = process.env, executable, platform = process.platform, run = runFile } = {}) {
|
|
16
|
+
const state = { agent: "antigravity", available: false, authenticated: null, ready: false };
|
|
17
|
+
let binary;
|
|
18
|
+
try { binary = resolveAntigravityCliBinary({ binary: executable, env, platform }); }
|
|
19
|
+
catch { return { ...state, code: "ANTIGRAVITY_CLI_UNAVAILABLE", reason: "The dedicated Antigravity CLI executable is unavailable." }; }
|
|
20
|
+
try {
|
|
21
|
+
const result = await run(binary, ["--version"], { env: { ...env }, windowsHide: true, shell: false, timeout: 5000, maxBuffer: 4096, encoding: "utf8" });
|
|
22
|
+
const status = result?.exitCode ?? result?.status ?? result?.code ?? 0;
|
|
23
|
+
const version = String(result?.stdout ?? "").trim();
|
|
24
|
+
if (status !== 0 || !/^\d+\.\d+\.\d+(?:-[a-z0-9.-]+)?$/i.test(version)) throw new Error("Invalid version result");
|
|
25
|
+
return { ...state, available: true, executable: binary, version, code: "ANTIGRAVITY_AUTH_UNVERIFIED", reason: "Antigravity CLI has no supported read-only authentication probe. An authorized job must establish runtime authentication." };
|
|
26
|
+
} catch {
|
|
27
|
+
return { ...state, executable: binary, code: "ANTIGRAVITY_INSPECTION_FAILED", reason: "Antigravity CLI version inspection failed or returned an unsupported response." };
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function classifyDiagnostic(value) {
|
|
32
|
+
if (typeof value !== "string") return null;
|
|
33
|
+
const text = value.slice(0, 8192).replace(/([a-z])([A-Z])/g, "$1 $2").replace(/[_-]+/g, " ");
|
|
34
|
+
if (/\b(?:unauthenticated|auth(?:entication)?[ _-]+(?:required|failed)|not[ _-]+authenticated|not[ _-]+logged[ _-]+in|(?:login|sign[ -]?in)[ _-]+required|(?:credentials?|tokens?)[ _-]+(?:are[ _-]+|is[ _-]+|has[ _-]+|have[ _-]+)?(?:expired|invalid|missing))\b/i.test(text)) return "authentication";
|
|
35
|
+
if (/\b(?:soft[ _-]+denied|permission[ _-]+(?:denied|required|rejected)|approval[ _-]+required|requires?[ _-]+(?:user[ _-]+)?(?:approval|permission))\b/i.test(text)
|
|
36
|
+
|| /\b(?:tool|mcp)\b[^\r\n]{0,160}\b(?:not allowed|blocked|denied)\b/i.test(text)) return "permission";
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function discoverRelayTools(tools) {
|
|
41
|
+
if (tools === undefined) return { relayToolsVerified: false, relayToolDiscovery: "unadvertised" };
|
|
42
|
+
if (!Array.isArray(tools) || tools.length > 4096 || tools.some((name) => typeof name !== "string" || !name || name.length > 256)) {
|
|
43
|
+
throw failure("ANTIGRAVITY_WORKER_PROTOCOL_ERROR", "Antigravity CLI advertised an invalid tool list.", "startup");
|
|
44
|
+
}
|
|
45
|
+
const names = new Set(tools.map((name) => name.replace(/[-_:/\.]+/g, "_")));
|
|
46
|
+
if (RELAY_TOOLS.every((name) => names.has(name) || names.has(`worker_relay_${name}`) || names.has(`mcp_worker_relay_${name}`))) return { relayToolsVerified: true, relayToolDiscovery: "explicit-tools" };
|
|
47
|
+
if (tools.includes("call_mcp_tool")) return { relayToolsVerified: false, relayToolDiscovery: "mcp-dispatcher" };
|
|
48
|
+
throw failure("ANTIGRAVITY_RELAY_TOOLS_MISSING", "Antigravity CLI advertised neither its native MCP dispatcher nor all required worker-relay tools. Inspect the CLI MCP configuration.", "relay");
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function workerEnvironment(env, relayMcp, requestId) {
|
|
52
|
+
const grant = relayMcp?.env;
|
|
53
|
+
if (typeof relayMcp?.command !== "string" || !relayMcp.command || !Array.isArray(relayMcp.args)
|
|
54
|
+
|| relayMcp.args.some((value) => typeof value !== "string") || !grant || typeof grant !== "object" || Array.isArray(grant)
|
|
55
|
+
|| RELAY_ENV.some((key) => typeof grant[key] !== "string" || !grant[key] || grant[key].length > 8192 || grant[key].includes("\0"))
|
|
56
|
+
|| grant.AGENT_RELAY_REQUEST_ID !== requestId) {
|
|
57
|
+
throw failure("ANTIGRAVITY_RELAY_GRANT_INVALID", "The Antigravity worker requires a complete relay grant bound to this request.", "preflight");
|
|
58
|
+
}
|
|
59
|
+
const cleaned = Object.fromEntries(Object.entries(env).filter(([name]) => !/^(?:CODEX_|CLAUDE|ANTHROPIC_|MCP_|AGENT_RELAY_)/i.test(name)));
|
|
60
|
+
for (const key of RELAY_ENV) cleaned[key] = grant[key];
|
|
61
|
+
return cleaned;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export async function runAntigravityWorker({ executable, cwd, prompt, requestId, sessionId, env = process.env, relayMcp, onEvent = () => {}, signal, timeoutMs = 600000, spawn, conversationId, resume } = {}) {
|
|
65
|
+
if (conversationId !== undefined || resume !== undefined) throw failure("ANTIGRAVITY_WORKER_RESUME_UNSUPPORTED", "Background Antigravity jobs require a fresh CLI conversation and cannot resume an external conversation.", "preflight");
|
|
66
|
+
if (typeof prompt !== "string" || !prompt.trim() || !UUID.test(requestId ?? "")
|
|
67
|
+
|| sessionId !== undefined && !UUID.test(sessionId) || !Number.isSafeInteger(timeoutMs) || timeoutMs < 1
|
|
68
|
+
|| typeof onEvent !== "function") {
|
|
69
|
+
throw failure("ANTIGRAVITY_WORKER_INPUT_INVALID", "The Antigravity worker requires a non-empty prompt, valid request identity, and bounded timeout.", "preflight");
|
|
70
|
+
}
|
|
71
|
+
if (signal?.aborted) throw failure("ANTIGRAVITY_WORKER_ABORTED", "The Antigravity worker was canceled before dispatch.", "cancellation");
|
|
72
|
+
const workerEnv = workerEnvironment(env, relayMcp, requestId);
|
|
73
|
+
const deadline = Date.now() + timeoutMs;
|
|
74
|
+
let session;
|
|
75
|
+
let initialized;
|
|
76
|
+
let accepted = false;
|
|
77
|
+
let terminal = false;
|
|
78
|
+
let diagnosticKind = null;
|
|
79
|
+
let stderrBytes = 0;
|
|
80
|
+
let stderrTail = "";
|
|
81
|
+
let stderrOverflow = false;
|
|
82
|
+
let observerFailed = false;
|
|
83
|
+
let finalError;
|
|
84
|
+
let deadlineTimer;
|
|
85
|
+
let rejectInterruption;
|
|
86
|
+
const interrupted = new Promise((resolve, reject) => { rejectInterruption = reject; });
|
|
87
|
+
interrupted.catch(() => {});
|
|
88
|
+
const observers = [];
|
|
89
|
+
const deliveryState = () => accepted ? "sent" : session && ["unknown", "completed"].includes(session.read().deliveryState) ? "unknown" : "not_sent";
|
|
90
|
+
const notify = (event) => {
|
|
91
|
+
try { observers.push(Promise.resolve(onEvent(event)).catch(() => { observerFailed = true; })); }
|
|
92
|
+
catch { observerFailed = true; }
|
|
93
|
+
};
|
|
94
|
+
const accept = () => {
|
|
95
|
+
if (accepted) return;
|
|
96
|
+
accepted = true;
|
|
97
|
+
notify({ type: "accepted", sessionId: initialized.conversation_id });
|
|
98
|
+
};
|
|
99
|
+
const observeDiagnostic = (value) => {
|
|
100
|
+
const kind = classifyDiagnostic(value);
|
|
101
|
+
if (kind && (!diagnosticKind || kind === "authentication")) diagnosticKind = kind;
|
|
102
|
+
};
|
|
103
|
+
const diagnosticFailure = () => diagnosticKind === "authentication"
|
|
104
|
+
? failure("ANTIGRAVITY_AUTH_REQUIRED", "Antigravity CLI could not authenticate the background worker. Restore its configured credentials without opening a Desktop chat.", "authentication", deliveryState())
|
|
105
|
+
: diagnosticKind === "permission"
|
|
106
|
+
? failure("ANTIGRAVITY_PERMISSION_DENIED", "Antigravity CLI denied a required tool operation. Inspect its scoped permission rules; no permissions were changed.", "permission", deliveryState())
|
|
107
|
+
: stderrOverflow ? failure("ANTIGRAVITY_WORKER_DIAGNOSTIC_LIMIT", "Antigravity CLI exceeded its bounded diagnostic limit; successful execution could not be verified.", "observation", deliveryState()) : null;
|
|
108
|
+
const abort = () => {
|
|
109
|
+
if (!terminal) {
|
|
110
|
+
const error = failure("ANTIGRAVITY_WORKER_ABORTED", "The Antigravity worker was canceled. Inspect its existing receipt before further action.", "cancellation", deliveryState());
|
|
111
|
+
session?.fail(error);
|
|
112
|
+
rejectInterruption(error);
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
const observe = (promise) => Promise.race([promise, interrupted]);
|
|
116
|
+
try {
|
|
117
|
+
session = new AntigravityCliSession({
|
|
118
|
+
binary: executable, cwd, env: workerEnv, spawn,
|
|
119
|
+
startupTimeoutMs: Math.min(30000, timeoutMs), turnTimeoutMs: timeoutMs, closeTimeoutMs: Math.min(1000, timeoutMs),
|
|
120
|
+
onStderr(chunk) {
|
|
121
|
+
const bytes = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
122
|
+
if (bytes.length > MAX_STDERR_BYTES - stderrBytes) stderrOverflow = true;
|
|
123
|
+
if (stderrBytes >= MAX_STDERR_BYTES) return;
|
|
124
|
+
const text = bytes.subarray(0, MAX_STDERR_BYTES - stderrBytes).toString("utf8");
|
|
125
|
+
stderrBytes += Math.min(bytes.length, MAX_STDERR_BYTES - stderrBytes);
|
|
126
|
+
for (let offset = 0; offset < text.length; offset += 4096) {
|
|
127
|
+
stderrTail = `${stderrTail}${text.slice(offset, offset + 4096)}`.slice(-8192);
|
|
128
|
+
observeDiagnostic(stderrTail);
|
|
129
|
+
}
|
|
130
|
+
},
|
|
131
|
+
onEvent(event) {
|
|
132
|
+
if (event.event === "init") {
|
|
133
|
+
initialized = { conversation_id: event.conversation_id, tools: event.init.tools };
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
if (event.event === "result") {
|
|
137
|
+
accept();
|
|
138
|
+
terminal = true;
|
|
139
|
+
observeDiagnostic(event.result.error);
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
if (event.event !== "step_update") return;
|
|
143
|
+
const step = event.step_update;
|
|
144
|
+
if (!step || step.conversation_id !== initialized?.conversation_id) throw new Error("Invalid Antigravity event");
|
|
145
|
+
if (!session.awaitingResult) throw new Error("Invalid Antigravity event");
|
|
146
|
+
if (step.step_type === "user_input" && step.state === "DONE") accept();
|
|
147
|
+
const error = step.tool_info?.error;
|
|
148
|
+
if (error) observeDiagnostic(typeof error === "string" ? error : `${typeof error.type === "string" ? error.type : ""} ${typeof error.message === "string" ? error.message : ""}`);
|
|
149
|
+
},
|
|
150
|
+
});
|
|
151
|
+
deadlineTimer = setTimeout(() => {
|
|
152
|
+
const error = failure("ANTIGRAVITY_WORKER_TIMEOUT", "The Antigravity worker exceeded its observation deadline. Inspect its existing receipt before further action.", "observation", deliveryState());
|
|
153
|
+
session.fail(error);
|
|
154
|
+
rejectInterruption(error);
|
|
155
|
+
}, Math.max(1, deadline - Date.now()));
|
|
156
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
157
|
+
if (signal?.aborted) throw failure("ANTIGRAVITY_WORKER_ABORTED", "The Antigravity worker was canceled before dispatch.", "cancellation");
|
|
158
|
+
const started = await observe(session.start());
|
|
159
|
+
if (diagnosticFailure()) throw diagnosticFailure();
|
|
160
|
+
const relayTools = discoverRelayTools(initialized?.tools);
|
|
161
|
+
notify({ type: "session", sessionId: started.conversationId });
|
|
162
|
+
await observe(Promise.all(observers));
|
|
163
|
+
if (observerFailed) throw failure("ANTIGRAVITY_WORKER_OBSERVER_FAILED", "The Antigravity worker could not record its session event.", "observation");
|
|
164
|
+
if (signal?.aborted) throw failure("ANTIGRAVITY_WORKER_ABORTED", "The Antigravity worker was canceled before dispatch.", "cancellation");
|
|
165
|
+
const remaining = deadline - Date.now();
|
|
166
|
+
if (remaining < 1) throw failure("ANTIGRAVITY_WORKER_TIMEOUT", "The Antigravity worker exceeded its observation deadline.", "observation");
|
|
167
|
+
session.turnTimeoutMs = remaining;
|
|
168
|
+
const result = await observe(session.send(prompt));
|
|
169
|
+
await observe(session.close());
|
|
170
|
+
await observe(Promise.all(observers));
|
|
171
|
+
if (observerFailed) throw failure("ANTIGRAVITY_WORKER_OBSERVER_FAILED", "The Antigravity worker could not record its acceptance event.", "observation", deliveryState());
|
|
172
|
+
if (diagnosticFailure()) throw diagnosticFailure();
|
|
173
|
+
if (result.status !== "SUCCESS" || result.error) throw failure("ANTIGRAVITY_WORKER_TURN_FAILED", "Antigravity CLI returned an unsuccessful background turn. Inspect the existing worker receipt.", "execution", deliveryState());
|
|
174
|
+
return { output: result.response, sessionId: result.conversation_id, ...(sessionId ? { workerSessionId: sessionId } : {}), deliveryState: "sent", sent: true, authenticated: true, ...relayTools };
|
|
175
|
+
} catch (error) {
|
|
176
|
+
const state = deliveryState();
|
|
177
|
+
if (/^ANTIGRAVITY_[A-Z_]+$/.test(error?.code ?? "")) finalError = failure(error.code, error.message, error.phase, state);
|
|
178
|
+
else if (diagnosticFailure()) finalError = diagnosticFailure();
|
|
179
|
+
else if (["STARTUP_TIMEOUT", "TURN_TIMEOUT"].includes(error?.code)) finalError = failure("ANTIGRAVITY_WORKER_TIMEOUT", "The Antigravity worker exceeded its observation deadline. Inspect its existing receipt before further action.", "observation", state);
|
|
180
|
+
else if (error?.code === "INVALID_PROTOCOL") finalError = failure("ANTIGRAVITY_WORKER_PROTOCOL_ERROR", "Antigravity CLI returned invalid or mismatched streaming data.", "protocol", state);
|
|
181
|
+
else finalError = failure("ANTIGRAVITY_WORKER_FAILED", "The Antigravity CLI worker could not complete its operation. Inspect its existing receipt before further action.", state === "not_sent" ? "startup" : "execution", state);
|
|
182
|
+
finalError.requestId = requestId;
|
|
183
|
+
if (initialized?.conversation_id) finalError.sessionId = initialized.conversation_id;
|
|
184
|
+
if (sessionId) finalError.workerSessionId = sessionId;
|
|
185
|
+
throw finalError;
|
|
186
|
+
} finally {
|
|
187
|
+
clearTimeout(deadlineTimer);
|
|
188
|
+
signal?.removeEventListener("abort", abort);
|
|
189
|
+
const closed = await session?.close();
|
|
190
|
+
if (!finalError && closed?.status === "closing") throw failure("ANTIGRAVITY_WORKER_STOP_FAILED", "The Antigravity worker did not confirm process shutdown.", "shutdown", deliveryState());
|
|
191
|
+
}
|
|
192
|
+
}
|