muonroi-cli 1.8.4 → 1.8.5

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 (458) hide show
  1. package/LICENSE +17 -5
  2. package/dist/packages/agent-harness-core/src/driver.d.ts +27 -1
  3. package/dist/packages/agent-harness-core/src/driver.js +46 -0
  4. package/dist/packages/agent-harness-core/src/event-tee.d.ts +48 -0
  5. package/dist/packages/agent-harness-core/src/event-tee.js +77 -0
  6. package/dist/packages/agent-harness-core/src/mcp-server.d.ts +11 -0
  7. package/dist/packages/agent-harness-core/src/mcp-server.js +87 -15
  8. package/dist/packages/agent-harness-core/src/protocol.d.ts +66 -2
  9. package/dist/packages/agent-harness-core/src/protocol.js +15 -0
  10. package/dist/packages/agent-harness-core/src/visual-quality.d.ts +58 -0
  11. package/dist/packages/agent-harness-core/src/visual-quality.js +141 -0
  12. package/dist/packages/agent-harness-opentui/src/agent-mode.d.ts +6 -0
  13. package/dist/packages/agent-harness-opentui/src/agent-mode.js +14 -1
  14. package/dist/packages/agent-harness-opentui/src/input-bridge.d.ts +2 -10
  15. package/dist/packages/agent-harness-opentui/src/input-bridge.js +103 -16
  16. package/dist/packages/agent-harness-opentui/src/install.d.ts +8 -0
  17. package/dist/packages/agent-harness-opentui/src/install.js +10 -0
  18. package/dist/packages/agent-harness-opentui/src/semantic.js +12 -10
  19. package/dist/packages/agent-harness-opentui/src/visual-capture.d.ts +56 -0
  20. package/dist/packages/agent-harness-opentui/src/visual-capture.js +103 -0
  21. package/dist/src/agent-harness/mock-model.d.ts +28 -0
  22. package/dist/src/agent-harness/mock-model.js +63 -1
  23. package/dist/src/agent-harness/test-spawn.js +31 -0
  24. package/dist/src/cli/config/screen-providers.js +1 -1
  25. package/dist/src/cli/cost-forensics.d.ts +10 -0
  26. package/dist/src/cli/cost-forensics.js +18 -3
  27. package/dist/src/cli/keys-bundle.d.ts +1 -1
  28. package/dist/src/cli/keys-bundle.js +1 -1
  29. package/dist/src/cli/keys.d.ts +2 -2
  30. package/dist/src/cli/keys.js +19 -81
  31. package/dist/src/council/clarifier.d.ts +28 -2
  32. package/dist/src/council/clarifier.js +81 -15
  33. package/dist/src/council/context.js +49 -15
  34. package/dist/src/council/debate-checkpoint.d.ts +129 -0
  35. package/dist/src/council/debate-checkpoint.js +176 -0
  36. package/dist/src/council/debate-planner.js +51 -3
  37. package/dist/src/council/debate-summary.d.ts +25 -0
  38. package/dist/src/council/debate-summary.js +85 -0
  39. package/dist/src/council/debate.d.ts +169 -2
  40. package/dist/src/council/debate.js +1210 -134
  41. package/dist/src/council/index.d.ts +85 -1
  42. package/dist/src/council/index.js +634 -196
  43. package/dist/src/council/leader.d.ts +26 -0
  44. package/dist/src/council/leader.js +150 -9
  45. package/dist/src/council/llm.d.ts +32 -0
  46. package/dist/src/council/llm.js +231 -38
  47. package/dist/src/council/panel-select.d.ts +30 -0
  48. package/dist/src/council/panel-select.js +72 -0
  49. package/dist/src/council/planner.js +23 -0
  50. package/dist/src/council/preflight.d.ts +7 -0
  51. package/dist/src/council/preflight.js +14 -2
  52. package/dist/src/council/prompts.d.ts +30 -3
  53. package/dist/src/council/prompts.js +234 -64
  54. package/dist/src/council/stance-recall.d.ts +42 -0
  55. package/dist/src/council/stance-recall.js +57 -0
  56. package/dist/src/council/strip-think.d.ts +17 -0
  57. package/dist/src/council/strip-think.js +33 -0
  58. package/dist/src/council/types.d.ts +128 -0
  59. package/dist/src/ee/artifact-cache.d.ts +16 -0
  60. package/dist/src/ee/artifact-cache.js +32 -0
  61. package/dist/src/ee/auth.d.ts +1 -0
  62. package/dist/src/ee/auth.js +15 -2
  63. package/dist/src/ee/bridge.d.ts +10 -0
  64. package/dist/src/ee/bridge.js +58 -0
  65. package/dist/src/ee/client.js +81 -18
  66. package/dist/src/ee/export-transcripts.d.ts +1 -0
  67. package/dist/src/ee/export-transcripts.js +8 -10
  68. package/dist/src/ee/extract-session.js +29 -0
  69. package/dist/src/ee/extract-style.d.ts +58 -0
  70. package/dist/src/ee/extract-style.js +270 -0
  71. package/dist/src/ee/recall-ledger.d.ts +9 -0
  72. package/dist/src/ee/recall-ledger.js +3 -0
  73. package/dist/src/ee/scope.d.ts +1 -0
  74. package/dist/src/ee/scope.js +26 -1
  75. package/dist/src/ee/search.d.ts +7 -0
  76. package/dist/src/ee/search.js +24 -0
  77. package/dist/src/ee/transcript-emit.js +2 -0
  78. package/dist/src/ee/types.d.ts +22 -0
  79. package/dist/src/ee/who-am-i-brain.d.ts +35 -0
  80. package/dist/src/ee/who-am-i-brain.js +220 -0
  81. package/dist/src/ee/who-am-i.d.ts +10 -3
  82. package/dist/src/ee/who-am-i.js +12 -0
  83. package/dist/src/ee/workflow-event.d.ts +48 -0
  84. package/dist/src/ee/workflow-event.js +81 -0
  85. package/dist/src/flow/compaction/compress.d.ts +3 -3
  86. package/dist/src/flow/compaction/compress.js +45 -8
  87. package/dist/src/flow/compaction/extract.d.ts +4 -7
  88. package/dist/src/flow/compaction/extract.js +50 -10
  89. package/dist/src/flow/compaction/index.d.ts +13 -1
  90. package/dist/src/flow/compaction/index.js +70 -3
  91. package/dist/src/flow/compaction/input-guard.d.ts +24 -0
  92. package/dist/src/flow/compaction/input-guard.js +43 -0
  93. package/dist/src/flow/fold-planning.d.ts +36 -0
  94. package/dist/src/flow/fold-planning.js +83 -0
  95. package/dist/src/flow/hierarchy.d.ts +146 -0
  96. package/dist/src/flow/hierarchy.js +427 -0
  97. package/dist/src/flow/index.d.ts +1 -0
  98. package/dist/src/flow/index.js +2 -0
  99. package/dist/src/flow/run-artifacts.d.ts +102 -0
  100. package/dist/src/flow/run-artifacts.js +208 -0
  101. package/dist/src/generated/version.d.ts +1 -1
  102. package/dist/src/generated/version.js +1 -1
  103. package/dist/src/gsd/assessment-schema.d.ts +44 -0
  104. package/dist/src/gsd/assessment-schema.js +134 -0
  105. package/dist/src/gsd/capability-registry.d.ts +45 -0
  106. package/dist/src/gsd/capability-registry.js +337 -0
  107. package/dist/src/gsd/complexity-assessor.d.ts +39 -0
  108. package/dist/src/gsd/complexity-assessor.js +152 -0
  109. package/dist/src/gsd/config-bridge.d.ts +7 -0
  110. package/dist/src/gsd/config-bridge.js +114 -0
  111. package/dist/src/gsd/config-loader.d.ts +27 -0
  112. package/dist/src/gsd/config-loader.js +50 -0
  113. package/dist/src/gsd/council-context.d.ts +44 -0
  114. package/dist/src/gsd/council-context.js +114 -0
  115. package/dist/src/gsd/ee-closure.d.ts +28 -0
  116. package/dist/src/gsd/ee-closure.js +49 -0
  117. package/dist/src/gsd/flags.d.ts +55 -0
  118. package/dist/src/gsd/flags.js +83 -0
  119. package/dist/src/gsd/gsd-dispatch.d.ts +58 -0
  120. package/dist/src/gsd/gsd-dispatch.js +131 -0
  121. package/dist/src/gsd/gsd-runtime.d.ts +22 -0
  122. package/dist/src/gsd/gsd-runtime.js +37 -0
  123. package/dist/src/gsd/host-adapter.d.ts +11 -0
  124. package/dist/src/gsd/host-adapter.js +29 -0
  125. package/dist/src/gsd/index.d.ts +24 -1
  126. package/dist/src/gsd/index.js +27 -0
  127. package/dist/src/gsd/loop-host-contract.d.ts +21 -0
  128. package/dist/src/gsd/loop-host-contract.js +39 -0
  129. package/dist/src/gsd/loop-host.d.ts +69 -0
  130. package/dist/src/gsd/loop-host.js +245 -0
  131. package/dist/src/gsd/loop-resolver.d.ts +36 -0
  132. package/dist/src/gsd/loop-resolver.js +79 -0
  133. package/dist/src/gsd/model-tier.d.ts +13 -0
  134. package/dist/src/gsd/model-tier.js +45 -0
  135. package/dist/src/gsd/mutation-gate.d.ts +16 -0
  136. package/dist/src/gsd/mutation-gate.js +41 -0
  137. package/dist/src/gsd/native-roadmap.d.ts +89 -0
  138. package/dist/src/gsd/native-roadmap.js +343 -0
  139. package/dist/src/gsd/native-state.d.ts +47 -0
  140. package/dist/src/gsd/native-state.js +220 -0
  141. package/dist/src/gsd/paths.d.ts +23 -0
  142. package/dist/src/gsd/paths.js +66 -0
  143. package/dist/src/gsd/phase-dag.d.ts +12 -0
  144. package/dist/src/gsd/phase-dag.js +94 -0
  145. package/dist/src/gsd/phase-sync.d.ts +42 -0
  146. package/dist/src/gsd/phase-sync.js +321 -0
  147. package/dist/src/gsd/pil-gate-context.d.ts +13 -0
  148. package/dist/src/gsd/pil-gate-context.js +64 -0
  149. package/dist/src/gsd/pil-gate-critic.d.ts +19 -0
  150. package/dist/src/gsd/pil-gate-critic.js +74 -0
  151. package/dist/src/gsd/plan-council-prompts.d.ts +25 -0
  152. package/dist/src/gsd/plan-council-prompts.js +79 -0
  153. package/dist/src/gsd/plan-council.d.ts +44 -0
  154. package/dist/src/gsd/plan-council.js +251 -0
  155. package/dist/src/gsd/plan-gate-vocabulary.d.ts +40 -0
  156. package/dist/src/gsd/plan-gate-vocabulary.js +64 -0
  157. package/dist/src/gsd/product-workspace.d.ts +13 -0
  158. package/dist/src/gsd/product-workspace.js +124 -0
  159. package/dist/src/gsd/ship-bridge.d.ts +25 -0
  160. package/dist/src/gsd/ship-bridge.js +65 -0
  161. package/dist/src/gsd/state-document.d.ts +40 -0
  162. package/dist/src/gsd/state-document.js +163 -0
  163. package/dist/src/gsd/verdict-schema.d.ts +39 -0
  164. package/dist/src/gsd/verdict-schema.js +144 -0
  165. package/dist/src/gsd/verify-context.d.ts +22 -0
  166. package/dist/src/gsd/verify-context.js +27 -0
  167. package/dist/src/gsd/verify-council-prompts.d.ts +19 -0
  168. package/dist/src/gsd/verify-council-prompts.js +85 -0
  169. package/dist/src/gsd/verify-council.d.ts +25 -0
  170. package/dist/src/gsd/verify-council.js +119 -0
  171. package/dist/src/gsd/verify-gate-vocabulary.d.ts +25 -0
  172. package/dist/src/gsd/verify-gate-vocabulary.js +46 -0
  173. package/dist/src/gsd/workflow-engine.d.ts +60 -0
  174. package/dist/src/gsd/workflow-engine.js +207 -0
  175. package/dist/src/gsd/workflow-tools.d.ts +13 -0
  176. package/dist/src/gsd/workflow-tools.js +277 -0
  177. package/dist/src/hooks/index.js +1 -1
  178. package/dist/src/index.js +44 -11
  179. package/dist/src/maintain/pr-builder.js +23 -13
  180. package/dist/src/mcp/auto-setup.js +57 -32
  181. package/dist/src/mcp/client-pool.js +1 -1
  182. package/dist/src/mcp/ee-tools.js +1 -0
  183. package/dist/src/mcp/research-onboarding.js +8 -7
  184. package/dist/src/mcp/runtime.js +34 -2
  185. package/dist/src/models/catalog-client.d.ts +87 -0
  186. package/dist/src/models/catalog-client.js +105 -38
  187. package/dist/src/models/catalog.json +528 -265
  188. package/dist/src/models/registry.d.ts +22 -7
  189. package/dist/src/models/registry.js +73 -10
  190. package/dist/src/ops/doctor.js +1 -1
  191. package/dist/src/orchestrator/auto-commit.js +1 -1
  192. package/dist/src/orchestrator/batch-turn-runner.js +2 -2
  193. package/dist/src/orchestrator/cache-prefix.d.ts +67 -0
  194. package/dist/src/orchestrator/cache-prefix.js +83 -0
  195. package/dist/src/orchestrator/compact-request.d.ts +32 -0
  196. package/dist/src/orchestrator/compact-request.js +41 -0
  197. package/dist/src/orchestrator/compaction.d.ts +10 -0
  198. package/dist/src/orchestrator/compaction.js +27 -7
  199. package/dist/src/orchestrator/council-manager.d.ts +12 -3
  200. package/dist/src/orchestrator/council-manager.js +65 -24
  201. package/dist/src/orchestrator/cross-turn-dedup.d.ts +6 -0
  202. package/dist/src/orchestrator/cross-turn-dedup.js +22 -1
  203. package/dist/src/orchestrator/error-utils.d.ts +29 -0
  204. package/dist/src/orchestrator/error-utils.js +132 -24
  205. package/dist/src/orchestrator/grounding-check.js +39 -1
  206. package/dist/src/orchestrator/message-processor.js +242 -33
  207. package/dist/src/orchestrator/orchestrator.d.ts +39 -3
  208. package/dist/src/orchestrator/orchestrator.js +651 -102
  209. package/dist/src/orchestrator/preprocessor.js +1 -1
  210. package/dist/src/orchestrator/proactive-compact-detector.d.ts +26 -0
  211. package/dist/src/orchestrator/proactive-compact-detector.js +36 -0
  212. package/dist/src/orchestrator/prompts.js +17 -17
  213. package/dist/src/orchestrator/reactive-delegation.d.ts +39 -0
  214. package/dist/src/orchestrator/reactive-delegation.js +59 -0
  215. package/dist/src/orchestrator/retry-classifier.d.ts +2 -1
  216. package/dist/src/orchestrator/retry-classifier.js +46 -2
  217. package/dist/src/orchestrator/safety-intercept.d.ts +45 -0
  218. package/dist/src/orchestrator/safety-intercept.js +55 -0
  219. package/dist/src/orchestrator/scope-reminder.js +1 -1
  220. package/dist/src/orchestrator/session-experience.d.ts +2 -1
  221. package/dist/src/orchestrator/session-experience.js +2 -1
  222. package/dist/src/orchestrator/should-run-gate.d.ts +5 -0
  223. package/dist/src/orchestrator/should-run-gate.js +18 -0
  224. package/dist/src/orchestrator/stall-watchdog.d.ts +24 -3
  225. package/dist/src/orchestrator/stall-watchdog.js +47 -13
  226. package/dist/src/orchestrator/stream-runner.js +62 -29
  227. package/dist/src/orchestrator/sub-agent-cap.d.ts +13 -1
  228. package/dist/src/orchestrator/sub-agent-cap.js +16 -1
  229. package/dist/src/orchestrator/sub-agent-model-tier.d.ts +13 -1
  230. package/dist/src/orchestrator/sub-agent-model-tier.js +16 -2
  231. package/dist/src/orchestrator/subagent-compactor.d.ts +53 -1
  232. package/dist/src/orchestrator/subagent-compactor.js +126 -15
  233. package/dist/src/orchestrator/tool-engine.d.ts +22 -0
  234. package/dist/src/orchestrator/tool-engine.js +620 -56
  235. package/dist/src/orchestrator/tool-limit-auto-recover.d.ts +22 -0
  236. package/dist/src/orchestrator/tool-limit-auto-recover.js +30 -0
  237. package/dist/src/orchestrator/turn-runner-deps.d.ts +19 -0
  238. package/dist/src/orchestrator/turn-watchdog.d.ts +37 -0
  239. package/dist/src/orchestrator/turn-watchdog.js +55 -0
  240. package/dist/src/pil/agent-operating-contract.d.ts +1 -1
  241. package/dist/src/pil/agent-operating-contract.js +1 -1
  242. package/dist/src/pil/cheap-model-playbook.d.ts +1 -1
  243. package/dist/src/pil/cheap-model-playbook.js +5 -1
  244. package/dist/src/pil/cheap-model-workbooks.d.ts +1 -1
  245. package/dist/src/pil/cheap-model-workbooks.js +1 -1
  246. package/dist/src/pil/discovery-types.d.ts +1 -0
  247. package/dist/src/pil/discovery.js +16 -11
  248. package/dist/src/pil/layer1-intent.d.ts +18 -6
  249. package/dist/src/pil/layer1-intent.js +66 -757
  250. package/dist/src/pil/layer15-context-scan.js +15 -1
  251. package/dist/src/pil/layer3-ee-injection.js +23 -8
  252. package/dist/src/pil/layer4-gsd.js +69 -16
  253. package/dist/src/pil/layer5-context.js +7 -3
  254. package/dist/src/pil/layer6-output.d.ts +23 -0
  255. package/dist/src/pil/layer6-output.js +5 -1
  256. package/dist/src/pil/llm-classify.d.ts +33 -2
  257. package/dist/src/pil/llm-classify.js +123 -131
  258. package/dist/src/pil/native-capabilities-workbook.d.ts +1 -1
  259. package/dist/src/pil/native-capabilities-workbook.js +1 -0
  260. package/dist/src/pil/pipeline.js +34 -2
  261. package/dist/src/pil/response-tools.js +5 -3
  262. package/dist/src/pil/schema.d.ts +1 -0
  263. package/dist/src/pil/schema.js +2 -0
  264. package/dist/src/pil/types.d.ts +18 -0
  265. package/dist/src/playbook/directives.d.ts +4 -0
  266. package/dist/src/playbook/directives.js +17 -5
  267. package/dist/src/product-loop/backlog-builder.d.ts +14 -1
  268. package/dist/src/product-loop/backlog-builder.js +30 -6
  269. package/dist/src/product-loop/discovery-context-format.js +3 -1
  270. package/dist/src/product-loop/discovery-ecosystem.js +4 -1
  271. package/dist/src/product-loop/discovery-interview.js +32 -3
  272. package/dist/src/product-loop/discovery-schema.js +5 -1
  273. package/dist/src/product-loop/ideal-trace.d.ts +7 -0
  274. package/dist/src/product-loop/ideal-trace.js +64 -0
  275. package/dist/src/product-loop/index.d.ts +13 -1
  276. package/dist/src/product-loop/index.js +333 -52
  277. package/dist/src/product-loop/loop-driver.d.ts +7 -0
  278. package/dist/src/product-loop/loop-driver.js +310 -99
  279. package/dist/src/product-loop/phase-plan.d.ts +5 -0
  280. package/dist/src/product-loop/phase-plan.js +39 -2
  281. package/dist/src/product-loop/phase-runner.js +9 -1
  282. package/dist/src/product-loop/sprint-runner.d.ts +111 -0
  283. package/dist/src/product-loop/sprint-runner.js +559 -16
  284. package/dist/src/product-loop/types.d.ts +36 -5
  285. package/dist/src/providers/adapter.d.ts +1 -1
  286. package/dist/src/providers/adapter.js +3 -4
  287. package/dist/src/providers/auth/browser-flow.d.ts +1 -1
  288. package/dist/src/providers/auth/browser-flow.js +1 -1
  289. package/dist/src/providers/auth/openai-oauth.js +1 -1
  290. package/dist/src/providers/auth/registry.js +0 -34
  291. package/dist/src/providers/auth/token-store.js +4 -1
  292. package/dist/src/providers/auth/types.d.ts +1 -1
  293. package/dist/src/providers/auth/types.js +1 -1
  294. package/dist/src/providers/capabilities.d.ts +24 -5
  295. package/dist/src/providers/capabilities.js +42 -24
  296. package/dist/src/providers/endpoints.d.ts +2 -2
  297. package/dist/src/providers/endpoints.js +11 -10
  298. package/dist/src/providers/keychain.d.ts +1 -1
  299. package/dist/src/providers/keychain.js +7 -9
  300. package/dist/src/providers/mcp-vision-bridge.js +56 -146
  301. package/dist/src/providers/openai-compatible.js +8 -1
  302. package/dist/src/providers/pricing.d.ts +2 -2
  303. package/dist/src/providers/pricing.js +3 -13
  304. package/dist/src/providers/runtime.d.ts +27 -2
  305. package/dist/src/providers/runtime.js +78 -15
  306. package/dist/src/providers/strategies/base.strategy.d.ts +16 -0
  307. package/dist/src/providers/strategies/base.strategy.js +24 -1
  308. package/dist/src/providers/strategies/{siliconflow.strategy.d.ts → opencode-go.strategy.d.ts} +3 -3
  309. package/dist/src/providers/strategies/opencode-go.strategy.js +83 -0
  310. package/dist/src/providers/strategies/registry.js +4 -4
  311. package/dist/src/providers/strategies/thinking-mode.d.ts +109 -0
  312. package/dist/src/providers/strategies/thinking-mode.js +280 -1
  313. package/dist/src/providers/strategies/zai.strategy.d.ts +14 -0
  314. package/dist/src/providers/strategies/zai.strategy.js +44 -0
  315. package/dist/src/providers/types.d.ts +5 -6
  316. package/dist/src/providers/types.js +2 -2
  317. package/dist/src/providers/vision-backend.d.ts +47 -0
  318. package/dist/src/providers/vision-backend.js +258 -0
  319. package/dist/src/providers/vision-proxy.d.ts +22 -9
  320. package/dist/src/providers/vision-proxy.js +63 -132
  321. package/dist/src/providers/wire-debug.js +95 -0
  322. package/dist/src/router/decide.d.ts +13 -0
  323. package/dist/src/router/decide.js +138 -36
  324. package/dist/src/router/peak-hour.d.ts +38 -0
  325. package/dist/src/router/peak-hour.js +107 -0
  326. package/dist/src/router/step-router.js +3 -2
  327. package/dist/src/router/warm.js +4 -5
  328. package/dist/src/scaffold/continuation-prompt.d.ts +11 -0
  329. package/dist/src/scaffold/continuation-prompt.js +26 -0
  330. package/dist/src/scaffold/point-to-existing.d.ts +21 -0
  331. package/dist/src/scaffold/point-to-existing.js +25 -0
  332. package/dist/src/self-qa/agentic-loop.js +3 -3
  333. package/dist/src/{ui/state → state}/active-run.d.ts +19 -0
  334. package/dist/src/{ui/state → state}/active-run.js +21 -0
  335. package/dist/src/{ui/status-bar/store.d.ts → state/status-bar-store.d.ts} +4 -1
  336. package/dist/src/{ui/status-bar/store.js → state/status-bar-store.js} +12 -9
  337. package/dist/src/state/turn-trace.d.ts +43 -0
  338. package/dist/src/state/turn-trace.js +32 -0
  339. package/dist/src/storage/db.js +2 -1
  340. package/dist/src/storage/index.d.ts +1 -1
  341. package/dist/src/storage/index.js +1 -1
  342. package/dist/src/storage/interaction-log.d.ts +1 -1
  343. package/dist/src/storage/migrations.js +71 -1
  344. package/dist/src/storage/sessions.d.ts +28 -10
  345. package/dist/src/storage/sessions.js +78 -21
  346. package/dist/src/storage/transcript-view.js +1 -1
  347. package/dist/src/storage/transcript.d.ts +51 -0
  348. package/dist/src/storage/transcript.js +284 -13
  349. package/dist/src/tools/file.d.ts +15 -0
  350. package/dist/src/tools/file.js +32 -0
  351. package/dist/src/tools/native-tools.js +5 -0
  352. package/dist/src/tools/registry.d.ts +3 -0
  353. package/dist/src/tools/registry.js +460 -22
  354. package/dist/src/tools/research.d.ts +29 -0
  355. package/dist/src/tools/research.js +233 -0
  356. package/dist/src/types/index.d.ts +118 -3
  357. package/dist/src/ui/app.js +0 -0
  358. package/dist/src/ui/cards/product-status-card.js +1 -1
  359. package/dist/src/ui/components/bubble-body-guard.d.ts +27 -0
  360. package/dist/src/ui/components/bubble-body-guard.js +50 -0
  361. package/dist/src/ui/components/context-rail.d.ts +26 -0
  362. package/dist/src/ui/components/context-rail.js +33 -0
  363. package/dist/src/ui/components/council-conclusion-card.d.ts +73 -0
  364. package/dist/src/ui/components/council-conclusion-card.js +420 -0
  365. package/dist/src/ui/components/council-debate-pill.d.ts +36 -0
  366. package/dist/src/ui/components/council-debate-pill.js +34 -0
  367. package/dist/src/ui/components/council-info-card.js +2 -2
  368. package/dist/src/ui/components/council-leader-bubble.d.ts +10 -3
  369. package/dist/src/ui/components/council-leader-bubble.js +21 -11
  370. package/dist/src/ui/components/council-message-bubble.d.ts +15 -2
  371. package/dist/src/ui/components/council-message-bubble.js +16 -15
  372. package/dist/src/ui/components/council-phase-timeline.d.ts +9 -1
  373. package/dist/src/ui/components/council-phase-timeline.js +49 -15
  374. package/dist/src/ui/components/council-placeholder-bubble.d.ts +16 -9
  375. package/dist/src/ui/components/council-placeholder-bubble.js +32 -29
  376. package/dist/src/ui/components/council-question-card.js +12 -12
  377. package/dist/src/ui/components/council-rail-rounds.d.ts +26 -0
  378. package/dist/src/ui/components/council-rail-rounds.js +57 -0
  379. package/dist/src/ui/components/council-round-group.d.ts +38 -0
  380. package/dist/src/ui/components/council-round-group.js +88 -0
  381. package/dist/src/ui/components/council-status-list.d.ts +3 -1
  382. package/dist/src/ui/components/council-status-list.js +36 -24
  383. package/dist/src/ui/components/council-synthesis-banner.d.ts +7 -2
  384. package/dist/src/ui/components/council-synthesis-banner.js +20 -5
  385. package/dist/src/ui/components/halt-recovery-card.js +9 -5
  386. package/dist/src/ui/components/jump-to-latest-pill.d.ts +11 -0
  387. package/dist/src/ui/components/jump-to-latest-pill.js +14 -0
  388. package/dist/src/ui/components/prompt-box.js +18 -16
  389. package/dist/src/ui/components/session-tree-card.d.ts +14 -0
  390. package/dist/src/ui/components/session-tree-card.js +46 -0
  391. package/dist/src/ui/components/slash-inline-menu.d.ts +12 -0
  392. package/dist/src/ui/components/slash-inline-menu.js +26 -5
  393. package/dist/src/ui/components/task-list-panel.d.ts +14 -1
  394. package/dist/src/ui/components/task-list-panel.js +22 -2
  395. package/dist/src/ui/containers/modals-layer.d.ts +2 -1
  396. package/dist/src/ui/hooks/use-session-picker.d.ts +3 -3
  397. package/dist/src/ui/mcp-modal.js +2 -4
  398. package/dist/src/ui/modals/api-key-modal.js +1 -1
  399. package/dist/src/ui/modals/connect-modal.js +4 -3
  400. package/dist/src/ui/modals/session-picker-modal.d.ts +2 -2
  401. package/dist/src/ui/modals/session-picker-modal.js +3 -5
  402. package/dist/src/ui/picker-providers.d.ts +1 -1
  403. package/dist/src/ui/picker-providers.js +1 -1
  404. package/dist/src/ui/primitives/index.d.ts +1 -0
  405. package/dist/src/ui/primitives/index.js +2 -0
  406. package/dist/src/ui/primitives/semantic-primitives.d.ts +76 -0
  407. package/dist/src/ui/primitives/semantic-primitives.js +81 -0
  408. package/dist/src/ui/slash/compact.js +5 -7
  409. package/dist/src/ui/slash/cost.js +1 -1
  410. package/dist/src/ui/slash/council.js +19 -1
  411. package/dist/src/ui/slash/debug.d.ts +3 -31
  412. package/dist/src/ui/slash/debug.js +9 -20
  413. package/dist/src/ui/slash/ideal.d.ts +6 -2
  414. package/dist/src/ui/slash/ideal.js +97 -7
  415. package/dist/src/ui/slash/menu-items.d.ts +7 -0
  416. package/dist/src/ui/slash/menu-items.js +12 -18
  417. package/dist/src/ui/slash/registry.d.ts +2 -0
  418. package/dist/src/ui/slash/registry.js +4 -0
  419. package/dist/src/ui/status-bar/cache-hit.d.ts +6 -0
  420. package/dist/src/ui/status-bar/cache-hit.js +9 -0
  421. package/dist/src/ui/status-bar/index.d.ts +1 -1
  422. package/dist/src/ui/status-bar/index.js +7 -3
  423. package/dist/src/ui/status-bar/usd-meter.d.ts +5 -4
  424. package/dist/src/ui/status-bar/usd-meter.js +6 -4
  425. package/dist/src/ui/theme.d.ts +1 -0
  426. package/dist/src/ui/theme.js +2 -0
  427. package/dist/src/ui/types.d.ts +7 -0
  428. package/dist/src/ui/use-app-logic.js +0 -0
  429. package/dist/src/ui/utils/format.d.ts +14 -0
  430. package/dist/src/ui/utils/format.js +23 -3
  431. package/dist/src/usage/downgrade.js +2 -2
  432. package/dist/src/usage/product-ledger.js +2 -2
  433. package/dist/src/utils/install-manager.js +2 -1
  434. package/dist/src/utils/logger.js +2 -2
  435. package/dist/src/utils/permission-mode.js +5 -3
  436. package/dist/src/utils/redactor.js +1 -1
  437. package/dist/src/utils/settings.d.ts +153 -5
  438. package/dist/src/utils/settings.js +233 -29
  439. package/dist/src/utils/visible-retry.d.ts +11 -0
  440. package/dist/src/utils/visible-retry.js +10 -1
  441. package/dist/src/verify/entrypoint.d.ts +1 -1
  442. package/dist/src/verify/entrypoint.js +1 -1
  443. package/dist/src/verify/recipes.d.ts +13 -0
  444. package/dist/src/verify/recipes.js +15 -0
  445. package/package.json +135 -132
  446. package/dist/src/providers/auth/gcloud.d.ts +0 -28
  447. package/dist/src/providers/auth/gcloud.js +0 -102
  448. package/dist/src/providers/auth/gemini-oauth.d.ts +0 -82
  449. package/dist/src/providers/auth/gemini-oauth.js +0 -472
  450. package/dist/src/providers/gemini.d.ts +0 -11
  451. package/dist/src/providers/gemini.js +0 -45
  452. package/dist/src/providers/siliconflow-sse-repair.d.ts +0 -58
  453. package/dist/src/providers/siliconflow-sse-repair.js +0 -177
  454. package/dist/src/providers/strategies/google.strategy.d.ts +0 -22
  455. package/dist/src/providers/strategies/google.strategy.js +0 -174
  456. package/dist/src/providers/strategies/siliconflow.strategy.js +0 -29
  457. package/dist/src/ui/containers/chat-feed.d.ts +0 -40
  458. package/dist/src/ui/containers/chat-feed.js +0 -66
