muonroi-cli 1.7.2 → 1.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (322) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +122 -122
  3. package/dist/packages/agent-harness-core/src/event-filter.js +1 -0
  4. package/dist/packages/agent-harness-core/src/event-redact.js +8 -0
  5. package/dist/packages/agent-harness-core/src/mcp-server.d.ts +16 -0
  6. package/dist/packages/agent-harness-core/src/mcp-server.js +88 -10
  7. package/dist/packages/agent-harness-core/src/protocol.d.ts +15 -0
  8. package/dist/packages/agent-harness-core/src/selector.js +2 -2
  9. package/dist/packages/agent-harness-core/src/transports/sidechannel.js +1 -1
  10. package/dist/packages/agent-harness-opentui/src/agent-mode.js +1 -1
  11. package/dist/packages/agent-harness-opentui/src/install.js +1 -1
  12. package/dist/src/__test-stubs__/vitest-setup.js +4 -0
  13. package/dist/src/agent-harness/__tests__/mock-model.spec.js +54 -0
  14. package/dist/src/agent-harness/mock-model.d.ts +1 -1
  15. package/dist/src/agent-harness/mock-model.js +32 -3
  16. package/dist/src/agent-harness/test-spawn.js +3 -1
  17. package/dist/src/chat/chat-keychain.js +9 -1
  18. package/dist/src/cli/cost-forensics.js +12 -12
  19. package/dist/src/cli/keys.d.ts +2 -2
  20. package/dist/src/cli/keys.js +107 -28
  21. package/dist/src/cli/usage-report.js +2 -2
  22. package/dist/src/council/__tests__/abort-threading.test.d.ts +1 -0
  23. package/dist/src/council/__tests__/abort-threading.test.js +193 -0
  24. package/dist/src/council/__tests__/clarification-prompt.test.js +21 -1
  25. package/dist/src/council/__tests__/council-turn-length-emit.test.d.ts +1 -0
  26. package/dist/src/council/__tests__/council-turn-length-emit.test.js +130 -0
  27. package/dist/src/council/__tests__/debate-round-budget.test.d.ts +1 -0
  28. package/dist/src/council/__tests__/debate-round-budget.test.js +45 -0
  29. package/dist/src/council/__tests__/evaluator-metrics.test.js +3 -0
  30. package/dist/src/council/__tests__/post-debate-recommendation.test.d.ts +1 -0
  31. package/dist/src/council/__tests__/post-debate-recommendation.test.js +58 -0
  32. package/dist/src/council/__tests__/research-tools.test.js +4 -0
  33. package/dist/src/council/__tests__/round-tools.test.js +6 -0
  34. package/dist/src/council/clarifier-question-cap.test.d.ts +12 -0
  35. package/dist/src/council/clarifier-question-cap.test.js +73 -0
  36. package/dist/src/council/clarifier.d.ts +29 -1
  37. package/dist/src/council/clarifier.js +64 -1
  38. package/dist/src/council/debate-planner.d.ts +2 -1
  39. package/dist/src/council/debate-planner.js +5 -3
  40. package/dist/src/council/debate.d.ts +9 -0
  41. package/dist/src/council/debate.js +88 -8
  42. package/dist/src/council/index.d.ts +46 -1
  43. package/dist/src/council/index.js +125 -19
  44. package/dist/src/council/llm.js +41 -13
  45. package/dist/src/council/prompts.js +56 -21
  46. package/dist/src/council/types.d.ts +14 -1
  47. package/dist/src/ee/__tests__/export-transcripts.test.js +1 -1
  48. package/dist/src/ee/bridge.d.ts +3 -1
  49. package/dist/src/ee/bridge.js +9 -3
  50. package/dist/src/ee/bridge.test.js +1 -2
  51. package/dist/src/ee/client.js +15 -2
  52. package/dist/src/ee/recall-ledger.d.ts +20 -0
  53. package/dist/src/ee/recall-ledger.js +40 -1
  54. package/dist/src/ee/search.d.ts +25 -0
  55. package/dist/src/ee/search.js +73 -0
  56. package/dist/src/ee/types.d.ts +15 -1
  57. package/dist/src/ee/who-am-i.d.ts +41 -0
  58. package/dist/src/ee/who-am-i.js +143 -0
  59. package/dist/src/ee/who-am-i.test.d.ts +1 -0
  60. package/dist/src/ee/who-am-i.test.js +101 -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 +55 -60
  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 +2 -2
  76. package/dist/src/lsp/manager.test.js +2 -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/client-pool.d.ts +1 -1
  86. package/dist/src/mcp/client-pool.js +25 -9
  87. package/dist/src/mcp/ee-tools.d.ts +10 -0
  88. package/dist/src/mcp/ee-tools.js +34 -0
  89. package/dist/src/mcp/oauth-callback.js +2 -2
  90. package/dist/src/mcp/parse-headers.test.js +14 -14
  91. package/dist/src/mcp/self-verify-jobs.js +1 -3
  92. package/dist/src/mcp/setup-guide-text.js +74 -74
  93. package/dist/src/mcp/smoke.test.js +43 -43
  94. package/dist/src/models/catalog-gemini.test.js +12 -10
  95. package/dist/src/models/catalog.json +106 -38
  96. package/dist/src/ops/doctor.js +7 -7
  97. package/dist/src/orchestrator/__tests__/auto-commit.test.d.ts +1 -0
  98. package/dist/src/orchestrator/__tests__/auto-commit.test.js +142 -0
  99. package/dist/src/orchestrator/__tests__/batch-turn-runner.test.js +2 -0
  100. package/dist/src/orchestrator/__tests__/delegations.test.d.ts +1 -0
  101. package/dist/src/orchestrator/__tests__/delegations.test.js +96 -0
  102. package/dist/src/orchestrator/__tests__/message-processor.test.js +1 -0
  103. package/dist/src/orchestrator/__tests__/stream-runner.test.js +29 -0
  104. package/dist/src/orchestrator/agent.test.js +5 -2
  105. package/dist/src/orchestrator/auto-commit.d.ts +110 -0
  106. package/dist/src/orchestrator/auto-commit.js +364 -0
  107. package/dist/src/orchestrator/batch-turn-runner.d.ts +3 -2
  108. package/dist/src/orchestrator/batch-turn-runner.js +2 -1
  109. package/dist/src/orchestrator/compaction-proposer-prompt.d.ts +8 -0
  110. package/dist/src/orchestrator/compaction-proposer-prompt.js +43 -0
  111. package/dist/src/orchestrator/compaction.d.ts +22 -0
  112. package/dist/src/orchestrator/compaction.js +42 -0
  113. package/dist/src/orchestrator/delegations.d.ts +1 -0
  114. package/dist/src/orchestrator/delegations.js +41 -0
  115. package/dist/src/orchestrator/error-utils.js +27 -2
  116. package/dist/src/orchestrator/message-processor.d.ts +21 -5
  117. package/dist/src/orchestrator/message-processor.js +219 -2573
  118. package/dist/src/orchestrator/orchestrator.d.ts +8 -2
  119. package/dist/src/orchestrator/orchestrator.js +187 -34
  120. package/dist/src/orchestrator/preprocessor.d.ts +13 -0
  121. package/dist/src/orchestrator/preprocessor.js +145 -0
  122. package/dist/src/orchestrator/prompts.d.ts +8 -0
  123. package/dist/src/orchestrator/prompts.js +76 -60
  124. package/dist/src/orchestrator/safety-askcard.d.ts +66 -0
  125. package/dist/src/orchestrator/safety-askcard.js +74 -0
  126. package/dist/src/orchestrator/sandbox.test.js +8 -5
  127. package/dist/src/orchestrator/scope-ceiling.js +1 -1
  128. package/dist/src/orchestrator/stall-watchdog.d.ts +47 -0
  129. package/dist/src/orchestrator/stall-watchdog.js +23 -0
  130. package/dist/src/orchestrator/stall-watchdog.test.js +36 -1
  131. package/dist/src/orchestrator/stream-runner.js +52 -30
  132. package/dist/src/orchestrator/sub-agent-model-tier.js +1 -1
  133. package/dist/src/orchestrator/subagent-compactor.d.ts +14 -0
  134. package/dist/src/orchestrator/subagent-compactor.js +83 -10
  135. package/dist/src/orchestrator/subagent-compactor.spec.js +132 -0
  136. package/dist/src/orchestrator/text-tool-call-detector.d.ts +9 -5
  137. package/dist/src/orchestrator/text-tool-call-detector.js +16 -12
  138. package/dist/src/orchestrator/text-tool-call-detector.test.js +66 -8
  139. package/dist/src/orchestrator/tool-args-repair.js +1 -1
  140. package/dist/src/orchestrator/tool-engine.d.ts +151 -0
  141. package/dist/src/orchestrator/tool-engine.js +3079 -0
  142. package/dist/src/orchestrator/turn-runner-deps.d.ts +1 -0
  143. package/dist/src/pil/__tests__/discovery-types.test.js +15 -4
  144. package/dist/src/pil/__tests__/discovery.test.js +89 -145
  145. package/dist/src/pil/__tests__/dual-run.test.js +2 -0
  146. package/dist/src/pil/__tests__/layer1-intent-trace.test.js +3 -0
  147. package/dist/src/pil/__tests__/layer1-intent.test.js +3 -0
  148. package/dist/src/pil/__tests__/layer16-clarity.test.js +35 -32
  149. package/dist/src/pil/__tests__/layer18-acceptance.test.js +1 -78
  150. package/dist/src/pil/__tests__/layer2_5-ponytail.test.d.ts +1 -0
  151. package/dist/src/pil/__tests__/layer2_5-ponytail.test.js +53 -0
  152. package/dist/src/pil/__tests__/layer3-ee-injection.test.js +67 -4
  153. package/dist/src/pil/__tests__/layer3-injected-chunk.test.js +14 -2
  154. package/dist/src/pil/__tests__/layer4-gsd.test.js +30 -0
  155. package/dist/src/pil/__tests__/layer5-context.test.js +39 -1
  156. package/dist/src/pil/__tests__/layer6-output.test.js +30 -2
  157. package/dist/src/pil/__tests__/orchestrator-integration.test.js +3 -1
  158. package/dist/src/pil/__tests__/pipeline.test.js +11 -8
  159. package/dist/src/pil/__tests__/response-tools.test.js +52 -0
  160. package/dist/src/pil/agent-operating-contract.d.ts +1 -1
  161. package/dist/src/pil/agent-operating-contract.js +5 -3
  162. package/dist/src/pil/agent-operating-contract.test.js +2 -2
  163. package/dist/src/pil/cheap-model-playbook.d.ts +1 -1
  164. package/dist/src/pil/cheap-model-playbook.js +1 -1
  165. package/dist/src/pil/cheap-model-workbooks.d.ts +1 -1
  166. package/dist/src/pil/cheap-model-workbooks.js +1 -1
  167. package/dist/src/pil/config.d.ts +20 -0
  168. package/dist/src/pil/config.js +35 -0
  169. package/dist/src/pil/discovery-types.d.ts +32 -9
  170. package/dist/src/pil/discovery.d.ts +3 -3
  171. package/dist/src/pil/discovery.js +138 -244
  172. package/dist/src/pil/index.d.ts +1 -1
  173. package/dist/src/pil/index.js +1 -1
  174. package/dist/src/pil/layer1-intent.d.ts +9 -1
  175. package/dist/src/pil/layer1-intent.js +99 -44
  176. package/dist/src/pil/layer1-intent.test.js +112 -2
  177. package/dist/src/pil/layer15-context-scan.js +1 -1
  178. package/dist/src/pil/layer16-clarity.d.ts +21 -25
  179. package/dist/src/pil/layer16-clarity.js +52 -88
  180. package/dist/src/pil/layer18-acceptance.d.ts +13 -3
  181. package/dist/src/pil/layer18-acceptance.js +1 -75
  182. package/dist/src/pil/layer2-personality.js +7 -8
  183. package/dist/src/pil/layer2_5-ponytail.d.ts +2 -0
  184. package/dist/src/pil/layer2_5-ponytail.js +41 -0
  185. package/dist/src/pil/layer3-ee-injection.js +180 -14
  186. package/dist/src/pil/layer4-gsd.js +22 -8
  187. package/dist/src/pil/layer5-context.d.ts +1 -0
  188. package/dist/src/pil/layer5-context.js +26 -11
  189. package/dist/src/pil/layer6-output.js +23 -5
  190. package/dist/src/pil/llm-classify.js +3 -3
  191. package/dist/src/pil/native-capabilities-workbook.d.ts +1 -1
  192. package/dist/src/pil/native-capabilities-workbook.js +4 -3
  193. package/dist/src/pil/pipeline.js +15 -1
  194. package/dist/src/pil/response-tools.d.ts +16 -0
  195. package/dist/src/pil/response-tools.js +41 -0
  196. package/dist/src/pil/schema.d.ts +8 -0
  197. package/dist/src/pil/schema.js +24 -3
  198. package/dist/src/pil/task-tier-map.js +7 -7
  199. package/dist/src/pil/types.d.ts +9 -3
  200. package/dist/src/product-loop/__tests__/discovery-interview.test.js +103 -1
  201. package/dist/src/product-loop/__tests__/gather-selectable-alts.test.js +34 -0
  202. package/dist/src/product-loop/__tests__/sprint-self-verify.test.js +12 -12
  203. package/dist/src/product-loop/discovery-interview.d.ts +20 -0
  204. package/dist/src/product-loop/discovery-interview.js +121 -7
  205. package/dist/src/product-loop/done-gate.js +3 -3
  206. package/dist/src/product-loop/gather.js +19 -5
  207. package/dist/src/product-loop/loop-driver.js +20 -20
  208. package/dist/src/product-loop/progress-snapshot.js +4 -4
  209. package/dist/src/product-loop/sprint-self-verify.js +2 -2
  210. package/dist/src/providers/__tests__/reasoning-roundtrip.test.js +12 -0
  211. package/dist/src/providers/auth/__tests__/gemini-oauth.test.d.ts +1 -5
  212. package/dist/src/providers/auth/__tests__/gemini-oauth.test.js +1 -5
  213. package/dist/src/providers/auth/gcloud.d.ts +28 -0
  214. package/dist/src/providers/auth/gcloud.js +102 -0
  215. package/dist/src/providers/auth/gemini-oauth.d.ts +46 -19
  216. package/dist/src/providers/auth/gemini-oauth.js +292 -105
  217. package/dist/src/providers/auth/grok-oauth.d.ts +1 -1
  218. package/dist/src/providers/auth/grok-oauth.js +1 -1
  219. package/dist/src/providers/auth/registry.js +28 -6
  220. package/dist/src/providers/gemini.d.ts +1 -1
  221. package/dist/src/providers/gemini.js +10 -3
  222. package/dist/src/providers/keychain.d.ts +2 -2
  223. package/dist/src/providers/keychain.js +20 -2
  224. package/dist/src/providers/mcp-vision-bridge.js +48 -48
  225. package/dist/src/providers/openai.js +5 -1
  226. package/dist/src/providers/runtime.d.ts +1 -1
  227. package/dist/src/providers/runtime.js +11 -2
  228. package/dist/src/providers/siliconflow-sse-repair.js +1 -1
  229. package/dist/src/providers/strategies/google.strategy.d.ts +9 -2
  230. package/dist/src/providers/strategies/google.strategy.js +159 -9
  231. package/dist/src/providers/strategies/thinking-mode.js +19 -6
  232. package/dist/src/providers/types.d.ts +1 -1
  233. package/dist/src/reporter/index.js +1 -1
  234. package/dist/src/router/decide.test.js +40 -8
  235. package/dist/src/router/step-router.d.ts +20 -0
  236. package/dist/src/router/step-router.js +73 -0
  237. package/dist/src/scaffold/bb-ecosystem-apply.js +47 -47
  238. package/dist/src/scaffold/bb-quality-gate.js +5 -5
  239. package/dist/src/scaffold/continuation-prompt.js +60 -60
  240. package/dist/src/scaffold/init-new.js +453 -453
  241. package/dist/src/self-qa/__tests__/scenario-planner.test.js +3 -3
  242. package/dist/src/self-qa/agentic-loop.js +19 -19
  243. package/dist/src/self-qa/orchestrator.js +5 -5
  244. package/dist/src/self-qa/scenario-planner.js +1 -1
  245. package/dist/src/self-qa/spec-emitter.js +4 -4
  246. package/dist/src/storage/__tests__/migrations.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/interaction-log.js +5 -5
  250. package/dist/src/storage/migrations.js +122 -122
  251. package/dist/src/storage/session-experience-store.js +4 -4
  252. package/dist/src/storage/sessions.js +43 -43
  253. package/dist/src/storage/transcript-view.js +7 -1
  254. package/dist/src/storage/transcript.d.ts +1 -0
  255. package/dist/src/storage/transcript.js +55 -0
  256. package/dist/src/storage/ui-interaction-log.d.ts +17 -0
  257. package/dist/src/storage/usage.js +14 -14
  258. package/dist/src/storage/workspaces.js +12 -12
  259. package/dist/src/tools/__tests__/native-tools.test.js +6 -0
  260. package/dist/src/tools/bash.d.ts +36 -23
  261. package/dist/src/tools/bash.js +59 -301
  262. package/dist/src/tools/bash.test.js +2 -257
  263. package/dist/src/tools/file-tracker.d.ts +5 -0
  264. package/dist/src/tools/file-tracker.js +14 -0
  265. package/dist/src/tools/file.js +2 -0
  266. package/dist/src/tools/git-safety.d.ts +38 -1
  267. package/dist/src/tools/git-safety.js +94 -4
  268. package/dist/src/tools/git-safety.test.js +45 -1
  269. package/dist/src/tools/native-tools.d.ts +1 -1
  270. package/dist/src/tools/native-tools.js +56 -0
  271. package/dist/src/tools/registry-bash-empty-command.test.js +4 -3
  272. package/dist/src/tools/registry-git-safety.test.js +17 -6
  273. package/dist/src/tools/registry.d.ts +5 -0
  274. package/dist/src/tools/registry.js +185 -11
  275. package/dist/src/types/index.d.ts +7 -1
  276. package/dist/src/ui/app.d.ts +1 -0
  277. package/dist/src/ui/app.js +0 -0
  278. package/dist/src/ui/components/council-question-card.js +1 -0
  279. package/dist/src/ui/components/message-view.js +37 -3
  280. package/dist/src/ui/components/structured-response-view.js +7 -0
  281. package/dist/src/ui/components/task-list-panel.js +3 -12
  282. package/dist/src/ui/containers/chat-feed.d.ts +40 -0
  283. package/dist/src/ui/containers/chat-feed.js +66 -0
  284. package/dist/src/ui/containers/modals-layer.d.ts +87 -0
  285. package/dist/src/ui/containers/modals-layer.js +18 -0
  286. package/dist/src/ui/slash/__tests__/menu-parity.test.d.ts +1 -0
  287. package/dist/src/ui/slash/__tests__/menu-parity.test.js +1 -0
  288. package/dist/src/ui/slash/__tests__/ponytail.test.d.ts +1 -0
  289. package/dist/src/ui/slash/__tests__/ponytail.test.js +37 -0
  290. package/dist/src/ui/slash/council-inspect.js +4 -4
  291. package/dist/src/ui/slash/export.js +16 -1
  292. package/dist/src/ui/slash/menu-items.js +1 -0
  293. package/dist/src/ui/slash/ponytail.d.ts +2 -0
  294. package/dist/src/ui/slash/ponytail.js +19 -0
  295. package/dist/src/ui/status-bar/store.js +0 -1
  296. package/dist/src/ui/status-bar/store.test.js +2 -2
  297. package/dist/src/ui/use-app-logic.d.ts +26 -0
  298. package/dist/src/ui/use-app-logic.js +0 -0
  299. package/dist/src/ui/utils/relaunch.js +1 -1
  300. package/dist/src/ui/utils/text.d.ts +11 -0
  301. package/dist/src/ui/utils/text.js +21 -1
  302. package/dist/src/ui/utils/text.test.js +25 -1
  303. package/dist/src/ui/utils/tools.js +3 -1
  304. package/dist/src/usage/cost-log.js +2 -2
  305. package/dist/src/usage/estimator.d.ts +14 -0
  306. package/dist/src/usage/estimator.js +22 -0
  307. package/dist/src/utils/__tests__/compaction-caps.test.d.ts +21 -0
  308. package/dist/src/utils/__tests__/compaction-caps.test.js +187 -0
  309. package/dist/src/utils/__tests__/llm-deadline-abort.test.d.ts +1 -0
  310. package/dist/src/utils/__tests__/llm-deadline-abort.test.js +33 -0
  311. package/dist/src/utils/clipboard-image.js +23 -23
  312. package/dist/src/utils/llm-deadline.d.ts +9 -1
  313. package/dist/src/utils/llm-deadline.js +31 -2
  314. package/dist/src/utils/permission-mode.d.ts +33 -0
  315. package/dist/src/utils/permission-mode.js +164 -3
  316. package/dist/src/utils/permission-mode.test.js +92 -1
  317. package/dist/src/utils/settings.d.ts +27 -31
  318. package/dist/src/utils/settings.js +100 -109
  319. package/dist/src/utils/side-question.js +2 -2
  320. package/dist/src/utils/skills.js +3 -3
  321. package/dist/src/verify/__tests__/coverage-parsers.test.js +30 -30
  322. package/package.json +1 -1
