muonroi-cli 1.7.2 → 1.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (326) hide show
  1. package/dist/packages/agent-harness-core/src/event-filter.js +1 -0
  2. package/dist/packages/agent-harness-core/src/event-redact.js +8 -0
  3. package/dist/packages/agent-harness-core/src/mcp-server.d.ts +16 -0
  4. package/dist/packages/agent-harness-core/src/mcp-server.js +88 -10
  5. package/dist/packages/agent-harness-core/src/protocol.d.ts +15 -0
  6. package/dist/packages/agent-harness-core/src/selector.js +2 -2
  7. package/dist/packages/agent-harness-core/src/transports/sidechannel.js +1 -1
  8. package/dist/packages/agent-harness-opentui/src/agent-mode.js +1 -1
  9. package/dist/packages/agent-harness-opentui/src/install.js +1 -1
  10. package/dist/src/__test-stubs__/vitest-setup.js +4 -0
  11. package/dist/src/agent-harness/__tests__/mock-model.spec.js +54 -0
  12. package/dist/src/agent-harness/mock-model.d.ts +1 -1
  13. package/dist/src/agent-harness/mock-model.js +143 -6
  14. package/dist/src/agent-harness/test-spawn.js +3 -1
  15. package/dist/src/chat/chat-keychain.js +9 -1
  16. package/dist/src/cli/cost-forensics.js +7 -4
  17. package/dist/src/cli/keys.d.ts +2 -2
  18. package/dist/src/cli/keys.js +107 -28
  19. package/dist/src/cli/usage-report.js +2 -2
  20. package/dist/src/council/__tests__/abort-threading.test.d.ts +1 -0
  21. package/dist/src/council/__tests__/abort-threading.test.js +193 -0
  22. package/dist/src/council/__tests__/clarification-prompt.test.js +21 -1
  23. package/dist/src/council/__tests__/council-turn-length-emit.test.d.ts +1 -0
  24. package/dist/src/council/__tests__/council-turn-length-emit.test.js +130 -0
  25. package/dist/src/council/__tests__/debate-round-budget.test.d.ts +1 -0
  26. package/dist/src/council/__tests__/debate-round-budget.test.js +45 -0
  27. package/dist/src/council/__tests__/evaluator-metrics.test.js +3 -0
  28. package/dist/src/council/__tests__/post-debate-recommendation.test.d.ts +1 -0
  29. package/dist/src/council/__tests__/post-debate-recommendation.test.js +58 -0
  30. package/dist/src/council/__tests__/research-tools.test.js +4 -0
  31. package/dist/src/council/__tests__/round-tools.test.js +6 -0
  32. package/dist/src/council/clarifier-question-cap.test.d.ts +12 -0
  33. package/dist/src/council/clarifier-question-cap.test.js +73 -0
  34. package/dist/src/council/clarifier.d.ts +29 -1
  35. package/dist/src/council/clarifier.js +64 -1
  36. package/dist/src/council/debate-planner.d.ts +2 -1
  37. package/dist/src/council/debate-planner.js +5 -3
  38. package/dist/src/council/debate.d.ts +9 -0
  39. package/dist/src/council/debate.js +88 -8
  40. package/dist/src/council/index.d.ts +46 -1
  41. package/dist/src/council/index.js +125 -19
  42. package/dist/src/council/llm.js +41 -13
  43. package/dist/src/council/prompts.js +36 -1
  44. package/dist/src/council/types.d.ts +14 -1
  45. package/dist/src/ee/__tests__/export-transcripts.test.js +1 -1
  46. package/dist/src/ee/bridge.d.ts +3 -1
  47. package/dist/src/ee/bridge.js +9 -3
  48. package/dist/src/ee/bridge.test.js +1 -2
  49. package/dist/src/ee/client.js +15 -2
  50. package/dist/src/ee/recall-ledger.d.ts +20 -0
  51. package/dist/src/ee/recall-ledger.js +40 -1
  52. package/dist/src/ee/search.d.ts +25 -0
  53. package/dist/src/ee/search.js +73 -0
  54. package/dist/src/ee/types.d.ts +15 -1
  55. package/dist/src/ee/who-am-i.d.ts +41 -0
  56. package/dist/src/ee/who-am-i.js +143 -0
  57. package/dist/src/ee/who-am-i.test.d.ts +1 -0
  58. package/dist/src/ee/who-am-i.test.js +101 -0
  59. package/dist/src/flow/compaction/index.d.ts +1 -0
  60. package/dist/src/flow/compaction/index.js +4 -0
  61. package/dist/src/generated/version.d.ts +1 -1
  62. package/dist/src/generated/version.js +1 -1
  63. package/dist/src/headless/council-answers.js +1 -0
  64. package/dist/src/headless/output.d.ts +30 -1
  65. package/dist/src/headless/output.js +180 -0
  66. package/dist/src/headless/output.test.js +61 -1
  67. package/dist/src/hooks/index.js +48 -2
  68. package/dist/src/index.d.ts +2 -0
  69. package/dist/src/index.js +59 -62
  70. package/dist/src/lsp/client-normalize.test.d.ts +12 -0
  71. package/dist/src/lsp/client-normalize.test.js +34 -0
  72. package/dist/src/lsp/client.d.ts +1 -0
  73. package/dist/src/lsp/client.js +10 -2
  74. package/dist/src/lsp/manager.d.ts +1 -1
  75. package/dist/src/lsp/manager.js +13 -5
  76. package/dist/src/lsp/manager.test.js +41 -2
  77. package/dist/src/lsp/runtime.d.ts +1 -1
  78. package/dist/src/lsp/runtime.js +2 -2
  79. package/dist/src/maintain/__tests__/gh-create-pr.test.js +7 -3
  80. package/dist/src/maintain/__tests__/pr-builder.test.js +7 -3
  81. package/dist/src/mcp/__tests__/client-pool.spec.js +3 -3
  82. package/dist/src/mcp/__tests__/ee-tools.test.js +42 -14
  83. package/dist/src/mcp/__tests__/forensics-tools.test.js +3 -3
  84. package/dist/src/mcp/__tests__/lsp-tools.test.js +4 -4
  85. package/dist/src/mcp/auto-setup.js +0 -8
  86. package/dist/src/mcp/client-pool.d.ts +1 -1
  87. package/dist/src/mcp/client-pool.js +25 -9
  88. package/dist/src/mcp/ee-tools.d.ts +10 -0
  89. package/dist/src/mcp/ee-tools.js +34 -0
  90. package/dist/src/mcp/self-verify-jobs.d.ts +1 -0
  91. package/dist/src/mcp/self-verify-jobs.js +1 -3
  92. package/dist/src/mcp/self-verify-runner.js +1 -1
  93. package/dist/src/models/catalog-gemini.test.js +12 -10
  94. package/dist/src/models/catalog.json +106 -38
  95. package/dist/src/orchestrator/__tests__/auto-commit.test.d.ts +1 -0
  96. package/dist/src/orchestrator/__tests__/auto-commit.test.js +142 -0
  97. package/dist/src/orchestrator/__tests__/batch-turn-runner.test.js +3 -0
  98. package/dist/src/orchestrator/__tests__/delegations.test.d.ts +1 -0
  99. package/dist/src/orchestrator/__tests__/delegations.test.js +96 -0
  100. package/dist/src/orchestrator/__tests__/message-processor.test.js +2 -0
  101. package/dist/src/orchestrator/__tests__/prompts.test.d.ts +1 -0
  102. package/dist/src/orchestrator/__tests__/prompts.test.js +88 -0
  103. package/dist/src/orchestrator/__tests__/stream-runner.test.js +29 -0
  104. package/dist/src/orchestrator/__tests__/sub-session-delegation.test.d.ts +1 -0
  105. package/dist/src/orchestrator/__tests__/sub-session-delegation.test.js +507 -0
  106. package/dist/src/orchestrator/agent.test.js +5 -2
  107. package/dist/src/orchestrator/auto-commit.d.ts +110 -0
  108. package/dist/src/orchestrator/auto-commit.js +366 -0
  109. package/dist/src/orchestrator/batch-turn-runner.d.ts +3 -2
  110. package/dist/src/orchestrator/batch-turn-runner.js +2 -1
  111. package/dist/src/orchestrator/compaction-proposer-prompt.d.ts +8 -0
  112. package/dist/src/orchestrator/compaction-proposer-prompt.js +43 -0
  113. package/dist/src/orchestrator/compaction.d.ts +22 -0
  114. package/dist/src/orchestrator/compaction.js +42 -0
  115. package/dist/src/orchestrator/delegations.d.ts +1 -0
  116. package/dist/src/orchestrator/delegations.js +41 -0
  117. package/dist/src/orchestrator/error-utils.js +27 -2
  118. package/dist/src/orchestrator/message-processor.d.ts +23 -5
  119. package/dist/src/orchestrator/message-processor.js +226 -2573
  120. package/dist/src/orchestrator/orchestrator.d.ts +11 -2
  121. package/dist/src/orchestrator/orchestrator.js +530 -43
  122. package/dist/src/orchestrator/pending-calls.js +2 -1
  123. package/dist/src/orchestrator/preprocessor.d.ts +13 -0
  124. package/dist/src/orchestrator/preprocessor.js +146 -0
  125. package/dist/src/orchestrator/prompts.d.ts +16 -0
  126. package/dist/src/orchestrator/prompts.js +130 -67
  127. package/dist/src/orchestrator/safety-askcard.d.ts +66 -0
  128. package/dist/src/orchestrator/safety-askcard.js +74 -0
  129. package/dist/src/orchestrator/sandbox.test.js +8 -5
  130. package/dist/src/orchestrator/scope-ceiling.js +1 -1
  131. package/dist/src/orchestrator/stall-watchdog.d.ts +47 -0
  132. package/dist/src/orchestrator/stall-watchdog.js +23 -0
  133. package/dist/src/orchestrator/stall-watchdog.test.js +36 -1
  134. package/dist/src/orchestrator/stream-runner.js +52 -30
  135. package/dist/src/orchestrator/sub-agent-cap.d.ts +2 -0
  136. package/dist/src/orchestrator/sub-agent-cap.js +12 -4
  137. package/dist/src/orchestrator/sub-agent-model-tier.js +1 -1
  138. package/dist/src/orchestrator/subagent-compactor.d.ts +21 -0
  139. package/dist/src/orchestrator/subagent-compactor.js +148 -24
  140. package/dist/src/orchestrator/subagent-compactor.spec.js +249 -1
  141. package/dist/src/orchestrator/text-tool-call-detector.d.ts +9 -5
  142. package/dist/src/orchestrator/text-tool-call-detector.js +16 -12
  143. package/dist/src/orchestrator/text-tool-call-detector.test.js +66 -8
  144. package/dist/src/orchestrator/tool-args-repair.js +1 -1
  145. package/dist/src/orchestrator/tool-engine.d.ts +153 -0
  146. package/dist/src/orchestrator/tool-engine.js +3138 -0
  147. package/dist/src/orchestrator/turn-runner-deps.d.ts +3 -0
  148. package/dist/src/pil/__tests__/discovery-types.test.js +15 -4
  149. package/dist/src/pil/__tests__/discovery.test.js +89 -145
  150. package/dist/src/pil/__tests__/dual-run.test.js +2 -0
  151. package/dist/src/pil/__tests__/layer1-intent-trace.test.js +3 -0
  152. package/dist/src/pil/__tests__/layer1-intent.test.js +3 -0
  153. package/dist/src/pil/__tests__/layer16-clarity.test.js +35 -32
  154. package/dist/src/pil/__tests__/layer18-acceptance.test.js +1 -78
  155. package/dist/src/pil/__tests__/layer2_5-ponytail.test.d.ts +1 -0
  156. package/dist/src/pil/__tests__/layer2_5-ponytail.test.js +53 -0
  157. package/dist/src/pil/__tests__/layer3-ee-injection.test.js +67 -4
  158. package/dist/src/pil/__tests__/layer3-injected-chunk.test.js +14 -2
  159. package/dist/src/pil/__tests__/layer4-gsd.test.js +30 -0
  160. package/dist/src/pil/__tests__/layer5-context.test.js +39 -1
  161. package/dist/src/pil/__tests__/layer6-output.test.js +30 -2
  162. package/dist/src/pil/__tests__/llm-classify.test.js +79 -1
  163. package/dist/src/pil/__tests__/orchestrator-integration.test.js +3 -1
  164. package/dist/src/pil/__tests__/pipeline.test.js +11 -8
  165. package/dist/src/pil/__tests__/response-tools.test.js +52 -0
  166. package/dist/src/pil/agent-operating-contract.d.ts +1 -1
  167. package/dist/src/pil/agent-operating-contract.js +5 -3
  168. package/dist/src/pil/agent-operating-contract.test.js +2 -2
  169. package/dist/src/pil/cheap-model-playbook.d.ts +1 -1
  170. package/dist/src/pil/cheap-model-playbook.js +1 -1
  171. package/dist/src/pil/cheap-model-workbooks.d.ts +1 -1
  172. package/dist/src/pil/cheap-model-workbooks.js +1 -1
  173. package/dist/src/pil/config.d.ts +20 -0
  174. package/dist/src/pil/config.js +35 -0
  175. package/dist/src/pil/discovery-types.d.ts +32 -9
  176. package/dist/src/pil/discovery.d.ts +3 -3
  177. package/dist/src/pil/discovery.js +138 -244
  178. package/dist/src/pil/index.d.ts +1 -1
  179. package/dist/src/pil/index.js +1 -1
  180. package/dist/src/pil/layer1-intent.d.ts +9 -1
  181. package/dist/src/pil/layer1-intent.js +62 -7
  182. package/dist/src/pil/layer1-intent.test.js +112 -2
  183. package/dist/src/pil/layer15-context-scan.js +1 -1
  184. package/dist/src/pil/layer16-clarity.d.ts +21 -25
  185. package/dist/src/pil/layer16-clarity.js +52 -88
  186. package/dist/src/pil/layer18-acceptance.d.ts +13 -3
  187. package/dist/src/pil/layer18-acceptance.js +1 -75
  188. package/dist/src/pil/layer2-personality.js +7 -8
  189. package/dist/src/pil/layer2_5-ponytail.d.ts +2 -0
  190. package/dist/src/pil/layer2_5-ponytail.js +41 -0
  191. package/dist/src/pil/layer3-ee-injection.js +180 -14
  192. package/dist/src/pil/layer4-gsd.js +22 -8
  193. package/dist/src/pil/layer5-context.d.ts +1 -0
  194. package/dist/src/pil/layer5-context.js +26 -11
  195. package/dist/src/pil/layer6-output.js +23 -5
  196. package/dist/src/pil/llm-classify.d.ts +11 -0
  197. package/dist/src/pil/llm-classify.js +196 -3
  198. package/dist/src/pil/native-capabilities-workbook.d.ts +1 -1
  199. package/dist/src/pil/native-capabilities-workbook.js +4 -3
  200. package/dist/src/pil/pipeline.js +15 -1
  201. package/dist/src/pil/response-tools.d.ts +16 -0
  202. package/dist/src/pil/response-tools.js +41 -0
  203. package/dist/src/pil/schema.d.ts +8 -0
  204. package/dist/src/pil/schema.js +24 -3
  205. package/dist/src/pil/task-tier-map.js +7 -7
  206. package/dist/src/pil/types.d.ts +9 -3
  207. package/dist/src/product-loop/__tests__/discovery-interview.test.js +103 -1
  208. package/dist/src/product-loop/__tests__/gather-selectable-alts.test.js +34 -0
  209. package/dist/src/product-loop/__tests__/sprint-self-verify.test.js +12 -12
  210. package/dist/src/product-loop/discovery-interview.d.ts +20 -0
  211. package/dist/src/product-loop/discovery-interview.js +121 -7
  212. package/dist/src/product-loop/gather.js +19 -5
  213. package/dist/src/product-loop/loop-driver.js +2 -2
  214. package/dist/src/product-loop/sprint-self-verify.js +2 -2
  215. package/dist/src/providers/__tests__/reasoning-roundtrip.test.js +12 -0
  216. package/dist/src/providers/auth/__tests__/gemini-oauth.test.d.ts +1 -5
  217. package/dist/src/providers/auth/__tests__/gemini-oauth.test.js +1 -5
  218. package/dist/src/providers/auth/gcloud.d.ts +28 -0
  219. package/dist/src/providers/auth/gcloud.js +102 -0
  220. package/dist/src/providers/auth/gemini-oauth.d.ts +46 -19
  221. package/dist/src/providers/auth/gemini-oauth.js +292 -105
  222. package/dist/src/providers/auth/grok-oauth.d.ts +1 -1
  223. package/dist/src/providers/auth/grok-oauth.js +1 -1
  224. package/dist/src/providers/auth/registry.js +28 -6
  225. package/dist/src/providers/gemini.d.ts +1 -1
  226. package/dist/src/providers/gemini.js +10 -3
  227. package/dist/src/providers/keychain.d.ts +2 -2
  228. package/dist/src/providers/keychain.js +20 -2
  229. package/dist/src/providers/openai.js +5 -1
  230. package/dist/src/providers/runtime.d.ts +1 -1
  231. package/dist/src/providers/runtime.js +11 -2
  232. package/dist/src/providers/siliconflow-sse-repair.js +1 -1
  233. package/dist/src/providers/strategies/google.strategy.d.ts +9 -2
  234. package/dist/src/providers/strategies/google.strategy.js +159 -9
  235. package/dist/src/providers/strategies/thinking-mode.js +19 -6
  236. package/dist/src/providers/types.d.ts +1 -1
  237. package/dist/src/router/decide.test.js +40 -8
  238. package/dist/src/router/step-router.d.ts +20 -0
  239. package/dist/src/router/step-router.js +73 -0
  240. package/dist/src/self-qa/agentic-loop.js +30 -21
  241. package/dist/src/self-qa/orchestrator.js +5 -5
  242. package/dist/src/self-qa/scenario-planner.js +1 -1
  243. package/dist/src/self-qa/spec-emitter.js +4 -4
  244. package/dist/src/storage/__tests__/hierarchical-session.test.d.ts +1 -0
  245. package/dist/src/storage/__tests__/hierarchical-session.test.js +161 -0
  246. package/dist/src/storage/__tests__/sweep-stale-pending.test.js +2 -2
  247. package/dist/src/storage/index.d.ts +1 -1
  248. package/dist/src/storage/index.js +1 -1
  249. package/dist/src/storage/migrations.js +13 -1
  250. package/dist/src/storage/transcript-response-entry.test.js +18 -5
  251. package/dist/src/storage/transcript-view.js +7 -1
  252. package/dist/src/storage/transcript.d.ts +3 -0
  253. package/dist/src/storage/transcript.js +254 -73
  254. package/dist/src/storage/ui-interaction-log.d.ts +17 -0
  255. package/dist/src/storage/usage.js +22 -5
  256. package/dist/src/tools/__tests__/native-tools.test.js +6 -0
  257. package/dist/src/tools/bash.d.ts +36 -23
  258. package/dist/src/tools/bash.js +59 -301
  259. package/dist/src/tools/bash.test.js +2 -257
  260. package/dist/src/tools/file-tracker.d.ts +5 -0
  261. package/dist/src/tools/file-tracker.js +14 -0
  262. package/dist/src/tools/file.js +2 -0
  263. package/dist/src/tools/git-safety.d.ts +38 -1
  264. package/dist/src/tools/git-safety.js +94 -4
  265. package/dist/src/tools/git-safety.test.js +45 -1
  266. package/dist/src/tools/native-tools.d.ts +1 -1
  267. package/dist/src/tools/native-tools.js +66 -1
  268. package/dist/src/tools/registry-bash-empty-command.test.js +4 -3
  269. package/dist/src/tools/registry-git-safety.test.js +17 -6
  270. package/dist/src/tools/registry.d.ts +6 -0
  271. package/dist/src/tools/registry.js +215 -13
  272. package/dist/src/types/index.d.ts +8 -2
  273. package/dist/src/ui/app.d.ts +1 -0
  274. package/dist/src/ui/app.js +0 -0
  275. package/dist/src/ui/components/council-question-card.js +1 -0
  276. package/dist/src/ui/components/message-view.js +37 -3
  277. package/dist/src/ui/components/structured-response-view.js +7 -0
  278. package/dist/src/ui/components/task-list-panel.js +3 -12
  279. package/dist/src/ui/containers/chat-feed.d.ts +40 -0
  280. package/dist/src/ui/containers/chat-feed.js +66 -0
  281. package/dist/src/ui/containers/modals-layer.d.ts +87 -0
  282. package/dist/src/ui/containers/modals-layer.js +18 -0
  283. package/dist/src/ui/slash/__tests__/menu-parity.test.d.ts +1 -0
  284. package/dist/src/ui/slash/__tests__/menu-parity.test.js +1 -0
  285. package/dist/src/ui/slash/__tests__/ponytail.test.d.ts +1 -0
  286. package/dist/src/ui/slash/__tests__/ponytail.test.js +37 -0
  287. package/dist/src/ui/slash/expand.js +14 -1
  288. package/dist/src/ui/slash/export.js +16 -1
  289. package/dist/src/ui/slash/menu-items.js +1 -0
  290. package/dist/src/ui/slash/ponytail.d.ts +2 -0
  291. package/dist/src/ui/slash/ponytail.js +19 -0
  292. package/dist/src/ui/status-bar/store.js +0 -1
  293. package/dist/src/ui/status-bar/store.test.js +2 -2
  294. package/dist/src/ui/use-app-logic.d.ts +26 -0
  295. package/dist/src/ui/use-app-logic.js +0 -0
  296. package/dist/src/ui/utils/relaunch.js +1 -1
  297. package/dist/src/ui/utils/text.d.ts +11 -0
  298. package/dist/src/ui/utils/text.js +21 -1
  299. package/dist/src/ui/utils/text.test.js +25 -1
  300. package/dist/src/ui/utils/tools.js +3 -1
  301. package/dist/src/usage/cost-log.js +2 -2
  302. package/dist/src/usage/estimator.d.ts +14 -0
  303. package/dist/src/usage/estimator.js +22 -0
  304. package/dist/src/utils/__tests__/compaction-caps.test.d.ts +21 -0
  305. package/dist/src/utils/__tests__/compaction-caps.test.js +187 -0
  306. package/dist/src/utils/__tests__/llm-deadline-abort.test.d.ts +1 -0
  307. package/dist/src/utils/__tests__/llm-deadline-abort.test.js +33 -0
  308. package/dist/src/utils/__tests__/llm-deadline.test.js +16 -4
  309. package/dist/src/utils/__tests__/logger.test.d.ts +1 -0
  310. package/dist/src/utils/__tests__/logger.test.js +115 -0
  311. package/dist/src/utils/__tests__/settings-agent-first.test.d.ts +1 -0
  312. package/dist/src/utils/__tests__/settings-agent-first.test.js +48 -0
  313. package/dist/src/utils/install-manager.d.ts +1 -0
  314. package/dist/src/utils/install-manager.js +76 -7
  315. package/dist/src/utils/install-manager.test.js +43 -1
  316. package/dist/src/utils/llm-deadline.d.ts +9 -1
  317. package/dist/src/utils/llm-deadline.js +31 -2
  318. package/dist/src/utils/logger.d.ts +28 -0
  319. package/dist/src/utils/logger.js +171 -0
  320. package/dist/src/utils/permission-mode.d.ts +33 -0
  321. package/dist/src/utils/permission-mode.js +164 -3
  322. package/dist/src/utils/permission-mode.test.js +92 -1
  323. package/dist/src/utils/settings.d.ts +36 -32
  324. package/dist/src/utils/settings.js +151 -123
  325. package/dist/src/utils/update-checker.test.js +5 -1
  326. package/package.json +1 -1
