dsh-agy-link 0.4.32 → 0.4.34

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 CHANGED
@@ -1,5 +1,28 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.4.34 (2026-09-18)
4
+
5
+ ### English
6
+
7
+ - **UI: cleaner tool cards in Code Mode.** When DSH registers `agy_tool`, the bridge now emits native tool-call blocks instead of `run_code` wrappers. Code Mode titles use a human preview (`$ ls · run_command`) instead of `replay agy tool step N`. A `run_code` toolview renders Antigravity cards when the program is still a mirror wrapper.
8
+ - **UI: less thinking spam.** Banner-only `[agy thinking turn · N tokens]` chips (no prose) emit at most once per run; turns that extract thought prose still show full reasoning.
9
+
10
+ ### 中文 (Chinese)
11
+
12
+ - **界面:** Code Mode 下工具卡片更干净;有 prose 的思考仍完整展示,无正文的 token 横幅每轮最多一条。
13
+
14
+ ## 0.4.33 (2026-09-18)
15
+
16
+ ### English
17
+
18
+ - **Fix: thinking / tool-args invisible for pool accounts.** agy conversation SQLite DBs for isolated accounts live under `~/.dsh/agy-accounts/<id>/.gemini/antigravity-cli/conversations`, not system `~/.gemini`. The reader now searches the run's account home, `GEMINI_CLI_HOME`, system home, and pool account dirs.
19
+ - **Fix: `/agy status` could hang the command UI.** Auth probe is now bounded (8s) so command results always return (issue #29 symptom).
20
+
21
+ ### 中文 (Chinese)
22
+
23
+ - **修复:号池隔离账号看不到思维链/工具参数** — 会话库在账号自己的 HOME 下,读取时会搜索账号目录。
24
+ - **修复:`/agy status` 可能挂起导致命令无输出** — 探测加 8s 超时。
25
+
3
26
  ## 0.4.32 (2026-09-17)
4
27
 
5
28
  ### English
package/dist/client.js CHANGED
@@ -664,13 +664,83 @@ window.__ModuleLoader__.load({
664
664
  inspect
665
665
  }))] : []);
666
666
  }
667
+ /** Extract the agy mirror cursor (and tool name) from a run_code program. */
668
+ function parseAgyMirrorFromCode(argsRaw) {
669
+ try {
670
+ const parsed = JSON.parse(argsRaw);
671
+ const code = typeof parsed?.code === "string" ? parsed.code : "";
672
+ const m = /tools\['agy_tool'\]\((\{.*?"step":\d+\})\)/.exec(code);
673
+ if (!m) return null;
674
+ const v = JSON.parse(m[1]);
675
+ if (typeof v.run !== "string" || typeof v.step !== "number") return null;
676
+ const tm = /replay recorded agy tool step \d+ \(([^)]+)\)/.exec(code);
677
+ return {
678
+ run: v.run,
679
+ step: v.step,
680
+ tool: tm?.[1]
681
+ };
682
+ } catch {}
683
+ return null;
684
+ }
685
+ /**
686
+ * run_code toolview: when the program is an agy mirror wrapper, render the
687
+ * native Antigravity card instead of a raw code row. Other run_code calls
688
+ * fall through to the host renderer via a minimal wrapper that still shows
689
+ * something useful.
690
+ */
691
+ function AgyRunCodeToolView(props) {
692
+ const block = props?.block;
693
+ const raw = block !== void 0 ? parsedArgsRaw(block) : "{}";
694
+ const mirror = parseAgyMirrorFromCode(raw);
695
+ if (mirror === null) {
696
+ let code = "";
697
+ try {
698
+ const parsed = JSON.parse(raw);
699
+ code = typeof parsed?.code === "string" ? parsed.code : raw;
700
+ } catch {
701
+ code = raw;
702
+ }
703
+ return hx("div", { className: cls("agy-tv-root") }, hx("div", { className: "agy-tv-header" }, hx("span", { className: "agy-tv-title" }, "run_code"), hx("span", { className: "agy-tv-badge" }, "code")), hx("pre", {
704
+ className: "agy-tv-pre",
705
+ style: {
706
+ margin: 0,
707
+ whiteSpace: "pre-wrap",
708
+ fontSize: "12px"
709
+ }
710
+ }, code.slice(0, 400)));
711
+ }
712
+ return AgyMirrorToolView({ block: {
713
+ ...block,
714
+ call: {
715
+ ...block?.call ?? {},
716
+ name: "agy_tool",
717
+ argsRaw: JSON.stringify({
718
+ run: mirror.run,
719
+ step: mirror.step,
720
+ ...mirror.tool !== void 0 ? { tool: mirror.tool } : {}
721
+ })
722
+ }
723
+ } });
724
+ }
667
725
  function installAgyToolView(ctx) {
668
- ctx.slots.inject("tool.call.toolview", () => ctx.slots.register({
669
- name: "tool.call.toolview",
670
- key: "agy_tool",
671
- id: "agy-tool-view",
672
- label: "Antigravity tool"
673
- }, AgyMirrorToolView));
726
+ ctx.slots.inject("tool.call.toolview", () => {
727
+ const d1 = ctx.slots.register({
728
+ name: "tool.call.toolview",
729
+ key: "agy_tool",
730
+ id: "agy-tool-view",
731
+ label: "Antigravity tool"
732
+ }, AgyMirrorToolView);
733
+ const d2 = ctx.slots.register({
734
+ name: "tool.call.toolview",
735
+ key: "run_code",
736
+ id: "agy-run-code-view",
737
+ label: "Antigravity (code mode)"
738
+ }, AgyRunCodeToolView);
739
+ return () => {
740
+ d2();
741
+ d1();
742
+ };
743
+ });
674
744
  }
