@kolisachint/hoocode-agent 0.4.163 → 0.4.165

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 (164) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/dist/cli/args.d.ts +0 -6
  3. package/dist/cli/args.d.ts.map +1 -1
  4. package/dist/cli/args.js +0 -22
  5. package/dist/cli/args.js.map +1 -1
  6. package/dist/core/agent-frontmatter.d.ts.map +1 -1
  7. package/dist/core/agent-frontmatter.js +0 -6
  8. package/dist/core/agent-frontmatter.js.map +1 -1
  9. package/dist/core/agent-session-services.d.ts +0 -2
  10. package/dist/core/agent-session-services.d.ts.map +1 -1
  11. package/dist/core/agent-session-services.js +0 -2
  12. package/dist/core/agent-session-services.js.map +1 -1
  13. package/dist/core/agent-session.d.ts.map +1 -1
  14. package/dist/core/agent-session.js +0 -2
  15. package/dist/core/agent-session.js.map +1 -1
  16. package/dist/core/embsearch/client.d.ts +18 -2
  17. package/dist/core/embsearch/client.d.ts.map +1 -1
  18. package/dist/core/embsearch/client.js +20 -3
  19. package/dist/core/embsearch/client.js.map +1 -1
  20. package/dist/core/embsearch/embsearch-service.d.ts +18 -1
  21. package/dist/core/embsearch/embsearch-service.d.ts.map +1 -1
  22. package/dist/core/embsearch/embsearch-service.js +52 -4
  23. package/dist/core/embsearch/embsearch-service.js.map +1 -1
  24. package/dist/core/sdk.d.ts +0 -16
  25. package/dist/core/sdk.d.ts.map +1 -1
  26. package/dist/core/sdk.js +0 -2
  27. package/dist/core/sdk.js.map +1 -1
  28. package/dist/core/search/eval-compare.d.ts +57 -0
  29. package/dist/core/search/eval-compare.d.ts.map +1 -0
  30. package/dist/core/search/eval-compare.js +114 -0
  31. package/dist/core/search/eval-compare.js.map +1 -0
  32. package/dist/core/search/eval-gold.d.ts +47 -0
  33. package/dist/core/search/eval-gold.d.ts.map +1 -0
  34. package/dist/core/search/eval-gold.js +172 -0
  35. package/dist/core/search/eval-gold.js.map +1 -0
  36. package/dist/core/search/eval-harness.d.ts +140 -0
  37. package/dist/core/search/eval-harness.d.ts.map +1 -0
  38. package/dist/core/search/eval-harness.js +225 -0
  39. package/dist/core/search/eval-harness.js.map +1 -0
  40. package/dist/core/search/eval.d.ts +66 -8
  41. package/dist/core/search/eval.d.ts.map +1 -1
  42. package/dist/core/search/eval.js +67 -12
  43. package/dist/core/search/eval.js.map +1 -1
  44. package/dist/core/search/hybrid-search.d.ts +16 -0
  45. package/dist/core/search/hybrid-search.d.ts.map +1 -1
  46. package/dist/core/search/hybrid-search.js +54 -2
  47. package/dist/core/search/hybrid-search.js.map +1 -1
  48. package/dist/core/search/mode.d.ts +21 -5
  49. package/dist/core/search/mode.d.ts.map +1 -1
  50. package/dist/core/search/mode.js +23 -10
  51. package/dist/core/search/mode.js.map +1 -1
  52. package/dist/core/search/rerank.d.ts.map +1 -1
  53. package/dist/core/search/rerank.js +70 -11
  54. package/dist/core/search/rerank.js.map +1 -1
  55. package/dist/core/search/rrf.d.ts +19 -6
  56. package/dist/core/search/rrf.d.ts.map +1 -1
  57. package/dist/core/search/rrf.js +19 -6
  58. package/dist/core/search/rrf.js.map +1 -1
  59. package/dist/core/search/types.d.ts +11 -1
  60. package/dist/core/search/types.d.ts.map +1 -1
  61. package/dist/core/search/types.js.map +1 -1
  62. package/dist/core/settings-defaults.d.ts +0 -3
  63. package/dist/core/settings-defaults.d.ts.map +1 -1
  64. package/dist/core/settings-defaults.js +0 -3
  65. package/dist/core/settings-defaults.js.map +1 -1
  66. package/dist/core/settings-manager.d.ts +0 -6
  67. package/dist/core/settings-manager.d.ts.map +1 -1
  68. package/dist/core/settings-manager.js +0 -24
  69. package/dist/core/settings-manager.js.map +1 -1
  70. package/dist/core/settings-types.d.ts +0 -3
  71. package/dist/core/settings-types.d.ts.map +1 -1
  72. package/dist/core/settings-types.js.map +1 -1
  73. package/dist/core/tools/bash.d.ts.map +1 -1
  74. package/dist/core/tools/bash.js +3 -1
  75. package/dist/core/tools/bash.js.map +1 -1
  76. package/dist/core/tools/edit.d.ts.map +1 -1
  77. package/dist/core/tools/edit.js +5 -5
  78. package/dist/core/tools/edit.js.map +1 -1
  79. package/dist/core/tools/find.d.ts.map +1 -1
  80. package/dist/core/tools/find.js +8 -10
  81. package/dist/core/tools/find.js.map +1 -1
  82. package/dist/core/tools/grep.d.ts.map +1 -1
  83. package/dist/core/tools/grep.js +4 -4
  84. package/dist/core/tools/grep.js.map +1 -1
  85. package/dist/core/tools/index.d.ts +0 -20
  86. package/dist/core/tools/index.d.ts.map +1 -1
  87. package/dist/core/tools/index.js +0 -13
  88. package/dist/core/tools/index.js.map +1 -1
  89. package/dist/core/tools/ls.d.ts.map +1 -1
  90. package/dist/core/tools/ls.js +2 -2
  91. package/dist/core/tools/ls.js.map +1 -1
  92. package/dist/core/tools/read.d.ts.map +1 -1
  93. package/dist/core/tools/read.js +10 -8
  94. package/dist/core/tools/read.js.map +1 -1
  95. package/dist/core/tools/search.d.ts.map +1 -1
  96. package/dist/core/tools/search.js +6 -2
  97. package/dist/core/tools/search.js.map +1 -1
  98. package/dist/main.d.ts.map +1 -1
  99. package/dist/main.js +0 -30
  100. package/dist/main.js.map +1 -1
  101. package/dist/modes/interactive/components/settings-selector.d.ts +1 -1
  102. package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  103. package/dist/modes/interactive/components/settings-selector.js +2 -2
  104. package/dist/modes/interactive/components/settings-selector.js.map +1 -1
  105. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  106. package/dist/modes/interactive/interactive-mode.js +0 -18
  107. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  108. package/dist/utils/tools-manager.d.ts +1 -1
  109. package/dist/utils/tools-manager.d.ts.map +1 -1
  110. package/dist/utils/tools-manager.js +1 -49
  111. package/dist/utils/tools-manager.js.map +1 -1
  112. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  113. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  114. package/examples/extensions/sandbox/package.json +1 -1
  115. package/examples/extensions/with-deps/package.json +1 -1
  116. package/package.json +7 -4
  117. package/dist/core/tools/browser/browser-continue.d.ts +0 -24
  118. package/dist/core/tools/browser/browser-continue.d.ts.map +0 -1
  119. package/dist/core/tools/browser/browser-continue.js +0 -84
  120. package/dist/core/tools/browser/browser-continue.js.map +0 -1
  121. package/dist/core/tools/browser/browser-run.d.ts +0 -47
  122. package/dist/core/tools/browser/browser-run.d.ts.map +0 -1
  123. package/dist/core/tools/browser/browser-run.js +0 -377
  124. package/dist/core/tools/browser/browser-run.js.map +0 -1
  125. package/dist/core/tools/browser/browsertools-shared.d.ts +0 -177
  126. package/dist/core/tools/browser/browsertools-shared.d.ts.map +0 -1
  127. package/dist/core/tools/browser/browsertools-shared.js +0 -335
  128. package/dist/core/tools/browser/browsertools-shared.js.map +0 -1
  129. package/dist/core/tools/browser/index.d.ts +0 -8
  130. package/dist/core/tools/browser/index.d.ts.map +0 -1
  131. package/dist/core/tools/browser/index.js +0 -8
  132. package/dist/core/tools/browser/index.js.map +0 -1
  133. package/dist/core/tools/doc/docedit.d.ts +0 -43
  134. package/dist/core/tools/doc/docedit.d.ts.map +0 -1
  135. package/dist/core/tools/doc/docedit.js +0 -133
  136. package/dist/core/tools/doc/docedit.js.map +0 -1
  137. package/dist/core/tools/doc/docgrep.d.ts +0 -27
  138. package/dist/core/tools/doc/docgrep.d.ts.map +0 -1
  139. package/dist/core/tools/doc/docgrep.js +0 -106
  140. package/dist/core/tools/doc/docgrep.js.map +0 -1
  141. package/dist/core/tools/doc/docpeek.d.ts +0 -24
  142. package/dist/core/tools/doc/docpeek.d.ts.map +0 -1
  143. package/dist/core/tools/doc/docpeek.js +0 -112
  144. package/dist/core/tools/doc/docpeek.js.map +0 -1
  145. package/dist/core/tools/doc/docread.d.ts +0 -31
  146. package/dist/core/tools/doc/docread.d.ts.map +0 -1
  147. package/dist/core/tools/doc/docread.js +0 -113
  148. package/dist/core/tools/doc/docread.js.map +0 -1
  149. package/dist/core/tools/doc/docscan.d.ts +0 -28
  150. package/dist/core/tools/doc/docscan.d.ts.map +0 -1
  151. package/dist/core/tools/doc/docscan.js +0 -110
  152. package/dist/core/tools/doc/docscan.js.map +0 -1
  153. package/dist/core/tools/doc/docwrite.d.ts +0 -43
  154. package/dist/core/tools/doc/docwrite.d.ts.map +0 -1
  155. package/dist/core/tools/doc/docwrite.js +0 -102
  156. package/dist/core/tools/doc/docwrite.js.map +0 -1
  157. package/dist/core/tools/doc/index.d.ts +0 -11
  158. package/dist/core/tools/doc/index.d.ts.map +0 -1
  159. package/dist/core/tools/doc/index.js +0 -11
  160. package/dist/core/tools/doc/index.js.map +0 -1
  161. package/dist/core/tools/filetools-shared.d.ts +0 -268
  162. package/dist/core/tools/filetools-shared.d.ts.map +0 -1
  163. package/dist/core/tools/filetools-shared.js +0 -403
  164. package/dist/core/tools/filetools-shared.js.map +0 -1
@@ -1,5 +1,5 @@
1
1
  /** Tools whose binaries hoocode can resolve from PATH or download on demand. */
