muonroi-cli 1.7.2 → 1.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (326) hide show
  1. package/dist/packages/agent-harness-core/src/event-filter.js +1 -0
  2. package/dist/packages/agent-harness-core/src/event-redact.js +8 -0
  3. package/dist/packages/agent-harness-core/src/mcp-server.d.ts +16 -0
  4. package/dist/packages/agent-harness-core/src/mcp-server.js +88 -10
  5. package/dist/packages/agent-harness-core/src/protocol.d.ts +15 -0
  6. package/dist/packages/agent-harness-core/src/selector.js +2 -2
  7. package/dist/packages/agent-harness-core/src/transports/sidechannel.js +1 -1
  8. package/dist/packages/agent-harness-opentui/src/agent-mode.js +1 -1
  9. package/dist/packages/agent-harness-opentui/src/install.js +1 -1
  10. package/dist/src/__test-stubs__/vitest-setup.js +4 -0
  11. package/dist/src/agent-harness/__tests__/mock-model.spec.js +54 -0
  12. package/dist/src/agent-harness/mock-model.d.ts +1 -1
  13. package/dist/src/agent-harness/mock-model.js +143 -6
  14. package/dist/src/agent-harness/test-spawn.js +3 -1
  15. package/dist/src/chat/chat-keychain.js +9 -1
  16. package/dist/src/cli/cost-forensics.js +7 -4
  17. package/dist/src/cli/keys.d.ts +2 -2
  18. package/dist/src/cli/keys.js +107 -28
  19. package/dist/src/cli/usage-report.js +2 -2
  20. package/dist/src/council/__tests__/abort-threading.test.d.ts +1 -0
  21. package/dist/src/council/__tests__/abort-threading.test.js +193 -0
  22. package/dist/src/council/__tests__/clarification-prompt.test.js +21 -1
  23. package/dist/src/council/__tests__/council-turn-length-emit.test.d.ts +1 -0
  24. package/dist/src/council/__tests__/council-turn-length-emit.test.js +130 -0
  25. package/dist/src/council/__tests__/debate-round-budget.test.d.ts +1 -0
  26. package/dist/src/council/__tests__/debate-round-budget.test.js +45 -0
  27. package/dist/src/council/__tests__/evaluator-metrics.test.js +3 -0
  28. package/dist/src/council/__tests__/post-debate-recommendation.test.d.ts +1 -0
  29. package/dist/src/council/__tests__/post-debate-recommendation.test.js +58 -0
  30. package/dist/src/council/__tests__/research-tools.test.js +4 -0
  31. package/dist/src/council/__tests__/round-tools.test.js +6 -0
  32. package/dist/src/council/clarifier-question-cap.test.d.ts +12 -0
  33. package/dist/src/council/clarifier-question-cap.test.js +73 -0
  34. package/dist/src/council/clarifier.d.ts +29 -1
  35. package/dist/src/council/clarifier.js +64 -1
  36. package/dist/src/council/debate-planner.d.ts +2 -1
  37. package/dist/src/council/debate-planner.js +5 -3
  38. package/dist/src/council/debate.d.ts +9 -0
  39. package/dist/src/council/debate.js +88 -8
  40. package/dist/src/council/index.d.ts +46 -1
  41. package/dist/src/council/index.js +125 -19
  42. package/dist/src/council/llm.js +41 -13
  43. package/dist/src/council/prompts.js +36 -1
  44. package/dist/src/council/types.d.ts +14 -1
  45. package/dist/src/ee/__tests__/export-transcripts.test.js +1 -1
  46. package/dist/src/ee/bridge.d.ts +3 -1
  47. package/dist/src/ee/bridge.js +9 -3
  48. package/dist/src/ee/bridge.test.js +1 -2
  49. package/dist/src/ee/client.js +15 -2
  50. package/dist/src/ee/recall-ledger.d.ts +20 -0
  51. package/dist/src/ee/recall-ledger.js +40 -1
  52. package/dist/src/ee/search.d.ts +25 -0
  53. package/dist/src/ee/search.js +73 -0
  54. package/dist/src/ee/types.d.ts +15 -1
  55. package/dist/src/ee/who-am-i.d.ts +41 -0
  56. package/dist/src/ee/who-am-i.js +143 -0
  57. package/dist/src/ee/who-am-i.test.d.ts +1 -0
  58. package/dist/src/ee/who-am-i.test.js +101 -0
  59. package/dist/src/flow/compaction/index.d.ts +1 -0
  60. package/dist/src/flow/compaction/index.js +4 -0
  61. package/dist/src/generated/version.d.ts +1 -1
  62. package/dist/src/generated/version.js +1 -1
  63. package/dist/src/headless/council-answers.js +1 -0
  64. package/dist/src/headless/output.d.ts +30 -1
  65. package/dist/src/headless/output.js +180 -0
  66. package/dist/src/headless/output.test.js +61 -1
  67. package/dist/src/hooks/index.js +48 -2
  68. package/dist/src/index.d.ts +2 -0
  69. package/dist/src/index.js +59 -62
  70. package/dist/src/lsp/client-normalize.test.d.ts +12 -0
  71. package/dist/src/lsp/client-normalize.test.js +34 -0
  72. package/dist/src/lsp/client.d.ts +1 -0
  73. package/dist/src/lsp/client.js +10 -2
  74. package/dist/src/lsp/manager.d.ts +1 -1
  75. package/dist/src/lsp/manager.js +13 -5
  76. package/dist/src/lsp/manager.test.js +41 -2
  77. package/dist/src/lsp/runtime.d.ts +1 -1
  78. package/dist/src/lsp/runtime.js +2 -2
  79. package/dist/src/maintain/__tests__/gh-create-pr.test.js +7 -3
  80. package/dist/src/maintain/__tests__/pr-builder.test.js +7 -3
  81. package/dist/src/mcp/__tests__/client-pool.spec.js +3 -3
  82. package/dist/src/mcp/__tests__/ee-tools.test.js +42 -14
  83. package/dist/src/mcp/__tests__/forensics-tools.test.js +3 -3
  84. package/dist/src/mcp/__tests__/lsp-tools.test.js +4 -4
  85. package/dist/src/mcp/auto-setup.js +0 -8
  86. package/dist/src/mcp/client-pool.d.ts +1 -1
  87. package/dist/src/mcp/client-pool.js +25 -9
  88. package/dist/src/mcp/ee-tools.d.ts +10 -0
  89. package/dist/src/mcp/ee-tools.js +34 -0
  90. package/dist/src/mcp/self-verify-jobs.d.ts +1 -0
  91. package/dist/src/mcp/self-verify-jobs.js +1 -3
  92. package/dist/src/mcp/self-verify-runner.js +1 -1
  93. package/dist/src/models/catalog-gemini.test.js +12 -10
  94. package/dist/src/models/catalog.json +106 -38
  95. package/dist/src/orchestrator/__tests__/auto-commit.test.d.ts +1 -0
  96. package/dist/src/orchestrator/__tests__/auto-commit.test.js +142 -0
  97. package/dist/src/orchestrator/__tests__/batch-turn-runner.test.js +3 -0
  98. package/dist/src/orchestrator/__tests__/delegations.test.d.ts +1 -0
  99. package/dist/src/orchestrator/__tests__/delegations.test.js +96 -0
  100. package/dist/src/orchestrator/__tests__/message-processor.test.js +2 -0
  101. package/dist/src/orchestrator/__tests__/prompts.test.d.ts +1 -0
  102. package/dist/src/orchestrator/__tests__/prompts.test.js +88 -0
  103. package/dist/src/orchestrator/__tests__/stream-runner.test.js +29 -0
  104. package/dist/src/orchestrator/__tests__/sub-session-delegation.test.d.ts +1 -0
  105. package/dist/src/orchestrator/__tests__/sub-session-delegation.test.js +507 -0
  106. package/dist/src/orchestrator/agent.test.js +5 -2
  107. package/dist/src/orchestrator/auto-commit.d.ts +110 -0
  108. package/dist/src/orchestrator/auto-commit.js +366 -0
  109. package/dist/src/orchestrator/batch-turn-runner.d.ts +3 -2
  110. package/dist/src/orchestrator/batch-turn-runner.js +2 -1
  111. package/dist/src/orchestrator/compaction-proposer-prompt.d.ts +8 -0
  112. package/dist/src/orchestrator/compaction-proposer-prompt.js +43 -0
  113. package/dist/src/orchestrator/compaction.d.ts +22 -0
  114. package/dist/src/orchestrator/compaction.js +42 -0
  115. package/dist/src/orchestrator/delegations.d.ts +1 -0
  116. package/dist/src/orchestrator/delegations.js +41 -0
  117. package/dist/src/orchestrator/error-utils.js +27 -2
  118. package/dist/src/orchestrator/message-processor.d.ts +23 -5
  119. package/dist/src/orchestrator/message-processor.js +226 -2573
  120. package/dist/src/orchestrator/orchestrator.d.ts +11 -2
  121. package/dist/src/orchestrator/orchestrator.js +530 -43
  122. package/dist/src/orchestrator/pending-calls.js +2 -1
  123. package/dist/src/orchestrator/preprocessor.d.ts +13 -0
  124. package/dist/src/orchestrator/preprocessor.js +146 -0
  125. package/dist/src/orchestrator/prompts.d.ts +16 -0
  126. package/dist/src/orchestrator/prompts.js +130 -67
  127. package/dist/src/orchestrator/safety-askcard.d.ts +66 -0
  128. package/dist/src/orchestrator/safety-askcard.js +74 -0
  129. package/dist/src/orchestrator/sandbox.test.js +8 -5
  130. package/dist/src/orchestrator/scope-ceiling.js +1 -1
  131. package/dist/src/orchestrator/stall-watchdog.d.ts +47 -0
  132. package/dist/src/orchestrator/stall-watchdog.js +23 -0
  133. package/dist/src/orchestrator/stall-watchdog.test.js +36 -1
  134. package/dist/src/orchestrator/stream-runner.js +52 -30
  135. package/dist/src/orchestrator/sub-agent-cap.d.ts +2 -0
  136. package/dist/src/orchestrator/sub-agent-cap.js +12 -4
  137. package/dist/src/orchestrator/sub-agent-model-tier.js +1 -1
  138. package/dist/src/orchestrator/subagent-compactor.d.ts +21 -0
  139. package/dist/src/orchestrator/subagent-compactor.js +148 -24
  140. package/dist/src/orchestrator/subagent-compactor.spec.js +249 -1
  141. package/dist/src/orchestrator/text-tool-call-detector.d.ts +9 -5
  142. package/dist/src/orchestrator/text-tool-call-detector.js +16 -12
  143. package/dist/src/orchestrator/text-tool-call-detector.test.js +66 -8
  144. package/dist/src/orchestrator/tool-args-repair.js +1 -1
  145. package/dist/src/orchestrator/tool-engine.d.ts +153 -0
  146. package/dist/src/orchestrator/tool-engine.js +3138 -0
  147. package/dist/src/orchestrator/turn-runner-deps.d.ts +3 -0
  148. package/dist/src/pil/__tests__/discovery-types.test.js +15 -4
  149. package/dist/src/pil/__tests__/discovery.test.js +89 -145
  150. package/dist/src/pil/__tests__/dual-run.test.js +2 -0
  151. package/dist/src/pil/__tests__/layer1-intent-trace.test.js +3 -0
  152. package/dist/src/pil/__tests__/layer1-intent.test.js +3 -0
  153. package/dist/src/pil/__tests__/layer16-clarity.test.js +35 -32
  154. package/dist/src/pil/__tests__/layer18-acceptance.test.js +1 -78
  155. package/dist/src/pil/__tests__/layer2_5-ponytail.test.d.ts +1 -0
  156. package/dist/src/pil/__tests__/layer2_5-ponytail.test.js +53 -0
  157. package/dist/src/pil/__tests__/layer3-ee-injection.test.js +67 -4
  158. package/dist/src/pil/__tests__/layer3-injected-chunk.test.js +14 -2
  159. package/dist/src/pil/__tests__/layer4-gsd.test.js +30 -0
  160. package/dist/src/pil/__tests__/layer5-context.test.js +39 -1
  161. package/dist/src/pil/__tests__/layer6-output.test.js +30 -2
  162. package/dist/src/pil/__tests__/llm-classify.test.js +79 -1
  163. package/dist/src/pil/__tests__/orchestrator-integration.test.js +3 -1
  164. package/dist/src/pil/__tests__/pipeline.test.js +11 -8
  165. package/dist/src/pil/__tests__/response-tools.test.js +52 -0
  166. package/dist/src/pil/agent-operating-contract.d.ts +1 -1
  167. package/dist/src/pil/agent-operating-contract.js +5 -3
  168. package/dist/src/pil/agent-operating-contract.test.js +2 -2
  169. package/dist/src/pil/cheap-model-playbook.d.ts +1 -1
  170. package/dist/src/pil/cheap-model-playbook.js +1 -1
  171. package/dist/src/pil/cheap-model-workbooks.d.ts +1 -1
  172. package/dist/src/pil/cheap-model-workbooks.js +1 -1
  173. package/dist/src/pil/config.d.ts +20 -0
  174. package/dist/src/pil/config.js +35 -0
  175. package/dist/src/pil/discovery-types.d.ts +32 -9
  176. package/dist/src/pil/discovery.d.ts +3 -3
  177. package/dist/src/pil/discovery.js +138 -244
  178. package/dist/src/pil/index.d.ts +1 -1
  179. package/dist/src/pil/index.js +1 -1
  180. package/dist/src/pil/layer1-intent.d.ts +9 -1
  181. package/dist/src/pil/layer1-intent.js +62 -7
  182. package/dist/src/pil/layer1-intent.test.js +112 -2
  183. package/dist/src/pil/layer15-context-scan.js +1 -1
  184. package/dist/src/pil/layer16-clarity.d.ts +21 -25
  185. package/dist/src/pil/layer16-clarity.js +52 -88
  186. package/dist/src/pil/layer18-acceptance.d.ts +13 -3
  187. package/dist/src/pil/layer18-acceptance.js +1 -75
  188. package/dist/src/pil/layer2-personality.js +7 -8
  189. package/dist/src/pil/layer2_5-ponytail.d.ts +2 -0
  190. package/dist/src/pil/layer2_5-ponytail.js +41 -0
  191. package/dist/src/pil/layer3-ee-injection.js +180 -14
  192. package/dist/src/pil/layer4-gsd.js +22 -8
  193. package/dist/src/pil/layer5-context.d.ts +1 -0
  194. package/dist/src/pil/layer5-context.js +26 -11
  195. package/dist/src/pil/layer6-output.js +23 -5
  196. package/dist/src/pil/llm-classify.d.ts +11 -0
  197. package/dist/src/pil/llm-classify.js +196 -3
  198. package/dist/src/pil/native-capabilities-workbook.d.ts +1 -1
  199. package/dist/src/pil/native-capabilities-workbook.js +4 -3
  200. package/dist/src/pil/pipeline.js +15 -1
  201. package/dist/src/pil/response-tools.d.ts +16 -0
  202. package/dist/src/pil/response-tools.js +41 -0
  203. package/dist/src/pil/schema.d.ts +8 -0
  204. package/dist/src/pil/schema.js +24 -3
  205. package/dist/src/pil/task-tier-map.js +7 -7
  206. package/dist/src/pil/types.d.ts +9 -3
  207. package/dist/src/product-loop/__tests__/discovery-interview.test.js +103 -1
  208. package/dist/src/product-loop/__tests__/gather-selectable-alts.test.js +34 -0
  209. package/dist/src/product-loop/__tests__/sprint-self-verify.test.js +12 -12
  210. package/dist/src/product-loop/discovery-interview.d.ts +20 -0
  211. package/dist/src/product-loop/discovery-interview.js +121 -7
  212. package/dist/src/product-loop/gather.js +19 -5
  213. package/dist/src/product-loop/loop-driver.js +2 -2
  214. package/dist/src/product-loop/sprint-self-verify.js +2 -2
  215. package/dist/src/providers/__tests__/reasoning-roundtrip.test.js +12 -0
  216. package/dist/src/providers/auth/__tests__/gemini-oauth.test.d.ts +1 -5
  217. package/dist/src/providers/auth/__tests__/gemini-oauth.test.js +1 -5
  218. package/dist/src/providers/auth/gcloud.d.ts +28 -0
  219. package/dist/src/providers/auth/gcloud.js +102 -0
  220. package/dist/src/providers/auth/gemini-oauth.d.ts +46 -19
  221. package/dist/src/providers/auth/gemini-oauth.js +292 -105
  222. package/dist/src/providers/auth/grok-oauth.d.ts +1 -1
  223. package/dist/src/providers/auth/grok-oauth.js +1 -1
  224. package/dist/src/providers/auth/registry.js +28 -6
  225. package/dist/src/providers/gemini.d.ts +1 -1
  226. package/dist/src/providers/gemini.js +10 -3
  227. package/dist/src/providers/keychain.d.ts +2 -2
  228. package/dist/src/providers/keychain.js +20 -2
  229. package/dist/src/providers/openai.js +5 -1
  230. package/dist/src/providers/runtime.d.ts +1 -1
  231. package/dist/src/providers/runtime.js +11 -2
  232. package/dist/src/providers/siliconflow-sse-repair.js +1 -1
  233. package/dist/src/providers/strategies/google.strategy.d.ts +9 -2
  234. package/dist/src/providers/strategies/google.strategy.js +159 -9
  235. package/dist/src/providers/strategies/thinking-mode.js +19 -6
  236. package/dist/src/providers/types.d.ts +1 -1
  237. package/dist/src/router/decide.test.js +40 -8
  238. package/dist/src/router/step-router.d.ts +20 -0
  239. package/dist/src/router/step-router.js +73 -0
  240. package/dist/src/self-qa/agentic-loop.js +30 -21
  241. package/dist/src/self-qa/orchestrator.js +5 -5
  242. package/dist/src/self-qa/scenario-planner.js +1 -1
  243. package/dist/src/self-qa/spec-emitter.js +4 -4
  244. package/dist/src/storage/__tests__/hierarchical-session.test.d.ts +1 -0
  245. package/dist/src/storage/__tests__/hierarchical-session.test.js +161 -0
  246. package/dist/src/storage/__tests__/sweep-stale-pending.test.js +2 -2
  247. package/dist/src/storage/index.d.ts +1 -1
  248. package/dist/src/storage/index.js +1 -1
  249. package/dist/src/storage/migrations.js +13 -1
  250. package/dist/src/storage/transcript-response-entry.test.js +18 -5
  251. package/dist/src/storage/transcript-view.js +7 -1
  252. package/dist/src/storage/transcript.d.ts +3 -0
  253. package/dist/src/storage/transcript.js +254 -73
  254. package/dist/src/storage/ui-interaction-log.d.ts +17 -0
  255. package/dist/src/storage/usage.js +22 -5
  256. package/dist/src/tools/__tests__/native-tools.test.js +6 -0
  257. package/dist/src/tools/bash.d.ts +36 -23
  258. package/dist/src/tools/bash.js +59 -301
  259. package/dist/src/tools/bash.test.js +2 -257
  260. package/dist/src/tools/file-tracker.d.ts +5 -0
  261. package/dist/src/tools/file-tracker.js +14 -0
  262. package/dist/src/tools/file.js +2 -0
  263. package/dist/src/tools/git-safety.d.ts +38 -1
  264. package/dist/src/tools/git-safety.js +94 -4
  265. package/dist/src/tools/git-safety.test.js +45 -1
  266. package/dist/src/tools/native-tools.d.ts +1 -1
  267. package/dist/src/tools/native-tools.js +66 -1
  268. package/dist/src/tools/registry-bash-empty-command.test.js +4 -3
  269. package/dist/src/tools/registry-git-safety.test.js +17 -6
  270. package/dist/src/tools/registry.d.ts +6 -0
  271. package/dist/src/tools/registry.js +215 -13
  272. package/dist/src/types/index.d.ts +8 -2
  273. package/dist/src/ui/app.d.ts +1 -0
  274. package/dist/src/ui/app.js +0 -0
  275. package/dist/src/ui/components/council-question-card.js +1 -0
  276. package/dist/src/ui/components/message-view.js +37 -3
  277. package/dist/src/ui/components/structured-response-view.js +7 -0
  278. package/dist/src/ui/components/task-list-panel.js +3 -12
  279. package/dist/src/ui/containers/chat-feed.d.ts +40 -0
  280. package/dist/src/ui/containers/chat-feed.js +66 -0
  281. package/dist/src/ui/containers/modals-layer.d.ts +87 -0
  282. package/dist/src/ui/containers/modals-layer.js +18 -0
  283. package/dist/src/ui/slash/__tests__/menu-parity.test.d.ts +1 -0
  284. package/dist/src/ui/slash/__tests__/menu-parity.test.js +1 -0
  285. package/dist/src/ui/slash/__tests__/ponytail.test.d.ts +1 -0
  286. package/dist/src/ui/slash/__tests__/ponytail.test.js +37 -0
  287. package/dist/src/ui/slash/expand.js +14 -1
  288. package/dist/src/ui/slash/export.js +16 -1
  289. package/dist/src/ui/slash/menu-items.js +1 -0
  290. package/dist/src/ui/slash/ponytail.d.ts +2 -0
  291. package/dist/src/ui/slash/ponytail.js +19 -0
  292. package/dist/src/ui/status-bar/store.js +0 -1
  293. package/dist/src/ui/status-bar/store.test.js +2 -2
  294. package/dist/src/ui/use-app-logic.d.ts +26 -0
  295. package/dist/src/ui/use-app-logic.js +0 -0
  296. package/dist/src/ui/utils/relaunch.js +1 -1
  297. package/dist/src/ui/utils/text.d.ts +11 -0
  298. package/dist/src/ui/utils/text.js +21 -1
  299. package/dist/src/ui/utils/text.test.js +25 -1
  300. package/dist/src/ui/utils/tools.js +3 -1
  301. package/dist/src/usage/cost-log.js +2 -2
  302. package/dist/src/usage/estimator.d.ts +14 -0
  303. package/dist/src/usage/estimator.js +22 -0
  304. package/dist/src/utils/__tests__/compaction-caps.test.d.ts +21 -0
  305. package/dist/src/utils/__tests__/compaction-caps.test.js +187 -0
  306. package/dist/src/utils/__tests__/llm-deadline-abort.test.d.ts +1 -0
  307. package/dist/src/utils/__tests__/llm-deadline-abort.test.js +33 -0
  308. package/dist/src/utils/__tests__/llm-deadline.test.js +16 -4
  309. package/dist/src/utils/__tests__/logger.test.d.ts +1 -0
  310. package/dist/src/utils/__tests__/logger.test.js +115 -0
  311. package/dist/src/utils/__tests__/settings-agent-first.test.d.ts +1 -0
  312. package/dist/src/utils/__tests__/settings-agent-first.test.js +48 -0
  313. package/dist/src/utils/install-manager.d.ts +1 -0
  314. package/dist/src/utils/install-manager.js +76 -7
  315. package/dist/src/utils/install-manager.test.js +43 -1
  316. package/dist/src/utils/llm-deadline.d.ts +9 -1
  317. package/dist/src/utils/llm-deadline.js +31 -2
  318. package/dist/src/utils/logger.d.ts +28 -0
  319. package/dist/src/utils/logger.js +171 -0
  320. package/dist/src/utils/permission-mode.d.ts +33 -0
  321. package/dist/src/utils/permission-mode.js +164 -3
  322. package/dist/src/utils/permission-mode.test.js +92 -1
  323. package/dist/src/utils/settings.d.ts +36 -32
  324. package/dist/src/utils/settings.js +151 -123
  325. package/dist/src/utils/update-checker.test.js +5 -1
  326. package/package.json +1 -1
