@qvac/harness 0.3.3 → 0.5.0

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 (301) hide show
  1. package/CHANGELOG.md +700 -126
  2. package/README.md +156 -0
  3. package/dist/harness.bundle +71 -103
  4. package/dist/prebuilds-manifest.json +0 -8
  5. package/dist-lib/index.d.ts +42 -40
  6. package/dist-lib/index.js +307 -117
  7. package/dist-lib/lib/audio-transcode-bare.d.ts +2 -2
  8. package/dist-lib/lib/audio-transcode-bare.js +28 -18
  9. package/dist-lib/lib/audio-transcode-node.d.ts +2 -2
  10. package/dist-lib/lib/capabilities/audio-resample.d.ts +7 -6
  11. package/dist-lib/lib/capabilities/audio-resample.js +30 -18
  12. package/dist-lib/lib/capabilities/completion.d.ts +20 -19
  13. package/dist-lib/lib/capabilities/completion.js +194 -135
  14. package/dist-lib/lib/capabilities/download.d.ts +6 -6
  15. package/dist-lib/lib/capabilities/download.js +15 -8
  16. package/dist-lib/lib/capabilities/embed.js +2 -1
  17. package/dist-lib/lib/capabilities/enhance.d.ts +6 -1
  18. package/dist-lib/lib/capabilities/enhance.js +7 -4
  19. package/dist-lib/lib/capabilities/invoke.js +7 -4
  20. package/dist-lib/lib/capabilities/kv-cache.js +2 -1
  21. package/dist-lib/lib/capabilities/preview-model-source.d.ts +69 -0
  22. package/dist-lib/lib/capabilities/preview-model-source.js +439 -0
  23. package/dist-lib/lib/capabilities/speech-text.js +5 -4
  24. package/dist-lib/lib/capabilities/tool-selection.js +10 -9
  25. package/dist-lib/lib/capabilities/tool-turns.js +30 -16
  26. package/dist-lib/lib/capabilities/usage-breakdown.js +2 -1
  27. package/dist-lib/lib/capabilities/voice.d.ts +5 -1
  28. package/dist-lib/lib/capabilities/voice.js +22 -12
  29. package/dist-lib/lib/connect.d.ts +15 -37
  30. package/dist-lib/lib/connect.js +161 -112
  31. package/dist-lib/lib/context-window.js +25 -21
  32. package/dist-lib/lib/contract-types.d.ts +26 -20
  33. package/dist-lib/lib/diagram-repair.d.ts +4 -4
  34. package/dist-lib/lib/diagram-repair.js +6 -3
  35. package/dist-lib/lib/docx/index.d.ts +3 -2
  36. package/dist-lib/lib/{error-fields.d.ts → guards.d.ts} +1 -0
  37. package/dist-lib/lib/{error-fields.js → guards.js} +3 -0
  38. package/dist-lib/lib/hardware/capability-probe.js +20 -10
  39. package/dist-lib/lib/hardware/gpu-classify.js +6 -3
  40. package/dist-lib/lib/hardware/gpu-probe-addon.js +8 -4
  41. package/dist-lib/lib/hardware/gpu-probe.js +4 -2
  42. package/dist-lib/lib/hardware/gpu-stats.native.cjs +8 -9
  43. package/dist-lib/lib/hardware/parsers.js +8 -4
  44. package/dist-lib/lib/helpers/buffer-burst.js +4 -2
  45. package/dist-lib/lib/helpers/control-tokens.js +4 -2
  46. package/dist-lib/lib/helpers/join-guard.d.ts +2 -2
  47. package/dist-lib/lib/helpers/join-guard.js +4 -2
  48. package/dist-lib/lib/helpers/load-stall.js +4 -2
  49. package/dist-lib/lib/helpers/markup-stream-filter.d.ts +2 -2
  50. package/dist-lib/lib/helpers/markup-stream-filter.js +6 -3
  51. package/dist-lib/lib/helpers/race-abort.js +2 -1
  52. package/dist-lib/lib/helpers/race-timeout.d.ts +5 -0
  53. package/dist-lib/lib/helpers/race-timeout.js +14 -0
  54. package/dist-lib/lib/helpers/run-outcome.d.ts +6 -8
  55. package/dist-lib/lib/helpers/run-outcome.js +36 -20
  56. package/dist-lib/lib/helpers/spawn-path.js +6 -3
  57. package/dist-lib/lib/helpers/text-coalescer.js +6 -3
  58. package/dist-lib/lib/helpers/thinking.js +38 -45
  59. package/dist-lib/lib/helpers/tool-call-markup.d.ts +9 -7
  60. package/dist-lib/lib/helpers/tool-call-markup.js +36 -20
  61. package/dist-lib/lib/helpers/tool-intent.d.ts +3 -2
  62. package/dist-lib/lib/helpers/tool-intent.js +4 -2
  63. package/dist-lib/lib/helpers/tool-label.js +18 -9
  64. package/dist-lib/lib/hooks/built-in/arg-repair.js +23 -13
  65. package/dist-lib/lib/hooks/built-in/egress.js +2 -1
  66. package/dist-lib/lib/hooks/built-in/skill-catalog.js +2 -1
  67. package/dist-lib/lib/hooks/pipeline.d.ts +5 -4
  68. package/dist-lib/lib/hooks/pipeline.js +6 -3
  69. package/dist-lib/lib/hooks/run.js +12 -8
  70. package/dist-lib/lib/model-download-progress.js +10 -8
  71. package/dist-lib/lib/models/context.d.ts +1 -1
  72. package/dist-lib/lib/models/context.js +5 -1
  73. package/dist-lib/lib/models/custom-model-cache.d.ts +13 -0
  74. package/dist-lib/lib/models/custom-model-cache.js +70 -0
  75. package/dist-lib/lib/models/gguf-header.d.ts +31 -0
  76. package/dist-lib/lib/models/gguf-header.js +230 -0
  77. package/dist-lib/lib/models/idle-unload.d.ts +14 -0
  78. package/dist-lib/lib/models/idle-unload.js +51 -0
  79. package/dist-lib/lib/models/kv-cache.d.ts +3 -2
  80. package/dist-lib/lib/models/kv-cache.js +4 -3
  81. package/dist-lib/lib/models/llm-presets.d.ts +2 -2
  82. package/dist-lib/lib/models/llm-presets.js +4 -2
  83. package/dist-lib/lib/models/model-catalog-constants.js +3 -2
  84. package/dist-lib/lib/models/model-catalog.d.ts +19 -21
  85. package/dist-lib/lib/models/model-catalog.js +66 -33
  86. package/dist-lib/lib/models/model-delete.d.ts +4 -1
  87. package/dist-lib/lib/models/model-delete.js +30 -15
  88. package/dist-lib/lib/models/model-handle.js +2 -1
  89. package/dist-lib/lib/models/model-list.d.ts +14 -3
  90. package/dist-lib/lib/models/model-list.js +33 -6
  91. package/dist-lib/lib/models/model-pool.d.ts +44 -10
  92. package/dist-lib/lib/models/model-pool.js +47 -42
  93. package/dist-lib/lib/models/model-source.js +11 -6
  94. package/dist-lib/lib/models/music-catalog.d.ts +2 -2
  95. package/dist-lib/lib/models/nmt-catalog.d.ts +4 -3
  96. package/dist-lib/lib/models/nmt-catalog.js +10 -5
  97. package/dist-lib/lib/models/opencl-cache.d.ts +6 -1
  98. package/dist-lib/lib/models/opencl-cache.js +5 -3
  99. package/dist-lib/lib/models/sdk-faults.d.ts +11 -8
  100. package/dist-lib/lib/models/sdk-faults.js +9 -6
  101. package/dist-lib/lib/models/shards.d.ts +11 -0
  102. package/dist-lib/lib/models/shards.js +15 -0
  103. package/dist-lib/lib/models/tool-dialect.js +10 -5
  104. package/dist-lib/lib/no-redirect-fetch-bare.d.ts +3 -3
  105. package/dist-lib/lib/no-redirect-fetch-bare.js +1 -1
  106. package/dist-lib/lib/no-redirect-fetch-impl.js +6 -3
  107. package/dist-lib/lib/no-redirect-fetch-node.d.ts +1 -1
  108. package/dist-lib/lib/no-redirect-fetch-node.js +9 -9
  109. package/dist-lib/lib/office/index.d.ts +3 -2
  110. package/dist-lib/lib/office/python.d.ts +3 -2
  111. package/dist-lib/lib/office/python.js +2 -1
  112. package/dist-lib/lib/prompt.d.ts +5 -4
  113. package/dist-lib/lib/prompt.js +8 -4
  114. package/dist-lib/lib/queue.js +10 -5
  115. package/dist-lib/lib/resolution-detector.js +21 -11
  116. package/dist-lib/lib/rpc-session.js +2 -1
  117. package/dist-lib/lib/sdk-loader.js +2 -1
  118. package/dist-lib/lib/sdk-storage.js +4 -2
  119. package/dist-lib/lib/serve.js +128 -68
  120. package/dist-lib/lib/spawn.js +7 -4
  121. package/dist-lib/lib/tmp-path.d.ts +7 -6
  122. package/dist-lib/lib/tool-call-display.d.ts +2 -2
  123. package/dist-lib/lib/tools/create-documents.js +18 -10
  124. package/dist-lib/lib/tools/edit-documents.js +23 -13
  125. package/dist-lib/lib/tools/edit-image.d.ts +2 -2
  126. package/dist-lib/lib/tools/edit-image.js +60 -36
  127. package/dist-lib/lib/tools/exec/adapters/adapter.d.ts +10 -10
  128. package/dist-lib/lib/tools/exec/adapters/python/attachments.d.ts +17 -11
  129. package/dist-lib/lib/tools/exec/adapters/python/attachments.js +53 -40
  130. package/dist-lib/lib/tools/exec/adapters/python/create-effect.d.ts +2 -2
  131. package/dist-lib/lib/tools/exec/adapters/python/create-effect.js +60 -32
  132. package/dist-lib/lib/tools/exec/adapters/python/guarded-fetch.d.ts +33 -22
  133. package/dist-lib/lib/tools/exec/adapters/python/guarded-fetch.js +58 -29
  134. package/dist-lib/lib/tools/exec/adapters/python/interrupt-timer.d.ts +2 -2
  135. package/dist-lib/lib/tools/exec/adapters/python/loader.js +23 -12
  136. package/dist-lib/lib/tools/exec/adapters/python/memfs.d.ts +13 -11
  137. package/dist-lib/lib/tools/exec/adapters/python/memfs.js +10 -5
  138. package/dist-lib/lib/tools/exec/adapters/python/net-shim.js +3 -1
  139. package/dist-lib/lib/tools/exec/adapters/python/output-policy.d.ts +6 -4
  140. package/dist-lib/lib/tools/exec/adapters/python/output-policy.js +20 -10
  141. package/dist-lib/lib/tools/exec/adapters/python/pyodide.d.ts +2 -2
  142. package/dist-lib/lib/tools/exec/adapters/python/pyodide.js +14 -7
  143. package/dist-lib/lib/tools/exec/adapters/python/python-adapter.d.ts +41 -34
  144. package/dist-lib/lib/tools/exec/adapters/python/python-adapter.js +316 -166
  145. package/dist-lib/lib/tools/exec/adapters/python/run-session.d.ts +9 -8
  146. package/dist-lib/lib/tools/exec/adapters/python/run-session.js +56 -28
  147. package/dist-lib/lib/tools/exec/adapters/python/wheels.d.ts +8 -10
  148. package/dist-lib/lib/tools/exec/adapters/python/wheels.js +22 -11
  149. package/dist-lib/lib/tools/exec/adapters/python/worker/client.d.ts +3 -2
  150. package/dist-lib/lib/tools/exec/adapters/python/worker/client.js +41 -26
  151. package/dist-lib/lib/tools/exec/adapters/python/worker/entry.js +27 -18
  152. package/dist-lib/lib/tools/exec/adapters/python/worker/protocol.d.ts +2 -2
  153. package/dist-lib/lib/tools/exec/adapters/python/worker/protocol.js +14 -13
  154. package/dist-lib/lib/tools/exec/adapters/python/worker/spawn-node.d.ts +4 -4
  155. package/dist-lib/lib/tools/exec/adapters/python/worker/spawn-node.js +10 -5
  156. package/dist-lib/lib/tools/exec/adapters/shell/shell-adapter.d.ts +4 -4
  157. package/dist-lib/lib/tools/exec/adapters/shell/shell-adapter.js +2 -1
  158. package/dist-lib/lib/tools/exec/obsidian/cli-process.d.ts +6 -1
  159. package/dist-lib/lib/tools/exec/obsidian/cli-process.js +8 -5
  160. package/dist-lib/lib/tools/exec/obsidian/cli.d.ts +6 -6
  161. package/dist-lib/lib/tools/exec/obsidian/cli.js +4 -4
  162. package/dist-lib/lib/tools/exec/obsidian/command.d.ts +3 -2
  163. package/dist-lib/lib/tools/exec/obsidian/command.js +6 -3
  164. package/dist-lib/lib/tools/exec/obsidian/output.js +2 -1
  165. package/dist-lib/lib/tools/exec/obsidian/route.d.ts +4 -6
  166. package/dist-lib/lib/tools/exec/obsidian/route.js +17 -11
  167. package/dist-lib/lib/tools/exec/obsidian/safe-path.js +2 -1
  168. package/dist-lib/lib/tools/exec/obsidian/vault-create.js +15 -7
  169. package/dist-lib/lib/tools/exec/obsidian/vault-paths.d.ts +2 -2
  170. package/dist-lib/lib/tools/exec/obsidian/vault-paths.js +20 -10
  171. package/dist-lib/lib/tools/exec/obsidian/vault-read.js +20 -13
  172. package/dist-lib/lib/tools/exec/obsidian/vault-write.js +24 -12
  173. package/dist-lib/lib/tools/exec/runner.d.ts +14 -14
  174. package/dist-lib/lib/tools/exec/runner.js +5 -3
  175. package/dist-lib/lib/tools/exec/scope.js +52 -28
  176. package/dist-lib/lib/tools/exec/shell-argv.js +31 -18
  177. package/dist-lib/lib/tools/exec/shell-guidance.d.ts +6 -1
  178. package/dist-lib/lib/tools/exec/shell-guidance.js +35 -20
  179. package/dist-lib/lib/tools/exec/video-tools.js +2 -2
  180. package/dist-lib/lib/tools/exec.d.ts +6 -8
  181. package/dist-lib/lib/tools/exec.js +113 -54
  182. package/dist-lib/lib/tools/file-glob.d.ts +4 -5
  183. package/dist-lib/lib/tools/file-glob.js +16 -11
  184. package/dist-lib/lib/tools/fs/glob.js +13 -18
  185. package/dist-lib/lib/tools/fs/remote-protocol.d.ts +10 -10
  186. package/dist-lib/lib/tools/fs/remote.d.ts +3 -2
  187. package/dist-lib/lib/tools/fs/remote.js +13 -7
  188. package/dist-lib/lib/tools/fs/scope.d.ts +8 -3
  189. package/dist-lib/lib/tools/fs/scope.js +20 -12
  190. package/dist-lib/lib/tools/fs/walk.js +6 -3
  191. package/dist-lib/lib/tools/fs/write.d.ts +3 -2
  192. package/dist-lib/lib/tools/generate-image.d.ts +11 -9
  193. package/dist-lib/lib/tools/generate-image.js +73 -37
  194. package/dist-lib/lib/tools/generate-music.d.ts +22 -11
  195. package/dist-lib/lib/tools/generate-music.js +79 -48
  196. package/dist-lib/lib/tools/grep.js +66 -41
  197. package/dist-lib/lib/tools/http-request/encode-fields.js +18 -9
  198. package/dist-lib/lib/tools/http-request/{utils.js → request.js} +20 -11
  199. package/dist-lib/lib/tools/http-request/truncate-response.js +57 -46
  200. package/dist-lib/lib/tools/http-request.d.ts +1 -1
  201. package/dist-lib/lib/tools/http-request.js +33 -18
  202. package/dist-lib/lib/tools/mcp/protocol.d.ts +53 -0
  203. package/dist-lib/lib/tools/mcp/{utils.js → protocol.js} +45 -23
  204. package/dist-lib/lib/tools/mcp-call.d.ts +1 -1
  205. package/dist-lib/lib/tools/mcp-call.js +37 -16
  206. package/dist-lib/lib/tools/operations/builders.js +10 -6
  207. package/dist-lib/lib/tools/operations/compile.js +20 -10
  208. package/dist-lib/lib/tools/operations/decoders.js +12 -6
  209. package/dist-lib/lib/tools/operations/gmail-message.js +3 -2
  210. package/dist-lib/lib/tools/outbound-guard.d.ts +4 -3
  211. package/dist-lib/lib/tools/outbound-guard.js +14 -8
  212. package/dist-lib/lib/tools/private-ip.js +8 -7
  213. package/dist-lib/lib/tools/rag-search.js +14 -7
  214. package/dist-lib/lib/tools/read-files.js +23 -20
  215. package/dist-lib/lib/tools/skill.js +19 -10
  216. package/dist-lib/lib/tools/skills/auth-resolver.d.ts +11 -18
  217. package/dist-lib/lib/tools/skills/auth-resolver.js +30 -18
  218. package/dist-lib/lib/tools/skills/bin-check.d.ts +3 -2
  219. package/dist-lib/lib/tools/skills/bin-check.js +10 -3
  220. package/dist-lib/lib/tools/skills/bundled-dir.js +20 -10
  221. package/dist-lib/lib/tools/skills/bundled-hash.d.ts +3 -2
  222. package/dist-lib/lib/tools/skills/bundled-hash.js +6 -1
  223. package/dist-lib/lib/tools/skills/catalog.d.ts +3 -2
  224. package/dist-lib/lib/tools/skills/catalog.js +33 -28
  225. package/dist-lib/lib/tools/skills/cli-argv.d.ts +3 -2
  226. package/dist-lib/lib/tools/skills/cli-argv.js +55 -52
  227. package/dist-lib/lib/tools/skills/cli-schema-check.js +21 -10
  228. package/dist-lib/lib/tools/skills/cli-schema-load.js +28 -20
  229. package/dist-lib/lib/tools/skills/cli-schema-types.d.ts +6 -6
  230. package/dist-lib/lib/tools/skills/cli-schema.js +2 -1
  231. package/dist-lib/lib/tools/skills/credential-check.d.ts +6 -3
  232. package/dist-lib/lib/tools/skills/credential-check.js +24 -12
  233. package/dist-lib/lib/tools/skills/credential-grants.js +1 -6
  234. package/dist-lib/lib/tools/skills/credential-health.d.ts +5 -1
  235. package/dist-lib/lib/tools/skills/credential-health.js +5 -3
  236. package/dist-lib/lib/tools/skills/frontmatter-parser.js +10 -5
  237. package/dist-lib/lib/tools/skills/manifest-parse.js +84 -64
  238. package/dist-lib/lib/tools/skills/mention.js +7 -6
  239. package/dist-lib/lib/tools/skills/obsidian-readiness.d.ts +2 -2
  240. package/dist-lib/lib/tools/skills/obsidian-readiness.js +11 -3
  241. package/dist-lib/lib/tools/skills/overlay-crud.d.ts +6 -4
  242. package/dist-lib/lib/tools/skills/overlay-crud.js +12 -6
  243. package/dist-lib/lib/tools/skills/overlay-dir.js +4 -2
  244. package/dist-lib/lib/tools/skills/overlay-watch.d.ts +3 -2
  245. package/dist-lib/lib/tools/skills/overlay-watch.js +8 -4
  246. package/dist-lib/lib/tools/skills/setup-routes.js +1 -3
  247. package/dist-lib/lib/tools/skills/skill-settings.js +4 -1
  248. package/dist-lib/lib/tools/skills/skill-state.d.ts +6 -11
  249. package/dist-lib/lib/tools/skills/skill-state.js +48 -29
  250. package/dist-lib/lib/tools/skills/tool-grants.js +6 -3
  251. package/dist-lib/lib/tools/toolsets.js +2 -1
  252. package/dist-lib/lib/tools/translate.d.ts +4 -5
  253. package/dist-lib/lib/tools/translate.js +31 -14
  254. package/dist-lib/lib/tools/url-guard.js +2 -1
  255. package/dist-lib/lib/tools/weather-lookup/place.js +8 -4
  256. package/dist-lib/lib/tools/weather-lookup/report.d.ts +7 -1
  257. package/dist-lib/lib/tools/weather-lookup/report.js +46 -9
  258. package/dist-lib/lib/tools/weather-lookup.js +18 -12
  259. package/dist-lib/lib/tools/web-fetch.js +47 -24
  260. package/dist-lib/lib/tools/web-search.js +8 -3
  261. package/dist-lib/lib/validating-lookup.d.ts +4 -4
  262. package/dist-lib/lib/wav-encode.d.ts +1 -1
  263. package/dist-lib/lib/wav-encode.js +11 -10
  264. package/dist-lib/lib/web-fetch/embedded-state.js +24 -12
  265. package/dist-lib/lib/web-fetch/extract.js +4 -2
  266. package/dist-lib/lib/web-fetch/images.js +8 -4
  267. package/dist-lib/lib/web-fetch/video.d.ts +2 -2
  268. package/dist-lib/lib/web-fetch/video.js +8 -4
  269. package/dist-lib/lib/web-search/engine-registry.d.ts +2 -2
  270. package/dist-lib/lib/web-search/engines/aol.js +1 -1
  271. package/dist-lib/lib/web-search/engines/bing.js +1 -1
  272. package/dist-lib/lib/web-search/engines/brave.js +4 -1
  273. package/dist-lib/lib/web-search/engines/duckduckgo.js +3 -2
  274. package/dist-lib/lib/web-search/engines/google.js +7 -3
  275. package/dist-lib/lib/web-search/engines/wikipedia.js +5 -4
  276. package/dist-lib/lib/web-search/engines/yahoo.js +1 -1
  277. package/dist-lib/lib/web-search/fetch-html.d.ts +5 -4
  278. package/dist-lib/lib/web-search/fetch-html.js +13 -7
  279. package/dist-lib/lib/web-search/orchestrator.d.ts +9 -5
  280. package/dist-lib/lib/web-search/orchestrator.js +3 -3
  281. package/dist-lib/lib/wire.d.ts +3 -2
  282. package/dist-lib/lib/wire.js +23 -15
  283. package/dist-lib/schema/baseline.js +10 -5
  284. package/dist-lib/schema/build.js +2 -1
  285. package/dist-lib/schema/harness.d.ts +15 -2
  286. package/dist-lib/schema/harness.js +55 -2
  287. package/dist-lib/schema/release-tags.d.ts +3 -2
  288. package/dist-lib/schema/release-tags.js +6 -3
  289. package/dist-lib/spec/bind.js +6 -0
  290. package/dist-lib/spec/calls.js +2 -0
  291. package/dist-lib/spec/hrpc/hrpc.json +26 -0
  292. package/dist-lib/spec/hrpc/index.js +27 -3
  293. package/dist-lib/spec/hrpc/messages.js +207 -8
  294. package/dist-lib/spec/hyperschema/index.js +207 -8
  295. package/dist-lib/spec/hyperschema/schema.json +165 -1
  296. package/dist-lib/spec/hyperschema/types.d.ts +42 -1
  297. package/package.json +13 -14
  298. package/worker/harness-sidecar.mjs +16 -22
  299. package/worker/stow-entry.mjs +7 -4
  300. package/dist-lib/lib/tools/mcp/utils.d.ts +0 -47
  301. /package/dist-lib/lib/tools/http-request/{utils.d.ts → request.d.ts} +0 -0