@@ -14,29 +14,32 @@ import { apiBaseFor } from "../providers/endpoints.js";
14
14
  import { loadKeyForProvider } from "../providers/keychain.js";
15
15
  import { createProviderFactory, createProviderFactoryAsync, detectProviderForModel, requireRuntimeProvider, resolveModelRuntime as resolveRuntime, } from "../providers/runtime.js";
16
16
  import { ALL_PROVIDER_IDS } from "../providers/types.js";
17
- import { appendCompaction, appendMessages, appendSystemMessage, buildChatEntries, getNextMessageSequence, getSessionTotalTokens, loadTranscript, loadTranscriptState, logInteraction, markMessageCompleted, recordUsageEvent, SessionStore, } from "../storage/index.js";
17
+ import { appendCompaction, appendMessages, appendSystemMessage, buildChatEntries, getLastTodoWriteArgs, getNextMessageSequence, getSessionTotalTokens, loadTranscript, loadTranscriptState, logInteraction, markMessageCompleted, recordUsageEvent, SessionStore, } from "../storage/index.js";
18
18
  import { BashTool } from "../tools/bash.js";
19
19
  import { createBuiltinTools } from "../tools/registry.js";
20
20
  import { ScheduleManager } from "../tools/schedule.js";
21
+ import { snapshotFromTodoWriteArgs } from "../tools/todo-write-snapshot.js";
21
22
  import { statusBarStore } from "../ui/status-bar/store.js";