@@ -48,39 +48,45 @@
48
48
  // - A4 (tool_call write-ahead) — persistToolCallWriteAhead
49
49
  // - A5 (message_seq write-ahead) — persistMessageWriteAhead
50
50
  // - O1 (providerOptions shape forensics) — extractProviderOptionsShape
51
- // - siliconflow reasoning-strip — turnCaps.sanitizeHistory
51
+ // - reasoning-strip (provider quirk) — turnCaps.sanitizeHistory
52
52
  import { generateText, stepCountIs, streamText } from "ai";
53
+ import { getEffectiveCouncilRoleCount } from "../council/leader.js";
53
54
  import { recordArtifact } from "../ee/artifact-cache.js";
54
55
  import { routeFeedback } from "../ee/bridge.js";
55
56
  import { getDefaultEEClient } from "../ee/intercept.js";
57
+ import { isGsdHardGateEnabled } from "../gsd/flags.js";
58
+ import { evaluateMutationGate } from "../gsd/mutation-gate.js";
56
59
  import { acquireMcpTools } from "../mcp/client-pool.js";
57
60
  import { dropRedundantFsMcpTools, filterMcpServersByMessage } from "../mcp/smart-filter.js";
58
- import { getModelInfo } from "../models/registry.js";
61
+ import { getModelInfo, isReasoningModel } from "../models/registry.js";
59
62
  import { cheapModelShellLine, injectCheapModelPlaybook, injectCheapModelShellDirective, shouldInjectCheapModelPlaybook, } from "../pil/cheap-model-playbook.js";
