@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,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,10 +725,27 @@ 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;
720
- let stubDropped = false;
721
749
  const preload = mention ?? resolveImplicitSkill(lastUserContent(messages), activeSkills);
722
750
  if (preload) {
723
751
  const loader = tools.find((tool) => tool.name === SKILL_TOOL_NAME);
@@ -726,7 +754,6 @@ export async function* completion(sdk, { modelId, acquireRound, model, agent, ct
726
754
  preloadedSkill = String(loaded.content);
727
755
  const inlined = preloadLastUser(messages, preload.name, preloadedSkill);
728
756
  turns = withoutSkillLoadTurns(inlined, preload.name);
729
- stubDropped = turns.length < inlined.length;
730
757
  if (!loadedSkills.has(preload.name)) {
731
758
  const preloadArgs = { name: preload.name };
732
759
  const preloadEffect = 'read';
@@ -797,15 +824,16 @@ export async function* completion(sdk, { modelId, acquireRound, model, agent, ct
797
824
  }
798
825
  logSelection();
799
826
  let history = annotateLastUserTime(systemContent ? [{ role: 'system', content: systemContent }, ...turns] : turns);
800
- let roundCacheKey = stubDropped ? undefined : cacheKey;
827
+ let roundCacheKey = preloadedSkill === null ? cacheKey : undefined;
801
828
  const runUsage = () => usageBreakdown(promptParts, stats.cacheTokens, model);
802
829
  const denied = new Set();
803
830
  const failed = new Map();
804
831
  let blockedRepeats = 0;
805
832
  let webSearches = 0;
806
833
  const sources = trustedSourcesFrom(messages);
807
- if (preloadedSkill)
834
+ if (preloadedSkill) {
808
835
  sources.push({ origin: 'skill', text: preloadedSkill });
836
+ }
809
837
  const query = explicitWebSearchQuery(lastUserContent(messages), enabled, {
810
838
  skillPreloaded: preloadedSkill !== null
811
839
  });
@@ -822,8 +850,9 @@ export async function* completion(sdk, { modelId, acquireRound, model, agent, ct
822
850
  const searchEffect = webSearch.effect(args);
823
851
  const searchResource = webSearch.resource?.(args) ?? null;
824
852
  const searchCallDisplay = labelOf(webSearch.name, args, null);
825
- if (signal?.aborted)
853
+ if (signal?.aborted) {
826
854
  throw new AbortedError();
855
+ }
827
856
  yield {
828
857
  type: 'tool-call',
829
858
  name: webSearch.name,
@@ -833,7 +862,8 @@ export async function* completion(sdk, { modelId, acquireRound, model, agent, ct
833
862
  };
834
863
  let activity = null;
835
864
  const execution = fold.ok
836
- ? await executeCall(webSearch, args, toolContext(turnImages, ctxSize), {
865
+ ? await executeCall(webSearch, args, {
866
+ ctx: toolContext(turnImages, ctxSize),
837
867
  approve,
838
868
  signal,
839
869
  denied,
@@ -856,8 +886,9 @@ export async function* completion(sdk, { modelId, acquireRound, model, agent, ct
856
886
  const executionOutcome = settled.outcome;
857
887
  runOutcomes.record(output, executionOutcome);
858
888
  runOutcomes.finishRound();
859
- if (fold.ok)
889
+ if (fold.ok) {
860
890
  webSearches++;
891
+ }
861
892
  if (!activity && !fold.ok) {
862
893
  activity = {
863
894
  type: 'tool-activity',
@@ -868,8 +899,9 @@ export async function* completion(sdk, { modelId, acquireRound, model, agent, ct
868
899
  decision: 'blocked'
869
900
  };
870
901
  }
871
- if (activity)
902
+ if (activity) {
872
903
  yield activity;
904
+ }
873
905
  yield {
874
906
  type: 'tool-result',
875
907
  name: webSearch.name,
@@ -922,11 +954,12 @@ export async function* completion(sdk, { modelId, acquireRound, model, agent, ct
922
954
  });
923
955
  roundsSent = round + 1;
924
956
  const final = recovered.final;
925
- if (!recovered.cacheable)
957
+ if (!recovered.cacheable) {
926
958
  roundCacheKey = undefined;
959
+ }
927
960
  history = recovered.history;
928
961
  collectStats(stats, final.stats, final.stopReason);
929
- hlog('round stats', { round, wallMs: Date.now() - roundStarted, ...(final.stats ?? {}) });
962
+ hlog('round stats', { round, wallMs: Date.now() - roundStarted, ...final.stats });
930
963
  const markup = stripToolResponseBlocks(truncateAtControlToken(final.raw.fullText) || recovered.unproven || '');
931
964
  let toolCalls = final.toolCalls?.length
932
965
  ? final.toolCalls
@@ -937,24 +970,29 @@ export async function* completion(sdk, { modelId, acquireRound, model, agent, ct
937
970
  const alreadyLoaded = rescuedArgs !== undefined &&
938
971
  (loadedSkillFiles.get(unquoteArg(rescuedArgs.name))?.has(unquoteArg(rescuedArgs.file)) ??
939
972
  false);
940
- if (!alreadyLoaded)
973
+ if (!alreadyLoaded) {
941
974
  toolCalls = rescued;
942
- if (toolCalls.length)
975
+ }
976
+ if (toolCalls.length) {
943
977
  hlog('recovered printed skill load', { round });
978
+ }
944
979
  }
945
980
  if (!toolCalls.length && enabled.some((tool) => tool.name === SKILL_TOOL_NAME)) {
946
981
  toolCalls = recoverSkillNamedCall(markup, inContextSkills
947
982
  .filter((skill) => !nudgedSkills.has(skill.name))
948
983
  .map((skill) => skill.name), SKILL_TOOL_NAME);
949
- for (const call of toolCalls)
984
+ for (const call of toolCalls) {
950
985
  nudgedSkills.add(unquoteArg(call.arguments.name));
951
- if (toolCalls.length)
986
+ }
987
+ if (toolCalls.length) {
952
988
  hlog('recovered skill-named call', { round });
989
+ }
953
990
  }
954
991
  if (!toolCalls.length) {
955
992
  const replay = recovered.replay ? stripToolCallMarkup(recovered.replay) : '';
956
- if (replay)
993
+ if (replay) {
957
994
  yield { type: 'content', text: replay };
995
+ }
958
996
  const spoken = recovered.contentText.trim();
959
997
  if (enabled.length && !narrated && !replay && NARRATED_INTENT.test(spoken)) {
960
998
  hlog('round narrated a tool call instead of making it', { round });
@@ -972,16 +1010,25 @@ export async function* completion(sdk, { modelId, acquireRound, model, agent, ct
972
1010
  break;
973
1011
  }
974
1012
  hlog('round produced no answer', { round, mute });
975
- if (mute++ < MAX_MUTE_ROUNDS)
1013
+ if (mute++ < MAX_MUTE_ROUNDS) {
976
1014
  continue;
1015
+ }
977
1016
  outcome = 'mute';
978
1017
  break;
979
1018
  }
980
1019
  mute = 0;
981
1020
  narrated = false;
982
- const resolved = await resolveToolCalls(toolCalls, enabled, agent?.id ?? null, pipeline, inContextSkills.map((skill) => skill.name), { sandboxMode: agent?.sandboxMode === true, signal });
983
- 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) {
984
1030
  throw new AbortedError();
1031
+ }
985
1032
  history = [
986
1033
  ...history,
987
1034
  {
@@ -1064,7 +1111,8 @@ export async function* completion(sdk, { modelId, acquireRound, model, agent, ct
1064
1111
  });
1065
1112
  }
1066
1113
  else if (tool) {
1067
- execution = await executeCall(tool, args, callCtx, {
1114
+ execution = await executeCall(tool, args, {
1115
+ ctx: callCtx,
1068
1116
  approve,
1069
1117
  signal,
1070
1118
  denied,
@@ -1105,21 +1153,8 @@ export async function* completion(sdk, { modelId, acquireRound, model, agent, ct
1105
1153
  else if (tool?.name === WEB_SEARCH_TOOL_NAME && !rejection && !priorFailure) {
1106
1154
  webSearches++;
1107
1155
  }
1108
- if (call.name === SKILL_TOOL_NAME &&
1109
- output &&
1110
- typeof output === 'object' &&
1111
- 'content' in output) {
1112
- loadedSkills.add(skillName);
1113
- if ('file' in output && typeof output.file === 'string') {
1114
- const files = loadedSkillFiles.get(skillName) ?? new Set();
1115
- files.add(output.file);
1116
- loadedSkillFiles.set(skillName, files);
1117
- }
1118
- if (mainRequested)
1119
- loadedThisTurn.add(skillName);
1120
- else
1121
- loadedFilesThisTurn.add(fileKey(skillName, unquoteArg(askedFile)));
1122
- skillIcon = skillEmoji(skillName) ?? skillIcon;
1156
+ if (call.name === SKILL_TOOL_NAME) {
1157
+ recordSkillLoad(skillName, output, { mainRequested, askedFile });
1123
1158
  }
1124
1159
  if (!activity &&
1125
1160
  !silent &&
@@ -1133,8 +1168,9 @@ export async function* completion(sdk, { modelId, acquireRound, model, agent, ct
1133
1168
  decision: 'blocked'
1134
1169
  };
1135
1170
  }
1136
- if (activity)
1171
+ if (activity) {
1137
1172
  yield activity;
1173
+ }
1138
1174
  if (!silent) {
1139
1175
  yield {
1140
1176
  type: 'tool-result',
@@ -1162,8 +1198,9 @@ export async function* completion(sdk, { modelId, acquireRound, model, agent, ct
1162
1198
  blockedRepeats = 0;
1163
1199
  }
1164
1200
  history.push({ role: 'user', content: TOOL_CONTINUATION_NUDGE });
1165
- if (blockedRepeats >= MAX_BLOCKED_REPEATS)
1201
+ if (blockedRepeats >= MAX_BLOCKED_REPEATS) {
1166
1202
  break;
1203
+ }
1167
1204
  if (cappedCalls === resolved.length) {
1168
1205
  if (cappedRounds++ >= MAX_CAPPED_ROUNDS) {
1169
1206
  outcome = 'search-capped';
@@ -1178,12 +1215,14 @@ export async function* completion(sdk, { modelId, acquireRound, model, agent, ct
1178
1215
  yield* reportUnnamedWrites(said, landed);
1179
1216
  const answered = metricsEvent(stats, runUsage());
1180
1217
  runOutcome = 'done';
1181
- if (answered)
1218
+ if (answered) {
1182
1219
  yield answered;
1220
+ }
1183
1221
  return;
1184
1222
  }
1185
- if (signal?.aborted)
1223
+ if (signal?.aborted) {
1186
1224
  throw new AbortedError();
1225
+ }
1187
1226
  const recovered = yield* runRecoveredRound(sdk, {
1188
1227
  modelId,
1189
1228
  acquireRound,
@@ -1198,8 +1237,9 @@ export async function* completion(sdk, { modelId, acquireRound, model, agent, ct
1198
1237
  });
1199
1238
  collectStats(stats, recovered.final.stats, recovered.final.stopReason);
1200
1239
  const answer = recovered.replay ? stripToolCallMarkup(recovered.replay) : '';
1201
- if (answer)
1240
+ if (answer) {
1202
1241
  yield { type: 'content', text: answer };
1242
+ }
1203
1243
  said = `${recovered.contentText}\n${answer}`;
1204
1244
  if (!said.trim()) {
1205
1245
  hlog('degenerate wrap-up', { outcome });
@@ -1213,13 +1253,18 @@ export async function* completion(sdk, { modelId, acquireRound, model, agent, ct
1213
1253
  }
1214
1254
  const metrics = metricsEvent(stats, runUsage());
1215
1255
  runOutcome = 'done';
1216
- if (metrics)
1256
+ if (metrics) {
1217
1257
  yield metrics;
1258
+ }
1259
+ }
1260
+ try {
1261
+ yield* runTurn();
1218
1262
  }
1219
1263
  catch (err) {
1220
1264
  const written = writeReport(landed);
1221
- if (written)
1265
+ if (written) {
1222
1266
  yield { type: 'content', text: written };
1267
+ }
1223
1268
  if (err instanceof AbortedError) {
1224
1269
  runOutcome = 'aborted';
1225
1270
  yield { type: 'aborted' };
@@ -1242,3 +1287,17 @@ export async function* completion(sdk, { modelId, acquireRound, model, agent, ct
1242
1287
  });
1243
1288
  }
1244
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
+ }