mixdog 0.9.1 → 0.9.3

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 (301) hide show
  1. package/package.json +9 -1
  2. package/scripts/_bench-cwc.json +20 -0
  3. package/scripts/agent-loop-policy-test.mjs +37 -0
  4. package/scripts/agent-parallel-smoke.mjs +54 -10
  5. package/scripts/anthropic-maxtokens-test.mjs +119 -0
  6. package/scripts/background-task-meta-smoke.mjs +1 -1
  7. package/scripts/bench-run.mjs +262 -0
  8. package/scripts/build-tui.mjs +13 -1
  9. package/scripts/compact-smoke.mjs +12 -0
  10. package/scripts/compact-trigger-migration-smoke.mjs +67 -1
  11. package/scripts/explore-bench.mjs +124 -0
  12. package/scripts/hook-bus-test.mjs +191 -0
  13. package/scripts/ingest-pure-conversation-smoke.mjs +148 -0
  14. package/scripts/internal-comms-bench.mjs +727 -0
  15. package/scripts/internal-comms-smoke.mjs +75 -0
  16. package/scripts/lead-workflow-smoke.mjs +4 -4
  17. package/scripts/live-worker-smoke.mjs +9 -9
  18. package/scripts/output-style-bench.mjs +285 -0
  19. package/scripts/output-style-smoke.mjs +13 -10
  20. package/scripts/patch-replay.mjs +90 -0
  21. package/scripts/path-suffix-test.mjs +57 -0
  22. package/scripts/provider-stream-stall-test.mjs +276 -0
  23. package/scripts/provider-toolcall-test.mjs +599 -1
  24. package/scripts/recall-bench.mjs +207 -0
  25. package/scripts/routing-corpus.mjs +281 -0
  26. package/scripts/session-bench.mjs +1526 -0
  27. package/scripts/session-diag.mjs +595 -0
  28. package/scripts/task-bench.mjs +207 -0
  29. package/scripts/tool-failures.mjs +6 -6
  30. package/scripts/tool-smoke.mjs +310 -67
  31. package/scripts/toolcall-args-test.mjs +81 -0
  32. package/src/agents/debugger/AGENT.md +4 -4
  33. package/src/agents/heavy-worker/AGENT.md +20 -9
  34. package/src/agents/reviewer/AGENT.md +4 -4
  35. package/src/agents/worker/AGENT.md +17 -9
  36. package/src/app.mjs +10 -6
  37. package/src/defaults/{hidden-roles.json → agents.json} +7 -7
  38. package/src/examples/schedules/SCHEDULE.example.md +32 -0
  39. package/src/examples/webhooks/WEBHOOK.example.md +40 -0
  40. package/src/headless-role.mjs +14 -14
  41. package/src/help.mjs +1 -0
  42. package/src/lib/rules-builder.cjs +32 -54
  43. package/src/mixdog-session-runtime.mjs +1040 -2036
  44. package/src/output-styles/default.md +12 -7
  45. package/src/output-styles/minimal.md +25 -0
  46. package/src/output-styles/oneline.md +21 -0
  47. package/src/output-styles/simple.md +10 -9
  48. package/src/repl.mjs +17 -7
  49. package/src/rules/agent/00-common.md +7 -5
  50. package/src/rules/agent/30-explorer.md +8 -12
  51. package/src/rules/lead/01-general.md +3 -1
  52. package/src/rules/lead/lead-tool.md +13 -2
  53. package/src/rules/shared/01-tool.md +23 -12
  54. package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch.mjs +90 -32
  55. package/src/runtime/agent/orchestrator/agent-runtime/agent-loop-policy.mjs +32 -0
  56. package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +18 -6
  57. package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +23 -20
  58. package/src/runtime/agent/orchestrator/agent-runtime/session-builder.mjs +48 -14
  59. package/src/runtime/agent/orchestrator/agent-trace.mjs +87 -12
  60. package/src/runtime/agent/orchestrator/config.mjs +3 -0
  61. package/src/runtime/agent/orchestrator/context/collect.mjs +182 -67
  62. package/src/runtime/agent/orchestrator/{internal-roles.mjs → internal-agents.mjs} +72 -72
  63. package/src/runtime/agent/orchestrator/internal-tools.mjs +13 -26
  64. package/src/runtime/agent/orchestrator/mcp/client.mjs +100 -18
  65. package/src/runtime/agent/orchestrator/providers/anthropic-betas.mjs +7 -0
  66. package/src/runtime/agent/orchestrator/providers/anthropic-effort.mjs +188 -0
  67. package/src/runtime/agent/orchestrator/providers/anthropic-leaked-toolcall.mjs +444 -0
  68. package/src/runtime/agent/orchestrator/providers/anthropic-max-tokens.mjs +93 -0
  69. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +313 -84
  70. package/src/runtime/agent/orchestrator/providers/anthropic.mjs +104 -38
  71. package/src/runtime/agent/orchestrator/providers/api-usage.mjs +28 -33
  72. package/src/runtime/agent/orchestrator/providers/gemini.mjs +184 -17
  73. package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +8 -1
  74. package/src/runtime/agent/orchestrator/providers/lib/usage-primitives.mjs +32 -0
  75. package/src/runtime/agent/orchestrator/providers/model-catalog.mjs +18 -8
  76. package/src/runtime/agent/orchestrator/providers/oauth-usage.mjs +54 -20
  77. package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +227 -31
  78. package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +83 -6
  79. package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +229 -115
  80. package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +213 -33
  81. package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +18 -0
  82. package/src/runtime/agent/orchestrator/providers/opencode-go-usage.mjs +49 -17
  83. package/src/runtime/agent/orchestrator/providers/registry.mjs +2 -1
  84. package/src/runtime/agent/orchestrator/providers/retry-classifier.mjs +22 -17
  85. package/src/runtime/agent/orchestrator/session/compact.mjs +560 -51
  86. package/src/runtime/agent/orchestrator/session/context-utils.mjs +250 -3
  87. package/src/runtime/agent/orchestrator/session/loop/compact-debug.mjs +28 -0
  88. package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +262 -0
  89. package/src/runtime/agent/orchestrator/session/loop/context-overflow.mjs +38 -0
  90. package/src/runtime/agent/orchestrator/session/loop/env.mjs +14 -0
  91. package/src/runtime/agent/orchestrator/session/loop/hidden-agents.mjs +21 -0
  92. package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +49 -0
  93. package/src/runtime/agent/orchestrator/session/loop/steering.mjs +63 -0
  94. package/src/runtime/agent/orchestrator/session/loop/stored-tool-args.mjs +100 -0
  95. package/src/runtime/agent/orchestrator/session/loop/tool-classify.mjs +52 -0
  96. package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +218 -0
  97. package/src/runtime/agent/orchestrator/session/loop/transcript-repair.mjs +101 -0
  98. package/src/runtime/agent/orchestrator/session/loop/usage.mjs +35 -0
  99. package/src/runtime/agent/orchestrator/session/loop.mjs +513 -1000
  100. package/src/runtime/agent/orchestrator/session/manager/context-meta.mjs +227 -0
  101. package/src/runtime/agent/orchestrator/session/manager/pending-messages.mjs +235 -0
  102. package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +137 -0
  103. package/src/runtime/agent/orchestrator/session/manager/rules-cache.mjs +155 -0
  104. package/src/runtime/agent/orchestrator/session/manager/tool-resolution.mjs +303 -0
  105. package/src/runtime/agent/orchestrator/session/manager.mjs +232 -1152
  106. package/src/runtime/agent/orchestrator/session/store.mjs +4 -4
  107. package/src/runtime/agent/orchestrator/session/tool-envelope.mjs +61 -0
  108. package/src/runtime/agent/orchestrator/session/tool-result-offload.mjs +5 -0
  109. package/src/runtime/agent/orchestrator/stall-policy.mjs +63 -15
  110. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +194 -24
  111. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.test.mjs +143 -0
  112. package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +34 -18
  113. package/src/runtime/agent/orchestrator/tools/builtin/external-tool-adapters.mjs +241 -0
  114. package/src/runtime/agent/orchestrator/tools/builtin/external-tool-adapters.test.mjs +162 -0
  115. package/src/runtime/agent/orchestrator/tools/builtin/fuzzy-match.mjs +1 -1
  116. package/src/runtime/agent/orchestrator/tools/builtin/list-formatting.mjs +10 -0
  117. package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +5 -4
  118. package/src/runtime/agent/orchestrator/tools/builtin/path-diagnostics.mjs +42 -2
  119. package/src/runtime/agent/orchestrator/tools/builtin/path-utils.mjs +15 -0
  120. package/src/runtime/agent/orchestrator/tools/builtin/read-args.mjs +9 -44
  121. package/src/runtime/agent/orchestrator/tools/builtin/read-constants.mjs +2 -1
  122. package/src/runtime/agent/orchestrator/tools/builtin/read-formatting.mjs +14 -5
  123. package/src/runtime/agent/orchestrator/tools/builtin/read-single-tool.mjs +11 -4
  124. package/src/runtime/agent/orchestrator/tools/builtin/read-tool.mjs +10 -17
  125. package/src/runtime/agent/orchestrator/tools/builtin/search-tool.mjs +23 -2
  126. package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +50 -39
  127. package/src/runtime/agent/orchestrator/tools/builtin/shell-output.mjs +3 -2
  128. package/src/runtime/agent/orchestrator/tools/builtin/tool-output-limit.mjs +10 -0
  129. package/src/runtime/agent/orchestrator/tools/builtin.mjs +70 -1
  130. package/src/runtime/agent/orchestrator/tools/code-graph/build.mjs +303 -0
  131. package/src/runtime/agent/orchestrator/tools/code-graph/constants.mjs +43 -0
  132. package/src/runtime/agent/orchestrator/tools/code-graph/disk-cache.mjs +382 -0
  133. package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +497 -0
  134. package/src/runtime/agent/orchestrator/tools/code-graph/graph-binary.mjs +295 -0
  135. package/src/runtime/agent/orchestrator/tools/code-graph/graph-model.mjs +158 -0
  136. package/src/runtime/agent/orchestrator/tools/code-graph/lang-predicates.mjs +128 -0
  137. package/src/runtime/agent/orchestrator/tools/code-graph/memory-cache.mjs +66 -0
  138. package/src/runtime/agent/orchestrator/tools/code-graph/project-root.mjs +44 -0
  139. package/src/runtime/agent/orchestrator/tools/code-graph/search.mjs +1192 -0
  140. package/src/runtime/agent/orchestrator/tools/code-graph/source-access.mjs +81 -0
  141. package/src/runtime/agent/orchestrator/tools/code-graph/span.mjs +19 -0
  142. package/src/runtime/agent/orchestrator/tools/code-graph/symbol-index.mjs +280 -0
  143. package/src/runtime/agent/orchestrator/tools/code-graph/text-mask.mjs +347 -0
  144. package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +5 -5
  145. package/src/runtime/agent/orchestrator/tools/code-graph.mjs +39 -4189
  146. package/src/runtime/agent/orchestrator/tools/patch.mjs +119 -5
  147. package/src/runtime/agent/orchestrator/tools/progress-message.mjs +1 -2
  148. package/src/runtime/agent/orchestrator/tools/shell-command.mjs +1 -2
  149. package/src/runtime/agent/orchestrator/tools/shell-snapshot.mjs +2 -4
  150. package/src/runtime/channels/backends/discord.mjs +99 -9
  151. package/src/runtime/channels/backends/telegram.mjs +501 -0
  152. package/src/runtime/channels/index.mjs +437 -1439
  153. package/src/runtime/channels/lib/boot-profile.mjs +23 -0
  154. package/src/runtime/channels/lib/config.mjs +54 -2
  155. package/src/runtime/channels/lib/crash-log.mjs +106 -0
  156. package/src/runtime/channels/lib/format.mjs +4 -2
  157. package/src/runtime/channels/lib/index-drop-trace.mjs +72 -0
  158. package/src/runtime/channels/lib/output-forwarder.mjs +88 -67
  159. package/src/runtime/channels/lib/runtime-paths.mjs +29 -0
  160. package/src/runtime/channels/lib/scheduler.mjs +1 -1
  161. package/src/runtime/channels/lib/telegram-format.mjs +280 -0
  162. package/src/runtime/channels/lib/tool-format.mjs +1 -1
  163. package/src/runtime/channels/lib/transcript-discovery.mjs +19 -1
  164. package/src/runtime/channels/lib/webhook.mjs +59 -31
  165. package/src/runtime/channels/lib/whisper-language.mjs +42 -0
  166. package/src/runtime/channels/tool-defs.mjs +1 -1
  167. package/src/runtime/memory/index.mjs +465 -345
  168. package/src/runtime/memory/lib/agent-ipc.mjs +2 -2
  169. package/src/runtime/memory/lib/core-memory-store.mjs +352 -2
  170. package/src/runtime/memory/lib/cycle-signatures.mjs +34 -0
  171. package/src/runtime/memory/lib/http-wire.mjs +57 -0
  172. package/src/runtime/memory/lib/memory-cycle1.mjs +1 -1
  173. package/src/runtime/memory/lib/memory-cycle2.mjs +65 -9
  174. package/src/runtime/memory/lib/memory-cycle3.mjs +1 -1
  175. package/src/runtime/memory/lib/memory-recall-scope-filter.mjs +24 -0
  176. package/src/runtime/memory/lib/memory-retrievers.mjs +8 -0
  177. package/src/runtime/memory/lib/memory.mjs +121 -4
  178. package/src/runtime/memory/lib/pg/adapter.mjs +139 -15
  179. package/src/runtime/memory/lib/promotion-fingerprint.mjs +50 -0
  180. package/src/runtime/memory/lib/recall-format.mjs +183 -0
  181. package/src/runtime/memory/lib/session-ingest.mjs +107 -0
  182. package/src/runtime/memory/lib/trace-store.mjs +69 -22
  183. package/src/runtime/memory/tool-defs.mjs +10 -7
  184. package/src/runtime/shared/abort-controller.mjs +1 -1
  185. package/src/runtime/shared/background-tasks.mjs +2 -3
  186. package/src/runtime/shared/buffered-appender.mjs +149 -0
  187. package/src/runtime/shared/channel-notification-routing.mjs +12 -0
  188. package/src/runtime/shared/channel-notification-routing.test.mjs +45 -0
  189. package/src/runtime/shared/config.mjs +9 -0
  190. package/src/runtime/shared/llm/http-agent.mjs +12 -5
  191. package/src/runtime/shared/schedules-store.mjs +21 -19
  192. package/src/runtime/shared/task-notification-envelope.mjs +98 -0
  193. package/src/runtime/shared/task-notification-envelope.test.mjs +107 -0
  194. package/src/runtime/shared/tool-execution-contract.mjs +2 -2
  195. package/src/runtime/shared/tool-surface.mjs +98 -13
  196. package/src/runtime/shared/transcript-writer.mjs +156 -0
  197. package/src/runtime/shared/update-checker.mjs +214 -0
  198. package/src/session-runtime/config-helpers.mjs +209 -0
  199. package/src/session-runtime/effort.mjs +128 -0
  200. package/src/session-runtime/fs-utils.mjs +10 -0
  201. package/src/session-runtime/model-capabilities.mjs +130 -0
  202. package/src/session-runtime/output-styles.mjs +124 -0
  203. package/src/session-runtime/plugin-mcp.mjs +114 -0
  204. package/src/session-runtime/session-text.mjs +100 -0
  205. package/src/session-runtime/statusline-route.mjs +35 -0
  206. package/src/session-runtime/tool-catalog.mjs +720 -0
  207. package/src/session-runtime/workflow.mjs +358 -0
  208. package/src/standalone/agent-tool.mjs +302 -117
  209. package/src/standalone/channel-admin.mjs +133 -40
  210. package/src/standalone/channel-worker.mjs +10 -292
  211. package/src/standalone/explore-tool.mjs +12 -5
  212. package/src/standalone/hook-bus.mjs +165 -8
  213. package/src/standalone/memory-runtime-proxy.mjs +3 -1
  214. package/src/standalone/opencode-go-login.mjs +121 -0
  215. package/src/standalone/provider-admin.mjs +25 -3
  216. package/src/standalone/usage-dashboard.mjs +1 -1
  217. package/src/tui/App.jsx +2883 -778
  218. package/src/tui/components/ConfirmBar.jsx +47 -0
  219. package/src/tui/components/ContextPanel.jsx +5 -3
  220. package/src/tui/components/ItemRightHintOverprint.jsx +54 -0
  221. package/src/tui/components/Markdown.jsx +22 -98
  222. package/src/tui/components/Message.jsx +14 -35
  223. package/src/tui/components/Picker.jsx +87 -12
  224. package/src/tui/components/PromptInput.jsx +355 -22
  225. package/src/tui/components/QueuedCommands.jsx +1 -1
  226. package/src/tui/components/SlashCommandPalette.jsx +8 -5
  227. package/src/tui/components/Spinner.jsx +7 -7
  228. package/src/tui/components/StatusLine.jsx +40 -21
  229. package/src/tui/components/TextEntryPanel.jsx +51 -7
  230. package/src/tui/components/ToolExecution.jsx +183 -101
  231. package/src/tui/components/TurnDone.jsx +4 -4
  232. package/src/tui/components/UsagePanel.jsx +1 -1
  233. package/src/tui/components/tool-output-format.mjs +161 -23
  234. package/src/tui/components/tool-output-format.test.mjs +87 -0
  235. package/src/tui/display-width.mjs +69 -0
  236. package/src/tui/display-width.test.mjs +35 -0
  237. package/src/tui/dist/index.mjs +6731 -2333
  238. package/src/tui/engine/agent-envelope.mjs +296 -0
  239. package/src/tui/engine/boot-profile.mjs +21 -0
  240. package/src/tui/engine/labels.mjs +67 -0
  241. package/src/tui/engine/notice-text.mjs +112 -0
  242. package/src/tui/engine/queue-helpers.mjs +161 -0
  243. package/src/tui/engine/session-stats.mjs +46 -0
  244. package/src/tui/engine/tool-call-fields.mjs +23 -0
  245. package/src/tui/engine/tool-result-text.mjs +126 -0
  246. package/src/tui/engine.mjs +569 -948
  247. package/src/tui/index.jsx +117 -7
  248. package/src/tui/input-editing.mjs +58 -8
  249. package/src/tui/input-editing.selection.test.mjs +75 -0
  250. package/src/tui/keyboard-protocol.mjs +42 -0
  251. package/src/tui/lib/voice-recorder.mjs +469 -0
  252. package/src/tui/markdown/format-token.mjs +128 -76
  253. package/src/tui/markdown/format-token.test.mjs +61 -19
  254. package/src/tui/markdown/measure-rendered-rows.mjs +85 -0
  255. package/src/tui/markdown/render-ansi.test.mjs +1 -1
  256. package/src/tui/markdown/streaming-markdown.mjs +167 -0
  257. package/src/tui/markdown/streaming-markdown.test.mjs +70 -0
  258. package/src/tui/markdown/table-layout.mjs +9 -9
  259. package/src/tui/paste-attachments.mjs +36 -9
  260. package/src/tui/paste-fix.test.mjs +119 -0
  261. package/src/tui/prompt-history-store.mjs +129 -0
  262. package/src/tui/prompt-history-store.test.mjs +52 -0
  263. package/src/tui/statusline-ansi-bridge.test.mjs +3 -3
  264. package/src/tui/theme.mjs +41 -657
  265. package/src/tui/themes/base.mjs +86 -0
  266. package/src/tui/themes/basic.mjs +85 -0
  267. package/src/tui/themes/catppuccin.mjs +72 -0
  268. package/src/tui/themes/dracula.mjs +70 -0
  269. package/src/tui/themes/everforest.mjs +71 -0
  270. package/src/tui/themes/gruvbox.mjs +71 -0
  271. package/src/tui/themes/index.mjs +71 -0
  272. package/src/tui/themes/indigo.mjs +78 -0
  273. package/src/tui/themes/kanagawa.mjs +80 -0
  274. package/src/tui/themes/light.mjs +81 -0
  275. package/src/tui/themes/nord.mjs +72 -0
  276. package/src/tui/themes/onedark.mjs +16 -0
  277. package/src/tui/themes/rosepine.mjs +70 -0
  278. package/src/tui/themes/teal.mjs +80 -0
  279. package/src/tui/themes/tokyonight.mjs +79 -0
  280. package/src/tui/themes/utils.mjs +106 -0
  281. package/src/tui/themes/warm.mjs +79 -0
  282. package/src/tui/transcript-tool-failures.mjs +13 -2
  283. package/src/ui/markdown.mjs +1 -1
  284. package/src/ui/model-display.mjs +2 -2
  285. package/src/ui/statusline.mjs +75 -27
  286. package/src/vendor/statusline/bin/statusline-route.mjs +5 -12
  287. package/src/vendor/statusline/src/gateway/claude-current.mjs +3 -3
  288. package/src/vendor/statusline/src/gateway/route-meta.mjs +30 -16
  289. package/src/workflows/default/WORKFLOW.md +46 -12
  290. package/src/workflows/sequential/WORKFLOW.md +51 -0
  291. package/src/workflows/solo/WORKFLOW.md +12 -1
  292. package/vendor/ink/build/display-width.js +62 -0
  293. package/vendor/ink/build/ink.js +100 -12
  294. package/vendor/ink/build/measure-text.js +4 -1
  295. package/vendor/ink/build/output.js +115 -9
  296. package/vendor/ink/build/render-node-to-output.js +4 -1
  297. package/vendor/ink/build/render.js +4 -0
  298. package/src/output-styles/extreme-simple.md +0 -20
  299. package/src/rules/lead/04-workflow.md +0 -51
  300. package/src/workflows/default/workflow.json +0 -13
  301. package/src/workflows/solo/workflow.json +0 -7
