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.
- package/LICENSES/oh-my-pi-MIT.txt +23 -0
- package/NOTICE.md +17 -2
- package/README.md +11 -0
- package/package.json +3 -11
- package/scripts/daemon-stub.mjs +1 -1
- package/scripts/embed-google-oauth-client.mjs +49 -0
- package/scripts/gate-local.mjs +4 -2
- package/scripts/lib/test-spawn-ledger.mjs +46 -1
- package/scripts/native-release-status.mjs +2 -2
- package/scripts/recall-history-cases.json +1 -1
- package/scripts/suite-health.mjs +10 -1
- package/scripts/test.mjs +12 -11
- package/src/agents/reviewer/AGENT.md +7 -11
- package/src/agents/reviewer/agent.json +1 -1
- package/src/defaults/shipped-history.json +25 -1
- package/src/defaults/skills/browser-use/SKILL.md +77 -17
- package/src/defaults/skills/code-tidy/SKILL.md +142 -270
- package/src/defaults/skills/code-tidy/references/agent-cleanup.md +4 -4
- package/src/defaults/skills/code-tidy/references/cleanup-report.md +31 -76
- package/src/defaults/skills/code-tidy/references/dead-code.md +6 -12
- package/src/defaults/skills/computer-use/SKILL.md +33 -9
- package/src/defaults/skills/setup/SKILL.md +29 -2
- package/src/defaults/skills/setup/references/actions.md +117 -28
- package/src/defaults/skills/setup/references/surfaces.md +77 -35
- package/src/defaults/skills/setup-guide/SKILL.md +3 -3
- package/src/defaults/skills/visualize/SKILL.md +2 -1
- package/src/lib/keychain-cjs.cjs +359 -29
- package/src/lib/rules-builder.cjs +10 -8
- package/src/rules/agent/AGENT.md +6 -6
- package/src/rules/agent-mode/AGENT_MODE.md +10 -2
- package/src/rules/agent-mode/MIXY.md +12 -0
- package/src/rules/lead/LEAD.md +18 -20
- package/src/rules/shared/05-tool-calls.md +5 -6
- package/src/rules/shared/10-tool-workflow.md +2 -0
- package/src/rules/shared/60-verification.md +7 -5
- package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +2 -3
- package/src/runtime/agent/orchestrator/agent-runtime/heartbeat-completion.mjs +193 -0
- package/src/runtime/agent/orchestrator/agent-runtime/title-completion.mjs +3 -1
- package/src/runtime/agent/orchestrator/agent-trace-format.mjs +3 -1
- package/src/runtime/agent/orchestrator/config.mjs +34 -0
- package/src/runtime/agent/orchestrator/context/collect.mjs +3 -0
- package/src/runtime/agent/orchestrator/context/deferred-tools.mjs +72 -67
- package/src/runtime/agent/orchestrator/context/skill-catalog.mjs +54 -1
- package/src/runtime/agent/orchestrator/mcp/agent-connectors.mjs +3 -3
- package/src/runtime/agent/orchestrator/mcp/client-transport.mjs +177 -73
- package/src/runtime/agent/orchestrator/mcp/client.mjs +25 -2
- package/src/runtime/agent/orchestrator/mcp/oauth-callback.mjs +31 -2
- package/src/runtime/agent/orchestrator/mcp/oauth-client.mjs +16 -3
- package/src/runtime/agent/orchestrator/mcp/oauth-login.mjs +42 -7
- package/src/runtime/agent/orchestrator/mcp/oauth-provider.mjs +21 -2
- package/src/runtime/agent/orchestrator/mcp/oauth-shared.mjs +23 -0
- package/src/runtime/agent/orchestrator/mcp/oauth.mjs +11 -1
- package/src/runtime/agent/orchestrator/providers/account-pool.mjs +230 -137
- package/src/runtime/agent/orchestrator/providers/admission-scheduler.mjs +64 -7
- package/src/runtime/agent/orchestrator/providers/anthropic-api-request.mjs +18 -5
- package/src/runtime/agent/orchestrator/providers/anthropic-cache-warm.mjs +165 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-client-version.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-credentials.mjs +4 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-request/gzip-post.mjs +14 -14
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +160 -69
- package/src/runtime/agent/orchestrator/providers/anthropic-overload-fallback.mjs +25 -3
- package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +64 -33
- package/src/runtime/agent/orchestrator/providers/anthropic.mjs +135 -87
- package/src/runtime/agent/orchestrator/providers/antigravity-oauth-catalog.mjs +52 -8
- package/src/runtime/agent/orchestrator/providers/antigravity-oauth.mjs +10 -1
- package/src/runtime/agent/orchestrator/providers/antigravity-request.mjs +22 -6
- package/src/runtime/agent/orchestrator/providers/antigravity-response.mjs +15 -4
- package/src/runtime/agent/orchestrator/providers/antigravity-stream.mjs +17 -2
- package/src/runtime/agent/orchestrator/providers/antigravity-transport.mjs +142 -104
- package/src/runtime/agent/orchestrator/providers/cache-warm-admission.mjs +90 -0
- package/src/runtime/agent/orchestrator/providers/codex-client-meta.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/cursor-wire-normalization.mjs +10 -4
- package/src/runtime/agent/orchestrator/providers/cursor-wire-protobuf.mjs +10 -217
- package/src/runtime/agent/orchestrator/providers/cursor-wire-transport.mjs +25 -9
- package/src/runtime/agent/orchestrator/providers/cursor.mjs +22 -6
- package/src/runtime/agent/orchestrator/providers/devin-auth.mjs +409 -0
- package/src/runtime/agent/orchestrator/providers/devin-wire-families.mjs +187 -0
- package/src/runtime/agent/orchestrator/providers/devin-wire-identity.mjs +342 -0
- package/src/runtime/agent/orchestrator/providers/devin-wire-models.mjs +541 -0
- package/src/runtime/agent/orchestrator/providers/devin-wire-protobuf.mjs +305 -0
- package/src/runtime/agent/orchestrator/providers/devin-wire-request.mjs +558 -0
- package/src/runtime/agent/orchestrator/providers/devin-wire-schema.mjs +880 -0
- package/src/runtime/agent/orchestrator/providers/devin-wire-session.mjs +190 -0
- package/src/runtime/agent/orchestrator/providers/devin-wire-tool-args.mjs +351 -0
- package/src/runtime/agent/orchestrator/providers/devin-wire-transport.mjs +560 -0
- package/src/runtime/agent/orchestrator/providers/devin.mjs +491 -0
- package/src/runtime/agent/orchestrator/providers/effort-configuration.mjs +3 -0
- package/src/runtime/agent/orchestrator/providers/gemini-output-limit.mjs +46 -0
- package/src/runtime/agent/orchestrator/providers/gemini-reasoning-relay.mjs +30 -0
- package/src/runtime/agent/orchestrator/providers/gemini-request-body.mjs +7 -11
- package/src/runtime/agent/orchestrator/providers/gemini-retry-policy.mjs +61 -22
- package/src/runtime/agent/orchestrator/providers/gemini-sdk-request.mjs +1 -0
- package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +71 -4
- package/src/runtime/agent/orchestrator/providers/gemini.mjs +25 -2
- package/src/runtime/agent/orchestrator/providers/grok-client-version.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/lib/anthropic-request-utils.mjs +96 -5
- package/src/runtime/agent/orchestrator/providers/lib/oauth-token-utils.mjs +17 -3
- package/src/runtime/agent/orchestrator/providers/lib/tail-reminder-split.mjs +23 -5
- package/src/runtime/agent/orchestrator/providers/media-normalization.mjs +52 -10
- package/src/runtime/agent/orchestrator/providers/model-proxy-channel.d.mts +8 -1
- package/src/runtime/agent/orchestrator/providers/model-proxy-channel.mjs +1 -0
- package/src/runtime/agent/orchestrator/providers/npm-cli-version.mjs +11 -3
- package/src/runtime/agent/orchestrator/providers/oauth-credential-probes.mjs +19 -1
- package/src/runtime/agent/orchestrator/providers/oauth-usage.mjs +39 -3
- package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/stream-state.mjs +3 -1
- package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream.mjs +2 -0
- package/src/runtime/agent/orchestrator/providers/openai-error-contract.mjs +6 -1
- package/src/runtime/agent/orchestrator/providers/openai-oauth-tokens.mjs +10 -9
- package/src/runtime/agent/orchestrator/providers/openai-ws-send/send-context.mjs +22 -3
- package/src/runtime/agent/orchestrator/providers/openai-ws-send-attempts/attempt-context.mjs +43 -5
- package/src/runtime/agent/orchestrator/providers/openai-ws-send-attempts/handshake-failure.mjs +3 -1
- package/src/runtime/agent/orchestrator/providers/openai-ws-send-attempts/stream-failure.mjs +6 -3
- package/src/runtime/agent/orchestrator/providers/opencode-go-gemini.mjs +1 -0
- package/src/runtime/agent/orchestrator/providers/provider-availability.mjs +19 -0
- package/src/runtime/agent/orchestrator/providers/provider-error-kind.mjs +65 -1
- package/src/runtime/agent/orchestrator/providers/quota-reading.mjs +38 -12
- package/src/runtime/agent/orchestrator/providers/registry.mjs +43 -3
- package/src/runtime/agent/orchestrator/providers/retry-executor.mjs +39 -7
- package/src/runtime/agent/orchestrator/providers/statusline-route-meta.mjs +6 -0
- package/src/runtime/agent/orchestrator/providers/wire-protobuf-codec.mjs +368 -0
- package/src/runtime/agent/orchestrator/runtime-core/ask-user-tool-defs.mjs +12 -7
- package/src/runtime/agent/orchestrator/runtime-core/builtin-features.mjs +2 -0
- package/src/runtime/agent/orchestrator/runtime-core/compaction-config-keys.mjs +62 -0
- package/src/runtime/agent/orchestrator/runtime-core/config-helpers.mjs +37 -16
- package/src/runtime/agent/orchestrator/runtime-core/deferred-tool-delta.mjs +100 -23
- package/src/runtime/agent/orchestrator/runtime-core/orchestration.mjs +1 -1
- package/src/runtime/agent/orchestrator/runtime-core/reaction-tool-defs.mjs +133 -0
- package/src/runtime/agent/orchestrator/runtime-core/session-text.mjs +2 -0
- package/src/runtime/agent/orchestrator/runtime-core/tool-bridge.mjs +19 -4
- package/src/runtime/agent/orchestrator/runtime-core/tool-catalog-data.mjs +3 -0
- package/src/runtime/agent/orchestrator/runtime-core/tool-catalog.mjs +125 -30
- package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +23 -5
- package/src/runtime/agent/orchestrator/runtime-core/tool-profile.mjs +35 -10
- package/src/runtime/agent/orchestrator/runtime-core/workflow/agents.mjs +57 -10
- package/src/runtime/agent/orchestrator/runtime-core/workflow/context.mjs +9 -6
- package/src/runtime/agent/orchestrator/runtime-core/workflow-ids.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/agent-loop.mjs +18 -0
- package/src/runtime/agent/orchestrator/session/agent-room.d.mts +0 -3
- package/src/runtime/agent/orchestrator/session/agent-room.mjs +10 -4
- package/src/runtime/agent/orchestrator/session/cache-warm.mjs +792 -0
- package/src/runtime/agent/orchestrator/session/compact/runner.mjs +148 -25
- package/src/runtime/agent/orchestrator/session/compaction-archive-capture.mjs +475 -58
- package/src/runtime/agent/orchestrator/session/compaction-archive.mjs +42 -17
- package/src/runtime/agent/orchestrator/session/eager-dispatch.mjs +8 -0
- package/src/runtime/agent/orchestrator/session/human-gate/gate.mjs +47 -9
- package/src/runtime/agent/orchestrator/session/human-gate/outbound-policy.mjs +6 -2
- package/src/runtime/agent/orchestrator/session/human-gate/requests.mjs +36 -4
- package/src/runtime/agent/orchestrator/session/human-gate/store.mjs +14 -2
- package/src/runtime/agent/orchestrator/session/loop/assistant-commit.mjs +8 -0
- package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +18 -22
- package/src/runtime/agent/orchestrator/session/loop/fresh-context.mjs +32 -3
- package/src/runtime/agent/orchestrator/session/loop/loop-state.mjs +11 -2
- package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/segments.mjs +4 -1
- package/src/runtime/agent/orchestrator/session/loop/no-tool-turn.mjs +51 -3
- package/src/runtime/agent/orchestrator/session/loop/route-fallback.mjs +17 -3
- package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +10 -2
- package/src/runtime/agent/orchestrator/session/loop/tool-exec/after-hook.mjs +2 -1
- package/src/runtime/agent/orchestrator/session/loop/tool-exec/before-hook.mjs +14 -4
- package/src/runtime/agent/orchestrator/session/loop/tool-exec/dispatch.mjs +16 -2
- package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +16 -1
- package/src/runtime/agent/orchestrator/session/loop/tool-exposure-migration.mjs +10 -0
- package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +3 -10
- package/src/runtime/agent/orchestrator/session/loop/tool-phase.mjs +23 -5
- package/src/runtime/agent/orchestrator/session/loop/turn-limits.mjs +60 -0
- package/src/runtime/agent/orchestrator/session/manager/ask-queue.mjs +12 -2
- package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +19 -1
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-commit.mjs +2 -0
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-execute.mjs +133 -90
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-loop-options.mjs +14 -0
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-settle.mjs +2 -1
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-state.mjs +31 -4
- package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +30 -18
- package/src/runtime/agent/orchestrator/session/manager/owner-route-fallbacks.mjs +23 -13
- package/src/runtime/agent/orchestrator/session/manager/pending-message-entry.mjs +11 -2
- package/src/runtime/agent/orchestrator/session/manager/provider-replay.mjs +13 -0
- package/src/runtime/agent/orchestrator/session/manager/route-fallback-chain.mjs +129 -0
- package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +20 -2
- package/src/runtime/agent/orchestrator/session/manager/session-close.mjs +2 -0
- package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +8 -0
- package/src/runtime/agent/orchestrator/session/manager/session-record.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/manager/session-tool-surface.mjs +13 -0
- package/src/runtime/agent/orchestrator/session/manager/tool-resolution.mjs +7 -2
- package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint-journal.mjs +102 -15
- package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint.mjs +18 -5
- package/src/runtime/agent/orchestrator/session/message-ids.mjs +5 -3
- package/src/runtime/agent/orchestrator/session/message-reactions.mjs +89 -0
- package/src/runtime/agent/orchestrator/session/message-reactions.test-support.mjs +75 -0
- package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +19 -8
- package/src/runtime/agent/orchestrator/session/prompt-cache-idle-gaps.mjs +113 -0
- package/src/runtime/agent/orchestrator/session/prompt-cache-touch-store.mjs +127 -0
- package/src/runtime/agent/orchestrator/session/routine-result.d.mts +0 -2
- package/src/runtime/agent/orchestrator/session/routine-result.mjs +47 -11
- package/src/runtime/agent/orchestrator/session/send-with-recovery.mjs +20 -2
- package/src/runtime/agent/orchestrator/session/sensitive-projection.mjs +100 -0
- package/src/runtime/agent/orchestrator/session/sensitive-values.mjs +257 -0
- package/src/runtime/agent/orchestrator/session/side-question-store.mjs +20 -7
- package/src/runtime/agent/orchestrator/session/side-question.mjs +18 -0
- package/src/runtime/agent/orchestrator/session/skill-update-notice.mjs +60 -0
- package/src/runtime/agent/orchestrator/session/store/live-state.mjs +6 -3
- package/src/runtime/agent/orchestrator/session/store/save-delta.mjs +9 -5
- package/src/runtime/agent/orchestrator/session/store/serialize.mjs +9 -8
- package/src/runtime/agent/orchestrator/session/store/session-persistence-events.mjs +29 -0
- package/src/runtime/agent/orchestrator/session/store-agent-worker-header-cache.mjs +85 -0
- package/src/runtime/agent/orchestrator/session/store-agent-worker-rows.mjs +133 -18
- package/src/runtime/agent/orchestrator/session/store-summary-catalog.mjs +12 -2
- package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +44 -14
- package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +84 -3
- package/src/runtime/agent/orchestrator/session/stored-attachment-history.mjs +32 -0
- package/src/runtime/agent/orchestrator/session/tail-reminder-rules.mjs +11 -5
- package/src/runtime/agent/orchestrator/session/thinking-binding.mjs +108 -0
- package/src/runtime/agent/orchestrator/session/tool-batch/pre-dispatch.mjs +2 -2
- package/src/runtime/agent/orchestrator/session/tool-batch/record-outcome.mjs +2 -1
- package/src/runtime/agent/orchestrator/session/tool-batch/state.mjs +4 -0
- package/src/runtime/agent/orchestrator/session/tool-batch.mjs +5 -0
- package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +25 -2
- package/src/runtime/agent/orchestrator/session/transcript-restore/tool-result-status.mjs +9 -7
- package/src/runtime/agent/orchestrator/session/transcript-restore/tool-result-text.mjs +2 -2
- package/src/runtime/agent/orchestrator/session/turn-failure.mjs +5 -1
- package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +5 -2
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/backgrounded-result.mjs +12 -1
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/misuse-guard.mjs +240 -0
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/result-format.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/shell-cwd.mjs +38 -0
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +25 -5
- package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +65 -7
- package/src/runtime/agent/orchestrator/tools/builtin/shell-runtime.mjs +7 -1
- package/src/runtime/agent/orchestrator/tools/shell-command.mjs +36 -13
- package/src/runtime/browser-bridge/action-schema.mjs +13 -2
- package/src/runtime/browser-bridge/client.mjs +73 -8
- package/src/runtime/browser-bridge/input-fields.mjs +8 -9
- package/src/runtime/browser-bridge/mail-links.mjs +107 -0
- package/src/runtime/browser-bridge/tool-defs.mjs +1 -1
- package/src/runtime/channels/lib/config.mjs +6 -61
- package/src/runtime/channels/lib/crash-log.mjs +1 -1
- package/src/runtime/channels/lib/event-pipeline.mjs +4 -19
- package/src/runtime/channels/lib/event-queue.mjs +3 -18
- package/src/runtime/channels/lib/owned-runtime/automation-services.mjs +2 -2
- package/src/runtime/channels/lib/owned-runtime/automation-start.mjs +3 -4
- package/src/runtime/channels/lib/owned-runtime/config-reload.mjs +4 -59
- package/src/runtime/channels/lib/owned-runtime/owned-start.mjs +33 -115
- package/src/runtime/channels/lib/owned-runtime/owned-stop.mjs +5 -22
- package/src/runtime/channels/lib/owned-runtime/state.mjs +2 -8
- package/src/runtime/channels/lib/owned-runtime.mjs +6 -23
- package/src/runtime/channels/lib/parent-bridge.mjs +2 -2
- package/src/runtime/channels/lib/runtime-paths.mjs +4 -43
- package/src/runtime/channels/lib/schedule-errors.mjs +51 -0
- package/src/runtime/channels/lib/scheduler.mjs +135 -154
- package/src/runtime/channels/lib/schedules-db.mjs +83 -11
- package/src/runtime/channels/lib/status-snapshot.mjs +1 -1
- package/src/runtime/channels/lib/tool-dispatch.mjs +14 -2
- package/src/runtime/channels/lib/webhooks-db.mjs +5 -9
- package/src/runtime/channels/lib/worker-bootstrap.mjs +0 -1
- package/src/runtime/channels/lib/worker-ipc.mjs +0 -1
- package/src/runtime/channels/lib/worker-main.mjs +19 -34
- package/src/runtime/computer-bridge/action-schema.mjs +42 -1
- package/src/runtime/computer-bridge/actions.mjs +9 -1
- package/src/runtime/computer-bridge/client.mjs +180 -8
- package/src/runtime/computer-bridge/core-actions.mjs +2 -2
- package/src/runtime/computer-bridge/error-recovery.mjs +27 -2
- package/src/runtime/computer-bridge/linux-readiness.mjs +65 -23
- package/src/runtime/computer-bridge/pending-continuation.mjs +1 -1
- package/src/runtime/connectors/connector-accounts-mcp.mjs +230 -0
- package/src/runtime/connectors/gmail-app-password.mjs +172 -39
- package/src/runtime/connectors/google-mcp.mjs +10 -12
- package/src/runtime/memory/lib/pg/adapter.mjs +77 -70
- package/src/runtime/memory/lib/pg/supervisor.mjs +24 -12
- package/src/runtime/memory/lib/runtime-fetcher.mjs +23 -5
- package/src/runtime/office/com/com-adapter.mjs +17 -1
- package/src/runtime/office/com/office-com-host.ps1 +51 -1
- package/src/runtime/office/pdf/cfb-fixture.mjs +328 -0
- package/src/runtime/office/pdf/cfb-protection.mjs +311 -0
- package/src/runtime/office/pdf/document-preview.mjs +326 -8
- package/src/runtime/office/pdf/ooxml-crypto.mjs +469 -0
- package/src/runtime/office/pdf/ooxml-key-worker.mjs +14 -0
- package/src/runtime/office/portable/portable-soffice.mjs +45 -5
- package/src/runtime/session-search/session-search-db.mjs +73 -9
- package/src/runtime/session-search/session-search-ingest.mjs +110 -20
- package/src/runtime/shared/abort-race.mjs +89 -0
- package/src/runtime/shared/agent-avatar.d.mts +0 -1
- package/src/runtime/shared/agent-kind.mjs +4 -3
- package/src/runtime/shared/bot-hops.mjs +62 -2
- package/src/runtime/shared/bounded-download.mjs +8 -3
- package/src/runtime/shared/config.mjs +51 -35
- package/src/runtime/shared/connector-accounts.mjs +20 -10
- package/src/runtime/shared/connector-catalog.d.mts +33 -2
- package/src/runtime/shared/connector-catalog.mjs +166 -8
- package/src/runtime/shared/connector-triggers.mjs +22 -19
- package/src/runtime/shared/defaults-separation.mjs +2 -14
- package/src/runtime/shared/delegated-approval-host.mjs +103 -11
- package/src/runtime/shared/developer-options.mjs +32 -1
- package/src/runtime/shared/err-text.mjs +24 -15
- package/src/runtime/shared/file-permissions.d.mts +0 -8
- package/src/runtime/shared/file-permissions.mjs +0 -11
- package/src/runtime/shared/git-direct.test-support.mjs +19 -0
- package/src/runtime/shared/google-oauth-client.generated.json +4 -0
- package/src/runtime/shared/google-oauth-client.mjs +34 -9
- package/src/runtime/shared/heartbeat-interval.d.mts +4 -0
- package/src/runtime/shared/heartbeat-interval.mjs +19 -0
- package/src/runtime/shared/idle-auto-clear-gate.mjs +104 -0
- package/src/runtime/shared/llm/anthropic-betas.mjs +7 -0
- package/src/runtime/shared/llm/anthropic-thinking-contract.mjs +12 -0
- package/src/runtime/shared/llm/provider-catalog-registry.mjs +1 -0
- package/src/runtime/shared/llm/usage-rollup.mjs +1 -1
- package/src/runtime/shared/llm/well-formed.mjs +66 -0
- package/src/runtime/shared/mixy-presence.mjs +22 -0
- package/src/runtime/shared/orchestration.d.mts +2 -0
- package/src/runtime/shared/pg-schema-db.mjs +27 -16
- package/src/runtime/shared/profile-config.cjs +1 -1
- package/src/runtime/shared/prompt-origin.d.mts +4 -0
- package/src/runtime/shared/prompt-origin.mjs +21 -0
- package/src/runtime/shared/provider-accounts.mjs +231 -59
- package/src/runtime/shared/provider-api-key.mjs +19 -8
- package/src/runtime/shared/provider-display-name.mjs +29 -0
- package/src/runtime/shared/provider-timeout-phase.mjs +2 -0
- package/src/runtime/shared/routine-trigger.d.mts +2 -0
- package/src/runtime/shared/routine-trigger.mjs +52 -0
- package/src/runtime/shared/sensitive-path-rules.d.mts +2 -10
- package/src/runtime/shared/sensitive-path-rules.mjs +19 -41
- package/src/runtime/shared/session-activity-signal.mjs +34 -1
- package/src/runtime/shared/session-sidebar-host.d.mts +1 -1
- package/src/runtime/shared/session-sidebar-host.mjs +7 -3
- package/src/runtime/shared/session-turn-prelude.mjs +18 -0
- package/src/runtime/shared/tool-execution-contract.mjs +2 -0
- package/src/runtime/shared/tool-result-summary.mjs +1 -14
- package/src/runtime/shared/transcript-metadata.mjs +38 -0
- package/src/runtime/shared/user-data-guard.mjs +30 -2
- package/src/runtime/shared/worktrees/activity-gate.mjs +12 -5
- package/src/runtime/shared/worktrees/test-support.mjs +9 -4
- package/src/runtime/terminal-bridge/client.mjs +10 -1
- package/src/runtime/visualize-bridge/client.mjs +27 -8
- package/src/runtime/visualize-bridge/tool-defs.mjs +22 -8
- package/src/runtime/visualize-bridge/tool-meta.d.mts +5 -0
- package/src/runtime/visualize-bridge/tool-meta.mjs +22 -0
- package/src/runtime/web-search/lib/fetch-pipeline.mjs +2 -2
- package/src/session-runtime/agent-home-delivery.mjs +486 -179
- package/src/session-runtime/agent-room-delivery.mjs +28 -1
- package/src/session-runtime/agent-task-sessions.mjs +561 -199
- package/src/session-runtime/boot/apis.mjs +9 -0
- package/src/session-runtime/boot/begin.mjs +3 -2
- package/src/session-runtime/boot/infrastructure.mjs +4 -0
- package/src/session-runtime/boot/providers.mjs +3 -0
- package/src/session-runtime/boot/tools.mjs +8 -1
- package/src/session-runtime/bot-chain.mjs +32 -0
- package/src/session-runtime/bot-status.mjs +7 -4
- package/src/session-runtime/channel-config-api.mjs +63 -17
- package/src/session-runtime/config-lifecycle.mjs +6 -0
- package/src/session-runtime/connector-merge-migration.mjs +305 -0
- package/src/session-runtime/internal-tool-executor/ask-user-tool.mjs +132 -28
- package/src/session-runtime/internal-tool-executor/bridge-tools.mjs +2 -0
- package/src/session-runtime/internal-tool-executor/reaction-tool.mjs +45 -0
- package/src/session-runtime/internal-tool-executor/workspace-tools.mjs +72 -5
- package/src/session-runtime/internal-tool-executor.mjs +4 -0
- package/src/session-runtime/lifecycle/inheritance.mjs +16 -3
- package/src/session-runtime/lifecycle/session-catalog.mjs +3 -1
- package/src/session-runtime/lifecycle/teardown/runtime-stops.mjs +50 -4
- package/src/session-runtime/lifecycle/teardown.mjs +23 -5
- package/src/session-runtime/mcp-glue/server-catalog.mjs +61 -3
- package/src/session-runtime/mcp-oauth-login.mjs +5 -3
- package/src/session-runtime/mixy-heartbeat.mjs +927 -0
- package/src/session-runtime/model-route/route-tuning.mjs +10 -6
- package/src/session-runtime/notification-bus.mjs +31 -117
- package/src/session-runtime/notification-completion-queue.mjs +54 -0
- package/src/session-runtime/notification-listeners.mjs +76 -0
- package/src/session-runtime/owned-automation-run.mjs +35 -13
- package/src/session-runtime/prewarm/channel-start.mjs +3 -7
- package/src/session-runtime/provider-auth/login.mjs +7 -0
- package/src/session-runtime/provider-models/row-hydration.mjs +13 -2
- package/src/session-runtime/resource-mcp-api.mjs +143 -224
- package/src/session-runtime/resource-mcp-connector-accounts.mjs +163 -0
- package/src/session-runtime/resource-mcp-oauth-api.mjs +217 -0
- package/src/session-runtime/resource-mcp-secrets.mjs +82 -0
- package/src/session-runtime/resource-mcp-servers.mjs +78 -0
- package/src/session-runtime/resource-mcp-store.mjs +49 -0
- package/src/session-runtime/routine-home-turn.mjs +33 -6
- package/src/session-runtime/routine-owner.mjs +40 -9
- package/src/session-runtime/schedule-session-run.mjs +7 -3
- package/src/session-runtime/services/agent-tool/close-flow.mjs +3 -0
- package/src/session-runtime/services/agent-tool/spawn-flow/spawn-plan.mjs +8 -12
- package/src/session-runtime/services/agent-tool/spawn-preset.mjs +7 -2
- package/src/session-runtime/services/agent-tool/tag-registry.mjs +1 -0
- package/src/session-runtime/services/agent-tool/worker-index/awaiting-user-sync.mjs +51 -0
- package/src/session-runtime/services/agent-tool/worker-index/disposal.mjs +55 -0
- package/src/session-runtime/services/agent-tool/worker-index/released-store.mjs +123 -0
- package/src/session-runtime/services/agent-tool/worker-index/restart-settlement.mjs +112 -0
- package/src/session-runtime/services/agent-tool/worker-index/row-reads.mjs +36 -0
- package/src/session-runtime/services/agent-tool/worker-index/row-shape.mjs +2 -0
- package/src/session-runtime/services/agent-tool/worker-index/row-store.mjs +17 -4
- package/src/session-runtime/services/agent-tool/worker-index/row-writes.mjs +58 -0
- package/src/session-runtime/services/agent-tool/worker-index.mjs +41 -241
- package/src/session-runtime/services/agent-tool/worker-rows.mjs +3 -0
- package/src/session-runtime/services/channel-admin.mjs +73 -31
- package/src/session-runtime/services/channel-session-router.mjs +4 -2
- package/src/session-runtime/services/channel-worker.mjs +1 -1
- package/src/session-runtime/services/provider-admin.mjs +128 -25
- package/src/session-runtime/services/schedule-access.mjs +14 -0
- package/src/session-runtime/services/seeds.mjs +0 -28
- package/src/session-runtime/services/usage-dashboard-model.mjs +1 -0
- package/src/session-runtime/session-compaction-sync.mjs +27 -0
- package/src/session-runtime/session-create/session-options.mjs +8 -4
- package/src/session-runtime/settings-builtin-tools-api.mjs +11 -0
- package/src/session-runtime/settings-compaction-api.mjs +2 -40
- package/src/session-runtime/settings-system-api.mjs +28 -1
- package/src/session-runtime/setup-tool/agent-settings-actions.mjs +65 -0
- package/src/session-runtime/setup-tool/executor.mjs +187 -29
- package/src/session-runtime/setup-tool/extended-actions.mjs +33 -2
- package/src/session-runtime/setup-tool/session-route-actions.mjs +152 -0
- package/src/session-runtime/setup-tool/settings-contract.mjs +68 -6
- package/src/session-runtime/setup-tool/tool-defs.mjs +92 -6
- package/src/session-runtime/task-session-route.mjs +99 -0
- package/src/session-runtime/tool-policy/agent-profile-fold.mjs +17 -13
- package/src/session-runtime/tool-policy/policy-stage.mjs +21 -4
- package/src/session-runtime/tool-policy/surface-helpers.mjs +2 -3
- package/src/session-runtime/tool-surface.mjs +4 -0
- package/src/session-runtime/turn/turn-run/ask-callbacks.mjs +1 -0
- package/src/session-runtime/workflow-agents-api/agent-editor.mjs +98 -21
- package/src/session-runtime/workflow-agents-api/style-and-mode.mjs +4 -0
- package/src/session-runtime/workflow-agents-api/workflow-packs.mjs +6 -1
- package/src/session-runtime/workspace/transitions.mjs +10 -2
- package/src/standalone/channel-transport/client-registry/client-records.mjs +65 -0
- package/src/standalone/channel-transport/client-registry/client-registration.mjs +118 -0
- package/src/standalone/channel-transport/client-registry.mjs +24 -163
- package/src/standalone/channel-transport.mjs +1 -1
- package/src/standalone/daemon-channels-loader.mjs +10 -8
- package/src/standalone/daemon-session-control/list-page.mjs +72 -0
- package/src/standalone/daemon-session-control/request.mjs +51 -0
- package/src/standalone/daemon-session-control/room-host.mjs +57 -0
- package/src/standalone/daemon-session-control/route-control.mjs +48 -0
- package/src/standalone/daemon-session-control/send-turn.mjs +60 -0
- package/src/standalone/daemon-session-control/worktree-control.mjs +40 -0
- package/src/standalone/daemon-session-control.mjs +161 -157
- package/src/standalone/daemon.mjs +33 -2
- package/src/standalone/session-client.mjs +7 -3
- package/src/standalone/session-protocol.mjs +10 -0
- package/src/standalone/session-service/agent-tree/agent-cancel.mjs +6 -2
- package/src/standalone/session-service/agent-tree/agent-child.mjs +1 -0
- package/src/standalone/session-service/quota-resume.mjs +3 -0
- package/src/standalone/session-service/retention/busy.mjs +65 -1
- package/src/standalone/session-service/session-calls/session-action.mjs +23 -2
- package/src/standalone/session-service/session-calls/session-turns.mjs +6 -0
- package/src/standalone/session-service/session-calls/session-views.mjs +8 -0
- package/src/standalone/session-service.mjs +13 -0
- package/src/standalone/session-tool-output.mjs +21 -10
- package/src/tui/app/model-options.mjs +4 -27
- package/src/tui/dist/index.mjs +81 -65
- package/src/tui/session/agent-job-feed/model-notification.mjs +3 -0
- package/src/tui/session/initial-state.mjs +3 -0
- package/src/tui/session/notice-surface.mjs +23 -1
- package/src/tui/session/queue-helpers.mjs +3 -42
- package/src/tui/session/session-api/integrations.mjs +1 -0
- package/src/tui/session/session-api/lifecycle.mjs +8 -0
- package/src/tui/session/session-api/routes-agent-definitions.mjs +14 -2
- package/src/tui/session/session-api/routes-write.mjs +5 -1
- package/src/tui/session/session-api/routes.mjs +1 -0
- package/src/tui/session/session-api/settings.mjs +30 -2
- package/src/tui/session/session-flow/auto-clear.mjs +4 -33
- package/src/tui/session/session-flow/drain-closed-queue.mjs +38 -0
- package/src/tui/session/session-flow/drain-enqueue.mjs +86 -0
- package/src/tui/session/session-flow/drain-retry.mjs +35 -0
- package/src/tui/session/session-flow/drain.mjs +118 -149
- package/src/tui/session/session-flow/queue/entry-shape.mjs +5 -0
- package/src/tui/session/session-flow/queue/take-entries.mjs +5 -2
- package/src/tui/session/tool-approval-delegation.mjs +64 -0
- package/src/tui/session/tool-approval-request.mjs +39 -0
- package/src/tui/session/tool-approval-settle.mjs +62 -0
- package/src/tui/session/tool-approval.mjs +69 -115
- package/src/tui/session/tui-steering-persist.mjs +8 -1
- package/src/tui/session/turn-stream.mjs +36 -3
- package/src/tui/session/turn.mjs +23 -2
- package/src/vendor/statusline/bin/statusline-route.mjs +1 -219
- package/src/vendor/statusline/src/gateway/route-meta.mjs +35 -13
- package/scripts/code-graph-description-contract.mjs +0 -188
- 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
|
-
|
|
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
|
-
|
|
125
|
-
|
|
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(
|
|
139
|
-
this.
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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.
|
|
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
|
|
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,
|
|
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
|
|
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,
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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,
|
|
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
|
|
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
|
|
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
|
-
|
|
581
|
-
|
|
582
|
-
|
|
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.
|
|
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
|
|
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,
|
|
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,
|
|
626
|
+
return await this.fireTimedPrompt(schedule, prompt, opts);
|
|
648
627
|
}
|
|
649
|
-
/**
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
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 (
|
|
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
|
|
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
|
|
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
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
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,
|
|
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,
|
|
164
|
-
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13
|
|
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]);
|