mixdog 1.3.4 → 1.4.0

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 (472) hide show
  1. package/LICENSES/oh-my-pi-MIT.txt +23 -0
  2. package/NOTICE.md +17 -2
  3. package/README.md +11 -0
  4. package/package.json +3 -11
  5. package/scripts/daemon-stub.mjs +1 -1
  6. package/scripts/embed-google-oauth-client.mjs +49 -0
  7. package/scripts/gate-local.mjs +4 -2
  8. package/scripts/lib/test-spawn-ledger.mjs +46 -1
  9. package/scripts/native-release-status.mjs +2 -2
  10. package/scripts/recall-history-cases.json +1 -1
  11. package/scripts/suite-health.mjs +10 -1
  12. package/scripts/test.mjs +12 -11
  13. package/src/agents/reviewer/AGENT.md +7 -11
  14. package/src/agents/reviewer/agent.json +1 -1
  15. package/src/defaults/shipped-history.json +25 -1
  16. package/src/defaults/skills/browser-use/SKILL.md +77 -17
  17. package/src/defaults/skills/code-tidy/SKILL.md +142 -270
  18. package/src/defaults/skills/code-tidy/references/agent-cleanup.md +4 -4
  19. package/src/defaults/skills/code-tidy/references/cleanup-report.md +31 -76
  20. package/src/defaults/skills/code-tidy/references/dead-code.md +6 -12
  21. package/src/defaults/skills/computer-use/SKILL.md +33 -9
  22. package/src/defaults/skills/setup/SKILL.md +29 -2
  23. package/src/defaults/skills/setup/references/actions.md +117 -28
  24. package/src/defaults/skills/setup/references/surfaces.md +77 -35
  25. package/src/defaults/skills/setup-guide/SKILL.md +3 -3
  26. package/src/defaults/skills/visualize/SKILL.md +2 -1
  27. package/src/lib/keychain-cjs.cjs +359 -29
  28. package/src/lib/rules-builder.cjs +10 -8
  29. package/src/rules/agent/AGENT.md +6 -6
  30. package/src/rules/agent-mode/AGENT_MODE.md +10 -2
  31. package/src/rules/agent-mode/MIXY.md +12 -0
  32. package/src/rules/lead/LEAD.md +18 -20
  33. package/src/rules/shared/05-tool-calls.md +5 -6
  34. package/src/rules/shared/10-tool-workflow.md +2 -0
  35. package/src/rules/shared/60-verification.md +7 -5
  36. package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +2 -3
  37. package/src/runtime/agent/orchestrator/agent-runtime/heartbeat-completion.mjs +193 -0
  38. package/src/runtime/agent/orchestrator/agent-runtime/title-completion.mjs +3 -1
  39. package/src/runtime/agent/orchestrator/agent-trace-format.mjs +3 -1
  40. package/src/runtime/agent/orchestrator/config.mjs +34 -0
  41. package/src/runtime/agent/orchestrator/context/collect.mjs +3 -0
  42. package/src/runtime/agent/orchestrator/context/deferred-tools.mjs +72 -67
  43. package/src/runtime/agent/orchestrator/context/skill-catalog.mjs +54 -1
  44. package/src/runtime/agent/orchestrator/mcp/agent-connectors.mjs +3 -3
  45. package/src/runtime/agent/orchestrator/mcp/client-transport.mjs +177 -73
  46. package/src/runtime/agent/orchestrator/mcp/client.mjs +25 -2
  47. package/src/runtime/agent/orchestrator/mcp/oauth-callback.mjs +31 -2
  48. package/src/runtime/agent/orchestrator/mcp/oauth-client.mjs +16 -3
  49. package/src/runtime/agent/orchestrator/mcp/oauth-login.mjs +42 -7
  50. package/src/runtime/agent/orchestrator/mcp/oauth-provider.mjs +21 -2
  51. package/src/runtime/agent/orchestrator/mcp/oauth-shared.mjs +23 -0
  52. package/src/runtime/agent/orchestrator/mcp/oauth.mjs +11 -1
  53. package/src/runtime/agent/orchestrator/providers/account-pool.mjs +230 -137
  54. package/src/runtime/agent/orchestrator/providers/admission-scheduler.mjs +64 -7
  55. package/src/runtime/agent/orchestrator/providers/anthropic-api-request.mjs +18 -5
  56. package/src/runtime/agent/orchestrator/providers/anthropic-cache-warm.mjs +165 -0
  57. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-client-version.mjs +1 -1
  58. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-credentials.mjs +4 -1
  59. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-request/gzip-post.mjs +14 -14
  60. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +160 -69
  61. package/src/runtime/agent/orchestrator/providers/anthropic-overload-fallback.mjs +25 -3
  62. package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +64 -33
  63. package/src/runtime/agent/orchestrator/providers/anthropic.mjs +135 -87
  64. package/src/runtime/agent/orchestrator/providers/antigravity-oauth-catalog.mjs +52 -8
  65. package/src/runtime/agent/orchestrator/providers/antigravity-oauth.mjs +10 -1
  66. package/src/runtime/agent/orchestrator/providers/antigravity-request.mjs +22 -6
  67. package/src/runtime/agent/orchestrator/providers/antigravity-response.mjs +15 -4
  68. package/src/runtime/agent/orchestrator/providers/antigravity-stream.mjs +17 -2
  69. package/src/runtime/agent/orchestrator/providers/antigravity-transport.mjs +142 -104
  70. package/src/runtime/agent/orchestrator/providers/cache-warm-admission.mjs +90 -0
  71. package/src/runtime/agent/orchestrator/providers/codex-client-meta.mjs +1 -1
  72. package/src/runtime/agent/orchestrator/providers/cursor-wire-normalization.mjs +10 -4
  73. package/src/runtime/agent/orchestrator/providers/cursor-wire-protobuf.mjs +10 -217
  74. package/src/runtime/agent/orchestrator/providers/cursor-wire-transport.mjs +25 -9
  75. package/src/runtime/agent/orchestrator/providers/cursor.mjs +22 -6
  76. package/src/runtime/agent/orchestrator/providers/devin-auth.mjs +409 -0
  77. package/src/runtime/agent/orchestrator/providers/devin-wire-families.mjs +187 -0
  78. package/src/runtime/agent/orchestrator/providers/devin-wire-identity.mjs +342 -0
  79. package/src/runtime/agent/orchestrator/providers/devin-wire-models.mjs +541 -0
  80. package/src/runtime/agent/orchestrator/providers/devin-wire-protobuf.mjs +305 -0
  81. package/src/runtime/agent/orchestrator/providers/devin-wire-request.mjs +558 -0
  82. package/src/runtime/agent/orchestrator/providers/devin-wire-schema.mjs +880 -0
  83. package/src/runtime/agent/orchestrator/providers/devin-wire-session.mjs +190 -0
  84. package/src/runtime/agent/orchestrator/providers/devin-wire-tool-args.mjs +351 -0
  85. package/src/runtime/agent/orchestrator/providers/devin-wire-transport.mjs +560 -0
  86. package/src/runtime/agent/orchestrator/providers/devin.mjs +491 -0
  87. package/src/runtime/agent/orchestrator/providers/effort-configuration.mjs +3 -0
  88. package/src/runtime/agent/orchestrator/providers/gemini-output-limit.mjs +46 -0
  89. package/src/runtime/agent/orchestrator/providers/gemini-reasoning-relay.mjs +30 -0
  90. package/src/runtime/agent/orchestrator/providers/gemini-request-body.mjs +7 -11
  91. package/src/runtime/agent/orchestrator/providers/gemini-retry-policy.mjs +61 -22
  92. package/src/runtime/agent/orchestrator/providers/gemini-sdk-request.mjs +1 -0
  93. package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +71 -4
  94. package/src/runtime/agent/orchestrator/providers/gemini.mjs +25 -2
  95. package/src/runtime/agent/orchestrator/providers/grok-client-version.mjs +1 -1
  96. package/src/runtime/agent/orchestrator/providers/lib/anthropic-request-utils.mjs +96 -5
  97. package/src/runtime/agent/orchestrator/providers/lib/oauth-token-utils.mjs +17 -3
  98. package/src/runtime/agent/orchestrator/providers/lib/tail-reminder-split.mjs +23 -5
  99. package/src/runtime/agent/orchestrator/providers/media-normalization.mjs +52 -10
  100. package/src/runtime/agent/orchestrator/providers/model-proxy-channel.d.mts +8 -1
  101. package/src/runtime/agent/orchestrator/providers/model-proxy-channel.mjs +1 -0
  102. package/src/runtime/agent/orchestrator/providers/npm-cli-version.mjs +11 -3
  103. package/src/runtime/agent/orchestrator/providers/oauth-credential-probes.mjs +19 -1
  104. package/src/runtime/agent/orchestrator/providers/oauth-usage.mjs +39 -3
  105. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/stream-state.mjs +3 -1
  106. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream.mjs +2 -0
  107. package/src/runtime/agent/orchestrator/providers/openai-error-contract.mjs +6 -1
  108. package/src/runtime/agent/orchestrator/providers/openai-oauth-tokens.mjs +10 -9
  109. package/src/runtime/agent/orchestrator/providers/openai-ws-send/send-context.mjs +22 -3
  110. package/src/runtime/agent/orchestrator/providers/openai-ws-send-attempts/attempt-context.mjs +43 -5
  111. package/src/runtime/agent/orchestrator/providers/openai-ws-send-attempts/handshake-failure.mjs +3 -1
  112. package/src/runtime/agent/orchestrator/providers/openai-ws-send-attempts/stream-failure.mjs +6 -3
  113. package/src/runtime/agent/orchestrator/providers/opencode-go-gemini.mjs +1 -0
  114. package/src/runtime/agent/orchestrator/providers/provider-availability.mjs +19 -0
  115. package/src/runtime/agent/orchestrator/providers/provider-error-kind.mjs +65 -1
  116. package/src/runtime/agent/orchestrator/providers/quota-reading.mjs +38 -12
  117. package/src/runtime/agent/orchestrator/providers/registry.mjs +43 -3
  118. package/src/runtime/agent/orchestrator/providers/retry-executor.mjs +39 -7
  119. package/src/runtime/agent/orchestrator/providers/statusline-route-meta.mjs +6 -0
  120. package/src/runtime/agent/orchestrator/providers/wire-protobuf-codec.mjs +368 -0
  121. package/src/runtime/agent/orchestrator/runtime-core/ask-user-tool-defs.mjs +12 -7
  122. package/src/runtime/agent/orchestrator/runtime-core/builtin-features.mjs +2 -0
  123. package/src/runtime/agent/orchestrator/runtime-core/compaction-config-keys.mjs +62 -0
  124. package/src/runtime/agent/orchestrator/runtime-core/config-helpers.mjs +37 -16
  125. package/src/runtime/agent/orchestrator/runtime-core/deferred-tool-delta.mjs +100 -23
  126. package/src/runtime/agent/orchestrator/runtime-core/orchestration.mjs +1 -1
  127. package/src/runtime/agent/orchestrator/runtime-core/reaction-tool-defs.mjs +133 -0
  128. package/src/runtime/agent/orchestrator/runtime-core/session-text.mjs +2 -0
  129. package/src/runtime/agent/orchestrator/runtime-core/tool-bridge.mjs +19 -4
  130. package/src/runtime/agent/orchestrator/runtime-core/tool-catalog-data.mjs +3 -0
  131. package/src/runtime/agent/orchestrator/runtime-core/tool-catalog.mjs +125 -30
  132. package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +23 -5
  133. package/src/runtime/agent/orchestrator/runtime-core/tool-profile.mjs +35 -10
  134. package/src/runtime/agent/orchestrator/runtime-core/workflow/agents.mjs +57 -10
  135. package/src/runtime/agent/orchestrator/runtime-core/workflow/context.mjs +9 -6
  136. package/src/runtime/agent/orchestrator/runtime-core/workflow-ids.mjs +1 -1
  137. package/src/runtime/agent/orchestrator/session/agent-loop.mjs +18 -0
  138. package/src/runtime/agent/orchestrator/session/agent-room.d.mts +0 -3
  139. package/src/runtime/agent/orchestrator/session/agent-room.mjs +10 -4
  140. package/src/runtime/agent/orchestrator/session/cache-warm.mjs +792 -0
  141. package/src/runtime/agent/orchestrator/session/compact/runner.mjs +148 -25
  142. package/src/runtime/agent/orchestrator/session/compaction-archive-capture.mjs +475 -58
  143. package/src/runtime/agent/orchestrator/session/compaction-archive.mjs +42 -17
  144. package/src/runtime/agent/orchestrator/session/eager-dispatch.mjs +8 -0
  145. package/src/runtime/agent/orchestrator/session/human-gate/gate.mjs +47 -9
  146. package/src/runtime/agent/orchestrator/session/human-gate/outbound-policy.mjs +6 -2
  147. package/src/runtime/agent/orchestrator/session/human-gate/requests.mjs +36 -4
  148. package/src/runtime/agent/orchestrator/session/human-gate/store.mjs +14 -2
  149. package/src/runtime/agent/orchestrator/session/loop/assistant-commit.mjs +8 -0
  150. package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +18 -22
  151. package/src/runtime/agent/orchestrator/session/loop/fresh-context.mjs +32 -3
  152. package/src/runtime/agent/orchestrator/session/loop/loop-state.mjs +11 -2
  153. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/segments.mjs +4 -1
  154. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn.mjs +51 -3
  155. package/src/runtime/agent/orchestrator/session/loop/route-fallback.mjs +17 -3
  156. package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +10 -2
  157. package/src/runtime/agent/orchestrator/session/loop/tool-exec/after-hook.mjs +2 -1
  158. package/src/runtime/agent/orchestrator/session/loop/tool-exec/before-hook.mjs +14 -4
  159. package/src/runtime/agent/orchestrator/session/loop/tool-exec/dispatch.mjs +16 -2
  160. package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +16 -1
  161. package/src/runtime/agent/orchestrator/session/loop/tool-exposure-migration.mjs +10 -0
  162. package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +3 -10
  163. package/src/runtime/agent/orchestrator/session/loop/tool-phase.mjs +23 -5
  164. package/src/runtime/agent/orchestrator/session/loop/turn-limits.mjs +60 -0
  165. package/src/runtime/agent/orchestrator/session/manager/ask-queue.mjs +12 -2
  166. package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +19 -1
  167. package/src/runtime/agent/orchestrator/session/manager/ask-turn-commit.mjs +2 -0
  168. package/src/runtime/agent/orchestrator/session/manager/ask-turn-execute.mjs +133 -90
  169. package/src/runtime/agent/orchestrator/session/manager/ask-turn-loop-options.mjs +14 -0
  170. package/src/runtime/agent/orchestrator/session/manager/ask-turn-settle.mjs +2 -1
  171. package/src/runtime/agent/orchestrator/session/manager/ask-turn-state.mjs +31 -4
  172. package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +30 -18
  173. package/src/runtime/agent/orchestrator/session/manager/owner-route-fallbacks.mjs +23 -13
  174. package/src/runtime/agent/orchestrator/session/manager/pending-message-entry.mjs +11 -2
  175. package/src/runtime/agent/orchestrator/session/manager/provider-replay.mjs +13 -0
  176. package/src/runtime/agent/orchestrator/session/manager/route-fallback-chain.mjs +129 -0
  177. package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +20 -2
  178. package/src/runtime/agent/orchestrator/session/manager/session-close.mjs +2 -0
  179. package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +8 -0
  180. package/src/runtime/agent/orchestrator/session/manager/session-record.mjs +1 -1
  181. package/src/runtime/agent/orchestrator/session/manager/session-tool-surface.mjs +13 -0
  182. package/src/runtime/agent/orchestrator/session/manager/tool-resolution.mjs +7 -2
  183. package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint-journal.mjs +102 -15
  184. package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint.mjs +18 -5
  185. package/src/runtime/agent/orchestrator/session/message-ids.mjs +5 -3
  186. package/src/runtime/agent/orchestrator/session/message-reactions.mjs +89 -0
  187. package/src/runtime/agent/orchestrator/session/message-reactions.test-support.mjs +75 -0
  188. package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +19 -8
  189. package/src/runtime/agent/orchestrator/session/prompt-cache-idle-gaps.mjs +113 -0
  190. package/src/runtime/agent/orchestrator/session/prompt-cache-touch-store.mjs +127 -0
  191. package/src/runtime/agent/orchestrator/session/routine-result.d.mts +0 -2
  192. package/src/runtime/agent/orchestrator/session/routine-result.mjs +47 -11
  193. package/src/runtime/agent/orchestrator/session/send-with-recovery.mjs +20 -2
  194. package/src/runtime/agent/orchestrator/session/sensitive-projection.mjs +100 -0
  195. package/src/runtime/agent/orchestrator/session/sensitive-values.mjs +257 -0
  196. package/src/runtime/agent/orchestrator/session/side-question-store.mjs +20 -7
  197. package/src/runtime/agent/orchestrator/session/side-question.mjs +18 -0
  198. package/src/runtime/agent/orchestrator/session/skill-update-notice.mjs +60 -0
  199. package/src/runtime/agent/orchestrator/session/store/live-state.mjs +6 -3
  200. package/src/runtime/agent/orchestrator/session/store/save-delta.mjs +9 -5
  201. package/src/runtime/agent/orchestrator/session/store/serialize.mjs +9 -8
  202. package/src/runtime/agent/orchestrator/session/store/session-persistence-events.mjs +29 -0
  203. package/src/runtime/agent/orchestrator/session/store-agent-worker-header-cache.mjs +85 -0
  204. package/src/runtime/agent/orchestrator/session/store-agent-worker-rows.mjs +133 -18
  205. package/src/runtime/agent/orchestrator/session/store-summary-catalog.mjs +12 -2
  206. package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +44 -14
  207. package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +84 -3
  208. package/src/runtime/agent/orchestrator/session/stored-attachment-history.mjs +32 -0
  209. package/src/runtime/agent/orchestrator/session/tail-reminder-rules.mjs +11 -5
  210. package/src/runtime/agent/orchestrator/session/thinking-binding.mjs +108 -0
  211. package/src/runtime/agent/orchestrator/session/tool-batch/pre-dispatch.mjs +2 -2
  212. package/src/runtime/agent/orchestrator/session/tool-batch/record-outcome.mjs +2 -1
  213. package/src/runtime/agent/orchestrator/session/tool-batch/state.mjs +4 -0
  214. package/src/runtime/agent/orchestrator/session/tool-batch.mjs +5 -0
  215. package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +25 -2
  216. package/src/runtime/agent/orchestrator/session/transcript-restore/tool-result-status.mjs +9 -7
  217. package/src/runtime/agent/orchestrator/session/transcript-restore/tool-result-text.mjs +2 -2
  218. package/src/runtime/agent/orchestrator/session/turn-failure.mjs +5 -1
  219. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +5 -2
  220. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/backgrounded-result.mjs +12 -1
  221. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/misuse-guard.mjs +240 -0
  222. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/result-format.mjs +1 -1
  223. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/shell-cwd.mjs +38 -0
  224. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +25 -5
  225. package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +65 -7
  226. package/src/runtime/agent/orchestrator/tools/builtin/shell-runtime.mjs +7 -1
  227. package/src/runtime/agent/orchestrator/tools/shell-command.mjs +36 -13
  228. package/src/runtime/browser-bridge/action-schema.mjs +13 -2
  229. package/src/runtime/browser-bridge/client.mjs +73 -8
  230. package/src/runtime/browser-bridge/input-fields.mjs +8 -9
  231. package/src/runtime/browser-bridge/mail-links.mjs +107 -0
  232. package/src/runtime/browser-bridge/tool-defs.mjs +1 -1
  233. package/src/runtime/channels/lib/config.mjs +6 -61
  234. package/src/runtime/channels/lib/crash-log.mjs +1 -1
  235. package/src/runtime/channels/lib/event-pipeline.mjs +4 -19
  236. package/src/runtime/channels/lib/event-queue.mjs +3 -18
  237. package/src/runtime/channels/lib/owned-runtime/automation-services.mjs +2 -2
  238. package/src/runtime/channels/lib/owned-runtime/automation-start.mjs +3 -4
  239. package/src/runtime/channels/lib/owned-runtime/config-reload.mjs +4 -59
  240. package/src/runtime/channels/lib/owned-runtime/owned-start.mjs +33 -115
  241. package/src/runtime/channels/lib/owned-runtime/owned-stop.mjs +5 -22
  242. package/src/runtime/channels/lib/owned-runtime/state.mjs +2 -8
  243. package/src/runtime/channels/lib/owned-runtime.mjs +6 -23
  244. package/src/runtime/channels/lib/parent-bridge.mjs +2 -2
  245. package/src/runtime/channels/lib/runtime-paths.mjs +4 -43
  246. package/src/runtime/channels/lib/schedule-errors.mjs +51 -0
  247. package/src/runtime/channels/lib/scheduler.mjs +135 -154
  248. package/src/runtime/channels/lib/schedules-db.mjs +83 -11
  249. package/src/runtime/channels/lib/status-snapshot.mjs +1 -1
  250. package/src/runtime/channels/lib/tool-dispatch.mjs +14 -2
  251. package/src/runtime/channels/lib/webhooks-db.mjs +5 -9
  252. package/src/runtime/channels/lib/worker-bootstrap.mjs +0 -1
  253. package/src/runtime/channels/lib/worker-ipc.mjs +0 -1
  254. package/src/runtime/channels/lib/worker-main.mjs +19 -34
  255. package/src/runtime/computer-bridge/action-schema.mjs +42 -1
  256. package/src/runtime/computer-bridge/actions.mjs +9 -1
  257. package/src/runtime/computer-bridge/client.mjs +180 -8
  258. package/src/runtime/computer-bridge/core-actions.mjs +2 -2
  259. package/src/runtime/computer-bridge/error-recovery.mjs +27 -2
  260. package/src/runtime/computer-bridge/linux-readiness.mjs +65 -23
  261. package/src/runtime/computer-bridge/pending-continuation.mjs +1 -1
  262. package/src/runtime/connectors/connector-accounts-mcp.mjs +230 -0
  263. package/src/runtime/connectors/gmail-app-password.mjs +172 -39
  264. package/src/runtime/connectors/google-mcp.mjs +10 -12
  265. package/src/runtime/memory/lib/pg/adapter.mjs +77 -70
  266. package/src/runtime/memory/lib/pg/supervisor.mjs +24 -12
  267. package/src/runtime/memory/lib/runtime-fetcher.mjs +23 -5
  268. package/src/runtime/office/com/com-adapter.mjs +17 -1
  269. package/src/runtime/office/com/office-com-host.ps1 +51 -1
  270. package/src/runtime/office/pdf/cfb-fixture.mjs +328 -0
  271. package/src/runtime/office/pdf/cfb-protection.mjs +311 -0
  272. package/src/runtime/office/pdf/document-preview.mjs +326 -8
  273. package/src/runtime/office/pdf/ooxml-crypto.mjs +469 -0
  274. package/src/runtime/office/pdf/ooxml-key-worker.mjs +14 -0
  275. package/src/runtime/office/portable/portable-soffice.mjs +45 -5
  276. package/src/runtime/session-search/session-search-db.mjs +73 -9
  277. package/src/runtime/session-search/session-search-ingest.mjs +110 -20
  278. package/src/runtime/shared/abort-race.mjs +89 -0
  279. package/src/runtime/shared/agent-avatar.d.mts +0 -1
  280. package/src/runtime/shared/agent-kind.mjs +4 -3
  281. package/src/runtime/shared/bot-hops.mjs +62 -2
  282. package/src/runtime/shared/bounded-download.mjs +8 -3
  283. package/src/runtime/shared/config.mjs +51 -35
  284. package/src/runtime/shared/connector-accounts.mjs +20 -10
  285. package/src/runtime/shared/connector-catalog.d.mts +33 -2
  286. package/src/runtime/shared/connector-catalog.mjs +166 -8
  287. package/src/runtime/shared/connector-triggers.mjs +22 -19
  288. package/src/runtime/shared/defaults-separation.mjs +2 -14
  289. package/src/runtime/shared/delegated-approval-host.mjs +103 -11
  290. package/src/runtime/shared/developer-options.mjs +32 -1
  291. package/src/runtime/shared/err-text.mjs +24 -15
  292. package/src/runtime/shared/file-permissions.d.mts +0 -8
  293. package/src/runtime/shared/file-permissions.mjs +0 -11
  294. package/src/runtime/shared/git-direct.test-support.mjs +19 -0
  295. package/src/runtime/shared/google-oauth-client.generated.json +4 -0
  296. package/src/runtime/shared/google-oauth-client.mjs +34 -9
  297. package/src/runtime/shared/heartbeat-interval.d.mts +4 -0
  298. package/src/runtime/shared/heartbeat-interval.mjs +19 -0
  299. package/src/runtime/shared/idle-auto-clear-gate.mjs +104 -0
  300. package/src/runtime/shared/llm/anthropic-betas.mjs +7 -0
  301. package/src/runtime/shared/llm/anthropic-thinking-contract.mjs +12 -0
  302. package/src/runtime/shared/llm/provider-catalog-registry.mjs +1 -0
  303. package/src/runtime/shared/llm/usage-rollup.mjs +1 -1
  304. package/src/runtime/shared/llm/well-formed.mjs +66 -0
  305. package/src/runtime/shared/mixy-presence.mjs +22 -0
  306. package/src/runtime/shared/orchestration.d.mts +2 -0
  307. package/src/runtime/shared/pg-schema-db.mjs +27 -16
  308. package/src/runtime/shared/profile-config.cjs +1 -1
  309. package/src/runtime/shared/prompt-origin.d.mts +4 -0
  310. package/src/runtime/shared/prompt-origin.mjs +21 -0
  311. package/src/runtime/shared/provider-accounts.mjs +231 -59
  312. package/src/runtime/shared/provider-api-key.mjs +19 -8
  313. package/src/runtime/shared/provider-display-name.mjs +29 -0
  314. package/src/runtime/shared/provider-timeout-phase.mjs +2 -0
  315. package/src/runtime/shared/routine-trigger.d.mts +2 -0
  316. package/src/runtime/shared/routine-trigger.mjs +52 -0
  317. package/src/runtime/shared/sensitive-path-rules.d.mts +2 -10
  318. package/src/runtime/shared/sensitive-path-rules.mjs +19 -41
  319. package/src/runtime/shared/session-activity-signal.mjs +34 -1
  320. package/src/runtime/shared/session-sidebar-host.d.mts +1 -1
  321. package/src/runtime/shared/session-sidebar-host.mjs +7 -3
  322. package/src/runtime/shared/session-turn-prelude.mjs +18 -0
  323. package/src/runtime/shared/tool-execution-contract.mjs +2 -0
  324. package/src/runtime/shared/tool-result-summary.mjs +1 -14
  325. package/src/runtime/shared/transcript-metadata.mjs +38 -0
  326. package/src/runtime/shared/user-data-guard.mjs +30 -2
  327. package/src/runtime/shared/worktrees/activity-gate.mjs +12 -5
  328. package/src/runtime/shared/worktrees/test-support.mjs +9 -4
  329. package/src/runtime/terminal-bridge/client.mjs +10 -1
  330. package/src/runtime/visualize-bridge/client.mjs +27 -8
  331. package/src/runtime/visualize-bridge/tool-defs.mjs +22 -8
  332. package/src/runtime/visualize-bridge/tool-meta.d.mts +5 -0
  333. package/src/runtime/visualize-bridge/tool-meta.mjs +22 -0
  334. package/src/runtime/web-search/lib/fetch-pipeline.mjs +2 -2
  335. package/src/session-runtime/agent-home-delivery.mjs +486 -179
  336. package/src/session-runtime/agent-room-delivery.mjs +28 -1
  337. package/src/session-runtime/agent-task-sessions.mjs +561 -199
  338. package/src/session-runtime/boot/apis.mjs +9 -0
  339. package/src/session-runtime/boot/begin.mjs +3 -2
  340. package/src/session-runtime/boot/infrastructure.mjs +4 -0
  341. package/src/session-runtime/boot/providers.mjs +3 -0
  342. package/src/session-runtime/boot/tools.mjs +8 -1
  343. package/src/session-runtime/bot-chain.mjs +32 -0
  344. package/src/session-runtime/bot-status.mjs +7 -4
  345. package/src/session-runtime/channel-config-api.mjs +63 -17
  346. package/src/session-runtime/config-lifecycle.mjs +6 -0
  347. package/src/session-runtime/connector-merge-migration.mjs +305 -0
  348. package/src/session-runtime/internal-tool-executor/ask-user-tool.mjs +132 -28
  349. package/src/session-runtime/internal-tool-executor/bridge-tools.mjs +2 -0
  350. package/src/session-runtime/internal-tool-executor/reaction-tool.mjs +45 -0
  351. package/src/session-runtime/internal-tool-executor/workspace-tools.mjs +72 -5
  352. package/src/session-runtime/internal-tool-executor.mjs +4 -0
  353. package/src/session-runtime/lifecycle/inheritance.mjs +16 -3
  354. package/src/session-runtime/lifecycle/session-catalog.mjs +3 -1
  355. package/src/session-runtime/lifecycle/teardown/runtime-stops.mjs +50 -4
  356. package/src/session-runtime/lifecycle/teardown.mjs +23 -5
  357. package/src/session-runtime/mcp-glue/server-catalog.mjs +61 -3
  358. package/src/session-runtime/mcp-oauth-login.mjs +5 -3
  359. package/src/session-runtime/mixy-heartbeat.mjs +927 -0
  360. package/src/session-runtime/model-route/route-tuning.mjs +10 -6
  361. package/src/session-runtime/notification-bus.mjs +31 -117
  362. package/src/session-runtime/notification-completion-queue.mjs +54 -0
  363. package/src/session-runtime/notification-listeners.mjs +76 -0
  364. package/src/session-runtime/owned-automation-run.mjs +35 -13
  365. package/src/session-runtime/prewarm/channel-start.mjs +3 -7
  366. package/src/session-runtime/provider-auth/login.mjs +7 -0
  367. package/src/session-runtime/provider-models/row-hydration.mjs +13 -2
  368. package/src/session-runtime/resource-mcp-api.mjs +143 -224
  369. package/src/session-runtime/resource-mcp-connector-accounts.mjs +163 -0
  370. package/src/session-runtime/resource-mcp-oauth-api.mjs +217 -0
  371. package/src/session-runtime/resource-mcp-secrets.mjs +82 -0
  372. package/src/session-runtime/resource-mcp-servers.mjs +78 -0
  373. package/src/session-runtime/resource-mcp-store.mjs +49 -0
  374. package/src/session-runtime/routine-home-turn.mjs +33 -6
  375. package/src/session-runtime/routine-owner.mjs +40 -9
  376. package/src/session-runtime/schedule-session-run.mjs +7 -3
  377. package/src/session-runtime/services/agent-tool/close-flow.mjs +3 -0
  378. package/src/session-runtime/services/agent-tool/spawn-flow/spawn-plan.mjs +8 -12
  379. package/src/session-runtime/services/agent-tool/spawn-preset.mjs +7 -2
  380. package/src/session-runtime/services/agent-tool/tag-registry.mjs +1 -0
  381. package/src/session-runtime/services/agent-tool/worker-index/awaiting-user-sync.mjs +51 -0
  382. package/src/session-runtime/services/agent-tool/worker-index/disposal.mjs +55 -0
  383. package/src/session-runtime/services/agent-tool/worker-index/released-store.mjs +123 -0
  384. package/src/session-runtime/services/agent-tool/worker-index/restart-settlement.mjs +112 -0
  385. package/src/session-runtime/services/agent-tool/worker-index/row-reads.mjs +36 -0
  386. package/src/session-runtime/services/agent-tool/worker-index/row-shape.mjs +2 -0
  387. package/src/session-runtime/services/agent-tool/worker-index/row-store.mjs +17 -4
  388. package/src/session-runtime/services/agent-tool/worker-index/row-writes.mjs +58 -0
  389. package/src/session-runtime/services/agent-tool/worker-index.mjs +41 -241
  390. package/src/session-runtime/services/agent-tool/worker-rows.mjs +3 -0
  391. package/src/session-runtime/services/channel-admin.mjs +73 -31
  392. package/src/session-runtime/services/channel-session-router.mjs +4 -2
  393. package/src/session-runtime/services/channel-worker.mjs +1 -1
  394. package/src/session-runtime/services/provider-admin.mjs +128 -25
  395. package/src/session-runtime/services/schedule-access.mjs +14 -0
  396. package/src/session-runtime/services/seeds.mjs +0 -28
  397. package/src/session-runtime/services/usage-dashboard-model.mjs +1 -0
  398. package/src/session-runtime/session-compaction-sync.mjs +27 -0
  399. package/src/session-runtime/session-create/session-options.mjs +8 -4
  400. package/src/session-runtime/settings-builtin-tools-api.mjs +11 -0
  401. package/src/session-runtime/settings-compaction-api.mjs +2 -40
  402. package/src/session-runtime/settings-system-api.mjs +28 -1
  403. package/src/session-runtime/setup-tool/agent-settings-actions.mjs +65 -0
  404. package/src/session-runtime/setup-tool/executor.mjs +187 -29
  405. package/src/session-runtime/setup-tool/extended-actions.mjs +33 -2
  406. package/src/session-runtime/setup-tool/session-route-actions.mjs +152 -0
  407. package/src/session-runtime/setup-tool/settings-contract.mjs +68 -6
  408. package/src/session-runtime/setup-tool/tool-defs.mjs +92 -6
  409. package/src/session-runtime/task-session-route.mjs +99 -0
  410. package/src/session-runtime/tool-policy/agent-profile-fold.mjs +17 -13
  411. package/src/session-runtime/tool-policy/policy-stage.mjs +21 -4
  412. package/src/session-runtime/tool-policy/surface-helpers.mjs +2 -3
  413. package/src/session-runtime/tool-surface.mjs +4 -0
  414. package/src/session-runtime/turn/turn-run/ask-callbacks.mjs +1 -0
  415. package/src/session-runtime/workflow-agents-api/agent-editor.mjs +98 -21
  416. package/src/session-runtime/workflow-agents-api/style-and-mode.mjs +4 -0
  417. package/src/session-runtime/workflow-agents-api/workflow-packs.mjs +6 -1
  418. package/src/session-runtime/workspace/transitions.mjs +10 -2
  419. package/src/standalone/channel-transport/client-registry/client-records.mjs +65 -0
  420. package/src/standalone/channel-transport/client-registry/client-registration.mjs +118 -0
  421. package/src/standalone/channel-transport/client-registry.mjs +24 -163
  422. package/src/standalone/channel-transport.mjs +1 -1
  423. package/src/standalone/daemon-channels-loader.mjs +10 -8
  424. package/src/standalone/daemon-session-control/list-page.mjs +72 -0
  425. package/src/standalone/daemon-session-control/request.mjs +51 -0
  426. package/src/standalone/daemon-session-control/room-host.mjs +57 -0
  427. package/src/standalone/daemon-session-control/route-control.mjs +48 -0
  428. package/src/standalone/daemon-session-control/send-turn.mjs +60 -0
  429. package/src/standalone/daemon-session-control/worktree-control.mjs +40 -0
  430. package/src/standalone/daemon-session-control.mjs +161 -157
  431. package/src/standalone/daemon.mjs +33 -2
  432. package/src/standalone/session-client.mjs +7 -3
  433. package/src/standalone/session-protocol.mjs +10 -0
  434. package/src/standalone/session-service/agent-tree/agent-cancel.mjs +6 -2
  435. package/src/standalone/session-service/agent-tree/agent-child.mjs +1 -0
  436. package/src/standalone/session-service/quota-resume.mjs +3 -0
  437. package/src/standalone/session-service/retention/busy.mjs +65 -1
  438. package/src/standalone/session-service/session-calls/session-action.mjs +23 -2
  439. package/src/standalone/session-service/session-calls/session-turns.mjs +6 -0
  440. package/src/standalone/session-service/session-calls/session-views.mjs +8 -0
  441. package/src/standalone/session-service.mjs +13 -0
  442. package/src/standalone/session-tool-output.mjs +21 -10
  443. package/src/tui/app/model-options.mjs +4 -27
  444. package/src/tui/dist/index.mjs +81 -65
  445. package/src/tui/session/agent-job-feed/model-notification.mjs +3 -0
  446. package/src/tui/session/initial-state.mjs +3 -0
  447. package/src/tui/session/notice-surface.mjs +23 -1
  448. package/src/tui/session/queue-helpers.mjs +3 -42
  449. package/src/tui/session/session-api/integrations.mjs +1 -0
  450. package/src/tui/session/session-api/lifecycle.mjs +8 -0
  451. package/src/tui/session/session-api/routes-agent-definitions.mjs +14 -2
  452. package/src/tui/session/session-api/routes-write.mjs +5 -1
  453. package/src/tui/session/session-api/routes.mjs +1 -0
  454. package/src/tui/session/session-api/settings.mjs +30 -2
  455. package/src/tui/session/session-flow/auto-clear.mjs +4 -33
  456. package/src/tui/session/session-flow/drain-closed-queue.mjs +38 -0
  457. package/src/tui/session/session-flow/drain-enqueue.mjs +86 -0
  458. package/src/tui/session/session-flow/drain-retry.mjs +35 -0
  459. package/src/tui/session/session-flow/drain.mjs +118 -149
  460. package/src/tui/session/session-flow/queue/entry-shape.mjs +5 -0
  461. package/src/tui/session/session-flow/queue/take-entries.mjs +5 -2
  462. package/src/tui/session/tool-approval-delegation.mjs +64 -0
  463. package/src/tui/session/tool-approval-request.mjs +39 -0
  464. package/src/tui/session/tool-approval-settle.mjs +62 -0
  465. package/src/tui/session/tool-approval.mjs +69 -115
  466. package/src/tui/session/tui-steering-persist.mjs +8 -1
  467. package/src/tui/session/turn-stream.mjs +36 -3
  468. package/src/tui/session/turn.mjs +23 -2
  469. package/src/vendor/statusline/bin/statusline-route.mjs +1 -219
  470. package/src/vendor/statusline/src/gateway/route-meta.mjs +35 -13
  471. package/scripts/code-graph-description-contract.mjs +0 -188
  472. package/src/runtime/channels/lib/owned-runtime/ownership-refresh.mjs +0 -36
