mixdog 0.9.3 → 0.9.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (329) hide show
  1. package/package.json +7 -3
  2. package/scripts/bench/lead-review-tasks-r3.json +20 -0
  3. package/scripts/bench/lead-review-tasks.json +20 -0
  4. package/scripts/bench/r4-mixed-tasks.json +20 -0
  5. package/scripts/bench/review-tasks.json +20 -0
  6. package/scripts/bench/round-codex.json +114 -0
  7. package/scripts/bench/round-mixdog-lead-r3.json +269 -0
  8. package/scripts/bench/round-mixdog-lead.json +269 -0
  9. package/scripts/bench/round-mixdog.json +126 -0
  10. package/scripts/bench/round-r10-bigsample.json +679 -0
  11. package/scripts/bench/round-r11-codexalign.json +257 -0
  12. package/scripts/bench/round-r4-codex.json +114 -0
  13. package/scripts/bench/round-r4-mixed.json +225 -0
  14. package/scripts/bench/round-r5-gpt-lead.json +259 -0
  15. package/scripts/bench/round-r6-codex.json +114 -0
  16. package/scripts/bench/round-r6-solo.json +257 -0
  17. package/scripts/bench/round-r7-full.json +254 -0
  18. package/scripts/bench/round-r8-fulldefault.json +255 -0
  19. package/scripts/bench-run.mjs +215 -29
  20. package/scripts/freevar-smoke.mjs +95 -0
  21. package/scripts/internal-comms-bench.mjs +1 -0
  22. package/scripts/internal-comms-smoke.mjs +10 -9
  23. package/scripts/mouse-probe.mjs +45 -0
  24. package/scripts/output-style-bench.mjs +13 -6
  25. package/scripts/output-style-smoke.mjs +4 -4
  26. package/scripts/provider-toolcall-test.mjs +7 -3
  27. package/scripts/recall-usecase-cases.json +18 -0
  28. package/scripts/recall-usecase-probe.json +6 -0
  29. package/scripts/session-bench.mjs +152 -6
  30. package/scripts/tool-smoke.mjs +23 -63
  31. package/scripts/tui-render-smoke.mjs +90 -0
  32. package/scripts/webhook-smoke.mjs +208 -0
  33. package/src/agents/debugger/AGENT.md +4 -1
  34. package/src/agents/heavy-worker/AGENT.md +6 -5
  35. package/src/agents/maintainer/AGENT.md +4 -0
  36. package/src/agents/reviewer/AGENT.md +2 -1
  37. package/src/agents/worker/AGENT.md +8 -4
  38. package/src/lib/rules-builder.cjs +4 -0
  39. package/src/mixdog-session-runtime.mjs +632 -2042
  40. package/src/output-styles/default.md +34 -9
  41. package/src/output-styles/{oneline.md → extreme-minimal.md} +5 -4
  42. package/src/output-styles/minimal.md +4 -1
  43. package/src/output-styles/simple.md +22 -7
  44. package/src/rules/agent/00-common.md +2 -0
  45. package/src/rules/lead/lead-brief.md +12 -0
  46. package/src/rules/lead/lead-tool.md +0 -11
  47. package/src/runtime/agent/orchestrator/agent-runtime/agent-loop-policy.mjs +25 -0
  48. package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +100 -23
  49. package/src/runtime/agent/orchestrator/agent-runtime/session-builder.mjs +6 -15
  50. package/src/runtime/agent/orchestrator/agent-trace-format.mjs +362 -0
  51. package/src/runtime/agent/orchestrator/agent-trace-io.mjs +410 -0
  52. package/src/runtime/agent/orchestrator/agent-trace.mjs +16 -735
  53. package/src/runtime/agent/orchestrator/config.mjs +69 -2
  54. package/src/runtime/agent/orchestrator/providers/anthropic-effort.mjs +62 -20
  55. package/src/runtime/agent/orchestrator/providers/anthropic-model-resolve.mjs +209 -0
  56. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-credentials.mjs +489 -0
  57. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +81 -1281
  58. package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +607 -0
  59. package/src/runtime/agent/orchestrator/providers/anthropic.mjs +32 -3
  60. package/src/runtime/agent/orchestrator/providers/codex-client-meta.mjs +81 -0
  61. package/src/runtime/agent/orchestrator/providers/gemini-cache.mjs +248 -0
  62. package/src/runtime/agent/orchestrator/providers/gemini-schema.mjs +303 -0
  63. package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +505 -0
  64. package/src/runtime/agent/orchestrator/providers/gemini.mjs +43 -1013
  65. package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +17 -3
  66. package/src/runtime/agent/orchestrator/providers/model-catalog.mjs +86 -11
  67. package/src/runtime/agent/orchestrator/providers/model-list-sanitize.mjs +348 -0
  68. package/src/runtime/agent/orchestrator/providers/openai-codex-model.mjs +108 -0
  69. package/src/runtime/agent/orchestrator/providers/openai-compat-trace.mjs +58 -0
  70. package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +368 -0
  71. package/src/runtime/agent/orchestrator/providers/openai-compat-xai.mjs +760 -0
  72. package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +40 -1143
  73. package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +732 -0
  74. package/src/runtime/agent/orchestrator/providers/openai-oauth-login.mjs +193 -0
  75. package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +297 -2123
  76. package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +130 -1002
  77. package/src/runtime/agent/orchestrator/providers/openai-ws-delta.mjs +227 -0
  78. package/src/runtime/agent/orchestrator/providers/openai-ws-events.mjs +67 -0
  79. package/src/runtime/agent/orchestrator/providers/openai-ws-pool.mjs +436 -0
  80. package/src/runtime/agent/orchestrator/providers/openai-ws-stream.mjs +1105 -0
  81. package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +2 -1
  82. package/src/runtime/agent/orchestrator/session/compact/budget.mjs +288 -0
  83. package/src/runtime/agent/orchestrator/session/compact/constants.mjs +85 -0
  84. package/src/runtime/agent/orchestrator/session/compact/engine.mjs +749 -0
  85. package/src/runtime/agent/orchestrator/session/compact/messages.mjs +82 -0
  86. package/src/runtime/agent/orchestrator/session/compact/summary-schema.mjs +315 -0
  87. package/src/runtime/agent/orchestrator/session/compact/summary.mjs +643 -0
  88. package/src/runtime/agent/orchestrator/session/compact/text-utils.mjs +326 -0
  89. package/src/runtime/agent/orchestrator/session/compact.mjs +40 -2282
  90. package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +14 -2
  91. package/src/runtime/agent/orchestrator/session/loop/completion-guards.mjs +61 -0
  92. package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +1 -3
  93. package/src/runtime/agent/orchestrator/session/loop/recall-fasttrack.mjs +182 -0
  94. package/src/runtime/agent/orchestrator/session/loop/steering-ladder.mjs +173 -0
  95. package/src/runtime/agent/orchestrator/session/loop/termination.mjs +58 -0
  96. package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +239 -0
  97. package/src/runtime/agent/orchestrator/session/loop.mjs +251 -397
  98. package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +471 -0
  99. package/src/runtime/agent/orchestrator/session/manager/context-meta.mjs +7 -4
  100. package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +12 -0
  101. package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +406 -0
  102. package/src/runtime/agent/orchestrator/session/manager/status-telemetry.mjs +80 -0
  103. package/src/runtime/agent/orchestrator/session/manager/usage-metrics.mjs +210 -0
  104. package/src/runtime/agent/orchestrator/session/manager.mjs +166 -1087
  105. package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +189 -0
  106. package/src/runtime/agent/orchestrator/session/store.mjs +74 -179
  107. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +70 -20
  108. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +22 -2
  109. package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +32 -41
  110. package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +40 -0
  111. package/src/runtime/agent/orchestrator/tools/builtin/rg-runner.mjs +29 -0
  112. package/src/runtime/agent/orchestrator/tools/builtin/search-builders.mjs +8 -0
  113. package/src/runtime/agent/orchestrator/tools/builtin/search-path-diagnostics.mjs +126 -0
  114. package/src/runtime/agent/orchestrator/tools/builtin/search-tool.mjs +81 -92
  115. package/src/runtime/agent/orchestrator/tools/builtin/shell-job-paths.mjs +161 -0
  116. package/src/runtime/agent/orchestrator/tools/builtin/shell-job-process.mjs +108 -0
  117. package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +28 -265
  118. package/src/runtime/agent/orchestrator/tools/builtin.mjs +0 -6
  119. package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +57 -3
  120. package/src/runtime/agent/orchestrator/tools/code-graph/keyword-match.mjs +82 -0
  121. package/src/runtime/agent/orchestrator/tools/code-graph/search.mjs +10 -122
  122. package/src/runtime/agent/orchestrator/tools/code-graph/text-columns.mjs +45 -0
  123. package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +6 -6
  124. package/src/runtime/agent/orchestrator/tools/graph-binary-fetcher.mjs +6 -3
  125. package/src/runtime/agent/orchestrator/tools/patch/constants.mjs +9 -0
  126. package/src/runtime/agent/orchestrator/tools/patch/dispatch.mjs +171 -0
  127. package/src/runtime/agent/orchestrator/tools/patch/matcher.mjs +471 -0
  128. package/src/runtime/agent/orchestrator/tools/patch/native-server.mjs +436 -0
  129. package/src/runtime/agent/orchestrator/tools/patch/orchestrator.mjs +342 -0
  130. package/src/runtime/agent/orchestrator/tools/patch/parsing.mjs +359 -0
  131. package/src/runtime/agent/orchestrator/tools/patch/paths.mjs +340 -0
  132. package/src/runtime/agent/orchestrator/tools/patch/v4a-convert.mjs +643 -0
  133. package/src/runtime/agent/orchestrator/tools/patch.mjs +36 -2959
  134. package/src/runtime/agent/orchestrator/tools/progress-message.mjs +0 -21
  135. package/src/runtime/agent/orchestrator/tools/shell-command.mjs +9 -72
  136. package/src/runtime/agent/orchestrator/tools/shell-powershell.mjs +77 -0
  137. package/src/runtime/agent/orchestrator/tools/shell-state.mjs +154 -0
  138. package/src/runtime/channels/backends/discord-access.mjs +32 -0
  139. package/src/runtime/channels/backends/discord-attachments.mjs +65 -0
  140. package/src/runtime/channels/backends/discord-gateway.mjs +233 -0
  141. package/src/runtime/channels/backends/discord.mjs +12 -292
  142. package/src/runtime/channels/index.mjs +229 -663
  143. package/src/runtime/channels/lib/backend-dispatch.mjs +44 -0
  144. package/src/runtime/channels/lib/event-pipeline.mjs +18 -1
  145. package/src/runtime/channels/lib/event-queue.mjs +63 -4
  146. package/src/runtime/channels/lib/inbound-routing.mjs +111 -0
  147. package/src/runtime/channels/lib/output-forwarder.mjs +1 -1
  148. package/src/runtime/channels/lib/owner-heartbeat.mjs +75 -0
  149. package/src/runtime/channels/lib/parent-bridge.mjs +88 -0
  150. package/src/runtime/channels/lib/runtime-paths.mjs +14 -4
  151. package/src/runtime/channels/lib/session-discovery.mjs +56 -4
  152. package/src/runtime/channels/lib/tool-dispatch.mjs +158 -0
  153. package/src/runtime/channels/lib/tool-format.mjs +1 -1
  154. package/src/runtime/channels/lib/transcript-discovery.mjs +4 -4
  155. package/src/runtime/channels/lib/voice-runtime-fetcher.mjs +6 -3
  156. package/src/runtime/channels/lib/voice-transcription.mjs +179 -0
  157. package/src/runtime/channels/lib/webhook/deliveries.mjs +312 -0
  158. package/src/runtime/channels/lib/webhook/log.mjs +42 -0
  159. package/src/runtime/channels/lib/webhook/ngrok.mjs +181 -0
  160. package/src/runtime/channels/lib/webhook/signature.mjs +60 -0
  161. package/src/runtime/channels/lib/webhook.mjs +36 -570
  162. package/src/runtime/channels/tool-defs.mjs +11 -130
  163. package/src/runtime/memory/index.mjs +201 -1948
  164. package/src/runtime/memory/lib/cycle-llm-adapters.mjs +58 -0
  165. package/src/runtime/memory/lib/cycle-scheduler.mjs +497 -0
  166. package/src/runtime/memory/lib/embedding-warmup.mjs +58 -0
  167. package/src/runtime/memory/lib/memory-config-flags.mjs +91 -0
  168. package/src/runtime/memory/lib/memory-cycle.mjs +1 -1
  169. package/src/runtime/memory/lib/memory-cycle2-gate.mjs +515 -0
  170. package/src/runtime/memory/lib/memory-cycle2-mutations.mjs +324 -0
  171. package/src/runtime/memory/lib/memory-cycle2-shared.mjs +18 -0
  172. package/src/runtime/memory/lib/memory-cycle2.mjs +24 -842
  173. package/src/runtime/memory/lib/memory-embed.mjs +149 -0
  174. package/src/runtime/memory/lib/memory-process-lock.mjs +162 -0
  175. package/src/runtime/memory/lib/memory-recall-store.mjs +22 -2
  176. package/src/runtime/memory/lib/pg/supervisor.mjs +1 -1
  177. package/src/runtime/memory/lib/query-handlers.mjs +780 -0
  178. package/src/runtime/memory/lib/recall-format.mjs +55 -0
  179. package/src/runtime/memory/lib/runtime-fetcher.mjs +8 -3
  180. package/src/runtime/memory/lib/transcript-ingest.mjs +425 -0
  181. package/src/runtime/memory/tool-defs.mjs +5 -13
  182. package/src/runtime/search/lib/http-fetch.mjs +274 -0
  183. package/src/runtime/search/lib/ssrf-guard.mjs +333 -0
  184. package/src/runtime/search/lib/web-tools.mjs +24 -602
  185. package/src/runtime/shared/atomic-file.mjs +26 -1
  186. package/src/runtime/shared/launcher-control.mjs +2 -2
  187. package/src/runtime/shared/tool-primitives.mjs +308 -0
  188. package/src/runtime/shared/tool-result-summary.mjs +515 -0
  189. package/src/runtime/shared/tool-surface.mjs +80 -898
  190. package/src/runtime/shared/transcript-writer.mjs +23 -0
  191. package/src/runtime/shared/update-checker.mjs +7 -4
  192. package/src/session-runtime/config-helpers.mjs +84 -2
  193. package/src/session-runtime/config-lifecycle.mjs +232 -0
  194. package/src/session-runtime/cwd-plugins.mjs +226 -0
  195. package/src/session-runtime/mcp-glue.mjs +177 -0
  196. package/src/session-runtime/model-recency.mjs +111 -0
  197. package/src/session-runtime/native-search.mjs +247 -0
  198. package/src/session-runtime/output-styles.mjs +11 -9
  199. package/src/session-runtime/prewarm.mjs +142 -0
  200. package/src/session-runtime/provider-models.mjs +278 -0
  201. package/src/session-runtime/provider-usage.mjs +120 -0
  202. package/src/session-runtime/quick-model-rows.mjs +170 -0
  203. package/src/session-runtime/quick-search-models.mjs +46 -0
  204. package/src/session-runtime/session-hooks.mjs +93 -0
  205. package/src/session-runtime/settings-api.mjs +319 -0
  206. package/src/session-runtime/tool-catalog.mjs +29 -29
  207. package/src/session-runtime/tool-defs.mjs +84 -0
  208. package/src/session-runtime/warmup-schedulers.mjs +201 -0
  209. package/src/standalone/agent-tool/helpers.mjs +237 -0
  210. package/src/standalone/agent-tool/notify.mjs +107 -0
  211. package/src/standalone/agent-tool/provider-init.mjs +143 -0
  212. package/src/standalone/agent-tool/render.mjs +152 -0
  213. package/src/standalone/agent-tool/tool-def.mjs +55 -0
  214. package/src/standalone/agent-tool.mjs +110 -671
  215. package/src/standalone/channel-worker.mjs +4 -7
  216. package/src/standalone/explore-tool.mjs +30 -9
  217. package/src/standalone/hook-bus/config.mjs +207 -0
  218. package/src/standalone/hook-bus/constants.mjs +90 -0
  219. package/src/standalone/hook-bus/handlers.mjs +481 -0
  220. package/src/standalone/hook-bus/payload.mjs +31 -0
  221. package/src/standalone/hook-bus/rules.mjs +77 -0
  222. package/src/standalone/hook-bus.mjs +77 -870
  223. package/src/standalone/memory-runtime-proxy.mjs +7 -0
  224. package/src/standalone/opencode-go-login.mjs +5 -1
  225. package/src/standalone/provider-admin.mjs +1 -16
  226. package/src/standalone/usage-dashboard.mjs +3 -1
  227. package/src/tui/App.jsx +945 -8094
  228. package/src/tui/app/app-format.mjs +206 -0
  229. package/src/tui/app/channel-pickers.mjs +510 -0
  230. package/src/tui/app/clipboard.mjs +67 -0
  231. package/src/tui/app/core-memory-picker.mjs +210 -0
  232. package/src/tui/app/extension-pickers.mjs +506 -0
  233. package/src/tui/app/input-parsers.mjs +193 -0
  234. package/src/tui/app/maintenance-pickers.mjs +324 -0
  235. package/src/tui/app/model-options.mjs +330 -0
  236. package/src/tui/app/model-picker.mjs +365 -0
  237. package/src/tui/app/onboarding-steps.mjs +400 -0
  238. package/src/tui/app/project-picker.mjs +247 -0
  239. package/src/tui/app/provider-setup-picker.mjs +580 -0
  240. package/src/tui/app/resume-picker.mjs +55 -0
  241. package/src/tui/app/route-pickers.mjs +419 -0
  242. package/src/tui/app/settings-picker.mjs +490 -0
  243. package/src/tui/app/slash-commands.mjs +101 -0
  244. package/src/tui/app/slash-dispatch.mjs +427 -0
  245. package/src/tui/app/text-layout.mjs +46 -0
  246. package/src/tui/app/theme-effort-pickers.mjs +154 -0
  247. package/src/tui/app/transcript-window.mjs +671 -0
  248. package/src/tui/app/use-mouse-input.mjs +460 -0
  249. package/src/tui/app/use-prompt-handlers.mjs +310 -0
  250. package/src/tui/app/use-transcript-scroll.mjs +510 -0
  251. package/src/tui/app/use-transcript-window.mjs +589 -0
  252. package/src/tui/components/ConfirmBar.jsx +1 -1
  253. package/src/tui/components/Picker.jsx +32 -4
  254. package/src/tui/components/PromptInput.jsx +23 -101
  255. package/src/tui/components/SlashCommandPalette.jsx +8 -1
  256. package/src/tui/components/StatusLine.jsx +63 -12
  257. package/src/tui/components/TextEntryPanel.jsx +11 -0
  258. package/src/tui/components/ToolExecution.jsx +52 -594
  259. package/src/tui/components/TranscriptItem.jsx +105 -0
  260. package/src/tui/components/UsagePanel.jsx +18 -4
  261. package/src/tui/components/prompt-input/edit-helpers.mjs +72 -0
  262. package/src/tui/components/prompt-input/voice-indicator.mjs +39 -0
  263. package/src/tui/components/tool-execution/ResultBody.jsx +56 -0
  264. package/src/tui/components/tool-execution/surface-detail.mjs +405 -0
  265. package/src/tui/components/tool-execution/text-format.mjs +161 -0
  266. package/src/tui/display-width.mjs +20 -3
  267. package/src/tui/dist/index.mjs +19652 -18630
  268. package/src/tui/engine/agent-job-feed.mjs +133 -0
  269. package/src/tui/engine/notification-plan.mjs +76 -0
  270. package/src/tui/engine/render-timing.mjs +17 -0
  271. package/src/tui/engine/tool-approval.mjs +94 -0
  272. package/src/tui/engine/tool-card-results.mjs +234 -0
  273. package/src/tui/engine/tool-result-status.mjs +135 -0
  274. package/src/tui/engine.mjs +122 -562
  275. package/src/tui/figures.mjs +5 -0
  276. package/src/tui/index.jsx +105 -0
  277. package/src/tui/input-editing.mjs +2 -2
  278. package/src/tui/markdown/format-token.mjs +4 -1
  279. package/src/tui/statusline-ansi-bridge.mjs +11 -3
  280. package/src/tui/theme.mjs +6 -0
  281. package/src/ui/statusline-agents.mjs +213 -0
  282. package/src/ui/statusline-format.mjs +146 -0
  283. package/src/ui/statusline-segments.mjs +148 -0
  284. package/src/ui/statusline.mjs +67 -501
  285. package/src/ui/tool-card.mjs +0 -1
  286. package/src/vendor/statusline/bin/statusline-route.mjs +15 -2
  287. package/src/workflows/default/WORKFLOW.md +1 -1
  288. package/src/workflows/sequential/WORKFLOW.md +1 -1
  289. package/vendor/ink/build/display-width.js +19 -3
  290. package/vendor/ink/build/ink.js +103 -6
  291. package/vendor/ink/build/log-update.js +17 -3
  292. package/vendor/ink/build/wrap-text.js +125 -0
  293. package/scripts/_test-folder-dialog.mjs +0 -30
  294. package/scripts/fix-brief-fn.mjs +0 -35
  295. package/scripts/fix-format-tool-surface.mjs +0 -24
  296. package/scripts/fix-tool-exec-visible.mjs +0 -42
  297. package/scripts/patch-agent-brief.mjs +0 -48
  298. package/scripts/patch-app.mjs +0 -21
  299. package/scripts/patch-app2.mjs +0 -18
  300. package/scripts/patch-dist-brief.mjs +0 -96
  301. package/scripts/patch-tool-exec.mjs +0 -70
  302. package/src/examples/schedules/SCHEDULE.example.md +0 -32
  303. package/src/examples/webhooks/WEBHOOK.example.md +0 -40
  304. package/src/runtime/agent/orchestrator/session/manager.reactive-persist.test.mjs +0 -107
  305. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.test.mjs +0 -143
  306. package/src/runtime/agent/orchestrator/tools/builtin/diagnostics-tool.mjs +0 -285
  307. package/src/runtime/agent/orchestrator/tools/builtin/external-tool-adapters.test.mjs +0 -162
  308. package/src/runtime/agent/orchestrator/tools/builtin/open-config-tool.mjs +0 -26
  309. package/src/runtime/shared/channel-notification-routing.test.mjs +0 -45
  310. package/src/runtime/shared/task-notification-envelope.test.mjs +0 -107
  311. package/src/runtime/shared/tool-execution-contract.test.mjs +0 -183
  312. package/src/standalone/agent-task-status.test.mjs +0 -76
  313. package/src/tui/components/tool-output-format.test.mjs +0 -399
  314. package/src/tui/display-width.test.mjs +0 -35
  315. package/src/tui/engine-runtime-notification.test.mjs +0 -115
  316. package/src/tui/engine-tool-result-text.test.mjs +0 -75
  317. package/src/tui/input-editing.selection.test.mjs +0 -75
  318. package/src/tui/markdown/format-token.test.mjs +0 -354
  319. package/src/tui/markdown/render-ansi.test.mjs +0 -108
  320. package/src/tui/markdown/stream-fence.test.mjs +0 -26
  321. package/src/tui/markdown/streaming-markdown.test.mjs +0 -70
  322. package/src/tui/paste-fix.test.mjs +0 -119
  323. package/src/tui/prompt-history-store.test.mjs +0 -52
  324. package/src/tui/statusline-ansi-bridge.test.mjs +0 -159
  325. package/src/tui/transcript-tool-failures.test.mjs +0 -111
  326. package/src/ui/markdown.test.mjs +0 -70
  327. package/src/ui/statusline-context-label.test.mjs +0 -15
  328. package/src/vendor/statusline/bin/statusline-lib.mjs +0 -186
  329. package/src/vendor/statusline/bin/statusline-route.test.mjs +0 -80