60
63
  import { injectCheapModelWorkbook, shouldInjectCheapModelWorkbook } from "../pil/cheap-model-workbooks.js";
61
64
  import { getResponseTaskType, isResponseTool, normalizeStructuredResponseTaskType, shouldHaltOnResponseTool, } from "../pil/index.js";
62
- import { isMetaAnalysisPrompt } from "../pil/layer6-output.js";
65
+ import { isMetaAnalysisPrompt, isSprintPlanExecution } from "../pil/layer6-output.js";
63
66
  import { taskTypeToMaxTokens, taskTypeToReasoningEffort, taskTypeToTier } from "../pil/task-tier-map.js";
64
67
  import { mentionsEcosystemScope } from "../playbook/directives.js";
65
68
  import { getProviderCapabilities } from "../providers/capabilities.js";
66
69
  import { bridgeMcpToolResult, listCachedImages, scrubImagePayloadsInMessages, } from "../providers/mcp-vision-bridge.js";
67
70
  import { captureToolSchemas } from "../providers/patch-zod-schema.js";
68
- import { buildTurnProviderOptions, requireRuntimeProvider, shouldDropParam, } from "../providers/runtime.js";
71
+ import { buildTurnProviderOptions, requireRuntimeProvider, resolveTemperatureParam, shouldDropParam, } from "../providers/runtime.js";
69
72
  import { wireDebug } from "../providers/wire-debug.js";
70
73
  import { reportRouteOutcome } from "../router/decide.js";
74
+ import { statusBarStore } from "../state/status-bar-store.js";
75
+ import { recordTurnTrace } from "../state/turn-trace.js";
71
76
  import { getNextMessageSequence, logInteraction, markMessageErrored, markToolCallErrored, persistToolCallWriteAhead, } from "../storage/index.js";