@@ -18,6 +18,7 @@ import {
18
18
  setNextFire,
19
19
  setSkippedUntil,
20
20
  } from './schedules-db.mjs';
21
+ import { scheduleRenameReloadError, scheduleRenameRunningError } from './schedule-errors.mjs';
21
22
  import { appendBuffered } from '../../shared/buffered-appender.mjs';
22
23
  import { tryRead } from './settings.mjs';
23
24
 
@@ -114,72 +115,36 @@ function releaseSchedulerRuntime(scheduler) {
114
115
  releaseOwnedSchedulerLock();
115
116
  }
116
117
  class Scheduler {
117
- nonInteractive;
118
- interactive;
118
+ schedules;
119
119
  promptsDir;
120
120
  tickTimer = null;
121
121
  lastFired = /* @__PURE__ */ new Map();
122
122
  // name -> "YYYY-MM-DDTHH:MM"
123
123
  running = /* @__PURE__ */ new Set();
124
- injectFn = null;
125
- injectReadyFn = null;
126
- pendingCheck = null;
124
+ // Names whose row is being renamed: no fire starts under them meanwhile.
125
+ renaming = /* @__PURE__ */ new Set();
127
126
  // Injected by the host: (schedule, { prompt }) => Promise<{ result }>.
128
127
  scheduleRunner = null;
129
- // Activity tracking
130
- lastActivity = 0;
131
- // timestamp of last inbound message
132
128
  deferred = /* @__PURE__ */ new Map();
133
129
  // name -> deferred-until timestamp
134
130
  cronJobs = /* @__PURE__ */ new Map();
135
131
  // name -> node-cron ScheduledTask for cron-expression entries
136
132
  oneShotTimers = /* @__PURE__ */ new Map();
137
133
  // name -> setTimeout handle for when_at one-shot entries
138
- constructor(nonInteractive, interactive) {
139
- this.nonInteractive = nonInteractive.filter((s) => s.enabled !== false);
140
- this.interactive = interactive.filter((s) => s.enabled !== false);
134
+ constructor(schedules) {
135
+ this.schedules = schedules.filter((s) => s.enabled !== false);
141
136
  this.promptsDir = join(DATA_DIR, 'prompts');
142
- for (const s of [...this.nonInteractive, ...this.interactive]) {
137
+ for (const s of this.schedules) {
143
138
  if (s.lastFiredAt) this.lastFired.set(s.name, new Date(s.lastFiredAt).toISOString());
144
139
  }
145
140
  this.refreshSkipCache();
146
141
  }
147
- setInjectHandler(fn) {
148
- this.injectFn = fn;
149
- }
150
- /** Probe for a live Lead bridge seat. When set, the interactive
151
- * inject only fires while it reports true; otherwise the visible-session
152
- * fallback keeps the fire from being lost. Unset → inject when wired. */
153
- setInjectReadyCheck(fn) {
154
- this.injectReadyFn = typeof fn === 'function' ? fn : null;
155
- }
156
- injectReady() {
157
- if (!this.injectReadyFn) return true;
158
- try {
159
- return !!this.injectReadyFn();
160
- } catch {
161
- return false;
162
- }
163
- }
164
- setPendingCheck(fn) {
165
- this.pendingCheck = typeof fn === 'function' ? fn : null;
166
- }
167
142
  setScheduleRunner(fn) {
168
143
  this.scheduleRunner = typeof fn === 'function' ? fn : null;
169
144
  }
170
- noteActivity() {
171
- this.lastActivity = Date.now();
172
- }
173
- /** Every loaded schedule with its routing type, non-interactive first. */
174
- typedSchedules() {
175
- return [
176
- ...this.nonInteractive.map((schedule) => ({ schedule, type: 'non-interactive' })),
177
- ...this.interactive.map((schedule) => ({ schedule, type: 'interactive' })),
178
- ];
179
- }
180
- /** Find a loaded schedule def by name (either routing bucket). */
145
+ /** Find a loaded schedule def by name. */
181
146
  findSchedule(name) {
182
- return [...this.nonInteractive, ...this.interactive].find((s) => s.name === name) ?? null;
147
+ return this.schedules.find((s) => s.name === name) ?? null;
183
148
  }
184
149
  /** Rebuild the in-memory deferred cache (used by the status snapshot) from
185
150
  * the loaded rows' deferred_until values. Called on construct + reload so
@@ -187,7 +152,7 @@ class Scheduler {
187
152
  refreshSkipCache() {
188
153
  this.deferred.clear();
189
154
  const now = Date.now();
190
- for (const s of [...this.nonInteractive, ...this.interactive]) {
155
+ for (const s of this.schedules) {
191
156
  const until = s.deferredUntil ? new Date(s.deferredUntil).getTime() : 0;
192
157
  if (until > now) this.deferred.set(s.name, until);
193
158
  }
@@ -234,56 +199,6 @@ class Scheduler {
234
199
  if (skippedUntil && now < skippedUntil) until = Math.max(until, skippedUntil);
235
200
  return until;
236
201
  }
237
- /** Get current session activity state.
238
- * Returns { lastActivityMs, pendingWork } — callers apply their own
239
- * thresholds. pendingWork is true when pendingCheck() reports work in
240
- * flight. lastActivityMs is 0 when no activity has been recorded. */
241
- getSessionState() {
242
- let pendingWork = false;
243
- try {
244
- if (this.pendingCheck) pendingWork = !!this.pendingCheck();
245
- } catch {
246
- /* probe failure is not fatal */
247
- }
248
- return { lastActivityMs: this.lastActivity, pendingWork };
249
- }
250
- /** Returns true when the session is considered idle (no pending work and
251
- * lastActivityMs is either 0 or older than the given threshold).
252
- * threshold defaults to 15 minutes but callers should pass their own. */
253
- isSessionIdle(thresholdMs = 15 * 6e4) {
254
- const { lastActivityMs, pendingWork } = this.getSessionState();
255
- if (pendingWork) return false;
256
- if (lastActivityMs === 0) return true;
257
- return Date.now() - lastActivityMs >= thresholdMs;
258
- }
259
- /** Get time context for prompt enrichment */
260
- getTimeContext() {
261
- const now = /* @__PURE__ */ new Date();
262
- const days = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
263
- const dow = now.getDay();
264
- return {
265
- hour: now.getHours(),
266
- minute: now.getMinutes(),
267
- dayOfWeek: days[dow],
268
- isWeekend: dow === 0 || dow === 6,
269
- };
270
- }
271
- /** Wrap prompt with session context metadata */
272
- wrapPrompt(name, prompt, type) {
273
- const { lastActivityMs, pendingWork } = this.getSessionState();
274
- let state = 'recent';
275
- if (pendingWork) state = 'active';
276
- else if (lastActivityMs === 0) state = 'idle';
277
- const time = this.getTimeContext();
278
- const header = [
279
- `[schedule: ${name} | type: ${type} | session: ${state}]`,
280
- `[time: ${time.dayOfWeek} ${String(time.hour).padStart(2, '0')}:${String(time.minute).padStart(2, '0')} | weekend: ${time.isWeekend}]`,
281
- `Before starting any work, briefly tell the user what you're about to do in one short sentence.`,
282
- ].join('\n');
283
- return `${header}
284
-
285
- ${prompt}`;
286
- }
287
202
  static SCHEDULER_LOCK = join(resolveRuntimeRoot(), 'scheduler.lock');
288
203
  static INSTANCE_UUID = randomUUID();
289
204
  static _exitHookInstalled = false;
@@ -342,8 +257,7 @@ ${prompt}`;
342
257
 
343
258
  start() {
344
259
  if (this.tickTimer) return;
345
- const total = this.nonInteractive.length + this.interactive.length;
346
- if (total === 0) {
260
+ if (this.schedules.length === 0) {
347
261
  process.stderr.write('mixdog scheduler: no schedules configured\n');
348
262
  return;
349
263
  }
@@ -362,7 +276,7 @@ ${prompt}`;
362
276
  Scheduler._exitHookInstalled = true;
363
277
  process.on('exit', releaseOwnedSchedulerLock);
364
278
  }
365
- logSchedule(`${this.nonInteractive.length} non-interactive, ${this.interactive.length} interactive\n`);
279
+ logSchedule(`${this.schedules.length} schedules\n`);
366
280
  this.registerCronJobs();
367
281
  this.tick();
368
282
  this.tickTimer = setInterval(() => this.tick(), TICK_INTERVAL);
@@ -370,14 +284,14 @@ ${prompt}`;
370
284
  /** Register schedule entries. `when_cron` entries bind to node-cron (dow
371
285
  * field covers the day guard); `when_at` entries arm a one-shot timer. */
372
286
  registerCronJobs() {
373
- for (const { schedule: s, type } of this.typedSchedules()) {
287
+ for (const s of this.schedules) {
374
288
  if (s.whenCron) {
375
289
  if (!isCronExpression(s.whenCron)) {
376
290
  process.stderr.write(`mixdog scheduler: invalid cron "${s.name}" (${s.whenCron}) — skipped\n`);
377
291
  continue;
378
292
  }
379
293
  try {
380
- const task = cron.schedule(s.whenCron, (ctx) => this.onCronFire(s, type, ctx?.date), {
294
+ const task = cron.schedule(s.whenCron, (ctx) => this.onCronFire(s, ctx?.date), {
381
295
  timezone: s.timezone || undefined,
382
296
  name: s.name,
383
297
  });
@@ -394,13 +308,13 @@ ${prompt}`;
394
308
  process.stderr.write(`mixdog scheduler: failed to register cron "${s.name}" (${s.whenCron}): ${err}\n`);
395
309
  }
396
310
  } else if (s.whenAt) {
397
- this.armOneShot(s, type);
311
+ this.armOneShot(s);
398
312
  }