@@ -0,0 +1,34 @@
1
+ /**
2
+ * src/lsp/client-normalize.test.ts
3
+ *
4
+ * Regression guard for the Windows diagnostics-cache key mismatch: the LSP
5
+ * client stores publishDiagnostics under normalizeUriPath(uri) (a LOWERCASE
6
+ * drive, decoded from `file:///c%3A/…`) but retrieves under
7
+ * normalizeFsPath(path.resolve(...)) (the cwd's UPPERCASE drive). Without
8
+ * canonicalizing the drive letter the keys never matched, so diagnostics were
9
+ * permanently [] on Windows — silently breaking write-time LSP feedback AND the
10
+ * commit gate. normalizeFsPath must produce the SAME key for both casings.
11
+ */
12
+ import { describe, expect, it } from "vitest";
13
+ import { normalizeFsPath } from "./client.js";
14
+ describe("normalizeFsPath", () => {
15
+ it("converts backslashes to forward slashes", () => {
16
+ expect(normalizeFsPath("C:\\Users\\x\\bug.ts")).toBe("c:/Users/x/bug.ts");
17
+ });
18
+ it("lowercases the Windows drive letter so store/retrieve keys converge", () => {
19
+ // The whole point: an UPPERCASE-drive query path and a LOWERCASE-drive
20
+ // publishDiagnostics URI path must normalize to the identical cache key.
21
+ expect(normalizeFsPath("C:/Users/x/bug.ts")).toBe(normalizeFsPath("c:/Users/x/bug.ts"));
22
+ expect(normalizeFsPath("C:/Users/x/bug.ts")).toBe("c:/Users/x/bug.ts");
23
+ expect(normalizeFsPath("D:\\proj\\a.ts")).toBe("d:/proj/a.ts");
24
+ });
25
+ it("leaves POSIX absolute paths untouched (no drive letter)", () => {
26
+ expect(normalizeFsPath("/home/u/bug.ts")).toBe("/home/u/bug.ts");
27
+ });
28
+ it("does not mangle a non-drive leading segment", () => {
29
+ // Only a single leading letter + ":/" is a drive; a path segment that
30
+ // merely starts with a letter must be preserved.
31
+ expect(normalizeFsPath("src/lsp/client.ts")).toBe("src/lsp/client.ts");
32
+ });
33
+ });
34
+ //# sourceMappingURL=client-normalize.test.js.map
@@ -18,3 +18,4 @@ export interface LspClientSession {
18
18
  stop(): Promise<void>;
19
19
  }
