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
@@ -22,28 +22,15 @@ class PendingInteractionStore {
22
22
  this.patch(userId, channelId, { _msgId: messageId });
23
23
  }
24
24
  }
25
- function getPendingSelectUpdate(customId, values) {
26
- const value = values?.[0];
27
- if (!value) return null;
28
- const scheduleMatch = customId.match(/^sched_(add|edit)_(period|exec|mode)$/);
29
- if (scheduleMatch) {
30
- return { [scheduleMatch[2]]: value };
31
- }
32
- if (customId === "quiet_holidays_select") {
33
- return { holidays: value };
34
- }
35
- if (customId === "activity_mode_select") {
36
- return { activityMode: value };
37
- }
38
- return null;
39
- }
40
25
  function buildModalRequestSpec(customId, pending, profile) {
41
26
  switch (customId) {
42
27
  case "sched_add_next": {
43
28
  const fields = [
44
29
  { id: "name", label: "Name", required: true },
45
30
  { id: "time", label: "Cron expression (e.g. 0 9 * * 1-5)", required: true },
46
- { id: "channel", label: "Channel", required: false, value: "general" }
31
+ // Single main channel: `channel` is a boolean-ish flag (true post
32
+ // to the main channel, empty/false → inject into the Lead session).
33
+ { id: "channel", label: "Post to channel? (true / false)", required: false, value: "true" }
47
34
  ];
48
35
  if (pending.exec?.includes("script")) {
49
36
  fields.push({ id: "script", label: "Script filename", required: true });
@@ -54,19 +41,10 @@ function buildModalRequestSpec(customId, pending, profile) {
54
41
  fields
55
42
  };
56
43
  }
57
- case "quiet_set_next":
58
- return {
59
- customId: "modal_quiet",
60
- title: "Quiet Hours",
61
- fields: [
62
- { id: "schedule", label: "Schedule quiet hours (e.g. 23:00-07:00)", required: false }
63
- ]
64
- };
65
44
  case "sched_edit_next": {
66
45
  const fields = [
67
46
  { id: "time", label: "Cron expression (e.g. 0 9 * * 1-5)", required: false },
68
- { id: "channel", label: "Channel", required: false },
69
- { id: "dnd", label: "Quiet hours (e.g. 23:00-07:00, leave empty to disable)", required: false }
47
+ { id: "channel", label: "Post to channel? (true / false)", required: false }
70
48
  ];
71
49
  if (pending.exec?.includes("script")) {
72
50
  fields.push({ id: "script", label: "Script filename", required: false });
@@ -77,15 +55,6 @@ function buildModalRequestSpec(customId, pending, profile) {
77
55
  fields
78
56
  };
79
57
  }
80
- case "activity_add_next":
81
- return {
82
- customId: "modal_activity_add",
83
- title: "Add Activity Channel",
84
- fields: [
85
- { id: "name", label: "Channel Name", required: true },
86
- { id: "id", label: "Channel ID", required: true }
87
- ]
88
- };
89
58
  case "profile_edit":
90
59
  return {
91
60
  customId: "modal_profile_edit",
@@ -101,84 +70,7 @@ function buildModalRequestSpec(customId, pending, profile) {
101
70
  return null;
102
71
  }
103
72
  }
104
- function buildModalExecutionPlan(customId, pending, fields) {
105
- switch (customId) {
106
- case "modal_sched_add": {
107
- const params = {
108
- time: fields.time,
109
- channel: fields.channel || "general",
110
- mode: pending.mode || "non-interactive",
111
- period: pending.period || "daily",
112
- exec: pending.exec || "prompt"
113
- };
114
- if (fields.script) params.script = fields.script;
115
- return {
116
- commands: [{ target: "bot", args: ["schedule", "add", fields.name], params }],
117
- followup: { target: "bot", args: ["schedule", "list"], params: {} }
118
- };
119
- }
120
- case "modal_quiet": {
121
- const commands = [];
122
- if (fields.schedule) commands.push({ target: "bot", args: ["quiet", "schedule", fields.schedule], params: {} });
123
- if (pending.holidays && pending.holidays !== "none") {
124
- commands.push({ target: "bot", args: ["quiet", "holidays", pending.holidays], params: {} });
125
- }
126
- return {
127
- commands,
128
- followup: { target: "bot", args: ["quiet", "list"], params: {} }
129
- };
130
- }
131
- case "modal_sched_edit": {
132
- const name = pending.editName;
133
- if (!name) return null;
134
- const params = {};
135
- if (fields.time) params.time = fields.time;
136
- if (fields.channel) params.channel = fields.channel;
137
- if (pending.period) params.period = pending.period;
138
- if (pending.exec) params.exec = pending.exec;
139
- if (pending.mode) params.mode = pending.mode;
140
- if (fields.script) params.script = fields.script;
141
- const commands = [
142
- { target: "bot", args: ["schedule", "edit", name], params }
143
- ];
144
- if (fields.dnd) {
145
- commands.push({ target: "bot", args: ["quiet", "schedule", fields.dnd], params: {} });
146
- }
147
- return {
148
- commands,
149
- followup: { target: "bot", args: ["schedule", "detail", name], params: {} }
150
- };
151
- }
152
- case "modal_activity_add":
153
- return {
154
- commands: [{
155
- target: "bot",
156
- args: ["activity", "add", fields.name],
157
- params: {
158
- id: fields.id,
159
- mode: pending.activityMode || "interactive"
160
- }
161
- }],
162
- followup: { target: "bot", args: ["activity", "list"], params: {} }
163
- };
164
- case "modal_profile_edit": {
165
- const params = {};
166
- if (fields.name) params.name = fields.name;
167
- if (fields.role) params.role = fields.role;
168
- if (fields.lang) params.lang = fields.lang;
169
- if (fields.tone) params.tone = fields.tone;
170
- return {
171
- commands: Object.keys(params).length > 0 ? [{ target: "profile", args: ["set"], params }] : [],
172
- followup: { target: "bot", args: ["profile", "list"], params: {} }
173
- };
174
- }
175
- default:
176
- return null;
177
- }
178
- }
179
73
  export {
180
74
  PendingInteractionStore,
181
- buildModalExecutionPlan,
182
- buildModalRequestSpec,
183
- getPendingSelectUpdate
75
+ buildModalRequestSpec
184
76
  };
@@ -588,7 +588,7 @@ ${_bt.trim()}` : _bt.trim();
588
588
  // isHidden, because that helper would re-consult the module-local
589
589
  // HIDDEN_TOOLS Set and ignore the OutputForwarder static.
590
590
  if (OutputForwarder.HIDDEN_TOOLS.has(name)) return true;
591
- if (name === "reply" || name === "react" || name === "edit_message" || name === "fetch" || name === "download_attachment") return true;
591
+ if (name === "reply" || name === "fetch") return true;
592
592
  return false;
593
593
  };
594
594
  /** Concatenate text blocks from a transcript entry (user or assistant). */
@@ -0,0 +1,75 @@
1
+ // Bridge ownership snapshot + owner heartbeat cluster. Extracted verbatim from
2
+ // channels/index.mjs (behavior-preserving). Self-contained: owns its own
3
+ // heartbeat timer + last-note dedup state, and depends only on the
4
+ // active-instance read/refresh primitives + identity, threaded as injected
5
+ // deps so the module reads live file-level references at call time.
6
+ function createOwnerHeartbeat({
7
+ getInstanceId,
8
+ readActiveInstance,
9
+ refreshActiveInstance,
10
+ OWNER_HEARTBEAT_INTERVAL_MS = 5e3,
11
+ }) {
12
+ let lastOwnershipNote = "";
13
+ let ownerHeartbeatTimer = null;
14
+
15
+ function logOwnership(note) {
16
+ if (lastOwnershipNote === note) return;
17
+ lastOwnershipNote = note;
18
+ process.stderr.write(`[ownership] ${note}
19
+ `);
20
+ }
21
+ function currentOwnerState() {
22
+ const active = readActiveInstance();
23
+ return {
24
+ active,
25
+ // Strict last-wins: this process owns the bridge ONLY when active-instance
26
+ // names exactly this INSTANCE_ID. A newer remote session that claims the
27
+ // seat overwrites instanceId, so the old owner immediately reads owned=false
28
+ // and disconnects on its next refresh tick. No PID/terminal fallback —
29
+ // that used to let a co-terminal worker wrongly self-claim.
30
+ owned: active?.instanceId === getInstanceId()
31
+ };
32
+ }
33
+ function getBridgeOwnershipSnapshot() {
34
+ return currentOwnerState();
35
+ }
36
+ function claimBridgeOwnership(reason) {
37
+ refreshActiveInstance(getInstanceId());
38
+ logOwnership(`claimed owner (${reason})`);
39
+ }
40
+ function startOwnerHeartbeat() {
41
+ if (ownerHeartbeatTimer) return;
42
+ ownerHeartbeatTimer = setInterval(() => {
43
+ try {
44
+ // Last-wins guard: only refresh the seat if we STILL own it. If a newer
45
+ // remote session claimed active-instance.json since our last tick, do
46
+ // NOT overwrite it back — that would re-steal ownership and cause
47
+ // ping-pong / double backend connections. The bridgeOwnershipTimer's
48
+ // refreshBridgeOwnership() will observe owned=false and disconnect us.
49
+ // onlyIfOwned: re-checks ownership INSIDE the file lock so a newer
50
+ // owner claiming the seat between currentOwnerState() and the locked
51
+ // write is never overwritten (see refreshActiveInstance CAS guard).
52
+ if (currentOwnerState().owned) refreshActiveInstance(getInstanceId(), undefined, { onlyIfOwned: true });
53
+ } catch (e) {
54
+ process.stderr.write(`[ownership] heartbeat refresh failed: ${e instanceof Error ? e.message : String(e)}\n`);
55
+ }
56
+ }, OWNER_HEARTBEAT_INTERVAL_MS);
57
+ ownerHeartbeatTimer.unref?.();
58
+ }
59
+ function stopOwnerHeartbeat() {
60
+ if (!ownerHeartbeatTimer) return;
61
+ clearInterval(ownerHeartbeatTimer);
62
+ ownerHeartbeatTimer = null;
63
+ }
64
+
65
+ return {
66
+ logOwnership,
67
+ currentOwnerState,
68
+ getBridgeOwnershipSnapshot,
69
+ claimBridgeOwnership,
70
+ startOwnerHeartbeat,
71
+ stopOwnerHeartbeat,
72
+ };
73
+ }
74
+
75
+ export { createOwnerHeartbeat };
@@ -0,0 +1,88 @@
1
+ // Worker → parent IPC bridge. Extracted verbatim from channels/index.mjs
2
+ // (behavior-preserving). Groups the notify-to-parent path and the
3
+ // worker → parent → memory call bridge. Bound to live getters
4
+ // (getInstanceId) so runtime identity stays consistent.
5
+ function normalizeChannelNotifyParams(method, params) {
6
+ if (method === 'notifications/claude/channel' && params && params.meta) {
7
+ const m = {};
8
+ for (const [k, v] of Object.entries(params.meta)) {
9
+ if (v === undefined || v === null) continue;
10
+ m[k] = k === 'silent_to_agent' ? (v === true || v === 'true') : String(v);
11
+ }
12
+ return { ...params, meta: m };
13
+ }
14
+ return params;
15
+ }
16
+
17
+ function createParentBridge({ getInstanceId }) {
18
+ function sendNotifyToParent(method, params) {
19
+ // CC channel schema requires meta: Record<string,string> (channelNotification.ts).
20
+ // Coerce every meta value to string so a non-string (e.g. a Discord
21
+ // interaction.type number) can't fail zod and silently drop the notify.
22
+ // silent_to_agent stays boolean — an internal routing flag the daemon
23
+ // router / agentNotify consume (=== true) before the CC zod boundary.
24
+ const outParams = normalizeChannelNotifyParams(method, params);
25
+ if (!process.send) {
26
+ try { process.stderr.write(`mixdog channels: notify dropped (no IPC channel): ${method}\n`); } catch {}
27
+ return;
28
+ }
29
+ try {
30
+ process.send({ type: 'notify', method, params: outParams });
31
+ } catch (err) {
32
+ try { process.stderr.write(`mixdog channels: notify IPC send failed: ${err && err.message || err}\n`); } catch {}
33
+ }
34
+ }
35
+
36
+ // ── Memory worker bridge (worker → parent → memory) ─────────────────
37
+ // The channels worker does not own the memory worker handle. To trigger
38
+ // memory tool actions (e.g. cycle1) we send `memory_call_request` to the
39
+ // parent, which routes through callWorker('memory', ...) and ships the
40
+ // result back as `memory_call_response`. The response listener is
41
+ // integrated into the main IPC handler below (not a second listener).
42
+ const _memoryCallPending = new Map();
43
+ let _memoryCallSeq = 0;
44
+
45
+ function callMemoryAction(action, args, timeoutMs) {
46
+ return new Promise((resolve, reject) => {
47
+ if (!process.send) return reject(new Error('not a worker process'));
48
+ const callId = `mc_${getInstanceId()}_${++_memoryCallSeq}_${Math.random().toString(36).slice(2, 8)}`;
49
+ const timer = setTimeout(() => {
50
+ _memoryCallPending.delete(callId);
51
+ reject(new Error(`memory_call ${action} timed out after ${timeoutMs}ms`));
52
+ }, timeoutMs);
53
+ _memoryCallPending.set(callId, {
54
+ resolve: (v) => { clearTimeout(timer); resolve(v); },
55
+ reject: (e) => { clearTimeout(timer); reject(e); },
56
+ });
57
+ try {
58
+ process.send({ type: 'memory_call_request', callId, action, args: args || {} });
59
+ } catch (e) {
60
+ _memoryCallPending.delete(callId);
61
+ clearTimeout(timer);
62
+ reject(e);
63
+ }
64
+ });
65
+ }
66
+
67
+ // Response side of the worker → parent → memory bridge. The caller routes
68
+ // `memory_call_response` messages here from its single process IPC handler
69
+ // (keeping IPC dispatch in one place). Returns true when the message was a
70
+ // recognised memory_call_response (handled), false otherwise.
71
+ function handleMemoryCallResponse(msg) {
72
+ if (!(msg && msg.type === 'memory_call_response' && msg.callId)) return false;
73
+ const pending = _memoryCallPending.get(msg.callId);
74
+ if (!pending) return true;
75
+ _memoryCallPending.delete(msg.callId);
76
+ if (msg.ok) pending.resolve(msg.result);
77
+ else pending.reject(new Error(msg.error || 'memory_call failed'));
78
+ return true;
79
+ }
80
+
81
+ return {
82
+ sendNotifyToParent,
83
+ callMemoryAction,
84
+ handleMemoryCallResponse,
85
+ };
86
+ }
87
+
88
+ export { createParentBridge, normalizeChannelNotifyParams };
@@ -104,7 +104,7 @@ function touchUiHeartbeat(instanceId) {
104
104
  if (!curRaw) return undefined;
105
105
  if (instanceId && curRaw.instanceId !== instanceId) return undefined;
106
106
  return { ...curRaw, ui_heartbeat_at: Date.now(), updatedAt: Date.now() };
107
- }, { compact: true, fsync: false, fsyncDir: false });
107
+ }, { compact: true, fsync: false, fsyncDir: false, renameFallback: 'truncate' });
108
108
  } catch { /* best-effort; a missed tick just relies on the next one */ }
109
109
  }
110
110
  function buildRuntimeIdentity() {
@@ -209,7 +209,7 @@ function readActiveInstance() {
209
209
  && _staleSup2 === undefined;
210
210
  if (ownerEmpty) return undefined;
211
211
  return { ...stableRestLocked, updatedAt: Date.now() };
212
- }, { compact: true, fsync: false, fsyncDir: false });
212
+ }, { compact: true, fsync: false, fsyncDir: false, renameFallback: 'truncate' });
213
213
  } catch {}
214
214
  }
215
215
  return null;
@@ -240,11 +240,21 @@ function buildActiveInstanceState(instanceId, meta) {
240
240
  ...typeof meta?.backendReady === "boolean" ? { backendReady: meta.backendReady } : {}
241
241
  };
242
242
  }
243
- function refreshActiveInstance(instanceId, meta) {
243
+ function refreshActiveInstance(instanceId, meta, options) {
244
244
  ensureRuntimeDirs();
245
245
  return updateJsonAtomicSync(ACTIVE_INSTANCE_FILE, (curRaw) => {
246
246
  const prevForPreserve = curRaw;
247
247
  const prev = activeInstanceStaleReason(curRaw) ? null : curRaw;
248
+ // CAS guard (opt-in via options.onlyIfOwned): heartbeat/refresh ticks
249
+ // check ownership OUTSIDE this lock, so a newer session can claim the
250
+ // seat between that check and this locked update. Without this re-read,
251
+ // the tick would overwrite the newer owner's instanceId (TOCTOU
252
+ // re-steal -> ownership ping-pong / double backend connections).
253
+ // Returning undefined aborts the update with no write. Explicit claims
254
+ // (boot, claimBridgeOwnership) omit the option and stay last-wins.
255
+ if (options?.onlyIfOwned && (!prev?.instanceId || prev.instanceId !== instanceId)) {
256
+ return undefined;
257
+ }
248
258
  // Drop stale fields (pid/startedAt) written by older server versions.
249
259
  const { pid: _legacyPid, startedAt: _legacyStartedAt, ...prevRest } = prev ?? {};
250
260
  const identity = buildRuntimeIdentity();
@@ -362,7 +372,7 @@ function refreshActiveInstance(instanceId, meta) {
362
372
  }
363
373
  }
364
374
  return { ...preservedExtra, ...next };
365
- }, { compact: true, fsync: false, fsyncDir: false });
375
+ }, { compact: true, fsync: false, fsyncDir: false, renameFallback: 'truncate' });
366
376
  }
367
377
  const SERVER_PID_FILE = join(
368
378
  RUNTIME_ROOT,
@@ -3,7 +3,7 @@ import { appendFile as _appendFile } from "fs";
3
3
  import { join, isAbsolute } from "path";
4
4
  import { tmpdir } from "os";
5
5
  import { randomUUID } from "crypto";
6
- import { DATA_DIR, DEFAULT_HOLIDAY_COUNTRY, isInQuietWindow } from "./config.mjs";
6
+ import { DATA_DIR } from "./config.mjs";
7
7
  import { runScript as execScript, ensureNopluginDir } from "./executor.mjs";
8
8
  import { withFileLockSync } from "../../shared/atomic-file.mjs";
9
9
  import { makeAgentDispatch } from '../../agent/orchestrator/agent-runtime/agent-dispatch.mjs';
@@ -39,7 +39,6 @@ function logSchedule(msg) {
39
39
  if (!_schedLogTimer) _schedLogTimer = setTimeout(_flushScheduleLog, 2000);
40
40
  }
41
41
 
42
- import { isHoliday } from "./holidays.mjs";
43
42
  import { tryRead } from "./settings.mjs";
44
43
  // node-cron is an optional runtime dep. If the module isn't installed
45
44
  // (e.g. a fresh v0.6.190 where node_modules predates the package.json
@@ -104,7 +103,7 @@ function tzSnapshot(now, tz) {
104
103
  class Scheduler {
105
104
  nonInteractive;
106
105
  interactive;
107
- channelsConfig;
106
+ channelId;
108
107
  promptsDir;
109
108
  tickTimer = null;
110
109
  lastFired = /* @__PURE__ */ new Map();
@@ -122,53 +121,16 @@ class Scheduler {
122
121
  // names skipped for today
123
122
  skippedTodayDate = "";
124
123
  // "YYYY-MM-DD" local date the skippedToday set belongs to
125
- holidayCountry = null;
126
- // ISO country code for holiday check
127
- holidayChecked = "";
128
- // "YYYY-MM-DD" last checked date
129
- todayIsHoliday = false;
130
- // cached result for today
131
- quietSchedule = null;
132
- // global quiet hours "HH:MM-HH:MM"
133
124
  cronJobs = /* @__PURE__ */ new Map();
134
125
  // name -> node-cron ScheduledTask for cron-expression entries
135
126
  //
136
- // 0.1.62 wiring:
137
- // `topConfig` is the normalized top-level channels config from
138
- // loadConfig()/applyDefaults() it carries `quiet`, `schedules`
139
- // at the top level. `channelsConfig` is still accepted separately
140
- // (channel metadata only, not quiet config) because resolveChannel()
141
- // needs the channel-label → platform-id map.
142
- constructor(nonInteractive, interactive, channelsConfig, topConfig) {
127
+ // `channelId` is the single resolved main-channel id used when a schedule's
128
+ // `channel` flag is set (post-to-channel); absent flag → inject into session.
129
+ constructor(nonInteractive, interactive, channelId) {
143
130
  this.nonInteractive = nonInteractive.filter((s) => s.enabled !== false);
144
131
  this.interactive = interactive.filter((s) => s.enabled !== false);
145
- this.channelsConfig = channelsConfig ?? null;
132
+ this.channelId = channelId ?? "";
146
133
  this.promptsDir = join(DATA_DIR, "prompts");
147
- this._applyQuietConfig(topConfig);
148
- }
149
- /** Resolve quiet/schedules flags from the top-level config
150
- * (0.1.62 shape: `topConfig.quiet`, `topConfig.schedules`). Falls
151
- * through silently to defaults (empty schedule, holidays off,
152
- * respect flag default true) when topConfig is missing or malformed
153
- * — defensive, not legacy. */
154
- _applyQuietConfig(topConfig) {
155
- const cfg = (topConfig && typeof topConfig === "object") ? topConfig : null;
156
- const quietSrc = cfg?.quiet ?? null;
157
- const schedulesSrc = cfg?.schedules ?? null;
158
- // Holidays: prefer an explicit ISO country code. Legacy boolean `true`
159
- // from older setup UI builds is normalized to the local default country.
160
- const hol = quietSrc?.holidays;
161
- if (hol === true) {
162
- this.holidayCountry = DEFAULT_HOLIDAY_COUNTRY;
163
- } else if (typeof hol === "string" && hol) {
164
- this.holidayCountry = hol.trim().toUpperCase();
165
- } else {
166
- this.holidayCountry = null;
167
- }
168
- // Quiet window string "HH:MM-HH:MM" from topConfig.quiet.schedule.
169
- this.quietSchedule = quietSrc?.schedule ?? null;
170
- // Opt-in flag: default true when unspecified, matching applyDefaults.
171
- this.respectQuietSchedules = schedulesSrc?.respectQuiet !== false;
172
134
  }
173
135
  setInjectHandler(fn) {
174
136
  this.injectFn = fn;
@@ -372,8 +334,8 @@ ${Scheduler.INSTANCE_UUID}`;
372
334
  }
373
335
  }
374
336
  }
375
- /** Fire path for a cron-triggered entry. Applies day/quiet/holiday
376
- * guards against the schedule's TZ (or local when absent). */
337
+ /** Fire path for a cron-triggered entry. Applies day guards against
338
+ * the schedule's TZ (or local when absent). */
377
339
  async onCronFire(schedule, type) {
378
340
  const now = /* @__PURE__ */ new Date();
379
341
  const tz = schedule.timezone || null;
@@ -381,16 +343,6 @@ ${Scheduler.INSTANCE_UUID}`;
381
343
  const isWeekend = snap.dow === 0 || snap.dow === 6;
382
344
  const days = schedule.days ?? "daily";
383
345
  if (!this.matchesDays(days, snap.dow, isWeekend)) return;
384
- if (this.holidayCountry) {
385
- try {
386
- const holiday = await isHoliday(this.tzDate(now, tz), this.holidayCountry);
387
- if (holiday && (schedule.skipHolidays || days === "weekday")) {
388
- logSchedule(`skipping "${schedule.name}" \u2014 public holiday\n`);
389
- return;
390
- }
391
- } catch {}
392
- }
393
- if ((schedule.dnd || this.respectQuietSchedules) && this.isQuietHours(now, tz)) return;
394
346
  if (this.shouldSkip(schedule.name)) return;
395
347
  // Record lastFired only when the fire actually proceeds past
396
348
  // fireTimed's running/precondition guards (it resolves truthy on a
@@ -401,13 +353,6 @@ ${Scheduler.INSTANCE_UUID}`;
401
353
  (err) => process.stderr.write(`mixdog scheduler: ${schedule.name} failed: ${err}\n`)
402
354
  );
403
355
  }
