@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.
@@ -2,329 +2,14 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.geminiAdapter = void 0;
4
4
  exports.runGeminiAdapter = runGeminiAdapter;
5
- const node_child_process_1 = require("node:child_process");
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
- ...result,
221
- ...(tokensIn !== undefined ? { tokensIn } : {}),
222
- ...(tokensOut !== undefined ? { tokensOut } : {}),
223
- ...(reportText.trim() ? { reportText: reportText.trim() } : {}),
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
- function nonNegativeMetric(value) {
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 };
@@ -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":
@@ -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
- "gemini",
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 !== "opencode" || !opencodeBlockedOnHost(sandboxMode, worktreeGuard));
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({
@@ -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 `codex login`), then resume the agent.";
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, Gemini, or OpenCode;
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
- // Gemini gets a native sandbox mount list; OpenCode fails closed on the
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", "gemini", "opencode", "acp"].includes(runtime)) {
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
- mcp.mcpServers.navarch_browser = browserEvidence.config;
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(sessionEnv);
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
- geminiSandboxMounts: geminiGuardMounts,
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
- cost: lastCompletion?.cost ?? { tokens_in: 0, tokens_out: 0, cost_usd: 0 },
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.107",
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
- "gemini-cli",
18
+ "antigravity-cli",
19
19
  "opencode",
20
20
  "agent-client-protocol",
21
21
  "deepseek-harness",