mixdog 1.3.5 → 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/NOTICE.md +2 -2
- package/README.md +2 -2
- package/package.json +2 -11
- package/scripts/daemon-stub.mjs +1 -1
- package/scripts/gate-local.mjs +4 -2
- 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 +7 -11
- package/src/agents/reviewer/AGENT.md +7 -11
- package/src/agents/reviewer/agent.json +1 -1
- package/src/defaults/shipped-history.json +17 -1
- package/src/defaults/skills/browser-use/SKILL.md +59 -11
- 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 +22 -0
- package/src/defaults/skills/setup/references/actions.md +39 -5
- package/src/defaults/skills/setup/references/surfaces.md +11 -2
- package/src/defaults/skills/visualize/SKILL.md +2 -1
- package/src/lib/rules-builder.cjs +10 -8
- package/src/rules/agent/AGENT.md +6 -6
- 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/title-completion.mjs +3 -1
- package/src/runtime/agent/orchestrator/agent-trace-format.mjs +3 -1
- package/src/runtime/agent/orchestrator/config.mjs +17 -1
- 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 +176 -72
- package/src/runtime/agent/orchestrator/mcp/client.mjs +19 -0
- package/src/runtime/agent/orchestrator/providers/account-pool.mjs +181 -133
- package/src/runtime/agent/orchestrator/providers/admission-scheduler.mjs +42 -6
- 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 +145 -66
- 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-transport.mjs +123 -102
- package/src/runtime/agent/orchestrator/providers/cache-warm-admission.mjs +90 -0
- package/src/runtime/agent/orchestrator/providers/cursor-wire-transport.mjs +25 -9
- package/src/runtime/agent/orchestrator/providers/cursor.mjs +22 -13
- package/src/runtime/agent/orchestrator/providers/lib/oauth-token-utils.mjs +17 -3
- 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 +2 -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/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 +26 -0
- 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/runtime-core/ask-user-tool-defs.mjs +12 -7
- 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/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.mjs +125 -30
- package/src/runtime/agent/orchestrator/session/cache-warm.mjs +792 -0
- package/src/runtime/agent/orchestrator/session/compact/runner.mjs +133 -22
- 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 +4 -1
- package/src/runtime/agent/orchestrator/session/human-gate/gate.mjs +11 -5
- package/src/runtime/agent/orchestrator/session/human-gate/outbound-policy.mjs +0 -2
- package/src/runtime/agent/orchestrator/session/human-gate/requests.mjs +36 -4
- package/src/runtime/agent/orchestrator/session/human-gate/store.mjs +1 -1
- 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 +3 -1
- package/src/runtime/agent/orchestrator/session/loop/no-tool-turn.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/loop/route-fallback.mjs +17 -3
- package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +7 -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 +3 -2
- 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 +12 -3
- 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-execute.mjs +133 -90
- 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/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/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.mjs +36 -9
- 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-rows.mjs +8 -6
- 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/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/transcript-restore/restore.mjs +18 -1
- 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/scheduler.mjs +33 -147
- package/src/runtime/channels/lib/schedules-db.mjs +34 -11
- package/src/runtime/channels/lib/status-snapshot.mjs +1 -1
- package/src/runtime/channels/lib/tool-dispatch.mjs +1 -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 +10 -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/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/bounded-download.mjs +8 -3
- package/src/runtime/shared/config.mjs +2 -13
- package/src/runtime/shared/connector-accounts.mjs +20 -10
- package/src/runtime/shared/connector-catalog.d.mts +29 -0
- package/src/runtime/shared/connector-catalog.mjs +150 -0
- 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/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/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/usage-rollup.mjs +1 -1
- 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 +7 -1
- 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 +10 -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-result-summary.mjs +1 -14
- 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 +448 -185
- package/src/session-runtime/agent-task-sessions.mjs +329 -180
- package/src/session-runtime/boot/apis.mjs +9 -0
- 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 +6 -1
- 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/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 +55 -3
- package/src/session-runtime/mcp-oauth-login.mjs +5 -3
- package/src/session-runtime/mixy-heartbeat.mjs +153 -33
- 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 +2 -1
- package/src/session-runtime/prewarm/channel-start.mjs +3 -7
- 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-owner.mjs +40 -9
- package/src/session-runtime/services/agent-tool/close-flow.mjs +3 -0
- 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 +7 -13
- package/src/session-runtime/services/channel-session-router.mjs +4 -2
- package/src/session-runtime/services/seeds.mjs +0 -28
- package/src/session-runtime/session-compaction-sync.mjs +27 -0
- package/src/session-runtime/session-create/session-options.mjs +1 -0
- 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 +13 -1
- package/src/session-runtime/setup-tool/agent-settings-actions.mjs +65 -0
- package/src/session-runtime/setup-tool/executor.mjs +145 -22
- package/src/session-runtime/setup-tool/extended-actions.mjs +8 -3
- package/src/session-runtime/setup-tool/session-route-actions.mjs +152 -0
- package/src/session-runtime/setup-tool/settings-contract.mjs +4 -3
- package/src/session-runtime/setup-tool/tool-defs.mjs +64 -5
- package/src/session-runtime/tool-policy/agent-profile-fold.mjs +17 -13
- package/src/session-runtime/tool-policy/policy-stage.mjs +16 -2
- package/src/session-runtime/turn/turn-run/ask-callbacks.mjs +1 -0
- package/src/session-runtime/workflow-agents-api/style-and-mode.mjs +4 -0
- 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 +124 -188
- package/src/standalone/daemon.mjs +26 -3
- package/src/standalone/session-client.mjs +7 -3
- package/src/standalone/session-protocol.mjs +5 -0
- 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-views.mjs +8 -0
- package/src/standalone/session-tool-output.mjs +15 -7
- package/src/tui/app/model-options.mjs +3 -28
- package/src/tui/dist/index.mjs +58 -64
- 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 +7 -1
- package/src/tui/session/queue-helpers.mjs +3 -42
- 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 +17 -1
- 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 +19 -2
- package/src/tui/session/turn.mjs +1 -0
- 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
|
@@ -1,15 +1,13 @@
|
|
|
1
|
-
// Owned-runtime lifecycle:
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
// semantics are preserved.
|
|
1
|
+
// Owned-runtime lifecycle: bridge activation, automation runtime (scheduler +
|
|
2
|
+
// webhook/event runtime) and config hot-reload. The phases live under
|
|
3
|
+
// owned-runtime/ and share one explicit state object; config /
|
|
4
|
+
// bridgeRuntimeConnected / webhookServer / eventPipeline stay on the worker
|
|
5
|
+
// (get/set) so file-level reference semantics are preserved.
|
|
7
6
|
import { createOwnedRuntimeState } from './owned-runtime/state.mjs';
|
|
8
7
|
import { createAutomationServices } from './owned-runtime/automation-services.mjs';
|
|
9
8
|
import { createAutomationStarter } from './owned-runtime/automation-start.mjs';
|
|
10
9
|
import { createOwnedStarter } from './owned-runtime/owned-start.mjs';
|
|
11
10
|
import { createOwnedStopper } from './owned-runtime/owned-stop.mjs';
|
|
12
|
-
import { createOwnershipRefresh } from './owned-runtime/ownership-refresh.mjs';
|
|
13
11
|
import { createConfigReloader } from './owned-runtime/config-reload.mjs';
|
|
14
12
|
|
|
15
13
|
export function createOwnedRuntime(deps) {
|
|
@@ -26,29 +24,14 @@ export function createOwnedRuntime(deps) {
|
|
|
26
24
|
...deps,
|
|
27
25
|
state,
|
|
28
26
|
services,
|
|
29
|
-
startAutomationRuntime,
|
|
30
27
|
notifyRemoteAcquired,
|
|
31
28
|
});
|
|
32
29
|
const stopOwnedRuntime = createOwnedStopper({ ...deps, state, services });
|
|
33
|
-
const {
|
|
34
|
-
...deps,
|
|
35
|
-
state,
|
|
36
|
-
startOwnedRuntime,
|
|
37
|
-
stopOwnedRuntime,
|
|
38
|
-
});
|
|
39
|
-
const reloadRuntimeConfig = createConfigReloader({
|
|
40
|
-
...deps,
|
|
41
|
-
state,
|
|
42
|
-
services,
|
|
43
|
-
stopOwnedRuntime,
|
|
44
|
-
refreshBridgeOwnershipSafe,
|
|
45
|
-
});
|
|
30
|
+
const reloadRuntimeConfig = createConfigReloader({ ...deps, state, services });
|
|
46
31
|
return {
|
|
47
32
|
startAutomationRuntime,
|
|
48
33
|
startOwnedRuntime,
|
|
49
34
|
stopOwnedRuntime,
|
|
50
|
-
refreshBridgeOwnership,
|
|
51
|
-
refreshBridgeOwnershipSafe,
|
|
52
35
|
reloadRuntimeConfig,
|
|
53
36
|
notifyRemoteAcquired,
|
|
54
37
|
};
|
|
@@ -28,8 +28,8 @@ function setChannelNotifySink(fn) {
|
|
|
28
28
|
function createParentBridge({ getInstanceId, ipcProcess = process }) {
|
|
29
29
|
function sendNotifyToParent(method, params) {
|
|
30
30
|
// The channel schema requires meta: Record<string,string>.
|
|
31
|
-
// Coerce every meta value to string so a non-string (e.g. a
|
|
32
|
-
//
|
|
31
|
+
// Coerce every meta value to string so a non-string (e.g. a numeric
|
|
32
|
+
// meta field) can't fail zod and silently drop the notify.
|
|
33
33
|
// silent_to_agent stays boolean — an internal routing flag the daemon
|
|
34
34
|
// router / agentNotify consume (=== true) before the zod boundary.
|
|
35
35
|
const outParams = normalizeChannelNotifyParams(method, params);
|
|
@@ -81,9 +81,9 @@ function activeInstanceStaleReason(state) {
|
|
|
81
81
|
// NOTE: ownership is no longer decided here — the machine-global channels
|
|
82
82
|
// daemon (singleton-owner lock in src/standalone) is the authority. This
|
|
83
83
|
// PID-death check only marks a metadata advert whose owning process is gone so
|
|
84
|
-
// refreshActiveInstance can avoid preserving its owner fields.
|
|
85
|
-
// ui_heartbeat_at staleness branch
|
|
86
|
-
//
|
|
84
|
+
// refreshActiveInstance can avoid preserving its owner fields. There is no
|
|
85
|
+
// ui_heartbeat_at staleness branch: its false-stale eviction wrongly dropped
|
|
86
|
+
// live owners.
|
|
87
87
|
return null;
|
|
88
88
|
}
|
|
89
89
|
function buildRuntimeIdentity() {
|
|
@@ -126,11 +126,8 @@ function readActiveInstance() {
|
|
|
126
126
|
/** Caller-supplied advert fields, in the order every writer publishes them. */
|
|
127
127
|
function metaFields(meta) {
|
|
128
128
|
return {
|
|
129
|
-
...(meta?.channelId ? { channelId: meta.channelId } : {}),
|
|
130
129
|
...(meta?.transcriptPath ? { transcriptPath: meta.transcriptPath } : {}),
|
|
131
130
|
...(meta?.httpPort ? { httpPort: meta.httpPort } : {}),
|
|
132
|
-
...Object.fromEntries(Object.entries(meta || {}).filter(([k]) => k.startsWith('gateway_'))),
|
|
133
|
-
...(typeof meta?.providerReady === 'boolean' ? { providerReady: meta.providerReady } : {}),
|
|
134
131
|
};
|
|
135
132
|
}
|
|
136
133
|
function buildActiveInstanceState(instanceId, meta) {
|
|
@@ -143,41 +140,6 @@ function buildActiveInstanceState(instanceId, meta) {
|
|
|
143
140
|
...metaFields(meta),
|
|
144
141
|
};
|
|
145
142
|
}
|
|
146
|
-
function isProcessAlive(pid) {
|
|
147
|
-
try {
|
|
148
|
-
process.kill(pid, 0);
|
|
149
|
-
return true;
|
|
150
|
-
} catch (e) {
|
|
151
|
-
return !(e && e.code === 'ESRCH');
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
// gateway_* fields are preserved while the gateway owner is alive: the gateway
|
|
155
|
-
// child advertises itself independently, and active-owner heartbeats must not
|
|
156
|
-
// erase that discovery record while its owning server-main process is still
|
|
157
|
-
// alive. Writes into `preservedExtra` (and clears from `next`).
|
|
158
|
-
function preserveGatewayFields(prev, meta, identity, preservedExtra, next) {
|
|
159
|
-
const prevGatewayServerPid = parsePositivePid(prev?.gateway_server_pid);
|
|
160
|
-
const prevGatewayOwnerAlive = prevGatewayServerPid !== null && isProcessAlive(prevGatewayServerPid);
|
|
161
|
-
const sameGatewayAdvertiser =
|
|
162
|
-
prevGatewayServerPid !== null && identity.server_pid !== null && prevGatewayServerPid === identity.server_pid;
|
|
163
|
-
if (!(sameGatewayAdvertiser || prevGatewayOwnerAlive)) return;
|
|
164
|
-
if (!prev || !Object.hasOwn(prev, 'gateway_port')) return;
|
|
165
|
-
for (const [key, value] of Object.entries(prev)) {
|
|
166
|
-
if (key.startsWith('gateway_')) preservedExtra[key] = value;
|
|
167
|
-
}
|
|
168
|
-
preservedExtra.gateway_server_pid = prevGatewayServerPid;
|
|
169
|
-
// Clear session-scoped gateway metrics when the transcript changes — a new
|
|
170
|
-
// session must not inherit the previous session's context usage before the
|
|
171
|
-
// gateway re-advertises.
|
|
172
|
-
const nonEmptyString = (value) => (typeof value === 'string' && value ? value : null);
|
|
173
|
-
const metricTranscript = nonEmptyString(prev.gateway_transcript_path) ?? nonEmptyString(prev.transcriptPath);
|
|
174
|
-
if (typeof meta?.transcriptPath === 'string' && meta.transcriptPath && metricTranscript !== meta.transcriptPath) {
|
|
175
|
-
delete preservedExtra.gateway_context_used_pct;
|
|
176
|
-
delete preservedExtra.gateway_last_usage;
|
|
177
|
-
delete next.gateway_context_used_pct;
|
|
178
|
-
delete next.gateway_last_usage;
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
143
|
function refreshActiveInstance(instanceId, meta, options) {
|
|
182
144
|
ensureRuntimeDirs();
|
|
183
145
|
// Periodic refresh/heartbeat callers pass options.timeoutMs:0 (try-once) so
|
|
@@ -237,7 +199,6 @@ function refreshActiveInstance(instanceId, meta, options) {
|
|
|
237
199
|
const preservedExtra = Object.fromEntries(
|
|
238
200
|
Object.entries(prevForPreserve ?? {}).filter(([k]) => k.startsWith('pg_'))
|
|
239
201
|
);
|
|
240
|
-
preserveGatewayFields(prevForPreserve, meta, identity, preservedExtra, next);
|
|
241
202
|
return { ...preservedExtra, ...next };
|
|
242
203
|
},
|
|
243
204
|
writeOpts
|
|
@@ -354,7 +315,7 @@ function cleanupInstanceRuntimeFiles(instanceId) {
|
|
|
354
315
|
for (const target of targets) {
|
|
355
316
|
removeFileIfExists(target);
|
|
356
317
|
}
|
|
357
|
-
// Remove artifacts left by pre-standalone
|
|
318
|
+
// Remove per-instance permission artifacts left by pre-standalone builds.
|
|
358
319
|
forEachFile(RUNTIME_ROOT, (fullPath, file) => {
|
|
359
320
|
if (file.startsWith(`perm-${sanitize(instanceId)}-`)) {
|
|
360
321
|
removeFileIfExists(fullPath);
|
|
@@ -115,8 +115,7 @@ function releaseSchedulerRuntime(scheduler) {
|
|
|
115
115
|
releaseOwnedSchedulerLock();
|
|
116
116
|
}
|
|
117
117
|
class Scheduler {
|
|
118
|
-
|
|
119
|
-
interactive;
|
|
118
|
+
schedules;
|
|
120
119
|
promptsDir;
|
|
121
120
|
tickTimer = null;
|
|
122
121
|
lastFired = /* @__PURE__ */ new Map();
|
|
@@ -124,65 +123,28 @@ class Scheduler {
|
|
|
124
123
|
running = /* @__PURE__ */ new Set();
|
|
125
124
|
// Names whose row is being renamed: no fire starts under them meanwhile.
|
|
126
125
|
renaming = /* @__PURE__ */ new Set();
|
|
127
|
-
injectFn = null;
|
|
128
|
-
injectReadyFn = null;
|
|
129
|
-
pendingCheck = null;
|
|
130
126
|
// Injected by the host: (schedule, { prompt }) => Promise<{ result }>.
|
|
131
127
|
scheduleRunner = null;
|
|
132
|
-
// Activity tracking
|
|
133
|
-
lastActivity = 0;
|
|
134
|
-
// timestamp of last inbound message
|
|
135
128
|
deferred = /* @__PURE__ */ new Map();
|
|
136
129
|
// name -> deferred-until timestamp
|
|
137
130
|
cronJobs = /* @__PURE__ */ new Map();
|
|
138
131
|
// name -> node-cron ScheduledTask for cron-expression entries
|
|
139
132
|
oneShotTimers = /* @__PURE__ */ new Map();
|
|
140
133
|
// name -> setTimeout handle for when_at one-shot entries
|
|
141
|
-
constructor(
|
|
142
|
-
this.
|
|
143
|
-
this.interactive = interactive.filter((s) => s.enabled !== false);
|
|
134
|
+
constructor(schedules) {
|
|
135
|
+
this.schedules = schedules.filter((s) => s.enabled !== false);
|
|
144
136
|
this.promptsDir = join(DATA_DIR, 'prompts');
|
|
145
|
-
for (const s of
|
|
137
|
+
for (const s of this.schedules) {
|
|
146
138
|
if (s.lastFiredAt) this.lastFired.set(s.name, new Date(s.lastFiredAt).toISOString());
|
|
147
139
|
}
|
|
148
140
|
this.refreshSkipCache();
|
|
149
141
|
}
|
|
150
|
-
setInjectHandler(fn) {
|
|
151
|
-
this.injectFn = fn;
|
|
152
|
-
}
|
|
153
|
-
/** Probe for a live Lead bridge seat. When set, the interactive
|
|
154
|
-
* inject only fires while it reports true; otherwise the visible-session
|
|
155
|
-
* fallback keeps the fire from being lost. Unset → inject when wired. */
|
|
156
|
-
setInjectReadyCheck(fn) {
|
|
157
|
-
this.injectReadyFn = typeof fn === 'function' ? fn : null;
|
|
158
|
-
}
|
|
159
|
-
injectReady() {
|
|
160
|
-
if (!this.injectReadyFn) return true;
|
|
161
|
-
try {
|
|
162
|
-
return !!this.injectReadyFn();
|
|
163
|
-
} catch {
|
|
164
|
-
return false;
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
setPendingCheck(fn) {
|
|
168
|
-
this.pendingCheck = typeof fn === 'function' ? fn : null;
|
|
169
|
-
}
|
|
170
142
|
setScheduleRunner(fn) {
|
|
171
143
|
this.scheduleRunner = typeof fn === 'function' ? fn : null;
|
|
172
144
|
}
|
|
173
|
-
|
|
174
|
-
this.lastActivity = Date.now();
|
|
175
|
-
}
|
|
176
|
-
/** Every loaded schedule with its routing type, non-interactive first. */
|
|
177
|
-
typedSchedules() {
|
|
178
|
-
return [
|
|
179
|
-
...this.nonInteractive.map((schedule) => ({ schedule, type: 'non-interactive' })),
|
|
180
|
-
...this.interactive.map((schedule) => ({ schedule, type: 'interactive' })),
|
|
181
|
-
];
|
|
182
|
-
}
|
|
183
|
-
/** Find a loaded schedule def by name (either routing bucket). */
|
|
145
|
+
/** Find a loaded schedule def by name. */
|
|
184
146
|
findSchedule(name) {
|
|
185
|
-
return
|
|
147
|
+
return this.schedules.find((s) => s.name === name) ?? null;
|
|
186
148
|
}
|
|
187
149
|
/** Rebuild the in-memory deferred cache (used by the status snapshot) from
|
|
188
150
|
* the loaded rows' deferred_until values. Called on construct + reload so
|
|
@@ -190,7 +152,7 @@ class Scheduler {
|
|
|
190
152
|
refreshSkipCache() {
|
|
191
153
|
this.deferred.clear();
|
|
192
154
|
const now = Date.now();
|
|
193
|
-
for (const s of
|
|
155
|
+
for (const s of this.schedules) {
|
|
194
156
|
const until = s.deferredUntil ? new Date(s.deferredUntil).getTime() : 0;
|
|
195
157
|
if (until > now) this.deferred.set(s.name, until);
|
|
196
158
|
}
|
|
@@ -237,56 +199,6 @@ class Scheduler {
|
|
|
237
199
|
if (skippedUntil && now < skippedUntil) until = Math.max(until, skippedUntil);
|
|
238
200
|
return until;
|
|
239
201
|
}
|
|
240
|
-
/** Get current session activity state.
|
|
241
|
-
* Returns { lastActivityMs, pendingWork } — callers apply their own
|
|
242
|
-
* thresholds. pendingWork is true when pendingCheck() reports work in
|
|
243
|
-
* flight. lastActivityMs is 0 when no activity has been recorded. */
|
|
244
|
-
getSessionState() {
|
|
245
|
-
let pendingWork = false;
|
|
246
|
-
try {
|
|
247
|
-
if (this.pendingCheck) pendingWork = !!this.pendingCheck();
|
|
248
|
-
} catch {
|
|
249
|
-
/* probe failure is not fatal */
|
|
250
|
-
}
|
|
251
|
-
return { lastActivityMs: this.lastActivity, pendingWork };
|
|
252
|
-
}
|
|
253
|
-
/** Returns true when the session is considered idle (no pending work and
|
|
254
|
-
* lastActivityMs is either 0 or older than the given threshold).
|
|
255
|
-
* threshold defaults to 15 minutes but callers should pass their own. */
|
|
256
|
-
isSessionIdle(thresholdMs = 15 * 6e4) {
|
|
257
|
-
const { lastActivityMs, pendingWork } = this.getSessionState();
|
|
258
|
-
if (pendingWork) return false;
|
|
259
|
-
if (lastActivityMs === 0) return true;
|
|
260
|
-
return Date.now() - lastActivityMs >= thresholdMs;
|
|
261
|
-
}
|
|
262
|
-
/** Get time context for prompt enrichment */
|
|
263
|
-
getTimeContext() {
|
|
264
|
-
const now = /* @__PURE__ */ new Date();
|
|
265
|
-
const days = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
|
|
266
|
-
const dow = now.getDay();
|
|
267
|
-
return {
|
|
268
|
-
hour: now.getHours(),
|
|
269
|
-
minute: now.getMinutes(),
|
|
270
|
-
dayOfWeek: days[dow],
|
|
271
|
-
isWeekend: dow === 0 || dow === 6,
|
|
272
|
-
};
|
|
273
|
-
}
|
|
274
|
-
/** Wrap prompt with session context metadata */
|
|
275
|
-
wrapPrompt(name, prompt, type) {
|
|
276
|
-
const { lastActivityMs, pendingWork } = this.getSessionState();
|
|
277
|
-
let state = 'recent';
|
|
278
|
-
if (pendingWork) state = 'active';
|
|
279
|
-
else if (lastActivityMs === 0) state = 'idle';
|
|
280
|
-
const time = this.getTimeContext();
|
|
281
|
-
const header = [
|
|
282
|
-
`[schedule: ${name} | type: ${type} | session: ${state}]`,
|
|
283
|
-
`[time: ${time.dayOfWeek} ${String(time.hour).padStart(2, '0')}:${String(time.minute).padStart(2, '0')} | weekend: ${time.isWeekend}]`,
|
|
284
|
-
`Before starting any work, briefly tell the user what you're about to do in one short sentence.`,
|
|
285
|
-
].join('\n');
|
|
286
|
-
return `${header}
|
|
287
|
-
|
|
288
|
-
${prompt}`;
|
|
289
|
-
}
|
|
290
202
|
static SCHEDULER_LOCK = join(resolveRuntimeRoot(), 'scheduler.lock');
|
|
291
203
|
static INSTANCE_UUID = randomUUID();
|
|
292
204
|
static _exitHookInstalled = false;
|
|
@@ -345,8 +257,7 @@ ${prompt}`;
|
|
|
345
257
|
|
|
346
258
|
start() {
|
|
347
259
|
if (this.tickTimer) return;
|
|
348
|
-
|
|
349
|
-
if (total === 0) {
|
|
260
|
+
if (this.schedules.length === 0) {
|
|
350
261
|
process.stderr.write('mixdog scheduler: no schedules configured\n');
|
|
351
262
|
return;
|
|
352
263
|
}
|
|
@@ -365,7 +276,7 @@ ${prompt}`;
|
|
|
365
276
|
Scheduler._exitHookInstalled = true;
|
|
366
277
|
process.on('exit', releaseOwnedSchedulerLock);
|
|
367
278
|
}
|
|
368
|
-
logSchedule(`${this.
|
|
279
|
+
logSchedule(`${this.schedules.length} schedules\n`);
|
|
369
280
|
this.registerCronJobs();
|
|
370
281
|
this.tick();
|
|
371
282
|
this.tickTimer = setInterval(() => this.tick(), TICK_INTERVAL);
|
|
@@ -373,14 +284,14 @@ ${prompt}`;
|
|
|
373
284
|
/** Register schedule entries. `when_cron` entries bind to node-cron (dow
|
|
374
285
|
* field covers the day guard); `when_at` entries arm a one-shot timer. */
|
|
375
286
|
registerCronJobs() {
|
|
376
|
-
for (const
|
|
287
|
+
for (const s of this.schedules) {
|
|
377
288
|
if (s.whenCron) {
|
|
378
289
|
if (!isCronExpression(s.whenCron)) {
|
|
379
290
|
process.stderr.write(`mixdog scheduler: invalid cron "${s.name}" (${s.whenCron}) — skipped\n`);
|
|
380
291
|
continue;
|
|
381
292
|
}
|
|
382
293
|
try {
|
|
383
|
-
const task = cron.schedule(s.whenCron, (ctx) => this.onCronFire(s,
|
|
294
|
+
const task = cron.schedule(s.whenCron, (ctx) => this.onCronFire(s, ctx?.date), {
|
|
384
295
|
timezone: s.timezone || undefined,
|
|
385
296
|
name: s.name,
|
|
386
297
|
});
|
|
@@ -397,13 +308,13 @@ ${prompt}`;
|
|
|
397
308
|
process.stderr.write(`mixdog scheduler: failed to register cron "${s.name}" (${s.whenCron}): ${err}\n`);
|
|
398
309
|
}
|
|
399
310
|
} else if (s.whenAt) {
|
|
400
|
-
this.armOneShot(s
|
|
311
|
+
this.armOneShot(s);
|
|
401
312
|
}
|
|
402
313
|
}
|
|
403
314
|
}
|
|
404
315
|
/** Fire path for a cron-triggered entry. node-cron's dow field covers the
|
|
405
316
|
* day guard, so there is no separate days filter. Persists last_fired_at. */
|
|
406
|
-
async onCronFire(schedule,
|
|
317
|
+
async onCronFire(schedule, scheduledDate = null) {
|
|
407
318
|
const startedAt = /* @__PURE__ */ new Date();
|
|
408
319
|
const scheduledAt =
|
|
409
320
|
scheduledDate instanceof Date && Number.isFinite(scheduledDate.getTime()) ? scheduledDate : startedAt;
|
|
@@ -438,7 +349,7 @@ ${prompt}`;
|
|
|
438
349
|
this.recordRefusal(schedule, refusal);
|
|
439
350
|
return;
|
|
440
351
|
}
|
|
441
|
-
const fired = await this.fireTimed(schedule,
|
|
352
|
+
const fired = await this.fireTimed(schedule, { awaitDispatch: true, reserved: true });
|
|
442
353
|
// An overlap skip is recorded as a skip, never as a failed run.
|
|
443
354
|
if (!fired && skippedSince(schedule, startedAt)) return;
|
|
444
355
|
if (!fired) {
|
|
@@ -481,7 +392,7 @@ ${prompt}`;
|
|
|
481
392
|
* entries (misfire recovery at start) fire immediately. setTimeout's
|
|
482
393
|
* ~24.8-day (2^31-1 ms) ceiling is handled by re-arming for far-future
|
|
483
394
|
* instants instead of firing early. */
|
|
484
|
-
armOneShot(schedule
|
|
395
|
+
armOneShot(schedule) {
|
|
485
396
|
const fireAt = new Date(schedule.whenAt).getTime();
|
|
486
397
|
if (!Number.isFinite(fireAt)) {
|
|
487
398
|
process.stderr.write(`mixdog scheduler: invalid when_at for "${schedule.name}" (${schedule.whenAt}) — skipped\n`);
|
|
@@ -489,11 +400,11 @@ ${prompt}`;
|
|
|
489
400
|
}
|
|
490
401
|
const delay = fireAt - Date.now();
|
|
491
402
|
if (delay > MAX_TIMER_DELAY_MS) {
|
|
492
|
-
const timer = setTimeout(() => this.armOneShot(schedule
|
|
403
|
+
const timer = setTimeout(() => this.armOneShot(schedule), MAX_TIMER_DELAY_MS);
|
|
493
404
|
this.oneShotTimers.set(schedule.name, timer);
|
|
494
405
|
return;
|
|
495
406
|
}
|
|
496
|
-
const timer = setTimeout(() => this.fireOneShot(schedule
|
|
407
|
+
const timer = setTimeout(() => this.fireOneShot(schedule), Math.max(0, delay));
|
|
497
408
|
this.oneShotTimers.set(schedule.name, timer);
|
|
498
409
|
schedule.nextFireAt = new Date(fireAt).toISOString();
|
|
499
410
|
void setNextFire(schedule.name, new Date(fireAt)).catch(() => {});
|
|
@@ -521,7 +432,7 @@ ${prompt}`;
|
|
|
521
432
|
* Idempotent: if last_fired_at is already set (a prior fire whose markDone
|
|
522
433
|
* failed left the row 'active'), do NOT fire again — only retry markDone so
|
|
523
434
|
* the stuck-active entry stops re-arming/misfiring. */
|
|
524
|
-
async fireOneShot(schedule
|
|
435
|
+
async fireOneShot(schedule) {
|
|
525
436
|
this.oneShotTimers.delete(schedule.name);
|
|
526
437
|
const now = /* @__PURE__ */ new Date();
|
|
527
438
|
if (schedule.lastFiredAt) {
|
|
@@ -530,7 +441,7 @@ ${prompt}`;
|
|
|
530
441
|
return;
|
|
531
442
|
}
|
|
532
443
|
if (this.shouldSkip(schedule.name)) {
|
|
533
|
-
this.rearmDeferredOneShot(schedule
|
|
444
|
+
this.rearmDeferredOneShot(schedule);
|
|
534
445
|
return;
|
|
535
446
|
}
|
|
536
447
|
// Mark done ONLY after a successful fire. A false/throwing fireTimed
|
|
@@ -546,7 +457,7 @@ ${prompt}`;
|
|
|
546
457
|
// dispatch outcome — a truthy fireTimed alone (cron's fire-and-forget
|
|
547
458
|
// contract) would retire the entry even when schedulerLlm() later
|
|
548
459
|
// rejects. Only a resolved dispatch counts as a real fire here.
|
|
549
|
-
const fired = await this.fireTimed(schedule,
|
|
460
|
+
const fired = await this.fireTimed(schedule, { awaitDispatch: true, reserved });
|
|
550
461
|
if (!fired && skippedSince(schedule, now)) {
|
|
551
462
|
logSchedule(`one-shot "${schedule.name}" skipped (previous run in progress) — leaving pending for retry\n`);
|
|
552
463
|
return;
|
|
@@ -571,13 +482,13 @@ ${prompt}`;
|
|
|
571
482
|
* already elapsed and tickAsync is a no-op, so simply returning would mean
|
|
572
483
|
* it never fires until a restart. Re-arm for the deferral expiry (the
|
|
573
484
|
* later of when_at and deferred/skipped-until) so it fires on its own. */
|
|
574
|
-
rearmDeferredOneShot(schedule
|
|
485
|
+
rearmDeferredOneShot(schedule) {
|
|
575
486
|
const until = this.skipUntil(schedule.name);
|
|
576
487
|
const fireAt = new Date(schedule.whenAt).getTime();
|
|
577
488
|
const target = Math.max(Number.isFinite(fireAt) ? fireAt : 0, until);
|
|
578
489
|
const delay = Math.min(Math.max(target - Date.now(), 1), MAX_TIMER_DELAY_MS);
|
|
579
490
|
logSchedule(`one-shot "${schedule.name}" deferred/skipped — re-arming for ${new Date(target).toISOString()}\n`);
|
|
580
|
-
const timer = setTimeout(() => this.fireOneShot(schedule
|
|
491
|
+
const timer = setTimeout(() => this.fireOneShot(schedule), delay);
|
|
581
492
|
this.oneShotTimers.set(schedule.name, timer);
|
|
582
493
|
schedule.nextFireAt = new Date(target).toISOString();
|
|
583
494
|
void setNextFire(schedule.name, new Date(target)).catch(() => {});
|
|
@@ -643,15 +554,13 @@ ${prompt}`;
|
|
|
643
554
|
this.oneShotTimers.delete(name);
|
|
644
555
|
this.lastFired.delete(name);
|
|
645
556
|
this.deferred.delete(name);
|
|
646
|
-
this.
|
|
647
|
-
this.interactive = this.interactive.filter((s) => s.name !== name);
|
|
557
|
+
this.schedules = this.schedules.filter((s) => s.name !== name);
|
|
648
558
|
}
|
|
649
|
-
reloadConfig(
|
|
650
|
-
this.
|
|
651
|
-
this.interactive = interactive.filter((s) => s.enabled !== false);
|
|
559
|
+
reloadConfig(schedules, options = {}) {
|
|
560
|
+
this.schedules = schedules.filter((s) => s.enabled !== false);
|
|
652
561
|
this.promptsDir = join(DATA_DIR, 'prompts');
|
|
653
562
|
// A name that no longer exists must not keep (or later regain) a stamp.
|
|
654
|
-
const live = new Set(
|
|
563
|
+
const live = new Set(this.schedules.map((s) => s.name));
|
|
655
564
|
for (const name of this.lastFired.keys()) if (!live.has(name)) this.lastFired.delete(name);
|
|
656
565
|
// Defer/skip state is persisted (deferred_until / skipped_until) and
|
|
657
566
|
// re-read from the reloaded rows, so a reload no longer drops it.
|
|
@@ -664,10 +573,9 @@ ${prompt}`;
|
|
|
664
573
|
this.restart();
|
|
665
574
|
}
|
|
666
575
|
getStatus() {
|
|
667
|
-
return this.
|
|
576
|
+
return this.schedules.map((s) => ({
|
|
668
577
|
name: s.name,
|
|
669
578
|
time: s.whenCron ?? s.whenAt ?? null,
|
|
670
|
-
type,
|
|
671
579
|
running: this.running.has(s.name),
|
|
672
580
|
lastFired: this.lastFired.get(s.name) ?? s.lastFiredAt ?? null,
|
|
673
581
|
lastSuccess: s.lastSuccessAt ?? null,
|
|
@@ -679,14 +587,13 @@ ${prompt}`;
|
|
|
679
587
|
const timed = this.findSchedule(name);
|
|
680
588
|
if (timed) {
|
|
681
589
|
if (this.running.has(name)) return `"${name}" is already running`;
|
|
682
|
-
const isNonInteractive = this.nonInteractive.includes(timed);
|
|
683
590
|
const now = /* @__PURE__ */ new Date();
|
|
684
591
|
const hhmm = `${String(now.getHours()).padStart(2, '0')}:${String(now.getMinutes()).padStart(2, '0')}`;
|
|
685
592
|
const dateStr = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')}`;
|
|
686
593
|
// Match onCronFire: record lastFired only when fireTimed actually
|
|
687
594
|
// proceeds past its running/precondition guards (resolves truthy),
|
|
688
595
|
// and reflect a non-fire in the returned status.
|
|
689
|
-
const fired = await this.fireTimed(timed
|
|
596
|
+
const fired = await this.fireTimed(timed);
|
|
690
597
|
if (fired) {
|
|
691
598
|
this.lastFired.set(name, `${dateStr}T${hhmm}`);
|
|
692
599
|
return `triggered "${name}"`;
|
|
@@ -708,7 +615,7 @@ ${prompt}`;
|
|
|
708
615
|
// interval is kept for future cadence-driven work and lifecycle parity.
|
|
709
616
|
}
|
|
710
617
|
// ── Fire timed schedule ─────────────────────────────────────────────
|
|
711
|
-
async fireTimed(schedule,
|
|
618
|
+
async fireTimed(schedule, opts = {}) {
|
|
712
619
|
const prompt = this.resolvePrompt(schedule);
|
|
713
620
|
if (!prompt) {
|
|
714
621
|
process.stderr.write(`mixdog scheduler: prompt not found for "${schedule.name}"
|
|
@@ -716,7 +623,7 @@ ${prompt}`;
|
|
|
716
623
|
this.notifyFailure(schedule, 'prompt not found — fire skipped');
|
|
717
624
|
return false;
|
|
718
625
|
}
|
|
719
|
-
return await this.fireTimedPrompt(schedule,
|
|
626
|
+
return await this.fireTimedPrompt(schedule, prompt, opts);
|
|
720
627
|
}
|
|
721
628
|
/** Why a fire of `name` may not start now: 'renaming', 'running' or null. */
|
|
722
629
|
fireRefusal(name) {
|
|
@@ -739,14 +646,13 @@ ${prompt}`;
|
|
|
739
646
|
}
|
|
740
647
|
/** Fire a timed schedule with the given prompt content. `reserved`: the
|
|
741
648
|
* caller already holds the name in `running` and releases it itself. */
|
|
742
|
-
async fireTimedPrompt(schedule,
|
|
743
|
-
logSchedule(`firing ${schedule.name}
|
|
649
|
+
async fireTimedPrompt(schedule, prompt, { awaitDispatch = false, reserved = false } = {}) {
|
|
650
|
+
logSchedule(`firing ${schedule.name}\n`);
|
|
744
651
|
const refusal = reserved ? null : this.fireRefusal(schedule.name);
|
|
745
652
|
if (refusal) {
|
|
746
653
|
this.recordRefusal(schedule, refusal);
|
|
747
654
|
return false;
|
|
748
655
|
}
|
|
749
|
-
if (type === 'interactive' && this.injectIntoLead(schedule, type, prompt)) return true;
|
|
750
656
|
if (!reserved) this.running.add(schedule.name);
|
|
751
657
|
// Cron (recurring) keeps the fire-and-forget contract: return truthy now
|
|
752
658
|
// and swallow async failures. A one-shot (awaitDispatch) instead awaits
|
|
@@ -757,28 +663,8 @@ ${prompt}`;
|
|
|
757
663
|
if (awaitDispatch) return await dispatch;
|
|
758
664
|
return true;
|
|
759
665
|
}
|
|
760
|
-
/** Interactive schedules enqueue into the live Lead session's command
|
|
761
|
-
* queue as system-generated task notifications (priority 'later'),
|
|
762
|
-
* exactly like inbound channel messages — enqueue counts as the fire.
|
|
763
|
-
* False when no live Lead seat is attached or the inject fails, so the
|
|
764
|
-
* visible-session run remains the fallback and a fire is never lost. */
|
|
765
|
-
injectIntoLead(schedule, type, prompt) {
|
|
766
|
-
if (!this.injectFn || !this.injectReady()) return false;
|
|
767
|
-
try {
|
|
768
|
-
const wrapped = this.wrapPrompt(schedule.name, prompt, type);
|
|
769
|
-
this.injectFn('', `schedule:${schedule.name}`, ' ', { type: 'schedule', instruction: wrapped });
|
|
770
|
-
logSchedule(`${schedule.name}: injected into Lead session queue (interactive fire)\n`);
|
|
771
|
-
return true;
|
|
772
|
-
} catch (err) {
|
|
773
|
-
logSchedule(
|
|
774
|
-
`${schedule.name}: Lead inject failed (${err?.message || err}) — falling back to visible session run\n`
|
|
775
|
-
);
|
|
776
|
-
return false;
|
|
777
|
-
}
|
|
778
|
-
}
|
|
779
666
|
/** Fires run as VISIBLE schedule sessions (desktop Recent / TUI resume)
|
|
780
|
-
* via runScheduleSession
|
|
781
|
-
* header. Resolves true on success; a failure rejects for awaitDispatch
|
|
667
|
+
* via runScheduleSession. Resolves true on success; a failure rejects for awaitDispatch
|
|
782
668
|
* callers and resolves false (after a logged notice) for fire-and-forget
|
|
783
669
|
* cron fires. */
|
|
784
670
|
dispatchScheduleRun(schedule, prompt, awaitDispatch) {
|
|
@@ -26,8 +26,6 @@ CREATE TABLE IF NOT EXISTS scheduler.schedules (
|
|
|
26
26
|
when_at timestamptz,
|
|
27
27
|
when_cron text,
|
|
28
28
|
timezone text,
|
|
29
|
-
target text NOT NULL CHECK (target IN ('channel','session')),
|
|
30
|
-
channel_id text,
|
|
31
29
|
model text,
|
|
32
30
|
cwd text,
|
|
33
31
|
workflow text,
|
|
@@ -90,8 +88,6 @@ function rowToDef(row) {
|
|
|
90
88
|
whenAt: row.when_at,
|
|
91
89
|
whenCron: row.when_cron,
|
|
92
90
|
timezone: row.timezone,
|
|
93
|
-
target: row.target,
|
|
94
|
-
channelId: row.channel_id,
|
|
95
91
|
cwd: row.cwd,
|
|
96
92
|
attachments: row.attachments || null,
|
|
97
93
|
delivery: row.delivery || null,
|
|
@@ -115,8 +111,32 @@ function rowToDef(row) {
|
|
|
115
111
|
};
|
|
116
112
|
}
|
|
117
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
|
+
|
|
118
138
|
const COLS =
|
|
119
|
-
'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';
|
|
120
140
|
|
|
121
141
|
// ---------------------------------------------------------------------------
|
|
122
142
|
// Public API
|
|
@@ -148,8 +168,6 @@ export async function upsertSchedule(def, { dataDir } = {}) {
|
|
|
148
168
|
def.whenAt ?? null,
|
|
149
169
|
def.whenCron ?? null,
|
|
150
170
|
def.timezone ?? null,
|
|
151
|
-
def.target,
|
|
152
|
-
def.channelId ?? null,
|
|
153
171
|
def.cwd ?? null,
|
|
154
172
|
def.attachments ? JSON.stringify(def.attachments) : null,
|
|
155
173
|
def.delivery ?? null,
|
|
@@ -159,18 +177,23 @@ export async function upsertSchedule(def, { dataDir } = {}) {
|
|
|
159
177
|
def.nextFireAt ?? null,
|
|
160
178
|
def.ownerAgentId || 'lead',
|
|
161
179
|
];
|
|
180
|
+
let legacyTarget = '';
|
|
181
|
+
let legacyTargetValue = '';
|
|
182
|
+
if (await hasRequiredTargetColumn(db)) {
|
|
183
|
+
params.push('session');
|
|
184
|
+
legacyTarget = ', target';
|
|
185
|
+
legacyTargetValue = `,$${params.length}`;
|
|
186
|
+
}
|
|
162
187
|
const { rows } = await db.query(
|
|
163
188
|
`INSERT INTO scheduler.schedules
|
|
164
|
-
(name, description, when_at, when_cron, timezone,
|
|
165
|
-
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})
|
|
166
191
|
ON CONFLICT (name) DO UPDATE SET
|
|
167
192
|
owner_agent_id = EXCLUDED.owner_agent_id,
|
|
168
193
|
description = EXCLUDED.description,
|
|
169
194
|
when_at = EXCLUDED.when_at,
|
|
170
195
|
when_cron = EXCLUDED.when_cron,
|
|
171
196
|
timezone = EXCLUDED.timezone,
|
|
172
|
-
target = EXCLUDED.target,
|
|
173
|
-
channel_id = EXCLUDED.channel_id,
|
|
174
197
|
cwd = EXCLUDED.cwd,
|
|
175
198
|
attachments = EXCLUDED.attachments,
|
|
176
199
|
delivery = EXCLUDED.delivery,
|
|
@@ -57,7 +57,7 @@ function* armedScheduleFires(scheduler) {
|
|
|
57
57
|
const fireAt = cronTaskNextFireAt(task);
|
|
58
58
|
if (fireAt !== null) yield { name, fireAt, kind: 'cron' };
|
|
59
59
|
}
|
|
60
|
-
const defs =
|
|
60
|
+
const defs = scheduler.schedules || [];
|
|
61
61
|
for (const name of (scheduler.oneShotTimers || new Map()).keys()) {
|
|
62
62
|
if (scheduler.shouldSkip?.(name)) continue;
|
|
63
63
|
const def = defs.find((s) => s.name === name);
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
// Worker/HTTP runtime-lifecycle dispatch (activate_channel_bridge /
|
|
2
|
-
// reload_config).
|
|
3
|
-
// is deleted with Discord/Telegram; only the automation lifecycle remains.
|
|
2
|
+
// reload_config). Only the automation lifecycle is dispatched here.
|
|
4
3
|
// Lifecycle functions are threaded in as a bag of lazy getters so the module
|
|
5
4
|
// reads the live file-level references at call time.
|
|
6
5
|
import { scheduleErrorFields } from './schedule-errors.mjs';
|