20
20
  export declare function createLspClientSession(options: LspClientOptions): Promise<LspClientSession>;
21
+ export declare function normalizeFsPath(filePath: string): string;
@@ -267,8 +267,16 @@ function normalizeUriPath(uri) {
267
267
  }
268
268
  return normalizeFsPath(uri);
269
269
  }
270
- function normalizeFsPath(filePath) {
271
- return filePath.replace(/\\/g, "/");
270
+ export function normalizeFsPath(filePath) {
271
+ const slashed = filePath.replace(/\\/g, "/");
272
+ // Windows drive letters are case-insensitive, but the two sides of the
273
+ // diagnostics cache disagree on case: publishDiagnostics URIs decode to a
274
+ // LOWERCASE drive (tsserver emits `file:///c%3A/…`), while query paths come
275
+ // from `path.resolve(cwd, …)` which yields the cwd's (usually UPPERCASE)
276
+ // drive. Without canonicalizing the drive, diagnostics stored under `c:/…`
277
+ // are never found under `C:/…` → the cache always returns [] on Windows
278
+ // (silently breaking both write-time LSP feedback and the commit gate).
279
+ return slashed.replace(/^([a-zA-Z]):\//, (_m, d) => `${d.toLowerCase()}:/`);
272
280
  }
273
281
  function ensureActive(stopped, serverId) {
274
282
  if (stopped) {
@@ -11,7 +11,7 @@ interface WorkspaceLspManagerOptions {
11
11
  }
12
12
  export interface WorkspaceLspManager {
13
13
  touchFile(filePath: string, waitForDiagnostics?: boolean): Promise<LspDiagnosticFile[]>;
14
- syncFile(filePath: string, content: string, save?: boolean, waitForDiagnostics?: boolean): Promise<LspDiagnosticFile[]>;
14
+ syncFile(filePath: string, content: string, save?: boolean, waitForDiagnostics?: boolean, diagnosticsTimeoutMs?: number): Promise<LspDiagnosticFile[]>;
15
15
  query(input: LspQueryInput): Promise<LspToolResponse>;
16
16
  close(): Promise<void>;
17
17
  }
@@ -64,10 +64,18 @@ export function createWorkspaceLspManager(cwd, settings, options = {}) {
64
64
  return resolved.filter((value) => value !== null);
65
65
  }
66
66
  async function touchFile(filePath, waitForDiagnostics = true) {
67
- const content = await readFile(filePath, "utf8");
68
- return syncFile(filePath, content, false, waitForDiagnostics);
67
+ try {
68
+ const content = await readFile(filePath, "utf8");
69
+ return await syncFile(filePath, content, false, waitForDiagnostics);
70
+ }
71
+ catch (err) {
72
+ if (err instanceof Error && err.code === "ENOENT") {
73
+ return [];
74
+ }
75
+ throw err;
76
+ }
69
77
  }
70
- async function syncFile(filePath, content, save = true, waitForDiagnostics = true) {
78
+ async function syncFile(filePath, content, save = true, waitForDiagnostics = true, diagnosticsTimeoutMs) {
71
79
  const records = await getClientsForFile(filePath);
72
80
  if (records.length === 0)
73
81
  return [];
@@ -80,7 +88,7 @@ export function createWorkspaceLspManager(cwd, settings, options = {}) {
80
88
  await client.saveFile(filePath);
81
89
  }
82
90
  if (waitForDiagnostics) {
83
- await client.waitForDiagnostics(filePath);
91
+ await client.waitForDiagnostics(filePath, diagnosticsTimeoutMs);
84
92
  }
85
93
  return {
86
94
  filePath,
@@ -104,7 +112,7 @@ export function createWorkspaceLspManager(cwd, settings, options = {}) {
104
112
  output: `No LSP server available for ${path.extname(normalizedPath) || "this file type"}.`,
105
113
  };
106
114
  }
107
- const lspDiagnostics = await touchFile(normalizedPath, true);
115
+ const lspDiagnostics = input.operation === "workspaceSymbol" ? [] : await touchFile(normalizedPath, true);
108
116
  const params = createOperationParams(input, normalizedPath);
109
117
  const timeoutMs = settings.requestTimeoutMs;
110
118
  const results = (await Promise.all(records.map(async ({ key, client }) => {
@@ -60,7 +60,7 @@ describe("createWorkspaceLspManager", () => {
60
60
  expect(result.success).toBe(true);
61
61
  expect(result.output).toContain("file:///demo.ts");
62
62
  expect(client.openOrChangeFile).toHaveBeenCalledWith(filePath, "typescript", "const demo = 1;\n");
63
- expect(client.waitForDiagnostics).toHaveBeenCalledWith(filePath);
63
+ expect(client.waitForDiagnostics).toHaveBeenCalledWith(filePath, undefined);
64
64
  await manager.close();
65
65
  expect(client.stop).toHaveBeenCalled();
66
66
  });
@@ -92,7 +92,7 @@ describe("createWorkspaceLspManager", () => {
92
92
  const result = await manager.syncFile(filePath, "const broken = true;\n", true, true);
93
93
  expect(result).toEqual(diagnostics);
94
94
  expect(client.saveFile).toHaveBeenCalledWith(filePath);
95
- expect(client.waitForDiagnostics).toHaveBeenCalledWith(filePath);
95
+ expect(client.waitForDiagnostics).toHaveBeenCalledWith(filePath, undefined);
96
96
  await manager.close();
97
97
  });
98
98
  it("times out a hanging request without dropping the client", async () => {
@@ -131,6 +131,45 @@ describe("createWorkspaceLspManager", () => {
131
131
  expect(result.output).toContain("No LSP server available");
132
132
  await manager.close();
133
133
  });
134
+ it("handles missing files gracefully and avoids touchFile on workspaceSymbol", async () => {
135
+ const root = await createTempWorkspace();
136
+ const filePath = path.join(root, "nonexistent.ts");
137
+ const sendRequest = vi.fn(async (method, params) => {
138
+ expect(method).toBe("workspace/symbol");
139
+ expect(params).toEqual({ query: "my-query" });
140
+ return [
141
+ {
142
+ name: "MySymbol",
143
+ kind: 1,
144
+ location: {
145
+ uri: "file:///demo.ts",
146
+ range: { start: { line: 0, character: 0 }, end: { line: 0, character: 8 } },
147
+ },
148
+ },
149
+ ];
150
+ });
151
+ const client = createFakeClient({ sendRequest });
152
+ const manager = createWorkspaceLspManager(root, BASE_SETTINGS, {
153
+ createClient: async () => client,
154
+ });
155
+ const result = await manager.query({
156
+ operation: "workspaceSymbol",
157
+ filePath,
158
+ query: "my-query",
159
+ });
160
+ expect(result.success).toBe(true);
161
+ expect(result.output).toContain("MySymbol");
162
+ expect(client.openOrChangeFile).not.toHaveBeenCalled();
163
+ const resultDef = await manager.query({
164
+ operation: "goToDefinition",
165
+ filePath,
166
+ line: 1,
167
+ character: 1,
168
+ });
169
+ expect(resultDef.success).toBe(true);
170
+ expect(resultDef.output).toContain("No results found");
171
+ await manager.close();
172
+ });
134
173
  });
135
174
  async function createTempWorkspace() {
136
175
  const root = await mkdtemp(path.join(os.tmpdir(), "muonroi-lsp-manager-"));
@@ -1,6 +1,6 @@
1
1
  import type { LspDiagnosticFile, LspQueryInput, LspToolResponse } from "./types.js";
2
2
  export declare function queryLsp(cwd: string, input: LspQueryInput): Promise<LspToolResponse>;
3
- export declare function syncFileWithLsp(cwd: string, filePath: string, content: string, save?: boolean, waitForDiagnostics?: boolean): Promise<LspDiagnosticFile[]>;
3
+ export declare function syncFileWithLsp(cwd: string, filePath: string, content: string, save?: boolean, waitForDiagnostics?: boolean, diagnosticsTimeoutMs?: number): Promise<LspDiagnosticFile[]>;
4
4
  export declare function isLspToolEnabled(_cwd: string): boolean;
5
5
  export declare function summarizeDiagnostics(diagnostics: LspDiagnosticFile[]): string | null;
6
6
  export declare function shutdownWorkspaceLspManager(cwd: string): Promise<void>;
@@ -10,9 +10,9 @@ export async function queryLsp(cwd, input) {
10
10
  filePath: path.isAbsolute(input.filePath) ? input.filePath : path.resolve(cwd, input.filePath),
11
11
  });
12
12
  }
13
- export async function syncFileWithLsp(cwd, filePath, content, save = true, waitForDiagnostics = true) {
13
+ export async function syncFileWithLsp(cwd, filePath, content, save = true, waitForDiagnostics = true, diagnosticsTimeoutMs) {
14
14
  const manager = getOrCreateManager(cwd);
15
- return manager.syncFile(path.isAbsolute(filePath) ? filePath : path.resolve(cwd, filePath), content, save, waitForDiagnostics);
15
+ return manager.syncFile(path.isAbsolute(filePath) ? filePath : path.resolve(cwd, filePath), content, save, waitForDiagnostics, diagnosticsTimeoutMs);
16
16
  }
17
17
  export function isLspToolEnabled(_cwd) {
18
18
  const settings = getCurrentLspSettings();
@@ -12,9 +12,13 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
12
12
  // ── Mocks ──────────────────────────────────────────────────────────────────
13
13
  // vi.hoisted ensures the factory variable is available when vi.mock is hoisted.
14
14
  const { mockExecFile } = vi.hoisted(() => ({ mockExecFile: vi.fn() }));
15
- vi.mock("node:child_process", () => ({
16
- execFile: mockExecFile,
17
- }));
15
+ vi.mock("node:child_process", async () => {
16
+ const actual = await vi.importActual("node:child_process");
17
+ return {
18
+ ...actual,
19
+ execFile: mockExecFile,
20
+ };
21
+ });
18
22
  vi.mock("node:fs", async (importOriginal) => {
19
23
  const actual = (await importOriginal());
20
24
  return {
@@ -11,9 +11,13 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
11
11
  // ── Mocks ──────────────────────────────────────────────────────────────────
12
12
  // vi.hoisted ensures the factory variable is available when vi.mock is hoisted.
13
13
  const { mockExecFile } = vi.hoisted(() => ({ mockExecFile: vi.fn() }));
14
- vi.mock("node:child_process", () => ({
15
- execFile: mockExecFile,
16
- }));
14
+ vi.mock("node:child_process", async () => {
15
+ const actual = await vi.importActual("node:child_process");
16
+ return {
17
+ ...actual,
18
+ execFile: mockExecFile,
19
+ };
20
+ });
17
21
  vi.mock("../../council/leader.js", () => ({
18
22
  pickCouncilTaskModel: vi.fn((_task, leaderId) => leaderId),
19
23
  }));
@@ -54,7 +54,7 @@ describe("acquireMcpTools — cross-turn client pool", () => {
54
54
  client: { close: async () => { } },
55
55
  }));
56
56
  const b1 = await acquireMcpTools([srv("fs")]);
57
- await expect(b1.tools["mcp_fs__boom"].execute({}, {})).rejects.toThrow(/transport closed/);
57
+ await expect(b1.tools.mcp_fs__boom.execute({}, {})).rejects.toThrow(/transport closed/);
58
58
  // Initial connect + exactly ONE in-turn reconnect — the retry is not looped.
59
59
  expect(connectOneServer).toHaveBeenCalledTimes(2);
60
60
  });
@@ -77,7 +77,7 @@ describe("acquireMcpTools — cross-turn client pool", () => {
77
77
  };
78
78
  });
79
79
  const b = await acquireMcpTools([srv("docs")]);
80
- const result = await b.tools["mcp_docs__ping"].execute({}, {});
80
+ const result = await b.tools.mcp_docs__ping.execute({}, {});
81
81
  expect(result).toBe("pong"); // recovered within the SAME turn
82
82
  expect(connectOneServer).toHaveBeenCalledTimes(2); // drop + one reconnect
83
83
  });
@@ -104,7 +104,7 @@ describe("acquireMcpTools — cross-turn client pool", () => {
104
104
  };
105
105
  });
106
106
  const b = await acquireMcpTools([srv("docs")]);
107
- const tool = b.tools["mcp_docs__ping"];
107
+ const tool = b.tools.mcp_docs__ping;
108
108
  const results = await Promise.all(Array.from({ length: 14 }, () => tool.execute({}, {})));
109
109
  expect(results.every((r) => r === "pong")).toBe(true);
110
110
  expect(connectOneServer).toHaveBeenCalledTimes(2); // 14 failures → exactly ONE shared reconnect
@@ -46,7 +46,7 @@ describe("ee-tools", () => {
46
46
  }),
47
47
  health: async () => ({ ok: true, status: 200 }),
48
48
  }));
49
- const out = rawTextOf(await handlers["ee_query"]({ query: "redactor" }));
49
+ const out = rawTextOf(await handlers.ee_query({ query: "redactor" }));
50
50
  expect(out.isError).toBeFalsy();
51
51
  expect(out.text).toContain("recall:redactor");
52
52
  expect(out.text).toContain("[id:abc col:experience-behavioral]"); // handle preserved for exp-feedback
@@ -58,7 +58,7 @@ describe("ee-tools", () => {
58
58
  recall: async () => ({ text: "x".repeat(50_000), entries: [], count: 42 }),
59
59
  health: async () => ({ ok: true, status: 200 }),
60
60
  }));
61
- const out = rawTextOf(await handlers["ee_query"]({ query: "wide", maxChars: 6000 }));
61
+ const out = rawTextOf(await handlers.ee_query({ query: "wide", maxChars: 6000 }));
62
62
  expect(out.isError).toBeFalsy();
63
63
  expect(out.text.length).toBeLessThan(7000); // capped, not the full 50k dump
64
64
  expect(out.text).toContain("truncated");
@@ -73,18 +73,18 @@ describe("ee-tools", () => {
73
73
  },
74
74
  health: async () => ({ ok: true, status: 200 }),
75
75
  }));
76
- await handlers["ee_query"]({ query: "scope filter", project: "storyflow" });
76
+ await handlers.ee_query({ query: "scope filter", project: "storyflow" });
77
77
  expect(seenProject).toBe("storyflow");
78
78
  });
