muonroi-cli 1.8.3 → 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 (477) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +133 -122
  3. package/dist/packages/agent-harness-core/src/driver.d.ts +27 -1
  4. package/dist/packages/agent-harness-core/src/driver.js +46 -0
  5. package/dist/packages/agent-harness-core/src/event-tee.d.ts +48 -0
  6. package/dist/packages/agent-harness-core/src/event-tee.js +77 -0
  7. package/dist/packages/agent-harness-core/src/mcp-server.d.ts +11 -0
  8. package/dist/packages/agent-harness-core/src/mcp-server.js +87 -15
  9. package/dist/packages/agent-harness-core/src/protocol.d.ts +66 -2
  10. package/dist/packages/agent-harness-core/src/protocol.js +15 -0
  11. package/dist/packages/agent-harness-core/src/visual-quality.d.ts +58 -0
  12. package/dist/packages/agent-harness-core/src/visual-quality.js +141 -0
  13. package/dist/packages/agent-harness-opentui/src/agent-mode.d.ts +6 -0
  14. package/dist/packages/agent-harness-opentui/src/agent-mode.js +14 -1
  15. package/dist/packages/agent-harness-opentui/src/input-bridge.d.ts +2 -10
  16. package/dist/packages/agent-harness-opentui/src/input-bridge.js +103 -16
  17. package/dist/packages/agent-harness-opentui/src/install.d.ts +8 -0
  18. package/dist/packages/agent-harness-opentui/src/install.js +10 -0
  19. package/dist/packages/agent-harness-opentui/src/semantic.js +12 -10
  20. package/dist/packages/agent-harness-opentui/src/visual-capture.d.ts +56 -0
  21. package/dist/packages/agent-harness-opentui/src/visual-capture.js +103 -0
  22. package/dist/src/agent-harness/mock-model.d.ts +28 -0
  23. package/dist/src/agent-harness/mock-model.js +63 -1
  24. package/dist/src/agent-harness/test-spawn.js +31 -0
  25. package/dist/src/cli/config/screen-providers.js +1 -1
  26. package/dist/src/cli/cost-forensics.d.ts +10 -0
  27. package/dist/src/cli/cost-forensics.js +30 -15
  28. package/dist/src/cli/keys-bundle.d.ts +1 -1
  29. package/dist/src/cli/keys-bundle.js +1 -1
  30. package/dist/src/cli/keys.d.ts +2 -2
  31. package/dist/src/cli/keys.js +19 -81
  32. package/dist/src/council/clarifier.d.ts +28 -2
  33. package/dist/src/council/clarifier.js +81 -15
  34. package/dist/src/council/context.js +49 -15
  35. package/dist/src/council/debate-checkpoint.d.ts +129 -0
  36. package/dist/src/council/debate-checkpoint.js +176 -0
  37. package/dist/src/council/debate-planner.js +51 -3
  38. package/dist/src/council/debate-summary.d.ts +25 -0
  39. package/dist/src/council/debate-summary.js +85 -0
  40. package/dist/src/council/debate.d.ts +169 -2
  41. package/dist/src/council/debate.js +1210 -134
  42. package/dist/src/council/index.d.ts +85 -1
  43. package/dist/src/council/index.js +634 -196
  44. package/dist/src/council/leader.d.ts +26 -0
  45. package/dist/src/council/leader.js +150 -9
  46. package/dist/src/council/llm.d.ts +32 -0
  47. package/dist/src/council/llm.js +231 -38
  48. package/dist/src/council/panel-select.d.ts +30 -0
  49. package/dist/src/council/panel-select.js +72 -0
  50. package/dist/src/council/planner.js +23 -0
  51. package/dist/src/council/preflight.d.ts +7 -0
  52. package/dist/src/council/preflight.js +14 -2
  53. package/dist/src/council/prompts.d.ts +30 -3
  54. package/dist/src/council/prompts.js +254 -84
  55. package/dist/src/council/stance-recall.d.ts +42 -0
  56. package/dist/src/council/stance-recall.js +57 -0
  57. package/dist/src/council/strip-think.d.ts +17 -0
  58. package/dist/src/council/strip-think.js +33 -0
  59. package/dist/src/council/types.d.ts +128 -0
  60. package/dist/src/ee/artifact-cache.d.ts +16 -0
  61. package/dist/src/ee/artifact-cache.js +32 -0
  62. package/dist/src/ee/auth.d.ts +1 -0
  63. package/dist/src/ee/auth.js +15 -2
  64. package/dist/src/ee/bridge.d.ts +10 -0
  65. package/dist/src/ee/bridge.js +58 -0
  66. package/dist/src/ee/client.js +81 -18
  67. package/dist/src/ee/export-transcripts.d.ts +1 -0
  68. package/dist/src/ee/export-transcripts.js +8 -10
  69. package/dist/src/ee/extract-session.js +29 -0
  70. package/dist/src/ee/extract-style.d.ts +58 -0
  71. package/dist/src/ee/extract-style.js +270 -0
  72. package/dist/src/ee/recall-ledger.d.ts +9 -0
  73. package/dist/src/ee/recall-ledger.js +3 -0
  74. package/dist/src/ee/scope.d.ts +1 -0
  75. package/dist/src/ee/scope.js +26 -1
  76. package/dist/src/ee/search.d.ts +7 -0
  77. package/dist/src/ee/search.js +24 -0
  78. package/dist/src/ee/transcript-emit.js +2 -0
  79. package/dist/src/ee/types.d.ts +22 -0
  80. package/dist/src/ee/who-am-i-brain.d.ts +35 -0
  81. package/dist/src/ee/who-am-i-brain.js +220 -0
  82. package/dist/src/ee/who-am-i.d.ts +10 -3
  83. package/dist/src/ee/who-am-i.js +12 -0
  84. package/dist/src/ee/workflow-event.d.ts +48 -0
  85. package/dist/src/ee/workflow-event.js +81 -0
  86. package/dist/src/flow/compaction/compress.d.ts +3 -3
  87. package/dist/src/flow/compaction/compress.js +45 -8
  88. package/dist/src/flow/compaction/extract.d.ts +4 -7
  89. package/dist/src/flow/compaction/extract.js +50 -10
  90. package/dist/src/flow/compaction/index.d.ts +13 -1
  91. package/dist/src/flow/compaction/index.js +70 -3
  92. package/dist/src/flow/compaction/input-guard.d.ts +24 -0
  93. package/dist/src/flow/compaction/input-guard.js +43 -0
  94. package/dist/src/flow/fold-planning.d.ts +36 -0
  95. package/dist/src/flow/fold-planning.js +83 -0
  96. package/dist/src/flow/hierarchy.d.ts +146 -0
  97. package/dist/src/flow/hierarchy.js +427 -0
  98. package/dist/src/flow/index.d.ts +1 -0
  99. package/dist/src/flow/index.js +2 -0
  100. package/dist/src/flow/run-artifacts.d.ts +102 -0
  101. package/dist/src/flow/run-artifacts.js +208 -0
  102. package/dist/src/generated/version.d.ts +1 -1
  103. package/dist/src/generated/version.js +1 -1
  104. package/dist/src/gsd/assessment-schema.d.ts +44 -0
  105. package/dist/src/gsd/assessment-schema.js +134 -0
  106. package/dist/src/gsd/capability-registry.d.ts +45 -0
  107. package/dist/src/gsd/capability-registry.js +337 -0
  108. package/dist/src/gsd/complexity-assessor.d.ts +39 -0
  109. package/dist/src/gsd/complexity-assessor.js +152 -0
  110. package/dist/src/gsd/config-bridge.d.ts +7 -0
  111. package/dist/src/gsd/config-bridge.js +114 -0
  112. package/dist/src/gsd/config-loader.d.ts +27 -0
  113. package/dist/src/gsd/config-loader.js +50 -0
  114. package/dist/src/gsd/council-context.d.ts +44 -0
  115. package/dist/src/gsd/council-context.js +114 -0
  116. package/dist/src/gsd/ee-closure.d.ts +28 -0
  117. package/dist/src/gsd/ee-closure.js +49 -0
  118. package/dist/src/gsd/flags.d.ts +55 -0
  119. package/dist/src/gsd/flags.js +83 -0
  120. package/dist/src/gsd/gsd-dispatch.d.ts +58 -0
  121. package/dist/src/gsd/gsd-dispatch.js +131 -0
  122. package/dist/src/gsd/gsd-runtime.d.ts +22 -0
  123. package/dist/src/gsd/gsd-runtime.js +37 -0
  124. package/dist/src/gsd/host-adapter.d.ts +11 -0
  125. package/dist/src/gsd/host-adapter.js +29 -0
  126. package/dist/src/gsd/index.d.ts +24 -1
  127. package/dist/src/gsd/index.js +27 -0
  128. package/dist/src/gsd/loop-host-contract.d.ts +21 -0
  129. package/dist/src/gsd/loop-host-contract.js +39 -0
  130. package/dist/src/gsd/loop-host.d.ts +69 -0
  131. package/dist/src/gsd/loop-host.js +245 -0
  132. package/dist/src/gsd/loop-resolver.d.ts +36 -0
  133. package/dist/src/gsd/loop-resolver.js +79 -0
  134. package/dist/src/gsd/model-tier.d.ts +13 -0
  135. package/dist/src/gsd/model-tier.js +45 -0
  136. package/dist/src/gsd/mutation-gate.d.ts +16 -0
  137. package/dist/src/gsd/mutation-gate.js +41 -0
  138. package/dist/src/gsd/native-roadmap.d.ts +89 -0
  139. package/dist/src/gsd/native-roadmap.js +343 -0
  140. package/dist/src/gsd/native-state.d.ts +47 -0
  141. package/dist/src/gsd/native-state.js +220 -0
  142. package/dist/src/gsd/paths.d.ts +23 -0
  143. package/dist/src/gsd/paths.js +66 -0
  144. package/dist/src/gsd/phase-dag.d.ts +12 -0
  145. package/dist/src/gsd/phase-dag.js +94 -0
  146. package/dist/src/gsd/phase-sync.d.ts +42 -0
  147. package/dist/src/gsd/phase-sync.js +321 -0
  148. package/dist/src/gsd/pil-gate-context.d.ts +13 -0
  149. package/dist/src/gsd/pil-gate-context.js +64 -0
  150. package/dist/src/gsd/pil-gate-critic.d.ts +19 -0
  151. package/dist/src/gsd/pil-gate-critic.js +74 -0
  152. package/dist/src/gsd/plan-council-prompts.d.ts +25 -0
  153. package/dist/src/gsd/plan-council-prompts.js +79 -0
  154. package/dist/src/gsd/plan-council.d.ts +44 -0
  155. package/dist/src/gsd/plan-council.js +251 -0
  156. package/dist/src/gsd/plan-gate-vocabulary.d.ts +40 -0
  157. package/dist/src/gsd/plan-gate-vocabulary.js +64 -0
  158. package/dist/src/gsd/product-workspace.d.ts +13 -0
  159. package/dist/src/gsd/product-workspace.js +124 -0
  160. package/dist/src/gsd/ship-bridge.d.ts +25 -0
  161. package/dist/src/gsd/ship-bridge.js +65 -0
  162. package/dist/src/gsd/state-document.d.ts +40 -0
  163. package/dist/src/gsd/state-document.js +163 -0
  164. package/dist/src/gsd/verdict-schema.d.ts +39 -0
  165. package/dist/src/gsd/verdict-schema.js +144 -0
  166. package/dist/src/gsd/verify-context.d.ts +22 -0
  167. package/dist/src/gsd/verify-context.js +27 -0
  168. package/dist/src/gsd/verify-council-prompts.d.ts +19 -0
  169. package/dist/src/gsd/verify-council-prompts.js +85 -0
  170. package/dist/src/gsd/verify-council.d.ts +25 -0
  171. package/dist/src/gsd/verify-council.js +119 -0
  172. package/dist/src/gsd/verify-gate-vocabulary.d.ts +25 -0
  173. package/dist/src/gsd/verify-gate-vocabulary.js +46 -0
  174. package/dist/src/gsd/workflow-engine.d.ts +60 -0
  175. package/dist/src/gsd/workflow-engine.js +207 -0
  176. package/dist/src/gsd/workflow-tools.d.ts +13 -0
  177. package/dist/src/gsd/workflow-tools.js +277 -0
  178. package/dist/src/hooks/index.js +1 -1
  179. package/dist/src/index.js +44 -11
  180. package/dist/src/maintain/pr-builder.js +23 -13
  181. package/dist/src/mcp/auto-setup.js +57 -32
  182. package/dist/src/mcp/client-pool.js +1 -1
  183. package/dist/src/mcp/ee-tools.js +1 -0
  184. package/dist/src/mcp/oauth-callback.js +2 -2
  185. package/dist/src/mcp/research-onboarding.js +8 -7
  186. package/dist/src/mcp/runtime.js +34 -2
  187. package/dist/src/mcp/setup-guide-text.d.ts +1 -1
  188. package/dist/src/mcp/setup-guide-text.js +77 -76
  189. package/dist/src/models/catalog-client.d.ts +87 -0
  190. package/dist/src/models/catalog-client.js +105 -38
  191. package/dist/src/models/catalog.json +528 -265
  192. package/dist/src/models/registry.d.ts +22 -7
  193. package/dist/src/models/registry.js +73 -10
  194. package/dist/src/ops/doctor.js +8 -8
  195. package/dist/src/orchestrator/auto-commit.js +1 -1
  196. package/dist/src/orchestrator/batch-turn-runner.js +2 -2
  197. package/dist/src/orchestrator/cache-prefix.d.ts +67 -0
  198. package/dist/src/orchestrator/cache-prefix.js +83 -0
  199. package/dist/src/orchestrator/compact-request.d.ts +32 -0
  200. package/dist/src/orchestrator/compact-request.js +41 -0
  201. package/dist/src/orchestrator/compaction.d.ts +10 -0
  202. package/dist/src/orchestrator/compaction.js +27 -7
  203. package/dist/src/orchestrator/council-manager.d.ts +12 -3
  204. package/dist/src/orchestrator/council-manager.js +65 -24
  205. package/dist/src/orchestrator/cross-turn-dedup.d.ts +6 -0
  206. package/dist/src/orchestrator/cross-turn-dedup.js +22 -1
  207. package/dist/src/orchestrator/error-utils.d.ts +29 -0
  208. package/dist/src/orchestrator/error-utils.js +132 -24
  209. package/dist/src/orchestrator/grounding-check.js +39 -1
  210. package/dist/src/orchestrator/message-processor.js +242 -33
  211. package/dist/src/orchestrator/orchestrator.d.ts +39 -3
  212. package/dist/src/orchestrator/orchestrator.js +651 -102
  213. package/dist/src/orchestrator/preprocessor.js +1 -1
  214. package/dist/src/orchestrator/proactive-compact-detector.d.ts +26 -0
  215. package/dist/src/orchestrator/proactive-compact-detector.js +36 -0
  216. package/dist/src/orchestrator/prompts.js +159 -159
  217. package/dist/src/orchestrator/reactive-delegation.d.ts +39 -0
  218. package/dist/src/orchestrator/reactive-delegation.js +59 -0
  219. package/dist/src/orchestrator/retry-classifier.d.ts +2 -1
  220. package/dist/src/orchestrator/retry-classifier.js +46 -2
  221. package/dist/src/orchestrator/safety-intercept.d.ts +45 -0
  222. package/dist/src/orchestrator/safety-intercept.js +55 -0
  223. package/dist/src/orchestrator/scope-reminder.js +1 -1
  224. package/dist/src/orchestrator/session-experience.d.ts +2 -1
  225. package/dist/src/orchestrator/session-experience.js +2 -1
  226. package/dist/src/orchestrator/should-run-gate.d.ts +5 -0
  227. package/dist/src/orchestrator/should-run-gate.js +18 -0
  228. package/dist/src/orchestrator/stall-watchdog.d.ts +24 -3
  229. package/dist/src/orchestrator/stall-watchdog.js +47 -13
  230. package/dist/src/orchestrator/stream-runner.js +62 -29
  231. package/dist/src/orchestrator/sub-agent-cap.d.ts +13 -1
  232. package/dist/src/orchestrator/sub-agent-cap.js +16 -1
  233. package/dist/src/orchestrator/sub-agent-model-tier.d.ts +13 -1
  234. package/dist/src/orchestrator/sub-agent-model-tier.js +16 -2
  235. package/dist/src/orchestrator/subagent-compactor.d.ts +53 -1
  236. package/dist/src/orchestrator/subagent-compactor.js +126 -15
  237. package/dist/src/orchestrator/tool-engine.d.ts +26 -0
  238. package/dist/src/orchestrator/tool-engine.js +669 -56
  239. package/dist/src/orchestrator/tool-limit-auto-recover.d.ts +22 -0
  240. package/dist/src/orchestrator/tool-limit-auto-recover.js +30 -0
  241. package/dist/src/orchestrator/turn-runner-deps.d.ts +19 -0
  242. package/dist/src/orchestrator/turn-watchdog.d.ts +37 -0
  243. package/dist/src/orchestrator/turn-watchdog.js +55 -0
  244. package/dist/src/pil/agent-operating-contract.d.ts +1 -1
  245. package/dist/src/pil/agent-operating-contract.js +1 -1
  246. package/dist/src/pil/cheap-model-playbook.d.ts +1 -1
  247. package/dist/src/pil/cheap-model-playbook.js +5 -1
  248. package/dist/src/pil/cheap-model-workbooks.d.ts +1 -1
  249. package/dist/src/pil/cheap-model-workbooks.js +1 -1
  250. package/dist/src/pil/discovery-types.d.ts +1 -0
  251. package/dist/src/pil/discovery.js +16 -11
  252. package/dist/src/pil/layer1-intent.d.ts +18 -6
  253. package/dist/src/pil/layer1-intent.js +66 -757
  254. package/dist/src/pil/layer15-context-scan.js +15 -1
  255. package/dist/src/pil/layer2_5-ponytail.js +8 -8
  256. package/dist/src/pil/layer3-ee-injection.js +23 -8
  257. package/dist/src/pil/layer4-gsd.js +69 -16
  258. package/dist/src/pil/layer5-context.js +7 -3
  259. package/dist/src/pil/layer6-output.d.ts +23 -0
  260. package/dist/src/pil/layer6-output.js +5 -1
  261. package/dist/src/pil/llm-classify.d.ts +33 -2
  262. package/dist/src/pil/llm-classify.js +123 -131
  263. package/dist/src/pil/native-capabilities-workbook.d.ts +1 -1
  264. package/dist/src/pil/native-capabilities-workbook.js +1 -0
  265. package/dist/src/pil/pipeline.js +34 -2
  266. package/dist/src/pil/response-tools.js +5 -3
  267. package/dist/src/pil/schema.d.ts +1 -0
  268. package/dist/src/pil/schema.js +2 -0
  269. package/dist/src/pil/types.d.ts +18 -0
  270. package/dist/src/playbook/directives.d.ts +4 -0
  271. package/dist/src/playbook/directives.js +17 -5
  272. package/dist/src/product-loop/backlog-builder.d.ts +14 -1
  273. package/dist/src/product-loop/backlog-builder.js +30 -6
  274. package/dist/src/product-loop/discovery-context-format.js +3 -1
  275. package/dist/src/product-loop/discovery-ecosystem.js +4 -1
  276. package/dist/src/product-loop/discovery-interview.js +32 -3
  277. package/dist/src/product-loop/discovery-schema.js +5 -1
  278. package/dist/src/product-loop/done-gate.js +3 -3
  279. package/dist/src/product-loop/ideal-trace.d.ts +7 -0
  280. package/dist/src/product-loop/ideal-trace.js +64 -0
  281. package/dist/src/product-loop/index.d.ts +13 -1
  282. package/dist/src/product-loop/index.js +333 -52
  283. package/dist/src/product-loop/loop-driver.d.ts +7 -0
  284. package/dist/src/product-loop/loop-driver.js +327 -116
  285. package/dist/src/product-loop/phase-plan.d.ts +5 -0
  286. package/dist/src/product-loop/phase-plan.js +39 -2
  287. package/dist/src/product-loop/phase-runner.js +9 -1
  288. package/dist/src/product-loop/progress-snapshot.js +4 -4
  289. package/dist/src/product-loop/sprint-runner.d.ts +111 -0
  290. package/dist/src/product-loop/sprint-runner.js +559 -16
  291. package/dist/src/product-loop/types.d.ts +36 -5
  292. package/dist/src/providers/adapter.d.ts +1 -1
  293. package/dist/src/providers/adapter.js +3 -4
  294. package/dist/src/providers/auth/browser-flow.d.ts +1 -1
  295. package/dist/src/providers/auth/browser-flow.js +1 -1
  296. package/dist/src/providers/auth/openai-oauth.js +1 -1
  297. package/dist/src/providers/auth/registry.js +0 -34
  298. package/dist/src/providers/auth/token-store.js +4 -1
  299. package/dist/src/providers/auth/types.d.ts +1 -1
  300. package/dist/src/providers/auth/types.js +1 -1
  301. package/dist/src/providers/capabilities.d.ts +24 -5
  302. package/dist/src/providers/capabilities.js +42 -24
  303. package/dist/src/providers/endpoints.d.ts +2 -2
  304. package/dist/src/providers/endpoints.js +11 -10
  305. package/dist/src/providers/keychain.d.ts +1 -1
  306. package/dist/src/providers/keychain.js +7 -9
  307. package/dist/src/providers/mcp-vision-bridge.js +82 -172
  308. package/dist/src/providers/openai-compatible.js +8 -1
  309. package/dist/src/providers/pricing.d.ts +2 -2
  310. package/dist/src/providers/pricing.js +3 -13
  311. package/dist/src/providers/runtime.d.ts +27 -2
  312. package/dist/src/providers/runtime.js +78 -15
  313. package/dist/src/providers/strategies/base.strategy.d.ts +16 -0
  314. package/dist/src/providers/strategies/base.strategy.js +24 -1
  315. package/dist/src/providers/strategies/{siliconflow.strategy.d.ts → opencode-go.strategy.d.ts} +3 -3
  316. package/dist/src/providers/strategies/opencode-go.strategy.js +83 -0
  317. package/dist/src/providers/strategies/registry.js +4 -4
  318. package/dist/src/providers/strategies/thinking-mode.d.ts +109 -0
  319. package/dist/src/providers/strategies/thinking-mode.js +280 -1
  320. package/dist/src/providers/strategies/zai.strategy.d.ts +14 -0
  321. package/dist/src/providers/strategies/zai.strategy.js +44 -0
  322. package/dist/src/providers/types.d.ts +5 -6
  323. package/dist/src/providers/types.js +2 -2
  324. package/dist/src/providers/vision-backend.d.ts +47 -0
  325. package/dist/src/providers/vision-backend.js +258 -0
  326. package/dist/src/providers/vision-proxy.d.ts +22 -9
  327. package/dist/src/providers/vision-proxy.js +63 -132
  328. package/dist/src/providers/wire-debug.js +95 -0
  329. package/dist/src/reporter/index.js +1 -1
  330. package/dist/src/router/decide.d.ts +13 -0
  331. package/dist/src/router/decide.js +138 -36
  332. package/dist/src/router/peak-hour.d.ts +38 -0
  333. package/dist/src/router/peak-hour.js +107 -0
  334. package/dist/src/router/step-router.js +3 -2
  335. package/dist/src/router/warm.js +4 -5
  336. package/dist/src/scaffold/bb-ecosystem-apply.js +47 -47
  337. package/dist/src/scaffold/bb-quality-gate.js +5 -5
  338. package/dist/src/scaffold/continuation-prompt.d.ts +11 -0
  339. package/dist/src/scaffold/continuation-prompt.js +86 -60
  340. package/dist/src/scaffold/init-new.js +453 -453
  341. package/dist/src/scaffold/point-to-existing.d.ts +21 -0
  342. package/dist/src/scaffold/point-to-existing.js +25 -0
  343. package/dist/src/self-qa/agentic-loop.js +22 -22
  344. package/dist/src/{ui/state → state}/active-run.d.ts +19 -0
  345. package/dist/src/{ui/state → state}/active-run.js +21 -0
  346. package/dist/src/{ui/status-bar/store.d.ts → state/status-bar-store.d.ts} +4 -1
  347. package/dist/src/{ui/status-bar/store.js → state/status-bar-store.js} +12 -9
  348. package/dist/src/state/turn-trace.d.ts +43 -0
  349. package/dist/src/state/turn-trace.js +32 -0
  350. package/dist/src/storage/db.js +2 -1
  351. package/dist/src/storage/index.d.ts +1 -1
  352. package/dist/src/storage/index.js +1 -1
  353. package/dist/src/storage/interaction-log.d.ts +1 -1
  354. package/dist/src/storage/interaction-log.js +5 -5
  355. package/dist/src/storage/migrations.js +196 -126
  356. package/dist/src/storage/session-experience-store.js +4 -4
  357. package/dist/src/storage/sessions.d.ts +28 -10
  358. package/dist/src/storage/sessions.js +112 -55
  359. package/dist/src/storage/transcript-view.js +1 -1
  360. package/dist/src/storage/transcript.d.ts +51 -0
  361. package/dist/src/storage/transcript.js +383 -112
  362. package/dist/src/storage/usage.js +14 -14
  363. package/dist/src/storage/workspaces.js +12 -12
  364. package/dist/src/tools/file.d.ts +15 -0
  365. package/dist/src/tools/file.js +32 -0
  366. package/dist/src/tools/native-tools.js +5 -0
  367. package/dist/src/tools/registry.d.ts +3 -0
  368. package/dist/src/tools/registry.js +460 -22
  369. package/dist/src/tools/research.d.ts +29 -0
  370. package/dist/src/tools/research.js +233 -0
  371. package/dist/src/types/index.d.ts +118 -3
  372. package/dist/src/ui/app.js +0 -0
  373. package/dist/src/ui/cards/product-status-card.js +1 -1
  374. package/dist/src/ui/components/bubble-body-guard.d.ts +27 -0
  375. package/dist/src/ui/components/bubble-body-guard.js +50 -0
  376. package/dist/src/ui/components/context-rail.d.ts +26 -0
  377. package/dist/src/ui/components/context-rail.js +33 -0
  378. package/dist/src/ui/components/council-conclusion-card.d.ts +73 -0
  379. package/dist/src/ui/components/council-conclusion-card.js +420 -0
  380. package/dist/src/ui/components/council-debate-pill.d.ts +36 -0
  381. package/dist/src/ui/components/council-debate-pill.js +34 -0
  382. package/dist/src/ui/components/council-info-card.js +2 -2
  383. package/dist/src/ui/components/council-leader-bubble.d.ts +10 -3
  384. package/dist/src/ui/components/council-leader-bubble.js +21 -11
  385. package/dist/src/ui/components/council-message-bubble.d.ts +15 -2
  386. package/dist/src/ui/components/council-message-bubble.js +16 -15
  387. package/dist/src/ui/components/council-phase-timeline.d.ts +9 -1
  388. package/dist/src/ui/components/council-phase-timeline.js +49 -15
  389. package/dist/src/ui/components/council-placeholder-bubble.d.ts +16 -9
  390. package/dist/src/ui/components/council-placeholder-bubble.js +32 -29
  391. package/dist/src/ui/components/council-question-card.js +12 -12
  392. package/dist/src/ui/components/council-rail-rounds.d.ts +26 -0
  393. package/dist/src/ui/components/council-rail-rounds.js +57 -0
  394. package/dist/src/ui/components/council-round-group.d.ts +38 -0
  395. package/dist/src/ui/components/council-round-group.js +88 -0
  396. package/dist/src/ui/components/council-status-list.d.ts +3 -1
  397. package/dist/src/ui/components/council-status-list.js +36 -24
  398. package/dist/src/ui/components/council-synthesis-banner.d.ts +7 -2
  399. package/dist/src/ui/components/council-synthesis-banner.js +20 -5
  400. package/dist/src/ui/components/halt-recovery-card.js +9 -5
  401. package/dist/src/ui/components/jump-to-latest-pill.d.ts +11 -0
  402. package/dist/src/ui/components/jump-to-latest-pill.js +14 -0
  403. package/dist/src/ui/components/prompt-box.js +18 -16
  404. package/dist/src/ui/components/session-tree-card.d.ts +14 -0
  405. package/dist/src/ui/components/session-tree-card.js +46 -0
  406. package/dist/src/ui/components/slash-inline-menu.d.ts +12 -0
  407. package/dist/src/ui/components/slash-inline-menu.js +26 -5
  408. package/dist/src/ui/components/task-list-panel.d.ts +14 -1
  409. package/dist/src/ui/components/task-list-panel.js +22 -2
  410. package/dist/src/ui/containers/modals-layer.d.ts +2 -1
  411. package/dist/src/ui/hooks/use-session-picker.d.ts +3 -3
  412. package/dist/src/ui/mcp-modal.js +2 -4
  413. package/dist/src/ui/modals/api-key-modal.js +1 -1
  414. package/dist/src/ui/modals/connect-modal.js +4 -3
  415. package/dist/src/ui/modals/session-picker-modal.d.ts +2 -2
  416. package/dist/src/ui/modals/session-picker-modal.js +3 -5
  417. package/dist/src/ui/picker-providers.d.ts +1 -1
  418. package/dist/src/ui/picker-providers.js +1 -1
  419. package/dist/src/ui/primitives/index.d.ts +1 -0
  420. package/dist/src/ui/primitives/index.js +2 -0
  421. package/dist/src/ui/primitives/semantic-primitives.d.ts +76 -0
  422. package/dist/src/ui/primitives/semantic-primitives.js +81 -0
  423. package/dist/src/ui/slash/compact.js +5 -7
  424. package/dist/src/ui/slash/cost.js +1 -1
  425. package/dist/src/ui/slash/council-inspect.js +4 -4
  426. package/dist/src/ui/slash/council.js +19 -1
  427. package/dist/src/ui/slash/debug.d.ts +3 -31
  428. package/dist/src/ui/slash/debug.js +9 -20
  429. package/dist/src/ui/slash/ideal.d.ts +6 -2
  430. package/dist/src/ui/slash/ideal.js +97 -7
  431. package/dist/src/ui/slash/menu-items.d.ts +7 -0
  432. package/dist/src/ui/slash/menu-items.js +12 -18
  433. package/dist/src/ui/slash/registry.d.ts +2 -0
  434. package/dist/src/ui/slash/registry.js +4 -0
  435. package/dist/src/ui/status-bar/cache-hit.d.ts +6 -0
  436. package/dist/src/ui/status-bar/cache-hit.js +9 -0
  437. package/dist/src/ui/status-bar/index.d.ts +1 -1
  438. package/dist/src/ui/status-bar/index.js +7 -3
  439. package/dist/src/ui/status-bar/usd-meter.d.ts +5 -4
  440. package/dist/src/ui/status-bar/usd-meter.js +6 -4
  441. package/dist/src/ui/theme.d.ts +1 -0
  442. package/dist/src/ui/theme.js +2 -0
  443. package/dist/src/ui/types.d.ts +7 -0
  444. package/dist/src/ui/use-app-logic.js +0 -0
  445. package/dist/src/ui/utils/format.d.ts +14 -0
  446. package/dist/src/ui/utils/format.js +23 -3
  447. package/dist/src/usage/downgrade.js +2 -2
  448. package/dist/src/usage/product-ledger.js +2 -2
  449. package/dist/src/utils/clipboard-image.js +23 -23
  450. package/dist/src/utils/install-manager.js +14 -11
  451. package/dist/src/utils/logger.js +2 -2
  452. package/dist/src/utils/permission-mode.js +5 -3
  453. package/dist/src/utils/redactor.js +1 -1
  454. package/dist/src/utils/settings.d.ts +153 -5
  455. package/dist/src/utils/settings.js +233 -29
  456. package/dist/src/utils/side-question.js +2 -2
  457. package/dist/src/utils/skills.js +3 -3
  458. package/dist/src/utils/visible-retry.d.ts +11 -0
  459. package/dist/src/utils/visible-retry.js +10 -1
  460. package/dist/src/verify/entrypoint.d.ts +1 -1
  461. package/dist/src/verify/entrypoint.js +1 -1
  462. package/dist/src/verify/recipes.d.ts +13 -0
  463. package/dist/src/verify/recipes.js +15 -0
  464. package/package.json +135 -132
  465. package/dist/src/providers/auth/gcloud.d.ts +0 -28
  466. package/dist/src/providers/auth/gcloud.js +0 -102
  467. package/dist/src/providers/auth/gemini-oauth.d.ts +0 -82
  468. package/dist/src/providers/auth/gemini-oauth.js +0 -472
  469. package/dist/src/providers/gemini.d.ts +0 -11
  470. package/dist/src/providers/gemini.js +0 -45
  471. package/dist/src/providers/siliconflow-sse-repair.d.ts +0 -58
  472. package/dist/src/providers/siliconflow-sse-repair.js +0 -177
  473. package/dist/src/providers/strategies/google.strategy.d.ts +0 -22
  474. package/dist/src/providers/strategies/google.strategy.js +0 -174
  475. package/dist/src/providers/strategies/siliconflow.strategy.js +0 -29
  476. package/dist/src/ui/containers/chat-feed.d.ts +0 -40
  477. 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
