@qvac/harness 0.4.0 → 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 (299) hide show
  1. package/CHANGELOG.md +689 -141
  2. package/README.md +29 -7
  3. package/dist/harness.bundle +71 -50
  4. package/dist-lib/index.d.ts +36 -39
  5. package/dist-lib/index.js +279 -114
  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 +11 -32
  29. package/dist-lib/lib/connect.js +152 -112
  30. package/dist-lib/lib/context-window.js +25 -21
  31. package/dist-lib/lib/contract-types.d.ts +22 -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 +13 -20
  84. package/dist-lib/lib/models/model-catalog.js +59 -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 +44 -10
  90. package/dist-lib/lib/models/model-pool.js +47 -42
  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 +110 -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 +14 -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 +3 -0
  288. package/dist-lib/spec/calls.js +1 -0
  289. package/dist-lib/spec/hrpc/hrpc.json +13 -0
  290. package/dist-lib/spec/hrpc/index.js +15 -3
  291. package/dist-lib/spec/hrpc/messages.js +39 -8
  292. package/dist-lib/spec/hyperschema/index.js +39 -8
  293. package/dist-lib/spec/hyperschema/schema.json +30 -1
  294. package/dist-lib/spec/hyperschema/types.d.ts +9 -1
  295. package/package.json +13 -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,10 +1,11 @@
1
1
  import process from 'process';
2
2
  import { contextCandidates } from '../context-window.js';
3
- import { errorCode, errorMessage } from '../error-fields.js';
3
+ import { errorCode, errorMessage, isRecord } from '../guards.js';
4
4
  import { bufferBurst } from '../helpers/buffer-burst.js';
5
5
  import { endsTurn, stopAtControlToken, stripControlTokens, truncateAtControlToken } from '../helpers/control-tokens.js';
6
6
  import { createMarkupStreamFilter } from '../helpers/markup-stream-filter.js';
7
7
  import { AbortedError, raceAbort } from '../helpers/race-abort.js';
8
+ import { raceTimeout } from '../helpers/race-timeout.js';
8
9
  import { createRunOutcomeTracker, degenerateAnswer, unnamedWrites, writeReport } from '../helpers/run-outcome.js';
9
10
  import { splitThinking } from '../helpers/thinking.js';
10
11
  import { clipRawCall, normalizeToolArgs, recoverMarkupToolCalls, recoverPrintedSkillLoad, recoverSkillNamedCall, stripToolCallMarkup, stripToolResponseBlocks, toolCallMarkup } from '../helpers/tool-call-markup.js';
@@ -16,7 +17,7 @@ import { resolveLlmGenerationParams } from '../models/llm-presets.js';
16
17
  import { customToolDialect } from '../models/model-catalog.js';
17
18
  import { isContextOverflowError, isSdkModelWedgedError } from '../models/sdk-faults.js';
18
19
  import { annotateLastUserTime, skillsPromptSection, systemPromptParts } from '../prompt.js';
19
- import { isMetadataMethod } from '../tools/mcp/utils.js';
20
+ import { isMetadataMethod } from '../tools/mcp/protocol.js';
20
21
  import { unquoteArg } from '../tools/model-args.js';
21
22
  import { SKILL_TOOL_NAME, WEB_SEARCH_TOOL_NAME } from './tool-names.js';
22
23
  import { mainFileRequested, skillFileArg, skillFileStem } from '../tools/skill.js';
@@ -33,14 +34,18 @@ export function imageSettingsFrom(agent) {
33
34
  if (!agent)
34
35
  return undefined;
35
36
  const settings = {};
36
- if (agent.imageModelChoice)
37
+ if (agent.imageModelChoice) {
37
38
  settings.modelChoice = agent.imageModelChoice;
38
- if (agent.imageSteps)
39
+ }
40
+ if (agent.imageSteps) {
39
41
  settings.steps = agent.imageSteps;
40
- if (agent.imageWidth)
42
+ }
43
+ if (agent.imageWidth) {
41
44
  settings.width = agent.imageWidth;
42
- if (agent.imageHeight)
45
+ }
46
+ if (agent.imageHeight) {
43
47
  settings.height = agent.imageHeight;
48
+ }
44
49
  return Object.keys(settings).length ? settings : undefined;
45
50
  }
46
51
  const BURST_WINDOW_MS = 100;
@@ -62,10 +67,12 @@ export function trustedSourcesFrom(history) {
62
67
  const sources = [];
63
68
  for (const entry of history) {
64
69
  const text = String(entry.content ?? '');
65
- if (entry.role === 'user')
70
+ if (entry.role === 'user') {
66
71
  sources.push({ origin: 'user', text });
67
- else if (entry.role === 'tool')
72
+ }
73
+ else if (entry.role === 'tool') {
68
74
  sources.push({ origin: originOfToolTurn(text), text });
75
+ }
69
76
  }
70
77
  return sources;
71
78
  }
