@minhspark/codex-mcp-bridge 1.26.8 → 1.26.10
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 +45 -0
- package/docs/daily-handoffs.md +18 -0
- package/package.json +1 -1
- package/plugin.json +1 -1
- package/scripts/install-antigravity-bridge.mjs +3 -5
- package/scripts/install-codex-mcp.mjs +2 -5
- package/scripts/install-native-relay.mjs +4 -7
- package/scripts/setup.mjs +2 -1
- package/scripts/update.mjs +22 -13
- package/src/antigravity-bridge.mjs +50 -10
- package/src/antigravity-deliveries.mjs +27 -2
- package/src/antigravity-desktop.mjs +206 -2
- package/src/antigravity-outbound-targets.mjs +1 -1
- package/src/antigravity-sidecar.mjs +5 -4
- package/src/background-codex-worker.mjs +26 -4
- package/src/claude-bridge.mjs +1 -1
- package/src/codex-mcp-registration.mjs +21 -0
- package/src/index.mjs +1 -1
- package/src/native-relay-companion.mjs +1 -1
- package/src/peer-protocol.mjs +116 -14
- package/src/platform.mjs +19 -18
- package/src/recipient-preflight.mjs +3 -2
- package/src/setup.mjs +18 -7
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
|
+
import crypto from "node:crypto";
|
|
2
3
|
import os from "node:os";
|
|
3
4
|
import path from "node:path";
|
|
4
5
|
import { fileURLToPath } from "node:url";
|
|
@@ -8,6 +9,207 @@ import { promisify } from "node:util";
|
|
|
8
9
|
const execute = promisify(execFile);
|
|
9
10
|
const SURFACES = { desktop: "antigravity", cli: "antigravity-cli" };
|
|
10
11
|
const UUID = /^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/i;
|
|
12
|
+
const TRANSCRIPT_ROW_LIMIT = 4096;
|
|
13
|
+
const TRANSCRIPT_BYTE_LIMIT = 32 * 1024 * 1024;
|
|
14
|
+
const utf8 = new TextDecoder("utf-8", { fatal: true });
|
|
15
|
+
const RESTART_NOTICE = "[Notice] All your subagents and background tasks have been stopped due to server restart. If you want a subagent to continue working, it needs to be revived by sending it a new message. If resuming work, please check on status and restart as needed.";
|
|
16
|
+
|
|
17
|
+
function transcriptFailure(code = "ANTIGRAVITY_TRANSCRIPT_UNSUPPORTED") {
|
|
18
|
+
return Object.assign(new Error("Native Antigravity reply observation is unavailable; retain the original receipt and do not resend."), { code });
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function protobufFields(value) {
|
|
22
|
+
if (!(value instanceof Uint8Array) || value.length > 2 * 1024 * 1024) throw transcriptFailure();
|
|
23
|
+
const buffer = Buffer.from(value.buffer, value.byteOffset, value.byteLength);
|
|
24
|
+
const fields = [];
|
|
25
|
+
let offset = 0;
|
|
26
|
+
const integer = () => {
|
|
27
|
+
let result = 0n;
|
|
28
|
+
for (let shift = 0n; shift < 70n; shift += 7n) {
|
|
29
|
+
if (offset >= buffer.length) throw transcriptFailure();
|
|
30
|
+
const byte = buffer[offset++];
|
|
31
|
+
result |= BigInt(byte & 127) << shift;
|
|
32
|
+
if (!(byte & 128)) {
|
|
33
|
+
if (result > BigInt(Number.MAX_SAFE_INTEGER)) throw transcriptFailure();
|
|
34
|
+
return Number(result);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
throw transcriptFailure();
|
|
38
|
+
};
|
|
39
|
+
while (offset < buffer.length) {
|
|
40
|
+
if (fields.length >= 4096) throw transcriptFailure();
|
|
41
|
+
const tag = integer();
|
|
42
|
+
const field = Math.floor(tag / 8);
|
|
43
|
+
const wire = tag % 8;
|
|
44
|
+
if (!field || field > 536870911) throw transcriptFailure();
|
|
45
|
+
if (wire === 0) fields.push({ field, wire, value: integer() });
|
|
46
|
+
else {
|
|
47
|
+
const length = wire === 2 ? integer() : wire === 1 ? 8 : wire === 5 ? 4 : -1;
|
|
48
|
+
if (length < 0 || length > buffer.length - offset) throw transcriptFailure();
|
|
49
|
+
fields.push({ field, wire, value: buffer.subarray(offset, offset + length) });
|
|
50
|
+
offset += length;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return fields;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function protobufField(fields, number, wire) {
|
|
57
|
+
const matches = fields.filter((field) => field.field === number);
|
|
58
|
+
if (matches.length > 1 || matches.length && matches[0].wire !== wire) throw transcriptFailure();
|
|
59
|
+
return matches[0]?.value;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function protobufText(fields, number) {
|
|
63
|
+
const value = protobufField(fields, number, 2);
|
|
64
|
+
return value === undefined ? "" : utf8.decode(value);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function protobufTime(fields, number) {
|
|
68
|
+
const value = protobufField(fields, number, 2);
|
|
69
|
+
if (!value) return null;
|
|
70
|
+
const timestamp = protobufFields(value);
|
|
71
|
+
const seconds = protobufField(timestamp, 1, 0) ?? 0;
|
|
72
|
+
const nanos = protobufField(timestamp, 2, 0) ?? 0;
|
|
73
|
+
if (nanos >= 1e9 || seconds > 253402300799) throw transcriptFailure();
|
|
74
|
+
return seconds * 1000 + nanos / 1e6;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function decodeNativeStep(row) {
|
|
78
|
+
if (!Number.isSafeInteger(row.idx) || row.idx < 0 || row.step_format !== 0) throw transcriptFailure();
|
|
79
|
+
const fields = protobufFields(row.step_payload);
|
|
80
|
+
if (protobufField(fields, 1, 0) !== row.step_type || protobufField(fields, 4, 0) !== row.status) throw transcriptFailure();
|
|
81
|
+
const rawMetadata = protobufField(fields, 5, 2);
|
|
82
|
+
if (!rawMetadata || !Buffer.from(row.metadata ?? []).equals(rawMetadata)) throw transcriptFailure();
|
|
83
|
+
const metadata = protobufFields(rawMetadata);
|
|
84
|
+
const step = { index: row.idx, type: row.step_type, status: row.status, executionId: protobufText(metadata, 12), createdAt: protobufTime(metadata, 1), completedAt: protobufTime(metadata, 8) };
|
|
85
|
+
if (step.type === 101) {
|
|
86
|
+
const system = protobufFields(protobufField(fields, 114, 2));
|
|
87
|
+
const rawAgent = protobufField(system, 4, 2);
|
|
88
|
+
if (rawAgent) {
|
|
89
|
+
const agent = protobufFields(rawAgent);
|
|
90
|
+
step.message = { id: protobufText(agent, 1), recipient: protobufText(agent, 2), sender: protobufText(agent, 3), content: protobufText(agent, 4), hidden: protobufField(agent, 11, 0) === 1 };
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
if (step.type === 15) {
|
|
94
|
+
const response = protobufFields(protobufField(fields, 20, 2));
|
|
95
|
+
step.response = protobufText(response, 8) || protobufText(response, 1);
|
|
96
|
+
step.hasTools = response.some((field) => field.field === 7);
|
|
97
|
+
step.stopReason = protobufField(response, 12, 0);
|
|
98
|
+
step.finishedAt = protobufTime(metadata, 7);
|
|
99
|
+
}
|
|
100
|
+
return step;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
async function readNativeSteps({ home, conversationId, boundaryOnly = false, afterStepIndex = -1, findRoot }) {
|
|
104
|
+
if (!UUID.test(conversationId ?? "")) throw transcriptFailure();
|
|
105
|
+
let directory = fs.realpathSync.native(home);
|
|
106
|
+
for (const segment of [".gemini", "antigravity", "conversations"]) {
|
|
107
|
+
directory = path.join(directory, segment);
|
|
108
|
+
const stat = fs.lstatSync(directory);
|
|
109
|
+
if (!stat.isDirectory() || stat.isSymbolicLink()) throw transcriptFailure();
|
|
110
|
+
}
|
|
111
|
+
const filename = path.join(directory, `${conversationId}.db`);
|
|
112
|
+
const before = fs.lstatSync(filename, { bigint: true });
|
|
113
|
+
if (!before.isFile() || before.isSymbolicLink() || before.nlink !== 1n || before.size > 512n * 1024n * 1024n) throw transcriptFailure();
|
|
114
|
+
for (const suffix of ["-wal", "-shm"]) {
|
|
115
|
+
try {
|
|
116
|
+
const stat = fs.lstatSync(`${filename}${suffix}`);
|
|
117
|
+
if (!stat.isFile() || stat.isSymbolicLink() || stat.nlink !== 1) throw transcriptFailure();
|
|
118
|
+
} catch (error) { if (error.code !== "ENOENT") throw error; }
|
|
119
|
+
}
|
|
120
|
+
const { DatabaseSync } = await import("node:sqlite");
|
|
121
|
+
const db = new DatabaseSync(filename, { readOnly: true });
|
|
122
|
+
try {
|
|
123
|
+
db.exec("PRAGMA query_only=ON; BEGIN");
|
|
124
|
+
const trajectory = db.prepare("SELECT trajectory_id, cascade_id FROM trajectory_meta LIMIT 2").all();
|
|
125
|
+
if (trajectory.length !== 1 || trajectory[0].cascade_id !== conversationId || !UUID.test(trajectory[0].trajectory_id ?? "")) throw transcriptFailure();
|
|
126
|
+
const maximum = db.prepare("SELECT MAX(idx) AS lastStepIndex FROM steps").get().lastStepIndex ?? -1;
|
|
127
|
+
if (!Number.isSafeInteger(maximum) || maximum < -1) throw transcriptFailure();
|
|
128
|
+
const readSteps = (lowerBound, rootsOnly = false) => {
|
|
129
|
+
const selector = rootsOnly ? "step_type = 101" : "idx > ?";
|
|
130
|
+
const parameters = rootsOnly ? [] : [lowerBound];
|
|
131
|
+
const rows = db.prepare(`SELECT idx, step_type, status, step_format, length(metadata) AS metadata_bytes, length(step_payload) AS payload_bytes FROM steps WHERE ${selector} ORDER BY idx LIMIT ?`).all(...parameters, TRANSCRIPT_ROW_LIMIT + 1);
|
|
132
|
+
if (rows.length > TRANSCRIPT_ROW_LIMIT || rows.some((row) => row.metadata_bytes > 2 * 1024 * 1024 || row.payload_bytes > 2 * 1024 * 1024)
|
|
133
|
+
|| rows.reduce((sum, row) => sum + row.metadata_bytes + row.payload_bytes, 0) > TRANSCRIPT_BYTE_LIMIT) throw transcriptFailure("ANTIGRAVITY_TRANSCRIPT_LIMIT");
|
|
134
|
+
return rows.length ? db.prepare(`SELECT idx, step_type, status, step_format, metadata, step_payload FROM steps WHERE idx >= ? AND idx <= ?${rootsOnly ? " AND step_type = 101" : ""} ORDER BY idx`).all(rows[0].idx, rows.at(-1).idx).map(decodeNativeStep) : [];
|
|
135
|
+
};
|
|
136
|
+
let steps = [];
|
|
137
|
+
let roots;
|
|
138
|
+
if (!boundaryOnly) {
|
|
139
|
+
if (findRoot) {
|
|
140
|
+
roots = readSteps(-1, true).filter(findRoot);
|
|
141
|
+
if (roots.length === 1) steps = readSteps(roots[0].index - 1);
|
|
142
|
+
} else steps = readSteps(afterStepIndex);
|
|
143
|
+
}
|
|
144
|
+
const after = fs.lstatSync(filename, { bigint: true });
|
|
145
|
+
if (!after.isFile() || after.isSymbolicLink() || after.nlink !== 1n || before.dev !== after.dev || before.ino !== after.ino || before.birthtimeNs !== after.birthtimeNs) throw transcriptFailure();
|
|
146
|
+
return { trajectoryId: trajectory[0].trajectory_id, lastStepIndex: maximum, steps, ...(roots ? { roots } : {}) };
|
|
147
|
+
} finally { db.close(); }
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export async function captureAntigravityObservation({ home = os.homedir(), conversationId, prompt, projectId }) {
|
|
151
|
+
const promptSha256 = crypto.createHash("sha256").update(prompt).digest("hex");
|
|
152
|
+
try {
|
|
153
|
+
const snapshot = await readNativeSteps({ home, conversationId, boundaryOnly: true });
|
|
154
|
+
return { version: 1, status: "ready", trajectoryId: snapshot.trajectoryId, beforeStepIndex: snapshot.lastStepIndex, promptSha256, projectId };
|
|
155
|
+
} catch {
|
|
156
|
+
return { version: 1, status: "unavailable", code: "ANTIGRAVITY_TRANSCRIPT_UNAVAILABLE", promptSha256, projectId };
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export async function observeAntigravityDelivery(record, { home = os.homedir(), list = listAntigravityConversations } = {}) {
|
|
161
|
+
if (record?.surface !== "desktop" || !["accepted", "unknown", "sent_unconfirmed"].includes(record.status)) return record;
|
|
162
|
+
const observation = (status, code) => ({ ...record, replyObservation: { status, code, source: "native-transcript", retrySafe: false } });
|
|
163
|
+
try {
|
|
164
|
+
if (!UUID.test(record.conversationId ?? "") || !/^[a-f0-9]{64}$/.test(record.digest ?? "") || !["codex", "claude"].includes(record.sender?.provider)
|
|
165
|
+
|| typeof record.sender.id !== "string" || !record.sender.id) throw transcriptFailure();
|
|
166
|
+
const matches = (await list({ home, surface: "desktop", limit: 1000 })).filter((row) => row.conversationId === record.conversationId);
|
|
167
|
+
if (matches.length !== 1 || matches[0].surface !== "desktop" || canonicalCwd(matches[0].cwd) !== canonicalCwd(record.cwd)) throw transcriptFailure("ANTIGRAVITY_REPLY_SCOPE_CHANGED");
|
|
168
|
+
const boundary = record.nativeObservation;
|
|
169
|
+
if (boundary && (boundary.version !== 1 || !["ready", "unavailable"].includes(boundary.status) || !/^[a-f0-9]{64}$/.test(boundary.promptSha256 ?? "") || boundary.projectId !== matches[0].projectId)) throw transcriptFailure();
|
|
170
|
+
if (boundary?.status === "ready" && (!Number.isSafeInteger(boundary.beforeStepIndex) || boundary.beforeStepIndex < -1 || !UUID.test(boundary.trajectoryId ?? ""))) throw transcriptFailure();
|
|
171
|
+
const createdAt = Date.parse(record.createdAt);
|
|
172
|
+
const finishedAt = Date.parse(record.finishedAt);
|
|
173
|
+
if (!Number.isFinite(createdAt) || boundary?.status !== "ready" && !Number.isFinite(finishedAt)) throw transcriptFailure();
|
|
174
|
+
const matchesRoot = (step) => {
|
|
175
|
+
if (step.type !== 101 || !step.message || step.status !== 3 || !UUID.test(step.message.id) || step.message.recipient !== record.conversationId
|
|
176
|
+
|| step.createdAt === null || step.createdAt < createdAt - (boundary?.status === "ready" ? 2000 : 0) || !step.executionId) return false;
|
|
177
|
+
if (boundary?.status !== "ready" && step.createdAt > Math.min(createdAt + 120000, finishedAt + 30000)) return false;
|
|
178
|
+
const prompt = step.message.content;
|
|
179
|
+
if (boundary?.promptSha256 && crypto.createHash("sha256").update(prompt).digest("hex") !== boundary.promptSha256) return false;
|
|
180
|
+
const digest = crypto.createHash("sha256").update(JSON.stringify({ surface: "desktop", conversationId: record.conversationId, cwd: record.cwd, sender: record.sender, prompt })).digest("hex");
|
|
181
|
+
return digest === record.digest;
|
|
182
|
+
};
|
|
183
|
+
const snapshot = await readNativeSteps({ home, conversationId: record.conversationId, ...(boundary?.status === "ready" ? { afterStepIndex: boundary.beforeStepIndex } : { findRoot: matchesRoot }) });
|
|
184
|
+
if (boundary?.status === "ready" && (boundary.trajectoryId !== snapshot.trajectoryId || snapshot.lastStepIndex < boundary.beforeStepIndex)) throw transcriptFailure("ANTIGRAVITY_TRANSCRIPT_REPLACED");
|
|
185
|
+
const roots = snapshot.roots ?? snapshot.steps.filter(matchesRoot);
|
|
186
|
+
if (!roots.length) return observation("pending", "ANTIGRAVITY_MESSAGE_NOT_OBSERVED");
|
|
187
|
+
if (roots.length !== 1) return observation("ambiguous", "ANTIGRAVITY_REPLY_AMBIGUOUS");
|
|
188
|
+
const root = roots[0];
|
|
189
|
+
let reply;
|
|
190
|
+
for (const step of snapshot.steps.filter((step) => step.index > root.index)) {
|
|
191
|
+
if (step.type === 14 || step.executionId !== root.executionId) break;
|
|
192
|
+
if (step.type === 101) {
|
|
193
|
+
if (step.message?.hidden && step.message.sender === "system" && step.message.content === RESTART_NOTICE) continue;
|
|
194
|
+
if (!reply) return observation("ambiguous", "ANTIGRAVITY_REPLY_INTERLEAVED");
|
|
195
|
+
break;
|
|
196
|
+
}
|
|
197
|
+
reply = step.type === 15 && step.status === 3 && step.response && !step.hasTools && step.stopReason === 2
|
|
198
|
+
&& step.finishedAt !== null && step.completedAt !== null && step.finishedAt >= root.createdAt && step.completedAt >= step.finishedAt ? step : null;
|
|
199
|
+
}
|
|
200
|
+
if (!reply) return observation("pending", "ANTIGRAVITY_REPLY_NOT_OBSERVED");
|
|
201
|
+
const current = (await list({ home, surface: "desktop", limit: 1000 })).filter((row) => row.conversationId === record.conversationId);
|
|
202
|
+
if (current.length !== 1 || current[0].surface !== "desktop" || current[0].projectId !== matches[0].projectId || canonicalCwd(current[0].cwd) !== canonicalCwd(record.cwd)) throw transcriptFailure("ANTIGRAVITY_REPLY_SCOPE_CHANGED");
|
|
203
|
+
if (current[0].status !== "CASCADE_RUN_STATUS_IDLE") return observation("pending", "ANTIGRAVITY_REPLY_STILL_RUNNING");
|
|
204
|
+
if (Buffer.byteLength(reply.response) > 256 * 1024) throw transcriptFailure("ANTIGRAVITY_REPLY_LIMIT");
|
|
205
|
+
const evidence = { source: "native-transcript", nativeMessageId: root.message.id, rootStepIndex: root.index, replyStepIndex: reply.index,
|
|
206
|
+
executionId: root.executionId, trajectoryId: snapshot.trajectoryId, replySha256: crypto.createHash("sha256").update(reply.response).digest("hex") };
|
|
207
|
+
return { ...record, status: "reply_received", replyObservation: { status: "reply_received", retrySafe: false, ...evidence },
|
|
208
|
+
result: { ...record.result, status: "reply_received", completed: true, response: reply.response, reply: reply.response, ...evidence } };
|
|
209
|
+
} catch (error) {
|
|
210
|
+
return observation("unavailable", /^ANTIGRAVITY_(?:TRANSCRIPT|REPLY)_[A-Z_]+$/.test(error.code ?? "") ? error.code : "ANTIGRAVITY_TRANSCRIPT_UNAVAILABLE");
|
|
211
|
+
}
|
|
212
|
+
}
|
|
11
213
|
|
|
12
214
|
function failure(code, message) {
|
|
13
215
|
return Object.assign(new Error(message), { code, deliveryState: "not_sent" });
|
|
@@ -105,7 +307,7 @@ export async function runAgentApi(options) {
|
|
|
105
307
|
}
|
|
106
308
|
}
|
|
107
309
|
|
|
108
|
-
async function executeAgentApi({ command, conversationId, prompt, expectedCwd, home = os.homedir(), env = process.env, executable = env.ANTIGRAVITY_AGENTAPI_EXE, run = execute, list = listAntigravityConversations }) {
|
|
310
|
+
async function executeAgentApi({ command, conversationId, prompt, expectedCwd, home = os.homedir(), env = process.env, executable = env.ANTIGRAVITY_AGENTAPI_EXE, run = execute, list = listAntigravityConversations, beforeDispatch }) {
|
|
109
311
|
if (!UUID.test(conversationId)) throw failure("ANTIGRAVITY_ID_INVALID", "An exact conversation UUID is required");
|
|
110
312
|
if (!["metadata", "send"].includes(command)) throw new Error("Unsupported Antigravity agentapi command");
|
|
111
313
|
if (!env.ANTIGRAVITY_LS_ADDRESS || !env.ANTIGRAVITY_CSRF_TOKEN) throw failure("ANTIGRAVITY_SIDECAR_REQUIRED", "Antigravity must launch the bridge sidecar before native requests are available");
|
|
@@ -141,6 +343,8 @@ async function executeAgentApi({ command, conversationId, prompt, expectedCwd, h
|
|
|
141
343
|
if (metadata.cwd !== cwd || metadata.projectId !== before.projectId) throw failure("ANTIGRAVITY_METADATA_MISMATCH", "Native metadata does not match the selected conversation workspace and project");
|
|
142
344
|
const current = await select();
|
|
143
345
|
if (current.projectId !== before.projectId) throw failure("ANTIGRAVITY_METADATA_MISMATCH", "Conversation project changed during delivery preflight");
|
|
346
|
+
const nativeObservation = await captureAntigravityObservation({ home, conversationId, prompt, projectId: current.projectId });
|
|
347
|
+
await beforeDispatch?.(nativeObservation);
|
|
144
348
|
await invoke(["send-message", conversationId, prompt]);
|
|
145
|
-
return { conversationId, status: "accepted", completed: false, surface: "desktop" };
|
|
349
|
+
return { conversationId, status: "accepted", completed: false, surface: "desktop", nativeObservation };
|
|
146
350
|
}
|
|
@@ -236,7 +236,7 @@ export function createAntigravityOutboundTargets(options = {}) {
|
|
|
236
236
|
await context.check();
|
|
237
237
|
return { target, sessionId: recipient.sessionId, taskId, cwd: context.cwd, msgId, status: outcome.delivery?.status ?? "sent_unconfirmed", completed: false, accountContext: context.accountContext };
|
|
238
238
|
} catch (error) {
|
|
239
|
-
if (error.msgId) receipts.set(error.msgId, { sender: context.identity, accountContext: context.accountContext, cwd: context.cwd });
|
|
239
|
+
if (error.msgId && error.code !== "PEER_REPLY_PENDING") receipts.set(error.msgId, { sender: context.identity, accountContext: context.accountContext, cwd: context.cwd });
|
|
240
240
|
if (msgId) error.msgId = msgId;
|
|
241
241
|
if (msgId || error.deliveryUncertain) error.deliveryState = "unknown";
|
|
242
242
|
else if (!error.deliveryState) error.deliveryState = "not_sent";
|
|
@@ -3,7 +3,7 @@ import path from "node:path";
|
|
|
3
3
|
import { pathToFileURL } from "node:url";
|
|
4
4
|
import { antigravityBridgeDirectory, serveAntigravityDesktop } from "./antigravity-ipc.mjs";
|
|
5
5
|
import { AntigravityDeliveries } from "./antigravity-deliveries.mjs";
|
|
6
|
-
import { listAntigravityConversations, runAgentApi, sanitizeAntigravityMetadata } from "./antigravity-desktop.mjs";
|
|
6
|
+
import { listAntigravityConversations, observeAntigravityDelivery, runAgentApi, sanitizeAntigravityMetadata } from "./antigravity-desktop.mjs";
|
|
7
7
|
import { exitForVersionRequest } from "./cli-version.mjs";
|
|
8
8
|
import { readProcessAncestry } from "./claude-sender-context.mjs";
|
|
9
9
|
|
|
@@ -34,7 +34,7 @@ export async function createAntigravityParentGuard({ parentPid = process.ppid, i
|
|
|
34
34
|
};
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
-
export async function startAntigravitySidecar({ directory = antigravityBridgeDirectory(), env = process.env, createParentGuard = createAntigravityParentGuard, serve = serveAntigravityDesktop, parentCheckIntervalMs = 5000, onParentLost = () => {} } = {}) {
|
|
37
|
+
export async function startAntigravitySidecar({ directory = antigravityBridgeDirectory(), env = process.env, createParentGuard = createAntigravityParentGuard, serve = serveAntigravityDesktop, parentCheckIntervalMs = 5000, onParentLost = () => {}, runNative = runAgentApi, observeDelivery = observeAntigravityDelivery } = {}) {
|
|
38
38
|
if (!env.ANTIGRAVITY_AGENTAPI_EXE || !env.ANTIGRAVITY_LS_ADDRESS) throw new Error("Antigravity must launch this sidecar. Do not run it as a replacement app server.");
|
|
39
39
|
const assertParent = await createParentGuard();
|
|
40
40
|
const deliveries = new AntigravityDeliveries(path.join(directory, "desktop-deliveries"));
|
|
@@ -68,7 +68,7 @@ export async function startAntigravitySidecar({ directory = antigravityBridgeDir
|
|
|
68
68
|
const native = sanitizeAntigravityMetadata(await runAgentApi({ command: "metadata", conversationId: request.conversationId, env }), request.conversationId);
|
|
69
69
|
return { ...record, ...native };
|
|
70
70
|
}
|
|
71
|
-
if (request.operation === "receipt") return deliveries.
|
|
71
|
+
if (request.operation === "receipt") return deliveries.refresh(request.requestId, observeDelivery);
|
|
72
72
|
if (request.operation !== "send" || typeof request.prompt !== "string" || !request.prompt.trim() || request.prompt.length > 30000 || request.prompt.includes("\0")
|
|
73
73
|
|| typeof request.cwd !== "string" || !path.isAbsolute(request.cwd) || !request.sender || !["codex", "claude"].includes(request.sender.provider)
|
|
74
74
|
|| typeof request.sender.id !== "string" || !request.sender.id) throw new Error("Invalid Antigravity delivery request");
|
|
@@ -76,7 +76,8 @@ export async function startAntigravitySidecar({ directory = antigravityBridgeDir
|
|
|
76
76
|
await checkParent();
|
|
77
77
|
if (sending.has(request.conversationId)) throw Object.assign(new Error("Another delivery is pending for this conversation"), { deliveryState: "not_sent" });
|
|
78
78
|
sending.add(request.conversationId);
|
|
79
|
-
try { return await
|
|
79
|
+
try { return await runNative({ command: "send", conversationId: request.conversationId, expectedCwd: request.cwd, prompt: request.prompt, env,
|
|
80
|
+
beforeDispatch: (observation) => deliveries.captureObservation(request.requestId, observation) }); }
|
|
80
81
|
finally { sending.delete(request.conversationId); }
|
|
81
82
|
});
|
|
82
83
|
}, { directory });
|
|
@@ -61,22 +61,29 @@ function workerEnv(env, relayEnv = {}) {
|
|
|
61
61
|
}
|
|
62
62
|
|
|
63
63
|
function binary(executable, env, platform = process.platform) {
|
|
64
|
-
const selected = executable ?? resolveCodexBin(undefined, { env, platform });
|
|
64
|
+
const selected = executable ?? env.CODEX_BIN ?? env.CODEX_CLI_PATH ?? resolveCodexBin(undefined, { env, platform });
|
|
65
65
|
try {
|
|
66
66
|
if (typeof selected !== "string" || !path.isAbsolute(selected) || /[\u0000-\u001f\u007f]/.test(selected)) throw new Error();
|
|
67
67
|
const resolved = fs.realpathSync.native(selected);
|
|
68
68
|
if (!fs.statSync(resolved).isFile() || platform === "win32" && !/\.(?:exe|com)$/i.test(resolved)) throw new Error();
|
|
69
69
|
fs.accessSync(resolved, platform === "win32" ? fs.constants.R_OK : fs.constants.X_OK);
|
|
70
70
|
return resolved;
|
|
71
|
-
} catch { throw failure("CLI_UNAVAILABLE", "inspect"); }
|
|
71
|
+
} catch (cause) { throw Object.assign(failure("CLI_UNAVAILABLE", "inspect"), { cause }); }
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
-
|
|
74
|
+
function missingExecutable(error) {
|
|
75
|
+
return ["ENOENT", "ENOTDIR"].includes(error?.code) || ["ENOENT", "ENOTDIR"].includes(error?.cause?.code);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async function inspectCodexWorkerAttempt({ env, executable, platform, run, retryMissing = false }) {
|
|
75
79
|
const result = { agent: "codex", available: false, authenticated: null, ready: false };
|
|
76
80
|
if (!object(env) || typeof run !== "function") return { ...result, code: "INVALID_INPUT", reason: ERRORS.INVALID_INPUT };
|
|
77
81
|
let selected;
|
|
78
82
|
try { selected = binary(executable, env, platform); }
|
|
79
|
-
catch (error) {
|
|
83
|
+
catch (error) {
|
|
84
|
+
if (retryMissing && missingExecutable(error)) throw error;
|
|
85
|
+
return { ...result, code: error.code, reason: error.message };
|
|
86
|
+
}
|
|
80
87
|
const options = { env: workerEnv(env), windowsHide: true, shell: false, timeout: 10000, maxBuffer: MAX_CAPTURE, encoding: "utf8" };
|
|
81
88
|
try {
|
|
82
89
|
const version = await run(selected, ["--version"], options);
|
|
@@ -88,6 +95,8 @@ export async function inspectCodexWorker({ env = process.env, executable, platfo
|
|
|
88
95
|
return { ...result, code: "CLI_UNSUPPORTED", reason: ERRORS.CLI_UNSUPPORTED };
|
|
89
96
|
}
|
|
90
97
|
} catch (error) {
|
|
98
|
+
if (retryMissing && missingExecutable(error)) throw error;
|
|
99
|
+
if (missingExecutable(error)) return { ...result, available: false, code: "CLI_UNAVAILABLE", reason: ERRORS.CLI_UNAVAILABLE };
|
|
91
100
|
const code = diagnosticCode(error.stderr, result.available ? "CLI_UNSUPPORTED" : "CLI_UNAVAILABLE");
|
|
92
101
|
return { ...result, code, reason: ERRORS[code] };
|
|
93
102
|
}
|
|
@@ -100,11 +109,24 @@ export async function inspectCodexWorker({ env = process.env, executable, platfo
|
|
|
100
109
|
const code = diagnosticCode(text, "AUTH_UNVERIFIED");
|
|
101
110
|
return { ...result, authenticated: code === "AUTH_REQUIRED" ? false : null, code, reason: ERRORS[code] };
|
|
102
111
|
} catch (error) {
|
|
112
|
+
if (retryMissing && missingExecutable(error)) throw error;
|
|
113
|
+
if (missingExecutable(error)) return { ...result, available: false, code: "CLI_UNAVAILABLE", reason: ERRORS.CLI_UNAVAILABLE };
|
|
103
114
|
const code = diagnosticCode(`${error.stdout ?? ""}\n${error.stderr ?? ""}`, "AUTH_UNVERIFIED");
|
|
104
115
|
return { ...result, authenticated: code === "AUTH_REQUIRED" ? false : null, code, reason: ERRORS[code] };
|
|
105
116
|
}
|
|
106
117
|
}
|
|
107
118
|
|
|
119
|
+
export async function inspectCodexWorker({ env = process.env, executable, platform = process.platform, run = execFileAsync } = {}) {
|
|
120
|
+
const settings = { env, executable, platform, run };
|
|
121
|
+
const pinned = executable != null || env?.CODEX_BIN != null || env?.CODEX_CLI_PATH != null;
|
|
122
|
+
try { return await inspectCodexWorkerAttempt({ ...settings, retryMissing: !pinned }); }
|
|
123
|
+
catch (error) {
|
|
124
|
+
if (!missingExecutable(error)) throw error;
|
|
125
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
126
|
+
return inspectCodexWorkerAttempt(settings);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
108
130
|
function capture(executable, args, { cwd, env, spawn, signal, timeoutMs }) {
|
|
109
131
|
return new Promise((resolve, reject) => {
|
|
110
132
|
if (signal?.aborted) return reject(failure("ABORTED", "configure"));
|
package/src/claude-bridge.mjs
CHANGED
|
@@ -31,7 +31,7 @@ import { BACKGROUND_WORKER_GUIDANCE, registerBackgroundTools } from "./backgroun
|
|
|
31
31
|
|
|
32
32
|
exitForVersionRequest(import.meta.url);
|
|
33
33
|
|
|
34
|
-
const VERSION = "1.26.
|
|
34
|
+
const VERSION = "1.26.10";
|
|
35
35
|
void import("./telemetry.mjs").then(({ startUsageReporting }) => startUsageReporting({ version: VERSION })).catch(() => {});
|
|
36
36
|
const FORWARD_MIN_INTERVAL_MS = 5000;
|
|
37
37
|
const FORWARD_MAX_PER_SESSION = 50;
|
|
@@ -6,6 +6,27 @@ import { backupRegularConfigFile } from "./project-policy.mjs";
|
|
|
6
6
|
|
|
7
7
|
const PRESERVED_FIELDS = ["enabled", "disabled_reason", "enabled_tools", "disabled_tools", "startup_timeout_sec", "tool_timeout_sec"];
|
|
8
8
|
|
|
9
|
+
export const CODEX_MCP_TIMEOUT_MS = 120000;
|
|
10
|
+
|
|
11
|
+
function missingServerError(error, name) {
|
|
12
|
+
const output = `${error?.stderr ?? ""}\n${error?.stdout ?? ""}\n${error?.message ?? ""}`;
|
|
13
|
+
return output.includes(`No MCP server named '${name}' found`);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function readCodexMcpServer(run, name) {
|
|
17
|
+
let output;
|
|
18
|
+
try { output = run(["mcp", "get", name, "--json"]); }
|
|
19
|
+
catch (error) {
|
|
20
|
+
if (missingServerError(error, name)) return null;
|
|
21
|
+
throw error;
|
|
22
|
+
}
|
|
23
|
+
const entry = JSON.parse(output);
|
|
24
|
+
if (!entry || typeof entry !== "object" || Array.isArray(entry) || entry.name !== name) {
|
|
25
|
+
throw new Error(`Codex returned an invalid ${name} entry; existing configuration was not changed`);
|
|
26
|
+
}
|
|
27
|
+
return entry;
|
|
28
|
+
}
|
|
29
|
+
|
|
9
30
|
function sameEnvironment(left = {}, right = {}) {
|
|
10
31
|
const keys = (value) => Object.keys(value ?? {}).sort();
|
|
11
32
|
return JSON.stringify(keys(left)) === JSON.stringify(keys(right)) && keys(left).every((key) => left[key] === right[key]);
|
package/src/index.mjs
CHANGED
|
@@ -44,7 +44,7 @@ import { BACKGROUND_WORKER_GUIDANCE, registerBackgroundTools } from "./backgroun
|
|
|
44
44
|
|
|
45
45
|
exitForVersionRequest(import.meta.url);
|
|
46
46
|
|
|
47
|
-
const VERSION = "1.26.
|
|
47
|
+
const VERSION = "1.26.10";
|
|
48
48
|
void import("./telemetry.mjs").then(({ startUsageReporting }) => startUsageReporting({ version: VERSION })).catch(() => {});
|
|
49
49
|
const log = (msg) => process.stderr.write(`[codex-mcp-bridge] ${msg}\n`);
|
|
50
50
|
|
|
@@ -43,7 +43,7 @@ import { remoteCodexSocketPath, remoteNativeAdapter, remoteRequestResponse } fro
|
|
|
43
43
|
|
|
44
44
|
exitForVersionRequest(import.meta.url);
|
|
45
45
|
|
|
46
|
-
const VERSION = "1.26.
|
|
46
|
+
const VERSION = "1.26.10";
|
|
47
47
|
const log = (msg) => process.stderr.write(`[native-relay] ${msg}\n`);
|
|
48
48
|
|
|
49
49
|
function errorResponse(code, message, sent, details) {
|