elanous 0.2.19 → 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/{J5H7BvqgSI8DUjtRRcA_S → Bam1YziK2iH9bN4K0utGk}/_buildManifest.js +1 -1
- package/apps/pwa/out/_next/static/chunks/{4323.c5dfb6f503304775.js → 1268.0840ff91e083fcbd.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/1307-77fad80cf54477c6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1612-f60c0c93ec773f81.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1826-5d279da64bbbc2de.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1844-9ec4b1d12f6e80eb.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1866-e7a6999f25548b30.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1948.15f356b15a625597.js +5 -0
- package/apps/pwa/out/_next/static/chunks/195-fb1d7690bf30bab4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1986-33e13d648a9df765.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{2977-e38442e86289f1d3.js → 211-9c9090c59e2c5907.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/2116-6429c188eb846ab8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2196-4700653d12a44b40.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/2537-6c289376caa88ca1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2549-89c518a4c8ed4d00.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2699-325cc923ba7b8631.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2957-96ffab3d521fb08f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/311.e685738f92e6e2ec.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3194-807b212fb8eae065.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3324-d1d2979821dfa845.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3558-bbb5ab553a0471d3.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3857-995ea9f5ef33d71e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3916-3d02dd1fc257dd3c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4144-fad095b48a1862ee.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4565-3d7e3fdeb43686a8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4795-f372490b178583fc.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5360-be1f1d8f9fcc9755.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/{4847.56f5bc454fbcc827.js → 6110.1297d4093fdfbb0d.js} +2 -2
- package/apps/pwa/out/_next/static/chunks/6158.af2e93b7adff709b.js +2 -0
- package/apps/pwa/out/_next/static/chunks/6877-74399f69c7da1433.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{1470.98a6ab61ccb4131c.js → 726.83532d69452ee24b.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/7290-f09c928a06c52e2d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{6062-73e68343a9814f6a.js → 7327-2d69fb321ba73389.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{8870-9614a213b990b10b.js → 7748-6e51bfe927019f5c.js} +3 -3
- package/apps/pwa/out/_next/static/chunks/7797-df5aa0381fdc078f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8060-cc008cc6fedfa818.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8085-d065d0393c1760f5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8401-35e94e9091f5eb8d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8682-7061dd4fa7bd62ed.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8695-846034ce44852c70.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9070.067c106528ec1ae6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9103-60fbef33e66f0a46.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9247-c1753a059e7bac08.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{7647.2fcd30d894d5ee30.js → 935.4ddaa99fc1d1126c.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{ae787e4c-e5b1ef00b2dbceca.js → a9f2b54d-33c0689b70ed8cf3.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/_not-found/page-c1a96669158471ec.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-c855c24de7db74fe.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/autopilot/page-85b316bb4235980b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/board/page-9b2b26a33cb2cd42.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/botlab/page-c9955fde67b1bcb5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/bots/page-7a852956c74015a3.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ceo/page-c5a5c621be0e04ae.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/chat/page-4ba4abe096b6c2a8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/consult/page-575d4a8b4f7becb2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/control/page-f5d56ff5f45ad164.js +1 -0
- 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-7c058069c5e7fa63.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/editor/page-dc5da0b8a2edb8a4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/exec/page-466bf0ee16ad1432.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/field/page-a23bc71713bcc08f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/healthz/route-c1a96669158471ec.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/inside/{page-5b29d9b06ceadade.js → page-ed4a6f799f005d09.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/intake/page-9cd565005b93aca6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/layout-55f0f40c6db95f15.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/live/page-474cb5888ea0e8f3.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/loops/page-35bfec2e631d3eae.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/market/page-9ca21f9ad3208a61.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-f9ccb1b1adbea14d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/morning/page-ec102c9b8b691e37.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/not-found-a9c2cfa8dda922a5.js +1 -0
- 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-db03ae3589a81c9e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-4a6463b97f0ad94f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-75bffecc9aa1004e.js +1 -0
- 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-31d5b875760bb62d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-c26fa10aac6c21c4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/layout-c1a96669158471ec.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/page-68793c6e10331d66.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/share/page-46d5201d2207a511.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-d81c07a7b4f618ae.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-be1c4e8697f22360.js +1 -0
- 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-0fd895fab9b62c1b.js +1 -0
- 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-34882c84f84021c5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-0506a1e0e4bdb2f5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{8046cadf-211ca3c0e00f2b72.js → f6231284-45726550aba08fca.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{2ea96958.e8c7e112de50b067.js → f6b7c9ba.64a895b8b2fedbdc.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/framework-5c169757551134ed.js +1 -0
- package/apps/pwa/out/_next/static/chunks/main-59fe95fed40c7a01.js +1 -0
- package/apps/pwa/out/_next/static/chunks/main-app-4ca74dbbee7057c6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/pages/_app-13a0a886df4d97a1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/pages/_error-6af8591cd3a3f038.js +1 -0
- package/apps/pwa/out/_next/static/chunks/webpack-c260571513bf43c2.js +1 -0
- package/apps/pwa/out/_next/static/css/c23d959d3ee15e57.css +1 -0
- package/apps/pwa/out/approvals/index.html +1 -1
- package/apps/pwa/out/approvals/index.txt +23 -23
- package/apps/pwa/out/autopilot/index.html +1 -1
- package/apps/pwa/out/autopilot/index.txt +23 -23
- package/apps/pwa/out/board/index.html +1 -1
- package/apps/pwa/out/board/index.txt +23 -23
- package/apps/pwa/out/botlab/index.html +1 -1
- package/apps/pwa/out/botlab/index.txt +22 -22
- package/apps/pwa/out/bots/index.html +1 -1
- package/apps/pwa/out/bots/index.txt +22 -22
- package/apps/pwa/out/ceo/index.html +1 -1
- package/apps/pwa/out/ceo/index.txt +23 -23
- package/apps/pwa/out/chat/index.html +1 -1
- package/apps/pwa/out/chat/index.txt +22 -22
- package/apps/pwa/out/consult/index.html +1 -1
- package/apps/pwa/out/consult/index.txt +23 -23
- package/apps/pwa/out/control/index.html +1 -1
- package/apps/pwa/out/control/index.txt +23 -23
- package/apps/pwa/out/decisions/index.html +1 -1
- package/apps/pwa/out/decisions/index.txt +22 -22
- package/apps/pwa/out/design-check/index.html +1 -1
- package/apps/pwa/out/design-check/index.txt +23 -23
- package/apps/pwa/out/editor/index.html +1 -1
- package/apps/pwa/out/editor/index.txt +23 -23
- package/apps/pwa/out/exec/index.html +1 -1
- package/apps/pwa/out/exec/index.txt +23 -23
- package/apps/pwa/out/field/index.html +1 -1
- package/apps/pwa/out/field/index.txt +23 -23
- package/apps/pwa/out/healthz +1 -1
- package/apps/pwa/out/index.html +1 -1
- package/apps/pwa/out/index.txt +22 -22
- package/apps/pwa/out/inside/index.html +1 -1
- package/apps/pwa/out/inside/index.txt +22 -22
- package/apps/pwa/out/intake/index.html +1 -1
- package/apps/pwa/out/intake/index.txt +23 -23
- package/apps/pwa/out/live/index.html +1 -1
- package/apps/pwa/out/live/index.txt +23 -23
- package/apps/pwa/out/loops/index.html +1 -1
- package/apps/pwa/out/loops/index.txt +22 -22
- package/apps/pwa/out/market/index.html +1 -1
- package/apps/pwa/out/market/index.txt +22 -22
- package/apps/pwa/out/missions/placeholder/index.html +1 -1
- package/apps/pwa/out/missions/placeholder/index.txt +22 -22
- package/apps/pwa/out/morning/index.html +1 -1
- package/apps/pwa/out/morning/index.txt +23 -23
- package/apps/pwa/out/observatory/index.html +1 -1
- package/apps/pwa/out/observatory/index.txt +23 -23
- package/apps/pwa/out/ops/checklist/index.html +1 -1
- package/apps/pwa/out/ops/checklist/index.txt +22 -22
- package/apps/pwa/out/ops/release/index.html +1 -1
- package/apps/pwa/out/ops/release/index.txt +22 -22
- package/apps/pwa/out/ops/seats/index.html +1 -1
- package/apps/pwa/out/ops/seats/index.txt +22 -22
- package/apps/pwa/out/outputs/index.html +1 -1
- package/apps/pwa/out/outputs/index.txt +22 -22
- package/apps/pwa/out/reflection/index.html +1 -1
- package/apps/pwa/out/reflection/index.txt +23 -23
- package/apps/pwa/out/scheduler/index.html +1 -1
- package/apps/pwa/out/scheduler/index.txt +23 -23
- package/apps/pwa/out/sessions/index.html +1 -1
- package/apps/pwa/out/sessions/index.txt +22 -22
- package/apps/pwa/out/settings/devices/index.html +1 -1
- package/apps/pwa/out/settings/devices/index.txt +23 -23
- package/apps/pwa/out/settings/index.html +1 -1
- package/apps/pwa/out/settings/index.txt +23 -23
- package/apps/pwa/out/setup/done/index.html +1 -1
- package/apps/pwa/out/setup/done/index.txt +23 -23
- package/apps/pwa/out/setup/index.html +1 -1
- package/apps/pwa/out/setup/index.txt +23 -23
- package/apps/pwa/out/share/index.html +1 -1
- package/apps/pwa/out/share/index.txt +23 -23
- package/apps/pwa/out/showroom/index.html +1 -1
- package/apps/pwa/out/showroom/index.txt +22 -22
- package/apps/pwa/out/tasks/index.html +1 -1
- package/apps/pwa/out/tasks/index.txt +23 -23
- package/apps/pwa/out/term/index.html +1 -1
- package/apps/pwa/out/term/index.txt +23 -23
- package/apps/pwa/out/today/index.html +1 -1
- package/apps/pwa/out/today/index.txt +23 -23
- package/apps/pwa/out/trace/index.html +1 -1
- package/apps/pwa/out/trace/index.txt +23 -23
- package/apps/pwa/out/vault/index.html +1 -1
- package/apps/pwa/out/vault/index.txt +23 -23
- package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
- package/apps/pwa/out/workflows/chat-ui/index.txt +23 -23
- package/apps/pwa/out/workflows/index.html +1 -1
- package/apps/pwa/out/workflows/index.txt +23 -23
- package/apps/pwa/out/workspace/index.html +1 -1
- package/apps/pwa/out/workspace/index.txt +23 -23
- package/apps/pwa/out/worktrees/index.html +1 -1
- package/apps/pwa/out/worktrees/index.txt +23 -23
- package/graphs/demo/editor-recipes.yaml +22 -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/land-global-checks.ts +89 -0
- package/scripts/lib/pch-regress-map.ts +2 -1
- package/scripts/release-loop/gate-node.ts +296 -25
- package/scripts/release-loop/gate-timing.ts +116 -7
- 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 +4 -1
- package/scripts/release-loop/unattended-release.ts +10 -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/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/release-watch.ts +20 -3
- package/src/cli/config-drift.ts +513 -0
- package/src/cli/log-event-names.ts +4 -0
- package/src/cli/pr-cli.ts +20 -0
- package/src/cli/release-cli.ts +20 -5
- package/src/cli/retro-cli.ts +78 -0
- package/src/cli/tasks-cli.ts +94 -8
- package/src/dashboard/slash-runtime/dashboard-handlers.ts +5 -4
- package/src/dashboard/slash-runtime/help-from-registry.ts +57 -31
- package/src/domains/outbound-log-probe.ts +6 -1
- package/src/graph-runner/graph-run-api.ts +205 -0
- package/src/graph-runner/runner.ts +11 -1
- package/src/harness/harness-cli-command.ts +419 -52
- package/src/harness/harness-queue.ts +315 -25
- package/src/harness/harness-salvage-cli.ts +62 -0
- package/src/hq/fence-outcomes.ts +63 -0
- package/src/hq/fence-wrapper.ts +33 -0
- package/src/hq/hq.ts +11 -1
- package/src/index.ts +69 -6
- package/src/maturity/feature-maturity.ts +2 -2
- 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 +38 -3
- package/src/nexus/api/loop-edges.ts +4 -2
- package/src/nexus/api/ops-api.ts +22 -7
- package/src/nexus/api/ops-board-route.ts +48 -0
- package/src/onboarding/entry-hints.ts +5 -0
- package/src/onboarding.ts +24 -6
- package/src/release-loop/feature-store.ts +14 -11
- package/src/release-loop/gate-shards.ts +158 -0
- package/src/release-loop/release-schedule.ts +6 -2
- 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/draft-sweep.ts +371 -16
- package/src/self-dev/draft-triage-rules.ts +19 -4
- package/src/self-dev/pr-preview.ts +84 -16
- package/src/self-implement/blocked-draft-policy.ts +101 -0
- package/src/self-implement/execution-clarification.ts +11 -0
- package/src/self-implement/gate-env-retry.ts +83 -0
- package/src/self-implement/orchestrator.ts +218 -5
- package/src/self-implement/run-stop.ts +123 -0
- package/src/self-implement/run-ttl.ts +258 -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/board.ts +1 -1
- package/src/task-agent/card-evidence.ts +99 -10
- package/src/task-agent/judge.ts +7 -0
- package/src/task-agent/live-moves.ts +120 -20
- package/src/task-agent/mission.ts +1 -1
- package/src/task-agent/shadow.ts +10 -1
- package/src/task-agent/task-hand.ts +7 -2
- package/src/task-orchestrator/surfaces/pod-command-job.ts +19 -5
- 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 +93 -10
- package/src/task-orchestrator/surfaces/pod-pool.ts +147 -34
- package/src/task-orchestrator/surfaces/self-implement-pod.ts +19 -3
- package/src/telegram-commands.ts +41 -0
- package/src/testing/real-cli-spawn.ts +106 -0
- package/src/user-config.ts +56 -3
- package/src/version/packed-revision.json +1 -1
- package/apps/pwa/out/_next/static/chunks/1105-c473ed46272fba79.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1157-cba0e8553d35a980.js +0 -1
- package/apps/pwa/out/_next/static/chunks/171-23a877ed11720de9.js +0 -1
- package/apps/pwa/out/_next/static/chunks/216-5acd01cbde4acda7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2330-c2f07246c8788af1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2336-e830c777eb6bce04.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2375-e061bb24cd2b5cde.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2415.ed6428ff334c7ce2.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3094.67339318c7f72a3a.js +0 -2
- package/apps/pwa/out/_next/static/chunks/338-ceb956612dcfaa86.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3395-835e4ceaa6335ba4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3403.4bc97b4066945a94.js +0 -5
- package/apps/pwa/out/_next/static/chunks/3441-be82e3595aef3a20.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3689-89905039bf3e7257.js +0 -1
- package/apps/pwa/out/_next/static/chunks/371-4ad6af55dad02ced.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3822-6f268bc26c6a84ac.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4114-3e63117452378807.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4457-d7df81c90f9d0268.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4551-77dadae76f6cbace.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4978.6fd93b974441e87f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5012-f746f6549b869f91.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5609-012bb3557211f07e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5791-bb82743fa6299562.js +0 -1
- package/apps/pwa/out/_next/static/chunks/62-5bfed1e23790b745.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6320-9bc8561fde416e07.js +0 -1
- package/apps/pwa/out/_next/static/chunks/649-ca96d1c5d09e02b8.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6725-68ab7cc0b1faf00d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6726-85a7db59b288c53f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6735-33267c4630d0d963.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6998-a654fe5c58271600.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7633-141682015873beb6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7673-2eda1507d38dadb8.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7990-e12efba616f46e03.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8067-143791231c99bd02.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8142-744998d65d7bb17e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8430-a10cfba8ef282d4e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/920-f7dceb78f1c585ef.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9454-12c2f30dea62291c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/976-4a9b4ebdca7c739a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9989-15cdf568bafa9fc8.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/_not-found/page-859cea1570b6227a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-deef5ee191830f14.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/autopilot/page-b193da485f9a14b2.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/board/page-b6e1a79df0e214f4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/botlab/page-e84df4d907f703c7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/bots/page-a5fb9a5db89ae4ea.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ceo/page-1499f481d7fc52d6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/chat/page-c7cf753aeb648790.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/consult/page-4118469e514e10fd.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/control/page-e968f7630654332a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/decisions/page-505915052f947022.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-c2f962fb4a3b06c6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/editor/page-7b5ed0bd8a589ec4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/exec/page-cf8193250dd42dd4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/field/page-cf3f5dfa35dea8e9.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/healthz/route-859cea1570b6227a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/intake/page-a15e7fcc283e9ca7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/layout-d2915e7b70ff3d0e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/live/page-b70cd005d9169e25.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/loops/page-2595f0eba29f57e1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/market/page-c776322454156e36.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-77bbd17f4435aa10.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/morning/page-049689e611ecc936.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/not-found-85954a4fb09c44eb.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-e773e552e1068ee6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-5633c4bfa7684b4a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-d829b92bd01b4994.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-061c317fc771becf.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/outputs/page-3a45ece8865ed4b6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/page-0be990618c0d56bd.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-bfea4865dc244b73.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-0d6ba8e337741484.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-76a88b55d816482e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-bba1542df59f61ce.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/page-4185c95689bb372e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-b699e1a8d5d34b8b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/layout-859cea1570b6227a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/page-c225e338c31bf274.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/share/page-449353f70507ca1f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-90b889714102476d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-f1ee7fbeb7f96ddc.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/term/page-d59a95e1434dfaa0.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/today/page-71491a23414d1a47.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/trace/page-de7112bdb65fa4fe.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/vault/page-feb4d0d88079660d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-f17d887b40dd6b1d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-8da77be2192bcd5d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-72c3b9a5e9582b29.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-c3afe4c8d0c48c39.js +0 -1
- package/apps/pwa/out/_next/static/chunks/framework-ea806bb57f2b8923.js +0 -1
- package/apps/pwa/out/_next/static/chunks/main-8d2302399c2c8f87.js +0 -1
- package/apps/pwa/out/_next/static/chunks/main-app-b9308f2d8a931531.js +0 -1
- package/apps/pwa/out/_next/static/chunks/pages/_app-b10928c60f0b8488.js +0 -1
- package/apps/pwa/out/_next/static/chunks/pages/_error-689039227d443fab.js +0 -1
- package/apps/pwa/out/_next/static/chunks/webpack-6f35f9d892fa630b.js +0 -1
- package/apps/pwa/out/_next/static/css/85f53c6f35d11382.css +0 -1
- /package/apps/pwa/out/_next/static/{J5H7BvqgSI8DUjtRRcA_S → Bam1YziK2iH9bN4K0utGk}/_ssgManifest.js +0 -0
|
@@ -31,6 +31,8 @@ export interface LoopEdge {
|
|
|
31
31
|
tick?: string;
|
|
32
32
|
/** live claimed 뒤 handed·released 없이 N분 지남 = 발사 실패(끊긴 넘김). ref = 오케스트레이터 카드 id. */
|
|
33
33
|
broken?: true;
|
|
34
|
+
/** launch 간선만 — 그 카드에 묶인 런 원장 runId(ID 정규식). 못 묶으면 생략(PWA 가 10분 뒤 «유령»으로 본다). */
|
|
35
|
+
run?: string;
|
|
34
36
|
}
|
|
35
37
|
|
|
36
38
|
/** 노드 접두 — 조율 루프는 레지스트리 행 `loop:orchestrator` 그대로 쓴다. `queue:` 는 대기열 대체 간선 자리(조각 A 밖). */
|
|
@@ -369,10 +371,10 @@ function taskAgentEdges(deps: LoopEdgesDeps, facts: readonly RunFact[], seats: R
|
|
|
369
371
|
if (typeof card.mission === 'string' && ID.test(card.mission)) journey.links.push({ from: card.id, to: card.mission, both: false });
|
|
370
372
|
if (Array.isArray(card.pieces)) journey.pieces.set(card.id, card.pieces.filter(piece => typeof piece === 'string' && ID.test(piece)));
|
|
371
373
|
const launches = launchesOf(card);
|
|
374
|
+
const runId = linkedRun(card, launches[0]?.at, launchedTexts, facts, childIds);
|
|
372
375
|
for (const launch of launches) {
|
|
373
|
-
if (seat(card.seat, seats) && inWindow(launch.at, since, now)) edges.push({ at: launch.at, kind: 'launch', from: TASK_AGENT_NODE, to: card.seat, ref: card.id });
|
|
376
|
+
if (seat(card.seat, seats) && inWindow(launch.at, since, now)) edges.push({ at: launch.at, kind: 'launch', from: TASK_AGENT_NODE, to: card.seat, ref: card.id, ...(runId ? { run: runId } : {}) });
|
|
374
377
|
}
|
|
375
|
-
const runId = linkedRun(card, launches[0]?.at, launchedTexts, facts, childIds);
|
|
376
378
|
if (runId) journey.links.push({ from: card.id, to: runId, both: true });
|
|
377
379
|
const green = object(card.greenProposal);
|
|
378
380
|
if (green && typeof green.at === 'string' && typeof green.checklistId === 'string' && ID.test(green.checklistId) && inWindow(green.at, since, now)) {
|
package/src/nexus/api/ops-api.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { lstatSync, readFileSync, readdirSync } from 'node:fs';
|
|
2
2
|
import { join } from 'node:path';
|
|
3
3
|
import { debug, redactSecretText } from '../../debug/log.js';
|
|
4
|
-
import { effectiveInstanceRoot } from '../../instance/resolve.js';
|
|
4
|
+
import { effectiveInstanceRoot, releaseLedgerRoot } from '../../instance/resolve.js';
|
|
5
|
+
import { gateShardsPath, readGateShards, summarizeShards, type GateShardsFile, type GateShardsSummary } from '../../release-loop/gate-shards.js';
|
|
5
6
|
import { listChecklist } from '../../release-loop/checklist.js';
|
|
6
7
|
import { getSchedule } from '../../release-loop/release-schedule.js';
|
|
7
8
|
import { jsonResponse } from './json-response.js';
|
|
@@ -123,13 +124,25 @@ export function releaseNodeFacts(output: unknown): ReleaseNodeFacts {
|
|
|
123
124
|
}
|
|
124
125
|
export interface ReleaseRunView {
|
|
125
126
|
runId: string; status: string; startedAt: string; version: string | null; path: string[]; nodes: ReleaseRunNodeView[];
|
|
127
|
+
/** GATE-LIVE-OBS — 지금 gate 노드에 있는 런에만 싣는다(발행 원장 `gate-logs/cut/shards.json`). 파일이 없으면 생략. */
|
|
128
|
+
gateShards?: GateShardsFile & { summary: GateShardsSummary };
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/** 도는 gate 노드의 조각 표 — 다른 판의 파일을 붙이지 않게 version 이 같을 때만. */
|
|
132
|
+
function gateShardsFor(version: string | null, status: string, nodes: ReleaseRunNodeView[], ledgerRoot: string, now: number):
|
|
133
|
+
ReleaseRunView['gateShards'] | undefined {
|
|
134
|
+
if (!version || status !== 'running' || !nodes.some((node) => node.nodeId === 'gate' && node.ok === null)) return undefined;
|
|
135
|
+
const file = readGateShards(gateShardsPath(version, ledgerRoot));
|
|
136
|
+
if (!file || file.version !== version) return undefined;
|
|
137
|
+
return { ...file, shards: file.shards.map((shard) => shard.waitReason ? { ...shard, waitReason: redactSecretText(shard.waitReason) } : shard),
|
|
138
|
+
summary: summarizeShards(file, now) };
|
|
126
139
|
}
|
|
127
140
|
|
|
128
141
|
/** The release run ledger as `/v1/ops/release/runs` serves it (newest 20) — shared with Telegram /release and away mode
|
|
129
142
|
* so every surface reads the same verdicts. `'unavailable'` = the directory exists but cannot be read. */
|
|
130
143
|
export function readReleaseRuns(version: string | null = null,
|
|
131
144
|
dir: string = join(effectiveInstanceRoot(), 'graph-runs', 'release-loop'),
|
|
132
|
-
opts: { facts?: boolean } = {}): ReleaseRunView[] | 'unavailable' {
|
|
145
|
+
opts: { facts?: boolean; ledgerRoot?: string; now?: number } = {}): ReleaseRunView[] | 'unavailable' {
|
|
133
146
|
let filenames: string[];
|
|
134
147
|
try {
|
|
135
148
|
filenames = readdirSync(dir, { withFileTypes: true })
|
|
@@ -146,15 +159,17 @@ export function readReleaseRuns(version: string | null = null,
|
|
|
146
159
|
if (!run) return [];
|
|
147
160
|
const v = runVersion(run.input);
|
|
148
161
|
if (version !== null && v !== version) return [];
|
|
162
|
+
const nodes: ReleaseRunNodeView[] = [...run.nodes.filter((node) => node && typeof node.nodeId === 'string').map((node) => ({
|
|
163
|
+
nodeId: redactSecretText(node.nodeId), ok: typeof node.ok === 'boolean' ? node.ok : null,
|
|
164
|
+
summary: nodeSummary(node.output), ...nodeTimes(node),
|
|
165
|
+
...(opts.facts ? { facts: releaseNodeFacts(node.output) } : {}),
|
|
166
|
+
})), ...runningNode(run, run.nodes.length)];
|
|
167
|
+
const gateShards = gateShardsFor(v, run.status, nodes, opts.ledgerRoot ?? releaseLedgerRoot(), opts.now ?? Date.now());
|
|
149
168
|
return [{
|
|
150
169
|
runId: redactSecretText(filename.slice(0, -5)), status: redactSecretText(run.status),
|
|
151
170
|
startedAt: redactSecretText(run.startedAt), version: v,
|
|
152
171
|
path: run.path.filter((step): step is string => typeof step === 'string').map(redactSecretText),
|
|
153
|
-
nodes
|
|
154
|
-
nodeId: redactSecretText(node.nodeId), ok: typeof node.ok === 'boolean' ? node.ok : null,
|
|
155
|
-
summary: nodeSummary(node.output), ...nodeTimes(node),
|
|
156
|
-
...(opts.facts ? { facts: releaseNodeFacts(node.output) } : {}),
|
|
157
|
-
})), ...runningNode(run, run.nodes.length)],
|
|
172
|
+
nodes, ...(gateShards ? { gateShards } : {}),
|
|
158
173
|
}];
|
|
159
174
|
}).sort((a, b) => b.startedAt.localeCompare(a.startedAt)).slice(0, 20);
|
|
160
175
|
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { canonicalSeatId, openMsgStore, type Message, type MsgStore } from '../../msg/msg-store.js';
|
|
2
|
+
import { jsonResponse } from './json-response.js';
|
|
3
|
+
|
|
4
|
+
function ownerNotices(store: Pick<MsgStore, 'list'>): Message[] {
|
|
5
|
+
const notices: Message[] = [];
|
|
6
|
+
let after = 0;
|
|
7
|
+
let batch: Message[];
|
|
8
|
+
do {
|
|
9
|
+
batch = store.list('CEO', after, 1000);
|
|
10
|
+
notices.push(...batch.filter(message => message.from !== 'CEO'));
|
|
11
|
+
if (batch.length) after = batch[batch.length - 1]!.id;
|
|
12
|
+
} while (batch.length === 1000);
|
|
13
|
+
return notices;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const OPS_BOARD_PATH = '/v1/ops-board/notices';
|
|
17
|
+
|
|
18
|
+
export interface OpsBoardDeps {
|
|
19
|
+
authorize: (request: Request) => boolean;
|
|
20
|
+
openStore?: () => Pick<MsgStore, 'list' | 'post' | 'close'>;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Owner reads the CEO inbox without acknowledging it; replies are addressed to the original sender. */
|
|
24
|
+
export async function handleOpsBoard(req: Request, deps: OpsBoardDeps): Promise<Response> {
|
|
25
|
+
if (!deps.authorize(req)) return jsonResponse({ error: 'unauthorized' }, 401);
|
|
26
|
+
if (req.method !== 'GET' && req.method !== 'POST') return jsonResponse({ error: 'method-not-allowed' }, 405);
|
|
27
|
+
const store = (deps.openStore ?? openMsgStore)();
|
|
28
|
+
try {
|
|
29
|
+
if (req.method === 'GET') {
|
|
30
|
+
const notices = ownerNotices(store);
|
|
31
|
+
return jsonResponse({ notices });
|
|
32
|
+
}
|
|
33
|
+
let body: unknown;
|
|
34
|
+
try { body = await req.json(); } catch { return jsonResponse({ error: 'bad_request' }, 400); }
|
|
35
|
+
if (!body || typeof body !== 'object' || Array.isArray(body)) return jsonResponse({ error: 'bad_request' }, 400);
|
|
36
|
+
const input = body as Record<string, unknown>;
|
|
37
|
+
if (!Number.isSafeInteger(input.noticeId) || (input.noticeId as number) < 1
|
|
38
|
+
|| typeof input.body !== 'string' || !input.body.trim() || input.body.length > 3000
|
|
39
|
+
|| Object.keys(input).some(key => key !== 'noticeId' && key !== 'body')) return jsonResponse({ error: 'bad_request' }, 400);
|
|
40
|
+
const notice: Message | undefined = ownerNotices(store).find(message => message.id === input.noticeId);
|
|
41
|
+
if (!notice) return jsonResponse({ error: 'not-found' }, 404);
|
|
42
|
+
const recipient = canonicalSeatId(notice.from);
|
|
43
|
+
const reply = store.post({ from: 'CEO', to: recipient, body: input.body.trim(), kind: 'reply' });
|
|
44
|
+
return jsonResponse({ reply }, 201);
|
|
45
|
+
} finally {
|
|
46
|
+
store.close();
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -5,4 +5,9 @@ export function unattendedSetupHint(): string {
|
|
|
5
5
|
return `unattended: \`${UNATTENDED_SETUP_COMMAND}\``;
|
|
6
6
|
}
|
|
7
7
|
|
|
8
|
+
/** One-line next step printed after a non-TTY onboarding refusal (FIRST-CHAT-NONTTY). */
|
|
9
|
+
export function unattendedNextStepLine(): string {
|
|
10
|
+
return `next: ${unattendedSetupHint()}`;
|
|
11
|
+
}
|
|
12
|
+
|
|
8
13
|
export const SKILLS_LIST_DESCRIPTION = 'List elanous skills from configured skill directories (skills.activeSet / skills.dirs and packaged skills): subdirectories and the first non-heading line of each SKILL.md. Read-only.';
|
package/src/onboarding.ts
CHANGED
|
@@ -59,7 +59,7 @@ import type {
|
|
|
59
59
|
import { showStepOr, chooseFrom } from './onboarding/io-extended.js';
|
|
60
60
|
import { askWithHelp } from './onboarding/wire-helpers.js';
|
|
61
61
|
import { stepTransition } from './onboarding/transition.js';
|
|
62
|
-
import { unattendedSetupHint } from './onboarding/entry-hints.js';
|
|
62
|
+
import { unattendedNextStepLine, unattendedSetupHint } from './onboarding/entry-hints.js';
|
|
63
63
|
import { continueHereLines } from './onboarding/continue-here.js';
|
|
64
64
|
import type { NexusShowResult } from './cli/nexus-show.js';
|
|
65
65
|
import type { TailscaleProbe } from './nexus/onboarding/tailscale-probe.js';
|
|
@@ -1370,15 +1370,33 @@ export class OnboardingRefusedError extends Error {
|
|
|
1370
1370
|
}
|
|
1371
1371
|
}
|
|
1372
1372
|
|
|
1373
|
-
|
|
1374
|
-
|
|
1373
|
+
export interface OnboardingRefusalIO {
|
|
1374
|
+
printError: (line: string) => void;
|
|
1375
|
+
setExitCode: (code: number) => void;
|
|
1376
|
+
}
|
|
1377
|
+
|
|
1378
|
+
const defaultRefusalIO: OnboardingRefusalIO = {
|
|
1379
|
+
printError: (line) => console.error(line),
|
|
1380
|
+
setExitCode: (code) => { process.exitCode = code; },
|
|
1381
|
+
};
|
|
1382
|
+
|
|
1383
|
+
/** Handle only typed refusals; the agent setup hint applies to its non-TTY path alone.
|
|
1384
|
+
* FIRST-CHAT-NONTTY: on the non-TTY path of any other entrance the refusal ends with a
|
|
1385
|
+
* one-line next step (`unattendedNextStepLine()`), so a script or app that called first knows
|
|
1386
|
+
* what to run. The exit code stays 2. */
|
|
1387
|
+
export function handleOnboardingRefusal(
|
|
1388
|
+
error: unknown,
|
|
1389
|
+
entrance: 'agent' | 'other',
|
|
1390
|
+
io: OnboardingRefusalIO = defaultRefusalIO,
|
|
1391
|
+
): boolean {
|
|
1375
1392
|
if (!(error instanceof OnboardingRefusedError) || error.code !== 'onboarding-refused') return false;
|
|
1376
1393
|
if (entrance === 'agent' && error.reason === 'non-tty') {
|
|
1377
|
-
|
|
1394
|
+
io.printError(`elanous agent needs a configured LLM. Run \`elanous onboarding\` in a terminal, or ${unattendedSetupHint()} for unattended setup.`);
|
|
1378
1395
|
} else {
|
|
1379
|
-
|
|
1396
|
+
io.printError(error.message);
|
|
1397
|
+
if (error.reason === 'non-tty') io.printError(unattendedNextStepLine());
|
|
1380
1398
|
}
|
|
1381
|
-
|
|
1399
|
+
io.setExitCode(2);
|
|
1382
1400
|
return true;
|
|
1383
1401
|
}
|
|
1384
1402
|
|
|
@@ -36,7 +36,7 @@ const SCHEMA_COLUMNS: Record<string, readonly string[]> = {
|
|
|
36
36
|
imported_versions: ['json_hash', 'imported_at'],
|
|
37
37
|
events: ['reason', 'generation'],
|
|
38
38
|
assignments: ['priority', 'predecessors', 'deadline_version', 'ceo_minutes', 'ceo_date', 'accelerator', 'refs'],
|
|
39
|
-
release_schedules: ['freeze_from', 'freeze_until'],
|
|
39
|
+
release_schedules: ['freeze_from', 'freeze_until', 'publish_at'],
|
|
40
40
|
};
|
|
41
41
|
function schemaCurrent(db: Database): boolean {
|
|
42
42
|
return Object.entries(SCHEMA_COLUMNS).every(([table, names]) => {
|
|
@@ -83,16 +83,16 @@ function open(root = releaseLedgerRoot()): Database {
|
|
|
83
83
|
if (!assignmentColumns.some((column) => column.name === name)) db.exec(`ALTER TABLE assignments ADD COLUMN ${name} ${sql}`);
|
|
84
84
|
}
|
|
85
85
|
const scheduleColumns = db.query('PRAGMA table_info(release_schedules)').all() as Array<{ name: string }>;
|
|
86
|
-
for (const name of ['freeze_from', 'freeze_until']) if (!scheduleColumns.some((column) => column.name === name)) db.exec(`ALTER TABLE release_schedules ADD COLUMN ${name} TEXT`);
|
|
86
|
+
for (const name of ['freeze_from', 'freeze_until', 'publish_at']) if (!scheduleColumns.some((column) => column.name === name)) db.exec(`ALTER TABLE release_schedules ADD COLUMN ${name} TEXT`);
|
|
87
87
|
}).immediate();
|
|
88
88
|
return db;
|
|
89
89
|
} catch (error) { db.close(); throw error; }
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
-
export interface ScheduleRow { version: string; cutAt: string; landBy: string | null; freezeFrom?: string | null; freezeUntil?: string | null; updatedAt: string; updatedBy: string }
|
|
93
|
-
type StoredSchedule = { version: string; cut_at: string; land_by: string | null; freeze_from: string | null; freeze_until: string | null; updated_at: string; updated_by: string };
|
|
92
|
+
export interface ScheduleRow { version: string; cutAt: string; landBy: string | null; freezeFrom?: string | null; freezeUntil?: string | null; publishAt?: string | null; updatedAt: string; updatedBy: string }
|
|
93
|
+
type StoredSchedule = { version: string; cut_at: string; land_by: string | null; freeze_from: string | null; freeze_until: string | null; publish_at?: string | null; updated_at: string; updated_by: string };
|
|
94
94
|
function scheduleRow(row: StoredSchedule): ScheduleRow {
|
|
95
|
-
return { version: row.version, cutAt: row.cut_at, landBy: row.land_by, ...(row.freeze_from ? { freezeFrom: row.freeze_from } : {}), ...(row.freeze_until ? { freezeUntil: row.freeze_until } : {}), updatedAt: row.updated_at, updatedBy: row.updated_by };
|
|
95
|
+
return { version: row.version, cutAt: row.cut_at, landBy: row.land_by, ...(row.freeze_from ? { freezeFrom: row.freeze_from } : {}), ...(row.freeze_until ? { freezeUntil: row.freeze_until } : {}), ...(row.publish_at ? { publishAt: row.publish_at } : {}), updatedAt: row.updated_at, updatedBy: row.updated_by };
|
|
96
96
|
}
|
|
97
97
|
|
|
98
98
|
export function readSchedule(version: string, root?: string): ScheduleRow | null {
|
|
@@ -119,7 +119,7 @@ export function assignedVersions(root = releaseLedgerRoot()): string[] {
|
|
|
119
119
|
finally { db.close(); }
|
|
120
120
|
}
|
|
121
121
|
|
|
122
|
-
export function writeSchedule(version: string, patch: { cutAt?: string; landBy?: string; freezeFrom?: string; freezeUntil?: string }, by: string, root?: string): ScheduleRow {
|
|
122
|
+
export function writeSchedule(version: string, patch: { cutAt?: string; landBy?: string; freezeFrom?: string; freezeUntil?: string; publishAt?: string }, by: string, root?: string): ScheduleRow {
|
|
123
123
|
validateVersion(version);
|
|
124
124
|
const db = open(root);
|
|
125
125
|
try {
|
|
@@ -130,19 +130,22 @@ export function writeSchedule(version: string, patch: { cutAt?: string; landBy?:
|
|
|
130
130
|
const landBy = patch.landBy ?? previous?.land_by ?? null;
|
|
131
131
|
const freezeFrom = patch.freezeFrom ?? previous?.freeze_from ?? null;
|
|
132
132
|
const freezeUntil = patch.freezeUntil ?? previous?.freeze_until ?? null;
|
|
133
|
+
const publishAt = patch.publishAt ?? previous?.publish_at ?? null;
|
|
134
|
+
if (publishAt && Date.parse(publishAt) <= Date.parse(cutAt)) throw new CliUserError('발행 시각은 컷보다 뒤여야 한다');
|
|
133
135
|
if (Boolean(freezeFrom) !== Boolean(freezeUntil) || (freezeFrom && freezeUntil && Date.parse(freezeFrom) >= Date.parse(freezeUntil))) throw new CliUserError('동결 시작·끝은 함께 주고 시작이 끝보다 앞서야 한다');
|
|
134
|
-
if (previous && previous.cut_at === cutAt && previous.land_by === landBy && previous.freeze_from === freezeFrom && previous.freeze_until === freezeUntil) return scheduleRow(previous);
|
|
136
|
+
if (previous && previous.cut_at === cutAt && previous.land_by === landBy && previous.freeze_from === freezeFrom && previous.freeze_until === freezeUntil && (previous.publish_at ?? null) === publishAt) return scheduleRow(previous);
|
|
135
137
|
const at = new Date().toISOString();
|
|
136
|
-
db.query(`INSERT INTO release_schedules (version, cut_at, land_by, freeze_from, freeze_until, updated_at, updated_by) VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
137
|
-
ON CONFLICT(version) DO UPDATE SET cut_at=excluded.cut_at, land_by=excluded.land_by, freeze_from=excluded.freeze_from, freeze_until=excluded.freeze_until, updated_at=excluded.updated_at, updated_by=excluded.updated_by`)
|
|
138
|
-
.run(version, cutAt, landBy, freezeFrom, freezeUntil, at, by);
|
|
138
|
+
db.query(`INSERT INTO release_schedules (version, cut_at, land_by, freeze_from, freeze_until, publish_at, updated_at, updated_by) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
139
|
+
ON CONFLICT(version) DO UPDATE SET cut_at=excluded.cut_at, land_by=excluded.land_by, freeze_from=excluded.freeze_from, freeze_until=excluded.freeze_until, publish_at=excluded.publish_at, updated_at=excluded.updated_at, updated_by=excluded.updated_by`)
|
|
140
|
+
.run(version, cutAt, landBy, freezeFrom, freezeUntil, publishAt, at, by);
|
|
139
141
|
for (const [field, from, to] of [
|
|
140
142
|
['cut_at', previous?.cut_at ?? null, cutAt], ['land_by', previous?.land_by ?? null, landBy],
|
|
141
143
|
['freeze_from', previous?.freeze_from ?? null, freezeFrom], ['freeze_until', previous?.freeze_until ?? null, freezeUntil],
|
|
144
|
+
['publish_at', previous?.publish_at ?? null, publishAt],
|
|
142
145
|
] as const) {
|
|
143
146
|
if (from !== to) record(db, version, { at, by, id: '@version', field, from, to, released: releasedVersion(root), dev: devVersion() });
|
|
144
147
|
}
|
|
145
|
-
return { version, cutAt, landBy, updatedAt: at, updatedBy: by };
|
|
148
|
+
return { version, cutAt, landBy, ...(publishAt ? { publishAt } : {}), updatedAt: at, updatedBy: by };
|
|
146
149
|
});
|
|
147
150
|
} finally { db.close(); }
|
|
148
151
|
}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
// GATE-LIVE-OBS — 게이트 «조각 상태» 한 장(`<ledger>/release/<v>/gate-logs/cut/shards.json`)의 계약.
|
|
2
|
+
// 쓰는 쪽 = gate-node(TC · GATE-TIMEOUT-HEAL) · 읽는 쪽 = /v1/ops/release/runs ⊕ 외출 알림(UX).
|
|
3
|
+
// 로그가 아니라 발행 원장 파일인 이유: 게이트 사건은 실행 트리의 test 우주로 가지만 원장은 운영 한 곳이다(TC·OP 10-07 19:12).
|
|
4
|
+
// ⛔ 못 읽으면 `null` — 「조각 0」으로 읽지 않는다.
|
|
5
|
+
import { mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
|
|
6
|
+
import { dirname, join } from 'node:path';
|
|
7
|
+
import { releaseLedgerRoot } from '../instance/resolve.js';
|
|
8
|
+
|
|
9
|
+
export const SHARD_STATES = ['pending', 'running', 'done', 'retry', 'timeout', 'failed'] as const;
|
|
10
|
+
export type ShardState = typeof SHARD_STATES[number];
|
|
11
|
+
|
|
12
|
+
export interface GateShard {
|
|
13
|
+
id: string;
|
|
14
|
+
state: ShardState;
|
|
15
|
+
startedAt?: string;
|
|
16
|
+
endedAt?: string;
|
|
17
|
+
/** 설치(install) 단계에 쓴 초. */
|
|
18
|
+
installSec?: number;
|
|
19
|
+
rc?: number;
|
|
20
|
+
/** 대기 이유(예: «CPU 부족 31.2/32»). pending 일 때 화면이 그대로 보인다. */
|
|
21
|
+
waitReason?: string;
|
|
22
|
+
/** 계획 분 — 남은 시간 추정의 재료. */
|
|
23
|
+
plannedMin?: number;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface GateShardsFile {
|
|
27
|
+
v: 1;
|
|
28
|
+
version: string;
|
|
29
|
+
updatedAt: string;
|
|
30
|
+
shards: GateShard[];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function gateShardsPath(version: string, root: string = releaseLedgerRoot()): string {
|
|
34
|
+
return join(root, 'release', version, 'gate-logs', 'cut', 'shards.json');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const str = (value: unknown, max = 160): string | undefined =>
|
|
38
|
+
typeof value === 'string' && value.trim() ? value.trim().slice(0, max) : undefined;
|
|
39
|
+
const num = (value: unknown): number | undefined => typeof value === 'number' && Number.isFinite(value) ? value : undefined;
|
|
40
|
+
|
|
41
|
+
function shardOf(value: unknown): GateShard | null {
|
|
42
|
+
if (!value || typeof value !== 'object') return null;
|
|
43
|
+
const raw = value as Record<string, unknown>;
|
|
44
|
+
const id = str(raw.id, 80);
|
|
45
|
+
const state = SHARD_STATES.find((s) => s === raw.state);
|
|
46
|
+
if (!id || !state) return null;
|
|
47
|
+
const startedAt = str(raw.startedAt, 40);
|
|
48
|
+
const endedAt = str(raw.endedAt, 40);
|
|
49
|
+
const installSec = num(raw.installSec);
|
|
50
|
+
const rc = num(raw.rc);
|
|
51
|
+
const waitReason = str(raw.waitReason);
|
|
52
|
+
const plannedMin = num(raw.plannedMin);
|
|
53
|
+
return {
|
|
54
|
+
id, state,
|
|
55
|
+
...(startedAt ? { startedAt } : {}), ...(endedAt ? { endedAt } : {}),
|
|
56
|
+
...(installSec !== undefined ? { installSec } : {}), ...(rc !== undefined ? { rc } : {}),
|
|
57
|
+
...(waitReason ? { waitReason } : {}), ...(plannedMin !== undefined ? { plannedMin } : {}),
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** 파일이 없거나 깨졌으면 null. 알 수 없는 상태값의 조각은 버리되 파일 전체는 살린다. */
|
|
62
|
+
export function readGateShards(path: string): GateShardsFile | null {
|
|
63
|
+
let parsed: unknown;
|
|
64
|
+
try { parsed = JSON.parse(readFileSync(path, 'utf8')); } catch { return null; }
|
|
65
|
+
if (!parsed || typeof parsed !== 'object') return null;
|
|
66
|
+
const raw = parsed as Record<string, unknown>;
|
|
67
|
+
if (raw.v !== 1 || !Array.isArray(raw.shards)) return null;
|
|
68
|
+
const version = str(raw.version, 40);
|
|
69
|
+
const updatedAt = str(raw.updatedAt, 40);
|
|
70
|
+
if (!version || !updatedAt) return null;
|
|
71
|
+
return { v: 1, version, updatedAt, shards: raw.shards.map(shardOf).filter((s): s is GateShard => s !== null) };
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** tmp → rename — 읽는 쪽이 반쯤 쓴 파일을 보지 않게. */
|
|
75
|
+
export function writeGateShards(path: string, file: GateShardsFile): void {
|
|
76
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
77
|
+
const tmp = `${path}.${process.pid}.tmp`;
|
|
78
|
+
writeFileSync(tmp, `${JSON.stringify(file)}\n`);
|
|
79
|
+
renameSync(tmp, path);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export interface GateShardsSummary {
|
|
83
|
+
total: number;
|
|
84
|
+
counts: Record<ShardState, number>;
|
|
85
|
+
/** 대기 이유별 조각 수(많은 순). */
|
|
86
|
+
waitReasons: Array<{ reason: string; count: number }>;
|
|
87
|
+
/** 남은 분 추정 — 계획 분이 모자라면 null(«추정 불가»). */
|
|
88
|
+
etaMin: number | null;
|
|
89
|
+
/** 도는 조각 중 계획 분을 가장 많이 넘긴 분(없으면 0). 넘겼으면 etaMin 은 «하한»일 뿐이라 화면이 그렇게 말한다
|
|
90
|
+
* — canary 10-07 실측: 계획 0.1분 · 실제 22분 → 넘김을 안 보이면 내내 «남은 약 0분». */
|
|
91
|
+
overrunMin: number;
|
|
92
|
+
/** 파일이 마지막으로 바뀐 뒤 지난 분. 크면 쓰는 쪽이 멎었다는 뜻이다. */
|
|
93
|
+
staleMin: number | null;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const minutesBetween = (fromIso: string | undefined, now: number): number | null => {
|
|
97
|
+
const at = fromIso ? Date.parse(fromIso) : Number.NaN;
|
|
98
|
+
return Number.isFinite(at) ? Math.max(0, (now - at) / 60_000) : null;
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* 남은 시간 추정: 도는 조각은 «계획 − 경과», 기다리는 조각(pending·retry)은 계획 분을 지금 도는 자리 수로 나눠 뒤에 잇는다.
|
|
103
|
+
* 도는 조각이 없으면 자리 1로 본다. 계획 분이 없는 조각이 하나라도 남아 있으면 null.
|
|
104
|
+
*/
|
|
105
|
+
function estimate(shards: GateShard[], now: number): number | null {
|
|
106
|
+
const running = shards.filter((s) => s.state === 'running');
|
|
107
|
+
const waiting = shards.filter((s) => s.state === 'pending' || s.state === 'retry');
|
|
108
|
+
if (!running.length && !waiting.length) return 0;
|
|
109
|
+
if ([...running, ...waiting].some((s) => s.plannedMin === undefined)) return null;
|
|
110
|
+
const runningLeft = running.map((s) => Math.max(0, s.plannedMin! - (minutesBetween(s.startedAt, now) ?? 0)));
|
|
111
|
+
const slots = Math.max(1, running.length);
|
|
112
|
+
const waitingSum = waiting.reduce((sum, s) => sum + s.plannedMin!, 0);
|
|
113
|
+
return Math.ceil(Math.max(0, ...runningLeft) + waitingSum / slots);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export function summarizeShards(file: GateShardsFile, now: number = Date.now()): GateShardsSummary {
|
|
117
|
+
const counts = Object.fromEntries(SHARD_STATES.map((s) => [s, 0])) as Record<ShardState, number>;
|
|
118
|
+
const reasons = new Map<string, number>();
|
|
119
|
+
for (const shard of file.shards) {
|
|
120
|
+
counts[shard.state] += 1;
|
|
121
|
+
if (shard.waitReason && (shard.state === 'pending' || shard.state === 'retry')) {
|
|
122
|
+
reasons.set(shard.waitReason, (reasons.get(shard.waitReason) ?? 0) + 1);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
const stale = minutesBetween(file.updatedAt, now);
|
|
126
|
+
const overrun = Math.max(0, ...file.shards
|
|
127
|
+
.filter((s) => s.state === 'running' && s.plannedMin !== undefined)
|
|
128
|
+
.map((s) => (minutesBetween(s.startedAt, now) ?? 0) - s.plannedMin!));
|
|
129
|
+
return {
|
|
130
|
+
total: file.shards.length,
|
|
131
|
+
counts,
|
|
132
|
+
waitReasons: [...reasons].map(([reason, count]) => ({ reason, count })).sort((a, b) => b.count - a.count),
|
|
133
|
+
etaMin: estimate(file.shards, now),
|
|
134
|
+
overrunMin: Math.floor(overrun),
|
|
135
|
+
staleMin: stale === null ? null : Math.floor(stale),
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export const SHARD_WORD: Record<ShardState, string> = {
|
|
140
|
+
pending: '대기', running: '돌기', done: '통과', retry: '재시도', timeout: '잘림', failed: '실패',
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
/** 외출 알림·/release 한 줄 — «조각 24 · 돌기 5 · 대기 8(CPU 부족 8) · 잘림 2 · 통과 9 · 남은 약 40분». */
|
|
144
|
+
export function shardsLine(summary: GateShardsSummary): string {
|
|
145
|
+
const order: ShardState[] = ['running', 'pending', 'retry', 'timeout', 'failed', 'done'];
|
|
146
|
+
const parts = [`조각 ${summary.total}`];
|
|
147
|
+
for (const state of order) {
|
|
148
|
+
const n = summary.counts[state];
|
|
149
|
+
if (!n) continue;
|
|
150
|
+
const why = state === 'pending' && summary.waitReasons.length
|
|
151
|
+
? `(${summary.waitReasons.slice(0, 2).map((w) => `${w.reason} ${w.count}`).join(' · ')})` : '';
|
|
152
|
+
parts.push(`${SHARD_WORD[state]} ${n}${why}`);
|
|
153
|
+
}
|
|
154
|
+
if (summary.overrunMin >= 1) parts.push(`계획보다 ${summary.overrunMin}분 넘게 도는 중 — 남은 시간 추정 불가`);
|
|
155
|
+
else parts.push(summary.etaMin === null ? '남은 시간 추정 불가(계획 분 없음)' : `남은 약 ${summary.etaMin}분`);
|
|
156
|
+
if (summary.staleMin !== null && summary.staleMin >= 15) parts.push(`⚠️ ${summary.staleMin}분째 갱신 없음`);
|
|
157
|
+
return parts.join(' · ');
|
|
158
|
+
}
|
|
@@ -35,12 +35,13 @@ export function listSchedules(root?: string): ReleaseSchedule[] {
|
|
|
35
35
|
return schedules;
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
export function setSchedule(version: string, patch: { cutAt?: string; landBy?: string; freezeFrom?: string; freezeUntil?: string }, by: string, root?: string): ReleaseSchedule {
|
|
38
|
+
export function setSchedule(version: string, patch: { cutAt?: string; landBy?: string; freezeFrom?: string; freezeUntil?: string; publishAt?: string }, by: string, root?: string): ReleaseSchedule {
|
|
39
39
|
const schedule = writeSchedule(version, {
|
|
40
40
|
...(patch.cutAt !== undefined ? { cutAt: utcIso(patch.cutAt) } : {}),
|
|
41
41
|
...(patch.landBy !== undefined ? { landBy: utcIso(patch.landBy) } : {}),
|
|
42
42
|
...(patch.freezeFrom !== undefined ? { freezeFrom: utcIso(patch.freezeFrom) } : {}),
|
|
43
43
|
...(patch.freezeUntil !== undefined ? { freezeUntil: utcIso(patch.freezeUntil) } : {}),
|
|
44
|
+
...(patch.publishAt !== undefined ? { publishAt: utcIso(patch.publishAt) } : {}),
|
|
44
45
|
}, by, root);
|
|
45
46
|
debug.log('release.schedule', 'set', { version, cutAt: schedule.cutAt, landBy: schedule.landBy });
|
|
46
47
|
return schedule;
|
|
@@ -51,5 +52,8 @@ export function formatSchedule(schedule: ReleaseSchedule): string {
|
|
|
51
52
|
const deadline = schedule.landBy ? formatKst(schedule.landBy) : null;
|
|
52
53
|
const sameDay = schedule.landBy && new Date(Date.parse(schedule.landBy) + 9 * 60 * 60_000).toISOString().slice(0, 10)
|
|
53
54
|
=== new Date(Date.parse(schedule.cutAt) + 9 * 60 * 60_000).toISOString().slice(0, 10);
|
|
54
|
-
|
|
55
|
+
const publish = schedule.publishAt ? formatKst(schedule.publishAt) : null;
|
|
56
|
+
const publishSameDay = schedule.publishAt && new Date(Date.parse(schedule.publishAt) + 9 * 60 * 60_000).toISOString().slice(0, 10)
|
|
57
|
+
=== new Date(Date.parse(schedule.cutAt) + 9 * 60 * 60_000).toISOString().slice(0, 10);
|
|
58
|
+
return `${schedule.version} 컷 ${cut}${deadline ? ` · 착지 마감 ${sameDay ? deadline.split(' ')[1] : deadline}` : ''}${publish ? ` · 발행 ${publishSameDay ? publish.split(' ')[1] : publish}` : ''}`;
|
|
55
59
|
}
|