dsh-agy-link 0.4.33 → 0.4.35

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,26 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.4.35 (2026-09-18)
4
+
5
+ ### English
6
+
7
+ - **Reasoning UI: only show thinking when there is real content.** Empty `[agy thinking turn · N thinking tokens]` chips no longer spam tool-heavy turns. When agy stores only `toolAction`/`toolSummary` (common), those become the visible thought line (e.g. `Searching the web — Search Wo Tianyu profile`). Full CoT prose still renders when present in the conversation DB.
8
+
9
+ ### 中文 (Chinese)
10
+
11
+ - **思考 UI:** 无正文时不再刷 token 横幅;工具步骤优先展示 `toolAction`/`toolSummary` 作为意图说明;完整思维链仍按 DB 正文展示。
12
+
13
+ ## 0.4.34 (2026-09-18)
14
+
15
+ ### English
16
+
17
+ - **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.
18
+ - **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.
19
+
20
+ ### 中文 (Chinese)
21
+
22
+ - **界面:** Code Mode 下工具卡片更干净;有 prose 的思考仍完整展示,无正文的 token 横幅每轮最多一条。
23
+
3
24
  ## 0.4.33 (2026-09-18)
4
25
 
5
26
  ### 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
@@ -650,16 +650,37 @@ function getGitHeadContent(filePath, execFn = execFileSync) {
650
650
  * whose single statement calls the registered mirror tool — the inner
651
651
  * dispatch is what renders the native tool card.
652
652
  */
653
- function buildMirrorRunCode(runId, eventIndex, toolName) {
653
+ function buildMirrorRunCode(runId, eventIndex, toolName, brief) {
654
654
  const invocation = JSON.stringify({
655
655
  run: runId,
656
656
  step: eventIndex
657
657
  });
658
658
  return {
659
659
  code: "// dsh-agy-link mirror: replay recorded agy tool step " + eventIndex + " (" + toolName + ")\nreturn await tools['agy_tool'](" + invocation + ")",
660
- description: "replay agy tool step " + eventIndex + " · " + toolName
660
+ description: brief !== void 0 && brief !== "" ? brief + " · " + toolName : toolName
661
661
  };
662
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
+ }
663
684
  /** agy serializes some tool args as a JSON string; presenters get an object. */
664
685
  function toolInput(args) {
665
686
  const raw = args.input;
@@ -967,6 +988,7 @@ var EventMapper = class {
967
988
  emittedByKey = /* @__PURE__ */ new Map();
968
989
  announcedTools = /* @__PURE__ */ new Set();
969
990
  thinkingAnnounced = /* @__PURE__ */ new Set();
991
+ bannerOnlyThinkingEmitted = false;
970
992
  sawTextStep;
971
993
  finished = false;
972
994
  constructor(opts) {
@@ -1029,18 +1051,11 @@ var EventMapper = class {
1029
1051
  */
1030
1052
  *emitThinking(absIndex, thoughtTokens) {
1031
1053
  const text = this.opts.resolvedThoughts?.get(absIndex);
1032
- const hasText = text !== void 0 && text.trim() !== "";
1033
- if (!hasText && thoughtTokens <= 0) return;
1054
+ if (!(text !== void 0 && text.trim() !== "")) return;
1034
1055
  yield* this.ensureBlock("reasoning");
1035
- const banner = thoughtTokens > 0 ? "[agy thinking turn · " + thoughtTokens + " thinking tokens]" : "[agy thinking turn]";
1036
- if (hasText) {
1037
- const combined = `${banner} ${text.trim()}\n`;
1038
- const d = this.appendDelta(combined);
1039
- if (d) yield d;
1040
- } else {
1041
- const d = this.appendDelta(`${banner}\n`);
1042
- if (d) yield d;
1043
- }
1056
+ const combined = `${thoughtTokens > 0 ? "[agy thinking turn · " + thoughtTokens + " thinking tokens]" : "[agy thinking turn]"} ${text.trim()}\n`;
1057
+ const d = this.appendDelta(combined);
1058
+ if (d) yield d;
1044
1059
  }
1045
1060
  /**
1046
1061
  * Map one event. `absIndex` is the event's position in the run recording;
@@ -1125,7 +1140,7 @@ var EventMapper = class {
1125
1140
  ...fullArgs,
1126
1141
  ...typeof ev.tool.args === "object" ? ev.tool.args : {}
1127
1142
  } : ev.tool.args;
1128
- const argumentsJson = useCode ? JSON.stringify(buildMirrorRunCode(this.opts.runId, absIndex, ev.tool.name)) : JSON.stringify({
1143
+ const argumentsJson = useCode ? JSON.stringify(buildMirrorRunCode(this.opts.runId, absIndex, ev.tool.name, toolStepBrief(ev.tool.name, effectiveArgs))) : JSON.stringify({
1129
1144
  run: this.opts.runId,
1130
1145
  step: absIndex,
1131
1146
  tool: ev.tool.name,
@@ -2307,65 +2322,111 @@ function readVarint(b, offset) {
2307
2322
  /**
2308
2323
  * Extract model thought text from a protobuf step_payload.
2309
2324
  *
2310
- * In agy SQLite databases (steps table, step_type = 15 agent_response),
2311
- * the thought text is stored inside Protobuf field 20 (response body) ->
2312
- * sub-field 3 (length-delimited UTF-8 string).
2325
+ * Primary: field 20 -> sub-field 3 (verified on simple print-mode turns).
2326
+ * Fallback: longest non-JSON prose string, then toolAction/toolSummary
2327
+ * embedded in tool-arg JSON (tool-heavy agent turns often store only those).
2313
2328
  */
2314
2329
  function extractStepThoughts(payload) {
2315
- let pos = 0;
2316
- while (pos < payload.length) {
2317
- const res = readVarint(payload, pos);
2318
- if (res === null) break;
2319
- pos = res.next;
2320
- const tag = res.val;
2321
- const wireType = tag & 7;
2322
- const fieldNum = tag >>> 3;
2323
- if (wireType === 2) {
2324
- const lenRes = readVarint(payload, pos);
2325
- if (lenRes === null) break;
2326
- pos = lenRes.next;
2327
- const len = lenRes.val;
2328
- if (pos + len > payload.length) break;
2329
- const slice = payload.subarray(pos, pos + len);
2330
- pos += len;
2331
- if (fieldNum === 20) {
2332
- let p20 = 0;
2333
- while (p20 < slice.length) {
2334
- const t20Res = readVarint(slice, p20);
2335
- if (t20Res === null) break;
2336
- p20 = t20Res.next;
2337
- const t20 = t20Res.val;
2338
- const w20 = t20 & 7;
2339
- const f20 = t20 >>> 3;
2340
- if (w20 === 2) {
2341
- const l20Res = readVarint(slice, p20);
2342
- if (l20Res === null) break;
2343
- p20 = l20Res.next;
2344
- const l20 = l20Res.val;
2345
- if (p20 + l20 > slice.length) break;
2346
- const s20 = slice.subarray(p20, p20 + l20);
2347
- p20 += l20;
2348
- if (f20 === 3) {
2349
- const str = s20.toString("utf-8");
2350
- if (str.length > 0) return str;
2351
- }
2352
- } else if (w20 === 0) {
2353
- const v20Res = readVarint(slice, p20);
2354
- if (v20Res === null) break;
2355
- p20 = v20Res.next;
2356
- } else if (w20 === 1) p20 += 8;
2357
- else if (w20 === 5) p20 += 4;
2358
- else break;
2330
+ const proseCandidates = [];
2331
+ const actionCandidates = [];
2332
+ const consider = (str) => {
2333
+ const t = str.trim();
2334
+ if (t.length < 8) return;
2335
+ if (/^SYSTEM ROLE/i.test(t)) return;
2336
+ if (t.startsWith("/plan ") || t.includes("<PLAN>The user is requesting that you think")) return;
2337
+ if (/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(t)) return;
2338
+ if (/^bot-|^call_/.test(t)) return;
2339
+ const act = /"toolAction"\s*:\s*"([^"]{3,200})"/.exec(t);
2340
+ const sum = /"toolSummary"\s*:\s*"([^"]{3,200})"/.exec(t);
2341
+ if (act || sum) {
2342
+ const line = [act?.[1] ?? "", sum?.[1] ?? ""].filter((s) => s !== "").join(" — ");
2343
+ if (line !== "") actionCandidates.push(line);
2344
+ }
2345
+ const looksJson = t.startsWith("{") || t.startsWith("[");
2346
+ if (!looksJson && t.length >= 24 && t.length <= 4e3 && /[a-zA-Z一-鿿]/.test(t) && !/^[0-9a-f-]{30,}$/i.test(t)) {
2347
+ if ((t.match(/[.!?]|,|。| /g) ?? []).length >= 2 || /[a-zA-Z]{4,}\s+[a-zA-Z]{3,}/.test(t)) proseCandidates.push(t);
2348
+ return;
2349
+ }
2350
+ if (looksJson) try {
2351
+ const obj = JSON.parse(t);
2352
+ const line = [typeof obj.toolAction === "string" ? obj.toolAction.trim() : "", typeof obj.toolSummary === "string" ? obj.toolSummary.trim() : ""].filter((s) => s !== "").join(" — ");
2353
+ if (line !== "") actionCandidates.push(line);
2354
+ } catch {}
2355
+ };
2356
+ const walk = (buf, depth) => {
2357
+ if (depth > 3) return;
2358
+ let pos = 0;
2359
+ while (pos < buf.length) {
2360
+ const res = readVarint(buf, pos);
2361
+ if (res === null) break;
2362
+ pos = res.next;
2363
+ const tag = res.val;
2364
+ const wireType = tag & 7;
2365
+ const fieldNum = tag >>> 3;
2366
+ if (wireType === 2) {
2367
+ const lenRes = readVarint(buf, pos);
2368
+ if (lenRes === null) break;
2369
+ pos = lenRes.next;
2370
+ const len = lenRes.val;
2371
+ if (pos + len > buf.length) break;
2372
+ const slice = buf.subarray(pos, pos + len);
2373
+ pos += len;
2374
+ if (fieldNum === 20) {
2375
+ let p20 = 0;
2376
+ while (p20 < slice.length) {
2377
+ const t20Res = readVarint(slice, p20);
2378
+ if (t20Res === null) break;
2379
+ p20 = t20Res.next;
2380
+ const t20 = t20Res.val;
2381
+ const w20 = t20 & 7;
2382
+ const f20 = t20 >>> 3;
2383
+ if (w20 === 2) {
2384
+ const l20Res = readVarint(slice, p20);
2385
+ if (l20Res === null) break;
2386
+ p20 = l20Res.next;
2387
+ const l20 = l20Res.val;
2388
+ if (p20 + l20 > slice.length) break;
2389
+ const s20 = slice.subarray(p20, p20 + l20);
2390
+ p20 += l20;
2391
+ if (f20 === 3) {
2392
+ const str = s20.toString("utf-8");
2393
+ if (str.trim() !== "") {
2394
+ consider(str);
2395
+ if (!str.trim().startsWith("{") && str.trim().length >= 24) proseCandidates.unshift(str.trim());
2396
+ }
2397
+ } else if (f20 === 2 || f20 === 1) consider(s20.toString("utf-8"));
2398
+ } else if (w20 === 0) {
2399
+ const v20Res = readVarint(slice, p20);
2400
+ if (v20Res === null) break;
2401
+ p20 = v20Res.next;
2402
+ } else if (w20 === 1) p20 += 8;
2403
+ else if (w20 === 5) p20 += 4;
2404
+ else break;
2405
+ }
2359
2406
  }
2360
- }
2361
- } else if (wireType === 0) {
2362
- const vRes = readVarint(payload, pos);
2363
- if (vRes === null) break;
2364
- pos = vRes.next;
2365
- } else if (wireType === 1) pos += 8;
2366
- else if (wireType === 5) pos += 4;
2367
- else break;
2368
- }
2407
+ if (len >= 8 && len < 2e5) walk(slice, depth + 1);
2408
+ } else if (wireType === 0) {
2409
+ const vRes = readVarint(buf, pos);
2410
+ if (vRes === null) break;
2411
+ pos = vRes.next;
2412
+ } else if (wireType === 1) pos += 8;
2413
+ else if (wireType === 5) pos += 4;
2414
+ else break;
2415
+ }
2416
+ };
2417
+ walk(payload, 0);
2418
+ const full = payload.toString("utf8");
2419
+ const act = /"toolAction"\s*:\s*"([^"]{3,200})"/.exec(full);
2420
+ const sum = /"toolSummary"\s*:\s*"([^"]{3,200})"/.exec(full);
2421
+ if (act || sum) {
2422
+ const line = [act?.[1] ?? "", sum?.[1] ?? ""].filter((s) => s !== "").join(" — ");
2423
+ if (line !== "") actionCandidates.push(line);
2424
+ }
2425
+ if (proseCandidates.length > 0) {
2426
+ proseCandidates.sort((a, b) => b.length - a.length);
2427
+ return proseCandidates[0];
2428
+ }
2429
+ if (actionCandidates.length > 0) return actionCandidates[0];
2369
2430
  return null;