@@ -0,0 +1,481 @@
1
+ import { existsSync } from 'node:fs';
2
+ import { spawn } from 'node:child_process';
3
+ import {
4
+ DEFAULT_AGENT_TIMEOUT_S,
5
+ DEFAULT_COMMAND_TIMEOUT_S,
6
+ DEFAULT_PROMPT_TIMEOUT_S,
7
+ EXIT2_BLOCK_EVENTS,
8
+ MAX_BUFFER_BYTES,
9
+ MESSAGE_DISPLAY_TIMEOUT_S,
10
+ PLAIN_STDOUT_CONTEXT_EVENTS,
11
+ TOOL_IF_EVENTS,
12
+ TOP_LEVEL_DECISION_EVENTS,
13
+ USER_PROMPT_TIMEOUT_S,
14
+ limitText,
15
+ } from './constants.mjs';
16
+
17
+ function globToRegExp(glob) {
18
+ let out = '^';
19
+ for (const ch of String(glob || '')) {
20
+ if (ch === '*') out += '.*';
21
+ else if (ch === '?') out += '.';
22
+ else out += ch.replace(/[.+^${}()|[\]\\]/g, '\\$&');
23
+ }
24
+ return new RegExp(`${out}$`);
25
+ }
26
+
27
+ function primaryArgFor(toolName, toolInput) {
28
+ const input = toolInput && typeof toolInput === 'object' ? toolInput : {};
29
+ if (toolName === 'Bash' || toolName === 'bash' || toolName === 'shell') return input.command ?? '';
30
+ if (input.file_path != null) return input.file_path;
31
+ if (input.path != null) return input.path;
32
+ if (input.command != null) return input.command;
33
+ if (input.file != null) return input.file;
34
+ return '';
35
+ }
36
+
37
+ export function ifConditionPasses(ifExpr, eventName, toolName, toolInput) {
38
+ if (ifExpr == null || String(ifExpr).trim() === '') return true;
39
+ if (!TOOL_IF_EVENTS.has(eventName)) return false;
40
+ const m = /^\s*([A-Za-z0-9_*]+)\s*\(([^)]*)\)\s*$/.exec(String(ifExpr));
41
+ if (!m) return true;
42
+ const [, tool, pattern] = m;
43
+ if (tool !== '*' && tool !== toolName) return false;
44
+ try {
45
+ return globToRegExp(pattern.trim()).test(String(primaryArgFor(toolName, toolInput)));
46
+ } catch {
47
+ return true;
48
+ }
49
+ }
50
+
51
+ function resolvePlaceholders(str, projectDir, pluginData, pluginRoot = null) {
52
+ if (typeof str !== 'string') return str;
53
+ const resolvedProject = projectDir || process.cwd();
54
+ const resolvedPluginRoot = pluginRoot || resolvedProject;
55
+ const resolvedPluginData = pluginData || resolvedProject;
56
+ return str
57
+ .replace(/\$\{CLAUDE_PROJECT_DIR\}/g, resolvedProject)
58
+ .replace(/\$\{CLAUDE_PLUGIN_ROOT\}/g, resolvedPluginRoot)
59
+ .replace(/\$\{CLAUDE_PLUGIN_DATA\}/g, resolvedPluginData)
60
+ .replace(/\$\{MIXDOG_PROJECT_DIR\}/g, resolvedProject)
61
+ .replace(/\$\{MIXDOG_PLUGIN_ROOT\}/g, resolvedPluginRoot)
62
+ .replace(/\$\{MIXDOG_PLUGIN_DATA\}/g, resolvedPluginData);
63
+ }
64
+
65
+ export function handlerTimeoutS(handler, eventName) {
66
+ if (Number.isFinite(handler.timeout) && handler.timeout > 0) return handler.timeout;
67
+ if (handler.type === 'prompt') return DEFAULT_PROMPT_TIMEOUT_S;
68
+ if (handler.type === 'agent') return DEFAULT_AGENT_TIMEOUT_S;
69
+ if (handler.type === 'mcp_tool') return DEFAULT_COMMAND_TIMEOUT_S;
70
+ if (eventName === 'UserPromptSubmit') return USER_PROMPT_TIMEOUT_S;
71
+ if (eventName === 'MessageDisplay') return MESSAGE_DISPLAY_TIMEOUT_S;
72
+ return DEFAULT_COMMAND_TIMEOUT_S;
73
+ }
74
+
75
+ export function defaultShellKind() {
76
+ return process.platform === 'win32' ? 'powershell' : 'bash';
77
+ }
78
+
79
+ function commandSpawnSpec(handler, projectDir, pluginData) {
80
+ const command = resolvePlaceholders(handler.command, projectDir, pluginData, handler._pluginRoot || null);
81
+ if (Array.isArray(handler.args)) {
82
+ return {
83
+ command,
84
+ args: handler.args.map((a) => resolvePlaceholders(String(a), projectDir, pluginData, handler._pluginRoot || null)),
85
+ shellKind: 'exec',
86
+ };
87
+ }
88
+ const shellKind = handler.shell === 'powershell' || handler.shell === 'bash'
89
+ ? handler.shell
90
+ : defaultShellKind();
91
+ if (shellKind === 'powershell') {
92
+ return {
93
+ command: process.platform === 'win32' ? 'powershell.exe' : 'pwsh',
94
+ args: ['-NoProfile', '-NonInteractive', '-Command', command],
95
+ shellKind,
96
+ };
97
+ }
98
+ return {
99
+ command: process.platform === 'win32' ? 'bash.exe' : 'bash',
100
+ args: ['-lc', command],
101
+ shellKind,
102
+ };
103
+ }
104
+
105
+ function hookEnv(projectDir, pluginData, payload, pluginRoot = null) {
106
+ const resolvedProject = projectDir || process.cwd();
107
+ const resolvedPluginRoot = pluginRoot || resolvedProject;
108
+ const resolvedPluginData = pluginData || resolvedProject;
109
+ const env = {
110
+ ...process.env,
111
+ MIXDOG_PROJECT_DIR: resolvedProject,
112
+ MIXDOG_PLUGIN_ROOT: resolvedPluginRoot,
113
+ MIXDOG_PLUGIN_DATA: resolvedPluginData,
114
+ CLAUDE_PROJECT_DIR: resolvedProject,
115
+ CLAUDE_PLUGIN_ROOT: resolvedPluginRoot,
116
+ CLAUDE_PLUGIN_DATA: resolvedPluginData,
117
+ };
118
+ const effortLevel = payload?.effort?.level || payload?.effort;
119
+ if (effortLevel) env.CLAUDE_EFFORT = String(effortLevel);
120
+ return env;
121
+ }
122
+
123
+ export function runCommandHandler(handler, payload, eventName, pluginData, onSpawnError = null, onRewake = null) {
124
+ const projectDir = payload.cwd || process.cwd();
125
+ const effectivePluginData = handler._pluginData || pluginData || null;
126
+ const stdin = JSON.stringify(payload);
127
+ const timeoutMs = Math.round(handlerTimeoutS(handler, eventName) * 1000);
128
+ const spec = commandSpawnSpec(handler, projectDir, effectivePluginData);
129
+ const baseOpts = {
130
+ cwd: existsSync(projectDir) ? projectDir : undefined,
131
+ env: hookEnv(projectDir, effectivePluginData, payload, handler._pluginRoot || null),
132
+ windowsHide: true,
133
+ stdio: ['pipe', 'pipe', 'pipe'],
134
+ };
135
+
136
+ if (handler.asyncRewake === true) {
137
+ try {
138
+ const child = spawn(spec.command, spec.args, {
139
+ ...baseOpts,
140
+ });
141
+ let bgStdout = '';
142
+ let bgStderr = '';
143
+ let bgStdoutBytes = 0;
144
+ let bgStderrBytes = 0;
145
+ let killed = false;
146
+ child.stdout?.on('data', (chunk) => {
147
+ bgStdoutBytes += chunk.length;
148
+ if (bgStdoutBytes <= MAX_BUFFER_BYTES) bgStdout += chunk.toString('utf8');
149
+ });
150
+ child.stderr?.on('data', (chunk) => {
151
+ bgStderrBytes += chunk.length;
152
+ if (bgStderrBytes <= MAX_BUFFER_BYTES) bgStderr += chunk.toString('utf8');
153
+ });
154
+ const killTimer = setTimeout(() => {
155
+ killed = true;
156
+ try { child.stdout?.removeAllListeners('data'); } catch {}
157
+ try { child.stderr?.removeAllListeners('data'); } catch {}
158
+ try { child.kill('SIGTERM'); } catch {}
159
+ }, timeoutMs);
160
+ killTimer.unref?.();
161
+ child.on('error', (error) => {
162
+ clearTimeout(killTimer);
163
+ if (typeof onSpawnError === 'function') onSpawnError(error);
164
+ });
165
+ child.on('close', (code) => {
166
+ clearTimeout(killTimer);
167
+ try {
168
+ if (!killed && code === 2 && typeof onRewake === 'function') {
169
+ const text = (bgStderr || '').trim() || (bgStdout || '').trim();
170
+ onRewake({ eventName, payload, text });
171
+ }
172
+ } catch {}
173
+ });
174
+ child.stdin?.end(stdin);
175
+ return Promise.resolve({ exitCode: 0, stdout: '', stderr: '', async: true });
176
+ } catch (error) {
177
+ return Promise.resolve({
178
+ exitCode: -1,
179
+ stdout: '',
180
+ stderr: error?.message || String(error),
181
+ timedOut: false,
182
+ spawnError: error,
183
+ });
184
+ }
185
+ }
186
+
187
+ if (handler.async === true) {
188
+ try {
189
+ const child = spawn(spec.command, spec.args, {
190
+ ...baseOpts,
191
+ detached: true,
192
+ stdio: ['pipe', 'ignore', 'ignore'],
193
+ });
194
+ child.on('error', (error) => {
195
+ if (typeof onSpawnError === 'function') onSpawnError(error);
196
+ });
197
+ child.stdin?.end(stdin);
198
+ child.unref?.();
199
+ return Promise.resolve({ exitCode: 0, stdout: '', stderr: '', async: true });
200
+ } catch (error) {
201
+ return Promise.resolve({
202
+ exitCode: -1,
203
+ stdout: '',
204
+ stderr: error?.message || String(error),
205
+ timedOut: false,
206
+ spawnError: error,
207
+ });
208
+ }
209
+ }
210
+
211
+ return new Promise((resolveRun) => {
212
+ let child;
213
+ let settled = false;
214
+ let stdout = '';
215
+ let stderr = '';
216
+ let stdoutBytes = 0;
217
+ let stderrBytes = 0;
218
+ let timedOut = false;
219
+ let timer = null;
220
+ const finish = (result) => {
221
+ if (settled) return;
222
+ settled = true;
223
+ if (timer) clearTimeout(timer);
224
+ resolveRun(result);
225
+ };
226
+ try {
227
+ child = spawn(spec.command, spec.args, baseOpts);
228
+ } catch (error) {
229
+ finish({
230
+ exitCode: -1,
231
+ stdout: '',
232
+ stderr: error?.message || String(error),
233
+ timedOut: false,
234
+ spawnError: error,
235
+ });
236
+ return;
237
+ }
238
+ timer = setTimeout(() => {
239
+ timedOut = true;
240
+ try { child.kill('SIGTERM'); } catch {}
241
+ finish({
242
+ exitCode: -1,
243
+ stdout,
244
+ stderr: stderr || `hook command timed out after ${timeoutMs}ms`,
245
+ timedOut,
246
+ spawnError: null,
247
+ });
248
+ }, timeoutMs);
249
+ timer.unref?.();
250
+ child.stdout?.on('data', (chunk) => {
251
+ stdoutBytes += chunk.length;
252
+ if (stdoutBytes <= MAX_BUFFER_BYTES) stdout += chunk.toString('utf8');
253
+ });
254
+ child.stderr?.on('data', (chunk) => {
255
+ stderrBytes += chunk.length;
256
+ if (stderrBytes <= MAX_BUFFER_BYTES) stderr += chunk.toString('utf8');
257
+ });
258
+ child.on('error', (error) => {
259
+ finish({
260
+ exitCode: -1,
261
+ stdout,
262
+ stderr: stderr || error?.message || String(error),
263
+ timedOut: false,
264
+ spawnError: error,
265
+ });
266
+ });
267
+ child.on('close', (code) => {
268
+ finish({
269
+ exitCode: timedOut ? -1 : (typeof code === 'number' ? code : 0),
270
+ stdout,
271
+ stderr,
272
+ timedOut,
273
+ spawnError: null,
274
+ });
275
+ });
276
+ try {
277
+ child.stdin?.end(stdin);
278
+ } catch {}
279
+ });
280
+ }
281
+
282
+ function resolveHeaderValue(value, allowed) {
283
+ if (typeof value !== 'string') return String(value ?? '');
284
+ return value.replace(/\$(?:\{([A-Za-z_][A-Za-z0-9_]*)\}|([A-Za-z_][A-Za-z0-9_]*))/g, (_m, braced, bare) => {
285
+ const name = braced || bare;
286
+ return allowed.has(name) ? (process.env[name] || '') : '';
287
+ });
288
+ }
289
+
290
+ export async function runHttpHandler(handler, payload, eventName) {
291
+ if (typeof fetch !== 'function') {
292
+ return { exitCode: -1, stdout: '', stderr: 'fetch is not available', timedOut: false, spawnError: new Error('fetch is not available') };
293
+ }
294
+ const timeoutMs = Math.round(handlerTimeoutS(handler, eventName) * 1000);
295
+ const controller = new AbortController();
296
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
297
+ timer.unref?.();
298
+ try {
299
+ const allowed = new Set(Array.isArray(handler.allowedEnvVars) ? handler.allowedEnvVars.map(String) : []);
300
+ const headers = { 'Content-Type': 'application/json' };
301
+ if (handler.headers && typeof handler.headers === 'object' && !Array.isArray(handler.headers)) {
302
+ for (const [key, value] of Object.entries(handler.headers)) {
303
+ headers[key] = resolveHeaderValue(value, allowed);
304
+ }
305
+ }
306
+ const response = await fetch(handler.url, {
307
+ method: 'POST',
308
+ headers,
309
+ body: JSON.stringify(payload),
310
+ signal: controller.signal,
311
+ });
312
+ const text = await response.text();
313
+ if (!response.ok) {
314
+ return { exitCode: 1, stdout: text, stderr: `HTTP ${response.status} ${response.statusText}`.trim(), timedOut: false, spawnError: null };
315
+ }
316
+ return { exitCode: 0, stdout: text, stderr: '', timedOut: false, spawnError: null };
317
+ } catch (error) {
318
+ const aborted = error?.name === 'AbortError';
319
+ return {
320
+ exitCode: -1,
321
+ stdout: '',
322
+ stderr: aborted ? `HTTP hook timed out: ${handler.url}` : (error?.message || String(error)),
323
+ timedOut: aborted,
324
+ spawnError: aborted ? null : error,
325
+ };
326
+ } finally {
327
+ clearTimeout(timer);
328
+ }
329
+ }
330
+
331
+ export async function runMcpToolHandler(handler, payload, eventName, mcpToolRunner) {
332
+ const timeoutMs = Math.round(handlerTimeoutS(handler, eventName) * 1000);
333
+ let name = String(handler.tool || '').trim();
334
+ if (handler.server && !name.startsWith('mcp__')) {
335
+ name = `mcp__${String(handler.server).trim()}__${name}`;
336
+ }
337
+ if (!name) {
338
+ return { exitCode: -1, stdout: '', stderr: 'mcp_tool handler missing tool name', timedOut: false, spawnError: null };
339
+ }
340
+ let timer = null;
341
+ try {
342
+ const runPromise = Promise.resolve(mcpToolRunner({ name, args: payload }));
343
+ const text = await Promise.race([
344
+ runPromise,
345
+ new Promise((_r, reject) => {
346
+ timer = setTimeout(() => reject(new Error(`mcp_tool hook timed out: ${name}`)), timeoutMs);
347
+ // No unref: this timer must keep the event loop alive so the race can
348
+ // settle even when the runner promise never resolves. Cleared in finally.
349
+ }),
350
+ ]);
351
+ return { exitCode: 0, stdout: limitText(String(text ?? '')), stderr: '', timedOut: false, spawnError: null };
352
+ } catch (error) {
353
+ const timedOut = /timed out/i.test(error?.message || '');
354
+ return { exitCode: -1, stdout: '', stderr: error?.message || String(error), timedOut, spawnError: null };
355
+ } finally {
356
+ if (timer) clearTimeout(timer);
357
+ }
358
+ }
359
+
360
+ export async function runPromptHandler(handler, payload, eventName, promptRunner) {
361
+ const timeoutMs = Math.round(handlerTimeoutS(handler, eventName) * 1000);
362
+ const prompt = String(handler.prompt || '');
363
+ if (!prompt) {
364
+ return { exitCode: -1, stdout: '', stderr: 'prompt handler missing prompt', timedOut: false, spawnError: null };
365
+ }
366
+ let timer = null;
367
+ try {
368
+ const runPromise = Promise.resolve(promptRunner({ prompt, payload, timeoutMs }));
369
+ const text = await Promise.race([
370
+ runPromise,
371
+ new Promise((_r, reject) => {
372
+ timer = setTimeout(() => reject(new Error(`prompt hook timed out: ${eventName}`)), timeoutMs);
373
+ // No unref: this timer must keep the event loop alive so the race can
374
+ // settle even when the runner promise never resolves. Cleared in finally.
375
+ }),
376
+ ]);
377
+ const raw = String(text ?? '').trim();
378
+ const deny = (reason) => ({ exitCode: 2, stdout: '', stderr: reason, timedOut: false, spawnError: null });
379
+ const allow = () => ({ exitCode: 0, stdout: '', stderr: '', timedOut: false, spawnError: null });
380
+ let verdict = null;
381
+ try {
382
+ verdict = JSON.parse(raw);
383
+ } catch {
384
+ verdict = undefined;
385
+ }
386
+ if (verdict && typeof verdict === 'object') {
387
+ if (verdict.ok === false) return deny(String(verdict.reason || `blocked by ${eventName} prompt hook`));
388
+ return allow();
389
+ }
390
+ // plain-text response
391
+ const lowered = raw.toLowerCase();
392
+ if (!raw || ['yes', 'true', 'allow', 'ok'].includes(lowered)) return allow();
393
+ if (['no', 'false', 'deny', 'block'].includes(lowered)) return deny(raw || `blocked by ${eventName} prompt hook`);
394
+ return allow();
395
+ } catch (error) {
396
+ const timedOut = /timed out/i.test(error?.message || '');
397
+ return { exitCode: -1, stdout: '', stderr: error?.message || String(error), timedOut, spawnError: null };
398
+ } finally {
399
+ if (timer) clearTimeout(timer);
400
+ }
401
+ }
402
+
403
+ export function parseHandlerOutput(run, eventName) {
404
+ const out = {
405
+ block: false,
406
+ reason: null,
407
+ permissionDecision: null,
408
+ updatedInput: null,
409
+ updatedToolOutput: null,
410
+ additionalContext: null,
411
+ systemMessage: null,
412
+ suppressOutput: false,
413
+ continueFlag: undefined,
414
+ askReason: null,
415
+ };
416
+ if (run.timedOut || run.spawnError || run.async) return out;
417
+ if (run.exitCode === 2) {
418
+ if (EXIT2_BLOCK_EVENTS.has(eventName)) {
419
+ out.block = true;
420
+ out.reason = limitText((run.stderr || '').trim()) || `blocked by ${eventName} hook`;
421
+ }
422
+ return out;
423
+ }
424
+ if (run.exitCode !== 0) return out;
425
+ const rawText = (run.stdout || '').trim();
426
+ if (!rawText) return out;
427
+ if (!(rawText.startsWith('{') || rawText.startsWith('['))) {
428
+ if (PLAIN_STDOUT_CONTEXT_EVENTS.has(eventName)) out.additionalContext = limitText(rawText);
429
+ return out;
430
+ }
431
+ try {
432
+ const json = JSON.parse(rawText);
433
+ if (!json || typeof json !== 'object' || Array.isArray(json)) return out;
434
+ if (json.continue === false) {
435
+ out.continueFlag = false;
436
+ out.block = true;
437
+ out.reason = limitText(json.stopReason || json.reason || `stopped by ${eventName} hook`);
438
+ }
439
+ if (json.decision === 'block' && TOP_LEVEL_DECISION_EVENTS.has(eventName)) {
440
+ out.block = true;
441
+ out.reason = limitText(json.reason || out.reason || `blocked by ${eventName} hook`);
442
+ }
443
+ if (json.suppressOutput) out.suppressOutput = true;
444
+ if (typeof json.systemMessage === 'string') out.systemMessage = limitText(json.systemMessage);
445
+ if (typeof json.additionalContext === 'string') out.additionalContext = limitText(json.additionalContext);
446
+
447
+ const hso = json.hookSpecificOutput;
448
+ const hsoMatches = hso && typeof hso === 'object'
449
+ && (!hso.hookEventName || hso.hookEventName === eventName);
450
+ if (hsoMatches) {
451
+ if (typeof hso.additionalContext === 'string') out.additionalContext = limitText(hso.additionalContext);
452
+ if (hso.updatedInput && typeof hso.updatedInput === 'object' && !Array.isArray(hso.updatedInput)) {
453
+ out.updatedInput = hso.updatedInput;
454
+ }
455
+ if (hso.updatedToolOutput != null) out.updatedToolOutput = hso.updatedToolOutput;
456
+ if (hso.permissionDecision) out.permissionDecision = String(hso.permissionDecision).toLowerCase();
457
+ if (hso.permissionDecisionReason) out.reason = out.reason || limitText(hso.permissionDecisionReason);
458
+ if (eventName === 'PermissionRequest' && hso.decision && typeof hso.decision === 'object') {
459
+ const behavior = String(hso.decision.behavior || '').toLowerCase();
460
+ if (behavior === 'deny') {
461
+ out.block = true;
462
+ out.reason = out.reason || limitText(hso.decision.reason || `denied by ${eventName} hook`);
463
+ }
464
+ if (hso.decision.updatedInput && typeof hso.decision.updatedInput === 'object' && !Array.isArray(hso.decision.updatedInput)) {
465
+ out.updatedInput = hso.decision.updatedInput;
466
+ }
467
+ }
468
+ }
469
+ if (eventName === 'PreToolUse') {
470
+ if (out.permissionDecision === 'deny') {
471
+ out.block = true;
472
+ out.reason = out.reason || `denied by ${eventName} hook`;
473
+ } else if (out.permissionDecision === 'ask') {
474
+ out.askReason = out.reason || `ask requested by ${eventName} hook`;
475
+ }
476
+ }
477
+ } catch {
478
+ if (PLAIN_STDOUT_CONTEXT_EVENTS.has(eventName)) out.additionalContext = limitText(rawText);
479
+ }
480
+ return out;
481
+ }
@@ -0,0 +1,31 @@
1
+ export function compactValue(value) {
2
+ if (value == null) return value;
3
+ if (typeof value === 'string') return value.length > 180 ? `${value.slice(0, 180)}...` : value;
4
+ if (typeof value === 'number' || typeof value === 'boolean') return value;
5
+ if (Array.isArray(value)) return value.slice(0, 8).map(compactValue);
6
+ if (typeof value === 'object') {
7
+ const out = {};
8
+ for (const [key, val] of Object.entries(value).slice(0, 12)) {
9
+ if (/token|secret|key|password/i.test(key)) {
10
+ out[key] = '<redacted>';
11
+ } else {
12
+ out[key] = compactValue(val);
13
+ }
14
+ }
15
+ return out;
16
+ }
17
+ return String(value);
18
+ }
19
+
20
+ export function summarizePayload(payload = {}) {
21
+ if (!payload || typeof payload !== 'object') return String(payload ?? '');
22
+ const parts = [];
23
+ if (payload.sessionId || payload.session_id) parts.push(`session=${payload.sessionId || payload.session_id}`);
24
+ if (payload.name || payload.tool_name) parts.push(`name=${payload.name || payload.tool_name}`);
25
+ if (payload.provider || payload.model) parts.push([payload.provider, payload.model].filter(Boolean).join('/'));
26
+ if (payload.prompt) parts.push(`prompt=${String(payload.prompt).slice(0, 60).replace(/\s+/g, ' ')}`);
27
+ if (payload.reason) parts.push(`reason=${String(payload.reason).slice(0, 120)}`);
28
+ if (payload.error) parts.push(`error=${String(payload.error).slice(0, 120)}`);
29
+ if (payload.elapsedMs != null) parts.push(`${payload.elapsedMs}ms`);
30
+ return parts.join(' · ');
31
+ }
@@ -0,0 +1,77 @@
1
+ import { compactValue } from './payload.mjs';
2
+ import { defaultShellKind } from './handlers.mjs';
3
+
4
+ export function matchesList(pattern, value) {
5
+ if (pattern == null || pattern === '*' || pattern === 'all') return true;
6
+ const items = Array.isArray(pattern) ? pattern : [pattern];
7
+ return items.some((item) => {
8
+ const text = String(item || '').trim();
9
+ if (!text || text === '*' || text === 'all') return true;
10
+ if (text.endsWith('*')) return String(value || '').startsWith(text.slice(0, -1));
11
+ return text === String(value || '');
12
+ });
13
+ }
14
+
15
+ export function ruleMatches(rule, input) {
16
+ if (!rule || typeof rule !== 'object' || rule.enabled === false) return false;
17
+ const toolPattern = rule.tool ?? rule.name ?? rule.tools;
18
+ if (!matchesList(toolPattern, input.name)) return false;
19
+ if (rule.cwd && !String(input.cwd || '').includes(String(rule.cwd))) return false;
20
+ const match = rule.match ?? rule.contains;
21
+ if (match != null) {
22
+ const haystack = JSON.stringify({ args: input.args ?? null, cwd: input.cwd ?? null });
23
+ const needles = Array.isArray(match) ? match : [match];
24
+ if (!needles.some((needle) => haystack.includes(String(needle)))) return false;
25
+ }
26
+ return true;
27
+ }
28
+
29
+ export function decisionFromRule(rule, input) {
30
+ const action = String(rule.action || rule.decision || 'allow').toLowerCase();
31
+ if (action === 'deny' || action === 'block') {
32
+ return { action: 'deny', reason: rule.reason || rule.message || `blocked by hook rule for ${input.name}` };
33
+ }
34
+ if (action === 'modify' || action === 'rewrite') {
35
+ const nextArgs = rule.args && typeof rule.args === 'object'
36
+ ? rule.args
37
+ : { ...(input.args || {}), ...(rule.patch || {}) };
38
+ return { action: 'modify', args: nextArgs, reason: rule.reason || rule.message || `modified by hook rule for ${input.name}` };
39
+ }
40
+ if (action === 'ask') {
41
+ return { action: 'ask', reason: rule.reason || rule.message || `approval requested by hook rule for ${input.name}` };
42
+ }
43
+ return { action: 'allow', reason: rule.reason || rule.message || null };
44
+ }
45
+
46
+ export function summarizeRule(rule, index) {
47
+ return {
48
+ index,
49
+ enabled: rule.enabled !== false,
50
+ tool: rule.tool ?? rule.name ?? rule.tools ?? '*',
51
+ action: rule.action || rule.decision || 'allow',
52
+ match: rule.match ?? rule.contains ?? null,
53
+ cwd: rule.cwd || null,
54
+ reason: rule.reason || rule.message || null,
55
+ patch: compactValue(rule.patch || rule.args || null),
56
+ };
57
+ }
58
+
59
+ export function handlerDedupeKey(handler) {
60
+ if (!handler || typeof handler !== 'object') return '';
61
+ const ifKey = handler.if != null ? `|if:${JSON.stringify(handler.if)}` : '';
62
+ if (handler.type === 'command') {
63
+ return `command:${handler._pluginRoot || ''}:${handler.command || ''}:${JSON.stringify(handler.args || null)}${ifKey}`;
64
+ }
65
+ if (handler.type === 'http') return `http:${handler.url || ''}${ifKey}`;
66
+ if (handler.type === 'mcp_tool') return `mcp_tool:${handler.server || ''}:${handler.tool || ''}${ifKey}`;
67
+ if (handler.type === 'prompt') return `prompt:${handler.prompt || ''}${ifKey}`;
68
+ return `${handler.type || 'unknown'}:${JSON.stringify(handler)}`;
69
+ }
70
+
71
+ export function shellCountFor(handler) {
72
+ if (handler?.type === 'command') {
73
+ if (handler.args) return 'exec';
74
+ return handler.shell === 'powershell' || handler.shell === 'bash' ? handler.shell : defaultShellKind();
75
+ }
76
+ return handler?.type || 'unknown';
77
+ }