elanous 0.2.14 → 0.2.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/apps/pwa/out/404/index.html +1 -1
- package/apps/pwa/out/404.html +1 -1
- package/apps/pwa/out/_next/static/chunks/{1307-6e7e0377c1db0bdc.js → 1307-b46d45b705e8c9ae.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/5977-c952712c87a1f757.js +1 -0
- package/apps/pwa/out/_next/static/chunks/899-46946fe8363e1262.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{9103-7a3ae7f60af3b99f.js → 9103-60fbef33e66f0a46.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-79c3ed84baaedd03.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/autopilot/{page-7b2b4f378354869a.js → page-3cb52e5ea8fc2fa5.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/board/page-a424ef80885d1743.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/botlab/page-c1e20d7d338713f1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/bots/{page-1512d91c7491ed62.js → page-482c5dbde699947c.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/ceo/page-1e962b31d1f11081.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/chat/{page-a627db94ec6904a0.js → page-8c2d9526164f5d09.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/consult/page-ffff6d819fbc6e0f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/control/{page-e6b84ab07d3567d5.js → page-a0e2a6d1bfb52fc5.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-c53598cfce6156f2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/editor/{page-fed69f9759d248d0.js → page-13a63953f53c6a23.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/exec/{page-15374909d8f1aebd.js → page-5d3c8429d04f3831.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/field/{page-012a742f81218b01.js → page-3193bdc0bbab047a.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/inside/page-76270efaee638f17.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/intake/{page-99effd9d893a43dc.js → page-6107ca8ddd5e3bdd.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/{layout-938de57ae0315e46.js → layout-dce686e582c23937.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/live/page-d3d53b146416e422.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/loops/{page-160089123a40dd91.js → page-1a61c7ca1ceb3ae7.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/market/{page-629e1ede482ec6eb.js → page-51f811fa119308b6.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-dacff46049e1557e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/morning/{page-27e99b4e54a849d4.js → page-4167348049166852.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/not-found-de52e8fdf9095f7a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-36a7d994f4ca0299.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/{page-32cde713be47cddb.js → page-ca3952cd78a83ca2.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/release/{page-e7e10cd3386222e5.js → page-5b042736aa113756.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-acb749f1e432bcf3.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/outputs/page-cd78862707999911.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/page-54b9ef8afe6278fd.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/reflection/{page-e64caff084f7ce21.js → page-1ebfe049c640e171.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/scheduler/{page-db07eaffba0df7e3.js → page-57924393e3e4e539.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-9e0ce6c8129fa641.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-31fa1ce4c4aff9ae.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/{page-86f91034bd5cabe5.js → page-0f5cd57ca928f42b.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-abe8e3b74bea1ffc.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/page-4364ef9f4e7c7f0f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/share/{page-ce4d8a1215ce53a3.js → page-d28573d6792b4275.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/showroom/{page-58043db78ba519ae.js → page-59f03fda76c03d22.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/tasks/{page-357750dfb91bf960.js → page-d751de5f8062ff5e.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/term/{page-274703bc41b71333.js → page-fe1759f94003b32b.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/today/page-d64c984e855c8c68.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/trace/page-86914b0dee8ab7f8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/vault/{page-3b7e821dc492bdfd.js → page-9afdee66b820c76e.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-d4aef67abf264874.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/{page-f8da5dc6f3752276.js → page-1ea64acadd704ace.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/workspace/{page-2f810b18d89ecba6.js → page-3544f154c87b43ee.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-30c2073bfc21c004.js +1 -0
- package/apps/pwa/out/_next/static/css/001d5fbaee388a25.css +1 -0
- package/apps/pwa/out/_next/static/css/{04dabcbcc4bfa6db.css → 085673d826b98b62.css} +1 -1
- package/apps/pwa/out/_next/static/rv9cierV0jSOvDBm1g0XB/_buildManifest.js +1 -0
- package/apps/pwa/out/approvals/index.html +1 -1
- package/apps/pwa/out/approvals/index.txt +15 -15
- package/apps/pwa/out/autopilot/index.html +1 -1
- package/apps/pwa/out/autopilot/index.txt +15 -15
- package/apps/pwa/out/board/index.html +1 -1
- package/apps/pwa/out/board/index.txt +15 -15
- package/apps/pwa/out/botlab/index.html +1 -1
- package/apps/pwa/out/botlab/index.txt +15 -15
- package/apps/pwa/out/bots/index.html +1 -1
- package/apps/pwa/out/bots/index.txt +15 -15
- package/apps/pwa/out/ceo/index.html +1 -0
- package/apps/pwa/out/ceo/index.txt +33 -0
- package/apps/pwa/out/chat/index.html +1 -1
- package/apps/pwa/out/chat/index.txt +15 -15
- package/apps/pwa/out/consult/index.html +1 -1
- package/apps/pwa/out/consult/index.txt +15 -15
- package/apps/pwa/out/control/index.html +1 -1
- package/apps/pwa/out/control/index.txt +15 -15
- package/apps/pwa/out/design-check/index.html +1 -1
- package/apps/pwa/out/design-check/index.txt +15 -15
- package/apps/pwa/out/editor/index.html +1 -1
- package/apps/pwa/out/editor/index.txt +15 -15
- package/apps/pwa/out/exec/index.html +1 -1
- package/apps/pwa/out/exec/index.txt +15 -15
- package/apps/pwa/out/field/index.html +1 -1
- package/apps/pwa/out/field/index.txt +15 -15
- package/apps/pwa/out/healthz +1 -1
- package/apps/pwa/out/index.html +1 -1
- package/apps/pwa/out/index.txt +15 -15
- package/apps/pwa/out/inside/index.html +1 -1
- package/apps/pwa/out/inside/index.txt +15 -15
- package/apps/pwa/out/intake/index.html +1 -1
- package/apps/pwa/out/intake/index.txt +15 -15
- package/apps/pwa/out/live/index.html +1 -1
- package/apps/pwa/out/live/index.txt +15 -15
- package/apps/pwa/out/loops/index.html +1 -1
- package/apps/pwa/out/loops/index.txt +15 -15
- package/apps/pwa/out/market/index.html +1 -1
- package/apps/pwa/out/market/index.txt +15 -15
- package/apps/pwa/out/missions/placeholder/index.html +1 -1
- package/apps/pwa/out/missions/placeholder/index.txt +15 -15
- package/apps/pwa/out/morning/index.html +1 -1
- package/apps/pwa/out/morning/index.txt +15 -15
- package/apps/pwa/out/observatory/index.html +1 -1
- package/apps/pwa/out/observatory/index.txt +15 -15
- package/apps/pwa/out/ops/checklist/index.html +1 -1
- package/apps/pwa/out/ops/checklist/index.txt +15 -15
- package/apps/pwa/out/ops/release/index.html +1 -1
- package/apps/pwa/out/ops/release/index.txt +15 -15
- package/apps/pwa/out/ops/seats/index.html +1 -1
- package/apps/pwa/out/ops/seats/index.txt +15 -15
- package/apps/pwa/out/outputs/index.html +1 -1
- package/apps/pwa/out/outputs/index.txt +15 -15
- package/apps/pwa/out/reflection/index.html +1 -1
- package/apps/pwa/out/reflection/index.txt +16 -16
- package/apps/pwa/out/scheduler/index.html +1 -1
- package/apps/pwa/out/scheduler/index.txt +15 -15
- package/apps/pwa/out/sessions/index.html +1 -1
- package/apps/pwa/out/sessions/index.txt +15 -15
- package/apps/pwa/out/settings/devices/index.html +1 -1
- package/apps/pwa/out/settings/devices/index.txt +15 -15
- package/apps/pwa/out/settings/index.html +1 -1
- package/apps/pwa/out/settings/index.txt +15 -15
- package/apps/pwa/out/setup/done/index.html +1 -1
- package/apps/pwa/out/setup/done/index.txt +15 -15
- package/apps/pwa/out/setup/index.html +1 -1
- package/apps/pwa/out/setup/index.txt +15 -15
- package/apps/pwa/out/share/index.html +1 -1
- package/apps/pwa/out/share/index.txt +15 -15
- package/apps/pwa/out/showroom/index.html +1 -1
- package/apps/pwa/out/showroom/index.txt +15 -15
- package/apps/pwa/out/tasks/index.html +1 -1
- package/apps/pwa/out/tasks/index.txt +15 -15
- package/apps/pwa/out/term/index.html +1 -1
- package/apps/pwa/out/term/index.txt +15 -15
- package/apps/pwa/out/today/index.html +1 -1
- package/apps/pwa/out/today/index.txt +15 -15
- package/apps/pwa/out/trace/index.html +1 -1
- package/apps/pwa/out/trace/index.txt +15 -15
- package/apps/pwa/out/vault/index.html +1 -1
- package/apps/pwa/out/vault/index.txt +15 -15
- package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
- package/apps/pwa/out/workflows/chat-ui/index.txt +15 -15
- package/apps/pwa/out/workflows/index.html +1 -1
- package/apps/pwa/out/workflows/index.txt +15 -15
- package/apps/pwa/out/workspace/index.html +1 -1
- package/apps/pwa/out/workspace/index.txt +15 -15
- package/apps/pwa/out/worktrees/index.html +1 -1
- package/apps/pwa/out/worktrees/index.txt +15 -15
- package/graphs/doc-gen/doc-gen.yaml +24 -0
- package/graphs/doc-gen/recipes.yaml +3 -0
- package/graphs/heal/heal-loop.yaml +1 -1
- package/graphs/implement-loop.yaml +1 -1
- package/graphs/orchestrator/orchestrator.yaml +7 -0
- package/graphs/orchestrator/recipes.yaml +3 -0
- package/graphs/release/recipes.yaml +3 -0
- package/graphs/release/release-loop.yaml +4 -2
- package/graphs/steward/recipes.yaml +2 -1
- package/package.json +1 -1
- package/scripts/brand/check.ts +7 -5
- package/scripts/ci-public-export-import-gate.ts +154 -0
- package/scripts/ci-test-interference-gate.ts +5 -1
- package/scripts/ci-typecheck-changed.ts +82 -31
- package/scripts/docs/doc-rot.ts +164 -0
- package/scripts/docs/generate-graph-anatomy.ts +437 -0
- package/scripts/docs-cli-check.ts +4 -4
- package/scripts/expose-rubric-suggest.ts +18 -12
- package/scripts/goals/goal-census.ts +343 -0
- package/scripts/lecture-note/node.ts +31 -11
- package/scripts/lessons/handbook-page.ts +30 -3
- package/scripts/marketing/generate-ax-brief.ts +140 -0
- package/scripts/marketing/generate-feature-map.ts +118 -0
- package/scripts/measure/author-section-contribution.ts +84 -0
- package/scripts/release-loop/checklist-gate-node.ts +53 -14
- package/scripts/release-loop/cut-branch.ts +43 -13
- package/scripts/release-loop/gate-node.ts +82 -14
- package/scripts/release-loop/vault-note-node.ts +257 -8
- package/scripts/release-story/draft.ts +57 -9
- package/scripts/rhythm/daily.ts +263 -76
- package/scripts/rhythm/monthly.ts +97 -0
- package/scripts/rhythm/weekly.ts +30 -9
- package/scripts/smart-merge/resync-shadow.ts +147 -0
- package/src/acp/client.ts +17 -2
- package/src/agent-env/profile.ts +161 -0
- package/src/agent-mission/driver.ts +51 -1
- package/src/agent-mission/supervisor-metrics-cli.ts +40 -0
- package/src/agent-mission/supervisor-metrics.ts +218 -0
- package/src/agent-substrate/pr-reviewer.ts +2 -0
- package/src/ask-user-question/pending-questions.ts +5 -0
- package/src/auth/github-app-token.ts +112 -13
- package/src/autopilot/build/llm-conflict-merge.ts +32 -6
- package/src/autopilot/track-agent-shadow.ts +2 -1
- package/src/briefing/brief-items.ts +276 -0
- package/src/budget/usage-outcomes.ts +204 -0
- package/src/claims/claims-render.ts +14 -5
- package/src/cli/agent-env-cli.ts +21 -0
- package/src/cli/brief-cli.ts +73 -0
- package/src/cli/decisions-cli.ts +9 -0
- package/src/cli/doctor-cli.ts +23 -1
- package/src/cli/doctor-node-modules.ts +147 -0
- package/src/cli/intake-cli.ts +46 -17
- package/src/cli/know-cli.ts +20 -0
- package/src/cli/logs-cli.ts +43 -1
- package/src/cli/pod-cli.ts +2 -0
- package/src/cli/pr-cli.ts +80 -12
- package/src/cli/release-cli.ts +66 -29
- package/src/cli/seat-requests-cli.ts +52 -0
- package/src/cli/self-update.ts +200 -1
- package/src/cli/usage-cli.ts +26 -0
- package/src/context-bus/context-now-public.ts +16 -6
- package/src/context-bus/context-now.ts +3 -1
- package/src/context-bus/coord-events.ts +6 -4
- package/src/decisions/decision-cards.ts +33 -7
- package/src/decisions/proact-meter.ts +286 -0
- package/src/decisions/proact1-lite.ts +86 -0
- package/src/decisions/web-push-decision-cards.ts +1 -1
- package/src/discord-self-message.ts +9 -0
- package/src/domains/alert-audit.ts +271 -0
- package/src/domains/core-tools.ts +4 -3
- package/src/domains/ops-facts-tool.ts +31 -1
- package/src/flow-loop/split.ts +24 -0
- package/src/harness/harness-cli-command.ts +147 -17
- package/src/harness/harness-incidents.ts +2 -0
- package/src/harness/harness-pod-dispatch.ts +67 -8
- package/src/harness/harness-queue.ts +380 -50
- package/src/harness/harness-seams.ts +13 -3
- package/src/harness/helper-repair.ts +218 -0
- package/src/harness/helper-scan.ts +27 -4
- package/src/harness/launch-stamp.ts +236 -0
- package/src/harness/ledger-write-guard-child.fixture.ts +12 -0
- package/src/harness/ledger-write-guard.ts +35 -0
- package/src/harness/nested-elanous-policy.ts +114 -0
- package/src/harness/pod-memory-policy.ts +15 -0
- package/src/harness/repository-name.ts +55 -0
- package/src/hq/fence-audit.ts +90 -0
- package/src/hq/hq.ts +70 -6
- package/src/hq/lease.ts +9 -7
- package/src/hq/move-plan.ts +126 -0
- package/src/hq/promote.ts +31 -0
- package/src/hq/transfer.ts +185 -0
- package/src/index.ts +189 -13
- package/src/intake-plane/check.ts +63 -0
- package/src/intake-plane/digest.ts +65 -21
- package/src/intake-plane/front-classifier.ts +6 -0
- package/src/intake-plane/items.ts +3 -2
- package/src/intake-plane/lens.ts +177 -0
- package/src/intake-plane/route.ts +145 -1
- package/src/knowledge/know-find.ts +105 -0
- package/src/llm.ts +75 -19
- package/src/loops/budget.ts +36 -5
- package/src/loops/checker.ts +2 -2
- package/src/loops/orchestrator/author-depth.ts +65 -9
- package/src/loops/orchestrator/author-input-check.ts +8 -3
- package/src/loops/orchestrator/author-ledger.ts +3 -1
- package/src/loops/orchestrator/finish-rate.ts +14 -3
- package/src/loops/orchestrator/seat-share.ts +56 -0
- package/src/loops/orchestrator/tick.ts +107 -29
- package/src/loops/orchestrator/traffic.ts +117 -11
- package/src/loops/registry.ts +142 -61
- package/src/maturity/bot-slash-catalog.ts +11 -2
- package/src/maturity/feature-maturity.ts +4 -3
- package/src/maturity/tui-slash-availability.ts +40 -0
- package/src/mss/logging/log-store.ts +35 -4
- package/src/nexus/api/http-server.ts +28 -14
- package/src/nexus/api/loop-edges.ts +173 -0
- package/src/nexus/api/schedules-read.ts +13 -0
- package/src/nexus/api/seat-requests.ts +79 -56
- package/src/oauth/overload-failover-launch.ts +75 -0
- package/src/oauth/overload-failover.ts +137 -0
- package/src/prompt-enhance/enhance.ts +6 -1
- package/src/release-loop/auto-start.ts +6 -3
- package/src/release-loop/checklist.ts +19 -5
- package/src/release-loop/feature-store.ts +59 -31
- package/src/release-loop/merged-pr-checklist.ts +125 -0
- package/src/release-loop/placement.ts +98 -29
- package/src/seat-dispatch/seat-ask.ts +33 -6
- package/src/seat-dispatch/seat-request-eligibility.ts +19 -0
- package/src/seat-dispatch/seat-request-ledger.ts +133 -0
- package/src/seat-loop/seat-loop.ts +215 -12
- package/src/self-dev/ask-launch-flow.ts +22 -1
- package/src/self-dev/authored-goal-trace.ts +64 -0
- package/src/self-dev/dev-cli.ts +19 -1
- package/src/self-dev/dev-pipeline.ts +14 -9
- package/src/self-dev/draft-sweep.ts +90 -10
- package/src/self-dev/draft-triage-rules.ts +25 -8
- package/src/self-dev/entrance-registry.ts +10 -6
- package/src/self-dev/old-door.ts +73 -0
- package/src/self-dev/orchestrate.ts +15 -1
- package/src/self-dev/review-provider-fallback.ts +6 -1
- package/src/self-dev/run-supervisor.ts +49 -2
- package/src/self-implement/app-api-budget.ts +166 -0
- package/src/self-implement/budget-gate.ts +7 -5
- package/src/self-implement/follow-up-goals.ts +289 -0
- package/src/self-implement/frozen-merges.ts +3 -2
- package/src/self-implement/goal-author.ts +50 -6
- package/src/self-implement/goal-clarification-escalation.ts +5 -0
- package/src/self-implement/heal-intake.ts +120 -2
- package/src/self-implement/heal-triage.ts +44 -0
- package/src/self-implement/host-regate.ts +9 -2
- package/src/self-implement/l8-merge-queue.ts +132 -0
- package/src/self-implement/orchestrator.ts +325 -47
- package/src/self-implement/pipeline-shape.ts +1 -1
- package/src/self-implement/revert-guard.ts +42 -7
- package/src/self-implement/run-ledger.ts +6 -0
- package/src/self-implement/run-status-mapping.ts +2 -0
- package/src/self-implement/seams.ts +45 -16
- package/src/self-implement/self-implement-cli.ts +1 -0
- package/src/self-implement/speed-claims.ts +338 -0
- package/src/session-runtime/index.ts +2 -0
- package/src/session-runtime/retry-api.ts +87 -0
- package/src/session-runtime/retry-policy.ts +107 -6
- package/src/steward/triage.ts +74 -3
- package/src/steward/working-backwards.ts +20 -2
- package/src/task-cards/card-cli.ts +25 -0
- package/src/task-cards/card-store.ts +5 -3
- package/src/task-orchestrator/surfaces/pod-artifact-return.ts +63 -48
- package/src/task-orchestrator/surfaces/pod-command-job.ts +84 -9
- package/src/task-orchestrator/surfaces/pod-failure-reason.ts +48 -0
- package/src/task-orchestrator/surfaces/pod-ledger-collect.ts +4 -3
- package/src/task-orchestrator/surfaces/pod-ledger-prod-sink.ts +75 -0
- package/src/task-orchestrator/surfaces/pod-pool.ts +70 -9
- package/src/task-orchestrator/surfaces/pod-source-receive.ts +37 -1
- package/src/task-orchestrator/surfaces/self-implement-pod.ts +166 -46
- package/src/task-orchestrator/surfaces/self-implement.ts +7 -1
- package/src/telegram-commands.ts +5 -0
- package/src/telegram.ts +72 -4
- package/src/user-config.ts +64 -7
- package/src/version/packed-revision.json +1 -1
- package/src/workflow-runtime/nodes/bash.ts +9 -1
- package/apps/pwa/out/_next/static/chunks/4562-071d8bac44a01069.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5977-2df489622a52a33a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-74383f890f075511.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/board/page-c496bb16da85385f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/botlab/page-1abfe3aec3be6bdc.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/consult/page-fb260b9283dbf646.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-61ec1d2e92bdd50a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/inside/page-fd7e200d4d83fbd7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/live/page-d47266766766d227.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-2ce5471926761fdd.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/not-found-e995d30f6800b6bf.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-39f89c53038a51d4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-5a753bb4ec0cc511.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/outputs/page-04250f0e2c0d03df.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/page-341480ed8e3c1ea8.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-8ac8d71c2adf9c8a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-e1e47d8d940aba3d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-94dc383d3d54db1a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/page-b3759cc14da80908.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/today/page-194dce4a83143fce.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/trace/page-35bdeae2e2c17147.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-16bef3da05a900c3.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-134c1bc762aee62c.js +0 -1
- package/apps/pwa/out/_next/static/css/2e33713c444d5b39.css +0 -1
- package/apps/pwa/out/_next/static/x6mjI6vqOkVVu0jFuDkz1/_buildManifest.js +0 -1
- /package/apps/pwa/out/_next/static/{x6mjI6vqOkVVu0jFuDkz1 → rv9cierV0jSOvDBm1g0XB}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { ChecklistItem } from '../../release-loop/checklist.js';
|
|
2
|
+
import type { OrchestratorSeat } from '../../user-config.js';
|
|
3
|
+
|
|
4
|
+
const SEATS: readonly OrchestratorSeat[] = ['OP', 'TC', 'MK', 'UX'];
|
|
5
|
+
type WorkCell = Pick<ChecklistItem, 'owner' | 'status'>;
|
|
6
|
+
|
|
7
|
+
/** Allocate whole base slots by remaining work; this calculation does not decide launches. */
|
|
8
|
+
export function calculateSeatBaseShares({ totalSlots, currentRound, nextRound, seatCaps }: {
|
|
9
|
+
totalSlots: number;
|
|
10
|
+
currentRound: readonly WorkCell[];
|
|
11
|
+
nextRound: readonly WorkCell[];
|
|
12
|
+
seatCaps: Readonly<Record<OrchestratorSeat, number>>;
|
|
13
|
+
}): Record<OrchestratorSeat, number> {
|
|
14
|
+
if (!Number.isSafeInteger(totalSlots) || totalSlots < 0 ||
|
|
15
|
+
SEATS.some(seat => !Number.isSafeInteger(seatCaps[seat]) || seatCaps[seat] < 0)) {
|
|
16
|
+
throw new RangeError('totalSlots and seatCaps must be nonnegative safe integers');
|
|
17
|
+
}
|
|
18
|
+
const shares: Record<OrchestratorSeat, number> = { OP: 0, TC: 0, MK: 0, UX: 0 };
|
|
19
|
+
const weights: Record<OrchestratorSeat, number> = { OP: 0, TC: 0, MK: 0, UX: 0 };
|
|
20
|
+
for (const [cells, weight] of [[currentRound, 2], [nextRound, 1]] as const) {
|
|
21
|
+
for (const cell of cells) {
|
|
22
|
+
if (cell.status !== 'yellow') continue;
|
|
23
|
+
const seat = cell.owner?.split('/')[0];
|
|
24
|
+
if (SEATS.some(id => id === seat)) weights[seat as OrchestratorSeat] += weight;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
let available = Math.min(totalSlots, SEATS.reduce((sum, seat) => sum + seatCaps[seat], 0));
|
|
29
|
+
let eligible = SEATS.filter(seat => weights[seat] > 0 && seatCaps[seat] > 0);
|
|
30
|
+
while (available > 0 && eligible.length > 0) {
|
|
31
|
+
const totalWeight = eligible.reduce((sum, seat) => sum + weights[seat], 0);
|
|
32
|
+
const ideal = (seat: OrchestratorSeat) => available * weights[seat] / totalWeight;
|
|
33
|
+
const capped = eligible.filter(seat => ideal(seat) >= seatCaps[seat]);
|
|
34
|
+
if (capped.length > 0) {
|
|
35
|
+
for (const seat of capped) {
|
|
36
|
+
shares[seat] = seatCaps[seat];
|
|
37
|
+
available -= seatCaps[seat];
|
|
38
|
+
}
|
|
39
|
+
eligible = eligible.filter(seat => !capped.includes(seat));
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
const remainders = eligible.map(seat => ({ seat, exact: ideal(seat) }));
|
|
43
|
+
for (const { seat, exact } of remainders) {
|
|
44
|
+
shares[seat] = Math.floor(exact);
|
|
45
|
+
available -= shares[seat];
|
|
46
|
+
}
|
|
47
|
+
remainders.sort((a, b) => (b.exact - Math.floor(b.exact)) - (a.exact - Math.floor(a.exact)));
|
|
48
|
+
for (const { seat } of remainders) {
|
|
49
|
+
if (available === 0) break;
|
|
50
|
+
shares[seat]++;
|
|
51
|
+
available--;
|
|
52
|
+
}
|
|
53
|
+
break;
|
|
54
|
+
}
|
|
55
|
+
return shares;
|
|
56
|
+
}
|
|
@@ -8,17 +8,22 @@ import { effectiveInstanceRoot } from '../../instance/resolve.js';
|
|
|
8
8
|
import { getUserConfig } from '../../user-config.js';
|
|
9
9
|
import { debug } from '../../debug/log.js';
|
|
10
10
|
import { listSelfDevRuns, runSummaryLine, selfDevRunsDir } from '../../self-dev/run-store.js';
|
|
11
|
+
import { planAction, runSeatLoopOnce, type SeatItem } from '../../seat-loop/seat-loop.js';
|
|
12
|
+
import { harnessQueueOutcome } from '../../harness/harness-queue.js';
|
|
11
13
|
|
|
12
14
|
export type Window = '08' | '12' | '18';
|
|
13
|
-
export type Node = 'intake' | 'split' | 'place' | 'delegate' | 'reconcile' | 'report';
|
|
15
|
+
export type Node = 'intake' | 'split' | 'place' | 'delegate' | 'launch' | 'reconcile' | 'report';
|
|
14
16
|
export type Mode = 'off' | 'shadow' | 'live';
|
|
15
17
|
const SEAT_LOOP_IDS: Readonly<Record<string, string>> = { OP: 'op-seat', MK: 'cmo-seat', TC: 'tc-seat', UX: 'ux-seat' };
|
|
16
18
|
export interface Cell { id: string; title: string; seat?: string; host?: string; version?: string; cardId: string; origin: 'candidate' | 'flow1a'; priority?: 'P0' | 'P1' | 'P2'; predecessors?: string[] }
|
|
17
19
|
export interface TickState {
|
|
18
20
|
runId: string; window: Window; mode: Mode; day: string;
|
|
19
|
-
cards: TaskCard[]; skippedCards: Array<{ id: string; reason: string }>; cells: Cell[]; placed: number; unplaced?: number; delegated: number; wouldDelegate: number; rebalanced?: number;
|
|
21
|
+
cards: TaskCard[]; skippedCards: Array<{ id: string; reason: string }>; cells: Cell[]; placed: number; unplaced?: number; delegated: number; wouldDelegate: number; rebalanced?: number; rebalanceBlocked?: Array<{ id: string; reason: string }>;
|
|
20
22
|
reconciled: { reached: number; progressing: number; blocked: number; unknown: number };
|
|
21
23
|
runSummaries?: string[];
|
|
24
|
+
launched?: Array<{ seat: string; status: string; key?: string; queueId?: string; runId?: string }>;
|
|
25
|
+
queueOutcomes?: Array<{ queueId: string; key?: string; outcome: string }>;
|
|
26
|
+
steps: Array<{ node: Node; reason: string; count: number; targetLoopId?: string }>;
|
|
22
27
|
nodes: Partial<Record<Node, 'ok' | 'skipped'>>;
|
|
23
28
|
missing: string[];
|
|
24
29
|
}
|
|
@@ -26,11 +31,13 @@ export interface TickState {
|
|
|
26
31
|
export type SplitAdapter = (card: TaskCard, opts?: { shadow?: boolean }) => Promise<Array<{ id: string; title: string; seat?: string; host?: string }>> | Array<{ id: string; title: string; seat?: string; host?: string }>;
|
|
27
32
|
/** RELPLAN1 `placeCell(input: PlacementCell)` — owner·priority·predecessors are required; the result carries no seat. */
|
|
28
33
|
export type PlacementInput = { id: string; title: string; owner: string; priority: 'P0' | 'P1' | 'P2'; predecessors: string[] };
|
|
29
|
-
export type PlaceAdapter = (cell: PlacementInput) => Promise<{ version?: string } | null> | { version?: string } | null;
|
|
30
|
-
export type RebalanceAdapter = (version: string) => Promise<unknown[]> | unknown[];
|
|
34
|
+
export type PlaceAdapter = (cell: PlacementInput, deps?: { dryRun?: boolean; now?: Date }) => Promise<{ version?: string } | null> | { version?: string } | null;
|
|
35
|
+
export type RebalanceAdapter = (version: string) => Promise<{ decisions: unknown[]; blocked: Array<{ id: string; reason: string }> }> | { decisions: unknown[]; blocked: Array<{ id: string; reason: string }> };
|
|
31
36
|
export interface TickDeps {
|
|
32
37
|
root?: string; now?: Date; mode?: Mode; runId?: string; window?: Window;
|
|
33
38
|
cards?: () => TaskCard[]; split?: SplitAdapter; placeCell?: PlaceAdapter;
|
|
39
|
+
seatTurn?: (seat: string, root: string) => Promise<{ status: string; queueId?: string; runId?: string; item?: { source: string; id: string } }>;
|
|
40
|
+
queueOutcome?: (queueId: string, root: string) => 'pending' | 'unknown' | 'retryable' | 'succeeded';
|
|
34
41
|
loadAdapter?: (path: string, name: string) => Promise<unknown>;
|
|
35
42
|
checklist?: (version: string) => Pick<ChecklistItem, 'id' | 'status'>[];
|
|
36
43
|
observe?: (event: string, data: { loopId: string; runId: string; window: Window; node: Node; reason: string; count: number; targetLoopId?: string }) => void;
|
|
@@ -54,7 +61,10 @@ export function wishCards(cards: readonly TaskCard[]): TaskCard[] {
|
|
|
54
61
|
return cards.filter(card => card.status === 'open' && card.goalId.startsWith('wish:') &&
|
|
55
62
|
!card.sections.some(section => section.key === 'orch:split' || section.key.startsWith('orch:split:')));
|
|
56
63
|
}
|
|
57
|
-
export function reconciliation(cells: readonly Cell[], requests: readonly { key: string; status: string }[], checklist: (version: string) => Pick<ChecklistItem, 'id' | 'status'>[]
|
|
64
|
+
export function reconciliation(cells: readonly Cell[], requests: readonly { key: string; status: string }[], checklist: (version: string) => Pick<ChecklistItem, 'id' | 'status'>[],
|
|
65
|
+
queue: readonly { key: string; outcome: string }[] = []): TickState['reconciled'] {
|
|
66
|
+
// 체크리스트가 정본이고, 칸이 아직 체크리스트에 없을 때만 «그 칸의 의뢰 키»로 발사된 대기열 결과를 본다.
|
|
67
|
+
const queueByKey = new Map(queue.map(row => [row.key, row.outcome]));
|
|
58
68
|
const result = { reached: 0, progressing: 0, blocked: 0, unknown: 0 };
|
|
59
69
|
const byVersion = new Map<string, Pick<ChecklistItem, 'id' | 'status'>[]>();
|
|
60
70
|
const keys = new Set(requests.filter(row => row.status === 'queued').map(row => row.key));
|
|
@@ -65,14 +75,20 @@ export function reconciliation(cells: readonly Cell[], requests: readonly { key:
|
|
|
65
75
|
if (status === 'green' || status === 'done') result.reached++;
|
|
66
76
|
else if (status === 'yellow') result.progressing++;
|
|
67
77
|
else if (status === 'red') result.blocked++;
|
|
68
|
-
else
|
|
78
|
+
else {
|
|
79
|
+
const outcome = queueByKey.get(`orch:${cell.cardId}:${cell.id}`);
|
|
80
|
+
if (outcome === 'pending' || outcome === 'succeeded') result.progressing++;
|
|
81
|
+
else if (outcome === 'retryable') result.blocked++;
|
|
82
|
+
else result.unknown++;
|
|
83
|
+
}
|
|
69
84
|
}
|
|
70
85
|
return result;
|
|
71
86
|
}
|
|
72
87
|
export function reportLine(state: TickState): string {
|
|
73
88
|
const r = state.reconciled;
|
|
74
89
|
const line = `orchestrator ${state.window} cards=${state.cards.length} cells=${state.cells.length} placed=${state.missing.includes('relplan1-absent') ? 'missing' : state.placed} delegated=${state.mode === 'shadow' ? `would ${state.wouldDelegate}` : state.delegated} reconciled=${r.reached}/${r.progressing}/${r.blocked}/${r.unknown} mode=${state.mode}`;
|
|
75
|
-
|
|
90
|
+
const details = [...(state.rebalanceBlocked ?? []).map(row => `⛔ 이월 거부 ${row.id}: ${row.reason}`), ...(state.runSummaries ?? [])];
|
|
91
|
+
return details.length ? `${line}\n${details.join('\n')}` : line;
|
|
76
92
|
}
|
|
77
93
|
function readCardSnapshot(root: string): { cards: TaskCard[]; skippedCards: TickState['skippedCards'] } {
|
|
78
94
|
const dir = taskCardsDir(root);
|
|
@@ -115,7 +131,7 @@ function stateFile(root: string, runId: string): string {
|
|
|
115
131
|
if (!/^[a-zA-Z0-9._-]+$/.test(runId) || runId === '.' || runId === '..') throw new Error('Invalid orchestrator runId');
|
|
116
132
|
return join(root, 'loop', 'orchestrator', `${runId}.json`);
|
|
117
133
|
}
|
|
118
|
-
function restorePrevious(root: string, day: string, mode: Mode): Pick<TickState, 'cards' | 'cells'> | null {
|
|
134
|
+
function restorePrevious(root: string, day: string, mode: Mode): Pick<TickState, 'cards' | 'cells' | 'launched'> | null {
|
|
119
135
|
const dir = join(root, 'loop', 'orchestrator');
|
|
120
136
|
if (!existsSync(dir)) return null;
|
|
121
137
|
const states = readdirSync(dir).filter(name => /^[a-zA-Z0-9._-]+\.json$/.test(name))
|
|
@@ -125,15 +141,17 @@ function restorePrevious(root: string, day: string, mode: Mode): Pick<TickState,
|
|
|
125
141
|
states.sort((a, b) => a.name.localeCompare(b.name));
|
|
126
142
|
const cards = new Map<string, TaskCard>();
|
|
127
143
|
const cells = new Map<string, Cell>();
|
|
144
|
+
const launched = new Map<string, NonNullable<TickState['launched']>[number]>();
|
|
128
145
|
for (const { state } of states) {
|
|
129
146
|
for (const card of state.cards) cards.set(card.id, card);
|
|
130
147
|
for (const cell of state.cells) cells.set(`${cell.cardId}:${cell.id}`, cell);
|
|
148
|
+
for (const turn of state.launched ?? []) if (turn.queueId) launched.set(turn.queueId, turn);
|
|
131
149
|
}
|
|
132
|
-
return { cards: [...cards.values()], cells: [...cells.values()] };
|
|
150
|
+
return { cards: [...cards.values()], cells: [...cells.values()], launched: [...launched.values()] };
|
|
133
151
|
}
|
|
134
152
|
function initial(runId: string, window: Window, mode: Mode, day: string): TickState {
|
|
135
153
|
return { runId, window, mode, day, cards: [], skippedCards: [], cells: [], placed: 0, delegated: 0, wouldDelegate: 0,
|
|
136
|
-
reconciled: { reached: 0, progressing: 0, blocked: 0, unknown: 0 }, nodes: {}, missing: [] };
|
|
154
|
+
reconciled: { reached: 0, progressing: 0, blocked: 0, unknown: 0 }, steps: [], nodes: {}, missing: [] };
|
|
137
155
|
}
|
|
138
156
|
function graphRunId(): string | undefined {
|
|
139
157
|
const context = process.env.ELANOUS_GRAPH_CONTEXT;
|
|
@@ -162,8 +180,9 @@ export async function runOrchestratorNode(node: Node, deps: TickDeps = {}): Prom
|
|
|
162
180
|
const configured = deps.mode ?? getUserConfig().loops?.orchestrator?.mode;
|
|
163
181
|
const mode: Mode = configured === 'off' || configured === 'live' ? configured : 'shadow';
|
|
164
182
|
const state: TickState = existsSync(path) ? JSON.parse(readFileSync(path, 'utf8')) as TickState : initial(runId, window, mode, kstDay(now));
|
|
183
|
+
state.steps ??= [];
|
|
165
184
|
if (state.window !== window || state.mode !== mode) throw new Error('orchestrator run window/mode changed');
|
|
166
|
-
const predecessor: Partial<Record<Node, Node>> = { split: 'intake', place: 'split', delegate: 'place',
|
|
185
|
+
const predecessor: Partial<Record<Node, Node>> = { split: 'intake', place: 'split', delegate: 'place', launch: 'delegate', reconcile: 'launch', report: 'reconcile' };
|
|
167
186
|
const preceding = predecessor[node];
|
|
168
187
|
if (preceding && !state.nodes[preceding]) throw new Error(`orchestrator ${node}: missing ${preceding} result`);
|
|
169
188
|
const loadAdapter = deps.loadAdapter ?? optionalAdapter;
|
|
@@ -173,6 +192,7 @@ export async function runOrchestratorNode(node: Node, deps: TickDeps = {}): Prom
|
|
|
173
192
|
};
|
|
174
193
|
const observe = (event: string, reason = '', count = 0, targetLoopId?: string): void => {
|
|
175
194
|
const data = { loopId: 'orchestrator', runId, window, node, reason, count, ...(targetLoopId ? { targetLoopId } : {}) };
|
|
195
|
+
if (event !== 'tick') state.steps.push({ node, reason, count, ...(targetLoopId ? { targetLoopId } : {}) });
|
|
176
196
|
try { (deps.observe ?? ((name, payload) => debug.log('loop.orchestrator', name, payload)))(event, data); }
|
|
177
197
|
catch { /* observation must not change the run */ }
|
|
178
198
|
};
|
|
@@ -197,6 +217,7 @@ export async function runOrchestratorNode(node: Node, deps: TickDeps = {}): Prom
|
|
|
197
217
|
if (!cell.id?.trim() || !cell.title?.trim()) throw new Error('invalid split cell');
|
|
198
218
|
state.cells.push({ cardId: card.id, id: cell.id, title: cell.title, origin: split ? 'flow1a' : 'candidate', ...(cell.seat ? { seat: cell.seat } : {}), ...(cell.host !== undefined ? { host: cell.host } : {}) });
|
|
199
219
|
}
|
|
220
|
+
observe('exchange', `split:${card.id}`, cells.length);
|
|
200
221
|
if (split && cells.length && mode === 'live') {
|
|
201
222
|
const store = new CardStore(root);
|
|
202
223
|
try {
|
|
@@ -208,15 +229,24 @@ export async function runOrchestratorNode(node: Node, deps: TickDeps = {}): Prom
|
|
|
208
229
|
const place = deps.placeCell ?? await adapter<PlaceAdapter>('../../release-loop/placement.js', 'placeCell');
|
|
209
230
|
const available = typeof place === 'function';
|
|
210
231
|
if (!available) { state.missing.push('relplan1-absent'); observe('node-missing', 'relplan1-absent', state.cells.length); }
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
if (
|
|
218
|
-
|
|
219
|
-
|
|
232
|
+
if (mode === 'shadow' && deps.placeCell) observe('exchange', 'relplan1-shadow-not-invoked', state.cells.length);
|
|
233
|
+
// 담당 없는 칸은 배치를 부르든 안 부르든 센다 — 배치 호출 여부와 집계를 묶지 않는다.
|
|
234
|
+
for (const cell of state.cells) {
|
|
235
|
+
if (cell.origin === 'flow1a' && (!cell.seat || !SEAT_LOOP_IDS[cell.seat])) { state.unplaced = (state.unplaced ?? 0) + 1; observe('exchange', 'unplaced-no-owner', 1); }
|
|
236
|
+
}
|
|
237
|
+
if (available && (mode === 'live' || !deps.placeCell)) for (const cell of state.cells) {
|
|
238
|
+
if (cell.origin !== 'flow1a' || !cell.seat || !SEAT_LOOP_IDS[cell.seat]) continue;
|
|
239
|
+
// RELPLAN's dryRun calculates the same release without writing a checklist in shadow.
|
|
240
|
+
try {
|
|
241
|
+
const placed = await place({ id: cell.id, title: cell.title, owner: cell.seat, priority: cell.priority ?? 'P2', predecessors: cell.predecessors ?? [] }, { dryRun: mode === 'shadow', now });
|
|
242
|
+
if (validVersion(placed?.version)) {
|
|
243
|
+
cell.version = placed.version;
|
|
244
|
+
state.placed++;
|
|
245
|
+
observe('exchange', mode === 'shadow' ? `would-place:${cell.id}:${cell.version}` : `placed:${cell.id}:${cell.version}`, 1);
|
|
246
|
+
} else { state.unplaced = (state.unplaced ?? 0) + 1; observe('exchange', 'unplaced-no-release', 1); }
|
|
247
|
+
} catch (error) {
|
|
248
|
+
state.unplaced = (state.unplaced ?? 0) + 1;
|
|
249
|
+
observe('exchange', `unplaced:${cell.id}:${String(error).slice(0, 160)}`, 1);
|
|
220
250
|
}
|
|
221
251
|
}
|
|
222
252
|
} else if (node === 'delegate') {
|
|
@@ -228,9 +258,11 @@ export async function runOrchestratorNode(node: Node, deps: TickDeps = {}): Prom
|
|
|
228
258
|
if (!targetLoopId) continue;
|
|
229
259
|
const key = `orch:${cell.cardId}:${cell.id}`;
|
|
230
260
|
if (keys.has(key)) continue;
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
261
|
+
if (!validVersion(cell.version)) {
|
|
262
|
+
if (mode === 'shadow') { keys.add(key); state.wouldDelegate++; observe('would-delegate', `${key} (unplaced)`, 1, targetLoopId); }
|
|
263
|
+
continue;
|
|
264
|
+
}
|
|
265
|
+
if (mode === 'shadow') { keys.add(key); state.wouldDelegate++; observe('would-delegate', key, 1, targetLoopId); continue; }
|
|
234
266
|
keys.add(key);
|
|
235
267
|
mkdirSync(dirname(path), { recursive: true });
|
|
236
268
|
// TC review must-fix ①: the seat-requests journal contract needs receiptId, and appends are serialized under the
|
|
@@ -248,13 +280,53 @@ export async function runOrchestratorNode(node: Node, deps: TickDeps = {}): Prom
|
|
|
248
280
|
state.delegated++;
|
|
249
281
|
observe('exchange', 'queued', 1, targetLoopId);
|
|
250
282
|
}
|
|
283
|
+
} else if (node === 'launch') {
|
|
284
|
+
const eligible = state.cells.filter(cell => cell.origin === 'flow1a' && cell.seat && SEAT_LOOP_IDS[cell.seat]
|
|
285
|
+
&& validVersion(cell.version) && (mode === 'shadow' || readRequests(root).some(row => row.key === `orch:${cell.cardId}:${cell.id}` && row.status === 'queued')));
|
|
286
|
+
const seats = [...new Set(eligible.map(cell => cell.seat!))];
|
|
287
|
+
state.launched = [];
|
|
288
|
+
for (const seat of seats) {
|
|
289
|
+
if (mode === 'shadow') {
|
|
290
|
+
const cell = eligible.find(row => row.seat === seat)!;
|
|
291
|
+
const item: SeatItem = { source: 'request', id: `orch:${cell.cardId}:${cell.id}`, title: cell.title, text: cell.title };
|
|
292
|
+
const action = planAction(item, seat);
|
|
293
|
+
state.launched.push({ seat, key: item.id, status: action.kind === 'harness' ? 'would-launch' : `would-${action.kind}` });
|
|
294
|
+
observe('exchange', `would-${action.kind}:${seat}`, 1, SEAT_LOOP_IDS[seat]);
|
|
295
|
+
} else {
|
|
296
|
+
const turn = await (deps.seatTurn ?? ((assigned: string, stateRoot: string) => runSeatLoopOnce(assigned, {
|
|
297
|
+
root: stateRoot, config: { ...getUserConfig().loops?.seat, mode: 'live-safe', seats: [assigned] },
|
|
298
|
+
})))(seat, root);
|
|
299
|
+
// 자리 턴이 «이 틱이 넣은 의뢰»를 처리했을 때만 칸에 잇는다 — 대기열의 다른 의뢰는 이 카드의 발사가 아니다.
|
|
300
|
+
const targets = new Set(eligible.filter(row => row.seat === seat).map(row => `orch:${row.cardId}:${row.id}`));
|
|
301
|
+
const handled = 'item' in turn && turn.item?.source === 'request' ? turn.item.id : undefined;
|
|
302
|
+
const key = handled && targets.has(handled) ? handled : undefined;
|
|
303
|
+
if (handled && !key) observe('exchange', `seat-turn-other-request:${seat}`, 1, SEAT_LOOP_IDS[seat]);
|
|
304
|
+
state.launched.push({ seat, status: turn.status, ...(key ? { key } : {}),
|
|
305
|
+
...('queueId' in turn && turn.queueId ? { queueId: turn.queueId } : {}),
|
|
306
|
+
...('runId' in turn && turn.runId ? { runId: turn.runId } : {}) });
|
|
307
|
+
observe('exchange', `seat-turn:${seat}:${turn.status}`, 1, SEAT_LOOP_IDS[seat]);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
251
310
|
} else if (node === 'reconcile') {
|
|
252
|
-
if (window !== '08'
|
|
311
|
+
if (window !== '08') {
|
|
312
|
+
// 새 카드가 있어도 앞 틱의 의뢰·발사를 같이 대조한다 — 지금 틱 것이 같은 키를 이긴다.
|
|
253
313
|
const previous = restorePrevious(root, state.day, mode);
|
|
254
|
-
if (previous) {
|
|
314
|
+
if (previous) {
|
|
315
|
+
const merge = <T>(older: readonly T[], newer: readonly T[], key: (row: T) => string) =>
|
|
316
|
+
[...new Map([...older, ...newer].map(row => [key(row), row])).values()];
|
|
317
|
+
state.cards = merge(previous.cards, state.cards, card => card.id);
|
|
318
|
+
state.cells = merge(previous.cells, state.cells, cell => `${cell.cardId}:${cell.id}`);
|
|
319
|
+
state.launched = merge(previous.launched ?? [], state.launched ?? [], turn => turn.queueId ?? `${turn.seat}:${turn.key ?? ''}`);
|
|
320
|
+
}
|
|
255
321
|
}
|
|
322
|
+
state.queueOutcomes = (state.launched ?? []).filter(turn => turn.queueId).map(turn => {
|
|
323
|
+
const outcome = (deps.queueOutcome ?? ((id: string, stateRoot: string) => harnessQueueOutcome(id, { root: stateRoot })))(turn.queueId!, root);
|
|
324
|
+
observe('exchange', `queue-outcome:${turn.queueId}:${outcome}`, 1, SEAT_LOOP_IDS[turn.seat]);
|
|
325
|
+
return { queueId: turn.queueId!, ...(turn.key ? { key: turn.key } : {}), outcome };
|
|
326
|
+
});
|
|
256
327
|
const requests = readRequests(root);
|
|
257
|
-
state.reconciled = reconciliation(state.cells, requests, mode === 'shadow' ? version => readChecklist(root, version) : (deps.checklist ?? (version => readChecklist(root, version)))
|
|
328
|
+
state.reconciled = reconciliation(state.cells, requests, mode === 'shadow' ? version => readChecklist(root, version) : (deps.checklist ?? (version => readChecklist(root, version))),
|
|
329
|
+
state.queueOutcomes.flatMap(row => row.key ? [{ key: row.key, outcome: row.outcome }] : []));
|
|
258
330
|
const from = new Date(`${state.day}T00:00:00+09:00`).getTime();
|
|
259
331
|
state.runSummaries = listSelfDevRuns(selfDevRunsDir(root))
|
|
260
332
|
.filter(run => run.updatedAt >= from && run.updatedAt < from + 86_400_000)
|
|
@@ -268,7 +340,13 @@ export async function runOrchestratorNode(node: Node, deps: TickDeps = {}): Prom
|
|
|
268
340
|
if (mode === 'shadow') observe('exchange', 'rebalance-shadow-not-invoked', versions.length);
|
|
269
341
|
else {
|
|
270
342
|
state.rebalanced = 0;
|
|
271
|
-
|
|
343
|
+
state.rebalanceBlocked = [];
|
|
344
|
+
for (const version of versions) {
|
|
345
|
+
const result = await rebalance(version);
|
|
346
|
+
state.rebalanced += result.decisions.length;
|
|
347
|
+
state.rebalanceBlocked.push(...result.blocked);
|
|
348
|
+
for (const blocked of result.blocked) observe('exchange', `rebalance-blocked: ${blocked.id} ${blocked.reason}`, 1);
|
|
349
|
+
}
|
|
272
350
|
}
|
|
273
351
|
}
|
|
274
352
|
}
|
|
@@ -288,9 +366,9 @@ if (import.meta.main) {
|
|
|
288
366
|
const node = process.argv[2];
|
|
289
367
|
const index = process.argv.indexOf('--window');
|
|
290
368
|
const window = index < 0 ? undefined : process.argv[index + 1];
|
|
291
|
-
if (!['intake', 'split', 'place', 'delegate', 'reconcile', 'report'].includes(node ?? '') ||
|
|
369
|
+
if (!['intake', 'split', 'place', 'delegate', 'launch', 'reconcile', 'report'].includes(node ?? '') ||
|
|
292
370
|
(window !== undefined && !['08', '12', '18'].includes(window))) {
|
|
293
|
-
console.error('orchestrator: expected <intake|split|place|delegate|reconcile|report> [--window 08|12|18]');
|
|
371
|
+
console.error('orchestrator: expected <intake|split|place|delegate|launch|reconcile|report> [--window 08|12|18]');
|
|
294
372
|
process.exitCode = 2;
|
|
295
373
|
} else {
|
|
296
374
|
void (async () => {
|
|
@@ -2,16 +2,19 @@ import { appendFileSync, existsSync, mkdirSync, readFileSync, realpathSync } fro
|
|
|
2
2
|
import { dirname, isAbsolute, join, resolve, sep } from 'node:path';
|
|
3
3
|
import { debug } from '../../debug/log.js';
|
|
4
4
|
import { defaultListHarnessProcesses, type HarnessProcessRecord } from '../../harness/harness-cli-command.js';
|
|
5
|
-
import { effectiveInstanceRoot } from '../../instance/resolve.js';
|
|
5
|
+
import { effectiveInstanceRoot, releaseLedgerRoot } from '../../instance/resolve.js';
|
|
6
6
|
import { devVersion, listChecklist, type ChecklistItem } from '../../release-loop/checklist.js';
|
|
7
|
+
import { listSchedules } from '../../release-loop/release-schedule.js';
|
|
7
8
|
import { withFileLockSync } from '../../storage/file-lock.js';
|
|
8
9
|
import { getUserConfig, ORCHESTRATOR_DEFAULTS, type OrchestratorLoopConfig, type OrchestratorSeat } from '../../user-config.js';
|
|
9
10
|
import { finishAdvice, measureFinish, type FinishAdvice, type FinishMetrics, type MeasureFinishDeps } from './finish-rate.js';
|
|
11
|
+
import { collectAuthorDepth, type AuthorDepthResult, type CollectAuthorDepthDeps } from './author-depth.js';
|
|
12
|
+
import { calculateSeatBaseShares } from './seat-share.js';
|
|
10
13
|
|
|
11
14
|
export const TRAFFIC_SEATS = ['OP', 'TC', 'MK', 'UX'] as const;
|
|
12
15
|
export type TrafficCell = Pick<ChecklistItem, 'id' | 'title' | 'owner' | 'status'>;
|
|
13
16
|
export type TrafficProcess = Pick<HarnessProcessRecord, 'command' | 'elapsedSeconds' | 'cwd'> & { seat: OrchestratorSeat | null };
|
|
14
|
-
export type TrafficSeatRow = { seat: OrchestratorSeat; running: number; cap: number; idleFor: number | null; idle: boolean; nextCell: TrafficCell | null };
|
|
17
|
+
export type TrafficSeatRow = { seat: OrchestratorSeat; running: number; cap: number; baseShare: number | null; borrowed: number; lent: number; launchCap: number; idleFor: number | null; idle: boolean; nextCell: TrafficCell | null };
|
|
15
18
|
export type TrafficResult = { seats: TrafficSeatRow[]; unassigned: number; now: Date; idleMinutes: number; finish?: { metrics: FinishMetrics; advice: FinishAdvice } };
|
|
16
19
|
|
|
17
20
|
/** Same bounded walk as the CTX1 hook: a marker at the git root is checked before stopping. */
|
|
@@ -49,15 +52,23 @@ function realOrResolved(path: string): string {
|
|
|
49
52
|
const IS_LAUNCH = /(?:^|\s)(?:\S*\/)?elanous\.mjs\s+(?:(?:--test|--config-dir\s+\S+)\s+)?harness\s+(?:ask|say)(?=\s|$)/;
|
|
50
53
|
|
|
51
54
|
/** Process-to-seat resolution occurs at the boundary; this decision does no IO. */
|
|
52
|
-
export function trafficTick({ processes, now, caps, lastLaunchAt, openCells, idleMinutes = 30 }: {
|
|
55
|
+
export function trafficTick({ processes, now, caps, lastLaunchAt, idleSince, openCells, nextRound, totalSlots, idleMinutes = 30 }: {
|
|
53
56
|
processes: readonly TrafficProcess[];
|
|
54
57
|
now: Date;
|
|
55
58
|
caps: Readonly<Record<OrchestratorSeat, number>>;
|
|
56
59
|
lastLaunchAt?: Partial<Record<OrchestratorSeat, Date | null>>;
|
|
60
|
+
idleSince?: Partial<Record<OrchestratorSeat, Date | null>>;
|
|
57
61
|
openCells: readonly TrafficCell[];
|
|
62
|
+
nextRound?: readonly TrafficCell[];
|
|
63
|
+
totalSlots?: number;
|
|
58
64
|
idleMinutes?: number;
|
|
59
65
|
}): TrafficResult {
|
|
60
66
|
const launches = processes.filter(process => IS_LAUNCH.test(process.command));
|
|
67
|
+
const slotBudget = totalSlots ?? TRAFFIC_SEATS.reduce((sum, seat) => sum + caps[seat], 0);
|
|
68
|
+
// A missing next round is unknown, not an empty round with zero remaining work.
|
|
69
|
+
const baseShares = nextRound === undefined ? null : calculateSeatBaseShares({
|
|
70
|
+
totalSlots: slotBudget, currentRound: openCells, nextRound, seatCaps: caps,
|
|
71
|
+
});
|
|
61
72
|
const seats = TRAFFIC_SEATS.map((seat): TrafficSeatRow => {
|
|
62
73
|
const owned = launches.filter(process => process.seat === seat);
|
|
63
74
|
const latest = owned.reduce<number | null>((current, process) => {
|
|
@@ -66,12 +77,57 @@ export function trafficTick({ processes, now, caps, lastLaunchAt, openCells, idl
|
|
|
66
77
|
return current === null ? start : Math.max(current, start);
|
|
67
78
|
}, null);
|
|
68
79
|
const fallback = owned.length ? lastLaunchAt?.[seat]?.getTime() : undefined;
|
|
69
|
-
const
|
|
80
|
+
const idleStart = idleSince?.[seat]?.getTime();
|
|
81
|
+
const last = idleStart !== undefined && Number.isFinite(idleStart) ? idleStart
|
|
82
|
+
: latest ?? (fallback !== undefined && Number.isFinite(fallback) ? fallback : null);
|
|
70
83
|
const idleFor = last === null ? null : Math.max(0, Math.floor((now.getTime() - last) / 60_000));
|
|
71
|
-
const
|
|
72
|
-
|
|
73
|
-
|
|
84
|
+
const nextCell = openCells.find(cell => cell.status !== 'done' && cell.owner?.split('/')[0] === seat) ?? null;
|
|
85
|
+
const baseShare = baseShares?.[seat] ?? null;
|
|
86
|
+
return { seat, running: owned.length, cap: caps[seat], baseShare, borrowed: 0, lent: 0,
|
|
87
|
+
launchCap: baseShare === null ? caps[seat] : Math.max(owned.length, baseShare), idleFor,
|
|
88
|
+
idle: owned.length < caps[seat] && last !== null && now.getTime() - last > idleMinutes * 60_000, nextCell };
|
|
74
89
|
});
|
|
90
|
+
if (baseShares !== null) {
|
|
91
|
+
// Account for outstanding loans before lending new capacity. A returning
|
|
92
|
+
// lender cannot launch into a slot still occupied by the borrower's run.
|
|
93
|
+
for (const borrower of seats) {
|
|
94
|
+
let excess = Math.max(0, borrower.running - borrower.baseShare!);
|
|
95
|
+
borrower.borrowed += excess;
|
|
96
|
+
for (const lender of seats) {
|
|
97
|
+
if (!excess) break;
|
|
98
|
+
if (lender === borrower) continue;
|
|
99
|
+
const amount = Math.min(excess, Math.max(0, lender.baseShare! - lender.running - lender.lent));
|
|
100
|
+
lender.lent += amount;
|
|
101
|
+
excess -= amount;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
for (const lender of seats) {
|
|
105
|
+
if (lender.nextCell) continue;
|
|
106
|
+
let available = Math.max(0, lender.baseShare! - lender.running - lender.lent);
|
|
107
|
+
for (const borrower of seats) {
|
|
108
|
+
if (!available) break;
|
|
109
|
+
if (borrower === lender || !borrower.nextCell) continue;
|
|
110
|
+
const needed = Math.max(0, Math.min(borrower.cap, borrower.running + 1) - borrower.launchCap);
|
|
111
|
+
const amount = Math.min(available, needed);
|
|
112
|
+
lender.lent += amount;
|
|
113
|
+
borrower.borrowed += amount;
|
|
114
|
+
borrower.launchCap += amount;
|
|
115
|
+
available -= amount;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
for (const lender of seats) lender.launchCap = Math.max(lender.running, lender.launchCap - lender.lent);
|
|
119
|
+
}
|
|
120
|
+
// Reserve one available global slot per possible request, including unknown-seat
|
|
121
|
+
// launches in the occupied count; returning a lent share does not end its old run.
|
|
122
|
+
let freeSlots = Math.max(0, slotBudget - launches.length);
|
|
123
|
+
for (const row of seats) {
|
|
124
|
+
if (row.nextCell && row.idle && row.running < row.launchCap) {
|
|
125
|
+
if (freeSlots > 0) freeSlots--;
|
|
126
|
+
else row.launchCap = row.running;
|
|
127
|
+
}
|
|
128
|
+
row.idle = row.idle && (row.nextCell === null || row.running < row.launchCap);
|
|
129
|
+
if (!row.idle) row.nextCell = null;
|
|
130
|
+
}
|
|
75
131
|
return { seats, unassigned: launches.filter(process => process.seat === null).length, now, idleMinutes };
|
|
76
132
|
}
|
|
77
133
|
|
|
@@ -92,7 +148,8 @@ export interface TrafficApplyDeps {
|
|
|
92
148
|
export function applyTraffic(result: TrafficResult, deps: TrafficApplyDeps = {}): number {
|
|
93
149
|
const log = deps.log ?? ((category: string, event: string, data: Record<string, unknown>) => debug.log(category, event, data));
|
|
94
150
|
const mode = deps.mode ?? 'shadow';
|
|
95
|
-
log('loop.orchestrator', 'tick', { mode, seats: result.seats.map(({ seat, running, cap,
|
|
151
|
+
log('loop.orchestrator', 'tick', { mode, seats: result.seats.map(({ seat, running, cap, baseShare, borrowed, lent, launchCap, idleFor, idle }) =>
|
|
152
|
+
({ seat, running, cap, baseShare, borrowed, lent, launchCap, idleFor, idle })), unassigned: result.unassigned });
|
|
96
153
|
if (result.finish) {
|
|
97
154
|
const { finishSlots, launchSlots, state, reasons } = result.finish.advice;
|
|
98
155
|
log('loop.orchestrator', 'would-rebalance', { finishSlots, launchSlots, state, reasons });
|
|
@@ -140,6 +197,49 @@ export function trafficLine(result: TrafficResult, mode: 'shadow' | 'live'): str
|
|
|
140
197
|
return `${prefix} · finish rate=${rate(metrics.landingRate)} stale=${metrics.staleDrafts ?? '?'} conflict=${rate(metrics.conflictRatio)} → finish=${advice.finishSlots}/${advice.finishSlots + advice.launchSlots}`;
|
|
141
198
|
}
|
|
142
199
|
|
|
200
|
+
export type AuthorShadow = { cells: number; held: number; queuedForAuthor: number; status: 'complete' | 'incomplete'; receiptFailures: number };
|
|
201
|
+
|
|
202
|
+
/** Traffic stays authoritative: shadow collection and its ledger cannot change its decision or exit status. */
|
|
203
|
+
export function runTrafficTick({ processes, now, caps, openCells, nextRound, totalSlots, mode, finishDeps, applyDeps, authorDeps, collectAuthor = collectAuthorDepth, log = debug.log.bind(debug) }: {
|
|
204
|
+
processes: readonly TrafficProcess[];
|
|
205
|
+
now: Date;
|
|
206
|
+
caps: Readonly<Record<OrchestratorSeat, number>>;
|
|
207
|
+
openCells: readonly TrafficCell[];
|
|
208
|
+
nextRound?: readonly TrafficCell[];
|
|
209
|
+
totalSlots?: number;
|
|
210
|
+
mode: 'shadow' | 'live';
|
|
211
|
+
finishDeps?: MeasureFinishDeps;
|
|
212
|
+
applyDeps?: TrafficApplyDeps;
|
|
213
|
+
authorDeps?: Omit<CollectAuthorDepthDeps, 'caps' | 'running' | 'now'>;
|
|
214
|
+
collectAuthor?: (deps: CollectAuthorDepthDeps) => AuthorDepthResult;
|
|
215
|
+
log?: (category: string, event: string, data: Record<string, unknown>) => void;
|
|
216
|
+
}): { result: TrafficResult; queued: number; authorShadow: AuthorShadow | null } {
|
|
217
|
+
const metrics = measureFinish(finishDeps, now);
|
|
218
|
+
const advice = finishAdvice(metrics, TRAFFIC_SEATS.reduce((sum, seat) => sum + caps[seat], 0));
|
|
219
|
+
const result = { ...trafficTick({ processes, now, caps, openCells, nextRound, totalSlots: totalSlots ?? advice.launchSlots }),
|
|
220
|
+
finish: { metrics, advice } };
|
|
221
|
+
const queued = applyTraffic(result, { ...applyDeps, mode });
|
|
222
|
+
let authorShadow: AuthorShadow | null = null;
|
|
223
|
+
let shadowFailure: Record<string, unknown> | null = null;
|
|
224
|
+
try {
|
|
225
|
+
const depth = collectAuthor({ ...authorDeps, now, caps: () => caps,
|
|
226
|
+
running: () => Object.fromEntries(result.seats.map(row => [row.seat, row.running])) });
|
|
227
|
+
const receiptFailures = depth.receiptFailures ?? depth.unreadable.filter(item => item.source === 'author-ledger').length;
|
|
228
|
+
const incomplete = depth.unreadable.length > 0 || !depth.receiptCounts;
|
|
229
|
+
authorShadow = { cells: depth.seats.reduce((total, row) => total + row.wouldAuthor.length, 0),
|
|
230
|
+
held: depth.receiptCounts?.held ?? 0, queuedForAuthor: depth.receiptCounts?.queuedForAuthor ?? 0,
|
|
231
|
+
status: incomplete ? 'incomplete' : 'complete', receiptFailures };
|
|
232
|
+
if (incomplete) shadowFailure = { unreadable: depth.unreadable, ...(depth.receiptCounts ? {} : { reason: 'receipt counts unavailable' }) };
|
|
233
|
+
} catch (error) {
|
|
234
|
+
shadowFailure = { reason: error instanceof Error ? error.message : String(error) };
|
|
235
|
+
}
|
|
236
|
+
if (shadowFailure) {
|
|
237
|
+
try { log('loops.author-depth', 'shadow-failed', shadowFailure); }
|
|
238
|
+
catch { /* Shadow logging cannot change traffic's outcome. */ }
|
|
239
|
+
}
|
|
240
|
+
return { result, queued, authorShadow };
|
|
241
|
+
}
|
|
242
|
+
|
|
143
243
|
if (import.meta.main) {
|
|
144
244
|
try {
|
|
145
245
|
const args = process.argv.slice(2);
|
|
@@ -154,9 +254,15 @@ if (import.meta.main) {
|
|
|
154
254
|
if (observation.status !== 'ok') throw new Error(`process observation ${observation.status}`);
|
|
155
255
|
const now = new Date();
|
|
156
256
|
const processes = observation.records.map(process => ({ ...process, seat: seatOfTree(process.cwdStatus === 'unknown' ? undefined : process.cwd, cfg) }));
|
|
157
|
-
const
|
|
158
|
-
|
|
159
|
-
|
|
257
|
+
const upcoming = listSchedules(releaseLedgerRoot()).filter(row => Date.parse(row.cutAt) > now.getTime())
|
|
258
|
+
.sort((a, b) => Date.parse(a.cutAt) - Date.parse(b.cutAt));
|
|
259
|
+
const currentIndex = upcoming.findIndex(row => row.version === version);
|
|
260
|
+
const nextVersion = currentIndex < 0 ? upcoming[0]?.version : upcoming[currentIndex + 1]?.version;
|
|
261
|
+
const { result, queued, authorShadow } = runTrafficTick({
|
|
262
|
+
processes, now, caps: cfg.seatCaps, openCells: listChecklist(version).items,
|
|
263
|
+
...(nextVersion ? { nextRound: listChecklist(nextVersion).items } : {}), mode: cfg.trafficMode,
|
|
264
|
+
});
|
|
265
|
+
if (args.includes('--json')) console.log(JSON.stringify({ ...result, mode: cfg.trafficMode, queued, version, authorShadow }));
|
|
160
266
|
else console.log(trafficLine(result, cfg.trafficMode));
|
|
161
267
|
} catch (error) {
|
|
162
268
|
console.error(`traffic: ${error instanceof Error ? error.message : String(error)}`);
|