@sagentlab/navarch-runtime 0.1.106 → 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.
@@ -0,0 +1,348 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.antigravityAdapter = exports.ANTIGRAVITY_PROCESS_WRAPPER = void 0;
7
+ exports.runAntigravityAdapter = runAntigravityAdapter;
8
+ exports.normalizeAntigravityOutput = normalizeAntigravityOutput;
9
+ const node_child_process_1 = require("node:child_process");
10
+ const node_path_1 = __importDefault(require("node:path"));
11
+ const node_string_decoder_1 = require("node:string_decoder");
12
+ const config_cjs_1 = require("../config.cjs");
13
+ const kill_agent_cjs_1 = require("../kill-agent.cjs");
14
+ const antigravity_mcp_cjs_1 = require("./antigravity-mcp.cjs");
15
+ /** Documented agy 1.3.1 contract, not Gemini CLI's similarly named format.
16
+ * https://antigravity.google/docs/cli/headless/
17
+ * https://antigravity.google/docs/changelog (1.1.27, 1.1.28, 1.2.6, 1.2.10)
18
+ * One stdin user event + EOF per process; later lease-local turns may resume
19
+ * only the exact validated AG conversation. No interactive approval protocol.
20
+ */
21
+ async function runAntigravityAdapter(options) {
22
+ if (options.signal?.aborted)
23
+ return failure("", { killedByLeaseLoss: true, exitCode: null });
24
+ if (options.antigravityHostGuard && !options.dockerExec)
25
+ return failure(config_cjs_1.ANTIGRAVITY_HOST_GUARD_MESSAGE);
26
+ if (!options.cwd || !node_path_1.default.isAbsolute(options.cwd))
27
+ return failure("Antigravity requires an absolute session working directory.");
28
+ const args = antigravityArgs(options);
29
+ if (typeof args === "string")
30
+ return failure(args);
31
+ const started = performance.now();
32
+ // --version is local and does not start a model turn or sign in. Require the
33
+ // verified contract rather than inheriting older silent-success behavior.
34
+ const version = await runProcess({ ...options, timeoutMs: options.timeoutMs > 0 ? Math.min(options.timeoutMs, 10_000) : 10_000, env: agentEnvironment(options.env), onOutput: undefined, onActivity: undefined }, ["--version"], "");
35
+ if (version.exitCode !== 0 || version.timedOut || version.killedByLeaseLoss)
36
+ return version;
37
+ const match = /\b(\d+)\.(\d+)\.(\d+)\b/.exec(version.stdout);
38
+ const current = match ? match.slice(1).map(Number) : [];
39
+ if (current.length !== 3 || (current[0] < 1) || (current[0] === 1 && (current[1] < 3 || (current[1] === 3 && current[2] < 1)))) {
40
+ return failure("Antigravity CLI 1.3.1 or later is required. Verify the official agy installation with agy --version.");
41
+ }
42
+ if (options.signal?.aborted)
43
+ return failure("", { killedByLeaseLoss: true, exitCode: null });
44
+ const remaining = options.timeoutMs > 0 ? options.timeoutMs - (performance.now() - started) : 0;
45
+ if (options.timeoutMs > 0 && remaining <= 0)
46
+ return failure("Antigravity startup exceeded the session deadline.", { timedOut: true, exitCode: null });
47
+ let mcp;
48
+ let raw = failure("Antigravity did not start.");
49
+ try {
50
+ mcp = await (0, antigravity_mcp_cjs_1.prepareAntigravityMcp)(options);
51
+ const turnRemaining = options.timeoutMs > 0 ? options.timeoutMs - (performance.now() - started) : 0;
52
+ if (options.timeoutMs > 0 && turnRemaining <= 0)
53
+ raw = failure("Antigravity setup exceeded the session deadline.", { timedOut: true, exitCode: null });
54
+ else if (options.signal?.aborted)
55
+ raw = failure("", { killedByLeaseLoss: true, exitCode: null });
56
+ else
57
+ raw = await runProcess({ ...options, timeoutMs: turnRemaining,
58
+ env: agentEnvironment(options.env),
59
+ }, args, `${JSON.stringify({ event: "user", message: { content: options.prompt } })}\n`);
60
+ }
61
+ catch {
62
+ // Configuration and network errors can contain bearer values. Diagnostics
63
+ // identify the phase only; source values are never copied into a report.
64
+ raw = failure("Antigravity MCP setup failed. The workspace must allow a new .agents/mcp_config.json; existing files and symlinks are never overwritten, and a repository-local .gitignore must ignore .agents/mcp_config.json. Check the lease MCP configuration and use an isolated session directory.");
65
+ }
66
+ finally {
67
+ if (mcp) {
68
+ try {
69
+ await mcp.close();
70
+ }
71
+ catch {
72
+ raw = { ...raw, exitCode: raw.exitCode === 0 ? 1 : raw.exitCode, stderr: `${raw.stderr}\nAntigravity MCP cleanup could not be verified; session failed closed.` };
73
+ }
74
+ }
75
+ }
76
+ if (mcp?.failed()) {
77
+ raw = { ...raw, exitCode: 1, stderr: `${raw.stderr}\nAntigravity lease MCP relay failed.` };
78
+ }
79
+ return normalizeAntigravityOutput(raw, options);
80
+ }
81
+ function agentEnvironment(env) {
82
+ const out = {};
83
+ for (const [name, value] of Object.entries(env)) {
84
+ if (typeof value === "string" && !name.startsWith("NAVARCH_OPENCODE_MCP_") && !name.startsWith("NAVARCH_AGY_MCP_") && !["NAVARCH_MACHINE_TOKEN", "NAVARCH_ENROLLMENT_SECRET", "NAVARCH_ENROLLMENT_TOKEN"].includes(name))
85
+ out[name] = value;
86
+ }
87
+ return out;
88
+ }
89
+ function failure(stderr, overrides = {}) {
90
+ return { exitCode: 1, timedOut: false, killedByLeaseLoss: false, stdout: "", stderr, ...overrides };
91
+ }
92
+ /** Extra args are deliberately bounded: no prompt/protocol/timeout/session overrides. */
93
+ function antigravityArgs(options) {
94
+ const args = ["--input-format", "stream-json", "--output-format", "stream-json"];
95
+ const valueFlags = new Set(["--agent", "--model", "--effort"]);
96
+ const boolFlags = new Set(["--sandbox", "--dangerously-skip-permissions"]);
97
+ const values = new Map();
98
+ for (let i = 0; i < options.extraArgs.length; i++) {
99
+ const token = options.extraArgs[i];
100
+ const eq = token.indexOf("=");
101
+ const name = eq >= 0 ? token.slice(0, eq) : token;
102
+ if (valueFlags.has(name)) {
103
+ const value = eq >= 0 ? token.slice(eq + 1) : options.extraArgs[++i];
104
+ if (!value || value.startsWith("-") || values.has(name))
105
+ return "Antigravity extra arguments contain a missing or duplicate value.";
106
+ values.set(name, value);
107
+ }
108
+ else if (boolFlags.has(name) && eq < 0) {
109
+ if (args.includes(name))
110
+ return "Antigravity extra arguments contain a duplicate flag.";
111
+ args.push(name);
112
+ }
113
+ else {
114
+ return "Unsupported Antigravity extra argument. Only --agent, --model, --effort, --sandbox and --dangerously-skip-permissions are allowed; Navarch owns input/output, timeout and conversation identity.";
115
+ }
116
+ }
117
+ const model = options.model && options.model !== "default" ? options.model : values.get("--model");
118
+ if (values.has("--model") && options.model && options.model !== "default" && values.get("--model") !== options.model)
119
+ return "Antigravity model arguments conflict with the selected execution model.";
120
+ if (model)
121
+ values.set("--model", model);
122
+ const effort = options.reasoningEffort ?? values.get("--effort");
123
+ if (effort) {
124
+ if (!["low", "medium", "high"].includes(effort))
125
+ return "Antigravity supports low, medium or high reasoning effort only.";
126
+ if (values.has("--effort") && values.get("--effort") !== effort)
127
+ return "Antigravity reasoning effort arguments conflict.";
128
+ values.set("--effort", effort);
129
+ }
130
+ for (const [name, value] of values)
131
+ args.push(name, value);
132
+ const resume = options.antigravityResume;
133
+ if (resume) {
134
+ if (resume.provider !== "antigravity" || !validId(resume.conversationId) || resume.cwd !== node_path_1.default.resolve(options.cwd) || resume.model !== (model ?? "default") || !metric(resume.numTurns) || resume.numTurns < 1) {
135
+ return "Antigravity resume identity does not match this provider, workspace and model. Start a new session explicitly.";
136
+ }
137
+ args.push("--conversation", resume.conversationId);
138
+ }
139
+ return args;
140
+ }
141
+ function object(value) {
142
+ return typeof value === "object" && value !== null && !Array.isArray(value);
143
+ }
144
+ function validId(value) {
145
+ return typeof value === "string" && /^[A-Za-z0-9][A-Za-z0-9_-]{0,199}$/.test(value);
146
+ }
147
+ function metric(value) {
148
+ return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
149
+ }
150
+ function hasDeniedActions(value) {
151
+ // The changelog specifies the field but not an item schema. Empty array is
152
+ // the only explicit no-denial value; unknown shapes fail closed.
153
+ return "denied_actions" in value && (!Array.isArray(value.denied_actions) || value.denied_actions.length > 0);
154
+ }
155
+ /** Require one complete, well-formed turn. OS exit 0 alone never means success. */
156
+ function normalizeAntigravityOutput(raw, options) {
157
+ if (raw.killedByLeaseLoss || raw.timedOut || raw.exitCode === null)
158
+ return raw;
159
+ let id;
160
+ let result;
161
+ let invalid = false;
162
+ let denied = false;
163
+ for (const line of raw.stdout.split(/\r?\n/).filter((line) => line.trim())) {
164
+ let event;
165
+ try {
166
+ event = JSON.parse(line);
167
+ }
168
+ catch {
169
+ invalid = true;
170
+ continue;
171
+ }
172
+ if (!object(event) || result) {
173
+ invalid = true;
174
+ continue;
175
+ }
176
+ denied ||= hasDeniedActions(event);
177
+ if (event.event === "init") {
178
+ if (id || !validId(event.conversation_id) || !object(event.init))
179
+ invalid = true;
180
+ else
181
+ id = event.conversation_id;
182
+ }
183
+ else if (event.event === "step_update") {
184
+ const step = event.step_update;
185
+ if (!id || !object(step) || step.conversation_id !== id || !metric(step.step_index) || !["ACTIVE", "DONE"].includes(String(step.state)) || typeof step.step_type !== "string" || ("text_delta" in step && typeof step.text_delta !== "string"))
186
+ invalid = true;
187
+ if (object(step)) {
188
+ denied ||= hasDeniedActions(step);
189
+ if (object(step.tool_info) && object(step.tool_info.error)) {
190
+ const error = step.tool_info.error;
191
+ denied ||= /permission|denied|approval|refused/i.test(`${String(error.type ?? "")} ${String(error.message ?? "")}`);
192
+ }
193
+ }
194
+ }
195
+ else if (event.event === "result" && object(event.result)) {
196
+ result = event.result;
197
+ denied ||= hasDeniedActions(result);
198
+ }
199
+ else
200
+ invalid = true;
201
+ }
202
+ const diagnosticFailure = /^\s*AGY_ERROR\s*:/m.test(raw.stderr)
203
+ || /(?:soft[- ]denied|permission[^\n]*(?:denied|required)|(?:denied|refused)[^\n]*(?:action|tool)|not permitted|print-timeout|(?:timeout|timed out)[^\n]*(?:partial|response)|partial[^\n]*(?:timeout|timed out))/i.test(raw.stderr);
204
+ const previous = options.antigravityResume;
205
+ if (!result || !id || result.conversation_id !== id || typeof result.response !== "string" || !metric(result.num_turns) || result.num_turns < 1)
206
+ invalid = true;
207
+ if (result && "error" in result && typeof result.error !== "string")
208
+ invalid = true;
209
+ if (previous && (id !== previous.conversationId || result?.num_turns !== previous.numTurns + 1))
210
+ invalid = true;
211
+ if (!previous && result?.num_turns !== 1)
212
+ invalid = true;
213
+ const out = { ...raw };
214
+ const usage = result && object(result.usage) ? result.usage : undefined;
215
+ const totalIn = usage && metric(usage.input_tokens) ? usage.input_tokens : undefined;
216
+ const totalOut = usage && metric(usage.output_tokens) ? usage.output_tokens : undefined;
217
+ // Resume counters are cumulative. Never sum the whole historical bill again.
218
+ if (!invalid) {
219
+ if (totalIn !== undefined && (!previous || previous.tokensIn !== undefined)) {
220
+ const delta = totalIn - (previous?.tokensIn ?? 0);
221
+ if (delta < 0)
222
+ invalid = true;
223
+ else
224
+ out.tokensIn = delta;
225
+ }
226
+ if (totalOut !== undefined && (!previous || previous.tokensOut !== undefined)) {
227
+ const delta = totalOut - (previous?.tokensOut ?? 0);
228
+ if (delta < 0)
229
+ invalid = true;
230
+ else
231
+ out.tokensOut = delta;
232
+ }
233
+ }
234
+ // Vendor docs do not establish cached-token inclusion consistently. Keep
235
+ // the raw input/output measures; neither fabricate USD nor a cache split.
236
+ const succeeded = raw.exitCode === 0 && !invalid && !denied && !diagnosticFailure && result?.status === "SUCCESS" && !(typeof result.error === "string" && result.error);
237
+ if (!succeeded) {
238
+ out.exitCode = raw.exitCode === 0 ? 1 : raw.exitCode;
239
+ const detail = denied ? "A required action was denied by Antigravity permission policy."
240
+ : invalid ? "Antigravity returned an invalid, incomplete or mismatched event stream."
241
+ : diagnosticFailure ? "Antigravity reported a CLI error, permission denial or partial timeout."
242
+ : `Antigravity did not complete successfully (status ${typeof result?.status === "string" ? result.status : "missing"}).`;
243
+ out.stderr = `${raw.stderr}\n${detail}`.trim();
244
+ // Only structured CLI errors and CLI stderr participate, never assistant prose.
245
+ const cliError = `${typeof result?.error === "string" ? result.error : ""}\n${raw.stderr}`;
246
+ if (/RESOURCE_EXHAUSTED|\brate[ _-]+limit|too many requests/i.test(cliError)) {
247
+ out.capacityError = "Antigravity rate limit or provider capacity exhausted; retry after the machine cooldown.";
248
+ }
249
+ if (/authentication required|not logged in|unauthenticated|please.*(?:login|sign in)/i.test(cliError)) {
250
+ out.authError = "Antigravity authentication is required. Authenticate agy interactively on this machine; Gemini CLI credentials are not reused.";
251
+ }
252
+ return out;
253
+ }
254
+ out.reportText = String(result.response).trim();
255
+ const modelArg = options.extraArgs.findIndex((arg) => arg === "--model");
256
+ const extraModel = modelArg >= 0 ? options.extraArgs[modelArg + 1] : options.extraArgs.find((arg) => arg.startsWith("--model="))?.slice(8);
257
+ out.antigravityResume = {
258
+ provider: "antigravity", conversationId: id, cwd: node_path_1.default.resolve(options.cwd),
259
+ model: options.model && options.model !== "default" ? options.model : extraModel ?? "default",
260
+ numTurns: result.num_turns, tokensIn: totalIn, tokensOut: totalOut,
261
+ };
262
+ return out;
263
+ }
264
+ async function runProcess(options, args, input) {
265
+ if (options.dockerExec)
266
+ return runDocker(options, args, input);
267
+ return new Promise((resolve) => {
268
+ let stdout = "", stderr = "", timedOut = false, killedByLeaseLoss = false;
269
+ const stdoutDecoder = new node_string_decoder_1.StringDecoder("utf8"), stderrDecoder = new node_string_decoder_1.StringDecoder("utf8");
270
+ const child = (0, node_child_process_1.spawn)(options.bin, args, { cwd: options.cwd, detached: process.platform !== "win32", env: agentEnvironment({ ...process.env, ...options.env }) });
271
+ child.stdin?.on("error", () => undefined);
272
+ child.stdin?.end(input);
273
+ options.onProcessStarted?.(child.pid);
274
+ let escalation;
275
+ const stop = () => {
276
+ (0, kill_agent_cjs_1.killAgent)(child, "SIGINT");
277
+ escalation ??= setTimeout(() => (0, kill_agent_cjs_1.killAgent)(child), 1_000);
278
+ };
279
+ const timer = options.timeoutMs > 0 ? setTimeout(() => { timedOut = true; stop(); }, options.timeoutMs) : undefined;
280
+ const onAbort = () => { killedByLeaseLoss = true; stop(); };
281
+ options.signal?.addEventListener("abort", onAbort, { once: true });
282
+ if (options.signal?.aborted)
283
+ onAbort();
284
+ child.stdout.on("data", (chunk) => { const text = stdoutDecoder.write(chunk); stdout += text; options.onActivity?.(); options.onOutput?.("stdout", text); });
285
+ child.stderr.on("data", (chunk) => { const text = stderrDecoder.write(chunk); stderr += text; options.onActivity?.(); options.onOutput?.("stderr", text); });
286
+ let settled = false;
287
+ const finish = (code) => {
288
+ if (settled)
289
+ return;
290
+ settled = true;
291
+ clearTimeout(timer);
292
+ clearTimeout(escalation);
293
+ options.signal?.removeEventListener("abort", onAbort);
294
+ // Stop the owned process group even when agy exits before its tools.
295
+ (0, kill_agent_cjs_1.killAgent)(child);
296
+ stdout += stdoutDecoder.end();
297
+ stderr += stderrDecoder.end();
298
+ resolve({ exitCode: code, timedOut, killedByLeaseLoss, stdout, stderr });
299
+ };
300
+ child.on("error", () => { stderr += "\nAntigravity process could not be started; check NAVARCH_ANTIGRAVITY_BIN."; finish(null); });
301
+ child.on("exit", () => (0, kill_agent_cjs_1.killAgent)(child));
302
+ child.on("close", finish);
303
+ });
304
+ }
305
+ /** Node is already required by the MCP relay. A separate process group also
306
+ * prevents redirected-stdio background tools outliving a successful Docker turn. */
307
+ exports.ANTIGRAVITY_PROCESS_WRAPPER = `const{spawn}=require('node:child_process');const c=spawn(process.argv[1],process.argv.slice(2),{detached:process.platform!=='win32',stdio:['pipe','inherit','inherit']});let timer;function kill(signal='SIGKILL'){try{if(c.pid&&process.platform!=='win32'){process.kill(-c.pid,signal);return;}}catch{}c.kill(signal);}c.stdin.on('error',()=>{});process.stdin.pipe(c.stdin);for(const signal of ['SIGINT','SIGTERM'])process.on(signal,()=>{kill(signal);timer??=setTimeout(()=>kill(),1000);});c.on('exit',()=>kill());c.on('error',()=>{process.stderr.write('Antigravity process could not start\\n');process.exitCode=1;process.stdin.destroy();});c.on('close',(code)=>{clearTimeout(timer);kill();process.exitCode=code??1;process.stdin.destroy();});`;
308
+ async function runDocker(options, args, input) {
309
+ const { containerName, runner } = options.dockerExec;
310
+ let killedByLeaseLoss = false, timedOut = false;
311
+ const abort = new AbortController();
312
+ let killing;
313
+ let killFailed = false;
314
+ const kill = () => {
315
+ abort.abort();
316
+ killing ??= runner.run("docker", ["kill", containerName], { timeoutMs: 10_000 })
317
+ .then((result) => { if (result.code !== 0)
318
+ killFailed = true; }, () => { killFailed = true; });
319
+ };
320
+ const onAbort = () => { killedByLeaseLoss = true; kill(); };
321
+ options.signal?.addEventListener("abort", onAbort, { once: true });
322
+ const timer = options.timeoutMs > 0 ? setTimeout(() => { timedOut = true; kill(); }, options.timeoutMs) : undefined;
323
+ if (options.signal?.aborted)
324
+ onAbort();
325
+ try {
326
+ if (abort.signal.aborted)
327
+ return failure("", { exitCode: null, timedOut, killedByLeaseLoss });
328
+ const command = `[ -f /tmp/session.env ] && . /tmp/session.env; cd ${quote(options.cwd)} && exec ${["node", "-e", exports.ANTIGRAVITY_PROCESS_WRAPPER, options.bin, ...args].map(quote).join(" ")}`;
329
+ const result = await runner.run("docker", ["exec", "-i", ...Object.keys(options.env).flatMap((name) => ["--env", name]), containerName, "sh", "-c", command], {
330
+ input, env: agentEnvironment({ ...process.env, ...options.env }), signal: abort.signal,
331
+ onActivity: options.onActivity, onStdout: (chunk) => options.onOutput?.("stdout", chunk), onStderr: (chunk) => options.onOutput?.("stderr", chunk),
332
+ });
333
+ await killing;
334
+ return { exitCode: result.code, timedOut, killedByLeaseLoss, stdout: result.stdout,
335
+ stderr: result.stderr + (killFailed ? "\nAntigravity container termination could not be verified." : "") };
336
+ }
337
+ catch {
338
+ await killing;
339
+ return failure(killFailed ? "Antigravity Docker process failed; container termination could not be verified." : "Antigravity Docker process failed.", { exitCode: null, timedOut, killedByLeaseLoss });
340
+ }
341
+ finally {
342
+ clearTimeout(timer);
343
+ options.signal?.removeEventListener("abort", onAbort);
344
+ await killing;
345
+ }
346
+ }
347
+ function quote(value) { return `'${value.replace(/'/g, `'\\''`)}'`; }
348
+ exports.antigravityAdapter = { agentType: "antigravity", run: runAntigravityAdapter };
@@ -2,292 +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 Linux's
21
- * 128 KiB per-string limit. Gemini also caps stdin at 8 MiB.
22
- */
5
+ /** Tombstone for stale claims. Provider IDs and credentials are never aliased. */
23
6
  async function runGeminiAdapter(options) {
24
- if (options.signal?.aborted) {
25
- return { exitCode: null, timedOut: false, killedByLeaseLoss: true, stdout: "", stderr: "" };
26
- }
27
- const promptBytes = Buffer.byteLength(options.prompt, "utf8");
28
- // Gemini's readStdin() silently truncates at 8 MiB. Reject before any
29
- // settings writes or process launch rather than execute a partial task.
30
- if (promptBytes > 8 * 1024 * 1024) {
31
- return inputFailure(`Gemini CLI stdin is limited to 8 MiB; the task prompt is ${promptBytes} bytes. Reduce the context bundle or select another runtime.`);
32
- }
33
- const args = [];
34
- const env = { ...options.env };
35
- if (!hasArg(options.extraArgs, "--output-format", "-o")) {
36
- args.push("--output-format", "stream-json");
37
- }
38
- if (!hasArg(options.extraArgs, "--skip-trust")) {
39
- args.push("--skip-trust");
40
- }
41
- if (!hasExplicitApprovalMode(options.extraArgs)) {
42
- // Navarch sessions have no interactive operator. Host sessions still use
43
- // Gemini's sandbox below when the worktree guard is enabled.
44
- args.push("--approval-mode", "yolo");
45
- }
46
- const sandboxMounts = [...(options.geminiSandboxMounts ?? [])];
47
- if (options.mcpConfigPath) {
48
- const mcp = await prepareGeminiMcpSettings(options.mcpConfigPath, env);
49
- env.GEMINI_CLI_SYSTEM_SETTINGS_PATH = mcp.settingsPath;
50
- sandboxMounts.push(`${mcp.settingsPath}:${mcp.settingsPath}:ro`);
51
- if (mcp.serverNames.length > 0 && !hasArg(options.extraArgs, "--allowed-mcp-server-names")) {
52
- args.push("--allowed-mcp-server-names", mcp.serverNames.join(","));
53
- }
54
- }
55
- if (!hasArg(options.extraArgs, "--sandbox", "-s")) {
56
- if (options.geminiSandboxMounts) {
57
- args.push("--sandbox");
58
- env.SANDBOX_MOUNTS = sandboxMounts.join(",");
59
- }
60
- else {
61
- // YOLO mode enables Gemini's own sandbox implicitly. Disable that
62
- // nested sandbox when Navarch already supplied Docker isolation, or
63
- // when an operator explicitly disabled the host worktree guard.
64
- args.push("--sandbox=false");
65
- }
66
- }
67
- args.push(...options.extraArgs);
68
- if (options.model)
69
- args.push("--model", options.model);
70
- // Gemini's sandbox launcher reads stdin and injects it back into --prompt
71
- // for its child process (packages/cli/src/gemini.tsx injectStdinIntoArgs).
72
- // Moving our first spawn's prompt to stdin cannot fix that second spawn.
73
- // Check the effective flag, including operator overrides, without disabling
74
- // the worktree guard. Even on macOS a Docker sandbox may launch on Linux.
75
- if (geminiSandboxEnabled(args) && promptBytes >= 128 * 1024) {
76
- 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.`);
77
- }
78
- const runOptions = { ...options, env };
79
- const raw = runOptions.dockerExec
80
- ? await runViaDocker(runOptions, args)
81
- : await runOnHost(runOptions, args);
82
- return attachGeminiOutput(raw);
83
- }
84
- function inputFailure(stderr) {
85
- return { exitCode: 1, timedOut: false, killedByLeaseLoss: false, stdout: "", stderr };
86
- }
87
- function geminiSandboxEnabled(args) {
88
- // There is always a generated --sandbox flag unless extraArgs supplies one.
89
- // Like yargs, later explicit values override earlier ones.
90
- let enabled = false;
91
- for (let index = 0; index < args.length; index++) {
92
- const arg = args[index];
93
- if (arg === "--sandbox" || arg === "-s") {
94
- enabled = args[index + 1] !== "false";
95
- }
96
- else if (arg.startsWith("--sandbox=") || arg.startsWith("-s=")) {
97
- enabled = arg.slice(arg.indexOf("=") + 1) !== "false";
98
- }
99
- else if (arg === "--no-sandbox") {
100
- enabled = false;
101
- }
102
- }
103
- return enabled;
104
- }
105
- function hasArg(args, ...flags) {
106
- return args.some((arg) => flags.some((flag) => arg === flag || arg.startsWith(`${flag}=`)));
107
- }
108
- function hasExplicitApprovalMode(args) {
109
- return hasArg(args, "--approval-mode", "--yolo", "-y");
110
- }
111
- async function prepareGeminiMcpSettings(mcpConfigPath, env) {
112
- const parsed = JSON.parse(await node_fs_1.promises.readFile(mcpConfigPath, "utf8"));
113
- const mcpServers = {};
114
- for (const [name, server] of Object.entries(parsed.mcpServers ?? {})) {
115
- const httpUrl = server.httpUrl ?? (server.type === "http" ? server.url : undefined);
116
- if (!httpUrl && !server.url)
117
- continue;
118
- const headers = {};
119
- let headerIndex = 0;
120
- for (const [header, value] of Object.entries(server.headers ?? {})) {
121
- const envName = `NAVARCH_GEMINI_MCP_${safeEnvSegment(name)}_${headerIndex++}`;
122
- env[envName] = value;
123
- headers[header] = `\${${envName}}`;
124
- }
125
- mcpServers[name] = {
126
- ...(httpUrl ? { httpUrl } : { url: server.url }),
127
- ...(Object.keys(headers).length > 0 ? { headers } : {}),
128
- };
129
- }
130
- const settingsPath = `${mcpConfigPath}.gemini-settings.json`;
131
- await node_fs_1.promises.writeFile(settingsPath, JSON.stringify({ mcpServers }, null, 2), { mode: 0o600 });
132
- await node_fs_1.promises.chmod(settingsPath, 0o600);
133
- return { settingsPath, serverNames: Object.keys(mcpServers) };
134
- }
135
- function safeEnvSegment(value) {
136
- return value.toUpperCase().replace(/[^A-Z0-9_]/g, "_");
137
- }
138
- function parseGeminiStream(stdout) {
139
- const events = [];
140
- for (const line of stdout.split(/\r?\n/)) {
141
- if (!line.trim())
142
- continue;
143
- try {
144
- const event = JSON.parse(line);
145
- if (event && typeof event === "object")
146
- events.push(event);
147
- }
148
- catch {
149
- // Parser drift must not turn a successfully completed CLI run into a
150
- // runtime crash. Unknown lines remain available in the transcript.
151
- }
152
- }
153
- return events;
154
- }
155
- function attachGeminiOutput(result) {
156
- const events = parseGeminiStream(result.stdout);
157
- if (events.length === 0)
158
- return result;
159
- let reportText = "";
160
- let tokensIn;
161
- let tokensOut;
162
- for (const event of events) {
163
- if (event.type === "tool_use") {
164
- // Gemini emits pre-tool assistant text as message deltas. Only retain
165
- // the answer produced after the last tool call as the completion report.
166
- reportText = "";
167
- }
168
- else if (event.type === "message" &&
169
- event.role === "assistant" &&
170
- typeof event.content === "string") {
171
- reportText = event.delta === false ? event.content : reportText + event.content;
172
- }
173
- else if (event.type === "result" && event.stats) {
174
- const reportedInput = nonNegativeMetric(event.stats.input_tokens);
175
- const reportedOutput = nonNegativeMetric(event.stats.output_tokens);
176
- if (reportedInput !== undefined)
177
- tokensIn = reportedInput;
178
- if (reportedOutput !== undefined)
179
- tokensOut = reportedOutput;
180
- }
181
- }
182
7
  return {
183
- ...result,
184
- ...(tokensIn !== undefined ? { tokensIn } : {}),
185
- ...(tokensOut !== undefined ? { tokensOut } : {}),
186
- ...(reportText.trim() ? { reportText: reportText.trim() } : {}),
187
- };
188
- }
189
- function nonNegativeMetric(value) {
190
- return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : undefined;
191
- }
192
- async function runOnHost(options, args) {
193
- return new Promise((resolve) => {
194
- let stdout = "";
195
- let stderr = "";
196
- let timedOut = false;
197
- let killedByLeaseLoss = false;
198
- const child = (0, node_child_process_1.spawn)(options.bin, args, {
199
- detached: process.platform !== "win32",
200
- cwd: options.cwd,
201
- env: { ...process.env, ...options.env },
202
- });
203
- // Ignore EPIPE from a child that exits before reading; `close` reports it.
204
- child.stdin?.on("error", () => undefined);
205
- child.stdin?.end(options.prompt);
206
- options.onProcessStarted?.(child.pid);
207
- const timer = options.timeoutMs > 0 ? setTimeout(() => {
208
- timedOut = true;
209
- (0, kill_agent_cjs_1.killAgent)(child);
210
- }, options.timeoutMs) : undefined;
211
- const onAbort = () => {
212
- killedByLeaseLoss = true;
213
- (0, kill_agent_cjs_1.killAgent)(child);
214
- };
215
- options.signal?.addEventListener("abort", onAbort, { once: true });
216
- if (options.signal?.aborted)
217
- onAbort();
218
- child.stdout.on("data", (data) => {
219
- options.onActivity?.();
220
- const text = data.toString();
221
- stdout += text;
222
- options.onOutput?.("stdout", text);
223
- });
224
- child.stderr.on("data", (data) => {
225
- options.onActivity?.();
226
- const text = data.toString();
227
- stderr += text;
228
- options.onOutput?.("stderr", text);
229
- });
230
- child.on("error", (err) => {
231
- clearTimeout(timer);
232
- options.signal?.removeEventListener("abort", onAbort);
233
- stderr += `\n${String(err)}`;
234
- resolve({ exitCode: null, timedOut, killedByLeaseLoss, stdout, stderr });
235
- });
236
- child.on("close", (code) => {
237
- clearTimeout(timer);
238
- options.signal?.removeEventListener("abort", onAbort);
239
- resolve({ exitCode: code, timedOut, killedByLeaseLoss, stdout, stderr });
240
- });
241
- });
242
- }
243
- async function runViaDocker(options, args) {
244
- const { containerName, runner } = options.dockerExec;
245
- const command = `[ -f /tmp/session.env ] && . /tmp/session.env; cd repo 2>/dev/null; ${[
246
- options.bin,
247
- ...args,
248
- ].map(shellQuote).join(" ")}`;
249
- let killedByLeaseLoss = false;
250
- const onAbort = () => {
251
- killedByLeaseLoss = true;
252
- runner.run("docker", ["kill", containerName], { timeoutMs: 10_000 }).catch(() => undefined);
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.",
253
13
  };
254
- options.signal?.addEventListener("abort", onAbort, { once: true });
255
- if (options.signal?.aborted)
256
- onAbort();
257
- try {
258
- const forwardedEnv = Object.keys(options.env).flatMap((name) => ["--env", name]);
259
- const result = await runner.run("docker", ["exec", "-i", ...forwardedEnv, containerName, "sh", "-c", command], { input: options.prompt,
260
- timeoutMs: options.timeoutMs,
261
- onActivity: options.onActivity,
262
- onStdout: (chunk) => options.onOutput?.("stdout", chunk),
263
- onStderr: (chunk) => options.onOutput?.("stderr", chunk),
264
- signal: options.signal,
265
- env: { ...process.env, ...options.env }, });
266
- return {
267
- exitCode: result.code,
268
- timedOut: false,
269
- killedByLeaseLoss,
270
- stdout: result.stdout,
271
- stderr: result.stderr,
272
- };
273
- }
274
- catch (err) {
275
- return {
276
- exitCode: null,
277
- timedOut: false,
278
- killedByLeaseLoss,
279
- stdout: "",
280
- stderr: String(err),
281
- };
282
- }
283
- finally {
284
- options.signal?.removeEventListener("abort", onAbort);
285
- }
286
- }
287
- function shellQuote(value) {
288
- return `'${value.replace(/'/g, `'\\''`)}'`;
289
14
  }
290
- exports.geminiAdapter = {
291
- agentType: "gemini",
292
- run: runGeminiAdapter,
293
- };
15
+ exports.geminiAdapter = { agentType: "gemini", run: runGeminiAdapter };