muonroi-cli 1.7.2 → 1.8.1

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 (326) hide show
  1. package/dist/packages/agent-harness-core/src/event-filter.js +1 -0
  2. package/dist/packages/agent-harness-core/src/event-redact.js +8 -0
  3. package/dist/packages/agent-harness-core/src/mcp-server.d.ts +16 -0
  4. package/dist/packages/agent-harness-core/src/mcp-server.js +88 -10
  5. package/dist/packages/agent-harness-core/src/protocol.d.ts +15 -0
  6. package/dist/packages/agent-harness-core/src/selector.js +2 -2
  7. package/dist/packages/agent-harness-core/src/transports/sidechannel.js +1 -1
  8. package/dist/packages/agent-harness-opentui/src/agent-mode.js +1 -1
  9. package/dist/packages/agent-harness-opentui/src/install.js +1 -1
  10. package/dist/src/__test-stubs__/vitest-setup.js +4 -0
  11. package/dist/src/agent-harness/__tests__/mock-model.spec.js +54 -0
  12. package/dist/src/agent-harness/mock-model.d.ts +1 -1
  13. package/dist/src/agent-harness/mock-model.js +143 -6
  14. package/dist/src/agent-harness/test-spawn.js +3 -1
  15. package/dist/src/chat/chat-keychain.js +9 -1
  16. package/dist/src/cli/cost-forensics.js +7 -4
  17. package/dist/src/cli/keys.d.ts +2 -2
  18. package/dist/src/cli/keys.js +107 -28
  19. package/dist/src/cli/usage-report.js +2 -2
  20. package/dist/src/council/__tests__/abort-threading.test.d.ts +1 -0
  21. package/dist/src/council/__tests__/abort-threading.test.js +193 -0
  22. package/dist/src/council/__tests__/clarification-prompt.test.js +21 -1
  23. package/dist/src/council/__tests__/council-turn-length-emit.test.d.ts +1 -0
  24. package/dist/src/council/__tests__/council-turn-length-emit.test.js +130 -0
  25. package/dist/src/council/__tests__/debate-round-budget.test.d.ts +1 -0
  26. package/dist/src/council/__tests__/debate-round-budget.test.js +45 -0
  27. package/dist/src/council/__tests__/evaluator-metrics.test.js +3 -0
  28. package/dist/src/council/__tests__/post-debate-recommendation.test.d.ts +1 -0
  29. package/dist/src/council/__tests__/post-debate-recommendation.test.js +58 -0
  30. package/dist/src/council/__tests__/research-tools.test.js +4 -0
  31. package/dist/src/council/__tests__/round-tools.test.js +6 -0
  32. package/dist/src/council/clarifier-question-cap.test.d.ts +12 -0
  33. package/dist/src/council/clarifier-question-cap.test.js +73 -0
  34. package/dist/src/council/clarifier.d.ts +29 -1
  35. package/dist/src/council/clarifier.js +64 -1
  36. package/dist/src/council/debate-planner.d.ts +2 -1
  37. package/dist/src/council/debate-planner.js +5 -3
  38. package/dist/src/council/debate.d.ts +9 -0
  39. package/dist/src/council/debate.js +88 -8
  40. package/dist/src/council/index.d.ts +46 -1
  41. package/dist/src/council/index.js +125 -19
  42. package/dist/src/council/llm.js +41 -13
  43. package/dist/src/council/prompts.js +36 -1
  44. package/dist/src/council/types.d.ts +14 -1
  45. package/dist/src/ee/__tests__/export-transcripts.test.js +1 -1
  46. package/dist/src/ee/bridge.d.ts +3 -1
  47. package/dist/src/ee/bridge.js +9 -3
  48. package/dist/src/ee/bridge.test.js +1 -2
  49. package/dist/src/ee/client.js +15 -2
  50. package/dist/src/ee/recall-ledger.d.ts +20 -0
  51. package/dist/src/ee/recall-ledger.js +40 -1
  52. package/dist/src/ee/search.d.ts +25 -0
  53. package/dist/src/ee/search.js +73 -0
  54. package/dist/src/ee/types.d.ts +15 -1
  55. package/dist/src/ee/who-am-i.d.ts +41 -0
  56. package/dist/src/ee/who-am-i.js +143 -0
  57. package/dist/src/ee/who-am-i.test.d.ts +1 -0
  58. package/dist/src/ee/who-am-i.test.js +101 -0
  59. package/dist/src/flow/compaction/index.d.ts +1 -0
  60. package/dist/src/flow/compaction/index.js +4 -0
  61. package/dist/src/generated/version.d.ts +1 -1
  62. package/dist/src/generated/version.js +1 -1
  63. package/dist/src/headless/council-answers.js +1 -0
  64. package/dist/src/headless/output.d.ts +30 -1
  65. package/dist/src/headless/output.js +180 -0
  66. package/dist/src/headless/output.test.js +61 -1
  67. package/dist/src/hooks/index.js +48 -2
  68. package/dist/src/index.d.ts +2 -0
  69. package/dist/src/index.js +59 -62
  70. package/dist/src/lsp/client-normalize.test.d.ts +12 -0
  71. package/dist/src/lsp/client-normalize.test.js +34 -0
  72. package/dist/src/lsp/client.d.ts +1 -0
  73. package/dist/src/lsp/client.js +10 -2
  74. package/dist/src/lsp/manager.d.ts +1 -1
  75. package/dist/src/lsp/manager.js +13 -5
  76. package/dist/src/lsp/manager.test.js +41 -2
  77. package/dist/src/lsp/runtime.d.ts +1 -1
  78. package/dist/src/lsp/runtime.js +2 -2
  79. package/dist/src/maintain/__tests__/gh-create-pr.test.js +7 -3
  80. package/dist/src/maintain/__tests__/pr-builder.test.js +7 -3
  81. package/dist/src/mcp/__tests__/client-pool.spec.js +3 -3
  82. package/dist/src/mcp/__tests__/ee-tools.test.js +42 -14
  83. package/dist/src/mcp/__tests__/forensics-tools.test.js +3 -3
  84. package/dist/src/mcp/__tests__/lsp-tools.test.js +4 -4
  85. package/dist/src/mcp/auto-setup.js +0 -8
  86. package/dist/src/mcp/client-pool.d.ts +1 -1
  87. package/dist/src/mcp/client-pool.js +25 -9
  88. package/dist/src/mcp/ee-tools.d.ts +10 -0
  89. package/dist/src/mcp/ee-tools.js +34 -0
  90. package/dist/src/mcp/self-verify-jobs.d.ts +1 -0
  91. package/dist/src/mcp/self-verify-jobs.js +1 -3
  92. package/dist/src/mcp/self-verify-runner.js +1 -1
  93. package/dist/src/models/catalog-gemini.test.js +12 -10
  94. package/dist/src/models/catalog.json +106 -38
  95. package/dist/src/orchestrator/__tests__/auto-commit.test.d.ts +1 -0
  96. package/dist/src/orchestrator/__tests__/auto-commit.test.js +142 -0
  97. package/dist/src/orchestrator/__tests__/batch-turn-runner.test.js +3 -0
  98. package/dist/src/orchestrator/__tests__/delegations.test.d.ts +1 -0
  99. package/dist/src/orchestrator/__tests__/delegations.test.js +96 -0
  100. package/dist/src/orchestrator/__tests__/message-processor.test.js +2 -0
  101. package/dist/src/orchestrator/__tests__/prompts.test.d.ts +1 -0
  102. package/dist/src/orchestrator/__tests__/prompts.test.js +88 -0
  103. package/dist/src/orchestrator/__tests__/stream-runner.test.js +29 -0
  104. package/dist/src/orchestrator/__tests__/sub-session-delegation.test.d.ts +1 -0
  105. package/dist/src/orchestrator/__tests__/sub-session-delegation.test.js +507 -0
  106. package/dist/src/orchestrator/agent.test.js +5 -2
  107. package/dist/src/orchestrator/auto-commit.d.ts +110 -0
  108. package/dist/src/orchestrator/auto-commit.js +366 -0
  109. package/dist/src/orchestrator/batch-turn-runner.d.ts +3 -2
  110. package/dist/src/orchestrator/batch-turn-runner.js +2 -1
  111. package/dist/src/orchestrator/compaction-proposer-prompt.d.ts +8 -0
  112. package/dist/src/orchestrator/compaction-proposer-prompt.js +43 -0
  113. package/dist/src/orchestrator/compaction.d.ts +22 -0
  114. package/dist/src/orchestrator/compaction.js +42 -0
  115. package/dist/src/orchestrator/delegations.d.ts +1 -0
  116. package/dist/src/orchestrator/delegations.js +41 -0
  117. package/dist/src/orchestrator/error-utils.js +27 -2
  118. package/dist/src/orchestrator/message-processor.d.ts +23 -5
  119. package/dist/src/orchestrator/message-processor.js +226 -2573
  120. package/dist/src/orchestrator/orchestrator.d.ts +11 -2
  121. package/dist/src/orchestrator/orchestrator.js +530 -43
  122. package/dist/src/orchestrator/pending-calls.js +2 -1
  123. package/dist/src/orchestrator/preprocessor.d.ts +13 -0
  124. package/dist/src/orchestrator/preprocessor.js +146 -0
  125. package/dist/src/orchestrator/prompts.d.ts +16 -0
  126. package/dist/src/orchestrator/prompts.js +130 -67
  127. package/dist/src/orchestrator/safety-askcard.d.ts +66 -0
  128. package/dist/src/orchestrator/safety-askcard.js +74 -0
  129. package/dist/src/orchestrator/sandbox.test.js +8 -5
  130. package/dist/src/orchestrator/scope-ceiling.js +1 -1
  131. package/dist/src/orchestrator/stall-watchdog.d.ts +47 -0
  132. package/dist/src/orchestrator/stall-watchdog.js +23 -0
  133. package/dist/src/orchestrator/stall-watchdog.test.js +36 -1
  134. package/dist/src/orchestrator/stream-runner.js +52 -30
  135. package/dist/src/orchestrator/sub-agent-cap.d.ts +2 -0
  136. package/dist/src/orchestrator/sub-agent-cap.js +12 -4
  137. package/dist/src/orchestrator/sub-agent-model-tier.js +1 -1
  138. package/dist/src/orchestrator/subagent-compactor.d.ts +21 -0
  139. package/dist/src/orchestrator/subagent-compactor.js +148 -24
  140. package/dist/src/orchestrator/subagent-compactor.spec.js +249 -1
  141. package/dist/src/orchestrator/text-tool-call-detector.d.ts +9 -5
  142. package/dist/src/orchestrator/text-tool-call-detector.js +16 -12
  143. package/dist/src/orchestrator/text-tool-call-detector.test.js +66 -8
  144. package/dist/src/orchestrator/tool-args-repair.js +1 -1
  145. package/dist/src/orchestrator/tool-engine.d.ts +153 -0
  146. package/dist/src/orchestrator/tool-engine.js +3138 -0
  147. package/dist/src/orchestrator/turn-runner-deps.d.ts +3 -0
  148. package/dist/src/pil/__tests__/discovery-types.test.js +15 -4
  149. package/dist/src/pil/__tests__/discovery.test.js +89 -145
  150. package/dist/src/pil/__tests__/dual-run.test.js +2 -0
  151. package/dist/src/pil/__tests__/layer1-intent-trace.test.js +3 -0
  152. package/dist/src/pil/__tests__/layer1-intent.test.js +3 -0
  153. package/dist/src/pil/__tests__/layer16-clarity.test.js +35 -32
  154. package/dist/src/pil/__tests__/layer18-acceptance.test.js +1 -78
  155. package/dist/src/pil/__tests__/layer2_5-ponytail.test.d.ts +1 -0
  156. package/dist/src/pil/__tests__/layer2_5-ponytail.test.js +53 -0
  157. package/dist/src/pil/__tests__/layer3-ee-injection.test.js +67 -4
  158. package/dist/src/pil/__tests__/layer3-injected-chunk.test.js +14 -2
  159. package/dist/src/pil/__tests__/layer4-gsd.test.js +30 -0
  160. package/dist/src/pil/__tests__/layer5-context.test.js +39 -1
  161. package/dist/src/pil/__tests__/layer6-output.test.js +30 -2
  162. package/dist/src/pil/__tests__/llm-classify.test.js +79 -1
  163. package/dist/src/pil/__tests__/orchestrator-integration.test.js +3 -1
  164. package/dist/src/pil/__tests__/pipeline.test.js +11 -8
  165. package/dist/src/pil/__tests__/response-tools.test.js +52 -0
  166. package/dist/src/pil/agent-operating-contract.d.ts +1 -1
  167. package/dist/src/pil/agent-operating-contract.js +5 -3
  168. package/dist/src/pil/agent-operating-contract.test.js +2 -2
  169. package/dist/src/pil/cheap-model-playbook.d.ts +1 -1
  170. package/dist/src/pil/cheap-model-playbook.js +1 -1
  171. package/dist/src/pil/cheap-model-workbooks.d.ts +1 -1
  172. package/dist/src/pil/cheap-model-workbooks.js +1 -1
  173. package/dist/src/pil/config.d.ts +20 -0
  174. package/dist/src/pil/config.js +35 -0
  175. package/dist/src/pil/discovery-types.d.ts +32 -9
  176. package/dist/src/pil/discovery.d.ts +3 -3
  177. package/dist/src/pil/discovery.js +138 -244
  178. package/dist/src/pil/index.d.ts +1 -1
  179. package/dist/src/pil/index.js +1 -1
  180. package/dist/src/pil/layer1-intent.d.ts +9 -1
  181. package/dist/src/pil/layer1-intent.js +62 -7
  182. package/dist/src/pil/layer1-intent.test.js +112 -2
  183. package/dist/src/pil/layer15-context-scan.js +1 -1
  184. package/dist/src/pil/layer16-clarity.d.ts +21 -25
  185. package/dist/src/pil/layer16-clarity.js +52 -88
  186. package/dist/src/pil/layer18-acceptance.d.ts +13 -3
  187. package/dist/src/pil/layer18-acceptance.js +1 -75
  188. package/dist/src/pil/layer2-personality.js +7 -8
  189. package/dist/src/pil/layer2_5-ponytail.d.ts +2 -0
  190. package/dist/src/pil/layer2_5-ponytail.js +41 -0
  191. package/dist/src/pil/layer3-ee-injection.js +180 -14
  192. package/dist/src/pil/layer4-gsd.js +22 -8
  193. package/dist/src/pil/layer5-context.d.ts +1 -0
  194. package/dist/src/pil/layer5-context.js +26 -11
  195. package/dist/src/pil/layer6-output.js +23 -5
  196. package/dist/src/pil/llm-classify.d.ts +11 -0
  197. package/dist/src/pil/llm-classify.js +196 -3
  198. package/dist/src/pil/native-capabilities-workbook.d.ts +1 -1
  199. package/dist/src/pil/native-capabilities-workbook.js +4 -3
  200. package/dist/src/pil/pipeline.js +15 -1
  201. package/dist/src/pil/response-tools.d.ts +16 -0
  202. package/dist/src/pil/response-tools.js +41 -0
  203. package/dist/src/pil/schema.d.ts +8 -0
  204. package/dist/src/pil/schema.js +24 -3
  205. package/dist/src/pil/task-tier-map.js +7 -7
  206. package/dist/src/pil/types.d.ts +9 -3
  207. package/dist/src/product-loop/__tests__/discovery-interview.test.js +103 -1
  208. package/dist/src/product-loop/__tests__/gather-selectable-alts.test.js +34 -0
  209. package/dist/src/product-loop/__tests__/sprint-self-verify.test.js +12 -12
  210. package/dist/src/product-loop/discovery-interview.d.ts +20 -0
  211. package/dist/src/product-loop/discovery-interview.js +121 -7
  212. package/dist/src/product-loop/gather.js +19 -5
  213. package/dist/src/product-loop/loop-driver.js +2 -2
  214. package/dist/src/product-loop/sprint-self-verify.js +2 -2
  215. package/dist/src/providers/__tests__/reasoning-roundtrip.test.js +12 -0
  216. package/dist/src/providers/auth/__tests__/gemini-oauth.test.d.ts +1 -5
  217. package/dist/src/providers/auth/__tests__/gemini-oauth.test.js +1 -5
  218. package/dist/src/providers/auth/gcloud.d.ts +28 -0
  219. package/dist/src/providers/auth/gcloud.js +102 -0
  220. package/dist/src/providers/auth/gemini-oauth.d.ts +46 -19
  221. package/dist/src/providers/auth/gemini-oauth.js +292 -105
  222. package/dist/src/providers/auth/grok-oauth.d.ts +1 -1
  223. package/dist/src/providers/auth/grok-oauth.js +1 -1
  224. package/dist/src/providers/auth/registry.js +28 -6
  225. package/dist/src/providers/gemini.d.ts +1 -1
  226. package/dist/src/providers/gemini.js +10 -3
  227. package/dist/src/providers/keychain.d.ts +2 -2
  228. package/dist/src/providers/keychain.js +20 -2
  229. package/dist/src/providers/openai.js +5 -1
  230. package/dist/src/providers/runtime.d.ts +1 -1
  231. package/dist/src/providers/runtime.js +11 -2
  232. package/dist/src/providers/siliconflow-sse-repair.js +1 -1
  233. package/dist/src/providers/strategies/google.strategy.d.ts +9 -2
  234. package/dist/src/providers/strategies/google.strategy.js +159 -9
  235. package/dist/src/providers/strategies/thinking-mode.js +19 -6
  236. package/dist/src/providers/types.d.ts +1 -1
  237. package/dist/src/router/decide.test.js +40 -8
  238. package/dist/src/router/step-router.d.ts +20 -0
  239. package/dist/src/router/step-router.js +73 -0
  240. package/dist/src/self-qa/agentic-loop.js +30 -21
  241. package/dist/src/self-qa/orchestrator.js +5 -5
  242. package/dist/src/self-qa/scenario-planner.js +1 -1
  243. package/dist/src/self-qa/spec-emitter.js +4 -4
  244. package/dist/src/storage/__tests__/hierarchical-session.test.d.ts +1 -0
  245. package/dist/src/storage/__tests__/hierarchical-session.test.js +161 -0
  246. package/dist/src/storage/__tests__/sweep-stale-pending.test.js +2 -2
  247. package/dist/src/storage/index.d.ts +1 -1
  248. package/dist/src/storage/index.js +1 -1
  249. package/dist/src/storage/migrations.js +13 -1
  250. package/dist/src/storage/transcript-response-entry.test.js +18 -5
  251. package/dist/src/storage/transcript-view.js +7 -1
  252. package/dist/src/storage/transcript.d.ts +3 -0
  253. package/dist/src/storage/transcript.js +254 -73
  254. package/dist/src/storage/ui-interaction-log.d.ts +17 -0
  255. package/dist/src/storage/usage.js +22 -5
  256. package/dist/src/tools/__tests__/native-tools.test.js +6 -0
  257. package/dist/src/tools/bash.d.ts +36 -23
  258. package/dist/src/tools/bash.js +59 -301
  259. package/dist/src/tools/bash.test.js +2 -257
  260. package/dist/src/tools/file-tracker.d.ts +5 -0
  261. package/dist/src/tools/file-tracker.js +14 -0
  262. package/dist/src/tools/file.js +2 -0
  263. package/dist/src/tools/git-safety.d.ts +38 -1
  264. package/dist/src/tools/git-safety.js +94 -4
  265. package/dist/src/tools/git-safety.test.js +45 -1
  266. package/dist/src/tools/native-tools.d.ts +1 -1
  267. package/dist/src/tools/native-tools.js +66 -1
  268. package/dist/src/tools/registry-bash-empty-command.test.js +4 -3
  269. package/dist/src/tools/registry-git-safety.test.js +17 -6
  270. package/dist/src/tools/registry.d.ts +6 -0
  271. package/dist/src/tools/registry.js +215 -13
  272. package/dist/src/types/index.d.ts +8 -2
  273. package/dist/src/ui/app.d.ts +1 -0
  274. package/dist/src/ui/app.js +0 -0
  275. package/dist/src/ui/components/council-question-card.js +1 -0
  276. package/dist/src/ui/components/message-view.js +37 -3
  277. package/dist/src/ui/components/structured-response-view.js +7 -0
  278. package/dist/src/ui/components/task-list-panel.js +3 -12
  279. package/dist/src/ui/containers/chat-feed.d.ts +40 -0
  280. package/dist/src/ui/containers/chat-feed.js +66 -0
  281. package/dist/src/ui/containers/modals-layer.d.ts +87 -0
  282. package/dist/src/ui/containers/modals-layer.js +18 -0
  283. package/dist/src/ui/slash/__tests__/menu-parity.test.d.ts +1 -0
  284. package/dist/src/ui/slash/__tests__/menu-parity.test.js +1 -0
  285. package/dist/src/ui/slash/__tests__/ponytail.test.d.ts +1 -0
  286. package/dist/src/ui/slash/__tests__/ponytail.test.js +37 -0
  287. package/dist/src/ui/slash/expand.js +14 -1
  288. package/dist/src/ui/slash/export.js +16 -1
  289. package/dist/src/ui/slash/menu-items.js +1 -0
  290. package/dist/src/ui/slash/ponytail.d.ts +2 -0
  291. package/dist/src/ui/slash/ponytail.js +19 -0
  292. package/dist/src/ui/status-bar/store.js +0 -1
  293. package/dist/src/ui/status-bar/store.test.js +2 -2
  294. package/dist/src/ui/use-app-logic.d.ts +26 -0
  295. package/dist/src/ui/use-app-logic.js +0 -0
  296. package/dist/src/ui/utils/relaunch.js +1 -1
  297. package/dist/src/ui/utils/text.d.ts +11 -0
  298. package/dist/src/ui/utils/text.js +21 -1
  299. package/dist/src/ui/utils/text.test.js +25 -1
  300. package/dist/src/ui/utils/tools.js +3 -1
  301. package/dist/src/usage/cost-log.js +2 -2
  302. package/dist/src/usage/estimator.d.ts +14 -0
  303. package/dist/src/usage/estimator.js +22 -0
  304. package/dist/src/utils/__tests__/compaction-caps.test.d.ts +21 -0
  305. package/dist/src/utils/__tests__/compaction-caps.test.js +187 -0
  306. package/dist/src/utils/__tests__/llm-deadline-abort.test.d.ts +1 -0
  307. package/dist/src/utils/__tests__/llm-deadline-abort.test.js +33 -0
  308. package/dist/src/utils/__tests__/llm-deadline.test.js +16 -4
  309. package/dist/src/utils/__tests__/logger.test.d.ts +1 -0
  310. package/dist/src/utils/__tests__/logger.test.js +115 -0
  311. package/dist/src/utils/__tests__/settings-agent-first.test.d.ts +1 -0
  312. package/dist/src/utils/__tests__/settings-agent-first.test.js +48 -0
  313. package/dist/src/utils/install-manager.d.ts +1 -0
  314. package/dist/src/utils/install-manager.js +76 -7
  315. package/dist/src/utils/install-manager.test.js +43 -1
  316. package/dist/src/utils/llm-deadline.d.ts +9 -1
  317. package/dist/src/utils/llm-deadline.js +31 -2
  318. package/dist/src/utils/logger.d.ts +28 -0
  319. package/dist/src/utils/logger.js +171 -0
  320. package/dist/src/utils/permission-mode.d.ts +33 -0
  321. package/dist/src/utils/permission-mode.js +164 -3
  322. package/dist/src/utils/permission-mode.test.js +92 -1
  323. package/dist/src/utils/settings.d.ts +36 -32
  324. package/dist/src/utils/settings.js +151 -123
  325. package/dist/src/utils/update-checker.test.js +5 -1
  326. package/package.json +1 -1
