mixdog 0.9.3 → 0.9.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (382) hide show
  1. package/README.md +112 -38
  2. package/package.json +10 -3
  3. package/scripts/bench/lead-review-tasks-r3.json +20 -0
  4. package/scripts/bench/lead-review-tasks.json +20 -0
  5. package/scripts/bench/r4-mixed-tasks.json +20 -0
  6. package/scripts/bench/r5-orchestrated-task.json +7 -0
  7. package/scripts/bench/review-tasks.json +20 -0
  8. package/scripts/bench/round-codex.json +114 -0
  9. package/scripts/bench/round-mixdog-lead-r3.json +269 -0
  10. package/scripts/bench/round-mixdog-lead.json +269 -0
  11. package/scripts/bench/round-mixdog.json +126 -0
  12. package/scripts/bench/round-r10-bigsample.json +679 -0
  13. package/scripts/bench/round-r11-codexalign.json +257 -0
  14. package/scripts/bench/round-r13-clientmeta.json +464 -0
  15. package/scripts/bench/round-r14-betafeatures.json +466 -0
  16. package/scripts/bench/round-r15-fulldefault.json +462 -0
  17. package/scripts/bench/round-r16-sessionid.json +466 -0
  18. package/scripts/bench/round-r17-wirebytes.json +456 -0
  19. package/scripts/bench/round-r18-prewarm.json +468 -0
  20. package/scripts/bench/round-r19-clean.json +472 -0
  21. package/scripts/bench/round-r20-prewarm-clean.json +475 -0
  22. package/scripts/bench/round-r21-delta-retry.json +473 -0
  23. package/scripts/bench/round-r22-full-probe.json +693 -0
  24. package/scripts/bench/round-r23-itemprobe.json +701 -0
  25. package/scripts/bench/round-r24-shapefix.json +677 -0
  26. package/scripts/bench/round-r25-serial.json +464 -0
  27. package/scripts/bench/round-r26-parallel3.json +671 -0
  28. package/scripts/bench/round-r27-parallel10.json +894 -0
  29. package/scripts/bench/round-r28-parallel10-stagger.json +882 -0
  30. package/scripts/bench/round-r29-parallel10-stagger166.json +886 -0
  31. package/scripts/bench/round-r30-instid.json +253 -0
  32. package/scripts/bench/round-r31-upgradeprobe.json +256 -0
  33. package/scripts/bench/round-r32-vs-codex-lead.json +254 -0
  34. package/scripts/bench/round-r33-vs-codex-codex.json +115 -0
  35. package/scripts/bench/round-r34-orchestrated.json +120 -0
  36. package/scripts/bench/round-r35-orchestrated-codex.json +61 -0
  37. package/scripts/bench/round-r36-orchestrated-capped.json +128 -0
  38. package/scripts/bench/round-r4-codex.json +114 -0
  39. package/scripts/bench/round-r4-mixed.json +225 -0
  40. package/scripts/bench/round-r5-gpt-lead.json +259 -0
  41. package/scripts/bench/round-r6-codex.json +114 -0
  42. package/scripts/bench/round-r6-solo.json +257 -0
  43. package/scripts/bench/round-r7-full.json +254 -0
  44. package/scripts/bench/round-r8-fulldefault.json +255 -0
  45. package/scripts/bench-run.mjs +251 -32
  46. package/scripts/freevar-smoke.mjs +95 -0
  47. package/scripts/internal-comms-bench.mjs +3 -4
  48. package/scripts/internal-comms-smoke.mjs +10 -9
  49. package/scripts/model-catalog-audit.mjs +209 -0
  50. package/scripts/model-list-sanitize-test.mjs +37 -0
  51. package/scripts/mouse-probe.mjs +45 -0
  52. package/scripts/output-style-bench.mjs +13 -6
  53. package/scripts/output-style-smoke.mjs +4 -4
  54. package/scripts/provider-toolcall-test.mjs +7 -3
  55. package/scripts/recall-bench.mjs +76 -13
  56. package/scripts/recall-quality-cases.json +12 -0
  57. package/scripts/recall-usecase-cases.json +18 -0
  58. package/scripts/session-bench.mjs +152 -6
  59. package/scripts/tool-smoke.mjs +25 -65
  60. package/scripts/tui-render-smoke.mjs +90 -0
  61. package/scripts/webhook-smoke.mjs +208 -0
  62. package/src/agents/debugger/AGENT.md +4 -1
  63. package/src/agents/heavy-worker/AGENT.md +9 -8
  64. package/src/agents/maintainer/AGENT.md +4 -0
  65. package/src/agents/reviewer/AGENT.md +2 -1
  66. package/src/agents/scheduler-task/AGENT.md +2 -3
  67. package/src/agents/webhook-handler/AGENT.md +2 -3
  68. package/src/agents/worker/AGENT.md +10 -7
  69. package/src/app.mjs +12 -1
  70. package/src/headless-role.mjs +7 -1
  71. package/src/lib/rules-builder.cjs +4 -0
  72. package/src/mixdog-session-runtime.mjs +647 -2056
  73. package/src/output-styles/default.md +30 -9
  74. package/src/output-styles/{oneline.md → extreme-minimal.md} +5 -4
  75. package/src/output-styles/minimal.md +8 -6
  76. package/src/output-styles/simple.md +21 -7
  77. package/src/rules/agent/00-common.md +6 -3
  78. package/src/rules/agent/30-explorer.md +16 -5
  79. package/src/rules/lead/01-general.md +5 -5
  80. package/src/rules/lead/lead-brief.md +15 -0
  81. package/src/rules/lead/lead-tool.md +6 -15
  82. package/src/rules/shared/01-tool.md +17 -21
  83. package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch.mjs +8 -3
  84. package/src/runtime/agent/orchestrator/agent-runtime/agent-loop-policy.mjs +25 -0
  85. package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +100 -23
  86. package/src/runtime/agent/orchestrator/agent-runtime/session-builder.mjs +6 -15
  87. package/src/runtime/agent/orchestrator/agent-trace-format.mjs +362 -0
  88. package/src/runtime/agent/orchestrator/agent-trace-io.mjs +410 -0
  89. package/src/runtime/agent/orchestrator/agent-trace.mjs +16 -735
  90. package/src/runtime/agent/orchestrator/config.mjs +69 -2
  91. package/src/runtime/agent/orchestrator/providers/anthropic-effort.mjs +62 -20
  92. package/src/runtime/agent/orchestrator/providers/anthropic-model-resolve.mjs +209 -0
  93. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-credentials.mjs +489 -0
  94. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +81 -1281
  95. package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +607 -0
  96. package/src/runtime/agent/orchestrator/providers/anthropic.mjs +32 -3
  97. package/src/runtime/agent/orchestrator/providers/codex-client-meta.mjs +81 -0
  98. package/src/runtime/agent/orchestrator/providers/gemini-cache.mjs +248 -0
  99. package/src/runtime/agent/orchestrator/providers/gemini-schema.mjs +303 -0
  100. package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +505 -0
  101. package/src/runtime/agent/orchestrator/providers/gemini.mjs +43 -1013
  102. package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +17 -3
  103. package/src/runtime/agent/orchestrator/providers/model-catalog.mjs +105 -11
  104. package/src/runtime/agent/orchestrator/providers/model-list-sanitize.mjs +356 -0
  105. package/src/runtime/agent/orchestrator/providers/openai-codex-model.mjs +108 -0
  106. package/src/runtime/agent/orchestrator/providers/openai-compat-trace.mjs +58 -0
  107. package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +368 -0
  108. package/src/runtime/agent/orchestrator/providers/openai-compat-xai.mjs +760 -0
  109. package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +40 -1143
  110. package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +740 -0
  111. package/src/runtime/agent/orchestrator/providers/openai-oauth-login.mjs +193 -0
  112. package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +349 -2131
  113. package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +143 -1002
  114. package/src/runtime/agent/orchestrator/providers/openai-ws-delta.mjs +229 -0
  115. package/src/runtime/agent/orchestrator/providers/openai-ws-events.mjs +67 -0
  116. package/src/runtime/agent/orchestrator/providers/openai-ws-pool.mjs +465 -0
  117. package/src/runtime/agent/orchestrator/providers/openai-ws-stream.mjs +1105 -0
  118. package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +2 -1
  119. package/src/runtime/agent/orchestrator/providers/provider-catalog-cache.mjs +80 -0
  120. package/src/runtime/agent/orchestrator/session/compact/budget.mjs +288 -0
  121. package/src/runtime/agent/orchestrator/session/compact/constants.mjs +85 -0
  122. package/src/runtime/agent/orchestrator/session/compact/engine.mjs +749 -0
  123. package/src/runtime/agent/orchestrator/session/compact/messages.mjs +82 -0
  124. package/src/runtime/agent/orchestrator/session/compact/summary-schema.mjs +315 -0
  125. package/src/runtime/agent/orchestrator/session/compact/summary.mjs +643 -0
  126. package/src/runtime/agent/orchestrator/session/compact/text-utils.mjs +326 -0
  127. package/src/runtime/agent/orchestrator/session/compact.mjs +40 -2282
  128. package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +14 -2
  129. package/src/runtime/agent/orchestrator/session/loop/completion-guards.mjs +61 -0
  130. package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +1 -3
  131. package/src/runtime/agent/orchestrator/session/loop/recall-fasttrack.mjs +275 -0
  132. package/src/runtime/agent/orchestrator/session/loop/steering-ladder.mjs +173 -0
  133. package/src/runtime/agent/orchestrator/session/loop/termination.mjs +58 -0
  134. package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +239 -0
  135. package/src/runtime/agent/orchestrator/session/loop.mjs +278 -402
  136. package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +471 -0
  137. package/src/runtime/agent/orchestrator/session/manager/context-meta.mjs +7 -4
  138. package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +12 -0
  139. package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +406 -0
  140. package/src/runtime/agent/orchestrator/session/manager/status-telemetry.mjs +80 -0
  141. package/src/runtime/agent/orchestrator/session/manager/usage-metrics.mjs +210 -0
  142. package/src/runtime/agent/orchestrator/session/manager.mjs +166 -1087
  143. package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +189 -0
  144. package/src/runtime/agent/orchestrator/session/store.mjs +74 -179
  145. package/src/runtime/agent/orchestrator/stall-policy.mjs +20 -1
  146. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +70 -20
  147. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +22 -2
  148. package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +35 -44
  149. package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +40 -0
  150. package/src/runtime/agent/orchestrator/tools/builtin/rg-runner.mjs +29 -0
  151. package/src/runtime/agent/orchestrator/tools/builtin/search-builders.mjs +8 -0
  152. package/src/runtime/agent/orchestrator/tools/builtin/search-path-diagnostics.mjs +126 -0
  153. package/src/runtime/agent/orchestrator/tools/builtin/search-tool.mjs +81 -92
  154. package/src/runtime/agent/orchestrator/tools/builtin/shell-job-paths.mjs +161 -0
  155. package/src/runtime/agent/orchestrator/tools/builtin/shell-job-process.mjs +108 -0
  156. package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +28 -265
  157. package/src/runtime/agent/orchestrator/tools/builtin.mjs +0 -6
  158. package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +57 -3
  159. package/src/runtime/agent/orchestrator/tools/code-graph/keyword-match.mjs +82 -0
  160. package/src/runtime/agent/orchestrator/tools/code-graph/search.mjs +10 -122
  161. package/src/runtime/agent/orchestrator/tools/code-graph/text-columns.mjs +45 -0
  162. package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +6 -6
  163. package/src/runtime/agent/orchestrator/tools/graph-binary-fetcher.mjs +6 -3
  164. package/src/runtime/agent/orchestrator/tools/patch/constants.mjs +9 -0
  165. package/src/runtime/agent/orchestrator/tools/patch/dispatch.mjs +171 -0
  166. package/src/runtime/agent/orchestrator/tools/patch/matcher.mjs +471 -0
  167. package/src/runtime/agent/orchestrator/tools/patch/native-server.mjs +436 -0
  168. package/src/runtime/agent/orchestrator/tools/patch/orchestrator.mjs +342 -0
  169. package/src/runtime/agent/orchestrator/tools/patch/parsing.mjs +359 -0
  170. package/src/runtime/agent/orchestrator/tools/patch/paths.mjs +340 -0
  171. package/src/runtime/agent/orchestrator/tools/patch/v4a-convert.mjs +643 -0
  172. package/src/runtime/agent/orchestrator/tools/patch.mjs +36 -2959
  173. package/src/runtime/agent/orchestrator/tools/progress-message.mjs +0 -21
  174. package/src/runtime/agent/orchestrator/tools/shell-command.mjs +9 -72
  175. package/src/runtime/agent/orchestrator/tools/shell-powershell.mjs +77 -0
  176. package/src/runtime/agent/orchestrator/tools/shell-state.mjs +154 -0
  177. package/src/runtime/channels/backends/discord-access.mjs +32 -0
  178. package/src/runtime/channels/backends/discord-attachments.mjs +65 -0
  179. package/src/runtime/channels/backends/discord-gateway.mjs +233 -0
  180. package/src/runtime/channels/backends/discord.mjs +27 -318
  181. package/src/runtime/channels/backends/telegram.mjs +8 -12
  182. package/src/runtime/channels/index.mjs +247 -701
  183. package/src/runtime/channels/lib/backend-dispatch.mjs +46 -0
  184. package/src/runtime/channels/lib/config.mjs +37 -149
  185. package/src/runtime/channels/lib/event-pipeline.mjs +22 -5
  186. package/src/runtime/channels/lib/event-queue.mjs +78 -13
  187. package/src/runtime/channels/lib/inbound-routing.mjs +74 -0
  188. package/src/runtime/channels/lib/interaction-workflows.mjs +5 -113
  189. package/src/runtime/channels/lib/output-forwarder.mjs +1 -1
  190. package/src/runtime/channels/lib/owner-heartbeat.mjs +75 -0
  191. package/src/runtime/channels/lib/parent-bridge.mjs +88 -0
  192. package/src/runtime/channels/lib/runtime-paths.mjs +14 -4
  193. package/src/runtime/channels/lib/scheduler.mjs +27 -113
  194. package/src/runtime/channels/lib/session-discovery.mjs +56 -4
  195. package/src/runtime/channels/lib/tool-dispatch.mjs +158 -0
  196. package/src/runtime/channels/lib/tool-format.mjs +1 -1
  197. package/src/runtime/channels/lib/transcript-discovery.mjs +4 -4
  198. package/src/runtime/channels/lib/voice-runtime-fetcher.mjs +6 -3
  199. package/src/runtime/channels/lib/voice-transcription.mjs +179 -0
  200. package/src/runtime/channels/lib/webhook/deliveries.mjs +313 -0
  201. package/src/runtime/channels/lib/webhook/log.mjs +42 -0
  202. package/src/runtime/channels/lib/webhook/ngrok.mjs +181 -0
  203. package/src/runtime/channels/lib/webhook/signature.mjs +60 -0
  204. package/src/runtime/channels/lib/webhook.mjs +43 -616
  205. package/src/runtime/channels/tool-defs.mjs +11 -130
  206. package/src/runtime/memory/index.mjs +210 -1948
  207. package/src/runtime/memory/lib/core-memory-store.mjs +5 -1
  208. package/src/runtime/memory/lib/cycle-llm-adapters.mjs +58 -0
  209. package/src/runtime/memory/lib/cycle-scheduler.mjs +497 -0
  210. package/src/runtime/memory/lib/embedding-warmup.mjs +58 -0
  211. package/src/runtime/memory/lib/ko-morph.mjs +195 -0
  212. package/src/runtime/memory/lib/memory-config-flags.mjs +91 -0
  213. package/src/runtime/memory/lib/memory-cycle.mjs +1 -1
  214. package/src/runtime/memory/lib/memory-cycle2-gate.mjs +515 -0
  215. package/src/runtime/memory/lib/memory-cycle2-mutations.mjs +324 -0
  216. package/src/runtime/memory/lib/memory-cycle2-shared.mjs +18 -0
  217. package/src/runtime/memory/lib/memory-cycle2.mjs +24 -842
  218. package/src/runtime/memory/lib/memory-embed.mjs +149 -0
  219. package/src/runtime/memory/lib/memory-process-lock.mjs +162 -0
  220. package/src/runtime/memory/lib/memory-recall-store.mjs +69 -12
  221. package/src/runtime/memory/lib/memory-text-utils.mjs +46 -0
  222. package/src/runtime/memory/lib/pg/supervisor.mjs +1 -1
  223. package/src/runtime/memory/lib/query-handlers.mjs +802 -0
  224. package/src/runtime/memory/lib/recall-format.mjs +55 -0
  225. package/src/runtime/memory/lib/runtime-fetcher.mjs +8 -3
  226. package/src/runtime/memory/lib/transcript-ingest.mjs +425 -0
  227. package/src/runtime/memory/tool-defs.mjs +5 -13
  228. package/src/runtime/search/lib/http-fetch.mjs +274 -0
  229. package/src/runtime/search/lib/ssrf-guard.mjs +333 -0
  230. package/src/runtime/search/lib/web-tools.mjs +24 -602
  231. package/src/runtime/shared/atomic-file.mjs +26 -1
  232. package/src/runtime/shared/config.mjs +14 -4
  233. package/src/runtime/shared/launcher-control.mjs +2 -2
  234. package/src/runtime/shared/markdown-frontmatter.mjs +19 -0
  235. package/src/runtime/shared/schedules-store.mjs +13 -3
  236. package/src/runtime/shared/tool-execution-contract.mjs +2 -2
  237. package/src/runtime/shared/tool-primitives.mjs +308 -0
  238. package/src/runtime/shared/tool-result-summary.mjs +515 -0
  239. package/src/runtime/shared/tool-surface.mjs +80 -898
  240. package/src/runtime/shared/transcript-writer.mjs +23 -0
  241. package/src/runtime/shared/update-checker.mjs +7 -4
  242. package/src/session-runtime/config-helpers.mjs +119 -2
  243. package/src/session-runtime/config-lifecycle.mjs +232 -0
  244. package/src/session-runtime/cwd-plugins.mjs +226 -0
  245. package/src/session-runtime/mcp-glue.mjs +177 -0
  246. package/src/session-runtime/model-recency.mjs +111 -0
  247. package/src/session-runtime/native-search.mjs +247 -0
  248. package/src/session-runtime/output-styles.mjs +11 -9
  249. package/src/session-runtime/prewarm.mjs +142 -0
  250. package/src/session-runtime/provider-models.mjs +278 -0
  251. package/src/session-runtime/provider-usage.mjs +120 -0
  252. package/src/session-runtime/quick-model-rows.mjs +205 -0
  253. package/src/session-runtime/quick-search-models.mjs +47 -0
  254. package/src/session-runtime/session-hooks.mjs +93 -0
  255. package/src/session-runtime/settings-api.mjs +352 -0
  256. package/src/session-runtime/tool-catalog.mjs +29 -29
  257. package/src/session-runtime/tool-defs.mjs +84 -0
  258. package/src/session-runtime/warmup-schedulers.mjs +201 -0
  259. package/src/session-runtime/workflow.mjs +1 -1
  260. package/src/standalone/agent-tool/helpers.mjs +237 -0
  261. package/src/standalone/agent-tool/notify.mjs +107 -0
  262. package/src/standalone/agent-tool/provider-init.mjs +143 -0
  263. package/src/standalone/agent-tool/render.mjs +152 -0
  264. package/src/standalone/agent-tool/tool-def.mjs +55 -0
  265. package/src/standalone/agent-tool.mjs +138 -669
  266. package/src/standalone/channel-admin.mjs +102 -90
  267. package/src/standalone/channel-worker.mjs +4 -7
  268. package/src/standalone/explore-tool.mjs +64 -14
  269. package/src/standalone/hook-bus/config.mjs +207 -0
  270. package/src/standalone/hook-bus/constants.mjs +90 -0
  271. package/src/standalone/hook-bus/handlers.mjs +481 -0
  272. package/src/standalone/hook-bus/payload.mjs +31 -0
  273. package/src/standalone/hook-bus/rules.mjs +77 -0
  274. package/src/standalone/hook-bus.mjs +77 -870
  275. package/src/standalone/memory-runtime-proxy.mjs +7 -0
  276. package/src/standalone/opencode-go-login.mjs +5 -1
  277. package/src/standalone/provider-admin.mjs +1 -16
  278. package/src/standalone/usage-dashboard.mjs +3 -1
  279. package/src/tui/App.jsx +1059 -8110
  280. package/src/tui/app/app-format.mjs +213 -0
  281. package/src/tui/app/channel-pickers.mjs +508 -0
  282. package/src/tui/app/clipboard.mjs +67 -0
  283. package/src/tui/app/core-memory-picker.mjs +210 -0
  284. package/src/tui/app/extension-pickers.mjs +506 -0
  285. package/src/tui/app/input-parsers.mjs +193 -0
  286. package/src/tui/app/maintenance-pickers.mjs +356 -0
  287. package/src/tui/app/model-options.mjs +334 -0
  288. package/src/tui/app/model-picker.mjs +365 -0
  289. package/src/tui/app/onboarding-steps.mjs +400 -0
  290. package/src/tui/app/project-picker.mjs +247 -0
  291. package/src/tui/app/provider-setup-picker.mjs +580 -0
  292. package/src/tui/app/resume-picker.mjs +55 -0
  293. package/src/tui/app/route-pickers.mjs +419 -0
  294. package/src/tui/app/settings-picker.mjs +489 -0
  295. package/src/tui/app/slash-commands.mjs +101 -0
  296. package/src/tui/app/slash-dispatch.mjs +427 -0
  297. package/src/tui/app/text-layout.mjs +46 -0
  298. package/src/tui/app/theme-effort-pickers.mjs +154 -0
  299. package/src/tui/app/transcript-window.mjs +677 -0
  300. package/src/tui/app/use-mouse-input.mjs +460 -0
  301. package/src/tui/app/use-prompt-handlers.mjs +310 -0
  302. package/src/tui/app/use-transcript-scroll.mjs +512 -0
  303. package/src/tui/app/use-transcript-window.mjs +607 -0
  304. package/src/tui/components/ConfirmBar.jsx +10 -7
  305. package/src/tui/components/Picker.jsx +64 -15
  306. package/src/tui/components/PromptInput.jsx +33 -102
  307. package/src/tui/components/SlashCommandPalette.jsx +8 -1
  308. package/src/tui/components/StatusLine.jsx +69 -15
  309. package/src/tui/components/TextEntryPanel.jsx +11 -0
  310. package/src/tui/components/ToolExecution.jsx +52 -594
  311. package/src/tui/components/TranscriptItem.jsx +105 -0
  312. package/src/tui/components/UsagePanel.jsx +18 -4
  313. package/src/tui/components/prompt-input/edit-helpers.mjs +72 -0
  314. package/src/tui/components/prompt-input/voice-indicator.mjs +39 -0
  315. package/src/tui/components/tool-execution/ResultBody.jsx +56 -0
  316. package/src/tui/components/tool-execution/surface-detail.mjs +405 -0
  317. package/src/tui/components/tool-execution/text-format.mjs +161 -0
  318. package/src/tui/display-width.mjs +20 -3
  319. package/src/tui/dist/index.mjs +13553 -12384
  320. package/src/tui/engine/agent-job-feed.mjs +133 -0
  321. package/src/tui/engine/notification-plan.mjs +76 -0
  322. package/src/tui/engine/render-timing.mjs +17 -0
  323. package/src/tui/engine/tool-approval.mjs +94 -0
  324. package/src/tui/engine/tool-card-results.mjs +234 -0
  325. package/src/tui/engine/tool-result-status.mjs +135 -0
  326. package/src/tui/engine.mjs +170 -574
  327. package/src/tui/figures.mjs +5 -0
  328. package/src/tui/index.jsx +65 -1
  329. package/src/tui/input-editing.mjs +2 -2
  330. package/src/tui/markdown/format-token.mjs +4 -1
  331. package/src/tui/statusline-ansi-bridge.mjs +11 -3
  332. package/src/tui/theme.mjs +6 -0
  333. package/src/ui/statusline-agents.mjs +213 -0
  334. package/src/ui/statusline-format.mjs +146 -0
  335. package/src/ui/statusline-segments.mjs +148 -0
  336. package/src/ui/statusline.mjs +77 -501
  337. package/src/ui/tool-card.mjs +0 -1
  338. package/src/vendor/statusline/bin/statusline-route.mjs +15 -2
  339. package/src/workflows/default/WORKFLOW.md +16 -18
  340. package/src/workflows/sequential/WORKFLOW.md +16 -18
  341. package/vendor/ink/build/display-width.js +19 -3
  342. package/vendor/ink/build/ink.js +112 -7
  343. package/vendor/ink/build/log-update.js +17 -3
  344. package/vendor/ink/build/wrap-text.js +125 -0
  345. package/scripts/_test-folder-dialog.mjs +0 -30
  346. package/scripts/fix-brief-fn.mjs +0 -35
  347. package/scripts/fix-format-tool-surface.mjs +0 -24
  348. package/scripts/fix-tool-exec-visible.mjs +0 -42
  349. package/scripts/patch-agent-brief.mjs +0 -48
  350. package/scripts/patch-app.mjs +0 -21
  351. package/scripts/patch-app2.mjs +0 -18
  352. package/scripts/patch-dist-brief.mjs +0 -96
  353. package/scripts/patch-tool-exec.mjs +0 -70
  354. package/src/examples/schedules/SCHEDULE.example.md +0 -32
  355. package/src/examples/webhooks/WEBHOOK.example.md +0 -40
  356. package/src/runtime/agent/orchestrator/session/manager.reactive-persist.test.mjs +0 -107
  357. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.test.mjs +0 -143
  358. package/src/runtime/agent/orchestrator/tools/builtin/diagnostics-tool.mjs +0 -285
  359. package/src/runtime/agent/orchestrator/tools/builtin/external-tool-adapters.test.mjs +0 -162
  360. package/src/runtime/agent/orchestrator/tools/builtin/open-config-tool.mjs +0 -26
  361. package/src/runtime/channels/lib/holidays.mjs +0 -138
  362. package/src/runtime/shared/channel-notification-routing.test.mjs +0 -45
  363. package/src/runtime/shared/task-notification-envelope.test.mjs +0 -107
  364. package/src/runtime/shared/tool-execution-contract.test.mjs +0 -183
  365. package/src/standalone/agent-task-status.test.mjs +0 -76
  366. package/src/tui/components/tool-output-format.test.mjs +0 -399
  367. package/src/tui/display-width.test.mjs +0 -35
  368. package/src/tui/engine-runtime-notification.test.mjs +0 -115
  369. package/src/tui/engine-tool-result-text.test.mjs +0 -75
  370. package/src/tui/input-editing.selection.test.mjs +0 -75
  371. package/src/tui/markdown/format-token.test.mjs +0 -354
  372. package/src/tui/markdown/render-ansi.test.mjs +0 -108
  373. package/src/tui/markdown/stream-fence.test.mjs +0 -26
  374. package/src/tui/markdown/streaming-markdown.test.mjs +0 -70
  375. package/src/tui/paste-fix.test.mjs +0 -119
  376. package/src/tui/prompt-history-store.test.mjs +0 -52
  377. package/src/tui/statusline-ansi-bridge.test.mjs +0 -159
  378. package/src/tui/transcript-tool-failures.test.mjs +0 -111
  379. package/src/ui/markdown.test.mjs +0 -70
  380. package/src/ui/statusline-context-label.test.mjs +0 -15
  381. package/src/vendor/statusline/bin/statusline-lib.mjs +0 -186
  382. package/src/vendor/statusline/bin/statusline-route.test.mjs +0 -80