72
77
  import { persistSessionExperience } from "../storage/session-experience-store.js";
73
78
  import { createBuiltinTools } from "../tools/registry.js";
74
79
  import { snapshotFromTodoWriteArgs } from "../tools/todo-write-snapshot.js";
75
80
  import { visionToolsNeeded } from "../tools/vision-gate.js";
76
- import { recordTurnTrace } from "../ui/slash/debug.js";
77
- import { statusBarStore } from "../ui/status-bar/store.js";
78
81
  import { appendDecisionLog } from "../usage/decision-log.js";
79
82
  import { logger } from "../utils/logger.js";
80
83
  import { openUrl } from "../utils/open-url.js";
81
84
  import { appendAudit, toolNeedsApproval } from "../utils/permission-mode.js";
82
- import { getAutoCouncilConfidence, getAutoCouncilMinRoles, getProviderStallRetries, getProviderStallTimeoutMs, getRoleModels, getSteerInjectionEnabled, getTopLevelCompactKeepLast, getTopLevelCompactThresholdChars, getTopLevelToolBudgetChars, isAutoCouncilEnabled, loadMcpServers, } from "../utils/settings.js";
85
+ import { getAutoCouncilConfidence, getAutoCouncilMinRoles, getProviderProgressTimeoutMs, getProviderStallRetries, getProviderStallTimeoutMs, getSteerInjectionEnabled, getTopLevelCompactHysteresis, getTopLevelCompactKeepLast, getTopLevelCompactTailBudgetChars, getTopLevelCompactThresholdChars, getTopLevelToolBudgetChars, isAutoCouncilClarifyEnabled, isAutoCouncilEnabled, loadMcpServers, } from "../utils/settings.js";
86
+ import { isAutoCouncilSkipReasoning } from "../utils/settings.js";
83
87
  import { resolveShell } from "../utils/shell.js";
88
+ import { foldDynamicTailIntoUserMessage, splitFrontAndDynamicTail } from "./cache-prefix.js";
89
+ import { consumeProactiveCompact } from "./compact-request.js";
84
90
  import { relaxCompactionSettings } from "./compaction.js";
85
91
  import { wrapToolSetWithDedup } from "./cross-turn-dedup.js";
86
92
  import { humanizeApiError, isAuthenticationError, isContextLimitError, summarizeApiErrorForLog } from "./error-utils.js";
@@ -94,6 +100,7 @@ import { containsEncryptedReasoning, sanitizeModelMessages } from "./reasoning.j
94
100
  import { repairToolCallHook } from "./repair-tool-call.js";
95
101
  import { buildRepetitionReminder, recordAssistantBurst, shouldInjectRepetitionReminder, } from "./repetition-detector.js";
96
102
  import { classifyStreamError } from "./retry-classifier.js";
103
+ import { parseSafetyBlock, shouldAutoAllowYolo } from "./safety-intercept.js";
97
104
  import { forcedFinalize, incSessionStep, } from "./scope-ceiling.js";
98
105
  import { attachReminderToMessages, buildCheckpointReminder, buildScopeReminder, cadenceForSize, shouldInjectCeilingCrossing, shouldInjectReminder, shouldInjectSoftWarn, shouldPreWarnCompaction, } from "./scope-reminder.js";
99
106
  import { formatElisionManifest, getSessionExperienceCounts, recordCompaction, recordElision, } from "./session-experience.js";
@@ -101,10 +108,35 @@ import { attemptStallRescue, pushStallToolResult } from "./stall-rescue.js";
101
108
  import { createStallWatchdog, STALL_ERROR_MESSAGE, shouldContinueAfterMidLoopStall, shouldRepromptStall, stallRepromptBackoffMs, } from "./stall-watchdog.js";
102
109
  import { planSteerInjection } from "./steer-inbox.js";
103
110
  import { wrapToolSetWithCap } from "./sub-agent-cap.js";
104
- import { applyAnthropicPromptCaching, compactSubAgentMessages, cumulativeMessageChars } from "./subagent-compactor.js";
111
+ import { applyAnthropicPromptCaching, applyCompactionHysteresis, compactSubAgentMessages, cumulativeMessageChars, initCompactionHysteresisState, } from "./subagent-compactor.js";
105
112
  import { detectTextEmittedToolCall, parseDsmlToolCalls } from "./text-tool-call-detector.js";
113
+ import { getToolLimitAutoRecoverCap, shouldAutoRecoverToolLimit } from "./tool-limit-auto-recover.js";
106
114
  import { createToolLoopCapPredicate } from "./tool-loop-cap.js";
107
115
  import { buildToolRepetitionAbortMessage, recordToolError as recordToolRepetitionError, recordToolSuccess as recordToolRepetitionSuccess, } from "./tool-repetition-detector.js";
