@sagentlab/navarch-runtime 0.1.107 → 0.1.112
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/conversation/loop.cjs +2 -2
- package/dist/conversation/turn-runner.cjs +3 -1
- package/dist/conversation/workspace.cjs +26 -4
- 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({
|
|
@@ -209,9 +209,9 @@ class ConversationLoop {
|
|
|
209
209
|
...(shutdownSignal ? { signal: shutdownSignal } : {}),
|
|
210
210
|
...this.options.turnOverrides,
|
|
211
211
|
log: this.log,
|
|
212
|
-
prepareWorkspace: async ({ conversationId, repositoryUrl, mode, signal }) => {
|
|
212
|
+
prepareWorkspace: async ({ conversationId, repositoryUrl, workingBranch, mode, signal }) => {
|
|
213
213
|
const workspace = await (0, workspace_cjs_1.prepareConversationWorkspace)({
|
|
214
|
-
workspaceRoot: config.workspaceRoot, conversationId, repositoryUrl, env, signal, mode,
|
|
214
|
+
workspaceRoot: config.workspaceRoot, conversationId, repositoryUrl, workingBranch, env, signal, mode,
|
|
215
215
|
...(this.options.gitRunner ? { runner: this.options.gitRunner } : {}),
|
|
216
216
|
});
|
|
217
217
|
const guard = await (0, worktree_guard_cjs_1.prepareWorktreeGuard)({
|
|
@@ -298,7 +298,9 @@ async function runConversationTurn(deps, conversationId, turnId) {
|
|
|
298
298
|
if (snapshot.mode === "workspace" && !deps.workspaceEnabled)
|
|
299
299
|
throw new Error("Workspace chat is disabled on this machine");
|
|
300
300
|
prompt = (0, prompt_cjs_1.buildConversationPrompt)(snapshot, journal.turnId);
|
|
301
|
-
|
|
301
|
+
// Older servers omit the working branch: the checkout stays on the default branch.
|
|
302
|
+
const workingBranch = (0, workspace_cjs_1.validWorkingBranch)(claim.workingBranch);
|
|
303
|
+
prepared = await deps.prepareWorkspace({ conversationId, repositoryUrl, workingBranch, mode: snapshot.mode ?? "read_only", signal: controller.signal });
|
|
302
304
|
}
|
|
303
305
|
catch (error) {
|
|
304
306
|
if (!run.lost)
|
|
@@ -5,6 +5,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.conversationEnv = conversationEnv;
|
|
7
7
|
exports.validRepositoryUrl = validRepositoryUrl;
|
|
8
|
+
exports.validWorkingBranch = validWorkingBranch;
|
|
8
9
|
exports.conversationRoot = conversationRoot;
|
|
9
10
|
exports.prepareConversationWorkspace = prepareConversationWorkspace;
|
|
10
11
|
exports.removeConversationWorkspace = removeConversationWorkspace;
|
|
@@ -50,6 +51,22 @@ function validRepositoryUrl(value) {
|
|
|
50
51
|
return null;
|
|
51
52
|
return `https://github.com/${fullName}.git`;
|
|
52
53
|
}
|
|
54
|
+
/**
|
|
55
|
+
* The project's working branch from the claim, checked as strictly as the
|
|
56
|
+
* hosted path's `buildWorkingBranchCheckoutCommand`
|
|
57
|
+
* (worker/hosted-agent/delivery.ts): a plain ref name that cannot be read as a
|
|
58
|
+
* git option. Absent (an older server, or no working branch) is null; anything
|
|
59
|
+
* else that is not a plain name throws, so the turn never runs on a branch it
|
|
60
|
+
* was not given.
|
|
61
|
+
*/
|
|
62
|
+
function validWorkingBranch(value) {
|
|
63
|
+
if (value === undefined || value === null)
|
|
64
|
+
return null;
|
|
65
|
+
if (typeof value !== "string" || value.length > 255 || !/^[A-Za-z0-9][A-Za-z0-9._/-]*$/.test(value) || value.includes("..")) {
|
|
66
|
+
throw new Error("working branch is not a valid branch name");
|
|
67
|
+
}
|
|
68
|
+
return value;
|
|
69
|
+
}
|
|
53
70
|
function conversationRoot(workspaceRoot, conversationId) {
|
|
54
71
|
return node_path_1.default.join(workspaceRoot, "conversations", (0, journal_cjs_1.assertUuid)(conversationId, "conversation id"));
|
|
55
72
|
}
|
|
@@ -67,6 +84,11 @@ async function prepareConversationWorkspace(options) {
|
|
|
67
84
|
const url = validRepositoryUrl(options.repositoryUrl);
|
|
68
85
|
if (!url)
|
|
69
86
|
throw new Error("repository URL is not a plain https URL");
|
|
87
|
+
const branch = validWorkingBranch(options.workingBranch);
|
|
88
|
+
// The marker records what the checkout was cloned from, so a changed
|
|
89
|
+
// repository or working branch is noticed. Default-branch checkouts keep the
|
|
90
|
+
// bare URL that earlier runtimes wrote.
|
|
91
|
+
const identity = branch ? `${url}\nbranch=${branch}` : url;
|
|
70
92
|
const runner = options.runner ?? sandbox_cjs_1.nodeCommandRunner;
|
|
71
93
|
const root = options.mode === "workspace"
|
|
72
94
|
? node_path_1.default.join(options.workspaceRoot, "chat-workspaces", (0, journal_cjs_1.assertUuid)(options.conversationId, "conversation id"))
|
|
@@ -83,7 +105,7 @@ async function prepareConversationWorkspace(options) {
|
|
|
83
105
|
if (options.mode === "workspace") {
|
|
84
106
|
await node_fs_1.promises.writeFile(node_path_1.default.join(workspace.metaDir, "preserve-workspace"), "Workspace chat: retain until the operator removes it.\n", { mode: 0o600 });
|
|
85
107
|
}
|
|
86
|
-
if (hasCheckout && recorded ===
|
|
108
|
+
if (hasCheckout && recorded === identity)
|
|
87
109
|
return workspace;
|
|
88
110
|
if (options.mode === "workspace") {
|
|
89
111
|
const exists = await node_fs_1.promises.lstat(workspace.checkout).then(() => true, (error) => {
|
|
@@ -94,7 +116,7 @@ async function prepareConversationWorkspace(options) {
|
|
|
94
116
|
if (exists)
|
|
95
117
|
throw new Error("Checkout identity changed; preserving the existing workspace. Start a new conversation.");
|
|
96
118
|
}
|
|
97
|
-
// New conversation, a changed repository URL, or an interrupted clone.
|
|
119
|
+
// New conversation, a changed repository URL or working branch, or an interrupted clone.
|
|
98
120
|
await node_fs_1.promises.rm(workspace.checkout, { recursive: true, force: true });
|
|
99
121
|
const env = {
|
|
100
122
|
...options.env,
|
|
@@ -104,12 +126,12 @@ async function prepareConversationWorkspace(options) {
|
|
|
104
126
|
GIT_CONFIG_KEY_0: "core.hooksPath",
|
|
105
127
|
GIT_CONFIG_VALUE_0: process.platform === "win32" ? "NUL" : "/dev/null",
|
|
106
128
|
};
|
|
107
|
-
const result = await runner.run("git", ["clone", "--quiet", "--filter=blob:none", "--no-tags", "--", url, workspace.checkout], { cwd: root, env, timeoutMs: CLONE_TIMEOUT_MS, signal: options.signal });
|
|
129
|
+
const result = await runner.run("git", ["clone", "--quiet", "--filter=blob:none", "--no-tags", ...(branch ? ["--branch", branch] : []), "--", url, workspace.checkout], { cwd: root, env, timeoutMs: CLONE_TIMEOUT_MS, signal: options.signal });
|
|
108
130
|
if (result.code !== 0) {
|
|
109
131
|
await node_fs_1.promises.rm(workspace.checkout, { recursive: true, force: true }).catch(() => undefined);
|
|
110
132
|
throw new Error(`git clone failed with exit ${result.code}: ${result.stderr.slice(-500)}`);
|
|
111
133
|
}
|
|
112
|
-
await node_fs_1.promises.writeFile(marker,
|
|
134
|
+
await node_fs_1.promises.writeFile(marker, identity, { encoding: "utf8", mode: 0o600 });
|
|
113
135
|
return workspace;
|
|
114
136
|
}
|
|
115
137
|
async function removeConversationWorkspace(workspaceRoot, conversationId) {
|
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`,
|