@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.
@@ -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 };