2
- export type ManagedTool = "fd" | "rg" | "webtools" | "filetools" | "browsertools" | "voicetools" | "embsearch";
2
+ export type ManagedTool = "fd" | "rg" | "webtools" | "voicetools" | "embsearch";
3
3
  /**
4
4
  * Live download progress: bytes received so far and the total from
5
5
  * Content-Length (null when the server doesn't send one). Callers use it to
@@ -1 +1 @@
1
- {"version":3,"file":"tools-manager.d.ts","sourceRoot":"","sources":["../../src/utils/tools-manager.ts"],"names":[],"mappings":"AA+BA,gFAAgF;AAChF,MAAM,MAAM,WAAW,GAAG,IAAI,GAAG,IAAI,GAAG,UAAU,GAAG,WAAW,GAAG,cAAc,GAAG,YAAY,GAAG,WAAW,CAAC;AA0Q/G;;;;GAIG;AACH,MAAM,MAAM,gBAAgB,GAAG,CAAC,aAAa,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAC;AAM1F,wBAAsB,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,CA4C1G;AA+ID,wBAAsB,UAAU,CAC/B,IAAI,EAAE,WAAW,EACjB,MAAM,GAAE,OAAe,EACvB,UAAU,CAAC,EAAE,gBAAgB,GAC3B,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CA2C7B","sourcesContent":["import chalk from \"chalk\";\nimport { spawnSync } from \"child_process\";\nimport { createHash, randomBytes } from \"crypto\";\nimport extractZip from \"extract-zip\";\nimport {\n\tchmodSync,\n\tcreateWriteStream,\n\texistsSync,\n\tmkdirSync,\n\treaddirSync,\n\treadFileSync,\n\trenameSync,\n\trmSync,\n\tstatSync,\n} from \"fs\";\nimport { arch, platform } from \"os\";\nimport { join } from \"path\";\nimport { Readable } from \"stream\";\nimport { pipeline } from \"stream/promises\";\nimport { APP_NAME, getBinDir } from \"../config.js\";\n\nconst TOOLS_DIR = getBinDir();\nconst NETWORK_TIMEOUT_MS = 10_000;\nconst DOWNLOAD_TIMEOUT_MS = 120_000;\n\nfunction isOfflineModeEnabled(): boolean {\n\tconst value = process.env.HOOCODE_OFFLINE;\n\tif (!value) return false;\n\treturn value === \"1\" || value.toLowerCase() === \"true\" || value.toLowerCase() === \"yes\";\n}\n\n/** Tools whose binaries hoocode can resolve from PATH or download on demand. */\nexport type ManagedTool = \"fd\" | \"rg\" | \"webtools\" | \"filetools\" | \"browsertools\" | \"voicetools\" | \"embsearch\";\n\ninterface ToolConfig {\n\tname: string;\n\trepo: string; // GitHub repo (e.g., \"sharkdp/fd\")\n\tbinaryName: string; // Name of the binary inside the archive\n\tsystemBinaryNames?: string[]; // Alternative system command names to try before downloading\n\ttagPrefix: string; // Prefix for tags (e.g., \"v\" for v1.0.0, \"\" for 1.0.0)\n\tgetAssetName: (version: string, plat: string, architecture: string) => string | null;\n}\n\nconst TOOLS: Record<string, ToolConfig> = {\n\tfd: {\n\t\tname: \"fd\",\n\t\trepo: \"sharkdp/fd\",\n\t\tbinaryName: \"fd\",\n\t\tsystemBinaryNames: [\"fd\", \"fdfind\"],\n\t\ttagPrefix: \"v\",\n\t\tgetAssetName: (version, plat, architecture) => {\n\t\t\tif (plat === \"darwin\") {\n\t\t\t\tconst archStr = architecture === \"arm64\" ? \"aarch64\" : \"x86_64\";\n\t\t\t\treturn `fd-v${version}-${archStr}-apple-darwin.tar.gz`;\n\t\t\t} else if (plat === \"linux\") {\n\t\t\t\tconst archStr = architecture === \"arm64\" ? \"aarch64\" : \"x86_64\";\n\t\t\t\treturn `fd-v${version}-${archStr}-unknown-linux-gnu.tar.gz`;\n\t\t\t} else if (plat === \"win32\") {\n\t\t\t\tconst archStr = architecture === \"arm64\" ? \"aarch64\" : \"x86_64\";\n\t\t\t\treturn `fd-v${version}-${archStr}-pc-windows-msvc.zip`;\n\t\t\t}\n\t\t\treturn null;\n\t\t},\n\t},\n\trg: {\n\t\tname: \"ripgrep\",\n\t\trepo: \"BurntSushi/ripgrep\",\n\t\tbinaryName: \"rg\",\n\t\ttagPrefix: \"\",\n\t\tgetAssetName: (version, plat, architecture) => {\n\t\t\tif (plat === \"darwin\") {\n\t\t\t\tconst archStr = architecture === \"arm64\" ? \"aarch64\" : \"x86_64\";\n\t\t\t\treturn `ripgrep-${version}-${archStr}-apple-darwin.tar.gz`;\n\t\t\t} else if (plat === \"linux\") {\n\t\t\t\tif (architecture === \"arm64\") {\n\t\t\t\t\treturn `ripgrep-${version}-aarch64-unknown-linux-gnu.tar.gz`;\n\t\t\t\t}\n\t\t\t\treturn `ripgrep-${version}-x86_64-unknown-linux-musl.tar.gz`;\n\t\t\t} else if (plat === \"win32\") {\n\t\t\t\tconst archStr = architecture === \"arm64\" ? \"aarch64\" : \"x86_64\";\n\t\t\t\treturn `ripgrep-${version}-${archStr}-pc-windows-msvc.zip`;\n\t\t\t}\n\t\t\treturn null;\n\t\t},\n\t},\n\twebtools: {\n\t\tname: \"webtools\",\n\t\trepo: \"kolisachint/webtools\",\n\t\tbinaryName: \"webtools\",\n\t\ttagPrefix: \"v\",\n\t\t// Release assets follow Rust target triples: webtools-<arch>-<target>.<ext>.\n\t\t// Some platforms may not be published yet; a missing asset 404s and ensureTool\n\t\t// degrades gracefully (returns undefined, tools fall back to an error message).\n\t\tgetAssetName: (_version, plat, architecture) => {\n\t\t\tconst archStr = architecture === \"arm64\" ? \"aarch64\" : \"x86_64\";\n\t\t\tif (plat === \"darwin\") {\n\t\t\t\treturn `webtools-${archStr}-apple-darwin.tar.gz`;\n\t\t\t} else if (plat === \"linux\") {\n\t\t\t\treturn `webtools-${archStr}-unknown-linux-gnu.tar.gz`;\n\t\t\t} else if (plat === \"win32\") {\n\t\t\t\treturn `webtools-${archStr}-pc-windows-msvc.zip`;\n\t\t\t}\n\t\t\treturn null;\n\t\t},\n\t},\n\tbrowsertools: {\n\t\tname: \"browsertools\",\n\t\trepo: \"kolisachint/browsertools\",\n\t\tbinaryName: \"browsertools\",\n\t\ttagPrefix: \"v\",\n\t\t// Release archives follow Rust target triples: browsertools-<arch>-<target>.<ext>\n\t\t// (release.yml builds gnu + musl for linux; we prefer the gnu variant to match\n\t\t// webtools/filetools). A missing platform asset 404s and ensureTool degrades\n\t\t// gracefully (returns undefined; the browser tools then surface an error).\n\t\tgetAssetName: (_version, plat, architecture) => {\n\t\t\tconst archStr = architecture === \"arm64\" ? \"aarch64\" : \"x86_64\";\n\t\t\tif (plat === \"darwin\") {\n\t\t\t\treturn `browsertools-${archStr}-apple-darwin.tar.gz`;\n\t\t\t} else if (plat === \"linux\") {\n\t\t\t\treturn `browsertools-${archStr}-unknown-linux-gnu.tar.gz`;\n\t\t\t} else if (plat === \"win32\") {\n\t\t\t\treturn `browsertools-${archStr}-pc-windows-msvc.zip`;\n\t\t\t}\n\t\t\treturn null;\n\t\t},\n\t},\n\tvoicetools: {\n\t\tname: \"voicetools\",\n\t\trepo: \"kolisachint/voicetools\",\n\t\tbinaryName: \"voicetools\",\n\t\ttagPrefix: \"v\",\n\t\t// Release archives follow Rust target triples: voicetools-<arch>-<target>.<ext>.\n\t\t// A missing platform asset 404s and ensureTool degrades gracefully (returns\n\t\t// undefined; the voice-transcribe caller then surfaces an error message).\n\t\tgetAssetName: (_version, plat, architecture) => {\n\t\t\tconst archStr = architecture === \"arm64\" ? \"aarch64\" : \"x86_64\";\n\t\t\tif (plat === \"darwin\") {\n\t\t\t\treturn `voicetools-${archStr}-apple-darwin.tar.gz`;\n\t\t\t} else if (plat === \"linux\") {\n\t\t\t\treturn `voicetools-${archStr}-unknown-linux-gnu.tar.gz`;\n\t\t\t} else if (plat === \"win32\") {\n\t\t\t\treturn `voicetools-${archStr}-pc-windows-msvc.zip`;\n\t\t\t}\n\t\t\treturn null;\n\t\t},\n\t},\n\tfiletools: {\n\t\tname: \"filetools\",\n\t\trepo: \"kolisachint/filetools\",\n\t\tbinaryName: \"filetools\",\n\t\ttagPrefix: \"v\",\n\t\t// Release archives are named `filetools-<target-triple>.<ext>` (see the repo's\n\t\t// release.yml `archive: filetools-$target`). A missing platform asset 404s and\n\t\t// ensureTool degrades gracefully (returns undefined; the doc tools then surface\n\t\t// an error result).\n\t\tgetAssetName: (_version, plat, architecture) => {\n\t\t\tconst archStr = architecture === \"arm64\" ? \"aarch64\" : \"x86_64\";\n\t\t\tif (plat === \"darwin\") {\n\t\t\t\treturn `filetools-${archStr}-apple-darwin.tar.gz`;\n\t\t\t} else if (plat === \"linux\") {\n\t\t\t\treturn `filetools-${archStr}-unknown-linux-gnu.tar.gz`;\n\t\t\t} else if (plat === \"win32\") {\n\t\t\t\treturn `filetools-${archStr}-pc-windows-msvc.zip`;\n\t\t\t}\n\t\t\treturn null;\n\t\t},\n\t},\n\tembsearch: {\n\t\tname: \"embsearch\",\n\t\trepo: \"kolisachint/embeddingsearchtools\",\n\t\tbinaryName: \"embsearch\",\n\t\ttagPrefix: \"v\",\n\t\t// Release archives follow Rust target triples: embsearch-<arch>-<target>.<ext>.\n\t\t// Must be the ONNX build (model bundled); the mock build is rejected by the\n\t\t// EmbsearchService. A missing platform asset 404s and ensureTool degrades\n\t\t// gracefully (returns undefined; the search tool stays lexical-only).\n\t\tgetAssetName: (_version, plat, architecture) => {\n\t\t\tconst archStr = architecture === \"arm64\" ? \"aarch64\" : \"x86_64\";\n\t\t\tif (plat === \"darwin\") {\n\t\t\t\treturn `embsearch-${archStr}-apple-darwin.tar.gz`;\n\t\t\t} else if (plat === \"linux\") {\n\t\t\t\treturn `embsearch-${archStr}-unknown-linux-gnu.tar.gz`;\n\t\t\t} else if (plat === \"win32\") {\n\t\t\t\treturn `embsearch-${archStr}-pc-windows-msvc.zip`;\n\t\t\t}\n\t\t\treturn null;\n\t\t},\n\t},\n};\n\n// Check if a command exists in PATH by trying to run it\nfunction commandExists(cmd: string): boolean {\n\ttry {\n\t\tconst result = spawnSync(cmd, [\"--version\"], { stdio: \"pipe\" });\n\t\t// Check for ENOENT error (command not found)\n\t\treturn result.error === undefined || result.error === null;\n\t} catch {\n\t\treturn false;\n\t}\n}\n\n// Resolved tool paths are stable for the life of the process. Cache the first\n// successful resolution so we never re-run the synchronous spawnSync probe in\n// commandExists() on every grep/find/glob invocation, which blocks the event loop.\nconst resolvedToolPathCache = new Map<ManagedTool, string>();\n\n// Get the path to a tool (system-wide or in our tools dir)\nfunction getToolPath(tool: ManagedTool): string | null {\n\tconst config = TOOLS[tool];\n\tif (!config) return null;\n\n\t// Explicit binary override (per-tool env var, e.g. HOOCODE_BROWSERTOOLS_BINARY).\n\t// Lets a developer point at a locally built binary that predates a release,\n\t// bypassing the tools-dir/PATH resolution and download. Authoritative when set\n\t// and the path exists.\n\tconst overrideEnv = `HOOCODE_${tool.toUpperCase()}_BINARY`;\n\tconst override = process.env[overrideEnv]?.trim();\n\tif (override && existsSync(override)) {\n\t\treturn override;\n\t}\n\n\t// Reuse a previously resolved path. A bare command name resolves via PATH;\n\t// an absolute path must still exist (revalidate cheaply with existsSync).\n\tconst cached = resolvedToolPathCache.get(tool);\n\tif (cached !== undefined) {\n\t\tconst isAbsolutePath = cached.includes(\"/\") || cached.includes(\"\\\\\");\n\t\tif (!isAbsolutePath || existsSync(cached)) {\n\t\t\treturn cached;\n\t\t}\n\t\tresolvedToolPathCache.delete(tool);\n\t}\n\n\t// Check our tools directory first\n\tconst localPath = join(TOOLS_DIR, config.binaryName + (platform() === \"win32\" ? \".exe\" : \"\"));\n\tif (existsSync(localPath)) {\n\t\tresolvedToolPathCache.set(tool, localPath);\n\t\treturn localPath;\n\t}\n\n\t// Check system PATH - if found, just return the command name (it's in PATH)\n\tconst systemBinaryNames = config.systemBinaryNames ?? [config.binaryName];\n\tfor (const systemBinaryName of systemBinaryNames) {\n\t\tif (commandExists(systemBinaryName)) {\n\t\t\tresolvedToolPathCache.set(tool, systemBinaryName);\n\t\t\treturn systemBinaryName;\n\t\t}\n\t}\n\n\treturn null;\n}\n\n// Fetch latest release version from GitHub\nasync function getLatestVersion(repo: string): Promise<string> {\n\tconst response = await fetch(`https://api.github.com/repos/${repo}/releases/latest`, {\n\t\theaders: { \"User-Agent\": `${APP_NAME}-coding-agent` },\n\t\tsignal: AbortSignal.timeout(NETWORK_TIMEOUT_MS),\n\t});\n\n\tif (!response.ok) {\n\t\tthrow new Error(`GitHub API error: ${response.status}`);\n\t}\n\n\tconst data = (await response.json()) as { tag_name: string };\n\treturn data.tag_name.replace(/^v/, \"\");\n}\n\n// Best-effort SHA-256 verification: fetch \"<downloadUrl>.sha256\" and, when it is\n// served (HTTP 200), verify the downloaded file against it. A 404 (or any other\n// non-200 / network error) means no published checksum, so verification is\n// skipped rather than treated as a failure. A genuine mismatch throws.\nasync function verifyChecksum(downloadUrl: string, filePath: string): Promise<void> {\n\tlet checksumResponse: Awaited<ReturnType<typeof fetch>>;\n\ttry {\n\t\tchecksumResponse = await fetch(`${downloadUrl}.sha256`, {\n\t\t\tsignal: AbortSignal.timeout(NETWORK_TIMEOUT_MS),\n\t\t});\n\t} catch {\n\t\t// Network error fetching the checksum is non-fatal for best-effort verification.\n\t\treturn;\n\t}\n\n\tif (checksumResponse.status !== 200) {\n\t\treturn;\n\t}\n\n\t// sha256 files are commonly \"<hex> <filename>\"; take the leading token.\n\tconst expectedHash = (await checksumResponse.text()).trim().split(/\\s+/)[0]?.toLowerCase();\n\tif (!expectedHash || !/^[0-9a-f]{64}$/.test(expectedHash)) {\n\t\t// Unusable checksum body: skip rather than fail (still best-effort).\n\t\treturn;\n\t}\n\n\tconst actualHash = createHash(\"sha256\").update(readFileSync(filePath)).digest(\"hex\");\n\tif (actualHash !== expectedHash) {\n\t\tthrow new Error(`Checksum mismatch for ${downloadUrl}: expected ${expectedHash}, got ${actualHash}`);\n\t}\n}\n\n/**\n * Live download progress: bytes received so far and the total from\n * Content-Length (null when the server doesn't send one). Callers use it to\n * drive a progress bar instead of an indefinite spinner.\n */\nexport type DownloadProgress = (receivedBytes: number, totalBytes: number | null) => void;\n\n// Download a file from URL into `dest`, validating integrity. Throws (and removes\n// the partial file) on a truncated transfer (bytes written != Content-Length when\n// the header is present) or a SHA-256 mismatch, so a corrupt artifact is never\n// left behind. Exported for tests.\nexport async function downloadFile(url: string, dest: string, onProgress?: DownloadProgress): Promise<void> {\n\ttry {\n\t\tconst response = await fetch(url, {\n\t\t\tsignal: AbortSignal.timeout(DOWNLOAD_TIMEOUT_MS),\n\t\t});\n\n\t\tif (!response.ok) {\n\t\t\tthrow new Error(`Failed to download: ${response.status}`);\n\t\t}\n\n\t\tif (!response.body) {\n\t\t\tthrow new Error(\"No response body\");\n\t\t}\n\n\t\tconst contentLengthHeader = response.headers.get(\"content-length\");\n\t\tconst expectedBytes =\n\t\t\tcontentLengthHeader !== null && contentLengthHeader.trim() !== \"\" ? Number(contentLengthHeader) : null;\n\n\t\tconst fileStream = createWriteStream(dest);\n\t\tconst source = Readable.fromWeb(response.body as Parameters<typeof Readable.fromWeb>[0]);\n\t\tif (onProgress) {\n\t\t\tlet received = 0;\n\t\t\tsource.on(\"data\", (chunk: Buffer) => {\n\t\t\t\treceived += chunk.length;\n\t\t\t\tonProgress(received, expectedBytes);\n\t\t\t});\n\t\t}\n\t\tawait pipeline(source, fileStream);\n\n\t\tif (expectedBytes !== null && Number.isFinite(expectedBytes)) {\n\t\t\tconst bytesWritten = statSync(dest).size;\n\t\t\tif (bytesWritten !== expectedBytes) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`Truncated download from ${url}: expected ${expectedBytes} bytes, received ${bytesWritten}`,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\tawait verifyChecksum(url, dest);\n\t} catch (e) {\n\t\t// Never leave a partial/corrupt file behind on any failure.\n\t\trmSync(dest, { force: true });\n\t\tthrow e;\n\t}\n}\n\nfunction findBinaryRecursively(rootDir: string, binaryFileName: string): string | null {\n\tconst stack: string[] = [rootDir];\n\n\twhile (stack.length > 0) {\n\t\tconst currentDir = stack.pop();\n\t\tif (!currentDir) continue;\n\n\t\tconst entries = readdirSync(currentDir, { withFileTypes: true });\n\t\tfor (const entry of entries) {\n\t\t\tconst fullPath = join(currentDir, entry.name);\n\t\t\tif (entry.isFile() && entry.name === binaryFileName) {\n\t\t\t\treturn fullPath;\n\t\t\t}\n\t\t\tif (entry.isDirectory()) {\n\t\t\t\tstack.push(fullPath);\n\t\t\t}\n\t\t}\n\t}\n\n\treturn null;\n}\n\n// Download and install a tool\nasync function downloadTool(tool: ManagedTool, onProgress?: DownloadProgress): Promise<string> {\n\tconst config = TOOLS[tool];\n\tif (!config) throw new Error(`Unknown tool: ${tool}`);\n\n\tconst plat = platform();\n\tconst architecture = arch();\n\n\t// Get latest version\n\tconst version = await getLatestVersion(config.repo);\n\n\t// Get asset name for this platform\n\tconst assetName = config.getAssetName(version, plat, architecture);\n\tif (!assetName) {\n\t\tthrow new Error(`Unsupported platform: ${plat}/${architecture}`);\n\t}\n\n\t// Create tools directory\n\tmkdirSync(TOOLS_DIR, { recursive: true });\n\n\tconst downloadUrl = `https://github.com/${config.repo}/releases/download/${config.tagPrefix}${version}/${assetName}`;\n\tconst archivePath = join(TOOLS_DIR, assetName);\n\tconst binaryExt = plat === \"win32\" ? \".exe\" : \"\";\n\tconst binaryPath = join(TOOLS_DIR, config.binaryName + binaryExt);\n\n\t// Download to a unique temp path, validate, then atomically rename into place.\n\t// Writing the shared archive path directly would leave a corrupt partial behind\n\t// if the transfer fails or is truncated. fd/rg/webtools can also download\n\t// concurrently at startup, so the per-attempt temp name must be unique.\n\tconst tempArchivePath = `${archivePath}.${process.pid}.${randomBytes(6).toString(\"hex\")}.part`;\n\n\t// Extract into a unique temp directory. fd and rg downloads can run concurrently\n\t// during startup, so sharing a fixed directory causes races.\n\tconst extractDir = join(\n\t\tTOOLS_DIR,\n\t\t`extract_tmp_${config.binaryName}_${process.pid}_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`,\n\t);\n\n\ttry {\n\t\t// One retry (2 attempts total) around download + integrity verification.\n\t\t// downloadFile removes its own partial on failure, so each attempt is clean.\n\t\tlet lastError: unknown;\n\t\tlet downloaded = false;\n\t\tfor (let attempt = 1; attempt <= 2 && !downloaded; attempt++) {\n\t\t\ttry {\n\t\t\t\tawait downloadFile(downloadUrl, tempArchivePath, onProgress);\n\t\t\t\tdownloaded = true;\n\t\t\t} catch (e) {\n\t\t\t\tlastError = e;\n\t\t\t\trmSync(tempArchivePath, { force: true });\n\t\t\t}\n\t\t}\n\t\tif (!downloaded) {\n\t\t\tthrow lastError instanceof Error ? lastError : new Error(String(lastError));\n\t\t}\n\n\t\t// Atomic publish of the verified archive, then extract.\n\t\trenameSync(tempArchivePath, archivePath);\n\t\tmkdirSync(extractDir, { recursive: true });\n\n\t\tif (assetName.endsWith(\".tar.gz\")) {\n\t\t\tconst extractResult = spawnSync(\"tar\", [\"xzf\", archivePath, \"-C\", extractDir], { stdio: \"pipe\" });\n\t\t\tif (extractResult.error || extractResult.status !== 0) {\n\t\t\t\tconst errMsg = extractResult.error?.message ?? extractResult.stderr?.toString().trim() ?? \"unknown error\";\n\t\t\t\tthrow new Error(`Failed to extract ${assetName}: ${errMsg}`);\n\t\t\t}\n\t\t} else if (assetName.endsWith(\".zip\")) {\n\t\t\tawait extractZip(archivePath, { dir: extractDir });\n\t\t} else {\n\t\t\tthrow new Error(`Unsupported archive format: ${assetName}`);\n\t\t}\n\n\t\t// Find the binary in extracted files. Some archives contain files directly\n\t\t// at root, others nest under a versioned subdirectory.\n\t\tconst binaryFileName = config.binaryName + binaryExt;\n\t\tconst extractedDir = join(extractDir, assetName.replace(/\\.(tar\\.gz|zip)$/, \"\"));\n\t\tconst extractedBinaryCandidates = [join(extractedDir, binaryFileName), join(extractDir, binaryFileName)];\n\t\tlet extractedBinary = extractedBinaryCandidates.find((candidate) => existsSync(candidate));\n\n\t\tif (!extractedBinary) {\n\t\t\textractedBinary = findBinaryRecursively(extractDir, binaryFileName) ?? undefined;\n\t\t}\n\n\t\tif (extractedBinary) {\n\t\t\trenameSync(extractedBinary, binaryPath);\n\t\t} else {\n\t\t\tthrow new Error(`Binary not found in archive: expected ${binaryFileName} under ${extractDir}`);\n\t\t}\n\n\t\t// Make executable (Unix only)\n\t\tif (plat !== \"win32\") {\n\t\t\tchmodSync(binaryPath, 0o755);\n\t\t}\n\t} finally {\n\t\t// Guaranteed cleanup of every transient artifact on ANY outcome: the temp\n\t\t// download (if a failure left it before the rename), the published archive,\n\t\t// and the temp extract dir.\n\t\trmSync(tempArchivePath, { force: true });\n\t\trmSync(archivePath, { force: true });\n\t\trmSync(extractDir, { recursive: true, force: true });\n\t}\n\n\treturn binaryPath;\n}\n\n// Termux package names for tools\nconst TERMUX_PACKAGES: Record<string, string> = {\n\tfd: \"fd\",\n\trg: \"ripgrep\",\n\twebtools: \"webtools\",\n\tfiletools: \"filetools\",\n\tbrowsertools: \"browsertools\",\n\tvoicetools: \"voicetools\",\n\tembsearch: \"embsearch\",\n};\n\n// Ensure a tool is available, downloading if necessary\n// Returns the path to the tool, or null if unavailable\n// onProgress receives live byte counts while the release archive downloads.\nexport async function ensureTool(\n\ttool: ManagedTool,\n\tsilent: boolean = false,\n\tonProgress?: DownloadProgress,\n): Promise<string | undefined> {\n\tconst existingPath = getToolPath(tool);\n\tif (existingPath) {\n\t\treturn existingPath;\n\t}\n\n\tconst config = TOOLS[tool];\n\tif (!config) return undefined;\n\n\tif (isOfflineModeEnabled()) {\n\t\tif (!silent) {\n\t\t\tconsole.log(chalk.yellow(`${config.name} not found. Offline mode enabled, skipping download.`));\n\t\t}\n\t\treturn undefined;\n\t}\n\n\t// On Android/Termux, Linux binaries don't work due to Bionic libc incompatibility.\n\t// Users must install via pkg.\n\tif (platform() === \"android\") {\n\t\tconst pkgName = TERMUX_PACKAGES[tool] ?? tool;\n\t\tif (!silent) {\n\t\t\tconsole.log(chalk.yellow(`${config.name} not found. Install with: pkg install ${pkgName}`));\n\t\t}\n\t\treturn undefined;\n\t}\n\n\t// Tool not found - download it\n\tif (!silent) {\n\t\tconsole.log(chalk.dim(`${config.name} not found. Downloading...`));\n\t}\n\n\ttry {\n\t\tconst path = await downloadTool(tool, onProgress);\n\t\tif (!silent) {\n\t\t\tconsole.log(chalk.dim(`${config.name} installed to ${path}`));\n\t\t}\n\t\treturn path;\n\t} catch (e) {\n\t\tif (!silent) {\n\t\t\tconsole.log(chalk.yellow(`Failed to download ${config.name}: ${e instanceof Error ? e.message : e}`));\n\t\t}\n\t\treturn undefined;\n\t}\n}\n"]}
1
+ {"version":3,"file":"tools-manager.d.ts","sourceRoot":"","sources":["../../src/utils/tools-manager.ts"],"names":[],"mappings":"AA+BA,gFAAgF;AAChF,MAAM,MAAM,WAAW,GAAG,IAAI,GAAG,IAAI,GAAG,UAAU,GAAG,YAAY,GAAG,WAAW,CAAC;AAgOhF;;;;GAIG;AACH,MAAM,MAAM,gBAAgB,GAAG,CAAC,aAAa,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAC;AAM1F,wBAAsB,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,CA4C1G;AA6ID,wBAAsB,UAAU,CAC/B,IAAI,EAAE,WAAW,EACjB,MAAM,GAAE,OAAe,EACvB,UAAU,CAAC,EAAE,gBAAgB,GAC3B,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CA2C7B","sourcesContent":["import chalk from \"chalk\";\nimport { spawnSync } from \"child_process\";\nimport { createHash, randomBytes } from \"crypto\";\nimport extractZip from \"extract-zip\";\nimport {\n\tchmodSync,\n\tcreateWriteStream,\n\texistsSync,\n\tmkdirSync,\n\treaddirSync,\n\treadFileSync,\n\trenameSync,\n\trmSync,\n\tstatSync,\n} from \"fs\";\nimport { arch, platform } from \"os\";\nimport { join } from \"path\";\nimport { Readable } from \"stream\";\nimport { pipeline } from \"stream/promises\";\nimport { APP_NAME, getBinDir } from \"../config.js\";\n\nconst TOOLS_DIR = getBinDir();\nconst NETWORK_TIMEOUT_MS = 10_000;\nconst DOWNLOAD_TIMEOUT_MS = 120_000;\n\nfunction isOfflineModeEnabled(): boolean {\n\tconst value = process.env.HOOCODE_OFFLINE;\n\tif (!value) return false;\n\treturn value === \"1\" || value.toLowerCase() === \"true\" || value.toLowerCase() === \"yes\";\n}\n\n/** Tools whose binaries hoocode can resolve from PATH or download on demand. */\nexport type ManagedTool = \"fd\" | \"rg\" | \"webtools\" | \"voicetools\" | \"embsearch\";\n\ninterface ToolConfig {\n\tname: string;\n\trepo: string; // GitHub repo (e.g., \"sharkdp/fd\")\n\tbinaryName: string; // Name of the binary inside the archive\n\tsystemBinaryNames?: string[]; // Alternative system command names to try before downloading\n\ttagPrefix: string; // Prefix for tags (e.g., \"v\" for v1.0.0, \"\" for 1.0.0)\n\tgetAssetName: (version: string, plat: string, architecture: string) => string | null;\n}\n\nconst TOOLS: Record<string, ToolConfig> = {\n\tfd: {\n\t\tname: \"fd\",\n\t\trepo: \"sharkdp/fd\",\n\t\tbinaryName: \"fd\",\n\t\tsystemBinaryNames: [\"fd\", \"fdfind\"],\n\t\ttagPrefix: \"v\",\n\t\tgetAssetName: (version, plat, architecture) => {\n\t\t\tif (plat === \"darwin\") {\n\t\t\t\tconst archStr = architecture === \"arm64\" ? \"aarch64\" : \"x86_64\";\n\t\t\t\treturn `fd-v${version}-${archStr}-apple-darwin.tar.gz`;\n\t\t\t} else if (plat === \"linux\") {\n\t\t\t\tconst archStr = architecture === \"arm64\" ? \"aarch64\" : \"x86_64\";\n\t\t\t\treturn `fd-v${version}-${archStr}-unknown-linux-gnu.tar.gz`;\n\t\t\t} else if (plat === \"win32\") {\n\t\t\t\tconst archStr = architecture === \"arm64\" ? \"aarch64\" : \"x86_64\";\n\t\t\t\treturn `fd-v${version}-${archStr}-pc-windows-msvc.zip`;\n\t\t\t}\n\t\t\treturn null;\n\t\t},\n\t},\n\trg: {\n\t\tname: \"ripgrep\",\n\t\trepo: \"BurntSushi/ripgrep\",\n\t\tbinaryName: \"rg\",\n\t\ttagPrefix: \"\",\n\t\tgetAssetName: (version, plat, architecture) => {\n\t\t\tif (plat === \"darwin\") {\n\t\t\t\tconst archStr = architecture === \"arm64\" ? \"aarch64\" : \"x86_64\";\n\t\t\t\treturn `ripgrep-${version}-${archStr}-apple-darwin.tar.gz`;\n\t\t\t} else if (plat === \"linux\") {\n\t\t\t\tif (architecture === \"arm64\") {\n\t\t\t\t\treturn `ripgrep-${version}-aarch64-unknown-linux-gnu.tar.gz`;\n\t\t\t\t}\n\t\t\t\treturn `ripgrep-${version}-x86_64-unknown-linux-musl.tar.gz`;\n\t\t\t} else if (plat === \"win32\") {\n\t\t\t\tconst archStr = architecture === \"arm64\" ? \"aarch64\" : \"x86_64\";\n\t\t\t\treturn `ripgrep-${version}-${archStr}-pc-windows-msvc.zip`;\n\t\t\t}\n\t\t\treturn null;\n\t\t},\n\t},\n\twebtools: {\n\t\tname: \"webtools\",\n\t\trepo: \"kolisachint/webtools\",\n\t\tbinaryName: \"webtools\",\n\t\ttagPrefix: \"v\",\n\t\t// Release assets follow Rust target triples: webtools-<arch>-<target>.<ext>.\n\t\t// Some platforms may not be published yet; a missing asset 404s and ensureTool\n\t\t// degrades gracefully (returns undefined, tools fall back to an error message).\n\t\tgetAssetName: (_version, plat, architecture) => {\n\t\t\tconst archStr = architecture === \"arm64\" ? \"aarch64\" : \"x86_64\";\n\t\t\tif (plat === \"darwin\") {\n\t\t\t\treturn `webtools-${archStr}-apple-darwin.tar.gz`;\n\t\t\t} else if (plat === \"linux\") {\n\t\t\t\treturn `webtools-${archStr}-unknown-linux-gnu.tar.gz`;\n\t\t\t} else if (plat === \"win32\") {\n\t\t\t\treturn `webtools-${archStr}-pc-windows-msvc.zip`;\n\t\t\t}\n\t\t\treturn null;\n\t\t},\n\t},\n\tvoicetools: {\n\t\tname: \"voicetools\",\n\t\trepo: \"kolisachint/voicetools\",\n\t\tbinaryName: \"voicetools\",\n\t\ttagPrefix: \"v\",\n\t\t// Release archives follow Rust target triples: voicetools-<arch>-<target>.<ext>.\n\t\t// A missing platform asset 404s and ensureTool degrades gracefully (returns\n\t\t// undefined; the voice-transcribe caller then surfaces an error message).\n\t\tgetAssetName: (_version, plat, architecture) => {\n\t\t\tconst archStr = architecture === \"arm64\" ? \"aarch64\" : \"x86_64\";\n\t\t\tif (plat === \"darwin\") {\n\t\t\t\treturn `voicetools-${archStr}-apple-darwin.tar.gz`;\n\t\t\t} else if (plat === \"linux\") {\n\t\t\t\treturn `voicetools-${archStr}-unknown-linux-gnu.tar.gz`;\n\t\t\t} else if (plat === \"win32\") {\n\t\t\t\treturn `voicetools-${archStr}-pc-windows-msvc.zip`;\n\t\t\t}\n\t\t\treturn null;\n\t\t},\n\t},\n\tembsearch: {\n\t\tname: \"embsearch\",\n\t\trepo: \"kolisachint/embeddingsearchtools\",\n\t\tbinaryName: \"embsearch\",\n\t\ttagPrefix: \"v\",\n\t\t// Release archives follow Rust target triples: embsearch-<arch>-<target>.<ext>.\n\t\t// Must be the ONNX build (model bundled); the mock build is rejected by the\n\t\t// EmbsearchService. A missing platform asset 404s and ensureTool degrades\n\t\t// gracefully (returns undefined; the search tool stays lexical-only).\n\t\tgetAssetName: (_version, plat, architecture) => {\n\t\t\tconst archStr = architecture === \"arm64\" ? \"aarch64\" : \"x86_64\";\n\t\t\tif (plat === \"darwin\") {\n\t\t\t\treturn `embsearch-${archStr}-apple-darwin.tar.gz`;\n\t\t\t} else if (plat === \"linux\") {\n\t\t\t\treturn `embsearch-${archStr}-unknown-linux-gnu.tar.gz`;\n\t\t\t} else if (plat === \"win32\") {\n\t\t\t\treturn `embsearch-${archStr}-pc-windows-msvc.zip`;\n\t\t\t}\n\t\t\treturn null;\n\t\t},\n\t},\n};\n\n// Check if a command exists in PATH by trying to run it\nfunction commandExists(cmd: string): boolean {\n\ttry {\n\t\tconst result = spawnSync(cmd, [\"--version\"], { stdio: \"pipe\" });\n\t\t// Check for ENOENT error (command not found)\n\t\treturn result.error === undefined || result.error === null;\n\t} catch {\n\t\treturn false;\n\t}\n}\n\n// Resolved tool paths are stable for the life of the process. Cache the first\n// successful resolution so we never re-run the synchronous spawnSync probe in\n// commandExists() on every grep/find/glob invocation, which blocks the event loop.\nconst resolvedToolPathCache = new Map<ManagedTool, string>();\n\n// Get the path to a tool (system-wide or in our tools dir)\nfunction getToolPath(tool: ManagedTool): string | null {\n\tconst config = TOOLS[tool];\n\tif (!config) return null;\n\n\t// Explicit binary override (per-tool env var, e.g. HOOCODE_WEBTOOLS_BINARY).\n\t// Lets a developer point at a locally built binary that predates a release,\n\t// bypassing the tools-dir/PATH resolution and download. Authoritative when set\n\t// and the path exists.\n\tconst overrideEnv = `HOOCODE_${tool.toUpperCase()}_BINARY`;\n\tconst override = process.env[overrideEnv]?.trim();\n\tif (override && existsSync(override)) {\n\t\treturn override;\n\t}\n\n\t// Reuse a previously resolved path. A bare command name resolves via PATH;\n\t// an absolute path must still exist (revalidate cheaply with existsSync).\n\tconst cached = resolvedToolPathCache.get(tool);\n\tif (cached !== undefined) {\n\t\tconst isAbsolutePath = cached.includes(\"/\") || cached.includes(\"\\\\\");\n\t\tif (!isAbsolutePath || existsSync(cached)) {\n\t\t\treturn cached;\n\t\t}\n\t\tresolvedToolPathCache.delete(tool);\n\t}\n\n\t// Check our tools directory first\n\tconst localPath = join(TOOLS_DIR, config.binaryName + (platform() === \"win32\" ? \".exe\" : \"\"));\n\tif (existsSync(localPath)) {\n\t\tresolvedToolPathCache.set(tool, localPath);\n\t\treturn localPath;\n\t}\n\n\t// Check system PATH - if found, just return the command name (it's in PATH)\n\tconst systemBinaryNames = config.systemBinaryNames ?? [config.binaryName];\n\tfor (const systemBinaryName of systemBinaryNames) {\n\t\tif (commandExists(systemBinaryName)) {\n\t\t\tresolvedToolPathCache.set(tool, systemBinaryName);\n\t\t\treturn systemBinaryName;\n\t\t}\n\t}\n\n\treturn null;\n}\n\n// Fetch latest release version from GitHub\nasync function getLatestVersion(repo: string): Promise<string> {\n\tconst response = await fetch(`https://api.github.com/repos/${repo}/releases/latest`, {\n\t\theaders: { \"User-Agent\": `${APP_NAME}-coding-agent` },\n\t\tsignal: AbortSignal.timeout(NETWORK_TIMEOUT_MS),\n\t});\n\n\tif (!response.ok) {\n\t\tthrow new Error(`GitHub API error: ${response.status}`);\n\t}\n\n\tconst data = (await response.json()) as { tag_name: string };\n\treturn data.tag_name.replace(/^v/, \"\");\n}\n\n// Best-effort SHA-256 verification: fetch \"<downloadUrl>.sha256\" and, when it is\n// served (HTTP 200), verify the downloaded file against it. A 404 (or any other\n// non-200 / network error) means no published checksum, so verification is\n// skipped rather than treated as a failure. A genuine mismatch throws.\nasync function verifyChecksum(downloadUrl: string, filePath: string): Promise<void> {\n\tlet checksumResponse: Awaited<ReturnType<typeof fetch>>;\n\ttry {\n\t\tchecksumResponse = await fetch(`${downloadUrl}.sha256`, {\n\t\t\tsignal: AbortSignal.timeout(NETWORK_TIMEOUT_MS),\n\t\t});\n\t} catch {\n\t\t// Network error fetching the checksum is non-fatal for best-effort verification.\n\t\treturn;\n\t}\n\n\tif (checksumResponse.status !== 200) {\n\t\treturn;\n\t}\n\n\t// sha256 files are commonly \"<hex> <filename>\"; take the leading token.\n\tconst expectedHash = (await checksumResponse.text()).trim().split(/\\s+/)[0]?.toLowerCase();\n\tif (!expectedHash || !/^[0-9a-f]{64}$/.test(expectedHash)) {\n\t\t// Unusable checksum body: skip rather than fail (still best-effort).\n\t\treturn;\n\t}\n\n\tconst actualHash = createHash(\"sha256\").update(readFileSync(filePath)).digest(\"hex\");\n\tif (actualHash !== expectedHash) {\n\t\tthrow new Error(`Checksum mismatch for ${downloadUrl}: expected ${expectedHash}, got ${actualHash}`);\n\t}\n}\n\n/**\n * Live download progress: bytes received so far and the total from\n * Content-Length (null when the server doesn't send one). Callers use it to\n * drive a progress bar instead of an indefinite spinner.\n */\nexport type DownloadProgress = (receivedBytes: number, totalBytes: number | null) => void;\n\n// Download a file from URL into `dest`, validating integrity. Throws (and removes\n// the partial file) on a truncated transfer (bytes written != Content-Length when\n// the header is present) or a SHA-256 mismatch, so a corrupt artifact is never\n// left behind. Exported for tests.\nexport async function downloadFile(url: string, dest: string, onProgress?: DownloadProgress): Promise<void> {\n\ttry {\n\t\tconst response = await fetch(url, {\n\t\t\tsignal: AbortSignal.timeout(DOWNLOAD_TIMEOUT_MS),\n\t\t});\n\n\t\tif (!response.ok) {\n\t\t\tthrow new Error(`Failed to download: ${response.status}`);\n\t\t}\n\n\t\tif (!response.body) {\n\t\t\tthrow new Error(\"No response body\");\n\t\t}\n\n\t\tconst contentLengthHeader = response.headers.get(\"content-length\");\n\t\tconst expectedBytes =\n\t\t\tcontentLengthHeader !== null && contentLengthHeader.trim() !== \"\" ? Number(contentLengthHeader) : null;\n\n\t\tconst fileStream = createWriteStream(dest);\n\t\tconst source = Readable.fromWeb(response.body as Parameters<typeof Readable.fromWeb>[0]);\n\t\tif (onProgress) {\n\t\t\tlet received = 0;\n\t\t\tsource.on(\"data\", (chunk: Buffer) => {\n\t\t\t\treceived += chunk.length;\n\t\t\t\tonProgress(received, expectedBytes);\n\t\t\t});\n\t\t}\n\t\tawait pipeline(source, fileStream);\n\n\t\tif (expectedBytes !== null && Number.isFinite(expectedBytes)) {\n\t\t\tconst bytesWritten = statSync(dest).size;\n\t\t\tif (bytesWritten !== expectedBytes) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`Truncated download from ${url}: expected ${expectedBytes} bytes, received ${bytesWritten}`,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\tawait verifyChecksum(url, dest);\n\t} catch (e) {\n\t\t// Never leave a partial/corrupt file behind on any failure.\n\t\trmSync(dest, { force: true });\n\t\tthrow e;\n\t}\n}\n\nfunction findBinaryRecursively(rootDir: string, binaryFileName: string): string | null {\n\tconst stack: string[] = [rootDir];\n\n\twhile (stack.length > 0) {\n\t\tconst currentDir = stack.pop();\n\t\tif (!currentDir) continue;\n\n\t\tconst entries = readdirSync(currentDir, { withFileTypes: true });\n\t\tfor (const entry of entries) {\n\t\t\tconst fullPath = join(currentDir, entry.name);\n\t\t\tif (entry.isFile() && entry.name === binaryFileName) {\n\t\t\t\treturn fullPath;\n\t\t\t}\n\t\t\tif (entry.isDirectory()) {\n\t\t\t\tstack.push(fullPath);\n\t\t\t}\n\t\t}\n\t}\n\n\treturn null;\n}\n\n// Download and install a tool\nasync function downloadTool(tool: ManagedTool, onProgress?: DownloadProgress): Promise<string> {\n\tconst config = TOOLS[tool];\n\tif (!config) throw new Error(`Unknown tool: ${tool}`);\n\n\tconst plat = platform();\n\tconst architecture = arch();\n\n\t// Get latest version\n\tconst version = await getLatestVersion(config.repo);\n\n\t// Get asset name for this platform\n\tconst assetName = config.getAssetName(version, plat, architecture);\n\tif (!assetName) {\n\t\tthrow new Error(`Unsupported platform: ${plat}/${architecture}`);\n\t}\n\n\t// Create tools directory\n\tmkdirSync(TOOLS_DIR, { recursive: true });\n\n\tconst downloadUrl = `https://github.com/${config.repo}/releases/download/${config.tagPrefix}${version}/${assetName}`;\n\tconst archivePath = join(TOOLS_DIR, assetName);\n\tconst binaryExt = plat === \"win32\" ? \".exe\" : \"\";\n\tconst binaryPath = join(TOOLS_DIR, config.binaryName + binaryExt);\n\n\t// Download to a unique temp path, validate, then atomically rename into place.\n\t// Writing the shared archive path directly would leave a corrupt partial behind\n\t// if the transfer fails or is truncated. fd/rg/webtools can also download\n\t// concurrently at startup, so the per-attempt temp name must be unique.\n\tconst tempArchivePath = `${archivePath}.${process.pid}.${randomBytes(6).toString(\"hex\")}.part`;\n\n\t// Extract into a unique temp directory. fd and rg downloads can run concurrently\n\t// during startup, so sharing a fixed directory causes races.\n\tconst extractDir = join(\n\t\tTOOLS_DIR,\n\t\t`extract_tmp_${config.binaryName}_${process.pid}_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`,\n\t);\n\n\ttry {\n\t\t// One retry (2 attempts total) around download + integrity verification.\n\t\t// downloadFile removes its own partial on failure, so each attempt is clean.\n\t\tlet lastError: unknown;\n\t\tlet downloaded = false;\n\t\tfor (let attempt = 1; attempt <= 2 && !downloaded; attempt++) {\n\t\t\ttry {\n\t\t\t\tawait downloadFile(downloadUrl, tempArchivePath, onProgress);\n\t\t\t\tdownloaded = true;\n\t\t\t} catch (e) {\n\t\t\t\tlastError = e;\n\t\t\t\trmSync(tempArchivePath, { force: true });\n\t\t\t}\n\t\t}\n\t\tif (!downloaded) {\n\t\t\tthrow lastError instanceof Error ? lastError : new Error(String(lastError));\n\t\t}\n\n\t\t// Atomic publish of the verified archive, then extract.\n\t\trenameSync(tempArchivePath, archivePath);\n\t\tmkdirSync(extractDir, { recursive: true });\n\n\t\tif (assetName.endsWith(\".tar.gz\")) {\n\t\t\tconst extractResult = spawnSync(\"tar\", [\"xzf\", archivePath, \"-C\", extractDir], { stdio: \"pipe\" });\n\t\t\tif (extractResult.error || extractResult.status !== 0) {\n\t\t\t\tconst errMsg = extractResult.error?.message ?? extractResult.stderr?.toString().trim() ?? \"unknown error\";\n\t\t\t\tthrow new Error(`Failed to extract ${assetName}: ${errMsg}`);\n\t\t\t}\n\t\t} else if (assetName.endsWith(\".zip\")) {\n\t\t\tawait extractZip(archivePath, { dir: extractDir });\n\t\t} else {\n\t\t\tthrow new Error(`Unsupported archive format: ${assetName}`);\n\t\t}\n\n\t\t// Find the binary in extracted files. Some archives contain files directly\n\t\t// at root, others nest under a versioned subdirectory.\n\t\tconst binaryFileName = config.binaryName + binaryExt;\n\t\tconst extractedDir = join(extractDir, assetName.replace(/\\.(tar\\.gz|zip)$/, \"\"));\n\t\tconst extractedBinaryCandidates = [join(extractedDir, binaryFileName), join(extractDir, binaryFileName)];\n\t\tlet extractedBinary = extractedBinaryCandidates.find((candidate) => existsSync(candidate));\n\n\t\tif (!extractedBinary) {\n\t\t\textractedBinary = findBinaryRecursively(extractDir, binaryFileName) ?? undefined;\n\t\t}\n\n\t\tif (extractedBinary) {\n\t\t\trenameSync(extractedBinary, binaryPath);\n\t\t} else {\n\t\t\tthrow new Error(`Binary not found in archive: expected ${binaryFileName} under ${extractDir}`);\n\t\t}\n\n\t\t// Make executable (Unix only)\n\t\tif (plat !== \"win32\") {\n\t\t\tchmodSync(binaryPath, 0o755);\n\t\t}\n\t} finally {\n\t\t// Guaranteed cleanup of every transient artifact on ANY outcome: the temp\n\t\t// download (if a failure left it before the rename), the published archive,\n\t\t// and the temp extract dir.\n\t\trmSync(tempArchivePath, { force: true });\n\t\trmSync(archivePath, { force: true });\n\t\trmSync(extractDir, { recursive: true, force: true });\n\t}\n\n\treturn binaryPath;\n}\n\n// Termux package names for tools\nconst TERMUX_PACKAGES: Record<string, string> = {\n\tfd: \"fd\",\n\trg: \"ripgrep\",\n\twebtools: \"webtools\",\n\tvoicetools: \"voicetools\",\n\tembsearch: \"embsearch\",\n};\n\n// Ensure a tool is available, downloading if necessary\n// Returns the path to the tool, or null if unavailable\n// onProgress receives live byte counts while the release archive downloads.\nexport async function ensureTool(\n\ttool: ManagedTool,\n\tsilent: boolean = false,\n\tonProgress?: DownloadProgress,\n): Promise<string | undefined> {\n\tconst existingPath = getToolPath(tool);\n\tif (existingPath) {\n\t\treturn existingPath;\n\t}\n\n\tconst config = TOOLS[tool];\n\tif (!config) return undefined;\n\n\tif (isOfflineModeEnabled()) {\n\t\tif (!silent) {\n\t\t\tconsole.log(chalk.yellow(`${config.name} not found. Offline mode enabled, skipping download.`));\n\t\t}\n\t\treturn undefined;\n\t}\n\n\t// On Android/Termux, Linux binaries don't work due to Bionic libc incompatibility.\n\t// Users must install via pkg.\n\tif (platform() === \"android\") {\n\t\tconst pkgName = TERMUX_PACKAGES[tool] ?? tool;\n\t\tif (!silent) {\n\t\t\tconsole.log(chalk.yellow(`${config.name} not found. Install with: pkg install ${pkgName}`));\n\t\t}\n\t\treturn undefined;\n\t}\n\n\t// Tool not found - download it\n\tif (!silent) {\n\t\tconsole.log(chalk.dim(`${config.name} not found. Downloading...`));\n\t}\n\n\ttry {\n\t\tconst path = await downloadTool(tool, onProgress);\n\t\tif (!silent) {\n\t\t\tconsole.log(chalk.dim(`${config.name} installed to ${path}`));\n\t\t}\n\t\treturn path;\n\t} catch (e) {\n\t\tif (!silent) {\n\t\t\tconsole.log(chalk.yellow(`Failed to download ${config.name}: ${e instanceof Error ? e.message : e}`));\n\t\t}\n\t\treturn undefined;\n\t}\n}\n"]}
@@ -85,29 +85,6 @@ const TOOLS = {
85
85
  return null;
86
86
  },
