@sagentlab/navarch-runtime 0.1.107 → 0.1.111
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 +97 -59
- package/dist/adapters/antigravity-mcp.cjs +221 -0
- package/dist/adapters/antigravity.cjs +348 -0
- package/dist/adapters/gemini.cjs +7 -322
- package/dist/adapters/index.cjs +6 -1
- package/dist/agent-auth.cjs +2 -0
- package/dist/config.cjs +18 -3
- package/dist/conversation/capability.cjs +1 -1
- package/dist/exit-conditions.cjs +1 -1
- package/dist/session.cjs +30 -19
- package/package.json +2 -2
package/dist/adapters/gemini.cjs
CHANGED
|
@@ -2,329 +2,14 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.geminiAdapter = void 0;
|
|
4
4
|
exports.runGeminiAdapter = runGeminiAdapter;
|
|
5
|
-
|
|
6
|
-
const node_fs_1 = require("node:fs");
|
|
7
|
-
const kill_agent_cjs_1 = require("../kill-agent.cjs");
|
|
8
|
-
/**
|
|
9
|
-
* Headless Google Gemini CLI adapter. Gemini's `stream-json` output gives the
|
|
10
|
-
* same normalized boundary as the Claude and Codex adapters: raw process
|
|
11
|
-
* signals plus an optional final report and token counts. Gemini CLI does not
|
|
12
|
-
* report USD cost, so costUsd remains absent.
|
|
13
|
-
*
|
|
14
|
-
* The CLI reads MCP servers from settings rather than accepting Claude's
|
|
15
|
-
* `--mcp-config` flag. The adapter converts the lease-scoped config into a
|
|
16
|
-
* temporary system-settings file and restricts the run to those server names.
|
|
17
|
-
*
|
|
18
|
-
* The prompt arrives on stdin rather than `--prompt`: Gemini runs headlessly
|
|
19
|
-
* whenever stdin is not a TTY and uses the piped text as the prompt. Its
|
|
20
|
-
* native sandbox puts stdin back into argv, so that path still has an argv
|
|
21
|
-
* limit: Linux's 128 KiB per-string limit for container sandboxes and Linux
|
|
22
|
-
* hosts, or macOS's 1 MiB total for sandbox-exec. Gemini also caps stdin at
|
|
23
|
-
* 8 MiB.
|
|
24
|
-
*/
|
|
5
|
+
/** Tombstone for stale claims. Provider IDs and credentials are never aliased. */
|
|
25
6
|
async function runGeminiAdapter(options) {
|
|
26
|
-
if (options.signal?.aborted) {
|
|
27
|
-
return { exitCode: null, timedOut: false, killedByLeaseLoss: true, stdout: "", stderr: "" };
|
|
28
|
-
}
|
|
29
|
-
const promptBytes = Buffer.byteLength(options.prompt, "utf8");
|
|
30
|
-
// Gemini's readStdin() silently truncates at 8 MiB. Reject before any
|
|
31
|
-
// settings writes or process launch rather than execute a partial task.
|
|
32
|
-
if (promptBytes > 8 * 1024 * 1024) {
|
|
33
|
-
return inputFailure(`Gemini CLI stdin is limited to 8 MiB; the task prompt is ${promptBytes} bytes. Reduce the context bundle or select another runtime.`);
|
|
34
|
-
}
|
|
35
|
-
const args = [];
|
|
36
|
-
const env = { ...options.env };
|
|
37
|
-
if (!hasArg(options.extraArgs, "--output-format", "-o")) {
|
|
38
|
-
args.push("--output-format", "stream-json");
|
|
39
|
-
}
|
|
40
|
-
if (!hasArg(options.extraArgs, "--skip-trust")) {
|
|
41
|
-
args.push("--skip-trust");
|
|
42
|
-
}
|
|
43
|
-
if (!hasExplicitApprovalMode(options.extraArgs)) {
|
|
44
|
-
// Navarch sessions have no interactive operator. Host sessions still use
|
|
45
|
-
// Gemini's sandbox below when the worktree guard is enabled.
|
|
46
|
-
args.push("--approval-mode", "yolo");
|
|
47
|
-
}
|
|
48
|
-
const sandboxMounts = [...(options.geminiSandboxMounts ?? [])];
|
|
49
|
-
if (options.mcpConfigPath) {
|
|
50
|
-
const mcp = await prepareGeminiMcpSettings(options.mcpConfigPath, env);
|
|
51
|
-
env.GEMINI_CLI_SYSTEM_SETTINGS_PATH = mcp.settingsPath;
|
|
52
|
-
sandboxMounts.push(`${mcp.settingsPath}:${mcp.settingsPath}:ro`);
|
|
53
|
-
if (mcp.serverNames.length > 0 && !hasArg(options.extraArgs, "--allowed-mcp-server-names")) {
|
|
54
|
-
args.push("--allowed-mcp-server-names", mcp.serverNames.join(","));
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
if (!hasArg(options.extraArgs, "--sandbox", "-s")) {
|
|
58
|
-
if (options.geminiSandboxMounts) {
|
|
59
|
-
args.push("--sandbox");
|
|
60
|
-
env.SANDBOX_MOUNTS = sandboxMounts.join(",");
|
|
61
|
-
}
|
|
62
|
-
else {
|
|
63
|
-
// YOLO mode enables Gemini's own sandbox implicitly. Disable that
|
|
64
|
-
// nested sandbox when Navarch already supplied Docker isolation, or
|
|
65
|
-
// when an operator explicitly disabled the host worktree guard.
|
|
66
|
-
args.push("--sandbox=false");
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
args.push(...options.extraArgs);
|
|
70
|
-
if (options.model)
|
|
71
|
-
args.push("--model", options.model);
|
|
72
|
-
// Gemini's sandbox launcher reads stdin and injects it back into --prompt
|
|
73
|
-
// for its child process (packages/cli/src/gemini.tsx injectStdinIntoArgs).
|
|
74
|
-
// Moving our first spawn's prompt to stdin cannot fix that second spawn.
|
|
75
|
-
// Check the effective sandbox, including operator overrides, without
|
|
76
|
-
// disabling the worktree guard. Even on macOS a Docker sandbox may launch on
|
|
77
|
-
// Linux, while macOS sandbox-exec re-quotes the prompt into one `sh -c`
|
|
78
|
-
// string that only the 1 MiB total ARG_MAX bounds.
|
|
79
|
-
const platform = options.dockerExec ? "linux" : process.platform;
|
|
80
|
-
const sandboxCommand = geminiSandboxCommand(args, geminiProcessEnv(options, env), platform);
|
|
81
|
-
if (sandboxCommand === "sandbox-exec" && platform === "darwin") {
|
|
82
|
-
// shell-quote backslash-escapes these inside the double-quoted prompt.
|
|
83
|
-
// Leave the rest of ARG_MAX for the environment and Gemini's own args.
|
|
84
|
-
const quotedBytes = promptBytes + (options.prompt.match(/["\\$`!]/g)?.length ?? 0);
|
|
85
|
-
if (quotedBytes > 900 * 1024) {
|
|
86
|
-
return inputFailure(`Gemini CLI re-encodes stdin as --prompt when launching its macOS sandbox-exec sandbox; the ${promptBytes}-byte task prompt exceeds macOS's 1 MiB argv limit. Reduce the context bundle or select another runtime that supports large prompts in its sandbox.`);
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
else if (sandboxCommand && promptBytes >= 128 * 1024) {
|
|
90
|
-
return inputFailure(`Gemini CLI re-encodes stdin as --prompt when launching its native sandbox; the ${promptBytes}-byte task prompt exceeds Linux's argv limit. Reduce the context bundle or select another runtime that supports large prompts in its sandbox.`);
|
|
91
|
-
}
|
|
92
|
-
const runOptions = { ...options, env };
|
|
93
|
-
const raw = runOptions.dockerExec
|
|
94
|
-
? await runViaDocker(runOptions, args)
|
|
95
|
-
: await runOnHost(runOptions, args);
|
|
96
|
-
return attachGeminiOutput(raw);
|
|
97
|
-
}
|
|
98
|
-
function inputFailure(stderr) {
|
|
99
|
-
return { exitCode: 1, timedOut: false, killedByLeaseLoss: false, stdout: "", stderr };
|
|
100
|
-
}
|
|
101
|
-
function geminiProcessEnv(options, env) {
|
|
102
|
-
// Docker exec forwards only the session env names into the container.
|
|
103
|
-
return options.dockerExec ? env : { ...process.env, ...env };
|
|
104
|
-
}
|
|
105
|
-
/**
|
|
106
|
-
* Mirrors Gemini's getSandboxCommand (packages/cli/src/config/sandboxConfig.ts):
|
|
107
|
-
* GEMINI_SANDBOX overrides the --sandbox flag and may name the command;
|
|
108
|
-
* otherwise an enabled sandbox is sandbox-exec on macOS and a container
|
|
109
|
-
* (docker or podman) elsewhere. Returns null when no sandbox relaunch occurs.
|
|
110
|
-
*/
|
|
111
|
-
function geminiSandboxCommand(args, env, platform) {
|
|
112
|
-
const configured = env.GEMINI_SANDBOX?.toLowerCase().trim() ?? "";
|
|
113
|
-
let sandbox = configured.length > 0 ? configured : geminiSandboxFlag(args);
|
|
114
|
-
if (sandbox === "1" || sandbox === "true")
|
|
115
|
-
sandbox = true;
|
|
116
|
-
else if (sandbox === "0" || sandbox === "false" || !sandbox)
|
|
117
|
-
sandbox = false;
|
|
118
|
-
if (sandbox === false)
|
|
119
|
-
return null;
|
|
120
|
-
if (typeof sandbox === "string")
|
|
121
|
-
return sandbox;
|
|
122
|
-
return platform === "darwin" ? "sandbox-exec" : "docker";
|
|
123
|
-
}
|
|
124
|
-
function geminiSandboxFlag(args) {
|
|
125
|
-
// There is always a generated --sandbox flag unless extraArgs supplies one.
|
|
126
|
-
// Like yargs, later explicit values override earlier ones.
|
|
127
|
-
let enabled = false;
|
|
128
|
-
for (let index = 0; index < args.length; index++) {
|
|
129
|
-
const arg = args[index];
|
|
130
|
-
if (arg === "--sandbox" || arg === "-s") {
|
|
131
|
-
enabled = args[index + 1] !== "false";
|
|
132
|
-
}
|
|
133
|
-
else if (arg.startsWith("--sandbox=") || arg.startsWith("-s=")) {
|
|
134
|
-
enabled = arg.slice(arg.indexOf("=") + 1) !== "false";
|
|
135
|
-
}
|
|
136
|
-
else if (arg === "--no-sandbox") {
|
|
137
|
-
enabled = false;
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
return enabled;
|
|
141
|
-
}
|
|
142
|
-
function hasArg(args, ...flags) {
|
|
143
|
-
return args.some((arg) => flags.some((flag) => arg === flag || arg.startsWith(`${flag}=`)));
|
|
144
|
-
}
|
|
145
|
-
function hasExplicitApprovalMode(args) {
|
|
146
|
-
return hasArg(args, "--approval-mode", "--yolo", "-y");
|
|
147
|
-
}
|
|
148
|
-
async function prepareGeminiMcpSettings(mcpConfigPath, env) {
|
|
149
|
-
const parsed = JSON.parse(await node_fs_1.promises.readFile(mcpConfigPath, "utf8"));
|
|
150
|
-
const mcpServers = {};
|
|
151
|
-
for (const [name, server] of Object.entries(parsed.mcpServers ?? {})) {
|
|
152
|
-
const httpUrl = server.httpUrl ?? (server.type === "http" ? server.url : undefined);
|
|
153
|
-
if (!httpUrl && !server.url)
|
|
154
|
-
continue;
|
|
155
|
-
const headers = {};
|
|
156
|
-
let headerIndex = 0;
|
|
157
|
-
for (const [header, value] of Object.entries(server.headers ?? {})) {
|
|
158
|
-
const envName = `NAVARCH_GEMINI_MCP_${safeEnvSegment(name)}_${headerIndex++}`;
|
|
159
|
-
env[envName] = value;
|
|
160
|
-
headers[header] = `\${${envName}}`;
|
|
161
|
-
}
|
|
162
|
-
mcpServers[name] = {
|
|
163
|
-
...(httpUrl ? { httpUrl } : { url: server.url }),
|
|
164
|
-
...(Object.keys(headers).length > 0 ? { headers } : {}),
|
|
165
|
-
};
|
|
166
|
-
}
|
|
167
|
-
const settingsPath = `${mcpConfigPath}.gemini-settings.json`;
|
|
168
|
-
await node_fs_1.promises.writeFile(settingsPath, JSON.stringify({ mcpServers }, null, 2), { mode: 0o600 });
|
|
169
|
-
await node_fs_1.promises.chmod(settingsPath, 0o600);
|
|
170
|
-
return { settingsPath, serverNames: Object.keys(mcpServers) };
|
|
171
|
-
}
|
|
172
|
-
function safeEnvSegment(value) {
|
|
173
|
-
return value.toUpperCase().replace(/[^A-Z0-9_]/g, "_");
|
|
174
|
-
}
|
|
175
|
-
function parseGeminiStream(stdout) {
|
|
176
|
-
const events = [];
|
|
177
|
-
for (const line of stdout.split(/\r?\n/)) {
|
|
178
|
-
if (!line.trim())
|
|
179
|
-
continue;
|
|
180
|
-
try {
|
|
181
|
-
const event = JSON.parse(line);
|
|
182
|
-
if (event && typeof event === "object")
|
|
183
|
-
events.push(event);
|
|
184
|
-
}
|
|
185
|
-
catch {
|
|
186
|
-
// Parser drift must not turn a successfully completed CLI run into a
|
|
187
|
-
// runtime crash. Unknown lines remain available in the transcript.
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
return events;
|
|
191
|
-
}
|
|
192
|
-
function attachGeminiOutput(result) {
|
|
193
|
-
const events = parseGeminiStream(result.stdout);
|
|
194
|
-
if (events.length === 0)
|
|
195
|
-
return result;
|
|
196
|
-
let reportText = "";
|
|
197
|
-
let tokensIn;
|
|
198
|
-
let tokensOut;
|
|
199
|
-
for (const event of events) {
|
|
200
|
-
if (event.type === "tool_use") {
|
|
201
|
-
// Gemini emits pre-tool assistant text as message deltas. Only retain
|
|
202
|
-
// the answer produced after the last tool call as the completion report.
|
|
203
|
-
reportText = "";
|
|
204
|
-
}
|
|
205
|
-
else if (event.type === "message" &&
|
|
206
|
-
event.role === "assistant" &&
|
|
207
|
-
typeof event.content === "string") {
|
|
208
|
-
reportText = event.delta === false ? event.content : reportText + event.content;
|
|
209
|
-
}
|
|
210
|
-
else if (event.type === "result" && event.stats) {
|
|
211
|
-
const reportedInput = nonNegativeMetric(event.stats.input_tokens);
|
|
212
|
-
const reportedOutput = nonNegativeMetric(event.stats.output_tokens);
|
|
213
|
-
if (reportedInput !== undefined)
|
|
214
|
-
tokensIn = reportedInput;
|
|
215
|
-
if (reportedOutput !== undefined)
|
|
216
|
-
tokensOut = reportedOutput;
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
7
|
return {
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
8
|
+
exitCode: options.signal?.aborted ? null : 1,
|
|
9
|
+
timedOut: false,
|
|
10
|
+
killedByLeaseLoss: options.signal?.aborted ?? false,
|
|
11
|
+
stdout: "",
|
|
12
|
+
stderr: "Gemini CLI is retired. Explicitly install and authenticate Antigravity, select antigravity, and update the task's runtime pin. No Gemini credentials or conversation IDs are transferred.",
|
|
224
13
|
};
|
|
225
14
|
}
|
|
226
|
-
|
|
227
|
-
return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : undefined;
|
|
228
|
-
}
|
|
229
|
-
async function runOnHost(options, args) {
|
|
230
|
-
return new Promise((resolve) => {
|
|
231
|
-
let stdout = "";
|
|
232
|
-
let stderr = "";
|
|
233
|
-
let timedOut = false;
|
|
234
|
-
let killedByLeaseLoss = false;
|
|
235
|
-
const child = (0, node_child_process_1.spawn)(options.bin, args, {
|
|
236
|
-
detached: process.platform !== "win32",
|
|
237
|
-
cwd: options.cwd,
|
|
238
|
-
env: { ...process.env, ...options.env },
|
|
239
|
-
});
|
|
240
|
-
// Ignore EPIPE from a child that exits before reading; `close` reports it.
|
|
241
|
-
child.stdin?.on("error", () => undefined);
|
|
242
|
-
child.stdin?.end(options.prompt);
|
|
243
|
-
options.onProcessStarted?.(child.pid);
|
|
244
|
-
const timer = options.timeoutMs > 0 ? setTimeout(() => {
|
|
245
|
-
timedOut = true;
|
|
246
|
-
(0, kill_agent_cjs_1.killAgent)(child);
|
|
247
|
-
}, options.timeoutMs) : undefined;
|
|
248
|
-
const onAbort = () => {
|
|
249
|
-
killedByLeaseLoss = true;
|
|
250
|
-
(0, kill_agent_cjs_1.killAgent)(child);
|
|
251
|
-
};
|
|
252
|
-
options.signal?.addEventListener("abort", onAbort, { once: true });
|
|
253
|
-
if (options.signal?.aborted)
|
|
254
|
-
onAbort();
|
|
255
|
-
child.stdout.on("data", (data) => {
|
|
256
|
-
options.onActivity?.();
|
|
257
|
-
const text = data.toString();
|
|
258
|
-
stdout += text;
|
|
259
|
-
options.onOutput?.("stdout", text);
|
|
260
|
-
});
|
|
261
|
-
child.stderr.on("data", (data) => {
|
|
262
|
-
options.onActivity?.();
|
|
263
|
-
const text = data.toString();
|
|
264
|
-
stderr += text;
|
|
265
|
-
options.onOutput?.("stderr", text);
|
|
266
|
-
});
|
|
267
|
-
child.on("error", (err) => {
|
|
268
|
-
clearTimeout(timer);
|
|
269
|
-
options.signal?.removeEventListener("abort", onAbort);
|
|
270
|
-
stderr += `\n${String(err)}`;
|
|
271
|
-
resolve({ exitCode: null, timedOut, killedByLeaseLoss, stdout, stderr });
|
|
272
|
-
});
|
|
273
|
-
child.on("close", (code) => {
|
|
274
|
-
clearTimeout(timer);
|
|
275
|
-
options.signal?.removeEventListener("abort", onAbort);
|
|
276
|
-
resolve({ exitCode: code, timedOut, killedByLeaseLoss, stdout, stderr });
|
|
277
|
-
});
|
|
278
|
-
});
|
|
279
|
-
}
|
|
280
|
-
async function runViaDocker(options, args) {
|
|
281
|
-
const { containerName, runner } = options.dockerExec;
|
|
282
|
-
const command = `[ -f /tmp/session.env ] && . /tmp/session.env; cd repo 2>/dev/null; ${[
|
|
283
|
-
options.bin,
|
|
284
|
-
...args,
|
|
285
|
-
].map(shellQuote).join(" ")}`;
|
|
286
|
-
let killedByLeaseLoss = false;
|
|
287
|
-
const onAbort = () => {
|
|
288
|
-
killedByLeaseLoss = true;
|
|
289
|
-
runner.run("docker", ["kill", containerName], { timeoutMs: 10_000 }).catch(() => undefined);
|
|
290
|
-
};
|
|
291
|
-
options.signal?.addEventListener("abort", onAbort, { once: true });
|
|
292
|
-
if (options.signal?.aborted)
|
|
293
|
-
onAbort();
|
|
294
|
-
try {
|
|
295
|
-
const forwardedEnv = Object.keys(options.env).flatMap((name) => ["--env", name]);
|
|
296
|
-
const result = await runner.run("docker", ["exec", "-i", ...forwardedEnv, containerName, "sh", "-c", command], { input: options.prompt,
|
|
297
|
-
timeoutMs: options.timeoutMs,
|
|
298
|
-
onActivity: options.onActivity,
|
|
299
|
-
onStdout: (chunk) => options.onOutput?.("stdout", chunk),
|
|
300
|
-
onStderr: (chunk) => options.onOutput?.("stderr", chunk),
|
|
301
|
-
signal: options.signal,
|
|
302
|
-
env: { ...process.env, ...options.env }, });
|
|
303
|
-
return {
|
|
304
|
-
exitCode: result.code,
|
|
305
|
-
timedOut: false,
|
|
306
|
-
killedByLeaseLoss,
|
|
307
|
-
stdout: result.stdout,
|
|
308
|
-
stderr: result.stderr,
|
|
309
|
-
};
|
|
310
|
-
}
|
|
311
|
-
catch (err) {
|
|
312
|
-
return {
|
|
313
|
-
exitCode: null,
|
|
314
|
-
timedOut: false,
|
|
315
|
-
killedByLeaseLoss,
|
|
316
|
-
stdout: "",
|
|
317
|
-
stderr: String(err),
|
|
318
|
-
};
|
|
319
|
-
}
|
|
320
|
-
finally {
|
|
321
|
-
options.signal?.removeEventListener("abort", onAbort);
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
function shellQuote(value) {
|
|
325
|
-
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
326
|
-
}
|
|
327
|
-
exports.geminiAdapter = {
|
|
328
|
-
agentType: "gemini",
|
|
329
|
-
run: runGeminiAdapter,
|
|
330
|
-
};
|
|
15
|
+
exports.geminiAdapter = { agentType: "gemini", run: runGeminiAdapter };
|
package/dist/adapters/index.cjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.runQoderAdapter = exports.qoderAdapter = exports.runAcpAdapter = exports.acpAdapter = exports.runPiAdapter = exports.piAdapter = exports.runLocalPiAdapter = exports.localPiAdapter = exports.runOpenCodeAdapter = exports.openCodeAdapter = exports.runGeminiAdapter = exports.geminiAdapter = exports.runCodexAdapter = exports.codexAdapter = exports.runClaudeCodeAdapter = exports.claudeCodeAdapter = void 0;
|
|
3
|
+
exports.runQoderAdapter = exports.qoderAdapter = exports.runAcpAdapter = exports.acpAdapter = exports.runPiAdapter = exports.piAdapter = exports.runLocalPiAdapter = exports.localPiAdapter = exports.runOpenCodeAdapter = exports.openCodeAdapter = exports.runGeminiAdapter = exports.geminiAdapter = exports.runAntigravityAdapter = exports.antigravityAdapter = exports.runCodexAdapter = exports.codexAdapter = exports.runClaudeCodeAdapter = exports.claudeCodeAdapter = void 0;
|
|
4
4
|
exports.selectAdapter = selectAdapter;
|
|
5
5
|
const claude_cjs_1 = require("./claude.cjs");
|
|
6
6
|
Object.defineProperty(exports, "claudeCodeAdapter", { enumerable: true, get: function () { return claude_cjs_1.claudeCodeAdapter; } });
|
|
@@ -8,6 +8,9 @@ Object.defineProperty(exports, "runClaudeCodeAdapter", { enumerable: true, get:
|
|
|
8
8
|
const codex_cjs_1 = require("./codex.cjs");
|
|
9
9
|
Object.defineProperty(exports, "codexAdapter", { enumerable: true, get: function () { return codex_cjs_1.codexAdapter; } });
|
|
10
10
|
Object.defineProperty(exports, "runCodexAdapter", { enumerable: true, get: function () { return codex_cjs_1.runCodexAdapter; } });
|
|
11
|
+
const antigravity_cjs_1 = require("./antigravity.cjs");
|
|
12
|
+
Object.defineProperty(exports, "antigravityAdapter", { enumerable: true, get: function () { return antigravity_cjs_1.antigravityAdapter; } });
|
|
13
|
+
Object.defineProperty(exports, "runAntigravityAdapter", { enumerable: true, get: function () { return antigravity_cjs_1.runAntigravityAdapter; } });
|
|
11
14
|
const gemini_cjs_1 = require("./gemini.cjs");
|
|
12
15
|
Object.defineProperty(exports, "geminiAdapter", { enumerable: true, get: function () { return gemini_cjs_1.geminiAdapter; } });
|
|
13
16
|
Object.defineProperty(exports, "runGeminiAdapter", { enumerable: true, get: function () { return gemini_cjs_1.runGeminiAdapter; } });
|
|
@@ -37,6 +40,8 @@ function selectAdapter(agentType) {
|
|
|
37
40
|
switch (agentType) {
|
|
38
41
|
case "codex":
|
|
39
42
|
return codex_cjs_1.codexAdapter;
|
|
43
|
+
case "antigravity":
|
|
44
|
+
return antigravity_cjs_1.antigravityAdapter;
|
|
40
45
|
case "gemini":
|
|
41
46
|
return gemini_cjs_1.geminiAdapter;
|
|
42
47
|
case "opencode":
|
package/dist/agent-auth.cjs
CHANGED
|
@@ -43,6 +43,8 @@ function matchingLine(text) {
|
|
|
43
43
|
function detectAgentAuthFailure(result) {
|
|
44
44
|
if (result.killedByLeaseLoss || result.timedOut || result.capacityError)
|
|
45
45
|
return null;
|
|
46
|
+
if (result.authError)
|
|
47
|
+
return { detail: result.authError };
|
|
46
48
|
const claude = (0, exit_conditions_cjs_1.parseClaudeJsonResult)(result.stdout) ?? (0, exit_conditions_cjs_1.parseClaudeJsonResult)(result.stderr);
|
|
47
49
|
if (claude) {
|
|
48
50
|
if (!claude.is_error && result.exitCode === 0)
|
package/dist/config.cjs
CHANGED
|
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.OPENCODE_HOST_GUARD_MESSAGE = exports.RUNTIME_AGENT_TYPES = exports.DEFAULT_SANDBOX_IMAGE = void 0;
|
|
6
|
+
exports.OPENCODE_HOST_GUARD_MESSAGE = exports.ANTIGRAVITY_HOST_GUARD_MESSAGE = exports.RUNTIME_AGENT_TYPES = exports.DEFAULT_SANDBOX_IMAGE = void 0;
|
|
7
7
|
exports.isRuntimeAgentType = isRuntimeAgentType;
|
|
8
8
|
exports.unknownRuntimeIds = unknownRuntimeIds;
|
|
9
9
|
exports.opencodeBlockedOnHost = opencodeBlockedOnHost;
|
|
@@ -21,7 +21,7 @@ exports.DEFAULT_SANDBOX_IMAGE = "ghcr.io/sagentlab/navarch-sandbox-agent:0.1.0";
|
|
|
21
21
|
exports.RUNTIME_AGENT_TYPES = [
|
|
22
22
|
"claude-code",
|
|
23
23
|
"codex",
|
|
24
|
-
"
|
|
24
|
+
"antigravity",
|
|
25
25
|
"opencode",
|
|
26
26
|
"acp",
|
|
27
27
|
"qoder",
|
|
@@ -77,6 +77,9 @@ function envList(env, name, fallback) {
|
|
|
77
77
|
function opencodeBlockedOnHost(sandboxMode, worktreeGuard) {
|
|
78
78
|
return sandboxMode === "host" && worktreeGuard;
|
|
79
79
|
}
|
|
80
|
+
exports.ANTIGRAVITY_HOST_GUARD_MESSAGE = "Antigravity cannot enforce Navarch's host worktree boundary or read-only Navi mode. " +
|
|
81
|
+
"Use an isolated Docker image with agy and Node installed, or explicitly set NAVARCH_WORKTREE_GUARD=off " +
|
|
82
|
+
"only on a dedicated trusted machine. Antigravity inherits that machine's global configuration.";
|
|
80
83
|
exports.OPENCODE_HOST_GUARD_MESSAGE = "OpenCode cannot enforce Navarch's host worktree boundary for shell side effects. " +
|
|
81
84
|
"Use NAVARCH_SANDBOX_MODE=docker, or explicitly set NAVARCH_WORKTREE_GUARD=off " +
|
|
82
85
|
"only on an otherwise isolated machine.";
|
|
@@ -86,6 +89,9 @@ exports.OPENCODE_HOST_GUARD_MESSAGE = "OpenCode cannot enforce Navarch's host wo
|
|
|
86
89
|
* it is trivially unit-testable without mutating global state.
|
|
87
90
|
*/
|
|
88
91
|
function loadRuntimeConfig(env = process.env) {
|
|
92
|
+
if (env.NAVARCH_AGENT === "gemini" || envList(env, "NAVARCH_RUNTIMES", []).includes("gemini")) {
|
|
93
|
+
throw new Error("Gemini CLI is retired. Install and authenticate Antigravity separately, then explicitly select NAVARCH_AGENT=antigravity (or --agent antigravity) and update NAVARCH_RUNTIMES. Existing Gemini config, credentials and sessions are not migrated.");
|
|
94
|
+
}
|
|
89
95
|
const configDir = env.NAVARCH_CONFIG_DIR ?? node_path_1.default.join(node_os_1.default.homedir(), ".navarch");
|
|
90
96
|
const leaseHeartbeatIntervalMs = envInt(env, "NAVARCH_LEASE_HEARTBEAT_INTERVAL_MS", 5 * 60 * 1000);
|
|
91
97
|
// Run the agent with the resources already available on its machine by
|
|
@@ -108,10 +114,17 @@ function loadRuntimeConfig(env = process.env) {
|
|
|
108
114
|
// time; keep it out of the claim instead of discovering this per session.
|
|
109
115
|
const runnableRuntimes = requestedRuntimes.filter((runtime) => runtime === "pi-local"
|
|
110
116
|
? sandboxMode === "host" && !worktreeGuard
|
|
111
|
-
: runtime
|
|
117
|
+
: runtime === "antigravity"
|
|
118
|
+
? process.platform !== "win32" && !opencodeBlockedOnHost(sandboxMode, worktreeGuard)
|
|
119
|
+
: runtime !== "opencode" || !opencodeBlockedOnHost(sandboxMode, worktreeGuard));
|
|
112
120
|
if (requestedRuntimes.includes("pi-local") && runnableRuntimes.length === 0) {
|
|
113
121
|
throw new Error("Pi (local) requires NAVARCH_SANDBOX_MODE=host and NAVARCH_WORKTREE_GUARD=off");
|
|
114
122
|
}
|
|
123
|
+
if (requestedRuntimes.includes("antigravity") && runnableRuntimes.length === 0) {
|
|
124
|
+
if (process.platform === "win32")
|
|
125
|
+
throw new Error("Antigravity's private lease MCP relay requires a Linux or macOS worker; Windows is unsupported.");
|
|
126
|
+
throw new Error(exports.ANTIGRAVITY_HOST_GUARD_MESSAGE);
|
|
127
|
+
}
|
|
115
128
|
if (requestedRuntimes.includes("opencode") && runnableRuntimes.length === 0) {
|
|
116
129
|
throw new Error(exports.OPENCODE_HOST_GUARD_MESSAGE);
|
|
117
130
|
}
|
|
@@ -143,6 +156,8 @@ function loadRuntimeConfig(env = process.env) {
|
|
|
143
156
|
claudeExtraArgs: envList(env, "NAVARCH_CLAUDE_EXTRA_ARGS", []),
|
|
144
157
|
codexBin: env.NAVARCH_CODEX_BIN ?? "codex",
|
|
145
158
|
codexExtraArgs: envList(env, "NAVARCH_CODEX_EXTRA_ARGS", []),
|
|
159
|
+
antigravityBin: env.NAVARCH_ANTIGRAVITY_BIN ?? "agy",
|
|
160
|
+
antigravityExtraArgs: envList(env, "NAVARCH_ANTIGRAVITY_EXTRA_ARGS", []),
|
|
146
161
|
geminiBin: env.NAVARCH_GEMINI_BIN ?? "gemini",
|
|
147
162
|
geminiExtraArgs: envList(env, "NAVARCH_GEMINI_EXTRA_ARGS", []),
|
|
148
163
|
opencodeBin: env.NAVARCH_OPENCODE_BIN ?? "opencode",
|
|
@@ -40,7 +40,7 @@ function conversationCapability(config) {
|
|
|
40
40
|
}
|
|
41
41
|
/** Per-adapter support table for docs and diagnostics (host sandbox, guard on, feature enabled). */
|
|
42
42
|
function conversationSupportMatrix() {
|
|
43
|
-
const agents = ["claude-code", "codex", "gemini", "opencode", "acp", "qoder", "pi-local", "pi"];
|
|
43
|
+
const agents = ["claude-code", "codex", "gemini", "antigravity", "opencode", "acp", "qoder", "pi-local", "pi"];
|
|
44
44
|
return agents.map((agentType) => ({
|
|
45
45
|
agentType,
|
|
46
46
|
capability: conversationCapability({
|
package/dist/exit-conditions.cjs
CHANGED
|
@@ -22,7 +22,7 @@ exports.mapExitCondition = mapExitCondition;
|
|
|
22
22
|
exports.AGENT_AUTH_FAILURE_PREFIX = "Coding agent authentication failed";
|
|
23
23
|
function agentAuthFailureReport(detail) {
|
|
24
24
|
return `${exports.AGENT_AUTH_FAILURE_PREFIX}: ${detail} ` +
|
|
25
|
-
"Log the coding agent in again on this machine (`claude /login` or `
|
|
25
|
+
"Log the coding agent in again on this machine (`claude /login`, `codex login`, or an interactive `agy` session), then resume the agent.";
|
|
26
26
|
}
|
|
27
27
|
/**
|
|
28
28
|
* Best-effort parse of `claude -p --output-format json` (or `stream-json`,
|
package/dist/session.cjs
CHANGED
|
@@ -50,7 +50,7 @@ function resolveSessionExecution(config, claimed) {
|
|
|
50
50
|
model: runtime === "codex" ? "gpt-6-astra"
|
|
51
51
|
: runtime === "pi-local" ? "qwen3.8-flash-next-iq2_xs"
|
|
52
52
|
: runtime === "gemini" ? "auto"
|
|
53
|
-
: runtime === "opencode" || runtime === "acp" || runtime === "qoder" ? "default"
|
|
53
|
+
: runtime === "antigravity" || runtime === "opencode" || runtime === "acp" || runtime === "qoder" ? "default"
|
|
54
54
|
: "claude-opus-5-5",
|
|
55
55
|
reasoning_effort: "medium",
|
|
56
56
|
};
|
|
@@ -65,7 +65,7 @@ function resolveSessionExecution(config, claimed) {
|
|
|
65
65
|
* 2. fetch secrets from the broker at session start (managed GitHub git
|
|
66
66
|
* credentials are subsequently refreshed by a lease-scoped helper)
|
|
67
67
|
* 3. optionally stand up a Docker sandbox when explicitly configured
|
|
68
|
-
* 4. run the adapter selected by the claim (Claude Code, Codex,
|
|
68
|
+
* 4. run the adapter selected by the claim (Claude Code, Codex, Antigravity, or OpenCode;
|
|
69
69
|
* adapters/index.cts#selectAdapter), heartbeating the
|
|
70
70
|
* lease throughout
|
|
71
71
|
* 5. redact + upload the transcript, map the exit condition, complete the
|
|
@@ -150,6 +150,8 @@ async function runClaimedSession(deps, claimed, sessionId, lifecycle) {
|
|
|
150
150
|
// GitWorktree.cleanup removes that session-marked entry before teardown.
|
|
151
151
|
// Docker env injection remains tmpfs-backed (sandbox.cts injectEnv).
|
|
152
152
|
const registry = new redact_cjs_1.SecretRegistry();
|
|
153
|
+
// Lease MCP and managed Git refresh can expose this value in CLI errors.
|
|
154
|
+
registry.register(api.getToken());
|
|
153
155
|
let secrets = {};
|
|
154
156
|
let managedGithubCredential = false;
|
|
155
157
|
const secretNames = bundle.secret_manifest.map((s) => s.name);
|
|
@@ -356,23 +358,25 @@ async function runClaimedSession(deps, claimed, sessionId, lifecycle) {
|
|
|
356
358
|
machineToken: api.getToken() ?? "",
|
|
357
359
|
leaseId,
|
|
358
360
|
};
|
|
359
|
-
const mcpConfig = runtime === "opencode"
|
|
361
|
+
const mcpConfig = (runtime === "opencode" || runtime === "antigravity")
|
|
360
362
|
? (0, mcp_config_cjs_1.buildEnvReferencedNavarchMcpConfig)(mcpOptions)
|
|
361
363
|
: { config: (0, mcp_config_cjs_1.buildNavarchMcpConfig)(mcpOptions), env: {} };
|
|
362
364
|
Object.assign(sessionEnv, mcpConfig.env);
|
|
363
365
|
await node_fs_1.promises.writeFile(node_path_1.default.join(workDir, MCP_CONFIG_FILENAME), JSON.stringify(mcpConfig.config, null, 2), "utf8");
|
|
364
366
|
mcpConfigPath = node_path_1.default.join(workDir, MCP_CONFIG_FILENAME);
|
|
365
367
|
}
|
|
368
|
+
if (runtime === "antigravity") {
|
|
369
|
+
promptText += "\n\nNavarch creates a temporary .agents/mcp_config.json solely for this turn. Do not edit, stage, commit or remove that file. It is removed by the runtime before delivery; staging it fails the session.\n";
|
|
370
|
+
}
|
|
366
371
|
// Worktree boundary guard (worktree-guard.cts): several sessions share this
|
|
367
372
|
// machine. Host-mode Claude gets a generated PreToolUse hook; host-mode
|
|
368
373
|
// Codex gets an OS-enforced native permission profile over the same roots;
|
|
369
|
-
//
|
|
374
|
+
// Antigravity and OpenCode fail closed on the
|
|
370
375
|
// guarded host path because its CLI cannot express the full boundary.
|
|
371
376
|
// Docker mode already has a container boundary. NAVARCH_WORKTREE_GUARD=off
|
|
372
377
|
// opts out for any agent.
|
|
373
378
|
let claudeSettingsPath = null;
|
|
374
379
|
let codexGuardArgs;
|
|
375
|
-
let geminiGuardMounts;
|
|
376
380
|
if (config.sandboxMode === "host" && config.worktreeGuard) {
|
|
377
381
|
if (runtime === "claude-code") {
|
|
378
382
|
if (config.claudeExtraArgs.includes("--settings")) {
|
|
@@ -396,15 +400,6 @@ async function runClaimedSession(deps, claimed, sessionId, lifecycle) {
|
|
|
396
400
|
Object.assign(sessionEnv, { TMPDIR: tmpDir, TMP: tmpDir, TEMP: tmpDir });
|
|
397
401
|
}
|
|
398
402
|
}
|
|
399
|
-
else if (runtime === "gemini") {
|
|
400
|
-
geminiGuardMounts = (0, worktree_guard_cjs_1.geminiSandboxMounts)({
|
|
401
|
-
workDir,
|
|
402
|
-
worktreePath: gitWorktree.worktreePath,
|
|
403
|
-
repositoryPath: gitWorktree.repositoryPath,
|
|
404
|
-
workspaceRoot: config.workspaceRoot,
|
|
405
|
-
extraRoots: config.guardExtraRoots,
|
|
406
|
-
});
|
|
407
|
-
}
|
|
408
403
|
}
|
|
409
404
|
// Kept outside the try so the crash path can report the telemetry this
|
|
410
405
|
// session actually produced instead of zeroing it.
|
|
@@ -452,14 +447,22 @@ async function runClaimedSession(deps, claimed, sessionId, lifecycle) {
|
|
|
452
447
|
if (!config.mcpConfigPath && config.sandboxMode === "host"
|
|
453
448
|
&& ["build", "verify", "qa"].includes(task.task_type)
|
|
454
449
|
&& task.required_capabilities?.includes("browser-use")
|
|
455
|
-
&& ["codex", "claude-code", "
|
|
450
|
+
&& ["codex", "claude-code", "antigravity", "opencode", "acp"].includes(runtime)) {
|
|
456
451
|
browserEvidence = await (0, browser_evidence_cjs_1.startBrowserEvidence)({
|
|
457
452
|
apiBase: api.getBaseUrl(), machineToken: api.getToken() ?? "", leaseId,
|
|
458
453
|
isActive: () => !leaseLost && !sessionClosing && !deps.shutdownSignal?.aborted,
|
|
459
454
|
});
|
|
460
455
|
registry.register(browserEvidence.token);
|
|
461
456
|
const mcp = JSON.parse(await node_fs_1.promises.readFile(mcpConfigPath, "utf8"));
|
|
462
|
-
|
|
457
|
+
const browserConfig = structuredClone(browserEvidence.config);
|
|
458
|
+
if (runtime === "antigravity") {
|
|
459
|
+
for (const [index, [header, value]] of Object.entries(browserConfig.headers).entries()) {
|
|
460
|
+
const name = `NAVARCH_AGY_MCP_BROWSER_${index}`;
|
|
461
|
+
sessionEnv[name] = value;
|
|
462
|
+
browserConfig.headers[header] = `{env:${name}}`;
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
mcp.mcpServers.navarch_browser = browserConfig;
|
|
463
466
|
await node_fs_1.promises.writeFile(mcpConfigPath, JSON.stringify(mcp), { mode: 0o600 });
|
|
464
467
|
promptText += "\n\nNavarch supplies capture_visual_evidence through navarch_browser. Use it first for desktop/mobile screenshots of the verified preview URL and source commit. It runs outside the coding-agent sandbox, uploads images as task artifacts, and the runtime attaches them to the delivery PR before completion. Inspect the returned images before accepting the UI. Do not use GitHub's upload UI or add capture tools to the target repository. If the base commit has no runnable UI, report before evidence as not applicable with the base SHA and reason; still capture the current head. For interactive walkthroughs of a service you start on this machine, use navarch_browser's local_browser_open, local_browser_click, local_browser_fill, local_browser_press, local_browser_snapshot, and local_browser_screenshot with http://localhost:<port> or http://127.0.0.1:<port>. That browser also runs outside the sandbox and reaches only this machine's loopback services; prefer it over launching Chromium yourself, which Codex's macOS sandbox kills at startup.\n";
|
|
465
468
|
}
|
|
@@ -499,7 +502,9 @@ async function runClaimedSession(deps, claimed, sessionId, lifecycle) {
|
|
|
499
502
|
for (const denial of sandbox.describe().denials) {
|
|
500
503
|
log.warn(`sandbox policy denial [${denial.policy}/${denial.code}]: ${denial.detail}`);
|
|
501
504
|
}
|
|
502
|
-
await sandbox.injectEnv(
|
|
505
|
+
await sandbox.injectEnv(runtime === "antigravity"
|
|
506
|
+
? Object.fromEntries(Object.entries(sessionEnv).filter(([name]) => !name.startsWith("NAVARCH_OPENCODE_MCP_") && !name.startsWith("NAVARCH_AGY_MCP_")))
|
|
507
|
+
: sessionEnv);
|
|
503
508
|
}
|
|
504
509
|
// The control plane returns the agent type selected for this worker.
|
|
505
510
|
// Any agent type may run any task; capabilities remain the task-level gate.
|
|
@@ -527,6 +532,7 @@ async function runClaimedSession(deps, claimed, sessionId, lifecycle) {
|
|
|
527
532
|
let completionRemediationRetries = 0;
|
|
528
533
|
let deliveryCorrectionRetries = 0;
|
|
529
534
|
let deliveredPullRequest = false;
|
|
535
|
+
let antigravityResume;
|
|
530
536
|
while (true) {
|
|
531
537
|
// Guidance can arrive while the worktree/sandbox is being prepared.
|
|
532
538
|
// It is already included in deliveredGuidance, so clear the pending
|
|
@@ -565,7 +571,8 @@ async function runClaimedSession(deps, claimed, sessionId, lifecycle) {
|
|
|
565
571
|
env: sessionEnv,
|
|
566
572
|
settingsPath: claudeSettingsPath,
|
|
567
573
|
codexGuardArgs,
|
|
568
|
-
|
|
574
|
+
antigravityHostGuard: runtime === "antigravity" && config.sandboxMode === "host" && config.worktreeGuard,
|
|
575
|
+
antigravityResume,
|
|
569
576
|
opencodeHostGuard: runtime === "opencode" && config.sandboxMode === "host" && config.worktreeGuard,
|
|
570
577
|
// ACP requires an absolute session cwd. Docker mounts the worktree at
|
|
571
578
|
// this same absolute path, while the other Docker adapters ignore cwd.
|
|
@@ -588,6 +595,7 @@ async function runClaimedSession(deps, claimed, sessionId, lifecycle) {
|
|
|
588
595
|
execution.model = turnResult.model;
|
|
589
596
|
executionReport = { ...executionReport, model: turnResult.model };
|
|
590
597
|
}
|
|
598
|
+
antigravityResume = turnResult.antigravityResume;
|
|
591
599
|
attempts.push(turnResult);
|
|
592
600
|
liveStdout = "";
|
|
593
601
|
liveTranscript.markDirty();
|
|
@@ -877,7 +885,8 @@ async function runClaimedSession(deps, claimed, sessionId, lifecycle) {
|
|
|
877
885
|
failure_summary: failureSummary,
|
|
878
886
|
failed_before_start: !adapterStarted,
|
|
879
887
|
evidence_urls: lastCompletion?.evidence_urls ?? [],
|
|
880
|
-
|
|
888
|
+
// Once the adapter has started, absent accounting is unknown, not free.
|
|
889
|
+
cost: lastCompletion?.cost ?? (adapterStarted ? {} : { tokens_in: 0, tokens_out: 0, cost_usd: 0 }),
|
|
881
890
|
...(crashTranscriptUrl ? { transcript_url: crashTranscriptUrl } : {}),
|
|
882
891
|
exit_status: "crashed",
|
|
883
892
|
agent_type: runtime,
|
|
@@ -928,6 +937,8 @@ function adapterCommand(config, runtime) {
|
|
|
928
937
|
return { bin: config.codexBin, extraArgs: config.codexExtraArgs };
|
|
929
938
|
case "gemini":
|
|
930
939
|
return { bin: config.geminiBin, extraArgs: config.geminiExtraArgs };
|
|
940
|
+
case "antigravity":
|
|
941
|
+
return { bin: config.antigravityBin, extraArgs: config.antigravityExtraArgs };
|
|
931
942
|
case "opencode":
|
|
932
943
|
return { bin: config.opencodeBin, extraArgs: config.opencodeExtraArgs };
|
|
933
944
|
case "acp":
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sagentlab/navarch-runtime",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.111",
|
|
4
4
|
"description": "Navarch machine-side session manager: claims delivery tasks and runs them through local coding agents.",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"license": "MIT",
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"ai-agent",
|
|
16
16
|
"claude-code",
|
|
17
17
|
"codex",
|
|
18
|
-
"
|
|
18
|
+
"antigravity-cli",
|
|
19
19
|
"opencode",
|
|
20
20
|
"agent-client-protocol",
|
|
21
21
|
"deepseek-harness",
|