muonroi-cli 1.7.1 → 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 (328) 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 +143 -104
  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.d.ts +6 -0
  253. package/dist/src/storage/sessions.js +49 -42
  254. package/dist/src/storage/transcript-view.js +7 -1
  255. package/dist/src/storage/transcript.d.ts +1 -0
  256. package/dist/src/storage/transcript.js +55 -0
  257. package/dist/src/storage/ui-interaction-log.d.ts +17 -0
  258. package/dist/src/storage/usage.js +14 -14
  259. package/dist/src/storage/workspaces.js +12 -12
  260. package/dist/src/tools/__tests__/native-tools.test.js +6 -0
  261. package/dist/src/tools/bash.d.ts +36 -23
  262. package/dist/src/tools/bash.js +59 -301
  263. package/dist/src/tools/bash.test.js +2 -257
  264. package/dist/src/tools/file-tracker.d.ts +5 -0
  265. package/dist/src/tools/file-tracker.js +14 -0
  266. package/dist/src/tools/file.js +2 -0
  267. package/dist/src/tools/git-safety.d.ts +38 -1
  268. package/dist/src/tools/git-safety.js +94 -4
  269. package/dist/src/tools/git-safety.test.js +45 -1
  270. package/dist/src/tools/native-tools.d.ts +1 -1
  271. package/dist/src/tools/native-tools.js +56 -0
  272. package/dist/src/tools/registry-bash-empty-command.test.js +4 -3
  273. package/dist/src/tools/registry-git-safety.test.js +17 -6
  274. package/dist/src/tools/registry.d.ts +5 -0
  275. package/dist/src/tools/registry.js +185 -11
  276. package/dist/src/types/index.d.ts +7 -1
  277. package/dist/src/ui/app.d.ts +2 -1
  278. package/dist/src/ui/app.js +0 -0
  279. package/dist/src/ui/components/council-question-card.js +1 -0
  280. package/dist/src/ui/components/message-view.js +37 -3
  281. package/dist/src/ui/components/structured-response-view.js +7 -0
  282. package/dist/src/ui/components/task-list-panel.js +3 -12
  283. package/dist/src/ui/containers/chat-feed.d.ts +40 -0
  284. package/dist/src/ui/containers/chat-feed.js +66 -0
  285. package/dist/src/ui/containers/modals-layer.d.ts +87 -0
  286. package/dist/src/ui/containers/modals-layer.js +18 -0
  287. package/dist/src/ui/modals/session-picker-modal.js +14 -9
  288. package/dist/src/ui/modals/update-modal.js +2 -1
  289. package/dist/src/ui/slash/__tests__/menu-parity.test.d.ts +1 -0
  290. package/dist/src/ui/slash/__tests__/menu-parity.test.js +1 -0
  291. package/dist/src/ui/slash/__tests__/ponytail.test.d.ts +1 -0
  292. package/dist/src/ui/slash/__tests__/ponytail.test.js +37 -0
  293. package/dist/src/ui/slash/council-inspect.js +4 -4
  294. package/dist/src/ui/slash/export.js +16 -1
  295. package/dist/src/ui/slash/menu-items.js +1 -0
  296. package/dist/src/ui/slash/ponytail.d.ts +2 -0
  297. package/dist/src/ui/slash/ponytail.js +19 -0
  298. package/dist/src/ui/status-bar/store.js +0 -1
  299. package/dist/src/ui/status-bar/store.test.js +2 -2
  300. package/dist/src/ui/types.d.ts +7 -0
  301. package/dist/src/ui/use-app-logic.d.ts +26 -0
  302. package/dist/src/ui/use-app-logic.js +0 -0
  303. package/dist/src/ui/utils/relaunch.d.ts +17 -0
  304. package/dist/src/ui/utils/relaunch.js +28 -4
  305. package/dist/src/ui/utils/relaunch.test.js +50 -0
  306. package/dist/src/ui/utils/text.d.ts +11 -0
  307. package/dist/src/ui/utils/text.js +21 -1
  308. package/dist/src/ui/utils/text.test.js +25 -1
  309. package/dist/src/ui/utils/tools.js +3 -1
  310. package/dist/src/usage/cost-log.js +2 -2
  311. package/dist/src/usage/estimator.d.ts +14 -0
  312. package/dist/src/usage/estimator.js +22 -0
  313. package/dist/src/utils/__tests__/compaction-caps.test.d.ts +21 -0
  314. package/dist/src/utils/__tests__/compaction-caps.test.js +187 -0
  315. package/dist/src/utils/__tests__/llm-deadline-abort.test.d.ts +1 -0
  316. package/dist/src/utils/__tests__/llm-deadline-abort.test.js +33 -0
  317. package/dist/src/utils/clipboard-image.js +23 -23
  318. package/dist/src/utils/llm-deadline.d.ts +9 -1
  319. package/dist/src/utils/llm-deadline.js +31 -2
  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 +27 -31
  324. package/dist/src/utils/settings.js +100 -109
  325. package/dist/src/utils/side-question.js +2 -2
  326. package/dist/src/utils/skills.js +3 -3
  327. package/dist/src/verify/__tests__/coverage-parsers.test.js +30 -30
  328. package/package.json +1 -1
