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
@@ -1,11 +1,10 @@
1
1
  import { execFile, fork, spawnSync } from 'node:child_process';
2
- import { appendFileSync, mkdirSync, readFileSync, rmSync, statSync, writeFileSync } from 'node:fs';
3
- import http from 'node:http';
2
+ import { mkdirSync, rmSync, writeFileSync } from 'node:fs';
4
3
  import { tmpdir } from 'node:os';
5
4
  import { dirname, join, resolve } from 'node:path';
6
5
  import { fileURLToPath, pathToFileURL } from 'node:url';
7
6
  import { startChildGuardian } from '../runtime/shared/child-guardian.mjs';
8
- import { claimSingletonOwner, readSingletonOwner, releaseSingletonOwner } from '../runtime/shared/singleton-owner.mjs';
7
+ import { appendBuffered } from '../runtime/shared/buffered-appender.mjs';
9
8
 
10
9
  const CHANNEL_TOOLS = new Set([
11
10
  'reply',
@@ -26,7 +25,7 @@ const WORKER_PRELOAD = fileURLToPath(new URL('./channel-worker-preload.cjs', imp
26
25
  function logLine(path, line) {
27
26
  try {
28
27
  mkdirSync(dirname(path), { recursive: true });
29
- appendFileSync(path, `[${new Date().toISOString()}] ${line}\n`);
28
+ appendBuffered(path, `[${new Date().toISOString()}] ${line}\n`);
30
29
  } catch {
31
30
  // Logging must never break the TUI.
32
31
  }
@@ -56,67 +55,6 @@ function runtimeRoot() {
56
55
  return process.env.MIXDOG_RUNTIME_ROOT ? resolve(process.env.MIXDOG_RUNTIME_ROOT) : join(tmpdir(), 'mixdog');
57
56
  }
58
57
 
59
- function readJson(path) {
60
- try { return JSON.parse(readFileSync(path, 'utf8')); } catch { return null; }
61
- }
62
-
63
- function parsePort(value) {
64
- const port = Number(value);
65
- return Number.isInteger(port) && port > 0 && port < 65536 ? port : null;
66
- }
67
-
68
- function readActiveInstance() {
69
- return readJson(join(runtimeRoot(), 'active-instance.json'));
70
- }
71
-
72
- function readOwnerSecret(instanceId) {
73
- const parsed = readJson(join(runtimeRoot(), `owner-secret-${String(instanceId)}.json`));
74
- return typeof parsed?.secret === 'string' ? parsed.secret : '';
75
- }
76
-
77
- function requestJson({ port, method = 'GET', path = '/', body = null, headers = {}, timeoutMs = 120_000 }) {
78
- return new Promise((resolvePromise, reject) => {
79
- const payload = body == null ? null : JSON.stringify(body);
80
- const req = http.request({
81
- hostname: '127.0.0.1',
82
- port,
83
- path,
84
- method,
85
- headers: {
86
- ...headers,
87
- ...(payload
88
- ? { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(payload) }
89
- : {}),
90
- },
91
- timeout: timeoutMs,
92
- }, (res) => {
93
- let data = '';
94
- res.setEncoding('utf8');
95
- res.on('data', chunk => { data += chunk; });
96
- res.on('end', () => {
97
- let parsed = null;
98
- try { parsed = data ? JSON.parse(data) : null; } catch {}
99
- if (res.statusCode && res.statusCode >= 400) {
100
- reject(new Error(parsed?.error || data || `HTTP ${res.statusCode}`));
101
- return;
102
- }
103
- resolvePromise(parsed ?? { raw: data });
104
- });
105
- });
106
- req.on('error', reject);
107
- req.on('timeout', () => {
108
- req.destroy();
109
- reject(new Error(`channel owner request timed out: ${method} ${path}`));
110
- });
111
- if (payload) req.write(payload);
112
- req.end();
113
- });
114
- }
115
-
116
- function textResult(text, isError = false) {
117
- return { content: [{ type: 'text', text: String(text ?? '') }], ...(isError ? { isError: true } : {}) };
118
- }
119
-
120
58
  export function createStandaloneChannelWorker({
121
59
  entry,
122
60
  rootDir,
@@ -141,16 +79,8 @@ export function createStandaloneChannelWorker({
141
79
  const ownedChildPids = new Set();
142
80
  const logPath = join(dataDir, 'channels-worker-standalone.log');
143
81
  const useProcessWorker = process.env.MIXDOG_CHANNEL_WORKER_PROCESS !== '0';
144
- const singletonEnabled = process.env.MIXDOG_CHANNEL_SINGLETON !== '0';
145
- const daemonEnabled = process.env.MIXDOG_CHANNEL_DAEMON !== '0';
146
- const ownerPath = join(dataDir, 'channels-worker-owner.json');
147
- const notifyBusPath = join(runtimeRoot(), 'channel-notifications.jsonl');
148
82
  const clientDir = join(runtimeRoot(), 'channel-clients');
149
83
  const clientPath = join(clientDir, `${process.pid}.json`);
150
- let ownerClaim = null;
151
- let ownerPid = process.pid;
152
- let notifyBusTimer = null;
153
- let notifyBusOffset = 0;
154
84
  let clientHeartbeatTimer = null;
155
85
  let clientHeartbeatExitCleanup = null;
156
86
 
@@ -186,56 +116,9 @@ export function createStandaloneChannelWorker({
186
116
  try { rmSync(clientPath, { force: true }); } catch {}
187
117
  }
188
118
 
189
- function startNotifyBus() {
190
- if (!onNotify || notifyBusTimer) return;
191
- try { notifyBusOffset = statSync(notifyBusPath).size; } catch { notifyBusOffset = 0; }
192
- notifyBusTimer = setInterval(pollNotifyBus, 500);
193
- notifyBusTimer.unref?.();
194
- }
195
-
196
- function stopNotifyBus() {
197
- if (!notifyBusTimer) return;
198
- clearInterval(notifyBusTimer);
199
- notifyBusTimer = null;
200
- }
201
-
202
- function pollNotifyBus() {
203
- let size = 0;
204
- try { size = statSync(notifyBusPath).size; } catch { return; }
205
- if (size < notifyBusOffset) notifyBusOffset = 0;
206
- if (size === notifyBusOffset) return;
207
- let chunk;
208
- try {
209
- const data = readFileSync(notifyBusPath);
210
- chunk = data.subarray(notifyBusOffset, size).toString('utf8');
211
- notifyBusOffset = size;
212
- } catch {
213
- return;
214
- }
215
- for (const line of chunk.split(/\r?\n/)) {
216
- if (!line.trim()) continue;
217
- try {
218
- const msg = JSON.parse(line);
219
- if (!msg || typeof msg !== 'object') continue;
220
- onNotify({ type: 'notify', method: msg.method, params: msg.params, busId: msg.id, busPid: msg.pid });
221
- } catch {}
222
- }
223
- }
224
-
225
- startNotifyBus();
226
119
  startClientHeartbeat();
227
120
 
228
121
  function status() {
229
- const external = externalOwner();
230
- if (external) {
231
- return {
232
- running: false,
233
- pid: null,
234
- pending: pending.size,
235
- mode: 'external-owner',
236
- ownerPid: Number(external.pid) || null,
237
- };
238
- }
239
122
  if (!useProcessWorker) {
240
123
  return {
241
124
  running: Boolean(inProcessMod),
@@ -252,36 +135,6 @@ export function createStandaloneChannelWorker({
252
135
  };
253
136
  }
254
137
 
255
- function externalOwner() {
256
- if (!singletonEnabled || ownerClaim?.owned) return null;
257
- const current = readSingletonOwner(ownerPath);
258
- if (current.alive && current.owner?.pid && Number(current.owner.pid) !== process.pid) return current.owner;
259
- return null;
260
- }
261
-
262
- function ensureOwner() {
263
- if (!singletonEnabled || ownerClaim?.owned) return true;
264
- ownerClaim = claimSingletonOwner(ownerPath, {
265
- kind: 'channel-worker-host',
266
- pid: process.pid,
267
- meta: { cwd },
268
- });
269
- if (!ownerClaim.owned) {
270
- logLine(logPath, `runtime owned by pid ${ownerClaim.owner?.pid || 'unknown'}; skipping local channel worker`);
271
- return false;
272
- }
273
- installParentExitHook();
274
- return true;
275
- }
276
-
277
- function releaseOwner() {
278
- if (!singletonEnabled || !ownerClaim?.owned) return;
279
- releaseSingletonOwner(ownerPath, ownerPid);
280
- ownerClaim = null;
281
- ownerPid = process.pid;
282
- uninstallParentExitHook();
283
- }
284
-
285
138
  function rejectPending(error) {
286
139
  for (const [, item] of pending) {
287
140
  clearTimeout(item.timer);
@@ -292,7 +145,6 @@ export function createStandaloneChannelWorker({
292
145
 
293
146
  function start() {
294
147
  if (!useProcessWorker) return startInProcess();
295
- if (!ensureOwner()) return Promise.resolve(status());
296
148
  if (stopPromise) {
297
149
  return stopPromise.then(() => start());
298
150
  }
@@ -306,35 +158,21 @@ export function createStandaloneChannelWorker({
306
158
  cwd,
307
159
  execArgv: ['--require', WORKER_PRELOAD],
308
160
  stdio: ['ignore', 'ignore', 'pipe', 'ipc'],
309
- detached: daemonEnabled,
161
+ detached: false,
310
162
  env: {
311
163
  ...process.env,
312
164
  MIXDOG_ROOT: rootDir,
313
165
  MIXDOG_DATA_DIR: dataDir,
314
166
  MIXDOG_STANDALONE: '1',
315
167
  MIXDOG_WORKER_MODE: '1',
316
- MIXDOG_CLI_OWNED: daemonEnabled ? '0' : '1',
317
- MIXDOG_CHANNEL_DAEMON: daemonEnabled ? '1' : '0',
318
- MIXDOG_CHANNELS_AUTO_BOOT: '0',
319
- MIXDOG_CHANNEL_FLAG: '1',
168
+ MIXDOG_CLI_OWNED: '0',
320
169
  MIXDOG_QUIET_SESSION_LOG: process.env.MIXDOG_QUIET_SESSION_LOG ?? '1',
321
170
  },
322
171
  windowsHide: true,
323
172
  });
324
173
  const spawnedPid = child.pid;
325
- if (daemonEnabled && spawnedPid && singletonEnabled) {
326
- releaseSingletonOwner(ownerPath, process.pid);
327
- ownerClaim = claimSingletonOwner(ownerPath, {
328
- kind: 'channel-worker-daemon',
329
- pid: spawnedPid,
330
- meta: { cwd, launcherPid: process.pid },
331
- });
332
- ownerPid = spawnedPid;
333
- }
334
- if (!daemonEnabled) {
335
- startChildGuardian({ childPid: spawnedPid, label: 'channel-worker', orphanGraceMs: 8000, forceGraceMs: 3000 });
336
- if (spawnedPid) ownedChildPids.add(spawnedPid);
337
- }
174
+ startChildGuardian({ childPid: spawnedPid, label: 'channel-worker', orphanGraceMs: 8000, forceGraceMs: 3000 });
175
+ if (spawnedPid) ownedChildPids.add(spawnedPid);
338
176
  installParentExitHook();
339
177
 
340
178
  child.stderr?.on('data', (chunk) => {
@@ -360,19 +198,12 @@ export function createStandaloneChannelWorker({
360
198
  return;
361
199
  }
362
200
  if (msg.type === 'notify') {
363
- if (!daemonEnabled) {
364
- try { onNotify?.(msg); } catch {}
365
- }
201
+ try { onNotify?.(msg); } catch {}
366
202
  }
367
203
  });
368
204
 
369
205
  child.on('exit', (code, signal) => {
370
206
  if (spawnedPid) ownedChildPids.delete(spawnedPid);
371
- if (daemonEnabled && spawnedPid) releaseSingletonOwner(ownerPath, spawnedPid);
372
- if (daemonEnabled && ownerPid === spawnedPid) {
373
- ownerClaim = null;
374
- ownerPid = process.pid;
375
- }
376
207
  const error = new Error(`channels runtime exited (${signal || (code ?? 'unknown')})`);
377
208
  if (readyReject) readyReject(error);
378
209
  readyResolve = null;
@@ -394,16 +225,12 @@ export function createStandaloneChannelWorker({
394
225
  }
395
226
 
396
227
  async function startInProcess() {
397
- if (!ensureOwner()) return status();
398
228
  if (inProcessMod) return status();
399
229
  if (inProcessStartPromise) return inProcessStartPromise;
400
230
  inProcessStartPromise = (async () => {
401
231
  process.env.MIXDOG_ROOT = rootDir;
402
232
  process.env.MIXDOG_DATA_DIR = dataDir;
403
233
  process.env.MIXDOG_STANDALONE ??= '1';
404
- process.env.MIXDOG_CHANNEL_FLAG ??= '1';
405
- process.env.MIXDOG_CHANNELS_AUTO_BOOT = '0';
406
- process.env.MIXDOG_CLI_OWNED = '1';
407
234
  const mod = await import(pathToFileURL(entry).href);
408
235
  if (typeof mod?.start !== 'function') throw new Error('channels runtime does not export start()');
409
236
  await mod.start();
@@ -417,9 +244,6 @@ export function createStandaloneChannelWorker({
417
244
 
418
245
  async function execute(name, args = {}, { timeoutMs = 120_000 } = {}) {
419
246
  if (!CHANNEL_TOOLS.has(name)) throw new Error(`unknown channel tool: ${name}`);
420
- if (!ensureOwner()) {
421
- return await executeViaOwnerHttp(name, args || {}, timeoutMs);
422
- }
423
247
  await start();
424
248
  if (!useProcessWorker) {
425
249
  const call = inProcessMod?.handleToolCallWithBridgeRetry || inProcessMod?.handleToolCall;
@@ -453,91 +277,6 @@ export function createStandaloneChannelWorker({
453
277
  });
454
278
  }
455
279
 
456
- async function executeViaOwnerHttp(name, args, timeoutMs) {
457
- const active = readActiveInstance();
458
- const port = parsePort(active?.httpPort);
459
- const instanceId = active?.instanceId;
460
- const token = readOwnerSecret(instanceId);
461
- if (!port || !token) {
462
- const owner = externalOwner();
463
- throw new Error(`channels runtime is owned by pid ${owner?.pid || 'unknown'} but owner HTTP is unavailable`);
464
- }
465
- const headers = {
466
- 'x-owner-token': token,
467
- 'x-owner-instance': String(instanceId || ''),
468
- };
469
- const post = (path, body) => requestJson({ port, method: 'POST', path, body, headers, timeoutMs });
470
- const get = (path) => requestJson({ port, method: 'GET', path, headers, timeoutMs });
471
-
472
- switch (name) {
473
- case 'reply': {
474
- const result = await post('/send', {
475
- chatId: args.chat_id,
476
- text: args.text,
477
- opts: {
478
- replyTo: args.reply_to,
479
- files: args.files ?? [],
480
- embeds: args.embeds ?? [],
481
- components: args.components ?? [],
482
- },
483
- });
484
- const ids = Array.isArray(result?.sentIds) ? result.sentIds : [];
485
- return textResult(ids.length === 1 ? `sent (id: ${ids[0]})` : `sent ${ids.length} parts (ids: ${ids.join(', ')})`);
486
- }
487
- case 'fetch': {
488
- const channel = encodeURIComponent(String(args.channel || ''));
489
- const limit = Math.max(1, Number(args.limit) || 20);
490
- const result = await get(`/fetch?channel=${channel}&limit=${limit}`);
491
- const msgs = Array.isArray(result?.messages) ? result.messages : [];
492
- const text = msgs.length === 0 ? '(no messages)' : msgs.map((m) => {
493
- const atts = Number(m.attachmentCount) > 0 ? ` +${m.attachmentCount}att` : '';
494
- return `[${m.ts}] ${m.user}: ${m.text} (id: ${m.id}${atts})`;
495
- }).join('\n');
496
- return textResult(text);
497
- }
498
- case 'react':
499
- await post('/react', { chatId: args.chat_id, messageId: args.message_id, emoji: args.emoji });
500
- return textResult('reacted');
501
- case 'edit_message': {
502
- const result = await post('/edit', {
503
- chatId: args.chat_id,
504
- messageId: args.message_id,
505
- text: args.text,
506
- opts: { embeds: args.embeds ?? [], components: args.components ?? [] },
507
- });
508
- return textResult(`edited (id: ${result?.id ?? ''})`);
509
- }
510
- case 'download_attachment': {
511
- const result = await post('/download', { chatId: args.chat_id, messageId: args.message_id });
512
- const files = Array.isArray(result?.files) ? result.files : [];
513
- if (files.length === 0) return textResult('message has no attachments');
514
- return textResult(`downloaded ${files.length} attachment(s):\n${files.map((f) => ` ${f.path} (${f.name}, ${f.contentType}, ${(Number(f.size || 0) / 1024).toFixed(0)}KB)`).join('\n')}`);
515
- }
516
- case 'schedule_status': {
517
- const result = await get('/schedule-status');
518
- return result?.result ?? textResult('no schedules configured');
519
- }
520
- case 'trigger_schedule': {
521
- const result = await post('/trigger-schedule', { name: args.name });
522
- return textResult(result?.result == null ? '' : String(result.result));
523
- }
524
- case 'schedule_control': {
525
- const result = await post('/schedule-control', { name: args.name, action: args.action, minutes: args.minutes });
526
- return result?.result ?? textResult(`unknown action: ${args.action}`, true);
527
- }
528
- case 'activate_channel_bridge':
529
- await post('/bridge/activate', { active: args.active === true });
530
- return textResult(`channel bridge ${args.active ? 'activated' : 'deactivated'}`);
531
- case 'inject_command':
532
- await post('/inject', { content: `/${String(args.command || '').trim()}`, source: 'mixdog-agent', type: 'command' });
533
- return textResult(`queued /${String(args.command || '').trim()} via channel owner`);
534
- case 'reload_config':
535
- return textResult('reload_config must run in the channel owner process', true);
536
- default:
537
- return textResult(`unknown channel tool: ${name}`, true);
538
- }
539
- }
540
-
541
280
  function forceKillTree(pid) {
542
281
  if (!pid) return;
543
282
  if (process.platform === 'win32') {
@@ -565,11 +304,8 @@ export function createStandaloneChannelWorker({
565
304
  if (parentExitCleanup) return;
566
305
  parentExitCleanup = registerChannelWorkerExitCleanup(() => {
567
306
  parentExitCleanup = null;
568
- if (!daemonEnabled) {
569
- for (const pid of Array.from(ownedChildPids)) {
570
- forceKillTreeSync(pid);
571
- }
572
- releaseOwner();
307
+ for (const pid of Array.from(ownedChildPids)) {
308
+ forceKillTreeSync(pid);
573
309
  }
574
310
  ownedChildPids.clear();
575
311
  });
@@ -592,12 +328,10 @@ export function createStandaloneChannelWorker({
592
328
 
593
329
  function stop(reason = 'standalone shutdown', options = {}) {
594
330
  const waitForExit = options?.waitForExit !== false;
595
- stopNotifyBus();
596
331
  stopClientHeartbeat();
597
332
  if (stopPromise) return stopPromise;
598
333
  if (!useProcessWorker) {
599
334
  if (!inProcessMod && !inProcessStartPromise) {
600
- releaseOwner();
601
335
  return Promise.resolve(false);
602
336
  }
603
337
  stopPromise = Promise.resolve(inProcessStartPromise)
@@ -605,7 +339,6 @@ export function createStandaloneChannelWorker({
605
339
  .then(async () => {
606
340
  try { await inProcessMod?.stop?.(reason); } catch {}
607
341
  inProcessMod = null;
608
- releaseOwner();
609
342
  return true;
610
343
  })
611
344
  .finally(() => {
@@ -614,23 +347,11 @@ export function createStandaloneChannelWorker({
614
347
  return stopPromise;
615
348
  }
616
349
  if (!child) {
617
- stopNotifyBus();
618
- releaseOwner();
619
350
  return Promise.resolve(false);
620
351
  }
621
352
  const target = child;
622
353
  const targetPid = target.pid;
623
354
  child = null;
624
- if (daemonEnabled && options?.force !== true) {
625
- rejectPending(new Error(`channels runtime detached (${reason})`));
626
- if (targetPid) ownedChildPids.delete(targetPid);
627
- try { target.disconnect?.(); } catch {}
628
- unrefChildHandles(target);
629
- ownerClaim = null;
630
- ownerPid = process.pid;
631
- uninstallParentExitHook();
632
- return Promise.resolve(true);
633
- }
634
355
  if (!waitForExit) {
635
356
  rejectPending(new Error(`channels runtime shutdown requested (${reason})`));
636
357
  if (targetPid) ownedChildPids.delete(targetPid);
@@ -642,7 +363,6 @@ export function createStandaloneChannelWorker({
642
363
  clearTimeout(sendTimer);
643
364
  stopPromise = null;
644
365
  unrefChildHandles(target);
645
- releaseOwner();
646
366
  uninstallParentExitHook();
647
367
  resolve(ok);
648
368
  };
@@ -667,8 +387,6 @@ export function createStandaloneChannelWorker({
667
387
  clearTimeout(termTimer);
668
388
  clearTimeout(killTimer);
669
389
  stopPromise = null;
670
- stopNotifyBus();
671
- releaseOwner();
672
390
  uninstallParentExitHook();
673
391
  resolve(ok);
674
392
  };
@@ -14,7 +14,7 @@ export const EXPLORE_TOOL = {
14
14
  name: 'explore',
15
15
  title: 'Explore',
16
16
  annotations: { title: 'Explore', readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
17
- description: 'Repo anchor locator. Use for broad/uncertain paths. Array only for independent targets.',
17
+ description: 'Repo anchor locator. LLM-backed; broad/uncertain only. Array only for independent targets.',
18
18
  inputSchema: {
19
19
  type: 'object',
20
20
  properties: {
@@ -170,8 +170,12 @@ function findConfigPreset(config, presetName) {
170
170
  }
171
171
 
172
172
  async function ensureExploreProviderReady(config) {
173
- const preset = findConfigPreset(config, config?.maintenance?.explore);
174
- const provider = clean(preset?.provider);
173
+ const route = config?.maintenance?.explore;
174
+ // Route object ({provider,model}) is the current shape; a string is a
175
+ // legacy preset NAME resolved against config.presets.
176
+ const provider = (route && typeof route === 'object')
177
+ ? clean(route.provider)
178
+ : clean(findConfigPreset(config, route)?.provider);
175
179
  if (!provider) return;
176
180
  const providers = { ...(config?.providers || {}) };
177
181
  providers[provider] = { ...(providers[provider] || {}), enabled: true };
@@ -303,9 +307,12 @@ async function runExploreSync(args = {}, ctx = {}) {
303
307
  scheduleExploreCodeGraphPrewarm(resolvedCwd);
304
308
  const config = loadConfig();
305
309
  await ensureExploreProviderReady(config);
306
- const presetName = config?.maintenance?.explore || '';
310
+ const route = config?.maintenance?.explore;
311
+ const presetName = (route && typeof route === 'object')
312
+ ? `${clean(route.provider)}/${clean(route.model)}`
313
+ : (route || '');
307
314
  const llm = makeAgentDispatch({
308
- role: 'explorer',
315
+ agent: 'explorer',
309
316
  cwd: resolvedCwd,
310
317
  brief: true,
311
318
  parentSessionId: ctx.callerSessionId || null,