@@ -1,12 +1,14 @@
1
1
  import { AbortController } from 'bare-abort-controller';
2
+ import b4a from 'b4a';
2
3
  import fs from 'fs';
3
4
  import { settleOnClose } from './rpc-session.js';
4
5
  import { pipeline, Readable } from 'streamx';
5
6
  import { bindApi } from '../spec/bind.js';
6
7
  import HRPC from '../spec/hrpc/index.js';
7
8
  import { isCapability } from './capabilities/invoke.js';
9
+ import { CUSTOM_TASK_TYPES } from './models/model-catalog.js';
8
10
  import { unsupportedKvCacheDeletion } from './capabilities/kv-cache.js';
9
- import { errorMessage } from './error-fields.js';
11
+ import { errorMessage } from './guards.js';
10
12
  import { hlog } from './log.js';
11
13
  import { pushQueue } from './queue.js';
12
14
  import { TextCoalescer } from './helpers/text-coalescer.js';
@@ -44,6 +46,10 @@ export function serveHarness(stream, harness) {
44
46
  await harness.setSkillSettings?.(entries ?? []);
45
47
  return { ok: true };
46
48
  },
49
+ setModelIdleTimeout: async ({ ms }) => {
50
+ await harness.setModelIdleTimeout?.(ms);
51
+ return { ok: true };
52
+ },
47
53
  listManagedSkills: async () => ({
48
54
  skills: (await harness.listManagedSkills?.()) ?? []
49
55
  }),
