elanous 0.2.18 → 0.2.19
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/{P6YTVgzuZz5oTZhY4Din_ → J5H7BvqgSI8DUjtRRcA_S}/_buildManifest.js +1 -1
- package/apps/pwa/out/_next/static/chunks/1105-c473ed46272fba79.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1157-cba0e8553d35a980.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{726.83532d69452ee24b.js → 1470.98a6ab61ccb4131c.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/171-23a877ed11720de9.js +1 -0
- package/apps/pwa/out/_next/static/chunks/216-5acd01cbde4acda7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2330-c2f07246c8788af1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2336-e830c777eb6bce04.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{9247-269e4464cc4b491c.js → 2375-e061bb24cd2b5cde.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/2415.ed6428ff334c7ce2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{211-9c9090c59e2c5907.js → 2977-e38442e86289f1d3.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{f6b7c9ba.64a895b8b2fedbdc.js → 2ea96958.e8c7e112de50b067.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/3094.67339318c7f72a3a.js +2 -0
- package/apps/pwa/out/_next/static/chunks/338-ceb956612dcfaa86.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3395-835e4ceaa6335ba4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3403.4bc97b4066945a94.js +5 -0
- package/apps/pwa/out/_next/static/chunks/3441-be82e3595aef3a20.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3689-89905039bf3e7257.js +1 -0
- package/apps/pwa/out/_next/static/chunks/371-4ad6af55dad02ced.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3822-6f268bc26c6a84ac.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4114-3e63117452378807.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{1268.0840ff91e083fcbd.js → 4323.c5dfb6f503304775.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/4457-d7df81c90f9d0268.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4551-77dadae76f6cbace.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{6110.1297d4093fdfbb0d.js → 4847.56f5bc454fbcc827.js} +2 -2
- package/apps/pwa/out/_next/static/chunks/4978.6fd93b974441e87f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5012-f746f6549b869f91.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5609-012bb3557211f07e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5791-bb82743fa6299562.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{7327-2d69fb321ba73389.js → 6062-73e68343a9814f6a.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/62-5bfed1e23790b745.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6320-9bc8561fde416e07.js +1 -0
- package/apps/pwa/out/_next/static/chunks/649-ca96d1c5d09e02b8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6725-68ab7cc0b1faf00d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6726-85a7db59b288c53f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6735-33267c4630d0d963.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6998-a654fe5c58271600.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7633-141682015873beb6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{935.4ddaa99fc1d1126c.js → 7647.2fcd30d894d5ee30.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/7673-2eda1507d38dadb8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7990-e12efba616f46e03.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{f6231284-45726550aba08fca.js → 8046cadf-211ca3c0e00f2b72.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/8067-143791231c99bd02.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8142-744998d65d7bb17e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8430-a10cfba8ef282d4e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8870-9614a213b990b10b.js +25 -0
- package/apps/pwa/out/_next/static/chunks/920-f7dceb78f1c585ef.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9454-12c2f30dea62291c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/976-4a9b4ebdca7c739a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9989-15cdf568bafa9fc8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/ae787e4c-e5b1ef00b2dbceca.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/_not-found/page-859cea1570b6227a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-deef5ee191830f14.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/autopilot/page-b193da485f9a14b2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/board/page-b6e1a79df0e214f4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/botlab/page-e84df4d907f703c7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/bots/page-a5fb9a5db89ae4ea.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ceo/page-1499f481d7fc52d6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/chat/page-c7cf753aeb648790.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/consult/page-4118469e514e10fd.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/control/page-e968f7630654332a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/decisions/page-505915052f947022.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-c2f962fb4a3b06c6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/editor/page-7b5ed0bd8a589ec4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/exec/page-cf8193250dd42dd4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/field/page-cf3f5dfa35dea8e9.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/healthz/route-859cea1570b6227a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/inside/page-5b29d9b06ceadade.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/intake/page-a15e7fcc283e9ca7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/layout-d2915e7b70ff3d0e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/live/page-b70cd005d9169e25.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/loops/page-2595f0eba29f57e1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/market/page-c776322454156e36.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-77bbd17f4435aa10.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/morning/page-049689e611ecc936.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/not-found-85954a4fb09c44eb.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-e773e552e1068ee6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-5633c4bfa7684b4a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-d829b92bd01b4994.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-061c317fc771becf.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/outputs/page-3a45ece8865ed4b6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/page-0be990618c0d56bd.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-bfea4865dc244b73.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-0d6ba8e337741484.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-76a88b55d816482e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-bba1542df59f61ce.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/page-4185c95689bb372e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-b699e1a8d5d34b8b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/layout-859cea1570b6227a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/page-c225e338c31bf274.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/share/page-449353f70507ca1f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-90b889714102476d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-f1ee7fbeb7f96ddc.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/term/page-d59a95e1434dfaa0.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/today/page-71491a23414d1a47.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/trace/page-de7112bdb65fa4fe.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/vault/page-feb4d0d88079660d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-f17d887b40dd6b1d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-8da77be2192bcd5d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-72c3b9a5e9582b29.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-c3afe4c8d0c48c39.js +1 -0
- package/apps/pwa/out/_next/static/chunks/framework-ea806bb57f2b8923.js +1 -0
- package/apps/pwa/out/_next/static/chunks/main-8d2302399c2c8f87.js +1 -0
- package/apps/pwa/out/_next/static/chunks/main-app-b9308f2d8a931531.js +1 -0
- package/apps/pwa/out/_next/static/chunks/pages/_app-b10928c60f0b8488.js +1 -0
- package/apps/pwa/out/_next/static/chunks/pages/_error-689039227d443fab.js +1 -0
- package/apps/pwa/out/_next/static/chunks/webpack-6f35f9d892fa630b.js +1 -0
- package/apps/pwa/out/_next/static/css/{e99e3cff94ced78a.css → 599369d853c61df7.css} +1 -1
- package/apps/pwa/out/_next/static/css/85f53c6f35d11382.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 +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 +25 -24
- 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 +24 -23
- 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 +28 -27
- 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/ops/guardian.yaml +26 -0
- package/graphs/ops/recipes.yaml +3 -0
- package/package.json +1 -1
- package/scripts/coord-channel-watch.sh +2 -2
- package/scripts/discord-seat-probe.ts +3 -1
- package/scripts/lib/pch-regress-map.ts +23 -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 +338 -46
- package/scripts/release-loop/gate-partial.ts +106 -0
- package/scripts/release-loop/gate-timing.ts +120 -0
- package/scripts/release-loop/resume-release.ts +64 -5
- package/scripts/release-loop/shard-plan.ts +65 -0
- package/scripts/release-loop/unattended-release.ts +2 -0
- package/scripts/video/scene-table.ts +159 -0
- package/src/acp/core-turn-bridge.ts +39 -2
- package/src/away/presence.ts +40 -0
- package/src/away/release-watch.ts +255 -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/guardian-cli.ts +49 -0
- package/src/cli/intake-cli.ts +13 -7
- package/src/cli/log-event-names.ts +1 -0
- package/src/cli/pod-cli.ts +5 -1
- package/src/cli/pty-drive-cli.ts +14 -3
- package/src/cli/release-cli.ts +32 -7
- package/src/cli/setup-cli.ts +31 -1
- package/src/cli/tasks-cli.ts +104 -11
- 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 +63 -36
- package/src/dashboard/slash-runtime/decide-slash.ts +60 -0
- package/src/dashboard/slash-runtime/help-from-registry.ts +21 -8
- 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/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/runner.ts +25 -5
- package/src/harness/harness-cli-command.ts +2 -0
- package/src/harness/harness-salvage-cli.ts +78 -0
- package/src/harness/harness-worktree-add.ts +68 -0
- package/src/harness/ledger-write-guard.ts +6 -16
- package/src/hq/hq.ts +6 -1
- package/src/index.ts +52 -3
- 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/http-server.ts +16 -5
- package/src/nexus/api/loop-edges.ts +290 -17
- package/src/nexus/api/ops-api.ts +96 -28
- package/src/nexus/index.ts +7 -0
- package/src/release-loop/checklist.ts +2 -0
- package/src/release-loop/feature-store.ts +54 -7
- package/src/release-loop/light-rc.ts +173 -0
- package/src/release-loop/placement.ts +51 -17
- package/src/release-loop/rubric.ts +57 -1
- package/src/self-dev/dev-cli.ts +36 -0
- package/src/self-dev/orchestrate.ts +1 -0
- package/src/self-dev/pr-preview.ts +179 -0
- package/src/self-dev/shard-run.ts +23 -7
- package/src/self-implement/gate-cli.ts +29 -8
- 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 +2 -1
- package/src/self-implement/salvage-branches.ts +254 -0
- package/src/task-agent/actions.ts +63 -2
- package/src/task-agent/board.ts +132 -0
- package/src/task-agent/card-evidence.ts +203 -0
- package/src/task-agent/completion-evidence.ts +126 -0
- package/src/task-agent/judge.ts +18 -0
- package/src/task-agent/live-moves.ts +176 -0
- package/src/task-agent/mission.ts +32 -6
- package/src/task-agent/shadow.ts +183 -15
- package/src/task-agent/task-hand.ts +117 -15
- package/src/task-orchestrator/surfaces/pod-command-job.ts +64 -4
- package/src/task-orchestrator/surfaces/pod-lease.ts +142 -13
- package/src/task-orchestrator/surfaces/pod-pool.ts +13 -5
- package/src/task-orchestrator/surfaces/self-implement-pod.ts +2 -2
- package/src/telegram-away-command.ts +38 -0
- package/src/telegram-commands.ts +11 -0
- package/src/telegram-report-log-probe.ts +21 -0
- package/src/telegram-report.ts +27 -3
- package/src/tui-client/dashboard-session.ts +1 -0
- package/src/user-config.ts +134 -2
- package/src/version/packed-revision.json +1 -1
- package/apps/pwa/out/_next/static/chunks/1307-30dff22c9e1f3073.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1826-5d279da64bbbc2de.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1844-9ec4b1d12f6e80eb.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1866-e7a6999f25548b30.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1948.15f356b15a625597.js +0 -5
- package/apps/pwa/out/_next/static/chunks/195-fb1d7690bf30bab4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1986-33e13d648a9df765.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2196-4700653d12a44b40.js +0 -1
- package/apps/pwa/out/_next/static/chunks/224-798346143007ff65.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2537-6c289376caa88ca1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2699-325cc923ba7b8631.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2957-96ffab3d521fb08f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/311.e685738f92e6e2ec.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3194-807b212fb8eae065.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3324-d1d2979821dfa845.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3558-bbb5ab553a0471d3.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3857-4f936cdb30322aa6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3916-3d02dd1fc257dd3c.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/4565-3d7e3fdeb43686a8.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4795-f372490b178583fc.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5360-be1f1d8f9fcc9755.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/6158.af2e93b7adff709b.js +0 -2
- package/apps/pwa/out/_next/static/chunks/6877-74399f69c7da1433.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7290-f09c928a06c52e2d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7797-df5aa0381fdc078f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8060-cc008cc6fedfa818.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8085-d065d0393c1760f5.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8401-8615a660c84cdb03.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8682-7061dd4fa7bd62ed.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8695-846034ce44852c70.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8868-10a3a4440fb0ccd8.js +0 -25
- package/apps/pwa/out/_next/static/chunks/9070.067c106528ec1ae6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9103-60fbef33e66f0a46.js +0 -1
- package/apps/pwa/out/_next/static/chunks/a9f2b54d-7865aba0f4d2ee49.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/_not-found/page-c1a96669158471ec.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-d92f7ee3994e84c7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/autopilot/page-4733c967b56a4277.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/board/page-d22fd849cc22f104.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/botlab/page-bb742f995c6769e7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/bots/page-02b073a2ac793b83.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/chat/page-8a9b3594bc23289b.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/control/page-308111abadf35670.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/design-check/page-5d97b2901e0d1ea6.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/exec/page-8c3d8e63ae2e1c98.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/field/page-9b72a07d424701c6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/healthz/route-c1a96669158471ec.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/intake/page-a572f58cd3ca5864.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/layout-59b7834d497c2255.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/live/page-be5ea965a07fbc41.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/market/page-fa244bd146a00ae8.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-44d56d6391965a6a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/morning/page-83a700d4c754d802.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/not-found-7b3170f374f77b9d.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/reflection/page-1467047104141297.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/sessions/page-102310930791197a.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/settings/page-6ed0b73bae8351d8.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/setup/layout-c1a96669158471ec.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/page-43336055451f6e37.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/share/page-3cc47dca0922ab9a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-a77ee355a2304ff0.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-b17cd46cf0e9b8cf.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/trace/page-7bf54c9bb9274af8.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/workspace/page-134da468538baf48.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-31ac4afa2dcd915c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/framework-5c169757551134ed.js +0 -1
- package/apps/pwa/out/_next/static/chunks/main-59fe95fed40c7a01.js +0 -1
- package/apps/pwa/out/_next/static/chunks/main-app-4ca74dbbee7057c6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/pages/_app-13a0a886df4d97a1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/pages/_error-6af8591cd3a3f038.js +0 -1
- package/apps/pwa/out/_next/static/chunks/webpack-c2782b330447ffb6.js +0 -1
- package/apps/pwa/out/_next/static/css/7fa358fec7da7c47.css +0 -1
- /package/apps/pwa/out/_next/static/{P6YTVgzuZz5oTZhY4Din_ → J5H7BvqgSI8DUjtRRcA_S}/_ssgManifest.js +0 -0
|
@@ -9,15 +9,58 @@ import { listLoops } from '../../loops/registry.js';
|
|
|
9
9
|
import { listSubSeatCharters } from './sub-seat-charters.js';
|
|
10
10
|
import { SEATS } from './ops-seats.js';
|
|
11
11
|
import { jsonResponse } from './json-response.js';
|
|
12
|
+
import { readTaskAgentState, type TaskCard as TaskAgentCard } from '../../task-agent/task-hand.js';
|
|
13
|
+
import { LogStore, logsDbPath } from '../../mss/logging/log-store.js';
|
|
14
|
+
|
|
15
|
+
/** `move` 칸에 실을 수 있는 값(allowlist) — 판단부 수 종류만, 문면은 싣지 않는다. */
|
|
16
|
+
export const LOOP_EDGE_MOVES = ['green-proposal'] as const;
|
|
12
17
|
|
|
13
18
|
export interface LoopEdge {
|
|
14
19
|
at: string;
|
|
15
|
-
kind: 'request' | 'decision' | 'report' | 'card' | 'run';
|
|
20
|
+
kind: 'request' | 'decision' | 'report' | 'card' | 'run' | 'hand' | 'launch' | 'move';
|
|
16
21
|
from: string;
|
|
17
22
|
to: string;
|
|
18
23
|
ref: string;
|
|
24
|
+
/** 체크리스트 칸 id(ID 정규식) — hand 간선만. */
|
|
25
|
+
cell?: string;
|
|
26
|
+
/** LOOP_EDGE_MOVES 중 하나 — move 간선만. */
|
|
27
|
+
move?: typeof LOOP_EDGE_MOVES[number];
|
|
28
|
+
/** hand 간선만 — 원장 행 mode, 없으면 key 접두(`shadow:orch:` = shadow · `orch:` = live). 못 정하면 생략. */
|
|
29
|
+
mode?: 'shadow' | 'live';
|
|
30
|
+
/** hand 간선만 — 그 행을 쓴 오케스트레이터 틱 runId(ID 정규식). 없으면 생략. */
|
|
31
|
+
tick?: string;
|
|
32
|
+
/** live claimed 뒤 handed·released 없이 N분 지남 = 발사 실패(끊긴 넘김). ref = 오케스트레이터 카드 id. */
|
|
33
|
+
broken?: true;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** 노드 접두 — 조율 루프는 레지스트리 행 `loop:orchestrator` 그대로 쓴다. `queue:` 는 대기열 대체 간선 자리(조각 A 밖). */
|
|
37
|
+
export const TASK_AGENT_NODE = 'agent:task-agent';
|
|
38
|
+
export const ORCHESTRATOR_NODE = 'loop:orchestrator';
|
|
39
|
+
|
|
40
|
+
/** 한 런 원장에서 투영한 사실 — 문면은 feature(연결 비교 전용 · 응답에 싣지 않는다)뿐. */
|
|
41
|
+
export interface RunFact {
|
|
42
|
+
runId: string;
|
|
43
|
+
startAt?: string;
|
|
44
|
+
seat?: string;
|
|
45
|
+
feature?: string;
|
|
46
|
+
goalId?: string;
|
|
47
|
+
mergedAt?: string;
|
|
48
|
+
prs: Array<{ at: string; number: number }>;
|
|
49
|
+
children: string[];
|
|
19
50
|
}
|
|
20
51
|
|
|
52
|
+
/** handed.jsonl 행 투영 — status 가 없으면 handed 로 읽는다(조각 A 주입 호환). claimed·released 는 끊긴 간선 판정에만 쓴다. */
|
|
53
|
+
export interface HandedRow {
|
|
54
|
+
at: string;
|
|
55
|
+
cardId?: string;
|
|
56
|
+
cell?: string;
|
|
57
|
+
status?: 'claimed' | 'handed' | 'released';
|
|
58
|
+
key?: string;
|
|
59
|
+
mode?: 'shadow' | 'live';
|
|
60
|
+
runId?: string;
|
|
61
|
+
}
|
|
62
|
+
export interface PodFinish { at: string; childRunId: string; prNumber: number }
|
|
63
|
+
|
|
21
64
|
export interface LoopEdgesDeps {
|
|
22
65
|
listCoord?: typeof listCoordEvents;
|
|
23
66
|
cardRoot?: string;
|
|
@@ -28,6 +71,16 @@ export interface LoopEdgesDeps {
|
|
|
28
71
|
listOrchestratorLaunches?: () => Array<{ key: string; runId: string }>;
|
|
29
72
|
listLoopOwners?: () => Array<{ id: string; owner?: string | null }>;
|
|
30
73
|
seatIds?: () => string[];
|
|
74
|
+
/** 기본 = `<root>/loop/orchestrator/handed.jsonl` · root = cardRoot ?? effectiveInstanceRoot(). */
|
|
75
|
+
listHanded?: () => HandedRow[];
|
|
76
|
+
/** live claimed 가 handed·released 없이 이만큼 지나면 끊긴 hand 간선(기본 10분). */
|
|
77
|
+
handBrokenAfterMs?: number;
|
|
78
|
+
/** 기본 = `<root>/task-agent-actions.json` 의 tasks(readTaskAgentState). */
|
|
79
|
+
listTaskCards?: () => TaskAgentCard[];
|
|
80
|
+
/** 기본 = ledgerDir(또는 연합 원장)에서 투영. listRunStarts 를 주입하고 이것을 안 주면 [] 다. */
|
|
81
|
+
listRunFacts?: () => RunFact[];
|
|
82
|
+
/** 대체 원천 — logs `self-implement.pod` `job-finished` 의 childRunId·prUrl(PR 번호만). */
|
|
83
|
+
listPodFinishes?: (since: string) => PodFinish[];
|
|
31
84
|
}
|
|
32
85
|
|
|
33
86
|
const ID = /^[a-zA-Z0-9_-]{1,128}$/;
|
|
@@ -100,9 +153,9 @@ function cardEdges(card: TaskCard, since: number, now: number, seats: ReadonlySe
|
|
|
100
153
|
return edges;
|
|
101
154
|
}
|
|
102
155
|
|
|
103
|
-
function
|
|
156
|
+
function runFacts(dir: string, owners: ReadonlyMap<string, string>, seats: ReadonlySet<string>): RunFact[] {
|
|
104
157
|
if (!existsSync(dir)) return [];
|
|
105
|
-
const result:
|
|
158
|
+
const result: RunFact[] = [];
|
|
106
159
|
let names: string[];
|
|
107
160
|
try { names = readdirSync(dir); } catch { return []; }
|
|
108
161
|
for (const name of names) {
|
|
@@ -113,20 +166,84 @@ function runStarts(since: string, dir: string, owners: ReadonlyMap<string, strin
|
|
|
113
166
|
const runId = name.slice(0, -6);
|
|
114
167
|
let entries: RunLedgerEntry[] | null;
|
|
115
168
|
try { entries = loadRunLedger(runId, dir); } catch { continue; }
|
|
116
|
-
|
|
169
|
+
if (!entries) continue;
|
|
170
|
+
const start = entries.find(entry => entry.event === 'start' && entry.runId === runId);
|
|
117
171
|
const feature = typeof start?.data.feature === 'string' ? start.data.feature : '';
|
|
118
172
|
const launchSeat = start?.data.launchSeat ?? start?.data.seat ?? owners.get(feature)
|
|
119
173
|
?? start?.data.originAgent ?? start?.data.controller;
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
174
|
+
const merged = entries.find(entry => entry.event === 'merged' && entry.data.merged === true && typeof entry.timestamp === 'string');
|
|
175
|
+
const goalId = [start, ...entries].map(entry => entry?.goalId ?? (typeof entry?.data.goalId === 'string' ? entry.data.goalId : undefined))
|
|
176
|
+
.find((value): value is string => typeof value === 'string' && /^[a-f0-9]{16}$/.test(value));
|
|
177
|
+
// pr-opened data = {url, number, branch, …} (TC 실측 10-07) — 번호는 data.number(양의 정수)만 읽는다. 못 읽으면 간선 0.
|
|
178
|
+
const prs = entries.filter(entry => entry.event === 'pr-opened' && typeof entry.timestamp === 'string'
|
|
179
|
+
&& Number.isSafeInteger(entry.data.number) && (entry.data.number as number) > 0)
|
|
180
|
+
.map(entry => ({ at: entry.timestamp!, number: entry.data.number as number }));
|
|
181
|
+
// `pod-ledger-incomplete` 첫 줄 원장은 시작이 없어도 버리지 않는다 — pr 근거로만 쓰고 run 단계는 만들지 않는다.
|
|
182
|
+
const children = [...new Set(entries.filter(entry => entry.event === 'pod-child-run' && typeof entry.data.childRunId === 'string'
|
|
183
|
+
&& ID.test(entry.data.childRunId) && entry.data.childRunId !== runId).map(entry => entry.data.childRunId as string))];
|
|
184
|
+
result.push({
|
|
185
|
+
runId, prs, children,
|
|
186
|
+
...(start?.timestamp ? { startAt: start.timestamp } : {}),
|
|
187
|
+
...(start?.timestamp && seat(launchSeat, seats) ? { seat: launchSeat } : {}),
|
|
188
|
+
...(start?.timestamp && feature ? { feature } : {}),
|
|
189
|
+
...(goalId ? { goalId } : {}),
|
|
190
|
+
...(merged?.timestamp ? { mergedAt: merged.timestamp } : {}),
|
|
191
|
+
});
|
|
126
192
|
}
|
|
127
193
|
return result;
|
|
128
194
|
}
|
|
129
195
|
|
|
196
|
+
function runStartsFrom(facts: readonly RunFact[], since: string): Array<{ runId: string; at: string; seat: string; mergedAt?: string }> {
|
|
197
|
+
return facts.filter(fact => fact.startAt !== undefined && fact.seat !== undefined
|
|
198
|
+
&& (fact.startAt >= since || (fact.mergedAt !== undefined && fact.mergedAt >= since)))
|
|
199
|
+
.map(fact => ({ runId: fact.runId, at: fact.startAt!, seat: fact.seat!, ...(fact.mergedAt ? { mergedAt: fact.mergedAt } : {}) }));
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/** handed.jsonl(`tick.ts` ORCH-TA-HAND) — 행의 at·status·key·cardId·cell·mode·runId 만. 넘김은 handed 뿐이고 claimed·released 는 끊긴 간선 판정용이다. */
|
|
203
|
+
function handedRows(root: string): HandedRow[] {
|
|
204
|
+
const path = join(root, 'loop', 'orchestrator', 'handed.jsonl');
|
|
205
|
+
try { if (!existsSync(path) || statSync(path).size > 5 * 1024 * 1024) return []; } catch { return []; }
|
|
206
|
+
let raw: string;
|
|
207
|
+
try { raw = readFileSync(path, 'utf8'); } catch { return []; }
|
|
208
|
+
const rows: HandedRow[] = [];
|
|
209
|
+
for (const line of raw.split('\n')) {
|
|
210
|
+
const row = line.trim() ? parse(line) : null;
|
|
211
|
+
const status = row?.status;
|
|
212
|
+
if ((status !== 'handed' && status !== 'claimed' && status !== 'released') || typeof row!.at !== 'string') continue;
|
|
213
|
+
if (status === 'handed' && typeof row!.cardId !== 'string') continue;
|
|
214
|
+
rows.push({
|
|
215
|
+
at: row!.at as string, status,
|
|
216
|
+
...(typeof row!.cardId === 'string' ? { cardId: row!.cardId } : {}),
|
|
217
|
+
...(typeof row!.cell === 'string' ? { cell: row!.cell } : {}),
|
|
218
|
+
...(typeof row!.key === 'string' ? { key: row!.key } : {}),
|
|
219
|
+
...(row!.mode === 'shadow' || row!.mode === 'live' ? { mode: row!.mode } : {}),
|
|
220
|
+
...(typeof row!.runId === 'string' ? { runId: row!.runId } : {}),
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
return rows;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function taskCards(root: string): TaskAgentCard[] {
|
|
227
|
+
const tasks = object(readTaskAgentState<{ tasks?: unknown }>(join(root, 'task-agent-actions.json')).tasks);
|
|
228
|
+
return tasks ? Object.values(tasks).filter((card): card is TaskAgentCard => object(card) !== null) : [];
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/** 대체 원천: Pod 런은 도는 동안 원장이 Pod 안에만 있다 — 호스트 logs 의 job-finished 에서 childRunId·PR 번호만 읽는다. */
|
|
232
|
+
function podFinishes(since: string): PodFinish[] {
|
|
233
|
+
const path = logsDbPath();
|
|
234
|
+
if (!existsSync(path)) return [];
|
|
235
|
+
const store = LogStore.openReadOnly(path);
|
|
236
|
+
try {
|
|
237
|
+
const result: PodFinish[] = [];
|
|
238
|
+
for (const row of store.queryAll({ exactCategories: ['self-implement.pod'], events: ['job-finished'], sinceMs: Date.parse(since) })) {
|
|
239
|
+
const data = row.data ? parse(row.data) : null;
|
|
240
|
+
const pr = typeof data?.prUrl === 'string' ? /^https:\/\/github\.com\/[\w.-]+\/[\w.-]+\/pull\/(\d+)$/.exec(data.prUrl) : null;
|
|
241
|
+
if (typeof data?.childRunId === 'string' && pr) result.push({ at: row.ts, childRunId: data.childRunId, prNumber: Number(pr[1]) });
|
|
242
|
+
}
|
|
243
|
+
return result;
|
|
244
|
+
} finally { store.close(); }
|
|
245
|
+
}
|
|
246
|
+
|
|
130
247
|
/** Project only the key and runId of orchestrator launches, never their card snapshots or prose. */
|
|
131
248
|
function orchestratorLaunches(root: string): Array<{ key: string; runId: string }> {
|
|
132
249
|
const dir = join(root, 'loop', 'orchestrator');
|
|
@@ -152,17 +269,166 @@ function orchestratorLaunches(root: string): Array<{ key: string; runId: string
|
|
|
152
269
|
return launches;
|
|
153
270
|
}
|
|
154
271
|
|
|
272
|
+
type JourneyLink = { from: string; to: string; both: boolean };
|
|
273
|
+
type Journey = { links: JourneyLink[]; pieces: Map<string, string[]> };
|
|
274
|
+
|
|
275
|
+
/** 런 원장 start.feature 는 `boundReadableText(feature, 120)` 로 잘린다(orchestrator.ts RUN_START_FEATURE_MAX_CHARS) —
|
|
276
|
+
* 꼬리 표식 ` [truncated; originalChars=N]`·`[truncated]`·`…`(⊕ 손으로 쓴 `...`)을 떼고 공백을 정규화한다. */
|
|
277
|
+
const FEATURE_TRUNCATION_TAIL = /(?:\s*\[truncated(?:; originalChars=\d+)?\]|…|\.\.\.)$/;
|
|
278
|
+
const FEATURE_PREFIX_MIN = 40;
|
|
279
|
+
function normalizeText(text: string): string { return text.replace(/\s+/g, ' ').trim(); }
|
|
280
|
+
/** 잘린 feature 의 비교 접두 — 표식이 없으면 undefined(완전 일치만). 끝의 짝 잃은 서로게이트도 뗀다(UTF-16 절단). */
|
|
281
|
+
function featurePrefix(feature: string): string | undefined {
|
|
282
|
+
if (!FEATURE_TRUNCATION_TAIL.test(feature)) return undefined;
|
|
283
|
+
const prefix = normalizeText(feature.replace(FEATURE_TRUNCATION_TAIL, '').replace(/[\ud800-\udbff]$/, ''));
|
|
284
|
+
return prefix.length >= FEATURE_PREFIX_MIN ? prefix : undefined;
|
|
285
|
+
}
|
|
286
|
+
/** 카드 text ↔ 런 feature — 완전 일치, 또는 잘린 feature(≥40자)가 카드 text(공백 정규화)의 접두. */
|
|
287
|
+
function featureMatches(feature: string | undefined, text: string): boolean {
|
|
288
|
+
if (!feature || !text) return false;
|
|
289
|
+
if (feature === text) return true;
|
|
290
|
+
const prefix = featurePrefix(feature);
|
|
291
|
+
return prefix !== undefined && normalizeText(text).startsWith(prefix);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/** 카드 → 런 연결: 카드 runId(있으면 최우선) → goalId 일치 → feature 일치(완전 또는 잘린 접두) ∧ start ≥ launch.at — 뒤 둘은 «유일할 때만»
|
|
295
|
+
* (같은 feature 에 닿는 발사 카드가 둘이면 잇지 않는다). 자식 런(pod-child-run)은 부모로 닿는다. */
|
|
296
|
+
function linkedRun(card: TaskAgentCard, launchAt: string | undefined, launchedTexts: readonly string[], facts: readonly RunFact[], childIds: ReadonlySet<string>): string | undefined {
|
|
297
|
+
const explicitRun = (card as { runId?: unknown }).runId;
|
|
298
|
+
if (typeof explicitRun === 'string' && ID.test(explicitRun)) return explicitRun;
|
|
299
|
+
const pick = (candidates: RunFact[]): string | undefined => {
|
|
300
|
+
const top = candidates.filter(fact => !childIds.has(fact.runId));
|
|
301
|
+
return top.length === 1 ? top[0]!.runId : candidates.length === 1 ? candidates[0]!.runId : undefined;
|
|
302
|
+
};
|
|
303
|
+
if (typeof card.goalId === 'string' && card.goalId) {
|
|
304
|
+
const byGoal = pick(facts.filter(fact => fact.goalId === card.goalId));
|
|
305
|
+
if (byGoal) return byGoal;
|
|
306
|
+
}
|
|
307
|
+
const launchMs = launchAt === undefined ? NaN : Date.parse(launchAt);
|
|
308
|
+
if (!Number.isFinite(launchMs) || typeof card.text !== 'string' || !card.text) return undefined;
|
|
309
|
+
const runId = pick(facts.filter(fact => featureMatches(fact.feature, card.text) && fact.startAt !== undefined && Date.parse(fact.startAt) >= launchMs));
|
|
310
|
+
if (!runId) return undefined;
|
|
311
|
+
const feature = facts.find(fact => fact.runId === runId)!.feature;
|
|
312
|
+
return launchedTexts.filter(text => featureMatches(feature, text)).length === 1 ? runId : undefined;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/** hand 행의 mode — 행의 mode 필드가 우선, 없으면 key 접두(`shadow:orch:` = shadow · `orch:` = live). */
|
|
316
|
+
function handMode(row: HandedRow): 'shadow' | 'live' | undefined {
|
|
317
|
+
if (row.mode === 'shadow' || row.mode === 'live') return row.mode;
|
|
318
|
+
if (typeof row.key !== 'string') return undefined;
|
|
319
|
+
if (row.key.startsWith('shadow:orch:')) return 'shadow';
|
|
320
|
+
if (row.key.startsWith('orch:')) return 'live';
|
|
321
|
+
return undefined;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/** 넘김(hand)·발사(launch)·런→PR·green 제안(move) 간선을 더하고, ?ref= 여정에 쓸 id 연결을 돌려준다. 문면은 싣지 않는다. */
|
|
325
|
+
function taskAgentEdges(deps: LoopEdgesDeps, facts: readonly RunFact[], seats: ReadonlySet<string>, since: number, now: number, sinceIso: string, edges: LoopEdge[]): Journey {
|
|
326
|
+
// 주입된 카드 목록(시험)에 뿌리가 없으면 운영 뿌리를 읽지 않는다 — 공개본엔 bunfig preload 격리가 없다.
|
|
327
|
+
const root = deps.cardRoot ?? (deps.listCards ? undefined : effectiveInstanceRoot());
|
|
328
|
+
const journey: Journey = { links: [], pieces: new Map() };
|
|
329
|
+
let handed: HandedRow[] = [];
|
|
330
|
+
try { handed = (deps.listHanded ?? (() => root === undefined ? [] : handedRows(root)))(); } catch { /* An unreadable hand ledger hands nothing. */ }
|
|
331
|
+
const settled = new Set<string>();
|
|
332
|
+
for (const row of handed) if (row.key !== undefined && (row.status ?? 'handed') !== 'claimed') settled.add(row.key);
|
|
333
|
+
const brokenAfter = deps.handBrokenAfterMs ?? 10 * 60 * 1000;
|
|
334
|
+
const brokenKeys = new Set<string>();
|
|
335
|
+
for (const row of handed) {
|
|
336
|
+
const status = row.status ?? 'handed';
|
|
337
|
+
const mode = handMode(row);
|
|
338
|
+
const tick = typeof row.runId === 'string' && ID.test(row.runId) ? { tick: row.runId } : {};
|
|
339
|
+
if (status === 'handed') {
|
|
340
|
+
if (typeof row.cardId !== 'string' || !ID.test(row.cardId) || !inWindow(row.at, since, now)) continue;
|
|
341
|
+
edges.push({ at: row.at, kind: 'hand', from: ORCHESTRATOR_NODE, to: TASK_AGENT_NODE, ref: row.cardId,
|
|
342
|
+
...(row.cell && ID.test(row.cell) ? { cell: row.cell } : {}), ...(mode ? { mode } : {}), ...tick });
|
|
343
|
+
// 넘김 행의 cardId 는 TASK-AGENT 카드다 — 소원(wish) 카드는 key(`orch:<wish>:<칸>`)에만 있다. 둘을 잇지 않으면
|
|
344
|
+
// `?ref=<wish>` 여정이 넘김에서 끊겨 데모 띠가 1단계 뒤로 «남음»에 멈춘다(10-07 리허설 2 실측 · JOURNEY-HAND-LINK).
|
|
345
|
+
const wish = row.key === undefined ? undefined : /^orch:([^:]+):([^:]+)$/.exec(row.key)?.[1];
|
|
346
|
+
if (wish !== undefined && ID.test(wish) && wish !== row.cardId) journey.links.push({ from: wish, to: row.cardId, both: true });
|
|
347
|
+
continue;
|
|
348
|
+
}
|
|
349
|
+
// live claimed 만(handed·released 없음 · N분 지남) = 발사 실패 — 끊긴 간선 하나. shadow claimed→released 는 간선 0.
|
|
350
|
+
if (status !== 'claimed' || mode !== 'live' || row.key === undefined || settled.has(row.key) || brokenKeys.has(row.key)) continue;
|
|
351
|
+
const claimedMs = Date.parse(row.at);
|
|
352
|
+
if (!Number.isFinite(claimedMs) || now - claimedMs < brokenAfter || !inWindow(row.at, since, now)) continue;
|
|
353
|
+
const parts = /^orch:([^:]+):([^:]+)$/.exec(row.key);
|
|
354
|
+
if (!parts || !ID.test(parts[1]!) || !ID.test(parts[2]!)) continue;
|
|
355
|
+
brokenKeys.add(row.key);
|
|
356
|
+
edges.push({ at: row.at, kind: 'hand', from: ORCHESTRATOR_NODE, to: TASK_AGENT_NODE, ref: parts[1]!, cell: parts[2]!, mode, ...tick, broken: true });
|
|
357
|
+
}
|
|
358
|
+
let cards: TaskAgentCard[] = [];
|
|
359
|
+
try { cards = (deps.listTaskCards ?? (() => root === undefined ? [] : taskCards(root)))().filter(card => typeof card?.id === 'string' && ID.test(card.id)); }
|
|
360
|
+
catch { /* An unreadable task-agent state launches nothing. */ }
|
|
361
|
+
const launchesOf = (card: TaskAgentCard) => (Array.isArray(card.history) ? card.history : [])
|
|
362
|
+
.filter(event => event?.event === 'launch' && typeof event.at === 'string');
|
|
363
|
+
const launchedTexts: string[] = [];
|
|
364
|
+
for (const card of cards) {
|
|
365
|
+
if (launchesOf(card).length && typeof card.text === 'string') launchedTexts.push(card.text);
|
|
366
|
+
}
|
|
367
|
+
const childIds = new Set(facts.flatMap(fact => fact.children));
|
|
368
|
+
for (const card of cards) {
|
|
369
|
+
if (typeof card.mission === 'string' && ID.test(card.mission)) journey.links.push({ from: card.id, to: card.mission, both: false });
|
|
370
|
+
if (Array.isArray(card.pieces)) journey.pieces.set(card.id, card.pieces.filter(piece => typeof piece === 'string' && ID.test(piece)));
|
|
371
|
+
const launches = launchesOf(card);
|
|
372
|
+
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 });
|
|
374
|
+
}
|
|
375
|
+
const runId = linkedRun(card, launches[0]?.at, launchedTexts, facts, childIds);
|
|
376
|
+
if (runId) journey.links.push({ from: card.id, to: runId, both: true });
|
|
377
|
+
const green = object(card.greenProposal);
|
|
378
|
+
if (green && typeof green.at === 'string' && typeof green.checklistId === 'string' && ID.test(green.checklistId) && inWindow(green.at, since, now)) {
|
|
379
|
+
edges.push({ at: green.at, kind: 'move', from: TASK_AGENT_NODE, to: `release:${green.checklistId}`, ref: card.id, move: 'green-proposal' });
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
const prSeen = new Set<string>();
|
|
383
|
+
const prEdge = (runId: string, at: string, number: number) => {
|
|
384
|
+
const key = `${runId}:${number}`;
|
|
385
|
+
if (!ID.test(runId) || !Number.isSafeInteger(number) || number < 1 || prSeen.has(key) || !inWindow(at, since, now)) return;
|
|
386
|
+
prSeen.add(key);
|
|
387
|
+
edges.push({ at, kind: 'run', from: `loop:${runId}`, to: `pr:${number}`, ref: runId });
|
|
388
|
+
};
|
|
389
|
+
for (const fact of facts) {
|
|
390
|
+
for (const child of fact.children) journey.links.push({ from: fact.runId, to: child, both: true });
|
|
391
|
+
for (const pr of fact.prs) prEdge(fact.runId, pr.at, pr.number);
|
|
392
|
+
}
|
|
393
|
+
let pods: PodFinish[] = [];
|
|
394
|
+
// 대체 원천은 연합 원장(운영 기본)과 짝이다 — 원장을 주입했으면(시험·한 디렉터리) logs 를 읽지 않는다.
|
|
395
|
+
const injectedLedger = deps.ledgerDir !== undefined || deps.listRunStarts !== undefined || deps.listRunFacts !== undefined;
|
|
396
|
+
try { pods = (deps.listPodFinishes ?? (injectedLedger ? () => [] : podFinishes))(sinceIso); } catch { /* logs outage — the run ledger stays the primary source. */ }
|
|
397
|
+
for (const pod of pods) prEdge(pod.childRunId, pod.at, pod.prNumber);
|
|
398
|
+
return journey;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
/** ?ref= 의 여정 id 집합 — 카드↔런·런↔자식 런은 양방향, 조각→미션은 위로만(형제 조각은 미션을 ref 로 줄 때만). */
|
|
402
|
+
function journeyIds(ref: string, journey: Journey): Set<string> {
|
|
403
|
+
const ids = new Set([ref, ...(journey.pieces.get(ref) ?? [])]);
|
|
404
|
+
const queue = [...ids];
|
|
405
|
+
while (queue.length) {
|
|
406
|
+
const id = queue.shift()!;
|
|
407
|
+
for (const link of journey.links) {
|
|
408
|
+
const next = link.from === id ? link.to : link.both && link.to === id ? link.from : undefined;
|
|
409
|
+
if (next !== undefined && !ids.has(next)) { ids.add(next); queue.push(next); }
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
return ids;
|
|
413
|
+
}
|
|
414
|
+
|
|
155
415
|
/** GET only; project an allowlist of fields from the ledgers. Never serialize ledger records. */
|
|
156
416
|
export function handleLoopEdgesGet(req: Request, deps: LoopEdgesDeps = {}): Response {
|
|
157
417
|
const params = new URL(req.url).searchParams;
|
|
158
418
|
const rawSince = params.get('since');
|
|
159
|
-
const
|
|
419
|
+
const journeyRef = params.get('ref');
|
|
420
|
+
// ?mode=live|shadow — hand 간선만 그 mode 로 거른다(다른 종류 간선은 그대로).
|
|
421
|
+
const handModeFilter = params.get('mode');
|
|
422
|
+
// ?ref= 는 한 카드·런의 여정 — 60분 창과 limit 에 잘리지 않게 since 를 안 주면 처음부터 본다.
|
|
423
|
+
const since = rawSince === null ? (journeyRef === null ? Date.now() - 60 * 60 * 1000 : 0) : Date.parse(rawSince);
|
|
160
424
|
const rawLimit = params.get('limit');
|
|
161
425
|
const limit = rawLimit === null ? 100 : Number(rawLimit);
|
|
162
426
|
const now = Date.now();
|
|
163
427
|
if (rawSince !== null && !/^\d{4}-\d\d-\d\dT\d\d:\d\d:\d\d(?:\.\d+)?(?:Z|[+-]\d\d:\d\d)$/.test(rawSince)
|
|
164
|
-
|| !Number.isFinite(since) || since > now || !Number.isSafeInteger(limit) || limit < 1 || limit > 500
|
|
165
|
-
|
|
428
|
+
|| !Number.isFinite(since) || since > now || !Number.isSafeInteger(limit) || limit < 1 || limit > 500
|
|
429
|
+
|| (journeyRef !== null && !ID.test(journeyRef))
|
|
430
|
+
|| (handModeFilter !== null && handModeFilter !== 'live' && handModeFilter !== 'shadow')) {
|
|
431
|
+
return jsonResponse({ error: 'usage: GET /v1/loops/edges?since=<ISO>&limit=<1..500>&ref=<card|run|mission id>' }, 400);
|
|
166
432
|
}
|
|
167
433
|
const sinceIso = new Date(since).toISOString();
|
|
168
434
|
const seats = new Set((deps.seatIds ?? (() => [...SEATS.map(s => s.seat), ...listSubSeatCharters().map(s => s.id)]))());
|
|
@@ -189,7 +455,7 @@ export function handleLoopEdgesGet(req: Request, deps: LoopEdgesDeps = {}): Resp
|
|
|
189
455
|
finally { store.close(); }
|
|
190
456
|
}
|
|
191
457
|
}
|
|
192
|
-
const
|
|
458
|
+
const readFacts = deps.listRunFacts ?? (deps.listRunStarts ? () => [] : () => {
|
|
193
459
|
const owners = new Map<string, string>();
|
|
194
460
|
try {
|
|
195
461
|
for (const entry of (deps.listLoopOwners ?? (() => listLoops()))()) {
|
|
@@ -197,8 +463,11 @@ export function handleLoopEdgesGet(req: Request, deps: LoopEdgesDeps = {}): Resp
|
|
|
197
463
|
}
|
|
198
464
|
} catch { /* A registry outage does not erase explicit launch-seat evidence. */ }
|
|
199
465
|
return (deps.ledgerDir ? [deps.ledgerDir] : resolveFederatedRunLedgerDirectories({ includeTest: false }))
|
|
200
|
-
.flatMap(dir =>
|
|
466
|
+
.flatMap(dir => runFacts(dir, owners, seats));
|
|
201
467
|
});
|
|
468
|
+
let facts: RunFact[] = [];
|
|
469
|
+
try { facts = readFacts().filter(fact => ID.test(fact.runId)); } catch { /* An unreadable ledger set links nothing. */ }
|
|
470
|
+
const readStarts = deps.listRunStarts ?? ((from: string) => runStartsFrom(facts, from));
|
|
202
471
|
const runs = readStarts(sinceIso).filter(run => seat(run.seat, seats) && ID.test(run.runId)
|
|
203
472
|
&& (inWindow(run.at, since, now) || (run.mergedAt !== undefined && inWindow(run.mergedAt, since, now))));
|
|
204
473
|
for (const run of runs) {
|
|
@@ -224,6 +493,10 @@ export function handleLoopEdgesGet(req: Request, deps: LoopEdgesDeps = {}): Resp
|
|
|
224
493
|
}
|
|
225
494
|
}
|
|
226
495
|
}
|
|
227
|
-
|
|
228
|
-
|
|
496
|
+
const links = taskAgentEdges(deps, facts, seats, since, now, sinceIso, edges);
|
|
497
|
+
const journey = journeyRef === null ? null : journeyIds(journeyRef, links);
|
|
498
|
+
const out = edges.filter(edge => (!journey || journey.has(edge.ref))
|
|
499
|
+
&& (handModeFilter === null || edge.kind !== 'hand' || edge.mode === handModeFilter));
|
|
500
|
+
out.sort((a, b) => b.at.localeCompare(a.at) || a.kind.localeCompare(b.kind) || a.from.localeCompare(b.from) || a.to.localeCompare(b.to));
|
|
501
|
+
return jsonResponse({ edges: out.slice(0, limit) });
|
|
229
502
|
}
|
package/src/nexus/api/ops-api.ts
CHANGED
|
@@ -18,13 +18,35 @@ let seatsCache: ReturnType<typeof createSeatsCache> | null = null;
|
|
|
18
18
|
export function setSeatsCacheForTest(cache: ReturnType<typeof createSeatsCache> | null): void { seatsCache = cache; }
|
|
19
19
|
const LOG_PATH = /^\/v1\/ops\/release\/runs\/([^/]+)\/nodes\/([^/]+)\/log$/;
|
|
20
20
|
|
|
21
|
-
interface RunNode { nodeId: string; ok?: unknown; output?: unknown }
|
|
21
|
+
interface RunNode { nodeId: string; ok?: unknown; output?: unknown; startedAt?: unknown; endedAt?: unknown }
|
|
22
22
|
interface RunRecord {
|
|
23
23
|
status: string;
|
|
24
24
|
path: string[];
|
|
25
25
|
startedAt: string;
|
|
26
26
|
input?: unknown;
|
|
27
27
|
nodes: RunNode[];
|
|
28
|
+
currentNode?: { nodeId?: unknown; startedAt?: unknown };
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Node times come only from the ledger (GRAPH-NODE-TIMES); older ledgers have none and the screen says so. */
|
|
32
|
+
const ISO_TIME = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,9})?(?:Z|[+-]\d{2}:\d{2})$/;
|
|
33
|
+
function isoTime(value: unknown): string | undefined {
|
|
34
|
+
return typeof value === 'string' && ISO_TIME.test(value) && Number.isFinite(Date.parse(value)) ? value : undefined;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function nodeTimes(node: RunNode): { startedAt?: string; endedAt?: string } {
|
|
38
|
+
const startedAt = isoTime(node.startedAt);
|
|
39
|
+
const endedAt = isoTime(node.endedAt);
|
|
40
|
+
return { ...(startedAt ? { startedAt } : {}), ...(endedAt ? { endedAt } : {}) };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** The running node has no record yet — show it with its start time when the ledger names it as the path tail. */
|
|
44
|
+
function runningNode(run: RunRecord, recorded: number): Array<{ nodeId: string; ok: null; summary: string; startedAt: string }> {
|
|
45
|
+
const current = run.currentNode;
|
|
46
|
+
const startedAt = isoTime(current?.startedAt);
|
|
47
|
+
if (run.status !== 'running' || !startedAt || typeof current?.nodeId !== 'string' ||
|
|
48
|
+
run.path.length !== recorded + 1 || run.path.at(-1) !== current.nodeId) return [];
|
|
49
|
+
return [{ nodeId: redactSecretText(current.nodeId), ok: null, summary: '', startedAt }];
|
|
28
50
|
}
|
|
29
51
|
|
|
30
52
|
function recordAt(path: string): RunRecord | null {
|
|
@@ -66,6 +88,77 @@ function runVersion(input: unknown): string | null {
|
|
|
66
88
|
return typeof version === 'string' && VERSION.test(version) ? version : null;
|
|
67
89
|
}
|
|
68
90
|
|
|
91
|
+
/** Facts away mode prints for a node — only built when asked, so the HTTP shape stays as it was. */
|
|
92
|
+
export interface ReleaseNodeFacts { verdict?: string; introduced?: number; preexisting?: number; waiver?: string }
|
|
93
|
+
export interface ReleaseRunNodeView {
|
|
94
|
+
nodeId: string; ok: boolean | null; summary: string; startedAt?: string; endedAt?: string; facts?: ReleaseNodeFacts;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function parsedOutput(output: unknown): Record<string, unknown> | null {
|
|
98
|
+
let value = output;
|
|
99
|
+
if (typeof value === 'string') {
|
|
100
|
+
const text = value;
|
|
101
|
+
try { value = JSON.parse(text) as unknown; }
|
|
102
|
+
catch {
|
|
103
|
+
const last = text.split('\n').map((line) => line.trim()).filter(Boolean).at(-1);
|
|
104
|
+
try { value = JSON.parse(last ?? '') as unknown; } catch { return null; }
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return value && typeof value === 'object' && !Array.isArray(value) ? value as Record<string, unknown> : null;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export function releaseNodeFacts(output: unknown): ReleaseNodeFacts {
|
|
111
|
+
const value = parsedOutput(output);
|
|
112
|
+
if (!value) return {};
|
|
113
|
+
const verdict = typeof value.verdict === 'string' ? value.verdict : typeof value.outcome === 'string' ? value.outcome : undefined;
|
|
114
|
+
const introduced = Array.isArray(value.introduced) ? value.introduced.length : typeof value.introduced === 'number' ? value.introduced : undefined;
|
|
115
|
+
const preexisting = typeof value.preexisting === 'number' ? value.preexisting : undefined;
|
|
116
|
+
const waiver = typeof value.waiver === 'string' && value.waiver.trim() ? redactSecretText(value.waiver).slice(0, 200) : undefined;
|
|
117
|
+
return {
|
|
118
|
+
...(verdict ? { verdict: redactSecretText(verdict).slice(0, 40) } : {}),
|
|
119
|
+
...(introduced !== undefined ? { introduced } : {}),
|
|
120
|
+
...(preexisting !== undefined ? { preexisting } : {}),
|
|
121
|
+
...(waiver ? { waiver } : {}),
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
export interface ReleaseRunView {
|
|
125
|
+
runId: string; status: string; startedAt: string; version: string | null; path: string[]; nodes: ReleaseRunNodeView[];
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/** The release run ledger as `/v1/ops/release/runs` serves it (newest 20) — shared with Telegram /release and away mode
|
|
129
|
+
* so every surface reads the same verdicts. `'unavailable'` = the directory exists but cannot be read. */
|
|
130
|
+
export function readReleaseRuns(version: string | null = null,
|
|
131
|
+
dir: string = join(effectiveInstanceRoot(), 'graph-runs', 'release-loop'),
|
|
132
|
+
opts: { facts?: boolean } = {}): ReleaseRunView[] | 'unavailable' {
|
|
133
|
+
let filenames: string[];
|
|
134
|
+
try {
|
|
135
|
+
filenames = readdirSync(dir, { withFileTypes: true })
|
|
136
|
+
.filter((entry) => entry.isFile() && entry.name.endsWith('.json')
|
|
137
|
+
&& IDENTIFIER.test(entry.name.slice(0, -5)))
|
|
138
|
+
.map((entry) => entry.name);
|
|
139
|
+
}
|
|
140
|
+
catch (error) {
|
|
141
|
+
if ((error as NodeJS.ErrnoException).code === 'ENOENT') return [];
|
|
142
|
+
return 'unavailable';
|
|
143
|
+
}
|
|
144
|
+
return filenames.flatMap((filename) => {
|
|
145
|
+
const run = recordAt(join(dir, filename));
|
|
146
|
+
if (!run) return [];
|
|
147
|
+
const v = runVersion(run.input);
|
|
148
|
+
if (version !== null && v !== version) return [];
|
|
149
|
+
return [{
|
|
150
|
+
runId: redactSecretText(filename.slice(0, -5)), status: redactSecretText(run.status),
|
|
151
|
+
startedAt: redactSecretText(run.startedAt), version: v,
|
|
152
|
+
path: run.path.filter((step): step is string => typeof step === 'string').map(redactSecretText),
|
|
153
|
+
nodes: [...run.nodes.filter((node) => node && typeof node.nodeId === 'string').map((node) => ({
|
|
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)],
|
|
158
|
+
}];
|
|
159
|
+
}).sort((a, b) => b.startedAt.localeCompare(a.startedAt)).slice(0, 20);
|
|
160
|
+
}
|
|
161
|
+
|
|
69
162
|
/** The sole data gate for every read-only /v1/ops endpoint. */
|
|
70
163
|
export function handleOpsApi(req: Request, metaApi: MetaApiOpts | undefined): Response | Promise<Response> {
|
|
71
164
|
const url = new URL(req.url);
|
|
@@ -103,33 +196,8 @@ export function handleOpsApi(req: Request, metaApi: MetaApiOpts | undefined): Re
|
|
|
103
196
|
}
|
|
104
197
|
if (pathname === '/v1/ops/release/runs') {
|
|
105
198
|
if (version !== null && !VERSION.test(version)) return jsonResponse({ error: 'invalid-version' }, 400);
|
|
106
|
-
const
|
|
107
|
-
|
|
108
|
-
try {
|
|
109
|
-
filenames = readdirSync(dir, { withFileTypes: true })
|
|
110
|
-
.filter((entry) => entry.isFile() && entry.name.endsWith('.json')
|
|
111
|
-
&& IDENTIFIER.test(entry.name.slice(0, -5)))
|
|
112
|
-
.map((entry) => entry.name);
|
|
113
|
-
}
|
|
114
|
-
catch (error) {
|
|
115
|
-
if ((error as NodeJS.ErrnoException).code === 'ENOENT') return served(jsonResponse([]));
|
|
116
|
-
return jsonResponse({ error: 'runs-unavailable' }, 500);
|
|
117
|
-
}
|
|
118
|
-
const runs = filenames.flatMap((filename) => {
|
|
119
|
-
const run = recordAt(join(dir, filename));
|
|
120
|
-
if (!run) return [];
|
|
121
|
-
const v = runVersion(run.input);
|
|
122
|
-
if (version !== null && v !== version) return [];
|
|
123
|
-
return [{
|
|
124
|
-
runId: redactSecretText(filename.slice(0, -5)), status: redactSecretText(run.status),
|
|
125
|
-
startedAt: redactSecretText(run.startedAt), version: v,
|
|
126
|
-
path: run.path.filter((step): step is string => typeof step === 'string').map(redactSecretText),
|
|
127
|
-
nodes: run.nodes.filter((node) => node && typeof node.nodeId === 'string').map((node) => ({
|
|
128
|
-
nodeId: redactSecretText(node.nodeId), ok: typeof node.ok === 'boolean' ? node.ok : null,
|
|
129
|
-
summary: nodeSummary(node.output),
|
|
130
|
-
})),
|
|
131
|
-
}];
|
|
132
|
-
}).sort((a, b) => b.startedAt.localeCompare(a.startedAt)).slice(0, 20);
|
|
199
|
+
const runs = readReleaseRuns(version);
|
|
200
|
+
if (runs === 'unavailable') return jsonResponse({ error: 'runs-unavailable' }, 500);
|
|
133
201
|
return served(jsonResponse(runs));
|
|
134
202
|
}
|
|
135
203
|
// WHATWG URL normalizes a literal runs/../nodes/... before routing.
|
package/src/nexus/index.ts
CHANGED
|
@@ -136,6 +136,7 @@ import {
|
|
|
136
136
|
type DailyReflectionSchedulerHandle,
|
|
137
137
|
} from '../notes/daily-reflection-scheduler.js';
|
|
138
138
|
import { type ScheduleRunnerHandle } from '../domains/schedule-runner.js';
|
|
139
|
+
import { startWatchDefaultScheduler } from '../domains/watch-default-scheduler.js';
|
|
139
140
|
import { resolvePwaStaticDir } from './static-dir-resolve.js';
|
|
140
141
|
import {
|
|
141
142
|
createHitlPendingCallbacks,
|
|
@@ -1310,6 +1311,7 @@ export async function runNexus(opts: RunNexusOptions = {}): Promise<RunNexusHand
|
|
|
1310
1311
|
// the daily-reflection scheduler regardless of which branch booted
|
|
1311
1312
|
// it. Tests with detachForTesting set leave this undefined.
|
|
1312
1313
|
let dailyReflectionScheduler: DailyReflectionSchedulerHandle | undefined;
|
|
1314
|
+
let watchDefaultScheduler: ReturnType<typeof startWatchDefaultScheduler> | undefined;
|
|
1313
1315
|
// RFC #2161 FU A8 (2026-05-11) — discovery cron via NEXUS. Dormant
|
|
1314
1316
|
// by default (opt-in via `ELANOUS_DISCOVERY_CRON_INTERVAL_MS`); when
|
|
1315
1317
|
// configured, fires runDiscovery every interval and pushes the
|
|
@@ -3198,6 +3200,8 @@ export async function runNexus(opts: RunNexusOptions = {}): Promise<RunNexusHand
|
|
|
3198
3200
|
// schedule migrate 로 이관.) 롤백: 아래 한 줄 복원.
|
|
3199
3201
|
// try { scheduleRunner = startScheduleRunner(); } catch { /* fail-soft */ }
|
|
3200
3202
|
void scheduleRunner; // 은퇴(undefined 유지)
|
|
3203
|
+
try { watchDefaultScheduler = startWatchDefaultScheduler(); }
|
|
3204
|
+
catch (error) { debug.log('watch.default', 'failed', { stage: 'boot', reason: error instanceof Error ? error.message : String(error) }); }
|
|
3201
3205
|
}
|
|
3202
3206
|
}
|
|
3203
3207
|
|
|
@@ -3335,6 +3339,9 @@ export async function runNexus(opts: RunNexusOptions = {}): Promise<RunNexusHand
|
|
|
3335
3339
|
}
|
|
3336
3340
|
// S2 (2026-07-07) · stop the schedule runner so its node-cron tasks +
|
|
3337
3341
|
// reconcile interval don't keep firing/holding the loop past shutdown.
|
|
3342
|
+
if (watchDefaultScheduler) {
|
|
3343
|
+
try { watchDefaultScheduler.stop(); } catch { /* swallow */ }
|
|
3344
|
+
}
|
|
3338
3345
|
if (scheduleRunner) {
|
|
3339
3346
|
try { scheduleRunner.stop(); } catch { /* swallow */ }
|
|
3340
3347
|
}
|
|
@@ -40,6 +40,7 @@ export interface ChecklistHistory {
|
|
|
40
40
|
dev: string;
|
|
41
41
|
force?: true;
|
|
42
42
|
reason?: string;
|
|
43
|
+
generation?: number;
|
|
43
44
|
}
|
|
44
45
|
export interface Checklist {
|
|
45
46
|
version: string;
|
|
@@ -101,6 +102,7 @@ export function statusChangesSince(history: readonly ChecklistHistory[], sinceIs
|
|
|
101
102
|
}
|
|
102
103
|
|
|
103
104
|
function mutate(v: string, apply: (data: Checklist, otherItems: (id: string) => store.ChecklistCollision[]) => boolean): Checklist {
|
|
105
|
+
// addItem/setItem/claimItem/removeItem/seedFromRoadmap reach the generation-fenced ledger write here.
|
|
104
106
|
return decodeClaimHistory(store.mutate(v, store.releasedVersion(), devVersion(), apply));
|
|
105
107
|
}
|
|
106
108
|
|