22
23
  import { appendCostLog } from "../usage/cost-log.js";
23
24
  import { appendDecisionLog } from "../usage/decision-log.js";
24
- import { projectCostUSD } from "../usage/estimator.js";
25
- import { getAutoCompactThresholdPct, getCouncilRounds, getCurrentModel, getCurrentShellSettings, getModeSpecificModel, getRoleModel, getRoleModels, isAutoCompactAfterTurnEnabled, isCouncilMultiProviderPreferred, isProviderDisabled, } from "../utils/settings.js";
25
+ import { projectCostUSD, sanitizeInputTokens } from "../usage/estimator.js";
26
+ import { logger } from "../utils/logger.js";
27
+ import { getAutoCompactThresholdPct, getCouncilRounds, getCurrentModel, getCurrentShellSettings, getModeSpecificModel, getRoleModel, getRoleModels, isAutoCompactAfterTurnEnabled, isCouncilMultiProviderPreferred, isProviderDisabled, loadUserSettings, } from "../utils/settings.js";
26
28
  import { runSideQuestion } from "../utils/side-question.js";
27
29
  import { buildVerifyDetectPrompt, normalizeVerifyRecipe } from "../verify/entrypoint.js";
28
30
  import { runVerifyOrchestration } from "../verify/orchestrator.js";
