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/CHANGELOG.md +86 -0
- package/README.md +22 -3
- package/dist/client.js +935 -77
- package/dist/index.js +546 -81
- package/docs/HEADLESS-PERMISSIONS.md +32 -0
- package/docs/KNOWN-GAPS.md +5 -0
- package/docs/README.en.md +67 -0
- package/docs/README.es.md +67 -0
- package/docs/README.pt-BR.md +67 -0
- package/docs/SECURITY-NOTES.md +20 -3
- package/docs/SPEC-triage-2026-09.md +239 -0
- package/package.json +8 -2
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 ?? {}
|
|
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
|
-
...
|
|
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 (
|
|
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("-
|
|
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
|
|
2786
|
+
const activeModelForArgs = activeModel === "" ? cfg.defaultModel : activeModel;
|
|
2787
|
+
const argsBase = this.buildArgs({
|
|
2501
2788
|
prompt,
|
|
2502
|
-
model:
|
|
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
|
-
|
|
2647
|
-
|
|
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
|
-
|
|
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$
|
|
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$
|
|
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
|
-
], {
|
|
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(
|
|
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(
|
|
27422
|
+
return ok("Logged in to Antigravity.");
|
|
27068
27423
|
}
|
|
27069
|
-
return err(st.
|
|
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.
|
|
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
|
|
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 ||
|
|
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 ||
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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("
|
|
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
|
-
|
|
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
|
-
|
|
28216
|
+
code: "account_activated"
|
|
27848
28217
|
});
|
|
27849
28218
|
} catch (err) {
|
|
27850
|
-
this.fail(
|
|
28219
|
+
this.fail("exchange_failed", err);
|
|
27851
28220
|
}
|
|
27852
28221
|
}
|
|
27853
|
-
fail(
|
|
28222
|
+
fail(code, detail) {
|
|
27854
28223
|
const flow = this.flow;
|
|
27855
|
-
this.log(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
28609
|
-
|
|
28610
|
-
|
|
28611
|
-
|
|
28612
|
-
|
|
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
|
-
|
|
28628
|
-
|
|
28629
|
-
|
|
28630
|
-
|
|
28631
|
-
|
|
28632
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
29211
|
-
|
|
29212
|
-
|
|
29213
|
-
|
|
29214
|
-
|
|
29215
|
-
|
|
29216
|
-
|
|
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
|