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