87
87
  },
88
- browsertools: {
89
- name: "browsertools",
90
- repo: "kolisachint/browsertools",
91
- binaryName: "browsertools",
92
- tagPrefix: "v",
93
- // Release archives follow Rust target triples: browsertools-<arch>-<target>.<ext>
94
- // (release.yml builds gnu + musl for linux; we prefer the gnu variant to match
95
- // webtools/filetools). A missing platform asset 404s and ensureTool degrades
96
- // gracefully (returns undefined; the browser tools then surface an error).
97
- getAssetName: (_version, plat, architecture) => {
98
- const archStr = architecture === "arm64" ? "aarch64" : "x86_64";
99
- if (plat === "darwin") {
100
- return `browsertools-${archStr}-apple-darwin.tar.gz`;
101
- }
102
- else if (plat === "linux") {
103
- return `browsertools-${archStr}-unknown-linux-gnu.tar.gz`;
104
- }
105
- else if (plat === "win32") {
106
- return `browsertools-${archStr}-pc-windows-msvc.zip`;
107
- }
108
- return null;
109
- },
110
- },
111
88
  voicetools: {
112
89
  name: "voicetools",
113
90
  repo: "kolisachint/voicetools",
@@ -130,29 +107,6 @@ const TOOLS = {
130
107
  return null;
131
108
  },
132
109
  },
