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
@@ -11,7 +11,7 @@ import { buildHeadlessSpawnArgs } from '../src/headless-role.mjs';
11
11
  import { createStandaloneChannelWorker } from '../src/standalone/channel-worker.mjs';
12
12
  import { OpenAIOAuthProvider, buildRequestBody, sendViaHttpSse } from '../src/runtime/agent/orchestrator/providers/openai-oauth.mjs';
13
13
  import { _test as _anthropicOAuthTest } from '../src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs';
14
- import { _logicalResponseItemMatch, _resolveOpenAiPromptCacheRatePolicy } from '../src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs';
14
+ import { _logicalResponseItemMatch } from '../src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs';
15
15
  import { _mergePendingMessageEntries, applyAskTerminalUsageTotals, closeSession, createSession, drainPendingMessages, enqueuePendingMessage, resumeSession } from '../src/runtime/agent/orchestrator/session/manager.mjs';
16
16
  import {
17
17
  contentHasImage,
@@ -137,46 +137,6 @@ function assertOk(name, result, pattern = null) {
137
137
  }
138
138
 
139
139
  {
140
- const fullPolicy = _resolveOpenAiPromptCacheRatePolicy({}, {
141
- mode: 'full',
142
- frameInputItems: 40,
143
- deltaTokens: 9000,
144
- hasPreviousResponseId: false,
145
- });
146
- assert(fullPolicy.policy === 'full_guard' && fullPolicy.limitPerMin === 12, `full cache lane should keep 12rpm guard: ${JSON.stringify(fullPolicy)}`);
147
-
148
- const deltaPolicy = _resolveOpenAiPromptCacheRatePolicy({}, {
149
- mode: 'delta',
150
- frameInputItems: 1,
151
- deltaTokens: 9000,
152
- hasPreviousResponseId: true,
153
- });
154
- assert(deltaPolicy.policy === 'delta_relaxed' && deltaPolicy.limitPerMin === 60, `small delta should use relaxed cache lane rpm: ${JSON.stringify(deltaPolicy)}`);
155
-
156
- const largeDeltaPolicy = _resolveOpenAiPromptCacheRatePolicy({}, {
157
- mode: 'delta',
158
- frameInputItems: 20,
159
- deltaTokens: 9000,
160
- hasPreviousResponseId: true,
161
- });
162
- assert(largeDeltaPolicy.policy === 'delta_guarded' && largeDeltaPolicy.limitPerMin === 12, `large delta should fall back to full guard: ${JSON.stringify(largeDeltaPolicy)}`);
163
-
164
- const customDeltaPolicy = _resolveOpenAiPromptCacheRatePolicy({ openaiCacheLaneDeltaRateLimitPerMin: 90 }, {
165
- mode: 'delta',
166
- frameInputItems: 1,
167
- deltaTokens: 9000,
168
- hasPreviousResponseId: true,
169
- });
170
- assert(customDeltaPolicy.limitPerMin === 90, `custom delta rpm should be honored: ${JSON.stringify(customDeltaPolicy)}`);
171
-
172
- const unlimitedDeltaPolicy = _resolveOpenAiPromptCacheRatePolicy({ openaiCacheLaneDeltaRateLimitPerMin: 0 }, {
173
- mode: 'delta',
174
- frameInputItems: 1,
175
- deltaTokens: 9000,
176
- hasPreviousResponseId: true,
177
- });
178
- assert(unlimitedDeltaPolicy.policy === 'delta_unlimited' && unlimitedDeltaPolicy.limitPerMin === 0, `delta rpm=0 should disable delta rate wait: ${JSON.stringify(unlimitedDeltaPolicy)}`);
179
-
180
140
  const originalFunctionCall = {
181
141
  type: 'function_call',
182
142
  call_id: 'call_tool_1',
@@ -589,20 +549,28 @@ if (grepContextPolicyArgs['-C'] !== GREP_CONTEXT_MAX || Object.prototype.hasOwnP
589
549
  throw new Error(`grep context policy must canonicalize and clamp explicit context: ${JSON.stringify(grepContextPolicyArgs)}`);
590
550
  }
591
551
 
592
- const invalidGrepPath = validateBuiltinArgs('grep', {
552
+ // Multiple absolute paths in one string are now auto-split into a path array
553
+ // (arg-guard splitMultipleAbsoluteWindowsPaths) instead of rejected.
554
+ const multiGrepPathArgs = {
593
555
  pattern: 'providerStatus',
594
556
  path: 'C:\\Project\\mixdog\\src\\tui C:\\Project\\mixdog\\src\\mixdog-session-runtime.mjs',
595
- });
596
- if (!/multiple absolute paths/i.test(invalidGrepPath || '')) {
597
- throw new Error(`grep multi-path guard failed: ${invalidGrepPath}`);
557
+ };
558
+ const multiGrepPathErr = validateBuiltinArgs('grep', multiGrepPathArgs);
559
+ if (multiGrepPathErr) {
560
+ throw new Error(`grep multi-path auto-split should pass validation: ${multiGrepPathErr}`);
561
+ }
562
+ if (!Array.isArray(multiGrepPathArgs.path) || multiGrepPathArgs.path.length !== 2) {
563
+ throw new Error(`grep multi-path auto-split should coerce to 2-element array: ${JSON.stringify(multiGrepPathArgs.path)}`);
598
564
  }
599
565
 
600
- const invalidGrepLookaround = validateBuiltinArgs('grep', {
566
+ // Lookaround/backrefs are no longer rejected at validation time: search-tool
567
+ // routes them to rg --pcre2 at runtime (arg-guard.mjs comment near globKeys).
568
+ const lookaroundGrepErr = validateBuiltinArgs('grep', {
601
569
  pattern: 'C:\\\\Project(?!\\\\mixdog)',
602
570
  path: root,
603
571
  });
604
- if (!/lookaround\/backrefs/i.test(invalidGrepLookaround || '')) {
605
- throw new Error(`grep unsupported-regex guard failed: ${invalidGrepLookaround}`);
572
+ if (lookaroundGrepErr) {
573
+ throw new Error(`grep lookaround pattern should pass validation (PCRE2 runtime routing): ${lookaroundGrepErr}`);
606
574
  }
607
575
 
608
576
  // Windows drive path + no explicit shell used to be rejected with a retry hint;
@@ -991,10 +959,7 @@ setInternalToolsProvider({
991
959
  { name: 'recall', description: 'Memory recall surface.', inputSchema: { type: 'object', properties: {} }, annotations: { readOnlyHint: true } },
992
960
  { name: 'search', description: 'Web search surface.', inputSchema: { type: 'object', properties: {} }, annotations: { readOnlyHint: true, openWorldHint: true } },
993
961
  { name: 'reply', description: 'Channel reply surface.', inputSchema: { type: 'object', properties: {} }, annotations: { destructiveHint: true } },
994
- { name: 'edit_message', description: 'Channel edit surface.', inputSchema: { type: 'object', properties: {} }, annotations: { destructiveHint: true } },
995
962
  { name: 'web_fetch', description: 'Web fetch surface.', inputSchema: { type: 'object', properties: {} }, annotations: { readOnlyHint: true, openWorldHint: true } },
996
- { name: 'reload_config', description: 'Config reload surface.', inputSchema: { type: 'object', properties: {} }, annotations: { destructiveHint: true } },
997
- { name: 'inject_command', description: 'Command injection surface.', inputSchema: { type: 'object', properties: {} }, annotations: { destructiveHint: true } },
998
963
  ],
999
964
  });
1000
965
  {
@@ -1197,12 +1162,12 @@ setInternalToolsProvider({
1197
1162
  if (readTools.includes('shell') || writeTools.includes('shell')) {
1198
1163
  throw new Error(`read/read-write agent schemas must omit shell: read=${readTools.join(', ')} write=${writeTools.join(', ')}`);
1199
1164
  }
1200
- for (const name of ['shell', 'apply_patch', 'task', 'diagnostics', 'open_config']) {
1165
+ for (const name of ['shell', 'apply_patch', 'task']) {
1201
1166
  if (readTools.includes(name)) {
1202
1167
  throw new Error(`read agent schema must omit non-read tool ${name}: read=${readTools.join(', ')}`);
1203
1168
  }
1204
1169
  }
1205
- for (const name of ['apply_patch', 'task', 'diagnostics']) {
1170
+ for (const name of ['apply_patch', 'task']) {
1206
1171
  if (name === 'apply_patch' && !writeTools.includes(name)) {
1207
1172
  throw new Error(`read-write agent schema must preserve apply_patch: write=${writeTools.join(', ')}`);
1208
1173
  }
@@ -1210,12 +1175,7 @@ setInternalToolsProvider({
1210
1175
  throw new Error(`read-write agent schema must omit non-edit tool ${name}: write=${writeTools.join(', ')}`);
1211
1176
  }
1212
1177
  }
1213
- for (const name of ['open_config']) {
1214
- if (writeTools.includes(name)) {
1215
- throw new Error(`read-write agent schema must omit config/skill tool ${name}: write=${writeTools.join(', ')}`);
1216
- }
1217
- }
1218
- for (const name of ['memory', 'recall', 'reply', 'edit_message', 'reload_config', 'inject_command']) {
1178
+ for (const name of ['memory', 'recall', 'reply']) {
1219
1179
  if (readTools.includes(name) || writeTools.includes(name)) {
1220
1180
  throw new Error(`read/read-write agent schema must not expose full-runtime internal tool ${name}: read=${readTools.join(', ')} write=${writeTools.join(', ')}`);
1221
1181
  }
@@ -1293,7 +1253,7 @@ setInternalToolsProvider({
1293
1253
  const hiddenAgents = JSON.parse(readFileSync(join(root, 'src', 'defaults', 'agents.json'), 'utf8')).agents || [];
1294
1254
  const hiddenPreset = { id: 'hidden-smoke', name: 'hidden-smoke', type: 'agent', provider: 'openai-oauth', model: 'tool-smoke-model', tools: 'full' };
1295
1255
  const hiddenRuntimeSpec = { scopeKey: 'hidden-role-smoke', lane: 'agent' };
1296
- const hiddenBadTools = new Set(['shell', 'task', 'diagnostics', 'open_config', 'Skill', 'memory', 'reply', 'edit_message', 'recall', 'reload_config', 'inject_command']);
1256
+ const hiddenBadTools = new Set(['shell', 'task', 'Skill', 'memory', 'reply', 'recall']);
1297
1257
  const expectedForHiddenAgent = (permission, schemaAllowedTools) => {
1298
1258
  if (Array.isArray(schemaAllowedTools)) return schemaAllowedTools.slice();
1299
1259
  if (permission === 'none') return [];
@@ -1517,7 +1477,7 @@ if (!/before text grep/i.test(codeGraphDescription)) {
1517
1477
  if (!/repo-local/i.test(codeGraphDescription) || !/NOT web search|not web/i.test(codeGraphDescription)) {
1518
1478
  throw new Error('code_graph description must mark itself repo-local (not web search)');
1519
1479
  }
1520
- if (!/repo-local|not web/i.test(codeGraphProps.mode?.description || '') || !/source file/i.test(codeGraphProps.file?.description || '')) {
1480
+ if (!/repo-local|not web/i.test(codeGraphProps.mode?.description || '') || !/source file/i.test(codeGraphProps.files?.description || '')) {
1521
1481
  throw new Error('code_graph schema must keep compact, repo-local field descriptions');
1522
1482
  }
1523
1483
  const recallTool = MEMORY_TOOL_DEFS.find((tool) => tool.name === 'recall');
@@ -1588,7 +1548,7 @@ if (!recallProps.sessionOnly || !/session only/i.test(recallProps.sessionOnly?.d
1588
1548
  }
1589
1549
  const memoryTool = MEMORY_TOOL_DEFS.find((tool) => tool.name === 'memory');
1590
1550
  const memoryProps = memoryTool?.inputSchema?.properties || {};
1591
- if (!/explicit mutation/i.test(memoryTool?.description || '') || !/Destructive jobs require exact confirm/i.test(memoryTool?.description || '') || !/Exact confirmation phrase/i.test(memoryProps.confirm?.description || '')) {
1551
+ if (!/mutation/i.test(memoryTool?.description || '') || !/Exact confirmation phrase/i.test(memoryProps.confirm?.description || '')) {
1592
1552
  throw new Error('memory schema must preserve mutation/destructive confirmation guidance');
1593
1553
  }
1594
1554
  const searchTool = SEARCH_TOOL_DEFS.find((tool) => tool.name === 'search');
@@ -1783,7 +1743,7 @@ if (grepTool?.inputSchema?.properties?.head_limit?.minimum !== 0 || !/keep small
1783
1743
  if (grepTool?.inputSchema?.properties?.type) {
1784
1744
  throw new Error('grep type schema must stay hidden; prefer glob for extension narrowing');
1785
1745
  }
1786
- if (!/code flow before text search/i.test(codeGraphProps.mode?.description || '') || !/one anchor is enough/i.test(codeGraphProps.symbol?.description || '') || !/one call/i.test(codeGraphProps.symbols?.description || '')) {
1746
+ if (!/code flow before text search/i.test(codeGraphProps.mode?.description || '') || !/one anchor is enough/i.test(codeGraphProps.symbols?.description || '') || !/one call/i.test(codeGraphProps.symbols?.description || '')) {
1787
1747
  throw new Error('code_graph schema fields must steer away from repeated lookup loops');
1788
1748
  }
1789
1749
 
@@ -0,0 +1,90 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * scripts/tui-render-smoke.mjs — undeclared-identifier gate for the TUI bundle.
4
+ *
5
+ * Why: esbuild does not flag free (undeclared) identifiers in JSX modules —
6
+ * they may legitimately be runtime globals — and no smoke actually renders
7
+ * App. That gap shipped two ReferenceErrors (promptContentRows,
8
+ * TRANSCRIPT_WINDOW_OVERSCAN_ROWS) during the App.jsx split.
9
+ *
10
+ * How: parse src/tui/dist/index.mjs with acorn, run eslint-scope, and fail on
11
+ * any reference that resolves to no binding and is not a known JS/Node/DOM
12
+ * global. Static, deterministic, no terminal or engine session needed.
13
+ *
14
+ * Run: node scripts/tui-render-smoke.mjs (or `npm run smoke:tui`)
15
+ * Exit: 0 = clean, 1 = undeclared identifiers found or bundle missing.
16
+ */
17
+ import { readFileSync, existsSync } from 'node:fs';
18
+ import { dirname, join } from 'node:path';
19
+ import { fileURLToPath } from 'node:url';
20
+ import { parse } from 'acorn';
21
+ import { analyze } from 'eslint-scope';
22
+
23
+ const ROOT = join(dirname(fileURLToPath(import.meta.url)), '..');
24
+ const BUNDLE = join(ROOT, 'src', 'tui', 'dist', 'index.mjs');
25
+
26
+ // Globals that are legal free identifiers in the bundle. Anything outside
27
+ // this set that resolves to no binding is a guaranteed ReferenceError at
28
+ // runtime (module scope is strict; there is no window fallback in node ESM).
29
+ const KNOWN_GLOBALS = new Set([
30
+ // ECMAScript builtins
31
+ 'Array', 'ArrayBuffer', 'Atomics', 'BigInt', 'BigInt64Array', 'BigUint64Array', 'Boolean',
32
+ 'DataView', 'Date', 'Error', 'EvalError', 'FinalizationRegistry', 'Float32Array',
33
+ 'Float64Array', 'Function', 'Infinity', 'Int8Array', 'Int16Array', 'Int32Array',
34
+ 'Intl', 'JSON', 'Map', 'Math', 'NaN', 'Number', 'Object', 'Promise', 'Proxy',
35
+ 'RangeError', 'ReferenceError', 'Reflect', 'RegExp', 'Set', 'SharedArrayBuffer',
36
+ 'String', 'Symbol', 'SyntaxError', 'TypeError', 'URIError', 'Uint8Array',
37
+ 'Uint8ClampedArray', 'Uint16Array', 'Uint32Array', 'WeakMap', 'WeakRef', 'WeakSet',
38
+ 'decodeURI', 'decodeURIComponent', 'encodeURI', 'encodeURIComponent', 'eval',
39
+ 'globalThis', 'isFinite', 'isNaN', 'parseFloat', 'parseInt', 'undefined',
40
+ // Node.js globals
41
+ 'AbortController', 'AbortSignal', 'Blob', 'Buffer', 'ByteLengthQueuingStrategy',
42
+ 'CompressionStream', 'CountQueuingStrategy', 'Crypto', 'CryptoKey', 'CustomEvent',
43
+ 'DecompressionStream', 'Event', 'EventTarget', 'File', 'FormData', 'Headers',
44
+ 'MessageChannel', 'MessageEvent', 'MessagePort', 'Navigator', 'PerformanceEntry',
45
+ 'PerformanceObserver', 'ReadableStream', 'ReadableStreamDefaultReader', 'Request',
46
+ 'Response', 'SubtleCrypto', 'TextDecoder', 'TextDecoderStream', 'TextEncoder',
47
+ 'TextEncoderStream', 'TransformStream', 'URL', 'URLSearchParams', 'WebAssembly',
48
+ 'WebSocket', 'WritableStream', 'atob', 'btoa', 'clearImmediate', 'clearInterval',
49
+ 'clearTimeout', 'console', 'crypto', 'fetch', 'navigator', 'performance',
50
+ 'process', 'queueMicrotask', 'require', 'setImmediate', 'setInterval',
51
+ 'setTimeout', 'structuredClone',
52
+ // Bundler/runtime artifacts occasionally referenced defensively
53
+ 'window', 'document', 'reportError',
54
+ ]);
55
+
56
+ if (!existsSync(BUNDLE)) {
57
+ console.error(`tui-render-smoke: FAIL — bundle not found at ${BUNDLE}; run \`npm run build:tui\` first`);
58
+ process.exit(1);
59
+ }
60
+
61
+ const source = readFileSync(BUNDLE, 'utf8');
62
+
63
+ let ast;
64
+ try {
65
+ ast = parse(source, { ecmaVersion: 'latest', sourceType: 'module', locations: true, ranges: true });
66
+ } catch (err) {
67
+ console.error(`tui-render-smoke: FAIL — bundle does not parse: ${err.message}`);
68
+ process.exit(1);
69
+ }
70
+
71
+ const scopeManager = analyze(ast, { ecmaVersion: 2022, sourceType: 'module' });
72
+ const unresolved = scopeManager.globalScope.through.filter(
73
+ (ref) => !KNOWN_GLOBALS.has(ref.identifier.name),
74
+ );
75
+
76
+ if (unresolved.length > 0) {
77
+ const seen = new Map();
78
+ for (const ref of unresolved) {
79
+ const { name } = ref.identifier;
80
+ if (!seen.has(name)) seen.set(name, ref.identifier.loc?.start);
81
+ }
82
+ console.error(`tui-render-smoke: FAIL — ${seen.size} undeclared identifier(s) in src/tui/dist/index.mjs:`);
83
+ for (const [name, loc] of seen) {
84
+ console.error(` ${name} (dist/index.mjs:${loc ? `${loc.line}:${loc.column}` : '?'})`);
85
+ }
86
+ console.error('These throw ReferenceError when the TUI renders. Fix missing imports in src/tui/ and rebuild.');
87
+ process.exit(1);
88
+ }
89
+
90
+ console.log('tui-render-smoke: ok — no undeclared identifiers in TUI bundle');
@@ -0,0 +1,208 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import * as crypto from 'node:crypto';
4
+ import { mkdirSync, mkdtempSync, writeFileSync } from 'node:fs';
5
+ import { tmpdir } from 'node:os';
6
+ import { join } from 'node:path';
7
+ import { readMarkdownDocument } from '../src/runtime/shared/markdown-frontmatter.mjs';
8
+
9
+ // DATA_DIR resolves from MIXDOG_DATA_DIR at module import time (via
10
+ // ./config.mjs -> resolvePluginData), so the env var MUST be set before
11
+ // the first dynamic import of webhook.mjs.
12
+ const dataDir = mkdtempSync(join(tmpdir(), 'mixdog-webhook-test-'));
13
+ process.env.MIXDOG_DATA_DIR = dataDir;
14
+
15
+ const {
16
+ extractSignature,
17
+ verifySignature,
18
+ loadEndpointConfig,
19
+ STRIPE_TOLERANCE_MS,
20
+ } = await import('../src/runtime/channels/lib/webhook.mjs');
21
+
22
+ function hmacHex(secret, payload) {
23
+ return crypto.createHmac('sha256', secret).update(payload).digest('hex');
24
+ }
25
+
26
+ // ── verifySignature: github/generic hex HMAC ──────────────────────────
27
+
28
+ test('verifySignature: github parser accepts a valid sha256 hex signature', () => {
29
+ const secret = 'shh';
30
+ const body = '{"hello":"world"}';
31
+ const sig = hmacHex(secret, body);
32
+ assert.equal(verifySignature(secret, body, sig, 'github'), true);
33
+ });
34
+
35
+ test('verifySignature: generic parser accepts a valid sha256 hex signature', () => {
36
+ const secret = 'shh';
37
+ const body = '{"hello":"world"}';
38
+ const sig = hmacHex(secret, body);
39
+ assert.equal(verifySignature(secret, body, sig, 'generic'), true);
40
+ });
41
+
42
+ test('verifySignature: github/generic reject a wrong signature', () => {
43
+ const secret = 'shh';
44
+ const body = '{"hello":"world"}';
45
+ const wrong = hmacHex('other-secret', body);
46
+ assert.equal(verifySignature(secret, body, wrong, 'github'), false);
47
+ });
48
+
49
+ test('verifySignature: length-mismatch hex signature returns false without throwing', () => {
50
+ const secret = 'shh';
51
+ const body = 'payload';
52
+ // Valid hex, wrong byte length vs the 32-byte sha256 digest.
53
+ assert.doesNotThrow(() => {
54
+ assert.equal(verifySignature(secret, body, 'ab', 'github'), false);
55
+ });
56
+ });
57
+
58
+ test('verifySignature: malformed (non-hex) signature returns false without throwing', () => {
59
+ const secret = 'shh';
60
+ const body = 'payload';
61
+ assert.doesNotThrow(() => {
62
+ assert.equal(verifySignature(secret, body, 'not-hex-zzzz', 'github'), false);
63
+ });
64
+ });
65
+
66
+ // ── verifySignature: stripe t= / v1= scheme ───────────────────────────
67
+
68
+ test('verifySignature: stripe accepts a valid t+v1 pair', () => {
69
+ const secret = 'stripe-secret';
70
+ const body = '{"id":"evt_1"}';
71
+ const t = Math.floor(Date.now() / 1000);
72
+ const v1 = hmacHex(secret, `${t}.${body}`);
73
+ assert.equal(verifySignature(secret, body, `t=${t},v1=${v1}`, 'stripe'), true);
74
+ });
75
+
76
+ test('verifySignature: stripe rejects a header missing t=', () => {
77
+ const secret = 'stripe-secret';
78
+ const body = '{"id":"evt_1"}';
79
+ const t = Math.floor(Date.now() / 1000);
80
+ const v1 = hmacHex(secret, `${t}.${body}`);
81
+ assert.equal(verifySignature(secret, body, `v1=${v1}`, 'stripe'), false);
82
+ });
83
+
84
+ test('verifySignature: stripe rejects a header missing v1=', () => {
85
+ const secret = 'stripe-secret';
86
+ const body = '{"id":"evt_1"}';
87
+ const t = Math.floor(Date.now() / 1000);
88
+ assert.equal(verifySignature(secret, body, `t=${t}`, 'stripe'), false);
89
+ });
90
+
91
+ test('verifySignature: stripe rejects a stale timestamp beyond the tolerance window (replay)', () => {
92
+ const secret = 'stripe-secret';
93
+ const body = '{"id":"evt_1"}';
94
+ const staleT = Math.floor((Date.now() - STRIPE_TOLERANCE_MS - 60_000) / 1000);
95
+ const v1 = hmacHex(secret, `${staleT}.${body}`);
96
+ assert.equal(verifySignature(secret, body, `t=${staleT},v1=${v1}`, 'stripe'), false);
97
+ });
98
+
99
+ test('verifySignature: stripe rejects a tampered v1 (HMAC mismatch) with a fresh t', () => {
100
+ const secret = 'stripe-secret';
101
+ const body = '{"id":"evt_1"}';
102
+ const t = Math.floor(Date.now() / 1000);
103
+ const tamperedV1 = hmacHex('wrong-secret', `${t}.${body}`);
104
+ assert.equal(verifySignature(secret, body, `t=${t},v1=${tamperedV1}`, 'stripe'), false);
105
+ });
106
+
107
+ // ── extractSignature: parser mapping + fallback scan ──────────────────
108
+
109
+ test('extractSignature: github parser strips the sha256= prefix from x-hub-signature-256', () => {
110
+ const headers = { 'x-hub-signature-256': 'sha256=deadbeef' };
111
+ assert.equal(extractSignature(headers, 'github'), 'deadbeef');
112
+ });
113
+
114
+ test('extractSignature: generic parser strips the sha256= prefix from x-signature-256', () => {
115
+ const headers = { 'x-signature-256': 'sha256=cafebabe' };
116
+ assert.equal(extractSignature(headers, 'generic'), 'cafebabe');
117
+ });
118
+
119
+ test('extractSignature: stripe parser returns the raw stripe-signature value (no prefix)', () => {
120
+ const headers = { 'stripe-signature': 't=1,v1=abc123' };
121
+ assert.equal(extractSignature(headers, 'stripe'), 't=1,v1=abc123');
122
+ });
123
+
124
+ test('extractSignature: sentry parser returns the raw sentry-hook-signature value', () => {
125
+ const headers = { 'sentry-hook-signature': 'raw-sig-value' };
126
+ assert.equal(extractSignature(headers, 'sentry'), 'raw-sig-value');
127
+ });
128
+
129
+ test('extractSignature: unknown parser name falls back to scanning all known headers', () => {
130
+ const headers = { 'x-signature-256': 'sha256=fallback-hit' };
131
+ assert.equal(extractSignature(headers, 'no-such-parser'), 'fallback-hit');
132
+ });
133
+
134
+ test('extractSignature: no parser given scans known headers in declaration order', () => {
135
+ const headers = { 'x-hub-signature-256': 'sha256=github-hit' };
136
+ assert.equal(extractSignature(headers, undefined), 'github-hit');
137
+ });
138
+
139
+ test('extractSignature: returns null when no known signature header is present', () => {
140
+ const headers = { 'x-some-other-header': 'value' };
141
+ assert.equal(extractSignature(headers, 'github'), null);
142
+ });
143
+
144
+ // ── endpoint config loader: WEBHOOK.md frontmatter + secret side file ─
145
+
146
+ test('loadEndpointConfig: loads frontmatter + reads secret from side file', () => {
147
+ const name = 'my-endpoint';
148
+ const dir = join(dataDir, 'webhooks', name);
149
+ mkdirSync(dir, { recursive: true });
150
+ writeFileSync(
151
+ join(dir, 'WEBHOOK.md'),
152
+ [
153
+ '---',
154
+ 'channel: main',
155
+ 'parser: github',
156
+ 'enabled: true',
157
+ '---',
158
+ '',
159
+ 'Handle this webhook by summarizing the payload.',
160
+ '',
161
+ ].join('\n'),
162
+ 'utf8',
163
+ );
164
+ writeFileSync(join(dir, 'secret'), 'my-webhook-secret\n', 'utf8');
165
+
166
+ const cfg = loadEndpointConfig(name);
167
+ assert.equal(cfg.channel, 'main');
168
+ assert.equal(cfg.parser, 'github');
169
+ assert.equal(cfg.enabled, true);
170
+ });
171
+
172
+ test('loadEndpointConfig: enabled: false frontmatter casts to boolean false', () => {
173
+ const name = 'disabled-endpoint';
174
+ const dir = join(dataDir, 'webhooks', name);
175
+ mkdirSync(dir, { recursive: true });
176
+ writeFileSync(
177
+ join(dir, 'WEBHOOK.md'),
178
+ ['---', 'channel: main', 'enabled: false', '---', '', 'body text', ''].join('\n'),
179
+ 'utf8',
180
+ );
181
+ const cfg = loadEndpointConfig(name);
182
+ assert.equal(cfg.enabled, false);
183
+ });
184
+
185
+ test('loadEndpointConfig: returns null for a name with no WEBHOOK.md', () => {
186
+ assert.equal(loadEndpointConfig('never-registered'), null);
187
+ });
188
+
189
+ // loadEndpointConfig only surfaces the frontmatter (by design — see
190
+ // webhook.mjs comment above loadEndpointConfig); confirm the underlying
191
+ // shared parser it delegates to also recovers the markdown body, since
192
+ // the loader's frontmatter/body split is not independently exercised
193
+ // through the exported loadEndpointConfig surface.
194
+ test('readMarkdownDocument: WEBHOOK.md-shaped input yields frontmatter + body', () => {
195
+ const raw = [
196
+ '---',
197
+ 'channel: main',
198
+ 'parser: github',
199
+ '---',
200
+ '',
201
+ 'Handle this webhook by summarizing the payload.',
202
+ '',
203
+ ].join('\n');
204
+ const { frontmatter, body } = readMarkdownDocument(raw);
205
+ assert.equal(frontmatter.channel, 'main');
206
+ assert.equal(frontmatter.parser, 'github');
207
+ assert.equal(body, 'Handle this webhook by summarizing the payload.');
208
+ });
@@ -8,4 +8,7 @@ Root-cause analysis agent.
8
8
 
9
9
  Smallest confirmed cause chain before fixes. Return likely cause, evidence
10
10
  (`file:line`), smallest next check/fix. Mark confirmed facts vs inferences;
11
- avoid broad speculation.
11
+ avoid broad speculation. Fragments only; no narration, no report bloat.
12
+
13
+ Converge, don't sweep: when new reads stop adding evidence, report the best
14
+ cause chain so far — a partial/blocked report is a valid completion.
@@ -11,10 +11,11 @@ growing blast radius, or Lead-only verification.
11
11
  EDIT-FIRST DISCIPLINE. Survey the slice with ONE batched read/grep round,
12
12
  then start patching the first bounded piece — broad scope means edit
13
13
  incrementally, not read exhaustively. NEVER "one more confirming read": if
14
- an anchor is plausible, the next call is `apply_patch`. 3+ consecutive
15
- read-only calls without an edit = stalling patch the piece you understand
16
- or stop and report blocked. Self-check comes AFTER edits; deep verification
17
- is Lead's.
14
+ an anchor is plausible, the next call is `apply_patch`. Repeated read-only
15
+ turns without an edit = stalling; any runtime reminder is your cue to patch
16
+ the piece you understand or report blocked a blocked report is a valid
17
+ completion. Self-check comes AFTER edits; deep verification is Lead's.
18
18
 
19
- Minimal checks + how-to-verify.
19
+ Minimal checks + how-to-verify. Hand off outcome as fragments anchored to
20
+ `file:line`; no narration, no report bloat.
20
21
 
@@ -5,3 +5,7 @@ permission: read-write
5
5
  # Maintainer
6
6
 
7
7
  Maintenance and cleanup agent.
8
+
9
+ Smallest coherent change; no scope growth. Repeated read-only turns without
10
+ an edit = stalling; patch or return blocked — a blocked report is a valid
11
+ completion. Hand off as fragments (`file:line`); no narration.
@@ -8,4 +8,5 @@ Regression/risk review agent.
8
8
 
9
9
  Find actionable correctness/regression/security/verification risks. Findings
10
10
  first, severity-ordered, one line with `file:line`; skip non-risky nits. If
11
- clean, one line + only material residual risk.
11
+ clean, one line + only material residual risk. Fragments only; no narration,
12
+ no report bloat.
@@ -10,8 +10,12 @@ Smallest scoped change; no drive-by cleanup. Stop when done/blocked.
10
10
  EDIT-FIRST DISCIPLINE. Brief anchors (`file:line`) are pre-verified — trust
11
11
  them and patch. No anchor: locate with AT MOST 1-2 reads, then edit. NEVER
12
12
  "one more confirming read": if you know the file and the change, the next
13
- call is `apply_patch`. 3+ read-only calls without an edit = you are stalling
14
- either patch now or return blocked with what's missing. Confidence
15
- threshold is "plausible", not "proven"; self-check comes AFTER the edit, and
16
- Lead/Reviewer own final verification.
13
+ call is `apply_patch`. Repeated read-only turns without an edit = stalling;
14
+ any runtime reminder is your cue to patch now or return blocked with what's
15
+ missing a blocked report is a valid completion. Confidence threshold is
16
+ "plausible", not "proven"; self-check comes AFTER the edit, and Lead/Reviewer
17
+ own final verification.
18
+
19
+ Hand off outcome as fragments anchored to `file:line`; no narration, no
20
+ report bloat.
17
21
 
@@ -21,6 +21,7 @@
21
21
  * Source files (rules/):
22
22
  * - shared/01-tool.md — universal tool policy (Lead + agent BP1, identical full set)
23
23
  * - lead/lead-tool.md — Lead-specific control-tower / delegation / ToolSearch guidance
24
+ * - lead/lead-brief.md — Lead brief contract (agent handoff briefs)
24
25
  * - lead/01-02 — Lead general / channels
25
26
  * - output-styles/<name>.md — Lead output style, selected by config outputStyle
26
27
  * - agent/00-common.md — agent common behavior + universal worker contract (BP2)
@@ -275,6 +276,9 @@ function buildLeadRoleContent({ PLUGIN_ROOT, DATA_DIR }) {
275
276
  const toolLead = readOptional(path.join(LEAD_DIR, 'lead-tool.md'));
276
277
  if (toolLead) parts.push(toolLead);
277
278
 
279
+ const briefLead = readOptional(path.join(LEAD_DIR, 'lead-brief.md'));
280
+ if (briefLead) parts.push(briefLead);
281
+
278
282
  if (generalSplit.role) parts.push(generalSplit.role);
279
283
 
280
284
  const channels = readOptional(path.join(LEAD_DIR, '02-channels.md'));