2370
2431
  }
2371
2432
  /**
@@ -2417,9 +2478,9 @@ async function loadAgyDbData(conversationId, accountHome) {
2417
2478
  const info = extractToolInfo(payload);
2418
2479
  if (info !== null) steps.set(idx, info);
2419
2480
  }
2420
- if (stepType === 14 || stepType === 15) {
2481
+ if (stepType === 14 || stepType === 15 || TOOL_STEP_TYPES.has(stepType)) {
2421
2482
  const th = extractStepThoughts(payload);
2422
- if (th !== null && th.trim() !== "") thoughts.set(idx, th);
2483
+ if (th !== null && th.trim() !== "" && !/^SYSTEM ROLE/i.test(th.trim())) thoughts.set(idx, th);
2423
2484
  }
2424
2485
  }
2425
2486
  } catch {} finally {
@@ -2824,7 +2885,8 @@ var AgyAdapter = class extends LlmAdapter {
2824
2885
  if (!bin) throw new LlmError("agy binary not found on PATH — install it via https://antigravity.google/docs/cli/install", Err.AGY_NOT_INSTALLED);
2825
2886
  const isAux = options.purpose === "compaction" || options.purpose === "session-title";
2826
2887
  if (isAux && !cfg.allowAuxiliary) throw new LlmError("auxiliary calls are disabled for the antigravity route (allowAuxiliary: false)", Err.AUX_DISABLED);
2827
- const isCodeMode = options.tools ? options.tools.some((t) => t.name === "run_code") : false;
2888
+ const toolNames = new Set((options.tools ?? []).map((t) => t.name));
2889
+ const isCodeMode = toolNames.has("run_code") && !toolNames.has("agy_tool");
2828
2890
  const sessionKey = options.sessionId !== void 0 ? String(options.sessionId) : "";
2829
2891
  let workspaceRoot = cfg.workspaceRoot;
2830
2892
  if (workspaceRoot === "") {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-agy-link",
3
- "version": "0.4.33",
3
+ "version": "0.4.35",
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",