133
- filetools: {
134
- name: "filetools",
135
- repo: "kolisachint/filetools",
136
- binaryName: "filetools",
137
- tagPrefix: "v",
138
- // Release archives are named `filetools-<target-triple>.<ext>` (see the repo's
139
- // release.yml `archive: filetools-$target`). A missing platform asset 404s and
140
- // ensureTool degrades gracefully (returns undefined; the doc tools then surface
141
- // an error result).
142
- getAssetName: (_version, plat, architecture) => {
143
- const archStr = architecture === "arm64" ? "aarch64" : "x86_64";
144
- if (plat === "darwin") {
145
- return `filetools-${archStr}-apple-darwin.tar.gz`;
146
- }
147
- else if (plat === "linux") {
148
- return `filetools-${archStr}-unknown-linux-gnu.tar.gz`;
149
- }
150
- else if (plat === "win32") {
151
- return `filetools-${archStr}-pc-windows-msvc.zip`;
152
- }
153
- return null;
154
- },
155
- },
156
110
  embsearch: {
157
111
  name: "embsearch",
158
112
  repo: "kolisachint/embeddingsearchtools",
@@ -197,7 +151,7 @@ function getToolPath(tool) {
197
151
  const config = TOOLS[tool];
198
152
  if (!config)
199
153
  return null;
200
- // Explicit binary override (per-tool env var, e.g. HOOCODE_BROWSERTOOLS_BINARY).
154
+ // Explicit binary override (per-tool env var, e.g. HOOCODE_WEBTOOLS_BINARY).
201
155
  // Lets a developer point at a locally built binary that predates a release,
202
156
  // bypassing the tools-dir/PATH resolution and download. Authoritative when set
203
157
  // and the path exists.
@@ -430,8 +384,6 @@ const TERMUX_PACKAGES = {
430
384
  fd: "fd",
431
385
  rg: "ripgrep",
432
386
  webtools: "webtools",
433
- filetools: "filetools",
434
- browsertools: "browsertools",
435
387
  voicetools: "voicetools",
436
388
  embsearch: "embsearch",
437
389
  };
@@ -1 +1 @@
1
- {"version":3,"file":"tools-manager.js","sourceRoot":"","sources":["../../src/utils/tools-manager.ts"],"names":[],"mappings":"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","sourcesContent":["import chalk from \"chalk\";\nimport { spawnSync } from \"child_process\";\nimport { createHash, randomBytes } from \"crypto\";\nimport extractZip from \"extract-zip\";\nimport {\n\tchmodSync,\n\tcreateWriteStream,\n\texistsSync,\n\tmkdirSync,\n\treaddirSync,\n\treadFileSync,\n\trenameSync,\n\trmSync,\n\tstatSync,\n} from \"fs\";\nimport { arch, platform } from \"os\";\nimport { join } from \"path\";\nimport { Readable } from \"stream\";\nimport { pipeline } from \"stream/promises\";\nimport { APP_NAME, getBinDir } from \"../config.js\";\n\nconst TOOLS_DIR = getBinDir();\nconst NETWORK_TIMEOUT_MS = 10_000;\nconst DOWNLOAD_TIMEOUT_MS = 120_000;\n\nfunction isOfflineModeEnabled(): boolean {\n\tconst value = process.env.HOOCODE_OFFLINE;\n\tif (!value) return false;\n\treturn value === \"1\" || value.toLowerCase() === \"true\" || value.toLowerCase() === \"yes\";\n}\n\n/** Tools whose binaries hoocode can resolve from PATH or download on demand. */\nexport type ManagedTool = \"fd\" | \"rg\" | \"webtools\" | \"filetools\" | \"browsertools\" | \"voicetools\" | \"embsearch\";\n\ninterface ToolConfig {\n\tname: string;\n\trepo: string; // GitHub repo (e.g., \"sharkdp/fd\")\n\tbinaryName: string; // Name of the binary inside the archive\n\tsystemBinaryNames?: string[]; // Alternative system command names to try before downloading\n\ttagPrefix: string; // Prefix for tags (e.g., \"v\" for v1.0.0, \"\" for 1.0.0)\n\tgetAssetName: (version: string, plat: string, architecture: string) => string | null;\n}\n\nconst TOOLS: Record<string, ToolConfig> = {\n\tfd: {\n\t\tname: \"fd\",\n\t\trepo: \"sharkdp/fd\",\n\t\tbinaryName: \"fd\",\n\t\tsystemBinaryNames: [\"fd\", \"fdfind\"],\n\t\ttagPrefix: \"v\",\n\t\tgetAssetName: (version, plat, architecture) => {\n\t\t\tif (plat === \"darwin\") {\n\t\t\t\tconst archStr = architecture === \"arm64\" ? \"aarch64\" : \"x86_64\";\n\t\t\t\treturn `fd-v${version}-${archStr}-apple-darwin.tar.gz`;\n\t\t\t} else if (plat === \"linux\") {\n\t\t\t\tconst archStr = architecture === \"arm64\" ? \"aarch64\" : \"x86_64\";\n\t\t\t\treturn `fd-v${version}-${archStr}-unknown-linux-gnu.tar.gz`;\n\t\t\t} else if (plat === \"win32\") {\n\t\t\t\tconst archStr = architecture === \"arm64\" ? \"aarch64\" : \"x86_64\";\n\t\t\t\treturn `fd-v${version}-${archStr}-pc-windows-msvc.zip`;\n\t\t\t}\n\t\t\treturn null;\n\t\t},\n\t},\n\trg: {\n\t\tname: \"ripgrep\",\n\t\trepo: \"BurntSushi/ripgrep\",\n\t\tbinaryName: \"rg\",\n\t\ttagPrefix: \"\",\n\t\tgetAssetName: (version, plat, architecture) => {\n\t\t\tif (plat === \"darwin\") {\n\t\t\t\tconst archStr = architecture === \"arm64\" ? \"aarch64\" : \"x86_64\";\n\t\t\t\treturn `ripgrep-${version}-${archStr}-apple-darwin.tar.gz`;\n\t\t\t} else if (plat === \"linux\") {\n\t\t\t\tif (architecture === \"arm64\") {\n\t\t\t\t\treturn `ripgrep-${version}-aarch64-unknown-linux-gnu.tar.gz`;\n\t\t\t\t}\n\t\t\t\treturn `ripgrep-${version}-x86_64-unknown-linux-musl.tar.gz`;\n\t\t\t} else if (plat === \"win32\") {\n\t\t\t\tconst archStr = architecture === \"arm64\" ? \"aarch64\" : \"x86_64\";\n\t\t\t\treturn `ripgrep-${version}-${archStr}-pc-windows-msvc.zip`;\n\t\t\t}\n\t\t\treturn null;\n\t\t},\n\t},\n\twebtools: {\n\t\tname: \"webtools\",\n\t\trepo: \"kolisachint/webtools\",\n\t\tbinaryName: \"webtools\",\n\t\ttagPrefix: \"v\",\n\t\t// Release assets follow Rust target triples: webtools-<arch>-<target>.<ext>.\n\t\t// Some platforms may not be published yet; a missing asset 404s and ensureTool\n\t\t// degrades gracefully (returns undefined, tools fall back to an error message).\n\t\tgetAssetName: (_version, plat, architecture) => {\n\t\t\tconst archStr = architecture === \"arm64\" ? \"aarch64\" : \"x86_64\";\n\t\t\tif (plat === \"darwin\") {\n\t\t\t\treturn `webtools-${archStr}-apple-darwin.tar.gz`;\n\t\t\t} else if (plat === \"linux\") {\n\t\t\t\treturn `webtools-${archStr}-unknown-linux-gnu.tar.gz`;\n\t\t\t} else if (plat === \"win32\") {\n\t\t\t\treturn `webtools-${archStr}-pc-windows-msvc.zip`;\n\t\t\t}\n\t\t\treturn null;\n\t\t},\n\t},\n\tbrowsertools: {\n\t\tname: \"browsertools\",\n\t\trepo: \"kolisachint/browsertools\",\n\t\tbinaryName: \"browsertools\",\n\t\ttagPrefix: \"v\",\n\t\t// Release archives follow Rust target triples: browsertools-<arch>-<target>.<ext>\n\t\t// (release.yml builds gnu + musl for linux; we prefer the gnu variant to match\n\t\t// webtools/filetools). A missing platform asset 404s and ensureTool degrades\n\t\t// gracefully (returns undefined; the browser tools then surface an error).\n\t\tgetAssetName: (_version, plat, architecture) => {\n\t\t\tconst archStr = architecture === \"arm64\" ? \"aarch64\" : \"x86_64\";\n\t\t\tif (plat === \"darwin\") {\n\t\t\t\treturn `browsertools-${archStr}-apple-darwin.tar.gz`;\n\t\t\t} else if (plat === \"linux\") {\n\t\t\t\treturn `browsertools-${archStr}-unknown-linux-gnu.tar.gz`;\n\t\t\t} else if (plat === \"win32\") {\n\t\t\t\treturn `browsertools-${archStr}-pc-windows-msvc.zip`;\n\t\t\t}\n\t\t\treturn null;\n\t\t},\n\t},\n\tvoicetools: {\n\t\tname: \"voicetools\",\n\t\trepo: \"kolisachint/voicetools\",\n\t\tbinaryName: \"voicetools\",\n\t\ttagPrefix: \"v\",\n\t\t// Release archives follow Rust target triples: voicetools-<arch>-<target>.<ext>.\n\t\t// A missing platform asset 404s and ensureTool degrades gracefully (returns\n\t\t// undefined; the voice-transcribe caller then surfaces an error message).\n\t\tgetAssetName: (_version, plat, architecture) => {\n\t\t\tconst archStr = architecture === \"arm64\" ? \"aarch64\" : \"x86_64\";\n\t\t\tif (plat === \"darwin\") {\n\t\t\t\treturn `voicetools-${archStr}-apple-darwin.tar.gz`;\n\t\t\t} else if (plat === \"linux\") {\n\t\t\t\treturn `voicetools-${archStr}-unknown-linux-gnu.tar.gz`;\n\t\t\t} else if (plat === \"win32\") {\n\t\t\t\treturn `voicetools-${archStr}-pc-windows-msvc.zip`;\n\t\t\t}\n\t\t\treturn null;\n\t\t},\n\t},\n\tfiletools: {\n\t\tname: \"filetools\",\n\t\trepo: \"kolisachint/filetools\",\n\t\tbinaryName: \"filetools\",\n\t\ttagPrefix: \"v\",\n\t\t// Release archives are named `filetools-<target-triple>.<ext>` (see the repo's\n\t\t// release.yml `archive: filetools-$target`). A missing platform asset 404s and\n\t\t// ensureTool degrades gracefully (returns undefined; the doc tools then surface\n\t\t// an error result).\n\t\tgetAssetName: (_version, plat, architecture) => {\n\t\t\tconst archStr = architecture === \"arm64\" ? \"aarch64\" : \"x86_64\";\n\t\t\tif (plat === \"darwin\") {\n\t\t\t\treturn `filetools-${archStr}-apple-darwin.tar.gz`;\n\t\t\t} else if (plat === \"linux\") {\n\t\t\t\treturn `filetools-${archStr}-unknown-linux-gnu.tar.gz`;\n\t\t\t} else if (plat === \"win32\") {\n\t\t\t\treturn `filetools-${archStr}-pc-windows-msvc.zip`;\n\t\t\t}\n\t\t\treturn null;\n\t\t},\n\t},\n\tembsearch: {\n\t\tname: \"embsearch\",\n\t\trepo: \"kolisachint/embeddingsearchtools\",\n\t\tbinaryName: \"embsearch\",\n\t\ttagPrefix: \"v\",\n\t\t// Release archives follow Rust target triples: embsearch-<arch>-<target>.<ext>.\n\t\t// Must be the ONNX build (model bundled); the mock build is rejected by the\n\t\t// EmbsearchService. A missing platform asset 404s and ensureTool degrades\n\t\t// gracefully (returns undefined; the search tool stays lexical-only).\n\t\tgetAssetName: (_version, plat, architecture) => {\n\t\t\tconst archStr = architecture === \"arm64\" ? \"aarch64\" : \"x86_64\";\n\t\t\tif (plat === \"darwin\") {\n\t\t\t\treturn `embsearch-${archStr}-apple-darwin.tar.gz`;\n\t\t\t} else if (plat === \"linux\") {\n\t\t\t\treturn `embsearch-${archStr}-unknown-linux-gnu.tar.gz`;\n\t\t\t} else if (plat === \"win32\") {\n\t\t\t\treturn `embsearch-${archStr}-pc-windows-msvc.zip`;\n\t\t\t}\n\t\t\treturn null;\n\t\t},\n\t},\n};\n\n// Check if a command exists in PATH by trying to run it\nfunction commandExists(cmd: string): boolean {\n\ttry {\n\t\tconst result = spawnSync(cmd, [\"--version\"], { stdio: \"pipe\" });\n\t\t// Check for ENOENT error (command not found)\n\t\treturn result.error === undefined || result.error === null;\n\t} catch {\n\t\treturn false;\n\t}\n}\n\n// Resolved tool paths are stable for the life of the process. Cache the first\n// successful resolution so we never re-run the synchronous spawnSync probe in\n// commandExists() on every grep/find/glob invocation, which blocks the event loop.\nconst resolvedToolPathCache = new Map<ManagedTool, string>();\n\n// Get the path to a tool (system-wide or in our tools dir)\nfunction getToolPath(tool: ManagedTool): string | null {\n\tconst config = TOOLS[tool];\n\tif (!config) return null;\n\n\t// Explicit binary override (per-tool env var, e.g. HOOCODE_BROWSERTOOLS_BINARY).\n\t// Lets a developer point at a locally built binary that predates a release,\n\t// bypassing the tools-dir/PATH resolution and download. Authoritative when set\n\t// and the path exists.\n\tconst overrideEnv = `HOOCODE_${tool.toUpperCase()}_BINARY`;\n\tconst override = process.env[overrideEnv]?.trim();\n\tif (override && existsSync(override)) {\n\t\treturn override;\n\t}\n\n\t// Reuse a previously resolved path. A bare command name resolves via PATH;\n\t// an absolute path must still exist (revalidate cheaply with existsSync).\n\tconst cached = resolvedToolPathCache.get(tool);\n\tif (cached !== undefined) {\n\t\tconst isAbsolutePath = cached.includes(\"/\") || cached.includes(\"\\\\\");\n\t\tif (!isAbsolutePath || existsSync(cached)) {\n\t\t\treturn cached;\n\t\t}\n\t\tresolvedToolPathCache.delete(tool);\n\t}\n\n\t// Check our tools directory first\n\tconst localPath = join(TOOLS_DIR, config.binaryName + (platform() === \"win32\" ? \".exe\" : \"\"));\n\tif (existsSync(localPath)) {\n\t\tresolvedToolPathCache.set(tool, localPath);\n\t\treturn localPath;\n\t}\n\n\t// Check system PATH - if found, just return the command name (it's in PATH)\n\tconst systemBinaryNames = config.systemBinaryNames ?? [config.binaryName];\n\tfor (const systemBinaryName of systemBinaryNames) {\n\t\tif (commandExists(systemBinaryName)) {\n\t\t\tresolvedToolPathCache.set(tool, systemBinaryName);\n\t\t\treturn systemBinaryName;\n\t\t}\n\t}\n\n\treturn null;\n}\n\n// Fetch latest release version from GitHub\nasync function getLatestVersion(repo: string): Promise<string> {\n\tconst response = await fetch(`https://api.github.com/repos/${repo}/releases/latest`, {\n\t\theaders: { \"User-Agent\": `${APP_NAME}-coding-agent` },\n\t\tsignal: AbortSignal.timeout(NETWORK_TIMEOUT_MS),\n\t});\n\n\tif (!response.ok) {\n\t\tthrow new Error(`GitHub API error: ${response.status}`);\n\t}\n\n\tconst data = (await response.json()) as { tag_name: string };\n\treturn data.tag_name.replace(/^v/, \"\");\n}\n\n// Best-effort SHA-256 verification: fetch \"<downloadUrl>.sha256\" and, when it is\n// served (HTTP 200), verify the downloaded file against it. A 404 (or any other\n// non-200 / network error) means no published checksum, so verification is\n// skipped rather than treated as a failure. A genuine mismatch throws.\nasync function verifyChecksum(downloadUrl: string, filePath: string): Promise<void> {\n\tlet checksumResponse: Awaited<ReturnType<typeof fetch>>;\n\ttry {\n\t\tchecksumResponse = await fetch(`${downloadUrl}.sha256`, {\n\t\t\tsignal: AbortSignal.timeout(NETWORK_TIMEOUT_MS),\n\t\t});\n\t} catch {\n\t\t// Network error fetching the checksum is non-fatal for best-effort verification.\n\t\treturn;\n\t}\n\n\tif (checksumResponse.status !== 200) {\n\t\treturn;\n\t}\n\n\t// sha256 files are commonly \"<hex> <filename>\"; take the leading token.\n\tconst expectedHash = (await checksumResponse.text()).trim().split(/\\s+/)[0]?.toLowerCase();\n\tif (!expectedHash || !/^[0-9a-f]{64}$/.test(expectedHash)) {\n\t\t// Unusable checksum body: skip rather than fail (still best-effort).\n\t\treturn;\n\t}\n\n\tconst actualHash = createHash(\"sha256\").update(readFileSync(filePath)).digest(\"hex\");\n\tif (actualHash !== expectedHash) {\n\t\tthrow new Error(`Checksum mismatch for ${downloadUrl}: expected ${expectedHash}, got ${actualHash}`);\n\t}\n}\n\n/**\n * Live download progress: bytes received so far and the total from\n * Content-Length (null when the server doesn't send one). Callers use it to\n * drive a progress bar instead of an indefinite spinner.\n */\nexport type DownloadProgress = (receivedBytes: number, totalBytes: number | null) => void;\n\n// Download a file from URL into `dest`, validating integrity. Throws (and removes\n// the partial file) on a truncated transfer (bytes written != Content-Length when\n// the header is present) or a SHA-256 mismatch, so a corrupt artifact is never\n// left behind. Exported for tests.\nexport async function downloadFile(url: string, dest: string, onProgress?: DownloadProgress): Promise<void> {\n\ttry {\n\t\tconst response = await fetch(url, {\n\t\t\tsignal: AbortSignal.timeout(DOWNLOAD_TIMEOUT_MS),\n\t\t});\n\n\t\tif (!response.ok) {\n\t\t\tthrow new Error(`Failed to download: ${response.status}`);\n\t\t}\n\n\t\tif (!response.body) {\n\t\t\tthrow new Error(\"No response body\");\n\t\t}\n\n\t\tconst contentLengthHeader = response.headers.get(\"content-length\");\n\t\tconst expectedBytes =\n\t\t\tcontentLengthHeader !== null && contentLengthHeader.trim() !== \"\" ? Number(contentLengthHeader) : null;\n\n\t\tconst fileStream = createWriteStream(dest);\n\t\tconst source = Readable.fromWeb(response.body as Parameters<typeof Readable.fromWeb>[0]);\n\t\tif (onProgress) {\n\t\t\tlet received = 0;\n\t\t\tsource.on(\"data\", (chunk: Buffer) => {\n\t\t\t\treceived += chunk.length;\n\t\t\t\tonProgress(received, expectedBytes);\n\t\t\t});\n\t\t}\n\t\tawait pipeline(source, fileStream);\n\n\t\tif (expectedBytes !== null && Number.isFinite(expectedBytes)) {\n\t\t\tconst bytesWritten = statSync(dest).size;\n\t\t\tif (bytesWritten !== expectedBytes) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`Truncated download from ${url}: expected ${expectedBytes} bytes, received ${bytesWritten}`,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\tawait verifyChecksum(url, dest);\n\t} catch (e) {\n\t\t// Never leave a partial/corrupt file behind on any failure.\n\t\trmSync(dest, { force: true });\n\t\tthrow e;\n\t}\n}\n\nfunction findBinaryRecursively(rootDir: string, binaryFileName: string): string | null {\n\tconst stack: string[] = [rootDir];\n\n\twhile (stack.length > 0) {\n\t\tconst currentDir = stack.pop();\n\t\tif (!currentDir) continue;\n\n\t\tconst entries = readdirSync(currentDir, { withFileTypes: true });\n\t\tfor (const entry of entries) {\n\t\t\tconst fullPath = join(currentDir, entry.name);\n\t\t\tif (entry.isFile() && entry.name === binaryFileName) {\n\t\t\t\treturn fullPath;\n\t\t\t}\n\t\t\tif (entry.isDirectory()) {\n\t\t\t\tstack.push(fullPath);\n\t\t\t}\n\t\t}\n\t}\n\n\treturn null;\n}\n\n// Download and install a tool\nasync function downloadTool(tool: ManagedTool, onProgress?: DownloadProgress): Promise<string> {\n\tconst config = TOOLS[tool];\n\tif (!config) throw new Error(`Unknown tool: ${tool}`);\n\n\tconst plat = platform();\n\tconst architecture = arch();\n\n\t// Get latest version\n\tconst version = await getLatestVersion(config.repo);\n\n\t// Get asset name for this platform\n\tconst assetName = config.getAssetName(version, plat, architecture);\n\tif (!assetName) {\n\t\tthrow new Error(`Unsupported platform: ${plat}/${architecture}`);\n\t}\n\n\t// Create tools directory\n\tmkdirSync(TOOLS_DIR, { recursive: true });\n\n\tconst downloadUrl = `https://github.com/${config.repo}/releases/download/${config.tagPrefix}${version}/${assetName}`;\n\tconst archivePath = join(TOOLS_DIR, assetName);\n\tconst binaryExt = plat === \"win32\" ? \".exe\" : \"\";\n\tconst binaryPath = join(TOOLS_DIR, config.binaryName + binaryExt);\n\n\t// Download to a unique temp path, validate, then atomically rename into place.\n\t// Writing the shared archive path directly would leave a corrupt partial behind\n\t// if the transfer fails or is truncated. fd/rg/webtools can also download\n\t// concurrently at startup, so the per-attempt temp name must be unique.\n\tconst tempArchivePath = `${archivePath}.${process.pid}.${randomBytes(6).toString(\"hex\")}.part`;\n\n\t// Extract into a unique temp directory. fd and rg downloads can run concurrently\n\t// during startup, so sharing a fixed directory causes races.\n\tconst extractDir = join(\n\t\tTOOLS_DIR,\n\t\t`extract_tmp_${config.binaryName}_${process.pid}_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`,\n\t);\n\n\ttry {\n\t\t// One retry (2 attempts total) around download + integrity verification.\n\t\t// downloadFile removes its own partial on failure, so each attempt is clean.\n\t\tlet lastError: unknown;\n\t\tlet downloaded = false;\n\t\tfor (let attempt = 1; attempt <= 2 && !downloaded; attempt++) {\n\t\t\ttry {\n\t\t\t\tawait downloadFile(downloadUrl, tempArchivePath, onProgress);\n\t\t\t\tdownloaded = true;\n\t\t\t} catch (e) {\n\t\t\t\tlastError = e;\n\t\t\t\trmSync(tempArchivePath, { force: true });\n\t\t\t}\n\t\t}\n\t\tif (!downloaded) {\n\t\t\tthrow lastError instanceof Error ? lastError : new Error(String(lastError));\n\t\t}\n\n\t\t// Atomic publish of the verified archive, then extract.\n\t\trenameSync(tempArchivePath, archivePath);\n\t\tmkdirSync(extractDir, { recursive: true });\n\n\t\tif (assetName.endsWith(\".tar.gz\")) {\n\t\t\tconst extractResult = spawnSync(\"tar\", [\"xzf\", archivePath, \"-C\", extractDir], { stdio: \"pipe\" });\n\t\t\tif (extractResult.error || extractResult.status !== 0) {\n\t\t\t\tconst errMsg = extractResult.error?.message ?? extractResult.stderr?.toString().trim() ?? \"unknown error\";\n\t\t\t\tthrow new Error(`Failed to extract ${assetName}: ${errMsg}`);\n\t\t\t}\n\t\t} else if (assetName.endsWith(\".zip\")) {\n\t\t\tawait extractZip(archivePath, { dir: extractDir });\n\t\t} else {\n\t\t\tthrow new Error(`Unsupported archive format: ${assetName}`);\n\t\t}\n\n\t\t// Find the binary in extracted files. Some archives contain files directly\n\t\t// at root, others nest under a versioned subdirectory.\n\t\tconst binaryFileName = config.binaryName + binaryExt;\n\t\tconst extractedDir = join(extractDir, assetName.replace(/\\.(tar\\.gz|zip)$/, \"\"));\n\t\tconst extractedBinaryCandidates = [join(extractedDir, binaryFileName), join(extractDir, binaryFileName)];\n\t\tlet extractedBinary = extractedBinaryCandidates.find((candidate) => existsSync(candidate));\n\n\t\tif (!extractedBinary) {\n\t\t\textractedBinary = findBinaryRecursively(extractDir, binaryFileName) ?? undefined;\n\t\t}\n\n\t\tif (extractedBinary) {\n\t\t\trenameSync(extractedBinary, binaryPath);\n\t\t} else {\n\t\t\tthrow new Error(`Binary not found in archive: expected ${binaryFileName} under ${extractDir}`);\n\t\t}\n\n\t\t// Make executable (Unix only)\n\t\tif (plat !== \"win32\") {\n\t\t\tchmodSync(binaryPath, 0o755);\n\t\t}\n\t} finally {\n\t\t// Guaranteed cleanup of every transient artifact on ANY outcome: the temp\n\t\t// download (if a failure left it before the rename), the published archive,\n\t\t// and the temp extract dir.\n\t\trmSync(tempArchivePath, { force: true });\n\t\trmSync(archivePath, { force: true });\n\t\trmSync(extractDir, { recursive: true, force: true });\n\t}\n\n\treturn binaryPath;\n}\n\n// Termux package names for tools\nconst TERMUX_PACKAGES: Record<string, string> = {\n\tfd: \"fd\",\n\trg: \"ripgrep\",\n\twebtools: \"webtools\",\n\tfiletools: \"filetools\",\n\tbrowsertools: \"browsertools\",\n\tvoicetools: \"voicetools\",\n\tembsearch: \"embsearch\",\n};\n\n// Ensure a tool is available, downloading if necessary\n// Returns the path to the tool, or null if unavailable\n// onProgress receives live byte counts while the release archive downloads.\nexport async function ensureTool(\n\ttool: ManagedTool,\n\tsilent: boolean = false,\n\tonProgress?: DownloadProgress,\n): Promise<string | undefined> {\n\tconst existingPath = getToolPath(tool);\n\tif (existingPath) {\n\t\treturn existingPath;\n\t}\n\n\tconst config = TOOLS[tool];\n\tif (!config) return undefined;\n\n\tif (isOfflineModeEnabled()) {\n\t\tif (!silent) {\n\t\t\tconsole.log(chalk.yellow(`${config.name} not found. Offline mode enabled, skipping download.`));\n\t\t}\n\t\treturn undefined;\n\t}\n\n\t// On Android/Termux, Linux binaries don't work due to Bionic libc incompatibility.\n\t// Users must install via pkg.\n\tif (platform() === \"android\") {\n\t\tconst pkgName = TERMUX_PACKAGES[tool] ?? tool;\n\t\tif (!silent) {\n\t\t\tconsole.log(chalk.yellow(`${config.name} not found. Install with: pkg install ${pkgName}`));\n\t\t}\n\t\treturn undefined;\n\t}\n\n\t// Tool not found - download it\n\tif (!silent) {\n\t\tconsole.log(chalk.dim(`${config.name} not found. Downloading...`));\n\t}\n\n\ttry {\n\t\tconst path = await downloadTool(tool, onProgress);\n\t\tif (!silent) {\n\t\t\tconsole.log(chalk.dim(`${config.name} installed to ${path}`));\n\t\t}\n\t\treturn path;\n\t} catch (e) {\n\t\tif (!silent) {\n\t\t\tconsole.log(chalk.yellow(`Failed to download ${config.name}: ${e instanceof Error ? e.message : e}`));\n\t\t}\n\t\treturn undefined;\n\t}\n}\n"]}
1
+ {"version":3,"file":"tools-manager.js","sourceRoot":"","sources":["../../src/utils/tools-manager.ts"],"names":[],"mappings":"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","sourcesContent":["import chalk from \"chalk\";\nimport { spawnSync } from \"child_process\";\nimport { createHash, randomBytes } from \"crypto\";\nimport extractZip from \"extract-zip\";\nimport {\n\tchmodSync,\n\tcreateWriteStream,\n\texistsSync,\n\tmkdirSync,\n\treaddirSync,\n\treadFileSync,\n\trenameSync,\n\trmSync,\n\tstatSync,\n} from \"fs\";\nimport { arch, platform } from \"os\";\nimport { join } from \"path\";\nimport { Readable } from \"stream\";\nimport { pipeline } from \"stream/promises\";\nimport { APP_NAME, getBinDir } from \"../config.js\";\n\nconst TOOLS_DIR = getBinDir();\nconst NETWORK_TIMEOUT_MS = 10_000;\nconst DOWNLOAD_TIMEOUT_MS = 120_000;\n\nfunction isOfflineModeEnabled(): boolean {\n\tconst value = process.env.HOOCODE_OFFLINE;\n\tif (!value) return false;\n\treturn value === \"1\" || value.toLowerCase() === \"true\" || value.toLowerCase() === \"yes\";\n}\n\n/** Tools whose binaries hoocode can resolve from PATH or download on demand. */\nexport type ManagedTool = \"fd\" | \"rg\" | \"webtools\" | \"voicetools\" | \"embsearch\";\n\ninterface ToolConfig {\n\tname: string;\n\trepo: string; // GitHub repo (e.g., \"sharkdp/fd\")\n\tbinaryName: string; // Name of the binary inside the archive\n\tsystemBinaryNames?: string[]; // Alternative system command names to try before downloading\n\ttagPrefix: string; // Prefix for tags (e.g., \"v\" for v1.0.0, \"\" for 1.0.0)\n\tgetAssetName: (version: string, plat: string, architecture: string) => string | null;\n}\n\nconst TOOLS: Record<string, ToolConfig> = {\n\tfd: {\n\t\tname: \"fd\",\n\t\trepo: \"sharkdp/fd\",\n\t\tbinaryName: \"fd\",\n\t\tsystemBinaryNames: [\"fd\", \"fdfind\"],\n\t\ttagPrefix: \"v\",\n\t\tgetAssetName: (version, plat, architecture) => {\n\t\t\tif (plat === \"darwin\") {\n\t\t\t\tconst archStr = architecture === \"arm64\" ? \"aarch64\" : \"x86_64\";\n\t\t\t\treturn `fd-v${version}-${archStr}-apple-darwin.tar.gz`;\n\t\t\t} else if (plat === \"linux\") {\n\t\t\t\tconst archStr = architecture === \"arm64\" ? \"aarch64\" : \"x86_64\";\n\t\t\t\treturn `fd-v${version}-${archStr}-unknown-linux-gnu.tar.gz`;\n\t\t\t} else if (plat === \"win32\") {\n\t\t\t\tconst archStr = architecture === \"arm64\" ? \"aarch64\" : \"x86_64\";\n\t\t\t\treturn `fd-v${version}-${archStr}-pc-windows-msvc.zip`;\n\t\t\t}\n\t\t\treturn null;\n\t\t},\n\t},\n\trg: {\n\t\tname: \"ripgrep\",\n\t\trepo: \"BurntSushi/ripgrep\",\n\t\tbinaryName: \"rg\",\n\t\ttagPrefix: \"\",\n\t\tgetAssetName: (version, plat, architecture) => {\n\t\t\tif (plat === \"darwin\") {\n\t\t\t\tconst archStr = architecture === \"arm64\" ? \"aarch64\" : \"x86_64\";\n\t\t\t\treturn `ripgrep-${version}-${archStr}-apple-darwin.tar.gz`;\n\t\t\t} else if (plat === \"linux\") {\n\t\t\t\tif (architecture === \"arm64\") {\n\t\t\t\t\treturn `ripgrep-${version}-aarch64-unknown-linux-gnu.tar.gz`;\n\t\t\t\t}\n\t\t\t\treturn `ripgrep-${version}-x86_64-unknown-linux-musl.tar.gz`;\n\t\t\t} else if (plat === \"win32\") {\n\t\t\t\tconst archStr = architecture === \"arm64\" ? \"aarch64\" : \"x86_64\";\n\t\t\t\treturn `ripgrep-${version}-${archStr}-pc-windows-msvc.zip`;\n\t\t\t}\n\t\t\treturn null;\n\t\t},\n\t},\n\twebtools: {\n\t\tname: \"webtools\",\n\t\trepo: \"kolisachint/webtools\",\n\t\tbinaryName: \"webtools\",\n\t\ttagPrefix: \"v\",\n\t\t// Release assets follow Rust target triples: webtools-<arch>-<target>.<ext>.\n\t\t// Some platforms may not be published yet; a missing asset 404s and ensureTool\n\t\t// degrades gracefully (returns undefined, tools fall back to an error message).\n\t\tgetAssetName: (_version, plat, architecture) => {\n\t\t\tconst archStr = architecture === \"arm64\" ? \"aarch64\" : \"x86_64\";\n\t\t\tif (plat === \"darwin\") {\n\t\t\t\treturn `webtools-${archStr}-apple-darwin.tar.gz`;\n\t\t\t} else if (plat === \"linux\") {\n\t\t\t\treturn `webtools-${archStr}-unknown-linux-gnu.tar.gz`;\n\t\t\t} else if (plat === \"win32\") {\n\t\t\t\treturn `webtools-${archStr}-pc-windows-msvc.zip`;\n\t\t\t}\n\t\t\treturn null;\n\t\t},\n\t},\n\tvoicetools: {\n\t\tname: \"voicetools\",\n\t\trepo: \"kolisachint/voicetools\",\n\t\tbinaryName: \"voicetools\",\n\t\ttagPrefix: \"v\",\n\t\t// Release archives follow Rust target triples: voicetools-<arch>-<target>.<ext>.\n\t\t// A missing platform asset 404s and ensureTool degrades gracefully (returns\n\t\t// undefined; the voice-transcribe caller then surfaces an error message).\n\t\tgetAssetName: (_version, plat, architecture) => {\n\t\t\tconst archStr = architecture === \"arm64\" ? \"aarch64\" : \"x86_64\";\n\t\t\tif (plat === \"darwin\") {\n\t\t\t\treturn `voicetools-${archStr}-apple-darwin.tar.gz`;\n\t\t\t} else if (plat === \"linux\") {\n\t\t\t\treturn `voicetools-${archStr}-unknown-linux-gnu.tar.gz`;\n\t\t\t} else if (plat === \"win32\") {\n\t\t\t\treturn `voicetools-${archStr}-pc-windows-msvc.zip`;\n\t\t\t}\n\t\t\treturn null;\n\t\t},\n\t},\n\tembsearch: {\n\t\tname: \"embsearch\",\n\t\trepo: \"kolisachint/embeddingsearchtools\",\n\t\tbinaryName: \"embsearch\",\n\t\ttagPrefix: \"v\",\n\t\t// Release archives follow Rust target triples: embsearch-<arch>-<target>.<ext>.\n\t\t// Must be the ONNX build (model bundled); the mock build is rejected by the\n\t\t// EmbsearchService. A missing platform asset 404s and ensureTool degrades\n\t\t// gracefully (returns undefined; the search tool stays lexical-only).\n\t\tgetAssetName: (_version, plat, architecture) => {\n\t\t\tconst archStr = architecture === \"arm64\" ? \"aarch64\" : \"x86_64\";\n\t\t\tif (plat === \"darwin\") {\n\t\t\t\treturn `embsearch-${archStr}-apple-darwin.tar.gz`;\n\t\t\t} else if (plat === \"linux\") {\n\t\t\t\treturn `embsearch-${archStr}-unknown-linux-gnu.tar.gz`;\n\t\t\t} else if (plat === \"win32\") {\n\t\t\t\treturn `embsearch-${archStr}-pc-windows-msvc.zip`;\n\t\t\t}\n\t\t\treturn null;\n\t\t},\n\t},\n};\n\n// Check if a command exists in PATH by trying to run it\nfunction commandExists(cmd: string): boolean {\n\ttry {\n\t\tconst result = spawnSync(cmd, [\"--version\"], { stdio: \"pipe\" });\n\t\t// Check for ENOENT error (command not found)\n\t\treturn result.error === undefined || result.error === null;\n\t} catch {\n\t\treturn false;\n\t}\n}\n\n// Resolved tool paths are stable for the life of the process. Cache the first\n// successful resolution so we never re-run the synchronous spawnSync probe in\n// commandExists() on every grep/find/glob invocation, which blocks the event loop.\nconst resolvedToolPathCache = new Map<ManagedTool, string>();\n\n// Get the path to a tool (system-wide or in our tools dir)\nfunction getToolPath(tool: ManagedTool): string | null {\n\tconst config = TOOLS[tool];\n\tif (!config) return null;\n\n\t// Explicit binary override (per-tool env var, e.g. HOOCODE_WEBTOOLS_BINARY).\n\t// Lets a developer point at a locally built binary that predates a release,\n\t// bypassing the tools-dir/PATH resolution and download. Authoritative when set\n\t// and the path exists.\n\tconst overrideEnv = `HOOCODE_${tool.toUpperCase()}_BINARY`;\n\tconst override = process.env[overrideEnv]?.trim();\n\tif (override && existsSync(override)) {\n\t\treturn override;\n\t}\n\n\t// Reuse a previously resolved path. A bare command name resolves via PATH;\n\t// an absolute path must still exist (revalidate cheaply with existsSync).\n\tconst cached = resolvedToolPathCache.get(tool);\n\tif (cached !== undefined) {\n\t\tconst isAbsolutePath = cached.includes(\"/\") || cached.includes(\"\\\\\");\n\t\tif (!isAbsolutePath || existsSync(cached)) {\n\t\t\treturn cached;\n\t\t}\n\t\tresolvedToolPathCache.delete(tool);\n\t}\n\n\t// Check our tools directory first\n\tconst localPath = join(TOOLS_DIR, config.binaryName + (platform() === \"win32\" ? \".exe\" : \"\"));\n\tif (existsSync(localPath)) {\n\t\tresolvedToolPathCache.set(tool, localPath);\n\t\treturn localPath;\n\t}\n\n\t// Check system PATH - if found, just return the command name (it's in PATH)\n\tconst systemBinaryNames = config.systemBinaryNames ?? [config.binaryName];\n\tfor (const systemBinaryName of systemBinaryNames) {\n\t\tif (commandExists(systemBinaryName)) {\n\t\t\tresolvedToolPathCache.set(tool, systemBinaryName);\n\t\t\treturn systemBinaryName;\n\t\t}\n\t}\n\n\treturn null;\n}\n\n// Fetch latest release version from GitHub\nasync function getLatestVersion(repo: string): Promise<string> {\n\tconst response = await fetch(`https://api.github.com/repos/${repo}/releases/latest`, {\n\t\theaders: { \"User-Agent\": `${APP_NAME}-coding-agent` },\n\t\tsignal: AbortSignal.timeout(NETWORK_TIMEOUT_MS),\n\t});\n\n\tif (!response.ok) {\n\t\tthrow new Error(`GitHub API error: ${response.status}`);\n\t}\n\n\tconst data = (await response.json()) as { tag_name: string };\n\treturn data.tag_name.replace(/^v/, \"\");\n}\n\n// Best-effort SHA-256 verification: fetch \"<downloadUrl>.sha256\" and, when it is\n// served (HTTP 200), verify the downloaded file against it. A 404 (or any other\n// non-200 / network error) means no published checksum, so verification is\n// skipped rather than treated as a failure. A genuine mismatch throws.\nasync function verifyChecksum(downloadUrl: string, filePath: string): Promise<void> {\n\tlet checksumResponse: Awaited<ReturnType<typeof fetch>>;\n\ttry {\n\t\tchecksumResponse = await fetch(`${downloadUrl}.sha256`, {\n\t\t\tsignal: AbortSignal.timeout(NETWORK_TIMEOUT_MS),\n\t\t});\n\t} catch {\n\t\t// Network error fetching the checksum is non-fatal for best-effort verification.\n\t\treturn;\n\t}\n\n\tif (checksumResponse.status !== 200) {\n\t\treturn;\n\t}\n\n\t// sha256 files are commonly \"<hex> <filename>\"; take the leading token.\n\tconst expectedHash = (await checksumResponse.text()).trim().split(/\\s+/)[0]?.toLowerCase();\n\tif (!expectedHash || !/^[0-9a-f]{64}$/.test(expectedHash)) {\n\t\t// Unusable checksum body: skip rather than fail (still best-effort).\n\t\treturn;\n\t}\n\n\tconst actualHash = createHash(\"sha256\").update(readFileSync(filePath)).digest(\"hex\");\n\tif (actualHash !== expectedHash) {\n\t\tthrow new Error(`Checksum mismatch for ${downloadUrl}: expected ${expectedHash}, got ${actualHash}`);\n\t}\n}\n\n/**\n * Live download progress: bytes received so far and the total from\n * Content-Length (null when the server doesn't send one). Callers use it to\n * drive a progress bar instead of an indefinite spinner.\n */\nexport type DownloadProgress = (receivedBytes: number, totalBytes: number | null) => void;\n\n// Download a file from URL into `dest`, validating integrity. Throws (and removes\n// the partial file) on a truncated transfer (bytes written != Content-Length when\n// the header is present) or a SHA-256 mismatch, so a corrupt artifact is never\n// left behind. Exported for tests.\nexport async function downloadFile(url: string, dest: string, onProgress?: DownloadProgress): Promise<void> {\n\ttry {\n\t\tconst response = await fetch(url, {\n\t\t\tsignal: AbortSignal.timeout(DOWNLOAD_TIMEOUT_MS),\n\t\t});\n\n\t\tif (!response.ok) {\n\t\t\tthrow new Error(`Failed to download: ${response.status}`);\n\t\t}\n\n\t\tif (!response.body) {\n\t\t\tthrow new Error(\"No response body\");\n\t\t}\n\n\t\tconst contentLengthHeader = response.headers.get(\"content-length\");\n\t\tconst expectedBytes =\n\t\t\tcontentLengthHeader !== null && contentLengthHeader.trim() !== \"\" ? Number(contentLengthHeader) : null;\n\n\t\tconst fileStream = createWriteStream(dest);\n\t\tconst source = Readable.fromWeb(response.body as Parameters<typeof Readable.fromWeb>[0]);\n\t\tif (onProgress) {\n\t\t\tlet received = 0;\n\t\t\tsource.on(\"data\", (chunk: Buffer) => {\n\t\t\t\treceived += chunk.length;\n\t\t\t\tonProgress(received, expectedBytes);\n\t\t\t});\n\t\t}\n\t\tawait pipeline(source, fileStream);\n\n\t\tif (expectedBytes !== null && Number.isFinite(expectedBytes)) {\n\t\t\tconst bytesWritten = statSync(dest).size;\n\t\t\tif (bytesWritten !== expectedBytes) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`Truncated download from ${url}: expected ${expectedBytes} bytes, received ${bytesWritten}`,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\tawait verifyChecksum(url, dest);\n\t} catch (e) {\n\t\t// Never leave a partial/corrupt file behind on any failure.\n\t\trmSync(dest, { force: true });\n\t\tthrow e;\n\t}\n}\n\nfunction findBinaryRecursively(rootDir: string, binaryFileName: string): string | null {\n\tconst stack: string[] = [rootDir];\n\n\twhile (stack.length > 0) {\n\t\tconst currentDir = stack.pop();\n\t\tif (!currentDir) continue;\n\n\t\tconst entries = readdirSync(currentDir, { withFileTypes: true });\n\t\tfor (const entry of entries) {\n\t\t\tconst fullPath = join(currentDir, entry.name);\n\t\t\tif (entry.isFile() && entry.name === binaryFileName) {\n\t\t\t\treturn fullPath;\n\t\t\t}\n\t\t\tif (entry.isDirectory()) {\n\t\t\t\tstack.push(fullPath);\n\t\t\t}\n\t\t}\n\t}\n\n\treturn null;\n}\n\n// Download and install a tool\nasync function downloadTool(tool: ManagedTool, onProgress?: DownloadProgress): Promise<string> {\n\tconst config = TOOLS[tool];\n\tif (!config) throw new Error(`Unknown tool: ${tool}`);\n\n\tconst plat = platform();\n\tconst architecture = arch();\n\n\t// Get latest version\n\tconst version = await getLatestVersion(config.repo);\n\n\t// Get asset name for this platform\n\tconst assetName = config.getAssetName(version, plat, architecture);\n\tif (!assetName) {\n\t\tthrow new Error(`Unsupported platform: ${plat}/${architecture}`);\n\t}\n\n\t// Create tools directory\n\tmkdirSync(TOOLS_DIR, { recursive: true });\n\n\tconst downloadUrl = `https://github.com/${config.repo}/releases/download/${config.tagPrefix}${version}/${assetName}`;\n\tconst archivePath = join(TOOLS_DIR, assetName);\n\tconst binaryExt = plat === \"win32\" ? \".exe\" : \"\";\n\tconst binaryPath = join(TOOLS_DIR, config.binaryName + binaryExt);\n\n\t// Download to a unique temp path, validate, then atomically rename into place.\n\t// Writing the shared archive path directly would leave a corrupt partial behind\n\t// if the transfer fails or is truncated. fd/rg/webtools can also download\n\t// concurrently at startup, so the per-attempt temp name must be unique.\n\tconst tempArchivePath = `${archivePath}.${process.pid}.${randomBytes(6).toString(\"hex\")}.part`;\n\n\t// Extract into a unique temp directory. fd and rg downloads can run concurrently\n\t// during startup, so sharing a fixed directory causes races.\n\tconst extractDir = join(\n\t\tTOOLS_DIR,\n\t\t`extract_tmp_${config.binaryName}_${process.pid}_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`,\n\t);\n\n\ttry {\n\t\t// One retry (2 attempts total) around download + integrity verification.\n\t\t// downloadFile removes its own partial on failure, so each attempt is clean.\n\t\tlet lastError: unknown;\n\t\tlet downloaded = false;\n\t\tfor (let attempt = 1; attempt <= 2 && !downloaded; attempt++) {\n\t\t\ttry {\n\t\t\t\tawait downloadFile(downloadUrl, tempArchivePath, onProgress);\n\t\t\t\tdownloaded = true;\n\t\t\t} catch (e) {\n\t\t\t\tlastError = e;\n\t\t\t\trmSync(tempArchivePath, { force: true });\n\t\t\t}\n\t\t}\n\t\tif (!downloaded) {\n\t\t\tthrow lastError instanceof Error ? lastError : new Error(String(lastError));\n\t\t}\n\n\t\t// Atomic publish of the verified archive, then extract.\n\t\trenameSync(tempArchivePath, archivePath);\n\t\tmkdirSync(extractDir, { recursive: true });\n\n\t\tif (assetName.endsWith(\".tar.gz\")) {\n\t\t\tconst extractResult = spawnSync(\"tar\", [\"xzf\", archivePath, \"-C\", extractDir], { stdio: \"pipe\" });\n\t\t\tif (extractResult.error || extractResult.status !== 0) {\n\t\t\t\tconst errMsg = extractResult.error?.message ?? extractResult.stderr?.toString().trim() ?? \"unknown error\";\n\t\t\t\tthrow new Error(`Failed to extract ${assetName}: ${errMsg}`);\n\t\t\t}\n\t\t} else if (assetName.endsWith(\".zip\")) {\n\t\t\tawait extractZip(archivePath, { dir: extractDir });\n\t\t} else {\n\t\t\tthrow new Error(`Unsupported archive format: ${assetName}`);\n\t\t}\n\n\t\t// Find the binary in extracted files. Some archives contain files directly\n\t\t// at root, others nest under a versioned subdirectory.\n\t\tconst binaryFileName = config.binaryName + binaryExt;\n\t\tconst extractedDir = join(extractDir, assetName.replace(/\\.(tar\\.gz|zip)$/, \"\"));\n\t\tconst extractedBinaryCandidates = [join(extractedDir, binaryFileName), join(extractDir, binaryFileName)];\n\t\tlet extractedBinary = extractedBinaryCandidates.find((candidate) => existsSync(candidate));\n\n\t\tif (!extractedBinary) {\n\t\t\textractedBinary = findBinaryRecursively(extractDir, binaryFileName) ?? undefined;\n\t\t}\n\n\t\tif (extractedBinary) {\n\t\t\trenameSync(extractedBinary, binaryPath);\n\t\t} else {\n\t\t\tthrow new Error(`Binary not found in archive: expected ${binaryFileName} under ${extractDir}`);\n\t\t}\n\n\t\t// Make executable (Unix only)\n\t\tif (plat !== \"win32\") {\n\t\t\tchmodSync(binaryPath, 0o755);\n\t\t}\n\t} finally {\n\t\t// Guaranteed cleanup of every transient artifact on ANY outcome: the temp\n\t\t// download (if a failure left it before the rename), the published archive,\n\t\t// and the temp extract dir.\n\t\trmSync(tempArchivePath, { force: true });\n\t\trmSync(archivePath, { force: true });\n\t\trmSync(extractDir, { recursive: true, force: true });\n\t}\n\n\treturn binaryPath;\n}\n\n// Termux package names for tools\nconst TERMUX_PACKAGES: Record<string, string> = {\n\tfd: \"fd\",\n\trg: \"ripgrep\",\n\twebtools: \"webtools\",\n\tvoicetools: \"voicetools\",\n\tembsearch: \"embsearch\",\n};\n\n// Ensure a tool is available, downloading if necessary\n// Returns the path to the tool, or null if unavailable\n// onProgress receives live byte counts while the release archive downloads.\nexport async function ensureTool(\n\ttool: ManagedTool,\n\tsilent: boolean = false,\n\tonProgress?: DownloadProgress,\n): Promise<string | undefined> {\n\tconst existingPath = getToolPath(tool);\n\tif (existingPath) {\n\t\treturn existingPath;\n\t}\n\n\tconst config = TOOLS[tool];\n\tif (!config) return undefined;\n\n\tif (isOfflineModeEnabled()) {\n\t\tif (!silent) {\n\t\t\tconsole.log(chalk.yellow(`${config.name} not found. Offline mode enabled, skipping download.`));\n\t\t}\n\t\treturn undefined;\n\t}\n\n\t// On Android/Termux, Linux binaries don't work due to Bionic libc incompatibility.\n\t// Users must install via pkg.\n\tif (platform() === \"android\") {\n\t\tconst pkgName = TERMUX_PACKAGES[tool] ?? tool;\n\t\tif (!silent) {\n\t\t\tconsole.log(chalk.yellow(`${config.name} not found. Install with: pkg install ${pkgName}`));\n\t\t}\n\t\treturn undefined;\n\t}\n\n\t// Tool not found - download it\n\tif (!silent) {\n\t\tconsole.log(chalk.dim(`${config.name} not found. Downloading...`));\n\t}\n\n\ttry {\n\t\tconst path = await downloadTool(tool, onProgress);\n\t\tif (!silent) {\n\t\t\tconsole.log(chalk.dim(`${config.name} installed to ${path}`));\n\t\t}\n\t\treturn path;\n\t} catch (e) {\n\t\tif (!silent) {\n\t\t\tconsole.log(chalk.yellow(`Failed to download ${config.name}: ${e instanceof Error ? e.message : e}`));\n\t\t}\n\t\treturn undefined;\n\t}\n}\n"]}
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@kolisachint/hoocode-extension-custom-provider-anthropic",
3
3
  "private": true,
4
- "version": "0.2.160",
4
+ "version": "0.2.162",
5
5
  "type": "module",
6
6
  "engines": {
7
7
  "bun": ">=1.0.0"
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@kolisachint/hoocode-extension-custom-provider-gitlab-duo",
3
3
  "private": true,
4
- "version": "0.2.160",
4
+ "version": "0.2.162",
5
5
  "type": "module",
6
6
  "engines": {
7
7
  "bun": ">=1.0.0"
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@kolisachint/hoocode-extension-sandbox",
3
3
  "private": true,
4
- "version": "0.2.160",
4
+ "version": "0.2.162",
5
5
  "type": "module",
6
6
  "engines": {
7
7
  "bun": ">=1.0.0"
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@kolisachint/hoocode-extension-with-deps",
3
3
  "private": true,
4
- "version": "0.2.160",
4
+ "version": "0.2.162",
5
5
  "type": "module",
6
6
  "engines": {
7
7
  "bun": ">=1.0.0"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kolisachint/hoocode-agent",
3
- "version": "0.4.163",
3
+ "version": "0.4.165",
4
4
  "description": "Coding agent CLI with read, bash, edit, write tools and session management",
5
5
  "type": "module",
6
6
  "hoocodeConfig": {
@@ -42,12 +42,15 @@
42
42
  "copy-assets": "shx mkdir -p dist/modes/interactive/theme && shx cp src/modes/interactive/theme/*.json dist/modes/interactive/theme/ && shx mkdir -p dist/core/export-html/vendor && shx cp src/core/export-html/template.html src/core/export-html/template.css src/core/export-html/template.js dist/core/export-html/ && shx cp src/core/export-html/vendor/*.js dist/core/export-html/vendor/ && shx mkdir -p dist/core/extensions/plugins && shx cp -r src/core/extensions/plugins/default-marketplace dist/core/extensions/plugins/",
43
43
  "copy-binary-assets": "shx cp package.json dist/ && shx cp README.md dist/ && shx cp CHANGELOG.md dist/ && shx mkdir -p dist/theme && shx cp src/modes/interactive/theme/*.json dist/theme/ && shx mkdir -p dist/export-html/vendor && shx cp src/core/export-html/template.html dist/export-html/ && shx cp src/core/export-html/vendor/*.js dist/export-html/vendor/ && shx cp -r docs dist/ && shx cp -r examples dist/ && shx cp ../../node_modules/@silvia-odwyer/photon-node/photon_rs_bg.wasm dist/",
44
44
  "test": "vitest --run",
45
+ "search-eval": "bun scripts/search-eval.ts",
46
+ "search-eval:gold": "bun scripts/search-eval-gold.ts",
47
+ "search-eval:compare": "bun scripts/search-eval-compare.ts",
45
48
  "prepublishOnly": "npm run clean && npm run build"
46
49
  },
47
50
  "dependencies": {
48
- "@kolisachint/hoocode-agent-core": "^0.4.163",
49
- "@kolisachint/hoocode-ai": "^0.4.163",
50
- "@kolisachint/hoocode-tui": "^0.4.163",
51
+ "@kolisachint/hoocode-agent-core": "^0.4.165",
52
+ "@kolisachint/hoocode-ai": "^0.4.165",
53
+ "@kolisachint/hoocode-tui": "^0.4.165",
51
54
  "@silvia-odwyer/photon-node": "^0.3.4",
52
55
  "chalk": "^5.5.0",
53
56
  "cli-highlight": "^2.1.11",
@@ -1,24 +0,0 @@
1
- /**
2
- * `browser_continue` tool: answer a `NeedsParent` suspension from `browser_run`.
3
- *
4
- * Looks up the paused serve session by its `ResumeToken`, issues `flow_resume`
5
- * with the parent's decision (a `ParentResponse` object), and maps the next
6
- * outcome the same way `browser_run` does — completing, failing, or suspending
7
- * again with a fresh token. The live serve process (and its browser state) is
8
- * reused across rounds. See {@link browsertools-shared}.
9
- */
10
- import type { AgentTool } from "@kolisachint/hoocode-agent-core";
11
- import { Type } from "typebox";
12
- import { type ToolDefinition } from "../../extensions/types.js";
13
- import { type BrowserRunDetails } from "./browser-run.js";
14
- import { type BrowsertoolsToolOptions } from "./browsertools-shared.js";
15
- declare const browserContinueSchema: Type.TObject<{
16
- token: Type.TString;
17
- response: Type.TRecord<"^.*$", Type.TUnknown>;
18
- }>;
19
- export interface BrowserContinueToolOptions extends BrowsertoolsToolOptions {
20
- }
21
- export declare function createBrowserContinueToolDefinition(_cwd: string, options?: BrowserContinueToolOptions): ToolDefinition<typeof browserContinueSchema, BrowserRunDetails>;
22
- export declare function createBrowserContinueTool(cwd: string, options?: BrowserContinueToolOptions): AgentTool<typeof browserContinueSchema>;
23
- export {};
24
- //# sourceMappingURL=browser-continue.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"browser-continue.d.ts","sourceRoot":"","sources":["../../../../src/core/tools/browser/browser-continue.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iCAAiC,CAAC;AACjE,OAAO,EAAe,IAAI,EAAE,MAAM,SAAS,CAAC;AAC5C,OAAO,EAAc,KAAK,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAE5E,OAAO,EAAe,KAAK,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AACvE,OAAO,EAEN,KAAK,uBAAuB,EAI5B,MAAM,0BAA0B,CAAC;AAElC,QAAA,MAAM,qBAAqB;;;EAOzB,CAAC;AAIH,MAAM,WAAW,0BAA2B,SAAQ,uBAAuB;CAAG;AAE9E,wBAAgB,mCAAmC,CAGlD,IAAI,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE,0BAA0B,GAClC,cAAc,CAAC,OAAO,qBAAqB,EAAE,iBAAiB,CAAC,CAyDjE;AAED,wBAAgB,yBAAyB,CACxC,GAAG,EAAE,MAAM,EACX,OAAO,CAAC,EAAE,0BAA0B,GAClC,SAAS,CAAC,OAAO,qBAAqB,CAAC,CAEzC","sourcesContent":["/**\n * `browser_continue` tool: answer a `NeedsParent` suspension from `browser_run`.\n *\n * Looks up the paused serve session by its `ResumeToken`, issues `flow_resume`\n * with the parent's decision (a `ParentResponse` object), and maps the next\n * outcome the same way `browser_run` does — completing, failing, or suspending\n * again with a fresh token. The live serve process (and its browser state) is\n * reused across rounds. See {@link browsertools-shared}.\n */\n\nimport type { AgentTool } from \"@kolisachint/hoocode-agent-core\";\nimport { type Static, Type } from \"typebox\";\nimport { defineTool, type ToolDefinition } from \"../../extensions/types.js\";\nimport { wrapToolDefinition } from \"../tool-definition-wrapper.js\";\nimport { advanceFlow, type BrowserRunDetails } from \"./browser-run.js\";\nimport {\n\ttype BrowserClientConfig,\n\ttype BrowsertoolsToolOptions,\n\ttype FlowOutcome,\n\tresolveBrowsertoolsOptions,\n\ttakeSession,\n} from \"./browsertools-shared.js\";\n\nconst browserContinueSchema = Type.Object({\n\ttoken: Type.String({ description: \"The resume token returned by a browser_run NeedsParent result.\" }),\n\tresponse: Type.Record(Type.String(), Type.Unknown(), {\n\t\tdescription:\n\t\t\t\"The ParentResponse object answering the request, e.g. \" +\n\t\t\t'{ \"response\": \"state\", \"state\": \"logged_in\" } or { \"response\": \"verified\", \"passed\": true }.',\n\t}),\n});\n\ntype BrowserContinueInput = Static<typeof browserContinueSchema>;\n\nexport interface BrowserContinueToolOptions extends BrowsertoolsToolOptions {}\n\nexport function createBrowserContinueToolDefinition(\n\t// cwd is part of the factory signature for parity with other tools, but continue\n\t// reuses the serve process parked by browser_run, so it is not needed here.\n\t_cwd: string,\n\toptions?: BrowserContinueToolOptions,\n): ToolDefinition<typeof browserContinueSchema, BrowserRunDetails> {\n\tconst opts = resolveBrowsertoolsOptions(options);\n\treturn defineTool({\n\t\tname: \"browser_continue\",\n\t\tlabel: \"browser continue\",\n\t\tdescription:\n\t\t\t\"Continue a browser flow that suspended with a NeedsParent request. Pass the token from the \" +\n\t\t\t\"browser_run result and a ParentResponse object answering the request. The flow continues \" +\n\t\t\t\"deterministically and either completes, fails, or suspends again with a new token — in which \" +\n\t\t\t\"case read the new screenshot and call browser_continue again, looping until the outcome is \" +\n\t\t\t\"`complete`. Do not abandon the loop to read the page with webfetch. ParentResponse by request \" +\n\t\t\t'kind: decide_next_action -> { response: \"next_action\", action: <Action e.g. {action:\"click\", ' +\n\t\t\t'selector, fallbacks?}> }; classify_state -> { response: \"state\", state }; verify_visual -> ' +\n\t\t\t'{ response: \"verified\", passed }; extract_semantic -> { response: \"extracted\", fields }; ' +\n\t\t\t'reidentify_element -> { response: \"element\", selector }. Off by default; enabled with ' +\n\t\t\t\"--enable-browsertools.\",\n\t\tparameters: browserContinueSchema,\n\t\tasync execute(_toolCallId, params: BrowserContinueInput, signal) {\n\t\t\tif (signal?.aborted) throw new Error(\"Operation aborted\");\n\n\t\t\tconst session = takeSession(params.token);\n\t\t\tif (!session) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`No paused browser flow for token \"${params.token}\" — it may have completed, expired (idle ` +\n\t\t\t\t\t\t`timeout), or never existed. Start a new flow with browser_run.`,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tconst { client } = session;\n\t\t\tconst onAbort = () => client.dispose();\n\t\t\tsignal?.addEventListener(\"abort\", onAbort, { once: true });\n\t\t\ttry {\n\t\t\t\tconst outcome = await client.request<FlowOutcome>(\"flow_resume\", {\n\t\t\t\t\ttoken: params.token,\n\t\t\t\t\tresponse: params.response,\n\t\t\t\t});\n\t\t\t\tif (signal?.aborted) {\n\t\t\t\t\tclient.dispose();\n\t\t\t\t\tthrow new Error(\"Operation aborted\");\n\t\t\t\t}\n\t\t\t\t// Preserve the original browser config so the idle client can be reused\n\t\t\t\t// by a subsequent browser_run with the same headful/browserPath settings.\n\t\t\t\tconst browserConfig: BrowserClientConfig = {\n\t\t\t\t\theadful: session.headful,\n\t\t\t\t\tbrowserPath: session.browserPath,\n\t\t\t\t\tidleTimeoutMs: opts.idleTimeoutMs,\n\t\t\t\t};\n\t\t\t\t// Count this resume as one more NeedsParent round if it suspends again.\n\t\t\t\treturn await advanceFlow(client, outcome, session.rounds + 1, opts, browserConfig);\n\t\t\t} catch (error) {\n\t\t\t\tclient.dispose();\n\t\t\t\tthrow error;\n\t\t\t} finally {\n\t\t\t\tsignal?.removeEventListener(\"abort\", onAbort);\n\t\t\t}\n\t\t},\n\t});\n}\n\nexport function createBrowserContinueTool(\n\tcwd: string,\n\toptions?: BrowserContinueToolOptions,\n): AgentTool<typeof browserContinueSchema> {\n\treturn wrapToolDefinition(createBrowserContinueToolDefinition(cwd, options));\n}\n"]}
@@ -1,84 +0,0 @@
1
- /**
2
- * `browser_continue` tool: answer a `NeedsParent` suspension from `browser_run`.
3
- *
4
- * Looks up the paused serve session by its `ResumeToken`, issues `flow_resume`
5
- * with the parent's decision (a `ParentResponse` object), and maps the next
6
- * outcome the same way `browser_run` does — completing, failing, or suspending
7
- * again with a fresh token. The live serve process (and its browser state) is
8
- * reused across rounds. See {@link browsertools-shared}.
9
- */
10
- import { Type } from "typebox";
11
- import { defineTool } from "../../extensions/types.js";
12
- import { wrapToolDefinition } from "../tool-definition-wrapper.js";
13
- import { advanceFlow } from "./browser-run.js";
14
- import { resolveBrowsertoolsOptions, takeSession, } from "./browsertools-shared.js";
15
- const browserContinueSchema = Type.Object({
16
- token: Type.String({ description: "The resume token returned by a browser_run NeedsParent result." }),
17
- response: Type.Record(Type.String(), Type.Unknown(), {
18
- description: "The ParentResponse object answering the request, e.g. " +
19
- '{ "response": "state", "state": "logged_in" } or { "response": "verified", "passed": true }.',
20
- }),
21
- });
22
- export function createBrowserContinueToolDefinition(
23
- // cwd is part of the factory signature for parity with other tools, but continue
24
- // reuses the serve process parked by browser_run, so it is not needed here.
25
- _cwd, options) {
26
- const opts = resolveBrowsertoolsOptions(options);
27
- return defineTool({
28
- name: "browser_continue",
29
- label: "browser continue",
30
- description: "Continue a browser flow that suspended with a NeedsParent request. Pass the token from the " +
31
- "browser_run result and a ParentResponse object answering the request. The flow continues " +
32
- "deterministically and either completes, fails, or suspends again with a new token — in which " +
33
- "case read the new screenshot and call browser_continue again, looping until the outcome is " +
34
- "`complete`. Do not abandon the loop to read the page with webfetch. ParentResponse by request " +
35
- 'kind: decide_next_action -> { response: "next_action", action: <Action e.g. {action:"click", ' +
36
- 'selector, fallbacks?}> }; classify_state -> { response: "state", state }; verify_visual -> ' +
37
- '{ response: "verified", passed }; extract_semantic -> { response: "extracted", fields }; ' +
38
- 'reidentify_element -> { response: "element", selector }. Off by default; enabled with ' +
39
- "--enable-browsertools.",
40
- parameters: browserContinueSchema,
41
- async execute(_toolCallId, params, signal) {
42
- if (signal?.aborted)
43
- throw new Error("Operation aborted");
44
- const session = takeSession(params.token);
45
- if (!session) {
46
- throw new Error(`No paused browser flow for token "${params.token}" — it may have completed, expired (idle ` +
47
- `timeout), or never existed. Start a new flow with browser_run.`);
48
- }
49
- const { client } = session;
50
- const onAbort = () => client.dispose();
51
- signal?.addEventListener("abort", onAbort, { once: true });
52
- try {
53
- const outcome = await client.request("flow_resume", {
54
- token: params.token,
55
- response: params.response,
56
- });
57
- if (signal?.aborted) {
58
- client.dispose();
59
- throw new Error("Operation aborted");
60
- }
61
- // Preserve the original browser config so the idle client can be reused
62
- // by a subsequent browser_run with the same headful/browserPath settings.
63
- const browserConfig = {
64
- headful: session.headful,
65
- browserPath: session.browserPath,
66
- idleTimeoutMs: opts.idleTimeoutMs,
67
- };
68
- // Count this resume as one more NeedsParent round if it suspends again.
69
- return await advanceFlow(client, outcome, session.rounds + 1, opts, browserConfig);
70
- }
71
- catch (error) {
72
- client.dispose();
73
- throw error;
74
- }
75
- finally {
76
- signal?.removeEventListener("abort", onAbort);
77
- }
78
- },
79
- });
80
- }
81
- export function createBrowserContinueTool(cwd, options) {
82
- return wrapToolDefinition(createBrowserContinueToolDefinition(cwd, options));
83
- }
84
- //# sourceMappingURL=browser-continue.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"browser-continue.js","sourceRoot":"","sources":["../../../../src/core/tools/browser/browser-continue.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * `browser_continue` tool: answer a `NeedsParent` suspension from `browser_run`.\n *\n * Looks up the paused serve session by its `ResumeToken`, issues `flow_resume`\n * with the parent's decision (a `ParentResponse` object), and maps the next\n * outcome the same way `browser_run` does — completing, failing, or suspending\n * again with a fresh token. The live serve process (and its browser state) is\n * reused across rounds. See {@link browsertools-shared}.\n */\n\nimport type { AgentTool } from \"@kolisachint/hoocode-agent-core\";\nimport { type Static, Type } from \"typebox\";\nimport { defineTool, type ToolDefinition } from \"../../extensions/types.js\";\nimport { wrapToolDefinition } from \"../tool-definition-wrapper.js\";\nimport { advanceFlow, type BrowserRunDetails } from \"./browser-run.js\";\nimport {\n\ttype BrowserClientConfig,\n\ttype BrowsertoolsToolOptions,\n\ttype FlowOutcome,\n\tresolveBrowsertoolsOptions,\n\ttakeSession,\n} from \"./browsertools-shared.js\";\n\nconst browserContinueSchema = Type.Object({\n\ttoken: Type.String({ description: \"The resume token returned by a browser_run NeedsParent result.\" }),\n\tresponse: Type.Record(Type.String(), Type.Unknown(), {\n\t\tdescription:\n\t\t\t\"The ParentResponse object answering the request, e.g. \" +\n\t\t\t'{ \"response\": \"state\", \"state\": \"logged_in\" } or { \"response\": \"verified\", \"passed\": true }.',\n\t}),\n});\n\ntype BrowserContinueInput = Static<typeof browserContinueSchema>;\n\nexport interface BrowserContinueToolOptions extends BrowsertoolsToolOptions {}\n\nexport function createBrowserContinueToolDefinition(\n\t// cwd is part of the factory signature for parity with other tools, but continue\n\t// reuses the serve process parked by browser_run, so it is not needed here.\n\t_cwd: string,\n\toptions?: BrowserContinueToolOptions,\n): ToolDefinition<typeof browserContinueSchema, BrowserRunDetails> {\n\tconst opts = resolveBrowsertoolsOptions(options);\n\treturn defineTool({\n\t\tname: \"browser_continue\",\n\t\tlabel: \"browser continue\",\n\t\tdescription:\n\t\t\t\"Continue a browser flow that suspended with a NeedsParent request. Pass the token from the \" +\n\t\t\t\"browser_run result and a ParentResponse object answering the request. The flow continues \" +\n\t\t\t\"deterministically and either completes, fails, or suspends again with a new token — in which \" +\n\t\t\t\"case read the new screenshot and call browser_continue again, looping until the outcome is \" +\n\t\t\t\"`complete`. Do not abandon the loop to read the page with webfetch. ParentResponse by request \" +\n\t\t\t'kind: decide_next_action -> { response: \"next_action\", action: <Action e.g. {action:\"click\", ' +\n\t\t\t'selector, fallbacks?}> }; classify_state -> { response: \"state\", state }; verify_visual -> ' +\n\t\t\t'{ response: \"verified\", passed }; extract_semantic -> { response: \"extracted\", fields }; ' +\n\t\t\t'reidentify_element -> { response: \"element\", selector }. Off by default; enabled with ' +\n\t\t\t\"--enable-browsertools.\",\n\t\tparameters: browserContinueSchema,\n\t\tasync execute(_toolCallId, params: BrowserContinueInput, signal) {\n\t\t\tif (signal?.aborted) throw new Error(\"Operation aborted\");\n\n\t\t\tconst session = takeSession(params.token);\n\t\t\tif (!session) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`No paused browser flow for token \"${params.token}\" — it may have completed, expired (idle ` +\n\t\t\t\t\t\t`timeout), or never existed. Start a new flow with browser_run.`,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tconst { client } = session;\n\t\t\tconst onAbort = () => client.dispose();\n\t\t\tsignal?.addEventListener(\"abort\", onAbort, { once: true });\n\t\t\ttry {\n\t\t\t\tconst outcome = await client.request<FlowOutcome>(\"flow_resume\", {\n\t\t\t\t\ttoken: params.token,\n\t\t\t\t\tresponse: params.response,\n\t\t\t\t});\n\t\t\t\tif (signal?.aborted) {\n\t\t\t\t\tclient.dispose();\n\t\t\t\t\tthrow new Error(\"Operation aborted\");\n\t\t\t\t}\n\t\t\t\t// Preserve the original browser config so the idle client can be reused\n\t\t\t\t// by a subsequent browser_run with the same headful/browserPath settings.\n\t\t\t\tconst browserConfig: BrowserClientConfig = {\n\t\t\t\t\theadful: session.headful,\n\t\t\t\t\tbrowserPath: session.browserPath,\n\t\t\t\t\tidleTimeoutMs: opts.idleTimeoutMs,\n\t\t\t\t};\n\t\t\t\t// Count this resume as one more NeedsParent round if it suspends again.\n\t\t\t\treturn await advanceFlow(client, outcome, session.rounds + 1, opts, browserConfig);\n\t\t\t} catch (error) {\n\t\t\t\tclient.dispose();\n\t\t\t\tthrow error;\n\t\t\t} finally {\n\t\t\t\tsignal?.removeEventListener(\"abort\", onAbort);\n\t\t\t}\n\t\t},\n\t});\n}\n\nexport function createBrowserContinueTool(\n\tcwd: string,\n\toptions?: BrowserContinueToolOptions,\n): AgentTool<typeof browserContinueSchema> {\n\treturn wrapToolDefinition(createBrowserContinueToolDefinition(cwd, options));\n}\n"]}