@@ -193,10 +225,65 @@ export function rewriteSafetyApprovedToolResults(messages) {
193
225
  return changed ? { ...m, content: newContent } : m;
194
226
  });
195
227
  }
228
+ export class SimpleMutex {
229
+ queue = Promise.resolve();
230
+ async run(fn) {
231
+ let resolveLock;
232
+ const lockPromise = new Promise((resolve) => {
233
+ resolveLock = resolve;
234
+ });
235
+ const previous = this.queue;
236
+ this.queue = lockPromise;
237
+ await previous;
238
+ try {
239
+ return await fn();
240
+ }
241
+ finally {
242
+ resolveLock();
243
+ }
244
+ }
245
+ }
196
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
+ }
197
276
  export async function* executeToolEngine(args) {
198
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;
199
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();
200
287
  let stallTriggered = false;
201
288
  // Time-to-first-byte stall RE-PROMPT: some providers (observed:
202
289
  // xai/grok-build-0.1) accept the request then never send the first byte —
@@ -225,6 +312,10 @@ export async function* executeToolEngine(args) {
225
312
  // the turn when exceeded. Prevents the session 526a83cf22df pattern
226
313
  // where 3 user messages burnt 82% of 2.44M tokens in 36 LLM calls.
227
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;
228
319
  // Live-queue steering: messages the user typed mid-turn are drained at a
229
320
  // prepareStep boundary and accumulated here, then re-appended (deduped) to
230
321
  // the messages returned for each subsequent step. Loop-persistent so they
@@ -251,11 +342,20 @@ export async function* executeToolEngine(args) {
251
342
  // and stay productive. `plan` keeps the old behaviour (architectural
252
343
  // decisions deserve debate regardless of length).
253
344
  const autoCouncilTypes = new Set(["plan", "analyze"]);
254
- const councilRoles = getRoleModels();
255
- const configuredRoleCount = Object.values(councilRoles).filter(Boolean).length;
256
- 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";
257
355
  const autoCouncilConfidence = getAutoCouncilConfidence();
258
356
  const autoCouncilMinRoles = getAutoCouncilMinRoles();
357
+ const sessionModelIsReasoning = isReasoningModel(deps.modelId);
358
+ const skipReasoningSetting = isAutoCouncilSkipReasoning();
259
359
  const _complexityFromTrace = pilCtx._intentTrace
260
360
  ?.complexity;
261
361
  const _complexityGatePassed = pilCtx.taskType === "plan" || _complexityFromTrace === undefined || _complexityFromTrace !== "low";
@@ -263,9 +363,13 @@ export async function* executeToolEngine(args) {
263
363
  autoCouncilTypes.has(pilCtx.taskType) &&
264
364
  pilCtx.confidence >= autoCouncilConfidence &&
265
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;
266
369
  const shouldAutoCouncil = !deps.councilManager.isContinuation &&
267
370
  isAutoCouncilEnabled() &&
268
371
  configuredRoleCount >= autoCouncilMinRoles &&
372
+ !shouldSkipForReasoning &&
269
373
  (taskTypeMatch || heavyTier);
270
374
  // Always log the auto-council decision (taken or skipped) with the gate
271
375
  // values that decided it. Lets reports answer "why did this turn cost
@@ -277,6 +381,8 @@ export async function* executeToolEngine(args) {
277
381
  return "feature-disabled";
278
382
  if (configuredRoleCount < autoCouncilMinRoles)
279
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)`;
280
386
  if (!taskTypeMatch && !heavyTier) {
281
387
  if (!pilCtx.taskType || !autoCouncilTypes.has(pilCtx.taskType))
282
388
  return `taskType=${pilCtx.taskType ?? "null"} not in plan|analyze`;
@@ -304,6 +410,8 @@ export async function* executeToolEngine(args) {
304
410
  autoCouncilConfidence,
305
411
  autoCouncilMinRoles,
306
412
  heavyTier,
413
+ sessionModelIsReasoning,
414
+ skipReasoningSetting,
307
415
  isContinuation: deps.councilManager.isContinuation,
308
416
  },
309
417
  }).catch(() => undefined);
@@ -312,14 +420,35 @@ export async function* executeToolEngine(args) {
312
420
  ? `complexity=heavy${pilCtx.taskType ? ` task=${pilCtx.taskType}` : ""}`
313
421
  : `${pilCtx.taskType} task detected with ${(pilCtx.confidence * 100).toFixed(0)}% confidence`;
314
422
  yield { type: "content", content: `\n[Auto-council triggered: ${reason}]\n` };
315
- 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
+ });
316
436
  const synthesis = deps.councilManager.lastSynthesis;
437
+ const chosenAction = deps.councilManager.lastPostDebateAction;
317
438
  deps.councilManager.setLastSynthesis(null);
318
- 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) {
319
448
  yield { type: "content", content: "\n[Auto-continuing with council recommendations...]\n" };
320
449
  deps.councilManager.setContinuation(true);
321
450
  try {
322
- yield* deps.processMessage(`Council debate completed. Synthesis:\n\n${synthesis}\n\nProceed with the recommended action items.`, observer);
451
+ yield* deps.processMessage(continuationPrompt, observer);
323
452
  }
324
453
  finally {
325
454
  deps.councilManager.setContinuation(false);
@@ -327,6 +456,12 @@ export async function* executeToolEngine(args) {
327
456
  }
328
457
  return;
329
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
+ }
330
465
  if (deps.batchApi) {
331
466
  try {
332
467
  yield* deps.processMessageBatchTurn({
@@ -464,14 +599,29 @@ export async function* executeToolEngine(args) {
464
599
  cachedImageCount: listCachedImages().length,
465
600
  priorTurnHadTools: deps.messages.some((m) => m?.role === "tool"),
466
601
  });
602
+ const depthTier = pilCtx.modelDepthTier ??
603
+ pilCtx.complexityTier ??
604
+ "standard";
467
605
  const baseToolsRaw = createBuiltinTools(deps.bash, deps.mode, {
468
606
  runTask: (request, abortSignal) => deps.runTask(request, combineAbortSignals(signal, abortSignal)),
469
607
  runDelegation: (request, abortSignal) => deps.runDelegation(request, combineAbortSignals(signal, abortSignal)),
470
608
  readDelegation: (id) => deps.readDelegation(id),
471
609
  listDelegations: () => deps.listDelegations(),
472
610
  modelId: turnModelId,
611
+ depthTier,
612
+ sessionId: deps.session?.id,
473
613
  includeVisionTools,
474
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
+ },
475
625
  });
476
626
  // Top-level cumulative cap state. We accumulate the raw tool set
477
627
  // (base + MCP + PIL response tools) across the assembly below,
@@ -495,16 +645,23 @@ export async function* executeToolEngine(args) {
495
645
  // history) is preserved.
496
646
  const turnCaps = getProviderCapabilities(requireRuntimeProvider(runtime));
497
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;
498
652
  let rawToolSet = !turnCaps.supportsClientTools(runtime.modelInfo)
499
653
  ? {}
500
654
  : isChitchat && !_priorTurnHadTools
501
655
  ? {}
502
- : baseToolsRaw;
503
- // 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'
504
660
  // worth of tool schemas (~20K input tokens). PIL Layer 1 already
505
661
  // gates this conservatively (≤10 chars + ≤2 words OR brain "none").
506
662
  if (deps.mode === "agent" &&
507
663
  (!isChitchat || _priorTurnHadTools) &&
664
+ !isDirectAnswer &&
508
665
  turnCaps.supportsClientTools(runtime.modelInfo)) {
509
666
  // Smart MCP filter: drop OPTIONAL MCP servers whose category the
510
667
  // current message gives no signal for. Browser/vision servers
@@ -611,13 +768,63 @@ export async function* executeToolEngine(args) {
611
768
  // Apply the top-level cumulative cap once over the fully-assembled
612
769
  // raw tool set. State is per-turn; each turn gets a fresh budget.
613
770
  const topLevelCap = wrapToolSetWithCap(rawToolSet, {
614
- maxCumulativeChars: getTopLevelToolBudgetChars(deps.maxToolRounds),
771
+ maxCumulativeChars: getTopLevelToolBudgetChars(deps.maxToolRounds, contextWindow),
615
772
  midTierRatio: 0.5,
616
773
  highTierRatio: 0.8,
617
774
  label: "top-level",
618
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;
619
779
  // Phase C3: layer cross-turn dedup on top of the top-level cap.
620
780
  const tools = wrapToolSetWithReadBudget(wrapToolSetWithDedup(topLevelCap.tools, deps.crossTurnDedup), deps.readBudget);
781
+ // Wrap non-read-only tools in a turn-scoped mutex to prevent race conditions during parallel execution.
782
+ const writeMutex = new SimpleMutex();
783
+ const READ_ONLY_TOOLS = new Set([
784
+ "read_file",
785
+ "grep",
786
+ "bash_output_get",
787
+ "process_list",
788
+ "delegation_read",
789
+ "delegation_list",
790
+ "ee_query",
791
+ "ee_health",
792
+ "usage_forensics",
793
+ "lsp_query",
794
+ "setup_guide",
795
+ "selfverify_status",
796
+ "selfverify_result",
797
+ "selfverify_list",
798
+ "list_vision_cache",
799
+ "ee_feedback",
800
+ "ee_write",
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;
808
+ for (const name of Object.keys(tools)) {
809
+ const tool = tools[name];
810
+ if (tool &&
811
+ typeof tool.execute === "function" &&
812
+ !READ_ONLY_TOOLS.has(name) &&
813
+ !name.startsWith("respond_")) {
814
+ const originalExecute = tool.execute;
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
+ }
824
+ return writeMutex.run(() => originalExecute(input, context));
825
+ };
826
+ }
827
+ }
621
828
  captureToolSchemas(tools);
622
829
  let responseToolCalled = false;
623
830
  // A turn must surface exactly ONE final structured answer. Cheap
@@ -712,7 +919,40 @@ export async function* executeToolEngine(args) {
712
919
  // (non-cached) system message via the slice below.
713
920
  const mcpCapabilityBlock = buildMcpCapabilityBlock(Object.keys(tools));
714
921
  const systemWithCaps = mcpCapabilityBlock ? `${systemWithShell}${mcpCapabilityBlock}` : systemWithShell;
715
- 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
716
956
  ? [
717
957
  {
718
958
  role: "system",
@@ -724,7 +964,9 @@ export async function* executeToolEngine(args) {
724
964
  content: systemWithCaps.slice(systemParts.staticPrefix.length),
725
965
  },
726
966
  ]
727
- : systemWithCaps;
967
+ : _willFoldDynamicTail
968
+ ? _nonClaudeFront
969
+ : systemWithCaps;
728
970
  // Capture prompt-size breakdown so recordUsage can attach it to the
729
971
  // cost-log entry. Without this, "system prompt is huge" is unfalsifiable.
730
972
  // chars/4 ≈ tokens for English; reported as chars to keep math obvious.
@@ -788,13 +1030,27 @@ export async function* executeToolEngine(args) {
788
1030
  // stubs. Symmetric to the B3 sub-agent path; reuses the same module
789
1031
  // with `label: "top-level"` so the stub text reflects which loop
790
1032
  // elided the content.
791
- const topLevelCompactThreshold = getTopLevelCompactThresholdChars();
792
- 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();
793
1045
  // Phase O1 — capture providerOptions SHAPE (types only) for forensics.
794
1046
  deps.setLastProviderOptionsShape(Object.keys(providerOpts).length > 0 ? extractProviderOptionsShape(providerOpts) : null);
795
1047
  // Substitute the enriched user message for the current turn so the LLM sees PIL additions,
796
1048
  // while leaving the DB-persisted `deps.messages` clean for future turns.
797
- 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);
798
1054
  if (wireDebug.enabled) {
799
1055
  wireDebug.logRequest({
800
1056
  providerId: runtime.modelInfo?.provider ?? "unknown",
@@ -816,6 +1072,37 @@ export async function* executeToolEngine(args) {
816
1072
  const _baseDynamicStopWhen = createToolLoopCapPredicate({
817
1073
  initialCap: deps.maxToolRounds,
818
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
+ }
819
1106
  if (info.kind === "pattern") {
820
1107
  if (patternLoopInjectCount < 1) {
821
1108
  patternLoopInjectCount++;
@@ -1032,8 +1319,24 @@ export async function* executeToolEngine(args) {
1032
1319
  // on every chunk via stall.pet(), so it never kills an actively
1033
1320
  // streaming call. Disposed when the stream ends or errors.
1034
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.
1035
1331
  const stall = createStallWatchdog(getProviderStallTimeoutMs(), () => {
1036
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
+ },
1037
1340
  });
1038
1341
  // F3c — hard-cap LLM calls per turn before this streamText()
1039
1342
  if (++llmCallsThisTurn > MAX_LLM_CALLS_PER_TURN) {
@@ -1211,10 +1514,17 @@ export async function* executeToolEngine(args) {
1211
1514
  };
1212
1515
  // T1.1 + T1.2 — reasoning models (DeepSeek V4 Flash, R1) emit 2K-5K
1213
1516
  // CoT tokens per turn that accumulate across the multi-step loop.
1214
- // Strip old reasoning and compact earlier (ratio 0.3 vs 0.5) to
1215
- // 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.
1216
1521
  const isReasoningModel = runtime.modelInfo?.reasoning === true;
1217
- 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, {
1218
1528
  thresholdChars: topLevelCompactThreshold,
1219
1529
  // Rec #1 (cheap part): on meta/self-eval turns keep a couple more
1220
1530
  // trailing tool turns verbatim — those carry the reasoning the
@@ -1225,28 +1535,120 @@ export async function* executeToolEngine(args) {
1225
1535
  label: "top-level",
1226
1536
  envelopeChars,
1227
1537
  contextWindowTokens,
1228
- contextFillRatio: isReasoningModel ? 0.3 : undefined,
1538
+ contextFillRatio: reasoningFillRatio,
1229
1539
  keepToolIds: keepToolIds.length ? keepToolIds : undefined,
1230
1540
  persistArtifact,
1231
1541
  stripOldReasoning: isReasoningModel,
1542
+ tailBudgetChars: topLevelCompactTailBudget,
1232
1543
  });
1233
- if (compacted !== stripped)
1234
- 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.)
1235
1598
  // Pre-compaction visibility: give the agent one step of notice
1236
1599
  // before B4 actually rewrites history into stubs. This is the
1237
1600
  // advance warning that was missing — agent can now decide to
1238
1601
  // summarize, finish, or request preservation. Fires when we did
1239
1602
  // NOT compact this step (compacted === stripped, restored by the
1240
- // compactSubAgentMessages no-op ref contract) AND the prompt is
1241
- // approaching the threshold. Must compare CHARS (messages +
1242
- // envelope), not stripped.length (a message count that never
1243
- // exceeds a char-scaled threshold) — session 2b7a10219499.
1244
1603
  const _preWarnChars = cumulativeMessageChars(stripped) + envelopeChars;
1245
- if (compacted === stripped && shouldPreWarnCompaction(_preWarnChars, topLevelCompactThreshold)) {
1604
+ if (coalesced === stripped && shouldPreWarnCompaction(_preWarnChars, topLevelCompactThreshold)) {
1246
1605
  const _cp = buildCheckpointReminder(sn, true);
1247
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.]`;
1248
1607
  return withSteers({ messages: attachReminderToMessages(stripped, _pre) });
1249
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
+ }
1250
1652
  // Phase 4A — scope reminder injection (REQ-005).
1251
1653
  // Cadence K = 3/5/8 for small/medium/large. Soft-warn fires
1252
1654
  // ONCE per session at floor(ceiling*0.7). Reminder lives in
@@ -1306,10 +1708,10 @@ export async function* executeToolEngine(args) {
1306
1708
  ? `${buildRepetitionReminder(_ceilingSessionId)}\n${_scopePart}`
1307
1709
  : buildRepetitionReminder(_ceilingSessionId)
1308
1710
  : _scopePart;
1309
- const withReminder = attachReminderToMessages(compacted, _reminder);
1711
+ const withReminder = attachReminderToMessages(coalesced, _reminder);
1310
1712
  return withSteers({ messages: withReminder });
1311
1713
  }
1312
- if (compacted === stripped && stripped === stepMessages)
1714
+ if (coalesced === stripped && stripped === stepMessages)
1313
1715
  return withSteers({});
1314
1716
  // Self-awareness note: tell the model compaction happened so it
1315
1717
  // knows earlier context was elided and can adjust its behavior.
@@ -1328,12 +1730,12 @@ export async function* executeToolEngine(args) {
1328
1730
  })()
1329
1731
  : null;
1330
1732
  if (_compactNote) {
1331
- return withSteers({ messages: attachReminderToMessages(compacted, _compactNote) });
1733
+ return withSteers({ messages: attachReminderToMessages(coalesced, _compactNote) });
1332
1734
  }
1333
- return withSteers({ messages: compacted });
1735
+ return withSteers({ messages: coalesced });
1334
1736
  },
1335
- ...(dropParam("temperature") ? {} : { temperature: 0.7 }),
1336
- ...(dropParam("maxOutputTokens") ? {} : { maxOutputTokens: taskTypeToMaxTokens(pilCtx.taskType) }),
1737
+ ...resolveTemperatureParam(runtime, 0.7),
1738
+ ...(dropParam("maxOutputTokens") ? {} : { maxOutputTokens: resolveTurnMaxOutputTokens(pilCtx) }),
1337
1739
  ...(Object.keys(providerOpts).length > 0 ? { providerOptions: providerOpts } : {}),
1338
1740
  experimental_onStepStart: (event) => {
1339
1741
  stepNumber = getStepNumber(event, stepNumber + 1);
@@ -1352,6 +1754,10 @@ export async function* executeToolEngine(args) {
1352
1754
  finishReason: getFinishReason(event),
1353
1755
  usage: stepUsage,
1354
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(() => { });
1355
1761
  // Realtime status bar update per step
1356
1762
  if (stepUsage.inputTokens || stepUsage.outputTokens) {
1357
1763
  // O1 — thread THIS turn's providerOptions shape per step so every
@@ -1426,8 +1832,23 @@ export async function* executeToolEngine(args) {
1426
1832
  wireDebug.logError(_wireProviderIdTop, part.error);
1427
1833
  }
1428
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
+ }
1429
1849
  switch (part.type) {
1430
1850
  case "text-delta":
1851
+ stall.petProgress(); // real forward progress — reset the no-progress guard
1431
1852
  assistantText += part.text;
1432
1853
  // Task 2.6b — emit llm-token (agent-mode only; high-volume, default-off per Phase 4).
1433
1854
  try {
@@ -1459,6 +1880,7 @@ export async function* executeToolEngine(args) {
1459
1880
  yield { type: "reasoning", content: part.text };
1460
1881
  break;
1461
1882
  case "tool-call": {
1883
+ stall.petProgress(); // real forward progress — reset the no-progress guard
1462
1884
  const tc = toToolCall(part);
1463
1885
  activeToolCalls.push(tc);
1464
1886
  // SAMR: track that Phase 1 produced tool calls → transition to Phase 2
@@ -1679,10 +2101,10 @@ export async function* executeToolEngine(args) {
1679
2101
  // the global __muonroiSafetyApproved map so registry.ts's
1680
2102
  // bash.execute bypasses the block on the retry.
1681
2103
  const _outputText = [tr.output, tr.error].filter((x) => typeof x === "string").join("\n");
1682
- const _blockMatch = _outputText.match(/^BLOCKED \(([^)]+)\):\s*(.*)/);
1683
- if (_blockMatch && part.toolName === "bash") {
1684
- const _blockKind = _blockMatch[1];
1685
- 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;
1686
2108
  const _command = typeof part.input === "object" && part.input != null
1687
2109
  ? String(part.input.command ?? "")
1688
2110
  : "";
@@ -1695,15 +2117,33 @@ export async function* executeToolEngine(args) {
1695
2117
  tr = { ...tr, success: false, error: _outputText, output: _outputText };
1696
2118
  }
1697
2119
  else {
1698
- const _verdict = deps.askSafetyOverride
1699
- ? await deps.askSafetyOverride({
1700
- kind: _blockKind,
1701
- toolName: part.toolName,
1702
- blockedItem: _command,
1703
- reason: _blockReason,
1704
- source: "bash.execute",
1705
- })
1706
- : { 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
+ }
1707
2147
  if (_verdict.action === "allow-once" || _verdict.action === "allow-session") {
1708
2148
  // Store approval so registry.ts can bypass the block on retry.
1709
2149
  const _globalSafety = globalThis;
@@ -1837,10 +2277,26 @@ export async function* executeToolEngine(args) {
1837
2277
  // dedicated chunk so the sticky checklist panel can re-render
1838
2278
  // without parsing tool args itself. Skipped when the snapshot
1839
2279
  // doesn't parse (malformed args) so the UI is never poisoned.
1840
- if (tc.function.name === "todo_write" && tr.success) {
1841
- const snap = snapshotFromTodoWriteArgs(tc.function.arguments);
1842
- if (snap)
1843
- 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
+ }
1844
2300
  }
1845
2301
  break;
1846
2302
  }
@@ -2972,6 +3428,10 @@ export async function* executeToolEngine(args) {
2972
3428
  if (modelInfo?.contextWindow) {
2973
3429
  void deps.postTurnCompact(provider, system, modelInfo.contextWindow, signal).catch(() => { });
2974
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);
2975
3435
  yield { type: "done" };
2976
3436
  return;
2977
3437
  }
@@ -3118,6 +3578,10 @@ export async function* executeToolEngine(args) {
3118
3578
  if (modelInfo?.contextWindow) {
3119
3579
  void deps.postTurnCompact(provider, system, modelInfo.contextWindow, signal).catch(() => { });
3120
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);
3121
3585
  yield { type: "done" };
3122
3586
  return;
3123
3587
  }
@@ -3135,4 +3599,153 @@ export async function* executeToolEngine(args) {
3135
3599
  }
3136
3600
  }
3137
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
+ }
3138
3751
  //# sourceMappingURL=tool-engine.js.map