mixdog 0.9.3 → 0.9.4

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 (329) hide show
  1. package/package.json +7 -3
  2. package/scripts/bench/lead-review-tasks-r3.json +20 -0
  3. package/scripts/bench/lead-review-tasks.json +20 -0
  4. package/scripts/bench/r4-mixed-tasks.json +20 -0
  5. package/scripts/bench/review-tasks.json +20 -0
  6. package/scripts/bench/round-codex.json +114 -0
  7. package/scripts/bench/round-mixdog-lead-r3.json +269 -0
  8. package/scripts/bench/round-mixdog-lead.json +269 -0
  9. package/scripts/bench/round-mixdog.json +126 -0
  10. package/scripts/bench/round-r10-bigsample.json +679 -0
  11. package/scripts/bench/round-r11-codexalign.json +257 -0
  12. package/scripts/bench/round-r4-codex.json +114 -0
  13. package/scripts/bench/round-r4-mixed.json +225 -0
  14. package/scripts/bench/round-r5-gpt-lead.json +259 -0
  15. package/scripts/bench/round-r6-codex.json +114 -0
  16. package/scripts/bench/round-r6-solo.json +257 -0
  17. package/scripts/bench/round-r7-full.json +254 -0
  18. package/scripts/bench/round-r8-fulldefault.json +255 -0
  19. package/scripts/bench-run.mjs +215 -29
  20. package/scripts/freevar-smoke.mjs +95 -0
  21. package/scripts/internal-comms-bench.mjs +1 -0
  22. package/scripts/internal-comms-smoke.mjs +10 -9
  23. package/scripts/mouse-probe.mjs +45 -0
  24. package/scripts/output-style-bench.mjs +13 -6
  25. package/scripts/output-style-smoke.mjs +4 -4
  26. package/scripts/provider-toolcall-test.mjs +7 -3
  27. package/scripts/recall-usecase-cases.json +18 -0
  28. package/scripts/recall-usecase-probe.json +6 -0
  29. package/scripts/session-bench.mjs +152 -6
  30. package/scripts/tool-smoke.mjs +23 -63
  31. package/scripts/tui-render-smoke.mjs +90 -0
  32. package/scripts/webhook-smoke.mjs +208 -0
  33. package/src/agents/debugger/AGENT.md +4 -1
  34. package/src/agents/heavy-worker/AGENT.md +6 -5
  35. package/src/agents/maintainer/AGENT.md +4 -0
  36. package/src/agents/reviewer/AGENT.md +2 -1
  37. package/src/agents/worker/AGENT.md +8 -4
  38. package/src/lib/rules-builder.cjs +4 -0
  39. package/src/mixdog-session-runtime.mjs +632 -2042
  40. package/src/output-styles/default.md +34 -9
  41. package/src/output-styles/{oneline.md → extreme-minimal.md} +5 -4
  42. package/src/output-styles/minimal.md +4 -1
  43. package/src/output-styles/simple.md +22 -7
  44. package/src/rules/agent/00-common.md +2 -0
  45. package/src/rules/lead/lead-brief.md +12 -0
  46. package/src/rules/lead/lead-tool.md +0 -11
  47. package/src/runtime/agent/orchestrator/agent-runtime/agent-loop-policy.mjs +25 -0
  48. package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +100 -23
  49. package/src/runtime/agent/orchestrator/agent-runtime/session-builder.mjs +6 -15
  50. package/src/runtime/agent/orchestrator/agent-trace-format.mjs +362 -0
  51. package/src/runtime/agent/orchestrator/agent-trace-io.mjs +410 -0
  52. package/src/runtime/agent/orchestrator/agent-trace.mjs +16 -735
  53. package/src/runtime/agent/orchestrator/config.mjs +69 -2
  54. package/src/runtime/agent/orchestrator/providers/anthropic-effort.mjs +62 -20
  55. package/src/runtime/agent/orchestrator/providers/anthropic-model-resolve.mjs +209 -0
  56. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-credentials.mjs +489 -0
  57. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +81 -1281
  58. package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +607 -0
  59. package/src/runtime/agent/orchestrator/providers/anthropic.mjs +32 -3
  60. package/src/runtime/agent/orchestrator/providers/codex-client-meta.mjs +81 -0
  61. package/src/runtime/agent/orchestrator/providers/gemini-cache.mjs +248 -0
  62. package/src/runtime/agent/orchestrator/providers/gemini-schema.mjs +303 -0
  63. package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +505 -0
  64. package/src/runtime/agent/orchestrator/providers/gemini.mjs +43 -1013
  65. package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +17 -3
  66. package/src/runtime/agent/orchestrator/providers/model-catalog.mjs +86 -11
  67. package/src/runtime/agent/orchestrator/providers/model-list-sanitize.mjs +348 -0
  68. package/src/runtime/agent/orchestrator/providers/openai-codex-model.mjs +108 -0
  69. package/src/runtime/agent/orchestrator/providers/openai-compat-trace.mjs +58 -0
  70. package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +368 -0
  71. package/src/runtime/agent/orchestrator/providers/openai-compat-xai.mjs +760 -0
  72. package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +40 -1143
  73. package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +732 -0
  74. package/src/runtime/agent/orchestrator/providers/openai-oauth-login.mjs +193 -0
  75. package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +297 -2123
  76. package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +130 -1002
  77. package/src/runtime/agent/orchestrator/providers/openai-ws-delta.mjs +227 -0
  78. package/src/runtime/agent/orchestrator/providers/openai-ws-events.mjs +67 -0
  79. package/src/runtime/agent/orchestrator/providers/openai-ws-pool.mjs +436 -0
  80. package/src/runtime/agent/orchestrator/providers/openai-ws-stream.mjs +1105 -0
  81. package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +2 -1
  82. package/src/runtime/agent/orchestrator/session/compact/budget.mjs +288 -0
  83. package/src/runtime/agent/orchestrator/session/compact/constants.mjs +85 -0
  84. package/src/runtime/agent/orchestrator/session/compact/engine.mjs +749 -0
  85. package/src/runtime/agent/orchestrator/session/compact/messages.mjs +82 -0
  86. package/src/runtime/agent/orchestrator/session/compact/summary-schema.mjs +315 -0
  87. package/src/runtime/agent/orchestrator/session/compact/summary.mjs +643 -0
  88. package/src/runtime/agent/orchestrator/session/compact/text-utils.mjs +326 -0
  89. package/src/runtime/agent/orchestrator/session/compact.mjs +40 -2282
  90. package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +14 -2
  91. package/src/runtime/agent/orchestrator/session/loop/completion-guards.mjs +61 -0
  92. package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +1 -3
  93. package/src/runtime/agent/orchestrator/session/loop/recall-fasttrack.mjs +182 -0
  94. package/src/runtime/agent/orchestrator/session/loop/steering-ladder.mjs +173 -0
  95. package/src/runtime/agent/orchestrator/session/loop/termination.mjs +58 -0
  96. package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +239 -0
  97. package/src/runtime/agent/orchestrator/session/loop.mjs +251 -397
  98. package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +471 -0
  99. package/src/runtime/agent/orchestrator/session/manager/context-meta.mjs +7 -4
  100. package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +12 -0
  101. package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +406 -0
  102. package/src/runtime/agent/orchestrator/session/manager/status-telemetry.mjs +80 -0
  103. package/src/runtime/agent/orchestrator/session/manager/usage-metrics.mjs +210 -0
  104. package/src/runtime/agent/orchestrator/session/manager.mjs +166 -1087
  105. package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +189 -0
  106. package/src/runtime/agent/orchestrator/session/store.mjs +74 -179
  107. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +70 -20
  108. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +22 -2
  109. package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +32 -41
  110. package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +40 -0
  111. package/src/runtime/agent/orchestrator/tools/builtin/rg-runner.mjs +29 -0
  112. package/src/runtime/agent/orchestrator/tools/builtin/search-builders.mjs +8 -0
  113. package/src/runtime/agent/orchestrator/tools/builtin/search-path-diagnostics.mjs +126 -0
  114. package/src/runtime/agent/orchestrator/tools/builtin/search-tool.mjs +81 -92
  115. package/src/runtime/agent/orchestrator/tools/builtin/shell-job-paths.mjs +161 -0
  116. package/src/runtime/agent/orchestrator/tools/builtin/shell-job-process.mjs +108 -0
  117. package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +28 -265
  118. package/src/runtime/agent/orchestrator/tools/builtin.mjs +0 -6
  119. package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +57 -3
  120. package/src/runtime/agent/orchestrator/tools/code-graph/keyword-match.mjs +82 -0
  121. package/src/runtime/agent/orchestrator/tools/code-graph/search.mjs +10 -122
  122. package/src/runtime/agent/orchestrator/tools/code-graph/text-columns.mjs +45 -0
  123. package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +6 -6
  124. package/src/runtime/agent/orchestrator/tools/graph-binary-fetcher.mjs +6 -3
  125. package/src/runtime/agent/orchestrator/tools/patch/constants.mjs +9 -0
  126. package/src/runtime/agent/orchestrator/tools/patch/dispatch.mjs +171 -0
  127. package/src/runtime/agent/orchestrator/tools/patch/matcher.mjs +471 -0
  128. package/src/runtime/agent/orchestrator/tools/patch/native-server.mjs +436 -0
  129. package/src/runtime/agent/orchestrator/tools/patch/orchestrator.mjs +342 -0
  130. package/src/runtime/agent/orchestrator/tools/patch/parsing.mjs +359 -0
  131. package/src/runtime/agent/orchestrator/tools/patch/paths.mjs +340 -0
  132. package/src/runtime/agent/orchestrator/tools/patch/v4a-convert.mjs +643 -0
  133. package/src/runtime/agent/orchestrator/tools/patch.mjs +36 -2959
  134. package/src/runtime/agent/orchestrator/tools/progress-message.mjs +0 -21
  135. package/src/runtime/agent/orchestrator/tools/shell-command.mjs +9 -72
  136. package/src/runtime/agent/orchestrator/tools/shell-powershell.mjs +77 -0
  137. package/src/runtime/agent/orchestrator/tools/shell-state.mjs +154 -0
  138. package/src/runtime/channels/backends/discord-access.mjs +32 -0
  139. package/src/runtime/channels/backends/discord-attachments.mjs +65 -0
  140. package/src/runtime/channels/backends/discord-gateway.mjs +233 -0
  141. package/src/runtime/channels/backends/discord.mjs +12 -292
  142. package/src/runtime/channels/index.mjs +229 -663
  143. package/src/runtime/channels/lib/backend-dispatch.mjs +44 -0
  144. package/src/runtime/channels/lib/event-pipeline.mjs +18 -1
  145. package/src/runtime/channels/lib/event-queue.mjs +63 -4
  146. package/src/runtime/channels/lib/inbound-routing.mjs +111 -0
  147. package/src/runtime/channels/lib/output-forwarder.mjs +1 -1
  148. package/src/runtime/channels/lib/owner-heartbeat.mjs +75 -0
  149. package/src/runtime/channels/lib/parent-bridge.mjs +88 -0
  150. package/src/runtime/channels/lib/runtime-paths.mjs +14 -4
  151. package/src/runtime/channels/lib/session-discovery.mjs +56 -4
  152. package/src/runtime/channels/lib/tool-dispatch.mjs +158 -0
  153. package/src/runtime/channels/lib/tool-format.mjs +1 -1
  154. package/src/runtime/channels/lib/transcript-discovery.mjs +4 -4
  155. package/src/runtime/channels/lib/voice-runtime-fetcher.mjs +6 -3
  156. package/src/runtime/channels/lib/voice-transcription.mjs +179 -0
  157. package/src/runtime/channels/lib/webhook/deliveries.mjs +312 -0
  158. package/src/runtime/channels/lib/webhook/log.mjs +42 -0
  159. package/src/runtime/channels/lib/webhook/ngrok.mjs +181 -0
  160. package/src/runtime/channels/lib/webhook/signature.mjs +60 -0
  161. package/src/runtime/channels/lib/webhook.mjs +36 -570
  162. package/src/runtime/channels/tool-defs.mjs +11 -130
  163. package/src/runtime/memory/index.mjs +201 -1948
  164. package/src/runtime/memory/lib/cycle-llm-adapters.mjs +58 -0
  165. package/src/runtime/memory/lib/cycle-scheduler.mjs +497 -0
  166. package/src/runtime/memory/lib/embedding-warmup.mjs +58 -0
  167. package/src/runtime/memory/lib/memory-config-flags.mjs +91 -0
  168. package/src/runtime/memory/lib/memory-cycle.mjs +1 -1
  169. package/src/runtime/memory/lib/memory-cycle2-gate.mjs +515 -0
  170. package/src/runtime/memory/lib/memory-cycle2-mutations.mjs +324 -0
  171. package/src/runtime/memory/lib/memory-cycle2-shared.mjs +18 -0
  172. package/src/runtime/memory/lib/memory-cycle2.mjs +24 -842
  173. package/src/runtime/memory/lib/memory-embed.mjs +149 -0
  174. package/src/runtime/memory/lib/memory-process-lock.mjs +162 -0
  175. package/src/runtime/memory/lib/memory-recall-store.mjs +22 -2
  176. package/src/runtime/memory/lib/pg/supervisor.mjs +1 -1
  177. package/src/runtime/memory/lib/query-handlers.mjs +780 -0
  178. package/src/runtime/memory/lib/recall-format.mjs +55 -0
  179. package/src/runtime/memory/lib/runtime-fetcher.mjs +8 -3
  180. package/src/runtime/memory/lib/transcript-ingest.mjs +425 -0
  181. package/src/runtime/memory/tool-defs.mjs +5 -13
  182. package/src/runtime/search/lib/http-fetch.mjs +274 -0
  183. package/src/runtime/search/lib/ssrf-guard.mjs +333 -0
  184. package/src/runtime/search/lib/web-tools.mjs +24 -602
  185. package/src/runtime/shared/atomic-file.mjs +26 -1
  186. package/src/runtime/shared/launcher-control.mjs +2 -2
  187. package/src/runtime/shared/tool-primitives.mjs +308 -0
  188. package/src/runtime/shared/tool-result-summary.mjs +515 -0
  189. package/src/runtime/shared/tool-surface.mjs +80 -898
  190. package/src/runtime/shared/transcript-writer.mjs +23 -0
  191. package/src/runtime/shared/update-checker.mjs +7 -4
  192. package/src/session-runtime/config-helpers.mjs +84 -2
  193. package/src/session-runtime/config-lifecycle.mjs +232 -0
  194. package/src/session-runtime/cwd-plugins.mjs +226 -0
  195. package/src/session-runtime/mcp-glue.mjs +177 -0
  196. package/src/session-runtime/model-recency.mjs +111 -0
  197. package/src/session-runtime/native-search.mjs +247 -0
  198. package/src/session-runtime/output-styles.mjs +11 -9
  199. package/src/session-runtime/prewarm.mjs +142 -0
  200. package/src/session-runtime/provider-models.mjs +278 -0
  201. package/src/session-runtime/provider-usage.mjs +120 -0
  202. package/src/session-runtime/quick-model-rows.mjs +170 -0
  203. package/src/session-runtime/quick-search-models.mjs +46 -0
  204. package/src/session-runtime/session-hooks.mjs +93 -0
  205. package/src/session-runtime/settings-api.mjs +319 -0
  206. package/src/session-runtime/tool-catalog.mjs +29 -29
  207. package/src/session-runtime/tool-defs.mjs +84 -0
  208. package/src/session-runtime/warmup-schedulers.mjs +201 -0
  209. package/src/standalone/agent-tool/helpers.mjs +237 -0
  210. package/src/standalone/agent-tool/notify.mjs +107 -0
  211. package/src/standalone/agent-tool/provider-init.mjs +143 -0
  212. package/src/standalone/agent-tool/render.mjs +152 -0
  213. package/src/standalone/agent-tool/tool-def.mjs +55 -0
  214. package/src/standalone/agent-tool.mjs +110 -671
  215. package/src/standalone/channel-worker.mjs +4 -7
  216. package/src/standalone/explore-tool.mjs +30 -9
  217. package/src/standalone/hook-bus/config.mjs +207 -0
  218. package/src/standalone/hook-bus/constants.mjs +90 -0
  219. package/src/standalone/hook-bus/handlers.mjs +481 -0
  220. package/src/standalone/hook-bus/payload.mjs +31 -0
  221. package/src/standalone/hook-bus/rules.mjs +77 -0
  222. package/src/standalone/hook-bus.mjs +77 -870
  223. package/src/standalone/memory-runtime-proxy.mjs +7 -0
  224. package/src/standalone/opencode-go-login.mjs +5 -1
  225. package/src/standalone/provider-admin.mjs +1 -16
  226. package/src/standalone/usage-dashboard.mjs +3 -1
  227. package/src/tui/App.jsx +945 -8094
  228. package/src/tui/app/app-format.mjs +206 -0
  229. package/src/tui/app/channel-pickers.mjs +510 -0
  230. package/src/tui/app/clipboard.mjs +67 -0
  231. package/src/tui/app/core-memory-picker.mjs +210 -0
  232. package/src/tui/app/extension-pickers.mjs +506 -0
  233. package/src/tui/app/input-parsers.mjs +193 -0
  234. package/src/tui/app/maintenance-pickers.mjs +324 -0
  235. package/src/tui/app/model-options.mjs +330 -0
  236. package/src/tui/app/model-picker.mjs +365 -0
  237. package/src/tui/app/onboarding-steps.mjs +400 -0
  238. package/src/tui/app/project-picker.mjs +247 -0
  239. package/src/tui/app/provider-setup-picker.mjs +580 -0
  240. package/src/tui/app/resume-picker.mjs +55 -0
  241. package/src/tui/app/route-pickers.mjs +419 -0
  242. package/src/tui/app/settings-picker.mjs +490 -0
  243. package/src/tui/app/slash-commands.mjs +101 -0
  244. package/src/tui/app/slash-dispatch.mjs +427 -0
  245. package/src/tui/app/text-layout.mjs +46 -0
  246. package/src/tui/app/theme-effort-pickers.mjs +154 -0
  247. package/src/tui/app/transcript-window.mjs +671 -0
  248. package/src/tui/app/use-mouse-input.mjs +460 -0
  249. package/src/tui/app/use-prompt-handlers.mjs +310 -0
  250. package/src/tui/app/use-transcript-scroll.mjs +510 -0
  251. package/src/tui/app/use-transcript-window.mjs +589 -0
  252. package/src/tui/components/ConfirmBar.jsx +1 -1
  253. package/src/tui/components/Picker.jsx +32 -4
  254. package/src/tui/components/PromptInput.jsx +23 -101
  255. package/src/tui/components/SlashCommandPalette.jsx +8 -1
  256. package/src/tui/components/StatusLine.jsx +63 -12
  257. package/src/tui/components/TextEntryPanel.jsx +11 -0
  258. package/src/tui/components/ToolExecution.jsx +52 -594
  259. package/src/tui/components/TranscriptItem.jsx +105 -0
  260. package/src/tui/components/UsagePanel.jsx +18 -4
  261. package/src/tui/components/prompt-input/edit-helpers.mjs +72 -0
  262. package/src/tui/components/prompt-input/voice-indicator.mjs +39 -0
  263. package/src/tui/components/tool-execution/ResultBody.jsx +56 -0
  264. package/src/tui/components/tool-execution/surface-detail.mjs +405 -0
  265. package/src/tui/components/tool-execution/text-format.mjs +161 -0
  266. package/src/tui/display-width.mjs +20 -3
  267. package/src/tui/dist/index.mjs +19652 -18630
  268. package/src/tui/engine/agent-job-feed.mjs +133 -0
  269. package/src/tui/engine/notification-plan.mjs +76 -0
  270. package/src/tui/engine/render-timing.mjs +17 -0
  271. package/src/tui/engine/tool-approval.mjs +94 -0
  272. package/src/tui/engine/tool-card-results.mjs +234 -0
  273. package/src/tui/engine/tool-result-status.mjs +135 -0
  274. package/src/tui/engine.mjs +122 -562
  275. package/src/tui/figures.mjs +5 -0
  276. package/src/tui/index.jsx +105 -0
  277. package/src/tui/input-editing.mjs +2 -2
  278. package/src/tui/markdown/format-token.mjs +4 -1
  279. package/src/tui/statusline-ansi-bridge.mjs +11 -3
  280. package/src/tui/theme.mjs +6 -0
  281. package/src/ui/statusline-agents.mjs +213 -0
  282. package/src/ui/statusline-format.mjs +146 -0
  283. package/src/ui/statusline-segments.mjs +148 -0
  284. package/src/ui/statusline.mjs +67 -501
  285. package/src/ui/tool-card.mjs +0 -1
  286. package/src/vendor/statusline/bin/statusline-route.mjs +15 -2
  287. package/src/workflows/default/WORKFLOW.md +1 -1
  288. package/src/workflows/sequential/WORKFLOW.md +1 -1
  289. package/vendor/ink/build/display-width.js +19 -3
  290. package/vendor/ink/build/ink.js +103 -6
  291. package/vendor/ink/build/log-update.js +17 -3
  292. package/vendor/ink/build/wrap-text.js +125 -0
  293. package/scripts/_test-folder-dialog.mjs +0 -30
  294. package/scripts/fix-brief-fn.mjs +0 -35
  295. package/scripts/fix-format-tool-surface.mjs +0 -24
  296. package/scripts/fix-tool-exec-visible.mjs +0 -42
  297. package/scripts/patch-agent-brief.mjs +0 -48
  298. package/scripts/patch-app.mjs +0 -21
  299. package/scripts/patch-app2.mjs +0 -18
  300. package/scripts/patch-dist-brief.mjs +0 -96
  301. package/scripts/patch-tool-exec.mjs +0 -70
  302. package/src/examples/schedules/SCHEDULE.example.md +0 -32
  303. package/src/examples/webhooks/WEBHOOK.example.md +0 -40
  304. package/src/runtime/agent/orchestrator/session/manager.reactive-persist.test.mjs +0 -107
  305. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.test.mjs +0 -143
  306. package/src/runtime/agent/orchestrator/tools/builtin/diagnostics-tool.mjs +0 -285
  307. package/src/runtime/agent/orchestrator/tools/builtin/external-tool-adapters.test.mjs +0 -162
  308. package/src/runtime/agent/orchestrator/tools/builtin/open-config-tool.mjs +0 -26
  309. package/src/runtime/shared/channel-notification-routing.test.mjs +0 -45
  310. package/src/runtime/shared/task-notification-envelope.test.mjs +0 -107
  311. package/src/runtime/shared/tool-execution-contract.test.mjs +0 -183
  312. package/src/standalone/agent-task-status.test.mjs +0 -76
  313. package/src/tui/components/tool-output-format.test.mjs +0 -399
  314. package/src/tui/display-width.test.mjs +0 -35
  315. package/src/tui/engine-runtime-notification.test.mjs +0 -115
  316. package/src/tui/engine-tool-result-text.test.mjs +0 -75
  317. package/src/tui/input-editing.selection.test.mjs +0 -75
  318. package/src/tui/markdown/format-token.test.mjs +0 -354
  319. package/src/tui/markdown/render-ansi.test.mjs +0 -108
  320. package/src/tui/markdown/stream-fence.test.mjs +0 -26
  321. package/src/tui/markdown/streaming-markdown.test.mjs +0 -70
  322. package/src/tui/paste-fix.test.mjs +0 -119
  323. package/src/tui/prompt-history-store.test.mjs +0 -52
  324. package/src/tui/statusline-ansi-bridge.test.mjs +0 -159
  325. package/src/tui/transcript-tool-failures.test.mjs +0 -111
  326. package/src/ui/markdown.test.mjs +0 -70
  327. package/src/ui/statusline-context-label.test.mjs +0 -15
  328. package/src/vendor/statusline/bin/statusline-lib.mjs +0 -186
  329. package/src/vendor/statusline/bin/statusline-route.test.mjs +0 -80