404
- /** Produce a Date whose calendar day matches the TZ-adjusted dateStr,
405
- * so holiday lookups by country work against the right day. */
406
- tzDate(now, tz) {
407
- if (!tz) return now;
408
- const snap = tzSnapshot(now, tz);
409
- return new Date(`${snap.dateStr}T12:00:00Z`);
410
- }
411
356
  stop() {
412
357
  if (this.tickTimer) {
413
358
  clearInterval(this.tickTimer);
@@ -447,14 +392,11 @@ ${Scheduler.INSTANCE_UUID}`;
447
392
  } catch {}
448
393
  this.start();
449
394
  }
450
- reloadConfig(nonInteractive, interactive, channelsConfig, topConfig, options = {}) {
395
+ reloadConfig(nonInteractive, interactive, channelId, options = {}) {
451
396
  this.nonInteractive = nonInteractive.filter((s) => s.enabled !== false);
452
397
  this.interactive = interactive.filter((s) => s.enabled !== false);
453
- this.channelsConfig = channelsConfig ?? null;
398
+ this.channelId = channelId ?? "";
454
399
  this.promptsDir = join(DATA_DIR, "prompts");
455
- this._applyQuietConfig(topConfig);
456
- this.holidayChecked = "";
457
- this.todayIsHoliday = false;
458
400
  if (this.deferred.size > 0 || this.skippedToday.size > 0) {
459
401
  process.stderr.write(`mixdog scheduler: reload clearing ${this.deferred.size} deferred, ${this.skippedToday.size} skipped
460
402
  `);
@@ -520,24 +462,9 @@ ${Scheduler.INSTANCE_UUID}`;
520
462
  );
521
463
  }
