elanous 0.2.12 → 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/{9906-482c6619b3fdda20.js → 8060-cc008cc6fedfa818.js} +1 -1
- 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-1abfe3aec3be6bdc.js +1 -0
- 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-a67e11ba962337b5.js → page-15374909d8f1aebd.js} +1 -1
- 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-793fa536b3a6ef37.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-e1e47d8d940aba3d.js +1 -0
- 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-e297866051595d81.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/examples/teams/personas/backend.yaml +4 -0
- package/examples/teams/personas/pm.yaml +4 -0
- package/examples/teams/personas/qa.yaml +4 -0
- package/examples/teams/small-product.yaml +20 -0
- package/graphs/lecture/lecture-note.yaml +28 -0
- package/graphs/lecture/recipes.yaml +17 -0
- 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 +2 -1
- package/scripts/audit-test-state-writes.ts +72 -17
- package/scripts/brand/check.ts +186 -0
- package/scripts/context-hooks/emit.ts +36 -14
- package/scripts/coord-channel-watch.sh +1 -0
- package/scripts/coord-post.sh +11 -1
- package/scripts/discord-seat-probe.ts +144 -0
- 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/hq-standby.sh +65 -0
- package/scripts/hq/standby-promote.ts +173 -0
- package/scripts/hq/standby-role.ts +59 -0
- package/scripts/hq/standby-snapshot.ts +226 -0
- package/scripts/hq/standby-verify.ts +223 -0
- package/scripts/lecture-note/node.ts +256 -0
- package/scripts/lessons/handbook-page.ts +85 -0
- package/scripts/release-loop/gate-node.ts +62 -15
- package/scripts/release-loop/ops-upgrade-node.ts +17 -5
- 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 +57 -11
- 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/omni-crawl/SKILL.md +16 -16
- package/skills/omni-crawl/package-lock.json +1 -544
- package/skills/omni-crawl/package.json +3 -4
- package/skills/omni-crawl/scripts/main.ts +13 -13
- package/skills/omni-crawl/scripts/monitor.ts +8 -8
- package/skills/omni-crawl/src/apify.ts +13 -2
- package/skills/omni-crawl/src/capture.ts +6 -0
- package/skills/omni-crawl/src/registry.ts +2 -2
- package/skills/omni-crawl/src/stdout-json.ts +4 -0
- package/skills/omni-digest/SKILL.md +10 -11
- package/skills/omni-digest/package-lock.json +0 -545
- package/skills/omni-digest/package.json +2 -5
- package/skills/omni-digest/scripts/main.ts +3 -3
- package/skills/omni-digest/src/delegate.ts +5 -5
- package/skills/omni-digest/src/fetch.ts +2 -2
- package/skills/omni-digest/src/media.ts +25 -11
- package/skills/omni-digest/src/visual.ts +3 -2
- package/skills/omni-market/scripts/main.ts +1 -1
- package/skills/omni-market/scripts/stdout-json.ts +4 -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/identity-env.ts +11 -0
- package/src/agent-mission/driver.ts +15 -10
- package/src/agent-mission/duo.ts +94 -0
- package/src/agent-mission/pty-decision.ts +1 -1
- package/src/autopilot/build/llm-conflict-merge.ts +45 -8
- package/src/autopilot/build/merge-intent.ts +53 -0
- package/src/autopilot/track-agent-shadow.ts +2 -1
- package/src/autopilot/track-agent.ts +4 -1
- package/src/boot/daemon-prompt-request.ts +3 -0
- package/src/boot/daemon-prompt-turn.ts +6 -2
- package/src/boot/daemon-runtime.ts +48 -16
- 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 +84 -20
- 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/outputs-cli.ts +92 -0
- package/src/cli/persona-cli.ts +29 -0
- package/src/cli/pod-cli.ts +10 -3
- package/src/cli/pr-cli.ts +44 -2
- package/src/cli/private-ledger-commands.ts +54 -4
- package/src/cli/pty-drive-verdict.ts +11 -2
- package/src/cli/release-cli.ts +248 -45
- package/src/cli/rfc-status.ts +91 -0
- package/src/connectors/linear.ts +5 -3
- package/src/context-bus/condense.ts +189 -0
- package/src/context-bus/context-day.ts +82 -0
- package/src/context-bus/context-now-public.ts +86 -0
- package/src/context-bus/context-now-surfaces.ts +70 -4
- package/src/context-bus/context-now.ts +4 -3
- package/src/context-bus/long-term-memory.ts +201 -24
- package/src/context-bus/nightly-condense.ts +1 -1
- 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-cards.ts +13 -7
- package/src/decisions/decision-ledger.ts +83 -18
- 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-kinds/registry.ts +2 -2
- package/src/graph-runner/graph-approval-card.ts +2 -2
- package/src/graph-runner/graph-grow.ts +64 -11
- package/src/graph-runner/graph-tick.ts +13 -2
- package/src/graph-runner/runner.ts +111 -22
- package/src/harness/harness-cli-command.ts +178 -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 +37 -0
- package/src/harness/harness-queue.ts +494 -0
- package/src/harness/helper-scan.ts +139 -0
- package/src/harness/model-input-observation.ts +37 -0
- package/src/hooks/github-shadow.ts +72 -2
- package/src/hooks/receiver.ts +4 -2
- package/src/hq/hq.ts +318 -0
- package/src/hq/lease.ts +210 -0
- package/src/hq/ledger-write-target.ts +41 -0
- package/src/hq/promote.ts +301 -0
- package/src/index.ts +281 -27
- package/src/intake-plane/discord-seat-work.ts +13 -6
- package/src/intake-plane/linear-wish.ts +72 -0
- package/src/intake-plane/persona-answer.ts +5 -0
- package/src/intake-plane/telegram-seat-work.ts +13 -6
- 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 +180 -0
- package/src/loops/contract/examples/orchestrator.loop.yaml +1 -1
- package/src/loops/loop-cli.ts +72 -6
- package/src/loops/neighbors.ts +40 -0
- package/src/loops/observe.ts +59 -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 +180 -0
- package/src/loops/orchestrator/resync-shadow.ts +227 -0
- package/src/loops/orchestrator/tick.ts +302 -0
- package/src/loops/orchestrator/traffic.ts +165 -0
- package/src/loops/package/verify.ts +125 -0
- package/src/loops/registry.ts +454 -9
- package/src/loops/status-rows.ts +118 -0
- package/src/loops/verdict.ts +34 -0
- package/src/maturity/bot-slash-catalog.ts +66 -0
- package/src/maturity/feature-maturity.ts +8 -5
- package/src/memory.ts +37 -4
- package/src/nexus/api/card-followup-route.ts +118 -0
- package/src/nexus/api/dashboard.ts +6 -1
- package/src/nexus/api/decisions-route.ts +68 -0
- package/src/nexus/api/http-server.ts +65 -2
- package/src/nexus/api/merge-approvals-gate.ts +1 -1
- package/src/nexus/api/personas.ts +2 -1
- package/src/nexus/api/schedules-read.ts +5 -1
- package/src/nexus/api/skill-exec-route.ts +97 -0
- package/src/nexus/api/task-cards-api.ts +44 -5
- package/src/nexus/api/workflows.ts +2 -1
- package/src/persona/loader.ts +18 -0
- package/src/persona/persona-todo.ts +14 -9
- package/src/persona/presets.ts +2 -1
- package/src/persona/team.ts +105 -0
- package/src/persona/types.ts +4 -0
- package/src/pod-lease/host-lease.ts +130 -20
- 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/landed-but-yellow.ts +12 -0
- 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 +73 -15
- package/src/seat-dispatch/seat-questions.ts +58 -0
- package/src/seat-loop/seat-loop.ts +495 -46
- package/src/seat-loop/seat-pack.ts +127 -0
- package/src/self-dev/ask-launch-flow.ts +3 -0
- 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/l8-merge-queue.ts +157 -1
- 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/steward/launch.ts +5 -1
- package/src/steward/readiness.ts +125 -0
- package/src/steward/rescue.ts +75 -0
- package/src/steward/triage.ts +48 -9
- package/src/task-cards/card-cli.ts +86 -0
- package/src/task-cards/card-store.ts +10 -6
- package/src/task-orchestrator/surfaces/pod-lease.ts +23 -8
- package/src/task-orchestrator/surfaces/pod-ledger-collect.ts +46 -6
- package/src/task-orchestrator/surfaces/pod-pool.ts +27 -10
- package/src/task-orchestrator/surfaces/self-implement-pod.ts +112 -18
- package/src/telegram-commands.ts +27 -3
- package/src/telegram-dispatch-forward.ts +1 -1
- package/src/telegram-fabric-plan.ts +90 -0
- package/src/telegram-loops-command.ts +44 -0
- package/src/telegram-project-command.ts +10 -4
- package/src/telegram.ts +5 -0
- package/src/tool-runtime/elanous-control-runtimes.ts +2 -1
- package/src/user-config.ts +204 -16
- package/src/version/packed-revision.json +1 -1
- package/src/workflow-runtime/executor.ts +4 -0
- package/src/workflow-runtime/node-catalog.ts +13 -0
- package/src/workflow-runtime/nodes/knowledge.ts +38 -0
- package/src/workflow-runtime/schema.ts +17 -1
- package/src/workflow-runtime/storage.ts +31 -13
- package/src/workflow-runtime/types.ts +13 -1
- package/apps/pwa/out/_next/static/-6XrAoD3eU7NyMHAE4xPS/_buildManifest.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1147-466ec15afacc1a65.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1159-0c5ef8bc21cb6afb.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1394-ee3795b112907122.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-2e522a8ac5f2afde.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/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/6751-bb3595948b2e311b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7274-19b7279f18b47ab3.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7277-0b2fc7c16ba2d64b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7551-a0e0707b79164ddd.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7758-0b018a9e4fc56200.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-0259994816604c49.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/9920-5cfdc5e8fd209825.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-cb169620945d1952.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/autopilot/page-cefe8169e36cf62a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/board/page-d2e35228bc497119.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/botlab/page-5c05b0da4bc1392c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/bots/page-e0ccdb576812b523.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/chat/page-813c4a52c9d05ca8.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/consult/page-4731f2c215358937.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/control/page-a022b257950f39e6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-a2ffc717e04b8b0a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/editor/page-b59ee4e70fc18fcf.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/field/page-c172dabd796eb7a3.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-1efd0cb9e0ed9d13.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/intake/page-d5dc7ff69a37b4cf.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/layout-e71c84189e8c683e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/live/page-1d6a533ce5a6efcb.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/market/page-dfca9b7d2de0b135.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/morning/page-6dd3bfbbde2a5c2c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/not-found-490653e8d4cc8852.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-77447356baad75d2.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-29e961191f6057df.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-c822f55b884d9104.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-fa4ccfc432424e52.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/outputs/page-6d604227e1664b71.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/page-9c7e8f744a765daf.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-4048543b4b7ad5e0.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-4c3a3126abed551a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-64b0aecb1ca75e75.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-4292d0149020e8a7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/page-db14d0be7f1c460c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-e5d8072cd7cfef73.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-9547b5cec7cf76bf.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/share/page-e6dcb83265530636.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-45248f08d6aecae5.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-2cddeb9e2aeedc55.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/today/page-baba2e047872fb0f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/trace/page-e895da4ec9cac345.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/vault/page-4344faf20216233b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-698bb29f11864b89.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-bd6a6dc1ddc273f2.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-2e9e2fb01c9da1c9.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-d62ceb13411ebba4.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/{-6XrAoD3eU7NyMHAE4xPS → x6mjI6vqOkVVu0jFuDkz1}/_ssgManifest.js +0 -0
package/src/steward/launch.ts
CHANGED
|
@@ -7,6 +7,7 @@ import { redactSecrets } from '../task-cards/card-store.js';
|
|
|
7
7
|
import { preLaunchGate, type PreLaunchGateDecision } from '../execution-loop/launch-gate.js';
|
|
8
8
|
import type { BudgetDecision } from '../self-implement/budget-gate.js';
|
|
9
9
|
import type { ScheduledDecision, StewardSettings, TriageIssue } from './triage.js';
|
|
10
|
+
import { launchModeOf, rescueAllows } from './rescue.js';
|
|
10
11
|
|
|
11
12
|
/** `completed` = the run finished without a merge (a non-code artifact, or nothing to deliver) — never reported as a failure. */
|
|
12
13
|
export type LaunchStatus = 'shadow' | 'launched' | 'skipped-budget' | 'blocked-duplicate' | 'blocked-location' | 'failed' | 'running' | 'merged' | 'completed';
|
|
@@ -50,7 +51,7 @@ const RUN_ID = /\brun-[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{1
|
|
|
50
51
|
const DEFAULT_POOL = 'pool-node-b@node-b:8';
|
|
51
52
|
// `harness budget --json` outcomes: proceed | next-provider | wait-reset | stop. next-provider still runs (the harness switches provider itself).
|
|
52
53
|
const BUDGET_PROCEED: readonly string[] = ['proceed', 'next-provider'];
|
|
53
|
-
const launchMode = (settings: StewardSettings): 'off' | 'shadow' | 'live' => settings.mode ?? settings.launch ?? 'shadow';
|
|
54
|
+
const launchMode = (settings: StewardSettings): 'off' | 'shadow' | 'live' => launchModeOf(settings.mode ?? settings.launch ?? 'shadow');
|
|
54
55
|
const cli = (args: string[]) => ['bun', 'bin/elanous.mjs', ...(process.env.NODE_ENV === 'test' ? ['--test'] : []), ...args];
|
|
55
56
|
const sourceOf = (issue: TriageIssue): string => /^출처: (telegram|pwa|tui|cli)$/m.exec(issue.body)?.[1] ?? `linear:${issue.identifier}`;
|
|
56
57
|
const promptOf = (issue: TriageIssue): string => `${issue.title}\n${issue.body.slice(0, 1500)}`;
|
|
@@ -125,6 +126,7 @@ export function launch(item: LaunchItem, deps: LaunchDeps): LaunchEntry {
|
|
|
125
126
|
const { ledger, settings, root } = deps;
|
|
126
127
|
if (item.row.rung !== 4 || item.row.hitlReason || item.row.disposition !== 'now' || (ledger.launches[item.issue.identifier] && !['skipped-budget', 'blocked-duplicate', 'blocked-location'].includes(ledger.launches[item.issue.identifier].status) && !(launchMode(settings) === 'live' && ledger.launches[item.issue.identifier].status === 'shadow'))) throw new Error('steward launch is not eligible');
|
|
127
128
|
if (launchMode(settings) === 'off') throw new Error('steward launch is off');
|
|
129
|
+
if (!rescueAllows(settings.mode, 'launch', { issue: item.issue.identifier })) throw new Error('steward rescue refused launch');
|
|
128
130
|
const key = item.issue.identifier;
|
|
129
131
|
const entry: LaunchEntry = { issue: key, title: redactSecrets(item.issue.title), source: item.source, command: redactSecrets(item.command), status: 'shadow' };
|
|
130
132
|
if (launchMode(settings) !== 'live') {
|
|
@@ -187,6 +189,7 @@ export function launch(item: LaunchItem, deps: LaunchDeps): LaunchEntry {
|
|
|
187
189
|
export function raiseHitl(issue: TriageIssue, row: ScheduledDecision, deps: LaunchDeps): HitlEntry {
|
|
188
190
|
const old = deps.ledger.hitl[issue.identifier];
|
|
189
191
|
if (old && (old.attempted || launchMode(deps.settings) !== 'live')) return old;
|
|
192
|
+
if (!rescueAllows(deps.settings.mode, 'decision-card', { issue: issue.identifier })) return old ?? { issue: issue.identifier, reason: redactSecrets(row.hitlReason ?? row.why), raised: false };
|
|
190
193
|
const entry: HitlEntry = old ?? { issue: issue.identifier, reason: redactSecrets(row.hitlReason ?? row.why), raised: false };
|
|
191
194
|
deps.ledger.hitl[issue.identifier] = entry;
|
|
192
195
|
if (launchMode(deps.settings) !== 'live') {
|
|
@@ -215,6 +218,7 @@ function alive(pid: number | undefined): boolean {
|
|
|
215
218
|
|
|
216
219
|
export function collectOutcomes(ledger: LaunchLedger, deps: LaunchDeps): LaunchEntry[] {
|
|
217
220
|
if (launchMode(deps.settings) !== 'live') return [];
|
|
221
|
+
if (!rescueAllows(deps.settings.mode, 'harvest')) return [];
|
|
218
222
|
const done: LaunchEntry[] = [];
|
|
219
223
|
for (const entry of Object.values(ledger.launches)) {
|
|
220
224
|
if (entry.reported || entry.status === 'shadow' || entry.status === 'skipped-budget' || entry.status === 'blocked-duplicate' || entry.status === 'blocked-location') continue;
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { appendFileSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { effectiveInstanceRoot } from '../instance/resolve.js';
|
|
4
|
+
import type { LaunchPlan } from './launch.js';
|
|
5
|
+
import type { ScheduledDecision } from './triage.js';
|
|
6
|
+
|
|
7
|
+
// Planning estimates, not invoices: one harness run per launch, one Pod-hour per run.
|
|
8
|
+
// No model/token or runtime telemetry is present in the shadow decision line.
|
|
9
|
+
const ESTIMATED_LLM_USD_PER_LAUNCH = 2;
|
|
10
|
+
const ESTIMATED_POD_USD_PER_LAUNCH = 1;
|
|
11
|
+
const CATEGORY = 'steward.readiness';
|
|
12
|
+
const EVENT = 'shadow-tick';
|
|
13
|
+
|
|
14
|
+
export interface ShadowTick {
|
|
15
|
+
at: string;
|
|
16
|
+
decisions: Array<{ issue: string; rung: ScheduledDecision['rung']; disposition: ScheduledDecision['disposition'] }>;
|
|
17
|
+
actions: Array<{ issue: string; kind: 'launch' | 'hitl'; llmUsd: number; podUsd: number }>;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function ledgerPath(root: string): string { return join(root, 'steward', 'readiness.jsonl'); }
|
|
21
|
+
|
|
22
|
+
/** Called by the shadow schedule after its launch/HITL candidates have been selected. */
|
|
23
|
+
export function recordShadowTick(root: string, rows: ScheduledDecision[], plan: LaunchPlan, now: Date = new Date()): ShadowTick {
|
|
24
|
+
const tick: ShadowTick = {
|
|
25
|
+
at: now.toISOString(),
|
|
26
|
+
decisions: rows.map(({ issue, rung, disposition }) => ({ issue, rung, disposition })),
|
|
27
|
+
actions: [
|
|
28
|
+
...plan.launches.map(({ issue }) => ({ issue: issue.identifier, kind: 'launch' as const, llmUsd: ESTIMATED_LLM_USD_PER_LAUNCH, podUsd: ESTIMATED_POD_USD_PER_LAUNCH })),
|
|
29
|
+
...plan.hitl.map(({ issue }) => ({ issue: issue.identifier, kind: 'hitl' as const, llmUsd: 0, podUsd: 0 })),
|
|
30
|
+
],
|
|
31
|
+
};
|
|
32
|
+
appendFileSync(ledgerPath(root), `${JSON.stringify(tick)}\n`);
|
|
33
|
+
return tick;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface ReadinessReport {
|
|
37
|
+
since: string;
|
|
38
|
+
until: string;
|
|
39
|
+
ticks: number;
|
|
40
|
+
judgments: number;
|
|
41
|
+
distribution: Record<string, number>;
|
|
42
|
+
actions: Array<ShadowTick['actions'][number] & { at: string }>;
|
|
43
|
+
budgetGate: { estimatedLlmUsd: number; estimatedPodUsd: number; estimatedTotalUsd: number; basis: string; verdict: 'estimate-only' };
|
|
44
|
+
hitlGate: { requiringHuman: number };
|
|
45
|
+
source: { decisions: string; logs: string };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Snapshot-only reader: no CardStore, stage, config setter, logger or write-capable DB handle. */
|
|
49
|
+
export function stewardReadiness(root: string, sinceHours = 24, now: Date = new Date()): ReadinessReport {
|
|
50
|
+
if (!Number.isFinite(sinceHours) || sinceHours <= 0) throw new Error('--since must be a positive duration such as 24h');
|
|
51
|
+
const since = new Date(now.getTime() - sinceHours * 3_600_000);
|
|
52
|
+
if (!Number.isFinite(since.getTime())) throw new Error('--since is out of range');
|
|
53
|
+
const path = ledgerPath(root);
|
|
54
|
+
let text: string;
|
|
55
|
+
try { text = readFileSync(path, 'utf8'); }
|
|
56
|
+
catch (error) {
|
|
57
|
+
if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error;
|
|
58
|
+
text = '';
|
|
59
|
+
}
|
|
60
|
+
const ticks: ShadowTick[] = [];
|
|
61
|
+
for (const [index, line] of text.split('\n').entries()) {
|
|
62
|
+
if (!line.trim()) continue;
|
|
63
|
+
let parsed: ShadowTick;
|
|
64
|
+
try { parsed = JSON.parse(line) as ShadowTick; }
|
|
65
|
+
catch { throw new Error(`Invalid steward shadow decision line ${index + 1}`); }
|
|
66
|
+
if (!parsed || typeof parsed.at !== 'string' || !Number.isFinite(Date.parse(parsed.at)) ||
|
|
67
|
+
!Array.isArray(parsed.decisions) || !parsed.decisions.every(row => row && typeof row.issue === 'string' &&
|
|
68
|
+
[0, 1, 2, 3, 4, 5, 'hitl'].includes(row.rung) && ['now', 'wait', 'hitl'].includes(row.disposition)) ||
|
|
69
|
+
!Array.isArray(parsed.actions) || !parsed.actions.every(action => action && typeof action.issue === 'string' &&
|
|
70
|
+
['launch', 'hitl'].includes(action.kind) && Number.isFinite(action.llmUsd) && action.llmUsd >= 0 &&
|
|
71
|
+
Number.isFinite(action.podUsd) && action.podUsd >= 0))
|
|
72
|
+
throw new Error(`Invalid steward shadow decision line ${index + 1}`);
|
|
73
|
+
const at = Date.parse(parsed.at);
|
|
74
|
+
if (at >= since.getTime() && at <= now.getTime()) ticks.push(parsed);
|
|
75
|
+
}
|
|
76
|
+
const distribution: Record<string, number> = {};
|
|
77
|
+
const actions = ticks.flatMap(tick => tick.actions.map(action => ({ at: tick.at, ...action })));
|
|
78
|
+
for (const row of ticks.flatMap(tick => tick.decisions)) {
|
|
79
|
+
const key = `${row.disposition} / rung ${row.rung}`;
|
|
80
|
+
distribution[key] = (distribution[key] ?? 0) + 1;
|
|
81
|
+
}
|
|
82
|
+
const estimatedLlmUsd = actions.reduce((sum, action) => sum + action.llmUsd, 0);
|
|
83
|
+
const estimatedPodUsd = actions.reduce((sum, action) => sum + action.podUsd, 0);
|
|
84
|
+
return {
|
|
85
|
+
since: since.toISOString(), until: now.toISOString(), ticks: ticks.length,
|
|
86
|
+
judgments: ticks.reduce((sum, tick) => sum + tick.decisions.length, 0), distribution, actions,
|
|
87
|
+
budgetGate: { estimatedLlmUsd, estimatedPodUsd, estimatedTotalUsd: estimatedLlmUsd + estimatedPodUsd,
|
|
88
|
+
basis: 'Planning assumption per launch: LLM $2 + Pod $1 (one Pod-hour); not metered usage or budget approval', verdict: 'estimate-only' },
|
|
89
|
+
hitlGate: { requiringHuman: actions.filter(action => action.kind === 'hitl').length },
|
|
90
|
+
source: { decisions: path, logs: `elanous logs --category ${CATEGORY} --event ${EVENT} --since ${sinceHours}h` },
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export function formatStewardReadiness(report: ReadinessReport): string {
|
|
95
|
+
return [
|
|
96
|
+
`Steward shadow readiness | ${report.since} → ${report.until}`,
|
|
97
|
+
`Ticks | ${report.ticks}`,
|
|
98
|
+
`Judgments | ${report.judgments}`,
|
|
99
|
+
`Judgment distribution | ${Object.entries(report.distribution).map(([kind, count]) => `${kind}: ${count}`).join(' · ') || 'none'}`,
|
|
100
|
+
`If live, actions | ${report.actions.map(a => `${a.at} ${a.kind} ${a.issue}`).join(' · ') || 'none'}`,
|
|
101
|
+
`Budget gate | LLM ~$${report.budgetGate.estimatedLlmUsd} · Pod ~$${report.budgetGate.estimatedPodUsd} · total ~$${report.budgetGate.estimatedTotalUsd} (estimate only; not approval)`,
|
|
102
|
+
`HITL gate | ${report.hitlGate.requiringHuman} human confirmations`,
|
|
103
|
+
`Estimate basis | ${report.budgetGate.basis}`,
|
|
104
|
+
`Shadow decisions | ${report.source.decisions}`,
|
|
105
|
+
`First-class logs | ${report.source.logs}`,
|
|
106
|
+
].join('\n');
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export function parseReadinessSince(value: string): number {
|
|
110
|
+
if (!/^(?:\d+)(?:\.\d+)?h$/.test(value)) throw new Error('--since must be a positive duration such as 24h');
|
|
111
|
+
const hours = Number(value.slice(0, -1));
|
|
112
|
+
if (!Number.isFinite(hours) || hours <= 0) throw new Error('--since must be a positive duration such as 24h');
|
|
113
|
+
return hours;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export function runStewardReadinessCli(opts: { since: string; json?: boolean }, root = effectiveInstanceRoot()): number {
|
|
117
|
+
try {
|
|
118
|
+
const report = stewardReadiness(root, parseReadinessSince(opts.since));
|
|
119
|
+
console.log(opts.json ? JSON.stringify(report) : formatStewardReadiness(report));
|
|
120
|
+
return 0;
|
|
121
|
+
} catch (error) {
|
|
122
|
+
console.error(`steward readiness: ${error instanceof Error ? error.message : String(error)}`);
|
|
123
|
+
return 1;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
// HQ-SEAT — steward «구조 모드»(rescue): after an HQ move the seat sessions are gone and the steward keeps
|
|
2
|
+
// the line live, but only for the four reversible, internal actions below (OP 10-04 10:22 · 대표 directive).
|
|
3
|
+
import { debug } from '../debug/log.js';
|
|
4
|
+
|
|
5
|
+
export type StewardMode = 'off' | 'shadow' | 'live' | 'rescue';
|
|
6
|
+
|
|
7
|
+
/** The whole rescue allow-list: launch queued goals · harvest finished runs · internal stuck alerts · decision cards. */
|
|
8
|
+
export const RESCUE_ALLOWED = ['launch', 'harvest', 'alert', 'decision-card'] as const;
|
|
9
|
+
export type RescueAllowedAction = typeof RESCUE_ALLOWED[number];
|
|
10
|
+
export type RescueAction = RescueAllowedAction | 'publish' | 'release-run' | 'external-post' | 'delete' | 'config-change' | 'git-force';
|
|
11
|
+
|
|
12
|
+
/** Shared, fail-closed policy for rescue and seat-loop live-safe decisions. */
|
|
13
|
+
export function isRescueAllowedAction(action: string): action is RescueAllowedAction {
|
|
14
|
+
return (RESCUE_ALLOWED as readonly string[]).includes(action);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** Launch semantics of a steward mode: rescue launches like live. */
|
|
18
|
+
export function launchModeOf(mode: StewardMode): 'off' | 'shadow' | 'live' {
|
|
19
|
+
return mode === 'rescue' ? 'live' : mode;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Rescue-mode action gate. Outside rescue the steward's own mode rules decide (returns true).
|
|
24
|
+
* In rescue only RESCUE_ALLOWED pass; every decision is observed as steward.rescue/<action> or steward.rescue/refused.
|
|
25
|
+
*/
|
|
26
|
+
export function rescueAllows(mode: StewardMode | undefined, action: RescueAction, data: Record<string, unknown> = {}): boolean {
|
|
27
|
+
if (mode !== 'rescue') return true;
|
|
28
|
+
const allowed = isRescueAllowedAction(action);
|
|
29
|
+
try { debug.log('steward.rescue', allowed ? action : 'refused', { action, ...data }); } catch { /* observation is fail-soft */ }
|
|
30
|
+
return allowed;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface StewardModeStore {
|
|
34
|
+
read: () => StewardMode;
|
|
35
|
+
write: (mode: StewardMode) => void;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const MODES: readonly StewardMode[] = ['off', 'shadow', 'live', 'rescue'];
|
|
39
|
+
|
|
40
|
+
export function parseStewardMode(value: string): StewardMode | null {
|
|
41
|
+
return (MODES as readonly string[]).includes(value) ? value as StewardMode : null;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** `elanous steward mode [<mode>]` — HQ promotion sets rescue; a reattaching human seat sets shadow. */
|
|
45
|
+
export async function runStewardModeCli(value: string | undefined, opts: { json?: boolean }, store?: StewardModeStore): Promise<number> {
|
|
46
|
+
const s = store ?? await defaultModeStore();
|
|
47
|
+
const before = s.read();
|
|
48
|
+
if (value === undefined) {
|
|
49
|
+
console.log(opts.json ? JSON.stringify({ mode: before }) : before);
|
|
50
|
+
return 0;
|
|
51
|
+
}
|
|
52
|
+
const mode = parseStewardMode(value);
|
|
53
|
+
if (!mode) { console.error(`steward mode: unknown mode ${JSON.stringify(value)} (off|shadow|live|rescue)`); return 2; }
|
|
54
|
+
s.write(mode);
|
|
55
|
+
const after = s.read();
|
|
56
|
+
try { debug.log('steward.rescue', 'mode-set', { before, after }); } catch { /* observation is fail-soft */ }
|
|
57
|
+
if (after !== mode) { console.error(`steward mode: saved value did not stick (read ${after})`); return 1; }
|
|
58
|
+
console.log(opts.json ? JSON.stringify({ before, mode: after }) : `steward mode ${before} → ${after}`);
|
|
59
|
+
return 0;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async function defaultModeStore(): Promise<StewardModeStore> {
|
|
63
|
+
const uc = await import('../user-config.js');
|
|
64
|
+
return {
|
|
65
|
+
read: () => (uc.getUserConfig().loops?.steward?.mode ?? 'shadow') as StewardMode,
|
|
66
|
+
write: (mode) => {
|
|
67
|
+
const cfg = uc.getUserConfig();
|
|
68
|
+
uc.backupUserConfig(uc.userConfigPath());
|
|
69
|
+
const loops = { ...(cfg.loops ?? {}) };
|
|
70
|
+
loops.steward = { ...(loops.steward ?? {}), mode };
|
|
71
|
+
uc.saveUserConfig({ ...cfg, loops });
|
|
72
|
+
uc.reloadUserConfig();
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
}
|
package/src/steward/triage.ts
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
1
2
|
import { closeSync, existsSync, openSync, readFileSync, writeFileSync, mkdirSync, rmSync } from 'node:fs';
|
|
3
|
+
import { launchModeOf, rescueAllows } from './rescue.js';
|
|
2
4
|
import { join } from 'node:path';
|
|
3
5
|
import { fetchLinearIssues } from '../connectors/linear.js';
|
|
4
6
|
import { getSecretAsync } from '../nexus/config/secrets/index.js';
|
|
5
7
|
import { getUserConfig } from '../user-config.js';
|
|
6
8
|
import { emitDecision } from '../live/detail-switch.js';
|
|
7
9
|
import { debug, redactSecretText } from '../debug/log.js';
|
|
10
|
+
import { loopEvent } from '../loops/observe.js';
|
|
8
11
|
import { effectiveInstanceRoot } from '../instance/resolve.js';
|
|
9
12
|
import { redactSecrets } from '../task-cards/card-store.js';
|
|
10
13
|
import { directiveHash } from './directive.js';
|
|
@@ -33,7 +36,8 @@ export interface TriageDecision {
|
|
|
33
36
|
}
|
|
34
37
|
export interface ScheduledDecision extends TriageDecision { disposition: 'now' | 'wait' | 'hitl' }
|
|
35
38
|
export interface StewardSettings {
|
|
36
|
-
|
|
39
|
+
/** `rescue` (HQ-SEAT): live launches/harvest/alerts/decision cards only — see ./rescue.ts. */
|
|
40
|
+
mode?: 'off' | 'shadow' | 'live' | 'rescue'; linearTeam?: string;
|
|
37
41
|
roles?: Record<string, { maxConcurrent?: number }>;
|
|
38
42
|
budget?: number;
|
|
39
43
|
tracks?: Record<string, string>;
|
|
@@ -62,11 +66,14 @@ export interface StewardDeps {
|
|
|
62
66
|
stageOwnedByCommand?: boolean;
|
|
63
67
|
}
|
|
64
68
|
|
|
69
|
+
/** rescue launches/harvests like live (HQ-SEAT). */
|
|
70
|
+
const liveLike = (mode: StewardSettings['mode']): boolean => mode !== undefined && launchModeOf(mode) === 'live';
|
|
71
|
+
|
|
65
72
|
export function stewardSettings(): StewardSettings {
|
|
66
73
|
return getUserConfig().loops?.steward ?? { mode: 'shadow' };
|
|
67
74
|
}
|
|
68
75
|
|
|
69
|
-
function effectiveStewardMode(settings: StewardSettings): 'off' | 'shadow' | 'live' {
|
|
76
|
+
function effectiveStewardMode(settings: StewardSettings): 'off' | 'shadow' | 'live' | 'rescue' {
|
|
70
77
|
return settings.mode ?? settings.launch ?? 'shadow';
|
|
71
78
|
}
|
|
72
79
|
|
|
@@ -115,6 +122,9 @@ export async function triageIssues(issues: TriageIssue[], judge?: NonNullable<St
|
|
|
115
122
|
// Inventory is shared by every classification in this batch; do not spawn --help per issue.
|
|
116
123
|
let inventory: ReturnType<typeof installedCapabilities> | undefined;
|
|
117
124
|
const signal = (result: TriageDecision): void => {
|
|
125
|
+
try { debug.log('steward.triage', 'decision', {
|
|
126
|
+
kind: String(result.rung), target: result.issue, wouldAct: false,
|
|
127
|
+
}); } catch { /* logging must not change the judgment */ }
|
|
118
128
|
decide({ kind: result.rung === 'hitl' ? 'ESCALATE' : 'ROUTE', what: result.issue, reason: result.why,
|
|
119
129
|
purpose: 'steward triage', target: result.owner ? `${result.rung} · ${result.owner}` : String(result.rung), refs: { issue: result.issue } });
|
|
120
130
|
};
|
|
@@ -279,6 +289,9 @@ export async function runStewardStage(stage: 'sync' | 'triage' | 'schedule' | 'r
|
|
|
279
289
|
const fetchFn = deps.fetch ?? fetch;
|
|
280
290
|
const skip = skippedPath(root);
|
|
281
291
|
if (stage !== 'sync' && existsSync(skip)) return;
|
|
292
|
+
const runId = (graphRunId() ?? process.env.ELANOUS_RUN_ID?.trim()) || `steward-stage-${randomUUID()}`;
|
|
293
|
+
let outcome: 'ok' | 'fail' = 'fail';
|
|
294
|
+
try {
|
|
282
295
|
const key = await (deps.getSecret ?? getSecretAsync)('connector.linear.apiKey');
|
|
283
296
|
if (!key) throw new Error('connector.linear.apiKey missing');
|
|
284
297
|
if (stage === 'sync') {
|
|
@@ -368,6 +381,9 @@ export async function runStewardStage(stage: 'sync' | 'triage' | 'schedule' | 'r
|
|
|
368
381
|
? { ...row, rung: 'hitl' as const, disposition: 'hitl' as const, hitlReason: 'other' as const, why: 'working-backwards draft invalid' }
|
|
369
382
|
: row);
|
|
370
383
|
}
|
|
384
|
+
for (const row of rows) try { debug.log('steward.schedule', 'decision', {
|
|
385
|
+
kind: row.disposition, target: row.issue, wouldAct: row.disposition === 'now' && liveLike(settings.mode),
|
|
386
|
+
}); } catch { /* logging must not change the schedule */ }
|
|
371
387
|
writeFileSync(schedule, JSON.stringify(rows));
|
|
372
388
|
const ledger = readLaunchLedger(root);
|
|
373
389
|
const launchDeps: LaunchDeps = { root, settings, ledger, command: deps.launchCommand, spawn: deps.spawnLaunch, gate: deps.launchGate };
|
|
@@ -384,6 +400,11 @@ export async function runStewardStage(stage: 'sync' | 'triage' | 'schedule' | 'r
|
|
|
384
400
|
const entry = launch(item, launchDeps);
|
|
385
401
|
recordLaunchOnCard(item.issue, entry, cardDeps);
|
|
386
402
|
}
|
|
403
|
+
if (settings.mode === 'shadow') {
|
|
404
|
+
const { recordShadowTick } = await import('./readiness.js');
|
|
405
|
+
const tick = recordShadowTick(root, rows, plan, (deps.now ?? (() => new Date()))());
|
|
406
|
+
debug.log('steward.readiness', 'shadow-tick', tick);
|
|
407
|
+
}
|
|
387
408
|
} else {
|
|
388
409
|
const rows = JSON.parse(readFileSync(schedule, 'utf8')) as ScheduledDecision[];
|
|
389
410
|
const path = reportPath(root);
|
|
@@ -422,7 +443,7 @@ export async function runStewardStage(stage: 'sync' | 'triage' | 'schedule' | 'r
|
|
|
422
443
|
const issue = issues.find(item => item.identifier === outcome.issue) ??
|
|
423
444
|
{ identifier: outcome.issue, title: outcome.title, ref: '', body: '' };
|
|
424
445
|
recordOutcomeOnCard(issue, outcome, { root, store: deps.cardStore, now: deps.now });
|
|
425
|
-
notifyOutcome(outcome, { root, ledger, shadow: settings.mode
|
|
446
|
+
notifyOutcome(outcome, { root, ledger, shadow: !(settings.mode === 'live' || settings.mode === 'rescue' && rescueAllows(settings.mode, 'alert', { issue: outcome.issue })), ...deps.outcomeNotify });
|
|
426
447
|
const prefix = ['telegram', 'pwa', 'tui', 'cli'].includes(outcome.source) ? `${outcome.source} · ` : '';
|
|
427
448
|
await (deps.sendDigest ?? sendStewardDigest)(`${prefix}${issue.title} · PR ${outcome.prNumber ?? '없음'} · ${outcome.status}`);
|
|
428
449
|
outcome.reported = true;
|
|
@@ -430,11 +451,11 @@ export async function runStewardStage(stage: 'sync' | 'triage' | 'schedule' | 'r
|
|
|
430
451
|
}
|
|
431
452
|
const justCollected = new Set(outcomes.map(outcome => outcome.issue));
|
|
432
453
|
for (const outcome of Object.values(ledger.launches)) {
|
|
433
|
-
if ((settings.mode
|
|
434
|
-
(settings.mode
|
|
454
|
+
if ((!liveLike(settings.mode) && outcome.status === 'shadow' && !outcome.notified) ||
|
|
455
|
+
(liveLike(settings.mode) && !justCollected.has(outcome.issue) && outcome.reported && outcome.notified !== 'shadow') ||
|
|
435
456
|
// A finished run whose PR waits for merge still owes the wish's chat its PR link (once; failures retry).
|
|
436
|
-
(settings.mode
|
|
437
|
-
notifyOutcome(outcome, { root, ledger, shadow: settings.mode
|
|
457
|
+
(liveLike(settings.mode) && outcome.awaitingMerge === true && !outcome.reported && outcome.notified !== 'sent')) {
|
|
458
|
+
notifyOutcome(outcome, { root, ledger, shadow: !(settings.mode === 'live' || settings.mode === 'rescue' && rescueAllows(settings.mode, 'alert', { issue: outcome.issue })), ...deps.outcomeNotify });
|
|
438
459
|
}
|
|
439
460
|
}
|
|
440
461
|
const day = at.slice(0, 10);
|
|
@@ -445,11 +466,17 @@ export async function runStewardStage(stage: 'sync' | 'triage' | 'schedule' | 'r
|
|
|
445
466
|
writeFileSync(path, JSON.stringify(state));
|
|
446
467
|
}
|
|
447
468
|
}
|
|
469
|
+
outcome = 'ok';
|
|
470
|
+
} finally {
|
|
471
|
+
try { loopEvent('steward', 'tick', { runId, stage, outcome, reason: outcome,
|
|
472
|
+
sourceRef: 'graphs/steward/steward.yaml', profile: settings.mode }); }
|
|
473
|
+
catch { /* Observation must not change the stage outcome. */ }
|
|
474
|
+
}
|
|
448
475
|
}
|
|
449
476
|
|
|
450
477
|
async function sendStewardDigest(text: string): Promise<void> {
|
|
451
478
|
const { deliver } = await import('../domains/outbound-alert.js');
|
|
452
|
-
if (!deliver(text, '
|
|
479
|
+
if (!deliver(text, 'ops-report')) throw new Error('steward digest delivery failed');
|
|
453
480
|
}
|
|
454
481
|
|
|
455
482
|
export interface StewardCommandDeps extends StewardDeps {
|
|
@@ -543,13 +570,25 @@ export async function runStewardStageCommand(stage: StewardStage, deps: StewardC
|
|
|
543
570
|
}, stage === 'sync' ? () => { skipOverlappingRun(root, currentOpen(root, stage)); return 0 as const; } : undefined);
|
|
544
571
|
}
|
|
545
572
|
|
|
573
|
+
export async function runStewardNodeCommand(
|
|
574
|
+
stage: StewardStage,
|
|
575
|
+
deps: StewardCommandDeps = {},
|
|
576
|
+
registerSink: () => Promise<unknown> = async () => (await import('../domains/standalone-log-sink.js')).registerStandaloneLogSink('steward'),
|
|
577
|
+
): Promise<0 | 1> {
|
|
578
|
+
try { await registerSink(); }
|
|
579
|
+
catch (error) {
|
|
580
|
+
console.error(`steward ${stage}: log sink registration failed — ${redactSecretText(error instanceof Error ? error.message : String(error))}`);
|
|
581
|
+
}
|
|
582
|
+
return runStewardStageCommand(stage, deps);
|
|
583
|
+
}
|
|
584
|
+
|
|
546
585
|
if (import.meta.main) {
|
|
547
586
|
const stage = process.argv[2];
|
|
548
587
|
if (!['sync', 'triage', 'schedule', 'report'].includes(stage ?? '')) {
|
|
549
588
|
console.error('steward: expected sync|triage|schedule|report');
|
|
550
589
|
process.exitCode = 2;
|
|
551
590
|
} else {
|
|
552
|
-
|
|
591
|
+
void runStewardNodeCommand(stage as StewardStage).then(code => { process.exitCode = code; }).catch((error: unknown) => {
|
|
553
592
|
console.error(`steward ${stage}: ${redactSecretText(error instanceof Error ? error.message : String(error))}`);
|
|
554
593
|
process.exitCode = 1;
|
|
555
594
|
});
|
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
import type { Command } from 'commander';
|
|
2
2
|
import { join, resolve } from 'node:path';
|
|
3
3
|
import { scanWishFolder } from '../intake-plane/wish-folder.js';
|
|
4
|
+
import { LINEAR_WISH_KEY_MISSING, scanLinearWishes, suggestLinearWishes } from '../intake-plane/linear-wish.js';
|
|
5
|
+
import type { fetchLinearIssues } from '../connectors/linear.js';
|
|
4
6
|
import { CardStore, type TaskCard } from './card-store.js';
|
|
5
7
|
|
|
6
8
|
export interface CardCliDeps {
|
|
7
9
|
createStore?: () => CardStore;
|
|
8
10
|
write?: (text: string) => void;
|
|
11
|
+
getApiKey?: () => Promise<string | undefined>;
|
|
12
|
+
fetchIssues?: typeof fetchLinearIssues;
|
|
9
13
|
}
|
|
10
14
|
|
|
11
15
|
function formatCard(card: TaskCard): string {
|
|
@@ -84,5 +88,87 @@ export function registerCardCommand(program: Command, deps: CardCliDeps = {}): C
|
|
|
84
88
|
}
|
|
85
89
|
});
|
|
86
90
|
|
|
91
|
+
card.command('intake-scan')
|
|
92
|
+
.description('Scan Wish notes then Linear wishes in one scheduled invocation')
|
|
93
|
+
.option('--dir <path>', 'Wish folder path')
|
|
94
|
+
.option('--team <key>', 'Linear team key (omit to skip Linear)')
|
|
95
|
+
.option('--label <name>', 'Wish label or title prefix', 'wish')
|
|
96
|
+
.option('--json', 'Output JSON')
|
|
97
|
+
.action(async (options: { dir?: string; team?: string; label: string; json?: boolean }) => {
|
|
98
|
+
const result = {
|
|
99
|
+
folder: { added: 0, updated: 0, skipped: 0 },
|
|
100
|
+
linear: { added: 0, duplicate: 0, failed: 0 },
|
|
101
|
+
skipped: [] as string[],
|
|
102
|
+
failed: [] as string[],
|
|
103
|
+
};
|
|
104
|
+
let store: CardStore | undefined;
|
|
105
|
+
try {
|
|
106
|
+
const dir = resolve(options.dir ?? join(process.env.OBSIDIAN_VAULT_ROOT ?? '', '00. Inbox', '00. Wish'));
|
|
107
|
+
store = createStore();
|
|
108
|
+
result.folder = scanWishFolder({ dir, store });
|
|
109
|
+
} catch {
|
|
110
|
+
result.failed.push('Wish 폴더 스캔 실패');
|
|
111
|
+
} finally {
|
|
112
|
+
try { store?.close(); } catch {
|
|
113
|
+
if (!result.failed.includes('Wish 폴더 스캔 실패')) result.failed.push('Wish 폴더 스캔 실패');
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
store = undefined;
|
|
118
|
+
if (!options.team?.trim()) {
|
|
119
|
+
result.skipped.push('Linear 팀 미지정');
|
|
120
|
+
} else {
|
|
121
|
+
try {
|
|
122
|
+
store = createStore();
|
|
123
|
+
result.linear = await scanLinearWishes({ teamKey: options.team, label: options.label, store,
|
|
124
|
+
deps: { getApiKey: deps.getApiKey, fetchIssues: deps.fetchIssues } });
|
|
125
|
+
if (result.linear.failed > 0) result.failed.push(`Linear 항목 ${result.linear.failed}건 실패`);
|
|
126
|
+
} catch (error) {
|
|
127
|
+
if (error instanceof Error && error.message === LINEAR_WISH_KEY_MISSING) {
|
|
128
|
+
result.skipped.push('Linear 키 없음');
|
|
129
|
+
} else {
|
|
130
|
+
result.failed.push('Linear 스캔 실패');
|
|
131
|
+
}
|
|
132
|
+
} finally {
|
|
133
|
+
try { store?.close(); } catch {
|
|
134
|
+
if (!result.failed.includes('Linear 스캔 실패')) result.failed.push('Linear 스캔 실패');
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
write(options.json ? `${JSON.stringify(result)}\n` :
|
|
140
|
+
`폴더 추가 ${result.folder.added} · Linear 추가 ${result.linear.added} · 건너뜀(${result.skipped.join(', ') || '없음'})${result.failed.length ? ` · 실패(${result.failed.join(', ')})` : ''}\n`);
|
|
141
|
+
if (result.failed.length) process.exitCode = 1;
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
card.command('linear-scan')
|
|
145
|
+
.description('Create wish cards from open Linear issues')
|
|
146
|
+
.requiredOption('--team <key>', 'Linear team key')
|
|
147
|
+
.option('--label <name>', 'Wish label or title prefix', 'wish')
|
|
148
|
+
.option('--suggest', 'Suggest wish labels without writing to Linear or creating cards')
|
|
149
|
+
.option('--json', 'Output JSON')
|
|
150
|
+
.action(async (options: { team: string; label: string; suggest?: boolean; json?: boolean }) => {
|
|
151
|
+
let store: CardStore | undefined;
|
|
152
|
+
try {
|
|
153
|
+
if (options.suggest) {
|
|
154
|
+
const suggestions = await suggestLinearWishes({ teamKey: options.team,
|
|
155
|
+
deps: { getApiKey: deps.getApiKey, fetchIssues: deps.fetchIssues } });
|
|
156
|
+
write(options.json ? `${JSON.stringify(suggestions)}\n` :
|
|
157
|
+
suggestions.map(item => `${item.identifier}\t${item.title}\t${item.reason}\n`).join(''));
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
store = createStore();
|
|
161
|
+
const result = await scanLinearWishes({ teamKey: options.team, label: options.label, store,
|
|
162
|
+
deps: { getApiKey: deps.getApiKey, fetchIssues: deps.fetchIssues } });
|
|
163
|
+
write(options.json ? `${JSON.stringify(result)}\n` : `추가 ${result.added} · 중복 ${result.duplicate} · 실패 ${result.failed}\n`);
|
|
164
|
+
} catch (error) {
|
|
165
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
166
|
+
process.stderr.write(`${message.split(/\r?\n/, 1)[0]}\n`);
|
|
167
|
+
process.exitCode = message === LINEAR_WISH_KEY_MISSING ? 2 : 1;
|
|
168
|
+
} finally {
|
|
169
|
+
store?.close();
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
|
|
87
173
|
return card;
|
|
88
174
|
}
|
|
@@ -42,7 +42,7 @@ export const SECTION_OWNERS: Readonly<Record<string, string | null>> = {
|
|
|
42
42
|
gates: 'execution-loop', relations: 'execution-loop', memory: 'execution-loop',
|
|
43
43
|
workspace: 'executor', run: 'executor',
|
|
44
44
|
landing: 'landing-loop', release: 'release-loop', 'test-diet': 'test-diet',
|
|
45
|
-
incidents: null,
|
|
45
|
+
incidents: null, orch: 'orchestrator', flow: 'flow',
|
|
46
46
|
};
|
|
47
47
|
|
|
48
48
|
export function sectionOf(key: string): string {
|
|
@@ -80,10 +80,11 @@ export class CardStore {
|
|
|
80
80
|
private readonly db: Database;
|
|
81
81
|
readonly root: string;
|
|
82
82
|
|
|
83
|
-
constructor(root: string = elanousStateRoot()) {
|
|
83
|
+
constructor(root: string = elanousStateRoot(), private readonly readOnly = false) {
|
|
84
84
|
this.root = root;
|
|
85
|
-
mkdirSync(taskCardsDir(root), { recursive: true });
|
|
86
|
-
this.db = new Database(cardIndexPath(root));
|
|
85
|
+
if (!readOnly) mkdirSync(taskCardsDir(root), { recursive: true });
|
|
86
|
+
this.db = readOnly ? new Database(cardIndexPath(root), { readonly: true }) : new Database(cardIndexPath(root));
|
|
87
|
+
if (readOnly) return;
|
|
87
88
|
try {
|
|
88
89
|
this.db.exec('PRAGMA busy_timeout = 5000');
|
|
89
90
|
this.db.exec('PRAGMA journal_mode = WAL');
|
|
@@ -127,6 +128,7 @@ export class CardStore {
|
|
|
127
128
|
}
|
|
128
129
|
|
|
129
130
|
private reconcile(): void {
|
|
131
|
+
if (this.readOnly) return;
|
|
130
132
|
for (const file of readdirSync(taskCardsDir(this.root))) {
|
|
131
133
|
if (!/^[a-zA-Z0-9_-]+\.jsonl$/.test(file)) continue;
|
|
132
134
|
const id = file.slice(0, -'.jsonl'.length);
|
|
@@ -220,6 +222,7 @@ export class CardStore {
|
|
|
220
222
|
}
|
|
221
223
|
|
|
222
224
|
getCard(id: string): TaskCard | null {
|
|
225
|
+
if (this.readOnly) return this.readCard(id);
|
|
223
226
|
return this.db.transaction(() => {
|
|
224
227
|
this.reconcile();
|
|
225
228
|
return this.readCard(id);
|
|
@@ -227,12 +230,13 @@ export class CardStore {
|
|
|
227
230
|
}
|
|
228
231
|
|
|
229
232
|
listCards(options: { open?: boolean } = {}): TaskCard[] {
|
|
230
|
-
|
|
233
|
+
const list = () => {
|
|
231
234
|
this.reconcile();
|
|
232
235
|
const rows = this.db.query(`SELECT id FROM cards ${options.open ? "WHERE status = 'open'" : ''} ORDER BY created_at DESC, id DESC`)
|
|
233
236
|
.all() as Array<{ id: string }>;
|
|
234
237
|
return rows.map(({ id }) => this.readCard(id)!);
|
|
235
|
-
}
|
|
238
|
+
};
|
|
239
|
+
return this.readOnly ? list() : this.db.transaction(list).immediate();
|
|
236
240
|
}
|
|
237
241
|
|
|
238
242
|
close(): void {
|
|
@@ -5,19 +5,22 @@ import { createHash, randomUUID } from 'node:crypto';
|
|
|
5
5
|
import { closeSync, mkdirSync, openSync, readFileSync, renameSync, rmSync, writeFileSync } from 'node:fs';
|
|
6
6
|
import { join } from 'node:path';
|
|
7
7
|
import { dlopen } from 'bun:ffi';
|
|
8
|
-
import { hostLeaseBaseDir } from '../../pod-lease/host-lease.js';
|
|
8
|
+
import { hostLeaseBaseDir, type PendingJobIdentity } from '../../pod-lease/host-lease.js';
|
|
9
9
|
|
|
10
|
-
/** Persisted on a Job admitted through the host lease; the file lease is
|
|
10
|
+
/** Persisted on a Job admitted through the host lease; the file lease remains until its Pod is observed Running or terminal. */
|
|
11
11
|
export const POD_HOST_LEASE_ANNOTATION = 'elanous.dev/host-lease-admitted';
|
|
12
12
|
|
|
13
13
|
// `get pods --all-namespaces -o json` on a busy cluster is several MB — the default spawnSync
|
|
14
14
|
// buffer fails with ENOBUFS (10-03 node-b measured). Same proxy stripping as the Pod launch kubectl.
|
|
15
|
-
const LEASE_KUBECTL_MAX_BUFFER = 256 * 1024 * 1024;
|
|
15
|
+
export const LEASE_KUBECTL_MAX_BUFFER = 256 * 1024 * 1024;
|
|
16
16
|
export function leaseKubectl(args: readonly string[], input?: string): { status: number | null; stdout: string; stderr: string } {
|
|
17
17
|
const env = { ...process.env };
|
|
18
18
|
for (const k of ['HTTPS_PROXY', 'https_proxy', 'HTTP_PROXY', 'http_proxy', 'ALL_PROXY', 'all_proxy']) delete env[k];
|
|
19
19
|
const r = spawnSync('kubectl', [...args], { encoding: 'utf8', input, env, timeout: 120_000, maxBuffer: LEASE_KUBECTL_MAX_BUFFER });
|
|
20
|
-
|
|
20
|
+
const error = (r.error as NodeJS.ErrnoException | undefined)?.code === 'ENOBUFS'
|
|
21
|
+
? `kubectl 출력 너무 큼 (ENOBUFS; maxBuffer=${LEASE_KUBECTL_MAX_BUFFER} bytes)`
|
|
22
|
+
: r.error ? String(r.error) : '';
|
|
23
|
+
return { status: r.status, stdout: r.stdout ?? '', stderr: (r.stderr ?? '') + error };
|
|
21
24
|
}
|
|
22
25
|
|
|
23
26
|
export interface PodLeaseMember {
|
|
@@ -25,6 +28,8 @@ export interface PodLeaseMember {
|
|
|
25
28
|
capacity: number;
|
|
26
29
|
running: number | null;
|
|
27
30
|
pending: number | null;
|
|
31
|
+
/** Job names of Pending harness Pods already included in pending (used to avoid counting the same Job's host lease twice). */
|
|
32
|
+
pendingJobs?: PendingJobIdentity[];
|
|
28
33
|
/** Running harness Pods whose Jobs predate host lease admission. */
|
|
29
34
|
unleasedRunning: number | null;
|
|
30
35
|
memoryLimitBytes: number | null;
|
|
@@ -52,6 +57,8 @@ export interface PoolLeaseRecommendation {
|
|
|
52
57
|
accountSlots: number | null;
|
|
53
58
|
running: number | null;
|
|
54
59
|
pending: number | null;
|
|
60
|
+
/** Names of Pending Jobs already counted in pending. */
|
|
61
|
+
pendingJobs?: PendingJobIdentity[];
|
|
55
62
|
/** Absent only for injected legacy status fixtures; real measurements always report a number or null. */
|
|
56
63
|
unleasedRunning?: number | null;
|
|
57
64
|
}
|
|
@@ -294,6 +301,7 @@ export function measurePoolLease(members: readonly PodPoolMember[], deps: { kube
|
|
|
294
301
|
}
|
|
295
302
|
if (!jobsValid) reasons.push('cluster jobs: missing name/labels');
|
|
296
303
|
let running = 0, pending = 0, unleasedRunning = 0, limits = 0, limitsValid = true, reservationsValid = nodeItems !== null && nodeNames.size === nodeItems.length;
|
|
304
|
+
const pendingJobs = new Map<string, PendingJobIdentity>();
|
|
297
305
|
let unknownPhase = false;
|
|
298
306
|
const seenPods = new Set<string>();
|
|
299
307
|
for (const pod of podItems) {
|
|
@@ -314,8 +322,14 @@ export function measurePoolLease(members: readonly PodPoolMember[], deps: { kube
|
|
|
314
322
|
const spec = object(pod.spec);
|
|
315
323
|
let limit: number | null = null;
|
|
316
324
|
if (harness) {
|
|
317
|
-
if (phase === 'Pending')
|
|
318
|
-
|
|
325
|
+
if (phase === 'Pending') {
|
|
326
|
+
pending++;
|
|
327
|
+
const jobName = labels?.['elanous.job'] ?? labels?.['job-name'];
|
|
328
|
+
if (typeof jobName === 'string' && jobName && typeof metadata.namespace === 'string' && metadata.namespace) {
|
|
329
|
+
const identity = { context: member.context, namespace: metadata.namespace, job: jobName };
|
|
330
|
+
pendingJobs.set(JSON.stringify(identity), identity);
|
|
331
|
+
}
|
|
332
|
+
} else {
|
|
319
333
|
running++;
|
|
320
334
|
const jobName = labels?.['elanous.job'] ?? labels?.['job-name'];
|
|
321
335
|
if (typeof jobName !== 'string' || !leasedJobs.has(jobName)) unleasedRunning++;
|
|
@@ -333,7 +347,7 @@ export function measurePoolLease(members: readonly PodPoolMember[], deps: { kube
|
|
|
333
347
|
if (nodeFree.has(nodeName)) nodeFree.set(nodeName, nodeFree.get(nodeName)! - Math.max(requested, limit ?? 0));
|
|
334
348
|
}
|
|
335
349
|
if (unknownPhase) reasons.push('cluster pods: missing/Unknown phase');
|
|
336
|
-
if (jobsValid && !unknownPhase) { result.running = running; result.pending = pending; result.unleasedRunning = unleasedRunning; }
|
|
350
|
+
if (jobsValid && !unknownPhase) { result.running = running; result.pending = pending; result.pendingJobs = [...pendingJobs.values()]; result.unleasedRunning = unleasedRunning; }
|
|
337
351
|
if (limitsValid && jobsValid && !unknownPhase) result.memoryLimitBytes = limits;
|
|
338
352
|
else if (!limitsValid) reasons.push('cluster pods: Running Pod memory limit missing/invalid');
|
|
339
353
|
if (reservationsValid && limitsValid && jobsValid && !unknownPhase) result.availableMemoryByNodeBytes = [...nodeFree.values()];
|
|
@@ -366,6 +380,7 @@ export function recommendConcurrency(measure: PoolLeaseMeasure, options: { capac
|
|
|
366
380
|
if (!Number.isSafeInteger(options.capacity) || options.capacity < 0) return { ...empty, reason: '측정 불가: capacity' };
|
|
367
381
|
const running = measure.members.reduce((sum, m) => sum + m.running!, 0);
|
|
368
382
|
const pending = measure.members.reduce((sum, m) => sum + m.pending!, 0);
|
|
383
|
+
const pendingJobs = measure.members.flatMap((m) => m.pendingJobs ?? []);
|
|
369
384
|
const unleasedRunning = measure.members.reduce((sum, m) => sum + m.unleasedRunning!, 0);
|
|
370
385
|
// Pool-wide free = sum of per-member free slots: a member over its cap counts as full, never negative,
|
|
371
386
|
// so it cannot zero another member's free slot (and its unleased Pods only consume its own free slots).
|
|
@@ -393,5 +408,5 @@ export function recommendConcurrency(measure: PoolLeaseMeasure, options: { capac
|
|
|
393
408
|
const recommended = Math.max(0, Math.min(capacitySlots, memoryBound) - eligibleUnleasedRunning);
|
|
394
409
|
const limitedBy = memoryBound < capacitySlots ? 'memory' : 'capacity';
|
|
395
410
|
if (unleasedRunning > 0) debug.log('pod-lease', 'unleased', { unleasedRunning, running, recommended });
|
|
396
|
-
return { recommended, limitedBy, reason: measure.members.some((m) => m.reason === 'dns') ? 'dns' : null, capacitySlots, memorySlots, liteMemorySlots, placeableSlots, accountSlots, running, pending, unleasedRunning };
|
|
411
|
+
return { recommended, limitedBy, reason: measure.members.some((m) => m.reason === 'dns') ? 'dns' : null, capacitySlots, memorySlots, liteMemorySlots, placeableSlots, accountSlots, running, pending, pendingJobs, unleasedRunning };
|
|
397
412
|
}
|