elanous 0.2.13 → 0.2.14
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/{7334.a6679d9168f37aa8.js → 1268.0840ff91e083fcbd.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/1307-6e7e0377c1db0bdc.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1826-5d279da64bbbc2de.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{9656-c1540088f5859c80.js → 1844-9ec4b1d12f6e80eb.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/1866-e7a6999f25548b30.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{390.c373e31b3b9d0c41.js → 1948.15f356b15a625597.js} +2 -2
- package/apps/pwa/out/_next/static/chunks/211-9c9090c59e2c5907.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2196-4700653d12a44b40.js +1 -0
- package/apps/pwa/out/_next/static/chunks/224-798346143007ff65.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2699-63b73fa4d3bcf6ff.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2957-96ffab3d521fb08f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/311.e685738f92e6e2ec.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3194-807b212fb8eae065.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3324-d1d2979821dfa845.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3558-bbb5ab553a0471d3.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3916-3d02dd1fc257dd3c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4144-a0041c8dc3be57ac.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4554-3565eea1f1495ba7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4562-071d8bac44a01069.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{4685-c3c1c625b71accd1.js → 4614-21403aac730d4586.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{8039-df92e08fede5de8f.js → 4795-f372490b178583fc.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/5135-44008fe8b1f2aa41.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5233-bf28bd6bd7a32099.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5360-be1f1d8f9fcc9755.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5977-2df489622a52a33a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5994-ae92c0de046ab9ba.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{1452.93700483b9d4fc61.js → 6110.1297d4093fdfbb0d.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/6158.9c0449a9a8d0064b.js +2 -0
- package/apps/pwa/out/_next/static/chunks/6877-7c6208705c284124.js +1 -0
- package/apps/pwa/out/_next/static/chunks/726.83532d69452ee24b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7290-f09c928a06c52e2d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{1829-b453a1b22d3999bb.js → 7327-2d69fb321ba73389.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/7395-164e238f471b5d7b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7787-02a1c0b04e25e811.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8060-cc008cc6fedfa818.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8085-d065d0393c1760f5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8682-7061dd4fa7bd62ed.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8827-e2f5b09a8d37e83b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8868-10a3a4440fb0ccd8.js +25 -0
- package/apps/pwa/out/_next/static/chunks/{8421-1c4acfb1395a0edf.js → 8949-ecb4128a0858b911.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/9070.067c106528ec1ae6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9103-7a3ae7f60af3b99f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{3869-3cd46526879f454c.js → 9247-29d889b809288e65.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/935.4ddaa99fc1d1126c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{79f8be9e-957d68913b590b08.js → a9f2b54d-7865aba0f4d2ee49.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/_not-found/page-c1a96669158471ec.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-74383f890f075511.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/autopilot/page-7b2b4f378354869a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/board/page-c496bb16da85385f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/botlab/{page-f14987d5850a31e0.js → page-1abfe3aec3be6bdc.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/bots/page-1512d91c7491ed62.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/chat/page-a627db94ec6904a0.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/consult/page-fb260b9283dbf646.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/control/page-e6b84ab07d3567d5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-61ec1d2e92bdd50a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/editor/page-fed69f9759d248d0.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/exec/page-15374909d8f1aebd.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/field/page-012a742f81218b01.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/healthz/route-c1a96669158471ec.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/inside/page-fd7e200d4d83fbd7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/intake/page-99effd9d893a43dc.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/layout-938de57ae0315e46.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/live/page-d47266766766d227.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/loops/page-160089123a40dd91.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/market/page-629e1ede482ec6eb.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/{page-66c27608f651b866.js → page-2ce5471926761fdd.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/morning/page-27e99b4e54a849d4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/not-found-e995d30f6800b6bf.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-39f89c53038a51d4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-32cde713be47cddb.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-e7e10cd3386222e5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-5a753bb4ec0cc511.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/outputs/page-04250f0e2c0d03df.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/page-341480ed8e3c1ea8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-e64caff084f7ce21.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-db07eaffba0df7e3.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-8ac8d71c2adf9c8a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/{page-78b40a344ed22f24.js → page-e1e47d8d940aba3d.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/page-86f91034bd5cabe5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-94dc383d3d54db1a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/layout-c1a96669158471ec.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/page-b3759cc14da80908.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/share/page-ce4d8a1215ce53a3.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-58043db78ba519ae.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-357750dfb91bf960.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/term/{page-63d14a4d8fe1e1c1.js → page-274703bc41b71333.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/today/page-194dce4a83143fce.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/trace/page-35bdeae2e2c17147.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/vault/page-3b7e821dc492bdfd.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-16bef3da05a900c3.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-f8da5dc6f3752276.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-2f810b18d89ecba6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-134c1bc762aee62c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{5cd7e3a9-404fd6b8d0ecd44c.js → f6231284-45726550aba08fca.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{f48170cc-d05d28bf87a43bf4.js → f6b7c9ba.64a895b8b2fedbdc.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/framework-5c169757551134ed.js +1 -0
- package/apps/pwa/out/_next/static/chunks/main-59fe95fed40c7a01.js +1 -0
- package/apps/pwa/out/_next/static/chunks/main-app-4ca74dbbee7057c6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/pages/_app-13a0a886df4d97a1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/pages/_error-6af8591cd3a3f038.js +1 -0
- package/apps/pwa/out/_next/static/chunks/webpack-539cde99554be20f.js +1 -0
- package/apps/pwa/out/_next/static/css/2e33713c444d5b39.css +1 -0
- package/apps/pwa/out/_next/static/x6mjI6vqOkVVu0jFuDkz1/_buildManifest.js +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/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/design-check/index.html +1 -1
- package/apps/pwa/out/design-check/index.txt +23 -23
- package/apps/pwa/out/editor/index.html +1 -1
- package/apps/pwa/out/editor/index.txt +23 -23
- package/apps/pwa/out/exec/index.html +1 -1
- package/apps/pwa/out/exec/index.txt +23 -23
- package/apps/pwa/out/field/index.html +1 -1
- package/apps/pwa/out/field/index.txt +23 -23
- package/apps/pwa/out/healthz +1 -1
- package/apps/pwa/out/index.html +1 -1
- package/apps/pwa/out/index.txt +22 -22
- package/apps/pwa/out/inside/index.html +1 -1
- package/apps/pwa/out/inside/index.txt +22 -22
- package/apps/pwa/out/intake/index.html +1 -1
- package/apps/pwa/out/intake/index.txt +23 -23
- package/apps/pwa/out/live/index.html +1 -1
- package/apps/pwa/out/live/index.txt +23 -23
- package/apps/pwa/out/loops/index.html +1 -0
- package/apps/pwa/out/loops/index.txt +30 -0
- 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/orchestrator/orchestrator.yaml +59 -0
- package/graphs/orchestrator/recipes.yaml +18 -0
- package/graphs/release/recipes.yaml +3 -0
- package/graphs/release/release-loop.yaml +4 -1
- package/graphs/rhythm/daily.yaml +38 -0
- package/graphs/rhythm/recipes.yaml +18 -0
- package/graphs/rhythm/weekly.yaml +39 -0
- package/graphs/video/recipes.yaml +3 -0
- package/graphs/video/youtube-summary.yaml +17 -0
- package/package.json +1 -1
- package/scripts/audit-test-state-writes.ts +72 -17
- package/scripts/brand/check.ts +186 -0
- package/scripts/coord-post.sh +11 -1
- package/scripts/docs-cli-check.ts +23 -6
- package/scripts/expose-rubric-check.ts +118 -0
- package/scripts/expose-rubric-suggest.ts +163 -0
- package/scripts/heal/run-role.ts +14 -0
- package/scripts/hq/standby-verify.ts +11 -7
- package/scripts/lessons/handbook-page.ts +85 -0
- package/scripts/release-loop/gate-node.ts +9 -0
- package/scripts/release-loop/publish-node.ts +14 -1
- package/scripts/release-loop/unattended-release.ts +23 -1
- package/scripts/release-loop/vault-note-node.ts +196 -0
- package/scripts/release-story/draft.ts +172 -0
- package/scripts/rhythm/daily.ts +383 -0
- package/scripts/rhythm/weekly.ts +294 -0
- package/scripts/skills/public-check.ts +169 -0
- package/scripts/test-deterministic.ts +4 -8
- package/scripts/test-diet/effectiveness.ts +195 -0
- package/scripts/test-diet/lib.ts +16 -8
- package/scripts/test-diet/node.ts +77 -9
- package/scripts/ux/demo-rehearsal.ts +2 -2
- package/scripts/ux/lib/rehearsal-verdict.ts +1 -0
- package/scripts/video/youtube-summary.ts +133 -0
- package/skills/diagram-master/SKILL.md +42 -49
- package/skills/diagram-master/references/pyproject.toml +4 -1
- package/skills/diagram-master/references/routing-guide.md +4 -2
- package/skills/grill-me/SKILL.md +1 -0
- package/skills/youtube-master/SKILL.md +13 -13
- package/skills/youtube-master/package-lock.json +0 -545
- package/skills/youtube-master/package.json +2 -5
- package/skills/youtube-master/scripts/main.ts +2 -2
- package/skills/youtube-master/scripts/transcribe-local.ts +1 -1
- package/skills/youtube-master/src/util.ts +30 -7
- package/src/agent-mission/driver.ts +3 -3
- package/src/autopilot/build/llm-conflict-merge.ts +45 -8
- package/src/autopilot/build/merge-intent.ts +53 -0
- package/src/chat/index.ts +2 -0
- package/src/claims/claims-ledger.ts +232 -0
- package/src/claims/claims-render.ts +157 -0
- package/src/cli/claims-cli.ts +94 -0
- package/src/cli/decisions-cli.ts +35 -3
- package/src/cli/docs-cli.ts +2 -0
- package/src/cli/flow-cli.ts +11 -0
- package/src/cli/freeze-cli.ts +45 -0
- package/src/cli/lesson-cli.ts +108 -0
- package/src/cli/logs-cli.ts +104 -2
- package/src/cli/logs-model-input.ts +131 -0
- package/src/cli/pr-cli.ts +29 -1
- package/src/cli/private-ledger-commands.ts +54 -4
- package/src/cli/pty-drive-verdict.ts +11 -2
- package/src/cli/release-cli.ts +144 -21
- package/src/cli/rfc-status.ts +91 -0
- package/src/connectors/linear.ts +5 -3
- package/src/context-bus/condense.ts +15 -20
- package/src/context-bus/context-now-public.ts +86 -0
- package/src/context-bus/context-now-surfaces.ts +55 -2
- package/src/context-bus/context-now.ts +4 -3
- package/src/context-bus/long-term-memory.ts +92 -11
- package/src/dashboard/index.ts +75 -22
- package/src/dashboard/slash-runtime/chat-persona.ts +66 -0
- package/src/dashboard/slash-runtime/dashboard-handlers.ts +99 -0
- package/src/dashboard/slash-runtime/loops-table.ts +77 -0
- package/src/decisions/decision-ledger.ts +40 -1
- package/src/discord/sprint21-runtime.ts +16 -0
- package/src/discord-self-message.ts +8 -0
- package/src/discord-slash-wire.ts +31 -2
- package/src/domains/outbound-alert.ts +1 -1
- package/src/elanous-config-dir.ts +29 -4
- package/src/execution-loop/launch-gate.ts +29 -14
- package/src/flow/cards.ts +239 -0
- package/src/graph-runner/graph-tick.ts +4 -0
- package/src/graph-runner/runner.ts +30 -5
- package/src/harness/harness-cli-command.ts +152 -27
- package/src/harness/harness-incidents.ts +43 -12
- package/src/harness/harness-pod-dispatch.ts +2 -0
- package/src/harness/harness-queue-child.ts +3 -3
- package/src/harness/harness-queue.ts +216 -28
- package/src/harness/helper-scan.ts +139 -0
- package/src/harness/model-input-observation.ts +37 -0
- package/src/hq/hq.ts +1 -1
- package/src/hq/promote.ts +301 -0
- package/src/index.ts +83 -21
- package/src/intake-plane/linear-wish.ts +39 -11
- package/src/intake-plane/wish-benchmark-ledger.ts +129 -0
- package/src/intake-plane/wish-card.ts +53 -2
- package/src/intake-plane/wish-folder.ts +5 -2
- package/src/intake-plane/wish-reply.ts +72 -0
- package/src/lessons/lesson-import.ts +106 -0
- package/src/lessons/lesson-ledger.ts +206 -0
- package/src/loops/activity.ts +203 -0
- package/src/loops/budget.ts +32 -0
- package/src/loops/checker.ts +5 -3
- package/src/loops/contract/examples/orchestrator.loop.yaml +1 -1
- package/src/loops/loop-cli.ts +36 -4
- package/src/loops/neighbors.ts +40 -0
- package/src/loops/observe.ts +10 -0
- package/src/loops/orchestrator/author-depth.ts +164 -0
- package/src/loops/orchestrator/author-input-check.ts +77 -0
- package/src/loops/orchestrator/author-ledger.ts +230 -0
- package/src/loops/orchestrator/finish-rate.ts +175 -0
- package/src/loops/orchestrator/overlap.ts +51 -12
- package/src/loops/orchestrator/resync-shadow.ts +227 -0
- package/src/loops/orchestrator/tick.ts +302 -0
- package/src/loops/orchestrator/traffic.ts +18 -3
- package/src/loops/registry.ts +454 -9
- package/src/loops/status-rows.ts +118 -0
- package/src/loops/verdict.ts +17 -5
- package/src/maturity/bot-slash-catalog.ts +66 -0
- package/src/maturity/feature-maturity.ts +7 -4
- package/src/nexus/api/dashboard.ts +6 -1
- package/src/nexus/api/http-server.ts +18 -2
- package/src/nexus/api/merge-approvals-gate.ts +1 -1
- package/src/nexus/api/schedules-read.ts +5 -1
- package/src/nexus/api/task-cards-api.ts +44 -5
- package/src/nexus/api/workflows.ts +2 -1
- package/src/release-loop/auto-start.ts +48 -0
- package/src/release-loop/checklist.ts +13 -4
- package/src/release-loop/feature-store.ts +28 -16
- package/src/release-loop/landing-freeze.ts +121 -0
- package/src/release-loop/placement.ts +242 -0
- package/src/release-loop/release-schedule.ts +3 -1
- package/src/seat-dispatch/ceo-commands.ts +2 -2
- package/src/seat-dispatch/seat-ask.ts +16 -3
- package/src/seat-dispatch/seat-questions.ts +58 -0
- package/src/seat-loop/seat-loop.ts +406 -31
- package/src/self-dev/dev-pipeline.ts +6 -2
- package/src/self-dev/draft-sweep.ts +76 -0
- package/src/self-dev/run-store.ts +73 -1
- package/src/self-dev/run-supervisor.ts +8 -0
- package/src/self-dev/self-orchestrate-runtime.ts +5 -1
- package/src/self-implement/budget-gate.ts +29 -0
- package/src/self-implement/expose-gate.ts +47 -0
- package/src/self-implement/frozen-merges.ts +294 -0
- package/src/self-implement/gate-cli.ts +10 -4
- package/src/self-implement/gate-scope.ts +46 -9
- package/src/self-implement/goal-archive.ts +222 -0
- package/src/self-implement/goal-lookup.ts +147 -0
- package/src/self-implement/heal-intake.ts +79 -0
- package/src/self-implement/host-regate.ts +25 -2
- package/src/self-implement/orchestrator.ts +114 -10
- package/src/self-implement/revert-guard.ts +62 -0
- package/src/self-implement/run-ledger.ts +1 -1
- package/src/self-implement/seams.ts +28 -8
- package/src/session/index.ts +2 -0
- package/src/task-cards/card-cli.ts +10 -2
- package/src/task-cards/card-store.ts +10 -6
- package/src/task-orchestrator/surfaces/pod-lease.ts +5 -2
- package/src/task-orchestrator/surfaces/pod-ledger-collect.ts +46 -6
- package/src/task-orchestrator/surfaces/self-implement-pod.ts +80 -16
- package/src/telegram-commands.ts +27 -3
- package/src/telegram-loops-command.ts +44 -0
- package/src/user-config.ts +120 -16
- package/src/version/packed-revision.json +1 -1
- package/src/workflow-runtime/storage.ts +31 -13
- package/apps/pwa/out/_next/static/D51l8jUBplJrIpWvENsDK/_buildManifest.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1159-0c5ef8bc21cb6afb.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1394-931c9827067b2276.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2176-c379e3ebce874151.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2426.8c6ee0c668d53def.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3170-7ec60a56fa0e4728.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3278-ae6ef084769f6195.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3454-988a53dac60a1763.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3638-aa80b4641ea439db.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3735-fe3402553ac1c9c6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3992-fe5c06d6b31f8b25.js +0 -25
- package/apps/pwa/out/_next/static/chunks/4215-c9d91dd644ec1719.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4508-5d0161d1e724d735.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4733-623a79345b171e1c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4883.3bd969ea1d3aaaff.js +0 -2
- package/apps/pwa/out/_next/static/chunks/5250-c78ca295585bbf95.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5532.6899a12958a89b72.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5845-e6c003ce8d5fd36a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5978-b27004c677119115.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6048-4c5c33579bc4ef78.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6303-422dbf0f67c72aac.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6511-ff8360be3d65aaa4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6588-ba380869b67c4fa7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6751-bb3595948b2e311b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7274-19b7279f18b47ab3.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7551-a0e0707b79164ddd.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7758-85c2b53f731da024.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7777-79f845045aa1ed86.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8025-cc24ee5d8f2fb6f8.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8206-c6684dcde12be721.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8605-9da4678f8a310d18.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8725.bf46bb1395dd0fa2.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8734-252d511cd276c0dc.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8834-ca1cb09c1cb1a6bf.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9269.d2a4795bc6812717.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9538-2816b90923606459.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9906-e7b8b5310a169dfe.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/_not-found/page-d16993889b125c42.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-e5b4e1b256f41076.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/autopilot/page-e49b9b76c565a0b1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/board/page-b4f032c893b21cc9.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/bots/page-b433308aad13ceb6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/chat/page-4c1dbb21861beab2.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/consult/page-c85b816e3b269dbb.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/control/page-f6bc66b919b2ea7d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-60603c1e056c9b25.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/editor/page-a0ea8f5a2341ee6b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/exec/page-45c5bf04b4a605e6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/field/page-36c6c4183353ac9e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/healthz/route-d16993889b125c42.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/inside/page-cb2eb0fd809980da.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/intake/page-31012264da8a341f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/layout-50309aeb0bdd554a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/live/page-bc00e964e855f4df.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/market/page-0696ee3fac9fe56c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/morning/page-6ddd2109b7fc3a80.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/not-found-4e50b7298c1f69a5.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-7a2bf830038a2264.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-1f327f18694fabca.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-e5cef2fe6e4028df.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-cf596128d2b420ba.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/outputs/page-f672536f14f0969f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/page-b52365662232db89.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-e7a6c80f288b1948.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-29a709616df9b050.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-fee842d7c2151129.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/page-75d199f448171014.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-a011dd5b0bff29c5.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/layout-d16993889b125c42.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/page-d92e32d51ab5157e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/share/page-5f21aed6932ef375.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-1e1a53946804debf.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-a1833cd43d832601.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/today/page-2a511d320f762e06.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/trace/page-1c3e03f88dac1e21.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/vault/page-c4d5289c2ea97a89.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-b91f8952deaeaf27.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-71b43a2588489987.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-2b256feed331546f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-505bb96d69a8622c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/framework-172630e6aa5e2d7f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/main-app-f0fe2d7477845181.js +0 -1
- package/apps/pwa/out/_next/static/chunks/main-fc98ac70b44eccb0.js +0 -1
- package/apps/pwa/out/_next/static/chunks/pages/_app-5bd7f10271f72413.js +0 -1
- package/apps/pwa/out/_next/static/chunks/pages/_error-97d0d85ea05fef9b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/webpack-9e4d02d567b98240.js +0 -1
- package/apps/pwa/out/_next/static/css/128fb7fb670b2fbf.css +0 -1
- /package/apps/pwa/out/_next/static/{D51l8jUBplJrIpWvENsDK → x6mjI6vqOkVVu0jFuDkz1}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
// HQ promotion runbook: read-only preview by default; all remote mutations are behind --apply.
|
|
2
|
+
import { spawnSync } from 'node:child_process';
|
|
3
|
+
import { getUserConfig } from '../user-config.js';
|
|
4
|
+
import { parseLease, sshLeaseStore, defaultSshRunner, localShellRunner } from './lease.js';
|
|
5
|
+
|
|
6
|
+
export interface PromoteRunner {
|
|
7
|
+
lease(): { holder: string | null; generation: number | null; expired: boolean };
|
|
8
|
+
run(host: string, script: string): { status: number | null; stdout: string; stderr: string };
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface PromoteLine { step: string; status: 'ready' | 'done' | 'failed'; measurement: string; command: string }
|
|
12
|
+
export interface PromoteResult { ok: boolean; apply: boolean; host: string; lines: PromoteLine[] }
|
|
13
|
+
|
|
14
|
+
const install = '"$HOME/.local/share/elanous/current/node_modules/elanous"';
|
|
15
|
+
const state = '"$HOME/.elanous-hqstate"';
|
|
16
|
+
const cli = 'elanous';
|
|
17
|
+
// A bind to the same address as nexus distinguishes an available socket from an
|
|
18
|
+
// occupied or uninspectable one; lsof's exit 1 alone cannot make that distinction.
|
|
19
|
+
const portProbe = `command -v lsof >/dev/null || { echo 'lsof unavailable' >&2; exit 1; }
|
|
20
|
+
python3 -c 'import os,socket,subprocess,sys; s=socket.socket(); s.bind(("127.0.0.1",0)); s.listen(); r=subprocess.run(["lsof","-nP","-t","-iTCP:"+str(s.getsockname()[1]),"-sTCP:LISTEN"],capture_output=True,text=True); s.close(); sys.exit(0 if r.returncode == 0 and r.stdout.strip() == str(os.getpid()) else "lsof control listener unreadable: "+r.stderr.strip())' || exit 1
|
|
21
|
+
port_detail=$(lsof -nP -t -iTCP:31416 -sTCP:LISTEN 2>&1); port_rc=$?
|
|
22
|
+
if [ "$port_rc" -ne 1 ] || [ -n "$port_detail" ]; then echo "port=31416 occupied or unreadable: $port_detail (lsof=$port_rc)" >&2; exit 1; fi
|
|
23
|
+
python3 -c 'import socket; s=socket.socket(); s.bind(("127.0.0.1", 31416)); s.close(); print("port=31416 available")'`;
|
|
24
|
+
// The drill uses short SSH aliases; host-local hq.hostName may use either the short or full name.
|
|
25
|
+
const hostNamesFor = (host: string): string[] => host === 'node-b' ? ['node-b', 'MacStudioB1']
|
|
26
|
+
: host === 'mbp' ? ['mbp', 'MacBookProM5'] : [host];
|
|
27
|
+
const checks: Array<[string, string]> = [
|
|
28
|
+
['PWA build', `test -f "$HOME/.local/share/elanous/current/node_modules/elanous/apps/pwa/out/index.html" && echo 'index.html present'`],
|
|
29
|
+
['MCP list', `set -o pipefail; codex mcp list --json | python3 -c 'import json,sys; data=json.load(sys.stdin); print("MCP entries="+str(len(data)))'`],
|
|
30
|
+
['installed hq', `${cli} hq promote --help >/dev/null && python3 -c 'import json,pathlib; p=pathlib.Path.home()/".elanous-hqcfg/config.json"; print("host="+json.loads(p.read_text())["hq"]["hostName"])'`],
|
|
31
|
+
];
|
|
32
|
+
const stages: Array<[string, string]> = [
|
|
33
|
+
['standby checksums', `cd ${install} && bun -e 'import("node:fs/promises").then(async ({ readlink }) => { const root = process.env.HOME + "/.elanous-standby"; const generations = { core: await readlink(root + "/core/latest"), big: await readlink(root + "/big/latest") }; const { verifyStandby } = await import("./scripts/hq/standby-verify.ts"); const result = await verifyStandby({ root, tiers: ["core", "big"], generations }); console.log(JSON.stringify(result)); if (!result.ok) process.exitCode = 1 }).catch(error => { console.error(error); process.exitCode = 1 })'`],
|
|
34
|
+
['copy to promotion universe', `python3 - <<'PY'
|
|
35
|
+
import hashlib, json, os, pathlib, shutil, tempfile
|
|
36
|
+
home = pathlib.Path.home()
|
|
37
|
+
standby = home / '.elanous-standby'
|
|
38
|
+
target = home / '.elanous-hqstate'
|
|
39
|
+
parent = target.parent
|
|
40
|
+
stage = pathlib.Path(tempfile.mkdtemp(prefix='.elanous-hqstate-promote-', dir=parent))
|
|
41
|
+
backup = None
|
|
42
|
+
try:
|
|
43
|
+
copied = 0
|
|
44
|
+
for tier in ('core', 'big'):
|
|
45
|
+
generation = os.environ['HQ_PROMOTE_' + tier.upper()]
|
|
46
|
+
source = standby / tier / generation
|
|
47
|
+
if not source.is_dir() or source.is_symlink():
|
|
48
|
+
raise ValueError('missing or symlinked pinned generation: ' + tier)
|
|
49
|
+
manifest_bytes = (source / 'MANIFEST.json').read_bytes()
|
|
50
|
+
expected_manifest = os.environ['HQ_PROMOTE_' + tier.upper() + '_MANIFEST_SHA256']
|
|
51
|
+
if hashlib.sha256(manifest_bytes).hexdigest() != expected_manifest:
|
|
52
|
+
raise ValueError('verified manifest changed: ' + tier)
|
|
53
|
+
manifest = json.loads(manifest_bytes)
|
|
54
|
+
if manifest['tier'] != tier or manifest['generation'] != generation:
|
|
55
|
+
raise ValueError('generation changed: ' + tier)
|
|
56
|
+
for entry in manifest['entries']:
|
|
57
|
+
name = entry['path']
|
|
58
|
+
rel = pathlib.PurePosixPath(name)
|
|
59
|
+
if not name or rel.is_absolute() or '..' in rel.parts or name.startswith('./') or chr(92) in name or name in ('MANIFEST.json', 'SHA256SUMS'):
|
|
60
|
+
raise ValueError('invalid manifest path: ' + name)
|
|
61
|
+
src = source
|
|
62
|
+
for part in rel.parts:
|
|
63
|
+
src = src / part
|
|
64
|
+
if src.is_symlink():
|
|
65
|
+
raise ValueError('symlinked source: ' + name)
|
|
66
|
+
if not src.is_file():
|
|
67
|
+
raise ValueError('missing source: ' + name)
|
|
68
|
+
dest = stage / name
|
|
69
|
+
if dest.parent.is_symlink():
|
|
70
|
+
raise ValueError('symlinked destination parent: ' + name)
|
|
71
|
+
dest.parent.mkdir(parents=True, exist_ok=True)
|
|
72
|
+
if dest.is_symlink():
|
|
73
|
+
raise ValueError('symlinked destination: ' + name)
|
|
74
|
+
shutil.copy2(src, dest)
|
|
75
|
+
digest = hashlib.sha256(dest.read_bytes()).hexdigest()
|
|
76
|
+
if digest != entry['sha256']:
|
|
77
|
+
raise ValueError('checksum changed during copy: ' + name)
|
|
78
|
+
copied += 1
|
|
79
|
+
if hashlib.sha256((source / 'MANIFEST.json').read_bytes()).hexdigest() != expected_manifest:
|
|
80
|
+
raise ValueError('verified manifest changed during copy: ' + tier)
|
|
81
|
+
for tier in ('core', 'big'):
|
|
82
|
+
source = standby / tier / os.environ['HQ_PROMOTE_' + tier.upper()]
|
|
83
|
+
expected_manifest = os.environ['HQ_PROMOTE_' + tier.upper() + '_MANIFEST_SHA256']
|
|
84
|
+
if hashlib.sha256((source / 'MANIFEST.json').read_bytes()).hexdigest() != expected_manifest:
|
|
85
|
+
raise ValueError('verified manifest changed before promotion: ' + tier)
|
|
86
|
+
if not (stage / 'release/features.sqlite').is_file() or not (stage / 'config.json').is_file():
|
|
87
|
+
raise ValueError('core promotion files missing')
|
|
88
|
+
if target.is_symlink() or (target.exists() and not target.is_dir()):
|
|
89
|
+
raise ValueError('promotion target is not a directory')
|
|
90
|
+
if target.exists():
|
|
91
|
+
backup = pathlib.Path(tempfile.mkdtemp(prefix='.elanous-hqstate-old-', dir=parent))
|
|
92
|
+
backup.rmdir()
|
|
93
|
+
target.rename(backup)
|
|
94
|
+
try:
|
|
95
|
+
stage.rename(target)
|
|
96
|
+
except BaseException:
|
|
97
|
+
if backup is not None:
|
|
98
|
+
backup.rename(target)
|
|
99
|
+
backup = None
|
|
100
|
+
raise
|
|
101
|
+
bytes_count = sum(p.stat().st_size for p in target.rglob('*') if p.is_file())
|
|
102
|
+
if backup is not None:
|
|
103
|
+
shutil.rmtree(backup)
|
|
104
|
+
backup = None
|
|
105
|
+
print('promotion files=' + str(copied) + ' bytes=' + str(bytes_count) + 'B')
|
|
106
|
+
finally:
|
|
107
|
+
if stage.exists():
|
|
108
|
+
shutil.rmtree(stage)
|
|
109
|
+
PY`],
|
|
110
|
+
['target hq config', `python3 - <<'PY'
|
|
111
|
+
import json, os, pathlib, tempfile
|
|
112
|
+
source = pathlib.Path.home() / '.elanous-hqcfg/config.json'
|
|
113
|
+
target = pathlib.Path.home() / '.elanous-hqstate/config.json'
|
|
114
|
+
with source.open() as f: hq = json.load(f)['hq']
|
|
115
|
+
with target.open() as f: config = json.load(f)
|
|
116
|
+
assert isinstance(hq, dict) and hq.get('hostName') == os.environ['HQ_PROMOTE_HOST'], 'host-specific hq config mismatch'
|
|
117
|
+
config['hq'] = hq
|
|
118
|
+
with tempfile.NamedTemporaryFile(mode='w', dir=target.parent, prefix='config.promote-', delete=False) as f:
|
|
119
|
+
tmp = pathlib.Path(f.name)
|
|
120
|
+
json.dump(config, f)
|
|
121
|
+
f.write(chr(10))
|
|
122
|
+
try:
|
|
123
|
+
os.chmod(tmp, 0o600)
|
|
124
|
+
os.replace(tmp, target)
|
|
125
|
+
finally:
|
|
126
|
+
tmp.unlink(missing_ok=True)
|
|
127
|
+
print('hq.hostName=' + hq['hostName'])
|
|
128
|
+
PY`],
|
|
129
|
+
['nexus health', `command -v lsof >/dev/null || { echo 'lsof unavailable: cannot identify nexus listener' >&2; exit 1; }
|
|
130
|
+
${portProbe} || { echo 'port=31416 already occupied or unreadable' >&2; exit 1; }
|
|
131
|
+
ELANOUS_STATE_DIR=${state} ${cli} --config-dir ${state} nexus run --port 31416 --http-host 127.0.0.1 --no-mcp --no-auto-build --no-auto-install --no-auto-restart --no-watch > "$HOME/.elanous-hqstate/nexus-promote.log" 2>&1 & pid=$!
|
|
132
|
+
descendants() {
|
|
133
|
+
for child in $(pgrep -P "$1" 2>/dev/null); do descendants "$child"; echo "$child"; done
|
|
134
|
+
}
|
|
135
|
+
cleanup() {
|
|
136
|
+
tree=$(descendants "$pid")
|
|
137
|
+
for proc in $tree; do kill "$proc" 2>/dev/null || true; done
|
|
138
|
+
kill "$pid" 2>/dev/null || true
|
|
139
|
+
wait "$pid" 2>/dev/null || true
|
|
140
|
+
n=0
|
|
141
|
+
until ${portProbe} >/dev/null 2>&1; do
|
|
142
|
+
if [ "$n" -ge 10 ]; then echo 'port=31416 still occupied after cleanup' >&2; return 1; fi
|
|
143
|
+
n=$((n+1)); sleep 1
|
|
144
|
+
done
|
|
145
|
+
echo 'nexus stopped port=31416 released' >&2
|
|
146
|
+
}
|
|
147
|
+
trap 'cleanup' EXIT
|
|
148
|
+
n=0
|
|
149
|
+
code=000
|
|
150
|
+
owner=''
|
|
151
|
+
owned=0
|
|
152
|
+
while [ "$n" -lt 15 ]; do
|
|
153
|
+
code=$(curl -s --max-time 2 -o /dev/null -w '%{http_code}' http://127.0.0.1:31416/v1/health) || code=000
|
|
154
|
+
listeners=$(lsof -nP -t -iTCP:31416 -sTCP:LISTEN 2> /dev/null) || listeners=''
|
|
155
|
+
owner=$(printf '%s\\n' "$listeners" | head -n 1)
|
|
156
|
+
ancestor=$owner
|
|
157
|
+
owned=0
|
|
158
|
+
while [ -n "$ancestor" ] && [ "$ancestor" != 1 ]; do
|
|
159
|
+
if [ "$ancestor" = "$pid" ]; then owned=1; break; fi
|
|
160
|
+
ancestor=$(ps -o ppid= -p "$ancestor" | tr -d '[:space:]')
|
|
161
|
+
done
|
|
162
|
+
if [ "$code" = 200 ] && [ "$owned" = 1 ] && [ "$listeners" = "$owner" ] && kill -0 "$pid" 2>/dev/null; then
|
|
163
|
+
trap - EXIT
|
|
164
|
+
echo "health=$code port=31416 pid=$pid listener_pid=$owner"; exit 0
|
|
165
|
+
fi
|
|
166
|
+
if ! kill -0 "$pid" 2>/dev/null; then break; fi
|
|
167
|
+
n=$((n+1)); sleep 2
|
|
168
|
+
done
|
|
169
|
+
echo "health=$code port=31416 nexus_pid=$pid listener_pid=$owner owned=$owned" >&2; exit 1`],
|
|
170
|
+
['steward rescue', `ELANOUS_STATE_DIR=${state} ${cli} --config-dir ${state} steward mode rescue && ELANOUS_STATE_DIR=${state} ${cli} --config-dir ${state} steward mode --json`],
|
|
171
|
+
];
|
|
172
|
+
|
|
173
|
+
// One --apply per target host: stages run as separate ssh calls, so the lock is a directory that outlives each call.
|
|
174
|
+
const lockDir = '"$HOME/.elanous-hq/promote.lock"';
|
|
175
|
+
export const promoteLockAcquire = `mkdir -p "$HOME/.elanous-hq" && if mkdir ${lockDir} 2>/dev/null; then printf '%s %s\\n' "$PPID" "$(date -u +%Y-%m-%dT%H:%M:%SZ)" > ${lockDir}/owner; else echo "promote lock held: $(cat ${lockDir}/owner 2>/dev/null) · remove ${lockDir} only after confirming no promote is running" >&2; exit 1; fi`;
|
|
176
|
+
export const promoteLockRelease = `rm -rf ${lockDir}`;
|
|
177
|
+
|
|
178
|
+
export function defaultPromoteRunner(): PromoteRunner {
|
|
179
|
+
const config = getUserConfig().hq ?? {};
|
|
180
|
+
const arbiter = config.arbiter ?? 'cloud-vm';
|
|
181
|
+
const store = sshLeaseStore(arbiter, arbiter === 'local' ? localShellRunner : defaultSshRunner);
|
|
182
|
+
return {
|
|
183
|
+
lease() {
|
|
184
|
+
const { now, raw } = store.read();
|
|
185
|
+
const record = parseLease(raw);
|
|
186
|
+
return { holder: record?.holder ?? null, generation: record?.generation ?? null,
|
|
187
|
+
expired: !record || now - record.renewedAt > record.ttlSeconds };
|
|
188
|
+
},
|
|
189
|
+
run(host, script) {
|
|
190
|
+
const r = spawnSync('ssh', ['-o', 'BatchMode=yes', host, `bash -c '${script.replaceAll("'", "'\\''")}'`], { encoding: 'utf8', timeout: 120_000 });
|
|
191
|
+
return { status: r.status, stdout: r.stdout ?? '', stderr: r.stderr ?? String(r.error ?? '') };
|
|
192
|
+
},
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export function promoteHq(host: string, apply = false, runner: PromoteRunner = defaultPromoteRunner()): PromoteResult {
|
|
197
|
+
if (!/^[a-zA-Z0-9][a-zA-Z0-9.-]*$/.test(host) || host.includes('..')) throw new Error('hq promote: invalid host');
|
|
198
|
+
const lines: PromoteLine[] = [];
|
|
199
|
+
const acceptedHostNames = hostNamesFor(host);
|
|
200
|
+
const result = (step: string, command: string, status: PromoteLine['status'], measurement: string) => {
|
|
201
|
+
lines.push({ step, status, measurement, command });
|
|
202
|
+
};
|
|
203
|
+
let preflightOk = true;
|
|
204
|
+
for (const [name, command] of checks) {
|
|
205
|
+
let r: ReturnType<PromoteRunner['run']>;
|
|
206
|
+
try { r = runner.run(host, command); } catch (error) { r = { status: null, stdout: '', stderr: String(error) }; }
|
|
207
|
+
const ok = r.status === 0 && (name !== 'installed hq' || acceptedHostNames.some(name => r.stdout.trim() === `host=${name}`));
|
|
208
|
+
if (!ok) preflightOk = false;
|
|
209
|
+
result(`preflight ${name}`, command, ok ? 'done' : 'failed', (r.status === 0 ? r.stdout : r.stderr).trim() || `exit=${r.status}`);
|
|
210
|
+
}
|
|
211
|
+
let lease: ReturnType<PromoteRunner['lease']>;
|
|
212
|
+
try { lease = runner.lease(); } catch (error) {
|
|
213
|
+
result('① lease', 'read arbiter lease', 'failed', String(error));
|
|
214
|
+
return { ok: false, apply, host, lines };
|
|
215
|
+
}
|
|
216
|
+
if (!acceptedHostNames.includes(lease.holder ?? '') || lease.expired) {
|
|
217
|
+
result('① lease', 'read arbiter lease', 'failed', `holder=${lease.holder ?? 'none'} generation=${lease.generation ?? 'none'} expired=${lease.expired}`);
|
|
218
|
+
return { ok: false, apply, host, lines };
|
|
219
|
+
}
|
|
220
|
+
result('① lease', 'read arbiter lease', 'done', `holder=${lease.holder} generation=${lease.generation} expired=false`);
|
|
221
|
+
const targetHostName = lines.find(line => line.step === 'preflight installed hq')?.measurement.trim().replace(/^host=/, '') ?? '';
|
|
222
|
+
if (!acceptedHostNames.includes(targetHostName) || targetHostName !== lease.holder) {
|
|
223
|
+
result('① lease', 'compare arbiter holder to target hq.hostName', 'failed', `holder=${lease.holder} hq.hostName=${targetHostName}`);
|
|
224
|
+
return { ok: false, apply, host, lines };
|
|
225
|
+
}
|
|
226
|
+
if (apply && !preflightOk) return { ok: false, apply, host, lines };
|
|
227
|
+
let pinned: { core: string; big: string; coreManifestSha256: string; bigManifestSha256: string } | null = null;
|
|
228
|
+
let locked = false;
|
|
229
|
+
try {
|
|
230
|
+
for (let i = 0; i < stages.length; i++) {
|
|
231
|
+
const [name, script] = stages[i];
|
|
232
|
+
const command = i === 1 && pinned
|
|
233
|
+
? `HQ_PROMOTE_CORE=${pinned.core} HQ_PROMOTE_BIG=${pinned.big} HQ_PROMOTE_CORE_MANIFEST_SHA256=${pinned.coreManifestSha256} HQ_PROMOTE_BIG_MANIFEST_SHA256=${pinned.bigManifestSha256} ${script}`
|
|
234
|
+
: i === 2 ? `HQ_PROMOTE_HOST=${targetHostName} ${script}` : script;
|
|
235
|
+
if (apply && i > 0) {
|
|
236
|
+
try {
|
|
237
|
+
const current = runner.lease();
|
|
238
|
+
if (current.holder !== targetHostName || current.generation !== lease.generation || current.expired) {
|
|
239
|
+
result(`${'②③④⑤⑥'[i]} ${name}`, command, 'failed', `lease changed: holder=${current.holder ?? 'none'} generation=${current.generation ?? 'none'} expired=${current.expired}`);
|
|
240
|
+
return { ok: false, apply, host, lines };
|
|
241
|
+
}
|
|
242
|
+
} catch (error) {
|
|
243
|
+
result(`${'②③④⑤⑥'[i]} ${name}`, command, 'failed', `lease read failed: ${String(error)}`);
|
|
244
|
+
return { ok: false, apply, host, lines };
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
if (!apply && i > 0) {
|
|
248
|
+
result(`${'②③④⑤⑥'[i]} ${name}`, command, 'ready', `dry-run: would run on ${host}; no writes`);
|
|
249
|
+
continue;
|
|
250
|
+
}
|
|
251
|
+
if (apply && i === 1) {
|
|
252
|
+
// The host lock and the port probe share one call: the lock is held from before the copy until the finally below.
|
|
253
|
+
const lockedProbe = `${promoteLockAcquire} && ${portProbe}`;
|
|
254
|
+
let probe: ReturnType<PromoteRunner['run']>;
|
|
255
|
+
try { probe = runner.run(host, lockedProbe); } catch (error) { probe = { status: null, stdout: '', stderr: String(error) }; }
|
|
256
|
+
locked = !probe.stderr.includes('promote lock held');
|
|
257
|
+
if (!locked) {
|
|
258
|
+
result('③ copy to promotion universe', lockedProbe, 'failed', probe.stderr.trim());
|
|
259
|
+
return { ok: false, apply, host, lines };
|
|
260
|
+
}
|
|
261
|
+
if (probe.status !== 0 || probe.stdout.trim() !== 'port=31416 available') {
|
|
262
|
+
result('③ copy to promotion universe', portProbe, 'failed', `port=31416 occupied or unreadable before copy: ${probe.stderr.trim() || probe.stdout.trim() || `exit=${probe.status}`}`);
|
|
263
|
+
return { ok: false, apply, host, lines };
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
let r: ReturnType<PromoteRunner['run']>;
|
|
267
|
+
try { r = runner.run(host, command); } catch (error) { r = { status: null, stdout: '', stderr: String(error) }; }
|
|
268
|
+
let measurement = (r.status === 0 ? r.stdout : r.stderr).trim() || `exit=${r.status}`;
|
|
269
|
+
let ok = r.status === 0;
|
|
270
|
+
if (i === 1 && ok) ok = /promotion files=\d+ bytes=\d+B/.test(r.stdout);
|
|
271
|
+
if (i === 2 && ok) ok = r.stdout.trim() === `hq.hostName=${targetHostName}`;
|
|
272
|
+
if (i === 3 && ok) ok = /health=200 port=31416 pid=\d+ listener_pid=\d+\b/.test(r.stdout);
|
|
273
|
+
if (i === 4 && ok) ok = /"mode"\s*:\s*"rescue"/.test(r.stdout);
|
|
274
|
+
if (i === 0 && ok) {
|
|
275
|
+
try {
|
|
276
|
+
const verification = JSON.parse(r.stdout) as { ok?: boolean; tiers?: Array<{ tier: string; generation: string | null; manifestSha256: string | null; checked: number; entries: number | null }> };
|
|
277
|
+
ok = verification.ok === true && verification.tiers?.length === 2
|
|
278
|
+
&& verification.tiers.map(t => t.tier).join(',') === 'core,big'
|
|
279
|
+
&& verification.tiers.every(t => t.generation && /^[a-zA-Z0-9][a-zA-Z0-9_-]*$/.test(t.generation)
|
|
280
|
+
&& typeof t.manifestSha256 === 'string' && /^[a-f0-9]{64}$/.test(t.manifestSha256)
|
|
281
|
+
&& t.entries !== null && t.entries > 0 && t.checked === t.entries) === true;
|
|
282
|
+
if (ok) pinned = { core: verification.tiers![0].generation!, big: verification.tiers![1].generation!,
|
|
283
|
+
coreManifestSha256: verification.tiers![0].manifestSha256!, bigManifestSha256: verification.tiers![1].manifestSha256! };
|
|
284
|
+
} catch { ok = false; }
|
|
285
|
+
if (!ok) measurement = `checksum verification failed · ${measurement}`;
|
|
286
|
+
}
|
|
287
|
+
result(`${'②③④⑤⑥'[i]} ${name}`, command, ok ? 'done' : 'failed', measurement);
|
|
288
|
+
if (!ok) return { ok: false, apply, host, lines };
|
|
289
|
+
}
|
|
290
|
+
return { ok: preflightOk, apply, host, lines };
|
|
291
|
+
} finally {
|
|
292
|
+
if (locked) {
|
|
293
|
+
try { runner.run(host, promoteLockRelease); } catch { /* the lock names its owner for manual release */ }
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
export function formatPromote(result: PromoteResult): string {
|
|
299
|
+
return [`hq promote --to ${result.host} ${result.apply ? '--apply' : '(dry-run)'}`,
|
|
300
|
+
...result.lines.map(line => `${line.step}: ${line.status} · ${line.measurement}${line.status === 'ready' ? ` · ${line.command}` : ''}`)].join('\n');
|
|
301
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -63,6 +63,8 @@ import { registerUsageCommand } from './cli/usage-cli.js';
|
|
|
63
63
|
import { registerLiveCommands } from './cli/live-cli.js';
|
|
64
64
|
import { registerResearchCommand } from './cli/research-cli.js';
|
|
65
65
|
import { registerReleaseCommands } from './cli/release-cli.js';
|
|
66
|
+
import { registerFreezeCommands } from './cli/freeze-cli.js';
|
|
67
|
+
import { registerFlowCommands } from './cli/flow-cli.js';
|
|
66
68
|
import { registerModelWatchCommand } from './cli/model-watch-cli.js';
|
|
67
69
|
import { registerIntakeCommands } from './cli/intake-cli.js';
|
|
68
70
|
import { registerTasksCommands } from './cli/tasks-cli.js';
|
|
@@ -869,6 +871,8 @@ registerUsageCommand(program);
|
|
|
869
871
|
registerLiveCommands(program);
|
|
870
872
|
registerResearchCommand(program);
|
|
871
873
|
registerReleaseCommands(program);
|
|
874
|
+
registerFreezeCommands(program);
|
|
875
|
+
registerFlowCommands(program);
|
|
872
876
|
// HQ-HB · HQ-FENCE (10-04): one writer across mbp · node-b · cloud-vm — lease on the arbiter, 2-of-3 quorum, generation fencing.
|
|
873
877
|
const hqCmd = program.command('hq').description('본부 임대 — 하트비트·중재·단일 작성자 래퍼');
|
|
874
878
|
async function hqSink(): Promise<void> {
|
|
@@ -897,6 +901,19 @@ hqCmd.command('lease').argument('<action>', 'acquire|renew|status|release')
|
|
|
897
901
|
if (!result.ok) process.exitCode = 2;
|
|
898
902
|
} catch (error) { if (error instanceof CliUserError) throw error; console.error(`hq lease: ${error instanceof Error ? error.message : String(error)}`); process.exitCode = 1; }
|
|
899
903
|
});
|
|
904
|
+
hqCmd.command('promote').description('HQ 승격 런북 (기본 dry-run · --apply 일 때만 변경)')
|
|
905
|
+
.requiredOption('--to <host>', '승격 대상 호스트')
|
|
906
|
+
.option('--apply', '대기 사본을 승격 우주에 반영하고 넥서스·rescue 실행')
|
|
907
|
+
.option('--json', 'JSON 출력')
|
|
908
|
+
.action(async (opts: { to: string; apply?: boolean; json?: boolean }) => {
|
|
909
|
+
try {
|
|
910
|
+
const { promoteHq, formatPromote } = await import('./hq/promote.js');
|
|
911
|
+
const result = promoteHq(opts.to, opts.apply === true);
|
|
912
|
+
if (opts.json) await writeStdoutJson(`${JSON.stringify(result)}\n`);
|
|
913
|
+
else console.log(formatPromote(result));
|
|
914
|
+
if (!result.ok) process.exitCode = 1;
|
|
915
|
+
} catch (error) { console.error(`hq promote: ${error instanceof Error ? error.message : String(error)}`); process.exitCode = 1; }
|
|
916
|
+
});
|
|
900
917
|
hqCmd.command('heartbeat').description('보유자는 갱신 · 아니면 대기(보유자에 닿는지 중재자에 보고)').option('--json', 'JSON 출력')
|
|
901
918
|
.action(async (opts: { json?: boolean }) => {
|
|
902
919
|
try {
|
|
@@ -3034,6 +3051,8 @@ export async function runHarnessOrchestrateExecution(
|
|
|
3034
3051
|
prior,
|
|
3035
3052
|
goals,
|
|
3036
3053
|
pid,
|
|
3054
|
+
...(['OP', 'TC', 'MK', 'UX'].includes(process.env.ELANOUS_HARNESS_SEAT ?? '')
|
|
3055
|
+
? { seat: process.env.ELANOUS_HARNESS_SEAT as SelfDevRunState['seat'] } : {}),
|
|
3037
3056
|
runIdSource,
|
|
3038
3057
|
now,
|
|
3039
3058
|
onPersistenceFailure: reportPersistenceFailure,
|
|
@@ -3151,7 +3170,7 @@ contextCmd.command('condense')
|
|
|
3151
3170
|
if (!/^[1-9]\d*h$/.test(opts.since)) throw new Error('--since requires positive hours, e.g. 24h');
|
|
3152
3171
|
const { runContextCondense } = await import('./context-bus/condense.js');
|
|
3153
3172
|
const report = await runContextCondense({ hours: Number(opts.since.slice(0, -1)), apply: opts.apply === true });
|
|
3154
|
-
await writeStdoutJson(JSON.stringify({ since: report.since, until: report.until, cards: report.cards, conflicts: report.conflicts, skipped: report.skipped, folded: report.folded, ...(report.applied ? { applied: report.applied } : {}) }) + '\n');
|
|
3173
|
+
await writeStdoutJson(JSON.stringify({ since: report.since, until: report.until, cards: report.cards, conflicts: report.conflicts, skipped: report.skipped, folded: report.folded, droppedHarnessChild: report.droppedHarnessChild, funnel: report.funnel, ...(report.applied ? { applied: report.applied } : {}) }) + '\n');
|
|
3155
3174
|
if (!opts.json) await writeStdoutFully(report.markdown);
|
|
3156
3175
|
} catch (error) {
|
|
3157
3176
|
console.error(`context condense: ${error instanceof Error ? error.message : String(error)}`);
|
|
@@ -4300,6 +4319,35 @@ selfCmd
|
|
|
4300
4319
|
} catch (e) { console.error(`⛔ ${(e as Error).message}`); process.exitCode = 1; }
|
|
4301
4320
|
});
|
|
4302
4321
|
|
|
4322
|
+
export async function announceLaunchQuotaPolicy(input: {
|
|
4323
|
+
runId: string;
|
|
4324
|
+
current: import('./oauth/codex-quota-policy.js').ResolvedCodexQuotaPolicy;
|
|
4325
|
+
universe: { kind: 'prod' | 'test'; root: string };
|
|
4326
|
+
production?: import('./oauth/codex-quota-policy.js').ResolvedCodexQuotaPolicy;
|
|
4327
|
+
}): Promise<{ line: string; warning?: string; quotaPolicy: {
|
|
4328
|
+
policy: import('./oauth/codex-quota-policy.js').CodexQuotaPolicy;
|
|
4329
|
+
source: import('./oauth/codex-quota-policy.js').ResolvedCodexQuotaPolicy['source'];
|
|
4330
|
+
universeKind: 'prod' | 'test';
|
|
4331
|
+
} }> {
|
|
4332
|
+
const { describeLaunchQuotaPolicy } = await import('./oauth/codex-quota-policy.js');
|
|
4333
|
+
const { appendRunLedgerEntry } = await import('./self-implement/run-ledger.js');
|
|
4334
|
+
const described = describeLaunchQuotaPolicy(input);
|
|
4335
|
+
const data = { policy: input.current.policy, source: input.current.source, universe: input.universe,
|
|
4336
|
+
...(described.warning ? { warning: described.warning } : {}) };
|
|
4337
|
+
try {
|
|
4338
|
+
appendRunLedgerEntry({ runId: input.runId, timestamp: new Date().toISOString(), event: 'launch-quota-policy', data });
|
|
4339
|
+
} catch (error) {
|
|
4340
|
+
debug.log('pod.quota-policy', 'ledger-unavailable', { runId: input.runId, error: String(error) }, { level: 'warn' });
|
|
4341
|
+
throw new Error(`pod launch quota policy ledger write failed: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
|
|
4342
|
+
}
|
|
4343
|
+
debug.log('pod.quota-policy', 'announced', data);
|
|
4344
|
+
return { ...described, quotaPolicy: { policy: input.current.policy, source: input.current.source, universeKind: input.universe.kind } };
|
|
4345
|
+
}
|
|
4346
|
+
|
|
4347
|
+
export function withLaunchQuotaPolicy<T extends object>(results: readonly T[], quotaPolicy: Awaited<ReturnType<typeof announceLaunchQuotaPolicy>>['quotaPolicy'] | undefined): (T & { quotaPolicy?: Awaited<ReturnType<typeof announceLaunchQuotaPolicy>>['quotaPolicy'] })[] {
|
|
4348
|
+
return quotaPolicy ? results.map((result) => ({ ...result, quotaPolicy })) : [...results];
|
|
4349
|
+
}
|
|
4350
|
+
|
|
4303
4351
|
// self orchestrate — 병렬 self-dev(S1·2026-07-21) — N개 독립 goal 을 각자 `elanous self implement` 서브프로세스로
|
|
4304
4352
|
// TOX 디스패처 위에서 동시성캡 병렬 실행. 각 잡=자기 프로세스=자기 harness-space(병렬안전). 엔진(그래프/
|
|
4305
4353
|
// 디스패처)은 기존 재사용·새 조각=self-implement surface 어댑터. [[PLAN-parallel-self-dev-orchestrator-2026-07-21]].
|
|
@@ -4458,6 +4506,8 @@ const selfOrchestrateCmd = selfCmd
|
|
|
4458
4506
|
prior,
|
|
4459
4507
|
goals,
|
|
4460
4508
|
pid: process.pid,
|
|
4509
|
+
...(['OP', 'TC', 'MK', 'UX'].includes(process.env.ELANOUS_HARNESS_SEAT ?? '')
|
|
4510
|
+
? { seat: process.env.ELANOUS_HARNESS_SEAT as SelfDevRunState['seat'] } : {}),
|
|
4461
4511
|
runIdSource,
|
|
4462
4512
|
now: () => Date.now(),
|
|
4463
4513
|
});
|
|
@@ -4492,6 +4542,7 @@ const selfOrchestrateCmd = selfCmd
|
|
|
4492
4542
|
// coordinator 해석(decompose·run-identity·checkpoint·board)은 액션에 유지·감독 콜백은 runtime 으로 주입.
|
|
4493
4543
|
// ☸️ 실행 칸 — pod 면 self-implement 자식을 k8s Job 으로(계약 동일 · 슈퍼바이저 무수정).
|
|
4494
4544
|
const substrate = (opts as { substrate?: string }).substrate ?? 'local';
|
|
4545
|
+
let launchQuotaPolicy: Awaited<ReturnType<typeof announceLaunchQuotaPolicy>>['quotaPolicy'] | undefined;
|
|
4495
4546
|
let podSpawn: import('./task-orchestrator/surfaces/self-implement.js').SelfImplementJobSpawn | undefined;
|
|
4496
4547
|
let podTargets: { context: string; namespace: string }[] | undefined;
|
|
4497
4548
|
if (substrate === 'pod') {
|
|
@@ -4574,30 +4625,39 @@ const selfOrchestrateCmd = selfCmd
|
|
|
4574
4625
|
const resolvedQuota = resolveCodexQuotaPolicy(getUserConfig().llm);
|
|
4575
4626
|
const quotaPolicy = resolvedQuota.policy;
|
|
4576
4627
|
// POL1 — say which policy this launch uses and where it came from; warn when a test universe disagrees with production.
|
|
4577
|
-
|
|
4578
|
-
|
|
4579
|
-
|
|
4580
|
-
|
|
4581
|
-
|
|
4582
|
-
|
|
4583
|
-
|
|
4584
|
-
|
|
4585
|
-
|
|
4586
|
-
|
|
4587
|
-
|
|
4588
|
-
|
|
4628
|
+
const { effectiveInstanceRoot, prodInstanceRoot } = await import('./instance/resolve.js');
|
|
4629
|
+
const root = effectiveInstanceRoot();
|
|
4630
|
+
const kind = root === prodInstanceRoot() ? 'prod' as const : 'test' as const;
|
|
4631
|
+
let production: ReturnType<typeof resolveCodexQuotaPolicy> | undefined;
|
|
4632
|
+
if (kind === 'test') {
|
|
4633
|
+
try {
|
|
4634
|
+
const { readFileSync } = await import('node:fs');
|
|
4635
|
+
const prodLlm = (JSON.parse(readFileSync(`${prodInstanceRoot()}/config.json`, 'utf8')) as { llm?: Record<string, unknown> }).llm;
|
|
4636
|
+
production = resolveCodexQuotaPolicy(prodLlm);
|
|
4637
|
+
} catch { /* no production config on this machine */ }
|
|
4638
|
+
}
|
|
4639
|
+
const described = await announceLaunchQuotaPolicy({ runId, current: resolvedQuota, universe: { kind, root }, ...(production ? { production } : {}) });
|
|
4640
|
+
launchQuotaPolicy = described.quotaPolicy;
|
|
4641
|
+
debug.log('self-implement.pod', 'quota-policy', { policy: resolvedQuota.policy, source: resolvedQuota.source, universe: kind, productionPolicy: production?.policy ?? null });
|
|
4642
|
+
if (!opts.json) { ui.info(described.line); if (described.warning) ui.warn(described.warning); }
|
|
4643
|
+
// GROK-OPTIONAL (10-05): an expiring grok subscription the host cannot refresh is dropped from the chain here,
|
|
4644
|
+
// so the launch goes on with codex instead of dying later in hostGrokCredentials.
|
|
4645
|
+
let grokSubscriptionUsable = credential?.kind === 'subscription';
|
|
4646
|
+
if (grokSubscriptionUsable && codexPolicyAllowsFallback(quotaPolicy)) {
|
|
4647
|
+
const { podGrokSubscriptionUsable, podGrokSkippedLine } = await import('./task-orchestrator/surfaces/self-implement-pod.js');
|
|
4648
|
+
const grokCheck = podGrokSubscriptionUsable();
|
|
4649
|
+
if (!grokCheck.usable) {
|
|
4650
|
+
grokSubscriptionUsable = false;
|
|
4651
|
+
if (!opts.json) ui.warn(podGrokSkippedLine(grokCheck));
|
|
4589
4652
|
}
|
|
4590
|
-
|
|
4591
|
-
debug.log('self-implement.pod', 'quota-policy', { policy: resolvedQuota.policy, source: resolvedQuota.source, universe: kind, productionPolicy: production?.policy ?? null });
|
|
4592
|
-
if (!opts.json) { ui.info(described.line); if (described.warning) ui.warn(described.warning); }
|
|
4593
|
-
} catch { /* the line is advice; the launch goes on */ }
|
|
4653
|
+
}
|
|
4594
4654
|
const plan = planPodProvider({
|
|
4595
4655
|
codexCandidates: inspectCodexRotation().candidates,
|
|
4596
4656
|
// Per-account caps (대표 default:97) override the Pod default cap — without them default was dropped at 60%.
|
|
4597
4657
|
thresholdPercentByAccount: getUserConfig().llm?.codexAccountRotationThresholdPercentByAccount,
|
|
4598
4658
|
// 대표 한도 정책 한 값(`llm.codexQuotaPolicy`) — 크레딧·폴백을 같이 정한다.
|
|
4599
4659
|
creditsAllowed: codexPolicyAllowsCredits(quotaPolicy),
|
|
4600
|
-
grokSubscription: codexPolicyAllowsFallback(quotaPolicy) &&
|
|
4660
|
+
grokSubscription: codexPolicyAllowsFallback(quotaPolicy) && grokSubscriptionUsable,
|
|
4601
4661
|
grokApiKey: codexPolicyAllowsFallback(quotaPolicy) && credential?.kind === 'api_key',
|
|
4602
4662
|
grokApiKeyOptIn,
|
|
4603
4663
|
});
|
|
@@ -4714,7 +4774,7 @@ const selfOrchestrateCmd = selfCmd
|
|
|
4714
4774
|
debug.log('self-dev.reduce', 'orchestrate-reduce', { runId, shards: shards.length, kind: r.kind });
|
|
4715
4775
|
}
|
|
4716
4776
|
}
|
|
4717
|
-
if (opts.json) { await writeStdoutJson(JSON.stringify(results) + '\n'); await reportRun(results); return; }
|
|
4777
|
+
if (opts.json) { await writeStdoutJson(JSON.stringify(withLaunchQuotaPolicy(results, launchQuotaPolicy)) + '\n'); await reportRun(results); return; }
|
|
4718
4778
|
const done = results.filter((r) => r.status === 'done').length;
|
|
4719
4779
|
const promoted = results.filter((r) => r.prUrl).length;
|
|
4720
4780
|
const incomplete = results.length - done;
|
|
@@ -6811,7 +6871,8 @@ const selfDevCmd = program
|
|
|
6811
6871
|
const { dispatchHarnessOnPod } = await import('./harness/harness-pod-dispatch.js');
|
|
6812
6872
|
const { devAskPodDispatchInput } = await import('./harness/harness-substrate-default.js');
|
|
6813
6873
|
const { classifyHarnessPodExit, harnessPodRunId, recordClassifiedHarnessPodExit, warnRecentIncidentBurst } = await import('./harness/harness-cli-command.js');
|
|
6814
|
-
warnRecentIncidentBurst(
|
|
6874
|
+
warnRecentIncidentBurst(undefined, undefined, undefined,
|
|
6875
|
+
{ entrance: 'cli-dev-ask', seat: opts.seat });
|
|
6815
6876
|
let podOutput = '';
|
|
6816
6877
|
let podRunId: string | undefined;
|
|
6817
6878
|
const previousLeaseId = process.env.ELANOUS_POD_RESERVED_LEASE;
|
|
@@ -6831,7 +6892,8 @@ const selfDevCmd = program
|
|
|
6831
6892
|
}
|
|
6832
6893
|
if (status !== 0) {
|
|
6833
6894
|
const classified = classifyHarnessPodExit({ status }, podOutput, { ...(podRunId ? { runId: podRunId } : {}) });
|
|
6834
|
-
recordClassifiedHarnessPodExit(podRunId ?? harnessPodRunId(podOutput), classified.reason, status
|
|
6895
|
+
recordClassifiedHarnessPodExit(podRunId ?? harnessPodRunId(podOutput), classified.reason, status,
|
|
6896
|
+
{ entrance: 'cli-dev-ask', seat: opts.seat, output: podOutput });
|
|
6835
6897
|
for (const line of classified.lines) console.error(line);
|
|
6836
6898
|
}
|
|
6837
6899
|
completionGuard.conclude();
|
|
@@ -6,24 +6,51 @@ import { createWishCard } from './wish-card.js';
|
|
|
6
6
|
|
|
7
7
|
export const LINEAR_WISH_KEY_MISSING = 'Linear 키가 없습니다 — elanous connector linear 로 먼저 등록하세요';
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
fetchIssues?: typeof fetchLinearIssues;
|
|
16
|
-
};
|
|
17
|
-
}): Promise<{ added: number; duplicate: number; failed: number }> {
|
|
9
|
+
type LinearWishDeps = {
|
|
10
|
+
getApiKey?: () => Promise<string | undefined>;
|
|
11
|
+
fetchIssues?: typeof fetchLinearIssues;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
async function readLinearWishes(teamKey: string, options: { labelOrPrefix?: string; excludeLabel?: string }, deps: LinearWishDeps) {
|
|
18
15
|
const apiKey = await (deps.getApiKey ?? (() => getSecretAsync('connector.linear.apiKey')))();
|
|
19
16
|
if (!apiKey?.trim()) throw new Error(LINEAR_WISH_KEY_MISSING);
|
|
20
|
-
let issues: Awaited<ReturnType<typeof fetchLinearIssues>>;
|
|
21
17
|
try {
|
|
22
|
-
|
|
18
|
+
return await (deps.fetchIssues ?? fetchLinearIssues)({ apiKey, teamKey, ...options });
|
|
23
19
|
} catch (error) {
|
|
24
20
|
const message = error instanceof Error ? error.message : String(error);
|
|
25
21
|
throw new Error(message.replaceAll(apiKey, '[redacted]'));
|
|
26
22
|
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface LinearWishSuggestion { identifier: string; title: string; reason: string }
|
|
26
|
+
|
|
27
|
+
const WISH_PHRASES = [
|
|
28
|
+
{ pattern: /해\s*줘(?:요|라)?/, reason: '요청' },
|
|
29
|
+
{ pattern: /만들어(?:\s*줘(?:요)?)?/, reason: '제작' },
|
|
30
|
+
{ pattern: /원해(?:요)?/, reason: '희망' },
|
|
31
|
+
{ pattern: /있으면\s*좋겠다/, reason: '희망' },
|
|
32
|
+
];
|
|
33
|
+
|
|
34
|
+
export async function suggestLinearWishes({ teamKey, deps = {} }: {
|
|
35
|
+
teamKey: string;
|
|
36
|
+
deps?: LinearWishDeps;
|
|
37
|
+
}): Promise<LinearWishSuggestion[]> {
|
|
38
|
+
const issues = await readLinearWishes(teamKey, { excludeLabel: 'wish' }, deps);
|
|
39
|
+
return issues.flatMap(issue => {
|
|
40
|
+
const match = WISH_PHRASES.find(({ pattern }) => pattern.test(`${issue.title}\n${issue.body}`));
|
|
41
|
+
return match && issue.identifier
|
|
42
|
+
? [{ identifier: issue.identifier, title: Array.from(issue.title).slice(0, 40).join(''), reason: match.reason }]
|
|
43
|
+
: [];
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export async function scanLinearWishes({ teamKey, label = 'wish', store, deps = {} }: {
|
|
48
|
+
teamKey: string;
|
|
49
|
+
label?: string;
|
|
50
|
+
store: CardStore;
|
|
51
|
+
deps?: LinearWishDeps;
|
|
52
|
+
}): Promise<{ added: number; duplicate: number; failed: number }> {
|
|
53
|
+
const issues = await readLinearWishes(teamKey, { labelOrPrefix: label }, deps);
|
|
27
54
|
let added = 0;
|
|
28
55
|
let duplicate = 0;
|
|
29
56
|
let failed = 0;
|
|
@@ -32,6 +59,7 @@ export async function scanLinearWishes({ teamKey, label = 'wish', store, deps =
|
|
|
32
59
|
const result = createWishCard({
|
|
33
60
|
text: `${issue.title}\n\n${issue.body}\n\n${issue.url}`,
|
|
34
61
|
source: 'linear', ref: issue.identifier ?? '',
|
|
62
|
+
replyTo: { surface: 'linear', issueId: issue.ref },
|
|
35
63
|
}, store);
|
|
36
64
|
if (result.created) added++;
|
|
37
65
|
else duplicate++;
|