29
31
  import { COUNCIL_COLOR_BG, COUNCIL_COLOR_RESET, COUNCIL_ROLE_COLORS, } from "./agent-options.js";
32
+ import { AUTO_COMMIT_ATTRIBUTION, isAutoCommitEnabled, maybeAutoCommitTurn, snapshotDirtyPaths, } from "./auto-commit.js";
30
33
  import { BatchTurnRunner } from "./batch-turn-runner.js";
31
34
  import { accumulateUsage, buildAssistantBatchMessage, buildBatchChatCompletionRequest, buildBatchName, buildToolBatchMessage, extractJsonObject, getBatchUsage, hasUsage, parseToolArgumentsOrRaw, toLocalToolCall, } from "./batch-utils.js";
32
- import { createCompactionSummaryMessage, DEFAULT_KEEP_RECENT_TOKENS, DEFAULT_RESERVE_TOKENS, estimateConversationTokens, extractUserContent, generateCompactionSummary, POST_TURN_MIN_TOKENS, prepareCompaction, shouldCompactContext, } from "./compaction.js";
35
+ import { createCompactionSummaryMessage, DEFAULT_KEEP_RECENT_TOKENS, DEFAULT_RESERVE_TOKENS, estimateConversationTokens, extractUserContent, generateCompactionSummary, POST_TURN_MIN_TOKENS, prepareCompaction, proposeCompaction, shouldCompactContext, } from "./compaction.js";
33
36
  import { CouncilManager } from "./council-manager.js";
34
37
  import { CrossTurnDedup, isCrossTurnDedupEnabled } from "./cross-turn-dedup.js";
35
38
  import { DelegationManager } from "./delegations.js";
36
39
  import { loadFlowResumeDigest } from "./flow-resume.js";
37
40
  import { MessageProcessor } from "./message-processor.js";
38
41
  import { lastPersistedSeq } from "./message-seq.js";
39
- import { buildSystemPrompt, MAX_TOOL_ROUNDS } from "./prompts.js";
42
+ import { buildSystemPrompt, HARD_MAX_TOOL_ROUNDS, MAX_TOOL_ROUNDS } from "./prompts.js";
40
43
  import { getReadPathBudgetCap, ReadPathBudget } from "./read-path-budget.js";
41
44
  import { withStreamRetry } from "./retry-stream.js";
42
45
  import { StreamRunner } from "./stream-runner.js";
@@ -105,6 +108,7 @@ function createTools(_bash, _provider, _mode, _opts) {
105
108
  runDelegation: _opts?.runDelegation,
106
109
  readDelegation: _opts?.readDelegation,
107
110
  listDelegations: _opts?.listDelegations,
111
+ killDelegation: _opts?.killDelegation,
108
112
  modelId: _opts?.modelId,
109
113
  });
110
114
  }
@@ -133,6 +137,7 @@ export class Agent {
133
137
  /** UI-registered live-queue steer drain; see Agent.setSteerDrain. */
134
138
  steerDrain = null;
135
139
  maxToolRounds;
140
+ hardMaxToolRounds;
136
141
  mode = "agent";
137
142
  modelId;
138
143
  maxTokens;
@@ -237,7 +242,10 @@ export class Agent {
237
242
  this.setApiKey(apiKey, baseURL);
238
243
  }
239
244
  this.schedules = new ScheduleManager(() => this.bash.getCwd(), () => this.modelId);
240
- this.maxToolRounds = maxToolRounds || MAX_TOOL_ROUNDS;
245
+ const settings = loadUserSettings();
246
+ this.maxToolRounds = maxToolRounds || settings.maxToolRounds || MAX_TOOL_ROUNDS;
247
+ const baseHardMax = settings.hardMaxToolRounds || HARD_MAX_TOOL_ROUNDS;
248
+ this.hardMaxToolRounds = Math.max(Math.floor(this.maxToolRounds * 1.5), baseHardMax);
241
249
  const envMax = Number(process.env.MUONROI_MAX_TOKENS);
242
250
  this.maxTokens = Number.isFinite(envMax) && envMax > 0 ? envMax : 16_384;
243
251
  this.batchApi = options.batchApi ?? false;
@@ -264,7 +272,7 @@ export class Agent {
264
272
  warmMcpClients(filterMcpServersByMessage(loadMcpServers(), ""));
265
273
  }
266
274
  catch (err) {
267
- console.error(`[orchestrator] MCP pre-warm skipped: ${err?.message}`);
275
+ logger.error("orchestrator", "MCP pre-warm skipped", { error: err });
268
276
  }
269
277
  })();