@@ -1,285 +0,0 @@
1
- // Server-less diagnostics tool.
2
- //
3
- // Mixdog has no resident LSP server. Instead this tool detects the project
4
- // marker(s) under the target path, runs the matching project CLI ONCE via the
5
- // same exec path bash-tool uses (execShellCommand + resolveShell), then PARSES
6
- // the checker's stdout/stderr into structured findings. No watch, no daemon.
7
- //
8
- // Graceful contract: a missing marker or a missing checker binary returns a
9
- // clear "no checker available for X" message — it never throws.
10
- import { existsSync, readFileSync, statSync } from 'fs';
11
- import { dirname, isAbsolute, resolve as pathResolve } from 'path';
12
- import { execShellCommand, stripAnsi } from '../shell-command.mjs';
13
- import { resolveShell } from './shell-runtime.mjs';
14
-
15
- const DIAGNOSTICS_TIMEOUT_MS = 120_000;
16
-
17
- // Probe whether a CLI binary is resolvable on PATH without running real work.
18
- // Uses the platform command-resolver so an absent checker degrades gracefully
19
- // into a "no checker available" message instead of a spawn error.
20
- async function _commandExists(bin, cwd, env) {
21
- const { shell, shellArg, shellArgs, shellType } = resolveShell();
22
- const probe = shellType === 'powershell'
23
- ? `Get-Command ${bin} -ErrorAction SilentlyContinue | Select-Object -First 1`
24
- : `command -v ${bin}`;
25
- try {
26
- const r = await execShellCommand({
27
- shell, shellArg, shellArgs, command: probe,
28
- env, cwd, timeoutMs: 10_000, abortSignal: null,
29
- });
30
- const out = `${r.stdout || ''}`.trim();
31
- if (shellType === 'powershell') return out.length > 0;
32
- return r.exitCode === 0 && out.length > 0;
33
- } catch {
34
- return false;
35
- }
36
- }
37
-
38
- async function _runChecker(command, cwd, env) {
39
- const { shell, shellArg, shellArgs } = resolveShell();
40
- const r = await execShellCommand({
41
- shell, shellArg, shellArgs, command,
42
- env, cwd, timeoutMs: DIAGNOSTICS_TIMEOUT_MS, abortSignal: null,
43
- });
44
- return {
45
- stdout: stripAnsi(r.stdout || ''),
46
- stderr: stripAnsi(r.stderr || ''),
47
- exitCode: r.timedOut ? null : r.exitCode,
48
- timedOut: !!r.timedOut,
49
- };
50
- }
51
-
52
- // ---- per-checker output parsers -> [{file,line,col,severity,code,message}] ----
53
-
54
- function _parseTsc(text) {
55
- const findings = [];
56
- const re = /^(.+?)\((\d+),(\d+)\):\s+(error|warning)\s+(TS\d+):\s+(.*)$/;
57
- for (const line of text.split(/\r?\n/)) {
58
- const m = re.exec(line.trim());
59
- if (m) findings.push({ file: m[1], line: +m[2], col: +m[3], severity: m[4], code: m[5], message: m[6] });
60
- }
61
- return findings;
62
- }
63
-
64
- function _parseRuff(text) {
65
- const findings = [];
66
- // ruff default text: path:line:col: CODE message
67
- const re = /^(.+?):(\d+):(\d+):\s+([A-Z]+\d+)\s+(.*)$/;
68
- for (const line of text.split(/\r?\n/)) {
69
- const m = re.exec(line.trim());
70
- if (!m) continue;
71
- const code = m[4];
72
- const severity = (/^(E9|F)/.test(code)) ? 'error' : 'warning';
73
- findings.push({ file: m[1], line: +m[2], col: +m[3], severity, code, message: m[5] });
74
- }
75
- return findings;
76
- }
77
-
78
- function _parsePyflakes(text) {
79
- const findings = [];
80
- // pyflakes: path:line: message (no column)
81
- const re = /^(.+?):(\d+):\s*(.*)$/;
82
- for (const line of text.split(/\r?\n/)) {
83
- const trimmed = line.trim();
84
- if (!trimmed) continue;
85
- const m = re.exec(trimmed);
86
- if (m) findings.push({ file: m[1], line: +m[2], col: null, severity: 'warning', code: null, message: m[3] });
87
- }
88
- return findings;
89
- }
90
-
91
- function _parseGoVet(text) {
92
- const findings = [];
93
- // go vet: path:line:col: message OR path:line: message
94
- const re = /^(.+?\.go):(\d+):(?:(\d+):)?\s+(.*)$/;
95
- for (const line of text.split(/\r?\n/)) {
96
- const m = re.exec(line.trim());
97
- if (m) findings.push({ file: m[1], line: +m[2], col: m[3] ? +m[3] : null, severity: 'error', code: 'vet', message: m[4] });
98
- }
99
- return findings;
100
- }
101
-
102
- function _parseCargo(text) {
103
- const findings = [];
104
- // cargo check --message-format=short: path:line:col: severity[CODE]: message
105
- const re = /^(.+?):(\d+):(\d+):\s+(warning|error)(?:\[([A-Za-z0-9]+)\])?:\s+(.*)$/;
106
- for (const line of text.split(/\r?\n/)) {
107
- const m = re.exec(line.trim());
108
- if (m) findings.push({ file: m[1], line: +m[2], col: +m[3], severity: m[4], code: m[5] || null, message: m[6] });
109
- }
110
- return findings;
111
- }
112
-
113
- // node --check failure shape: "<abs file>:<line>" on its own line, followed
114
- // by the offending source + caret, then "SyntaxError: <message>".
115
- function _parseNodeCheck(text) {
116
- const findings = [];
117
- const head = /^(.+?):(\d+)\s*$/m.exec(text);
118
- const err = /^([A-Za-z]*Error):\s+(.*)$/m.exec(text);
119
- if (head && err) {
120
- findings.push({ file: head[1], line: +head[2], col: null, severity: 'error', code: err[1], message: err[2] });
121
- }
122
- return findings;
123
- }
124
-
125
- function _parseEslintCompact(text) {
126
- const findings = [];
127
- // eslint --format compact: path: line N, col M, Severity - message (rule)
128
- const re = /^(.+?):\s+line\s+(\d+),\s+col\s+(\d+),\s+(Error|Warning)\s+-\s+(.*?)(?:\s+\(([^)]+)\))?$/;
129
- for (const line of text.split(/\r?\n/)) {
130
- const m = re.exec(line.trim());
131
- if (m) findings.push({ file: m[1], line: +m[2], col: +m[3], severity: m[4].toLowerCase(), code: m[6] || null, message: m[5] });
132
- }
133
- return findings;
134
- }
135
-
136
- // Detect the project marker under projectDir and return the checker plan.
137
- // Order is deterministic; the first matching marker wins.
138
- function _detectChecker(projectDir) {
139
- const has = (f) => existsSync(pathResolve(projectDir, f));
140
- if (has('tsconfig.json')) {
141
- return { kind: 'tsc', bin: 'tsc', command: 'tsc --noEmit', parse: _parseTsc, marker: 'tsconfig.json' };
142
- }
143
- if (has('pyproject.toml') || has('setup.py')) {
144
- return { kind: 'python', marker: has('pyproject.toml') ? 'pyproject.toml' : 'setup.py' };
145
- }
146
- if (has('go.mod')) {
147
- return { kind: 'go', bin: 'go', command: 'go vet ./...', parse: _parseGoVet, marker: 'go.mod' };
148
- }
149
- if (has('Cargo.toml')) {
150
- return { kind: 'cargo', bin: 'cargo', command: 'cargo check --message-format=short', parse: _parseCargo, marker: 'Cargo.toml' };
151
- }
152
- if (has('package.json')) {
153
- let usesEslint = false;
154
- try {
155
- const pkg = JSON.parse(readFileSync(pathResolve(projectDir, 'package.json'), 'utf8'));
156
- const deps = { ...(pkg.dependencies || {}), ...(pkg.devDependencies || {}) };
157
- usesEslint = Object.prototype.hasOwnProperty.call(deps, 'eslint');
158
- } catch { /* unreadable/invalid package.json */ }
159
- const hasEslintConfig = ['.eslintrc', '.eslintrc.js', '.eslintrc.cjs', '.eslintrc.json', '.eslintrc.yml', '.eslintrc.yaml', 'eslint.config.js', 'eslint.config.mjs', 'eslint.config.cjs']
160
- .some((f) => has(f));
161
- if (usesEslint || hasEslintConfig) {
162
- return { kind: 'eslint', bin: 'eslint', command: 'eslint . --format compact', parse: _parseEslintCompact, marker: 'package.json (eslint)' };
163
- }
164
- return { kind: 'none', reason: 'package.json present but no eslint dependency/config detected' };
165
- }
166
- return { kind: 'none', reason: 'no recognized project marker (tsconfig.json / pyproject.toml / setup.py / go.mod / Cargo.toml / package.json)' };
167
- }
168
-
169
- function _formatResult({ marker, checker, findings, raw, note }) {
170
- return JSON.stringify({
171
- ok: true,
172
- marker: marker || null,
173
- checker: checker || null,
174
- note: note || null,
175
- count: findings.length,
176
- findings,
177
- raw: raw ? raw.slice(0, 4000) : undefined,
178
- }, null, 2);
179
- }
180
-
181
- export async function executeDiagnosticsTool(args, workDir, options = {}) {
182
- const result = await _executeDiagnosticsImpl(args, workDir);
183
- // ② completion progress (claude "Found N" parity). Best-effort, no-op
184
- // when onProgress is absent (no progressToken). Never throws — the tool
185
- // result is returned regardless.
186
- if (typeof options?.onProgress === 'function') {
187
- try {
188
- let _n = null;
189
- try { _n = JSON.parse(result)?.count; } catch { /* non-JSON envelope */ }
190
- if (Number.isFinite(_n)) {
191
- options.onProgress(_n === 0 ? 'no issues' : `${_n} issue${_n === 1 ? '' : 's'}`);
192
- }
193
- } catch { /* best-effort */ }
194
- }
195
- return result;
196
- }
197
-
198
- async function _executeDiagnosticsImpl(args, workDir) {
199
- try {
200
- // Resolve the target path (file or dir). Default: cwd.
201
- const rawPath = (args && typeof args.path === 'string' && args.path.trim()) ? args.path.trim() : '';
202
- const absTarget = rawPath
203
- ? (isAbsolute(rawPath) ? rawPath : pathResolve(workDir, rawPath))
204
- : workDir;
205
- if (rawPath && !existsSync(absTarget)) {
206
- return _formatResult({ note: `path not found: ${rawPath}`, findings: [] });
207
- }
208
- let projectDir = absTarget;
209
- try {
210
- if (statSync(absTarget).isFile()) projectDir = dirname(absTarget);
211
- } catch { /* fall through with absTarget */ }
212
-
213
- let plan = _detectChecker(projectDir);
214
- // A file deep in the tree (src/search/lib/foo.mjs) has its project
215
- // marker at the repo ROOT — walk parent directories until a marker is
216
- // found instead of reporting "no recognized project marker" from the
217
- // file's immediate dirname. Stops at the first directory that yields
218
- // any non-"no marker" answer (including "package.json present but no
219
- // eslint"), or at the filesystem root.
220
- {
221
- const _noMarker = (p) => p.kind === 'none' && String(p.reason || '').startsWith('no recognized project marker');
222
- let walk = projectDir;
223
- while (_noMarker(plan)) {
224
- const parent = dirname(walk);
225
- if (!parent || parent === walk) break;
226
- walk = parent;
227
- plan = _detectChecker(walk);
228
- }
229
- if (!_noMarker(plan)) projectDir = walk;
230
- }
231
- if (plan.kind === 'none') {
232
- // Syntax-check fallback: a JS file target can still be validated
233
- // with `node --check` even when the project has no lint setup —
234
- // catch the dominant failure class (parse errors) instead of
235
- // going dark with "no checker".
236
- let isJsFile = false;
237
- try { isJsFile = /\.(mjs|cjs|js)$/i.test(absTarget) && statSync(absTarget).isFile(); } catch { /* keep false */ }
238
- if (isJsFile) {
239
- const env = { ...process.env, LANG: 'C.UTF-8', LC_ALL: 'C.UTF-8' };
240
- if (await _commandExists('node', projectDir, env)) {
241
- const r = await _runChecker(`node --check "${absTarget}"`, projectDir, env);
242
- const combined = `${r.stdout}\n${r.stderr}`;
243
- const findings = _parseNodeCheck(combined);
244
- return _formatResult({ checker: 'node --check (no-lint fallback)', findings, raw: combined, note: `${plan.reason} — fell back to syntax check only` });
245
- }
246
- }
247
- return _formatResult({ note: `no checker available for this path — ${plan.reason}`, findings: [] });
248
- }
249
-
250
- const env = { ...process.env, LANG: 'C.UTF-8', LC_ALL: 'C.UTF-8' };
251
-
252
- // Python is special: prefer ruff, fall back to pyflakes.
253
- if (plan.kind === 'python') {
254
- if (await _commandExists('ruff', projectDir, env)) {
255
- const r = await _runChecker('ruff check .', projectDir, env);
256
- const findings = _parseRuff(`${r.stdout}\n${r.stderr}`);
257
- return _formatResult({ marker: plan.marker, checker: 'ruff check', findings, raw: `${r.stdout}\n${r.stderr}` });
258
- }
259
- if (await _commandExists('python', projectDir, env)) {
260
- const r = await _runChecker('python -m pyflakes .', projectDir, env);
261
- const out = `${r.stdout}\n${r.stderr}`;
262
- if (/No module named pyflakes/i.test(out)) {
263
- return _formatResult({ marker: plan.marker, note: 'no checker available for Python — ruff absent and pyflakes module not installed', findings: [] });
264
- }
265
- const findings = _parsePyflakes(out);
266
- return _formatResult({ marker: plan.marker, checker: 'python -m pyflakes (ruff fallback)', findings, raw: out });
267
- }
268
- return _formatResult({ marker: plan.marker, note: 'no checker available for Python — neither ruff nor python found on PATH', findings: [] });
269
- }
270
-
271
- if (!(await _commandExists(plan.bin, projectDir, env))) {
272
- return _formatResult({ marker: plan.marker, note: `no checker available for ${plan.kind} — "${plan.bin}" not found on PATH`, findings: [] });
273
- }
274
- const r = await _runChecker(plan.command, projectDir, env);
275
- const combined = `${r.stdout}\n${r.stderr}`;
276
- const findings = plan.parse(combined);
277
- const note = r.timedOut ? `checker timed out after ${DIAGNOSTICS_TIMEOUT_MS}ms` : null;
278
- return _formatResult({ marker: plan.marker, checker: plan.command, findings, raw: combined, note });
279
- } catch (err) {
280
- // Never throw — surface a graceful diagnostic envelope.
281
- return JSON.stringify({ ok: false, error: `diagnostics failed: ${err && err.message ? err.message : String(err)}`, findings: [] }, null, 2);
282
- }
283
- }
284
-
285
- export default executeDiagnosticsTool;
@@ -1,162 +0,0 @@
1
- import test from 'node:test';
2
- import assert from 'node:assert/strict';
3
- import { mkdtempSync, writeFileSync, readFileSync, existsSync } from 'node:fs';
4
- import { tmpdir } from 'node:os';
5
- import { join } from 'node:path';
6
- import { tryExecuteExternalToolAdapter, isExternalAdapterTool } from './external-tool-adapters.mjs';
7
-
8
- function makeDir() {
9
- return mkdtempSync(join(tmpdir(), 'ext-adapter-test-'));
10
- }
11
-
12
- test('isExternalAdapterTool recognizes adapted names only', () => {
13
- assert.equal(isExternalAdapterTool('StrReplace'), true);
14
- assert.equal(isExternalAdapterTool('str_replace'), true);
15
- assert.equal(isExternalAdapterTool('str_replace_editor'), true);
16
- assert.equal(isExternalAdapterTool('search_replace'), true);
17
- assert.equal(isExternalAdapterTool('Write'), true);
18
- assert.equal(isExternalAdapterTool('create_file'), true);
19
- assert.equal(isExternalAdapterTool('Bash'), true);
20
- assert.equal(isExternalAdapterTool('run_terminal_cmd'), true);
21
- // Destructive / different-shape names stay on the redirect path.
22
- assert.equal(isExternalAdapterTool('Delete'), false);
23
- assert.equal(isExternalAdapterTool('delete_file'), false);
24
- assert.equal(isExternalAdapterTool('edit'), false);
25
- assert.equal(isExternalAdapterTool('multiedit'), false);
26
- assert.equal(isExternalAdapterTool(''), false);
27
- assert.equal(isExternalAdapterTool(null), false);
28
- });
29
-
30
- test('StrReplace: unique match replaces content', async () => {
31
- const dir = makeDir();
32
- const file = join(dir, 'a.txt');
33
- writeFileSync(file, 'alpha\nbeta\ngamma\n');
34
- const out = await tryExecuteExternalToolAdapter('StrReplace', {
35
- path: file, old_string: 'beta', new_string: 'BETA',
36
- }, dir, {});
37
- assert.match(out, /^Updated .*\(1 replacement\)$/);
38
- assert.equal(readFileSync(file, 'utf8'), 'alpha\nBETA\ngamma\n');
39
- });
40
-
41
- test('StrReplace: zero matches errors without writing', async () => {
42
- const dir = makeDir();
43
- const file = join(dir, 'a.txt');
44
- writeFileSync(file, 'alpha\n');
45
- const out = await tryExecuteExternalToolAdapter('str_replace', {
46
- path: file, old_string: 'missing', new_string: 'x',
47
- }, dir, {});
48
- assert.match(out, /old_string not found/);
49
- assert.equal(readFileSync(file, 'utf8'), 'alpha\n');
50
- });
51
-
52
- test('StrReplace: ambiguous matches error with count', async () => {
53
- const dir = makeDir();
54
- const file = join(dir, 'a.txt');
55
- writeFileSync(file, 'dup\ndup\n');
56
- const out = await tryExecuteExternalToolAdapter('search_replace', {
57
- path: file, old_string: 'dup', new_string: 'x',
58
- }, dir, {});
59
- assert.match(out, /ambiguous: 2 matches/);
60
- assert.equal(readFileSync(file, 'utf8'), 'dup\ndup\n');
61
- });
62
-
63
- test('StrReplace: missing old_string falls back (null)', async () => {
64
- const dir = makeDir();
65
- const out = await tryExecuteExternalToolAdapter('StrReplace', {
66
- path: join(dir, 'a.txt'), new_string: 'x',
67
- }, dir, {});
68
- assert.equal(out, null);
69
- });
70
-
71
- test('StrReplace: unreadable target is a concrete error', async () => {
72
- const dir = makeDir();
73
- const out = await tryExecuteExternalToolAdapter('StrReplace', {
74
- path: join(dir, 'nope.txt'), old_string: 'a', new_string: 'b',
75
- }, dir, {});
76
- assert.match(out, /^Error: cannot read /);
77
- });
78
-
79
- test('Write: creates a new file with contents', async () => {
80
- const dir = makeDir();
81
- const file = join(dir, 'new', 'nested.txt');
82
- const out = await tryExecuteExternalToolAdapter('Write', {
83
- path: file, contents: 'created body',
84
- }, dir, {});
85
- assert.match(out, /^Created .*nested\.txt \(12 bytes\)$/);
86
- assert.equal(readFileSync(file, 'utf8'), 'created body');
87
- });
88
-
89
- test('Write: overwrites an existing file and says Updated', async () => {
90
- const dir = makeDir();
91
- const file = join(dir, 'exists.txt');
92
- writeFileSync(file, 'old');
93
- const out = await tryExecuteExternalToolAdapter('write', {
94
- file_path: file, content: 'new body',
95
- }, dir, {});
96
- assert.match(out, /^Updated /);
97
- assert.equal(readFileSync(file, 'utf8'), 'new body');
98
- });
99
-
100
- test('Write: missing contents falls back to redirect (null)', async () => {
101
- const dir = makeDir();
102
- const out = await tryExecuteExternalToolAdapter('Write', {
103
- path: join(dir, 'x.txt'),
104
- }, dir, {});
105
- assert.equal(out, null);
106
- assert.equal(existsSync(join(dir, 'x.txt')), false);
107
- });
108
-
109
- test('StrReplace: missing old_string/new_string falls back (null)', async () => {
110
- const dir = makeDir();
111
- const file = join(dir, 'a.txt');
112
- writeFileSync(file, 'body');
113
- const out = await tryExecuteExternalToolAdapter('StrReplace', {
114
- path: file, old_string: 'body',
115
- }, dir, {});
116
- assert.equal(out, null);
117
- });
118
-
119
- test('StrReplace: empty old_string is a concrete error (no hang)', async () => {
120
- const dir = makeDir();
121
- const file = join(dir, 'a.txt');
122
- writeFileSync(file, 'body');
123
- const out = await tryExecuteExternalToolAdapter('StrReplace', {
124
- path: file, old_string: '', new_string: 'x',
125
- }, dir, {});
126
- assert.match(out, /old_string must be a non-empty string/);
127
- assert.equal(readFileSync(file, 'utf8'), 'body');
128
- });
129
-
130
- test('bash family: missing command falls back (null)', async () => {
131
- const dir = makeDir();
132
- const out = await tryExecuteExternalToolAdapter('Bash', {}, dir, {});
133
- assert.equal(out, null);
134
- });
135
-
136
- test('Write: target outside workDir is refused (base containment)', async () => {
137
- const workDir = makeDir();
138
- const outsideDir = makeDir();
139
- const outside = join(outsideDir, 'escape.txt');
140
- const out = await tryExecuteExternalToolAdapter('Write', {
141
- path: outside, contents: 'should not land',
142
- }, workDir, {});
143
- assert.match(out, /cannot write outside the working directory/);
144
- assert.equal(existsSync(outside), false);
145
- });
146
-
147
- test('non-adapted names return null without touching fs', async () => {
148
- const dir = makeDir();
149
- const out = await tryExecuteExternalToolAdapter('Delete', {
150
- path: join(dir, 'x.txt'),
151
- }, dir, {});
152
- assert.equal(out, null);
153
- });
154
-
155
- test('bash family: executes a command via the shell runner', async () => {
156
- const dir = makeDir();
157
- const out = await tryExecuteExternalToolAdapter('run_terminal_cmd', {
158
- command: 'node -e "console.log(\'adapter-ok\')"',
159
- }, dir, {});
160
- assert.equal(typeof out, 'string');
161
- assert.match(out, /adapter-ok/);
162
- });
@@ -1,26 +0,0 @@
1
- // open_config builtin tool.
2
- //
3
- // Lives in the always-on `builtin` module (not the gated `agent` module) so
4
- // the config UI stays reachable even when modules.agent.enabled === false —
5
- // otherwise a user who disabled the agent module would lose every way to open
6
- // the settings UI (the old `bun launch.mjs` slash shell-out is gone).
7
- //
8
- // Launches the config UI through the resident MCP server. Because this server
9
- // is a long-lived background process, its spawn of setup-server
10
- // (windowsHide:true inside launch-core) creates NO console window — unlike the
11
- // old `!bun launch.mjs` slash-command shell-out, which flashed a conhost. The
12
- // launch-core import is lazy so its child_process/http deps stay off the hot
13
- // builtin path until the tool is actually called.
14
-
15
- export async function executeOpenConfigTool() {
16
- const { launchConfigUi, LaunchError } = await import(
17
- new URL('../../../../../setup/launch-core.mjs', import.meta.url).href
18
- );
19
- try {
20
- const url = await launchConfigUi();
21
- return `Config UI opened at ${url}`;
22
- } catch (err) {
23
- if (err instanceof LaunchError) return `Error: ${err.message.trim()}`;
24
- throw err;
25
- }
26
- }
@@ -1,138 +0,0 @@
1
- import { readFileSync, writeFileSync, mkdirSync, existsSync } from "fs";
2
- import { join, dirname } from "path";
3
- import { homedir } from "os";
4
- import { DATA_DIR } from "./config.mjs";
5
- // Computed lazily (not at module top) so this module can be imported in a
6
- // cycle with config.mjs without touching DATA_DIR before config.mjs has
7
- // finished evaluating it (ESM temporal-dead-zone safety).
8
- function cacheFile() {
9
- return join(DATA_DIR, "holidays-cache.json");
10
- }
11
- const FALLBACK_FILE = join(homedir(), ".mixdog", "schedules", "holidays.json");
12
- const CACHE_MAX_AGE_MS = 30 * 24 * 60 * 60 * 1e3;
13
- // In-memory, pre-warmed holiday cache for SYNC reads (isHolidaySync).
14
- // Keyed by `${year}:${countryCode}` -> Set<dateStr>. Absent key == cold
15
- // (not yet warmed); callers fall back to their documented cold-cache
16
- // behavior until a warm completes.
17
- const memCache = /* @__PURE__ */ new Map();
18
- const warming = /* @__PURE__ */ new Set();
19
- function cacheKey(year, countryCode) {
20
- return `${year}:${countryCode}`;
21
- }
22
- function holidaySet(holidays) {
23
- return new Set((holidays || []).map((h) => h.date));
24
- }
25
- async function fetchHolidays(year, countryCode) {
26
- const url = `https://date.nager.at/api/v3/publicholidays/${year}/${countryCode}`;
27
- const res = await fetch(url, { signal: AbortSignal.timeout(10_000) });
28
- if (!res.ok) throw new Error(`Nager API ${res.status}: ${res.statusText}`);
29
- return res.json();
30
- }
31
- function loadCache(year, countryCode) {
32
- try {
33
- const file = cacheFile();
34
- if (!existsSync(file)) return null;
35
- const cache = JSON.parse(readFileSync(file, "utf8"));
36
- if (cache.year !== year || cache.countryCode !== countryCode) return null;
37
- if (Date.now() - cache.fetchedAt > CACHE_MAX_AGE_MS) return null;
38
- return cache.holidays;
39
- } catch {
40
- return null;
41
- }
42
- }
43
- function saveCache(year, countryCode, holidays) {
44
- const cache = { year, countryCode, fetchedAt: Date.now(), holidays };
45
- try {
46
- const file = cacheFile();
47
- mkdirSync(dirname(file), { recursive: true });
48
- writeFileSync(file, JSON.stringify(cache, null, 2));
49
- } catch {
50
- }
51
- }
52
- function loadFallback() {
53
- try {
54
- if (!existsSync(FALLBACK_FILE)) return /* @__PURE__ */ new Set();
55
- const data = JSON.parse(readFileSync(FALLBACK_FILE, "utf8"));
56
- const dates = data.holidays ?? [];
57
- return new Set(dates);
58
- } catch {
59
- return /* @__PURE__ */ new Set();
60
- }
61
- }
62
- async function isHoliday(date, countryCode) {
63
- const year = date.getFullYear();
64
- const dateStr = `${year}-${String(date.getMonth() + 1).padStart(2, "0")}-${String(date.getDate()).padStart(2, "0")}`;
65
- let holidays = loadCache(year, countryCode);
66
- if (!holidays) {
67
- try {
68
- holidays = await fetchHolidays(year, countryCode);
69
- saveCache(year, countryCode, holidays);
70
- } catch (err) {
71
- process.stderr.write(`mixdog holidays: API fetch failed: ${err}
72
- `);
73
- holidays = null;
74
- }
75
- }
76
- if (holidays) {
77
- return holidays.some((h) => h.date === dateStr);
78
- }
79
- const fallback = loadFallback();
80
- return fallback.has(dateStr);
81
- }
82
- // Synchronous, non-blocking holiday lookup backed by the pre-warmed
83
- // in-memory cache. `dateStr` is a plain 'YYYY-MM-DD' string (the same
84
- // representation warmHolidays stores), so there is no Date round-trip to
85
- // shift the day across host offsets. Returns:
86
- // true — date is a recognized public holiday (cache warm),
87
- // false — date is NOT a holiday (cache warm),
88
- // null — cache is cold (not warmed yet); the async warm is kicked off
89
- // and the caller should apply its documented cold-cache
90
- // fallback. Never performs network OR blocking disk I/O — the
91
- // cold path consults the in-memory cache only.
92
- function isHolidaySync(dateStr, countryCode) {
93
- if (!countryCode) return false;
94
- const year = Number(dateStr.slice(0, 4));
95
- const key = cacheKey(year, countryCode);
96
- const set = memCache.get(key);
97
- if (set === void 0) {
98
- // Cold: no synchronous disk probe here. Kick off the async warm
99
- // (which does any on-disk/network lookup off the sync path) and
100
- // report cold so the caller applies its documented fallback.
101
- warmHolidays(dateStr, countryCode);
102
- return null;
103
- }
104
- return set.has(dateStr);
105
- }
106
- // Asynchronously populate the in-memory sync cache for the given
107
- // dateStr's year/country (from the on-disk cache when fresh, else Nager).
108
- // `dateStr` is a plain 'YYYY-MM-DD' string. Idempotent and
109
- // self-deduplicating; failures leave the entry cold so the next sync
110
- // read retries.
111
- async function warmHolidays(dateStr, countryCode) {
112
- if (!countryCode) return;
113
- const year = Number(dateStr.slice(0, 4));
114
- const key = cacheKey(year, countryCode);
115
- if (memCache.has(key) || warming.has(key)) return;
116
- warming.add(key);
117
- try {
118
- // Yield once before touching disk so the cold sync caller that
119
- // triggered this warm is never blocked by the loadCache file read.
120
- await Promise.resolve();
121
- let holidays = loadCache(year, countryCode);
122
- if (!holidays) {
123
- holidays = await fetchHolidays(year, countryCode);
124
- saveCache(year, countryCode, holidays);
125
- }
126
- memCache.set(key, holidaySet(holidays));
127
- } catch (err) {
128
- process.stderr.write(`mixdog holidays: warm failed: ${err}
129
- `);
130
- } finally {
131
- warming.delete(key);
132
- }
133
- }
134
- export {
135
- isHoliday,
136
- isHolidaySync,
137
- warmHolidays
138
- };
@@ -1,45 +0,0 @@
1
- import test from 'node:test';
2
- import assert from 'node:assert/strict';
3
- import {
4
- channelNotificationModelContent,
5
- shouldMirrorChannelNotificationToPending,
6
- } from './channel-notification-routing.mjs';
7
-
8
- test('channel schedule notification uses instruction as model content', () => {
9
- const content = channelNotificationModelContent({
10
- content: ' ',
11
- meta: {
12
- type: 'schedule',
13
- instruction: 'scheduled prompt body',
14
- },
15
- });
16
- assert.equal(content, 'scheduled prompt body');
17
- });
18
-
19
- test('silent channel notification has no model content', () => {
20
- const content = channelNotificationModelContent({
21
- content: 'hidden',
22
- meta: {
23
- type: 'schedule',
24
- silent_to_agent: true,
25
- },
26
- });
27
- assert.equal(content, '');
28
- });
29
-
30
- test('non-schedule notification preserves instruction-first routing', () => {
31
- const content = channelNotificationModelContent({
32
- content: 'worker result body',
33
- meta: {
34
- type: 'webhook',
35
- instruction: 'relay instruction',
36
- },
37
- });
38
- assert.equal(content, 'relay instruction');
39
- });
40
-
41
- test('only schedule channel notifications are mirrored to pending fallback', () => {
42
- assert.equal(shouldMirrorChannelNotificationToPending({ type: 'schedule' }), true);
43
- assert.equal(shouldMirrorChannelNotificationToPending({ type: 'webhook' }), false);
44
- assert.equal(shouldMirrorChannelNotificationToPending({}), false);
45
- });