79
79
  it("ee_query returns ee_unavailable when recall yields null", async () => {
80
80
  const handlers = collectTools((s) => registerEETools(s, { recall: async () => null, health: async () => ({ ok: false, status: 0 }) }));
81
- const out = textOf(await handlers["ee_query"]({ query: "x" }));
81
+ const out = textOf(await handlers.ee_query({ query: "x" }));
82
82
  expect(out.isError).toBe(true);
83
83
  expect(out.json.error).toBe("ee_unavailable");
84
84
  });
85
85
  it("ee_health returns the injected status", async () => {
86
86
  const handlers = collectTools((s) => registerEETools(s, { recall: async () => null, health: async () => ({ ok: true, status: 200 }) }));
87
- const out = textOf(await handlers["ee_health"]({}));
87
+ const out = textOf(await handlers.ee_health({}));
88
88
  expect(out.json).toEqual({ ok: true, status: 200 });
89
89
  });
90
90
  it("ee_health returns ee_unavailable when health throws", async () => {
@@ -94,7 +94,7 @@ describe("ee-tools", () => {
94
94
  throw new Error("boom");
95
95
  },
96
96
  }));
97
- const out = textOf(await handlers["ee_health"]({}));
97
+ const out = textOf(await handlers.ee_health({}));
98
98
  expect(out.isError).toBe(true);