399
313
  }
400
314
  }
401
315
  /** Fire path for a cron-triggered entry. node-cron's dow field covers the
402
316
  * day guard, so there is no separate days filter. Persists last_fired_at. */
403
- async onCronFire(schedule, type, scheduledDate = null) {
317
+ async onCronFire(schedule, scheduledDate = null) {
404
318
  const startedAt = /* @__PURE__ */ new Date();
405
319
  const scheduledAt =
406
320
  scheduledDate instanceof Date && Number.isFinite(scheduledDate.getTime()) ? scheduledDate : startedAt;
@@ -415,6 +329,12 @@ ${prompt}`;
415
329
  }
416
330
  return;
417
331
  }
332
+ // The name is held in `running` from before the claim until the completion
333
+ // stamps are written, so a rename can neither complete meanwhile nor leave
334
+ // this fire to run under the old name. A fire refused here still claims its
335
+ // slot and is recorded as a skip, never run.
336
+ const refusal = this.fireRefusal(schedule.name);
337
+ if (!refusal) this.running.add(schedule.name);
418
338
  try {
419
339
  const claimed = await claimScheduleRun(schedule.name, scheduledAt, startedAt, nextFireAt);
420
340
  if (!claimed) {
@@ -425,7 +345,11 @@ ${prompt}`;
425
345
  schedule.lastStartedAt = startedAt.toISOString();
426
346
  schedule.lastFiredAt = startedAt.toISOString();
427
347
  this.lastFired.set(schedule.name, startedAt.toISOString());
428
- const fired = await this.fireTimed(schedule, type, { awaitDispatch: true });
348
+ if (refusal) {
349
+ this.recordRefusal(schedule, refusal);
350
+ return;
351
+ }
352
+ const fired = await this.fireTimed(schedule, { awaitDispatch: true, reserved: true });
429
353
  // An overlap skip is recorded as a skip, never as a failed run.
430
354
  if (!fired && skippedSince(schedule, startedAt)) return;
431
355
  if (!fired) {
@@ -443,6 +367,8 @@ ${prompt}`;
443
367
  } catch {}
444
368
  process.stderr.write(`mixdog scheduler: ${schedule.name} failed: ${err}\n`);
445
369
  this.notifyFailure(schedule, `run failed: ${err?.message || err}`);
370
+ } finally {
371
+ if (!refusal) this.running.delete(schedule.name);
446
372
  }
447
373
  }
448
374
  stop() {
@@ -466,7 +392,7 @@ ${prompt}`;
466
392
  * entries (misfire recovery at start) fire immediately. setTimeout's
467
393
  * ~24.8-day (2^31-1 ms) ceiling is handled by re-arming for far-future
468
394
  * instants instead of firing early. */
469
- armOneShot(schedule, type) {
395
+ armOneShot(schedule) {
470
396
  const fireAt = new Date(schedule.whenAt).getTime();
471
397
  if (!Number.isFinite(fireAt)) {
472
398
  process.stderr.write(`mixdog scheduler: invalid when_at for "${schedule.name}" (${schedule.whenAt}) — skipped\n`);
@@ -474,11 +400,11 @@ ${prompt}`;
474
400
  }
475
401
  const delay = fireAt - Date.now();
476
402
  if (delay > MAX_TIMER_DELAY_MS) {
477
- const timer = setTimeout(() => this.armOneShot(schedule, type), MAX_TIMER_DELAY_MS);
403
+ const timer = setTimeout(() => this.armOneShot(schedule), MAX_TIMER_DELAY_MS);
478
404
  this.oneShotTimers.set(schedule.name, timer);
479
405
  return;
480
406
  }
481
- const timer = setTimeout(() => this.fireOneShot(schedule, type), Math.max(0, delay));
407
+ const timer = setTimeout(() => this.fireOneShot(schedule), Math.max(0, delay));
482
408
  this.oneShotTimers.set(schedule.name, timer);
483
409
  schedule.nextFireAt = new Date(fireAt).toISOString();
484
410
  void setNextFire(schedule.name, new Date(fireAt)).catch(() => {});
@@ -506,7 +432,7 @@ ${prompt}`;
506
432
  * Idempotent: if last_fired_at is already set (a prior fire whose markDone
507
433
  * failed left the row 'active'), do NOT fire again — only retry markDone so
508
434
  * the stuck-active entry stops re-arming/misfiring. */
509
- async fireOneShot(schedule, type) {
435
+ async fireOneShot(schedule) {
510
436
  this.oneShotTimers.delete(schedule.name);
511
437
  const now = /* @__PURE__ */ new Date();
512
438
  if (schedule.lastFiredAt) {
@@ -515,18 +441,23 @@ ${prompt}`;
515
441
  return;
516
442
  }
517
443
  if (this.shouldSkip(schedule.name)) {
518
- this.rearmDeferredOneShot(schedule, type);
444
+ this.rearmDeferredOneShot(schedule);
519
445
  return;
520
446
  }
521
447
  // Mark done ONLY after a successful fire. A false/throwing fireTimed
522
448
  // (missing prompt, running guard, paused owner, dispatch error) must leave the
523
449
  // one-shot pending so a reload/restart can retry it — never retire it.
450
+ // The name stays in `running` until the fired/done stamps are written, so
451
+ // a rename cannot move the row before they land (onCronFire). A refused
452
+ // fire is left to fireTimedPrompt, which records the skip.
453
+ const reserved = !this.fireRefusal(schedule.name);
454
+ if (reserved) this.running.add(schedule.name);
524
455
  try {
525
456
  // awaitDispatch: for a one-shot we must wait for the actual LLM/relay
526
457
  // dispatch outcome — a truthy fireTimed alone (cron's fire-and-forget
527
458
  // contract) would retire the entry even when schedulerLlm() later
528
459
  // rejects. Only a resolved dispatch counts as a real fire here.
529
- const fired = await this.fireTimed(schedule, type, { awaitDispatch: true });
460
+ const fired = await this.fireTimed(schedule, { awaitDispatch: true, reserved });
530
461
  if (!fired && skippedSince(schedule, now)) {
531
462
  logSchedule(`one-shot "${schedule.name}" skipped (previous run in progress) — leaving pending for retry\n`);
532
463
  return;
@@ -542,6 +473,8 @@ ${prompt}`;
542
473
  await markOneShotFailed(schedule);
543
474
  process.stderr.write(`mixdog scheduler: ${schedule.name} one-shot failed: ${err} — leaving pending for retry\n`);
544
475
  this.notifyFailure(schedule, `one-shot failed: ${err?.message || err} — left pending for retry`);
476
+ } finally {
477
+ if (reserved) this.running.delete(schedule.name);
545
478
  }
546
479
  }
547
480
  /** when_at one-shots must honor deferred_until / skipped_until just like
@@ -549,13 +482,13 @@ ${prompt}`;
549
482
  * already elapsed and tickAsync is a no-op, so simply returning would mean
550
483
  * it never fires until a restart. Re-arm for the deferral expiry (the
551
484
  * later of when_at and deferred/skipped-until) so it fires on its own. */
552
- rearmDeferredOneShot(schedule, type) {
485
+ rearmDeferredOneShot(schedule) {
553
486
  const until = this.skipUntil(schedule.name);
554
487
  const fireAt = new Date(schedule.whenAt).getTime();
555
488
  const target = Math.max(Number.isFinite(fireAt) ? fireAt : 0, until);
556
489
  const delay = Math.min(Math.max(target - Date.now(), 1), MAX_TIMER_DELAY_MS);
557
490
  logSchedule(`one-shot "${schedule.name}" deferred/skipped — re-arming for ${new Date(target).toISOString()}\n`);
558
- const timer = setTimeout(() => this.fireOneShot(schedule, type), delay);
491
+ const timer = setTimeout(() => this.fireOneShot(schedule), delay);
559
492
  this.oneShotTimers.set(schedule.name, timer);
560
493
  schedule.nextFireAt = new Date(target).toISOString();
561
494
  void setNextFire(schedule.name, new Date(target)).catch(() => {});
@@ -577,10 +510,58 @@ ${prompt}`;
577
510
  releaseSchedulerRuntime(this);
578
511
  this.start();
579
512
  }
580
- reloadConfig(nonInteractive, interactive, options = {}) {
581
- this.nonInteractive = nonInteractive.filter((s) => s.enabled !== false);
582
- this.interactive = interactive.filter((s) => s.enabled !== false);
513
+ /** Run `rename` (which moves schedule `name`'s row) while no run of `name`
514
+ * can start or be in flight. The check and the reservation are one
515
+ * synchronous step, and a fire checks for the reservation (fireRefusal)
516
+ * in the same step that adds the name to `running`, which it holds until
517
+ * its completion stamps are written, so a fire either is already in
518
+ * `running` (refused here) or finds the reservation (not fired). */
519
+ async renameLocked(name, rename, { newName = name, reload = async () => {} } = {}) {
520
+ if (this.running.has(name) || this.renaming.has(name)) throw scheduleRenameRunningError(name);
521
+ this.renaming.add(name);
522
+ this.renameBlocked?.delete(name);
523
+ try {
524
+ const saved = await rename();
525
+ if (!saved) return saved;
526
+ try {
527
+ await reload();
528
+ } catch (error) {
529
+ // The row has moved but the reload that arms the new name failed:
530
+ // nothing may keep running under the old one, and the call reports it.
531
+ this.dropSchedule(name);
532
+ throw scheduleRenameReloadError(newName, error?.message || error);
533
+ }
534
+ return saved;
535
+ } catch (error) {
536
+ // A one-shot whose timer a blocked fire consumed is armed again from the
537
+ // loaded schedules, so it fires once (after a failed rename the old
538
+ // entry is still loaded; after a dropped one only the others re-arm).
539
+ if (this.renameBlocked?.has(name) && this.tickTimer) this.restart();
540
+ throw error;
541
+ } finally {
542
+ this.renaming.delete(name);
543
+ this.renameBlocked?.delete(name);
544
+ }
545
+ }
546
+ /** Forget `name` entirely: its cron job, one-shot timer, stamps and loaded
547
+ * entries. Used when its row moved but the reload that re-arms it failed. */
548
+ dropSchedule(name) {
549
+ try {
550
+ this.cronJobs.get(name)?.destroy();
551
+ } catch {}
552
+ this.cronJobs.delete(name);
553
+ clearTimeout(this.oneShotTimers.get(name));
554
+ this.oneShotTimers.delete(name);
555
+ this.lastFired.delete(name);
556
+ this.deferred.delete(name);
557
+ this.schedules = this.schedules.filter((s) => s.name !== name);
558
+ }
559
+ reloadConfig(schedules, options = {}) {
560
+ this.schedules = schedules.filter((s) => s.enabled !== false);
583
561
  this.promptsDir = join(DATA_DIR, 'prompts');
562
+ // A name that no longer exists must not keep (or later regain) a stamp.
563
+ const live = new Set(this.schedules.map((s) => s.name));
564
+ for (const name of this.lastFired.keys()) if (!live.has(name)) this.lastFired.delete(name);
584
565
  // Defer/skip state is persisted (deferred_until / skipped_until) and
585
566
  // re-read from the reloaded rows, so a reload no longer drops it.
586
567
  this.refreshSkipCache();
@@ -592,10 +573,9 @@ ${prompt}`;
592
573
  this.restart();
593
574
  }
594
575
  getStatus() {
595
- return this.typedSchedules().map(({ schedule: s, type }) => ({
576
+ return this.schedules.map((s) => ({
596
577
  name: s.name,
597
578
  time: s.whenCron ?? s.whenAt ?? null,
598
- type,
599
579
  running: this.running.has(s.name),
600
580
  lastFired: this.lastFired.get(s.name) ?? s.lastFiredAt ?? null,
601
581
  lastSuccess: s.lastSuccessAt ?? null,
@@ -607,14 +587,13 @@ ${prompt}`;
607
587
  const timed = this.findSchedule(name);
608
588
  if (timed) {
609
589
  if (this.running.has(name)) return `"${name}" is already running`;
610
- const isNonInteractive = this.nonInteractive.includes(timed);
611
590
  const now = /* @__PURE__ */ new Date();
612
591
  const hhmm = `${String(now.getHours()).padStart(2, '0')}:${String(now.getMinutes()).padStart(2, '0')}`;
613
592
  const dateStr = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')}`;
614
593
  // Match onCronFire: record lastFired only when fireTimed actually
615
594
  // proceeds past its running/precondition guards (resolves truthy),
616
595
  // and reflect a non-fire in the returned status.
617
- const fired = await this.fireTimed(timed, isNonInteractive ? 'non-interactive' : 'interactive');
596
+ const fired = await this.fireTimed(timed);
618
597
  if (fired) {
619
598
  this.lastFired.set(name, `${dateStr}T${hhmm}`);
620
599
  return `triggered "${name}"`;
@@ -636,7 +615,7 @@ ${prompt}`;
636
615
  // interval is kept for future cadence-driven work and lifecycle parity.
637
616
  }
638
617
  // ── Fire timed schedule ─────────────────────────────────────────────
639
- async fireTimed(schedule, type, opts = {}) {
618
+ async fireTimed(schedule, opts = {}) {
640
619
  const prompt = this.resolvePrompt(schedule);
641
620
  if (!prompt) {
642
621
  process.stderr.write(`mixdog scheduler: prompt not found for "${schedule.name}"
@@ -644,56 +623,59 @@ ${prompt}`;
644
623
  this.notifyFailure(schedule, 'prompt not found — fire skipped');
645
624
  return false;
646
625
  }
647
- return await this.fireTimedPrompt(schedule, type, prompt, opts);
626
+ return await this.fireTimedPrompt(schedule, prompt, opts);
648
627
  }
649
- /** Fire a timed schedule with the given prompt content */
650
- async fireTimedPrompt(schedule, type, prompt, { awaitDispatch = false } = {}) {
651
- logSchedule(`firing ${schedule.name} (${type})\n`);
652
- if (this.running.has(schedule.name)) {
628
+ /** Why a fire of `name` may not start now: 'renaming', 'running' or null. */
629
+ fireRefusal(name) {
630
+ if (this.renaming.has(name)) return 'renaming';
631
+ return this.running.has(name) ? 'running' : null;
632
+ }
633
+ /** Record a fire refused for `reason` (fireRefusal) as a skip. */
634
+ recordRefusal(schedule, reason) {
635
+ if (reason === 'running') {
653
636
  this.recordSkip(schedule);
637
+ return;
638
+ }
639
+ logSchedule(`${schedule.name}: not fired — being renamed\n`);
640
+ // A skip, not a failure: a one-shot stays pending (fireOneShot) and
641
+ // renameLocked re-arms it if the rename does not go through.
642
+ schedule.lastSkippedAt = new Date().toISOString();
643
+ // Per name: another rename settling meanwhile must not hide this one.
644
+ this.renameBlocked ??= new Set();
645
+ this.renameBlocked.add(schedule.name);
646
+ }
647
+ /** Fire a timed schedule with the given prompt content. `reserved`: the
648
+ * caller already holds the name in `running` and releases it itself. */
649
+ async fireTimedPrompt(schedule, prompt, { awaitDispatch = false, reserved = false } = {}) {
650
+ logSchedule(`firing ${schedule.name}\n`);
651
+ const refusal = reserved ? null : this.fireRefusal(schedule.name);
652
+ if (refusal) {
653
+ this.recordRefusal(schedule, refusal);
654
654
  return false;
655
655
  }
656
- if (type === 'interactive' && this.injectIntoLead(schedule, type, prompt)) return true;
657
- this.running.add(schedule.name);
656
+ if (!reserved) this.running.add(schedule.name);
658
657
  // Cron (recurring) keeps the fire-and-forget contract: return truthy now
659
658
  // and swallow async failures. A one-shot (awaitDispatch) instead awaits
660
659
  // the dispatch so a rejected run propagates and the caller leaves the
661
660
  // entry pending for retry instead of retiring it.
662
- const dispatch = this.dispatchScheduleRun(schedule, prompt, awaitDispatch);
661
+ const run = this.dispatchScheduleRun(schedule, prompt, awaitDispatch);
662
+ const dispatch = reserved ? run : run.finally(() => this.running.delete(schedule.name));
663
663
  if (awaitDispatch) return await dispatch;
664
664
  return true;
665
665
  }
666
- /** Interactive schedules enqueue into the live Lead session's command
667
- * queue as system-generated task notifications (priority 'later'),
668
- * exactly like inbound channel messages — enqueue counts as the fire.
669
- * False when no live Lead seat is attached or the inject fails, so the
670
- * visible-session run remains the fallback and a fire is never lost. */
671
- injectIntoLead(schedule, type, prompt) {
672
- if (!this.injectFn || !this.injectReady()) return false;
673
- try {
674
- const wrapped = this.wrapPrompt(schedule.name, prompt, type);
675
- this.injectFn('', `schedule:${schedule.name}`, ' ', { type: 'schedule', instruction: wrapped });
676
- logSchedule(`${schedule.name}: injected into Lead session queue (interactive fire)\n`);
677
- return true;
678
- } catch (err) {
679
- logSchedule(
680
- `${schedule.name}: Lead inject failed (${err?.message || err}) — falling back to visible session run\n`
681
- );
682
- return false;
683
- }
684
- }
685
666
  /** Fires run as VISIBLE schedule sessions (desktop Recent / TUI resume)
686
- * via runScheduleSession; the wrapped prompt keeps the schedule context
687
- * header. Resolves true on success; a failure rejects for awaitDispatch
667
+ * via runScheduleSession. Resolves true on success; a failure rejects for awaitDispatch
688
668
  * callers and resolves false (after a logged notice) for fire-and-forget
689
669
  * cron fires. */
690
670
  dispatchScheduleRun(schedule, prompt, awaitDispatch) {
691
- const run = this.scheduleRunner
692
- ? this.scheduleRunner(schedule, { prompt })
693
- : Promise.reject(new Error('schedule runner not configured'));
694
- return run
671
+ // Called inside a then, so a runner that throws synchronously rejects
672
+ // like one that rejects, and the caller's cleanup still runs.
673
+ return Promise.resolve()
674
+ .then(() => {
675
+ if (!this.scheduleRunner) throw new Error('schedule runner not configured');
676
+ return this.scheduleRunner(schedule, { prompt });
677
+ })
695
678
  .then((outcome) => {
696
- this.running.delete(schedule.name);
697
679
  // The run path found this schedule's previous run still going (a
698
680
  // "run now" from the app) or backing off after failures in a row; it
699
681
  // already persisted the skip.
@@ -706,7 +688,6 @@ ${prompt}`;
706
688
  return true;
707
689
  })
708
690
  .catch((err) => {
709
- this.running.delete(schedule.name);
710
691
  logSchedule(`${schedule.name} LLM error: ${err.message}\n`);
711
692
  // The cron fire-and-forget contract swallowed failures entirely;
712
693
  // log them so a failed scheduled run is never
@@ -15,6 +15,7 @@ import { ensurePgInstance, withSchemaBootstrapLock } from '../../memory/lib/pg/a
15
15
  import { createPgSchemaDb } from '../../shared/pg-schema-db.mjs';
16
16
  import { resolvePluginData } from '../../shared/plugin-paths.mjs';
17
17
  import { ROUTINE_LIMITS } from '../../shared/routine-limits.mjs';
18
+ import { scheduleNameTakenError } from './schedule-errors.mjs';
18
19
 
19
20
  const SCHEMA = 'scheduler';
20
21
 
@@ -25,8 +26,6 @@ CREATE TABLE IF NOT EXISTS scheduler.schedules (
25
26
  when_at timestamptz,
26
27
  when_cron text,
27
28
  timezone text,
28
- target text NOT NULL CHECK (target IN ('channel','session')),
29
- channel_id text,
30
29
  model text,
31
30
  cwd text,
32
31
  workflow text,
@@ -89,8 +88,6 @@ function rowToDef(row) {
89
88
  whenAt: row.when_at,
90
89
  whenCron: row.when_cron,
91
90
  timezone: row.timezone,
92
- target: row.target,
93
- channelId: row.channel_id,
94
91
  cwd: row.cwd,
95
92
  attachments: row.attachments || null,
96
93
  delivery: row.delivery || null,
@@ -114,8 +111,32 @@ function rowToDef(row) {
114
111
  };
115
112
  }
116
113
 
114
+ // A table created by an older release still has `target` as a NOT NULL column
115
+ // with no default, so an insert must keep filling it; every schedule runs as a
116
+ // session. A table created now has no such column. The schema is probed once
117
+ // per connection; the column is never read, altered or dropped.
118
+ const requiredTargetColumn = new WeakMap();
119
+ function hasRequiredTargetColumn(db) {
120
+ if (!requiredTargetColumn.has(db)) {
121
+ requiredTargetColumn.set(
122
+ db,
123
+ db
124
+ .query(
125
+ `SELECT column_name FROM information_schema.columns
126
+ WHERE table_schema = 'scheduler' AND table_name = 'schedules' AND column_name = 'target'`
127
+ )
128
+ .then(({ rows }) => rows.some((row) => row.column_name === 'target'))
129
+ .catch((error) => {
130
+ requiredTargetColumn.delete(db);
131
+ throw error;
132
+ })
133
+ );
134
+ }
135
+ return requiredTargetColumn.get(db);
136
+ }
137
+
117
138
  const COLS =
118
- 'name, description, when_at, when_cron, timezone, target, channel_id, cwd, attachments, delivery, prompt, enabled, status, last_fired_at, last_scheduled_at, last_started_at, last_success_at, last_failed_at, last_skipped_at, owner_agent_id, consecutive_failures, result_delivery, next_fire_at, deferred_until, skipped_until, created_at, updated_at';
139
+ 'name, description, when_at, when_cron, timezone, cwd, attachments, delivery, prompt, enabled, status, last_fired_at, last_scheduled_at, last_started_at, last_success_at, last_failed_at, last_skipped_at, owner_agent_id, consecutive_failures, result_delivery, next_fire_at, deferred_until, skipped_until, created_at, updated_at';
119
140
 
120
141
  // ---------------------------------------------------------------------------
121
142
  // Public API
@@ -147,8 +168,6 @@ export async function upsertSchedule(def, { dataDir } = {}) {
147
168
  def.whenAt ?? null,
148
169
  def.whenCron ?? null,
149
170
  def.timezone ?? null,
150
- def.target,
151
- def.channelId ?? null,
152
171
  def.cwd ?? null,
153
172
  def.attachments ? JSON.stringify(def.attachments) : null,
154
173
  def.delivery ?? null,
@@ -158,18 +177,23 @@ export async function upsertSchedule(def, { dataDir } = {}) {
158
177
  def.nextFireAt ?? null,
159
178
  def.ownerAgentId || 'lead',
160
179
  ];
180
+ let legacyTarget = '';
181
+ let legacyTargetValue = '';
182
+ if (await hasRequiredTargetColumn(db)) {
183
+ params.push('session');
184
+ legacyTarget = ', target';
185
+ legacyTargetValue = `,$${params.length}`;
186
+ }
161
187
  const { rows } = await db.query(
162
188
  `INSERT INTO scheduler.schedules
163
- (name, description, when_at, when_cron, timezone, target, channel_id, cwd, attachments, delivery, prompt, enabled, status, next_fire_at, owner_agent_id)
164
- VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15)
189
+ (name, description, when_at, when_cron, timezone, cwd, attachments, delivery, prompt, enabled, status, next_fire_at, owner_agent_id${legacyTarget})
190
+ VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13${legacyTargetValue})
165
191
  ON CONFLICT (name) DO UPDATE SET
166
192
  owner_agent_id = EXCLUDED.owner_agent_id,
167
193
  description = EXCLUDED.description,
168
194
  when_at = EXCLUDED.when_at,
169
195
  when_cron = EXCLUDED.when_cron,
170
196
  timezone = EXCLUDED.timezone,
171
- target = EXCLUDED.target,
172
- channel_id = EXCLUDED.channel_id,
173
197
  cwd = EXCLUDED.cwd,
174
198
  attachments = EXCLUDED.attachments,
175
199
  delivery = EXCLUDED.delivery,
@@ -195,6 +219,54 @@ export async function upsertSchedule(def, { dataDir } = {}) {
195
219
  return rowToDef(rows[0]);
196
220
  }
197
221
 
222
+ /**
223
+ * Rename schedule `oldName` to `def.name` in one UPDATE of the primary key,
224
+ * applying the edited settings. Run history (last_* stamps, failure streak,
225
+ * undelivered results), status and the next_fire_at cursor (a deferral, or
226
+ * none for a finished one-shot) stay on the row. When `timingChanged`, the row
227
+ * is re-armed the way `upsertSchedule` re-arms a redefinition: a one-shot's
228
+ * cursor becomes its `when_at`, a recurring one's is cleared for the scheduler
229
+ * to compute. Resolves null when `oldName` is gone; an occupied `def.name`
230
+ * rejects and changes nothing.
231
+ */
232
+ export async function renameSchedule(oldName, def, { timingChanged = false, dataDir } = {}) {
233
+ const db = await getDb(dataDir);
234
+ try {
235
+ const { rows } = await db.query(
236
+ `UPDATE scheduler.schedules SET
237
+ name = $2, description = $3, when_at = $4, when_cron = $5, timezone = $6, cwd = $7,
238
+ attachments = $8, prompt = $9, enabled = $10, owner_agent_id = $11,
239
+ next_fire_at = CASE WHEN $12::boolean THEN $4::timestamptz ELSE next_fire_at END,
240
+ status = CASE WHEN $12::boolean THEN 'active' ELSE status END,
241
+ deferred_until = CASE WHEN $12::boolean THEN NULL ELSE deferred_until END,
242
+ skipped_until = CASE WHEN $12::boolean THEN NULL ELSE skipped_until END,
243
+ last_fired_at = CASE WHEN $12::boolean THEN NULL ELSE last_fired_at END,
244
+ last_scheduled_at = CASE WHEN $12::boolean THEN NULL ELSE last_scheduled_at END,
245
+ updated_at = now()
246
+ WHERE name = $1
247
+ RETURNING ${COLS}`,
248
+ [
249
+ oldName,
250
+ def.name,
251
+ def.description ?? '',
252
+ def.whenAt ?? null,
253
+ def.whenCron ?? null,
254
+ def.timezone ?? null,
255
+ def.cwd ?? null,
256
+ def.attachments ? JSON.stringify(def.attachments) : null,
257
+ def.prompt,
258
+ def.enabled ?? true,
259
+ def.ownerAgentId || 'lead',
260
+ timingChanged === true,
261
+ ]
262
+ );
263
+ return rowToDef(rows[0]);
264
+ } catch (error) {
265
+ if (error?.code === '23505') throw scheduleNameTakenError(def.name);
266
+ throw error;
267
+ }
268
+ }
269
+
198
270
  export async function deleteSchedule(name, { dataDir } = {}) {
199
271
  const db = await getDb(dataDir);
200
272
  const { rowCount } = await db.query(`DELETE FROM scheduler.schedules WHERE name = $1`, [name]);