dsh-agy-link 0.4.26 → 0.4.28

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/dist/index.js CHANGED
@@ -7,7 +7,8 @@ import * as dshLlm from "@deepseek-ai/dsh-llm";
7
7
  import { LlmAdapter, LlmError } from "@deepseek-ai/dsh-llm";
8
8
  import { createHash, randomBytes, randomUUID } from "node:crypto";
9
9
  import { defineTool } from "@deepseek-ai/dsh-tools";
10
- import { mkdir, mkdtemp, readFile, readdir, rm, stat, unlink, writeFile } from "node:fs/promises";
10
+ import { copyFile, mkdir, mkdtemp, readFile, readdir, rm, stat, unlink, writeFile } from "node:fs/promises";
11
+ import { promisify } from "node:util";
11
12
  import { createServer } from "node:http";
12
13
  import { fileURLToPath } from "node:url";
13
14
  //#region \0rolldown/runtime.js
@@ -219,7 +220,7 @@ function asMode(v) {
219
220
  /** Layered config read; cheap enough to call per request (thunk pattern). */
220
221
  function resolveConfig(entry, env = process.env, overrides = readOverrides()) {
221
222
  const base = defaultConfig();
222
- const layers = [entry ?? {}, overrides];
223
+ const layers = [overrides, entry ?? {}];
223
224
  const get = (k) => {
224
225
  for (const l of layers) if (l[k] !== void 0 && l[k] !== null && l[k] !== "") return l[k];
225
226
  };
@@ -379,6 +380,12 @@ function diffConversations(before, dir = defaultConversationsDir()) {
379
380
  }
380
381
  //#endregion
381
382
  //#region src/host/recording.ts
383
+ function classifyToolError(error) {
384
+ if (/\b(no such file|file not found|cannot find (?:the )?file|enoent)\b/i.test(error)) return "missing_file";
385
+ if (/\b(system protection|protected by (?:the )?system|hardcoded.*protection|cannot bypass.*protection)\b/i.test(error)) return "system_protection";
386
+ if (/\b(permission|denied|denial|approval)\b/i.test(error)) return "approval";
387
+ return "tool_error";
388
+ }
382
389
  const waiters = Symbol("waiters");
383
390
  /** One agy run's append-only event log plus its settlement state. */
384
391
  var RunRecording = class {
@@ -389,6 +396,8 @@ var RunRecording = class {
389
396
  resultConversationId = null;
390
397
  lastStepUsageRaw = null;
391
398
  [waiters] = /* @__PURE__ */ new Set();
399
+ /** DB-resolved full tool args keyed by event index (set during span driving). */
400
+ fullArgs = null;
392
401
  /**
393
402
  * Set by the adapter right after spawn. A mid-turn user steer makes DSH
394
403
  * open a NEW stream() call for the same session while this run's process
@@ -404,6 +413,7 @@ var RunRecording = class {
404
413
  if (this.settled) return;
405
414
  this.events.push(ev);
406
415
  if (ev.kind === "result") this.resultConversationId = ev.conversationId ?? null;
416
+ if (ev.kind === "init" && typeof ev.conversationId === "string" && ev.conversationId !== "") this.resultConversationId = this.resultConversationId ?? ev.conversationId;
407
417
  this.wake();
408
418
  }
409
419
  /**
@@ -505,8 +515,27 @@ var RunRecording = class {
505
515
  toolEventAt(eventIndex) {
506
516
  const ev = this.events[eventIndex];
507
517
  if (ev === void 0 || ev.kind !== "step" || ev.stepKind !== "tool" || !ev.tool) return null;
518
+ const full = this.fullArgs?.get(eventIndex);
519
+ if (full !== void 0 && full !== null) return {
520
+ ...ev.tool,
521
+ args: {
522
+ ...full,
523
+ ...typeof ev.tool.args === "object" ? ev.tool.args : {}
524
+ }
525
+ };
508
526
  return ev.tool;
509
527
  }
528
+ /** Raw errors reported by completed agy tool steps, in event order. */
529
+ toolErrors() {
530
+ const errors = [];
531
+ for (const ev of this.events) if (ev.kind === "step" && ev.stepKind === "tool" && ev.tool?.error !== void 0) errors.push(ev.tool.error);
532
+ return errors;
533
+ }
534
+ /** Store full DB-resolved tool args for a step (set during span driving). */
535
+ setFullArgs(eventIndex, args) {
536
+ this.fullArgs ??= /* @__PURE__ */ new Map();
537
+ this.fullArgs.set(eventIndex, args);
538
+ }
510
539
  };
511
540
  /** Prefix every mirrored agy tool callId carries; continuation detection key. */
512
541
  const AGY_CALL_PREFIX = "agytc-";
@@ -1044,7 +1073,7 @@ var EventMapper = class {
1044
1073
  return;
1045
1074
  }
1046
1075
  if (ev.stepKind === "tool" && ev.tool) {
1047
- if (!(ev.tool.output !== void 0 || ev.tool.error !== void 0)) return;
1076
+ if (!(ev.state === "DONE" || ev.state === "ERROR" || ev.state === void 0 && (ev.tool.output !== void 0 || ev.tool.error !== void 0))) return;
1048
1077
  if (this.announcedTools.has(ev.stepKey)) return;
1049
1078
  this.announcedTools.add(ev.stepKey);
1050
1079
  if (!this.opts.cutOnTool) return;
@@ -1053,11 +1082,16 @@ var EventMapper = class {
1053
1082
  const idx = this.blockIdx;
1054
1083
  const useCode = this.opts.useCodeWrapper === true;
1055
1084
  const toolName = useCode ? WRAPPER_TOOL_NAME : MIRROR_TOOL_NAME;
1085
+ const fullArgs = this.opts.resolvedFullArgs?.get(absIndex);
1086
+ const effectiveArgs = fullArgs !== void 0 ? {
1087
+ ...fullArgs,
1088
+ ...typeof ev.tool.args === "object" ? ev.tool.args : {}
1089
+ } : ev.tool.args;
1056
1090
  const argumentsJson = useCode ? JSON.stringify(buildMirrorRunCode(this.opts.runId, absIndex, ev.tool.name)) : JSON.stringify({
1057
1091
  run: this.opts.runId,
1058
1092
  step: absIndex,
1059
1093
  tool: ev.tool.name,
1060
- ...ev.tool.args !== void 0 ? { input: ev.tool.args } : {}
1094
+ ...effectiveArgs !== void 0 ? { input: effectiveArgs } : {}
1061
1095
  });
1062
1096
  yield {
1063
1097
  type: "block-start",
@@ -1625,6 +1659,7 @@ function classifyEvent(obj, seq) {
1625
1659
  const delta = extractTextDelta(src);
1626
1660
  const text = delta !== void 0 ? delta : extractText(src);
1627
1661
  const stateV = pick(src, ["state"]);
1662
+ if (stateV === "ERROR" && toolInfo && !toolInfo.error) toolInfo.error = "tool execution failed";
1628
1663
  const usageRaw = pick(src, ["usage"]);
1629
1664
  return {
1630
1665
  kind: "step",
@@ -1900,7 +1935,13 @@ function resolveAgyBin(cfg) {
1900
1935
  const local = process.env.LOCALAPPDATA ?? "";
1901
1936
  const appData = process.env.APPDATA ?? "";
1902
1937
  if (local !== "") {
1938
+ candidates.push(join(local, "agy", "bin", "agy.exe"));
1939
+ candidates.push(join(local, "agy", "bin", "agy.cmd"));
1940
+ candidates.push(join(local, "agy", "bin", "agy.bat"));
1941
+ candidates.push(join(local, "agy", "agy.exe"));
1903
1942
  candidates.push(join(local, "Programs", "agy", "agy.exe"));
1943
+ candidates.push(join(local, "Programs", "agy", "bin", "agy.exe"));
1944
+ candidates.push(join(local, "Microsoft", "WinGet", "Links", "agy.exe"));
1904
1945
  candidates.push(join(local, "pnpm", "agy.cmd"));
1905
1946
  candidates.push(join(local, "pnpm", "agy.exe"));
1906
1947
  }
@@ -1959,6 +2000,44 @@ function compareVersions(a, b) {
1959
2000
  function parseVersion(out) {
1960
2001
  return out.match(/(\d+\.\d+\.\d+)/)?.[1] ?? null;
1961
2002
  }
2003
+ /**
2004
+ * Windows CreateProcess caps the command line at ~32767 chars. Leave headroom
2005
+ * for env, cwd, quoting, and the rest of the argv so a long `-p` prompt does
2006
+ * not fail with spawn ENAMETOOLONG (issue #14 / #11). Above this, the prompt
2007
+ * rides agy `--input-format stream-json` on stdin instead.
2008
+ */
2009
+ const ARGV_PROMPT_LIMIT = 24e3;
2010
+ /** One NDJSON line for agy --input-format stream-json (verified on agy 1.2.x). */
2011
+ function buildStreamInputLine(prompt) {
2012
+ return JSON.stringify({
2013
+ event: "user",
2014
+ message: {
2015
+ role: "user",
2016
+ content: prompt
2017
+ }
2018
+ }) + "\n";
2019
+ }
2020
+ /** Whether the assembled argv (including a trailing `-p <prompt>`) risks ENAMETOOLONG. */
2021
+ function shouldUsePromptStdin(argsWithPrompt) {
2022
+ let size = 0;
2023
+ for (const a of argsWithPrompt) size += a.length + 1;
2024
+ return size > ARGV_PROMPT_LIMIT;
2025
+ }
2026
+ /**
2027
+ * Quiet env for every agy spawn (issue #23).
2028
+ *
2029
+ * agy.exe itself creates a visible conhost on Windows — especially via its
2030
+ * `--bg-updater` auto-update child chain — even when the parent spawn used
2031
+ * `windowsHide: true`. Both switches are present in the official binary.
2032
+ * Existing values win so operators can opt out (`=0`).
2033
+ */
2034
+ function withAgyQuietEnv(env = process.env) {
2035
+ return {
2036
+ ...env,
2037
+ AGY_CLI_DISABLE_AUTO_UPDATE: env.AGY_CLI_DISABLE_AUTO_UPDATE ?? "1",
2038
+ AGY_CLI_INTERACTIVE_HEADLESS: env.AGY_CLI_INTERACTIVE_HEADLESS ?? "1"
2039
+ };
2040
+ }
1962
2041
  function killTree(child) {
1963
2042
  if (child.pid === void 0) return;
1964
2043
  if (IS_WIN) {
@@ -1990,7 +2069,7 @@ function killTree(child) {
1990
2069
  function startAgyProcess(opts) {
1991
2070
  const started = Date.now();
1992
2071
  const viaCmd = IS_WIN && isCmdShim(opts.bin);
1993
- const env = opts.env ?? process.env;
2072
+ const env = withAgyQuietEnv(opts.env ?? process.env);
1994
2073
  const child = viaCmd ? spawn(process.env.ComSpec ?? "cmd.exe", [
1995
2074
  "/d",
1996
2075
  "/s",
@@ -2022,7 +2101,11 @@ function startAgyProcess(opts) {
2022
2101
  let timedOut = false;
2023
2102
  let aborted = false;
2024
2103
  let settled = false;
2025
- if (!opts.keepStdin) try {
2104
+ if (opts.stdinPayload !== void 0) try {
2105
+ child.stdin?.write(opts.stdinPayload);
2106
+ child.stdin?.end();
2107
+ } catch {}
2108
+ else if (!opts.keepStdin) try {
2026
2109
  child.stdin?.end();
2027
2110
  } catch {}
2028
2111
  let watchdog = null;
@@ -2106,6 +2189,208 @@ async function probeProcess(bin, args, timeoutMs = 3e4, signal, env) {
2106
2189
  }).outcome;
2107
2190
  }
2108
2191
  //#endregion
2192
+ //#region src/host/agy-db.ts
2193
+ const execFileAsync = promisify(execFile);
2194
+ let AGY_DB_DIR = join(homedir(), ".gemini", "antigravity-cli", "conversations");
2195
+ const MAX_CACHE_AGE_MS = 3e5;
2196
+ let cache = null;
2197
+ let cacheTime = 0;
2198
+ /** agy conversation ids are path-safe tokens; reject anything else before join(). */
2199
+ const SAFE_CONVERSATION_ID = /^[A-Za-z0-9_-]{1,64}$/;
2200
+ function isSafeConversationId(id) {
2201
+ return SAFE_CONVERSATION_ID.test(id);
2202
+ }
2203
+ /**
2204
+ * Copy the agy SQLite DB to a temp path (avoiding WAL lock issues), then
2205
+ * query tool step payloads via sqlite3 CLI. Returns a Map of stepIndex →
2206
+ * {name, args} for every tool step found.
2207
+ */
2208
+ async function loadToolSteps(conversationId) {
2209
+ const result = /* @__PURE__ */ new Map();
2210
+ if (!isSafeConversationId(conversationId)) return result;
2211
+ const dbPath = join(AGY_DB_DIR, `${conversationId}.db`);
2212
+ try {
2213
+ if ((await stat(dbPath)).size === 0) return result;
2214
+ } catch {
2215
+ return result;
2216
+ }
2217
+ const tmpDb = join(tmpdir(), `agy-db-${conversationId.slice(0, 8)}-${Date.now()}.db`);
2218
+ try {
2219
+ await copyFile(dbPath, tmpDb);
2220
+ try {
2221
+ await copyFile(dbPath + "-wal", tmpDb + "-wal");
2222
+ } catch {}
2223
+ try {
2224
+ await copyFile(dbPath + "-shm", tmpDb + "-shm");
2225
+ } catch {}
2226
+ const { stdout } = await execFileAsync("sqlite3", [tmpDb, `SELECT idx, hex(step_payload) FROM steps WHERE step_type IN (5,7,8,9,17,21,33,101,132) AND length(step_payload) > 20`], {
2227
+ timeout: 5e3,
2228
+ encoding: "utf-8",
2229
+ maxBuffer: 10485760
2230
+ });
2231
+ for (const line of stdout.split("\n")) {
2232
+ const pipeIdx = line.indexOf("|");
2233
+ if (pipeIdx < 0) continue;
2234
+ const idx = parseInt(line.slice(0, pipeIdx), 10);
2235
+ if (!Number.isFinite(idx)) continue;
2236
+ const hexPayload = line.slice(pipeIdx + 1).trim();
2237
+ if (hexPayload === "" || hexPayload === "NULL") continue;
2238
+ const info = extractToolInfo(Buffer.from(hexPayload, "hex"));
2239
+ if (info !== null) result.set(idx, info);
2240
+ }
2241
+ } catch {} finally {
2242
+ try {
2243
+ await unlink(tmpDb);
2244
+ } catch {}
2245
+ try {
2246
+ await unlink(tmpDb + "-wal");
2247
+ } catch {}
2248
+ try {
2249
+ await unlink(tmpDb + "-shm");
2250
+ } catch {}
2251
+ }
2252
+ return result;
2253
+ }
2254
+ /**
2255
+ * Extract tool name and full JSON args from a protobuf step_payload.
2256
+ *
2257
+ * agy's tool step payload wraps the call in standard protobuf fields:
2258
+ * field 2 (tag 0x12, length-delimited) → tool_name ("write_to_file", ...)
2259
+ * field 3 (tag 0x1a, length-delimited) → args JSON ('{"CodeContent":"...",...}')
2260
+ *
2261
+ * We scan for this deterministic sequence directly (sub-millisecond, no
2262
+ * recursive descent or backtracking) and fall back to scanning for balanced
2263
+ * JSON slices containing known parameter keys.
2264
+ */
2265
+ function extractToolInfo(payload) {
2266
+ for (let i = 0; i < payload.length - 8; i++) if (payload[i] === 18) {
2267
+ const nameLen = payload[i + 1];
2268
+ if (nameLen >= 2 && nameLen <= 64 && i + 2 + nameLen < payload.length) {
2269
+ if (payload[i + 2 + nameLen] === 26) {
2270
+ const nameStr = payload.subarray(i + 2, i + 2 + nameLen).toString("utf-8");
2271
+ if (/^[a-zA-Z0-9_]+$/.test(nameStr)) {
2272
+ let pos = i + 3 + nameLen;
2273
+ let jsonLen = 0;
2274
+ let shift = 0;
2275
+ while (pos < payload.length) {
2276
+ const b = payload[pos];
2277
+ pos++;
2278
+ jsonLen |= (b & 127) << shift;
2279
+ shift += 7;
2280
+ if ((b & 128) === 0) break;
2281
+ }
2282
+ if (jsonLen > 1 && pos + jsonLen <= payload.length) {
2283
+ const jsonSlice = payload.subarray(pos, pos + jsonLen);
2284
+ if (jsonSlice[0] === 123) try {
2285
+ const obj = JSON.parse(jsonSlice.toString("utf-8"));
2286
+ if (typeof obj === "object" && obj !== null && !Array.isArray(obj)) return {
2287
+ name: nameStr,
2288
+ args: obj
2289
+ };
2290
+ } catch {}
2291
+ }
2292
+ }
2293
+ }
2294
+ }
2295
+ }
2296
+ return scanFallbackJson(payload);
2297
+ }
2298
+ function scanFallbackJson(payload) {
2299
+ const toolKeys = [
2300
+ "TargetFile",
2301
+ "TargetContent",
2302
+ "ReplacementContent",
2303
+ "CodeContent",
2304
+ "CommandLine",
2305
+ "AbsolutePath",
2306
+ "Query",
2307
+ "Pattern",
2308
+ "DirectoryPath"
2309
+ ];
2310
+ let pos = 0;
2311
+ let bestObj = null;
2312
+ let bestKeyCount = 0;
2313
+ while (pos < payload.length) {
2314
+ const nextBrace = payload.indexOf(123, pos);
2315
+ if (nextBrace === -1) break;
2316
+ pos = nextBrace + 1;
2317
+ const sample = payload.subarray(nextBrace, Math.min(payload.length, nextBrace + 200)).toString("utf-8");
2318
+ if (!toolKeys.some((k) => sample.includes(k))) continue;
2319
+ let brace = 0;
2320
+ let inStr = false;
2321
+ let esc = false;
2322
+ let end = -1;
2323
+ for (let j = nextBrace; j < Math.min(payload.length, nextBrace + 2e5); j++) {
2324
+ const b = payload[j];
2325
+ if (esc) {
2326
+ esc = false;
2327
+ continue;
2328
+ }
2329
+ if (b === 92) {
2330
+ esc = true;
2331
+ continue;
2332
+ }
2333
+ if (b === 34) {
2334
+ inStr = !inStr;
2335
+ continue;
2336
+ }
2337
+ if (!inStr) {
2338
+ if (b === 123) brace++;
2339
+ else if (b === 125) {
2340
+ brace--;
2341
+ if (brace === 0) {
2342
+ end = j + 1;
2343
+ break;
2344
+ }
2345
+ }
2346
+ }
2347
+ }
2348
+ if (end > nextBrace) {
2349
+ try {
2350
+ const obj = JSON.parse(payload.subarray(nextBrace, end).toString("utf-8"));
2351
+ if (typeof obj === "object" && obj !== null && !Array.isArray(obj)) {
2352
+ const rec = obj;
2353
+ const count = Object.keys(rec).length;
2354
+ if (count > bestKeyCount) {
2355
+ bestObj = rec;
2356
+ bestKeyCount = count;
2357
+ }
2358
+ }
2359
+ } catch {}
2360
+ pos = end;
2361
+ }
2362
+ }
2363
+ if (bestObj !== null) return {
2364
+ name: typeof bestObj.tool === "string" ? bestObj.tool : "",
2365
+ args: bestObj
2366
+ };
2367
+ return null;
2368
+ }
2369
+ /**
2370
+ * Read the full tool parameter object for a given step index from the agy
2371
+ * conversation database. Returns null when the DB is unavailable or does
2372
+ * not contain the requested step.
2373
+ *
2374
+ * Results are cached per conversationId for up to 5 minutes to avoid
2375
+ * repeated DB reads within the same session.
2376
+ */
2377
+ async function readFullToolArgs(conversationId, stepIndex) {
2378
+ if (!isSafeConversationId(conversationId)) return null;
2379
+ const now = Date.now();
2380
+ if (cache?.conversationId === conversationId && cache.loaded && now - cacheTime < MAX_CACHE_AGE_MS) {
2381
+ const cached = cache.steps.get(stepIndex);
2382
+ if (cached !== void 0) return cached;
2383
+ }
2384
+ const steps = await loadToolSteps(conversationId);
2385
+ cache = {
2386
+ conversationId,
2387
+ steps,
2388
+ loaded: true
2389
+ };
2390
+ cacheTime = now;
2391
+ return steps.get(stepIndex) ?? null;
2392
+ }
2393
+ //#endregion
2109
2394
  //#region src/host/adapter.ts
2110
2395
  function textOf(m) {
2111
2396
  const parts = [];
@@ -2314,7 +2599,8 @@ var AgyAdapter = class extends LlmAdapter {
2314
2599
  if (opts.conversationId) args.push("--conversation", opts.conversationId);
2315
2600
  for (const d of opts.addDirs ?? []) args.push("--add-dir", d);
2316
2601
  args.push(...opts.extraArgs);
2317
- args.push("-p", opts.prompt);
2602
+ if (opts.promptViaStdin) args.push("--input-format", "stream-json");
2603
+ else args.push("-p", opts.prompt);
2318
2604
  return args;
2319
2605
  }
2320
2606
  async *stream(options) {
@@ -2497,9 +2783,10 @@ var AgyAdapter = class extends LlmAdapter {
2497
2783
  const parser = new StreamJsonParser();
2498
2784
  this.deps.onParser?.(parser);
2499
2785
  let streamCid = null;
2500
- const args = this.buildArgs({
2786
+ const activeModelForArgs = activeModel === "" ? cfg.defaultModel : activeModel;
2787
+ const argsBase = this.buildArgs({
2501
2788
  prompt,
2502
- model: activeModel === "" ? cfg.defaultModel : activeModel,
2789
+ model: activeModelForArgs,
2503
2790
  effort,
2504
2791
  conversationId: !isAux && binding !== void 0 ? binding.conversationId : void 0,
2505
2792
  permissionMode: isAux ? "plan" : cfg.permissionMode,
@@ -2508,6 +2795,20 @@ var AgyAdapter = class extends LlmAdapter {
2508
2795
  extraArgs: cfg.extraArgs,
2509
2796
  addDirs: stagedDirs
2510
2797
  });
2798
+ const promptViaStdin = shouldUsePromptStdin(argsBase);
2799
+ const args = promptViaStdin ? this.buildArgs({
2800
+ prompt,
2801
+ model: activeModelForArgs,
2802
+ effort,
2803
+ conversationId: !isAux && binding !== void 0 ? binding.conversationId : void 0,
2804
+ permissionMode: isAux ? "plan" : cfg.permissionMode,
2805
+ timeoutMs: cfg.timeoutMs,
2806
+ printTimeoutMinutes: Math.max(240, Math.ceil(cfg.timeoutMs / 6e4)),
2807
+ extraArgs: cfg.extraArgs,
2808
+ addDirs: stagedDirs,
2809
+ promptViaStdin: true
2810
+ }) : argsBase;
2811
+ const stdinPayload = promptViaStdin ? buildStreamInputLine(prompt) : void 0;
2511
2812
  const release = await this.deps.acquire();
2512
2813
  let released = false;
2513
2814
  const releaseOnce = () => {
@@ -2550,6 +2851,7 @@ var AgyAdapter = class extends LlmAdapter {
2550
2851
  timeoutMs: cfg.timeoutMs,
2551
2852
  signal: options.signal,
2552
2853
  env,
2854
+ stdinPayload,
2553
2855
  onLine: (line) => {
2554
2856
  for (const ev of parser.feed(line + "\n")) {
2555
2857
  if (ev.kind === "init" && ev.conversationId) streamCid = ev.conversationId;
@@ -2643,8 +2945,9 @@ var AgyAdapter = class extends LlmAdapter {
2643
2945
  }
2644
2946
  }
2645
2947
  this.deps.onRun?.({
2646
- ok: failure === null,
2647
- code: failure !== null ? failure.code : "OK",
2948
+ processOk: !outcome.aborted && !outcome.timedOut && outcome.code === 0,
2949
+ processCode: outcome.aborted ? "ABORTED" : outcome.timedOut ? Err.TIMEOUT : outcome.code === 0 ? "OK" : "EXIT_" + String(outcome.code),
2950
+ toolErrors: rec.toolErrors(),
2648
2951
  durationMs: outcome.durationMs,
2649
2952
  model
2650
2953
  });
@@ -2668,16 +2971,52 @@ var AgyAdapter = class extends LlmAdapter {
2668
2971
  async *driveSpan(rec, from, cutOnTool, useCodeWrapper) {
2669
2972
  const queue = new ChunkQueue();
2670
2973
  (async () => {
2974
+ const resolved = /* @__PURE__ */ new Map();
2975
+ rec.conversationId;
2671
2976
  const mapper = new EventMapper({
2672
2977
  runId: rec.runId,
2673
2978
  cutOnTool,
2674
2979
  initialSawText: rec.sawTextBefore(from),
2675
2980
  useCodeWrapper,
2676
- usage: rec
2981
+ usage: rec,
2982
+ resolvedFullArgs: resolved
2677
2983
  });
2678
2984
  let i = from;
2679
2985
  try {
2680
2986
  for await (const ev of rec.eventsFrom(from)) {
2987
+ if (ev.kind === "step" && ev.stepKind === "tool" && ev.tool) {
2988
+ const rawObj = ev.raw && typeof ev.raw === "object" ? ev.raw : null;
2989
+ const stepUpdate = rawObj?.step_update && typeof rawObj.step_update === "object" ? rawObj.step_update : null;
2990
+ const activeConvId = rec.conversationId ?? (typeof rawObj?.conversation_id === "string" ? rawObj.conversation_id : null) ?? (typeof stepUpdate?.conversation_id === "string" ? stepUpdate.conversation_id : null);
2991
+ const stepIdx = parseInt(ev.stepKey, 10);
2992
+ if (activeConvId !== null && Number.isFinite(stepIdx)) try {
2993
+ let full = await readFullToolArgs(activeConvId, stepIdx);
2994
+ if (full === null) {
2995
+ await new Promise((r) => setTimeout(r, 50));
2996
+ full = await readFullToolArgs(activeConvId, stepIdx);
2997
+ }
2998
+ if (full !== null && full.args !== void 0) {
2999
+ resolved.set(i, full.args);
3000
+ rec.setFullArgs(i, full.args);
3001
+ }
3002
+ } catch {}
3003
+ const toolName = ev.tool.name;
3004
+ if (toolName === "write_to_file" || toolName === "write_file" || toolName === "create_file") {
3005
+ const currentArgs = resolved.get(i) ?? (typeof ev.tool.args === "object" && ev.tool.args !== null ? ev.tool.args : {});
3006
+ const targetFile = currentArgs.TargetFile ?? currentArgs.target_file ?? currentArgs.path ?? currentArgs.AbsolutePath;
3007
+ if (targetFile && typeof targetFile === "string" && !currentArgs.oldText && !currentArgs.old_string && !currentArgs.TargetContent) {
3008
+ const head = getGitHeadContent(targetFile);
3009
+ if (head !== null) {
3010
+ const updated = {
3011
+ ...currentArgs,
3012
+ old_string: head
3013
+ };
3014
+ resolved.set(i, updated);
3015
+ rec.setFullArgs(i, updated);
3016
+ }
3017
+ }
3018
+ }
3019
+ }
2681
3020
  for (const ch of mapper.map(ev, i)) queue.push(ch);
2682
3021
  i++;
2683
3022
  if (mapper.isFinished) break;
@@ -2701,7 +3040,13 @@ var AgyAdapter = class extends LlmAdapter {
2701
3040
  * run and the event index to resume after.
2702
3041
  */
2703
3042
  function detectContinuation(messages) {
2704
- const last = messages[messages.length - 1];
3043
+ let i = messages.length - 1;
3044
+ while (i >= 0) {
3045
+ const snapshot = messages[i];
3046
+ if (snapshot.source?.kind !== "plugin" || snapshot.source.form !== "snapshot") break;
3047
+ i--;
3048
+ }
3049
+ const last = messages[i];
2705
3050
  if (last === void 0 || last.role !== "user") return null;
2706
3051
  const src = last.source;
2707
3052
  if (src === void 0 || src.kind !== "tool" || typeof src.callId !== "string") return null;
@@ -2793,6 +3138,12 @@ async function runAgyOnce(deps, req) {
2793
3138
  cleanup = sa.cleanup;
2794
3139
  }
2795
3140
  args.push("-p", prompt);
3141
+ let stdinPayload;
3142
+ if (shouldUsePromptStdin(args)) {
3143
+ args.splice(args.length - 2, 2);
3144
+ args.push("--input-format", "stream-json");
3145
+ stdinPayload = buildStreamInputLine(prompt);
3146
+ }
2796
3147
  const parser = new StreamJsonParser();
2797
3148
  const textParts = [];
2798
3149
  let resultText = "";
@@ -2803,6 +3154,7 @@ async function runAgyOnce(deps, req) {
2803
3154
  cwd: cfg.workspaceRoot !== "" ? cfg.workspaceRoot : void 0,
2804
3155
  timeoutMs,
2805
3156
  signal: req.signal,
3157
+ stdinPayload,
2806
3158
  onLine: (line) => {
2807
3159
  for (const ev of parser.feed(line + "\n")) {
2808
3160
  if (ev.kind === "init" && ev.conversationId) conversationId = ev.conversationId;
@@ -15410,7 +15762,7 @@ var require_mock_interceptor = /* @__PURE__ */ __commonJSMin(((exports, module)
15410
15762
  //#endregion
15411
15763
  //#region node_modules/undici/lib/mock/mock-client.js
15412
15764
  var require_mock_client = /* @__PURE__ */ __commonJSMin(((exports, module) => {
15413
- const { promisify: promisify$1 } = __require("node:util");
15765
+ const { promisify: promisify$2 } = __require("node:util");
15414
15766
  const Client = require_client();
15415
15767
  const { buildMockDispatch } = require_mock_utils();
15416
15768
  const { kDispatches, kMockAgent, kClose, kOriginalClose, kOrigin, kOriginalDispatch, kConnected, kIgnoreTrailingSlash } = require_mock_symbols();
@@ -15450,7 +15802,7 @@ var require_mock_client = /* @__PURE__ */ __commonJSMin(((exports, module) => {
15450
15802
  this[kDispatches] = [];
15451
15803
  }
15452
15804
  async [kClose]() {
15453
- await promisify$1(this[kOriginalClose])();
15805
+ await promisify$2(this[kOriginalClose])();
15454
15806
  this[kConnected] = 0;
15455
15807
  this[kMockAgent][Symbols.kClients].delete(this[kOrigin]);
15456
15808
  }
@@ -15614,7 +15966,7 @@ var require_mock_call_history = /* @__PURE__ */ __commonJSMin(((exports, module)
15614
15966
  //#endregion
15615
15967
  //#region node_modules/undici/lib/mock/mock-pool.js
15616
15968
  var require_mock_pool = /* @__PURE__ */ __commonJSMin(((exports, module) => {
15617
- const { promisify } = __require("node:util");
15969
+ const { promisify: promisify$1 } = __require("node:util");
15618
15970
  const Pool = require_pool();
15619
15971
  const { buildMockDispatch } = require_mock_utils();
15620
15972
  const { kDispatches, kMockAgent, kClose, kOriginalClose, kOrigin, kOriginalDispatch, kConnected, kIgnoreTrailingSlash } = require_mock_symbols();
@@ -15654,7 +16006,7 @@ var require_mock_pool = /* @__PURE__ */ __commonJSMin(((exports, module) => {
15654
16006
  this[kDispatches] = [];
15655
16007
  }
15656
16008
  async [kClose]() {
15657
- await promisify(this[kOriginalClose])();
16009
+ await promisify$1(this[kOriginalClose])();
15658
16010
  this[kConnected] = 0;
15659
16011
  this[kMockAgent][Symbols.kClients].delete(this[kOrigin]);
15660
16012
  }
@@ -26658,7 +27010,10 @@ function openBrowser(url) {
26658
27010
  "/s",
26659
27011
  "/c",
26660
27012
  `start "" "${url}"`
26661
- ], { windowsVerbatimArguments: true }, (err) => resolve(!err));
27013
+ ], {
27014
+ windowsVerbatimArguments: true,
27015
+ windowsHide: true
27016
+ }, (err) => resolve(!err));
26662
27017
  else execFile("xdg-open", [url], (err) => resolve(!err));
26663
27018
  } catch {
26664
27019
  resolve(false);
@@ -27055,7 +27410,7 @@ async function handle(deps, raw) {
27055
27410
  "",
27056
27411
  "备用:授权完成后运行 `/agy auth-code <授权码或完整回调URL>`。"
27057
27412
  ].join("\n"));
27058
- return err(st.message ?? "failed to start the login flow");
27413
+ return err(`failed to start the login flow (${st.code ?? "request_failed"})`);
27059
27414
  }
27060
27415
  if (sub === "auth-code") {
27061
27416
  if (arg === "") return err("usage: /agy auth-code <code-or-callback-url>");
@@ -27064,9 +27419,9 @@ async function handle(deps, raw) {
27064
27419
  const st = await flow.submitCode(arg);
27065
27420
  if (st.ok) {
27066
27421
  deps.store().clear();
27067
- return ok(st.message ?? "Logged in to Antigravity.");
27422
+ return ok("Logged in to Antigravity.");
27068
27423
  }
27069
- return err(st.message ?? "login failed");
27424
+ return err(`login failed (${st.code ?? "request_failed"})`);
27070
27425
  }
27071
27426
  if (sub === "models") {
27072
27427
  const cat = await deps.catalog().forceRefresh();
@@ -27127,6 +27482,8 @@ async function renderStatus(deps) {
27127
27482
  const cat = deps.catalog().get();
27128
27483
  const bindings = Object.keys(deps.store().all()).length;
27129
27484
  const last = deps.lastRun();
27485
+ const errorKinds = last?.toolErrors.map(classifyToolError) ?? [];
27486
+ const hasHeadlessPermissionDenial = errorKinds.includes("approval");
27130
27487
  return [
27131
27488
  "**dsh-agy-link status**",
27132
27489
  "- agy binary: " + (bin ?? "not found — install via https://antigravity.google/docs/cli/install"),
@@ -27138,7 +27495,13 @@ async function renderStatus(deps) {
27138
27495
  "- default effort: " + (cfg.defaultEffort === "" ? "(model default)" : cfg.defaultEffort),
27139
27496
  "- catalog: " + cat.models.length + " models — " + cat.source + (cat.lastError === void 0 ? "" : " — last error: " + cat.lastError),
27140
27497
  "- conversation bindings: " + bindings,
27141
- "- last run: " + (last ? (last.ok ? "ok" : last.code) + " — " + last.model + " in " + Math.round(last.durationMs / 100) / 10 + "s" : "none yet")
27498
+ "- last run process: " + (last ? (last.processOk ? "ok" : last.processCode) + " — " + last.model + " in " + Math.round(last.durationMs / 100) / 10 + "s" : "none yet"),
27499
+ ...last && last.toolErrors.length > 0 ? [
27500
+ "- last run tool errors (" + last.toolErrors.length + "): " + last.toolErrors.join(" | "),
27501
+ ...errorKinds.includes("missing_file") ? ["- Guidance: `missing_file` — verify the expected workspace/artifact path for this conversation; do not invent paths or bypass protections."] : [],
27502
+ ...errorKinds.includes("system_protection") ? ["- Guidance: `system_protection` — AGY hardcoded system protection remains enforced; do not attempt a bypass."] : [],
27503
+ ...hasHeadlessPermissionDenial && (cfg.permissionMode === "plan" || cfg.permissionMode === "accept-edits") ? ["- Note: in headless operation, agy can automatically deny a tool under this mode; the raw denial above is not evidence of a human approval or denial."] : []
27504
+ ] : []
27142
27505
  ].join("\n");
27143
27506
  }
27144
27507
  function ok(text) {
@@ -27298,7 +27661,8 @@ var AccountPoolManager = class {
27298
27661
  if (this.data.accounts.some((a) => a.systemHome)) return;
27299
27662
  const primary = {
27300
27663
  id: "acc_primary",
27301
- alias: "主账号 (系统登录)",
27664
+ alias: "Primary account (system sign-in)",
27665
+ defaultAlias: true,
27302
27666
  dir: "",
27303
27667
  systemHome: true,
27304
27668
  enabled: true,
@@ -27318,10 +27682,12 @@ var AccountPoolManager = class {
27318
27682
  */
27319
27683
  normalizeLegacyPrimary() {
27320
27684
  const primary = this.data.accounts.find((a) => a.id === "acc_primary");
27321
- if (!primary || primary.systemHome) return;
27685
+ if (!primary) return;
27686
+ if (primary.systemHome) return;
27322
27687
  primary.dir = "";
27323
27688
  primary.systemHome = true;
27324
- primary.alias = "主账号 (系统登录)";
27689
+ primary.alias = "Primary account (system sign-in)";
27690
+ primary.defaultAlias = true;
27325
27691
  this.data.primaryAccountId = primary.id;
27326
27692
  this.persist();
27327
27693
  }
@@ -27357,7 +27723,8 @@ var AccountPoolManager = class {
27357
27723
  const count = this.data.accounts.length + 1;
27358
27724
  const newAccount = {
27359
27725
  id,
27360
- alias: alias || `备用 Google 账号 ${count}`,
27726
+ alias: alias?.trim() || `Backup Google account ${count}`,
27727
+ defaultAlias: !alias?.trim(),
27361
27728
  dir: existsSync(finalDir) ? finalDir : dir,
27362
27729
  ...email ? { email } : {},
27363
27730
  ...proxyUrl ? { proxyUrl } : {},
@@ -27435,7 +27802,8 @@ var AccountPoolManager = class {
27435
27802
  const count = this.data.accounts.length + 1;
27436
27803
  const newAccount = {
27437
27804
  id,
27438
- alias: alias || `备用账号 ${count} (Account ${count})`,
27805
+ alias: alias?.trim() || `Backup Google account ${count}`,
27806
+ defaultAlias: !alias?.trim(),
27439
27807
  dir,
27440
27808
  enabled: true,
27441
27809
  createdAt: Date.now(),
@@ -27474,6 +27842,7 @@ var AccountPoolManager = class {
27474
27842
  const acc = this.getAccount(id);
27475
27843
  if (!acc) return false;
27476
27844
  acc.alias = alias.trim();
27845
+ acc.defaultAlias = false;
27477
27846
  this.persist();
27478
27847
  return true;
27479
27848
  }
@@ -27747,12 +28116,12 @@ var PoolAuthFlow = class {
27747
28116
  url,
27748
28117
  mode,
27749
28118
  browserOpened,
27750
- message: browserOpened ? void 0 : "无法自动打开浏览器,请手动打开下方链接"
28119
+ code: browserOpened ? void 0 : "browser_manual"
27751
28120
  });
27752
28121
  this.log(`pool auth begun (${mode} mode, browserOpened=${browserOpened}${active.primary ? ", primary" : ""})`);
27753
28122
  if (listener) listener.result.then(({ code, state: returnedState }) => this.finishWithCode(code, returnedState)).catch((err) => {
27754
28123
  if (!this.flow || this.flow.listener !== listener) return;
27755
- this.fail(`授权回调失败: ${err instanceof Error ? err.message : String(err)}`);
28124
+ this.fail("callback_failed", err);
27756
28125
  });
27757
28126
  return {
27758
28127
  ok: true,
@@ -27788,18 +28157,18 @@ var PoolAuthFlow = class {
27788
28157
  if (!flow || this.statusValue.phase !== "waiting") return {
27789
28158
  ok: false,
27790
28159
  phase: this.statusValue.phase,
27791
- message: "当前没有进行中的授权流程"
28160
+ code: "no_active_flow"
27792
28161
  };
27793
28162
  const parsed = parsePastedCode(input);
27794
28163
  if (!parsed) return {
27795
28164
  ok: false,
27796
28165
  phase: "waiting",
27797
- message: "无法识别的授权码,请粘贴授权码或完整的回调 URL"
28166
+ code: "invalid_code"
27798
28167
  };
27799
28168
  if (parsed.state && parsed.state !== flow.state) return {
27800
28169
  ok: false,
27801
28170
  phase: "waiting",
27802
- message: "state 校验失败:这段 URL 不属于本次授权流程"
28171
+ code: "invalid_state"
27803
28172
  };
27804
28173
  await this.finishWithCode(parsed.code, parsed.state ?? flow.state);
27805
28174
  const after = this.status();
@@ -27812,7 +28181,7 @@ var PoolAuthFlow = class {
27812
28181
  const flow = this.flow;
27813
28182
  if (!flow || this.statusValue.phase !== "waiting") return;
27814
28183
  if (returnedState !== flow.state) {
27815
- this.fail("state 校验失败(可能的 CSRF 或过期回调)");
28184
+ this.fail("invalid_state");
27816
28185
  return;
27817
28186
  }
27818
28187
  this.setStatus({ phase: "exchanging" });
@@ -27832,7 +28201,7 @@ var PoolAuthFlow = class {
27832
28201
  this.setStatus({
27833
28202
  phase: "done",
27834
28203
  alias: email,
27835
- message: `主账号已登录${email ? `: ${email}` : ""}`
28204
+ code: "primary_activated"
27836
28205
  });
27837
28206
  return;
27838
28207
  }
@@ -27844,15 +28213,15 @@ var PoolAuthFlow = class {
27844
28213
  this.setStatus({
27845
28214
  phase: "done",
27846
28215
  alias: acc.alias,
27847
- message: `账号 ${acc.alias} 已激活`
28216
+ code: "account_activated"
27848
28217
  });
27849
28218
  } catch (err) {
27850
- this.fail(`授权码交换失败: ${err instanceof Error ? err.message : String(err)}`);
28219
+ this.fail("exchange_failed", err);
27851
28220
  }
27852
28221
  }
27853
- fail(message) {
28222
+ fail(code, detail) {
27854
28223
  const flow = this.flow;
27855
- this.log("pool auth failed: " + message);
28224
+ this.log(`pool auth failed [${code}]${detail ? `: ${detail instanceof Error ? detail.message : String(detail)}` : ""}`);
27856
28225
  if (flow) {
27857
28226
  if (flow.listener) flow.listener.close().catch(() => void 0);
27858
28227
  if (!flow.primary) this.pool.cleanupStagingSlot(flow.dir);
@@ -27860,7 +28229,7 @@ var PoolAuthFlow = class {
27860
28229
  }
27861
28230
  this.setStatus({
27862
28231
  phase: "failed",
27863
- message
28232
+ code
27864
28233
  });
27865
28234
  }
27866
28235
  async cancel() {
@@ -27948,6 +28317,85 @@ function normalizeStoredToken(raw) {
27948
28317
  };
27949
28318
  }
27950
28319
  /**
28320
+ * Parse a go-keyring payload (optionally "go-keyring-base64:" prefixed) into
28321
+ * a normalized StoredToken. Shared by the macOS and Linux keyring readers.
28322
+ */
28323
+ function parseGoKeyringPayload(raw) {
28324
+ let jsonStr = raw;
28325
+ if (raw.startsWith("go-keyring-base64:")) {
28326
+ const b64 = raw.slice(18);
28327
+ jsonStr = Buffer.from(b64, "base64").toString("utf8");
28328
+ }
28329
+ try {
28330
+ const tok = normalizeStoredToken(JSON.parse(jsonStr));
28331
+ return tok && (tok.accessToken || tok.refreshToken) ? tok : null;
28332
+ } catch {
28333
+ return null;
28334
+ }
28335
+ }
28336
+ /**
28337
+ * Reads the active primary Antigravity OAuth token from the Linux Secret
28338
+ * Service (GNOME Keyring / KDE Wallet via FreeDesktop secrets). agy 1.1.15+
28339
+ * on Linux stores primary credentials via go-keyring under service "gemini"
28340
+ * / username "antigravity" — the same JSON shape as the macOS Keychain.
28341
+ * GH #8: without this reader, Linux quota resolution always returned null.
28342
+ */
28343
+ function readLinuxSecretToken() {
28344
+ if (process.platform !== "linux") return null;
28345
+ try {
28346
+ const raw = execFileSync("secret-tool", [
28347
+ "lookup",
28348
+ "service",
28349
+ "gemini",
28350
+ "username",
28351
+ "antigravity"
28352
+ ], {
28353
+ encoding: "utf8",
28354
+ timeout: 3e3,
28355
+ stdio: [
28356
+ "ignore",
28357
+ "pipe",
28358
+ "ignore"
28359
+ ]
28360
+ }).trim();
28361
+ if (raw) {
28362
+ const tok = parseGoKeyringPayload(raw);
28363
+ if (tok) return tok;
28364
+ }
28365
+ } catch {}
28366
+ const pyScript = [
28367
+ "import dbus, sys",
28368
+ "try:",
28369
+ " bus = dbus.SessionBus()",
28370
+ " svc = bus.get_object(\"org.freedesktop.secrets\", \"/org/freedesktop/secrets\")",
28371
+ " iface = dbus.Interface(svc, \"org.freedesktop.Secret.Service\")",
28372
+ " sp = iface.OpenSession(\"plain\", \"\")[1]",
28373
+ " res = iface.SearchItems({\"service\": \"gemini\", \"username\": \"antigravity\"})",
28374
+ " if res and res[0]:",
28375
+ " item = bus.get_object(\"org.freedesktop.secrets\", res[0][0])",
28376
+ " sec = dbus.Interface(item, \"org.freedesktop.Secret.Item\").GetSecret(sp)",
28377
+ " sys.stdout.write(bytes(sec[2]).decode(\"utf-8\"))",
28378
+ "except Exception:",
28379
+ " pass"
28380
+ ].join("\n");
28381
+ for (const pyBin of ["python3", "python"]) try {
28382
+ const raw = execFileSync(pyBin, ["-c", pyScript], {
28383
+ encoding: "utf8",
28384
+ timeout: 4e3,
28385
+ stdio: [
28386
+ "ignore",
28387
+ "pipe",
28388
+ "ignore"
28389
+ ]
28390
+ }).trim();
28391
+ if (raw) {
28392
+ const tok = parseGoKeyringPayload(raw);
28393
+ if (tok) return tok;
28394
+ }
28395
+ } catch {}
28396
+ return null;
28397
+ }
28398
+ /**
27951
28399
  * Reads the active primary Antigravity OAuth token from the macOS Keychain.
27952
28400
  * agy 1.1.15+ on macOS stores primary credentials via go-keyring in the Keychain
27953
28401
  * under service "gemini" / account "antigravity" (base64-encoded JSON).
@@ -27972,12 +28420,7 @@ function readMacKeychainToken() {
27972
28420
  ]
27973
28421
  }).trim();
27974
28422
  if (!raw) return null;
27975
- let jsonStr = raw;
27976
- if (raw.startsWith("go-keyring-base64:")) {
27977
- const b64 = raw.slice(18);
27978
- jsonStr = Buffer.from(b64, "base64").toString("utf8");
27979
- }
27980
- return normalizeStoredToken(JSON.parse(jsonStr));
28423
+ return parseGoKeyringPayload(raw);
27981
28424
  } catch {
27982
28425
  return null;
27983
28426
  }
@@ -27997,7 +28440,9 @@ var QuotaService = class {
27997
28440
  * platforms) can substitute the reader without touching the real Keychain.
27998
28441
  */
27999
28442
  readSystemKeychainToken() {
28000
- return readMacKeychainToken();
28443
+ if (process.platform === "darwin") return readMacKeychainToken();
28444
+ if (process.platform === "linux") return readLinuxSecretToken();
28445
+ return null;
28001
28446
  }
28002
28447
  /**
28003
28448
  * Read the active token document and normalize it to a flat StoredToken.
@@ -28402,6 +28847,7 @@ function startMcpBridge(opts) {
28402
28847
  return new Promise((resolve, reject) => {
28403
28848
  server.once("error", reject);
28404
28849
  server.listen(0, "127.0.0.1", () => {
28850
+ server.unref();
28405
28851
  const addr = server.address();
28406
28852
  const port = typeof addr === "object" && addr !== null ? addr.port : 0;
28407
28853
  opts.log?.("mcp bridge listening on 127.0.0.1:" + port);
@@ -28605,32 +29051,36 @@ function apply(ctx, entryConfig = {}) {
28605
29051
  log("failed to persist override: " + String(e));
28606
29052
  }
28607
29053
  };
28608
- (async () => {
28609
- if (!getConfig().enabled) {
28610
- dormantReason = "disabled by config";
28611
- log("dormant: disabled by config");
28612
- return;
28613
- }
28614
- const currentBin = bin();
28615
- if (!currentBin) {
28616
- dormantReason = "agy binary not found — install via https://antigravity.google/docs/cli/install";
28617
- log("dormant: agy binary not found");
28618
- return;
28619
- }
28620
- try {
28621
- versionCache = parseVersion((await probeProcess(currentBin, ["--version"], 1e4)).stdout);
28622
- if (versionCache && compareVersions(versionCache, "1.1.8") < 0) {
28623
- dormantReason = "agy " + versionCache + " is older than 1.1.8 — run: agy update";
28624
- log("dormant: " + dormantReason);
29054
+ const bootProbeTimer = setTimeout(() => {
29055
+ (async () => {
29056
+ if (!getConfig().enabled) {
29057
+ dormantReason = "disabled by config";
29058
+ log("dormant: disabled by config");
28625
29059
  return;
28626
29060
  }
28627
- dormantReason = null;
28628
- log("agy detected: " + (versionCache ?? "unknown version"));
28629
- } catch {
28630
- log("version probe failed — continuing with fallback catalog");
28631
- }
28632
- await catalog.refreshIfNeeded().catch(() => void 0);
28633
- })();
29061
+ const currentBin = bin();
29062
+ if (!currentBin) {
29063
+ dormantReason = "agy binary not found — install via https://antigravity.google/docs/cli/install";
29064
+ log("dormant: agy binary not found");
29065
+ return;
29066
+ }
29067
+ try {
29068
+ versionCache = parseVersion((await probeProcess(currentBin, ["--version"], 1e4)).stdout);
29069
+ if (versionCache && compareVersions(versionCache, "1.1.8") < 0) {
29070
+ dormantReason = "agy " + versionCache + " is older than 1.1.8 — run: agy update";
29071
+ log("dormant: " + dormantReason);
29072
+ return;
29073
+ }
29074
+ dormantReason = null;
29075
+ log("agy detected: " + (versionCache ?? "unknown version"));
29076
+ } catch {
29077
+ log("version probe failed — continuing with fallback catalog");
29078
+ }
29079
+ await catalog.refreshIfNeeded().catch(() => void 0);
29080
+ })();
29081
+ }, 4e3);
29082
+ bootProbeTimer.unref?.();
29083
+ ctx.effect(() => () => clearTimeout(bootProbeTimer));
28634
29084
  if (getConfig().enabled) try {
28635
29085
  ctx.llm.registerAdapter([PROVIDER_ID], adapter);
28636
29086
  log("registered provider route: antigravity");
@@ -28859,7 +29309,8 @@ function apply(ctx, entryConfig = {}) {
28859
29309
  if (!code) {
28860
29310
  sendJson(res, 400, {
28861
29311
  ok: false,
28862
- error: "missing code"
29312
+ phase: "waiting",
29313
+ code: "missing_code"
28863
29314
  });
28864
29315
  return;
28865
29316
  }
@@ -28867,7 +29318,7 @@ function apply(ctx, entryConfig = {}) {
28867
29318
  sendJson(res, st.ok ? 200 : 400, {
28868
29319
  ok: st.ok,
28869
29320
  phase: st.phase,
28870
- message: st.message,
29321
+ code: st.code,
28871
29322
  pool: pool.getPoolData()
28872
29323
  });
28873
29324
  })();
@@ -28913,7 +29364,7 @@ function apply(ctx, entryConfig = {}) {
28913
29364
  "cmd.exe",
28914
29365
  "/k",
28915
29366
  `set "HOME=${acc.dir}" && set "USERPROFILE=${acc.dir}" && agy`
28916
- ], () => {});
29367
+ ], { windowsHide: true }, () => {});
28917
29368
  else execFile("x-terminal-emulator", ["-e", `sh -c "export HOME='${acc.dir}'; agy; exec sh"`], () => {});
28918
29369
  }
28919
29370
  sendJson(res, 200, {
@@ -28949,7 +29400,7 @@ function apply(ctx, entryConfig = {}) {
28949
29400
  "cmd.exe",
28950
29401
  "/k",
28951
29402
  `set "HOME=${acc.dir}" && set "USERPROFILE=${acc.dir}" && agy`
28952
- ], () => {});
29403
+ ], { windowsHide: true }, () => {});
28953
29404
  else execFile("x-terminal-emulator", ["-e", `sh -c "export HOME='${acc.dir}'; agy; exec sh"`], () => {});
28954
29405
  sendJson(res, 200, {
28955
29406
  ok: true,
@@ -29159,6 +29610,7 @@ function apply(ctx, entryConfig = {}) {
29159
29610
  const timer = setInterval(() => {
29160
29611
  sweepDir(mediaDir(), getConfig().mediaTtlMs).catch(() => void 0);
29161
29612
  }, Math.max(6e4, Math.min(getConfig().mediaTtlMs, 36e5)));
29613
+ timer.unref?.();
29162
29614
  return () => clearInterval(timer);
29163
29615
  });
29164
29616
  ctx.effect(() => {
@@ -29167,7 +29619,9 @@ function apply(ctx, entryConfig = {}) {
29167
29619
  quota.refreshAllQuotas().catch(() => void 0);
29168
29620
  };
29169
29621
  const boot = setTimeout(refresh, 5e3);
29622
+ boot.unref?.();
29170
29623
  const timer = setInterval(refresh, Math.max(6e4, getConfig().quotaPollIntervalMs));
29624
+ timer.unref?.();
29171
29625
  return () => {
29172
29626
  clearTimeout(boot);
29173
29627
  clearInterval(timer);
@@ -29177,6 +29631,7 @@ function apply(ctx, entryConfig = {}) {
29177
29631
  bridge: null,
29178
29632
  restore: null
29179
29633
  };
29634
+ let bridgeDisposed = false;
29180
29635
  const syncMcpBridge = () => {
29181
29636
  const cfg = getConfig();
29182
29637
  const want = cfg.mcpBridge && cfg.enabled;
@@ -29190,11 +29645,19 @@ function apply(ctx, entryConfig = {}) {
29190
29645
  allowlist: () => getConfig().mcpToolAllowlist,
29191
29646
  log
29192
29647
  });
29648
+ if (bridgeDisposed) {
29649
+ await bridge.close();
29650
+ return;
29651
+ }
29193
29652
  bridgeState.bridge = bridge;
29194
29653
  const root = cfg.workspaceRoot !== "" ? cfg.workspaceRoot : process.cwd();
29195
29654
  bridgeState.restore = writeMcpConfig(root, bridge);
29196
29655
  log("mcp bridge ready at " + bridge.url + (toolsSvc ? "" : " (tools service not yet available)"));
29197
29656
  } catch (e) {
29657
+ bridgeState.restore?.();
29658
+ await bridgeState.bridge?.close();
29659
+ bridgeState.bridge = null;
29660
+ bridgeState.restore = null;
29198
29661
  log("mcp bridge failed to start: " + String(e));
29199
29662
  }
29200
29663
  })();
@@ -29207,13 +29670,15 @@ function apply(ctx, entryConfig = {}) {
29207
29670
  };
29208
29671
  syncMcpBridge();
29209
29672
  ctx.effect(() => {
29210
- auth.dispose();
29211
- poolAuth.cancel();
29212
- if (askToolDispose.current !== null) askToolDispose.current();
29213
- if (mirrorToolDispose.current !== null) mirrorToolDispose.current();
29214
- bridgeState.restore?.();
29215
- bridgeState.bridge?.close();
29216
- return () => void 0;
29673
+ return () => {
29674
+ bridgeDisposed = true;
29675
+ auth.dispose();
29676
+ poolAuth.cancel();
29677
+ if (askToolDispose.current !== null) askToolDispose.current();
29678
+ if (mirrorToolDispose.current !== null) mirrorToolDispose.current();
29679
+ bridgeState.restore?.();
29680
+ bridgeState.bridge?.close();
29681
+ };
29217
29682
  });
29218
29683
  }
29219
29684
  //#endregion