@@ -86,7 +86,7 @@ const PLAIN_STDOUT_CONTEXT_EVENTS = new Set([
86
86
  'UserPromptExpansion',
87
87
  ]);
88
88
 
89
- const SUPPORTED_HANDLER_TYPES = new Set(['command', 'http']);
89
+ const SUPPORTED_HANDLER_TYPES = new Set(['command', 'http', 'mcp_tool', 'prompt']);
90
90
 
91
91
  function compactValue(value) {
92
92
  if (value == null) return value;
@@ -349,6 +349,7 @@ function buildEventPayload(eventName, input = {}) {
349
349
  'expansion_type',
350
350
  'stop_reason',
351
351
  'error_type',
352
+ 'agent_type',
352
353
  ]) {
353
354
  if (input[key] != null) payload[key] = input[key];
354
355
  }
@@ -374,6 +375,7 @@ function handlerTimeoutS(handler, eventName) {
374
375
  if (Number.isFinite(handler.timeout) && handler.timeout > 0) return handler.timeout;
375
376
  if (handler.type === 'prompt') return DEFAULT_PROMPT_TIMEOUT_S;
376
377
  if (handler.type === 'agent') return DEFAULT_AGENT_TIMEOUT_S;
378
+ if (handler.type === 'mcp_tool') return DEFAULT_COMMAND_TIMEOUT_S;
377
379
  if (eventName === 'UserPromptSubmit') return USER_PROMPT_TIMEOUT_S;
378
380
  if (eventName === 'MessageDisplay') return MESSAGE_DISPLAY_TIMEOUT_S;
379
381
  return DEFAULT_COMMAND_TIMEOUT_S;
@@ -427,7 +429,7 @@ function hookEnv(projectDir, pluginData, payload, pluginRoot = null) {
427
429
  return env;
428
430
  }
429
431
 
430
- function runCommandHandler(handler, payload, eventName, pluginData, onSpawnError = null) {
432
+ function runCommandHandler(handler, payload, eventName, pluginData, onSpawnError = null, onRewake = null) {
431
433
  const projectDir = payload.cwd || process.cwd();
432
434
  const effectivePluginData = handler._pluginData || pluginData || null;
433
435
  const stdin = JSON.stringify(payload);
@@ -440,7 +442,58 @@ function runCommandHandler(handler, payload, eventName, pluginData, onSpawnError
440
442
  stdio: ['pipe', 'pipe', 'pipe'],
441
443
  };
442
444
 
443
- if (handler.async === true || handler.asyncRewake === true) {
445
+ if (handler.asyncRewake === true) {
446
+ try {
447
+ const child = spawn(spec.command, spec.args, {
448
+ ...baseOpts,
449
+ });
450
+ let bgStdout = '';
451
+ let bgStderr = '';
452
+ let bgStdoutBytes = 0;
453
+ let bgStderrBytes = 0;
454
+ let killed = false;
455
+ child.stdout?.on('data', (chunk) => {
456
+ bgStdoutBytes += chunk.length;
457
+ if (bgStdoutBytes <= MAX_BUFFER_BYTES) bgStdout += chunk.toString('utf8');
458
+ });
459
+ child.stderr?.on('data', (chunk) => {
460
+ bgStderrBytes += chunk.length;
461
+ if (bgStderrBytes <= MAX_BUFFER_BYTES) bgStderr += chunk.toString('utf8');
462
+ });
463
+ const killTimer = setTimeout(() => {
464
+ killed = true;
465
+ try { child.stdout?.removeAllListeners('data'); } catch {}
466
+ try { child.stderr?.removeAllListeners('data'); } catch {}
467
+ try { child.kill('SIGTERM'); } catch {}
468
+ }, timeoutMs);
469
+ killTimer.unref?.();
470
+ child.on('error', (error) => {
471
+ clearTimeout(killTimer);
472
+ if (typeof onSpawnError === 'function') onSpawnError(error);
473
+ });
474
+ child.on('close', (code) => {
475
+ clearTimeout(killTimer);
476
+ try {
477
+ if (!killed && code === 2 && typeof onRewake === 'function') {
478
+ const text = (bgStderr || '').trim() || (bgStdout || '').trim();
479
+ onRewake({ eventName, payload, text });
480
+ }
481
+ } catch {}
482
+ });
483
+ child.stdin?.end(stdin);
484
+ return Promise.resolve({ exitCode: 0, stdout: '', stderr: '', async: true });
485
+ } catch (error) {
486
+ return Promise.resolve({
487
+ exitCode: -1,
488
+ stdout: '',
489
+ stderr: error?.message || String(error),
490
+ timedOut: false,
491
+ spawnError: error,
492
+ });
493
+ }
494
+ }
495
+
496
+ if (handler.async === true) {
444
497
  try {
445
498
  const child = spawn(spec.command, spec.args, {
446
499
  ...baseOpts,
@@ -584,6 +637,78 @@ async function runHttpHandler(handler, payload, eventName) {
584
637
  }
585
638
  }
586
639
 
640
+ async function runMcpToolHandler(handler, payload, eventName, mcpToolRunner) {
641
+ const timeoutMs = Math.round(handlerTimeoutS(handler, eventName) * 1000);
642
+ let name = String(handler.tool || '').trim();
643
+ if (handler.server && !name.startsWith('mcp__')) {
644
+ name = `mcp__${String(handler.server).trim()}__${name}`;
645
+ }
646
+ if (!name) {
647
+ return { exitCode: -1, stdout: '', stderr: 'mcp_tool handler missing tool name', timedOut: false, spawnError: null };
648
+ }
649
+ let timer = null;
650
+ try {
651
+ const runPromise = Promise.resolve(mcpToolRunner({ name, args: payload }));
652
+ const text = await Promise.race([
653
+ runPromise,
654
+ new Promise((_r, reject) => {
655
+ timer = setTimeout(() => reject(new Error(`mcp_tool hook timed out: ${name}`)), timeoutMs);
656
+ // No unref: this timer must keep the event loop alive so the race can
657
+ // settle even when the runner promise never resolves. Cleared in finally.
658
+ }),
659
+ ]);
660
+ return { exitCode: 0, stdout: limitText(String(text ?? '')), stderr: '', timedOut: false, spawnError: null };
661
+ } catch (error) {
662
+ const timedOut = /timed out/i.test(error?.message || '');
663
+ return { exitCode: -1, stdout: '', stderr: error?.message || String(error), timedOut, spawnError: null };
664
+ } finally {
665
+ if (timer) clearTimeout(timer);
666
+ }
667
+ }
668
+
669
+ async function runPromptHandler(handler, payload, eventName, promptRunner) {
670
+ const timeoutMs = Math.round(handlerTimeoutS(handler, eventName) * 1000);
671
+ const prompt = String(handler.prompt || '');
672
+ if (!prompt) {
673
+ return { exitCode: -1, stdout: '', stderr: 'prompt handler missing prompt', timedOut: false, spawnError: null };
674
+ }
675
+ let timer = null;
676
+ try {
677
+ const runPromise = Promise.resolve(promptRunner({ prompt, payload, timeoutMs }));
678
+ const text = await Promise.race([
679
+ runPromise,
680
+ new Promise((_r, reject) => {
681
+ timer = setTimeout(() => reject(new Error(`prompt hook timed out: ${eventName}`)), timeoutMs);
682
+ // No unref: this timer must keep the event loop alive so the race can
683
+ // settle even when the runner promise never resolves. Cleared in finally.
684
+ }),
685
+ ]);
686
+ const raw = String(text ?? '').trim();
687
+ const deny = (reason) => ({ exitCode: 2, stdout: '', stderr: reason, timedOut: false, spawnError: null });
688
+ const allow = () => ({ exitCode: 0, stdout: '', stderr: '', timedOut: false, spawnError: null });
689
+ let verdict = null;
690
+ try {
691
+ verdict = JSON.parse(raw);
692
+ } catch {
693
+ verdict = undefined;
694
+ }
695
+ if (verdict && typeof verdict === 'object') {
696
+ if (verdict.ok === false) return deny(String(verdict.reason || `blocked by ${eventName} prompt hook`));
697
+ return allow();
698
+ }
699
+ // plain-text response
700
+ const lowered = raw.toLowerCase();
701
+ if (!raw || ['yes', 'true', 'allow', 'ok'].includes(lowered)) return allow();
702
+ if (['no', 'false', 'deny', 'block'].includes(lowered)) return deny(raw || `blocked by ${eventName} prompt hook`);
703
+ return allow();
704
+ } catch (error) {
705
+ const timedOut = /timed out/i.test(error?.message || '');
706
+ return { exitCode: -1, stdout: '', stderr: error?.message || String(error), timedOut, spawnError: null };
707
+ } finally {
708
+ if (timer) clearTimeout(timer);
709
+ }
710
+ }
711
+
587
712
  function limitText(text) {
588
713
  const value = String(text || '');
589
714
  if (value.length <= MAX_OUTPUT_CHARS) return value;
@@ -727,10 +852,13 @@ function summarizeRule(rule, index) {
727
852
 
728
853
  function handlerDedupeKey(handler) {
729
854
  if (!handler || typeof handler !== 'object') return '';
855
+ const ifKey = handler.if != null ? `|if:${JSON.stringify(handler.if)}` : '';
730
856
  if (handler.type === 'command') {
731
- return `command:${handler._pluginRoot || ''}:${handler.command || ''}:${JSON.stringify(handler.args || null)}`;
857
+ return `command:${handler._pluginRoot || ''}:${handler.command || ''}:${JSON.stringify(handler.args || null)}${ifKey}`;
732
858
  }
733
- if (handler.type === 'http') return `http:${handler.url || ''}`;
859
+ if (handler.type === 'http') return `http:${handler.url || ''}${ifKey}`;
860
+ if (handler.type === 'mcp_tool') return `mcp_tool:${handler.server || ''}:${handler.tool || ''}${ifKey}`;
861
+ if (handler.type === 'prompt') return `prompt:${handler.prompt || ''}${ifKey}`;
734
862
  return `${handler.type || 'unknown'}:${JSON.stringify(handler)}`;
735
863
  }
736
864
 
@@ -742,11 +870,12 @@ function shellCountFor(handler) {
742
870
  return handler?.type || 'unknown';
743
871
  }
744
872
 
745
- export function createStandaloneHookBus({ maxEvents = 80, dataDir = null } = {}) {
873
+ export function createStandaloneHookBus({ maxEvents = 80, dataDir = null, promptRunner = null, mcpToolRunner = null } = {}) {
746
874
  const recent = [];
747
875
  const counts = new Map(DEFAULT_EVENTS.map((name) => [name, 0]));
748
876
  const rulesPath = hookRulesPath(dataDir);
749
877
  const pluginData = dataDir || null;
878
+ let rewakeHandler = null;
750
879
  let rulesCache = { mtimeMs: -1, rules: [] };
751
880
  let configCache = {
752
881
  key: '',
@@ -883,12 +1012,35 @@ export function createStandaloneHookBus({ maxEvents = 80, dataDir = null } = {})
883
1012
  error: `hook spawn failed: ${error?.message || error}`,
884
1013
  });
885
1014
  };
886
- return await runCommandHandler(handler, payload, eventName, pluginData, reportSpawnError);
1015
+ const reportRewake = ({ eventName: en, payload: pl, text }) => {
1016
+ try {
1017
+ if (typeof rewakeHandler === 'function') {
1018
+ rewakeHandler({ eventName: en, payload: pl, text });
1019
+ } else {
1020
+ emit('hook:rewake', { name: pl?.tool_name || en, text: text || null });
1021
+ }
1022
+ } catch {}
1023
+ };
1024
+ return await runCommandHandler(handler, payload, eventName, pluginData, reportSpawnError, reportRewake);
887
1025
  }
888
1026
  if (type === 'http') {
889
1027
  if (!handler.url) return null;
890
1028
  return await runHttpHandler(handler, payload, eventName);
891
1029
  }
1030
+ if (type === 'mcp_tool') {
1031
+ if (typeof mcpToolRunner !== 'function') {
1032
+ emit('hook:error', { name: payload.tool_name || eventName, error: 'handler type mcp_tool not configured' });
1033
+ return null;
1034
+ }
1035
+ return await runMcpToolHandler(handler, payload, eventName, mcpToolRunner);
1036
+ }
1037
+ if (type === 'prompt') {
1038
+ if (typeof promptRunner !== 'function') {
1039
+ emit('hook:error', { name: payload.tool_name || eventName, error: 'handler type prompt not configured' });
1040
+ return null;
1041
+ }
1042
+ return await runPromptHandler(handler, payload, eventName, promptRunner);
1043
+ }
892
1044
  return null;
893
1045
  }
894
1046
 
@@ -1118,5 +1270,10 @@ export function createStandaloneHookBus({ maxEvents = 80, dataDir = null } = {})
1118
1270
  };
1119
1271
  }
1120
1272
 
1121
- return { addRule, beforeTool, deleteRule, dispatch, emit, listRules, setRuleEnabled, status };
1273
+ function setRewakeHandler(fn) {
1274
+ rewakeHandler = typeof fn === 'function' ? fn : null;
1275
+ return rewakeHandler;
1276
+ }
1277
+
1278
+ return { addRule, beforeTool, deleteRule, dispatch, emit, listRules, setRewakeHandler, setRuleEnabled, status };
1122
1279
  }
@@ -188,12 +188,14 @@ export function createStandaloneMemoryRuntime({
188
188
  releaseOwnerIfSelf();
189
189
  }
190
190
 
191
+ const daemonEnv = { ...process.env };
192
+ delete daemonEnv.MIXDOG_QUIET_MEMORY_LOG;
191
193
  child = fork(entry, [], {
192
194
  cwd,
193
195
  stdio: ['ignore', 'ignore', 'pipe', 'ipc'],
194
196
  detached: true,
195
197
  env: {
196
- ...process.env,
198
+ ...daemonEnv,
197
199
  MIXDOG_DATA_DIR: dataDir,
198
200
  MIXDOG_WORKER_MODE: '1',
199
201
  MIXDOG_STANDALONE: '1',
@@ -0,0 +1,121 @@
1
+ import fs from 'node:fs';
2
+
3
+ const COMMON_BROWSER_PATHS = (() => {
4
+ const platform = process.platform;
5
+ if (platform === 'win32') {
6
+ const localAppData = process.env.LOCALAPPDATA;
7
+ const programFiles = process.env.PROGRAMFILES || 'C:/Program Files';
8
+ const programFilesX86 = process.env['PROGRAMFILES(X86)'] || 'C:/Program Files (x86)';
9
+ return [
10
+ `${programFiles}/Google/Chrome/Application/chrome.exe`,
11
+ `${programFilesX86}/Google/Chrome/Application/chrome.exe`,
12
+ localAppData && `${localAppData}/Google/Chrome/Application/chrome.exe`,
13
+ `${programFiles}/Microsoft/Edge/Application/msedge.exe`,
14
+ `${programFilesX86}/Microsoft/Edge/Application/msedge.exe`,
15
+ localAppData && `${localAppData}/Microsoft/Edge/Application/msedge.exe`,
16
+ ].filter(Boolean);
17
+ }
18
+ if (platform === 'linux') {
19
+ return [
20
+ '/usr/bin/google-chrome',
21
+ '/usr/bin/google-chrome-stable',
22
+ '/usr/bin/chromium',
23
+ '/usr/bin/chromium-browser',
24
+ '/snap/bin/chromium',
25
+ '/usr/bin/microsoft-edge',
26
+ ];
27
+ }
28
+ return [
29
+ '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
30
+ '/Applications/Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary',
31
+ '/Applications/Chromium.app/Contents/MacOS/Chromium',
32
+ '/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge',
33
+ ];
34
+ })();
35
+
36
+ function resolveExecutablePath() {
37
+ if (process.env.PUPPETEER_EXECUTABLE_PATH && fs.existsSync(process.env.PUPPETEER_EXECUTABLE_PATH)) {
38
+ return process.env.PUPPETEER_EXECUTABLE_PATH;
39
+ }
40
+ for (const executablePath of COMMON_BROWSER_PATHS) {
41
+ if (fs.existsSync(executablePath)) return executablePath;
42
+ }
43
+ return null;
44
+ }
45
+
46
+ let _inflight = null;
47
+
48
+ export async function loginOpenCodeGoConsoleWithBrowser({ timeoutMs = 300_000, onStatus } = {}) {
49
+ if (_inflight) return _inflight;
50
+ _inflight = _run({ timeoutMs, onStatus }).finally(() => { _inflight = null; });
51
+ return _inflight;
52
+ }
53
+
54
+ async function _run({ timeoutMs, onStatus }) {
55
+ const status = (msg) => { try { if (typeof onStatus === 'function') onStatus(msg); } catch {} };
56
+ const executablePath = resolveExecutablePath();
57
+ if (!executablePath) {
58
+ throw new Error('No Chrome/Edge browser found. Set PUPPETEER_EXECUTABLE_PATH to a Chrome or Edge executable.');
59
+ }
60
+
61
+ const puppeteer = (await import('puppeteer-core')).default;
62
+ let browser = null;
63
+ let closed = false;
64
+ const closeBrowser = async () => {
65
+ if (closed) return;
66
+ closed = true;
67
+ try { if (browser) await browser.close(); } catch {}
68
+ };
69
+
70
+ try {
71
+ status('Launching browser…');
72
+ browser = await puppeteer.launch({
73
+ headless: false,
74
+ executablePath,
75
+ defaultViewport: null,
76
+ args: ['--disable-dev-shm-usage'],
77
+ });
78
+
79
+ let disconnected = false;
80
+ browser.on('disconnected', () => { disconnected = true; });
81
+
82
+ const page = (await browser.pages())[0] || (await browser.newPage());
83
+ status('Opening opencode.ai/auth — please sign in…');
84
+ await page.goto('https://opencode.ai/auth', { waitUntil: 'domcontentloaded' }).catch(() => {});
85
+
86
+ const deadline = Date.now() + timeoutMs;
87
+ while (Date.now() < deadline) {
88
+ if (disconnected || page.isClosed()) {
89
+ throw new Error('Browser was closed before login completed.');
90
+ }
91
+ let cookies;
92
+ try {
93
+ cookies = await page.cookies('https://opencode.ai');
94
+ } catch {
95
+ throw new Error('Browser was closed before login completed.');
96
+ }
97
+ const auth = cookies.find(c => c.name === 'auth');
98
+ const url = page.url();
99
+ const wsMatch = url.match(/\/workspace\/(wrk_[a-zA-Z0-9]+)/);
100
+ // Only treat the login as complete on an authenticated console page.
101
+ // A pre-existing/stale auth cookie on a marketing or docs page must not
102
+ // short-circuit the flow with workspaceId:null.
103
+ const onWorkspace = (() => {
104
+ try {
105
+ const parsed = new URL(url);
106
+ return parsed.hostname.endsWith('opencode.ai') && /^\/(?:[a-z-]+\/)?workspace(\/|$)/.test(parsed.pathname);
107
+ } catch {
108
+ return false;
109
+ }
110
+ })();
111
+ if (auth && auth.value && (wsMatch || onWorkspace)) {
112
+ status('Auth cookie captured.');
113
+ return { authCookie: auth.value, workspaceId: wsMatch?.[1] || null };
114
+ }
115
+ await new Promise(r => setTimeout(r, 1000));
116
+ }
117
+ throw new Error(`Timed out after ${Math.round(timeoutMs / 1000)}s waiting for OpenCode Go login.`);
118
+ } finally {
119
+ await closeBrowser();
120
+ }
121
+ }
@@ -52,6 +52,17 @@ const API_PROVIDER_IDS = new Set(API_PROVIDERS.map((p) => p.id));
52
52
  const OAUTH_BY_ID = new Map(OAUTH_PROVIDERS.map((p) => [p.id, p]));
53
53
  const LOCAL_BY_ID = new Map(LOCAL_PROVIDERS.map((p) => [p.id, p]));
54
54
 
55
+ const ALL_PROVIDER_IDS = new Set([
56
+ ...API_PROVIDERS.map((p) => p.id),
57
+ ...OAUTH_PROVIDERS.map((p) => p.id),
58
+ ...LOCAL_PROVIDERS.map((p) => p.id),
59
+ ]);
60
+
61
+ export function isKnownProvider(provider) {
62
+ const id = String(provider || '').trim();
63
+ return id !== '' && ALL_PROVIDER_IDS.has(id);
64
+ }
65
+
55
66
  async function detectLocalProvider(baseURL) {
56
67
  const url = String(baseURL || '').replace(/\/+$/, '') + '/models';
57
68
  const controller = new AbortController();
@@ -303,12 +314,23 @@ export function saveOpenCodeGoUsageAuth(cfgMod, { workspaceId, authCookie } = {}
303
314
  if (!cookie) throw new Error('OpenCode auth cookie is required for usage lookup');
304
315
  const authMatch = /(?:^|;\s*)auth=([^;]+)/.exec(cookie);
305
316
  saveSecret(SECRET_ACCOUNTS.opencodeGoAuthCookie, authMatch ? authMatch[1] : cookie);
306
- updateConfigProvider(cfgMod, 'opencode-go', workspace
307
- ? { enabled: true, workspaceId: workspace }
308
- : { enabled: true });
317
+ // Usage auth is a console cookie, not a model API key. Only flip the model
318
+ // provider on when a key actually exists; otherwise cookie-only setups get
319
+ // routed to the provider with apiKey 'no-key' and fail with 401s.
320
+ const hasApiKey = Boolean(getAgentApiKey('opencode-go'));
321
+ updateConfigProvider(cfgMod, 'opencode-go', {
322
+ ...(hasApiKey ? { enabled: true } : {}),
323
+ ...(workspace ? { workspaceId: workspace } : {}),
324
+ });
309
325
  return { provider: 'opencode-go', type: 'usage-auth', authenticated: true, workspaceId: workspace || null };
310
326
  }
311
327
 
328
+ export async function loginOpenCodeGoUsage(cfgMod) {
329
+ const { loginOpenCodeGoConsoleWithBrowser } = await import('./opencode-go-login.mjs');
330
+ const { workspaceId, authCookie } = await loginOpenCodeGoConsoleWithBrowser();
331
+ return saveOpenCodeGoUsageAuth(cfgMod, { workspaceId, authCookie });
332
+ }
333
+
312
334
  export function setLocalProvider(cfgMod, provider, { enabled, baseURL } = {}) {
313
335
  const id = String(provider || '').trim();
314
336
  const local = LOCAL_BY_ID.get(id);
@@ -29,7 +29,7 @@ function clean(value) {
29
29
 
30
30
  function money(value) {
31
31
  const n = Number(value);
32
- if (!Number.isFinite(n)) return 'n/a';
32
+ if (!Number.isFinite(n)) return 'N/A';
33
33
  if (n === 0) return '$0';
34
34
  if (n >= 10) return `$${n.toFixed(0)}`;
35
35
  if (n >= 1) return `$${n.toFixed(2)}`;