@@ -39,21 +39,23 @@ afterEach(() => {
39
39
  vi.doUnmock("../storage/index.js");
40
40
  });
41
41
  describe("Agent sandbox mode", { timeout: 30_000 }, () => {
42
- it("can switch sandbox mode at runtime", async () => {
42
+ it("can switch sandbox mode at runtime — always 'off' (sandbox removed)", async () => {
43
43
  const { Agent } = await importAgentModule();
44
44
  const agent = new Agent(undefined, undefined, undefined, undefined, {
45
45
  persistSession: false,
46
46
  sandboxMode: "off",
47
47
  });
48
48
  expect(agent.getSandboxMode()).toBe("off");
49
+ // Sandbox has been removed: setSandboxMode is a no-op stub.
50
+ // The mode remains "off" regardless of what is passed.
49
51
  agent.setSandboxMode("shuru");
50
- expect(agent.getSandboxMode()).toBe("shuru");
52
+ expect(agent.getSandboxMode()).toBe("off");
51
53
  });
52
- it("passes sandbox mode into background delegations", async () => {
54
+ it("passes sandbox mode into background delegations — always 'off' (sandbox removed)", async () => {
53
55
  const { Agent } = await importAgentModule();
54
56
  const agent = new Agent(undefined, undefined, undefined, undefined, {
55
57
  persistSession: false,
56
- sandboxMode: "shuru",
58
+ sandboxMode: "shuru", // accepted for back-compat but ignored
57
59
  });
58
60
  const startMock = vi.fn(async () => ({ success: true, output: "ok" }));
59
61
  agent.delegations.start = startMock;
@@ -62,7 +64,8 @@ describe("Agent sandbox mode", { timeout: 30_000 }, () => {
62
64
  description: "Inspect",
63
65
  prompt: "Look around",
64
66
  });
65
- expect(startMock).toHaveBeenCalledWith(expect.objectContaining({ agent: "explore" }), expect.objectContaining({ sandboxMode: "shuru" }));
67
+ // Sandbox removed: delegations always receive sandboxMode: "off".
68
+ expect(startMock).toHaveBeenCalledWith(expect.objectContaining({ agent: "explore" }), expect.objectContaining({ sandboxMode: "off" }));
66
69
  });
67
70
  it("can get and set sandbox settings", async () => {
68
71
  const { Agent } = await importAgentModule();
@@ -80,7 +80,7 @@ export function parseBudgetOverride(raw) {
80
80
  return { override: undefined, cleanedPrompt: raw };
81
81
  // Strip the entire matched segment including its leading/trailing whitespace
82
82
  // and re-collapse the surrounding spaces.
83
- const cleaned = (raw.slice(0, match.index ?? 0) + " " + raw.slice((match.index ?? 0) + match[0].length))
83
+ const cleaned = `${raw.slice(0, match.index ?? 0)} ${raw.slice((match.index ?? 0) + match[0].length)}`
84
84
  .replace(/\s+/g, " ")
85
85
  .trim();
86
86
  return { override: n, cleanedPrompt: cleaned };
@@ -57,6 +57,53 @@ export interface StallRepromptState {
57
57
  * effects) so it is unit-testable in isolation from the orchestrator loop.
58
58
  */
59
59
  export declare function shouldRepromptStall(s: StallRepromptState): boolean;
60
+ /** Inputs to the mid-loop stall continuation decision — see {@link shouldContinueAfterMidLoopStall}. */
61
+ export interface MidLoopStallState {
62
+ /** The watchdog fired for this attempt. */
63
+ stallTriggered: boolean;
64
+ /**
65
+ * Real content parts received across the WHOLE streamText attempt (all steps).
66
+ * `> 0` proves earlier tool steps already ran — so this is NOT a time-to-first-
67
+ * byte stall (that case is handled by {@link shouldRepromptStall}).
68
+ */
69
+ chunksThisAttempt: number;
70
+ /**
71
+ * Real content parts received since the last step boundary (reset in
72
+ * `prepareStep`). `0` means the in-flight step's provider request produced no
73
+ * byte before the watchdog fired — a dead socket on a SINGLE step, with every
74
+ * prior step fully completed. Safe to continue: the completed steps'
75
+ * assistant+tool messages are appended to history before re-issuing, so no
76
+ * tool is re-run and no text is duplicated.
77
+ */
78
+ chunksThisStep: number;
79
+ /** How many mid-loop continuations have already happened this turn. */
80
+ retryCount: number;
81
+ /** Configured cap (getProviderStallRetries); 0 disables continuation. */
82
+ maxRetries: number;
83
+ /** True on genuine user cancel (never continue over a cancel). */
84
+ aborted: boolean;
85
+ }
86
+ /**
87
+ * Decide whether a fired stall watchdog should CONTINUE the turn (append the
88
+ * completed steps' messages, then re-issue streamText to resume from the
89
+ * stalled step) instead of falling through to the partial-answer rescue.
90
+ *
91
+ * This is the mid-loop counterpart to {@link shouldRepromptStall}. The TTFB
92
+ * re-prompt restarts the WHOLE request from the original prompt, so it is gated
93
+ * on `chunksThisAttempt === 0` to avoid re-running tools. Continuation instead
94
+ * preserves all completed steps in history (assistant tool-calls + their
95
+ * tool-results), so re-issuing cannot re-run a tool — making it safe even when
96
+ * earlier steps had side effects (writes, commits).
97
+ *
98
+ * Qualifies ONLY when: the watchdog fired, earlier steps ran
99
+ * (`chunksThisAttempt > 0`), the CURRENT step produced nothing
100
+ * (`chunksThisStep === 0` → a clean dead socket, no partial text to duplicate),
101
+ * under the retry cap, and not a user cancel. A step that emitted partial text
102
+ * then stalled (`chunksThisStep > 0`) falls through to rescue instead, since
103
+ * re-issuing would duplicate that partial output. Pure (no side effects) so it
104
+ * is unit-testable in isolation from the orchestrator loop.
105
+ */
106
+ export declare function shouldContinueAfterMidLoopStall(s: MidLoopStallState): boolean;
60
107
  /**
61
108
  * Exponential backoff (ms, capped at 4s) before the Nth stall re-prompt
62
109
  * (1-based): 500 → 1000 → 2000 → 4000 → 4000.
@@ -40,6 +40,29 @@ export function shouldRepromptStall(s) {
40
40
  s.assistantTextEmpty &&
41
41
  !s.aborted);
42
42
  }
43
+ /**
44
+ * Decide whether a fired stall watchdog should CONTINUE the turn (append the
45
+ * completed steps' messages, then re-issue streamText to resume from the
46
+ * stalled step) instead of falling through to the partial-answer rescue.
47
+ *
48
+ * This is the mid-loop counterpart to {@link shouldRepromptStall}. The TTFB
49
+ * re-prompt restarts the WHOLE request from the original prompt, so it is gated
50
+ * on `chunksThisAttempt === 0` to avoid re-running tools. Continuation instead
51
+ * preserves all completed steps in history (assistant tool-calls + their
52
+ * tool-results), so re-issuing cannot re-run a tool — making it safe even when
53
+ * earlier steps had side effects (writes, commits).
54
+ *
55
+ * Qualifies ONLY when: the watchdog fired, earlier steps ran
56
+ * (`chunksThisAttempt > 0`), the CURRENT step produced nothing
57
+ * (`chunksThisStep === 0` → a clean dead socket, no partial text to duplicate),
58
+ * under the retry cap, and not a user cancel. A step that emitted partial text
59
+ * then stalled (`chunksThisStep > 0`) falls through to rescue instead, since
60
+ * re-issuing would duplicate that partial output. Pure (no side effects) so it
61
+ * is unit-testable in isolation from the orchestrator loop.
62
+ */
63
+ export function shouldContinueAfterMidLoopStall(s) {
64
+ return (s.stallTriggered && s.chunksThisAttempt > 0 && s.chunksThisStep === 0 && s.retryCount < s.maxRetries && !s.aborted);
65
+ }
43
66
  /**
44
67
  * Exponential backoff (ms, capped at 4s) before the Nth stall re-prompt
45
68
  * (1-based): 500 → 1000 → 2000 → 4000 → 4000.
@@ -1,5 +1,5 @@
1
1
  import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
2
- import { createStallWatchdog, STALL_ABORT_REASON, shouldRepromptStall, stallRepromptBackoffMs, } from "./stall-watchdog.js";
2
+ import { createStallWatchdog, STALL_ABORT_REASON, shouldContinueAfterMidLoopStall, shouldRepromptStall, stallRepromptBackoffMs, } from "./stall-watchdog.js";
3
3
  describe("createStallWatchdog", () => {
4
4
  beforeEach(() => vi.useFakeTimers());
5
5
  afterEach(() => vi.useRealTimers());
@@ -103,6 +103,41 @@ describe("shouldRepromptStall", () => {
103
103
  expect(shouldRepromptStall(ttfb({ aborted: true }))).toBe(false);
104
104
  });
105
105
  });
106
+ describe("shouldContinueAfterMidLoopStall", () => {
107
+ // A clean mid-loop dead socket: watchdog fired AFTER earlier steps ran
108
+ // (chunksThisAttempt > 0), but the in-flight step produced nothing
109
+ // (chunksThisStep === 0). The ONLY shape that continues. Distinct from the
110
+ // TTFB re-prompt, which requires chunksThisAttempt === 0.
111
+ const midLoop = (over = {}) => ({
112
+ stallTriggered: true,
113
+ chunksThisAttempt: 132, // 66 prior steps' worth of chunks
114
+ chunksThisStep: 0,
115
+ retryCount: 0,
116
+ maxRetries: 1,
117
+ aborted: false,
118
+ ...over,
119
+ });
120
+ it("continues a mid-loop dead-socket stall under the cap", () => {
121
+ expect(shouldContinueAfterMidLoopStall(midLoop())).toBe(true);
122
+ });
123
+ it("does NOT continue when the watchdog never fired", () => {
124
+ expect(shouldContinueAfterMidLoopStall(midLoop({ stallTriggered: false }))).toBe(false);
125
+ });
126
+ it("does NOT continue a time-to-first-byte stall (chunksThisAttempt === 0 → TTFB re-prompt owns it)", () => {
127
+ expect(shouldContinueAfterMidLoopStall(midLoop({ chunksThisAttempt: 0 }))).toBe(false);
128
+ });
129
+ it("does NOT continue when the stalled step already emitted output (would duplicate)", () => {
130
+ expect(shouldContinueAfterMidLoopStall(midLoop({ chunksThisStep: 3 }))).toBe(false);
131
+ });
132
+ it("does NOT continue once the retry cap is reached", () => {
133
+ expect(shouldContinueAfterMidLoopStall(midLoop({ retryCount: 1, maxRetries: 1 }))).toBe(false);
134
+ // maxRetries=0 means the feature is disabled — never continue.
135
+ expect(shouldContinueAfterMidLoopStall(midLoop({ retryCount: 0, maxRetries: 0 }))).toBe(false);
136
+ });
137
+ it("does NOT continue over a genuine user cancel", () => {
138
+ expect(shouldContinueAfterMidLoopStall(midLoop({ aborted: true }))).toBe(false);
139
+ });
140
+ });
106
141
  describe("stallRepromptBackoffMs", () => {
107
142
  it("grows exponentially and caps at 4s", () => {
108
143
  expect(stallRepromptBackoffMs(1)).toBe(500);
@@ -34,8 +34,10 @@ import { normalizeModelId } from "../models/registry.js";
34
34
  import { cheapModelShellLine, injectCheapModelPlaybook, injectCheapModelShellDirective, shouldInjectCheapModelPlaybook, } from "../pil/cheap-model-playbook.js";
35
35
  import { injectCheapModelWorkbook, shouldInjectCheapModelWorkbook, subagentTaskType, } from "../pil/cheap-model-workbooks.js";
36
36
  import { getProviderCapabilities } from "../providers/capabilities.js";
37
+ import { getVisionGuidanceForTextOnly } from "../providers/mcp-vision-bridge.js";
37
38
  import { captureToolSchemas } from "../providers/patch-zod-schema.js";
38
39
  import { buildTurnProviderOptions, requireRuntimeProvider, resolveModelRuntime, shouldDropParam, } from "../providers/runtime.js";
40
+ import { needsVisionProxy } from "../providers/vision-proxy.js";
39
41
  import { wireDebug } from "../providers/wire-debug.js";
40
42
  import { BashTool } from "../tools/bash.js";
41
43
  import { createBuiltinTools } from "../tools/registry.js";
@@ -56,7 +58,7 @@ import { attachReminderToMessages, buildScopeReminder, cadenceForSize, shouldInj
56
58
  import { recordCompaction, recordElision } from "./session-experience.js";
57
59
  import { createStallWatchdog, STALL_ERROR_MESSAGE } from "./stall-watchdog.js";
58
60
  import { wrapToolSetWithCap } from "./sub-agent-cap.js";
59
- import { compactSubAgentMessages } from "./subagent-compactor.js";
61
+ import { compactSubAgentMessages, applyAnthropicPromptCaching } from "./subagent-compactor.js";
60
62
  import { combineAbortSignals, firstLine, formatSubagentActivity } from "./tool-utils.js";
61
63
  /**
62
64
  * StreamRunner — extracted sub-agent stream lifecycle.
@@ -133,10 +135,19 @@ export class StreamRunner {
133
135
  : topBash.getSandboxSettings(),
134
136
  shellSettings: getCurrentShellSettings(),
135
137
  });
138
+ // Resolve child model early so we can pass modelId to createBuiltinTools
139
+ // (needed for vision-proxy tools: analyze_image / ask_vision_proxy).
140
+ const childModelId = normalizeModelId(isVision
141
+ ? VISION_MODEL
142
+ : isComputer
143
+ ? COMPUTER_MODEL
144
+ : custom
145
+ ? custom.model
146
+ : this.deps.resolveModelForTask(isExplore ? "explore" : isVerify || isVerifyDetect || isVerifyManifest ? "verify" : "general"));
136
147
  // Mirror the file-local `createTools` wrapper from orchestrator.ts —
137
- // it calls createBuiltinTools(bash, mode) without provider/opts; the
138
- // provider arg in the original wrapper was unused.
139
- const childBaseToolsRaw = createBuiltinTools(childBash, childMode);
148
+ // pass modelId so registry can inject analyze_image/ask_vision_proxy for
149
+ // text-only child models (needsVisionProxy).
150
+ const childBaseToolsRaw = createBuiltinTools(childBash, childMode, { modelId: childModelId });
140
151
  // Wrap with the cumulative cap so the sub-agent's tool loop cannot
141
152
  // accumulate unbounded tool_result tokens. See sub-agent-cap.ts for the
142
153
  // tiered compression schedule. The cap is per-invocation; each sub-agent
@@ -161,13 +172,6 @@ export class StreamRunner {
161
172
  let lastActivity = initialDetail;
162
173
  let childTools = childBaseTools;
163
174
  let closeMcp;
164
- const childModelId = normalizeModelId(isVision
165
- ? VISION_MODEL
166
- : isComputer
167
- ? COMPUTER_MODEL
168
- : custom
169
- ? custom.model
170
- : this.deps.resolveModelForTask(isExplore ? "explore" : isVerify || isVerifyDetect || isVerifyManifest ? "verify" : "general"));
171
175
  const topModelId = this.deps.getModelId();
172
176
  if (childModelId !== topModelId) {
173
177
  statusBarStore.setState({ routed_from: topModelId, model: childModelId });
@@ -212,6 +216,11 @@ export class StreamRunner {
212
216
  const childSystem = shouldInjectCheapModelPlaybook(childRuntime.modelInfo)
213
217
  ? injectCheapModelShellDirective(childWithPlaybook, cheapModelShellLine(resolveShell({}).kind, process.platform))
214
218
  : childWithPlaybook;
219
+ // Inject vision proxy guidance for text-only child models (DeepSeek etc.)
220
+ // so sub-agents know to use analyze_image / ask_vision_proxy when they
221
+ // receive image context or file paths. Mirrors top-level in message-processor.
222
+ const visionGuidance = needsVisionProxy(childModelId) ? getVisionGuidanceForTextOnly(childModelId) : "";
223
+ const childSystemWithVision = visionGuidance ? `${childSystem}\n\n${visionGuidance}` : childSystem;
215
224
  onActivity?.(initialDetail);
216
225
  if (childMode === "agent" && taskCaps.supportsClientTools(childRuntime.modelInfo)) {
217
226
  const mcpBundle = await acquireMcpTools(loadMcpServers(), {
@@ -245,7 +254,8 @@ export class StreamRunner {
245
254
  // signal is intentionally unused on this branch — vision was the only consumer.
246
255
  void signal;
247
256
  const childMessages = [{ role: "user", content: childPrompt }];
248
- const maxSteps = Math.min(this.deps.getMaxToolRounds(), isExplore ? 60 : 120);
257
+ // The main agent manages its sub-agents, so don't apply an arbitrary hard limit.
258
+ const maxSteps = request.maxToolRounds ?? (this.deps.getMaxToolRounds() * 2);
249
259
  // F1 parity — derive per-turn providerOptions so the sub-agent OpenAI calls
250
260
  // carry a stable session-derived promptCacheKey (every tool round routes to
251
261
  // the same cache node, keeping the unchanging prefix cached). The top-level
@@ -265,7 +275,7 @@ export class StreamRunner {
265
275
  childMode,
266
276
  childBash,
267
277
  childRuntime,
268
- childSystem,
278
+ childSystem: childSystemWithVision,
269
279
  childMessages,
270
280
  childTools,
271
281
  childProviderOptions,
@@ -348,16 +358,16 @@ export class StreamRunner {
348
358
  // Explore sub-agents are READ-ONLY research — a codebase investigation
349
359
  // legitimately needs more grep/read steps than the tight general/medium=10
350
360
  // cell allows. Cutting it early (esp. for reasoning models that front-load
351
- // tool calls) leaves no budget to reach the synthesis turn (root cause of
352
- // empty "Task completed. Last action: grep" returns). Give explore the
353
- // analyze/large cell (15); edit-capable agents keep general/medium (10).
361
+ // Sub-agent ceiling is no longer a hard halt. Per user request:
362
+ // "cũng áp dụng với sub agent nhé không nên hardcode maxtool mà nếu có
363
+ // vấn đề sẽ main agent (khi spawn) kiểm soát đừng hard"
354
364
  const _subCeiling = isExplore ? resolveCeiling("analyze", "large") : resolveCeiling("general", "medium");
355
365
  const _subCounterKey = `subagent:${subCallId}`;
356
366
  const _subStopWhen = (async (state) => {
367
+ incSessionStep(_subCounterKey); // Keep telemetry counter ticking
357
368
  if (state.steps.length >= maxSteps)
358
369
  return true;
359
- const next = incSessionStep(_subCounterKey);
360
- return next >= _subCeiling;
370
+ return false;
361
371
  });
362
372
  // Silent-hang guard — mirror the top-level loop (message-processor.ts).
363
373
  // A sub-agent provider connection can accept the request but never send a
@@ -436,12 +446,19 @@ export class StreamRunner {
436
446
  /* fail-open */
437
447
  }
438
448
  };
449
+ // T1.1 + T1.2 — reasoning models (DeepSeek V4 Flash, R1) emit 2K-5K
450
+ // CoT tokens per turn that accumulate across the multi-step loop.
451
+ // Strip old reasoning and compact earlier (ratio 0.3 vs 0.5) to
452
+ // cut ~40-60% of cumulative input tokens.
453
+ const isReasoningModel = childRuntime.modelInfo?.reasoning === true;
439
454
  const compacted = compactSubAgentMessages(stripped, {
440
455
  thresholdChars: compactThreshold,
441
456
  keepLastTurns: compactKeepLast,
442
457
  contextWindowTokens: childCtxWindow,
458
+ contextFillRatio: isReasoningModel ? 0.3 : undefined,
443
459
  keepToolIds: subKeepToolIds.length ? subKeepToolIds : undefined,
444
460
  persistArtifact: persistSubArtifact,
461
+ stripOldReasoning: isReasoningModel,
445
462
  });
446
463
  if (compacted !== stripped)
447
464
  recordCompaction(stepNumber);
@@ -456,21 +473,26 @@ export class StreamRunner {
456
473
  const _subK = cadenceForSize(_subSize);
457
474
  const _subShouldRemind = shouldInjectReminder(stepNumber, _subK);
458
475
  const _subShouldWarn = shouldInjectSoftWarn(stepNumber, _subCeiling, _subCounterKey);
459
- if (_subShouldRemind || _subShouldWarn) {
460
- const _baseReminder = buildScopeReminder({
461
- step: stepNumber,
462
- ceiling: _subCeiling,
463
- taskType: "general",
464
- size: _subSize,
465
- originalPrompt: prepared.request.prompt,
466
- });
467
- const _reminder = _subShouldWarn ? `[approaching ceiling] ${_baseReminder}` : _baseReminder;
468
- const withReminder = attachReminderToMessages(compacted, _reminder);
469
- return { messages: withReminder };
476
+ const finalMessages = (() => {
477
+ if (_subShouldRemind || _subShouldWarn) {
478
+ const _baseReminder = buildScopeReminder({
479
+ step: stepNumber,
480
+ ceiling: _subCeiling,
481
+ taskType: "general",
482
+ size: _subSize,
483
+ originalPrompt: prepared.request.prompt,
484
+ });
485
+ const _reminder = _subShouldWarn ? `[approaching ceiling] ${_baseReminder}` : _baseReminder;
486
+ return attachReminderToMessages(compacted, _reminder);
487
+ }
488
+ return compacted;
489
+ })();
490
+ if (childRuntime.modelId.startsWith("claude")) {
491
+ return { messages: applyAnthropicPromptCaching(finalMessages, childRuntime.modelId) };
470
492
  }
471
493
  if (compacted === stripped && stripped === messages)
472
494
  return undefined;
473
- return { messages: compacted };
495
+ return { messages: finalMessages };
474
496
  },
475
497
  ...(childDropTemperature ? {} : { temperature: isExplore ? 0.2 : 0.5 }),
476
498
  ...(childDropMaxOutput ? {} : { maxOutputTokens: Math.min(this.deps.getMaxTokens(), 8_192) }),
@@ -64,6 +64,8 @@ export interface SubAgentCapState {
64
64
  cumulative: number;
65
65
  /** Configured ceiling. */
66
66
  max: number;
67
+ /** Hard cap ceiling (e.g. max * 2). */
68
+ hardMax?: number;
67
69
  /** True once `cumulative >= max` (sub-agent should wrap up). */
68
70
  exhausted: boolean;
69
71
  /** Number of duplicate-output detections (telemetry / tests). */
@@ -46,7 +46,9 @@ function shortHash(text) {
46
46
  return createHash("sha1").update(text).digest("hex").slice(0, 12);
47
47
  }
48
48
  export function compressForCap(state, raw) {
49
- if (state.exhausted) {
49
+ const hardCeiling = state.hardMax ?? state.max;
50
+ if (state.exhausted || state.cumulative >= hardCeiling) {
51
+ state.exhausted = true;
50
52
  return `[${state.label} tool budget exhausted (${state.cumulative}/${state.max} chars). Further tool calls will return this stub. Summarize findings now and return.]`;
51
53
  }
52
54
  state.callIndex += 1;
@@ -68,7 +70,11 @@ export function compressForCap(state, raw) {
68
70
  }
69
71
  const ratio = state.cumulative / state.max;
70
72
  let out;
71
- if (ratio >= state.highTierRatio) {
73
+ if (state.cumulative >= state.max) {
74
+ const trimmed = trimHead(raw, state.highTierChars, state.label);
75
+ out = `${trimmed}\n\n[Warning: ${state.label} tool budget exceeded (${state.cumulative}/${state.max} chars). Please finalize your work and summarize findings now.]`;
76
+ }
77
+ else if (ratio >= state.highTierRatio) {
72
78
  out = trimHead(raw, state.highTierChars, state.label);
73
79
  }
74
80
  else if (ratio >= state.midTierRatio) {
@@ -78,7 +84,7 @@ export function compressForCap(state, raw) {
78
84
  out = raw;
79
85
  }
80
86
  state.cumulative += out.length;
81
- if (state.cumulative >= state.max)
87
+ if (state.cumulative >= hardCeiling)
82
88
  state.exhausted = true;
83
89
  return out;
84
90
  }
@@ -141,9 +147,11 @@ function wrapInternal(tools, state) {
141
147
  * cumulative state.
142
148
  */
143
149
  export function wrapToolSetWithCap(tools, opts = {}) {
150
+ const max = Math.max(20_000, opts.maxCumulativeChars ?? DEFAULT_MAX_CUMULATIVE_CHARS);
144
151
  const state = {
145
152
  cumulative: 0,
146
- max: Math.max(20_000, opts.maxCumulativeChars ?? DEFAULT_MAX_CUMULATIVE_CHARS),
153
+ max,
154
+ hardMax: max * 2,
147
155
  exhausted: false,
148
156
  dedupHits: 0,
149
157
  seenHashes: new Map(),
@@ -24,7 +24,7 @@ export const TASK_TIER_PREFS = {
24
24
  // general sub-agents downgrade to balanced (was premium-first) — the top
25
25
  // model already planned the delegation; the focused execution doesn't need
26
26
  // premium. Premium remains the fallback so premium-only providers are unaffected.
27
- general: ["balanced", "premium"],
27
+ general: ["balanced", "fast", "premium"],
28
28
  // verify keeps premium-first: a downgrade here would weaken the very check it
29
29
  // exists to perform. Mirrors council `verify` role preference.
30
30
  verify: ["premium", "balanced"],
@@ -96,6 +96,14 @@ export interface SubAgentCompactorOptions {
96
96
  * source:"tool-artifact" so layer3/ee.query can later fetch "full tool result id=xxx".
97
97
  */
98
98
  persistArtifact?: (toolCallId: string, toolName: string, fullContent: string, reason: string) => void;
99
+ /**
100
+ * T1.1 — strip reasoning parts from old assistant turns (older than
101
+ * keepLastTurns). Reasoning tokens (CoT / <think>) from prior turns are
102
+ * never re-read by the model but accumulate O(N) chars per turn, costing
103
+ * full input billing on every subsequent step. Default true for reasoning
104
+ * models, false otherwise.
105
+ */
106
+ stripOldReasoning?: boolean;
99
107
  }
100
108
  /**
101
109
  * G1 — coarse char→token conversion. The real ratio is provider/tokenizer
@@ -140,3 +148,16 @@ export declare function cumulativeMessageChars(messages: ReadonlyArray<ModelMess
140
148
  * step.
141
149
  */
142
150
  export declare function compactSubAgentMessages(messages: ReadonlyArray<ModelMessage>, opts?: SubAgentCompactorOptions): ModelMessage[];
151
+ /**
152
+ * Injects Anthropic prompt caching (cacheControl) into the last message's content
153
+ * block(s) if the model is Claude (starts with 'claude').
154
+ * Creates a copy of the messages array and the last message to avoid mutating in-place.
155
+ */
156
+ export declare function applyAnthropicPromptCaching(messages: readonly ModelMessage[], modelId: string): ModelMessage[];
157
+ /**
158
+ * Safely slice message history to keep at most `maxMessages` messages.
159
+ * Preserves the system message(s) at the front and ensures that the slice
160
+ * starts with a "user" message and does not split assistant tool calls and
161
+ * corresponding tool results.
162
+ */
163
+ export declare function sliceMessageHistory(messages: ReadonlyArray<ModelMessage>, maxMessages?: number): ModelMessage[];