99
99
  expect(out.json.error).toBe("ee_unavailable");
100
100
  });
@@ -111,7 +111,7 @@ describe("ee-tools", () => {
111
111
  return { ok: true, resolvedId: id, verdict: verdict === "followed" ? "FOLLOWED" : verdict };
112
112
  },
113
113
  }));
114
- const out = textOf(await handlers["ee_feedback"]({ id: "abc", collection: "experience-behavioral", verdict: "followed" }));
114
+ const out = textOf(await handlers.ee_feedback({ id: "abc", collection: "experience-behavioral", verdict: "followed" }));
115
115
  expect(out.isError).toBeFalsy();
116
116
  expect(seen).toEqual({ id: "abc", collection: "experience-behavioral", verdict: "followed", reason: undefined });
117
117
  expect(out.json.ok).toBe(true);
@@ -120,7 +120,7 @@ describe("ee-tools", () => {
120
120
  });
121
121
  it("ee_feedback requires a reason for verdict=noise", async () => {
122
122
  const handlers = collectTools((s) => registerEETools(s, { feedback: async () => ({ ok: true }) }));
123
- const out = textOf(await handlers["ee_feedback"]({ id: "x", collection: "experience-selfqa", verdict: "noise" }));
123
+ const out = textOf(await handlers.ee_feedback({ id: "x", collection: "experience-selfqa", verdict: "noise" }));
124
124
  expect(out.isError).toBe(true);
125
125
  expect(out.json.error).toBe("reason_required");
126
126
  });
