elanous 0.2.18 → 0.2.20
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/apps/pwa/out/404/index.html +1 -1
- package/apps/pwa/out/404.html +1 -1
- package/apps/pwa/out/_next/static/chunks/{1307-30dff22c9e1f3073.js → 1307-77fad80cf54477c6.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/1612-f60c0c93ec773f81.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2116-6429c188eb846ab8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/224-e7be8f762fe74986.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2489-ff55a35b202c0f94.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2549-89c518a4c8ed4d00.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3857-995ea9f5ef33d71e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4144-fad095b48a1862ee.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5755-ea7ed88eb956f47b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5977-94ed6f78e9814b2b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7748-6e51bfe927019f5c.js +25 -0
- package/apps/pwa/out/_next/static/chunks/8401-35e94e9091f5eb8d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9247-c1753a059e7bac08.js +1 -0
- package/apps/pwa/out/_next/static/chunks/a9f2b54d-33c0689b70ed8cf3.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/approvals/{page-d92f7ee3994e84c7.js → page-c855c24de7db74fe.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/autopilot/{page-4733c967b56a4277.js → page-85b316bb4235980b.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/board/{page-d22fd849cc22f104.js → page-9b2b26a33cb2cd42.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/botlab/{page-bb742f995c6769e7.js → page-c9955fde67b1bcb5.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/bots/{page-02b073a2ac793b83.js → page-7a852956c74015a3.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/ceo/page-c5a5c621be0e04ae.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/chat/{page-8a9b3594bc23289b.js → page-4ba4abe096b6c2a8.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/consult/page-575d4a8b4f7becb2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/control/{page-308111abadf35670.js → page-f5d56ff5f45ad164.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/decisions/page-2260e9eef0357421.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/design-check/{page-5d97b2901e0d1ea6.js → page-7c058069c5e7fa63.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/editor/page-dc5da0b8a2edb8a4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/exec/{page-8c3d8e63ae2e1c98.js → page-466bf0ee16ad1432.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/field/{page-9b72a07d424701c6.js → page-a23bc71713bcc08f.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/inside/page-ed4a6f799f005d09.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/intake/{page-a572f58cd3ca5864.js → page-9cd565005b93aca6.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/layout-55f0f40c6db95f15.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/live/{page-be5ea965a07fbc41.js → page-474cb5888ea0e8f3.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/loops/page-35bfec2e631d3eae.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/market/{page-fa244bd146a00ae8.js → page-9ca21f9ad3208a61.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/{page-44d56d6391965a6a.js → page-f9ccb1b1adbea14d.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/morning/{page-83a700d4c754d802.js → page-ec102c9b8b691e37.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/{not-found-7b3170f374f77b9d.js → not-found-a9c2cfa8dda922a5.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-6da35144878feb6d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-f6055495e9ed7ea8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-25204694fa9984a5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-a60e035f151d84d3.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/outputs/page-189c587f36874538.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/page-c56634bde705a65d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/reflection/{page-1467047104141297.js → page-db03ae3589a81c9e.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-4a6463b97f0ad94f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/sessions/{page-102310930791197a.js → page-75bffecc9aa1004e.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-a8d2fe0c669e0de4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/{page-6ed0b73bae8351d8.js → page-31d5b875760bb62d.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-c26fa10aac6c21c4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/{page-43336055451f6e37.js → page-68793c6e10331d66.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/share/{page-3cc47dca0922ab9a.js → page-46d5201d2207a511.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/showroom/{page-a77ee355a2304ff0.js → page-d81c07a7b4f618ae.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/tasks/{page-b17cd46cf0e9b8cf.js → page-be1c4e8697f22360.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/term/page-cf0a4b4abead71ea.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/today/page-a4717e2c4bf7a28f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/trace/{page-7bf54c9bb9274af8.js → page-0fd895fab9b62c1b.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/vault/page-8b6c1454a8e8f306.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-c6e4364d9de8f717.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-0acc8e198f7b0dfb.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workspace/{page-134da468538baf48.js → page-34882c84f84021c5.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-0506a1e0e4bdb2f5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{webpack-c2782b330447ffb6.js → webpack-c260571513bf43c2.js} +1 -1
- package/apps/pwa/out/_next/static/css/{e99e3cff94ced78a.css → 599369d853c61df7.css} +1 -1
- package/apps/pwa/out/_next/static/css/c23d959d3ee15e57.css +1 -0
- package/apps/pwa/out/_next/static/css/d3427996da11e480.css +1 -0
- package/apps/pwa/out/approvals/index.html +1 -1
- package/apps/pwa/out/approvals/index.txt +14 -14
- package/apps/pwa/out/autopilot/index.html +1 -1
- package/apps/pwa/out/autopilot/index.txt +14 -14
- package/apps/pwa/out/board/index.html +1 -1
- package/apps/pwa/out/board/index.txt +14 -14
- package/apps/pwa/out/botlab/index.html +1 -1
- package/apps/pwa/out/botlab/index.txt +14 -14
- package/apps/pwa/out/bots/index.html +1 -1
- package/apps/pwa/out/bots/index.txt +14 -14
- package/apps/pwa/out/ceo/index.html +1 -1
- package/apps/pwa/out/ceo/index.txt +14 -14
- package/apps/pwa/out/chat/index.html +1 -1
- package/apps/pwa/out/chat/index.txt +14 -14
- package/apps/pwa/out/consult/index.html +1 -1
- package/apps/pwa/out/consult/index.txt +14 -14
- package/apps/pwa/out/control/index.html +1 -1
- package/apps/pwa/out/control/index.txt +14 -14
- package/apps/pwa/out/decisions/index.html +1 -1
- package/apps/pwa/out/decisions/index.txt +14 -14
- package/apps/pwa/out/design-check/index.html +1 -1
- package/apps/pwa/out/design-check/index.txt +14 -14
- package/apps/pwa/out/editor/index.html +1 -1
- package/apps/pwa/out/editor/index.txt +16 -15
- package/apps/pwa/out/exec/index.html +1 -1
- package/apps/pwa/out/exec/index.txt +14 -14
- package/apps/pwa/out/field/index.html +1 -1
- package/apps/pwa/out/field/index.txt +14 -14
- package/apps/pwa/out/healthz +1 -1
- package/apps/pwa/out/index.html +1 -1
- package/apps/pwa/out/index.txt +14 -14
- package/apps/pwa/out/inside/index.html +1 -1
- package/apps/pwa/out/inside/index.txt +16 -15
- package/apps/pwa/out/intake/index.html +1 -1
- package/apps/pwa/out/intake/index.txt +14 -14
- package/apps/pwa/out/live/index.html +1 -1
- package/apps/pwa/out/live/index.txt +14 -14
- package/apps/pwa/out/loops/index.html +1 -1
- package/apps/pwa/out/loops/index.txt +24 -23
- package/apps/pwa/out/market/index.html +1 -1
- package/apps/pwa/out/market/index.txt +14 -14
- package/apps/pwa/out/missions/placeholder/index.html +1 -1
- package/apps/pwa/out/missions/placeholder/index.txt +14 -14
- package/apps/pwa/out/morning/index.html +1 -1
- package/apps/pwa/out/morning/index.txt +14 -14
- package/apps/pwa/out/observatory/index.html +1 -1
- package/apps/pwa/out/observatory/index.txt +14 -14
- package/apps/pwa/out/ops/checklist/index.html +1 -1
- package/apps/pwa/out/ops/checklist/index.txt +14 -14
- package/apps/pwa/out/ops/release/index.html +1 -1
- package/apps/pwa/out/ops/release/index.txt +14 -14
- package/apps/pwa/out/ops/seats/index.html +1 -1
- package/apps/pwa/out/ops/seats/index.txt +14 -14
- package/apps/pwa/out/outputs/index.html +1 -1
- package/apps/pwa/out/outputs/index.txt +14 -14
- package/apps/pwa/out/reflection/index.html +1 -1
- package/apps/pwa/out/reflection/index.txt +15 -15
- package/apps/pwa/out/scheduler/index.html +1 -1
- package/apps/pwa/out/scheduler/index.txt +14 -14
- package/apps/pwa/out/sessions/index.html +1 -1
- package/apps/pwa/out/sessions/index.txt +14 -14
- package/apps/pwa/out/settings/devices/index.html +1 -1
- package/apps/pwa/out/settings/devices/index.txt +14 -14
- package/apps/pwa/out/settings/index.html +1 -1
- package/apps/pwa/out/settings/index.txt +14 -14
- package/apps/pwa/out/setup/done/index.html +1 -1
- package/apps/pwa/out/setup/done/index.txt +14 -14
- package/apps/pwa/out/setup/index.html +1 -1
- package/apps/pwa/out/setup/index.txt +14 -14
- package/apps/pwa/out/share/index.html +1 -1
- package/apps/pwa/out/share/index.txt +14 -14
- package/apps/pwa/out/showroom/index.html +1 -1
- package/apps/pwa/out/showroom/index.txt +14 -14
- package/apps/pwa/out/tasks/index.html +1 -1
- package/apps/pwa/out/tasks/index.txt +14 -14
- package/apps/pwa/out/term/index.html +1 -1
- package/apps/pwa/out/term/index.txt +14 -14
- package/apps/pwa/out/today/index.html +1 -1
- package/apps/pwa/out/today/index.txt +14 -14
- package/apps/pwa/out/trace/index.html +1 -1
- package/apps/pwa/out/trace/index.txt +14 -14
- package/apps/pwa/out/vault/index.html +1 -1
- package/apps/pwa/out/vault/index.txt +14 -14
- package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
- package/apps/pwa/out/workflows/chat-ui/index.txt +14 -14
- package/apps/pwa/out/workflows/index.html +1 -1
- package/apps/pwa/out/workflows/index.txt +14 -14
- package/apps/pwa/out/workspace/index.html +1 -1
- package/apps/pwa/out/workspace/index.txt +14 -14
- package/apps/pwa/out/worktrees/index.html +1 -1
- package/apps/pwa/out/worktrees/index.txt +14 -14
- package/graphs/demo/editor-recipes.yaml +22 -0
- package/graphs/ops/guardian.yaml +26 -0
- package/graphs/ops/recipes.yaml +3 -0
- package/graphs/retro/daily.yaml +25 -0
- package/graphs/retro/recipes.yaml +24 -0
- package/package.json +1 -1
- package/scripts/ask-marker-check.ts +3 -1
- package/scripts/coord-channel-watch.sh +2 -2
- package/scripts/discord-seat-probe.ts +3 -1
- package/scripts/land-global-checks.ts +89 -0
- package/scripts/lib/pch-regress-map.ts +24 -0
- package/scripts/morning-report.ts +18 -4
- package/scripts/pch-regress.ts +88 -0
- package/scripts/release-loop/gate-diff.ts +18 -0
- package/scripts/release-loop/gate-node.ts +628 -65
- package/scripts/release-loop/gate-partial.ts +106 -0
- package/scripts/release-loop/gate-timing.ts +229 -0
- package/scripts/release-loop/preflight.ts +6 -4
- package/scripts/release-loop/publish-window.ts +141 -0
- package/scripts/release-loop/release-docs.ts +1 -4
- package/scripts/release-loop/release-universe.ts +38 -0
- package/scripts/release-loop/resume-release.ts +67 -5
- package/scripts/release-loop/shard-plan.ts +65 -0
- package/scripts/release-loop/unattended-release.ts +12 -2
- package/scripts/release-story/draft.ts +5 -1
- package/scripts/retro/shadow-compare.ts +71 -0
- package/scripts/rhythm/daily.ts +6 -1
- package/scripts/video/scene-table.ts +159 -0
- package/src/acp/core-turn-bridge.ts +39 -2
- package/src/agent-mission/driver.ts +34 -6
- package/src/autopilot/build/llm-conflict-merge.ts +132 -7
- package/src/autopilot/build/merge-intent.ts +135 -7
- package/src/away/presence.ts +40 -0
- package/src/away/release-watch.ts +272 -0
- package/src/chat/index.ts +2 -1
- package/src/chat/short-question.ts +3 -40
- package/src/cli/away-cli.ts +68 -0
- package/src/cli/config-drift.ts +513 -0
- package/src/cli/guardian-cli.ts +49 -0
- package/src/cli/intake-cli.ts +13 -7
- package/src/cli/log-event-names.ts +5 -0
- package/src/cli/pod-cli.ts +5 -1
- package/src/cli/pr-cli.ts +20 -0
- package/src/cli/pty-drive-cli.ts +14 -3
- package/src/cli/release-cli.ts +52 -12
- package/src/cli/retro-cli.ts +78 -0
- package/src/cli/setup-cli.ts +31 -1
- package/src/cli/tasks-cli.ts +193 -14
- package/src/context-bus/context-now-public.ts +1 -1
- package/src/context-bus/context-now-surfaces.ts +16 -6
- package/src/context-bus/context-now.ts +97 -3
- package/src/core-turn/short-question.ts +100 -0
- package/src/dashboard/dashboard-status-lines.ts +24 -4
- package/src/dashboard/index.ts +50 -5
- package/src/dashboard/input/slash-executor.ts +6 -2
- package/src/dashboard/slash-runtime/dashboard-handlers.ts +68 -40
- package/src/dashboard/slash-runtime/decide-slash.ts +60 -0
- package/src/dashboard/slash-runtime/help-from-registry.ts +62 -23
- package/src/dashboard/slash-runtime/help-groups.ts +44 -0
- package/src/dashboard/slash-runtime/model-choices.ts +125 -0
- package/src/domains/guardian-tick.ts +138 -0
- package/src/domains/outbound-alert.ts +44 -13
- package/src/domains/outbound-log-probe.ts +6 -1
- package/src/domains/schedule-registry.ts +23 -0
- package/src/domains/telegram-kind-route.ts +39 -0
- package/src/domains/watch-default-scheduler.ts +95 -0
- package/src/esc-abort-gate.ts +44 -0
- package/src/graph-runner/graph-run-api.ts +205 -0
- package/src/graph-runner/runner.ts +36 -6
- package/src/harness/harness-cli-command.ts +420 -51
- package/src/harness/harness-queue.ts +315 -25
- package/src/harness/harness-salvage-cli.ts +140 -0
- package/src/harness/harness-worktree-add.ts +68 -0
- package/src/harness/ledger-write-guard.ts +6 -16
- package/src/hq/fence-outcomes.ts +63 -0
- package/src/hq/fence-wrapper.ts +33 -0
- package/src/hq/hq.ts +17 -2
- package/src/index.ts +118 -6
- package/src/instance/test-write-guard-spy-first.fixture.ts +13 -0
- package/src/instance/test-write-guard.ts +200 -0
- package/src/intake-plane/digest.ts +39 -15
- package/src/intake-plane/harness-target.ts +4 -3
- package/src/intake-plane/intake-sources.ts +1 -0
- package/src/intake-plane/items.ts +1 -0
- package/src/loops/orchestrator/degradation.ts +57 -0
- package/src/loops/orchestrator/finish-rate.ts +33 -3
- package/src/loops/orchestrator/tick.ts +166 -45
- package/src/maturity/feature-maturity.ts +6 -4
- package/src/maturity/tui-slash-maturity.ts +1 -0
- package/src/mss/logging/log-store.ts +3 -0
- package/src/nexus/api/graph-kinds.ts +10 -4
- package/src/nexus/api/graphs-api.ts +178 -6
- package/src/nexus/api/http-server.ts +54 -8
- package/src/nexus/api/loop-edges.ts +292 -17
- package/src/nexus/api/ops-api.ts +112 -29
- package/src/nexus/api/ops-board-route.ts +48 -0
- package/src/nexus/index.ts +7 -0
- package/src/onboarding/entry-hints.ts +5 -0
- package/src/onboarding.ts +24 -6
- package/src/release-loop/checklist.ts +2 -0
- package/src/release-loop/feature-store.ts +68 -18
- package/src/release-loop/gate-shards.ts +158 -0
- package/src/release-loop/light-rc.ts +173 -0
- package/src/release-loop/placement.ts +51 -17
- package/src/release-loop/release-schedule.ts +6 -2
- package/src/release-loop/rubric.ts +57 -1
- package/src/retro/daily.ts +372 -0
- package/src/seat-loop/op-decide.ts +173 -0
- package/src/seat-loop/seat-loop.ts +60 -3
- package/src/seat-loop/seat-report.ts +58 -3
- package/src/self-dev/ask-launch-flow.ts +5 -4
- package/src/self-dev/ask-launch-io.ts +49 -1
- package/src/self-dev/ask-path-candidates.ts +40 -2
- package/src/self-dev/dev-cli.ts +36 -0
- package/src/self-dev/draft-sweep.ts +371 -16
- package/src/self-dev/draft-triage-rules.ts +19 -4
- package/src/self-dev/orchestrate.ts +1 -0
- package/src/self-dev/pr-preview.ts +247 -0
- package/src/self-dev/shard-run.ts +23 -7
- package/src/self-implement/blocked-draft-policy.ts +101 -0
- package/src/self-implement/execution-clarification.ts +11 -0
- package/src/self-implement/gate-cli.ts +29 -8
- package/src/self-implement/gate-env-retry.ts +83 -0
- package/src/self-implement/goal-author-clarification.ts +88 -13
- package/src/self-implement/goal-clarification-escalation.ts +2 -0
- package/src/self-implement/orchestrator.ts +220 -6
- package/src/self-implement/run-stop.ts +123 -0
- package/src/self-implement/run-ttl.ts +258 -0
- package/src/self-implement/salvage-branches.ts +254 -0
- package/src/self-implement/salvage-retention.ts +123 -0
- package/src/self-implement/seams.ts +4 -0
- package/src/self-implement/self-implement-cli.ts +2 -0
- package/src/self-implement/sibling-resync.ts +223 -0
- package/src/self-implement/stop-autoheal.ts +204 -0
- package/src/task-agent/actions.ts +63 -2
- package/src/task-agent/board.ts +132 -0
- package/src/task-agent/card-evidence.ts +292 -0
- package/src/task-agent/completion-evidence.ts +126 -0
- package/src/task-agent/judge.ts +25 -0
- package/src/task-agent/live-moves.ts +276 -0
- package/src/task-agent/mission.ts +33 -7
- package/src/task-agent/shadow.ts +192 -15
- package/src/task-agent/task-hand.ts +124 -17
- package/src/task-orchestrator/surfaces/pod-command-job.ts +82 -8
- package/src/task-orchestrator/surfaces/pod-failure-reason.ts +4 -0
- package/src/task-orchestrator/surfaces/pod-install-slots.ts +89 -0
- package/src/task-orchestrator/surfaces/pod-lease.ts +233 -21
- package/src/task-orchestrator/surfaces/pod-pool.ts +155 -34
- package/src/task-orchestrator/surfaces/self-implement-pod.ts +21 -5
- package/src/telegram-away-command.ts +38 -0
- package/src/telegram-commands.ts +52 -0
- package/src/telegram-report-log-probe.ts +21 -0
- package/src/telegram-report.ts +27 -3
- package/src/testing/real-cli-spawn.ts +106 -0
- package/src/tui-client/dashboard-session.ts +1 -0
- package/src/user-config.ts +190 -5
- package/src/version/packed-revision.json +1 -1
- package/apps/pwa/out/_next/static/chunks/224-798346143007ff65.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3857-4f936cdb30322aa6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4144-a0041c8dc3be57ac.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4554-5761612531d0146b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5977-a11f37254196b659.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5994-ae92c0de046ab9ba.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8401-8615a660c84cdb03.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8868-10a3a4440fb0ccd8.js +0 -25
- package/apps/pwa/out/_next/static/chunks/9247-269e4464cc4b491c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/a9f2b54d-7865aba0f4d2ee49.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ceo/page-60c3da708f9a0ccc.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/consult/page-a0eb02638b1e6f31.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/decisions/page-6eaf7bac37830c21.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/editor/page-c39554c321576982.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/inside/page-bd5d586429167fea.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/layout-59b7834d497c2255.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/loops/page-c130b63862437814.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-5f23d0c66a3b4793.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-a3c58f5c72a5f3f9.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-a2c4625596f8d69c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-0e9099531d239f11.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/outputs/page-e8e1ecaea7cd71d2.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/page-a1b7ffed710a93e0.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-2da6f9b8a38c6978.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-f331e38fefc00393.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-44d5f767491692bf.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/term/page-61553851aa606146.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/today/page-a1f585e154e61c59.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/vault/page-5150ef17b5383a57.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-b9d8ce70792c1672.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-a5a3de231f2cc7d5.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-31ac4afa2dcd915c.js +0 -1
- package/apps/pwa/out/_next/static/css/7fa358fec7da7c47.css +0 -1
- /package/apps/pwa/out/_next/static/{P6YTVgzuZz5oTZhY4Din_ → Bam1YziK2iH9bN4K0utGk}/_buildManifest.js +0 -0
- /package/apps/pwa/out/_next/static/{P6YTVgzuZz5oTZhY4Din_ → Bam1YziK2iH9bN4K0utGk}/_ssgManifest.js +0 -0
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { spawn, spawnSync } from 'node:child_process';
|
|
2
2
|
import { createHash, randomUUID } from 'node:crypto';
|
|
3
|
-
import { appendFileSync, closeSync, existsSync, mkdirSync, openSync, readFileSync, readlinkSync, renameSync, unlinkSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import { appendFileSync, closeSync, existsSync, mkdirSync, openSync, readdirSync, readFileSync, readlinkSync, renameSync, statSync, unlinkSync, writeFileSync } from 'node:fs';
|
|
4
4
|
import { join, resolve } from 'node:path';
|
|
5
5
|
import { Database } from 'bun:sqlite';
|
|
6
6
|
import { debug } from '../debug/log.js';
|
|
7
|
+
import { findTreeRoot } from '../cli/test-flag.js';
|
|
8
|
+
import { readLogInstances, type LogInstanceView } from '../mss/logging/instance-registry.js';
|
|
7
9
|
import { effectiveInstanceRoot, prodInstanceRoot, releaseLedgerRoot } from '../instance/resolve.js';
|
|
8
10
|
import { listChecklist, type ChecklistItem } from '../release-loop/checklist.js';
|
|
9
11
|
import { readLandingFreeze } from '../release-loop/landing-freeze.js';
|
|
@@ -29,11 +31,17 @@ export type QueueItem = {
|
|
|
29
31
|
priority?: number;
|
|
30
32
|
};
|
|
31
33
|
export type QueuePool = { running: number; pending: number; reserved: number; limit: number };
|
|
32
|
-
export type QueueTick = { outcome: 'launched' | 'waiting' | 'skipped'; item?: QueueItem; reason: string
|
|
34
|
+
export type QueueTick = { outcome: 'launched' | 'waiting' | 'skipped'; item?: QueueItem; reason: string;
|
|
35
|
+
/** QUEUE-BURST: every row this tick launched, first one = `item` (present only on a scheduled tick that launched). */ launched?: QueueItem[] };
|
|
33
36
|
/** progressAt = newest run-ledger updatedAt among the tree's members; stopReason = the ledger's supervisor stop (SEAT-CAP-STALE).
|
|
34
37
|
* ledgerRunIds = run ids of the members' ledger records — the `elanous.run` label of their Pod Jobs (SEAT-CAP-STALE2). */
|
|
35
38
|
export type QueueProcess = { pid: number; seat?: QueueSeat; launchId?: string; runId?: string; rootPid?: number;
|
|
36
|
-
progressAt?: number; stopReason?: string; ledgerRunIds?: string[]
|
|
39
|
+
progressAt?: number; stopReason?: string; ledgerRunIds?: string[];
|
|
40
|
+
/** CHILD-UNIV-COUNT: present only when a member's ledger record lives in a derived child universe (not the parent's). */
|
|
41
|
+
universe?: 'child' };
|
|
42
|
+
/** CHILD-UNIV-COUNT: the host inventory plus which derived child universes it read. `unreadable` roots are «못 읽음» —
|
|
43
|
+
* their runs may hold a seat but cannot be attributed, so the count is not trusted as complete. */
|
|
44
|
+
export type QueueInventory = { processes: readonly QueueProcess[]; childUniverses: { roots: readonly string[]; unreadable: readonly string[] } };
|
|
37
45
|
type ProcessProbe = {
|
|
38
46
|
run?: typeof spawnSync; platform?: NodeJS.Platform;
|
|
39
47
|
cwd?: (pid: number) => string;
|
|
@@ -41,6 +49,11 @@ type ProcessProbe = {
|
|
|
41
49
|
runsDir?: string;
|
|
42
50
|
environ?: (pid: number) => string;
|
|
43
51
|
birthId?: (pid: number) => string | undefined;
|
|
52
|
+
/** CHILD-UNIV-COUNT: derived child universe roots whose self-dev ledgers are read with the parent's. Default:
|
|
53
|
+
* `harnessQueueChildUniverseRoots` (never in a test process). */
|
|
54
|
+
childUniverseRoots?: () => readonly string[];
|
|
55
|
+
/** Extra launch trees (queue rows' launchCwd) whose derived universes the default enumeration includes. */
|
|
56
|
+
launchTrees?: readonly string[];
|
|
44
57
|
};
|
|
45
58
|
const queueSeatNames = ['OP', 'TC', 'MK', 'UX'] as const;
|
|
46
59
|
|
|
@@ -65,8 +78,23 @@ export interface HarnessQueueDeps {
|
|
|
65
78
|
afterEnqueue?: (item: QueueItem) => void | Promise<void>;
|
|
66
79
|
/** Script the default launcher runs under the queue child wrapper (default bin/elanous.mjs). */
|
|
67
80
|
launchCommand?: string;
|
|
81
|
+
/** QUEUE-BURST: most launches one scheduled tick makes. Absent = `harness.queue.burstMax`, else 5. A requested (direct) tick always launches at most one. */
|
|
82
|
+
burstMax?: number;
|
|
83
|
+
/** QUEUE-BURST: least gap between two queue launches, across ticks and processes (default 30 s — simultaneous Pod launches died in a chain). */
|
|
84
|
+
launchStaggerMs?: number;
|
|
85
|
+
/** Test seam: the wait between burst launches (default Bun.sleep). */
|
|
86
|
+
sleep?: (ms: number) => Promise<void>;
|
|
68
87
|
alive?: (pid: number) => boolean;
|
|
69
88
|
processes?: () => readonly QueueProcess[];
|
|
89
|
+
/** CHILD-UNIV-COUNT: inventory with child-universe coverage; wins over `processes`. A `processes`-only seam reads no
|
|
90
|
+
* child universe, so its limit line stays parent-only. */
|
|
91
|
+
inventory?: (launchTrees: readonly string[]) => QueueInventory;
|
|
92
|
+
/** Production freeze root a tick also honors (a work-tree launch ticks in its test universe). Default: prodInstanceRoot(),
|
|
93
|
+
* except in a test process where it is the tick's own root. */
|
|
94
|
+
prodFreezeRoot?: string;
|
|
95
|
+
/** CHILD-UNIV-HOLD: ops-health alert for a universe unreadable over an hour (default: sendOutbound 'ops-health'; none in
|
|
96
|
+
* a test process). */
|
|
97
|
+
unreadableAlert?: (text: string) => void | Promise<void>;
|
|
70
98
|
receipt?: (root: string, launchId: string) => 'started' | 'finished' | 'not-started' | null;
|
|
71
99
|
now?: () => Date;
|
|
72
100
|
authorShadow?: (root: string, now: Date) => void | Promise<void>;
|
|
@@ -199,11 +227,20 @@ function lastLaunchedSeat(path: string): QueueSeat | undefined {
|
|
|
199
227
|
return (value as { lastSeat: QueueSeat }).lastSeat;
|
|
200
228
|
}
|
|
201
229
|
|
|
202
|
-
|
|
230
|
+
/** QUEUE-BURST: when the queue last launched (epoch ms), from the round-robin marker. Missing or unreadable = unknown. */
|
|
231
|
+
function lastQueueLaunchAt(path: string): number | undefined {
|
|
232
|
+
try {
|
|
233
|
+
const value: unknown = JSON.parse(readFileSync(`${path}.round-robin.json`, 'utf8'));
|
|
234
|
+
const at = value && typeof value === 'object' ? (value as { lastLaunchAt?: unknown }).lastLaunchAt : undefined;
|
|
235
|
+
return typeof at === 'number' && Number.isFinite(at) ? at : undefined;
|
|
236
|
+
} catch { return undefined; }
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function saveLastLaunchedSeat(path: string, lastSeat: QueueSeat, lastLaunchAt?: number): void {
|
|
203
240
|
const marker = `${path}.round-robin.json`;
|
|
204
241
|
const temporary = `${marker}.${randomUUID()}.tmp`;
|
|
205
242
|
try {
|
|
206
|
-
writeFileSync(temporary, JSON.stringify({ lastSeat }), { flag: 'wx', mode: 0o600 });
|
|
243
|
+
writeFileSync(temporary, JSON.stringify({ lastSeat, ...(lastLaunchAt === undefined ? {} : { lastLaunchAt }) }), { flag: 'wx', mode: 0o600 });
|
|
207
244
|
renameSync(temporary, marker);
|
|
208
245
|
} catch (error) {
|
|
209
246
|
try { unlinkSync(temporary); } catch { /* write did not finish */ }
|
|
@@ -537,8 +574,60 @@ function alive(pid: number): boolean {
|
|
|
537
574
|
try { process.kill(pid, 0); return true; } catch { return false; }
|
|
538
575
|
}
|
|
539
576
|
|
|
577
|
+
/** CHILD-UNIV-COUNT: derived child universes a tick reads with its own — never a glob of home directories. Sources: the
|
|
578
|
+
* instance registry's live local test universes (`~/.elanous/logs/instances.json`), the derived universe
|
|
579
|
+
* (`<tree>/.elanous-test`) of every seat tree, of every queue row's launch tree and of this process's own tree. A derived
|
|
580
|
+
* root that does not exist was never a child universe and is skipped; the parent root itself is never a child. */
|
|
581
|
+
export function harnessQueueChildUniverseRoots(input: {
|
|
582
|
+
parentRoot: string;
|
|
583
|
+
seatTrees?: Partial<Record<QueueSeat, string[]>>;
|
|
584
|
+
launchTrees?: readonly string[];
|
|
585
|
+
registry?: () => readonly Pick<LogInstanceView, 'stateDir' | 'kind' | 'liveness'>[];
|
|
586
|
+
exists?: (path: string) => boolean;
|
|
587
|
+
ownTree?: string | null;
|
|
588
|
+
}): string[] {
|
|
589
|
+
const exists = input.exists ?? existsSync;
|
|
590
|
+
const parent = resolve(input.parentRoot);
|
|
591
|
+
const roots = new Set<string>();
|
|
592
|
+
const add = (root: string | null | undefined): void => {
|
|
593
|
+
if (!root || !root.startsWith('/')) return;
|
|
594
|
+
const resolved = resolve(root);
|
|
595
|
+
if (resolved !== parent && exists(resolved)) roots.add(resolved);
|
|
596
|
+
};
|
|
597
|
+
const derived = (tree: string): string | null => {
|
|
598
|
+
if (!tree.startsWith('/')) return null;
|
|
599
|
+
const top = findTreeRoot(tree);
|
|
600
|
+
return top ? join(top, '.elanous-test') : null;
|
|
601
|
+
};
|
|
602
|
+
try {
|
|
603
|
+
for (const entry of (input.registry ?? readLogInstances)()) {
|
|
604
|
+
if (entry.kind === 'test' && entry.liveness === 'alive') add(entry.stateDir);
|
|
605
|
+
}
|
|
606
|
+
} catch (error) {
|
|
607
|
+
try { debug.log('harness.queue', 'child-universe-registry-unavailable', { error: String(error).slice(0, 240) }, { level: 'warn' }); }
|
|
608
|
+
catch { /* Observation cannot affect dispatch. */ }
|
|
609
|
+
}
|
|
610
|
+
for (const trees of Object.values(input.seatTrees ?? {})) for (const tree of trees ?? []) add(derived(tree));
|
|
611
|
+
for (const tree of input.launchTrees ?? []) add(derived(tree));
|
|
612
|
+
add(input.ownTree === undefined ? derived(process.cwd()) : input.ownTree);
|
|
613
|
+
return [...roots].sort();
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
/** A child universe's self-dev ledgers; a store that exists but cannot be listed is «unreadable», never «none». */
|
|
617
|
+
function readChildUniverseRuns(root: string): ReturnType<typeof listSelfDevRuns> | 'unreadable' {
|
|
618
|
+
const dir = selfDevRunsDir(root);
|
|
619
|
+
try { statSync(dir); readdirSync(dir); }
|
|
620
|
+
catch (error) { return (error as NodeJS.ErrnoException).code === 'ENOENT' ? [] : 'unreadable'; }
|
|
621
|
+
return listSelfDevRuns(dir);
|
|
622
|
+
}
|
|
623
|
+
|
|
540
624
|
/** Host inventory includes harness authoring before it has a run ledger. Unattributed processes count for every seat. */
|
|
541
625
|
export function readHarnessQueueProcesses(probe: ProcessProbe = {}): readonly QueueProcess[] {
|
|
626
|
+
return readHarnessQueueInventory(probe).processes;
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
/** CHILD-UNIV-COUNT: the host inventory with the parent's ledgers ⊕ the derived child universes' ledgers. */
|
|
630
|
+
export function readHarnessQueueInventory(probe: ProcessProbe = {}): QueueInventory {
|
|
542
631
|
const run = probe.run ?? spawnSync;
|
|
543
632
|
const platform = probe.platform ?? process.platform;
|
|
544
633
|
const result = run('ps', ['-eo', 'pid=,ppid=,args='], { encoding: 'utf8', timeout: 5_000, maxBuffer: 16 * 1024 * 1024 });
|
|
@@ -572,7 +661,23 @@ export function readHarnessQueueProcesses(probe: ProcessProbe = {}): readonly Qu
|
|
|
572
661
|
const births = new Map<number, string | undefined>();
|
|
573
662
|
const ledgerProgress = new Map<number, { at: number; stopReason?: string }>();
|
|
574
663
|
const ledgerRunIds = new Map<number, Set<string>>();
|
|
575
|
-
|
|
664
|
+
const childPids = new Set<number>();
|
|
665
|
+
const testProcess = process.env.NODE_ENV === 'test' || Boolean(process.env.ELANOUS_TEST_HOME);
|
|
666
|
+
const childRoots = (probe.childUniverseRoots ?? (testProcess ? () => [] : () => harnessQueueChildUniverseRoots({
|
|
667
|
+
parentRoot: effectiveInstanceRoot(), seatTrees: trees, launchTrees: probe.launchTrees ?? [] })))();
|
|
668
|
+
const unreadable: string[] = [];
|
|
669
|
+
const sources: { records: ReturnType<typeof listSelfDevRuns>; child: boolean }[] = [
|
|
670
|
+
{ records: listSelfDevRuns(probe.runsDir ?? selfDevRunsDir()), child: false }];
|
|
671
|
+
for (const root of childRoots) {
|
|
672
|
+
const records = readChildUniverseRuns(root);
|
|
673
|
+
if (records === 'unreadable') unreadable.push(root);
|
|
674
|
+
else sources.push({ records, child: true });
|
|
675
|
+
}
|
|
676
|
+
if (unreadable.length) {
|
|
677
|
+
try { debug.log('harness.queue', 'child-universe-unreadable', { roots: unreadable, read: childRoots.length - unreadable.length }, { level: 'warn' }); }
|
|
678
|
+
catch { /* Observation cannot affect dispatch. */ }
|
|
679
|
+
}
|
|
680
|
+
for (const { records, child } of sources) for (const record of records) {
|
|
576
681
|
const pid = record.pid;
|
|
577
682
|
if (!pid || !candidatePids.has(pid) || !record.pidStart) continue;
|
|
578
683
|
if (!births.has(pid)) {
|
|
@@ -580,6 +685,7 @@ export function readHarnessQueueProcesses(probe: ProcessProbe = {}): readonly Qu
|
|
|
580
685
|
catch { births.set(pid, undefined); }
|
|
581
686
|
}
|
|
582
687
|
if (births.get(pid) !== record.pidStart) continue;
|
|
688
|
+
if (child) childPids.add(pid);
|
|
583
689
|
if (record.runId) ledgerRunIds.set(pid, (ledgerRunIds.get(pid) ?? new Set<string>()).add(record.runId));
|
|
584
690
|
// Newest updatedAt is the process's latest progress; it is stopped only when every matched record stopped.
|
|
585
691
|
if (Number.isFinite(record.updatedAt)) {
|
|
@@ -659,9 +765,10 @@ export function readHarnessQueueProcesses(probe: ProcessProbe = {}): readonly Qu
|
|
|
659
765
|
catch { /* Observation does not block inventory. */ }
|
|
660
766
|
rows.push({ pid: root.pid, ...(assigned ? { seat: assigned.seat } : {}), ...(launchId ? { launchId } : {}),
|
|
661
767
|
...(runId ? { runId } : {}), ...(progressAt !== undefined ? { progressAt } : {}), ...(stopReason ? { stopReason } : {}),
|
|
662
|
-
...(runIds.length ? { ledgerRunIds: runIds } : {})
|
|
768
|
+
...(runIds.length ? { ledgerRunIds: runIds } : {}),
|
|
769
|
+
...(members.some(({ process: { pid } }) => childPids.has(pid)) ? { universe: 'child' as const } : {}) });
|
|
663
770
|
}
|
|
664
|
-
return rows;
|
|
771
|
+
return { processes: rows, childUniverses: { roots: childRoots, unreadable } };
|
|
665
772
|
}
|
|
666
773
|
|
|
667
774
|
export function harnessQueueReceiptPath(root: string, launchId: string): string {
|
|
@@ -770,7 +877,9 @@ function warnLegacySeatCap(path: string): void {
|
|
|
770
877
|
|
|
771
878
|
type QueueRun = { seat?: QueueSeat; launchIds: Set<string>; pids: Set<number>; runIds: Set<string>; progressAt?: number; stopReason?: string;
|
|
772
879
|
/** Every run id of the group (env and ledger) — matched against Pod Job labels only, never used for grouping. */
|
|
773
|
-
podRunIds: Set<string
|
|
880
|
+
podRunIds: Set<string>;
|
|
881
|
+
/** CHILD-UNIV-COUNT: some member's ledger lives in a derived child universe. */
|
|
882
|
+
universe?: 'child' };
|
|
774
883
|
|
|
775
884
|
function groupQueueRuns(processes: readonly QueueProcess[]): QueueRun[] {
|
|
776
885
|
const groups: { seats: Set<QueueSeat>; unknown: boolean; launchIds: Set<string>; keys: Set<string>; rows: QueueProcess[] }[] = [];
|
|
@@ -806,6 +915,7 @@ function groupQueueRuns(processes: readonly QueueProcess[]): QueueRun[] {
|
|
|
806
915
|
runIds: new Set(group.rows.flatMap((row) => row.runId ? [row.runId] : [])),
|
|
807
916
|
podRunIds: new Set(group.rows.flatMap((row) => [...(row.runId ? [row.runId] : []), ...(row.ledgerRunIds ?? [])])),
|
|
808
917
|
...(progressAt !== undefined ? { progressAt } : {}), ...(stopReason ? { stopReason } : {}),
|
|
918
|
+
...(group.rows.some((row) => row.universe === 'child') ? { universe: 'child' as const } : {}),
|
|
809
919
|
};
|
|
810
920
|
});
|
|
811
921
|
}
|
|
@@ -941,18 +1051,145 @@ export function requestIdleSeats(root: string, now: Date, items: readonly QueueI
|
|
|
941
1051
|
}
|
|
942
1052
|
|
|
943
1053
|
/** ORCH2 ③ — 발사를 막는 동결이면 사유(waitingReason)와 관측 칸을, 아니면 null. 동결 파일을 못 읽으면 닫힌 쪽(막는다). */
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
1054
|
+
type UnreadableUniverseState = { holding: boolean; penalty: number };
|
|
1055
|
+
/** CHILD-UNIV-HOLD: ticks a newly unreadable child universe holds every launch before the pool penalty takes over. */
|
|
1056
|
+
export const UNREADABLE_UNIVERSE_HOLD_TICKS = 2;
|
|
1057
|
+
/** CHILD-UNIV-HOLD: a universe unreadable this long raises one ops-health alert per universe per (KST) day. */
|
|
1058
|
+
export const UNREADABLE_UNIVERSE_ALERT_MS = 60 * 60 * 1_000;
|
|
1059
|
+
|
|
1060
|
+
export function unreadableUniverseStatePath(root: string): string {
|
|
1061
|
+
return join(root, 'harness', 'child-universe-unreadable.sqlite');
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
/** CHILD-UNIV-HOLD — the «못 읽음» hold is finite. Per unreadable universe the queue state keeps when it first became
|
|
1065
|
+
* unreadable and how many ticks it held: the first `UNREADABLE_UNIVERSE_HOLD_TICKS` scheduled/direct passes hold every
|
|
1066
|
+
* launch; afterwards launches go on with one pool slot charged per unreadable universe. A universe readable again (or no
|
|
1067
|
+
* longer enumerated) drops its row. Unpersistable state never holds (a hold that cannot count its ticks would be endless). */
|
|
1068
|
+
async function trackUnreadableUniverses(root: string, unreadable: readonly string[], now: Date, followUp: boolean,
|
|
1069
|
+
deps: HarnessQueueDeps): Promise<UnreadableUniverseState> {
|
|
1070
|
+
const nowMs = now.getTime();
|
|
1071
|
+
const day = new Date(nowMs + 9 * 3_600_000).toISOString().slice(0, 10);
|
|
1072
|
+
const alerts: string[] = [];
|
|
1073
|
+
let holding = false;
|
|
1074
|
+
let rows: { universe: string; firstAt: number; heldTicks: number }[] = [];
|
|
1075
|
+
try {
|
|
1076
|
+
mkdirSync(join(root, 'harness'), { recursive: true, mode: 0o700 });
|
|
1077
|
+
const db = new Database(unreadableUniverseStatePath(root), { create: true, strict: true });
|
|
1078
|
+
try {
|
|
1079
|
+
db.exec('CREATE TABLE IF NOT EXISTS unreadable_universe (universe TEXT PRIMARY KEY, first_at INTEGER NOT NULL, held_ticks INTEGER NOT NULL, alerted_day TEXT)');
|
|
1080
|
+
const known = db.query('SELECT universe, first_at AS firstAt, held_ticks AS heldTicks, alerted_day AS alertedDay FROM unreadable_universe')
|
|
1081
|
+
.all() as { universe: string; firstAt: number; heldTicks: number; alertedDay: string | null }[];
|
|
1082
|
+
const current = new Set(unreadable);
|
|
1083
|
+
const cleared = known.filter((row) => !current.has(row.universe)).map((row) => row.universe);
|
|
1084
|
+
for (const universe of cleared) db.query('DELETE FROM unreadable_universe WHERE universe = ?').run(universe);
|
|
1085
|
+
if (cleared.length) {
|
|
1086
|
+
try { debug.log('harness.queue', 'unreadable-universe-cleared', { universes: cleared }); }
|
|
1087
|
+
catch { /* Observation cannot affect dispatch. */ }
|
|
1088
|
+
}
|
|
1089
|
+
for (const universe of unreadable) {
|
|
1090
|
+
db.query('INSERT OR IGNORE INTO unreadable_universe (universe, first_at, held_ticks, alerted_day) VALUES (?, ?, 0, NULL)').run(universe, nowMs);
|
|
1091
|
+
}
|
|
1092
|
+
const live = db.query('SELECT universe, first_at AS firstAt, held_ticks AS heldTicks, alerted_day AS alertedDay FROM unreadable_universe')
|
|
1093
|
+
.all() as { universe: string; firstAt: number; heldTicks: number; alertedDay: string | null }[];
|
|
1094
|
+
holding = live.some((row) => row.heldTicks < UNREADABLE_UNIVERSE_HOLD_TICKS);
|
|
1095
|
+
// A burst follow-up pass belongs to a tick already counted.
|
|
1096
|
+
if (holding && !followUp) db.query('UPDATE unreadable_universe SET held_ticks = held_ticks + 1 WHERE held_ticks < ?').run(UNREADABLE_UNIVERSE_HOLD_TICKS);
|
|
1097
|
+
for (const row of live) {
|
|
1098
|
+
if (nowMs - row.firstAt < UNREADABLE_UNIVERSE_ALERT_MS || row.alertedDay === day) continue;
|
|
1099
|
+
db.query('UPDATE unreadable_universe SET alerted_day = ? WHERE universe = ?').run(day, row.universe);
|
|
1100
|
+
alerts.push(row.universe);
|
|
1101
|
+
}
|
|
1102
|
+
rows = live;
|
|
1103
|
+
} finally { db.close(); }
|
|
1104
|
+
} catch (error) {
|
|
1105
|
+
holding = false;
|
|
1106
|
+
try { debug.log('harness.queue', 'unreadable-universe-state-unavailable', { error: String(error).slice(0, 240), universes: unreadable }, { level: 'warn' }); }
|
|
1107
|
+
catch { /* Observation cannot affect dispatch. */ }
|
|
1108
|
+
}
|
|
1109
|
+
const penalty = holding ? 0 : unreadable.length;
|
|
1110
|
+
if (unreadable.length) {
|
|
1111
|
+
try { debug.log('harness.queue', 'unreadable-universe', { universes: unreadable, holding, penalty,
|
|
1112
|
+
rows: rows.map((row) => ({ universe: row.universe, firstAt: new Date(row.firstAt).toISOString(), heldTicks: row.heldTicks })) }, { level: 'warn' }); }
|
|
1113
|
+
catch { /* Observation cannot affect dispatch. */ }
|
|
1114
|
+
}
|
|
1115
|
+
for (const universe of alerts) {
|
|
1116
|
+
const first = rows.find((row) => row.universe === universe)?.firstAt ?? nowMs;
|
|
1117
|
+
const text = `⚠️ 하니스 대기열: 자식 우주를 ${Math.floor((nowMs - first) / 60_000)}분째 못 읽는다 — ${universe} · 발사는 Pod 풀 여유에서 1칸 보수 차감 중`;
|
|
1118
|
+
try {
|
|
1119
|
+
const send = deps.unreadableAlert ?? (process.env.NODE_ENV === 'test' || process.env.ELANOUS_TEST_HOME ? () => {}
|
|
1120
|
+
: async (body: string) => { (await import('../domains/outbound-alert.js')).sendOutbound(body, 'ops-health'); });
|
|
1121
|
+
await send(text);
|
|
1122
|
+
debug.log('harness.queue', 'unreadable-universe-alert', { universe, firstAt: new Date(first).toISOString(), day });
|
|
1123
|
+
} catch (error) {
|
|
1124
|
+
try { debug.log('harness.queue', 'unreadable-universe-alert-failed', { universe, error: String(error).slice(0, 240) }, { level: 'warn' }); }
|
|
1125
|
+
catch { /* Observation cannot affect dispatch. */ }
|
|
1126
|
+
}
|
|
1127
|
+
}
|
|
1128
|
+
return { holding, penalty };
|
|
1129
|
+
}
|
|
1130
|
+
|
|
1131
|
+
/** CHILD-UNIV-COUNT: the production freeze binds a tick in any universe (a work-tree launch ticks in its derived test
|
|
1132
|
+
* universe and used not to see it); the tick's own root still counts. A test process defaults to its own root only. */
|
|
1133
|
+
function queueProdFreezeRoot(root: string, deps: HarnessQueueDeps): string {
|
|
1134
|
+
if (deps.prodFreezeRoot !== undefined) return deps.prodFreezeRoot;
|
|
1135
|
+
return process.env.NODE_ENV === 'test' || process.env.ELANOUS_TEST_HOME ? root : prodInstanceRoot();
|
|
1136
|
+
}
|
|
1137
|
+
|
|
1138
|
+
function launchFreezeHold(root: string, now: Date, prodRoot: string = root): { reason: string; freezeReason: string; until: string | null } | null {
|
|
1139
|
+
let freeze: ReturnType<typeof readLandingFreeze> = null;
|
|
1140
|
+
try {
|
|
1141
|
+
for (const authority of new Set([prodRoot, root])) {
|
|
1142
|
+
const found = readLandingFreeze(authority, now);
|
|
1143
|
+
if (found?.holdLaunches === true) { freeze = found; break; }
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
947
1146
|
catch { return { reason: '동결 상태 읽기 실패', freezeReason: '동결 상태 읽기 실패', until: null }; }
|
|
948
1147
|
if (freeze?.holdLaunches !== true) return null;
|
|
949
1148
|
return { reason: `동결 — ${freeze.reason}${freeze.until ? ` · ~${freeze.until}` : ''}`, freezeReason: freeze.reason, until: freeze.until };
|
|
950
1149
|
}
|
|
951
1150
|
|
|
1151
|
+
/** QUEUE-BURST default: five launches 30 s apart fit one two-minute cron interval. */
|
|
1152
|
+
export const QUEUE_BURST_MAX_DEFAULT = 5;
|
|
1153
|
+
export const QUEUE_LAUNCH_STAGGER_MS = 30_000;
|
|
1154
|
+
const queueStaggerPrefix = '발사 간격';
|
|
1155
|
+
|
|
1156
|
+
/**
|
|
1157
|
+
* One scheduled tick launches several queued heads when seats and the Pod pool have room (QUEUE-BURST). Each launch is its
|
|
1158
|
+
* own locked pass that re-measures seat counts, pool headroom, the finish gate and the freeze, so the pool headroom is what
|
|
1159
|
+
* bounds the burst; launches are `launchStaggerMs` apart. A requested tick (direct `harness say|ask`) launches at most its row.
|
|
1160
|
+
*/
|
|
952
1161
|
export async function tickHarnessQueue(deps: HarnessQueueDeps = {}, requestedId?: string): Promise<QueueTick> {
|
|
1162
|
+
if (requestedId) return tickHarnessQueueOnce(deps, requestedId, false);
|
|
1163
|
+
const first = await tickHarnessQueueOnce(deps, undefined, false);
|
|
1164
|
+
if (first.outcome !== 'launched' || !first.item) return first;
|
|
1165
|
+
const configured = deps.burstMax ?? (deps.configPath === undefined ? getUserConfig() : getUserConfig(deps.configPath)).harness?.queue?.burstMax;
|
|
1166
|
+
const burstMax = typeof configured === 'number' && Number.isSafeInteger(configured) && configured > 0 ? configured : QUEUE_BURST_MAX_DEFAULT;
|
|
1167
|
+
const stagger = deps.launchStaggerMs ?? QUEUE_LAUNCH_STAGGER_MS;
|
|
1168
|
+
const launched: QueueItem[] = [first.item];
|
|
1169
|
+
let stop = burstMax <= 1 ? 'burst-max' : '';
|
|
1170
|
+
while (!stop && launched.length < burstMax) {
|
|
1171
|
+
// Nothing left to launch: end the burst without spending the stagger wait.
|
|
1172
|
+
if (!listHarnessQueue(deps).some((row) => row.status === 'queued')) { stop = 'empty'; break; }
|
|
1173
|
+
if (stagger > 0) await (deps.sleep ?? ((ms: number) => Bun.sleep(ms)))(stagger);
|
|
1174
|
+
let next: QueueTick;
|
|
1175
|
+
// Launches already made stand: a failing follow-up pass ends the burst (its own row stays for reconcile) instead of
|
|
1176
|
+
// turning the whole tick into an error.
|
|
1177
|
+
try { next = await tickHarnessQueueOnce(deps, undefined, true); }
|
|
1178
|
+
catch (error) { stop = `follow-up failed: ${String(error).slice(0, 240)}`; break; }
|
|
1179
|
+
if (next.outcome !== 'launched' || !next.item) { stop = next.reason.split('\n')[0] ?? next.outcome; break; }
|
|
1180
|
+
launched.push(next.item);
|
|
1181
|
+
}
|
|
1182
|
+
try { debug.log('harness.queue', 'burst', { launched: launched.length, burstMax, staggerMs: stagger, stop: stop || 'burst-max',
|
|
1183
|
+
ids: launched.map((row) => row.id), seats: launched.map((row) => row.seat) }); }
|
|
1184
|
+
catch { /* Observation cannot change launches already made. */ }
|
|
1185
|
+
return { ...first, launched };
|
|
1186
|
+
}
|
|
1187
|
+
|
|
1188
|
+
async function tickHarnessQueueOnce(deps: HarnessQueueDeps, requestedId: string | undefined, followUp: boolean): Promise<QueueTick> {
|
|
953
1189
|
const root = deps.root ?? effectiveInstanceRoot();
|
|
954
1190
|
return locked(root, async (path) => {
|
|
955
|
-
|
|
1191
|
+
// A burst follow-up already ran the author shadow and the idle request in this tick's first pass.
|
|
1192
|
+
if (!followUp) try {
|
|
956
1193
|
const now = (deps.now ?? (() => new Date()))();
|
|
957
1194
|
// A test process must not run the real author shadow — it reads operational ledgers and blew the 5s test budget (10-05 P0).
|
|
958
1195
|
if (!deps.authorShadow && process.env.ELANOUS_AUTHOR_SHADOW_LIVE !== '1' && (process.env.NODE_ENV === 'test' || process.env.ELANOUS_TEST_HOME)) {
|
|
@@ -967,7 +1204,7 @@ export async function tickHarnessQueue(deps: HarnessQueueDeps = {}, requestedId?
|
|
|
967
1204
|
}
|
|
968
1205
|
const items = read(path);
|
|
969
1206
|
// ORCH2 ③ 동결 창의 «발사» 절반 — `freeze on --hold-launches` 면 이 틱은 발사 0. 동결 파일을 못 읽으면 착지 쪽과 같이 닫힌 쪽(발사 0).
|
|
970
|
-
const hold = launchFreezeHold(root, (deps.now ?? (() => new Date()))());
|
|
1207
|
+
const hold = launchFreezeHold(root, (deps.now ?? (() => new Date()))(), queueProdFreezeRoot(root, deps));
|
|
971
1208
|
if (hold) {
|
|
972
1209
|
const reason = hold.reason;
|
|
973
1210
|
const queued = items.filter((item) => item.status === 'queued');
|
|
@@ -982,7 +1219,7 @@ export async function tickHarnessQueue(deps: HarnessQueueDeps = {}, requestedId?
|
|
|
982
1219
|
return subject ? { outcome: 'waiting', item: { ...subject, waitingReason: reason }, reason }
|
|
983
1220
|
: { outcome: 'skipped', reason: requestedId ? '이미 처리됨 또는 항목 없음' : 'empty' };
|
|
984
1221
|
}
|
|
985
|
-
try { await (deps.idleRequest ?? requestIdleSeats)(root, (deps.now ?? (() => new Date()))(), items, deps); }
|
|
1222
|
+
if (!followUp) try { await (deps.idleRequest ?? requestIdleSeats)(root, (deps.now ?? (() => new Date()))(), items, deps); }
|
|
986
1223
|
catch (error) {
|
|
987
1224
|
try { debug.log('loop.orchestrator', 'idle-request-failed', { reason: String(error) }); }
|
|
988
1225
|
catch { /* Idle observation cannot affect queue dispatch. */ }
|
|
@@ -1043,7 +1280,22 @@ export async function tickHarnessQueue(deps: HarnessQueueDeps = {}, requestedId?
|
|
|
1043
1280
|
}
|
|
1044
1281
|
let current = items;
|
|
1045
1282
|
let processes: readonly QueueProcess[];
|
|
1046
|
-
|
|
1283
|
+
let childUniverses: QueueInventory['childUniverses'] | undefined;
|
|
1284
|
+
let unreadableState: UnreadableUniverseState = { holding: false, penalty: 0 };
|
|
1285
|
+
try {
|
|
1286
|
+
const launchTrees = [...new Set(items.flatMap((row) => row.launchCwd ? [row.launchCwd] : []))];
|
|
1287
|
+
const inventory: QueueInventory = deps.inventory ? deps.inventory(launchTrees)
|
|
1288
|
+
: deps.processes ? { processes: deps.processes(), childUniverses: { roots: [], unreadable: [] } }
|
|
1289
|
+
: readHarnessQueueInventory({ launchTrees });
|
|
1290
|
+
processes = inventory.processes;
|
|
1291
|
+
// A seam or host that read no child universe keeps the parent-only line; any consulted universe shows the split.
|
|
1292
|
+
if (inventory.childUniverses.roots.length || inventory.childUniverses.unreadable.length) childUniverses = inventory.childUniverses;
|
|
1293
|
+
// CHILD-UNIV-HOLD: a `processes`-only seam consulted no child universe, so it neither sets nor clears the state.
|
|
1294
|
+
if (deps.inventory || !deps.processes) {
|
|
1295
|
+
unreadableState = await trackUnreadableUniverses(root, inventory.childUniverses.unreadable,
|
|
1296
|
+
(deps.now ?? (() => new Date()))(), followUp, deps);
|
|
1297
|
+
}
|
|
1298
|
+
}
|
|
1047
1299
|
catch (error) {
|
|
1048
1300
|
for (const item of candidates) {
|
|
1049
1301
|
const injectedCap = deps.cap?.(item.seat);
|
|
@@ -1176,11 +1428,35 @@ export async function tickHarnessQueue(deps: HarnessQueueDeps = {}, requestedId?
|
|
|
1176
1428
|
let pool: QueuePool | undefined;
|
|
1177
1429
|
let poolReason: string | undefined;
|
|
1178
1430
|
let finishHeld: string | null | undefined;
|
|
1431
|
+
// CHILD-UNIV-COUNT: a seat's live runs whose ledger lives in a derived child universe (a work-tree launch's children).
|
|
1432
|
+
const childActive = (seat: QueueSeat): number => runs.filter((run) => run.seat === seat && run.universe === 'child'
|
|
1433
|
+
&& !runVerdict(run, seat)).length;
|
|
1434
|
+
const universeSplit = (seat: QueueSeat, active: number): string => {
|
|
1435
|
+
if (!childUniverses) return '';
|
|
1436
|
+
const child = childActive(seat);
|
|
1437
|
+
const parent = active - child;
|
|
1438
|
+
const n = childUniverses.unreadable.length;
|
|
1439
|
+
return !n ? ` · 부모 ${parent} ⊕ 자식 우주 ${child}`
|
|
1440
|
+
: unreadableState.holding ? ` · 부모 ${parent} ⊕ 자식 우주 ${child} ⊕ 못 읽음 ${n}곳`
|
|
1441
|
+
: ` · 부모 ${parent} ⊕ 자식 우주 ${child} ⊕ 못 읽음 ${n}곳(보수 차감)`;
|
|
1442
|
+
};
|
|
1443
|
+
const seatLine = (seat: QueueSeat, active: number, details: ReturnType<typeof seatCapDetails>): string =>
|
|
1444
|
+
`${seatCapReason(seat, active, details)}${universeSplit(seat, active)}`;
|
|
1445
|
+
if (childUniverses?.unreadable.length && unreadableState.holding) {
|
|
1446
|
+
// «못 읽음» is not «0»: an unreadable child universe may hold this seat's runs, so no seat launches on a partial count —
|
|
1447
|
+
// but only for the first ticks (CHILD-UNIV-HOLD); afterwards the pool headroom is reduced instead.
|
|
1448
|
+
for (const item of candidates) {
|
|
1449
|
+
const details = seatCapDetails({ seat: item.seat, caps, gate, injectedCap: deps.cap?.(item.seat) });
|
|
1450
|
+
const active = seatActive(item.seat);
|
|
1451
|
+
blocked.set(item.seat, { reason: `${seatLine(item.seat, active, details)} — 자식 우주 못 읽음: ${childUniverses.unreadable.join(', ')}`, active });
|
|
1452
|
+
}
|
|
1453
|
+
return waiting();
|
|
1454
|
+
}
|
|
1179
1455
|
for (const item of candidates) {
|
|
1180
1456
|
const injectedCap = deps.cap?.(item.seat);
|
|
1181
1457
|
const details = seatCapDetails({ seat: item.seat, caps, gate, injectedCap });
|
|
1182
1458
|
const active = seatActive(item.seat);
|
|
1183
|
-
if (active >= details.cap) { blocked.set(item.seat, { reason:
|
|
1459
|
+
if (active >= details.cap) { blocked.set(item.seat, { reason: seatLine(item.seat, active, details), active }); continue; }
|
|
1184
1460
|
if (!pool && !poolReason) {
|
|
1185
1461
|
try { pool = (deps.pool ?? readHarnessQueuePool)(); }
|
|
1186
1462
|
catch (error) { poolReason = `pod lease status unavailable: ${String(error)}`; }
|
|
@@ -1189,20 +1465,22 @@ export async function tickHarnessQueue(deps: HarnessQueueDeps = {}, requestedId?
|
|
|
1189
1465
|
}
|
|
1190
1466
|
if (pool && !poolReason) {
|
|
1191
1467
|
const queueLaunching = current.filter((row) => row.status === 'launching' || row.status === 'launched').length;
|
|
1192
|
-
|
|
1193
|
-
|
|
1468
|
+
// CHILD-UNIV-HOLD: each unreadable child universe is charged one pool slot (conservative), never a seat slot.
|
|
1469
|
+
const penalty = unreadableState.penalty;
|
|
1470
|
+
if (pool.running + pool.pending + pool.reserved + queueLaunching + penalty >= pool.limit) {
|
|
1471
|
+
poolReason = `pool: ${pool.running}+${pool.pending}+${pool.reserved}+${queueLaunching}${penalty ? `+못 읽음 ${penalty}곳(보수 차감)` : ''}/${pool.limit}`;
|
|
1194
1472
|
}
|
|
1195
1473
|
}
|
|
1196
1474
|
}
|
|
1197
1475
|
if (poolReason) { blocked.set(item.seat, { reason: poolReason, active }); continue; }
|
|
1198
1476
|
const budget = decideSpawn({ seat: item.seat, running: active, caps, gate, injectedCap });
|
|
1199
1477
|
if (!budget.allow) {
|
|
1200
|
-
blocked.set(item.seat, { reason: `${budget.reason}: ${
|
|
1478
|
+
blocked.set(item.seat, { reason: `${budget.reason}: ${seatLine(item.seat, active, details)}`, active });
|
|
1201
1479
|
continue;
|
|
1202
1480
|
}
|
|
1203
1481
|
if (finishHeld === undefined) {
|
|
1204
1482
|
finishHeld = await finishGateReason(root, (deps.now ?? (() => new Date()))(), config.loops?.orchestrator?.finishGate ?? 'shadow',
|
|
1205
|
-
pool!.running + pool!.pending + pool!.reserved
|
|
1483
|
+
pool!.running + pool!.pending + pool!.reserved + unreadableState.penalty
|
|
1206
1484
|
+ current.filter((row) => row.status === 'launching' || row.status === 'launched').length, pool!.limit, deps);
|
|
1207
1485
|
}
|
|
1208
1486
|
if (finishHeld) { blocked.set(item.seat, { reason: finishHeld, active }); continue; }
|
|
@@ -1214,9 +1492,9 @@ export async function tickHarnessQueue(deps: HarnessQueueDeps = {}, requestedId?
|
|
|
1214
1492
|
const ownCap = deps.cap?.(requested.seat);
|
|
1215
1493
|
const details = seatCapDetails({ seat: requested.seat, caps, gate, injectedCap: ownCap });
|
|
1216
1494
|
const active = seatActive(requested.seat);
|
|
1217
|
-
if (active >= details.cap) return
|
|
1495
|
+
if (active >= details.cap) return seatLine(requested.seat, active, details);
|
|
1218
1496
|
const budget = decideSpawn({ seat: requested.seat, running: active, caps, gate, injectedCap: ownCap });
|
|
1219
|
-
return budget.allow ? undefined : `${budget.reason}: ${
|
|
1497
|
+
return budget.allow ? undefined : `${budget.reason}: ${seatLine(requested.seat, active, details)}`;
|
|
1220
1498
|
})();
|
|
1221
1499
|
const reason = own ?? `다른 자리 차례 — ${item.seat}`;
|
|
1222
1500
|
if (requested.waitingReason !== reason) save(path, current.map((row) => row.id === requestedId ? { ...row, waitingReason: reason } : row));
|
|
@@ -1224,7 +1502,7 @@ export async function tickHarnessQueue(deps: HarnessQueueDeps = {}, requestedId?
|
|
|
1224
1502
|
return { outcome: 'waiting', item: { ...requested, waitingReason: reason }, reason };
|
|
1225
1503
|
}
|
|
1226
1504
|
// 발사 직전 다시 본다 — 틱 앞의 확인과 여기 사이에 await(idleRequest·pool·finish 측정)가 있어 그새 켜진 동결을 놓치지 않게.
|
|
1227
|
-
const lateHold = launchFreezeHold(root, (deps.now ?? (() => new Date()))());
|
|
1505
|
+
const lateHold = launchFreezeHold(root, (deps.now ?? (() => new Date()))(), queueProdFreezeRoot(root, deps));
|
|
1228
1506
|
if (lateHold) {
|
|
1229
1507
|
save(path, current.map((row) => row.id === item.id ? { ...row, waitingReason: lateHold.reason } : row));
|
|
1230
1508
|
observe('waiting', { id: item.id, seat: item.seat, reason: lateHold.reason }, deps);
|
|
@@ -1232,6 +1510,18 @@ export async function tickHarnessQueue(deps: HarnessQueueDeps = {}, requestedId?
|
|
|
1232
1510
|
catch { /* Observation cannot affect dispatch. */ }
|
|
1233
1511
|
return { outcome: 'waiting', item: { ...item, waitingReason: lateHold.reason }, reason: lateHold.reason };
|
|
1234
1512
|
}
|
|
1513
|
+
// QUEUE-BURST: scheduled launches stay `launchStaggerMs` after the last queue launch — any tick or process, a direct
|
|
1514
|
+
// launch included — so an overlapping cron tick cannot double a burst. A requested (direct say/ask) tick keeps its
|
|
1515
|
+
// launch-at-once contract (ONEDOOR-2) and only records its time for the next scheduled tick.
|
|
1516
|
+
const stagger = requestedId ? 0 : deps.launchStaggerMs ?? QUEUE_LAUNCH_STAGGER_MS;
|
|
1517
|
+
const lastAt = stagger > 0 ? lastQueueLaunchAt(path) : undefined;
|
|
1518
|
+
const sinceLast = lastAt === undefined ? undefined : (deps.now ?? (() => new Date()))().getTime() - lastAt;
|
|
1519
|
+
if (sinceLast !== undefined && sinceLast >= 0 && sinceLast < stagger) {
|
|
1520
|
+
const reason = `${queueStaggerPrefix} — 마지막 발사 ${Math.floor(sinceLast / 1000)}s 전 · ${Math.round(stagger / 1000)}s 간격`;
|
|
1521
|
+
save(path, current.map((row) => row.id === item.id ? { ...row, waitingReason: reason } : row));
|
|
1522
|
+
observe('waiting', { id: item.id, seat: item.seat, reason, sinceLastMs: sinceLast, staggerMs: stagger }, deps);
|
|
1523
|
+
return { outcome: 'waiting', item: { ...item, waitingReason: reason }, reason };
|
|
1524
|
+
}
|
|
1235
1525
|
// UUIDv7 carries the launch time, so the dead-row grace can run from launch without a new queue field.
|
|
1236
1526
|
const launching: QueueItem = { ...item, status: 'launching', launchId: `hq-${Bun.randomUUIDv7()}` };
|
|
1237
1527
|
delete launching.waitingReason;
|
|
@@ -1241,7 +1531,7 @@ export async function tickHarnessQueue(deps: HarnessQueueDeps = {}, requestedId?
|
|
|
1241
1531
|
if (!Number.isSafeInteger(pid) || pid < 1) throw new Error('launcher returned no pid');
|
|
1242
1532
|
const launched: QueueItem = { ...launching, pid, status: 'launched' };
|
|
1243
1533
|
save(path, current.map((row) => row.id === item.id ? launched : row));
|
|
1244
|
-
try { saveLastLaunchedSeat(path, item.seat); }
|
|
1534
|
+
try { saveLastLaunchedSeat(path, item.seat, (deps.now ?? (() => new Date()))().getTime()); }
|
|
1245
1535
|
catch (error) {
|
|
1246
1536
|
try { debug.log('harness.queue', 'round-robin-save-failed', { id: item.id, seat: item.seat, reason: String(error) }, { level: 'warn' }); }
|
|
1247
1537
|
catch { /* Observation cannot change an already persisted launch. */ }
|