@@ -54,8 +60,9 @@ export function serveHarness(stream, harness) {
54
60
  : { found: false, content: '', origin: '' };
55
61
  },
56
62
  writeSkillFile: async ({ name, content }) => {
57
- if (!harness.writeSkillFile)
63
+ if (!harness.writeSkillFile) {
58
64
  return { ok: false };
65
+ }
59
66
  return harness.writeSkillFile(name, content);
60
67
  },
61
68
  copySkillForEdit: async ({ name }) => ({
@@ -65,13 +72,32 @@ export function serveHarness(stream, harness) {
65
72
  removed: (await harness.deleteOverlaySkill?.(name))?.removed ?? false
66
73
  }),
67
74
  setCustomModels: async ({ models }) => {
68
- await harness.setCustomModels?.((models ?? []).map(({ name, src, projectionSrc }) => ({
69
- name,
70
- src,
71
- projectionSrc: projectionSrc ?? undefined
72
- })));
75
+ await harness.setCustomModels?.((models ?? []).flatMap((model) => {
76
+ const { name, src, projectionSrc, taskType, expectedSize, label, contextLength } = model;
77
+ const known = CUSTOM_TASK_TYPES.find((task) => task === taskType);
78
+ if (taskType && !known) {
79
+ return [];
80
+ }
81
+ return [
82
+ {
83
+ name,
84
+ src,
85
+ projectionSrc: projectionSrc ?? undefined,
86
+ taskType: known,
87
+ expectedSize: expectedSize || undefined,
88
+ label: label || undefined,
89
+ contextLength: contextLength || undefined
90
+ }
91
+ ];
92
+ }));
73
93
  return { ok: true };
74
94
  },
95
+ previewModelSource: async ({ src, mmprojSrc }) => {
96
+ if (!harness.previewModelSource) {
97
+ throw new Error('harness does not support previewModelSource');
98
+ }
99
+ return harness.previewModelSource({ src: src ?? '', mmprojSrc: mmprojSrc ?? undefined });
100
+ },
75
101
  describe: async () => (await harness.describe?.()) ?? {
76
102
  toolNames: [],
77
103
  skills: [],
@@ -91,15 +117,17 @@ export function serveHarness(stream, harness) {
91
117
  return packVectors(await harness.embed({ model, texts: texts ?? [] }));
92
118
  },
93
119
  transcribe: async ({ model, audio }) => {
94
- if (!harness.transcribe)
120
+ if (!harness.transcribe) {
95
121
  throw new Error('harness does not support transcribe');
122
+ }
96
123
  return {
97
- text: await harness.transcribe({ model: model ?? '', audio: audio ?? Buffer.alloc(0) })
124
+ text: await harness.transcribe({ model: model ?? '', audio: audio ?? b4a.alloc(0) })
98
125
  };
99
126
  },
100
127
  detectLanguage: async ({ text, topK }) => {
101
- if (!harness.detectLanguage)
128
+ if (!harness.detectLanguage) {
102
129
  throw new Error('harness does not support detectLanguage');
130
+ }
103
131
  const languages = await harness.detectLanguage({
104
132
  text: text ?? '',
105
133
  topK: topK ?? undefined
@@ -113,13 +141,15 @@ export function serveHarness(stream, harness) {
113
141
  },
114
142
  speakStream: speakStream(harness),
115
143
  getEmbeddingDevice: async () => {
116
- if (!harness.getEmbeddingDevice)
144
+ if (!harness.getEmbeddingDevice) {
117
145
  throw unsupportedEmbeddingDeviceControl();
146
+ }
118
147
  return { device: await harness.getEmbeddingDevice() };
119
148
  },
120
149
  setEmbeddingDevice: async ({ device }) => {
121
- if (!harness.setEmbeddingDevice)
150
+ if (!harness.setEmbeddingDevice) {
122
151
  throw unsupportedEmbeddingDeviceControl();
152
+ }
123
153
  await harness.setEmbeddingDevice(device);
124
154
  return { ok: true };
125
155
  },
@@ -134,7 +164,7 @@ export function serveHarness(stream, harness) {
134
164
  }),
135
165
  listModels: async (req) => ({
136
166
  models: (await harness.list?.({
137
- type: (req?.type || undefined),
167
+ type: req?.type || undefined,
138
168
  cachedOnly: req?.cachedOnly ?? undefined,
139
169
  fullRegistry: req?.fullRegistry ?? undefined
140
170
  })) ?? []
@@ -149,13 +179,15 @@ export function serveHarness(stream, harness) {
149
179
  models: (await harness.infraModels?.(profile ?? '')) ?? []
150
180
  }),
151
181
  capability: async () => {
152
- if (!harness.capability)
182
+ if (!harness.capability) {
153
183
  throw new Error('harness does not support capability');
184
+ }
154
185
  return harness.capability();
155
186
  },
156
187
  warmModel: async ({ model, agent }) => {
157
- if (!harness.warmModel)
188
+ if (!harness.warmModel) {
158
189
  throw new Error('harness does not support warmModel');
190
+ }
159
191
  await harness.warmModel({ model, agent: agent ?? {} });
160
192
  return { ok: true };
161
193
  },
@@ -183,19 +215,15 @@ function toWireSkill(skill) {
183
215
  allowList: skill.allowList,
184
216
  platforms: skill.platform,
185
217
  status: skill.status,
186
- ...(skill.requires
187
- ? {
188
- requirements: {
189
- binaries,
190
- credentials: skill.requires.credentials ?? []
191
- }
192
- }
193
- : {}),
194
- ...(skill.install ? { install: skill.install } : {}),
195
- ...(skill.setup ? { setup: skill.setup, ...(setupV2 ? { setupV2 } : {}) } : {}),
196
- ...(skill.stub !== undefined ? { stub: skill.stub } : {}),
197
- ...(skill.minCapabilityProfile ? { minCapabilityProfile: skill.minCapabilityProfile } : {}),
198
- ...(skill.emoji ? { emoji: skill.emoji } : {})
218
+ requirements: skill.requires
219
+ ? { binaries, credentials: skill.requires.credentials ?? [] }
220
+ : undefined,
221
+ install: skill.install || undefined,
222
+ setup: skill.setup || undefined,
223
+ setupV2,
224
+ stub: skill.stub,
225
+ minCapabilityProfile: skill.minCapabilityProfile || undefined,
226
+ emoji: skill.emoji || undefined
199
227
  };
200
228
  }
201
229
  function unsupportedEmbeddingDeviceControl() {
@@ -230,8 +258,9 @@ function modelEvents(harness) {
230
258
  catalogChanged,
231
259
  ...loaded.map((entry) => `${entry.model}:${entry.refs}`)
232
260
  ].join('\0');
233
- if (key === last)
261
+ if (key === last) {
234
262
  return;
263
+ }
235
264
  last = key;
236
265
  const frame = {
237
266
  status: state.status,
@@ -269,16 +298,18 @@ function modelEvents(harness) {
269
298
  }
270
299
  function speakStream(harness) {
271
300
  return async function* (duplex) {
272
- if (!harness.speakStream)
301
+ if (!harness.speakStream) {
273
302
  throw new Error('harness does not support speakStream');
303
+ }
274
304
  const text = pushQueue();
275
305
  const end = () => text.end();
276
306
  duplex.on('close', end);
277
307
  duplex.on('end', end);
278
308
  duplex.readStream?.on('close', end);
279
309
  duplex.on('data', (frame) => {
280
- if (frame.text)
310
+ if (frame.text) {
281
311
  text.push(frame.text);
312
+ }
282
313
  });
283
314
  for await (const chunk of harness.speakStream({ model: '', text })) {
284
315
  yield { audio: chunk.audio, sampleRate: chunk.sampleRate };
@@ -286,13 +317,15 @@ function speakStream(harness) {
286
317
  };
287
318
  }
288
319
  function runFrame(harness, frame, context) {
289
- if (frame.type !== 'invoke')
320
+ if (frame.type !== 'invoke') {
290
321
  return harness.completion(context);
322
+ }
291
323
  if (!harness.invoke)
292
324
  throw new Error('harness does not support invoke');
293
325
  const capability = frame.capability ?? '';
294
- if (!isCapability(capability))
326
+ if (!isCapability(capability)) {
295
327
  throw new Error(`unknown capability: ${capability}`);
328
+ }
296
329
  return harness.invoke({
297
330
  capability,
298
331
  args: frame.arguments ?? {},
@@ -318,26 +351,31 @@ function completion(harness) {
318
351
  let started = false;
319
352
  const gone = () => {
320
353
  controller.abort('client gone');
321
- for (const resolve of pending.values())
354
+ for (const resolve of pending.values()) {
322
355
  resolve(false);
356
+ }
323
357
  pending.clear();
324
358
  for (const { reject, staged } of pendingAttach.values()) {
325
359
  void fs.promises.unlink(staged).catch(noop);
326
360
  reject(new Error('client gone'));
327
361
  }
328
362
  pendingAttach.clear();
329
- for (const { reject } of pendingFetch.values())
363
+ for (const { reject } of pendingFetch.values()) {
330
364
  reject(new Error('client gone'));
365
+ }
331
366
  pendingFetch.clear();
332
- for (const { reject } of pendingRag.values())
367
+ for (const { reject } of pendingRag.values()) {
333
368
  reject(new Error('client gone'));
369
+ }
334
370
  pendingRag.clear();
335
- for (const { resolve } of pendingQuestion.values())
371
+ for (const { resolve } of pendingQuestion.values()) {
336
372
  resolve(null);
373
+ }
337
374
  pendingQuestion.clear();
338
375
  for (const { reject, path } of pendingDrive.values()) {
339
- if (path)
376
+ if (path) {
340
377
  void fs.promises.unlink(path).catch(noop);
378
+ }
341
379
  reject(new Error('client gone'));
342
380
  }
343
381
  pendingDrive.clear();
@@ -348,35 +386,43 @@ function completion(harness) {
348
386
  duplex.readStream?.on('close', gone);
349
387
  duplex.on('data', (frame) => {
350
388
  if (frame.type === 'decision') {
351
- if (typeof frame.callId !== 'string')
389
+ if (typeof frame.callId !== 'string') {
352
390
  return;
391
+ }
353
392
  pending.get(frame.callId)?.(frame.approved === true);
354
393
  pending.delete(frame.callId);
355
394
  return;
356
395
  }
357
396
  if (frame.type === 'attach-result') {
358
- if (typeof frame.callId !== 'string')
397
+ if (typeof frame.callId !== 'string') {
359
398
  return;
399
+ }
360
400
  const waiter = pendingAttach.get(frame.callId);
361
401
  pendingAttach.delete(frame.callId);
362
- if (!waiter)
402
+ if (!waiter) {
363
403
  return;
404
+ }
364
405
  void fs.promises.unlink(waiter.staged).catch(noop);
365
- if (frame.attachment)
406
+ if (frame.attachment) {
366
407
  waiter.resolve(frame.attachment);
367
- else
408
+ }
409
+ else {
368
410
  waiter.reject(new Error(frame.message || 'the host failed to store the attachment'));
411
+ }
369
412
  return;
370
413
  }
371
414
  if (frame.type === 'fetch-result') {
372
- if (typeof frame.callId !== 'string')
415
+ if (typeof frame.callId !== 'string') {
373
416
  return;
417
+ }
374
418
  const waiter = pendingFetch.get(frame.callId);
375
419
  pendingFetch.delete(frame.callId);
376
- if (!waiter)
420
+ if (!waiter) {
377
421
  return;
378
- if (frame.message)
422
+ }
423
+ if (frame.message) {
379
424
  return waiter.reject(new Error(frame.message));
425
+ }
380
426
  const staged = frame.path;
381
427
  const { fileName, mimeType, attachmentId, size } = frame;
382
428
  if (frame.meta === true) {
@@ -387,8 +433,9 @@ function completion(harness) {
387
433
  byteLength: typeof size === 'number' ? size : 0
388
434
  });
389
435
  }
390
- if (!staged)
436
+ if (!staged) {
391
437
  return waiter.resolve(null);
438
+ }
392
439
  fs.promises.readFile(staged).then((bytes) => waiter.resolve({
393
440
  id: attachmentId ?? '',
394
441
  fileName: fileName ?? '',
@@ -399,42 +446,52 @@ function completion(harness) {
399
446
  return;
400
447
  }
401
448
  if (frame.type === 'rag-result') {
402
- if (typeof frame.callId !== 'string')
449
+ if (typeof frame.callId !== 'string') {
403
450
  return;
451
+ }
404
452
  const waiter = pendingRag.get(frame.callId);
405
453
  pendingRag.delete(frame.callId);
406
- if (!waiter)
454
+ if (!waiter) {
407
455
  return;
408
- if (frame.message)
456
+ }
457
+ if (frame.message) {
409
458
  return waiter.reject(new Error(frame.message));
459
+ }
410
460
  waiter.resolve(frame.result ?? { enabled: false, indexEmpty: true, hasKnowledge: false, hits: [] });
411
461
  }
412
462
  if (frame.type === 'question-result') {
413
- if (typeof frame.callId !== 'string')
463
+ if (typeof frame.callId !== 'string') {
414
464
  return;
465
+ }
415
466
  const waiter = pendingQuestion.get(frame.callId);
416
467
  pendingQuestion.delete(frame.callId);
417
- if (!waiter)
468
+ if (!waiter) {
418
469
  return;
419
- if (frame.message)
470
+ }
471
+ if (frame.message) {
420
472
  hlog('question failed', { callId: frame.callId, message: frame.message });
473
+ }
421
474
  waiter.resolve(frame.answer || null);
422
475
  return;
423
476
  }
424
477
  if (frame.type === 'drive-result') {
425
- if (typeof frame.callId !== 'string')
478
+ if (typeof frame.callId !== 'string') {
426
479
  return;
480
+ }
427
481
  const waiter = pendingDrive.get(frame.callId);
428
482
  pendingDrive.delete(frame.callId);
429
- if (!waiter)
483
+ if (!waiter) {
430
484
  return;
431
- if (frame.message)
485
+ }
486
+ if (frame.message) {
432
487
  return waiter.reject(new Error(frame.message));
433
- waiter.resolve((frame.payload ?? {}));
488
+ }
489
+ waiter.resolve(frame.payload ?? {});
434
490
  return;
435
491
  }
436
- if (started || (frame.type !== 'start' && frame.type !== 'invoke'))
492
+ if (started || (frame.type !== 'start' && frame.type !== 'invoke')) {
437
493
  return;
494
+ }
438
495
  started = true;
439
496
  const context = {
440
497
  model: frame.model ?? '',
@@ -447,9 +504,9 @@ function completion(harness) {
447
504
  messages: (frame.messages ?? []).map(({ role, content, attachments }) => ({
448
505
  role,
449
506
  content,
450
- ...(attachments?.length
451
- ? { attachments: attachments.map(({ path: file }) => ({ path: file })) }
452
- : {})
507
+ attachments: attachments?.length
508
+ ? attachments.map(({ path: file }) => ({ path: file }))
509
+ : undefined
453
510
  })),
454
511
  toolConfigs: frame.toolConfigs ?? undefined,
455
512
  grantedDirs: frame.grantedDirs ?? undefined,
@@ -467,7 +524,7 @@ function completion(harness) {
467
524
  pendingDrive.set(callId, {
468
525
  resolve,
469
526
  reject,
470
- ...(request.op === 'write' ? { path: request.path } : {})
527
+ path: request.op === 'write' ? request.path : undefined
471
528
  });
472
529
  events.push({ type: 'drive-ask', callId, op: request.op, payload: request });
473
530
  })
@@ -516,7 +573,7 @@ function completion(harness) {
516
573
  type: 'fetch-ask',
517
574
  callId,
518
575
  attachmentId,
519
- ...(meta === true ? { meta: true } : {})
576
+ meta: meta === true ? true : undefined
520
577
  });
521
578
  }),
522
579
  ragSearch: ({ query, limit }) => controller.signal.aborted
@@ -611,6 +668,13 @@ function downloadStream(harness, req) {
611
668
  pendingProgress = null;
612
669
  lastProgressAt = at;
613
670
  };
671
+ const queueProgress = (wire) => {
672
+ const now = Date.now();
673
+ pendingProgress = wire;
674
+ if (lastProgressAt === 0 || now - lastProgressAt >= DOWNLOAD_PROGRESS_INTERVAL_MS) {
675
+ flushProgress(now);
676
+ }
677
+ };
614
678
  try {
615
679
  for await (const event of harness.download({
616
680
  model: req.model,
@@ -620,11 +684,7 @@ function downloadStream(harness, req) {
620
684
  })) {
621
685
  const wire = toDownloadWire(event);
622
686
  if (wire.type === 'progress') {
623
- const now = Date.now();
624
- pendingProgress = wire;
625
- if (lastProgressAt === 0 || now - lastProgressAt >= DOWNLOAD_PROGRESS_INTERVAL_MS) {
626
- flushProgress(now);
627
- }
687
+ queueProgress(wire);
628
688
  continue;
629
689
  }
630
690
  flushProgress();
@@ -3,8 +3,9 @@ import { connectHarness } from './connect.js';
3
3
  import { setHarnessLog } from './log.js';
4
4
  import { createEmbedConfig } from './models/embed-config.js';
5
5
  export function harnessChild(entry, opts) {
6
- if (opts.logger)
6
+ if (opts.logger) {
7
7
  setHarnessLog(true);
8
+ }
8
9
  const { runner, skillsDir, gpuStats, wheelsDir, sdkStorage, embedDevice, embedGpuLayers, embedBatchSize, imageModel, imageClamps, musicModel, musicClamps, llm, onIpc, logger = null, ...spec } = opts;
9
10
  const embedConfig = createEmbedConfig({
10
11
  device: embedDevice,
@@ -36,8 +37,9 @@ export function harnessChild(entry, opts) {
36
37
  }
37
38
  export function spawnHarness(opts) {
38
39
  const logger = opts.logger ?? null;
39
- if (logger)
40
+ if (logger) {
40
41
  setHarnessLog(true);
42
+ }
41
43
  const embedConfig = createEmbedConfig({
42
44
  device: opts.embedDevice,
43
45
  gpuLayers: opts.embedGpuLayers,
@@ -45,7 +47,7 @@ export function spawnHarness(opts) {
45
47
  });
46
48
  return connectHarness(async () => {
47
49
  const { default: Sidecar } = await import('bare-sidecar');
48
- const { wrap } = (await import('bare-stow/host'));
50
+ const { wrap } = await import('bare-stow/host');
49
51
  const sidecar = new Sidecar(opts.entry, [
50
52
  opts.skillsDir ?? '',
51
53
  JSON.stringify(opts.sdkStorage),
@@ -66,8 +68,9 @@ export function spawnHarness(opts) {
66
68
  sidecar.once('exit', (code) => resolve(`harness exited (${code}) before ready`));
67
69
  });
68
70
  const failure = await Promise.race([ipc.ready.then(() => null), died]);
69
- if (failure)
71
+ if (failure) {
70
72
  throw new Error(failure);
73
+ }
71
74
  return ipc;
72
75
  });
73
76
  }
@@ -1,7 +1,8 @@
1
- export type UserTmpOptions = {
2
- readonly readOwner?: () => {
3
- uid: number;
4
- username: string;
5
- };
6
- };
1
+ export interface TmpOwner {
2
+ uid: number;
3
+ username: string;
4
+ }
5
+ export interface UserTmpOptions {
6
+ readonly readOwner?: () => TmpOwner;
7
+ }
7
8
  export declare function userTmpPath(name: string, opts?: UserTmpOptions): string;
@@ -1,8 +1,8 @@
1
- export type ToolCallDisplay = {
1
+ export interface ToolCallDisplay {
2
2
  readonly label: string;
3
3
  readonly description?: string;
4
4
  readonly iconEmoji?: string;
5
- };
5
+ }
6
6
  export type ToolExecutionOutcome = {
7
7
  readonly kind: 'success';
8
8
  } | {
@@ -1,19 +1,25 @@
1
1
  import { canonicalDenyRoots, defaultRealpath, resolveKey } from './fs/scope.js';
2
2
  import { writeText } from './fs/write.js';
3
+ import { errorMessage } from '../guards.js';
3
4
  function documentRequests(raw) {
4
5
  if (!Array.isArray(raw) || raw.length === 0)
5
6
  return null;
6
7
  const documents = [];
7
8
  for (const entry of raw) {
8
- const record = entry;
9
- if (typeof record?.path !== 'string' || !record.path.trim())
9
+ if (!isDocumentRequest(entry))
10
10
  return null;
11
- if (typeof record.content !== 'string')
12
- return null;
13
- documents.push({ path: record.path, content: record.content });
11
+ documents.push({ path: entry.path, content: entry.content });
14
12
  }
15
13
  return documents;
16
14
  }
15
+ function isDocumentRequest(value) {
16
+ if (typeof value !== 'object' || value === null)
17
+ return false;
18
+ if (!('path' in value) || typeof value.path !== 'string' || !value.path.trim()) {
19
+ return false;
20
+ }
21
+ return 'content' in value && typeof value.content === 'string';
22
+ }
17
23
  export function createCreateDocumentsTool({ realpath = defaultRealpath, deniedRoots } = {}) {
18
24
  return {
19
25
  name: 'create_documents',
@@ -48,8 +54,9 @@ export function createCreateDocumentsTool({ realpath = defaultRealpath, deniedRo
48
54
  },
49
55
  resource(args) {
50
56
  const documents = documentRequests(args.documents);
51
- if (!documents)
57
+ if (!documents) {
52
58
  return null;
59
+ }
53
60
  const bytes = documents.reduce((sum, doc) => sum + doc.content.length, 0);
54
61
  const first = documents[0];
55
62
  const suffix = documents.length > 1 ? ` (+${documents.length - 1} more)` : '';
@@ -58,13 +65,14 @@ export function createCreateDocumentsTool({ realpath = defaultRealpath, deniedRo
58
65
  async execute(rawArgs, ctx) {
59
66
  const roots = ctx?.fsWriteRoots ?? [];
60
67
  const documents = documentRequests(rawArgs.documents);
61
- if (!documents)
68
+ if (!documents) {
62
69
  return { error: 'documents must be a non-empty array of { path, content }' };
70
+ }
63
71
  const created = [];
64
72
  const failed = [];
65
73
  const deny = await canonicalDenyRoots(deniedRoots, realpath);
66
74
  for (const doc of documents) {
67
- const resolved = await resolveKey(doc.path, roots, realpath, deny);
75
+ const resolved = await resolveKey(doc.path, roots, { realpath, deny });
68
76
  if ('error' in resolved) {
69
77
  failed.push({ path: doc.path, error: resolved.error });
70
78
  continue;
@@ -78,10 +86,10 @@ export function createCreateDocumentsTool({ realpath = defaultRealpath, deniedRo
78
86
  created.push(doc.path);
79
87
  }
80
88
  catch (err) {
81
- failed.push({ path: doc.path, error: err.message });
89
+ failed.push({ path: doc.path, error: errorMessage(err) });
82
90
  }
83
91
  }
84
- return { created, ...(failed.length ? { failed } : {}) };
92
+ return { created, failed: failed.length ? failed : undefined };
85
93
  }
86
94
  };
87
95
  }
@@ -1,28 +1,36 @@
1
1
  import b4a from 'b4a';
2
2
  import { canonicalDenyRoots, defaultRealpath, resolveKey } from './fs/scope.js';
3
3
  import { writeText } from './fs/write.js';
4
+ import { errorMessage } from '../guards.js';
4
5
  function editRequests(raw) {
5
6
  if (!Array.isArray(raw) || raw.length === 0)
6
7
  return null;
7
8
  const edits = [];
8
9
  for (const entry of raw) {
9
- const record = entry;
10
- if (typeof record?.path !== 'string' || !record.path.trim())
10
+ if (!isEditRequest(entry))
11
11
  return null;
12
- if (typeof record.search !== 'string' || record.search.length === 0)
13
- return null;
14
- if (typeof record.replace !== 'string')
15
- return null;
16
- edits.push({ path: record.path, search: record.search, replace: record.replace });
12
+ edits.push({ path: entry.path, search: entry.search, replace: entry.replace });
17
13
  }
18
14
  return edits;
19
15
  }
16
+ function isEditRequest(value) {
17
+ if (typeof value !== 'object' || value === null)
18
+ return false;
19
+ if (!('path' in value) || typeof value.path !== 'string' || !value.path.trim()) {
20
+ return false;
21
+ }
22
+ if (!('search' in value) || typeof value.search !== 'string' || !value.search) {
23
+ return false;
24
+ }
25
+ return 'replace' in value && typeof value.replace === 'string';
26
+ }
20
27
  function applyEdits(content, edits) {
21
28
  let next = content;
22
29
  for (const edit of edits) {
23
30
  const first = next.indexOf(edit.search);
24
- if (first === -1)
31
+ if (first === -1) {
25
32
  return { error: 'search text not found' };
33
+ }
26
34
  if (next.indexOf(edit.search, first + edit.search.length) !== -1) {
27
35
  return { error: 'search text is ambiguous (matches more than once)' };
28
36
  }
@@ -66,8 +74,9 @@ export function createEditDocumentsTool({ realpath = defaultRealpath, deniedRoot
66
74
  },
67
75
  resource(args) {
68
76
  const edits = editRequests(args.edits);
69
- if (!edits)
77
+ if (!edits) {
70
78
  return null;
79
+ }
71
80
  const paths = [...new Set(edits.map((edit) => edit.path))];
72
81
  const label = paths.slice(0, 3).join(', ');
73
82
  return {
@@ -78,8 +87,9 @@ export function createEditDocumentsTool({ realpath = defaultRealpath, deniedRoot
78
87
  async execute(rawArgs, ctx) {
79
88
  const roots = ctx?.fsWriteRoots ?? [];
80
89
  const edits = editRequests(rawArgs.edits);
81
- if (!edits)
90
+ if (!edits) {
82
91
  return { error: 'edits must be a non-empty array of { path, search, replace }' };
92
+ }
83
93
  const byPath = new Map();
84
94
  for (const edit of edits) {
85
95
  const list = byPath.get(edit.path) ?? [];
@@ -90,7 +100,7 @@ export function createEditDocumentsTool({ realpath = defaultRealpath, deniedRoot
90
100
  const failed = [];
91
101
  const deny = await canonicalDenyRoots(deniedRoots, realpath);
92
102
  for (const [file, fileEdits] of byPath) {
93
- const resolved = await resolveKey(file, roots, realpath, deny);
103
+ const resolved = await resolveKey(file, roots, { realpath, deny });
94
104
  if ('error' in resolved) {
95
105
  failed.push({ path: file, error: resolved.error });
96
106
  continue;
@@ -113,10 +123,10 @@ export function createEditDocumentsTool({ realpath = defaultRealpath, deniedRoot
113
123
  edited.push(file);
114
124
  }
115
125
  catch (err) {
116
- failed.push({ path: file, error: err.message });
126
+ failed.push({ path: file, error: errorMessage(err) });
117
127
  }
118
128
  }
119
- return { edited, ...(failed.length ? { failed } : {}) };
129
+ return { edited, failed: failed.length ? failed : undefined };
120
130
  }
121
131
  };
122
132
  }