dsh-agy-link 0.4.22 → 0.4.25

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.
Files changed (3) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/dist/index.js +255 -78
  3. package/package.json +5 -5
package/CHANGELOG.md CHANGED
@@ -1,5 +1,29 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.4.25 (2026-09-04)
4
+
5
+ - **Fixed: Plugin Startup Blocker on DSH >= 0.1.1-rc.x / 0.1.2-rc.1 (`missing export 'CallId'`, issue #4).**
6
+ - **Root cause**: Newer `@deepseek-ai/dsh-llm` versions (e.g. `0.1.2-alpha.1` ~ `0.1.2-rc.1`) renamed `CallId` to `ToolCallId`. A named import `import { CallId } from '@deepseek-ai/dsh-llm'` caused Node ESM static resolution to fail at startup with `The requested module '@deepseek-ai/dsh-llm' does not provide an export named 'CallId'`, surfacing as a plugin load failure on DSH Desktop 2.0.5 and CLI.
7
+ - **Fix**: Replaced named import with a namespace fallback resolution `toToolCallId = dshLlm.ToolCallId ?? dshLlm.CallId ?? identity`. This avoids missing named export evaluation errors and ensures seamless backwards and forwards compatibility across all DSH host versions.
8
+ - **Ecosystem & Test Hardening**: Upgraded development dependencies to `@deepseek-ai/dsh-*@0.1.2-rc.1`. Configured `--test-concurrency=1` in test runner to prevent mock environment variable collisions across concurrent subprocess tests, achieving 100% pass rate across all 151 unit tests. Verified live startup with DSH 0.1.2-rc.1 and `dsh-lark-link`.
9
+
10
+ ## 0.4.24 (2026-08-28)
11
+
12
+ - **Fixed: Antigravity Models Missing From Picker When Logged In (已登录/显示余量但模型列表不显示 — issue #1).**
13
+ - **Decoupled Adapter Registration from Synchronous Binary Discovery**: `registerAdapter` now executes regardless of initial `bin()` probe results (using resilient fallback model catalog), ensuring the Antigravity provider is never dropped during startup even if CLI path resolution is deferred.
14
+ - **Expanded macOS / Linux GUI App Path Discovery**: extended `resolveAgyBin` search candidates to include `~/.bun/bin`, `~/.cargo/bin`, `~/.local/share/pnpm`, `~/Library/pnpm`, `~/.yarn/bin`, `~/.npm-global/bin`, NVM (`~/.nvm/versions/node/*/bin`), FNM, Volta, ASDF shims, Linuxbrew, etc., fixing GUI desktop apps starting with minimal default system PATH.
15
+ - **Account-Aware Model Discovery & Environment Isolation**: model discovery now inherits current active/primary account isolated environment and proxy settings, falling back across ready pool accounts if default system HOME is unauthenticated.
16
+ - **Defensive Model & Effort Sanitization**: defensive catalog cleaning against empty IDs, blank names, empty reasoning effort arrays, and out-of-bounds `defaultEffort` to strictly satisfy DSH host validation and prevent fail-all provider group drops (`INVALID_CATALOG` / `INVALID_MODEL_*`).
17
+ - **Cross-Platform & Unit Tests**: added defensive catalog normalization and explicit binary path resolution tests across test suites.
18
+
19
+ ## 0.4.23 (2026-08-27)
20
+
21
+ - **Enhanced: Antigravity Tool Mirroring with Native Git +/- Diff Cards & Full Tool Vocabulary.**
22
+ - **Tool Vocabulary & Parameter Mapping**: mapped agy's official editing tool `replace_file_content` (`TargetFile`, `TargetContent`, `ReplacementContent`, `Description`, `Instruction`) and write tool `write_to_file` (`TargetFile`, `CodeContent`, `Overwrite`, `Description`) to DSH native `DiffCallView` / `DiffResultView` cards.
23
+ - **Git Head Base Line Diffing**: added cross-platform `getGitHeadContent` to retrieve committed file content for full-file writes (`write_to_file`), allowing DSH to render line-by-line git `+`/`-` additions and deletions rather than treating existing modified files as blank new files.
24
+ - **Expanded Tool Support**: mapped `view_file` (with `AbsolutePath`/`StartLine` line location navigation), `grep_search`, `find_by_name`, `ask_question`, `read_url_content`, and `generate_image` onto their respective native/generic tool cards with descriptive titles.
25
+ - **Cross-Platform Hardened**: path normalization with forward-slash compatibility across Windows/macOS/Linux, `windowsHide: true`, strict subprocess timeout, and safe error fallback.
26
+
3
27
  ## 0.4.22 (2026-08-27)
4
28
 
5
29
  - **Fixed: `registration.adapter.prepareCall is not a function` on new DSH hosts (本轮运行失败 UNKNOWN).**
package/dist/index.js CHANGED
@@ -1,9 +1,10 @@
1
1
  import { createRequire } from "node:module";
2
2
  import { execFile, execFileSync, spawn } from "node:child_process";
3
- import { delimiter, dirname, isAbsolute, join, resolve } from "node:path";
3
+ import path, { delimiter, dirname, isAbsolute, join, resolve } from "node:path";
4
4
  import { accessSync, chmodSync, constants, existsSync, mkdirSync, readFileSync, readdirSync, renameSync, rmSync, statSync, unlinkSync, writeFileSync } from "node:fs";
5
5
  import { homedir, tmpdir } from "node:os";
6
- import { CallId, LlmAdapter, LlmError } from "@deepseek-ai/dsh-llm";
6
+ import * as dshLlm from "@deepseek-ai/dsh-llm";
7
+ import { LlmAdapter, LlmError } from "@deepseek-ai/dsh-llm";
7
8
  import { createHash, randomBytes, randomUUID } from "node:crypto";
8
9
  import { defineTool } from "@deepseek-ai/dsh-tools";
9
10
  import { mkdir, mkdtemp, readFile, readdir, rm, stat, unlink, writeFile } from "node:fs/promises";
@@ -549,6 +550,48 @@ const MIRROR_TOOL_NAME = "agy_tool";
549
550
  /** The only tool this DSH deployment lets a model call directly. */
550
551
  const WRAPPER_TOOL_NAME = "run_code";
551
552
  /**
553
+ * Retrieve the committed HEAD content of a file via git, for computing
554
+ * line diffs on full-file writes. Pure & safe: catches all failures (not in git,
555
+ * untracked, git missing, binary) and returns null in <=500ms.
556
+ */
557
+ function getGitHeadContent(filePath, execFn = execFileSync) {
558
+ if (!filePath || typeof filePath !== "string" || filePath === "file") return null;
559
+ try {
560
+ const resolved = path.resolve(filePath);
561
+ const toplevel = execFn("git", ["rev-parse", "--show-toplevel"], {
562
+ cwd: path.dirname(resolved),
563
+ encoding: "utf-8",
564
+ stdio: [
565
+ "ignore",
566
+ "pipe",
567
+ "ignore"
568
+ ],
569
+ timeout: 500,
570
+ windowsHide: true
571
+ });
572
+ const root = typeof toplevel === "string" ? toplevel.trim() : "";
573
+ if (!root) return null;
574
+ const relPath = path.relative(root, resolved).replace(/\\/g, "/");
575
+ if (relPath.startsWith("..")) return null;
576
+ const content = execFn("git", ["show", `HEAD:${relPath}`], {
577
+ cwd: root,
578
+ encoding: "utf-8",
579
+ stdio: [
580
+ "ignore",
581
+ "pipe",
582
+ "ignore"
583
+ ],
584
+ timeout: 500,
585
+ windowsHide: true
586
+ });
587
+ const str = typeof content === "string" ? content : "";
588
+ if (str.includes("\0")) return null;
589
+ return str;
590
+ } catch {
591
+ return null;
592
+ }
593
+ }
594
+ /**
552
595
  * Build the run_code tool-call arguments that replay one recorded agy tool
553
596
  * step. The deployment's tool-dispatch policy only allows `run_code` as a
554
597
  * direct model call (everything else must be invoked from inside a
@@ -581,8 +624,9 @@ function toolInput(args) {
581
624
  }
582
625
  /**
583
626
  * agy serializes tool args with PascalCase keys (CommandLine, AbsolutePath,
584
- * SearchDirectory, …); pick() accepts both spellings so cards always read
585
- * the real field instead of falling back to raw JSON.
627
+ * SearchDirectory, TargetFile, TargetContent, ReplacementContent, CodeContent…);
628
+ * pick() accepts all casing spellings so cards always read the real field
629
+ * instead of falling back to raw JSON.
586
630
  */
587
631
  function pick$1(input, ...keys) {
588
632
  for (const k of keys) {
@@ -598,41 +642,50 @@ function presentMirrorCall(args) {
598
642
  switch (name) {
599
643
  case "run_command":
600
644
  case "bash":
601
- case "execute_command": return {
602
- card: "terminal",
603
- title: pick$1(input, "command", "cmd", "CommandLine", "Cmd") ?? JSON.stringify(input),
604
- ...pick$1(input, "description", "Description") !== void 0 ? { description: pick$1(input, "description", "Description") } : {},
605
- ...pick$1(input, "cwd", "Cwd", "WorkingDirectory") !== void 0 ? { cwd: pick$1(input, "cwd", "Cwd", "WorkingDirectory") } : {}
606
- };
607
- case "write_to_file":
608
- case "write_file":
609
- case "create_file": {
610
- const path = pick$1(input, "path", "file_path", "filename", "Path", "FilePath", "FileName", "AbsolutePath") ?? "file";
611
- const content = pick$1(input, "content", "Content", "FileContents", "contents") ?? "";
645
+ case "execute_command": {
646
+ const command = pick$1(input, "CommandLine", "command_line", "command", "cmd", "Cmd") ?? JSON.stringify(input);
647
+ const desc = pick$1(input, "Description", "description", "toolAction", "toolSummary");
648
+ const cwd = pick$1(input, "Cwd", "cwd", "WorkingDirectory", "working_directory");
649
+ return {
650
+ card: "terminal",
651
+ title: command,
652
+ ...desc !== void 0 ? { description: desc } : {},
653
+ ...cwd !== void 0 ? { cwd } : {}
654
+ };
655
+ }
656
+ case "replace_file_content":
657
+ case "edit_file":
658
+ case "replace_in_file":
659
+ case "edit": {
660
+ const path = pick$1(input, "TargetFile", "target_file", "path", "file_path", "Path", "FilePath", "AbsolutePath", "targetFile") ?? "file";
661
+ const oldText = pick$1(input, "TargetContent", "target_content", "old_string", "oldText", "OldString", "OldText", "targetContent") ?? null;
662
+ const newText = pick$1(input, "ReplacementContent", "replacement_content", "new_string", "newText", "content", "NewString", "NewText", "Content", "replacementContent") ?? "";
663
+ const desc = pick$1(input, "Description", "description", "Instruction", "instruction", "toolAction", "toolSummary");
612
664
  return {
613
665
  card: "diff",
614
- title: "Write " + path,
666
+ title: desc ? `${desc} · ${path}` : "Edit " + path,
615
667
  diffs: [{
616
668
  path,
617
- oldText: null,
618
- newText: content
669
+ oldText,
670
+ newText
619
671
  }],
620
672
  locations: [{ path }]
621
673
  };
622
674
  }
623
- case "edit_file":
624
- case "replace_in_file":
625
- case "edit": {
626
- const path = pick$1(input, "path", "file_path", "Path", "FilePath", "AbsolutePath") ?? "file";
627
- const newText = pick$1(input, "new_string", "newText", "content", "NewString", "NewText", "Content") ?? "";
628
- const oldText = pick$1(input, "old_string", "oldText", "OldString", "OldText") ?? null;
675
+ case "write_to_file":
676
+ case "write_file":
677
+ case "create_file": {
678
+ const path = pick$1(input, "TargetFile", "target_file", "path", "file_path", "filename", "Path", "FilePath", "FileName", "AbsolutePath", "targetFile") ?? "file";
679
+ const content = pick$1(input, "CodeContent", "code_content", "content", "Content", "FileContents", "contents", "codeContent") ?? "";
680
+ const desc = pick$1(input, "Description", "description", "toolAction", "toolSummary");
681
+ const oldText = pick$1(input, "old_string", "oldText", "OldString", "OldText") ?? getGitHeadContent(path);
629
682
  return {
630
683
  card: "diff",
631
- title: "Edit " + path,
684
+ title: desc ? `${desc} · ${path}` : "Write " + path,
632
685
  diffs: [{
633
686
  path,
634
687
  oldText,
635
- newText
688
+ newText: content
636
689
  }],
637
690
  locations: [{ path }]
638
691
  };
@@ -641,11 +694,12 @@ function presentMirrorCall(args) {
641
694
  case "view_file":
642
695
  case "read":
643
696
  case "open_file": {
644
- const path = pick$1(input, "path", "file_path", "filename", "Path", "FilePath", "FileName", "AbsolutePath") ?? "";
645
- const offset = typeof input.offset === "number" ? input.offset : typeof input.Offset === "number" ? input.Offset : void 0;
697
+ const path = pick$1(input, "AbsolutePath", "absolute_path", "TargetFile", "target_file", "path", "file_path", "filename", "Path", "FilePath", "FileName", "targetFile") ?? "";
698
+ const offset = typeof input.offset === "number" ? input.offset : typeof input.Offset === "number" ? input.Offset : typeof input.StartLine === "number" ? input.StartLine - 1 : typeof input.start_line === "number" ? input.start_line - 1 : void 0;
699
+ const desc = pick$1(input, "Description", "description", "toolAction", "toolSummary");
646
700
  return {
647
701
  card: "generic",
648
- title: "Read " + path,
702
+ title: desc ? `${desc} · ${path}` : "Read " + path,
649
703
  kind: "read",
650
704
  ...path !== "" ? { locations: [{
651
705
  path,
@@ -655,35 +709,70 @@ function presentMirrorCall(args) {
655
709
  }
656
710
  case "find_by_name":
657
711
  case "glob":
658
- case "search_files":
712
+ case "search_files": {
713
+ const q = pick$1(input, "pattern", "Pattern", "query", "Query", "regex", "Regex", "QueryString") ?? "";
714
+ return {
715
+ card: "generic",
716
+ title: pick$1(input, "Description", "description", "toolAction", "toolSummary") ?? (q !== "" ? "Search " + q : "Search"),
717
+ kind: "search"
718
+ };
719
+ }
720
+ case "grep_search":
659
721
  case "search":
660
722
  case "search_file_content":
661
723
  case "grep": {
662
- const q = pick$1(input, "pattern", "query", "regex", "Pattern", "Query", "Regex", "QueryString") ?? "";
724
+ const q = pick$1(input, "query", "Query", "pattern", "Pattern", "regex", "Regex", "QueryString") ?? "";
663
725
  return {
664
726
  card: "generic",
665
- title: q !== "" ? "Search " + q : "Search",
727
+ title: pick$1(input, "Description", "description", "toolAction", "toolSummary") ?? (q !== "" ? "Search " + q : "Search"),
666
728
  kind: "search"
667
729
  };
668
730
  }
669
731
  case "list_dir":
670
732
  case "ls": {
671
- const path = pick$1(input, "path", "directory", "Path", "Directory", "SearchDirectory", "AbsolutePath") ?? "";
733
+ const path = pick$1(input, "DirectoryPath", "directory_path", "path", "directory", "Path", "Directory", "SearchDirectory", "search_directory", "AbsolutePath") ?? "";
672
734
  return {
673
735
  card: "generic",
674
- title: path !== "" ? "List " + path : "List directory"
736
+ title: pick$1(input, "Description", "description", "toolAction", "toolSummary") ?? (path !== "" ? "List " + path : "List directory")
675
737
  };
676
738
  }
677
739
  case "delete_file":
678
740
  case "remove_file":
679
- case "rm": return {
680
- card: "generic",
681
- title: "Delete " + (pick$1(input, "path", "file_path", "Path", "FilePath", "AbsolutePath") ?? ""),
682
- kind: "delete"
683
- };
741
+ case "rm": {
742
+ const path = pick$1(input, "TargetFile", "target_file", "path", "file_path", "Path", "FilePath", "AbsolutePath") ?? "";
743
+ return {
744
+ card: "generic",
745
+ title: pick$1(input, "Description", "description", "toolAction", "toolSummary") ?? "Delete " + path,
746
+ kind: "delete"
747
+ };
748
+ }
749
+ case "ask_question": {
750
+ const questions = input.questions;
751
+ const firstQ = Array.isArray(questions) && questions.length > 0 && typeof questions[0].question === "string" ? questions[0].question : void 0;
752
+ return {
753
+ card: "generic",
754
+ title: firstQ ? `Ask Question: ${firstQ}` : "Ask Question",
755
+ rawInput: input
756
+ };
757
+ }
758
+ case "read_url_content": {
759
+ const url = pick$1(input, "Url", "url") ?? "";
760
+ return {
761
+ card: "generic",
762
+ title: pick$1(input, "Description", "description", "toolAction", "toolSummary") ?? (url ? `Fetch ${url}` : "Fetch URL"),
763
+ kind: "fetch"
764
+ };
765
+ }
766
+ case "generate_image": {
767
+ const prompt = pick$1(input, "Prompt", "prompt", "ImageName", "image_name") ?? "";
768
+ return {
769
+ card: "generic",
770
+ title: prompt ? `Generate Image: ${prompt}` : "Generate Image"
771
+ };
772
+ }
684
773
  default: return {
685
774
  card: "generic",
686
- title: name !== "" ? name : "agy tool",
775
+ title: pick$1(input, "Description", "description", "toolAction", "toolSummary") ?? (name !== "" ? name : "agy tool"),
687
776
  rawInput: input
688
777
  };
689
778
  }
@@ -700,6 +789,7 @@ function presentMirrorResult(args, result) {
700
789
  card: "terminal",
701
790
  output: text
702
791
  };
792
+ case "replace_file_content":
703
793
  case "write_to_file":
704
794
  case "write_file":
705
795
  case "create_file":
@@ -800,6 +890,7 @@ function defineAgyMirrorTool(deps) {
800
890
  }
801
891
  //#endregion
802
892
  //#region src/host/mapper.ts
893
+ const toToolCallId = dshLlm.ToolCallId ?? dshLlm.CallId ?? ((id) => id);
803
894
  function usageFromRaw(raw) {
804
895
  const usage = {
805
896
  inputTokens: raw.input_tokens ?? 0,
@@ -969,7 +1060,7 @@ var EventMapper = class {
969
1060
  index: idx,
970
1061
  block: {
971
1062
  type: "tool-call",
972
- id: CallId(mirrorCallId(this.opts.runId, absIndex)),
1063
+ id: toToolCallId(mirrorCallId(this.opts.runId, absIndex)),
973
1064
  name: toolName,
974
1065
  arguments: argumentsJson
975
1066
  }
@@ -1079,7 +1170,7 @@ function parseModelsOutput(stdout) {
1079
1170
  const out = [];
1080
1171
  for (const line of text.split(/\n/)) {
1081
1172
  const t = line.trim();
1082
- if (t === "" || t.startsWith("Fetching") || t.startsWith("Error")) continue;
1173
+ if (t === "" || t.startsWith("Fetching") || t.startsWith("Error") || /^(please sign in|warning|tip:)/i.test(t)) continue;
1083
1174
  const m = t.match(/^(\S+)(?:\t+|\s{2,})(.+)$/);
1084
1175
  if (m && m[1] !== void 0 && m[2] !== void 0) out.push({
1085
1176
  slug: m[1],
@@ -1097,9 +1188,14 @@ function dedupeBySlug(raw) {
1097
1188
  const seen = /* @__PURE__ */ new Set();
1098
1189
  const out = [];
1099
1190
  for (const r of raw) {
1100
- if (seen.has(r.slug)) continue;
1101
- seen.add(r.slug);
1102
- out.push(r);
1191
+ if (!r.slug || typeof r.slug !== "string") continue;
1192
+ const slug = r.slug.trim();
1193
+ if (slug === "" || seen.has(slug)) continue;
1194
+ seen.add(slug);
1195
+ out.push({
1196
+ slug,
1197
+ label: typeof r.label === "string" && r.label.trim() !== "" ? r.label.trim() : slug
1198
+ });
1103
1199
  }
1104
1200
  return out;
1105
1201
  }
@@ -1207,10 +1303,10 @@ function stripEffortLabel(label, eff) {
1207
1303
  return label.replace(re, "").trim();
1208
1304
  }
1209
1305
  function buildFallbackCatalog(defs) {
1210
- return defs.map((d) => ({
1211
- id: d.id,
1212
- name: d.name,
1213
- efforts: d.efforts ?? null
1306
+ return defs.filter((d) => Boolean(d && typeof d.id === "string" && d.id.trim() !== "")).map((d) => ({
1307
+ id: d.id.trim(),
1308
+ name: typeof d.name === "string" && d.name.trim() !== "" ? d.name.trim() : d.id.trim(),
1309
+ efforts: Array.isArray(d.efforts) && d.efforts.length > 0 ? d.efforts.filter((e) => typeof e === "string" && e.trim() !== "") : null
1214
1310
  }));
1215
1311
  }
1216
1312
  var ModelCatalog = class {
@@ -1288,7 +1384,7 @@ function findEntry(catalog, id) {
1288
1384
  if (resolved !== id) return catalog.models.find((m) => m.id === resolved);
1289
1385
  }
1290
1386
  function defaultEffortFor(entry, cfg) {
1291
- if (!entry.efforts) return void 0;
1387
+ if (!entry.efforts || entry.efforts.length === 0) return void 0;
1292
1388
  if (cfg.defaultEffort !== "" && entry.efforts.includes(cfg.defaultEffort)) return cfg.defaultEffort;
1293
1389
  for (const pref of [
1294
1390
  "high",
@@ -1790,15 +1886,47 @@ function resolveAgyBin(cfg) {
1790
1886
  if (cfg.agyBin !== "") candidates.push(cfg.agyBin);
1791
1887
  const pathEnv = process.env.PATH ?? "";
1792
1888
  for (const dir of pathEnv.split(delimiter)) if (dir !== "") candidates.push(...binCandidates(dir));
1889
+ const home = homedir();
1793
1890
  if (IS_WIN) {
1794
1891
  const local = process.env.LOCALAPPDATA ?? "";
1795
- if (local !== "") candidates.push(join(local, "Programs", "agy", "agy.exe"));
1796
- candidates.push(join(homedir(), ".local", "bin", "agy.exe"));
1797
- candidates.push(join(homedir(), "AppData", "Roaming", "npm", "agy.cmd"));
1892
+ const appData = process.env.APPDATA ?? "";
1893
+ if (local !== "") {
1894
+ candidates.push(join(local, "Programs", "agy", "agy.exe"));
1895
+ candidates.push(join(local, "pnpm", "agy.cmd"));
1896
+ candidates.push(join(local, "pnpm", "agy.exe"));
1897
+ }
1898
+ if (appData !== "") {
1899
+ candidates.push(join(appData, "npm", "agy.cmd"));
1900
+ candidates.push(join(appData, "Roaming", "npm", "agy.cmd"));
1901
+ }
1902
+ candidates.push(join(home, ".local", "bin", "agy.exe"));
1903
+ candidates.push(join(home, ".local", "bin", "agy.cmd"));
1904
+ candidates.push(join(home, ".bun", "bin", "agy.exe"));
1905
+ candidates.push(join(home, ".cargo", "bin", "agy.exe"));
1906
+ candidates.push(join(home, "scoop", "shims", "agy.exe"));
1798
1907
  } else {
1799
- candidates.push(join(homedir(), ".local", "bin", "agy"));
1908
+ candidates.push(join(home, ".local", "bin", "agy"));
1800
1909
  candidates.push("/usr/local/bin/agy");
1801
1910
  candidates.push("/opt/homebrew/bin/agy");
1911
+ candidates.push("/opt/homebrew/sbin/agy");
1912
+ candidates.push("/home/linuxbrew/.linuxbrew/bin/agy");
1913
+ candidates.push(join(home, ".bun", "bin", "agy"));
1914
+ candidates.push(join(home, ".cargo", "bin", "agy"));
1915
+ candidates.push(join(home, ".local", "share", "pnpm", "agy"));
1916
+ candidates.push(join(home, "Library", "pnpm", "agy"));
1917
+ candidates.push(join(home, ".yarn", "bin", "agy"));
1918
+ candidates.push(join(home, ".npm-global", "bin", "agy"));
1919
+ candidates.push(join(home, ".volta", "bin", "agy"));
1920
+ candidates.push(join(home, ".asdf", "shims", "agy"));
1921
+ candidates.push(join(home, ".nix-profile", "bin", "agy"));
1922
+ candidates.push("/run/current-system/sw/bin/agy");
1923
+ try {
1924
+ const nvmNodeDir = join(home, ".nvm", "versions", "node");
1925
+ if (existsSync(nvmNodeDir)) for (const v of readdirSync(nvmNodeDir)) candidates.push(join(nvmNodeDir, v, "bin", "agy"));
1926
+ } catch {}
1927
+ candidates.push(join(home, ".local", "share", "fnm", "current", "bin", "agy"));
1928
+ candidates.push(join(home, ".fnm", "current", "bin", "agy"));
1929
+ candidates.push(join(home, "Library", "Application Support", "fnm", "current", "bin", "agy"));
1802
1930
  }
1803
1931
  const hits = [];
1804
1932
  for (const c of candidates) try {
@@ -2087,15 +2215,19 @@ var AgyAdapter = class extends LlmAdapter {
2087
2215
  const models = [];
2088
2216
  const dropped = [];
2089
2217
  for (const m of cat.models) {
2090
- if (seen.has(m.id)) {
2091
- dropped.push(m.id);
2218
+ if (!m || typeof m.id !== "string") continue;
2219
+ const id = m.id.trim();
2220
+ if (id === "") continue;
2221
+ if (seen.has(id)) {
2222
+ dropped.push(id);
2092
2223
  continue;
2093
2224
  }
2094
- seen.add(m.id);
2225
+ seen.add(id);
2226
+ const name = typeof m.name === "string" && m.name.trim() !== "" ? m.name.trim() : id;
2095
2227
  models.push({
2096
2228
  provider: PROVIDER_ID,
2097
- id: m.id,
2098
- name: m.name,
2229
+ id,
2230
+ name,
2099
2231
  inputModalities: ["text", "image"]
2100
2232
  });
2101
2233
  }
@@ -2104,26 +2236,37 @@ var AgyAdapter = class extends LlmAdapter {
2104
2236
  }
2105
2237
  async resolveModel(_provider, model, _signal) {
2106
2238
  const cfg = this.deps.getConfig();
2107
- const entry = findEntry(this.deps.catalog.get(), model);
2108
- const name = entry ? entry.name : model;
2109
- const contextWindow = cfg.contextWindowDefault;
2239
+ const cat = this.deps.catalog.get();
2240
+ const rawModel = typeof model === "string" ? model.trim() : "";
2241
+ const cleanId = rawModel !== "" ? rawModel : cfg.defaultModel;
2242
+ const entry = findEntry(cat, cleanId);
2243
+ const rawName = entry ? entry.name : cleanId;
2244
+ const name = typeof rawName === "string" && rawName.trim() !== "" ? rawName.trim() : cleanId;
2245
+ const contextWindow = typeof cfg.contextWindowDefault === "number" && cfg.contextWindowDefault > 0 ? cfg.contextWindowDefault : 2e5;
2110
2246
  const resolved = {
2111
2247
  provider: PROVIDER_ID,
2112
- id: model,
2248
+ id: cleanId,
2113
2249
  name,
2114
2250
  inputModalities: ["text", "image"],
2115
2251
  context: { contextWindow },
2116
- defaultMaxTokens: cfg.maxTokensDefault
2252
+ defaultMaxTokens: typeof cfg.maxTokensDefault === "number" && cfg.maxTokensDefault > 0 ? cfg.maxTokensDefault : 8192
2117
2253
  };
2118
- if (entry && entry.efforts) {
2119
- const def = defaultEffortFor(entry, cfg);
2120
- resolved.reasoning = {
2121
- efforts: entry.efforts.map((e) => ({
2122
- id: e,
2123
- name: e
2124
- })),
2125
- ...def ? { defaultEffort: def } : {}
2126
- };
2254
+ if (entry && Array.isArray(entry.efforts) && entry.efforts.length > 0) {
2255
+ const cleanEfforts = Array.from(new Set(entry.efforts.filter((e) => typeof e === "string" && e.trim() !== "")));
2256
+ if (cleanEfforts.length > 0) {
2257
+ const def = defaultEffortFor({
2258
+ ...entry,
2259
+ efforts: cleanEfforts
2260
+ }, cfg);
2261
+ const validDef = def && cleanEfforts.includes(def) ? def : cleanEfforts[0];
2262
+ resolved.reasoning = {
2263
+ efforts: cleanEfforts.map((e) => ({
2264
+ id: e,
2265
+ name: e
2266
+ })),
2267
+ ...validDef ? { defaultEffort: validDef } : {}
2268
+ };
2269
+ }
2127
2270
  }
2128
2271
  return resolved;
2129
2272
  }
@@ -28346,7 +28489,7 @@ function apply(ctx, entryConfig = {}) {
28346
28489
  let lastParser = new StreamJsonParser();
28347
28490
  const getConfig = () => resolveConfig(entryConfig);
28348
28491
  const bin = () => {
28349
- if (binCache === void 0) binCache = resolveAgyBin(getConfig());
28492
+ if (binCache === void 0 || binCache === null) binCache = resolveAgyBin(getConfig());
28350
28493
  return binCache;
28351
28494
  };
28352
28495
  const version = () => versionCache;
@@ -28354,10 +28497,41 @@ function apply(ctx, entryConfig = {}) {
28354
28497
  const pool = new AccountPoolManager();
28355
28498
  const quota = new QuotaService(pool);
28356
28499
  const semaphore = new Semaphore(() => getConfig().maxConcurrent);
28500
+ const getDiscoveryEnv = (account) => {
28501
+ const cfg = getConfig();
28502
+ return {
28503
+ ...process.env,
28504
+ ...cfg.disableTelemetry ? {
28505
+ DO_NOT_TRACK: "1",
28506
+ DISABLE_TELEMETRY: "1",
28507
+ GOOGLE_CLOUD_DISABLE_TELEMETRY: "1",
28508
+ ANTIGRAVITY_DISABLE_TELEMETRY: "1"
28509
+ } : {},
28510
+ ...account && account.dir ? isolatedHomeEnv(account.dir) : {},
28511
+ ...account?.proxyUrl ? {
28512
+ ALL_PROXY: account.proxyUrl,
28513
+ HTTPS_PROXY: account.proxyUrl,
28514
+ HTTP_PROXY: account.proxyUrl,
28515
+ all_proxy: account.proxyUrl,
28516
+ https_proxy: account.proxyUrl,
28517
+ http_proxy: account.proxyUrl
28518
+ } : {}
28519
+ };
28520
+ };
28357
28521
  const catalog = new ModelCatalog(async (signal) => {
28358
28522
  const b = bin();
28359
28523
  if (!b) throw new Error("agy binary not found");
28360
- const out = await probeProcess(b, ["models"], 3e4, signal);
28524
+ const poolData = pool.getPoolData();
28525
+ const primaryAcc = poolData.primaryAccountId ? pool.getAccount(poolData.primaryAccountId) : void 0;
28526
+ const candidateAccount = primaryAcc && primaryAcc.enabled && !primaryAcc.authRequired ? primaryAcc : pool.getAccounts().find((a) => a.enabled && !a.authRequired);
28527
+ let out = await probeProcess(b, ["models"], 3e4, signal, getDiscoveryEnv(candidateAccount));
28528
+ if ((out.code !== 0 || out.stdout.includes("Please sign in") || /sign in|auth|not logged in/i.test(out.stderrTail)) && (!candidateAccount || !candidateAccount.dir)) {
28529
+ const secondary = pool.getAccounts().find((a) => a.enabled && !a.authRequired && a.dir);
28530
+ if (secondary) {
28531
+ const secondaryOut = await probeProcess(b, ["models"], 3e4, signal, getDiscoveryEnv(secondary));
28532
+ if (secondaryOut.code === 0 && secondaryOut.stdout.trim() !== "") out = secondaryOut;
28533
+ }
28534
+ }
28361
28535
  if (out.code !== 0 && out.stdout.trim() === "") throw new Error("agy models failed: " + (out.stderrTail.trim() !== "" ? out.stderrTail.trim().slice(-200) : "exit " + String(out.code)));
28362
28536
  return {
28363
28537
  stdout: out.stdout,
@@ -28411,6 +28585,7 @@ function apply(ctx, entryConfig = {}) {
28411
28585
  const file = overridesPath();
28412
28586
  const current = readOverrides(file);
28413
28587
  current[key] = value;
28588
+ binCache = void 0;
28414
28589
  try {
28415
28590
  mkdirSync(stateDir(), { recursive: true });
28416
28591
  writeFileSync(file, JSON.stringify(current, null, 2), "utf8");
@@ -28424,25 +28599,27 @@ function apply(ctx, entryConfig = {}) {
28424
28599
  log("dormant: disabled by config");
28425
28600
  return;
28426
28601
  }
28427
- if (!bin()) {
28602
+ const currentBin = bin();
28603
+ if (!currentBin) {
28428
28604
  dormantReason = "agy binary not found — install via https://antigravity.google/docs/cli/install";
28429
28605
  log("dormant: agy binary not found");
28430
28606
  return;
28431
28607
  }
28432
28608
  try {
28433
- versionCache = parseVersion((await probeProcess(bin(), ["--version"], 1e4)).stdout);
28609
+ versionCache = parseVersion((await probeProcess(currentBin, ["--version"], 1e4)).stdout);
28434
28610
  if (versionCache && compareVersions(versionCache, "1.1.8") < 0) {
28435
28611
  dormantReason = "agy " + versionCache + " is older than 1.1.8 — run: agy update";
28436
28612
  log("dormant: " + dormantReason);
28437
28613
  return;
28438
28614
  }
28615
+ dormantReason = null;
28439
28616
  log("agy detected: " + (versionCache ?? "unknown version"));
28440
28617
  } catch {
28441
28618
  log("version probe failed — continuing with fallback catalog");
28442
28619
  }
28443
28620
  await catalog.refreshIfNeeded().catch(() => void 0);
28444
28621
  })();
28445
- if (getConfig().enabled && bin()) try {
28622
+ if (getConfig().enabled) try {
28446
28623
  ctx.llm.registerAdapter([PROVIDER_ID], adapter);
28447
28624
  log("registered provider route: antigravity");
28448
28625
  } catch (e) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-agy-link",
3
- "version": "0.4.22",
3
+ "version": "0.4.25",
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",
@@ -52,9 +52,9 @@
52
52
  },
53
53
  "devDependencies": {
54
54
  "@deepseek-ai/cordis": "^4.0.1",
55
- "@deepseek-ai/dsh-commands": "^0.1.0-rc.6",
56
- "@deepseek-ai/dsh-llm": "^0.1.0-rc.6",
57
- "@deepseek-ai/dsh-tools": "^0.1.0-rc.6",
55
+ "@deepseek-ai/dsh-commands": "0.1.2-rc.1",
56
+ "@deepseek-ai/dsh-llm": "0.1.2-rc.1",
57
+ "@deepseek-ai/dsh-tools": "0.1.2-rc.1",
58
58
  "@types/node": "^24.3.0",
59
59
  "@types/qrcode": "^1.5.5",
60
60
  "tsdown": "^0.22.14",
@@ -83,7 +83,7 @@
83
83
  "scripts": {
84
84
  "build": "tsdown",
85
85
  "check": "tsc --noEmit",
86
- "test": "node --experimental-transform-types --test \"test/**/*.test.ts\"",
86
+ "test": "node --experimental-transform-types --test --test-concurrency=1 \"test/**/*.test.ts\"",
87
87
  "prepack": "npm run build"
88
88
  },
89
89
  "engines": {