@@ -102,16 +109,18 @@ function sanitizeTurn(turn) {
102
109
  function lastUserContent(messages) {
103
110
  for (let i = messages.length - 1; i >= 0; i--) {
104
111
  const turn = messages[i];
105
- if (turn?.role === 'user')
112
+ if (turn?.role === 'user') {
106
113
  return turn.content;
114
+ }
107
115
  }
108
116
  return '';
109
117
  }
110
118
  function lastUserImages(messages) {
111
119
  for (let i = messages.length - 1; i >= 0; i--) {
112
120
  const turn = messages[i];
113
- if (turn?.role === 'user')
121
+ if (turn?.role === 'user') {
114
122
  return turn.attachments ?? [];
123
+ }
115
124
  }
116
125
  return [];
117
126
  }
@@ -149,8 +158,9 @@ function assistantToolTurn(contentText, calls) {
149
158
  function preloadLastUser(messages, name, body) {
150
159
  for (let i = messages.length - 1; i >= 0; i--) {
151
160
  const turn = messages[i];
152
- if (turn?.role !== 'user')
161
+ if (turn?.role !== 'user') {
153
162
  continue;
163
+ }
154
164
  const next = [...messages];
155
165
  next[i] = { ...turn, content: skillPreloadMessage(turn.content, name, body) };
156
166
  return next;
@@ -159,10 +169,12 @@ function preloadLastUser(messages, name, body) {
159
169
  }
160
170
  function collectStats(state, stats, stopReason) {
161
171
  state.stopReason = stopReason;
162
- if (!stats)
172
+ if (!stats) {
163
173
  return;
164
- if (state.ttft === undefined && stats.timeToFirstToken)
174
+ }
175
+ if (state.ttft === undefined && stats.timeToFirstToken) {
165
176
  state.ttft = stats.timeToFirstToken;
177
+ }
166
178
  if (state.promptTokensPerSecond === undefined && stats.promptTokensPerSecond) {
167
179
  state.promptTokensPerSecond = stats.promptTokensPerSecond;
168
180
  }
@@ -173,10 +185,12 @@ function collectStats(state, stats, stopReason) {
173
185
  state.timedTokens += stats.generatedTokens;
174
186
  }
175
187
  }
176
- if (stats.promptTokens)
188
+ if (stats.promptTokens) {
177
189
  state.promptTokens = stats.promptTokens;
178
- if (stats.cacheTokens)
190
+ }
191
+ if (stats.cacheTokens) {
179
192
  state.cacheTokens = stats.cacheTokens;
193
+ }
180
194
  }
181
195
  function metricsEvent(state, usage) {
182
196
  if (!state.tokens)
@@ -190,10 +204,10 @@ function metricsEvent(state, usage) {
190
204
  tokensPerSecond: state.durationMs > 0 ? (state.timedTokens / state.durationMs) * 1000 : undefined,
191
205
  promptTokens: state.promptTokens,
192
206
  cacheTokens: state.cacheTokens,
193
- ...(state.ctxSize ? { ctxSize: state.ctxSize } : {}),
194
- ...(state.backends.length ? { backends: state.backends } : {}),
195
- ...(usage ? { usage } : {}),
196
- ...(state.stopReason ? { stopReason: state.stopReason } : {})
207
+ ctxSize: state.ctxSize || undefined,
208
+ backends: state.backends.length ? state.backends : undefined,
209
+ usage: usage ?? undefined,
210
+ stopReason: state.stopReason || undefined
197
211
  };
198
212
  }
199
213
  const TOOL_DENIED_MESSAGE = 'The user declined this call. Do not retry it or call it with different arguments to get ' +
@@ -215,16 +229,17 @@ const RETRYABLE_CODE = new Set(['timeout', 'transport', 'busy']);
215
229
  function callSucceeded(result, outcome) {
216
230
  if (outcome.kind !== 'success')
217
231
  return false;
218
- const exitCode = result?.exitCode;
232
+ const exitCode = isRecord(result) ? result.exitCode : undefined;
219
233
  return typeof exitCode !== 'number' || exitCode === 0;
220
234
  }
221
235
  function retryHints(result) {
222
236
  if (!result || typeof result !== 'object' || Array.isArray(result))
223
237
  return {};
224
- const record = result;
238
+ const status = 'status' in result ? result.status : undefined;
239
+ const code = 'code' in result ? result.code : undefined;
225
240
  return {
226
- ...(typeof record.status === 'number' ? { status: record.status } : {}),
227
- ...(typeof record.code === 'string' ? { code: record.code } : {})
241
+ status: typeof status === 'number' ? status : undefined,
242
+ code: typeof code === 'string' ? code : undefined
228
243
  };
229
244
  }
230
245
  function settledFailure(result, outcome) {
@@ -233,8 +248,9 @@ function settledFailure(result, outcome) {
233
248
  if (outcome.kind === 'error' && outcome.error !== undefined)
234
249
  return false;
235
250
  const { code, status } = retryHints(result);
236
- if (code !== undefined && RETRYABLE_CODE.has(code))
251
+ if (code !== undefined && RETRYABLE_CODE.has(code)) {
237
252
  return false;
253
+ }
238
254
  return status === undefined || (status < 500 && !RETRYABLE_STATUS.has(status));
239
255
  }
240
256
  const MAX_BLOCKED_REPEATS = 3;
@@ -257,13 +273,14 @@ function deniedCall() {
257
273
  executed: false
258
274
  });
259
275
  }
260
- async function executeCall(tool, args, ctx, { approve, signal, denied, sources = [], effect: classified, skipApprovals, onToolActivity, intent, display }) {
276
+ async function executeCall(tool, args, { ctx, approve, signal, denied, sources = [], effect: classified, skipApprovals, onToolActivity, intent, display }) {
261
277
  const validationError = tool.validate?.(args, ctx);
262
278
  if (validationError) {
263
279
  hlog('tool rejected: arguments failed validation', { tool: tool.name });
264
280
  return guidanceResult(validationError);
265
281
  }
266
- const refusal = guardToolCall(tool.name, args, sources, {
282
+ const refusal = guardToolCall(tool.name, args, {
283
+ sources,
267
284
  externalWrite: tool.externalWrite === true
268
285
  });
269
286
  if (refusal) {
@@ -304,13 +321,11 @@ async function executeCall(tool, args, ctx, { approve, signal, denied, sources =
304
321
  }), signal)
305
322
  : false);
306
323
  hlog(`tool ${approved ? 'approved' : 'denied'}`, { tool: tool.name });
307
- const decision = tool.autoApprove === true || effect === 'read'
308
- ? 'auto'
309
- : skipApprovals === true
310
- ? 'skip-all'
311
- : approved
312
- ? 'allowed'
313
- : 'denied';
324
+ const decision = toolDecision({
325
+ auto: tool.autoApprove === true || effect === 'read',
326
+ skipAll: skipApprovals === true,
327
+ approved
328
+ });
314
329
  onToolActivity?.({
315
330
  type: 'tool-activity',
316
331
  name: tool.name,
@@ -343,15 +358,15 @@ function runToolContext(scope) {
343
358
  return (turnImages = [], ctxSize) => ({
344
359
  agentId: agent?.id ?? null,
345
360
  ctxSize,
346
- ...(image ? { image } : {}),
347
- ...(turnImages.length ? { turnImages } : {}),
348
- ...(attach ? { attach } : {}),
349
- ...(fetchAttachment ? { fetchAttachment } : {}),
350
- ...(ragSearch ? { ragSearch } : {}),
351
- ...(progress ? { progress } : {}),
352
- ...(askUser ? { askUser } : {}),
353
- ...(signal ? { signal } : {}),
354
- ...(runBudget ? { runBudget } : {})
361
+ image,
362
+ turnImages: turnImages.length ? turnImages : undefined,
363
+ attach,
364
+ fetchAttachment,
365
+ ragSearch,
366
+ progress,
367
+ askUser,
368
+ signal,
369
+ runBudget
355
370
  });
356
371
  }
357
372
  function labelOf(name, args, emoji) {
@@ -394,24 +409,21 @@ function blockedSkillMessage(name, catalog, listShown) {
394
409
  `skill is available this turn — do not guess another name, and answer from what you ` +
395
410
  `already have.`;
396
411
  }
397
- async function resolveToolCalls(toolCalls, enabled, agentId, pipeline, skillNames, { sandboxMode, signal }) {
412
+ async function resolveToolCalls(toolCalls, { enabled, agentId, pipeline, skillNames, sandboxMode, signal }) {
398
413
  const resolved = [];
399
414
  for (const call of toolCalls) {
400
415
  if (signal?.aborted)
401
416
  throw new AbortedError();
402
417
  const tool = enabled.find((t) => t.name === call.name);
418
+ const rawCall = call.raw === undefined ? undefined : clipRawCall(call.raw);
403
419
  if (!tool) {
404
420
  const { args } = takeToolIntent(call.arguments);
405
- resolved.push({
406
- call,
407
- args,
408
- ...(call.raw === undefined ? {} : { rawCall: clipRawCall(call.raw) })
409
- });
421
+ resolved.push({ call, args, rawCall });
410
422
  continue;
411
423
  }
412
424
  const taken = takeToolIntent(call.arguments);
413
425
  const args = normalizeToolArgs(taken.args, Object.keys(tool.parameters.properties));
414
- const { intent } = taken;
426
+ const intent = taken.intent || undefined;
415
427
  const fold = await pipeline.runPreToolFold({
416
428
  toolName: tool.name,
417
429
  args,
@@ -422,37 +434,27 @@ async function resolveToolCalls(toolCalls, enabled, agentId, pipeline, skillName
422
434
  signal
423
435
  });
424
436
  resolved.push(fold.ok
425
- ? {
426
- call,
427
- tool,
428
- args: fold.args,
429
- ...(call.raw === undefined ? {} : { rawCall: clipRawCall(call.raw) }),
430
- ...(intent ? { intent } : {})
431
- }
432
- : {
433
- call,
434
- tool,
435
- args,
436
- rejection: fold.rejection,
437
- ...(call.raw === undefined ? {} : { rawCall: clipRawCall(call.raw) }),
438
- ...(intent ? { intent } : {})
439
- });
437
+ ? { call, tool, args: fold.args, rawCall, intent }
438
+ : { call, tool, args, rejection: fold.rejection, rawCall, intent });
440
439
  }
441
440
  return resolved;
442
441
  }
443
442
  function unprovenReplay(state, final) {
444
443
  if (!state.unproven)
445
444
  return null;
446
- if (state.stopToken)
445
+ if (state.stopToken) {
447
446
  return endsTurn(state.stopToken) ? state.unproven : null;
448
- if (final.stopReason === 'length' || final.stopReason === 'cancelled')
447
+ }
448
+ if (final.stopReason === 'length' || final.stopReason === 'cancelled') {
449
449
  return null;
450
+ }
450
451
  return state.unproven;
451
452
  }
452
- async function* runRoundTokens(sdk, result, state, signal, startInThink = false) {
453
+ async function* runRoundTokens(result, state, { sdk, signal, startInThink = false }) {
453
454
  const cancelThisRound = () => {
454
- if (result.requestId)
455
+ if (result.requestId) {
455
456
  sdk.cancel?.({ requestId: result.requestId });
457
+ }
456
458
  };
457
459
  signal?.addEventListener('abort', cancelThisRound, { once: true });
458
460
  const tokens = stopAtControlToken(result.tokenStream, (token) => {
@@ -467,27 +469,33 @@ async function* runRoundTokens(sdk, result, state, signal, startInThink = false)
467
469
  const next = await events.next();
468
470
  if (next.done) {
469
471
  state.unproven = next.value;
470
- const thinkingTail = thinkingFilter.flush();
471
- if (thinkingTail)
472
- yield { type: 'thinking', text: thinkingTail };
473
- const contentTail = contentFilter.flush();
474
- if (contentTail)
475
- yield { type: 'content', text: contentTail };
476
472
  break;
477
473
  }
478
- if (signal?.aborted)
474
+ if (signal?.aborted) {
479
475
  throw new AbortedError();
476
+ }
480
477
  tokenEvents++;
481
- if (next.value.type === 'content')
478
+ if (next.value.type === 'content') {
482
479
  state.rawContentText += next.value.text;
480
+ }
483
481
  const filter = next.value.type === 'thinking' ? thinkingFilter : contentFilter;
484
482
  const text = filter.push(next.value.text);
485
- if (text)
483
+ if (text) {
486
484
  yield { type: next.value.type, text };
485
+ }
486
+ }
487
+ const thinkingTail = thinkingFilter.flush();
488
+ if (thinkingTail) {
489
+ yield { type: 'thinking', text: thinkingTail };
490
+ }
491
+ const contentTail = contentFilter.flush();
492
+ if (contentTail) {
493
+ yield { type: 'content', text: contentTail };
487
494
  }
488
495
  hlog('stream ended', { events: tokenEvents, stopToken: state.stopToken });
489
- if (state.stopToken)
496
+ if (state.stopToken) {
490
497
  cancelThisRound();
498
+ }
491
499
  }
492
500
  finally {
493
501
  signal?.removeEventListener('abort', cancelThisRound);
@@ -512,17 +520,13 @@ function roundTokens(stats) {
512
520
  const TRUNCATED_FINAL_MS = 2000;
513
521
  function truncatedFinal(final, contentText) {
514
522
  const accepted = { contentText, toolCalls: [], raw: { fullText: contentText } };
515
- let timer;
516
- const expired = new Promise((resolve) => {
517
- timer = setTimeout(() => resolve(accepted), TRUNCATED_FINAL_MS);
518
- });
519
523
  const guarded = final.catch((err) => {
520
524
  if (isSdkModelWedgedError(err))
521
525
  throw err;
522
526
  return accepted;
523
527
  });
524
528
  guarded.catch(noop);
525
- return Promise.race([guarded, expired]).finally(() => clearTimeout(timer));
529
+ return raceTimeout(guarded, { ms: TRUNCATED_FINAL_MS, fallback: accepted });
526
530
  }
527
531
  async function* runRecoveredRound(sdk, args) {
528
532
  const lease = args.acquireRound ? await args.acquireRound() : null;
@@ -573,9 +577,12 @@ async function* runRecoveredRoundWith(sdk, args, modelId) {
573
577
  const result = sdk.completion(params);
574
578
  result.final.catch(noop);
575
579
  const startInThink = args.generationParams?.reasoning_budget === -1;
576
- for await (const event of runRoundTokens(sdk, result, state, args.signal, startInThink)) {
577
- if (event.type === 'content')
578
- contentText += event.text;
580
+ for await (const event of runRoundTokens(result, state, {
581
+ sdk,
582
+ signal: args.signal,
583
+ startInThink
584
+ })) {
585
+ contentText += event.type === 'content' ? event.text : '';
579
586
  emitted = true;
580
587
  yield event;
581
588
  }
@@ -618,24 +625,28 @@ async function* runRecoveredRoundWith(sdk, args, modelId) {
618
625
  messages: undefined,
619
626
  tokens: undefined,
620
627
  outcome: isContextOverflowError(err) ? 'overflowed' : 'failed',
621
- reason: err instanceof Error ? err.message : String(err)
628
+ reason: errorMessage(err)
622
629
  };
623
630
  }
624
- if (!isContextOverflowError(err))
631
+ if (!isContextOverflowError(err)) {
625
632
  throw err;
626
- if (emitted)
633
+ }
634
+ if (emitted) {
627
635
  throw new Error(CONTEXT_OVERFLOW_MESSAGE, { cause: err });
636
+ }
628
637
  lastOverflow = err;
629
638
  }
630
639
  }
631
- if (lastOverflow)
640
+ if (lastOverflow) {
632
641
  throw new Error(CONTEXT_OVERFLOW_MESSAGE, { cause: lastOverflow });
642
+ }
633
643
  throw new Error('context recovery produced no completion candidate');
634
644
  }
635
645
  function* reportUnnamedWrites(said, landed) {
636
646
  const unreported = writeReport(unnamedWrites(said, landed));
637
- if (unreported)
647
+ if (unreported) {
638
648
  yield { type: 'content', text: unreported };
649
+ }
639
650
  }
640
651
  export async function* completion(sdk, { modelId, acquireRound, model, agent, ctxSize, cacheKey, thinkingDefault, generationParams: generationParamsOverride, responseFormat, messages: storedMessages, tools: allTools = [], skillCatalog = [], approve, attach, fetchAttachment, ragSearch, progress, askUser, signal, toolConfigs, fsRoots = [], fsWriteRoots = [], captureInferenceCalls = false }) {
641
652
  const pipeline = new HookPipeline();
@@ -654,7 +665,7 @@ export async function* completion(sdk, { modelId, acquireRound, model, agent, ct
654
665
  let runError;
655
666
  const runOutcomes = createRunOutcomeTracker();
656
667
  const landed = runOutcomes.confirmedWrites;
657
- try {
668
+ async function* runTurn() {
658
669
  if (signal?.aborted)
659
670
  throw new AbortedError();
660
671
  await pipeline.onRunStart({ agentId, messages });
@@ -702,8 +713,8 @@ export async function* completion(sdk, { modelId, acquireRound, model, agent, ct
702
713
  hasActiveSkills: activeSkills.length > 0
703
714
  });
704
715
  }
705
- const reasoningEnabled = agent?.reasoning === 'enabled' ? true : agent?.reasoning === 'disabled' ? false : undefined;
706
- const deviceReasoning = thinkingDefault === 'enabled' ? true : thinkingDefault === 'disabled' ? false : undefined;
716
+ const reasoningEnabled = reasoningFlag(agent?.reasoning);
717
+ const deviceReasoning = reasoningFlag(thinkingDefault);
707
718
  const resolvedGenerationParams = resolveLlmGenerationParams(model, reasoningEnabled, deviceReasoning);
708
719
  const generationParams = generationParamsOverride
709
720
  ? { ...resolvedGenerationParams, ...generationParamsOverride }
@@ -714,6 +725,24 @@ export async function* completion(sdk, { modelId, acquireRound, model, agent, ct
714
725
  let skillIcon = null;
715
726
  const skillEmoji = (name) => skillCatalog.find((entry) => entry.name === name)?.emoji ?? null;
716
727
  const nudgedSkills = new Set();
728
+ function recordSkillLoad(skillName, output, { mainRequested, askedFile }) {
729
+ if (!output || typeof output !== 'object' || !('content' in output)) {
730
+ return;
731
+ }
732
+ loadedSkills.add(skillName);
733
+ if ('file' in output && typeof output.file === 'string') {
734
+ const files = loadedSkillFiles.get(skillName) ?? new Set();
735
+ files.add(output.file);
736
+ loadedSkillFiles.set(skillName, files);
737
+ }
738
+ if (mainRequested) {
739
+ loadedThisTurn.add(skillName);
740
+ }
741
+ else {
742
+ loadedFilesThisTurn.add(fileKey(skillName, unquoteArg(askedFile)));
743
+ }
744
+ skillIcon = skillEmoji(skillName) ?? skillIcon;
745
+ }
717
746
  const toolDialect = model ? customToolDialect(model) : undefined;
718
747
  let turns = messages;
719
748
  let preloadedSkill = null;
@@ -802,8 +831,9 @@ export async function* completion(sdk, { modelId, acquireRound, model, agent, ct
802
831
  let blockedRepeats = 0;
803
832
  let webSearches = 0;
804
833
  const sources = trustedSourcesFrom(messages);
805
- if (preloadedSkill)
834
+ if (preloadedSkill) {
806
835
  sources.push({ origin: 'skill', text: preloadedSkill });
836
+ }
807
837
  const query = explicitWebSearchQuery(lastUserContent(messages), enabled, {
808
838
  skillPreloaded: preloadedSkill !== null
809
839
  });
@@ -820,8 +850,9 @@ export async function* completion(sdk, { modelId, acquireRound, model, agent, ct
820
850
  const searchEffect = webSearch.effect(args);
821
851
  const searchResource = webSearch.resource?.(args) ?? null;
822
852
  const searchCallDisplay = labelOf(webSearch.name, args, null);
823
- if (signal?.aborted)
853
+ if (signal?.aborted) {
824
854
  throw new AbortedError();
855
+ }
825
856
  yield {
826
857
  type: 'tool-call',
827
858
  name: webSearch.name,
@@ -831,7 +862,8 @@ export async function* completion(sdk, { modelId, acquireRound, model, agent, ct
831
862
  };
832
863
  let activity = null;
833
864
  const execution = fold.ok
834
- ? await executeCall(webSearch, args, toolContext(turnImages, ctxSize), {
865
+ ? await executeCall(webSearch, args, {
866
+ ctx: toolContext(turnImages, ctxSize),
835
867
  approve,
836
868
  signal,
837
869
  denied,
@@ -854,8 +886,9 @@ export async function* completion(sdk, { modelId, acquireRound, model, agent, ct
854
886
  const executionOutcome = settled.outcome;
855
887
  runOutcomes.record(output, executionOutcome);
856
888
  runOutcomes.finishRound();
857
- if (fold.ok)
889
+ if (fold.ok) {
858
890
  webSearches++;
891
+ }
859
892
  if (!activity && !fold.ok) {
860
893
  activity = {
861
894
  type: 'tool-activity',
@@ -866,8 +899,9 @@ export async function* completion(sdk, { modelId, acquireRound, model, agent, ct
866
899
  decision: 'blocked'
867
900
  };
868
901
  }
869
- if (activity)
902
+ if (activity) {
870
903
  yield activity;
904
+ }
871
905
  yield {
872
906
  type: 'tool-result',
873
907
  name: webSearch.name,
@@ -920,11 +954,12 @@ export async function* completion(sdk, { modelId, acquireRound, model, agent, ct
920
954
  });
921
955
  roundsSent = round + 1;
922
956
  const final = recovered.final;
923
- if (!recovered.cacheable)
957
+ if (!recovered.cacheable) {
924
958
  roundCacheKey = undefined;
959
+ }
925
960
  history = recovered.history;
926
961
  collectStats(stats, final.stats, final.stopReason);
927
- hlog('round stats', { round, wallMs: Date.now() - roundStarted, ...(final.stats ?? {}) });
962
+ hlog('round stats', { round, wallMs: Date.now() - roundStarted, ...final.stats });
928
963
  const markup = stripToolResponseBlocks(truncateAtControlToken(final.raw.fullText) || recovered.unproven || '');
929
964
  let toolCalls = final.toolCalls?.length
930
965
  ? final.toolCalls
@@ -935,24 +970,29 @@ export async function* completion(sdk, { modelId, acquireRound, model, agent, ct
935
970
  const alreadyLoaded = rescuedArgs !== undefined &&
936
971
  (loadedSkillFiles.get(unquoteArg(rescuedArgs.name))?.has(unquoteArg(rescuedArgs.file)) ??
937
972
  false);
938
- if (!alreadyLoaded)
973
+ if (!alreadyLoaded) {
939
974
  toolCalls = rescued;
940
- if (toolCalls.length)
975
+ }
976
+ if (toolCalls.length) {
941
977
  hlog('recovered printed skill load', { round });
978
+ }
942
979
  }
943
980
  if (!toolCalls.length && enabled.some((tool) => tool.name === SKILL_TOOL_NAME)) {
944
981
  toolCalls = recoverSkillNamedCall(markup, inContextSkills
945
982
  .filter((skill) => !nudgedSkills.has(skill.name))
946
983
  .map((skill) => skill.name), SKILL_TOOL_NAME);
947
- for (const call of toolCalls)
984
+ for (const call of toolCalls) {
948
985
  nudgedSkills.add(unquoteArg(call.arguments.name));
949
- if (toolCalls.length)
986
+ }
987
+ if (toolCalls.length) {
950
988
  hlog('recovered skill-named call', { round });
989
+ }
951
990
  }
952
991
  if (!toolCalls.length) {
953
992
  const replay = recovered.replay ? stripToolCallMarkup(recovered.replay) : '';
954
- if (replay)
993
+ if (replay) {
955
994
  yield { type: 'content', text: replay };
995
+ }
956
996
  const spoken = recovered.contentText.trim();
957
997
  if (enabled.length && !narrated && !replay && NARRATED_INTENT.test(spoken)) {
958
998
  hlog('round narrated a tool call instead of making it', { round });
@@ -970,16 +1010,25 @@ export async function* completion(sdk, { modelId, acquireRound, model, agent, ct
970
1010
  break;
971
1011
  }
972
1012
  hlog('round produced no answer', { round, mute });
973
- if (mute++ < MAX_MUTE_ROUNDS)
1013
+ if (mute++ < MAX_MUTE_ROUNDS) {
974
1014
  continue;
1015
+ }
975
1016
  outcome = 'mute';
976
1017
  break;
977
1018
  }
978
1019
  mute = 0;
979
1020
  narrated = false;
980
- const resolved = await resolveToolCalls(toolCalls, enabled, agent?.id ?? null, pipeline, inContextSkills.map((skill) => skill.name), { sandboxMode: agent?.sandboxMode === true, signal });
981
- if (signal?.aborted)
1021
+ const resolved = await resolveToolCalls(toolCalls, {
1022
+ enabled,
1023
+ agentId: agent?.id ?? null,
1024
+ pipeline,
1025
+ skillNames: inContextSkills.map((skill) => skill.name),
1026
+ sandboxMode: agent?.sandboxMode === true,
1027
+ signal
1028
+ });
1029
+ if (signal?.aborted) {
982
1030
  throw new AbortedError();
1031
+ }
983
1032
  history = [
984
1033
  ...history,
985
1034
  {
@@ -1062,7 +1111,8 @@ export async function* completion(sdk, { modelId, acquireRound, model, agent, ct
1062
1111
  });
1063
1112
  }
1064
1113
  else if (tool) {
1065
- execution = await executeCall(tool, args, callCtx, {
1114
+ execution = await executeCall(tool, args, {
1115
+ ctx: callCtx,
1066
1116
  approve,
1067
1117
  signal,
1068
1118
  denied,
@@ -1103,21 +1153,8 @@ export async function* completion(sdk, { modelId, acquireRound, model, agent, ct
1103
1153
  else if (tool?.name === WEB_SEARCH_TOOL_NAME && !rejection && !priorFailure) {
1104
1154
  webSearches++;
1105
1155
  }
1106
- if (call.name === SKILL_TOOL_NAME &&
1107
- output &&
1108
- typeof output === 'object' &&
1109
- 'content' in output) {
1110
- loadedSkills.add(skillName);
1111
- if ('file' in output && typeof output.file === 'string') {
1112
- const files = loadedSkillFiles.get(skillName) ?? new Set();
1113
- files.add(output.file);
1114
- loadedSkillFiles.set(skillName, files);
1115
- }
1116
- if (mainRequested)
1117
- loadedThisTurn.add(skillName);
1118
- else
1119
- loadedFilesThisTurn.add(fileKey(skillName, unquoteArg(askedFile)));
1120
- skillIcon = skillEmoji(skillName) ?? skillIcon;
1156
+ if (call.name === SKILL_TOOL_NAME) {
1157
+ recordSkillLoad(skillName, output, { mainRequested, askedFile });
1121
1158
  }
1122
1159
  if (!activity &&
1123
1160
  !silent &&
@@ -1131,8 +1168,9 @@ export async function* completion(sdk, { modelId, acquireRound, model, agent, ct
1131
1168
  decision: 'blocked'
1132
1169
  };
1133
1170
  }
1134
- if (activity)
1171
+ if (activity) {
1135
1172
  yield activity;
1173
+ }
1136
1174
  if (!silent) {
1137
1175
  yield {
1138
1176
  type: 'tool-result',
@@ -1160,8 +1198,9 @@ export async function* completion(sdk, { modelId, acquireRound, model, agent, ct
1160
1198
  blockedRepeats = 0;
1161
1199
  }
1162
1200
  history.push({ role: 'user', content: TOOL_CONTINUATION_NUDGE });
1163
- if (blockedRepeats >= MAX_BLOCKED_REPEATS)
1201
+ if (blockedRepeats >= MAX_BLOCKED_REPEATS) {
1164
1202
  break;
1203
+ }
1165
1204
  if (cappedCalls === resolved.length) {
1166
1205
  if (cappedRounds++ >= MAX_CAPPED_ROUNDS) {
1167
1206
  outcome = 'search-capped';
@@ -1176,12 +1215,14 @@ export async function* completion(sdk, { modelId, acquireRound, model, agent, ct
1176
1215
  yield* reportUnnamedWrites(said, landed);
1177
1216
  const answered = metricsEvent(stats, runUsage());
1178
1217
  runOutcome = 'done';
1179
- if (answered)
1218
+ if (answered) {
1180
1219
  yield answered;
1220
+ }
1181
1221
  return;
1182
1222
  }
1183
- if (signal?.aborted)
1223
+ if (signal?.aborted) {
1184
1224
  throw new AbortedError();
1225
+ }
1185
1226
  const recovered = yield* runRecoveredRound(sdk, {
1186
1227
  modelId,
1187
1228
  acquireRound,
@@ -1196,8 +1237,9 @@ export async function* completion(sdk, { modelId, acquireRound, model, agent, ct
1196
1237
  });
1197
1238
  collectStats(stats, recovered.final.stats, recovered.final.stopReason);
1198
1239
  const answer = recovered.replay ? stripToolCallMarkup(recovered.replay) : '';
1199
- if (answer)
1240
+ if (answer) {
1200
1241
  yield { type: 'content', text: answer };
1242
+ }
1201
1243
  said = `${recovered.contentText}\n${answer}`;
1202
1244
  if (!said.trim()) {
1203
1245
  hlog('degenerate wrap-up', { outcome });
@@ -1211,13 +1253,18 @@ export async function* completion(sdk, { modelId, acquireRound, model, agent, ct
1211
1253
  }
1212
1254
  const metrics = metricsEvent(stats, runUsage());
1213
1255
  runOutcome = 'done';
1214
- if (metrics)
1256
+ if (metrics) {
1215
1257
  yield metrics;
1258
+ }
1259
+ }
1260
+ try {
1261
+ yield* runTurn();
1216
1262
  }
1217
1263
  catch (err) {
1218
1264
  const written = writeReport(landed);
1219
- if (written)
1265
+ if (written) {
1220
1266
  yield { type: 'content', text: written };
1267
+ }
1221
1268
  if (err instanceof AbortedError) {
1222
1269
  runOutcome = 'aborted';
1223
1270
  yield { type: 'aborted' };
@@ -1240,3 +1287,17 @@ export async function* completion(sdk, { modelId, acquireRound, model, agent, ct
1240
1287
  });
1241
1288
  }
1242
1289
  }
1290
+ function toolDecision({ auto, skipAll, approved }) {
1291
+ if (auto)
1292
+ return 'auto';
1293
+ if (skipAll)
1294
+ return 'skip-all';
1295
+ return approved ? 'allowed' : 'denied';
1296
+ }
1297
+ function reasoningFlag(setting) {
1298
+ if (setting === 'enabled')
1299
+ return true;
1300
+ if (setting === 'disabled')
1301
+ return false;
1302
+ return undefined;
1303
+ }