522
464
  async tickAsync() {
523
- const now = /* @__PURE__ */ new Date();
524
- const dateStr = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}-${String(now.getDate()).padStart(2, "0")}`;
525
- // All timed schedules are now handled exclusively by node-cron (registerCronJobs).
526
- // tick() only drives the holiday cache refresh.
527
- if (this.holidayCountry && this.holidayChecked !== dateStr) {
528
- this.holidayChecked = dateStr;
529
- try {
530
- this.todayIsHoliday = await isHoliday(now, this.holidayCountry);
531
- if (this.todayIsHoliday) {
532
- process.stderr.write(`mixdog scheduler: today (${dateStr}) is a holiday \u2014 weekday schedules will be skipped
533
- `);
534
- }
535
- } catch (err) {
536
- process.stderr.write(`mixdog scheduler: holiday check failed: ${err}
537
- `);
538
- this.todayIsHoliday = false;
539
- }
540
- }
465
+ // All timed schedules are handled exclusively by node-cron
466
+ // (registerCronJobs). tick() no longer does any per-day work; the
467
+ // interval is kept for future cadence-driven work and lifecycle parity.
541
468
  }
542
469
  /** Day abbreviation → JS day number (0=Sun...6=Sat) */
543
470
  static DAY_ABBRS = {
@@ -557,18 +484,6 @@ ${Scheduler.INSTANCE_UUID}`;
557
484
  const dayList = days.split(",").map((d) => d.trim().toLowerCase());