@@ -0,0 +1,506 @@
1
+ /**
2
+ * extension-pickers.mjs — MCP / Skills / Plugins / Hooks picker cluster.
3
+ *
4
+ * Extracted from App.jsx behavior-preservingly as a dependency-injection
5
+ * factory. These openers drive setPicker + setSettingsPrompt and read live
6
+ * store state, so they can't be pure. Every function body is the original App
7
+ * logic verbatim, with closure identifiers threaded through the factory
8
+ * argument. `disabledSkills` is read fresh via getDisabledSkills() so toggles
9
+ * observe the latest state at call time. openMcpPicker is exposed for the
10
+ * plugin-detail enable-mcp path (also aliased on the App body).
11
+ */
12
+ export function createExtensionPickers({
13
+ store,
14
+ theme,
15
+ clean,
16
+ copyToClipboard,
17
+ setPicker,
18
+ setProviderPrompt,
19
+ setChannelPrompt,
20
+ setHookPrompt,
21
+ setSettingsPrompt,
22
+ getDisabledSkills,
23
+ setDisabledSkills,
24
+ }) {
25
+ const mcpStatus = () => {
26
+ let status;
27
+ try {
28
+ status = store.mcpStatus?.() || { servers: [] };
29
+ } catch (e) {
30
+ store.pushNotice(`mcp status failed: ${e?.message || e}`, 'error');
31
+ return null;
32
+ }
33
+ return { ...status, servers: status.servers || [] };
34
+ };
35
+
36
+ const openMcpServersPicker = (options = {}) => {
37
+ const status = mcpStatus();
38
+ if (!status) return;
39
+ const servers = status.servers || [];
40
+ const items = [];
41
+ if (servers.length === 0) {
42
+ items.push({
43
+ value: 'empty',
44
+ label: 'No MCP servers',
45
+ description: 'no configured MCP servers',
46
+ _action: 'noop',
47
+ });
48
+ }
49
+ for (const server of servers) {
50
+ const enabled = server.enabled !== false;
51
+ items.push({
52
+ value: `server:${server.name}`,
53
+ label: server.name,
54
+ marker: enabled ? '●' : '○',
55
+ markerColor: enabled ? theme.success : theme.inactive,
56
+ description: `${server.status || 'unknown'} · ${server.transport || 'unknown'} · ${server.toolCount || 0} tools${server.error ? ` · ${server.error}` : ''}`,
57
+ _action: 'server',
58
+ _server: server,
59
+ _enabled: enabled,
60
+ });
61
+ }
62
+ setProviderPrompt(null);
63
+ setChannelPrompt(null);
64
+ setHookPrompt(null);
65
+ setSettingsPrompt(null);
66
+ const toggleServer = (item) => {
67
+ if (item._action !== 'server' || !item._server?.name) return;
68
+ void store.setMcpServerEnabled?.(item._server.name, !item._enabled)
69
+ .then(() => openMcpServersPicker({ highlightValue: `server:${item._server.name}` }))
70
+ .catch((e) => store.pushNotice(`mcp toggle failed: ${e?.message || e}`, 'error'));
71
+ };
72
+ setPicker({
73
+ title: 'MCP servers',
74
+ description: 'Enable or disable configured MCP servers.',
75
+ initialIndex: Math.max(0, items.findIndex((entry) => entry.value === options?.highlightValue)),
76
+ items,
77
+ onSelect: (_value, item) => toggleServer(item),
78
+ onLeft: (item) => toggleServer(item),
79
+ onRight: (item) => toggleServer(item),
80
+ onCancel: () => {
81
+ setPicker(null);
82
+ },
83
+ });
84
+ };
85
+
86
+ const openMcpPicker = () => {
87
+ openMcpServersPicker();
88
+ };
89
+
90
+ const skillsStatus = () => {
91
+ let status;
92
+ try {
93
+ status = store.skillsStatus?.() || { skills: [] };
94
+ } catch (e) {
95
+ store.pushNotice(`skills status failed: ${e?.message || e}`, 'error');
96
+ return null;
97
+ }
98
+ return { ...status, skills: status.skills || [] };
99
+ };
100
+
101
+ const openProjectSkillsPicker = () => {
102
+ const status = skillsStatus();
103
+ if (!status) return;
104
+ const skills = status.skills || [];
105
+ const items = [];
106
+ if (skills.length === 0) {
107
+ items.push({
108
+ value: 'empty',
109
+ label: 'No project skills',
110
+ description: 'no project skills available',
111
+ _action: 'noop',
112
+ });
113
+ }
114
+ for (const skill of skills) {
115
+ items.push({
116
+ value: skill.name,
117
+ label: skill.name,
118
+ description: `${skill.source || 'skill'} · ${skill.description || skill.filePath || ''}`,
119
+ _action: 'view',
120
+ _skill: skill,
121
+ });
122
+ }
123
+ setProviderPrompt(null);
124
+ setChannelPrompt(null);
125
+ setHookPrompt(null);
126
+ setSettingsPrompt(null);
127
+ setPicker({
128
+ title: 'Project skills',
129
+ description: 'Skills bundled with this project.',
130
+ items,
131
+ onSelect: (_value, item) => {
132
+ setPicker(null);
133
+ if (item._action !== 'view') return;
134
+ openSkillDetailPicker(item._skill);
135
+ },
136
+ onCancel: () => {
137
+ setPicker(null);
138
+ void openSkillsPicker();
139
+ },
140
+ });
141
+ };
142
+
143
+ const openSkillsPicker = (options = {}) => {
144
+ const status = skillsStatus();
145
+ if (!status) return;
146
+ const skills = status.skills || [];
147
+ const disabledSet = options.disabledOverride instanceof Set ? options.disabledOverride : getDisabledSkills();
148
+ const items = [];
149
+ if (skills.length === 0) {
150
+ items.push({
151
+ value: 'empty',
152
+ label: 'No skills',
153
+ description: 'no project skills available',
154
+ _action: 'noop',
155
+ });
156
+ }
157
+ for (const skill of skills) {
158
+ const enabled = !disabledSet.has(skill.name);
159
+ items.push({
160
+ value: skill.name,
161
+ label: skill.name,
162
+ marker: enabled ? '●' : '○',
163
+ markerColor: enabled ? theme.success : theme.inactive,
164
+ description: `${skill.source || 'skill'} · ${skill.description || skill.filePath || ''}`,
165
+ _action: 'skill',
166
+ _skill: skill,
167
+ _enabled: enabled,
168
+ });
169
+ }
170
+ setProviderPrompt(null);
171
+ setChannelPrompt(null);
172
+ setHookPrompt(null);
173
+ setSettingsPrompt(null);
174
+ const toggleSkill = (item) => {
175
+ if (item._action !== 'skill' || !item._skill?.name) return;
176
+ const name = item._skill.name;
177
+ const next = new Set(disabledSet);
178
+ if (item._enabled) next.add(name); else next.delete(name);
179
+ setDisabledSkills(next);
180
+ store.pushNotice(`skill ${item._enabled ? 'disabled' : 'enabled'}: ${name}`, 'info');
181
+ openSkillsPicker({ highlightValue: name, disabledOverride: next });
182
+ };
183
+ setPicker({
184
+ title: 'Skills',
185
+ description: 'Enable or disable project skills.',
186
+ initialIndex: Math.max(0, items.findIndex((entry) => entry.value === options.highlightValue)),
187
+ items,
188
+ onSelect: (_value, item) => toggleSkill(item),
189
+ onLeft: (item) => toggleSkill(item),
190
+ onRight: (item) => toggleSkill(item),
191
+ onCancel: () => {
192
+ setPicker(null);
193
+ },
194
+ });
195
+ };
196
+
197
+ const openSkillDetailPicker = (skill) => {
198
+ const disabled = getDisabledSkills().has(skill.name);
199
+ setPicker({
200
+ title: `Skill · ${skill.name}`,
201
+ description: clean(skill.description) || 'Enable, disable, or run this skill.',
202
+ items: [
203
+ {
204
+ value: 'use',
205
+ label: 'Use skill',
206
+ description: disabled ? 'enable this skill first' : 'write a request with this skill',
207
+ _action: disabled ? 'noop' : 'use',
208
+ },
209
+ {
210
+ value: disabled ? 'enable' : 'disable',
211
+ label: disabled ? 'Enable skill' : 'Disable skill',
212
+ description: disabled ? 'show and allow this skill in the TUI' : 'hide use action until re-enabled',
213
+ _action: disabled ? 'enable' : 'disable',
214
+ },
215
+ ],
216
+ onSelect: (_value, item) => {
217
+ setPicker(null);
218
+ if (item._action === 'enable') {
219
+ setDisabledSkills((current) => {
220
+ const next = new Set(current);
221
+ next.delete(skill.name);
222
+ return next;
223
+ });
224
+ store.pushNotice(`skill enabled: ${skill.name}`, 'info');
225
+ openSkillsPicker();
226
+ return;
227
+ }
228
+ if (item._action === 'disable') {
229
+ setDisabledSkills((current) => {
230
+ const next = new Set(current);
231
+ next.add(skill.name);
232
+ return next;
233
+ });
234
+ store.pushNotice(`skill disabled: ${skill.name}`, 'info');
235
+ openSkillsPicker();
236
+ return;
237
+ }
238
+ if (item._action === 'use') {
239
+ setSettingsPrompt({
240
+ kind: 'skill-use',
241
+ label: `Skill · ${skill.name}`,
242
+ hint: 'Write the request to run with this skill.',
243
+ skillName: skill.name,
244
+ });
245
+ return;
246
+ }
247
+ },
248
+ onCancel: () => {
249
+ setPicker(null);
250
+ void openSkillsPicker();
251
+ },
252
+ });
253
+ };
254
+
255
+ const pluginStatus = () => {
256
+ let status;
257
+ try {
258
+ status = store.pluginsStatus?.() || { plugins: [] };
259
+ } catch (e) {
260
+ store.pushNotice(`plugins status failed: ${e?.message || e}`, 'error');
261
+ return null;
262
+ }
263
+ return { ...status, plugins: status.plugins || [] };
264
+ };
265
+
266
+ const beginAddPlugin = () => {
267
+ setPicker(null);
268
+ setSettingsPrompt({ kind: 'plugin-add', label: 'Plugin URL', hint: 'Git URL, owner/repo, or local path' });
269
+ };
270
+
271
+ const openPluginDetailPicker = (p) => {
272
+ setPicker({
273
+ title: p.title || p.name,
274
+ description: clean(p.description) || 'Update, MCP, or uninstall this plugin.',
275
+ items: [
276
+ {
277
+ value: 'info',
278
+ label: 'Plugin info',
279
+ description: `${p.sourceType || p.source}${p.version ? ` · ${p.version}` : ''} · skills ${p.skillCount || 0}`,
280
+ _action: 'info',
281
+ },
282
+ {
283
+ value: 'update',
284
+ label: p.sourceType === 'local' ? 'Refresh metadata' : 'Update plugin',
285
+ description: p.sourceType === 'local' ? 'rescan local plugin manifest' : 'pull latest from source URL',
286
+ _action: 'update',
287
+ },
288
+ {
289
+ value: 'enable-mcp',
290
+ label: p.mcpScript ? (p.mcpEnabled ? 'Refresh MCP server' : 'Enable MCP server') : 'No MCP script',
291
+ description: p.mcpScript ? `${p.mcpServerName || 'plugin-mcp'} · ${p.mcpEnabled ? 'configured' : p.mcpScript}` : 'plugin does not expose scripts/run-mcp.mjs or mcp/server.mjs',
292
+ _action: p.mcpScript ? 'enable-mcp' : 'noop',
293
+ },
294
+ {
295
+ value: 'copy-root',
296
+ label: 'Copy root path',
297
+ description: p.root,
298
+ _action: 'copy-root',
299
+ },
300
+ {
301
+ value: 'copy-mcp-name',
302
+ label: p.mcpScript ? 'Copy MCP server name' : 'No MCP server name',
303
+ description: p.mcpServerName || '',
304
+ _action: p.mcpScript ? 'copy-mcp-name' : 'noop',
305
+ },
306
+ {
307
+ value: 'uninstall',
308
+ label: 'Uninstall plugin',
309
+ description: p.managed === false ? 'remove from registry only' : 'remove registry entry and installed files',
310
+ _action: 'uninstall',
311
+ },
312
+ ],
313
+ onSelect: (_detailValue, detail) => {
314
+ setPicker(null);
315
+ if (detail._action === 'info') {
316
+ store.pushNotice([
317
+ `${p.title || p.name}${p.version ? ` ${p.version}` : ''}`,
318
+ `source: ${p.sourceType || p.source}${p.sourceUrl ? ` / ${p.sourceUrl}` : ''}`,
319
+ `skills: ${p.skillCount || 0}`,
320
+ `mcp: ${p.mcpScript ? `${p.mcpEnabled ? 'enabled' : 'available'} (${p.mcpServerName || 'plugin-mcp'})` : '(none)'}`,
321
+ `root: ${p.root}`,
322
+ p.description ? `\n${p.description}` : '',
323
+ ].filter(Boolean).join('\n'), 'info');
324
+ return;
325
+ }
326
+ if (detail._action === 'update') {
327
+ void store.updatePlugin?.(p)
328
+ .then(() => openInstalledPluginsPicker())
329
+ .catch((e) => store.pushNotice(`plugin update failed: ${e?.message || e}`, 'error'));
330
+ return;
331
+ }
332
+ if (detail._action === 'enable-mcp') {
333
+ void store.enablePluginMcp?.(p)
334
+ .then(() => openMcpPicker())
335
+ .catch((e) => store.pushNotice(`plugin MCP enable failed: ${e?.message || e}`, 'error'));
336
+ return;
337
+ }
338
+ if (detail._action === 'copy-root') {
339
+ void copyToClipboard(p.root)
340
+ .then(() => store.pushNotice(`copied plugin root: ${p.name}`, 'plain'))
341
+ .catch((e) => store.pushNotice(`copy failed: ${e?.message || e}`, 'error'));
342
+ return;
343
+ }
344
+ if (detail._action === 'copy-mcp-name') {
345
+ void copyToClipboard(p.mcpServerName || '')
346
+ .then(() => store.pushNotice(`copied plugin MCP server: ${p.mcpServerName}`, 'plain'))
347
+ .catch((e) => store.pushNotice(`copy failed: ${e?.message || e}`, 'error'));
348
+ return;
349
+ }
350
+ if (detail._action === 'uninstall') {
351
+ void store.removePlugin?.(p)
352
+ .then(() => openInstalledPluginsPicker())
353
+ .catch((e) => store.pushNotice(`plugin uninstall failed: ${e?.message || e}`, 'error'));
354
+ }
355
+ },
356
+ onCancel: () => {
357
+ setPicker(null);
358
+ void openInstalledPluginsPicker();
359
+ },
360
+ });
361
+ };
362
+
363
+ const openInstalledPluginsPicker = () => {
364
+ const status = pluginStatus();
365
+ if (!status) return;
366
+ const plugins = status.plugins || [];
367
+ const items = [];
368
+ if (plugins.length === 0) {
369
+ items.push({
370
+ value: 'empty',
371
+ label: 'No installed plugins',
372
+ description: 'Esc back · add from Plugins > Add plugin',
373
+ _action: 'noop',
374
+ });
375
+ }
376
+ for (const plugin of plugins) {
377
+ items.push({
378
+ value: `${plugin.id || plugin.name}:${plugin.version || ''}`,
379
+ label: plugin.title || plugin.name,
380
+ description: `${plugin.sourceType || plugin.source}${plugin.version ? ` · ${plugin.version}` : ''} · skills ${plugin.skillCount || 0}${plugin.mcpScript ? ` · mcp ${plugin.mcpEnabled ? 'enabled' : plugin.mcpScript}` : ''}`,
381
+ _action: 'plugin',
382
+ _plugin: plugin,
383
+ });
384
+ }
385
+ setProviderPrompt(null);
386
+ setChannelPrompt(null);
387
+ setHookPrompt(null);
388
+ setSettingsPrompt(null);
389
+ setPicker({
390
+ title: 'Installed plugins',
391
+ description: 'Open an installed plugin to manage it.',
392
+ items,
393
+ onSelect: (_value, item) => {
394
+ setPicker(null);
395
+ if (item._action !== 'plugin') return;
396
+ openPluginDetailPicker(item._plugin);
397
+ },
398
+ onCancel: () => {
399
+ setPicker(null);
400
+ void openPluginsPicker();
401
+ },
402
+ });
403
+ };
404
+
405
+ const openPluginsPicker = () => {
406
+ const status = pluginStatus();
407
+ if (!status) return;
408
+ setProviderPrompt(null);
409
+ setChannelPrompt(null);
410
+ setHookPrompt(null);
411
+ setSettingsPrompt(null);
412
+ setPicker({
413
+ title: 'Plugins',
414
+ description: 'Add or manage local plugin integrations.',
415
+ items: [
416
+ {
417
+ value: 'installed',
418
+ label: 'Installed plugins',
419
+ description: `${status.count || 0} installed`,
420
+ _action: 'installed',
421
+ },
422
+ {
423
+ value: 'add',
424
+ label: 'Add plugin',
425
+ description: 'Git URL, owner/repo, or local path',
426
+ _action: 'add',
427
+ },
428
+ ],
429
+ onSelect: (_value, item) => {
430
+ setPicker(null);
431
+ if (item._action === 'installed') {
432
+ openInstalledPluginsPicker();
433
+ return;
434
+ }
435
+ if (item._action === 'add') beginAddPlugin();
436
+ },
437
+ onCancel: () => {
438
+ setPicker(null);
439
+ },
440
+ });
441
+ };
442
+
443
+ const openHooksPicker = () => {
444
+ let status;
445
+ try {
446
+ status = store.hooksStatus?.() || { events: [], recent: [] };
447
+ } catch (e) {
448
+ store.pushNotice(`hooks status failed: ${e?.message || e}`, 'error');
449
+ return;
450
+ }
451
+ const rules = status.rules || [];
452
+ const items = [
453
+ ...(rules.length ? rules.map((rule) => ({
454
+ value: `rule:${rule.index}`,
455
+ label: `${rule.tool} -> ${rule.action}`,
456
+ marker: rule.enabled ? '●' : '○',
457
+ markerColor: rule.enabled ? theme.success : theme.inactive,
458
+ description: `${rule.match ? `match ${rule.match} · ` : ''}${rule.reason || 'Enter toggle'}`,
459
+ _action: 'rule',
460
+ _rule: rule,
461
+ })) : [{
462
+ value: 'rules:none',
463
+ label: 'No rules',
464
+ description: status.rulesPath || 'hooks.json not configured',
465
+ _action: 'noop',
466
+ }]),
467
+ ];
468
+ setProviderPrompt(null);
469
+ setChannelPrompt(null);
470
+ setHookPrompt(null);
471
+ setSettingsPrompt(null);
472
+ const toggleRule = (item) => {
473
+ if (item._action !== 'rule') return;
474
+ try {
475
+ store.setHookRuleEnabled?.(item._rule.index, !item._rule.enabled);
476
+ void openHooksPicker();
477
+ } catch (e) {
478
+ store.pushNotice(`hook toggle failed: ${e?.message || e}`, 'error');
479
+ }
480
+ };
481
+ setPicker({
482
+ title: 'Hooks',
483
+ description: 'Before-tool hook rules; Enter toggles a rule.',
484
+ items,
485
+ onSelect: (_value, item) => toggleRule(item),
486
+ onLeft: (item) => toggleRule(item),
487
+ onRight: (item) => toggleRule(item),
488
+ onCancel: () => {
489
+ setPicker(null);
490
+ },
491
+ });
492
+ };
493
+
494
+ return {
495
+ openMcpServersPicker,
496
+ openMcpPicker,
497
+ openProjectSkillsPicker,
498
+ openSkillsPicker,
499
+ openSkillDetailPicker,
500
+ beginAddPlugin,
501
+ openPluginDetailPicker,
502
+ openInstalledPluginsPicker,
503
+ openPluginsPicker,
504
+ openHooksPicker,
505
+ };
506
+ }
@@ -0,0 +1,193 @@
1
+ /**
2
+ * input-parsers.mjs — pure text-to-structure parsers for slash-command inputs
3
+ * (hook rules, MCP servers, skills, memory commands + memory row tables).
4
+ * Extracted verbatim from App.jsx; no hooks, no App state, no closures.
5
+ */
6
+ export function parseHookRuleInput(text) {
7
+ const parts = String(text || '').split('|').map((part) => part.trim());
8
+ const [tool, actionRaw, match, reason, patchText] = parts;
9
+ const action = String(actionRaw || '').toLowerCase();
10
+ if (!tool || !action) return { error: 'usage: tool | allow|deny|modify | match(optional) | reason(optional) | json patch(optional)' };
11
+ if (!['allow', 'deny', 'block', 'modify', 'rewrite'].includes(action)) {
12
+ return { error: 'hook action must be allow, deny, block, modify, or rewrite' };
13
+ }
14
+ const rule = { tool, action };
15
+ if (match) rule.match = match;
16
+ if (reason) rule.reason = reason;
17
+ if (patchText) {
18
+ try {
19
+ const patch = JSON.parse(patchText);
20
+ if (!patch || typeof patch !== 'object' || Array.isArray(patch)) return { error: 'json patch must be an object' };
21
+ rule.patch = patch;
22
+ } catch (e) {
23
+ return { error: `invalid json patch: ${e?.message || e}` };
24
+ }
25
+ }
26
+ if ((action === 'modify' || action === 'rewrite') && !rule.patch) {
27
+ return { error: 'modify/rewrite needs a json patch object in the last field' };
28
+ }
29
+ return { rule };
30
+ }
31
+
32
+ export function parseMcpServerInput(text) {
33
+ const parts = String(text || '').split('|').map((part) => part.trim());
34
+ const [name, commandOrUrl, argsText = '', cwd = ''] = parts;
35
+ if (!name || !commandOrUrl) return { error: 'usage: name | command-or-url | args(optional) | cwd(optional)' };
36
+ if (/^(?:https?|wss?):\/\//i.test(commandOrUrl)) return { server: { name, url: commandOrUrl } };
37
+ return {
38
+ server: {
39
+ name,
40
+ command: commandOrUrl,
41
+ args: argsText.split(/\s+/).filter(Boolean),
42
+ cwd,
43
+ },
44
+ };
45
+ }
46
+
47
+ export function parseSkillInput(text) {
48
+ const parts = String(text || '').split('|').map((part) => part.trim());
49
+ const [name, description = 'Project skill.'] = parts;
50
+ if (!name) return { error: 'usage: name | description(optional)' };
51
+ return { skill: { name, description } };
52
+ }
53
+
54
+ export function parseMemoryCommand(text) {
55
+ const parts = String(text || '').trim().split(/\s+/).filter(Boolean);
56
+ const action = parts[0] || 'status';
57
+ const out = { action };
58
+ for (const part of parts.slice(1)) {
59
+ const [key, ...rest] = part.split('=');
60
+ if (!key || rest.length === 0) continue;
61
+ const raw = rest.join('=');
62
+ const num = Number(raw);
63
+ out[key] = Number.isFinite(num) && raw.trim() !== '' ? num : raw;
64
+ }
65
+ return out;
66
+ }
67
+
68
+ export function parseMemoryStatusRows(text) {
69
+ return String(text || '')
70
+ .split('\n')
71
+ .map((line) => line.trim())
72
+ .filter(Boolean)
73
+ .map((line, index) => {
74
+ const sep = line.indexOf(':');
75
+ const label = sep === -1 ? line : line.slice(0, sep);
76
+ const description = sep === -1 ? '' : line.slice(sep + 1).trim();
77
+ return {
78
+ value: `status-${index}`,
79
+ label,
80
+ description,
81
+ _line: line,
82
+ };
83
+ });
84
+ }
85
+
86
+ export function parseMemoryCoreRows(text) {
87
+ let currentProjectId = null;
88
+ return String(text || '')
89
+ .split('\n')
90
+ .filter((line) => line.trim())
91
+ .map((line, index) => {
92
+ const raw = line.trim();
93
+ if (raw.endsWith(':') && !raw.includes('id=')) {
94
+ const label = raw.slice(0, -1);
95
+ currentProjectId = label === 'COMMON' ? null : label;
96
+ // Group headers (COMMON / project slug) are scope markers, not
97
+ // selectable rows — drop them and carry the scope on each entry's
98
+ // meta column instead (blank for common).
99
+ return null;
100
+ }
101
+ const match = raw.match(/^id=(\d+)\s+\[([^\]]*)\]\s+(.+?)(?:\s+—\s+(.+))?$/);
102
+ if (match) {
103
+ const [, id, category, element, summary = ''] = match;
104
+ return {
105
+ value: `core-${id}`,
106
+ // Display is summary-first: session injection only ever uses the
107
+ // summary sentence (buildSessionCoreMemoryPayload), so id/category/
108
+ // element are UI noise. They stay in the hidden _fields for
109
+ // edit/delete plumbing.
110
+ label: `#${id}`,
111
+ meta: currentProjectId || '',
112
+ description: summary || element,
113
+ _line: raw,
114
+ _action: 'core-entry',
115
+ _id: Number(id),
116
+ _category: category,
117
+ _element: element,
118
+ _summary: summary || element,
119
+ _projectId: currentProjectId,
120
+ // Raw pre-edit values, distinct from the (possibly later-mutated)
121
+ // _element/_summary above -- beginEditCoreMemory reads these to
122
+ // decide whether the row's element duplicated its summary (single-
123
+ // sentence entry) before deciding whether to touch element on edit.
124
+ _origElement: element,
125
+ _origSummary: summary,
126
+ };
127
+ }
128
+ return {
129
+ value: `core-${index}`,
130
+ label: raw,
131
+ description: '',
132
+ _line: raw,
133
+ };
134
+ })
135
+ .filter(Boolean);
136
+ }
137
+
138
+ export function parseMemoryCandidateRows(text) {
139
+ const trimmed = String(text || '').trim();
140
+ // op:'candidates' returns this exact sentinel (index.mjs ~3158) when the
141
+ // resolved scope has none — treat as an empty list, not an inert text row.
142
+ if (!trimmed || /^core candidates:\s*none$/i.test(trimmed)) return [];
143
+ // Backend row shape (index.mjs ~3164), one candidate per line, no group
144
+ // headers:
145
+ // id=<n> project=<COMMON|slug> [<category>] score=<x.xx|-> <element> — <summary> (<reason>)
146
+ const rowPattern = /^id=(\d+)\s+project=(\S+)\s+\[([^\]]*)\]\s+score=(\S+)\s+(.+?)\s+—\s+(.+?)\s+\(([^)]*)\)$/;
147
+ return trimmed
148
+ .split('\n')
149
+ .filter((line) => line.trim())
150
+ .map((line, index) => {
151
+ const raw = line.trim();
152
+ const match = raw.match(rowPattern);
153
+ if (match) {
154
+ const [, id, project, category, score, element, summary, reason] = match;
155
+ return {
156
+ value: `candidate-${id}`,
157
+ label: `#${id} [${category}] ${element}`,
158
+ meta: project === 'COMMON' ? 'common' : project,
159
+ description: `${summary}${score !== '-' ? ` (score ${score})` : ''} — ${reason}`,
160
+ _line: raw,
161
+ _action: 'candidate-entry',
162
+ _id: Number(id),
163
+ _projectId: project === 'COMMON' ? null : project,
164
+ };
165
+ }
166
+ return {
167
+ value: `candidate-${index}`,
168
+ label: raw,
169
+ description: '',
170
+ _line: raw,
171
+ };
172
+ });
173
+ }
174
+
175
+ // Backend "core" op errors are flattened to plain text by store.memoryControl
176
+ // (isError is dropped -- see engine.mjs memoryControl / toolResponseText in
177
+ // mixdog-session-runtime.mjs). Success text always uses a past-tense verb
178
+ // ("core added/edited/deleted/promoted...", "core candidate dismissed...");
179
+ // every declared failure in index.mjs's core-op handling uses the op word
180
+ // followed by either ":" (validation message) or " failed" (caught
181
+ // exception) -- e.g. "core add: project_id required...", "core edit failed:
182
+ // no entry with id=5". That shape is used here to recover the error signal
183
+ // since store.memoryControl resolves instead of rejecting on isError.
184
+ export function memoryCoreResultErrorText(text) {
185
+ const value = String(text || '').trim();
186
+ if (/^core (add|edit|delete|promote|dismiss)(:| failed)/i.test(value)) return value;
187
+ // Broader catch-all for other flattened "core" failures that don't name an
188
+ // op word right after "core" (e.g. "core: memory data dir is not
189
+ // initialized", "core requires op: ..."), plus any bare error/failed lead-in.
190
+ if (/^core:.*(not initialized|failed|error)/i.test(value)) return value;
191
+ if (/^(error|failed)\b/i.test(value)) return value;
192
+ return null;
193
+ }