@@ -128,7 +128,7 @@ describe("ee-tools", () => {
128
128
  const ledger = createRecallLedger();
129
129
  ledger.record([{ id: "abc", collection: "experience-behavioral" }], "q");
130
130
  const handlers = collectTools((s) => registerEETools(s, { ledger, feedback: async () => ({ ok: false, error: "HTTP 500" }) }));
131
- const out = textOf(await handlers["ee_feedback"]({ id: "abc", collection: "experience-behavioral", verdict: "ignored" }));
131
+ const out = textOf(await handlers.ee_feedback({ id: "abc", collection: "experience-behavioral", verdict: "ignored" }));
132
132
  expect(out.isError).toBe(true);
133
133
  expect(out.json.error).toBe("feedback_failed");
134
134
  expect(ledger.pendingCount()).toBe(1); // NOT cleared on failure
@@ -145,11 +145,11 @@ describe("ee-tools", () => {
145
145
  health: async () => ({ ok: true, status: 200 }),
146
146
  }));
147
147
  // First recall — no prior debt, so no gate prefix.
148
- const first = rawTextOf(await handlers["ee_query"]({ query: "first" }));
148
+ const first = rawTextOf(await handlers.ee_query({ query: "first" }));
149
149
  expect(first.text).not.toContain("still unrated");
150
150
  expect(ledger.pendingCount()).toBe(1);
151
151
  // Second recall — the prior id1 is still unrated → soft prefix, but recall still returned.
152
- const second = rawTextOf(await handlers["ee_query"]({ query: "second" }));
152
+ const second = rawTextOf(await handlers.ee_query({ query: "second" }));
153
153
  expect(second.isError).toBeFalsy();
154
154
  expect(second.text).toContain("still unrated");
155
155
  expect(second.text).toContain("id1");
@@ -172,7 +172,7 @@ describe("ee-tools", () => {
172
172
  return { text: "should not run", entries: [], count: 0 };
173
173
  },
174
174
  }));