@@ -23,13 +23,32 @@ export interface RelevantModule {
23
23
  relevance: string;
24
24
  exists: boolean;
25
25
  }
26
- export type ClarityDimension = "outcome" | "scope" | "constraint";
27
- export interface ClarityGap {
28
- dimension: ClarityDimension;
29
- description: string;
30
- suggestedQuestion: string;
31
- options: string[];
32
- defaultIndex: number;
26
+ /**
27
+ * A card designed entirely by the model — the model controls the question,
28
+ * context, options (labels, kinds, which is cancel/adjust), and default.
29
+ * The CLI only assigns questionId and renders via CouncilQuestionData.
30
+ */
31
+ export interface ModelCard {
32
+ /** The question text shown to the user */
33
+ question: string;
34
+ /** Optional context/explanation shown below the question */
35
+ context?: string;
36
+ /** User-selectable options. Model controls labels, kinds, cancel/adjust markers */
37
+ options: ModelCardOption[];
38
+ /** Which option is pre-selected (0 = first). Defaults to 0. */
39
+ defaultIndex?: number;
40
+ }
41
+ export interface ModelCardOption {
42
+ /** Button label or freetext field label */
43
+ label: string;
44
+ /** Optional tooltip / description */
45
+ description?: string;
46
+ /** 'choice' = clickable button, 'freetext' = free-text input field */
47
+ kind: "choice" | "freetext";
48
+ /** If true, picking this option cancels the entire interaction */
49
+ isCancel?: boolean;
50
+ /** If true, picking this indicates the user wants to clarify further (triggers re-interview) */
51
+ isAdjust?: boolean;
33
52
  }
34
53
  export interface ClarifiedIntent {
35
54
  intentStatement?: string;
@@ -59,7 +78,7 @@ export type ModelClarificationProposer = (input: {
59
78
  domain?: string | null;
60
79
  };
61
80
  additionalContext?: string;
62
- }) => Promise<string[]>;
81
+ }) => Promise<ModelCard[]>;
63
82
  export interface FeasibilityResult {
64
83
  viable: boolean;
65
84
  warnings: string[];
@@ -81,6 +100,11 @@ export interface DiscoveryResult {
81
100
  domain: string | null;
82
101
  outputStyle: OutputStyle | null;
83
102
  discoveryMs: number;
103
+ /** Raw Q&A pairs from the interview, visible to the model in enrichment */
104
+ interviewTranscript: Array<{
105
+ question: string;
106
+ answer: string;
107
+ }>;
84
108
  }
85
109
  export interface AcceptanceCardData {
86
110
  intentStatement: string;
@@ -90,5 +114,4 @@ export interface AcceptanceCardData {
90
114
  }
91
115
  export interface DiscoveryInteractionHandler {
92
116
  askQuestion(question: CouncilQuestionData): Promise<CouncilQuestionAnswer>;
93
- showAcceptance(card: AcceptanceCardData): Promise<"accept" | "adjust" | "cancel">;
94
117
  }
@@ -11,8 +11,8 @@ export interface L1Result {
11
11
  export declare function runDiscovery(raw: string, l1: L1Result, cwd: string, handler: DiscoveryInteractionHandler | null, sessionId?: string | null, clarificationProposer?: ModelClarificationProposer | null, recentTurnsSummary?: string | null): Promise<DiscoveryResult>;
12
12
  /**
13
13
  * Create a ModelClarificationProposer backed by the actual task model.
14
- * The model receives the user raw + CLI enrichment (l1, project modules, etc.)
15
- * and outputs the specific questions *it* needs the user to answer.
16
- * This is the effective way to let the model interview based on what is still missing.
14
+ * The model receives the user raw + CLI enrichment and outputs the
15
+ * exact ModelCard[] it wants shown full control over questions, options,
16
+ * option kinds, and cancel/adjust markers.
17
17
  */
18
18
  export declare function createModelClarificationProposer(providerFactory: any, modelId: string): ModelClarificationProposer;
@@ -4,9 +4,8 @@ import { getMaxInterviewQuestions, isDiscoveryEnabled } from "./config.js";
4
4
  import { getCachedProjectContext, setCachedProjectContext } from "./discovery-cache.js";
5
5
  import { isMetaAnalysisPrompt } from "./layer6-output.js";
6
6
  import { scanProjectContext } from "./layer15-context-scan.js";
7
- import { buildInterviewQuestion, getAutofilledOutcome, isProvideOwnDetailsSentinel, PROVIDE_OWN_DETAILS_OPTION_EN, resolveGapsNonInteractive, } from "./layer16-clarity.js";
7
+ import { modelCardToQuestion, resolveGapsNonInteractive } from "./layer16-clarity.js";
8
8
  import { checkFeasibility } from "./layer17-feasibility.js";
9
- import { buildAcceptanceCard, buildAcceptanceQuestion } from "./layer18-acceptance.js";
10
9
  import { markDiscoveryAccepted } from "./session-state.js";
11
10
  function emptyProjectContext(cwd) {
12
11
  return {
@@ -23,7 +22,7 @@ function emptyProjectContext(cwd) {
23
22
  }
24
23
  export async function runDiscovery(raw, l1, cwd, handler, sessionId = null, clarificationProposer = null, recentTurnsSummary = null) {
25
24
  const start = Date.now();
26
- const baseResult = () => ({
25
+ const baseResult = (transcript = []) => ({
27
26
  raw,
28
27
  projectContext: emptyProjectContext(cwd),
29
28
  clarifiedIntent: { outcome: raw, scope: [], constraints: [], gaps: [] },
@@ -39,41 +38,28 @@ export async function runDiscovery(raw, l1, cwd, handler, sessionId = null, clar
39
38
  domain: l1.domain,
40
39
  outputStyle: l1.outputStyle,
41
40
  discoveryMs: Date.now() - start,
41
+ interviewTranscript: transcript,
42
42
  });
43
- if (!isDiscoveryEnabled())
43
+ if (!isDiscoveryEnabled()) {
44
+ // process.stderr.write(`[discovery] isDiscoveryEnabled is FALSE\n`);
44
45
  return baseResult();
45
- if (l1.intentKind === "chitchat" || l1.taskType === null)
46
+ }
47
+ if (l1.intentKind === "chitchat" || l1.taskType === null) {
48
+ // process.stderr.write(`[discovery] intent is chitchat or taskType is null: ${l1.intentKind}, ${l1.taskType}\n`);
46
49
  return baseResult();
47
- // The user explicitly told the agent not to clarify ("don't ask" / "trả lời
48
- // thẳng"). This is the USER's consent decision, not a classification heuristic,
49
- // so it stays: skip the entire interview + acceptance ceremony.
50
- if (detectNoClarifySignal(raw))
50
+ }
51
+ if (detectNoClarifySignal(raw)) {
52
+ // process.stderr.write(`[discovery] detectNoClarifySignal is TRUE\n`);
51
53
  return baseResult();
52
- // ── Model-driven clarification gate (Phase 2) ──────────────────────────────
53
- // The configured chat model — NOT a regex/keyword heuristic — is the sole
54
- // decider of whether this turn has a genuine gray area, what to ask, which
55
- // options to offer, why, and which option is recommended (user directive
56
- // 2026-06-16: "các askcard bắt buộc xuất phát từ model muốn hỏi"). The CLI only
57
- // injects the proposer prompt to open the path.
58
- //
59
- // There is deliberately NO regex fallback. The old path ran shouldAutoPass() +
60
- // detectClarityGaps() keyword heuristics and fabricated questions/outcomes from
61
- // them — the exact "phân loại task qua regex từ khoá ... bad bad bad UX, miss
62
- // hàng tỷ case" behaviour this phase removes (it also fabricated a build outcome
63
- // for a yes/no question — live repro f6f7881a5fae). If the model cannot propose
64
- // questions (no proposer wired, or it throws), we log loudly and proceed WITHOUT
65
- // an interview; we never invent a question from keywords ("không bao giờ hardcode
66
- // fallback... có vấn đề = fail = ghi logs"). The agent can still clarify inline.
54
+ }
67
55
  if (!clarificationProposer) {
68
- // Interactive turns always wire a proposer (orchestrator/message-processor).
69
- // A missing one there is a wiring bug — surface it, never paper over with regex.
70
56
  if (handler) {
71
57
  console.error("[Agent:discovery] interactive turn has no model clarification proposer — skipping interview (no regex fallback by design)");
72
58
  }
59
+ // process.stderr.write(`[discovery] clarificationProposer is NULL\n`);
73
60
  return baseResult();
74
61
  }
75
- // L1.5: Context Discovery (cacheable) — gives the model real project facts so
76
- // it never asks for something it can inspect itself (language/framework/modules).
62
+ // L1.5: Context Discovery (cacheable)
77
63
  let projectContext;
78
64
  const cached = getCachedProjectContext(cwd);
79
65
  if (cached) {
@@ -91,104 +77,105 @@ export async function runDiscovery(raw, l1, cwd, handler, sessionId = null, clar
91
77
  projectContext = emptyProjectContext(cwd);
92
78
  }
93
79
  }
94
- // L1.6: Ask the MODEL what (if anything) it still needs. The model owns the
95
- // gray-area decision, the questions, the options, the "why", and the
96
- // recommended default. An empty result means it sees nothing worth asking →
97
- // no interview, no fabricated [Discovery] outcome.
98
- let gaps;
80
+ // L1.6: Ask the MODEL to design interview cards.
81
+ // The model returns ModelCard[] it controls the question, options,
82
+ // option kinds (choice/freetext), and which options cancel/adjust.
83
+ // An empty array means the model sees no gray area → proceed directly.
84
+ let cards;
99
85
  try {
100
- gaps = await proposeModelGaps(clarificationProposer, raw, l1, projectContext, recentTurnsSummary);
86
+ // process.stderr.write(`[discovery] calling proposeModelCards...\n`);
87
+ cards = await proposeModelCards(clarificationProposer, raw, l1, projectContext, recentTurnsSummary);
88
+ // process.stderr.write(`[discovery] proposeModelCards returned ${cards?.length} cards\n`);
101
89
  }
102
90
  catch (err) {
103
- // No Silent Catch + fail-loud: log with context, then proceed WITHOUT an
104
- // interview. We do NOT fall back to regex-generated questions.
105
- console.error(`[Agent:discovery] model clarification proposer threw — proceeding without interview (no regex fallback): ${err?.message}`, { stack: err?.stack?.split("\n").slice(0, 3) });
106
91
  return baseResult();
107
92
  }
108
- // Model decided there is no gray area worth asking about → proceed directly.
109
- if (gaps.length === 0)
93
+ if (cards.length === 0)
110
94
  return baseResult();
111
- let clarifiedIntent;
112
95
  let interviewed = false;
113
- if (gaps.length > 0 && handler) {
96
+ const interviewTranscript = [];
97
+ // Interactive interview: show model-designed cards and collect raw answers
98
+ if (handler) {
114
99
  interviewed = true;
115
- const answeredGaps = gaps.map((g) => ({
116
- ...g,
117
- answer: null,
118
- }));
119
- const maxQ = Math.min(gaps.length, getMaxInterviewQuestions());
100
+ const maxQ = Math.min(cards.length, getMaxInterviewQuestions());
120
101
  for (let i = 0; i < maxQ; i++) {
121
- const gap = answeredGaps[i];
122
- const question = buildInterviewQuestion(gap, randomUUID());
102
+ const card = cards[i];
103
+ const question = modelCardToQuestion(card, randomUUID());
123
104
  const answer = await handler.askQuestion(question);
124
- answeredGaps[i] = { ...gap, answer: answer.text };
105
+ interviewTranscript.push({ question: card.question, answer: answer.text });
106
+ // If the user picked a cancel option → stop the whole discovery
107
+ const chosenOption = card.options.find((o) => o.label === answer.text);
108
+ if (chosenOption?.isCancel) {
109
+ const result = baseResult(interviewTranscript);
110
+ result.accepted = false;
111
+ return result;
112
+ }
125
113
  }
126
- clarifiedIntent = buildClarifiedIntentFromAnswers(answeredGaps, raw, projectContext);
127
- }
128
- else {
129
- clarifiedIntent = resolveGapsNonInteractive(gaps, projectContext, raw);
130
- }
131
- // Auto-fill outcome for analyze/plan/documentation when no outcome gap was asked.
132
- // Override only when the resolved outcome is a raw-derived generic ("Complete: …" /
133
- // "Complete the task …") or a genuine filesystem-path leak (Local path, project root,
134
- // "src/foo.ts" scope-option shapes). It must NOT clobber a legitimate user answer that
135
- // merely contains a slash ("support REST/GraphQL", "input/output") — see looksLikePathLeak.
136
- const autoOutcome = getAutofilledOutcome(l1.taskType, raw);
137
- const currentOutcome = clarifiedIntent.outcome ?? "";
138
- const isGenericComplete = /^Complete(?::| the task)/i.test(currentOutcome);
139
- if (autoOutcome && (!currentOutcome || isGenericComplete || looksLikePathLeak(currentOutcome))) {
140
- clarifiedIntent = { ...clarifiedIntent, outcome: autoOutcome };
141
- }
142
- // L1.7: Feasibility Check
143
- let feasibility = await checkFeasibility(clarifiedIntent, projectContext).catch(() => ({
144
- viable: true,
145
- warnings: [],
146
- adjustedScope: clarifiedIntent.scope,
147
- }));
148
- // L1.8: User Acceptance
149
- const intentStatement = `${l1.taskType ?? "task"}: ${clarifiedIntent.outcome}`;
150
- let accepted = true;
151
- if (handler && interviewed) {
152
- const card = buildAcceptanceCard(intentStatement, clarifiedIntent, feasibility, raw);
153
- const question = buildAcceptanceQuestion(card, randomUUID());
154
- const answer = await handler.askQuestion(question);
155
- const decision = answer.text.toLowerCase();
156
- if (decision === "cancel") {
157
- accepted = false;
114
+ // After the first round, if any answer had isAdjust flag → re-interview
115
+ let needsReInterview = false;
116
+ for (let i = 0; i < Math.min(cards.length, getMaxInterviewQuestions()); i++) {
117
+ const card = cards[i];
118
+ const chosenOption = card.options.find((o) => o.label === interviewTranscript[i].answer);
119
+ if (chosenOption?.isAdjust) {
120
+ needsReInterview = true;
121
+ break;
122
+ }
158
123
  }
159
- else if (decision === "adjust") {
160
- // The user asked to change something — let the MODEL re-propose questions
161
- // given the same context (it owns the gray-area decision, exactly like the
162
- // initial pass). On a proposer failure we keep the already-resolved intent
163
- // rather than fabricate regex questions.
164
- let reGaps = [];
124
+ if (needsReInterview) {
125
+ let reCards = [];
165
126
  try {
166
- reGaps = await proposeModelGaps(clarificationProposer, raw, l1, projectContext, recentTurnsSummary);
127
+ reCards = await proposeModelCards(clarificationProposer, raw, l1, projectContext, recentTurnsSummary);
167
128
  }
168
129
  catch (err) {
169
- console.error(`[Agent:discovery] re-interview proposer threw — keeping prior intent: ${err?.message}`);
130
+ console.error(`[Agent:discovery] re-interview proposer threw — keeping prior answers: ${err?.message}`);
170
131
  }
171
- if (reGaps.length > 0) {
172
- const reAnswered = reGaps.map((g) => ({
173
- ...g,
174
- answer: null,
175
- }));
176
- const maxRe = Math.min(reGaps.length, getMaxInterviewQuestions());
132
+ if (reCards.length > 0) {
133
+ const maxRe = Math.min(reCards.length, getMaxInterviewQuestions());
177
134
  for (let i = 0; i < maxRe; i++) {
178
- const gap = reAnswered[i];
179
- const q = buildInterviewQuestion(gap, randomUUID());
180
- const ans = await handler.askQuestion(q);
181
- reAnswered[i] = { ...gap, answer: ans.text };
135
+ const card = reCards[i];
136
+ const question = modelCardToQuestion(card, randomUUID());
137
+ const answer = await handler.askQuestion(question);
138
+ interviewTranscript.push({ question: card.question, answer: answer.text });
139
+ const chosenOption = card.options.find((o) => o.label === answer.text);
140
+ if (chosenOption?.isCancel) {
141
+ const result = baseResult(interviewTranscript);
142
+ result.accepted = false;
143
+ return result;
144
+ }
182
145
  }
183
- clarifiedIntent = buildClarifiedIntentFromAnswers(reAnswered, raw, projectContext);
184
- feasibility = await checkFeasibility(clarifiedIntent, projectContext).catch(() => feasibility);
185
146
  }
186
- accepted = true;
187
147
  }
188
148
  }
189
- if (accepted) {
190
- markDiscoveryAccepted(sessionId);
149
+ else {
150
+ // Headless: resolve defaults
151
+ const _resolved = resolveGapsNonInteractive(cards, projectContext, raw);
152
+ interviewTranscript.push(...cards.map((c) => ({
153
+ question: c.question,
154
+ answer: c.options[c.defaultIndex ?? 0]?.label ?? "",
155
+ })));
191
156
  }
157
+ // Build a clarified intent summary from the transcript
158
+ const resolvedOutcome = interviewTranscript
159
+ .filter((qa) => qa.answer.length > 0)
160
+ .map((qa) => qa.answer)
161
+ .join("; ") || raw;
162
+ const intentStatement = `${l1.taskType}: ${resolvedOutcome.slice(0, 120)}`;
163
+ const clarifiedIntent = {
164
+ outcome: resolvedOutcome,
165
+ scope: projectContext.relevantModules.length > 0 ? projectContext.relevantModules.map((m) => m.path) : ["project root"],
166
+ constraints: [],
167
+ interviewed,
168
+ accepted: true,
169
+ };
170
+ // L1.7: Feasibility Check
171
+ const feasibility = await checkFeasibility(clarifiedIntent, projectContext).catch(() => ({
172
+ viable: true,
173
+ warnings: [],
174
+ adjustedScope: clarifiedIntent.scope,
175
+ }));
176
+ // L1.8: Acceptance — use the model's own cards; no separate acceptance ceremony.
177
+ // Feasibility warnings are added to the result but don't trigger a separate card.
178
+ markDiscoveryAccepted(sessionId);
192
179
  return {
193
180
  raw,
194
181
  projectContext,
@@ -201,82 +188,21 @@ export async function runDiscovery(raw, l1, cwd, handler, sessionId = null, clar
201
188
  ? (feasibility.adjustedScope ?? [])
202
189
  : (clarifiedIntent.scope ?? ["project root"]),
203
190
  feasibilityWarnings: feasibility.warnings ?? [],
204
- accepted,
191
+ accepted: true,
205
192
  taskType: l1.taskType,
206
193
  confidence: l1.confidence,
207
194
  domain: l1.domain,
208
195
  outputStyle: l1.outputStyle,
209
196
  discoveryMs: Date.now() - start,
197
+ interviewTranscript,
210
198
  };
211
199
  }
212
200
  /**
213
- * True only for outcomes that are genuine filesystem-path leakage — known
214
- * leaked-scope phrases ("Local path", "project root", …) or a real path segment
215
- * ("src/foo.ts", a "src/cli (cli)" scope-option shape) that also carries a
216
- * path-ish signal (file extension, path keyword, or trailing "(name)").
217
- *
218
- * A bare "/" is NOT sufficient: "support REST/GraphQL", "validate input/output",
219
- * and "details / constraints" use the slash as an "or" separator and must be
220
- * preserved. The previous implementation matched any "/" and silently clobbered
221
- * such legitimate answers with the generic taskType default.
222
- */
223
- function looksLikePathLeak(outcome) {
224
- // Anchored known leaked-scope phrases (preserves prior override behaviour).
225
- if (/(?:\b(?:local path|in prompts|directory as|project root|absolute)\b)|local\/repo/i.test(outcome)) {
226
- return true;
227
- }
228
- // word/word path segment (no spaces around the slash) — e.g. "src/foo.ts".
229
- if (!/\b[\w.-]+\/[\w./-]+/.test(outcome))
230
- return false;
231
- const hasFileExtension = /\/[\w-]+\.[a-z0-9]{1,5}\b/i.test(outcome); // ".../foo.ts"
232
- const hasPathKeyword = /\b(?:path|dir|directory|folder|repo|root|module|src|lib|dist|tests?)\b/i.test(outcome);
233
- const hasScopeOptionSuffix = /\([^)]+\)\s*$/.test(outcome); // "src/cli (cli)" scope-option shape
234
- return hasFileExtension || hasPathKeyword || hasScopeOptionSuffix;
235
- }
236
- function buildClarifiedIntentFromAnswers(answeredGaps, raw, projectContext) {
237
- const outcomeGap = answeredGaps.find((g) => g.dimension === "outcome");
238
- const scopeGap = answeredGaps.find((g) => g.dimension === "scope");
239
- const constraintGap = answeredGaps.find((g) => g.dimension === "constraint");
240
- // The "provide my own details" meta-option is a no-answer sentinel; treat it
241
- // as missing so the raw-derived generic (and downstream inferred outcome) is
242
- // used instead of the sentinel string surviving verbatim as the outcome.
243
- const outcomeAnswer = isProvideOwnDetailsSentinel(outcomeGap?.answer) ? null : (outcomeGap?.answer ?? null);
244
- const outcome = outcomeAnswer ?? `Complete: ${raw.slice(0, 80)}`;
245
- const scope = (() => {
246
- if (scopeGap?.answer)
247
- return [scopeGap.answer.replace(/\s*\(.*\)\s*$/, "").trim()];
248
- return projectContext.relevantModules.map((m) => m.path);
249
- })();
250
- const constraints = constraintGap?.answer ? [constraintGap.answer] : [];
251
- return {
252
- intentStatement: outcome,
253
- outcome,
254
- scope: scope.length > 0 ? scope : ["project root"],
255
- feasibilityWarnings: [],
256
- interviewed: false,
257
- accepted: false,
258
- constraints,
259
- gaps: answeredGaps.map((g) => ({
260
- dimension: g.dimension,
261
- description: "",
262
- suggestedQuestion: "",
263
- options: g.options,
264
- defaultIndex: g.defaultIndex,
265
- answer: g.answer,
266
- })),
267
- };
268
- }
269
- /**
270
- * Build the enrichment context the model sees, call the proposer, and map each
271
- * returned line into a ClarityGap. This is the SINGLE place discovery turns a
272
- * model response into askcard gaps — the model owns the question, the options
273
- * (recommends), the recommended default (first option), and the "why" (threaded
274
- * into the gap description → askcard context). No regex/keyword gap synthesis.
275
- *
276
- * Throws on proposer error so the caller can decide how to degrade (initial pass
277
- * proceeds without interview; the "adjust" pass keeps the prior intent).
201
+ * Call the model proposer and parse its ModelCard[] response.
202
+ * The model is the sole designer of all cards questions, options,
203
+ * option kinds (choice/freetext), cancel/adjust markers, and defaults.
278
204
  */
279
- async function proposeModelGaps(proposer, raw, l1, projectContext, recentTurnsSummary) {
205
+ async function proposeModelCards(proposer, raw, l1, projectContext, recentTurnsSummary) {
280
206
  const additionalContext = [
281
207
  projectContext.language ? `Language: ${projectContext.language}` : "",
282
208
  projectContext.framework ? `Framework: ${projectContext.framework}` : "",
@@ -292,54 +218,22 @@ async function proposeModelGaps(proposer, raw, l1, projectContext, recentTurnsSu
292
218
  ]
293
219
  .filter(Boolean)
294
220
  .join("\n");
295
- const modelQuestions = await proposer({
221
+ const modelCards = await proposer({
296
222
  raw,
297
223
  l1: { taskType: l1.taskType, confidence: l1.confidence },
298
224
  additionalContext: additionalContext || undefined,
299
225
  });
300
- return modelQuestions.slice(0, 3).map(parseModelQuestionToGap);
301
- }
302
- /**
303
- * Parse one proposer line ("question [MODEL RECS: a | b] [WHY: reason]") into a
304
- * ClarityGap. The recommends become the askcard options (first = recommended
305
- * default); the WHY clause becomes the askcard context so the user sees the
306
- * model's own reason for asking.
307
- */
308
- function parseModelQuestionToGap(line, idx) {
309
- let recs = [PROVIDE_OWN_DETAILS_OPTION_EN];
310
- const recMatch = line.match(/\[MODEL RECS:?\s*(.+?)\]/i) || line.match(/RECS:\s*(.+)$/i);
311
- if (recMatch) {
312
- const parsed = recMatch[1]
313
- .split(/\s*\|\s*/)
314
- .map((r) => r.trim())
315
- .filter(Boolean)
316
- .slice(0, 3);
317
- if (parsed.length > 0)
318
- recs = parsed;
319
- }
320
- const whyMatch = line.match(/\[WHY:\s*(.+?)\]/i);
321
- const why = whyMatch ? whyMatch[1].trim() : "";
322
- const question = line
323
- .replace(/\[WHY:.*?\]/i, "")
324
- .replace(/\[MODEL RECS:?.*?\]/i, "")
325
- .replace(/RECS:.*$/, "")
326
- .trim();
327
- return {
328
- dimension: "outcome",
329
- description: why || `Model-generated clarification #${idx + 1}`,
330
- suggestedQuestion: question || "What else needs clarification?",
331
- options: [...recs, "Other (type free answer)"],
332
- defaultIndex: 0,
333
- };
226
+ return modelCards.slice(0, 3);
334
227
  }
335
228
  /**
336
229
  * Create a ModelClarificationProposer backed by the actual task model.
337
- * The model receives the user raw + CLI enrichment (l1, project modules, etc.)
338
- * and outputs the specific questions *it* needs the user to answer.
339
- * This is the effective way to let the model interview based on what is still missing.
230
+ * The model receives the user raw + CLI enrichment and outputs the
231
+ * exact ModelCard[] it wants shown full control over questions, options,
232
+ * option kinds, and cancel/adjust markers.
340
233
  */
341
234
  export function createModelClarificationProposer(providerFactory, modelId) {
342
235
  return async (input) => {
236
+ // process.stderr.write(`[discovery] createModelClarificationProposer CALLED!\n`);
343
237
  try {
344
238
  const { resolveModelRuntime } = await import("../providers/runtime.js");
345
239
  const { generateText } = await import("ai");
@@ -350,31 +244,47 @@ export function createModelClarificationProposer(providerFactory, modelId) {
350
244
  const special = isMetaAnalysisPrompt(input.raw)
351
245
  ? `\nIf the request is a self-evaluation, meta-analysis or review of the CLI by the agent running inside it, do NOT ask about repo path, current directory, absolute path, local repo location or "which directory". Scope is always the full project root. Focus questions and recommends on which CLI internals (PIL, discovery, tools, compaction, EE, model BE, loop guard) to evaluate or specific improvements to assess after fixes. Use the enrichment context.`
352
246
  : "";
353
- // Environment/self header — the main system prompt has buildEnvironmentBlock,
354
- // but THIS discovery question-generator is a separate LLM call that lacked it,
355
- // so it assumed Python and asked the user to paste the directory tree despite
356
- // running inside the repo (live grok session). Escape hatch:
357
- // MUONROI_DISCOVERY_SKIP_ENV_CONTEXT=1.
358
247
  const osLabel = process.platform === "win32" ? "Windows" : process.platform === "darwin" ? "macOS" : "Linux";
359
248
  const envHeader = process.env.MUONROI_DISCOVERY_SKIP_ENV_CONTEXT === "1"
360
249
  ? ""
361
250
  : `Runtime: ${osLabel} (${process.platform}); \`bash\` is POSIX. The project's language/framework is in the context below — do NOT assume Python or a POSIX-only layout. Do NOT ask the user to paste the directory tree, file list, or project structure: you run INSIDE the repository and can inspect it with your own tools. Ask only about genuine intent / scope ambiguities.\n`;
362
- const prompt = `You are the AI agent executing inside muonroi-cli.
363
- ${envHeader}User request: "${input.raw}"
364
- Task type from CLI: ${input.l1.taskType}
365
- ${contextStr}
366
-
367
- You decide whether this turn has a genuine gray area that BLOCKS you from delivering correctly. Output the FEW specific, concise questions you (the model) genuinely still need answered — ask only what is truly blocking, not a quota. Most well-scoped requests need 0-1 questions. If everything you need is already inferable from the request + context above, OR the request is a plain question you can simply answer, return an empty array [].
368
- If the User request is a follow-up or continuation of the recent conversation history (if provided above), do NOT ask for new project details; assume the context is already established and return [] unless there is a critical new ambiguity.
369
- Consider the provided language/framework/modules/EE patterns when suggesting questions and recs — never ask something the context above already answers.${special}
370
- For each question: (1) provide 1-2 short concrete recommendations the user can pick from, ALWAYS listing the ONE you would choose FIRST — it becomes the default the user accepts with one keypress; (2) give a short "reason" clause explaining WHY answering it changes what you do. Be decisive; do not hand back an unranked list.
371
- Return ONLY valid JSON array, nothing else:
372
- [{"question":"...","recommends":["rec1","rec2"],"reason":"why this matters"}, ...]
373
- Max 3 items.`;
251
+ const prompt = `You are the AI agent executing inside muonroi-cli.
252
+ ${envHeader}User request: "${input.raw}"
253
+ Task type from CLI: ${input.l1.taskType}
254
+ ${contextStr}
255
+
256
+ You design question cards shown to the user *before* you start working.
257
+ Each card is a structured question with selectable options.
258
+
259
+ Rules:
260
+ 1. Ask ONLY what is genuinely blocking. Most well-scoped requests need 0 cards.
261
+ 2. If everything you need is inferable from the request + context above, OR the request is a plain question you can simply answer, return [] (empty array).
262
+ 3. If this is a follow-up or continuation of recent conversation history, assume context is already established and return [] unless there is a critical new ambiguity.
263
+ 4. Consider the provided language/framework/modules/EE patterns — never ask what the context already answers.${special}
264
+ 5. For each card, design the options however you want:
265
+ - Use kind="choice" for clickable buttons (recommendations, accept/adjust/cancel, etc.)
266
+ - Use kind="freetext" when the user should type their own answer
267
+ - Mark an option with isCancel:true when picking it should cancel the entire request
268
+ - Mark an option with isAdjust:true when picking it means the user wants to clarify further
269
+ - Set defaultIndex to the option most users would pick (0 = first)
270
+ 6. Return ONLY valid JSON array, nothing else.
271
+ 7. Max 3 cards.
272
+
273
+ JSON format:
274
+ [{
275
+ "question": "string — the question text",
276
+ "context": "string — optional explanation shown below the question",
277
+ "options": [
278
+ {"label": "string", "description": "string", "kind": "choice", "isCancel": false, "isAdjust": false},
279
+ {"label": "string", "kind": "freetext"}
280
+ ],
281
+ "defaultIndex": 0
282
+ }]`;
374
283
  const result = await generateText({
375
284
  model: runtime.model,
376
285
  prompt,
377
- maxOutputTokens: 320,
286
+ maxOutputTokens: 600,
287
+ abortSignal: AbortSignal.timeout(15000),
378
288
  });
379
289
  let items;
380
290
  try {
@@ -386,29 +296,13 @@ Max 3 items.`;
386
296
  items = Array.isArray(parsed) ? parsed : [];
387
297
  }
388
298
  catch (parseErr) {
389
- // A malformed (non-JSON) model response must NOT be coerced into a junk
390
- // askcard — log and return no questions (proceed without interview).
391
- console.error(`[Agent:discovery] clarification proposer returned non-JSON — no questions this turn: ${parseErr?.message}`, { sample: result.text.slice(0, 160) });
392
299
  return [];
393
300
  }
394
301
  return items
395
302
  .filter((it) => it && typeof it.question === "string" && it.question.trim())
396
- .slice(0, 3)
397
- .map((it) => {
398
- const recs = (it.recommends || []).slice(0, 2).join(" | ");
399
- const recTag = recs ? ` [MODEL RECS: ${recs}]` : "";
400
- const why = (it.reason || "").trim();
401
- const whyTag = why ? ` [WHY: ${why}]` : "";
402
- return `${it.question.trim()}${recTag}${whyTag}`;
403
- });
303
+ .slice(0, 3);
404
304
  }
405
305
  catch (err) {
406
- // No Silent Catch + fail-loud: the model call failed. Log with context and
407
- // return no questions — discovery proceeds WITHOUT an interview rather than
408
- // fabricating a regex-derived one ("có vấn đề = fail = ghi logs").
409
- console.error(`[Agent:discovery] clarification proposer failed (${modelId}): ${err?.message}`, {
410
- stack: err?.stack?.split("\n").slice(0, 3),
411
- });
412
306
  return [];
413
307
  }
414
308
  };
@@ -8,6 +8,6 @@ export { isDiscoveryEnabled } from "./config.js";
8
8
  export type { AcceptanceCardData, DiscoveryInteractionHandler, DiscoveryResult, ProjectContext, } from "./discovery-types.js";
9
9
  export { applyPilSuffix, getResponseToolSet } from "./layer6-output.js";
10
10
  export { type PipelineOptions, runPipeline } from "./pipeline.js";
11
- export { getResponseTaskType, isResponseTool, shouldHaltOnResponseTool, stepEmittedResponseTool, } from "./response-tools.js";
11
+ export { getResponseTaskType, isResponseTool, normalizeStructuredResponseTaskType, shouldHaltOnResponseTool, stepEmittedResponseTool, } from "./response-tools.js";
12
12
  export { getPilLastResult, setPilLastResult } from "./store.js";
13
13
  export type { LayerResult, OutputStyle, PipelineContext, PipelineMetrics, TaskType } from "./types.js";
@@ -7,6 +7,6 @@ export { DEFAULT_TOKEN_BUDGET, truncateToBudget } from "./budget.js";
7
7
  export { isDiscoveryEnabled } from "./config.js";
8
8
  export { applyPilSuffix, getResponseToolSet } from "./layer6-output.js";
9
9
  export { runPipeline } from "./pipeline.js";
10
- export { getResponseTaskType, isResponseTool, shouldHaltOnResponseTool, stepEmittedResponseTool, } from "./response-tools.js";
10
+ export { getResponseTaskType, isResponseTool, normalizeStructuredResponseTaskType, shouldHaltOnResponseTool, stepEmittedResponseTool, } from "./response-tools.js";
11
11
  export { getPilLastResult, setPilLastResult } from "./store.js";
12
12
  //# sourceMappingURL=index.js.map
@@ -10,7 +10,7 @@
10
10
  * Fail-open: any error returns ctx unchanged with applied=false.
11
11
  */
12
12
  import type { LlmClassifyFn } from "./llm-classify.js";
13
- import type { PipelineContext } from "./types.js";
13
+ import type { OutputStyle, PipelineContext } from "./types.js";
14
14
  export interface ComplexityInput {
15
15
  rawText: string;
16
16
  taskType: string | null;
@@ -75,6 +75,14 @@ export declare function isStatusCheckQuestion(raw: string): boolean;
75
75
  export interface Layer1Options {
76
76
  /** Pass 4 LLM fallback closure — fires when brain returned null or confidence < 0.7. */
77
77
  llmFallback?: LlmClassifyFn;
78
+ /**
79
+ * WhoAmI v4.0 output-style baseline, derived once in the pipeline from the
80
+ * device-local profile (via ../ee/bridge.js getWhoAmIProfile + outputStyleFromProfile).
81
+ * Applied as the standing default when no per-turn style signal resolves — replaces
82
+ * the generic "balanced" classifier-default. null/undefined ⇒ behaviour unchanged.
83
+ * Passed in (not read here) to keep layer1 off the EE/profile import path.
84
+ */
85
+ profileStyleBaseline?: OutputStyle | null;
78
86
  }
79
87
  /**
80
88
  * True when the prompt is an explicit request to RUN a command / use a shell