@@ -28,6 +28,11 @@ export function buildClarificationPrompt(topic, conversationContext, previousQA)
28
28
  `the project IS the one described in the "## Current Project" section of the context. DO NOT ask which project.\n` +
29
29
  `- Only ask about project identity when the topic mentions multiple distinct projects or external products.\n` +
30
30
  `- Use the project's package.json name and description as implicit context for follow-up questions.\n\n` +
31
+ `## Language Rule (mandatory)\n` +
32
+ `Write the "question", "why", and every "suggestions"/"recommended" option in the SAME ` +
33
+ `language the user used in the Topic below — detect it; if the user wrote Vietnamese, write ` +
34
+ `Vietnamese; if English, English. The user reads and answers these on a card, so they must ` +
35
+ `NOT default to English. Keep code identifiers, file paths, tech/product names, and JSON keys in English.\n\n` +
31
36
  `Output ONLY a JSON array (no markdown, no preamble):\n` +
32
37
  `[{"question": "...", "why": "why this matters for a focused discussion", "suggestions": ["option A", "option B"], "recommended": "option A", "isRequired": true}]\n\n` +
33
38
  `Rules for "recommended" (be decisive — the user should never face an unranked list):\n` +
@@ -49,7 +54,15 @@ export function buildSpecSynthesisPrompt(topic, conversationContext, qa) {
49
54
  ` "constraints": ["constraint 1", "constraint 2"],\n` +
50
55
  ` "successCriteria": ["criterion 1", "criterion 2"],\n` +
51
56
  ` "scope": "what is in and out of scope"\n` +
52
- `}`,
57
+ `}\n\n` +
58
+ `## Language Rule (mandatory)\n` +
59
+ `Write EVERY field (problemStatement, constraints, successCriteria, scope) in the SAME ` +
60
+ `language the user used — detect it from the Original Topic and Clarification Q&A below. ` +
61
+ `If the user wrote Vietnamese, write Vietnamese; if English, English. Do NOT default to ` +
62
+ `English. This brief is shown to the user on the approval card AND the final synthesis ` +
63
+ `detects its output language from this problemStatement, so writing it in the wrong ` +
64
+ `language drags the whole council's output off the user's language. Keep code identifiers, ` +
65
+ `tech/product names, and JSON keys in English.`,
53
66
  prompt: `## Original Topic\n${topic}\n\n` +
54
67
  (conversationContext ? `## Context\n${conversationContext}\n\n` : "") +
55
68
  `## Clarification Q&A\n${qa.map((qa) => `Q: ${qa.question}\nA: ${qa.answer}`).join("\n\n")}`,
@@ -146,6 +159,25 @@ const EVIDENCE_RULE_FOLLOWUP = `\n## Evidence Rule\n` +
146
159
  `- Most follow-up turns will not need any tool call.\n` +
147
160
  `Tag verified results: \`[CONFIRMED via <tool>:<evidence>]\` or \`[REFUTED via <tool>:<evidence>]\`.\n` +
148
161
  `Uncited numbers you cannot verify: \`[UNVERIFIED: <claim>]\`.\n`;
162
+ /**
163
+ * Length discipline injected into every debate turn (F13 token-thrift). The
164
+ * debate-turn ceilings are generous (4096-6144 maxTokens) and reasoning models
165
+ * left unbounded emit ~400-600 words/turn — ~17 turns/debate is token-heavy and
166
+ * directly fights the "tiết kiệm token" goal. A concrete word cap + "density
167
+ * over length" cuts filler / preamble / brief-restating while keeping the
168
+ * analytical core (position, reasoning bullets, the cross-question). This does
169
+ * NOT lower maxTokens: reasoning models need that headroom to think without
170
+ * empty-turn truncation (see llm.ts) — the saving comes from the model writing
171
+ * less, not from a hard output ceiling. The text deliberately contains none of
172
+ * the prompt substrings the council mocks route on (e.g. "responding to",
173
+ * "continuing a discussion", "team lead", "Summarize this discussion").
174
+ */
175
+ function concisenessRule(maxWords) {
176
+ return (`\n## Length (be token-thrifty)\n` +
177
+ `Keep this turn under ~${maxWords} words. Open with your position in one line, ` +
178
+ `then 2-4 bullets of your strongest reasoning. No preamble, no restating the ` +
179
+ `brief, no filler — density over length. A tight argument beats a long one.\n`);
180
+ }
149
181
  /** Resolve the persona label used inside debate prompts. Stance wins; role is fallback. */
150
182
  function personaOf(role, stance) {
151
183
  if (stance) {
@@ -171,6 +203,7 @@ export function buildOpeningPrompt(ctx) {
171
203
  focusLine +
172
204
  ENGLISH_ONLY_RULE +
173
205
  EVIDENCE_RULE_OPENING +
206
+ concisenessRule(220) +
174
207
  (stackLock ? `\n${stackLock}\n` : "") +
175
208
  guardrails +
176
209
  (ctx.conversationContext ? `\n## Conversation Context\n${ctx.conversationContext}\n\n---\n\n` : "\n") +
@@ -192,6 +225,7 @@ export function buildResponsePrompt(ctx) {
192
225
  system: `You are the "${me.label}" (lens: ${me.lens}) responding to the "${them.label}" (lens: ${them.lens}).\n` +
193
226
  ENGLISH_ONLY_RULE +
194
227
  EVIDENCE_RULE_RESPONSE +
228
+ concisenessRule(160) +
195
229
  (stackLock ? `\n${stackLock}\n` : "") +
196
230
  `\n## Discussion Brief\n` +
197
231
  `Problem: ${ctx.spec.problemStatement}\n` +
@@ -216,6 +250,7 @@ export function buildFollowupPrompt(ctx) {
216
250
  system: `You are the "${me.label}" (lens: ${me.lens}) continuing a discussion (round ${ctx.round}) with the "${them.label}" (lens: ${them.lens}).\n` +
217
251
  ENGLISH_ONLY_RULE +
218
252
  EVIDENCE_RULE_FOLLOWUP +
253
+ concisenessRule(140) +
219
254
  (stackLock ? `\n${stackLock}\n` : "") +
220
255
  `\n` +
221
256
  (ctx.runningSummary
@@ -513,33 +548,33 @@ export function buildSynthesisPrompt(ctx) {
513
548
  }
514
549
  let extraContext = "";
515
550
  if (ctx.refineContext) {
516
- extraContext += `
517
- ## User Refinements
518
- ${ctx.refineContext}
551
+ extraContext += `
552
+ ## User Refinements
553
+ ${ctx.refineContext}
519
554
  `;
520
555
  }
521
556
  if (ctx.planEmphasis) {
522
- extraContext += `
523
- ## Additional Instruction
524
- The user has requested a concrete action plan with executable steps. Each action item MUST be an object with these fields:
525
- {
526
- "step": "<imperative action>",
527
- "owner_lens": "<which stance owns this — frontend / backend / architecture / etc>",
528
- "time_estimate": "<rough — e.g. '2h', '1d', '~30min'>",
529
- "depends_on": ["<step keys this requires>"] or [],
530
- "acceptance_criteria": "<how we know it's done>"
531
- }
532
- Order action items by dependency: predecessors first, dependents after.
533
- Risks MUST be objects with: {"description", "severity": "High|Medium|Low", "mitigation"}.
534
- Do NOT emit loose strings for these fields — the user needs structured plan output.
557
+ extraContext += `
558
+ ## Additional Instruction
559
+ The user has requested a concrete action plan with executable steps. Each action item MUST be an object with these fields:
560
+ {
561
+ "step": "<imperative action>",
562
+ "owner_lens": "<which stance owns this — frontend / backend / architecture / etc>",
563
+ "time_estimate": "<rough — e.g. '2h', '1d', '~30min'>",
564
+ "depends_on": ["<step keys this requires>"] or [],
565
+ "acceptance_criteria": "<how we know it's done>"
566
+ }
567
+ Order action items by dependency: predecessors first, dependents after.
568
+ Risks MUST be objects with: {"description", "severity": "High|Medium|Low", "mitigation"}.
569
+ Do NOT emit loose strings for these fields — the user needs structured plan output.
535
570
  `;
536
571
  }
537
572
  return {
538
573
  system,
539
- prompt: `Final positions:
540
- ${ctx.finalPositions}
541
-
542
- Full discussion:
574
+ prompt: `Final positions:
575
+ ${ctx.finalPositions}
576
+
577
+ Full discussion:
543
578
  ${ctx.allExchanges}${extraContext}`,
544
579
  };
545
580
  }
@@ -211,6 +211,13 @@ export interface CouncilConfig {
211
211
  internetFirst?: boolean;
212
212
  /** When true, leader sub-tasks downshift to cheaper tier models on the same provider. */
213
213
  costAware?: boolean;
214
+ /**
215
+ * Enclosing council run id (= sessionId). Used only as the correlationId on the
216
+ * observe-only `council-turn-length` harness event so per-turn length samples can
217
+ * be grouped by run. Optional: direct callers/tests may omit it (falls back to a
218
+ * stable literal); has no effect on debate behaviour.
219
+ */
220
+ runId?: string;
214
221
  }
215
222
  /**
216
223
  * JSON shape persisted as `[Council Memory] {...}` in the session messages.
@@ -270,7 +277,13 @@ export interface CouncilCallUsage {
270
277
  }
271
278
  export type UsageCallback = (usage: CouncilCallUsage) => void;
272
279
  export interface CouncilLLM {
273
- generate(modelId: string, system: string, prompt: string, maxTokens?: number, onUsage?: UsageCallback): Promise<string>;
280
+ generate(modelId: string, system: string, prompt: string, maxTokens?: number, onUsage?: UsageCallback,
281
+ /**
282
+ * User-abort signal. Threaded so a mid-council cancel (Esc/Ctrl-C) aborts
283
+ * the in-flight generate call. Trailing+optional for back-compat — existing
284
+ * positional callers/mocks are unaffected. See withCouncilSignal in index.ts.
285
+ */
286
+ signal?: AbortSignal): Promise<string>;
274
287
  research(modelId: string, topic: string, conversationContext: string, signal?: AbortSignal, persistTrace?: ToolTraceEmitter, options?: {
275
288
  internetFirst?: boolean;
276
289
  }, onUsage?: UsageCallback): Promise<string>;
@@ -49,7 +49,7 @@ function bootstrapDb(sessions) {
49
49
  db.close();
50
50
  }
51
51
  beforeEach(() => {
52
- tmpHome = join(tmpdir(), "muonroi-test-export-" + Date.now());
52
+ tmpHome = join(tmpdir(), `muonroi-test-export-${Date.now()}`);
53
53
  const muonroiDir = join(tmpHome, ".muonroi-cli");
54
54
  const experienceDir = join(tmpHome, ".experience");
55
55
  mkdirSync(muonroiDir, { recursive: true });
@@ -1,4 +1,6 @@
1
1
  import { type PilContextResponse } from "../pil/schema.js";
2
+ export type { WhoAmIDim, WhoAmIDimName, WhoAmIProfile } from "./who-am-i.js";
3
+ export { getWhoAmIProfile, outputStyleFromProfile, resetWhoAmICache } from "./who-am-i.js";
2
4
  interface EEPoint {
3
5
  id: string | number;
4
6
  score?: number;
@@ -37,7 +39,7 @@ export type { EEPoint, EERouteResult, EERouteTaskResult };
37
39
  * 2. Fat clients (local experience-core.js present) → in-process call.
38
40
  * 3. Otherwise → null (caller falls back to regex/keyword paths).
39
41
  */
40
- export declare function classifyViaBrain(prompt: string, timeoutMs?: number): Promise<string | null>;
42
+ export declare function classifyViaBrain(prompt: string, timeoutMs?: number, options?: import("./types.js").BrainProxyOptions): Promise<string | null>;
41
43
  /**
42
44
  * Search a named Qdrant collection by embedding vector.
43
45
  * Falls back to FileStore inside experience-core.js if Qdrant is unavailable.
@@ -11,6 +11,12 @@ import * as os from "node:os";
11
11
  import * as path from "node:path";
12
12
  import { PilContextResponseSchema } from "../pil/schema.js";
13
13
  import { classifyEeError, logEeFailure, readTimeoutEnv, withEeTimeout } from "../utils/ee-logger.js";
14
+ // "Who Am I" v4.0 profile access. Re-exported through the bridge so PIL layers
15
+ // reach it via the single sanctioned EE entry point (see the no-network-in-pil
16
+ // arch guard: layer1/types may import `../ee/bridge` only). The provider is
17
+ // local-only (no network), cached, and fail-open — the same contract as the rest
18
+ // of this bridge.
19
+ export { getWhoAmIProfile, outputStyleFromProfile, resetWhoAmICache } from "./who-am-i.js";
14
20
  /**
15
21
  * Phase 21.5 — `routeModel` / `routeTask` budget. EE server unreachability used
16
22
  * to hang `/ideal` for the OS-level fetch connect timeout (~5 min on Windows).
@@ -82,7 +88,7 @@ async function silentCall(fn) {
82
88
  * 2. Fat clients (local experience-core.js present) → in-process call.
83
89
  * 3. Otherwise → null (caller falls back to regex/keyword paths).
84
90
  */
85
- export async function classifyViaBrain(prompt, timeoutMs = 5000) {
91
+ export async function classifyViaBrain(prompt, timeoutMs = 5000, options) {
86
92
  // Thin-client HTTP path — required for CLIs without local experience-core.js.
87
93
  try {
88
94
  const { getCachedEEClientMode } = await import("./client-mode.js");
@@ -92,7 +98,7 @@ export async function classifyViaBrain(prompt, timeoutMs = 5000) {
92
98
  : !!(await import("./auth.js")).getCachedServerBaseUrl();
93
99
  if (useRemote) {
94
100
  const { getDefaultEEClient } = await import("./intercept.js");
95
- const result = await getDefaultEEClient().brainProxy(prompt, timeoutMs);
101
+ const result = await getDefaultEEClient().brainProxy(prompt, timeoutMs, options);
96
102
  if (typeof result === "string")
97
103
  return result;
98
104
  // Server unreachable — fall through to in-process path so a fat-client
@@ -110,7 +116,7 @@ export async function classifyViaBrain(prompt, timeoutMs = 5000) {
110
116
  try {
111
117
  // Race the in-process call against `timeoutMs + 200ms` so a buggy core that
112
118
  // ignores its own timeout argument cannot hang the host CLI.
113
- return await withEeTimeout(silentCall(() => core.classifyViaBrain(prompt, timeoutMs)), timeoutMs + 200);
119
+ return await withEeTimeout(silentCall(() => core.classifyViaBrain(prompt, timeoutMs, options)), timeoutMs + 200);
114
120
  }
115
121
  catch (err) {
116
122
  logEeFailure("bridge.classifyViaBrain", classifyEeError(err), err, {
@@ -52,7 +52,7 @@ describe("bridge — loaded core", () => {
52
52
  it("Test 1: classifyViaBrain returns classification string when core loaded", async () => {
53
53
  const result = await classifyViaBrain("write a function to sort an array");
54
54
  expect(result).toBe("generate");
55
- expect(mockCore.classifyViaBrain).toHaveBeenCalledWith("write a function to sort an array", 5000);
55
+ expect(mockCore.classifyViaBrain).toHaveBeenCalledWith("write a function to sort an array", 5000, undefined);
56
56
  });
57
57
  it("Test 2: searchCollection returns EEPoint[] when core loaded", async () => {
58
58
  const vector = [0.1, 0.2, 0.3];
@@ -210,7 +210,6 @@ describe("bridge — config isolation (BRIDGE-03)", () => {
210
210
  const _validCall = () => classifyViaBrain("prompt");
211
211
  const _validCallWithTimeout = () => classifyViaBrain("prompt", 3000);
212
212
  // These would cause @ts-expect-error if uncommented:
213
- // @ts-expect-error — qdrantUrl is not a valid parameter
214
213
  const _invalidCall = () => classifyViaBrain("prompt", 3000, "http://localhost:6333");
215
214
  expect(_validCall).toBeDefined();
216
215
  expect(_validCallWithTimeout).toBeDefined();
@@ -568,7 +568,7 @@ export function createEEClient(opts = {}) {
568
568
  return null;
569
569
  }
570
570
  },
571
- async brainProxy(prompt, timeoutMs = 2000) {
571
+ async brainProxy(prompt, timeoutMs = 2000, options) {
572
572
  try {
573
573
  // Tight client-side ceiling (~150ms grace over caller's intent budget)
574
574
  // so we never wait longer than the PIL pipeline can use the result.
@@ -576,10 +576,23 @@ export function createEEClient(opts = {}) {
576
576
  // we forward in the body — both ends abort together.
577
577
  const ctrl = new AbortController();
578
578
  const t = setTimeout(() => ctrl.abort(), timeoutMs + 150);
579
+ const body = { prompt, timeoutMs };
580
+ if (options) {
581
+ if (options.systemPrompt)
582
+ body.systemPrompt = options.systemPrompt;
583
+ if (options.responseFormat)
584
+ body.responseFormat = options.responseFormat;
585
+ if (options.model)
586
+ body.model = options.model;
587
+ if (options.maxTokens != null)
588
+ body.maxTokens = options.maxTokens;
589
+ if (options.provider)
590
+ body.provider = options.provider;
591
+ }
579
592
  const resp = await f(`${baseUrl}/api/brain`, {
580
593
  method: "POST",
581
594
  headers: headers(),
582
- body: JSON.stringify({ prompt, timeoutMs }),
595
+ body: JSON.stringify(body),
583
596
  signal: ctrl.signal,
584
597
  });
585
598
  clearTimeout(t);
@@ -22,6 +22,8 @@ export interface RecallLedger {
22
22
  record(entries: EERecallEntry[] | undefined, query: string): void;
23
23
  /** Clear one id once it has been rated. Returns true if it was actually pending. */
24
24
  clear(id: string): boolean;
25
+ /** Check if an id was ever cleared (fed back) in this session. Used by PIL Layer 3 to suppress re-injection. */
26
+ wasCleared(id: string): boolean;
25
27
  /** Oldest-first list of still-unrated recalls. */
26
28
  pending(): PendingRecall[];
27
29
  pendingCount(): number;
@@ -30,3 +32,21 @@ export interface RecallLedger {
30
32
  export declare function createRecallLedger(): RecallLedger;
31
33
  /** Process-scoped singleton = the current MCP session's unrated-recall debt. */
32
34
  export declare const sessionRecallLedger: RecallLedger;
35
+ /**
36
+ * Whether the recall-feedback ledger is active. Mirrors the gate env the external
37
+ * MCP ee.query already honours (EXPERIENCE_RECALL_FEEDBACK_GATE) so an operator
38
+ * can disable in-CLI ledger accounting with the same switch. In-CLI we only need
39
+ * on/off — never a hard refusal — so a turn is never blocked. Default on (soft).
40
+ */
41
+ export declare function isRecallLedgerEnabled(): boolean;
42
+ /**
43
+ * Compact, token-bounded reminder of still-unrated surfaced/recalled handles, for
44
+ * injection next to the `[id]` handles the agent already saw. Names the actual
45
+ * `{id, collection}` pairs so an `ee_feedback(id, collection, verdict)` call is
46
+ * actionable — the legacy static nudge named no ids, so the model could not
47
+ * complete the rating even when willing. Capped so a long session can't bloat the
48
+ * prompt (token-thrift).
49
+ */
50
+ export declare function formatPendingReminder(pending: PendingRecall[], opts?: {
51
+ max?: number;
52
+ }): string;
@@ -12,6 +12,8 @@
12
12
  */
13
13
  export function createRecallLedger() {
14
14
  const map = new Map();
15
+ // Tracks ids that have been fed back, so PIL Layer 3 can suppress re-injection.
16
+ const cleared = new Set();
15
17
  return {
16
18
  record(entries, query) {
17
19
  if (!Array.isArray(entries))
@@ -34,7 +36,14 @@ export function createRecallLedger() {
34
36
  }
35
37
  },
36
38
  clear(id) {
37
- return map.delete(String(id ?? "").trim());
39
+ const nid = String(id ?? "").trim();
40
+ const deleted = map.delete(nid);
41
+ if (deleted)
42
+ cleared.add(nid);
43
+ return deleted;
44
+ },
45
+ wasCleared(id) {
46
+ return cleared.has(String(id ?? "").trim());
38
47
  },
39
48
  pending() {
40
49
  return [...map.values()].sort((a, b) => a.ts - b.ts);
@@ -44,9 +53,39 @@ export function createRecallLedger() {
44
53
  },
45
54
  reset() {
46
55
  map.clear();
56
+ cleared.clear();
47
57
  },
48
58
  };
49
59
  }
50
60
  /** Process-scoped singleton = the current MCP session's unrated-recall debt. */
51
61
  export const sessionRecallLedger = createRecallLedger();
62
+ /**
63
+ * Whether the recall-feedback ledger is active. Mirrors the gate env the external
64
+ * MCP ee.query already honours (EXPERIENCE_RECALL_FEEDBACK_GATE) so an operator
65
+ * can disable in-CLI ledger accounting with the same switch. In-CLI we only need
66
+ * on/off — never a hard refusal — so a turn is never blocked. Default on (soft).
67
+ */
68
+ export function isRecallLedgerEnabled() {
69
+ return (String(process.env.EXPERIENCE_RECALL_FEEDBACK_GATE ?? "soft")
70
+ .trim()
71
+ .toLowerCase() !== "off");
72
+ }
73
+ /**
74
+ * Compact, token-bounded reminder of still-unrated surfaced/recalled handles, for
75
+ * injection next to the `[id]` handles the agent already saw. Names the actual
76
+ * `{id, collection}` pairs so an `ee_feedback(id, collection, verdict)` call is
77
+ * actionable — the legacy static nudge named no ids, so the model could not
78
+ * complete the rating even when willing. Capped so a long session can't bloat the
79
+ * prompt (token-thrift).
80
+ */
81
+ export function formatPendingReminder(pending, opts = {}) {
82
+ if (pending.length === 0)
83
+ return "";
84
+ const max = Math.max(1, Math.min(opts.max ?? 5, 20));
85
+ const shown = pending.slice(0, max);
86
+ const lines = shown.map((p) => ` - [${p.id} ${p.collection ?? "?"}]`);
87
+ const more = pending.length > max ? `\n …and ${pending.length - max} more` : "";
88
+ return (`↳ ${pending.length} earlier EE hint(s) still unrated — rate the one(s) you acted on so the brain keeps what helped: ` +
89
+ `ee_feedback(id, collection, followed|ignored|noise).\n${lines.join("\n")}${more}`);
90
+ }
52
91
  //# sourceMappingURL=recall-ledger.js.map
@@ -92,6 +92,31 @@ export declare function feedbackEE(pointId: string, collection: string, verdict:
92
92
  * compute unrated-recall debt as recalled ids minus fed-back ids. Best-effort.
93
93
  */
94
94
  export declare function mirrorFeedbackLocally(pointId: string, collection: string, verdict: string, reason?: string | null, logPath?: string): Promise<void>;
95
+ export interface WriteExperienceResult {
96
+ ok: boolean;
97
+ id?: string;
98
+ error?: string;
99
+ }
100
+ /**
101
+ * Write a NEW experience/lesson into the brain mid-session so it is RECALLABLE.
102
+ * POSTs to /api/import-memory → storeImportedExperience → upsertEntry, which
103
+ * embeds the text AND writes the `text_bm25` sparse vector (+ a proper payload via
104
+ * buildStorePayload). That dense+sparse write is what makes the lesson reliably
105
+ * recallable via ee_query / passive injection (both the dense and lexical legs can
106
+ * match it). NB: /api/ingest-point writes DENSE-ONLY (no sparse) — its points are
107
+ * lexically invisible and only surface on a strong dense match, so they are NOT
108
+ * reliably recallable; do not use it for recall-critical writes.
109
+ *
110
+ * This is the missing "agent proactively records a mistake→fix lesson" arm of the
111
+ * recall loop: ee_query recalls, ee_feedback rates, ee_write creates. Mirrors
112
+ * feedbackEE's direct-fetch transport (cached baseUrl + bearer token); fails soft.
113
+ */
114
+ export declare function writeExperienceEE(lesson: string, opts?: {
115
+ collection?: string;
116
+ title?: string;
117
+ projectSlug?: string;
118
+ confidence?: number;
119
+ }): Promise<WriteExperienceResult>;
95
120
  /** Reachability probe for the EE server. */
96
121
  export declare function healthEE(): Promise<{
97
122
  ok: boolean;
@@ -211,6 +211,79 @@ export async function mirrorFeedbackLocally(pointId, collection, verdict, reason
211
211
  logEeFailure("search.mirrorFeedbackLocally", classifyEeError(err), err);
212
212
  }
213
213
  }
214
+ /**
215
+ * Write a NEW experience/lesson into the brain mid-session so it is RECALLABLE.
216
+ * POSTs to /api/import-memory → storeImportedExperience → upsertEntry, which
217
+ * embeds the text AND writes the `text_bm25` sparse vector (+ a proper payload via
218
+ * buildStorePayload). That dense+sparse write is what makes the lesson reliably
219
+ * recallable via ee_query / passive injection (both the dense and lexical legs can
220
+ * match it). NB: /api/ingest-point writes DENSE-ONLY (no sparse) — its points are
221
+ * lexically invisible and only surface on a strong dense match, so they are NOT
222
+ * reliably recallable; do not use it for recall-critical writes.
223
+ *
224
+ * This is the missing "agent proactively records a mistake→fix lesson" arm of the
225
+ * recall loop: ee_query recalls, ee_feedback rates, ee_write creates. Mirrors
226
+ * feedbackEE's direct-fetch transport (cached baseUrl + bearer token); fails soft.
227
+ */
228
+ export async function writeExperienceEE(lesson, opts = {}) {
229
+ const { loadEEAuthToken, getCachedServerBaseUrl } = await import("./auth.js");
230
+ const { randomUUID } = await import("node:crypto");
231
+ const authToken = (await loadEEAuthToken()) ?? undefined;
232
+ const baseUrl = (getCachedServerBaseUrl() ?? "http://localhost:8082").replace(/\/+$/, "");
233
+ const collection = opts.collection ?? "experience-behavioral";
234
+ const id = randomUUID();
235
+ // storeImportedExperience embeds `${qa.trigger} ${qa.question} ${qa.solution}`,
236
+ // so put the lesson in solution (and the title in question) to anchor the vector.
237
+ const qa = {
238
+ trigger: "",
239
+ question: opts.title ?? "",
240
+ solution: lesson,
241
+ scope: opts.projectSlug ? { project_slug: opts.projectSlug } : {},
242
+ };
243
+ const body = {
244
+ experiences: [
245
+ {
246
+ id,
247
+ collection,
248
+ qa,
249
+ tier: 2,
250
+ confidence: opts.confidence ?? 0.65,
251
+ runtime: "muonroi-cli-agent",
252
+ },
253
+ ],
254
+ };
255
+ const headers = { "Content-Type": "application/json" };
256
+ if (authToken)
257
+ headers.Authorization = `Bearer ${authToken}`;
258
+ try {
259
+ const res = await fetch(`${baseUrl}/api/import-memory`, {
260
+ method: "POST",
261
+ headers,
262
+ body: JSON.stringify(body),
263
+ });
264
+ const respText = await res.text();
265
+ let parsed = null;
266
+ try {
267
+ parsed = JSON.parse(respText);
268
+ }
269
+ catch {
270
+ /* non-JSON error body — fall through to status handling */
271
+ }
272
+ if (!res.ok) {
273
+ return { ok: false, error: parsed?.error || respText || `HTTP ${res.status}` };
274
+ }
275
+ const r0 = parsed?.results?.[0];
276
+ if (!parsed?.stored || r0?.ok !== true) {
277
+ return { ok: false, error: r0?.reason ?? "not_stored" };
278
+ }
279
+ return { ok: true, id };
280
+ }
281
+ catch (err) {
282
+ const { logEeFailure, classifyEeError } = await import("../utils/ee-logger.js");
283
+ logEeFailure("search.writeExperienceEE", classifyEeError(err), err);
284
+ return { ok: false, error: err instanceof Error ? err.message : String(err) };
285
+ }
286
+ }
214
287
  /** Reachability probe for the EE server. */
215
288
  export async function healthEE() {
216
289
  const { createEEClient } = await import("./client.js");
@@ -361,6 +361,20 @@ export interface EERecallOptions {
361
361
  export interface EEUserResponse {
362
362
  user: string;
363
363
  }
364
+ export interface BrainProxyOptions {
365
+ /** Override the default system prompt sent to the LLM. */
366
+ systemPrompt?: string;
367
+ /** Request JSON-mode response_format (e.g. { type: "json_object" }). */
368
+ responseFormat?: {
369
+ type: string;
370
+ };
371
+ /** Override the LLM model id used for classification. */
372
+ model?: string;
373
+ /** Override max_tokens for the LLM response. */
374
+ maxTokens?: number;
375
+ /** Override the brain provider (siliconflow | ollama | openai | ...). */
376
+ provider?: string;
377
+ }
364
378
  export interface EEClient {
365
379
  health(): Promise<{
366
380
  ok: boolean;
@@ -392,7 +406,7 @@ export interface EEClient {
392
406
  /** Active recall via /api/recall (recallMode). Returns [id col] index + records a surface. */
393
407
  recall(query: string, opts?: EERecallOptions): Promise<EERecallResponse | null>;
394
408
  user(): Promise<EEUserResponse | null>;
395
- brainProxy(prompt: string, timeoutMs?: number): Promise<string | null>;
409
+ brainProxy(prompt: string, timeoutMs?: number, options?: BrainProxyOptions): Promise<string | null>;
396
410
  pilContext(prompt: string, options?: {
397
411
  localeHint?: string;
398
412
  projectCtx?: Record<string, unknown>;
@@ -0,0 +1,41 @@
1
+ import type { OutputStyle } from "../pil/types.js";
2
+ export type PrivacyLevel = "minimal" | "standard" | "full";
3
+ export type WhoAmIDimName = "communication.question_style" | "communication.feedback_style" | "communication.brevity" | "personality.conflict_style" | "personality.risk_tolerance" | "personality.decision_speed" | "work_patterns.energy" | "work_patterns.multitasking" | "work_patterns.session_length" | "work_patterns.delegation_style";
4
+ export interface WhoAmIDim {
5
+ value: string;
6
+ confidence: number;
7
+ samples: number;
8
+ }
9
+ export interface WhoAmIProfile {
10
+ level: PrivacyLevel;
11
+ dims: Partial<Record<WhoAmIDimName, WhoAmIDim>>;
12
+ }
13
+ interface RawDim {
14
+ value: string | null;
15
+ confidence?: number;
16
+ sampleCount?: number;
17
+ samples?: number;
18
+ }
19
+ interface RawProfile {
20
+ dimensions?: Record<string, RawDim>;
21
+ }
22
+ /**
23
+ * PURE: project a raw EE profile to the privacy-gated dims for `level`, applying the
24
+ * positive name allowlist + value!=null commit gate + per-tier confidence floor.
25
+ */
26
+ export declare function selectWhoAmIDims(raw: RawProfile | null | undefined, level: PrivacyLevel): WhoAmIProfile["dims"];
27
+ /**
28
+ * PURE: derive an output-style baseline from the profile. brevity is the primary
29
+ * signal; decision_speed is a weaker fallback. Returns null when no usable signal
30
+ * exists so the caller keeps its own per-turn default.
31
+ */
32
+ export declare function outputStyleFromProfile(profile: WhoAmIProfile | null): OutputStyle | null;
33
+ /**
34
+ * Load + cache the privacy-gated profile for this process. The profile changes
35
+ * slowly (rebuilt by the EE Stop hook), so one read per process is enough.
36
+ * Returns null when EE is not installed, privacy is off, or nothing is committed.
37
+ */
38
+ export declare function getWhoAmIProfile(): WhoAmIProfile | null;
39
+ /** Clear the cache — test-only / after a known profile change. */
40
+ export declare function resetWhoAmICache(): void;
41
+ export {};