175
- const out = textOf(await handlers["ee_query"]({ query: "blocked" }));
175
+ const out = textOf(await handlers.ee_query({ query: "blocked" }));
176
176
  expect(out.isError).toBe(true);
177
177
  expect(out.json.error).toBe("feedback_required");
178
178
  expect(out.json.message).toContain("FEEDBACK GATE");
@@ -190,9 +190,37 @@ describe("ee-tools", () => {
190
190
  }),
191
191
  health: async () => ({ ok: true, status: 200 }),
192
192
  }));
193
- await handlers["ee_query"]({ query: "one" });
194
- await handlers["ee_query"]({ query: "two" });
193
+ await handlers.ee_query({ query: "one" });
194
+ await handlers.ee_query({ query: "two" });
195
195
  expect(ledger.pendingCount()).toBe(0); // nothing stamped when gate is off
196
196
  });
197
+ // ─── ee_write (Layer 1 — agent records a new lesson) ───────────────────────
198
+ it("ee_write forwards the lesson to the write helper and returns the new id", async () => {
199
+ let seen = null;
200
+ const handlers = collectTools((s) => registerEETools(s, {
201
+ write: async (lesson, opts) => {
202
+ seen = { lesson, opts };
203
+ return { ok: true, id: "new-point-1" };
204
+ },
205
+ }));
206
+ const out = textOf(await handlers.ee_write({
207
+ lesson: "always call flushFrob() before reindex or reads go stale",
208
+ title: "reindex pitfall",
209
+ project: "experience-engine",
210
+ }));
211
+ expect(out.isError).toBeFalsy();
212
+ expect(out.json.ok).toBe(true);
213
+ expect(out.json.id).toBe("new-point-1");
214
+ expect(out.json.collection).toBe("experience-behavioral");
215
+ expect(seen.lesson).toContain("flushFrob");
216
+ expect(seen.opts.collection).toBe("experience-behavioral");
217
+ expect(seen.opts.projectSlug).toBe("experience-engine");
218
+ });
219
+ it("ee_write surfaces write_failed when the write helper fails", async () => {
220
+ const handlers = collectTools((s) => registerEETools(s, { write: async () => ({ ok: false, error: "import-memory HTTP 500" }) }));
221
+ const out = textOf(await handlers.ee_write({ lesson: "a sufficiently long lesson body here" }));
222
+ expect(out.isError).toBe(true);
223
+ expect(out.json.error).toBe("write_failed");
224
+ });
197
225
  });
198
226
  //# sourceMappingURL=ee-tools.test.js.map
