mixdog 0.9.3 → 0.9.5

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 (382) hide show
  1. package/README.md +112 -38
  2. package/package.json +10 -3
  3. package/scripts/bench/lead-review-tasks-r3.json +20 -0
  4. package/scripts/bench/lead-review-tasks.json +20 -0
  5. package/scripts/bench/r4-mixed-tasks.json +20 -0
  6. package/scripts/bench/r5-orchestrated-task.json +7 -0
  7. package/scripts/bench/review-tasks.json +20 -0
  8. package/scripts/bench/round-codex.json +114 -0
  9. package/scripts/bench/round-mixdog-lead-r3.json +269 -0
  10. package/scripts/bench/round-mixdog-lead.json +269 -0
  11. package/scripts/bench/round-mixdog.json +126 -0
  12. package/scripts/bench/round-r10-bigsample.json +679 -0
  13. package/scripts/bench/round-r11-codexalign.json +257 -0
  14. package/scripts/bench/round-r13-clientmeta.json +464 -0
  15. package/scripts/bench/round-r14-betafeatures.json +466 -0
  16. package/scripts/bench/round-r15-fulldefault.json +462 -0
  17. package/scripts/bench/round-r16-sessionid.json +466 -0
  18. package/scripts/bench/round-r17-wirebytes.json +456 -0
  19. package/scripts/bench/round-r18-prewarm.json +468 -0
  20. package/scripts/bench/round-r19-clean.json +472 -0
  21. package/scripts/bench/round-r20-prewarm-clean.json +475 -0
  22. package/scripts/bench/round-r21-delta-retry.json +473 -0
  23. package/scripts/bench/round-r22-full-probe.json +693 -0
  24. package/scripts/bench/round-r23-itemprobe.json +701 -0
  25. package/scripts/bench/round-r24-shapefix.json +677 -0
  26. package/scripts/bench/round-r25-serial.json +464 -0
  27. package/scripts/bench/round-r26-parallel3.json +671 -0
  28. package/scripts/bench/round-r27-parallel10.json +894 -0
  29. package/scripts/bench/round-r28-parallel10-stagger.json +882 -0
  30. package/scripts/bench/round-r29-parallel10-stagger166.json +886 -0
  31. package/scripts/bench/round-r30-instid.json +253 -0
  32. package/scripts/bench/round-r31-upgradeprobe.json +256 -0
  33. package/scripts/bench/round-r32-vs-codex-lead.json +254 -0
  34. package/scripts/bench/round-r33-vs-codex-codex.json +115 -0
  35. package/scripts/bench/round-r34-orchestrated.json +120 -0
  36. package/scripts/bench/round-r35-orchestrated-codex.json +61 -0
  37. package/scripts/bench/round-r36-orchestrated-capped.json +128 -0
  38. package/scripts/bench/round-r4-codex.json +114 -0
  39. package/scripts/bench/round-r4-mixed.json +225 -0
  40. package/scripts/bench/round-r5-gpt-lead.json +259 -0
  41. package/scripts/bench/round-r6-codex.json +114 -0
  42. package/scripts/bench/round-r6-solo.json +257 -0
  43. package/scripts/bench/round-r7-full.json +254 -0
  44. package/scripts/bench/round-r8-fulldefault.json +255 -0
  45. package/scripts/bench-run.mjs +251 -32
  46. package/scripts/freevar-smoke.mjs +95 -0
  47. package/scripts/internal-comms-bench.mjs +3 -4
  48. package/scripts/internal-comms-smoke.mjs +10 -9
  49. package/scripts/model-catalog-audit.mjs +209 -0
  50. package/scripts/model-list-sanitize-test.mjs +37 -0
  51. package/scripts/mouse-probe.mjs +45 -0
  52. package/scripts/output-style-bench.mjs +13 -6
  53. package/scripts/output-style-smoke.mjs +4 -4
  54. package/scripts/provider-toolcall-test.mjs +7 -3
  55. package/scripts/recall-bench.mjs +76 -13
  56. package/scripts/recall-quality-cases.json +12 -0
  57. package/scripts/recall-usecase-cases.json +18 -0
  58. package/scripts/session-bench.mjs +152 -6
  59. package/scripts/tool-smoke.mjs +25 -65
  60. package/scripts/tui-render-smoke.mjs +90 -0
  61. package/scripts/webhook-smoke.mjs +208 -0
  62. package/src/agents/debugger/AGENT.md +4 -1
  63. package/src/agents/heavy-worker/AGENT.md +9 -8
  64. package/src/agents/maintainer/AGENT.md +4 -0
  65. package/src/agents/reviewer/AGENT.md +2 -1
  66. package/src/agents/scheduler-task/AGENT.md +2 -3
  67. package/src/agents/webhook-handler/AGENT.md +2 -3
  68. package/src/agents/worker/AGENT.md +10 -7
  69. package/src/app.mjs +12 -1
  70. package/src/headless-role.mjs +7 -1
  71. package/src/lib/rules-builder.cjs +4 -0
  72. package/src/mixdog-session-runtime.mjs +647 -2056
  73. package/src/output-styles/default.md +30 -9
  74. package/src/output-styles/{oneline.md → extreme-minimal.md} +5 -4
  75. package/src/output-styles/minimal.md +8 -6
  76. package/src/output-styles/simple.md +21 -7
  77. package/src/rules/agent/00-common.md +6 -3
  78. package/src/rules/agent/30-explorer.md +16 -5
  79. package/src/rules/lead/01-general.md +5 -5
  80. package/src/rules/lead/lead-brief.md +15 -0
  81. package/src/rules/lead/lead-tool.md +6 -15
  82. package/src/rules/shared/01-tool.md +17 -21
  83. package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch.mjs +8 -3
  84. package/src/runtime/agent/orchestrator/agent-runtime/agent-loop-policy.mjs +25 -0
  85. package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +100 -23
  86. package/src/runtime/agent/orchestrator/agent-runtime/session-builder.mjs +6 -15
  87. package/src/runtime/agent/orchestrator/agent-trace-format.mjs +362 -0
  88. package/src/runtime/agent/orchestrator/agent-trace-io.mjs +410 -0
  89. package/src/runtime/agent/orchestrator/agent-trace.mjs +16 -735
  90. package/src/runtime/agent/orchestrator/config.mjs +69 -2
  91. package/src/runtime/agent/orchestrator/providers/anthropic-effort.mjs +62 -20
  92. package/src/runtime/agent/orchestrator/providers/anthropic-model-resolve.mjs +209 -0
  93. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-credentials.mjs +489 -0
  94. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +81 -1281
  95. package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +607 -0
  96. package/src/runtime/agent/orchestrator/providers/anthropic.mjs +32 -3
  97. package/src/runtime/agent/orchestrator/providers/codex-client-meta.mjs +81 -0
  98. package/src/runtime/agent/orchestrator/providers/gemini-cache.mjs +248 -0
  99. package/src/runtime/agent/orchestrator/providers/gemini-schema.mjs +303 -0
  100. package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +505 -0
  101. package/src/runtime/agent/orchestrator/providers/gemini.mjs +43 -1013
  102. package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +17 -3
  103. package/src/runtime/agent/orchestrator/providers/model-catalog.mjs +105 -11
  104. package/src/runtime/agent/orchestrator/providers/model-list-sanitize.mjs +356 -0
  105. package/src/runtime/agent/orchestrator/providers/openai-codex-model.mjs +108 -0
  106. package/src/runtime/agent/orchestrator/providers/openai-compat-trace.mjs +58 -0
  107. package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +368 -0
  108. package/src/runtime/agent/orchestrator/providers/openai-compat-xai.mjs +760 -0
  109. package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +40 -1143
  110. package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +740 -0
  111. package/src/runtime/agent/orchestrator/providers/openai-oauth-login.mjs +193 -0
  112. package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +349 -2131
  113. package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +143 -1002
  114. package/src/runtime/agent/orchestrator/providers/openai-ws-delta.mjs +229 -0
  115. package/src/runtime/agent/orchestrator/providers/openai-ws-events.mjs +67 -0
  116. package/src/runtime/agent/orchestrator/providers/openai-ws-pool.mjs +465 -0
  117. package/src/runtime/agent/orchestrator/providers/openai-ws-stream.mjs +1105 -0
  118. package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +2 -1
  119. package/src/runtime/agent/orchestrator/providers/provider-catalog-cache.mjs +80 -0
  120. package/src/runtime/agent/orchestrator/session/compact/budget.mjs +288 -0
  121. package/src/runtime/agent/orchestrator/session/compact/constants.mjs +85 -0
  122. package/src/runtime/agent/orchestrator/session/compact/engine.mjs +749 -0
  123. package/src/runtime/agent/orchestrator/session/compact/messages.mjs +82 -0
  124. package/src/runtime/agent/orchestrator/session/compact/summary-schema.mjs +315 -0
  125. package/src/runtime/agent/orchestrator/session/compact/summary.mjs +643 -0
  126. package/src/runtime/agent/orchestrator/session/compact/text-utils.mjs +326 -0
  127. package/src/runtime/agent/orchestrator/session/compact.mjs +40 -2282
  128. package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +14 -2
  129. package/src/runtime/agent/orchestrator/session/loop/completion-guards.mjs +61 -0
  130. package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +1 -3
  131. package/src/runtime/agent/orchestrator/session/loop/recall-fasttrack.mjs +275 -0
  132. package/src/runtime/agent/orchestrator/session/loop/steering-ladder.mjs +173 -0
  133. package/src/runtime/agent/orchestrator/session/loop/termination.mjs +58 -0
  134. package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +239 -0
  135. package/src/runtime/agent/orchestrator/session/loop.mjs +278 -402
  136. package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +471 -0
  137. package/src/runtime/agent/orchestrator/session/manager/context-meta.mjs +7 -4
  138. package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +12 -0
  139. package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +406 -0
  140. package/src/runtime/agent/orchestrator/session/manager/status-telemetry.mjs +80 -0
  141. package/src/runtime/agent/orchestrator/session/manager/usage-metrics.mjs +210 -0
  142. package/src/runtime/agent/orchestrator/session/manager.mjs +166 -1087
  143. package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +189 -0
  144. package/src/runtime/agent/orchestrator/session/store.mjs +74 -179
  145. package/src/runtime/agent/orchestrator/stall-policy.mjs +20 -1
  146. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +70 -20
  147. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +22 -2
  148. package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +35 -44
  149. package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +40 -0
  150. package/src/runtime/agent/orchestrator/tools/builtin/rg-runner.mjs +29 -0
  151. package/src/runtime/agent/orchestrator/tools/builtin/search-builders.mjs +8 -0
  152. package/src/runtime/agent/orchestrator/tools/builtin/search-path-diagnostics.mjs +126 -0
  153. package/src/runtime/agent/orchestrator/tools/builtin/search-tool.mjs +81 -92
  154. package/src/runtime/agent/orchestrator/tools/builtin/shell-job-paths.mjs +161 -0
  155. package/src/runtime/agent/orchestrator/tools/builtin/shell-job-process.mjs +108 -0
  156. package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +28 -265
  157. package/src/runtime/agent/orchestrator/tools/builtin.mjs +0 -6
  158. package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +57 -3
  159. package/src/runtime/agent/orchestrator/tools/code-graph/keyword-match.mjs +82 -0
  160. package/src/runtime/agent/orchestrator/tools/code-graph/search.mjs +10 -122
  161. package/src/runtime/agent/orchestrator/tools/code-graph/text-columns.mjs +45 -0
  162. package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +6 -6
  163. package/src/runtime/agent/orchestrator/tools/graph-binary-fetcher.mjs +6 -3
  164. package/src/runtime/agent/orchestrator/tools/patch/constants.mjs +9 -0
  165. package/src/runtime/agent/orchestrator/tools/patch/dispatch.mjs +171 -0
  166. package/src/runtime/agent/orchestrator/tools/patch/matcher.mjs +471 -0
  167. package/src/runtime/agent/orchestrator/tools/patch/native-server.mjs +436 -0
  168. package/src/runtime/agent/orchestrator/tools/patch/orchestrator.mjs +342 -0
  169. package/src/runtime/agent/orchestrator/tools/patch/parsing.mjs +359 -0
  170. package/src/runtime/agent/orchestrator/tools/patch/paths.mjs +340 -0
  171. package/src/runtime/agent/orchestrator/tools/patch/v4a-convert.mjs +643 -0
  172. package/src/runtime/agent/orchestrator/tools/patch.mjs +36 -2959
  173. package/src/runtime/agent/orchestrator/tools/progress-message.mjs +0 -21
  174. package/src/runtime/agent/orchestrator/tools/shell-command.mjs +9 -72
  175. package/src/runtime/agent/orchestrator/tools/shell-powershell.mjs +77 -0
  176. package/src/runtime/agent/orchestrator/tools/shell-state.mjs +154 -0
  177. package/src/runtime/channels/backends/discord-access.mjs +32 -0
  178. package/src/runtime/channels/backends/discord-attachments.mjs +65 -0
  179. package/src/runtime/channels/backends/discord-gateway.mjs +233 -0
  180. package/src/runtime/channels/backends/discord.mjs +27 -318
  181. package/src/runtime/channels/backends/telegram.mjs +8 -12
  182. package/src/runtime/channels/index.mjs +247 -701
  183. package/src/runtime/channels/lib/backend-dispatch.mjs +46 -0
  184. package/src/runtime/channels/lib/config.mjs +37 -149
  185. package/src/runtime/channels/lib/event-pipeline.mjs +22 -5
  186. package/src/runtime/channels/lib/event-queue.mjs +78 -13
  187. package/src/runtime/channels/lib/inbound-routing.mjs +74 -0
  188. package/src/runtime/channels/lib/interaction-workflows.mjs +5 -113
  189. package/src/runtime/channels/lib/output-forwarder.mjs +1 -1
  190. package/src/runtime/channels/lib/owner-heartbeat.mjs +75 -0
  191. package/src/runtime/channels/lib/parent-bridge.mjs +88 -0
  192. package/src/runtime/channels/lib/runtime-paths.mjs +14 -4
  193. package/src/runtime/channels/lib/scheduler.mjs +27 -113
  194. package/src/runtime/channels/lib/session-discovery.mjs +56 -4
  195. package/src/runtime/channels/lib/tool-dispatch.mjs +158 -0
  196. package/src/runtime/channels/lib/tool-format.mjs +1 -1
  197. package/src/runtime/channels/lib/transcript-discovery.mjs +4 -4
  198. package/src/runtime/channels/lib/voice-runtime-fetcher.mjs +6 -3
  199. package/src/runtime/channels/lib/voice-transcription.mjs +179 -0
  200. package/src/runtime/channels/lib/webhook/deliveries.mjs +313 -0
  201. package/src/runtime/channels/lib/webhook/log.mjs +42 -0
  202. package/src/runtime/channels/lib/webhook/ngrok.mjs +181 -0
  203. package/src/runtime/channels/lib/webhook/signature.mjs +60 -0
  204. package/src/runtime/channels/lib/webhook.mjs +43 -616
  205. package/src/runtime/channels/tool-defs.mjs +11 -130
  206. package/src/runtime/memory/index.mjs +210 -1948
  207. package/src/runtime/memory/lib/core-memory-store.mjs +5 -1
  208. package/src/runtime/memory/lib/cycle-llm-adapters.mjs +58 -0
  209. package/src/runtime/memory/lib/cycle-scheduler.mjs +497 -0
  210. package/src/runtime/memory/lib/embedding-warmup.mjs +58 -0
  211. package/src/runtime/memory/lib/ko-morph.mjs +195 -0
  212. package/src/runtime/memory/lib/memory-config-flags.mjs +91 -0
  213. package/src/runtime/memory/lib/memory-cycle.mjs +1 -1
  214. package/src/runtime/memory/lib/memory-cycle2-gate.mjs +515 -0
  215. package/src/runtime/memory/lib/memory-cycle2-mutations.mjs +324 -0
  216. package/src/runtime/memory/lib/memory-cycle2-shared.mjs +18 -0
  217. package/src/runtime/memory/lib/memory-cycle2.mjs +24 -842
  218. package/src/runtime/memory/lib/memory-embed.mjs +149 -0
  219. package/src/runtime/memory/lib/memory-process-lock.mjs +162 -0
  220. package/src/runtime/memory/lib/memory-recall-store.mjs +69 -12
  221. package/src/runtime/memory/lib/memory-text-utils.mjs +46 -0
  222. package/src/runtime/memory/lib/pg/supervisor.mjs +1 -1
  223. package/src/runtime/memory/lib/query-handlers.mjs +802 -0
  224. package/src/runtime/memory/lib/recall-format.mjs +55 -0
  225. package/src/runtime/memory/lib/runtime-fetcher.mjs +8 -3
  226. package/src/runtime/memory/lib/transcript-ingest.mjs +425 -0
  227. package/src/runtime/memory/tool-defs.mjs +5 -13
  228. package/src/runtime/search/lib/http-fetch.mjs +274 -0
  229. package/src/runtime/search/lib/ssrf-guard.mjs +333 -0
  230. package/src/runtime/search/lib/web-tools.mjs +24 -602
  231. package/src/runtime/shared/atomic-file.mjs +26 -1
  232. package/src/runtime/shared/config.mjs +14 -4
  233. package/src/runtime/shared/launcher-control.mjs +2 -2
  234. package/src/runtime/shared/markdown-frontmatter.mjs +19 -0
  235. package/src/runtime/shared/schedules-store.mjs +13 -3
  236. package/src/runtime/shared/tool-execution-contract.mjs +2 -2
  237. package/src/runtime/shared/tool-primitives.mjs +308 -0
  238. package/src/runtime/shared/tool-result-summary.mjs +515 -0
  239. package/src/runtime/shared/tool-surface.mjs +80 -898
  240. package/src/runtime/shared/transcript-writer.mjs +23 -0
  241. package/src/runtime/shared/update-checker.mjs +7 -4
  242. package/src/session-runtime/config-helpers.mjs +119 -2
  243. package/src/session-runtime/config-lifecycle.mjs +232 -0
  244. package/src/session-runtime/cwd-plugins.mjs +226 -0
  245. package/src/session-runtime/mcp-glue.mjs +177 -0
  246. package/src/session-runtime/model-recency.mjs +111 -0
  247. package/src/session-runtime/native-search.mjs +247 -0
  248. package/src/session-runtime/output-styles.mjs +11 -9
  249. package/src/session-runtime/prewarm.mjs +142 -0
  250. package/src/session-runtime/provider-models.mjs +278 -0
  251. package/src/session-runtime/provider-usage.mjs +120 -0
  252. package/src/session-runtime/quick-model-rows.mjs +205 -0
  253. package/src/session-runtime/quick-search-models.mjs +47 -0
  254. package/src/session-runtime/session-hooks.mjs +93 -0
  255. package/src/session-runtime/settings-api.mjs +352 -0
  256. package/src/session-runtime/tool-catalog.mjs +29 -29
  257. package/src/session-runtime/tool-defs.mjs +84 -0
  258. package/src/session-runtime/warmup-schedulers.mjs +201 -0
  259. package/src/session-runtime/workflow.mjs +1 -1
  260. package/src/standalone/agent-tool/helpers.mjs +237 -0
  261. package/src/standalone/agent-tool/notify.mjs +107 -0
  262. package/src/standalone/agent-tool/provider-init.mjs +143 -0
  263. package/src/standalone/agent-tool/render.mjs +152 -0
  264. package/src/standalone/agent-tool/tool-def.mjs +55 -0
  265. package/src/standalone/agent-tool.mjs +138 -669
  266. package/src/standalone/channel-admin.mjs +102 -90
  267. package/src/standalone/channel-worker.mjs +4 -7
  268. package/src/standalone/explore-tool.mjs +64 -14
  269. package/src/standalone/hook-bus/config.mjs +207 -0
  270. package/src/standalone/hook-bus/constants.mjs +90 -0
  271. package/src/standalone/hook-bus/handlers.mjs +481 -0
  272. package/src/standalone/hook-bus/payload.mjs +31 -0
  273. package/src/standalone/hook-bus/rules.mjs +77 -0
  274. package/src/standalone/hook-bus.mjs +77 -870
  275. package/src/standalone/memory-runtime-proxy.mjs +7 -0
  276. package/src/standalone/opencode-go-login.mjs +5 -1
  277. package/src/standalone/provider-admin.mjs +1 -16
  278. package/src/standalone/usage-dashboard.mjs +3 -1
  279. package/src/tui/App.jsx +1059 -8110
  280. package/src/tui/app/app-format.mjs +213 -0
  281. package/src/tui/app/channel-pickers.mjs +508 -0
  282. package/src/tui/app/clipboard.mjs +67 -0
  283. package/src/tui/app/core-memory-picker.mjs +210 -0
  284. package/src/tui/app/extension-pickers.mjs +506 -0
  285. package/src/tui/app/input-parsers.mjs +193 -0
  286. package/src/tui/app/maintenance-pickers.mjs +356 -0
  287. package/src/tui/app/model-options.mjs +334 -0
  288. package/src/tui/app/model-picker.mjs +365 -0
  289. package/src/tui/app/onboarding-steps.mjs +400 -0
  290. package/src/tui/app/project-picker.mjs +247 -0
  291. package/src/tui/app/provider-setup-picker.mjs +580 -0
  292. package/src/tui/app/resume-picker.mjs +55 -0
  293. package/src/tui/app/route-pickers.mjs +419 -0
  294. package/src/tui/app/settings-picker.mjs +489 -0
  295. package/src/tui/app/slash-commands.mjs +101 -0
  296. package/src/tui/app/slash-dispatch.mjs +427 -0
  297. package/src/tui/app/text-layout.mjs +46 -0
  298. package/src/tui/app/theme-effort-pickers.mjs +154 -0
  299. package/src/tui/app/transcript-window.mjs +677 -0
  300. package/src/tui/app/use-mouse-input.mjs +460 -0
  301. package/src/tui/app/use-prompt-handlers.mjs +310 -0
  302. package/src/tui/app/use-transcript-scroll.mjs +512 -0
  303. package/src/tui/app/use-transcript-window.mjs +607 -0
  304. package/src/tui/components/ConfirmBar.jsx +10 -7
  305. package/src/tui/components/Picker.jsx +64 -15
  306. package/src/tui/components/PromptInput.jsx +33 -102
  307. package/src/tui/components/SlashCommandPalette.jsx +8 -1
  308. package/src/tui/components/StatusLine.jsx +69 -15
  309. package/src/tui/components/TextEntryPanel.jsx +11 -0
  310. package/src/tui/components/ToolExecution.jsx +52 -594
  311. package/src/tui/components/TranscriptItem.jsx +105 -0
  312. package/src/tui/components/UsagePanel.jsx +18 -4
  313. package/src/tui/components/prompt-input/edit-helpers.mjs +72 -0
  314. package/src/tui/components/prompt-input/voice-indicator.mjs +39 -0
  315. package/src/tui/components/tool-execution/ResultBody.jsx +56 -0
  316. package/src/tui/components/tool-execution/surface-detail.mjs +405 -0
  317. package/src/tui/components/tool-execution/text-format.mjs +161 -0
  318. package/src/tui/display-width.mjs +20 -3
  319. package/src/tui/dist/index.mjs +13553 -12384
  320. package/src/tui/engine/agent-job-feed.mjs +133 -0
  321. package/src/tui/engine/notification-plan.mjs +76 -0
  322. package/src/tui/engine/render-timing.mjs +17 -0
  323. package/src/tui/engine/tool-approval.mjs +94 -0
  324. package/src/tui/engine/tool-card-results.mjs +234 -0
  325. package/src/tui/engine/tool-result-status.mjs +135 -0
  326. package/src/tui/engine.mjs +170 -574
  327. package/src/tui/figures.mjs +5 -0
  328. package/src/tui/index.jsx +65 -1
  329. package/src/tui/input-editing.mjs +2 -2
  330. package/src/tui/markdown/format-token.mjs +4 -1
  331. package/src/tui/statusline-ansi-bridge.mjs +11 -3
  332. package/src/tui/theme.mjs +6 -0
  333. package/src/ui/statusline-agents.mjs +213 -0
  334. package/src/ui/statusline-format.mjs +146 -0
  335. package/src/ui/statusline-segments.mjs +148 -0
  336. package/src/ui/statusline.mjs +77 -501
  337. package/src/ui/tool-card.mjs +0 -1
  338. package/src/vendor/statusline/bin/statusline-route.mjs +15 -2
  339. package/src/workflows/default/WORKFLOW.md +16 -18
  340. package/src/workflows/sequential/WORKFLOW.md +16 -18
  341. package/vendor/ink/build/display-width.js +19 -3
  342. package/vendor/ink/build/ink.js +112 -7
  343. package/vendor/ink/build/log-update.js +17 -3
  344. package/vendor/ink/build/wrap-text.js +125 -0
  345. package/scripts/_test-folder-dialog.mjs +0 -30
  346. package/scripts/fix-brief-fn.mjs +0 -35
  347. package/scripts/fix-format-tool-surface.mjs +0 -24
  348. package/scripts/fix-tool-exec-visible.mjs +0 -42
  349. package/scripts/patch-agent-brief.mjs +0 -48
  350. package/scripts/patch-app.mjs +0 -21
  351. package/scripts/patch-app2.mjs +0 -18
  352. package/scripts/patch-dist-brief.mjs +0 -96
  353. package/scripts/patch-tool-exec.mjs +0 -70
  354. package/src/examples/schedules/SCHEDULE.example.md +0 -32
  355. package/src/examples/webhooks/WEBHOOK.example.md +0 -40
  356. package/src/runtime/agent/orchestrator/session/manager.reactive-persist.test.mjs +0 -107
  357. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.test.mjs +0 -143
  358. package/src/runtime/agent/orchestrator/tools/builtin/diagnostics-tool.mjs +0 -285
  359. package/src/runtime/agent/orchestrator/tools/builtin/external-tool-adapters.test.mjs +0 -162
  360. package/src/runtime/agent/orchestrator/tools/builtin/open-config-tool.mjs +0 -26
  361. package/src/runtime/channels/lib/holidays.mjs +0 -138
  362. package/src/runtime/shared/channel-notification-routing.test.mjs +0 -45
  363. package/src/runtime/shared/task-notification-envelope.test.mjs +0 -107
  364. package/src/runtime/shared/tool-execution-contract.test.mjs +0 -183
  365. package/src/standalone/agent-task-status.test.mjs +0 -76
  366. package/src/tui/components/tool-output-format.test.mjs +0 -399
  367. package/src/tui/display-width.test.mjs +0 -35
  368. package/src/tui/engine-runtime-notification.test.mjs +0 -115
  369. package/src/tui/engine-tool-result-text.test.mjs +0 -75
  370. package/src/tui/input-editing.selection.test.mjs +0 -75
  371. package/src/tui/markdown/format-token.test.mjs +0 -354
  372. package/src/tui/markdown/render-ansi.test.mjs +0 -108
  373. package/src/tui/markdown/stream-fence.test.mjs +0 -26
  374. package/src/tui/markdown/streaming-markdown.test.mjs +0 -70
  375. package/src/tui/paste-fix.test.mjs +0 -119
  376. package/src/tui/prompt-history-store.test.mjs +0 -52
  377. package/src/tui/statusline-ansi-bridge.test.mjs +0 -159
  378. package/src/tui/transcript-tool-failures.test.mjs +0 -111
  379. package/src/ui/markdown.test.mjs +0 -70
  380. package/src/ui/statusline-context-label.test.mjs +0 -15
  381. package/src/vendor/statusline/bin/statusline-lib.mjs +0 -186
  382. 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
+ }