270
278
  if (options.persistSession !== false) {
@@ -355,10 +363,12 @@ export class Agent {
355
363
  return this.mode;
356
364
  }
357
365
  getSandboxMode() {
366
+ // Sandbox has been removed; BashTool.getSandboxMode() always returns "off".
358
367
  return this.bash.getSandboxMode();
359
368
  }
360
- setSandboxMode(mode) {
361
- this.bash.setSandboxMode(mode);
369
+ setSandboxMode(_mode) {
370
+ // No-op: sandbox has been removed. BashTool no longer has a mutable sandbox mode.
371
+ // Will be re-implemented when the new sandbox is ready.
362
372
  }
363
373
  getSandboxSettings() {
364
374
  return this.bash.getSandboxSettings();
@@ -419,6 +429,10 @@ export class Agent {
419
429
  getMessages() {
420
430
  return this.messages;
421
431
  }
432
+ setMessages(messages) {
433
+ this.messages = messages;
434
+ this.messageSeqs = messages.map(() => null);
435
+ }
422
436
  async listSchedules() {
423
437
  return this.schedules.list();
424
438
  }
@@ -516,6 +530,15 @@ export class Agent {
516
530
  setToolLoopCapHandler(fn) {
517
531
  this._toolLoopCapHandler = fn;
518
532
  }
533
+ // Safety-override handler — set by the UI (app.tsx) at startup. Invoked
534
+ // from the message-processor tool-result pipeline when a safety block
535
+ // (catastrophic, dangerous, git-safety, empty-bash) is detected. The UI
536
+ // surfaces an askcard ("Allow once / Allow session / Block") and resolves
537
+ // with the verdict. When unset, blocks pass through as normal errors.
538
+ _safetyOverrideHandler = null;
539
+ setSafetyOverrideHandler(fn) {
540
+ this._safetyOverrideHandler = fn;
541
+ }
519
542
  respondToToolApproval(approvalId, approved) {
520
543
  const toolApprovalResponse = {
521
544
  role: "tool",
@@ -640,6 +663,14 @@ export class Agent {
640
663
  return [];
641
664
  return buildChatEntries(this.session.id);
642
665
  }
666
+ getLastTodoSnapshot() {
667
+ if (!this.session)
668
+ return null;
669
+ const argsJson = getLastTodoWriteArgs(this.session.id);
670
+ if (!argsJson)
671
+ return null;
672
+ return snapshotFromTodoWriteArgs(argsJson);
673
+ }
643
674
  getSessionSnapshot() {
644
675
  if (!this.session || !this.workspace)
645
676
  return null;
@@ -765,6 +796,10 @@ export class Agent {
765
796
  // where orchestrator/task/title traffic is actually spending.
766
797
  // Best-effort: failures inside appendCostLog are swallowed (see cost-log.ts).
767
798
  const breakdown = source === "message" ? (this._lastPromptBreakdown ?? undefined) : undefined;
799
+ // Sanitize actualInputTokens for providers (e.g. SiliconFlow) that return
800
+ // implausibly low prompt_tokens (e.g. 10) regardless of prompt size.
801
+ const estIn = breakdown ? Math.ceil(((breakdown.systemChars ?? 0) + (breakdown.messagesChars ?? 0)) / 4) : 0;
802
+ const actualInput = sanitizeInputTokens(totalInput, estIn);
768
803
  appendCostLog({
769
804
  ts: Date.now(),
770
805
  provider: this.providerId,
@@ -772,7 +807,7 @@ export class Agent {
772
807
  estimatedUsd: turnCostMicros / 1_000_000,
773
808
  callsite: `orchestrator.${source}`,
774
809
  phase: source,
775
- actualInputTokens: totalInput,
810
+ actualInputTokens: actualInput,
776
811
  actualOutputTokens: output,
777
812
  cachedInputTokens: cacheRead,
778
813
  systemChars: breakdown?.systemChars,
@@ -1162,15 +1197,43 @@ export class Agent {
1162
1197
  };
1163
1198
  }
1164
1199
  }
1200
+ async killDelegation(id) {
1201
+ try {
1202
+ return await this.delegations.kill(id);
1203
+ }
1204
+ catch (err) {
1205
+ const msg = err instanceof Error ? err.message : String(err);
1206
+ return {
1207
+ success: false,
1208
+ output: `Failed to terminate delegation: ${msg}`,
1209
+ };
1210
+ }
1211
+ }
1165
1212
  getCompactionSettings(contextWindow) {
1166
1213
  let keepRecentTokens = DEFAULT_KEEP_RECENT_TOKENS;
1167
1214
  // For models with very large context windows, keep more recent tokens
1168
1215
  if (contextWindow && contextWindow > 200_000) {
1169
1216
  keepRecentTokens = Math.min(100_000, Math.max(20_000, Math.floor(contextWindow * 0.1)));
1170
1217
  }
1171
- // Compact more aggressively for long sessions to prevent runaway token growth
1218
+ // For long sessions, reduce keepRecentTokens slightly to slow context growth
1219
+ // (0.85 instead of old 0.75 — session bf58d0f46b51 showed 0.75 created a
1220
+ // tight compact-loop: keep 15K → add 50K tool results → re-compact immediately).
1172
1221
  if (this._compactionStats.count >= 2) {
1173
- keepRecentTokens = Math.floor(keepRecentTokens * 0.75);
1222
+ keepRecentTokens = Math.floor(keepRecentTokens * 0.85);
1223
+ }
1224
+ // For sub-sessions, reduce keepRecentTokens to 8000 to keep active history light
1225
+ try {
1226
+ if (this.session) {
1227
+ const { getDatabase } = require("../storage/db.js");
1228
+ const db = getDatabase();
1229
+ const row = db.prepare("SELECT parent_session_id FROM sessions WHERE id = ?").get(this.session.id);
1230
+ if (row?.parent_session_id) {
1231
+ keepRecentTokens = 8000;
1232
+ }
1233
+ }
1234
+ }
1235
+ catch {
1236
+ // Ignored
1174
1237
  }
1175
1238
  return {
1176
1239
  reserveTokens: Math.max(this.maxTokens, DEFAULT_RESERVE_TOKENS),
@@ -1186,12 +1249,28 @@ export class Agent {
1186
1249
  async compactForContext(provider, system, contextWindow, signal, settings = this.getCompactionSettings(contextWindow), force = false) {
1187
1250
  if (!this.session)
1188
1251
  return false;
1252
+ // Phase 1: ask the compaction proposer model whether to compact and what to keep/drop.
1253
+ // Only compact if the model says yes. On error/skip, fall back to heuristic.
1254
+ const compactModelId = this._resolveCompactModel();
1255
+ const proposal = await proposeCompaction(provider, compactModelId, this.messages, signal);
1256
+ if (proposal !== null) {
1257
+ // Model decided — compact only if model says shouldCompact
1258
+ if (force) {
1259
+ // force always proceeds; the proposer's actions still guide what to keep/drop
1260
+ }
1261
+ else if (!proposal.shouldCompact) {
1262
+ return false;
1263
+ }
1264
+ }
1265
+ else {
1266
+ // Proposer unavailable — fall back to heuristic token-threshold check
1267
+ if (!force && !shouldCompactContext(estimateConversationTokens(system, this.messages), contextWindow, settings)) {
1268
+ return false;
1269
+ }
1270
+ }
1189
1271
  const preparation = prepareCompaction(this.messages, system, settings);
1190
1272
  if (!preparation)
1191
1273
  return false;
1192
- if (!force && !shouldCompactContext(preparation.tokensBefore, contextWindow, settings)) {
1193
- return false;
1194
- }
1195
1274
  const trigger = force ? "manual" : "auto";
1196
1275
  // Fire-and-forget: notify EE of stale suggestions before compaction
1197
1276
  const { surfacedIds, timestamp } = getLastSurfacedState();
@@ -1219,9 +1298,24 @@ export class Agent {
1219
1298
  await this.fireHook(preCompactInput, signal).catch(() => { });
1220
1299
  const keptSeqs = this.messageSeqs.slice(preparation.firstKeptIndex);
1221
1300
  const firstKeptSeq = keptSeqs.find((seq) => seq !== null) ?? getNextMessageSequence(this.session.id);
1222
- const compactModelId = this._resolveCompactModel();
1223
1301
  const compactStartedAt = Date.now();
1224
- const { summary, usage: compactUsage } = await generateCompactionSummary(provider, compactModelId, preparation, undefined, signal);
1302
+ const isSubSession = await (async () => {
1303
+ try {
1304
+ if (!this.session)
1305
+ return false;
1306
+ const { getDatabase } = await import("../storage/db.js");
1307
+ const db = getDatabase();
1308
+ const row = db.prepare("SELECT parent_session_id FROM sessions WHERE id = ?").get(this.session.id);
1309
+ return !!row?.parent_session_id;
1310
+ }
1311
+ catch {
1312
+ return false;
1313
+ }
1314
+ })();
1315
+ const customInstructions = isSubSession
1316
+ ? "This is a temporary sub-session. Under sub-sessions, it is CRITICAL to preserve active files being worked on, compiler/linter error states, and exact line coordinates in the summary. Do not omit details of files edited, tests run, or compiler diagnostics, as the model needs this specific context to continue working without re-reading the files."
1317
+ : undefined;
1318
+ const { summary, usage: compactUsage } = await generateCompactionSummary(provider, compactModelId, preparation, customInstructions, signal);
1225
1319
  // Record compaction call in cost-log — bypasses recordUsage because
1226
1320
  // compaction returns usage separately and isn't routed through the
1227
1321
  // status-bar / usage event pipeline (intentional: it's overhead, not user spend).
@@ -1285,11 +1379,15 @@ export class Agent {
1285
1379
  .catch(() => { });
1286
1380
  // Mark as surfaced for prompt-stale reconciliation (per plan Phase 1).
1287
1381
  updateLastSurfacedState([`compact-checkpoint-${this._compactionStats.count + 1}`]);
1288
- // Track compaction stats — net of the tokens spent ON compaction itself.
1382
+ // Track compaction stats — report the GROSS context reduction, NOT net-after-compact-cost.
1383
+ // Rationale: the compaction call's own prompt/completion tokens ARE already counted in
1384
+ // in_tokens/out_tokens/session_usd (updateStatusBar at line 986). Subtracting them here
1385
+ // would double-penalize: the user sees the real cost in the token counter AND sees
1386
+ // "0 saved" because compactCost >= grossSaved (the compact prompt is the full conversation).
1387
+ // grossSaved tells the user how many context tokens were reclaimed — a real benefit that
1388
+ // reduces subsequent turn costs.
1289
1389
  const tokensAfter = estimateConversationTokens(system, this.messages);
1290
- const grossSaved = Math.max(0, preparation.tokensBefore - tokensAfter);
1291
- const compactCost = compactUsage.promptTokens + compactUsage.completionTokens;
1292
- const saved = Math.max(0, grossSaved - compactCost);
1390
+ const saved = Math.max(0, preparation.tokensBefore - tokensAfter);
1293
1391
  const pct = preparation.tokensBefore > 0 ? ((saved / preparation.tokensBefore) * 100).toFixed(1) : "0.0";
1294
1392
  this._compactionStats.count++;
1295
1393
  this._compactionStats.totalSaved += saved;
@@ -1317,8 +1415,6 @@ export class Agent {
1317
1415
  tokensBefore: preparation.tokensBefore,
1318
1416
  tokensAfter,
1319
1417
  saved,
1320
- grossSaved,
1321
- compactCost,
1322
1418
  pct,
1323
1419
  isLongSession,
1324
1420
  },
@@ -1357,7 +1453,7 @@ export class Agent {
1357
1453
  });
1358
1454
  }
1359
1455
  log(true, `over-threshold (${tokens} >= ${minMeaningfulTokens})`, { tokens, thresholdPct, minMeaningfulTokens });
1360
- await this.compactForContext(provider, system, contextWindow, signal, this.getCompactionSettings(contextWindow), true).catch((err) => console.warn("[compact] failed:", err?.message));
1456
+ await this.compactForContext(provider, system, contextWindow, signal, this.getCompactionSettings(contextWindow), true).catch((err) => logger.warn("orchestrator", "compaction failed", { error: err }));
1361
1457
  }
1362
1458
  // ========================================================================
1363
1459
  // Council system — delegated to CouncilManager (Phase 12.1-02)
@@ -1383,23 +1479,67 @@ export class Agent {
1383
1479
  const councilStats = { calls: 0, startMs: Date.now(), phases: [] };
1384
1480
  const llm = createCouncilLLM(this.bash, this.mode, this.session?.id, councilStats);
1385
1481
  const processMessageFn = (message) => this.processMessage(message, options?.observer);
1386
- const gen = runCouncil(topic, this.modelId, this.messages, this.session?.id, llm, this.councilManager.createQuestionResponder(), this.councilManager.createPreflightResponder(), processMessageFn, {
1387
- skipClarification: options?.skipClarification,
1388
- userModelMessage: options?.userModelMessage,
1389
- cwd: this.bash.getCwd(),
1390
- councilStats, // NEWshare orchestrator's stats object with runCouncil (Phase 14 CQ-01)
1391
- });
1392
- let result;
1393
- do {
1394
- result = await gen.next();
1395
- if (!result.done && result.value) {
1396
- yield result.value;
1482
+ // Ensure a turn-scoped AbortController exists so Esc agent.abort()
1483
+ // this.abortController.abort() actually cancels the council. Two entry points
1484
+ // reach here: the auto-council path runs INSIDE processMessage, which already
1485
+ // set this.abortController; the /council SLASH path calls runCouncilV2
1486
+ // directly with NONE set so before this guard, pressing Esc during an
1487
+ // explicit /council run was a no-op (abort() hit a null controller) and the
1488
+ // multi-minute debate ran to completion regardless. Create one when absent
1489
+ // so the user-abort signal threads into every council generate/debate/
1490
+ // research call for BOTH entry points. Only tear down a controller we own.
1491
+ const ownsController = !this.abortController;
1492
+ if (ownsController) {
1493
+ this.abortController = new AbortController();
1494
+ }
1495
+ const signal = this.abortController?.signal;
1496
+ // B1: Resolve a run directory so runCouncil persists decisions.lock.md after
1497
+ // synthesis. The auto-council/sprint paths get a runDir from their own flow
1498
+ // wiring, but the explicit /council SLASH path never did — so its outcome was
1499
+ // never written to disk (no audit trail, nothing for a later /ideal to read).
1500
+ // Reuse the session's active flow run; create one if the session never booted
1501
+ // a flow. Fail-open: a runDir-resolution error must not block the council.
1502
+ let runDir;
1503
+ try {
1504
+ const nodePath = await import("node:path");
1505
+ const flowDir = await ensureFlowDir(this.bash.getCwd());
1506
+ let runId = this._activeRunId ?? (await getActiveRunId(flowDir));
1507
+ if (!runId) {
1508
+ runId = (await createRun(flowDir)).id;
1509
+ await setActiveRunId(flowDir, runId);
1510
+ this._activeRunId = runId;
1511
+ }
1512
+ runDir = nodePath.join(flowDir, "runs", runId);
1513
+ }
1514
+ catch (err) {
1515
+ logger.error("router", "runDir resolution failed (decisions.lock will be skipped)", { error: err });
1516
+ }
1517
+ try {
1518
+ const gen = runCouncil(topic, this.modelId, this.messages, this.session?.id, llm, this.councilManager.createQuestionResponder(), this.councilManager.createPreflightResponder(), processMessageFn, {
1519
+ skipClarification: options?.skipClarification,
1520
+ userModelMessage: options?.userModelMessage,
1521
+ cwd: this.bash.getCwd(),
1522
+ councilStats, // NEW — share orchestrator's stats object with runCouncil (Phase 14 CQ-01)
1523
+ signal,
1524
+ runDir, // B1 — persist decisions.lock.md for the /council slash path
1525
+ });
1526
+ let result;
1527
+ do {
1528
+ result = await gen.next();
1529
+ if (!result.done && result.value) {
1530
+ yield result.value;
1531
+ }
1532
+ } while (!result.done);
1533
+ const synthesis = result.value;
1534
+ this.councilManager.setLastSynthesis(synthesis);
1535
+ if (options?.userModelMessage && synthesis) {
1536
+ this.appendCompletedTurn(options.userModelMessage, [{ role: "assistant", content: synthesis }]);
1537
+ }
1538
+ }
1539
+ finally {
1540
+ if (ownsController && this.abortController?.signal === signal) {
1541
+ this.abortController = null;
1397
1542
  }
1398
- } while (!result.done);
1399
- const synthesis = result.value;
1400
- this.councilManager.setLastSynthesis(synthesis);
1401
- if (options?.userModelMessage && synthesis) {
1402
- this.appendCompletedTurn(options.userModelMessage, [{ role: "assistant", content: synthesis }]);
1403
1543
  }
1404
1544
  }
1405
1545
  // ========================================================================
@@ -1904,6 +2044,9 @@ export class Agent {
1904
2044
  get maxToolRounds() {
1905
2045
  return self.maxToolRounds;
1906
2046
  },
2047
+ get hardMaxToolRounds() {
2048
+ return self.hardMaxToolRounds;
2049
+ },
1907
2050
  get maxTokens() {
1908
2051
  return self.maxTokens;
1909
2052
  },
@@ -1930,12 +2073,27 @@ export class Agent {
1930
2073
  runDelegation: (request, signal) => self.runDelegation(request, signal),
1931
2074
  readDelegation: (id) => self.readDelegation(id),
1932
2075
  listDelegations: () => self.listDelegations(),
2076
+ killDelegation: (id) => self.killDelegation(id),
1933
2077
  executeBatchToolCall: (tools, toolCall, messages, signal) => self.executeBatchToolCall(tools, toolCall, messages, signal),
1934
2078
  appendCompletedTurn: (user, asst) => self.appendCompletedTurn(user, asst),
1935
2079
  discardAbortedTurn: (user) => self.discardAbortedTurn(user),
1936
2080
  recordUsage: (usage, source, model, shape) => self.recordUsage(usage, source, model, shape),
1937
2081
  };
1938
2082
  }
2083
+ appendMidTurnMessages(newMessages) {
2084
+ if (newMessages.length === 0)
2085
+ return;
2086
+ if (!this.sessionStore || !this.session) {
2087
+ this.messages.push(...newMessages);
2088
+ this.messageSeqs.push(...newMessages.map(() => null));
2089
+ return;
2090
+ }
2091
+ const insertedSeqs = appendMessages(this.session.id, newMessages);
2092
+ this.messages.push(...newMessages);
2093
+ this.messageSeqs.push(...insertedSeqs);
2094
+ this.sessionStore.touchSession(this.session.id, this.bash.getCwd());
2095
+ this.session = this.sessionStore.getRequiredSession(this.session.id);
2096
+ }
1939
2097
  appendCompletedTurn(userMessage, newMessages) {
1940
2098
  if (newMessages.length === 0)
1941
2099
  return;
@@ -1981,13 +2139,257 @@ export class Agent {
1981
2139
  fireHook(input, signal) {
1982
2140
  return executeEventHooks(input, this.bash.getCwd(), signal);
1983
2141
  }
2142
+ estimateConversationChars() {
2143
+ let count = 0;
2144
+ for (const m of this.messages) {
2145
+ if (typeof m.content === "string") {
2146
+ count += m.content.length;
2147
+ }
2148
+ else if (Array.isArray(m.content)) {
2149
+ for (const p of m.content) {
2150
+ if (p.type === "text" && p.text) {
2151
+ count += p.text.length;
2152
+ }
2153
+ }
2154
+ }
2155
+ }
2156
+ return count;
2157
+ }
1984
2158
  // ========================================================================
1985
2159
  // processMessage — main streaming turn loop (PIL enrichment, routing, LLM
1986
2160
  // stream, tool execution, compaction, hooks, observer notifications)
1987
2161
  // ========================================================================
1988
2162
  async *processMessage(userMessage, observer, images) {
1989
- const processor = new MessageProcessor(this._buildMessageProcessorDeps());
1990
- yield* processor.run(userMessage, observer, images);
2163
+ const threshold = Number(process.env.MUONROI_SILENT_ROTATION_THRESHOLD) || 80000;
2164
+ const currentChars = this.estimateConversationChars();
2165
+ logger.debug("orchestrator", "Checking silent session rotation threshold", { currentChars, threshold });
2166
+ // 1. Run classifier to decide execution route
2167
+ let routeAction = "DIRECT_ANSWER";
2168
+ try {
2169
+ const { classifySubSessionAction } = await import("../pil/llm-classify.js");
2170
+ const routeResult = await classifySubSessionAction(this.requireProvider(), this.modelId, userMessage);
2171
+ if (routeResult) {
2172
+ routeAction = routeResult.action;
2173
+ logger.info("orchestrator", "Routing action selected for user message", {
2174
+ action: routeAction,
2175
+ confidence: routeResult.confidence,
2176
+ reason: routeResult.reason,
2177
+ });
2178
+ }
2179
+ }
2180
+ catch (err) {
2181
+ logger.error("orchestrator", "Routing classification failed, falling back to DIRECT_ANSWER", { error: err });
2182
+ }
2183
+ const shouldRotate = currentChars > threshold || routeAction === "ROTATE_SESSION";
2184
+ if (shouldRotate && this.session && this.sessionStore) {
2185
+ logger.info("orchestrator", "Triggering silent session rotation", {
2186
+ parentSessionId: this.session.id,
2187
+ currentChars,
2188
+ });
2189
+ yield { type: "content", content: "\n⋯ Xoay vòng session ngầm để tối ưu hóa context (Anti-Mù active)...\n" };
2190
+ const parentSessionId = this.session.id;
2191
+ try {
2192
+ const path = await import("node:path");
2193
+ const flowDir = path.join(this.bash.getCwd(), ".muonroi-flow");
2194
+ const { deliberateCompact } = await import("../flow/compaction/index.js");
2195
+ const { getDatabase } = await import("../storage/db.js");
2196
+ const { appendCompaction, getNextMessageSequence } = await import("../storage/transcript.js");
2197
+ const cr = await deliberateCompact(flowDir, this.messages, "", 4096, this.requireProvider(), this.modelId);
2198
+ const newSession = this.sessionStore.createSession(this.modelId, this.mode, this.bash.getCwd());
2199
+ const db = getDatabase();
2200
+ db.prepare("UPDATE sessions SET parent_session_id = ? WHERE id = ?").run(parentSessionId, newSession.id);
2201
+ const summaryMessage = createCompactionSummaryMessage(cr.summary);
2202
+ const nextSeq = getNextMessageSequence(newSession.id);
2203
+ appendCompaction(newSession.id, nextSeq, cr.summary, cr.tokensBeforeCompress);
2204
+ this.session = this.sessionStore.getRequiredSession(newSession.id);
2205
+ this.messages = [summaryMessage];
2206
+ this.messageSeqs = [null];
2207
+ this.sessionStore.touchSession(newSession.id, this.bash.getCwd());
2208
+ logger.info("orchestrator", "Silent session rotation completed successfully", {
2209
+ parentSessionId,
2210
+ newSessionId: newSession.id,
2211
+ summaryLength: cr.summary.length,
2212
+ tokensBeforeCompress: cr.tokensBeforeCompress,
2213
+ });
2214
+ }
2215
+ catch (err) {
2216
+ logger.error("orchestrator", "silent session rotation failed", { error: err });
2217
+ }
2218
+ }
2219
+ let isSubSessionForked = false;
2220
+ let parentSessionId = null;
2221
+ let subSessionId = null;
2222
+ if (routeAction === "SPAWN_SUB_SESSION" && this.session && this.sessionStore) {
2223
+ yield { type: "content", content: "\n⋯ Đang khởi tạo sub-session ngầm để xử lý tác vụ...\n" };
2224
+ parentSessionId = this.session.id;
2225
+ try {
2226
+ const { loadLatestCompaction, getNextMessageSequence, appendCompaction, loadTranscriptState } = await import("../storage/transcript.js");
2227
+ const { getDatabase } = await import("../storage/db.js");
2228
+ const db = getDatabase();
2229
+ // Check if there is already an active child session for this parent session
2230
+ const activeSubSession = db
2231
+ .prepare("SELECT id, updated_at FROM sessions WHERE parent_session_id = ? AND status = 'active' ORDER BY updated_at DESC LIMIT 1")
2232
+ .get(parentSessionId);
2233
+ let shouldResume = false;
2234
+ if (activeSubSession) {
2235
+ const updatedAt = new Date(activeSubSession.updated_at).getTime();
2236
+ const now = Date.now();
2237
+ const diffMins = (now - updatedAt) / (60 * 1000);
2238
+ if (diffMins > 15) {
2239
+ // Stale: mark as abandoned
2240
+ db.prepare("UPDATE sessions SET status = 'abandoned', updated_at = ? WHERE id = ?").run(new Date().toISOString(), activeSubSession.id);
2241
+ logger.info("orchestrator", "Stale sub-session found, marked as abandoned", {
2242
+ parentSessionId,
2243
+ subSessionId: activeSubSession.id,
2244
+ diffMins,
2245
+ });
2246
+ }
2247
+ else {
2248
+ shouldResume = true;
2249
+ subSessionId = activeSubSession.id;
2250
+ }
2251
+ }
2252
+ if (shouldResume && subSessionId) {
2253
+ yield { type: "content", content: "\n⋯ Phát hiện sub-session trước đó bị gián đoạn, đang khôi phục...\n" };
2254
+ this.session = this.sessionStore.getRequiredSession(subSessionId);
2255
+ const childState = loadTranscriptState(subSessionId);
2256
+ this.messages = childState.messages;
2257
+ this.messageSeqs = childState.seqs;
2258
+ this.sessionStore.touchSession(subSessionId, this.bash.getCwd());
2259
+ isSubSessionForked = true;
2260
+ logger.info("orchestrator", "Resumed child sub-session successfully", {
2261
+ parentSessionId,
2262
+ subSessionId,
2263
+ });
2264
+ }
2265
+ else {
2266
+ const latest = loadLatestCompaction(parentSessionId);
2267
+ const newSession = this.sessionStore.createSession(this.modelId, this.mode, this.bash.getCwd());
2268
+ db.prepare("UPDATE sessions SET parent_session_id = ? WHERE id = ?").run(parentSessionId, newSession.id);
2269
+ subSessionId = newSession.id;
2270
+ let seedMessages = [];
2271
+ let seedSeqs = [];
2272
+ if (latest?.summary) {
2273
+ const summaryMsg = createCompactionSummaryMessage(latest.summary);
2274
+ seedMessages = [summaryMsg];
2275
+ seedSeqs = [null];
2276
+ const nextSeq = getNextMessageSequence(subSessionId);
2277
+ appendCompaction(subSessionId, nextSeq, latest.summary, latest.tokensBefore);
2278
+ }
2279
+ else {
2280
+ seedMessages = [...this.messages];
2281
+ seedSeqs = [...this.messageSeqs];
2282
+ }
2283
+ // Add sub-session overlay system message
2284
+ const overlayMessage = {
2285
+ role: "system",
2286
+ content: `You are executing a sub-task delegated by the Main Session in an isolated, temporary Sub-Session.\n` +
2287
+ `Your goal is to satisfy the user's request: "${userMessage}"\n\n` +
2288
+ `Your Operating Boundaries & Rules:\n` +
2289
+ `1. You have full access to tools (bash, edit_file, read_file, grep, etc.). Execute them as needed to build, debug, and verify the work.\n` +
2290
+ `2. Stay strictly focused on completing the request. Do not engage in social chat or pleasantries.\n` +
2291
+ `3. Once the goal is achieved and verified, you MUST return your final response using the 'respond_general' (or final answer) tool.\n` +
2292
+ `4. Your final response MUST contain a structured summary (Key Changes, Verification Details, Result Summary).\n` +
2293
+ `5. All intermediate tool calls, raw tool outputs, and diagnostic traces will remain isolated inside this sub-session and will not bloat the parent session.`,
2294
+ };
2295
+ seedMessages.push(overlayMessage);
2296
+ seedSeqs.push(null);
2297
+ this.session = this.sessionStore.getRequiredSession(subSessionId);
2298
+ this.messages = seedMessages;
2299
+ this.messageSeqs = seedSeqs;
2300
+ this.sessionStore.touchSession(subSessionId, this.bash.getCwd());
2301
+ isSubSessionForked = true;
2302
+ logger.info("orchestrator", "Forked child sub-session successfully", {
2303
+ parentSessionId,
2304
+ subSessionId,
2305
+ });
2306
+ }
2307
+ }
2308
+ catch (err) {
2309
+ logger.error("orchestrator", "Forking child sub-session failed, falling back to main session", { error: err });
2310
+ }
2311
+ }
2312
+ let processor = new MessageProcessor(this._buildMessageProcessorDeps());
2313
+ const autoCommitOn = isAutoCommitEnabled();
2314
+ const cwd = this.bash.getCwd();
2315
+ const dirtyBefore = autoCommitOn ? await snapshotDirtyPaths(cwd) : new Set();
2316
+ try {
2317
+ let attempts = 0;
2318
+ const maxAttempts = 3;
2319
+ const messagesSnapshot = [...this.messages];
2320
+ const seqsSnapshot = [...this.messageSeqs];
2321
+ while (attempts < maxAttempts) {
2322
+ try {
2323
+ attempts++;
2324
+ yield* processor.run(userMessage, observer, images);
2325
+ break;
2326
+ }
2327
+ catch (err) {
2328
+ if (isSubSessionForked && isTransientError(err) && attempts < maxAttempts && subSessionId) {
2329
+ logger.warn("orchestrator", "Transient error in sub-session, retrying", {
2330
+ attempt: attempts,
2331
+ error: err,
2332
+ });
2333
+ yield {
2334
+ type: "content",
2335
+ content: `\n⚠️ Có lỗi mạng hoặc rate limit xảy ra trong sub-session (lần thử ${attempts}/${maxAttempts}). Đang thử lại...\n`,
2336
+ };
2337
+ this.messages = [...messagesSnapshot];
2338
+ this.messageSeqs = [...seqsSnapshot];
2339
+ processor = new MessageProcessor(this._buildMessageProcessorDeps());
2340
+ }
2341
+ else {
2342
+ throw err;
2343
+ }
2344
+ }
2345
+ }
2346
+ if (autoCommitOn) {
2347
+ const auto = await maybeAutoCommitTurn({ cwd, dirtyBefore, userMessage }).catch((err) => {
2348
+ logger.error("orchestrator", "auto-commit unexpected failure", { error: err });
2349
+ return { committed: false };
2350
+ });
2351
+ if (auto.committed) {
2352
+ yield {
2353
+ type: "content",
2354
+ content: `\n✓ Auto-committed ${auto.fileCount} file(s) → ${auto.sha} (${AUTO_COMMIT_ATTRIBUTION})\n`,
2355
+ };
2356
+ }
2357
+ }
2358
+ }
2359
+ finally {
2360
+ if (isSubSessionForked && parentSessionId && this.sessionStore) {
2361
+ try {
2362
+ const finalMessages = salvageSubSessionOutput(this.messages);
2363
+ // Restore parent session
2364
+ this.session = this.sessionStore.getRequiredSession(parentSessionId);
2365
+ const { loadTranscriptState } = await import("../storage/transcript.js");
2366
+ const parentState = loadTranscriptState(parentSessionId);
2367
+ this.messages = parentState.messages;
2368
+ this.messageSeqs = parentState.seqs;
2369
+ const userModelMessage = {
2370
+ role: "user",
2371
+ content: userMessage,
2372
+ };
2373
+ if (finalMessages.length > 0) {
2374
+ this.appendCompletedTurn(userModelMessage, finalMessages);
2375
+ logger.info("orchestrator", "Absorbed sub-session outcome into parent session", {
2376
+ parentSessionId,
2377
+ subSessionId,
2378
+ absorbedMessagesCount: finalMessages.length,
2379
+ });
2380
+ }
2381
+ else {
2382
+ logger.warn("orchestrator", "No assistant messages found to absorb from sub-session", {
2383
+ parentSessionId,
2384
+ subSessionId,
2385
+ });
2386
+ }
2387
+ }
2388
+ catch (err) {
2389
+ logger.error("orchestrator", "Failed to absorb sub-session final summary", { error: err });
2390
+ }
2391
+ }
2392
+ }
1991
2393
  }
1992
2394
  /**
1993
2395
  * Build the DI surface MessageProcessor (Phase 12.4) needs to reach back
@@ -2026,6 +2428,9 @@ export class Agent {
2026
2428
  get maxToolRounds() {
2027
2429
  return self.maxToolRounds;
2028
2430
  },
2431
+ get hardMaxToolRounds() {
2432
+ return self.hardMaxToolRounds;
2433
+ },
2029
2434
  get batchApi() {
2030
2435
  return self.batchApi;
2031
2436
  },
@@ -2120,6 +2525,39 @@ export class Agent {
2120
2525
  },
2121
2526
  requireProvider: () => self.requireProvider(),
2122
2527
  emitSubagentStatus: (s) => self.emitSubagentStatus(s),
2528
+ consultParentSession: async (question) => {
2529
+ if (!self.session) {
2530
+ return "No active session found to consult.";
2531
+ }
2532
+ const { getDatabase } = await import("../storage/db.js");
2533
+ const db = getDatabase();
2534
+ const row = db.prepare("SELECT parent_session_id FROM sessions WHERE id = ?").get(self.session.id);
2535
+ if (!row?.parent_session_id) {
2536
+ return "No parent session found to consult.";
2537
+ }
2538
+ const parentSessionId = row.parent_session_id;
2539
+ const { loadTranscriptState } = await import("../storage/transcript.js");
2540
+ const parentState = loadTranscriptState(parentSessionId);
2541
+ const { serializeConversation } = await import("./compaction.js");
2542
+ const serializedParent = serializeConversation(parentState.messages);
2543
+ const systemPrompt = `You are the Main Session of an AI coding assistant.\n` +
2544
+ `A child sub-session has been spawned to execute a sub-task and is requesting your advice/guidance.\n` +
2545
+ `Below is your conversation history (Main Session Context):\n\n` +
2546
+ `${serializedParent}\n\n` +
2547
+ `Provide clear, actionable guidance to resolve the child's query.`;
2548
+ const { generateText } = await import("ai");
2549
+ const provider = self.requireProvider();
2550
+ const modelId = self.modelId;
2551
+ const runtime = resolveModelRuntime(provider, modelId);
2552
+ const result = await generateText({
2553
+ model: runtime.model,
2554
+ system: systemPrompt,
2555
+ prompt: `Child Sub-session is stuck. Question:\n${question}`,
2556
+ temperature: 0.2,
2557
+ ...(runtime.providerOptions ? { providerOptions: runtime.providerOptions } : {}),
2558
+ });
2559
+ return result.text;
2560
+ },
2123
2561
  fireHook: (input, signal) => self.fireHook(input, signal),
2124
2562
  consumeBackgroundNotifications: () => self.consumeBackgroundNotifications(),
2125
2563
  initOAuthProvider: () => self._initOAuthProvider(),
@@ -2133,7 +2571,9 @@ export class Agent {
2133
2571
  runDelegation: (request, signal) => self.runDelegation(request, signal),
2134
2572
  readDelegation: (id) => self.readDelegation(id),
2135
2573
  listDelegations: () => self.listDelegations(),
2574
+ killDelegation: (id) => self.killDelegation(id),
2136
2575
  drainSteerMessages: () => self.steerDrain?.() ?? [],
2576
+ appendMidTurnMessages: (msgs) => self.appendMidTurnMessages(msgs),
2137
2577
  appendCompletedTurn: (user, asst) => self.appendCompletedTurn(user, asst),
2138
2578
  discardAbortedTurn: (user) => self.discardAbortedTurn(user),
2139
2579
  recordUsage: (usage, source, model, shape) => self.recordUsage(usage, source, model, shape),
@@ -2146,10 +2586,24 @@ export class Agent {
2146
2586
  return await h(info);
2147
2587
  }
2148
2588
  catch (err) {
2149
- console.error(`[Agent] askToolLoopContinue crashed: ${err?.message ?? err}`);
2589
+ logger.error("orchestrator", "askToolLoopContinue crashed", { error: err });
2150
2590
  return "stop";
2151
2591
  }
2152
2592
  },
2593
+ askSafetyOverride: async (info) => {
2594
+ const h = self._safetyOverrideHandler;
2595
+ if (!h) {
2596
+ logger.warn("orchestrator", `askSafetyOverride called but no handler registered — blocking ${info.kind}: ${info.reason}`);
2597
+ return { action: "block" };
2598
+ }
2599
+ try {
2600
+ return await h(info);
2601
+ }
2602
+ catch (err) {
2603
+ logger.error("orchestrator", "askSafetyOverride crashed", { error: err });
2604
+ return { action: "block" };
2605
+ }
2606
+ },
2153
2607
  runCouncilV2: (msg, opts) => self.runCouncilV2(msg, opts),
2154
2608
  processMessage: (msg, obs, imgs) => self.processMessage(msg, obs, imgs),
2155
2609
  processMessageBatchTurn: (args) => self.processMessageBatchTurn(args),
@@ -2322,4 +2776,37 @@ export class Agent {
2322
2776
  }
2323
2777
  }
2324
2778
  }
2779
+ function isTransientError(err) {
2780
+ if (!(err instanceof Error))
2781
+ return false;
2782
+ const msg = err.message.toLowerCase();
2783
+ return (msg.includes("fetch failed") ||
2784
+ msg.includes("rate limit") ||
2785
+ msg.includes("429") ||
2786
+ msg.includes("503") ||
2787
+ msg.includes("502") ||
2788
+ msg.includes("network") ||
2789
+ msg.includes("timeout") ||
2790
+ msg.includes("econnreset") ||
2791
+ msg.includes("econnrefused"));
2792
+ }
2793
+ function salvageSubSessionOutput(messages) {
2794
+ let lastAsstIdx = -1;
2795
+ for (let i = messages.length - 1; i >= 0; i--) {
2796
+ if (messages[i].role === "assistant") {
2797
+ lastAsstIdx = i;
2798
+ break;
2799
+ }
2800
+ }
2801
+ const finalMessages = [];
2802
+ if (lastAsstIdx >= 0) {
2803
+ finalMessages.push(messages[lastAsstIdx]);
2804
+ for (let i = lastAsstIdx + 1; i < messages.length; i++) {
2805
+ if (messages[i].role === "tool") {
2806
+ finalMessages.push(messages[i]);
2807
+ }
2808
+ }
2809
+ }
2810
+ return finalMessages;
2811
+ }
2325
2812
  //# sourceMappingURL=orchestrator.js.map