@@ -32,20 +32,20 @@ const fakeSummary = (id) => ({
32
32
  describe("forensics-tools", () => {
33
33
  it("usage_forensics returns the summary for a unique prefix", async () => {
34
34
  const handlers = collectTools((s) => registerForensicsTools(s, { resolve: () => ["sess123"], collect: (id) => fakeSummary(id) }));
35
- const out = parse(await handlers["usage_forensics"]({ prefix: "sess" }));
35
+ const out = parse(await handlers.usage_forensics({ prefix: "sess" }));
36
36
  expect(out.isError).toBeFalsy();
37
37
  expect(out.json.sessionId).toBe("sess123");
38
38
  expect(out.json.peakSingleCallInput).toBe(100);
39
39
  });
40
40
  it("usage_forensics returns not_found for zero matches", async () => {
41
41
  const handlers = collectTools((s) => registerForensicsTools(s, { resolve: () => [], collect: () => fakeSummary("x") }));
42
- const out = parse(await handlers["usage_forensics"]({ prefix: "nope" }));
42
+ const out = parse(await handlers.usage_forensics({ prefix: "nope" }));
43
43
  expect(out.isError).toBe(true);
44
44
  expect(out.json.error).toBe("not_found");
45
45
  });
46
46
  it("usage_forensics returns ambiguous for multiple matches", async () => {
47
47
  const handlers = collectTools((s) => registerForensicsTools(s, { resolve: () => ["a1", "a2"], collect: () => fakeSummary("a1") }));
48
- const out = parse(await handlers["usage_forensics"]({ prefix: "a" }));
48
+ const out = parse(await handlers.usage_forensics({ prefix: "a" }));
49
49
  expect(out.isError).toBe(true);
50
50
  expect(out.json.error).toBe("ambiguous");
51
51
  expect(out.json.matches).toEqual(["a1", "a2"]);
@@ -18,13 +18,13 @@ describe("lsp-tools", () => {
18
18
  it("lsp_query passes through the LspToolResponse when enabled", async () => {
19
19
  const resp = { success: true, output: "def at file.ts:10" };
20
20
  const handlers = collectTools((s) => registerLspTools(s, { enabled: () => true, query: async () => resp }));
21
- const out = parse(await handlers["lsp_query"]({ operation: "goToDefinition", filePath: "a.ts", line: 1, character: 2 }));
21
+ const out = parse(await handlers.lsp_query({ operation: "goToDefinition", filePath: "a.ts", line: 1, character: 2 }));
22
22
  expect(out.isError).toBeFalsy();
23
23
  expect(out.json).toEqual(resp);
24
24
  });
25
25
  it("lsp_query returns lsp_disabled when LSP is off", async () => {
26
26
  const handlers = collectTools((s) => registerLspTools(s, { enabled: () => false, query: async () => ({ success: true, output: "x" }) }));
27
- const out = parse(await handlers["lsp_query"]({ operation: "hover", filePath: "a.ts", line: 0, character: 0 }));
27
+ const out = parse(await handlers.lsp_query({ operation: "hover", filePath: "a.ts", line: 0, character: 0 }));
28
28
  expect(out.isError).toBe(true);
29
29
  expect(out.json.error).toBe("lsp_disabled");
30
30
  });
@@ -35,7 +35,7 @@ describe("lsp-tools", () => {
35
35
  throw new Error("server launch failed");
36
36
  },
37
37
  }));
38
- const out = parse(await handlers["lsp_query"]({ operation: "findReferences", filePath: "a.ts", line: 3, character: 4 }));
38
+ const out = parse(await handlers.lsp_query({ operation: "findReferences", filePath: "a.ts", line: 3, character: 4 }));
39
39
  expect(out.isError).toBe(true);
40
40
  expect(out.json.error).toBe("lsp_error");
41
41
  expect(out.json.message).toContain("server launch failed");
@@ -47,7 +47,7 @@ describe("lsp-tools", () => {
47
47
  },
48
48
  query: async () => ({ success: true, output: "x" }),
49
49
  }));
50
- const out = parse(await handlers["lsp_query"]({ operation: "hover", filePath: "a.ts", line: 0, character: 0 }));
50
+ const out = parse(await handlers.lsp_query({ operation: "hover", filePath: "a.ts", line: 0, character: 0 }));
51
51
  expect(out.isError).toBe(true);
52
52
  expect(out.json.error).toBe("lsp_error");
53
53
  expect(out.json.message).toContain("settings read failed");
@@ -39,14 +39,6 @@ const DEFAULT_CONFIGS = [
39
39
  transport: "http",
40
40
  url: "https://docs-mcp.muonroi.com/mcp",
41
41
  },
42
- {
43
- id: "filesystem",
44
- label: "Filesystem",
45
- enabled: true,
46
- transport: "stdio",
47
- command: "npx",
48
- args: ["-y", "@modelcontextprotocol/server-filesystem", "."],
49
- },
50
42
  {
51
43
  id: "playwright",
52
44
  label: "Playwright",
@@ -44,7 +44,7 @@ export declare function acquireMcpTools(servers: McpServerConfig[], opts?: McpBu
44
44
  * has connected. Idempotent (cached entries are reused); a failed connect is
45
45
  * evicted by getOrConnect so a real turn retries.
46
46
  */
47
- export declare function warmMcpClients(servers: McpServerConfig[]): void;
47
+ export declare function warmMcpClients(servers: McpServerConfig[], syncAndLog?: boolean): Promise<void>;
48
48
  /** Tear down every pooled client. Call on orchestrator/process shutdown. */
49
49
  export declare function closeAllMcpClients(): Promise<void>;
50
50
  /** Test-only: reset pool state between cases. */
@@ -236,15 +236,31 @@ export async function acquireMcpTools(servers, opts) {
236
236
  * has connected. Idempotent (cached entries are reused); a failed connect is
237
237
  * evicted by getOrConnect so a real turn retries.
238
238
  */
239
- export function warmMcpClients(servers) {
240
- for (const s of servers) {
241
- if (!s.enabled)
242
- continue;
243
- if (!validateMcpServerConfig(s).ok)
244
- continue;
245
- void getOrConnect(s).catch(() => {
246
- /* warm is best-effort — the eviction in getOrConnect lets a real turn retry */
247
- });
239
+ export async function warmMcpClients(servers, syncAndLog = false) {
240
+ const validServers = servers.filter(s => s.enabled && validateMcpServerConfig(s).ok);
241
+ if (syncAndLog && validServers.length > 0) {
242
+ console.log(`[MCP] Warming up ${validServers.length} server(s)...`);
243
+ }
244
+ const promises = validServers.map(async (s) => {
245
+ try {
246
+ await getOrConnect(s);
247
+ if (syncAndLog) {
248
+ console.log(`[MCP] ✅ ${s.id}: healthy`);
249
+ }
250
+ }
251
+ catch (e) {
252
+ if (syncAndLog) {
253
+ const msg = e instanceof Error ? e.message : String(e);
254
+ console.log(`[MCP] ❌ ${s.id}: failed - ${msg}`);
255
+ }
256
+ }
257
+ });
258
+ if (syncAndLog) {
259
+ await Promise.all(promises);
260
+ }
261
+ else {
262
+ // Fire-and-forget for background warmup
263
+ void Promise.all(promises);
248
264
  }
249
265
  }
250
266
  /** Tear down every pooled client. Call on orchestrator/process shutdown. */
@@ -30,6 +30,16 @@ export interface EEToolDeps {
30
30
  status: number;
31
31
  }>;
32
32
  feedback?: (pointId: string, collection: string, verdict: FeedbackVerdict, reason?: NoiseReason) => Promise<FeedbackResult>;
33
+ write?: (lesson: string, opts: {
34
+ collection?: string;
35
+ title?: string;
36
+ projectSlug?: string;
37
+ confidence?: number;
38
+ }) => Promise<{
39
+ ok: boolean;
40
+ id?: string;
41
+ error?: string;
42
+ }>;
33
43
  /** Session pending-recall ledger. Defaults to the process singleton. */
34
44
  ledger?: RecallLedger;
35
45
  }
@@ -155,5 +155,39 @@ export function registerEETools(server, deps = {}) {
155
155
  return fail("ee_unavailable", e instanceof Error ? e.message : String(e));
156
156
  }
157
157
  });
158
+ server.registerTool("ee_write", {
159
+ description: "Save a NEW lesson to the Experience Engine brain so you and future sessions recall it. Call the MOMENT you " +
160
+ "hit a mistake / error / dead-end and find the working fix: record the pitfall AND the fix in one concise, " +
161
+ "generalizable lesson (1-3 sentences — what to do or avoid next time, NOT a play-by-play of this turn). The " +
162
+ "lesson is embedded immediately (via /api/import-memory: dense + sparse) and becomes recallable via ee_query " +
163
+ "in this and future sessions. Use ee_query first if unsure a lesson already exists. collection defaults to " +
164
+ "experience-behavioral (use experience-principles only for a broad, project-independent principle); project " +
165
+ "scopes the lesson.",
166
+ inputSchema: {
167
+ lesson: z.string().min(12).max(4000),
168
+ title: z.string().max(200).optional(),
169
+ collection: z.enum(["experience-behavioral", "experience-principles"]).optional(),
170
+ project: z.string().max(200).optional(),
171
+ },
172
+ }, async ({ lesson, title, collection, project }) => {
173
+ const trimmed = lesson.trim();
174
+ const text = trimmed.length > 1500 ? `${trimmed.slice(0, 1497)}...` : trimmed;
175
+ const targetCollection = collection ?? "experience-behavioral";
176
+ const write = deps.write ?? ((l, o) => import("../ee/search.js").then((m) => m.writeExperienceEE(l, o)));
177
+ try {
178
+ const result = await write(text, {
179
+ collection: targetCollection,
180
+ title,
181
+ projectSlug: project,
182
+ confidence: 0.65,
183
+ });
184
+ if (!result.ok)
185
+ return fail("write_failed", result.error ?? "import-memory POST failed");
186
+ return ok({ ok: true, id: result.id, collection: targetCollection, recallable: "now via ee_query" });
187
+ }
188
+ catch (e) {
189
+ return fail("write_failed", e instanceof Error ? e.message : String(e));
190
+ }
191
+ });
158
192
  }
159
193
  //# sourceMappingURL=ee-tools.js.map
@@ -21,6 +21,7 @@ export interface StartAgenticOpts {
21
21
  goal: string;
22
22
  llm: string;
23
23
  turns?: number;
24
+ mockLlmDir?: string;
24
25
  }
25
26
  export type StartOpts = StartTier1Opts | StartAgenticOpts;
26
27
  export interface Job {
@@ -34,9 +34,7 @@ export class JobManager {
34
34
  job.logBuffer.splice(0, job.logBuffer.length - LOG_CAP);
35
35
  }
36
36
  };
37
- const run = opts.kind === "tier1"
38
- ? this.runner.tier1(opts, log, ctrl.signal)
39
- : this.runner.agentic(opts, log, ctrl.signal);
37
+ const run = opts.kind === "tier1" ? this.runner.tier1(opts, log, ctrl.signal) : this.runner.agentic(opts, log, ctrl.signal);
40
38
  run.then((report) => {
41
39
  this.controllers.delete(runId); // I2: keep maps symmetric after natural completion
42
40
  if (job.status === "cancelled")
@@ -25,7 +25,7 @@ export const defaultRunner = {
25
25
  async agentic(opts, log) {
26
26
  const { createLLMBrain, runAgenticLoop } = await import("../self-qa/agentic-loop.js");
27
27
  const brain = await createLLMBrain({ modelId: opts.llm });
28
- return runAgenticLoop({ goal: opts.goal, brain, maxTurns: opts.turns ?? 20, log });
28
+ return runAgenticLoop({ goal: opts.goal, brain, maxTurns: opts.turns ?? 20, mockLlmDir: opts.mockLlmDir, log });
29
29
  },
30
30
  };
31
31
  let shared = null;