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
@@ -1,874 +1,81 @@
1
1
  import { existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from 'node:fs';
2
- import { dirname, join, resolve } from 'node:path';
3
- import { spawn } from 'node:child_process';
4
- import { listRegisteredPlugins } from './plugin-admin.mjs';
5
-
6
- const DEFAULT_EVENTS = Object.freeze([
7
- 'runtime:start',
8
- 'session:create',
9
- 'turn:start',
10
- 'turn:end',
11
- 'turn:error',
12
- 'tool:planned',
13
- 'tool:before',
14
- 'tool:ask',
15
- 'tool:deny',
16
- 'tool:modify',
17
- 'hook:error',
18
- 'SessionStart',
19
- 'UserPromptSubmit',
20
- 'PreToolUse',
21
- 'PostToolUse',
22
- 'Stop',
23
- ]);
24
-
25
- const DEFAULT_COMMAND_TIMEOUT_S = 600;
26
- const USER_PROMPT_TIMEOUT_S = 30;
27
- const MESSAGE_DISPLAY_TIMEOUT_S = 10;
28
- const DEFAULT_PROMPT_TIMEOUT_S = 30;
29
- const DEFAULT_AGENT_TIMEOUT_S = 60;
30
- const MAX_OUTPUT_CHARS = 10_000;
31
- const MAX_BUFFER_BYTES = 10 * 1024 * 1024;
32
-
33
- const TOOL_IF_EVENTS = new Set([
34
- 'PreToolUse',
35
- 'PostToolUse',
36
- 'PostToolUseFailure',
37
- 'PermissionRequest',
38
- 'PermissionDenied',
39
- ]);
40
-
41
- const NO_MATCHER_EVENTS = new Set([
42
- 'UserPromptSubmit',
43
- 'PostToolBatch',
44
- 'Stop',
45
- 'TeammateIdle',
46
- 'TaskCreated',
47
- 'TaskCompleted',
48
- 'WorktreeCreate',
49
- 'WorktreeRemove',
50
- 'CwdChanged',
51
- 'MessageDisplay',
52
- ]);
53
-
54
- const EXIT2_BLOCK_EVENTS = new Set([
55
- 'PreToolUse',
56
- 'PermissionRequest',
57
- 'UserPromptSubmit',
58
- 'UserPromptExpansion',
59
- 'Stop',
60
- 'SubagentStop',
61
- 'TeammateIdle',
62
- 'TaskCreated',
63
- 'TaskCompleted',
64
- 'ConfigChange',
65
- 'PostToolBatch',
66
- 'PreCompact',
67
- 'Elicitation',
68
- 'ElicitationResult',
69
- ]);
70
-
71
- const TOP_LEVEL_DECISION_EVENTS = new Set([
72
- 'UserPromptSubmit',
73
- 'UserPromptExpansion',
74
- 'PostToolUse',
75
- 'PostToolUseFailure',
76
- 'PostToolBatch',
77
- 'Stop',
78
- 'SubagentStop',
79
- 'ConfigChange',
80
- 'PreCompact',
81
- ]);
82
-
83
- const PLAIN_STDOUT_CONTEXT_EVENTS = new Set([
84
- 'SessionStart',
85
- 'UserPromptSubmit',
86
- 'UserPromptExpansion',
87
- ]);
88
-
89
- const SUPPORTED_HANDLER_TYPES = new Set(['command', 'http', 'mcp_tool', 'prompt']);
90
-
91
- function compactValue(value) {
92
- if (value == null) return value;
93
- if (typeof value === 'string') return value.length > 180 ? `${value.slice(0, 180)}...` : value;
94
- if (typeof value === 'number' || typeof value === 'boolean') return value;
95
- if (Array.isArray(value)) return value.slice(0, 8).map(compactValue);
96
- if (typeof value === 'object') {
97
- const out = {};
98
- for (const [key, val] of Object.entries(value).slice(0, 12)) {
99
- if (/token|secret|key|password/i.test(key)) {
100
- out[key] = '<redacted>';
101
- } else {
102
- out[key] = compactValue(val);
103
- }
104
- }
105
- return out;
106
- }
107
- return String(value);
108
- }
109
-
110
- function summarizePayload(payload = {}) {
111
- if (!payload || typeof payload !== 'object') return String(payload ?? '');
112
- const parts = [];
113
- if (payload.sessionId || payload.session_id) parts.push(`session=${payload.sessionId || payload.session_id}`);
114
- if (payload.name || payload.tool_name) parts.push(`name=${payload.name || payload.tool_name}`);
115
- if (payload.provider || payload.model) parts.push([payload.provider, payload.model].filter(Boolean).join('/'));
116
- if (payload.prompt) parts.push(`prompt=${String(payload.prompt).slice(0, 60).replace(/\s+/g, ' ')}`);
117
- if (payload.reason) parts.push(`reason=${String(payload.reason).slice(0, 120)}`);
118
- if (payload.error) parts.push(`error=${String(payload.error).slice(0, 120)}`);
119
- if (payload.elapsedMs != null) parts.push(`${payload.elapsedMs}ms`);
120
- return parts.join(' · ');
121
- }
122
-
123
- function hookRulesPath(dataDir) {
124
- if (process.env.MIXDOG_HOOKS_FILE) return resolve(process.env.MIXDOG_HOOKS_FILE);
125
- return dataDir ? join(dataDir, 'hooks.json') : null;
126
- }
127
-
128
- function normalizeRules(raw) {
129
- if (Array.isArray(raw)) return raw;
130
- if (Array.isArray(raw?.toolBefore)) return raw.toolBefore;
131
- if (Array.isArray(raw?.beforeTool)) return raw.beforeTool;
132
- if (Array.isArray(raw?.hooks?.toolBefore)) return raw.hooks.toolBefore;
133
- return [];
134
- }
135
-
136
- function uniqueHookEntries(entries) {
137
- const seen = new Set();
138
- const out = [];
139
- for (const entry of entries) {
140
- if (!entry?.path) continue;
141
- const resolved = resolve(entry.path);
142
- const key = process.platform === 'win32' ? resolved.toLowerCase() : resolved;
143
- if (seen.has(key)) continue;
144
- seen.add(key);
145
- out.push({ ...entry, path: resolved });
146
- }
147
- return out;
148
- }
149
-
150
- function cleanHookId(value) {
151
- return String(value ?? '').trim()
152
- .toLowerCase()
153
- .replace(/[^a-z0-9_.-]+/g, '-')
154
- .replace(/^-+|-+$/g, '');
155
- }
156
-
157
- function pluginHookConfigEntries(dataDir) {
158
- if (!dataDir) return [];
159
- let plugins = [];
160
- try {
161
- plugins = listRegisteredPlugins({ dataDir });
162
- } catch {
163
- return [];
164
- }
165
- const entries = [];
166
- for (const plugin of plugins || []) {
167
- const root = String(plugin?.root || '').trim();
168
- if (!root || !existsSync(root)) continue;
169
- const id = cleanHookId(plugin.id || plugin.name || plugin.title || root.split(/[\\/]/).pop());
170
- const pluginData = join(dataDir, 'plugins', 'data', id || 'plugin');
171
- entries.push({
172
- path: join(root, 'hooks', 'hooks.json'),
173
- pluginRoot: root,
174
- pluginData,
175
- sourceType: 'plugin',
176
- });
177
- entries.push({
178
- path: join(root, '.mixdog', 'hooks.json'),
179
- pluginRoot: root,
180
- pluginData,
181
- sourceType: 'plugin',
182
- });
183
- }
184
- return entries;
185
- }
186
-
187
- function hookConfigEntries(dataDir, cwd) {
188
- if (process.env.MIXDOG_HOOKS_FILE) {
189
- return uniqueHookEntries([{ path: process.env.MIXDOG_HOOKS_FILE, sourceType: 'env' }]);
190
- }
191
- const projectDir = cwd ? resolve(cwd) : process.cwd();
192
- return uniqueHookEntries([
193
- projectDir ? { path: join(projectDir, '.mixdog', 'hooks.json'), sourceType: 'project' } : null,
194
- projectDir ? { path: join(projectDir, '.mixdog', 'hooks', 'hooks.json'), sourceType: 'project' } : null,
195
- dataDir ? { path: join(dataDir, 'hooks.json'), sourceType: 'data' } : null,
196
- dataDir ? { path: join(dataDir, 'hooks', 'hooks.json'), sourceType: 'data' } : null,
197
- ...pluginHookConfigEntries(dataDir),
198
- ]);
199
- }
200
-
201
- function isStandardConfig(parsed) {
202
- if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) return false;
203
- const hooks = parsed.hooks;
204
- if (!hooks || typeof hooks !== 'object' || Array.isArray(hooks)) return false;
205
- const values = Object.values(hooks);
206
- if (values.length === 0) return false;
207
- return values.every((groups) => (
208
- Array.isArray(groups)
209
- && groups.every((g) => g && typeof g === 'object' && Array.isArray(g.hooks))
210
- ));
211
- }
212
-
213
- const SIMPLE_MATCHER_RE = /^[A-Za-z0-9_ ,|]*$/;
214
- function matcherFires(matcher, field) {
215
- if (matcher == null) return true;
216
- const text = String(matcher).trim();
217
- if (text === '' || text === '*') return true;
218
- const value = String(field ?? '');
219
- if (SIMPLE_MATCHER_RE.test(text)) {
220
- const items = text.split(/[|,]/).map((s) => s.trim()).filter(Boolean);
221
- if (items.length === 0) return true;
222
- return items.includes(value);
223
- }
224
- try {
225
- return new RegExp(text).test(value);
226
- } catch {
227
- return true;
228
- }
229
- }
230
-
231
- function globToRegExp(glob) {
232
- let out = '^';
233
- for (const ch of String(glob || '')) {
234
- if (ch === '*') out += '.*';
235
- else if (ch === '?') out += '.';
236
- else out += ch.replace(/[.+^${}()|[\]\\]/g, '\\$&');
237
- }
238
- return new RegExp(`${out}$`);
239
- }
240
-
241
- function primaryArgFor(toolName, toolInput) {
242
- const input = toolInput && typeof toolInput === 'object' ? toolInput : {};
243
- if (toolName === 'Bash' || toolName === 'bash' || toolName === 'shell') return input.command ?? '';
244
- if (input.file_path != null) return input.file_path;
245
- if (input.path != null) return input.path;
246
- if (input.command != null) return input.command;
247
- if (input.file != null) return input.file;
248
- return '';
249
- }
250
-
251
- function ifConditionPasses(ifExpr, eventName, toolName, toolInput) {
252
- if (ifExpr == null || String(ifExpr).trim() === '') return true;
253
- if (!TOOL_IF_EVENTS.has(eventName)) return false;
254
- const m = /^\s*([A-Za-z0-9_*]+)\s*\(([^)]*)\)\s*$/.exec(String(ifExpr));
255
- if (!m) return true;
256
- const [, tool, pattern] = m;
257
- if (tool !== '*' && tool !== toolName) return false;
258
- try {
259
- return globToRegExp(pattern.trim()).test(String(primaryArgFor(toolName, toolInput)));
260
- } catch {
261
- return true;
262
- }
263
- }
264
-
265
- function resolvePlaceholders(str, projectDir, pluginData, pluginRoot = null) {
266
- if (typeof str !== 'string') return str;
267
- const resolvedProject = projectDir || process.cwd();
268
- const resolvedPluginRoot = pluginRoot || resolvedProject;
269
- const resolvedPluginData = pluginData || resolvedProject;
270
- return str
271
- .replace(/\$\{CLAUDE_PROJECT_DIR\}/g, resolvedProject)
272
- .replace(/\$\{CLAUDE_PLUGIN_ROOT\}/g, resolvedPluginRoot)
273
- .replace(/\$\{CLAUDE_PLUGIN_DATA\}/g, resolvedPluginData)
274
- .replace(/\$\{MIXDOG_PROJECT_DIR\}/g, resolvedProject)
275
- .replace(/\$\{MIXDOG_PLUGIN_ROOT\}/g, resolvedPluginRoot)
276
- .replace(/\$\{MIXDOG_PLUGIN_DATA\}/g, resolvedPluginData);
277
- }
278
-
279
- function parseStandardConfig(parsed, source, meta = {}) {
280
- const events = {};
281
- const hooks = parsed.hooks || {};
282
- for (const [eventName, groups] of Object.entries(hooks)) {
283
- if (!Array.isArray(groups)) continue;
284
- const cleanGroups = [];
285
- for (const group of groups) {
286
- if (!group || typeof group !== 'object') continue;
287
- const handlers = Array.isArray(group.hooks)
288
- ? group.hooks
289
- .filter((h) => h && typeof h === 'object' && h.type)
290
- .map((h) => ({
291
- ...h,
292
- _source: source,
293
- ...(meta.pluginRoot ? { _pluginRoot: meta.pluginRoot } : {}),
294
- ...(meta.pluginData ? { _pluginData: meta.pluginData } : {}),
295
- }))
296
- : [];
297
- if (handlers.length === 0) continue;
298
- cleanGroups.push({ matcher: group.matcher, hooks: handlers, _source: source });
299
- }
300
- if (cleanGroups.length > 0) events[eventName] = cleanGroups;
301
- }
302
- return events;
303
- }
304
-
305
- function mergeEvents(target, source) {
306
- for (const [eventName, groups] of Object.entries(source || {})) {
307
- if (!Array.isArray(target[eventName])) target[eventName] = [];
308
- target[eventName].push(...groups);
309
- }
310
- }
311
-
312
- function buildEventPayload(eventName, input = {}) {
313
- const payload = {
314
- session_id: input.sessionId ?? input.session_id ?? null,
315
- cwd: input.cwd ?? null,
316
- hook_event_name: eventName,
317
- };
318
- if (input.transcriptPath != null || input.transcript_path != null) {
319
- payload.transcript_path = input.transcriptPath ?? input.transcript_path;
320
- }
321
- if (input.permissionMode != null || input.permission_mode != null) {
322
- payload.permission_mode = input.permissionMode ?? input.permission_mode;
323
- }
324
- if (input.effort != null) payload.effort = input.effort;
325
- const toolName = input.name ?? input.tool_name;
326
- if (toolName != null) payload.tool_name = toolName;
327
- const toolInput = input.args ?? input.tool_input;
328
- if (toolInput != null) payload.tool_input = toolInput;
329
- if (input.toolCallId != null || input.tool_use_id != null) {
330
- payload.tool_use_id = input.toolCallId ?? input.tool_use_id;
331
- }
332
- if (input.toolResponse != null || input.tool_response != null || input.result != null) {
333
- payload.tool_response = input.toolResponse ?? input.tool_response ?? input.result;
334
- }
335
- for (const key of [
336
- 'prompt',
337
- 'source',
338
- 'model',
339
- 'session_title',
340
- 'message',
341
- 'notification_type',
342
- 'trigger',
343
- 'load_reason',
344
- 'file_path',
345
- 'memory_type',
346
- 'command_name',
347
- 'command_args',
348
- 'command_source',
349
- 'expansion_type',
350
- 'stop_reason',
351
- 'error_type',
352
- 'agent_type',
353
- ]) {
354
- if (input[key] != null) payload[key] = input[key];
355
- }
356
- return payload;
357
- }
358
-
359
- function matchFieldFor(eventName, payload) {
360
- if (NO_MATCHER_EVENTS.has(eventName)) return null;
361
- if (eventName === 'SessionStart') return payload.source ?? '';
362
- if (eventName === 'Setup') return payload.trigger ?? '';
363
- if (eventName === 'SessionEnd') return payload.reason ?? '';
364
- if (eventName === 'Notification') return payload.notification_type ?? payload.type ?? '';
365
- if (eventName === 'SubagentStart' || eventName === 'SubagentStop') return payload.agent_type ?? '';
366
- if (eventName === 'PreCompact' || eventName === 'PostCompact') return payload.trigger ?? '';
367
- if (eventName === 'ConfigChange') return payload.source ?? '';
368
- if (eventName === 'InstructionsLoaded') return payload.load_reason ?? '';
369
- if (eventName === 'UserPromptExpansion') return payload.command_name ?? '';
370
- if (eventName === 'StopFailure') return payload.error_type ?? '';
371
- return payload.tool_name ?? '';
372
- }
373
-
374
- function handlerTimeoutS(handler, eventName) {
375
- if (Number.isFinite(handler.timeout) && handler.timeout > 0) return handler.timeout;
376
- if (handler.type === 'prompt') return DEFAULT_PROMPT_TIMEOUT_S;
377
- if (handler.type === 'agent') return DEFAULT_AGENT_TIMEOUT_S;
378
- if (handler.type === 'mcp_tool') return DEFAULT_COMMAND_TIMEOUT_S;
379
- if (eventName === 'UserPromptSubmit') return USER_PROMPT_TIMEOUT_S;
380
- if (eventName === 'MessageDisplay') return MESSAGE_DISPLAY_TIMEOUT_S;
381
- return DEFAULT_COMMAND_TIMEOUT_S;
382
- }
383
-
384
- function defaultShellKind() {
385
- return process.platform === 'win32' ? 'powershell' : 'bash';
386
- }
387
-
388
- function commandSpawnSpec(handler, projectDir, pluginData) {
389
- const command = resolvePlaceholders(handler.command, projectDir, pluginData, handler._pluginRoot || null);
390
- if (Array.isArray(handler.args)) {
391
- return {
392
- command,
393
- args: handler.args.map((a) => resolvePlaceholders(String(a), projectDir, pluginData, handler._pluginRoot || null)),
394
- shellKind: 'exec',
395
- };
396
- }
397
- const shellKind = handler.shell === 'powershell' || handler.shell === 'bash'
398
- ? handler.shell
399
- : defaultShellKind();
400
- if (shellKind === 'powershell') {
401
- return {
402
- command: process.platform === 'win32' ? 'powershell.exe' : 'pwsh',
403
- args: ['-NoProfile', '-NonInteractive', '-Command', command],
404
- shellKind,
405
- };
406
- }
407
- return {
408
- command: process.platform === 'win32' ? 'bash.exe' : 'bash',
409
- args: ['-lc', command],
410
- shellKind,
411
- };
412
- }
413
-
414
- function hookEnv(projectDir, pluginData, payload, pluginRoot = null) {
415
- const resolvedProject = projectDir || process.cwd();
416
- const resolvedPluginRoot = pluginRoot || resolvedProject;
417
- const resolvedPluginData = pluginData || resolvedProject;
418
- const env = {
419
- ...process.env,
420
- MIXDOG_PROJECT_DIR: resolvedProject,
421
- MIXDOG_PLUGIN_ROOT: resolvedPluginRoot,
422
- MIXDOG_PLUGIN_DATA: resolvedPluginData,
423
- CLAUDE_PROJECT_DIR: resolvedProject,
424
- CLAUDE_PLUGIN_ROOT: resolvedPluginRoot,
425
- CLAUDE_PLUGIN_DATA: resolvedPluginData,
426
- };
427
- const effortLevel = payload?.effort?.level || payload?.effort;
428
- if (effortLevel) env.CLAUDE_EFFORT = String(effortLevel);
429
- return env;
430
- }
431
-
432
- function runCommandHandler(handler, payload, eventName, pluginData, onSpawnError = null, onRewake = null) {
433
- const projectDir = payload.cwd || process.cwd();
434
- const effectivePluginData = handler._pluginData || pluginData || null;
435
- const stdin = JSON.stringify(payload);
436
- const timeoutMs = Math.round(handlerTimeoutS(handler, eventName) * 1000);
437
- const spec = commandSpawnSpec(handler, projectDir, effectivePluginData);
438
- const baseOpts = {
439
- cwd: existsSync(projectDir) ? projectDir : undefined,
440
- env: hookEnv(projectDir, effectivePluginData, payload, handler._pluginRoot || null),
441
- windowsHide: true,
442
- stdio: ['pipe', 'pipe', 'pipe'],
443
- };
444
-
445
- if (handler.asyncRewake === true) {
446
- try {
447
- const child = spawn(spec.command, spec.args, {
448
- ...baseOpts,
449
- });
450
- let bgStdout = '';
451
- let bgStderr = '';
452
- let bgStdoutBytes = 0;
453
- let bgStderrBytes = 0;
454
- let killed = false;
455
- child.stdout?.on('data', (chunk) => {
456
- bgStdoutBytes += chunk.length;
457
- if (bgStdoutBytes <= MAX_BUFFER_BYTES) bgStdout += chunk.toString('utf8');
458
- });
459
- child.stderr?.on('data', (chunk) => {
460
- bgStderrBytes += chunk.length;
461
- if (bgStderrBytes <= MAX_BUFFER_BYTES) bgStderr += chunk.toString('utf8');
462
- });
463
- const killTimer = setTimeout(() => {
464
- killed = true;
465
- try { child.stdout?.removeAllListeners('data'); } catch {}
466
- try { child.stderr?.removeAllListeners('data'); } catch {}
467
- try { child.kill('SIGTERM'); } catch {}
468
- }, timeoutMs);
469
- killTimer.unref?.();
470
- child.on('error', (error) => {
471
- clearTimeout(killTimer);
472
- if (typeof onSpawnError === 'function') onSpawnError(error);
473
- });
474
- child.on('close', (code) => {
475
- clearTimeout(killTimer);
476
- try {
477
- if (!killed && code === 2 && typeof onRewake === 'function') {
478
- const text = (bgStderr || '').trim() || (bgStdout || '').trim();
479
- onRewake({ eventName, payload, text });
480
- }
481
- } catch {}
482
- });
483
- child.stdin?.end(stdin);
484
- return Promise.resolve({ exitCode: 0, stdout: '', stderr: '', async: true });
485
- } catch (error) {
486
- return Promise.resolve({
487
- exitCode: -1,
488
- stdout: '',
489
- stderr: error?.message || String(error),
490
- timedOut: false,
491
- spawnError: error,
492
- });
493
- }
494
- }
495
-
496
- if (handler.async === true) {
497
- try {
498
- const child = spawn(spec.command, spec.args, {
499
- ...baseOpts,
500
- detached: true,
501
- stdio: ['pipe', 'ignore', 'ignore'],
502
- });
503
- child.on('error', (error) => {
504
- if (typeof onSpawnError === 'function') onSpawnError(error);
505
- });
506
- child.stdin?.end(stdin);
507
- child.unref?.();
508
- return Promise.resolve({ exitCode: 0, stdout: '', stderr: '', async: true });
509
- } catch (error) {
510
- return Promise.resolve({
511
- exitCode: -1,
512
- stdout: '',
513
- stderr: error?.message || String(error),
514
- timedOut: false,
515
- spawnError: error,
516
- });
517
- }
518
- }
519
-
520
- return new Promise((resolveRun) => {
521
- let child;
522
- let settled = false;
523
- let stdout = '';
524
- let stderr = '';
525
- let stdoutBytes = 0;
526
- let stderrBytes = 0;
527
- let timedOut = false;
528
- let timer = null;
529
- const finish = (result) => {
530
- if (settled) return;
531
- settled = true;
532
- if (timer) clearTimeout(timer);
533
- resolveRun(result);
534
- };
535
- try {
536
- child = spawn(spec.command, spec.args, baseOpts);
537
- } catch (error) {
538
- finish({
539
- exitCode: -1,
540
- stdout: '',
541
- stderr: error?.message || String(error),
542
- timedOut: false,
543
- spawnError: error,
544
- });
545
- return;
546
- }
547
- timer = setTimeout(() => {
548
- timedOut = true;
549
- try { child.kill('SIGTERM'); } catch {}
550
- finish({
551
- exitCode: -1,
552
- stdout,
553
- stderr: stderr || `hook command timed out after ${timeoutMs}ms`,
554
- timedOut,
555
- spawnError: null,
556
- });
557
- }, timeoutMs);
558
- timer.unref?.();
559
- child.stdout?.on('data', (chunk) => {
560
- stdoutBytes += chunk.length;
561
- if (stdoutBytes <= MAX_BUFFER_BYTES) stdout += chunk.toString('utf8');
562
- });
563
- child.stderr?.on('data', (chunk) => {
564
- stderrBytes += chunk.length;
565
- if (stderrBytes <= MAX_BUFFER_BYTES) stderr += chunk.toString('utf8');
566
- });
567
- child.on('error', (error) => {
568
- finish({
569
- exitCode: -1,
570
- stdout,
571
- stderr: stderr || error?.message || String(error),
572
- timedOut: false,
573
- spawnError: error,
574
- });
575
- });
576
- child.on('close', (code) => {
577
- finish({
578
- exitCode: timedOut ? -1 : (typeof code === 'number' ? code : 0),
579
- stdout,
580
- stderr,
581
- timedOut,
582
- spawnError: null,
583
- });
584
- });
585
- try {
586
- child.stdin?.end(stdin);
587
- } catch {}
588
- });
589
- }
590
-
591
- function resolveHeaderValue(value, allowed) {
592
- if (typeof value !== 'string') return String(value ?? '');
593
- return value.replace(/\$(?:\{([A-Za-z_][A-Za-z0-9_]*)\}|([A-Za-z_][A-Za-z0-9_]*))/g, (_m, braced, bare) => {
594
- const name = braced || bare;
595
- return allowed.has(name) ? (process.env[name] || '') : '';
596
- });
597
- }
598
-
599
- async function runHttpHandler(handler, payload, eventName) {
600
- if (typeof fetch !== 'function') {
601
- return { exitCode: -1, stdout: '', stderr: 'fetch is not available', timedOut: false, spawnError: new Error('fetch is not available') };
602
- }
603
- const timeoutMs = Math.round(handlerTimeoutS(handler, eventName) * 1000);
604
- const controller = new AbortController();
605
- const timer = setTimeout(() => controller.abort(), timeoutMs);
606
- timer.unref?.();
607
- try {
608
- const allowed = new Set(Array.isArray(handler.allowedEnvVars) ? handler.allowedEnvVars.map(String) : []);
609
- const headers = { 'Content-Type': 'application/json' };
610
- if (handler.headers && typeof handler.headers === 'object' && !Array.isArray(handler.headers)) {
611
- for (const [key, value] of Object.entries(handler.headers)) {
612
- headers[key] = resolveHeaderValue(value, allowed);
613
- }
614
- }
615
- const response = await fetch(handler.url, {
616
- method: 'POST',
617
- headers,
618
- body: JSON.stringify(payload),
619
- signal: controller.signal,
620
- });
621
- const text = await response.text();
622
- if (!response.ok) {
623
- return { exitCode: 1, stdout: text, stderr: `HTTP ${response.status} ${response.statusText}`.trim(), timedOut: false, spawnError: null };
624
- }
625
- return { exitCode: 0, stdout: text, stderr: '', timedOut: false, spawnError: null };
626
- } catch (error) {
627
- const aborted = error?.name === 'AbortError';
628
- return {
629
- exitCode: -1,
630
- stdout: '',
631
- stderr: aborted ? `HTTP hook timed out: ${handler.url}` : (error?.message || String(error)),
632
- timedOut: aborted,
633
- spawnError: aborted ? null : error,
634
- };
635
- } finally {
636
- clearTimeout(timer);
637
- }
638
- }
639
-
640
- async function runMcpToolHandler(handler, payload, eventName, mcpToolRunner) {
641
- const timeoutMs = Math.round(handlerTimeoutS(handler, eventName) * 1000);
642
- let name = String(handler.tool || '').trim();
643
- if (handler.server && !name.startsWith('mcp__')) {
644
- name = `mcp__${String(handler.server).trim()}__${name}`;
645
- }
646
- if (!name) {
647
- return { exitCode: -1, stdout: '', stderr: 'mcp_tool handler missing tool name', timedOut: false, spawnError: null };
648
- }
649
- let timer = null;
650
- try {
651
- const runPromise = Promise.resolve(mcpToolRunner({ name, args: payload }));
652
- const text = await Promise.race([
653
- runPromise,
654
- new Promise((_r, reject) => {
655
- timer = setTimeout(() => reject(new Error(`mcp_tool hook timed out: ${name}`)), timeoutMs);
656
- // No unref: this timer must keep the event loop alive so the race can
657
- // settle even when the runner promise never resolves. Cleared in finally.
658
- }),
659
- ]);
660
- return { exitCode: 0, stdout: limitText(String(text ?? '')), stderr: '', timedOut: false, spawnError: null };
661
- } catch (error) {
662
- const timedOut = /timed out/i.test(error?.message || '');
663
- return { exitCode: -1, stdout: '', stderr: error?.message || String(error), timedOut, spawnError: null };
664
- } finally {
665
- if (timer) clearTimeout(timer);
666
- }
667
- }
668
-
669
- async function runPromptHandler(handler, payload, eventName, promptRunner) {
670
- const timeoutMs = Math.round(handlerTimeoutS(handler, eventName) * 1000);
671
- const prompt = String(handler.prompt || '');
672
- if (!prompt) {
673
- return { exitCode: -1, stdout: '', stderr: 'prompt handler missing prompt', timedOut: false, spawnError: null };
674
- }
675
- let timer = null;
676
- try {
677
- const runPromise = Promise.resolve(promptRunner({ prompt, payload, timeoutMs }));
678
- const text = await Promise.race([
679
- runPromise,
680
- new Promise((_r, reject) => {
681
- timer = setTimeout(() => reject(new Error(`prompt hook timed out: ${eventName}`)), timeoutMs);
682
- // No unref: this timer must keep the event loop alive so the race can
683
- // settle even when the runner promise never resolves. Cleared in finally.
684
- }),
685
- ]);
686
- const raw = String(text ?? '').trim();
687
- const deny = (reason) => ({ exitCode: 2, stdout: '', stderr: reason, timedOut: false, spawnError: null });
688
- const allow = () => ({ exitCode: 0, stdout: '', stderr: '', timedOut: false, spawnError: null });
689
- let verdict = null;
690
- try {
691
- verdict = JSON.parse(raw);
692
- } catch {
693
- verdict = undefined;
694
- }
695
- if (verdict && typeof verdict === 'object') {
696
- if (verdict.ok === false) return deny(String(verdict.reason || `blocked by ${eventName} prompt hook`));
697
- return allow();
698
- }
699
- // plain-text response
700
- const lowered = raw.toLowerCase();
701
- if (!raw || ['yes', 'true', 'allow', 'ok'].includes(lowered)) return allow();
702
- if (['no', 'false', 'deny', 'block'].includes(lowered)) return deny(raw || `blocked by ${eventName} prompt hook`);
703
- return allow();
704
- } catch (error) {
705
- const timedOut = /timed out/i.test(error?.message || '');
706
- return { exitCode: -1, stdout: '', stderr: error?.message || String(error), timedOut, spawnError: null };
707
- } finally {
708
- if (timer) clearTimeout(timer);
709
- }
710
- }
711
-
712
- function limitText(text) {
713
- const value = String(text || '');
714
- if (value.length <= MAX_OUTPUT_CHARS) return value;
715
- return `${value.slice(0, MAX_OUTPUT_CHARS)}\n... [hook output truncated; original ${value.length} chars]`;
716
- }
717
-
718
- function parseHandlerOutput(run, eventName) {
719
- const out = {
720
- block: false,
721
- reason: null,
722
- permissionDecision: null,
723
- updatedInput: null,
724
- updatedToolOutput: null,
725
- additionalContext: null,
726
- systemMessage: null,
727
- suppressOutput: false,
728
- continueFlag: undefined,
729
- askReason: null,
730
- };
731
- if (run.timedOut || run.spawnError || run.async) return out;
732
- if (run.exitCode === 2) {
733
- if (EXIT2_BLOCK_EVENTS.has(eventName)) {
734
- out.block = true;
735
- out.reason = limitText((run.stderr || '').trim()) || `blocked by ${eventName} hook`;
736
- }
737
- return out;
738
- }
739
- if (run.exitCode !== 0) return out;
740
- const rawText = (run.stdout || '').trim();
741
- if (!rawText) return out;
742
- if (!(rawText.startsWith('{') || rawText.startsWith('['))) {
743
- if (PLAIN_STDOUT_CONTEXT_EVENTS.has(eventName)) out.additionalContext = limitText(rawText);
744
- return out;
745
- }
746
- try {
747
- const json = JSON.parse(rawText);
748
- if (!json || typeof json !== 'object' || Array.isArray(json)) return out;
749
- if (json.continue === false) {
750
- out.continueFlag = false;
751
- out.block = true;
752
- out.reason = limitText(json.stopReason || json.reason || `stopped by ${eventName} hook`);
753
- }
754
- if (json.decision === 'block' && TOP_LEVEL_DECISION_EVENTS.has(eventName)) {
755
- out.block = true;
756
- out.reason = limitText(json.reason || out.reason || `blocked by ${eventName} hook`);
757
- }
758
- if (json.suppressOutput) out.suppressOutput = true;
759
- if (typeof json.systemMessage === 'string') out.systemMessage = limitText(json.systemMessage);
760
- if (typeof json.additionalContext === 'string') out.additionalContext = limitText(json.additionalContext);
761
-
762
- const hso = json.hookSpecificOutput;
763
- const hsoMatches = hso && typeof hso === 'object'
764
- && (!hso.hookEventName || hso.hookEventName === eventName);
765
- if (hsoMatches) {
766
- if (typeof hso.additionalContext === 'string') out.additionalContext = limitText(hso.additionalContext);
767
- if (hso.updatedInput && typeof hso.updatedInput === 'object' && !Array.isArray(hso.updatedInput)) {
768
- out.updatedInput = hso.updatedInput;
769
- }
770
- if (hso.updatedToolOutput != null) out.updatedToolOutput = hso.updatedToolOutput;
771
- if (hso.permissionDecision) out.permissionDecision = String(hso.permissionDecision).toLowerCase();
772
- if (hso.permissionDecisionReason) out.reason = out.reason || limitText(hso.permissionDecisionReason);
773
- if (eventName === 'PermissionRequest' && hso.decision && typeof hso.decision === 'object') {
774
- const behavior = String(hso.decision.behavior || '').toLowerCase();
775
- if (behavior === 'deny') {
776
- out.block = true;
777
- out.reason = out.reason || limitText(hso.decision.reason || `denied by ${eventName} hook`);
778
- }
779
- if (hso.decision.updatedInput && typeof hso.decision.updatedInput === 'object' && !Array.isArray(hso.decision.updatedInput)) {
780
- out.updatedInput = hso.decision.updatedInput;
781
- }
782
- }
783
- }
784
- if (eventName === 'PreToolUse') {
785
- if (out.permissionDecision === 'deny') {
786
- out.block = true;
787
- out.reason = out.reason || `denied by ${eventName} hook`;
788
- } else if (out.permissionDecision === 'ask') {
789
- out.askReason = out.reason || `ask requested by ${eventName} hook`;
790
- }
791
- }
792
- } catch {
793
- if (PLAIN_STDOUT_CONTEXT_EVENTS.has(eventName)) out.additionalContext = limitText(rawText);
794
- }
795
- return out;
796
- }
797
-
798
- function matchesList(pattern, value) {
799
- if (pattern == null || pattern === '*' || pattern === 'all') return true;
800
- const items = Array.isArray(pattern) ? pattern : [pattern];
801
- return items.some((item) => {
802
- const text = String(item || '').trim();
803
- if (!text || text === '*' || text === 'all') return true;
804
- if (text.endsWith('*')) return String(value || '').startsWith(text.slice(0, -1));
805
- return text === String(value || '');
806
- });
807
- }
808
-
809
- function ruleMatches(rule, input) {
810
- if (!rule || typeof rule !== 'object' || rule.enabled === false) return false;
811
- const toolPattern = rule.tool ?? rule.name ?? rule.tools;
812
- if (!matchesList(toolPattern, input.name)) return false;
813
- if (rule.cwd && !String(input.cwd || '').includes(String(rule.cwd))) return false;
814
- const match = rule.match ?? rule.contains;
815
- if (match != null) {
816
- const haystack = JSON.stringify({ args: input.args ?? null, cwd: input.cwd ?? null });
817
- const needles = Array.isArray(match) ? match : [match];
818
- if (!needles.some((needle) => haystack.includes(String(needle)))) return false;
819
- }
820
- return true;
821
- }
822
-
823
- function decisionFromRule(rule, input) {
824
- const action = String(rule.action || rule.decision || 'allow').toLowerCase();
825
- if (action === 'deny' || action === 'block') {
826
- return { action: 'deny', reason: rule.reason || rule.message || `blocked by hook rule for ${input.name}` };
827
- }
828
- if (action === 'modify' || action === 'rewrite') {
829
- const nextArgs = rule.args && typeof rule.args === 'object'
830
- ? rule.args
831
- : { ...(input.args || {}), ...(rule.patch || {}) };
832
- return { action: 'modify', args: nextArgs, reason: rule.reason || rule.message || `modified by hook rule for ${input.name}` };
833
- }
834
- if (action === 'ask') {
835
- return { action: 'ask', reason: rule.reason || rule.message || `approval requested by hook rule for ${input.name}` };
836
- }
837
- return { action: 'allow', reason: rule.reason || rule.message || null };
838
- }
839
-
840
- function summarizeRule(rule, index) {
841
- return {
842
- index,
843
- enabled: rule.enabled !== false,
844
- tool: rule.tool ?? rule.name ?? rule.tools ?? '*',
845
- action: rule.action || rule.decision || 'allow',
846
- match: rule.match ?? rule.contains ?? null,
847
- cwd: rule.cwd || null,
848
- reason: rule.reason || rule.message || null,
849
- patch: compactValue(rule.patch || rule.args || null),
850
- };
851
- }
852
-
853
- function handlerDedupeKey(handler) {
854
- if (!handler || typeof handler !== 'object') return '';
855
- const ifKey = handler.if != null ? `|if:${JSON.stringify(handler.if)}` : '';
856
- if (handler.type === 'command') {
857
- return `command:${handler._pluginRoot || ''}:${handler.command || ''}:${JSON.stringify(handler.args || null)}${ifKey}`;
858
- }
859
- if (handler.type === 'http') return `http:${handler.url || ''}${ifKey}`;
860
- if (handler.type === 'mcp_tool') return `mcp_tool:${handler.server || ''}:${handler.tool || ''}${ifKey}`;
861
- if (handler.type === 'prompt') return `prompt:${handler.prompt || ''}${ifKey}`;
862
- return `${handler.type || 'unknown'}:${JSON.stringify(handler)}`;
863
- }
864
-
865
- function shellCountFor(handler) {
866
- if (handler?.type === 'command') {
867
- if (handler.args) return 'exec';
868
- return handler.shell === 'powershell' || handler.shell === 'bash' ? handler.shell : defaultShellKind();
869
- }
870
- return handler?.type || 'unknown';
871
- }
2
+ import { dirname } from 'node:path';
3
+ import {
4
+ DEFAULT_EVENTS,
5
+ NO_MATCHER_EVENTS,
6
+ SUPPORTED_HANDLER_TYPES,
7
+ limitText,
8
+ } from './hook-bus/constants.mjs';
9
+ import {
10
+ buildEventPayload,
11
+ hookConfigEntries,
12
+ hookRulesPath,
13
+ isStandardConfig,
14
+ matchFieldFor,
15
+ matcherFires,
16
+ mergeEvents,
17
+ normalizeRules,
18
+ parseStandardConfig,
19
+ } from './hook-bus/config.mjs';
20
+ import {
21
+ handlerTimeoutS,
22
+ ifConditionPasses,
23
+ parseHandlerOutput,
24
+ runCommandHandler,
25
+ runHttpHandler,
26
+ runMcpToolHandler,
27
+ runPromptHandler,
28
+ defaultShellKind,
29
+ } from './hook-bus/handlers.mjs';
30
+ import {
31
+ compactValue,
32
+ summarizePayload,
33
+ } from './hook-bus/payload.mjs';
34
+ import {
35
+ decisionFromRule,
36
+ handlerDedupeKey,
37
+ ruleMatches,
38
+ shellCountFor,
39
+ summarizeRule,
40
+ } from './hook-bus/rules.mjs';
41
+
42
+ // Re-export extracted helpers so existing deep importers keep resolving.
43
+ export {
44
+ DEFAULT_EVENTS,
45
+ SUPPORTED_HANDLER_TYPES,
46
+ limitText,
47
+ } from './hook-bus/constants.mjs';
48
+ export {
49
+ buildEventPayload,
50
+ hookConfigEntries,
51
+ hookRulesPath,
52
+ isStandardConfig,
53
+ matchFieldFor,
54
+ matcherFires,
55
+ mergeEvents,
56
+ normalizeRules,
57
+ parseStandardConfig,
58
+ } from './hook-bus/config.mjs';
59
+ export {
60
+ handlerTimeoutS,
61
+ ifConditionPasses,
62
+ parseHandlerOutput,
63
+ runCommandHandler,
64
+ runHttpHandler,
65
+ runMcpToolHandler,
66
+ runPromptHandler,
67
+ defaultShellKind,
68
+ } from './hook-bus/handlers.mjs';
69
+ export {
70
+ compactValue,
71
+ summarizePayload,
72
+ } from './hook-bus/payload.mjs';
73
+ export {
74
+ decisionFromRule,
75
+ handlerDedupeKey,
76
+ ruleMatches,
77
+ summarizeRule,
78
+ } from './hook-bus/rules.mjs';
872
79
 
873
80
  export function createStandaloneHookBus({ maxEvents = 80, dataDir = null, promptRunner = null, mcpToolRunner = null } = {}) {
874
81
  const recent = [];