116
+ /**
117
+ * Resolve the per-turn `maxOutputTokens` budget.
118
+ *
119
+ * Normally the budget is derived from the PIL-classified `taskType`
120
+ * (`taskTypeToMaxTokens`). But a sprint IMPLEMENTATION turn — the /ideal
121
+ * loop's handoff into the host orchestrator via `processMessageFn`, marked
122
+ * with `SPRINT_EXECUTION_MARKER` — is a KNOWN code-writing task that must not
123
+ * be starved by a noisy classify. Observed live (2026-07-10, gsd-core
124
+ * migration): the impl prompt was classified `analyze`/default → capped at
125
+ * 4_096 output → the model spent the whole budget narrating its plan, hit
126
+ * `finishReason:"length"` mid-word, produced ZERO code, and the turn wedged.
127
+ *
128
+ * Fix: for a sprint-execution turn, floor the budget at the build/generate
129
+ * tier (12_288) regardless of the classified type. Scoped to the marker only
130
+ * (NOT the broad `isImplementationIntent`) so ordinary refactor/debug turns
131
+ * keep their intentionally tighter L6 budgets.
132
+ */
133
+ export function resolveTurnMaxOutputTokens(pilCtx) {
134
+ const base = taskTypeToMaxTokens(pilCtx.taskType);
135
+ if (isSprintPlanExecution(pilCtx.raw ?? "")) {
136
+ return Math.max(base, taskTypeToMaxTokens("build"));
137
+ }
138
+ return base;
139
+ }
108
140
  /**
109
141
  * F2 — approximate the char cost of the FIXED prompt envelope (system +
110
142
  * tools JSON-Schema) that streamText re-sends on every step. Used to feed
@@ -212,9 +244,46 @@ export class SimpleMutex {
212
244
  }
213
245
  }
214
246
  import { stripDsmlMarkup } from "./message-processor.js";
247
+ /** Tools that produce zero side-effects — safe for Q&A-only (direct-answer) mode. */
248
+ function stripWriteTools(tools) {
249
+ const readonly = new Set([
250
+ "read_file",
251
+ "grep",
252
+ "bash_output_get",
253
+ "process_list",
254
+ "delegation_read",
255
+ "delegation_list",
256
+ "ee_query",
257
+ "ee_health",
258
+ "usage_forensics",
259
+ "lsp_query",
260
+ "setup_guide",
261
+ "selfverify_status",
262
+ "selfverify_result",
263
+ "selfverify_list",
264
+ "list_vision_cache",
265
+ "ee_feedback",
266
+ "ee_write",
267
+ ]);
268
+ const result = {};
269
+ for (const [name, tool] of Object.entries(tools)) {
270
+ if (readonly.has(name) || name.startsWith("respond_") || name.startsWith("mcp_")) {
271
+ result[name] = tool;
272
+ }
273
+ }
274
+ return result;
275
+ }
215
276
  export async function* executeToolEngine(args) {
216
277
  let { deps, stepRouterPhase, phase2Runtime, runtime, modelInfo, _debugSteps, _ceilingHit, userMessage, pilCtx, turnModelId, turnProvider, _stepCeiling, userModelMessage, userEnrichedMessage, signal, observer, taskHash, provider, system, routerStore, attemptedOverflowRecovery, patternLoopForceHalt, userWriteAheadSeq, streamRetryCount, MAX_STREAM_RETRIES, subagents, systemParts, toolTurnSystem, playwrightGuidance, _hasResponseTools, _pilResponseTools, patternLoopInjectCount, agentLoopDecisionCount, MAX_AGENT_LOOP_DECISIONS, _naturalCeiling, _ceilingTaskType, _ceilingSize, textToolReSteerCount, MAX_TEXT_TOOL_RESTEER, turnStartMs, _debugOn, _debugTurnId, _pilEnrichmentDeltaSnapshot, isChitchat, } = args;
217
278
  // Put all extracted code here:
279
+ // Auto-recover budget for "cap" (tool-round ceiling) halts: compact
280
+ // the history and keep going instead of stopping and asking the user
281
+ // to /compact. Turn-scoped (not per-stream-attempt) so a stream-error
282
+ // retry or stall reprompt (`continue streamAttempt`) cannot reset the
283
+ // counter and exceed the intended cap of auto-compactions per turn
284
+ // (default 6, env MUONROI_TOOL_LIMIT_AUTO_RECOVER_CAP).
285
+ let toolLimitAutoRecoverCount = 0;
286
+ const TOOL_LIMIT_AUTO_RECOVER_CAP = getToolLimitAutoRecoverCap();
218
287
  let stallTriggered = false;
219
288
  // Time-to-first-byte stall RE-PROMPT: some providers (observed:
220
289
  // xai/grok-build-0.1) accept the request then never send the first byte —
@@ -243,6 +312,10 @@ export async function* executeToolEngine(args) {
243
312
  // the turn when exceeded. Prevents the session 526a83cf22df pattern
244
313
  // where 3 user messages burnt 82% of 2.44M tokens in 36 LLM calls.
245
314
  let llmCallsThisTurn = 0;
315
+ // Reactive delegation signal: reference to the top-level cap's live state so
316
+ // this turn's cumulative tool-output load can be reported to the Agent at
317
+ // turn end (drives next-turn sub-session escalation). See reactive-delegation.ts.
318
+ let _topLevelCapState = null;
246
319
  // Live-queue steering: messages the user typed mid-turn are drained at a
247
320
  // prepareStep boundary and accumulated here, then re-appended (deduped) to
248
321
  // the messages returned for each subsequent step. Loop-persistent so they
@@ -269,11 +342,20 @@ export async function* executeToolEngine(args) {
269
342
  // and stay productive. `plan` keeps the old behaviour (architectural
270
343
  // decisions deserve debate regardless of length).
271
344
  const autoCouncilTypes = new Set(["plan", "analyze"]);
272
- const councilRoles = getRoleModels();
273
- const configuredRoleCount = Object.values(councilRoles).filter(Boolean).length;
274
- const heavyTier = pilCtx.complexityTier === "heavy";
345
+ const configuredRoleCount = getEffectiveCouncilRoleCount();
346
+ // Task 8 Step 7: prefer the complexity assessor's own auto-council verdict
347
+ // (pilCtx.gsdAutoCouncil, set at message-processor.ts:685 when the assessor ran)
348
+ // over the raw heavy-tier heuristic below — the assessor already reasoned about
349
+ // depth + task shape, so its verdict is the more intelligent router. Fall back to
350
+ // the heuristic when the assessor didn't run (gsdAutoCouncil undefined).
351
+ const assessorAutoCouncil = pilCtx.gsdAutoCouncil;
352
+ const heavyTier = typeof assessorAutoCouncil === "boolean"
353
+ ? assessorAutoCouncil
354
+ : pilCtx.complexityTier === "heavy";
275
355
  const autoCouncilConfidence = getAutoCouncilConfidence();
276
356
  const autoCouncilMinRoles = getAutoCouncilMinRoles();
357
+ const sessionModelIsReasoning = isReasoningModel(deps.modelId);
358
+ const skipReasoningSetting = isAutoCouncilSkipReasoning();
277
359
  const _complexityFromTrace = pilCtx._intentTrace
278
360
  ?.complexity;
279
361
  const _complexityGatePassed = pilCtx.taskType === "plan" || _complexityFromTrace === undefined || _complexityFromTrace !== "low";
@@ -281,9 +363,13 @@ export async function* executeToolEngine(args) {
281
363
  autoCouncilTypes.has(pilCtx.taskType) &&
282
364
  pilCtx.confidence >= autoCouncilConfidence &&
283
365
  _complexityGatePassed;
366
+ // Skip reasoning-model skip for heavy/complex tasks — they benefit from
367
+ // multi-role diversity even when the session model already does extended thinking.
368
+ const shouldSkipForReasoning = sessionModelIsReasoning && skipReasoningSetting && !heavyTier;
284
369
  const shouldAutoCouncil = !deps.councilManager.isContinuation &&
285
370
  isAutoCouncilEnabled() &&
286
371
  configuredRoleCount >= autoCouncilMinRoles &&
372
+ !shouldSkipForReasoning &&
287
373
  (taskTypeMatch || heavyTier);
288
374
  // Always log the auto-council decision (taken or skipped) with the gate
289
375
  // values that decided it. Lets reports answer "why did this turn cost
@@ -295,6 +381,8 @@ export async function* executeToolEngine(args) {
295
381
  return "feature-disabled";
296
382
  if (configuredRoleCount < autoCouncilMinRoles)
297
383
  return `role-count<${autoCouncilMinRoles} (have ${configuredRoleCount})`;
384
+ if (shouldSkipForReasoning)
385
+ return `reasoning-model=${deps.modelId} (internal self-debate active; skip with MUONROI_AUTOCOUNCIL_SKIP_REASONING=0)`;
298
386
  if (!taskTypeMatch && !heavyTier) {
299
387
  if (!pilCtx.taskType || !autoCouncilTypes.has(pilCtx.taskType))
300
388
  return `taskType=${pilCtx.taskType ?? "null"} not in plan|analyze`;
@@ -322,6 +410,8 @@ export async function* executeToolEngine(args) {
322
410
  autoCouncilConfidence,
323
411
  autoCouncilMinRoles,
324
412
  heavyTier,
413
+ sessionModelIsReasoning,
414
+ skipReasoningSetting,
325
415
  isContinuation: deps.councilManager.isContinuation,
326
416
  },
327
417
  }).catch(() => undefined);
@@ -330,14 +420,35 @@ export async function* executeToolEngine(args) {
330
420
  ? `complexity=heavy${pilCtx.taskType ? ` task=${pilCtx.taskType}` : ""}`
331
421
  : `${pilCtx.taskType} task detected with ${(pilCtx.confidence * 100).toFixed(0)}% confidence`;
332
422
  yield { type: "content", content: `\n[Auto-council triggered: ${reason}]\n` };
333
- yield* deps.runCouncilV2(userMessage, { skipClarification: true, observer, userModelMessage });
423
+ // Pre-debate interview: unless disabled, run the model-designed clarification
424
+ // askcards BEFORE the debate so a broadly-scoped "debate mode" request is
425
+ // chốt-ed first (each card's options carry a recommended default + per-option
426
+ // why — see runClarification/buildClarifyOptions). The clarifier is ROI-gated
427
+ // and yields 0 cards on already-detailed topics, so this stays quiet when the
428
+ // prompt is already specific. Skip only when the user turned it off. The
429
+ // clarifier reuses PIL gray-areas as seed questions (no hardcoded questions),
430
+ // and its models come from pickCouncilTaskModel (no hardcoded model/provider).
431
+ yield* deps.runCouncilV2(userMessage, {
432
+ skipClarification: !isAutoCouncilClarifyEnabled(),
433
+ observer,
434
+ userModelMessage,
435
+ });
334
436
  const synthesis = deps.councilManager.lastSynthesis;
437
+ const chosenAction = deps.councilManager.lastPostDebateAction;
335
438
  deps.councilManager.setLastSynthesis(null);
336
- if (synthesis) {
439
+ deps.councilManager.setLastPostDebateAction(null);
440
+ // Honor the user's post-debate choice instead of always continuing: an
441
+ // evaluation/decision debate whose deliverable is the conclusion (default
442
+ // save_exit) now returns to the composer rather than being force-fed a
443
+ // meaningless "proceed with the action items" turn. postDebateContinuation
444
+ // is shared with the /council slash path (orchestrator.runCouncilV2).
445
+ const { postDebateContinuation } = await import("../council/index.js");
446
+ const continuationPrompt = synthesis ? postDebateContinuation(chosenAction ?? undefined, synthesis) : null;
447
+ if (continuationPrompt) {
337
448
  yield { type: "content", content: "\n[Auto-continuing with council recommendations...]\n" };
338
449
  deps.councilManager.setContinuation(true);
339
450
  try {
340
- yield* deps.processMessage(`Council debate completed. Synthesis:\n\n${synthesis}\n\nProceed with the recommended action items.`, observer);
451
+ yield* deps.processMessage(continuationPrompt, observer);
341
452
  }
342
453
  finally {
343
454
  deps.councilManager.setContinuation(false);
@@ -345,6 +456,12 @@ export async function* executeToolEngine(args) {
345
456
  }
346
457
  return;
347
458
  }
459
+ if (shouldSkipForReasoning) {
460
+ yield {
461
+ type: "content",
462
+ content: `\n[Auto-council skipped: ${deps.modelId} is a reasoning model and already performs internal self-debate. Set MUONROI_AUTOCOUNCIL_SKIP_REASONING=0 or autoCouncilSkipReasoning=false to force council.]\n`,
463
+ };
464
+ }
348
465
  if (deps.batchApi) {
349
466
  try {
350
467
  yield* deps.processMessageBatchTurn({
@@ -482,14 +599,29 @@ export async function* executeToolEngine(args) {
482
599
  cachedImageCount: listCachedImages().length,
483
600
  priorTurnHadTools: deps.messages.some((m) => m?.role === "tool"),
484
601
  });
602
+ const depthTier = pilCtx.modelDepthTier ??
603
+ pilCtx.complexityTier ??
604
+ "standard";
485
605
  const baseToolsRaw = createBuiltinTools(deps.bash, deps.mode, {
486
606
  runTask: (request, abortSignal) => deps.runTask(request, combineAbortSignals(signal, abortSignal)),
487
607
  runDelegation: (request, abortSignal) => deps.runDelegation(request, combineAbortSignals(signal, abortSignal)),
488
608
  readDelegation: (id) => deps.readDelegation(id),
489
609
  listDelegations: () => deps.listDelegations(),
490
610
  modelId: turnModelId,
611
+ depthTier,
612
+ sessionId: deps.session?.id,
491
613
  includeVisionTools,
492
614
  consultParentSession: deps.consultParentSession,
615
+ runDebate: async (topic) => {
616
+ const gen = deps.runCouncilV2(topic, {
617
+ skipClarification: true,
618
+ userModelMessage: { role: "user", content: `/council ${topic}` },
619
+ });
620
+ for await (const chunk of gen) {
621
+ // Drain the generator
622
+ }
623
+ return deps.councilManager.lastSynthesis ?? "";
624
+ },
493
625
  });
494
626
  // Top-level cumulative cap state. We accumulate the raw tool set
495
627
  // (base + MCP + PIL response tools) across the assembly below,
@@ -513,16 +645,23 @@ export async function* executeToolEngine(args) {
513
645
  // history) is preserved.
514
646
  const turnCaps = getProviderCapabilities(requireRuntimeProvider(runtime));
515
647
  const _priorTurnHadTools = deps.messages.some((m) => m?.role === "tool");
648
+ // Direct-answer mode: purely informational Q&A (no code changes).
649
+ // Strip write tools (bash, edit_file, write_file) AND skip MCP;
650
+ // only readonly tools remain (read_file, grep, ee_query, etc.).
651
+ const isDirectAnswer = pilCtx.directAnswer === true;
516
652
  let rawToolSet = !turnCaps.supportsClientTools(runtime.modelInfo)
517
653
  ? {}
518
654
  : isChitchat && !_priorTurnHadTools
519
655
  ? {}
520
- : baseToolsRaw;
521
- // MCP skip: chitchat / greeting inputs don't need 7 MCP servers'
656
+ : isDirectAnswer && !_priorTurnHadTools
657
+ ? stripWriteTools(baseToolsRaw)
658
+ : baseToolsRaw;
659
+ // MCP skip: chitchat / direct-answer / greeting inputs don't need 7 MCP servers'
522
660
  // worth of tool schemas (~20K input tokens). PIL Layer 1 already
523
661
  // gates this conservatively (≤10 chars + ≤2 words OR brain "none").
524
662
  if (deps.mode === "agent" &&
525
663
  (!isChitchat || _priorTurnHadTools) &&
664
+ !isDirectAnswer &&
526
665
  turnCaps.supportsClientTools(runtime.modelInfo)) {
527
666
  // Smart MCP filter: drop OPTIONAL MCP servers whose category the
528
667
  // current message gives no signal for. Browser/vision servers
@@ -629,11 +768,14 @@ export async function* executeToolEngine(args) {
629
768
  // Apply the top-level cumulative cap once over the fully-assembled
630
769
  // raw tool set. State is per-turn; each turn gets a fresh budget.
631
770
  const topLevelCap = wrapToolSetWithCap(rawToolSet, {
632
- maxCumulativeChars: getTopLevelToolBudgetChars(deps.maxToolRounds),
771
+ maxCumulativeChars: getTopLevelToolBudgetChars(deps.maxToolRounds, contextWindow),
633
772
  midTierRatio: 0.5,
634
773
  highTierRatio: 0.8,
635
774
  label: "top-level",
636
775
  });
776
+ // Expose the cap state so the reactive-delegation signal can read this
777
+ // turn's cumulative tool load at turn end (see report at success exit).
778
+ _topLevelCapState = topLevelCap.state;
637
779
  // Phase C3: layer cross-turn dedup on top of the top-level cap.
638
780
  const tools = wrapToolSetWithReadBudget(wrapToolSetWithDedup(topLevelCap.tools, deps.crossTurnDedup), deps.readBudget);
639
781
  // Wrap non-read-only tools in a turn-scoped mutex to prevent race conditions during parallel execution.
@@ -654,7 +796,15 @@ export async function* executeToolEngine(args) {
654
796
  "selfverify_result",
655
797
  "selfverify_list",
656
798
  "list_vision_cache",
799
+ "ee_feedback",
800
+ "ee_write",
657
801
  ]);
802
+ // Task 8: native GSD mutation gate. Read once per turn (not per call) since
803
+ // hardGateEnabled/directAnswer don't change mid-turn; the gate itself
804
+ // re-reads STATE.md per call (cheap fs read) so it stays live if the
805
+ // model advances phase/verdict mid-turn via gsd_* tools.
806
+ const gsdHardGateEnabled = isGsdHardGateEnabled();
807
+ const gsdDirectAnswer = pilCtx.directAnswer;
658
808
  for (const name of Object.keys(tools)) {
659
809
  const tool = tools[name];
660
810
  if (tool &&
@@ -663,6 +813,14 @@ export async function* executeToolEngine(args) {
663
813
  !name.startsWith("respond_")) {
664
814
  const originalExecute = tool.execute;
665
815
  tool.execute = async (input, context) => {
816
+ const gate = evaluateMutationGate(deps.bash.getCwd(), {
817
+ toolName: name,
818
+ hardGateEnabled: gsdHardGateEnabled,
819
+ directAnswer: gsdDirectAnswer,
820
+ });
821
+ if (gate.blocked) {
822
+ return { success: false, output: gate.reason, error: gate.reason };
823
+ }
666
824
  return writeMutex.run(() => originalExecute(input, context));
667
825
  };
668
826
  }
@@ -761,7 +919,40 @@ export async function* executeToolEngine(args) {
761
919
  // (non-cached) system message via the slice below.
762
920
  const mcpCapabilityBlock = buildMcpCapabilityBlock(Object.keys(tools));
763
921
  const systemWithCaps = mcpCapabilityBlock ? `${systemWithShell}${mcpCapabilityBlock}` : systemWithShell;
764
- const systemForModel = runtime.modelId.startsWith("claude")
922
+ // Task 3 — non-Claude prompt-cache prefix stability. On non-Claude the
923
+ // system is a single string; per-turn-dynamic content (dynamicSuffix +
924
+ // PIL suffix + MCP roster) that sits AFTER the byte-stable staticPrefix
925
+ // but BEFORE the conversation shifts the cached prefix and nukes the
926
+ // cache on PIL-active turns (session 47a774d272da: pil_active=1 ⟺
927
+ // cache_read=0). We keep the front byte-stable and relocate the dynamic
928
+ // tail into the trailing user message (variant b) — NOT a mid-conversation
929
+ // system-role message, which OpenAI-compatible providers (DeepSeek/GLM)
930
+ // do not reliably accept. Claude keeps its untouched two-block split.
931
+ const _isClaudeModel = runtime.modelId.startsWith("claude");
932
+ const { front: _nonClaudeFront, dynamicTail: _nonClaudeDynamicTail } = _isClaudeModel
933
+ ? { front: systemWithCaps, dynamicTail: "" }
934
+ : splitFrontAndDynamicTail({
935
+ modelId: runtime.modelId,
936
+ systemWithCaps,
937
+ staticPrefix: systemParts.staticPrefix,
938
+ });
939
+ // Only relocate when the enriched user message is actually present to
940
+ // receive the tail; otherwise fall back to the original single string so
941
+ // no instruction content is dropped.
942
+ const _willFoldDynamicTail = !_isClaudeModel &&
943
+ _nonClaudeDynamicTail.trim().length > 0 &&
944
+ deps.messages.includes(userModelMessage);
945
+ if (process.env.MUONROI_DEBUG_CACHE_PREFIX === "1") {
946
+ try {
947
+ console.error(`[cache-prefix] model=${runtime.modelId} staticPrefixLen=${systemParts.staticPrefix.length} ` +
948
+ `frontLen=${_nonClaudeFront.length} dynTailLen=${_nonClaudeDynamicTail.length} ` +
949
+ `fold=${_willFoldDynamicTail} msgCount=${deps.messages.length}`);
950
+ }
951
+ catch (err) {
952
+ console.error(`[cache-prefix] log failed: ${err?.message}`);
953
+ }
954
+ }
955
+ const systemForModel = _isClaudeModel
765
956
  ? [
766
957
  {
767
958
  role: "system",
@@ -773,7 +964,9 @@ export async function* executeToolEngine(args) {
773
964
  content: systemWithCaps.slice(systemParts.staticPrefix.length),
774
965
  },
775
966
  ]
776
- : systemWithCaps;
967
+ : _willFoldDynamicTail
968
+ ? _nonClaudeFront
969
+ : systemWithCaps;
777
970
  // Capture prompt-size breakdown so recordUsage can attach it to the
778
971
  // cost-log entry. Without this, "system prompt is huge" is unfalsifiable.
779
972
  // chars/4 ≈ tokens for English; reported as chars to keep math obvious.
@@ -837,13 +1030,27 @@ export async function* executeToolEngine(args) {
837
1030
  // stubs. Symmetric to the B3 sub-agent path; reuses the same module
838
1031
  // with `label: "top-level"` so the stub text reflects which loop
839
1032
  // elided the content.
840
- const topLevelCompactThreshold = getTopLevelCompactThresholdChars();
841
- const topLevelCompactKeepLast = getTopLevelCompactKeepLast();
1033
+ const topLevelCompactThreshold = getTopLevelCompactThresholdChars(contextWindow);
1034
+ const topLevelCompactKeepLast = getTopLevelCompactKeepLast(contextWindow);
1035
+ // O2 — byte budget for the verbatim tail; shrinks keepLast on read-heavy
1036
+ // turns that stay large at low fill (the 60-80k-per-call bucket).
1037
+ const topLevelCompactTailBudget = getTopLevelCompactTailBudgetChars(contextWindow);
1038
+ // O3 — compaction hysteresis state (per-turn; this scope runs once per
1039
+ // streamText turn). Once we compact, freeze the compacted prefix and
1040
+ // only append new messages until size grows past the hysteresis ceiling,
1041
+ // so the provider prompt-cache prefix stays byte-stable across steps
1042
+ // instead of breaking every step as the keepLast boundary slides.
1043
+ const compactHysteresis = getTopLevelCompactHysteresis();
1044
+ let hysteresisState = initCompactionHysteresisState();
842
1045
  // Phase O1 — capture providerOptions SHAPE (types only) for forensics.
843
1046
  deps.setLastProviderOptionsShape(Object.keys(providerOpts).length > 0 ? extractProviderOptionsShape(providerOpts) : null);
844
1047
  // Substitute the enriched user message for the current turn so the LLM sees PIL additions,
845
1048
  // while leaving the DB-persisted `deps.messages` clean for future turns.
846
- const _messagesForCall = deps.messages.map((m) => m === userModelMessage ? userEnrichedMessage : m);
1049
+ const _messagesForCall = deps.messages.map((m) => m === userModelMessage
1050
+ ? _willFoldDynamicTail
1051
+ ? foldDynamicTailIntoUserMessage(userEnrichedMessage, _nonClaudeDynamicTail)
1052
+ : userEnrichedMessage
1053
+ : m);
847
1054
  if (wireDebug.enabled) {
848
1055
  wireDebug.logRequest({
849
1056
  providerId: runtime.modelInfo?.provider ?? "unknown",
@@ -865,6 +1072,37 @@ export async function* executeToolEngine(args) {
865
1072
  const _baseDynamicStopWhen = createToolLoopCapPredicate({
866
1073
  initialCap: deps.maxToolRounds,
867
1074
  ask: async (info) => {
1075
+ // Auto-recover a "cap" (tool-round ceiling) halt: compact the history and keep
1076
+ // going, instead of stopping and telling the user to /compact
1077
+ // (prompts.ts). Capped so a runaway turn still terminates;
1078
+ // pattern-loop halts are excluded (agent is stuck).
1079
+ if (shouldAutoRecoverToolLimit(info, toolLimitAutoRecoverCount, TOOL_LIMIT_AUTO_RECOVER_CAP)) {
1080
+ toolLimitAutoRecoverCount++;
1081
+ try {
1082
+ const _cw = runtime.modelInfo?.contextWindow ?? 0;
1083
+ if (_cw > 0) {
1084
+ await deps.compactForContext(provider, system, _cw, signal, deps.getCompactionSettings(_cw), false);
1085
+ // A compacted round resets context to O(N) input (cheap), so the
1086
+ // hard-cap's cost-runaway purpose is served — grant the turn more
1087
+ // headroom instead of letting the 1.5× hard ceiling strand a
1088
+ // genuinely productive long task. Bounded inside the Agent.
1089
+ deps.extendHardCeilingForAutoCompaction?.();
1090
+ }
1091
+ }
1092
+ catch (err) {
1093
+ logger.error("orchestrator", "tool-limit auto-recover compaction failed", {
1094
+ message: err instanceof Error ? err.message : String(err),
1095
+ });
1096
+ }
1097
+ const _ar2 = globalThis.__muonroiAgentRuntime;
1098
+ _ar2?.emitEvent({
1099
+ t: "event",
1100
+ kind: "toast",
1101
+ level: "info",
1102
+ text: `đạt giới hạn bước — đã tự nén ngữ cảnh và tiếp tục (lần ${toolLimitAutoRecoverCount}/${TOOL_LIMIT_AUTO_RECOVER_CAP})`,
1103
+ });
1104
+ return "continue";
1105
+ }
868
1106
  if (info.kind === "pattern") {
869
1107
  if (patternLoopInjectCount < 1) {
870
1108
  patternLoopInjectCount++;
@@ -1081,8 +1319,24 @@ export async function* executeToolEngine(args) {
1081
1319
  // on every chunk via stall.pet(), so it never kills an actively
1082
1320
  // streaming call. Disposed when the stream ends or errors.
1083
1321
  stallTriggered = false;
1322
+ // Second timer (progressTimeoutMs) is the no-forward-progress guard.
1323
+ // stall.pet() re-arms the any-activity timer on EVERY chunk — including
1324
+ // a reasoning model's reasoning-delta — so an endless chain-of-thought
1325
+ // keeps it alive and it never fires (observed live 2026-07-10: a
1326
+ // deepseek-v4-flash sub-SESSION churned reasoning 30+ min, 1.4M input
1327
+ // tokens, ZERO text/tool output; the 2-min stall watchdog never tripped).
1328
+ // The progress timer is reset ONLY by stall.petProgress() on real output
1329
+ // (text-delta / tool-call), aborting a runaway-reasoning loop while a
1330
+ // legitimately long reasoning burst that DOES emit output survives.
1084
1331
  const stall = createStallWatchdog(getProviderStallTimeoutMs(), () => {
1085
1332
  stallTriggered = true;
1333
+ }, {
1334
+ progressTimeoutMs: getProviderProgressTimeoutMs(),
1335
+ onProgressFire: () => {
1336
+ stallTriggered = true;
1337
+ console.error(`[tool-engine] stream aborted: no text/tool output for ${getProviderProgressTimeoutMs()}ms ` +
1338
+ `(runaway reasoning / no forward progress) model=${runtime.modelId}`);
1339
+ },
1086
1340
  });
1087
1341
  // F3c — hard-cap LLM calls per turn before this streamText()
1088
1342
  if (++llmCallsThisTurn > MAX_LLM_CALLS_PER_TURN) {
@@ -1260,10 +1514,17 @@ export async function* executeToolEngine(args) {
1260
1514
  };
1261
1515
  // T1.1 + T1.2 — reasoning models (DeepSeek V4 Flash, R1) emit 2K-5K
1262
1516
  // CoT tokens per turn that accumulate across the multi-step loop.
1263
- // Strip old reasoning and compact earlier (ratio 0.3 vs 0.5) to
1264
- // cut ~40-60% of cumulative input tokens.
1517
+ // Strip old reasoning and compact earlier to cut cumulative input.
1518
+ // Small-context reasoning models (< 100K) use ratio 0.2 (fire at
1519
+ // 20% fill) because their per-step overhead (system + tools + CoT)
1520
+ // already consumes ~30-40% of the window, leaving little headroom.
1265
1521
  const isReasoningModel = runtime.modelInfo?.reasoning === true;
1266
- const compacted = compactSubAgentMessages(stripped, {
1522
+ const reasoningFillRatio = isReasoningModel
1523
+ ? contextWindowTokens > 0 && contextWindowTokens < 100_000
1524
+ ? 0.2
1525
+ : 0.3
1526
+ : undefined;
1527
+ const runCompaction = () => compactSubAgentMessages(stripped, {
1267
1528
  thresholdChars: topLevelCompactThreshold,
1268
1529
  // Rec #1 (cheap part): on meta/self-eval turns keep a couple more
1269
1530
  // trailing tool turns verbatim — those carry the reasoning the
@@ -1274,28 +1535,120 @@ export async function* executeToolEngine(args) {
1274
1535
  label: "top-level",
1275
1536
  envelopeChars,
1276
1537
  contextWindowTokens,
1277
- contextFillRatio: isReasoningModel ? 0.3 : undefined,
1538
+ contextFillRatio: reasoningFillRatio,
1278
1539
  keepToolIds: keepToolIds.length ? keepToolIds : undefined,
1279
1540
  persistArtifact,
1280
1541
  stripOldReasoning: isReasoningModel,
1542
+ tailBudgetChars: topLevelCompactTailBudget,
1281
1543
  });
1282
- if (compacted !== stripped)
1283
- recordCompaction(sn);
1544
+ // O3 — compaction hysteresis (holds the frozen compacted prefix
1545
+ // between compactions so the provider prompt-cache prefix stays
1546
+ // byte-stable across steps instead of breaking as the keepLast
1547
+ // boundary slides). See applyCompactionHysteresis.
1548
+ const currChars = cumulativeMessageChars(stripped) + envelopeChars;
1549
+ // Proactive compaction (agent called the `compact` tool). Consume the
1550
+ // one-shot request and FORCE a compaction this step, bypassing the
1551
+ // hysteresis threshold — the agent explicitly asked to shed context,
1552
+ // so a one-time cache-prefix break is the intended trade. Re-seed the
1553
+ // hysteresis state to the fresh compacted prefix so the following
1554
+ // steps hold it steady (no per-step churn) until it grows again.
1555
+ const _proactiveCompact = consumeProactiveCompact();
1556
+ let compacted;
1557
+ if (_proactiveCompact) {
1558
+ const _forced = runCompaction();
1559
+ const _didForce = _forced !== stripped;
1560
+ compacted = _forced;
1561
+ hysteresisState = _didForce
1562
+ ? { frozenCompacted: _forced, frozenStrippedLen: stripped.length, lastCompactTriggerChars: currChars }
1563
+ : hysteresisState;
1564
+ if (_didForce)
1565
+ recordCompaction(sn);
1566
+ try {
1567
+ const _arPc = globalThis.__muonroiAgentRuntime;
1568
+ _arPc?.emitEvent({
1569
+ t: "event",
1570
+ kind: "toast",
1571
+ level: "info",
1572
+ text: _didForce
1573
+ ? "đã nén ngữ cảnh theo yêu cầu của agent — tiếp tục tác vụ"
1574
+ : "agent yêu cầu nén nhưng chưa có gì để nén — tiếp tục",
1575
+ });
1576
+ }
1577
+ catch {
1578
+ /* toast best-effort */
1579
+ }
1580
+ }
1581
+ else {
1582
+ const _hyst = applyCompactionHysteresis({
1583
+ stripped,
1584
+ currChars,
1585
+ hysteresis: compactHysteresis,
1586
+ state: hysteresisState,
1587
+ runCompaction,
1588
+ });
1589
+ compacted = _hyst.compacted;
1590
+ hysteresisState = _hyst.state;
1591
+ // Count only ACTUAL (re)compactions, not held-boundary steps — the
1592
+ // compaction counter drives the cache-churn telemetry this fixes.
1593
+ if (_hyst.didRecompact)
1594
+ recordCompaction(sn);
1595
+ }
1596
+ const coalesced = coalesceReadOnlyMessages(compacted);
1597
+ // (recordCompaction already handled per-branch above.)
1284
1598
  // Pre-compaction visibility: give the agent one step of notice
1285
1599
  // before B4 actually rewrites history into stubs. This is the
1286
1600
  // advance warning that was missing — agent can now decide to
1287
1601
  // summarize, finish, or request preservation. Fires when we did
1288
1602
  // NOT compact this step (compacted === stripped, restored by the
1289
- // compactSubAgentMessages no-op ref contract) AND the prompt is
1290
- // approaching the threshold. Must compare CHARS (messages +
1291
- // envelope), not stripped.length (a message count that never
1292
- // exceeds a char-scaled threshold) — session 2b7a10219499.
1293
1603
  const _preWarnChars = cumulativeMessageChars(stripped) + envelopeChars;
1294
- if (compacted === stripped && shouldPreWarnCompaction(_preWarnChars, topLevelCompactThreshold)) {
1604
+ if (coalesced === stripped && shouldPreWarnCompaction(_preWarnChars, topLevelCompactThreshold)) {
1295
1605
  const _cp = buildCheckpointReminder(sn, true);
1296
1606
  const _pre = `[pre-compaction warning at step ${sn} — next step(s) will likely rewrite older tool results to stubs (threshold ${topLevelCompactThreshold}, keepLast=${topLevelCompactKeepLast}). ${_cp} Summarize or finish if possible, or warn the user they can run the "/compact" command if they want a clean compressed history.]`;
1297
1607
  return withSteers({ messages: attachReminderToMessages(stripped, _pre) });
1298
1608
  }
1609
+ // ---- Read-only tool batching interceptor ----
1610
+ // The system prompt already instructs batching (BATCH ALL TOOL
1611
+ // CALLS — HARD RULE), but models like DeepSeek frequently ignore
1612
+ // it and emit 1 read-only call per step. This injects a concrete,
1613
+ // in-context reminder right before the next LLM call when the
1614
+ // previous step had ≤2 all-read-only tool calls. Cost: ~0 extra
1615
+ // context. Effect: reduces tool rounds 2-3x on the same work.
1616
+ if (sn >= 1) {
1617
+ let _lastAsst = null;
1618
+ for (let _i = stepMessages.length - 1; _i >= 0; _i--) {
1619
+ if (stepMessages[_i].role === "assistant") {
1620
+ _lastAsst = stepMessages[_i];
1621
+ break;
1622
+ }
1623
+ }
1624
+ if (_lastAsst && Array.isArray(_lastAsst.content)) {
1625
+ let _total = 0, _ro = 0;
1626
+ for (const _p of _lastAsst.content) {
1627
+ if (_p.type === "tool-call") {
1628
+ _total++;
1629
+ if (READ_ONLY_TOOLS.has(_p.toolName))
1630
+ _ro++;
1631
+ }
1632
+ }
1633
+ if (_total > 0 && _ro === _total && _total <= 2) {
1634
+ // Prefer the SINGLE-CALL multi-path form (read_file file_paths=[…])
1635
+ // over "parallel tool_calls": a batch of parallel tool_calls is
1636
+ // reshaped into sequential single-call turns for the kimi/glm/
1637
+ // deepseek-go cohort (splitParallelToolCalls) — which re-inflates
1638
+ // history and defeats the batching win. One read_file carrying N
1639
+ // paths is ONE tool_call → never split → the token cut holds on
1640
+ // every provider.
1641
+ const _b = `[Tool batching: you called ${_total} read-only tool(s) one-at-a-time — each extra call re-sends the whole conversation, so N single reads cost O(N²) tokens. To read MULTIPLE files, call read_file ONCE with file_paths=["a","b","c"] — a SINGLE tool_call that no provider splits. For other read-only tools (grep, bash_output_get), emit all pending calls in ONE assistant turn. Do NOT sequence reads across steps.]`;
1642
+ // Attach to `coalesced` (the B4-compacted history), NOT `stripped`:
1643
+ // read-only tools (read_file/grep/…) are exactly what triggers this
1644
+ // reminder, and returning `stripped` here silently discarded the
1645
+ // compaction computed above — so on any loop where the model emits
1646
+ // ≤2 read-only calls per step (DeepSeek does this constantly) older
1647
+ // tool results were never elided and cumulative input grew unbounded.
1648
+ return withSteers({ messages: attachReminderToMessages(coalesced, _b) });
1649
+ }
1650
+ }
1651
+ }
1299
1652
  // Phase 4A — scope reminder injection (REQ-005).
1300
1653
  // Cadence K = 3/5/8 for small/medium/large. Soft-warn fires
1301
1654
  // ONCE per session at floor(ceiling*0.7). Reminder lives in
@@ -1355,10 +1708,10 @@ export async function* executeToolEngine(args) {
1355
1708
  ? `${buildRepetitionReminder(_ceilingSessionId)}\n${_scopePart}`
1356
1709
  : buildRepetitionReminder(_ceilingSessionId)
1357
1710
  : _scopePart;
1358
- const withReminder = attachReminderToMessages(compacted, _reminder);
1711
+ const withReminder = attachReminderToMessages(coalesced, _reminder);
1359
1712
  return withSteers({ messages: withReminder });
1360
1713
  }
1361
- if (compacted === stripped && stripped === stepMessages)
1714
+ if (coalesced === stripped && stripped === stepMessages)
1362
1715
  return withSteers({});
1363
1716
  // Self-awareness note: tell the model compaction happened so it
1364
1717
  // knows earlier context was elided and can adjust its behavior.
@@ -1377,12 +1730,12 @@ export async function* executeToolEngine(args) {
1377
1730
  })()
1378
1731
  : null;
1379
1732
  if (_compactNote) {
1380
- return withSteers({ messages: attachReminderToMessages(compacted, _compactNote) });
1733
+ return withSteers({ messages: attachReminderToMessages(coalesced, _compactNote) });
1381
1734
  }
1382
- return withSteers({ messages: compacted });
1735
+ return withSteers({ messages: coalesced });
1383
1736
  },
1384
- ...(dropParam("temperature") ? {} : { temperature: 0.7 }),
1385
- ...(dropParam("maxOutputTokens") ? {} : { maxOutputTokens: taskTypeToMaxTokens(pilCtx.taskType) }),
1737
+ ...resolveTemperatureParam(runtime, 0.7),
1738
+ ...(dropParam("maxOutputTokens") ? {} : { maxOutputTokens: resolveTurnMaxOutputTokens(pilCtx) }),
1386
1739
  ...(Object.keys(providerOpts).length > 0 ? { providerOptions: providerOpts } : {}),
1387
1740
  experimental_onStepStart: (event) => {
1388
1741
  stepNumber = getStepNumber(event, stepNumber + 1);
@@ -1401,6 +1754,10 @@ export async function* executeToolEngine(args) {
1401
1754
  finishReason: getFinishReason(event),
1402
1755
  usage: stepUsage,
1403
1756
  });
1757
+ // Pull any completed background delegations so their results can be
1758
+ // injected (as system messages) for the *next* LLM step in this same turn.
1759
+ // This improves "self wake" for background jobs without waiting for a new user turn.
1760
+ void deps.consumeBackgroundNotifications?.().catch(() => { });
1404
1761
  // Realtime status bar update per step
1405
1762
  if (stepUsage.inputTokens || stepUsage.outputTokens) {
1406
1763
  // O1 — thread THIS turn's providerOptions shape per step so every
@@ -1475,8 +1832,23 @@ export async function* executeToolEngine(args) {
1475
1832
  wireDebug.logError(_wireProviderIdTop, part.error);
1476
1833
  }
1477
1834
  }
1835
+ // Terminal part of the ENTIRE multi-step turn (AI SDK v6 emits exactly
1836
+ // one `finish` after every step's `finish-step`; it carries the final
1837
+ // finishReason/totalUsage). onFinish has already run by now (usage
1838
+ // recorded + llm-done emitted, and result.response is resolved), so
1839
+ // there is nothing left to drain. Some providers — observed live:
1840
+ // xai/grok-composer-2.5-fast — emit `finish` but then never CLOSE the
1841
+ // fullStream async iterator, so `for await` would block on the next
1842
+ // `.next()` until the turn watchdog fires (up to MUONROI_TURN_IDLE_MS).
1843
+ // Breaking on `finish` finalizes the turn immediately instead. Safe:
1844
+ // `finish` is strictly last, so this can never truncate a multi-step
1845
+ // turn (per-step boundaries are `finish-step`, handled by fall-through).
1846
+ if (part.type === "finish") {
1847
+ break;
1848
+ }
1478
1849
  switch (part.type) {
1479
1850
  case "text-delta":
1851
+ stall.petProgress(); // real forward progress — reset the no-progress guard
1480
1852
  assistantText += part.text;
1481
1853
  // Task 2.6b — emit llm-token (agent-mode only; high-volume, default-off per Phase 4).
1482
1854
  try {
@@ -1508,6 +1880,7 @@ export async function* executeToolEngine(args) {
1508
1880
  yield { type: "reasoning", content: part.text };
1509
1881
  break;
1510
1882
  case "tool-call": {
1883
+ stall.petProgress(); // real forward progress — reset the no-progress guard
1511
1884
  const tc = toToolCall(part);
1512
1885
  activeToolCalls.push(tc);
1513
1886
  // SAMR: track that Phase 1 produced tool calls → transition to Phase 2
@@ -1728,10 +2101,10 @@ export async function* executeToolEngine(args) {
1728
2101
  // the global __muonroiSafetyApproved map so registry.ts's
1729
2102
  // bash.execute bypasses the block on the retry.
1730
2103
  const _outputText = [tr.output, tr.error].filter((x) => typeof x === "string").join("\n");
1731
- const _blockMatch = _outputText.match(/^BLOCKED \(([^)]+)\):\s*(.*)/);
1732
- if (_blockMatch && part.toolName === "bash") {
1733
- const _blockKind = _blockMatch[1];
1734
- const _blockReason = _blockMatch[2];
2104
+ const _parsedBlock = parseSafetyBlock(_outputText);
2105
+ if (_parsedBlock && part.toolName === "bash") {
2106
+ const _blockKind = _parsedBlock.kind;
2107
+ const _blockReason = _parsedBlock.reason;
1735
2108
  const _command = typeof part.input === "object" && part.input != null
1736
2109
  ? String(part.input.command ?? "")
1737
2110
  : "";
@@ -1744,15 +2117,33 @@ export async function* executeToolEngine(args) {
1744
2117
  tr = { ...tr, success: false, error: _outputText, output: _outputText };
1745
2118
  }
1746
2119
  else {
1747
- const _verdict = deps.askSafetyOverride
1748
- ? await deps.askSafetyOverride({
1749
- kind: _blockKind,
1750
- toolName: part.toolName,
1751
- blockedItem: _command,
1752
- reason: _blockReason,
1753
- source: "bash.execute",
1754
- })
1755
- : { action: "block" };
2120
+ // yolo mode auto-approves lower-severity blocks (git-safety /
2121
+ // dangerous) so "don't ask me" actually stops asking — but a
2122
+ // catastrophic, irreversible command STILL shows the askcard,
2123
+ // even in yolo. Everything else surfaces the interactive
2124
+ // safety-override card via deps.askSafetyOverride (registered
2125
+ // by the TUI); when no handler is wired (headless / batch),
2126
+ // that resolves to { action: "block" }, preserving the
2127
+ // backward-compatible hard-stop for non-interactive runs.
2128
+ let _verdict;
2129
+ if (shouldAutoAllowYolo(_blockKind, deps.permissionMode)) {
2130
+ _verdict = { action: "allow-once" };
2131
+ yield {
2132
+ type: "content",
2133
+ content: `[yolo: auto-approved blocked command: ${_blockKind}]\n`,
2134
+ };
2135
+ }
2136
+ else {
2137
+ _verdict = deps.askSafetyOverride
2138
+ ? await deps.askSafetyOverride({
2139
+ kind: _blockKind,
2140
+ toolName: part.toolName,
2141
+ blockedItem: _command,
2142
+ reason: _blockReason,
2143
+ source: "bash.execute",
2144
+ })
2145
+ : { action: "block" };
2146
+ }
1756
2147
  if (_verdict.action === "allow-once" || _verdict.action === "allow-session") {
1757
2148
  // Store approval so registry.ts can bypass the block on retry.
1758
2149
  const _globalSafety = globalThis;
@@ -1886,10 +2277,26 @@ export async function* executeToolEngine(args) {
1886
2277
  // dedicated chunk so the sticky checklist panel can re-render
1887
2278
  // without parsing tool args itself. Skipped when the snapshot
1888
2279
  // doesn't parse (malformed args) so the UI is never poisoned.
1889
- if (tc.function.name === "todo_write" && tr.success) {
1890
- const snap = snapshotFromTodoWriteArgs(tc.function.arguments);
1891
- if (snap)
1892
- yield { type: "task_list_update", taskListSnapshot: snap };
2280
+ if (tr.success && (tc.function.name === "todo_write" || tc.function.name.startsWith("gsd_"))) {
2281
+ try {
2282
+ const { getTaskListSnapshotFromGsd } = require("../gsd/phase-sync.js");
2283
+ const snap = getTaskListSnapshotFromGsd(deps.bash.getCwd());
2284
+ if (snap) {
2285
+ yield { type: "task_list_update", taskListSnapshot: snap };
2286
+ }
2287
+ else if (tc.function.name === "todo_write") {
2288
+ const snapLegacy = snapshotFromTodoWriteArgs(tc.function.arguments);
2289
+ if (snapLegacy)
2290
+ yield { type: "task_list_update", taskListSnapshot: snapLegacy };
2291
+ }
2292
+ }
2293
+ catch (err) {
2294
+ if (tc.function.name === "todo_write") {
2295
+ const snapLegacy = snapshotFromTodoWriteArgs(tc.function.arguments);
2296
+ if (snapLegacy)
2297
+ yield { type: "task_list_update", taskListSnapshot: snapLegacy };
2298
+ }
2299
+ }
1893
2300
  }
1894
2301
  break;
1895
2302
  }
@@ -3021,6 +3428,10 @@ export async function* executeToolEngine(args) {
3021
3428
  if (modelInfo?.contextWindow) {
3022
3429
  void deps.postTurnCompact(provider, system, modelInfo.contextWindow, signal).catch(() => { });
3023
3430
  }
3431
+ // Reactive delegation: report this turn's observed tool-output load so
3432
+ // the next turn can escalate to an isolated sub-session when it proves
3433
+ // heavy — independent of the fragile upfront router. See reactive-delegation.ts.
3434
+ deps.reportTurnToolLoad?.(_topLevelCapState?.cumulative ?? 0);
3024
3435
  yield { type: "done" };
3025
3436
  return;
3026
3437
  }
@@ -3167,6 +3578,10 @@ export async function* executeToolEngine(args) {
3167
3578
  if (modelInfo?.contextWindow) {
3168
3579
  void deps.postTurnCompact(provider, system, modelInfo.contextWindow, signal).catch(() => { });
3169
3580
  }
3581
+ // Reactive delegation: report this turn's observed tool-output load so
3582
+ // the next turn can escalate to an isolated sub-session when it proves
3583
+ // heavy — independent of the fragile upfront router. See reactive-delegation.ts.
3584
+ deps.reportTurnToolLoad?.(_topLevelCapState?.cumulative ?? 0);
3170
3585
  yield { type: "done" };
3171
3586
  return;
3172
3587
  }
@@ -3184,4 +3599,153 @@ export async function* executeToolEngine(args) {
3184
3599
  }
3185
3600
  }
3186
3601
  }
3602
+ export function coalesceReadOnlyMessages(messages) {
3603
+ if (!messages || messages.length === 0)
3604
+ return messages;
3605
+ const READ_ONLY_TOOLS = new Set([
3606
+ "read_file",
3607
+ "grep",
3608
+ "bash_output_get",
3609
+ "process_list",
3610
+ "delegation_read",
3611
+ "delegation_list",
3612
+ "ee_query",
3613
+ "ee_health",
3614
+ "usage_forensics",
3615
+ "lsp_query",
3616
+ "setup_guide",
3617
+ "selfverify_status",
3618
+ "selfverify_result",
3619
+ "selfverify_list",
3620
+ "list_vision_cache",
3621
+ "ee_feedback",
3622
+ "ee_write",
3623
+ ]);
3624
+ const result = [];
3625
+ let lastUserIdx = -1;
3626
+ for (let k = messages.length - 1; k >= 0; k--) {
3627
+ if (messages[k].role === "user") {
3628
+ lastUserIdx = k;
3629
+ break;
3630
+ }
3631
+ }
3632
+ if (lastUserIdx === -1)
3633
+ return messages;
3634
+ for (let k = 0; k <= lastUserIdx; k++) {
3635
+ result.push(messages[k]);
3636
+ }
3637
+ const postUser = messages.slice(lastUserIdx + 1);
3638
+ const groups = [];
3639
+ let currentAsst = null;
3640
+ for (const msg of postUser) {
3641
+ if (msg.role === "assistant") {
3642
+ if (currentAsst) {
3643
+ groups.push({ assistant: currentAsst, tool: null });
3644
+ }
3645
+ currentAsst = msg;
3646
+ }
3647
+ else if (msg.role === "tool") {
3648
+ if (currentAsst) {
3649
+ groups.push({ assistant: currentAsst, tool: msg });
3650
+ currentAsst = null;
3651
+ }
3652
+ else {
3653
+ result.push(msg);
3654
+ }
3655
+ }
3656
+ else {
3657
+ if (currentAsst) {
3658
+ groups.push({ assistant: currentAsst, tool: null });
3659
+ currentAsst = null;
3660
+ }
3661
+ result.push(msg);
3662
+ }
3663
+ }
3664
+ if (currentAsst) {
3665
+ groups.push({ assistant: currentAsst, tool: null });
3666
+ }
3667
+ const coalescedGroups = [];
3668
+ for (const group of groups) {
3669
+ const isReadOnly = (() => {
3670
+ if (!group.tool || !group.assistant)
3671
+ return false;
3672
+ const toolCalls = getToolCalls(group.assistant);
3673
+ if (toolCalls.length === 0)
3674
+ return false;
3675
+ return toolCalls.every((tc) => {
3676
+ const name = tc.toolName || tc.function?.name;
3677
+ return name && READ_ONLY_TOOLS.has(name);
3678
+ });
3679
+ })();
3680
+ if (isReadOnly) {
3681
+ const prev = coalescedGroups[coalescedGroups.length - 1];
3682
+ const prevIsReadOnly = prev &&
3683
+ (() => {
3684
+ if (!prev.tool || !prev.assistant)
3685
+ return false;
3686
+ const toolCalls = getToolCalls(prev.assistant);
3687
+ if (toolCalls.length === 0)
3688
+ return false;
3689
+ return toolCalls.every((tc) => {
3690
+ const name = tc.toolName || tc.function?.name;
3691
+ return name && READ_ONLY_TOOLS.has(name);
3692
+ });
3693
+ })();
3694
+ if (prevIsReadOnly) {
3695
+ prev.assistant = mergeAssistantMessages(prev.assistant, group.assistant);
3696
+ prev.tool = mergeToolMessages(prev.tool, group.tool);
3697
+ }
3698
+ else {
3699
+ coalescedGroups.push({ ...group });
3700
+ }
3701
+ }
3702
+ else {
3703
+ coalescedGroups.push(group);
3704
+ }
3705
+ }
3706
+ for (const group of coalescedGroups) {
3707
+ if (group.assistant)
3708
+ result.push(group.assistant);
3709
+ if (group.tool)
3710
+ result.push(group.tool);
3711
+ }
3712
+ return result;
3713
+ }
3714
+ function getToolCalls(msg) {
3715
+ if (Array.isArray(msg.toolCalls))
3716
+ return msg.toolCalls;
3717
+ if (Array.isArray(msg.content)) {
3718
+ return msg.content.filter((p) => p && p.type === "tool-call");
3719
+ }
3720
+ return [];
3721
+ }
3722
+ function mergeAssistantMessages(msg1, msg2) {
3723
+ const parts1 = Array.isArray(msg1.content)
3724
+ ? msg1.content
3725
+ : typeof msg1.content === "string" && msg1.content
3726
+ ? [{ type: "text", text: msg1.content }]
3727
+ : [];
3728
+ const parts2 = Array.isArray(msg2.content)
3729
+ ? msg2.content
3730
+ : typeof msg2.content === "string" && msg2.content
3731
+ ? [{ type: "text", text: msg2.content }]
3732
+ : [];
3733
+ const mergedContent = [...parts1, ...parts2];
3734
+ const res = {
3735
+ role: "assistant",
3736
+ content: mergedContent,
3737
+ };
3738
+ if (Array.isArray(msg1.toolCalls) || Array.isArray(msg2.toolCalls)) {
3739
+ res.toolCalls = [...(msg1.toolCalls ?? []), ...(msg2.toolCalls ?? [])];
3740
+ }
3741
+ return res;
3742
+ }
3743
+ function mergeToolMessages(msg1, msg2) {
3744
+ const parts1 = Array.isArray(msg1.content) ? msg1.content : [];
3745
+ const parts2 = Array.isArray(msg2.content) ? msg2.content : [];
3746
+ return {
3747
+ role: "tool",
3748
+ content: [...parts1, ...parts2],
3749
+ };
3750
+ }
3187
3751
  //# sourceMappingURL=tool-engine.js.map