558
485
  return dayList.some((d) => Scheduler.DAY_ABBRS[d] === dow);
559
486
  }
560
- /** Check if current time is within global quiet hours (quiet.schedule).
561
- * tz optional — when set, HH:MM is evaluated in the given IANA zone.
562
- *
563
- * Delegates to the shared, now TZ-aware isInQuietWindow(cfg, now, tz)
564
- * helper in lib/config.mjs. Holidays are passed as `false` here on
565
- * purpose: public-holiday skips for the scheduler are handled by the
566
- * separate holidayCountry / skipHolidays / days==="weekday" path in
567
- * onCronFire, so this quiet-window check stays schedule-window only —
568
- * identical to the prior local implementation. */
569
- isQuietHours(now, tz) {
570
- return isInQuietWindow({ schedule: this.quietSchedule, holidays: false }, now, tz);
571
- }
572
487
  // ── Fire timed schedule ─────────────────────────────────────────────
573
488
  async fireTimed(schedule, type) {
574
489
  const execMode = schedule.exec ?? "prompt";
@@ -680,21 +595,20 @@ ${scriptResult}
680
595
  });
681
596
  }
682
597
  // ── Helpers ─────────────────────────────────────────────────────────
683
- /** Resolve a channel label to its platform ID via channelsConfig, fallback to raw value */
684
- resolveChannel(label) {
685
- const entry = this.channelsConfig?.[label];
686
- if (entry?.channelId) return entry.channelId;
687
- // Misconfigured label: no channelsConfig entry, so we fall back to
688
- // using the raw label as the channel id. Warn once per label so the
689
- // misconfiguration is diagnosable without spamming stderr.
690
- if (label != null) {
691
- this._channelFallbackWarned ??= new Set();
692
- if (!this._channelFallbackWarned.has(label)) {
693
- this._channelFallbackWarned.add(label);
694
- process.stderr.write(`mixdog scheduler: channel label "${label}" not found in channelsConfig using it as a raw channel id\n`);
695
- }
696
- }
697
- return label;
598
+ /**
599
+ * Map a schedule's `channel` flag to a target channel id. The flag is
600
+ * boolean-ish: any non-empty value except "false" (including legacy labels
601
+ * like "main") means "post to the main channel"; absent/false → "" (inject
602
+ * into the Lead session). No label resolution. A pure-digit / snowflake
603
+ * value is honored verbatim as an explicit id override (matches
604
+ * resolveWebhookChannelId in index.mjs).
605
+ */
606
+ resolveChannel(flag) {
607
+ if (flag == null) return "";
608
+ const v = String(flag).trim();
609
+ if (v === "" || v.toLowerCase() === "false") return "";
610
+ if (/^-?\d+$/.test(v)) return v;
611
+ return this.channelId ?? "";
698
612
  }
699
613
  /** Resolve prompt: try file first, fall back to inline text */
700
614
  resolvePrompt(schedule) {