675
745
  const ROW_CSS = `
676
746
  /* Container */
package/dist/index.js CHANGED
@@ -400,6 +400,8 @@ var RunRecording = class {
400
400
  fullArgs = null;
401
401
  /** DB-resolved thoughts keyed by event index (set during span driving). */
402
402
  thoughts = null;
403
+ /** Isolated pool-account HOME used for this run's agy conversation DB. */
404
+ accountHome = void 0;
403
405
  /**
404
406
  * Set by the adapter right after spawn. A mid-turn user steer makes DSH
405
407
  * open a NEW stream() call for the same session while this run's process
@@ -648,16 +650,37 @@ function getGitHeadContent(filePath, execFn = execFileSync) {
648
650
  * whose single statement calls the registered mirror tool — the inner
649
651
  * dispatch is what renders the native tool card.
650
652
  */
651
- function buildMirrorRunCode(runId, eventIndex, toolName) {
653
+ function buildMirrorRunCode(runId, eventIndex, toolName, brief) {
652
654
  const invocation = JSON.stringify({
653
655
  run: runId,
654
656
  step: eventIndex
655
657
  });
656
658
  return {
657
659
  code: "// dsh-agy-link mirror: replay recorded agy tool step " + eventIndex + " (" + toolName + ")\nreturn await tools['agy_tool'](" + invocation + ")",
658
- description: "replay agy tool step " + eventIndex + " · " + toolName
660
+ description: brief !== void 0 && brief !== "" ? brief + " · " + toolName : toolName
659
661
  };
660
662
  }
663
+ /** Short human preview for a recorded tool step (used in Code Mode titles). */
664
+ function toolStepBrief(toolName, args) {
665
+ const a = args !== null && typeof args === "object" ? args : {};
666
+ const pickStr = (...keys) => {
667
+ for (const k of keys) {
668
+ const v = a[k];
669
+ if (typeof v === "string" && v.trim() !== "") return v.trim();
670
+ }
671
+ return "";
672
+ };
673
+ if (toolName === "run_command" || toolName === "execute_command") {
674
+ const cmd = pickStr("Command", "command", "Cmd", "cmd");
675
+ return cmd !== "" ? "$ " + cmd.slice(0, 80) : "run command";
676
+ }
677
+ if (toolName === "view_file" || toolName === "read_file") return "read " + (pickStr("Path", "path", "TargetFile", "File") || "file");
678
+ if (toolName === "write_to_file" || toolName === "create_file") return "write " + (pickStr("Path", "path", "TargetFile", "File") || "file");
679
+ if (toolName === "replace_file_content" || toolName === "edit_file") return "edit " + (pickStr("Path", "path", "TargetFile", "File") || "file");
680
+ if (toolName === "grep_search" || toolName === "search") return "search " + (pickStr("Query", "query", "SearchPattern") || "");
681
+ if (toolName === "list_dir" || toolName === "list_directory") return "ls " + (pickStr("Path", "path", "DirectoryPath") || ".");
682
+ return toolName;
683
+ }
661
684
  /** agy serializes some tool args as a JSON string; presenters get an object. */
662
685
  function toolInput(args) {
663
686
  const raw = args.input;
@@ -965,6 +988,7 @@ var EventMapper = class {
965
988
  emittedByKey = /* @__PURE__ */ new Map();
966
989
  announcedTools = /* @__PURE__ */ new Set();
967
990
  thinkingAnnounced = /* @__PURE__ */ new Set();
991
+ bannerOnlyThinkingEmitted = false;
968
992
  sawTextStep;
969
993
  finished = false;
970
994
  constructor(opts) {
@@ -1029,6 +1053,7 @@ var EventMapper = class {
1029
1053
  const text = this.opts.resolvedThoughts?.get(absIndex);
1030
1054
  const hasText = text !== void 0 && text.trim() !== "";
1031
1055
  if (!hasText && thoughtTokens <= 0) return;
1056
+ if (!hasText && this.bannerOnlyThinkingEmitted) return;
1032
1057
  yield* this.ensureBlock("reasoning");
1033
1058
  const banner = thoughtTokens > 0 ? "[agy thinking turn · " + thoughtTokens + " thinking tokens]" : "[agy thinking turn]";
1034
1059
  if (hasText) {
@@ -1036,6 +1061,7 @@ var EventMapper = class {
1036
1061
  const d = this.appendDelta(combined);
1037
1062
  if (d) yield d;
1038
1063
  } else {
1064
+ this.bannerOnlyThinkingEmitted = true;
1039
1065
  const d = this.appendDelta(`${banner}\n`);
1040
1066
  if (d) yield d;
1041
1067
  }
@@ -1123,7 +1149,7 @@ var EventMapper = class {
1123
1149
  ...fullArgs,
1124
1150
  ...typeof ev.tool.args === "object" ? ev.tool.args : {}
1125
1151
  } : ev.tool.args;
1126
- const argumentsJson = useCode ? JSON.stringify(buildMirrorRunCode(this.opts.runId, absIndex, ev.tool.name)) : JSON.stringify({
1152
+ const argumentsJson = useCode ? JSON.stringify(buildMirrorRunCode(this.opts.runId, absIndex, ev.tool.name, toolStepBrief(ev.tool.name, effectiveArgs))) : JSON.stringify({
1127
1153
  run: this.opts.runId,
1128
1154
  step: absIndex,
1129
1155
  tool: ev.tool.name,
@@ -2228,6 +2254,43 @@ async function probeProcess(bin, args, timeoutMs = 3e4, signal, env) {
2228
2254
  //#region src/host/agy-db.ts
2229
2255
  const execFileAsync = promisify(execFile);
2230
2256
  let AGY_DB_DIR = join(homedir(), ".gemini", "antigravity-cli", "conversations");
2257
+ /**
2258
+ * Candidate conversations directories for a conversation id.
2259
+ * Pool/isolated accounts write agy state under their own HOME
2260
+ * (~/.dsh/agy-accounts/<id>/.gemini/...), NOT the system ~/.gemini
2261
+ * (issue: thinking/tool-args invisible when using isolated accounts).
2262
+ */
2263
+ function conversationsDirCandidates(accountHome) {
2264
+ const dirs = [];
2265
+ const push = (d) => {
2266
+ if (!dirs.includes(d)) dirs.push(d);
2267
+ };
2268
+ if (accountHome !== void 0 && accountHome !== "") push(join(accountHome, ".gemini", "antigravity-cli", "conversations"));
2269
+ const gch = process.env.GEMINI_CLI_HOME;
2270
+ if (gch !== void 0 && gch !== "") push(join(gch, "antigravity-cli", "conversations"));
2271
+ push(AGY_DB_DIR);
2272
+ push(join(homedir(), ".gemini", "antigravity-cli", "conversations"));
2273
+ const poolBase = join(homedir(), ".dsh", "agy-accounts");
2274
+ try {
2275
+ for (const ent of readdirSync(poolBase)) {
2276
+ if (ent.startsWith(".")) continue;
2277
+ push(join(poolBase, ent, ".gemini", "antigravity-cli", "conversations"));
2278
+ }
2279
+ } catch {}
2280
+ return dirs;
2281
+ }
2282
+ /** Locate an existing conversation DB across system + pool homes. */
2283
+ function findConversationDb(conversationId, accountHome) {
2284
+ if (!isSafeConversationId(conversationId)) return null;
2285
+ const name = `${conversationId}.db`;
2286
+ for (const dir of conversationsDirCandidates(accountHome)) {
2287
+ const p = join(dir, name);
2288
+ try {
2289
+ if (existsSync(p)) return p;
2290
+ } catch {}
2291
+ }
2292
+ return null;
2293
+ }
2231
2294
  const TOOL_STEP_TYPES = /* @__PURE__ */ new Set([
2232
2295
  5,
2233
2296
  7,
@@ -2333,7 +2396,7 @@ function extractStepThoughts(payload) {
2333
2396
  * Copy the agy SQLite DB to a temp path (avoiding WAL lock issues), then
2334
2397
  * query tool and thought step payloads via sqlite3 CLI.
2335
2398
  */
2336
- async function loadAgyDbData(conversationId) {
2399
+ async function loadAgyDbData(conversationId, accountHome) {
2337
2400
  const steps = /* @__PURE__ */ new Map();
2338
2401
  const thoughts = /* @__PURE__ */ new Map();
2339
2402
  const seenSteps = /* @__PURE__ */ new Set();
@@ -2342,20 +2405,12 @@ async function loadAgyDbData(conversationId) {
2342
2405
  thoughts,
2343
2406
  seenSteps
2344
2407
  };
2345
- const dbPath = join(AGY_DB_DIR, `${conversationId}.db`);
2346
- try {
2347
- if ((await stat(dbPath)).size === 0) return {
2348
- steps,
2349
- thoughts,
2350
- seenSteps
2351
- };
2352
- } catch {
2353
- return {
2354
- steps,
2355
- thoughts,
2356
- seenSteps
2357
- };
2358
- }
2408
+ const dbPath = findConversationDb(conversationId, accountHome);
2409
+ if (dbPath === null) return {
2410
+ steps,
2411
+ thoughts,
2412
+ seenSteps
2413
+ };
2359
2414
  const tmpDb = join(tmpdir(), `agy-db-${conversationId.slice(0, 8)}-${Date.now()}.db`);
2360
2415
  try {
2361
2416
  await copyFile(dbPath, tmpDb);
@@ -2531,7 +2586,7 @@ function scanFallbackJson(payload) {
2531
2586
  * Results are cached per conversationId for up to 5 minutes to avoid
2532
2587
  * repeated DB reads within the same session.
2533
2588
  */
2534
- async function readFullToolArgs(conversationId, stepIndex) {
2589
+ async function readFullToolArgs(conversationId, stepIndex, accountHome) {
2535
2590
  if (!isSafeConversationId(conversationId)) return null;
2536
2591
  const now = Date.now();
2537
2592
  if (cache?.conversationId === conversationId && cache.loaded && now - cacheTime < MAX_CACHE_AGE_MS) {
@@ -2539,7 +2594,7 @@ async function readFullToolArgs(conversationId, stepIndex) {
2539
2594
  if (cached !== void 0) return cached;
2540
2595
  if (cache.checkedSteps.has(stepIndex)) return null;
2541
2596
  }
2542
- const data = await loadAgyDbData(conversationId);
2597
+ const data = await loadAgyDbData(conversationId, accountHome);
2543
2598
  cache = {
2544
2599
  conversationId,
2545
2600
  steps: data.steps,
@@ -2555,7 +2610,7 @@ async function readFullToolArgs(conversationId, stepIndex) {
2555
2610
  * conversation database. Returns null when the DB is unavailable or the
2556
2611
  * step does not contain thought text.
2557
2612
  */
2558
- async function readStepThoughts(conversationId, stepIndex) {
2613
+ async function readStepThoughts(conversationId, stepIndex, accountHome) {
2559
2614
  if (!isSafeConversationId(conversationId)) return null;
2560
2615
  const now = Date.now();
2561
2616
  if (cache?.conversationId === conversationId && cache.loaded && now - cacheTime < MAX_CACHE_AGE_MS) {
@@ -2563,7 +2618,7 @@ async function readStepThoughts(conversationId, stepIndex) {
2563
2618
  if (cached !== void 0) return cached;
2564
2619
  if (cache.checkedSteps.has(stepIndex)) return null;
2565
2620
  }
2566
- const data = await loadAgyDbData(conversationId);
2621
+ const data = await loadAgyDbData(conversationId, accountHome);
2567
2622
  cache = {
2568
2623
  conversationId,
2569
2624
  steps: data.steps,
@@ -2793,7 +2848,8 @@ var AgyAdapter = class extends LlmAdapter {
2793
2848
  if (!bin) throw new LlmError("agy binary not found on PATH — install it via https://antigravity.google/docs/cli/install", Err.AGY_NOT_INSTALLED);
2794
2849
  const isAux = options.purpose === "compaction" || options.purpose === "session-title";
2795
2850
  if (isAux && !cfg.allowAuxiliary) throw new LlmError("auxiliary calls are disabled for the antigravity route (allowAuxiliary: false)", Err.AUX_DISABLED);
2796
- const isCodeMode = options.tools ? options.tools.some((t) => t.name === "run_code") : false;
2851
+ const toolNames = new Set((options.tools ?? []).map((t) => t.name));
2852
+ const isCodeMode = toolNames.has("run_code") && !toolNames.has("agy_tool");
2797
2853
  const sessionKey = options.sessionId !== void 0 ? String(options.sessionId) : "";
2798
2854
  let workspaceRoot = cfg.workspaceRoot;
2799
2855
  if (workspaceRoot === "") {
@@ -2964,6 +3020,7 @@ var AgyAdapter = class extends LlmAdapter {
2964
3020
  }
2965
3021
  const before = snapshotConversations();
2966
3022
  const rec = this.deps.runs.create();
3023
+ rec.accountHome = account && account.dir ? account.dir : void 0;
2967
3024
  const parser = new StreamJsonParser();
2968
3025
  this.deps.onParser?.(parser);
2969
3026
  let streamCid = null;
@@ -3179,10 +3236,10 @@ var AgyAdapter = class extends LlmAdapter {
3179
3236
  const activeConvId = rec.conversationId ?? (typeof rawObj?.conversation_id === "string" ? rawObj.conversation_id : null) ?? (typeof stepUpdate?.conversation_id === "string" ? stepUpdate.conversation_id : null);
3180
3237
  const stepIdx = parseInt(ev.stepKey, 10);
3181
3238
  if (activeConvId !== null && Number.isFinite(stepIdx)) try {
3182
- let th = await readStepThoughts(activeConvId, stepIdx);
3239
+ let th = await readStepThoughts(activeConvId, stepIdx, rec.accountHome);
3183
3240
  if (th === null && (ev.state === "DONE" || (ev.usage?.thinking_tokens ?? 0) > 0 || ev.stepKind === "thinking")) {
3184
3241
  await new Promise((r) => setTimeout(r, 50));
3185
- th = await readStepThoughts(activeConvId, stepIdx);
3242
+ th = await readStepThoughts(activeConvId, stepIdx, rec.accountHome);
3186
3243
  }
3187
3244
  if (th !== null && th.trim() !== "") {
3188
3245
  resolvedThoughts.set(i, th);
@@ -3196,10 +3253,10 @@ var AgyAdapter = class extends LlmAdapter {
3196
3253
  const activeConvId = rec.conversationId ?? (typeof rawObj?.conversation_id === "string" ? rawObj.conversation_id : null) ?? (typeof stepUpdate?.conversation_id === "string" ? stepUpdate.conversation_id : null);
3197
3254
  const stepIdx = parseInt(ev.stepKey, 10);
3198
3255
  if (activeConvId !== null && Number.isFinite(stepIdx)) try {
3199
- let full = await readFullToolArgs(activeConvId, stepIdx);
3256
+ let full = await readFullToolArgs(activeConvId, stepIdx, rec.accountHome);
3200
3257
  if (full === null) {
3201
3258
  await new Promise((r) => setTimeout(r, 50));
3202
- full = await readFullToolArgs(activeConvId, stepIdx);
3259
+ full = await readFullToolArgs(activeConvId, stepIdx, rec.accountHome);
3203
3260
  }
3204
3261
  if (full !== null && full.args !== void 0) {
3205
3262
  resolved.set(i, full.args);
@@ -27685,7 +27742,7 @@ async function renderStatus(deps) {
27685
27742
  const cfg = deps.cfg();
27686
27743
  const bin = deps.bin();
27687
27744
  const authHelper = deps.auth();
27688
- const auth = authHelper ? await authHelper.resolvedStatus() : void 0;
27745
+ const auth = authHelper ? await Promise.race([authHelper.resolvedStatus(), new Promise((r) => setTimeout(() => r(void 0), 8e3))]) : void 0;
27689
27746
  const cat = deps.catalog().get();
27690
27747
  const bindings = Object.keys(deps.store().all()).length;
27691
27748
  const last = deps.lastRun();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-agy-link",
3
- "version": "0.4.32",
3
+ "version": "0.4.34",
4
4
  "description": "Google Antigravity (agy CLI) models for DeepSeek Harness — stream Gemini/Claude/GPT-OSS subscriptions into DSH with thinking, tool activity, token usage and in-GUI Google OAuth login.",
5
5
  "type": "module",
6
6
  "license": "MIT",