elanous 0.2.13 → 0.2.15
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/LZcstaO9_NXypPFmujolv/_buildManifest.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{7334.a6679d9168f37aa8.js → 1268.0840ff91e083fcbd.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/1307-33ffcc49ab45123b.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/{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-c952712c87a1f757.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/899-46946fe8363e1262.js +1 -0
- 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-0e2c57fe6add4b2a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/autopilot/page-e05a77df7e005b04.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/board/page-f544338193a9a762.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/botlab/page-5491c115b1f9a923.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/bots/page-b8c49f6b4fa2df91.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ceo/page-96398e364fbd4548.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/chat/page-976813c91d7394ea.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/consult/page-28b9448091776c06.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/control/page-1d4901eb7c2d8822.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-72d3e7ff35a8c954.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/editor/page-60c7a8e1a4fe62ce.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/exec/page-2a52b316cb143d9c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/field/page-c3ce483c8d5db370.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-13c7b53e0c2519a9.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/intake/page-96f074f6538e8a2c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/layout-e97be5334d83a9f5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/live/page-65e484302921a932.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/loops/page-34d08facaf08f556.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/market/page-955ec2da3be4c560.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-9f88fbc15d9058d1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/morning/page-c1343f9499d86f3f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/not-found-8739a2e0ae793130.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-c9fc5706b8abaa11.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-d23b12bb92242558.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-8d47f45a0dc044ac.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-c7fea475488201a8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/outputs/page-e2ee2896811e98d0.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/page-83b19dae8619de05.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-5efbf81124c474ae.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-85f1a1fc0a82738c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-da6c08aae0f92768.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-06ee266953d9c879.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/page-153c2b3bcc89e8de.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-87a11e6a05f15199.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-7d4182964407c09c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/share/page-0d6d23b40263a7ea.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-7e8c39241f8eb6d0.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-008c1a96618a4887.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/term/{page-63d14a4d8fe1e1c1.js → page-f5597e894c02cd33.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/today/page-0337fdf7fb5e37b6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/trace/page-73735b64bfe1ae67.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/vault/page-8fcec4c9ea2e7f70.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-f7e631e9b4daf66e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-4881b8f9f63694b4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-9817c7f9007466cc.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-97f9f43172a3a90e.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/1f7c958c41ffa3b9.css +1 -0
- package/apps/pwa/out/approvals/index.html +1 -1
- package/apps/pwa/out/approvals/index.txt +23 -23
- package/apps/pwa/out/autopilot/index.html +1 -1
- package/apps/pwa/out/autopilot/index.txt +23 -23
- package/apps/pwa/out/board/index.html +1 -1
- package/apps/pwa/out/board/index.txt +23 -23
- package/apps/pwa/out/botlab/index.html +1 -1
- package/apps/pwa/out/botlab/index.txt +22 -22
- package/apps/pwa/out/bots/index.html +1 -1
- package/apps/pwa/out/bots/index.txt +22 -22
- package/apps/pwa/out/ceo/index.html +1 -0
- package/apps/pwa/out/ceo/index.txt +33 -0
- package/apps/pwa/out/chat/index.html +1 -1
- package/apps/pwa/out/chat/index.txt +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/heal/heal-loop.yaml +1 -1
- package/graphs/implement-loop.yaml +1 -1
- package/graphs/orchestrator/orchestrator.yaml +59 -0
- package/graphs/orchestrator/recipes.yaml +18 -0
- package/graphs/release/recipes.yaml +6 -0
- package/graphs/release/release-loop.yaml +6 -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 +188 -0
- package/scripts/ci-typecheck-changed.ts +50 -18
- package/scripts/coord-post.sh +11 -1
- package/scripts/docs/generate-graph-anatomy.ts +315 -0
- package/scripts/docs-cli-check.ts +23 -6
- package/scripts/expose-rubric-check.ts +118 -0
- package/scripts/expose-rubric-suggest.ts +169 -0
- package/scripts/heal/run-role.ts +14 -0
- package/scripts/hq/standby-verify.ts +11 -7
- package/scripts/lecture-note/node.ts +31 -11
- package/scripts/lessons/handbook-page.ts +112 -0
- package/scripts/marketing/generate-ax-brief.ts +140 -0
- package/scripts/marketing/generate-feature-map.ts +118 -0
- package/scripts/release-loop/checklist-gate-node.ts +53 -14
- package/scripts/release-loop/gate-node.ts +89 -14
- 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 +428 -0
- package/scripts/release-story/draft.ts +220 -0
- package/scripts/rhythm/daily.ts +570 -0
- package/scripts/rhythm/weekly.ts +315 -0
- package/scripts/skills/public-check.ts +169 -0
- package/scripts/smart-merge/resync-shadow.ts +147 -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-env/profile.ts +161 -0
- package/src/agent-mission/driver.ts +3 -3
- package/src/agent-substrate/pr-reviewer.ts +2 -0
- package/src/ask-user-question/pending-questions.ts +5 -0
- package/src/auth/github-app-token.ts +112 -13
- package/src/autopilot/build/llm-conflict-merge.ts +73 -10
- package/src/autopilot/build/merge-intent.ts +53 -0
- package/src/briefing/brief-items.ts +121 -0
- package/src/chat/index.ts +2 -0
- package/src/claims/claims-ledger.ts +232 -0
- package/src/claims/claims-render.ts +166 -0
- package/src/cli/agent-env-cli.ts +21 -0
- package/src/cli/claims-cli.ts +94 -0
- package/src/cli/decisions-cli.ts +44 -3
- package/src/cli/docs-cli.ts +2 -0
- package/src/cli/doctor-cli.ts +23 -1
- package/src/cli/doctor-node-modules.ts +147 -0
- package/src/cli/flow-cli.ts +11 -0
- package/src/cli/freeze-cli.ts +45 -0
- package/src/cli/intake-cli.ts +46 -17
- 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/pod-cli.ts +2 -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 +190 -33
- package/src/cli/rfc-status.ts +91 -0
- package/src/cli/seat-requests-cli.ts +52 -0
- package/src/connectors/linear.ts +5 -3
- package/src/context-bus/condense.ts +15 -20
- package/src/context-bus/context-now-public.ts +96 -0
- package/src/context-bus/context-now-surfaces.ts +55 -2
- package/src/context-bus/context-now.ts +6 -3
- package/src/context-bus/coord-events.ts +6 -4
- 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-cards.ts +33 -7
- package/src/decisions/decision-ledger.ts +40 -1
- package/src/decisions/proact-meter.ts +286 -0
- package/src/decisions/proact1-lite.ts +86 -0
- package/src/decisions/web-push-decision-cards.ts +1 -1
- package/src/discord/sprint21-runtime.ts +16 -0
- package/src/discord-self-message.ts +8 -0
- package/src/discord-slash-wire.ts +31 -2
- package/src/domains/alert-audit.ts +271 -0
- package/src/domains/core-tools.ts +4 -3
- package/src/domains/ops-facts-tool.ts +31 -1
- package/src/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 +259 -38
- package/src/harness/harness-incidents.ts +45 -12
- package/src/harness/harness-pod-dispatch.ts +102 -11
- package/src/harness/harness-queue-child.ts +3 -3
- package/src/harness/harness-queue.ts +583 -65
- package/src/harness/helper-repair.ts +87 -0
- package/src/harness/helper-scan.ts +151 -0
- package/src/harness/launch-stamp.ts +236 -0
- package/src/harness/ledger-write-guard-child.fixture.ts +12 -0
- package/src/harness/ledger-write-guard.ts +35 -0
- package/src/harness/model-input-observation.ts +37 -0
- package/src/harness/pod-memory-policy.ts +15 -0
- package/src/harness/repository-name.ts +55 -0
- package/src/hq/fence-audit.ts +90 -0
- package/src/hq/hq.ts +63 -5
- package/src/hq/lease.ts +4 -4
- package/src/hq/move-plan.ts +126 -0
- package/src/hq/promote.ts +332 -0
- package/src/index.ts +159 -27
- package/src/intake-plane/check.ts +63 -0
- package/src/intake-plane/digest.ts +37 -15
- package/src/intake-plane/front-classifier.ts +6 -0
- package/src/intake-plane/items.ts +3 -2
- package/src/intake-plane/lens.ts +177 -0
- package/src/intake-plane/linear-wish.ts +39 -11
- package/src/intake-plane/route.ts +145 -1
- 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/launch-head/seat-goal-queue-lock.fixture.ts +15 -0
- package/src/launch-head/seat-goal-queue.ts +198 -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 +63 -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 +220 -0
- package/src/loops/orchestrator/author-input-check.ts +82 -0
- package/src/loops/orchestrator/author-ledger.ts +232 -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/seat-share.ts +56 -0
- package/src/loops/orchestrator/tick.ts +309 -0
- package/src/loops/orchestrator/traffic.ts +134 -13
- package/src/loops/registry.ts +535 -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 +9 -5
- package/src/maturity/tui-slash-availability.ts +31 -0
- package/src/mss/logging/log-store.ts +35 -4
- package/src/nexus/api/dashboard.ts +6 -1
- package/src/nexus/api/http-server.ts +33 -3
- package/src/nexus/api/loop-edges.ts +173 -0
- package/src/nexus/api/merge-approvals-gate.ts +1 -1
- package/src/nexus/api/schedules-read.ts +18 -1
- package/src/nexus/api/seat-requests.ts +79 -56
- package/src/nexus/api/task-cards-api.ts +44 -5
- package/src/nexus/api/workflows.ts +2 -1
- package/src/prompt-enhance/enhance.ts +6 -1
- package/src/release-loop/auto-start.ts +51 -0
- package/src/release-loop/checklist.ts +22 -4
- package/src/release-loop/feature-store.ts +71 -32
- package/src/release-loop/landing-freeze.ts +121 -0
- package/src/release-loop/merged-pr-checklist.ts +125 -0
- package/src/release-loop/placement.ts +302 -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-dispatch/seat-request-eligibility.ts +19 -0
- package/src/seat-dispatch/seat-request-ledger.ts +133 -0
- package/src/seat-loop/seat-loop.ts +531 -40
- package/src/self-dev/dev-pipeline.ts +14 -6
- package/src/self-dev/draft-sweep.ts +162 -6
- package/src/self-dev/draft-triage-rules.ts +25 -8
- package/src/self-dev/orchestrate.ts +15 -1
- package/src/self-dev/review-provider-fallback.ts +6 -1
- package/src/self-dev/run-store.ts +73 -1
- package/src/self-dev/run-supervisor.ts +57 -2
- 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 +295 -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-author.ts +43 -5
- package/src/self-implement/goal-clarification-escalation.ts +5 -0
- package/src/self-implement/goal-lookup.ts +147 -0
- package/src/self-implement/heal-intake.ts +197 -0
- package/src/self-implement/host-regate.ts +32 -2
- package/src/self-implement/l8-merge-queue.ts +132 -0
- package/src/self-implement/orchestrator.ts +299 -48
- package/src/self-implement/pipeline-shape.ts +1 -1
- package/src/self-implement/revert-guard.ts +97 -0
- package/src/self-implement/run-ledger.ts +3 -1
- package/src/self-implement/run-status-mapping.ts +2 -0
- package/src/self-implement/seams.ts +29 -9
- package/src/self-implement/self-implement-cli.ts +1 -0
- package/src/self-implement/speed-claims.ts +338 -0
- package/src/session/index.ts +2 -0
- package/src/steward/triage.ts +74 -3
- package/src/steward/working-backwards.ts +4 -1
- package/src/task-cards/card-cli.ts +10 -2
- package/src/task-cards/card-store.ts +10 -6
- package/src/task-orchestrator/surfaces/pod-artifact-return.ts +63 -48
- package/src/task-orchestrator/surfaces/pod-command-job.ts +84 -9
- package/src/task-orchestrator/surfaces/pod-failure-reason.ts +48 -0
- package/src/task-orchestrator/surfaces/pod-lease.ts +5 -2
- package/src/task-orchestrator/surfaces/pod-ledger-collect.ts +46 -6
- package/src/task-orchestrator/surfaces/pod-pool.ts +60 -8
- package/src/task-orchestrator/surfaces/self-implement-pod.ts +224 -49
- package/src/task-orchestrator/surfaces/self-implement.ts +5 -1
- package/src/telegram-commands.ts +27 -3
- package/src/telegram-loops-command.ts +44 -0
- package/src/telegram.ts +25 -4
- package/src/user-config.ts +146 -18
- package/src/version/packed-revision.json +1 -1
- package/src/workflow-runtime/nodes/bash.ts +9 -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/botlab/page-f14987d5850a31e0.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/missions/[id]/page-66c27608f651b866.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/devices/page-78b40a344ed22f24.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 → LZcstaO9_NXypPFmujolv}/_ssgManifest.js +0 -0
package/src/steward/triage.ts
CHANGED
|
@@ -260,9 +260,56 @@ function skipOverlappingRun(root: string, open: FailureStreak['open'] & { pid?:
|
|
|
260
260
|
}
|
|
261
261
|
debug.log('steward.stage', 'skipped-overlap', { openStage: open.stage, pid: open.pid });
|
|
262
262
|
writeFileSync(skippedPath(root), JSON.stringify({ openStage: open.stage, pid: open.pid }));
|
|
263
|
+
logTickDecision(root, 'skipped', `overlapping ${open.stage} (pid ${open.pid ?? 'unknown'})`);
|
|
263
264
|
}
|
|
264
265
|
|
|
265
266
|
function reportPath(root: string): string { return join(root, 'steward', 'observe.json'); }
|
|
267
|
+
|
|
268
|
+
type TickDecision = { verdict: 'launched' | 'failed'; reason: string; goalId?: string };
|
|
269
|
+
|
|
270
|
+
function logTickDecision(root: string | undefined, verdict: 'launched' | 'skipped' | 'reported' | 'failed', reason: string, goalId?: string): void {
|
|
271
|
+
const runId = graphRunId();
|
|
272
|
+
const marker = root && runId && existsSync(join(root, 'steward')) ? join(root, 'steward', `decision-logged-${runId}.json`) : undefined;
|
|
273
|
+
const decision = { verdict, reason: redactSecretText(reason), ...(goalId ? { goalId } : {}), ...(runId ? { runId } : {}) };
|
|
274
|
+
if (marker) try {
|
|
275
|
+
const previous = JSON.parse(readFileSync(marker, 'utf8')) as { verdict?: string; reason?: string };
|
|
276
|
+
// A replay keeps the first success; a failure replaces a success; a success after a failure replaces the failure.
|
|
277
|
+
if (previous.verdict === decision.verdict && previous.reason === decision.reason) return;
|
|
278
|
+
if (previous.verdict !== 'failed' && verdict !== 'failed') return;
|
|
279
|
+
} catch { /* Missing or stale marker: retry the observation. */ }
|
|
280
|
+
try {
|
|
281
|
+
debug.log('loop.steward', 'decision', decision);
|
|
282
|
+
if (marker) writeFileSync(marker, JSON.stringify(decision));
|
|
283
|
+
} catch { /* Observation must not change the tick outcome. */ }
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
function tickDecisionPath(root: string, runId: string): string {
|
|
287
|
+
return join(root, 'steward', `decision-${runId}.json`);
|
|
288
|
+
}
|
|
289
|
+
function rememberLaunchDecision(root: string, entry: TickDecision): void {
|
|
290
|
+
const runId = graphRunId();
|
|
291
|
+
if (!runId) return;
|
|
292
|
+
const path = tickDecisionPath(root, runId);
|
|
293
|
+
try {
|
|
294
|
+
let current: TickDecision | undefined;
|
|
295
|
+
try { current = JSON.parse(readFileSync(path, 'utf8')) as TickDecision; }
|
|
296
|
+
catch (error) { if ((error as NodeJS.ErrnoException).code !== 'ENOENT') return; }
|
|
297
|
+
// Keep the first failure (and its goal/cause), even if a retried schedule later succeeds.
|
|
298
|
+
if (current?.verdict === 'failed' || current?.verdict === entry.verdict) return;
|
|
299
|
+
writeFileSync(path, JSON.stringify(entry));
|
|
300
|
+
} catch { /* An unavailable observation store must not block execution. */ }
|
|
301
|
+
}
|
|
302
|
+
function readLaunchDecision(root: string): TickDecision | undefined {
|
|
303
|
+
const runId = graphRunId();
|
|
304
|
+
if (!runId) return undefined;
|
|
305
|
+
try { return JSON.parse(readFileSync(tickDecisionPath(root, runId), 'utf8')) as TickDecision; }
|
|
306
|
+
catch { return undefined; }
|
|
307
|
+
}
|
|
308
|
+
function logStageFailure(root: string, stage: StewardStage, reason: string): void {
|
|
309
|
+
const runId = graphRunId();
|
|
310
|
+
if (runId) try { rmSync(tickDecisionPath(root, runId), { force: true }); } catch { /* Observation must not change the failure. */ }
|
|
311
|
+
logTickDecision(root, 'failed', `${stage}: ${reason}`);
|
|
312
|
+
}
|
|
266
313
|
function readState(path: string): { issues: Record<string, string>; digestDay?: string } {
|
|
267
314
|
try { return JSON.parse(readFileSync(path, 'utf8')) as ReturnType<typeof readState>; }
|
|
268
315
|
catch (error) { if ((error as NodeJS.ErrnoException).code === 'ENOENT') return { issues: {} }; throw error; }
|
|
@@ -396,10 +443,18 @@ export async function runStewardStage(stage: 'sync' | 'triage' | 'schedule' | 'r
|
|
|
396
443
|
const entry = raiseHitl(issue, row, launchDeps);
|
|
397
444
|
recordHitlOnCard(issue, entry, cardDeps);
|
|
398
445
|
}
|
|
446
|
+
let launchDecision: TickDecision | undefined;
|
|
399
447
|
for (const item of plan.launches) {
|
|
400
448
|
const entry = launch(item, launchDeps);
|
|
449
|
+
const decision: TickDecision | undefined = entry.status === 'launched'
|
|
450
|
+
? { verdict: 'launched', reason: 'harness run spawned', goalId: `linear:${item.issue.identifier}` }
|
|
451
|
+
: entry.status === 'failed'
|
|
452
|
+
? { verdict: 'failed', reason: entry.reason ?? 'launch failed', goalId: `linear:${item.issue.identifier}` }
|
|
453
|
+
: undefined;
|
|
454
|
+
if (decision && (!launchDecision || (decision.verdict === 'failed' && launchDecision.verdict !== 'failed'))) launchDecision = decision;
|
|
401
455
|
recordLaunchOnCard(item.issue, entry, cardDeps);
|
|
402
456
|
}
|
|
457
|
+
if (launchDecision) rememberLaunchDecision(root, launchDecision);
|
|
403
458
|
if (settings.mode === 'shadow') {
|
|
404
459
|
const { recordShadowTick } = await import('./readiness.js');
|
|
405
460
|
const tick = recordShadowTick(root, rows, plan, (deps.now ?? (() => new Date()))());
|
|
@@ -459,12 +514,18 @@ export async function runStewardStage(stage: 'sync' | 'triage' | 'schedule' | 'r
|
|
|
459
514
|
}
|
|
460
515
|
}
|
|
461
516
|
const day = at.slice(0, 10);
|
|
517
|
+
let digested = false;
|
|
462
518
|
if (state.digestDay !== day) {
|
|
463
519
|
const send = deps.sendDigest ?? sendStewardDigest;
|
|
464
520
|
await send(`스튜어드 ${day}: ${rows.map(row => `${row.issue} ${row.disposition}`).join(' · ')}`);
|
|
465
521
|
state.digestDay = day;
|
|
466
522
|
writeFileSync(path, JSON.stringify(state));
|
|
523
|
+
digested = true;
|
|
467
524
|
}
|
|
525
|
+
const launched = readLaunchDecision(root);
|
|
526
|
+
if (launched) logTickDecision(root, launched.verdict, launched.reason, launched.goalId);
|
|
527
|
+
else if (outcomes.length || digested) logTickDecision(root, 'reported', outcomes.length ? `${outcomes.length} outcome(s) reported` : 'daily digest sent');
|
|
528
|
+
else logTickDecision(root, 'skipped', 'no new outcome or digest');
|
|
468
529
|
}
|
|
469
530
|
outcome = 'ok';
|
|
470
531
|
} finally {
|
|
@@ -513,10 +574,14 @@ async function withStageSlot<T>(root: string, run: () => Promise<T>, onBusy?: ()
|
|
|
513
574
|
|
|
514
575
|
/** The command-node boundary records both normal exits and an unclosed stage from a killed process. */
|
|
515
576
|
export async function runStewardStageCommand(stage: StewardStage, deps: StewardCommandDeps = {}): Promise<0 | 1> {
|
|
516
|
-
if (resolvedStewardSettings(deps.launchSettings).mode === 'off')
|
|
577
|
+
if (resolvedStewardSettings(deps.launchSettings).mode === 'off') {
|
|
578
|
+
if (stage === 'sync') logTickDecision(deps.root ?? effectiveInstanceRoot(), 'skipped', 'steward mode off');
|
|
579
|
+
return 0;
|
|
580
|
+
}
|
|
517
581
|
const root = deps.root ?? effectiveInstanceRoot();
|
|
518
582
|
if (stage !== 'sync' && existsSync(skippedPath(root))) return 0;
|
|
519
|
-
|
|
583
|
+
try {
|
|
584
|
+
return await withStageSlot(root, async () => {
|
|
520
585
|
const now = deps.now ?? (() => new Date());
|
|
521
586
|
const threshold = stewardSettings().alertAfterFailures ?? 3;
|
|
522
587
|
const alertIfNeeded = async (state: FailureStreak): Promise<void> => {
|
|
@@ -564,10 +629,16 @@ export async function runStewardStageCommand(stage: StewardStage, deps: StewardC
|
|
|
564
629
|
if (!ok) {
|
|
565
630
|
console.error(`steward ${stage}: stage failed — ${reason}`);
|
|
566
631
|
debug.log('steward.stage', 'failed', { stage, reason });
|
|
632
|
+
if (stage === 'sync' || !existsSync(skippedPath(root))) logStageFailure(root, stage, reason);
|
|
567
633
|
return 1;
|
|
568
634
|
}
|
|
569
635
|
return 0;
|
|
570
|
-
|
|
636
|
+
}, stage === 'sync' ? () => { skipOverlappingRun(root, currentOpen(root, stage)); return 0 as const; } : undefined);
|
|
637
|
+
} catch (error) {
|
|
638
|
+
try { if (stage === 'sync' || !existsSync(skippedPath(root))) logStageFailure(root, stage, error instanceof Error ? error.message : String(error)); }
|
|
639
|
+
catch { /* Observation must not change the stage error. */ }
|
|
640
|
+
throw error;
|
|
641
|
+
}
|
|
571
642
|
}
|
|
572
643
|
|
|
573
644
|
export async function runStewardNodeCommand(
|
|
@@ -53,7 +53,10 @@ export async function recordWorkingBackwardsOnCards(
|
|
|
53
53
|
}
|
|
54
54
|
const card = store.createCard({ goalId: `linear:${issue.identifier}`, title: redactSecrets(issue.title) });
|
|
55
55
|
if (card.sections.some(section => section.key.startsWith('prfaq:')) && card.sections.some(section => section.key.startsWith('manual:'))) continue;
|
|
56
|
-
if (card.sections.some(section => section.key.startsWith('launch:')))
|
|
56
|
+
if (card.sections.some(section => section.key.startsWith('launch:'))) {
|
|
57
|
+
debug.log('steward.working-backwards', 'skipped-launched-before-draft', { issueId: issue.identifier });
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
57
60
|
const draft = await draftWorkingBackwards(issue, ask);
|
|
58
61
|
for (const [section, content] of [['prfaq', draft.prfaq], ['manual', draft.manual]] as const) {
|
|
59
62
|
const key = `${section}:${createHash('sha256').update(content).digest('hex')}`;
|
|
@@ -1,7 +1,7 @@
|
|
|
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 } from '../intake-plane/linear-wish.js';
|
|
4
|
+
import { LINEAR_WISH_KEY_MISSING, scanLinearWishes, suggestLinearWishes } from '../intake-plane/linear-wish.js';
|
|
5
5
|
import type { fetchLinearIssues } from '../connectors/linear.js';
|
|
6
6
|
import { CardStore, type TaskCard } from './card-store.js';
|
|
7
7
|
|
|
@@ -145,10 +145,18 @@ export function registerCardCommand(program: Command, deps: CardCliDeps = {}): C
|
|
|
145
145
|
.description('Create wish cards from open Linear issues')
|
|
146
146
|
.requiredOption('--team <key>', 'Linear team key')
|
|
147
147
|
.option('--label <name>', 'Wish label or title prefix', 'wish')
|
|
148
|
+
.option('--suggest', 'Suggest wish labels without writing to Linear or creating cards')
|
|
148
149
|
.option('--json', 'Output JSON')
|
|
149
|
-
.action(async (options: { team: string; label: string; json?: boolean }) => {
|
|
150
|
+
.action(async (options: { team: string; label: string; suggest?: boolean; json?: boolean }) => {
|
|
150
151
|
let store: CardStore | undefined;
|
|
151
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
|
+
}
|
|
152
160
|
store = createStore();
|
|
153
161
|
const result = await scanLinearWishes({ teamKey: options.team, label: options.label, store,
|
|
154
162
|
deps: { getApiKey: deps.getApiKey, fetchIssues: deps.fetchIssues } });
|
|
@@ -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 {
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { lstatSync, mkdirSync, writeFileSync } from 'node:fs';
|
|
1
|
+
import { lstatSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
2
|
import { isAbsolute, join, relative, resolve, sep } from 'node:path';
|
|
3
3
|
import { gunzipSync } from 'node:zlib';
|
|
4
4
|
import { debug } from '../../debug/log.js';
|
|
5
5
|
import { getDefaultLogStore, type LogStore } from '../../mss/logging/log-store.js';
|
|
6
|
-
import type
|
|
6
|
+
import { fromJsonl, type LogRecord } from '../../mss/logging/record.js';
|
|
7
7
|
|
|
8
8
|
const FILE_LIMIT = 5 * 1024 * 1024;
|
|
9
9
|
const TOTAL_LIMIT = 20 * 1024 * 1024;
|
|
@@ -83,50 +83,58 @@ export function parsePodArtifactChunks(logs: string): ParseResult {
|
|
|
83
83
|
|
|
84
84
|
const POD_LOGS_PATH = 'pod-logs/logs.jsonl';
|
|
85
85
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
category: 'harness.decision',
|
|
106
|
-
event: row.event,
|
|
107
|
-
data,
|
|
108
|
-
...(typeof row.level === 'string' ? { level: row.level as LogRecord['level'] } : {}),
|
|
109
|
-
...(typeof row.trace_id === 'string' ? { trace_id: row.trace_id } : {}),
|
|
110
|
-
},
|
|
111
|
-
surface: typeof row.surface === 'string' && row.surface ? row.surface : 'pod',
|
|
112
|
-
};
|
|
86
|
+
/** Last measured child ledger context when a failed Pod emitted no terminal JSON. */
|
|
87
|
+
export function podNoResultDiagnostic(ledger: string): string | null {
|
|
88
|
+
let stage: string | undefined;
|
|
89
|
+
let round: number | undefined;
|
|
90
|
+
let mustFix: string | undefined;
|
|
91
|
+
for (const line of ledger.split(/\r?\n/)) {
|
|
92
|
+
try {
|
|
93
|
+
const entry = JSON.parse(line) as { event?: unknown; data?: Record<string, unknown> };
|
|
94
|
+
if (!entry || typeof entry !== 'object' || !entry.data || typeof entry.data !== 'object' || typeof entry.event !== 'string' || entry.event === 'pod-ledger-incomplete') continue;
|
|
95
|
+
if (typeof entry.data.round === 'number' && Number.isSafeInteger(entry.data.round)) round = entry.data.round;
|
|
96
|
+
if (entry.event !== 'run-origin') stage = entry.event;
|
|
97
|
+
// `reviewed` carries pointers only (count + finding ids); the text stays in the review artifact.
|
|
98
|
+
if (entry.event === 'reviewed' && typeof entry.data.mustFix === 'number') {
|
|
99
|
+
const ids = Array.isArray(entry.data.findingIds) ? entry.data.findingIds.filter((id): id is string => typeof id === 'string') : [];
|
|
100
|
+
mustFix = `${entry.data.mustFix}${ids.length ? ` ${JSON.stringify(ids)}` : ''}`;
|
|
101
|
+
}
|
|
102
|
+
} catch { /* incomplete ledger line */ }
|
|
103
|
+
}
|
|
104
|
+
return stage ? `child terminal result missing; last ledger stage=${stage}; round=${round ?? 'unknown'}; mustFix=${mustFix ?? 'unknown'}` : null;
|
|
113
105
|
}
|
|
114
106
|
|
|
115
|
-
/** Import
|
|
116
|
-
export function
|
|
107
|
+
/** Import valid JSONL log records into the launching host store, with durable per-job line receipts. */
|
|
108
|
+
export function importPodLogs(bytes: Buffer, job: string, store: LogStore | null = getDefaultLogStore()): { imported: number; skipped: number } {
|
|
117
109
|
if (!store) return { imported: 0, skipped: 0 };
|
|
118
|
-
const
|
|
119
|
-
const rows: Array<{ rec: LogRecord; surface: string }> = [];
|
|
110
|
+
const rows: Array<{ rec: LogRecord; surface: string; line: number }> = [];
|
|
120
111
|
let skipped = 0;
|
|
121
|
-
for (const line of
|
|
112
|
+
for (const [lineNumber, line] of bytes.toString('utf8').split(/\r?\n/).entries()) {
|
|
122
113
|
if (!line) continue;
|
|
123
|
-
const
|
|
124
|
-
if (!
|
|
125
|
-
|
|
126
|
-
|
|
114
|
+
const rec = fromJsonl(line);
|
|
115
|
+
if (!rec || !Number.isFinite(Date.parse(rec.ts))) { skipped++; continue; }
|
|
116
|
+
const data = rec.data && typeof rec.data === 'object' && !Array.isArray(rec.data)
|
|
117
|
+
? rec.data as Record<string, unknown>
|
|
118
|
+
: rec.data === undefined ? {} : { originalData: rec.data };
|
|
119
|
+
const surface = (rec as LogRecord & { surface?: unknown }).surface;
|
|
120
|
+
rows.push({
|
|
121
|
+
rec: { ...rec, data: { ...data, origin: 'pod', podJob: job } },
|
|
122
|
+
surface: typeof surface === 'string' && surface ? surface : 'pod',
|
|
123
|
+
line: lineNumber,
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
return { imported: store.insertPodBatch(job, rows), skipped };
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function importReturnedLogs(bytes: Buffer, job: string, store: LogStore | null,
|
|
130
|
+
log: (category: string, event: string, data: Record<string, unknown>) => void): void {
|
|
131
|
+
try {
|
|
132
|
+
if (!store) throw new Error('host log store unavailable');
|
|
133
|
+
const imported = importPodLogs(bytes, job, store);
|
|
134
|
+
log('self-implement.pod', 'pod-logs-imported', { job, imported: imported.imported, skipped: imported.skipped });
|
|
135
|
+
} catch (error) {
|
|
136
|
+
debug.log('self-implement.pod', 'pod-logs-import-failed', { job, reason: error instanceof Error ? error.message : String(error) });
|
|
127
137
|
}
|
|
128
|
-
if (rows.length > 0) store.insertBatch(rows);
|
|
129
|
-
return { imported: rows.length, skipped };
|
|
130
138
|
}
|
|
131
139
|
|
|
132
140
|
/** Reject symlink parents as well as lexical traversal; never replace a host file. */
|
|
@@ -176,17 +184,24 @@ export function collectPodArtifacts(logs: string, { dir, job, log = (c, e, d) =>
|
|
|
176
184
|
podLogsReturned = true;
|
|
177
185
|
const lines = bytes.length ? bytes.toString('utf8').split('\n').length - (bytes.at(-1) === 10 ? 1 : 0) : 0;
|
|
178
186
|
log('self-implement.pod', 'pod-logs-returned', { job, path: destination, lines });
|
|
179
|
-
|
|
180
|
-
const imported = importPodDecisionLogs(bytes, job, logStore);
|
|
181
|
-
log('self-implement.pod', 'pod-decisions-imported', { job, imported: imported.imported, skipped: imported.skipped });
|
|
182
|
-
} catch (error) {
|
|
183
|
-
log('self-implement.pod', 'pod-logs-import-failed', { job, reason: error instanceof Error ? error.message : String(error) });
|
|
184
|
-
}
|
|
187
|
+
importReturnedLogs(bytes, job, logStore, log);
|
|
185
188
|
}
|
|
186
189
|
} catch (e) {
|
|
187
190
|
const reason = (e as NodeJS.ErrnoException).code === 'EEXIST' ? 'exists' : e instanceof Error ? e.message : String(e);
|
|
188
|
-
if ((e as NodeJS.ErrnoException).code === 'EEXIST')
|
|
189
|
-
|
|
191
|
+
if ((e as NodeJS.ErrnoException).code === 'EEXIST') {
|
|
192
|
+
log('self-implement.pod', 'artifact-collect-skipped', { job, path, reason });
|
|
193
|
+
if (path === podLogsPath) {
|
|
194
|
+
try {
|
|
195
|
+
const existing = resolve(dir, job, path);
|
|
196
|
+
if (!lstatSync(existing).isSymbolicLink() && readFileSync(existing).equals(bytes)) {
|
|
197
|
+
podLogsReturned = true;
|
|
198
|
+
importReturnedLogs(bytes, job, logStore, log);
|
|
199
|
+
}
|
|
200
|
+
} catch (error) {
|
|
201
|
+
debug.log('self-implement.pod', 'pod-logs-import-failed', { job, reason: error instanceof Error ? error.message : String(error) });
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
} else log('self-implement.pod', 'artifact-collect-incomplete', { job, path, reason });
|
|
190
205
|
if (path === podLogsPath) podLogsMissingReason = reason;
|
|
191
206
|
}
|
|
192
207
|
}
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
import { randomBytes } from 'node:crypto';
|
|
9
9
|
import { spawnSync } from 'node:child_process';
|
|
10
|
+
import { mkdirSync, writeFileSync } from 'node:fs';
|
|
10
11
|
import { isAbsolute, resolve } from 'node:path';
|
|
11
12
|
import { getUserConfig } from '../../user-config.js';
|
|
12
13
|
import { debug } from '../../debug/log.js';
|
|
@@ -35,6 +36,14 @@ export const POD_LITE_IMAGE = 'elanous-harness-lite:local';
|
|
|
35
36
|
|
|
36
37
|
export const POD_LITE_MEMORY_LIMIT = '2Gi';
|
|
37
38
|
|
|
39
|
+
/** 요청은 한도를 넘을 수 없다(쿠버네티스가 Job 을 거부한다) — lite 2Gi 처럼 한도가 기본 요청보다 작으면 한도로 맞춘다. */
|
|
40
|
+
export function memoryRequestWithin(limit: string | undefined): string {
|
|
41
|
+
const gi = (value: string | undefined) => { const m = /^(\d+)Gi$/.exec(value ?? ''); return m ? Number(m[1]) : null; };
|
|
42
|
+
const request = gi(POD_CHILD_REQUESTS.memory);
|
|
43
|
+
const max = gi(limit);
|
|
44
|
+
return request !== null && max !== null && request > max ? limit! : POD_CHILD_REQUESTS.memory;
|
|
45
|
+
}
|
|
46
|
+
|
|
38
47
|
/** POD7 must-fix: the skill list alone can't tell a network-only job from one that needs ffmpeg/browser — so lite is
|
|
39
48
|
* only ever chosen when the caller says so (`lite`), and then only for network skills without a clone. Anything we
|
|
40
49
|
* can't judge keeps the full image. A lite request that doesn't fit is refused with the reason, not silently widened. */
|
|
@@ -50,6 +59,26 @@ export function podCommandImageFor(skills: readonly string[], clone: boolean, li
|
|
|
50
59
|
|
|
51
60
|
const COMMAND_LOG_CHARS = 80;
|
|
52
61
|
|
|
62
|
+
/** 자식 로그 전문 상한(바이트) — 넘으면 끝부분을 남긴다. config `pod.childLogMaxBytes` 가 양의 정수일 때만 그 값. */
|
|
63
|
+
export const POD_CHILD_LOG_MAX_BYTES_DEFAULT = 1_048_576;
|
|
64
|
+
|
|
65
|
+
export function podChildLogMaxBytes(read: () => number | undefined = () => getUserConfig().pod?.childLogMaxBytes): number {
|
|
66
|
+
const configured = read();
|
|
67
|
+
return typeof configured === 'number' && Number.isSafeInteger(configured) && configured > 0
|
|
68
|
+
? configured
|
|
69
|
+
: POD_CHILD_LOG_MAX_BYTES_DEFAULT;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** 크기 상한을 넘으면 끝부분을 남긴다(잘린 앞은 표시). */
|
|
73
|
+
export function tailBytes(text: string, maxBytes: number): string {
|
|
74
|
+
const bytes = Buffer.from(text);
|
|
75
|
+
if (bytes.length <= maxBytes) return text;
|
|
76
|
+
const cut = bytes.subarray(bytes.length - maxBytes);
|
|
77
|
+
let start = 0;
|
|
78
|
+
while (start < cut.length && (cut[start]! & 0xc0) === 0x80) start += 1;
|
|
79
|
+
return `…[앞 ${bytes.length - maxBytes}바이트 생략]\n${cut.subarray(start).toString('utf8')}`;
|
|
80
|
+
}
|
|
81
|
+
|
|
53
82
|
const GATE = `ok=0
|
|
54
83
|
for i in $(seq 1 60); do
|
|
55
84
|
if curl -s -m 1 -o /dev/null http://host.orb.internal:31415/health || curl -s -m 1 -o /dev/null http://core.elanous-prod:8080/; then ok=0; else ok=$((ok+1)); fi
|
|
@@ -156,7 +185,7 @@ export function podCommandJobManifest(o: PodCommandJobInput): Record<string, unk
|
|
|
156
185
|
initContainers: [{ name: 'isolation-gate', image: o.image, imagePullPolicy: o.imagePullPolicy ?? 'Never', command: ['bash', '-c'], args: [GATE] }],
|
|
157
186
|
containers: [{
|
|
158
187
|
name: 'child', image: o.image, imagePullPolicy: o.imagePullPolicy ?? 'Never',
|
|
159
|
-
resources: { requests: { ...POD_CHILD_REQUESTS }, limits: { memory: o.memoryLimit ?? '16Gi', cpu: '4' } },
|
|
188
|
+
resources: { requests: { ...POD_CHILD_REQUESTS, memory: memoryRequestWithin(o.memoryLimit) }, limits: { memory: o.memoryLimit ?? '16Gi', cpu: '4' } },
|
|
160
189
|
command: ['bash', '-c'], args: [script],
|
|
161
190
|
env: [
|
|
162
191
|
...(o.runId ? [{ name: 'ELANOUS_RUN_ID', value: o.runId }] : []),
|
|
@@ -214,12 +243,23 @@ export function podCommandSecretKeys(secret: Record<string, unknown> | null): st
|
|
|
214
243
|
return Object.keys((secret.stringData as Record<string, string> | undefined) ?? {});
|
|
215
244
|
}
|
|
216
245
|
|
|
246
|
+
export interface PodArtifactCollection {
|
|
247
|
+
/** 회수한 산출 파일 수(child.log 제외). 로그 조회 실패면 null. */
|
|
248
|
+
files: number | null;
|
|
249
|
+
/** 회수한 산출 파일의 상대 경로. */
|
|
250
|
+
names: string[];
|
|
251
|
+
/** 로그 조회 자체가 실패했을 때의 사유. 성공이면 null. */
|
|
252
|
+
error: string | null;
|
|
253
|
+
}
|
|
254
|
+
|
|
217
255
|
export interface PodCommandResult {
|
|
218
256
|
exitCode: number;
|
|
219
257
|
artifactsDir: string;
|
|
220
258
|
job: string;
|
|
221
259
|
/** 이 런이 실제로 쓴 이미지(레지스트리 판 태그 또는 로컬 이름). */
|
|
222
260
|
image?: string;
|
|
261
|
+
/** collectPodArtifacts 가 회수한 파일. 로그 조회 실패면 files=null · error=사유. runPodCommand 는 항상 채운다. */
|
|
262
|
+
artifacts?: PodArtifactCollection;
|
|
223
263
|
}
|
|
224
264
|
|
|
225
265
|
export interface RunPodCommandOptions {
|
|
@@ -255,6 +295,8 @@ export interface RunPodCommandOptions {
|
|
|
255
295
|
log?: (category: string, event: string, data: Record<string, unknown>) => void;
|
|
256
296
|
env?: NodeJS.ProcessEnv;
|
|
257
297
|
configHostMirror?: () => string | undefined;
|
|
298
|
+
/** 자식 로그 전문 상한(바이트). 생략 시 config `pod.childLogMaxBytes`, 없으면 1MiB. 넘으면 끝부분 우선. */
|
|
299
|
+
childLogMaxBytes?: number;
|
|
258
300
|
}
|
|
259
301
|
|
|
260
302
|
function jobFinished(types: string): 'complete' | 'failed' | null {
|
|
@@ -263,6 +305,19 @@ function jobFinished(types: string): 'complete' | 'failed' | null {
|
|
|
263
305
|
return null;
|
|
264
306
|
}
|
|
265
307
|
|
|
308
|
+
function artifactNames(logs: string): string[] {
|
|
309
|
+
const names = new Set<string>();
|
|
310
|
+
for (const line of logs.split(/\r?\n/)) {
|
|
311
|
+
const match = /^ELANOUS_POD_ARTIFACT (\S+) \d+\/\d+ /.exec(line);
|
|
312
|
+
if (!match) continue;
|
|
313
|
+
try {
|
|
314
|
+
const path = Buffer.from(match[1]!, 'base64url').toString('utf8');
|
|
315
|
+
if (path && !path.split('/').some((part) => part === '' || part === '.' || part === '..')) names.add(path);
|
|
316
|
+
} catch { /* 회수 쪽(collectPodArtifacts)이 불완전 사유를 남긴다 */ }
|
|
317
|
+
}
|
|
318
|
+
return [...names];
|
|
319
|
+
}
|
|
320
|
+
|
|
266
321
|
function exitCodeFromPod(kubectl: Kubectl, namespace: string, job: string, state: 'complete' | 'failed'): number {
|
|
267
322
|
const got = kubectl(['-n', namespace, 'get', 'pods', '-l', `job-name=${job}`, '-o', 'jsonpath={.items[0].status.containerStatuses[?(@.name=="child")].state.terminated.exitCode}']);
|
|
268
323
|
const code = Number(got.stdout.trim());
|
|
@@ -316,7 +371,7 @@ export async function runPodCommand(options: RunPodCommandOptions): Promise<PodC
|
|
|
316
371
|
}
|
|
317
372
|
if (pool) {
|
|
318
373
|
for (;;) {
|
|
319
|
-
member = pool.tryAcquire();
|
|
374
|
+
member = await pool.tryAcquire();
|
|
320
375
|
if (member) break;
|
|
321
376
|
await sleep(options.pollMs ?? 15_000);
|
|
322
377
|
}
|
|
@@ -387,12 +442,29 @@ export async function runPodCommand(options: RunPodCommandOptions): Promise<PodC
|
|
|
387
442
|
if (finished) { state = finished; break; }
|
|
388
443
|
await sleep(options.pollMs ?? 15_000);
|
|
389
444
|
}
|
|
390
|
-
const
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
445
|
+
const logsResult = kubectl(['-n', namespace, 'logs', `job/${name}`, '-c', 'child']);
|
|
446
|
+
const logsFailed = logsResult.status !== 0;
|
|
447
|
+
const logs = logsFailed ? '' : logsResult.stdout;
|
|
448
|
+
const artifactDir = options.artifactsRoot ?? `${effectiveInstanceRoot()}/pod-artifacts`;
|
|
449
|
+
let artifacts: PodArtifactCollection;
|
|
450
|
+
if (logsFailed) {
|
|
451
|
+
const reason = logsResult.stderr.trim() || `kubectl logs exit ${logsResult.status}`;
|
|
452
|
+
artifacts = { files: null, names: [], error: reason };
|
|
453
|
+
log('pod.command', 'artifact-collect-failed', { job: name, reason });
|
|
454
|
+
} else {
|
|
455
|
+
collectPodArtifacts(logs, {
|
|
456
|
+
dir: artifactDir,
|
|
457
|
+
job: name,
|
|
458
|
+
log: (c, e, d) => log(c, e, d),
|
|
459
|
+
});
|
|
460
|
+
const names = artifactNames(logs);
|
|
461
|
+
artifacts = { files: names.length, names, error: null };
|
|
462
|
+
}
|
|
463
|
+
// 표지가 없어도 알리는 경로는 실제로 존재해야 한다. 자식 로그 전문은 child.log (상한 넘으면 끝부분).
|
|
464
|
+
mkdirSync(artifactsDir, { recursive: true });
|
|
465
|
+
const maxBytes = options.childLogMaxBytes ?? podChildLogMaxBytes();
|
|
466
|
+
const childLog = logsFailed ? `산출 회수 못 함: ${artifacts.error}` : tailBytes(logs, maxBytes);
|
|
467
|
+
writeFileSync(resolve(artifactsDir, 'child.log'), childLog);
|
|
396
468
|
const exitCode = exitCodeFromPod(kubectl, namespace, name, state);
|
|
397
469
|
cleanupSecret();
|
|
398
470
|
log('pod.command', 'job-finished', {
|
|
@@ -401,8 +473,11 @@ export async function runPodCommand(options: RunPodCommandOptions): Promise<PodC
|
|
|
401
473
|
skills,
|
|
402
474
|
llm: llm ?? null,
|
|
403
475
|
command: command.join(' ').slice(0, COMMAND_LOG_CHARS),
|
|
476
|
+
artifactsDir,
|
|
477
|
+
artifacts,
|
|
478
|
+
...(logsFailed ? { artifactError: `산출 회수 못 함: ${artifacts.error}` } : {}),
|
|
404
479
|
});
|
|
405
|
-
return { exitCode, artifactsDir, job: name, image: imageRef ?? image };
|
|
480
|
+
return { exitCode, artifactsDir, job: name, image: imageRef ?? image, artifacts };
|
|
406
481
|
} catch (err) {
|
|
407
482
|
cleanupSecret();
|
|
408
483
|
throw err;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/** Extract a human-readable reason from a failed child Pod without treating transfer markers as child errors. */
|
|
2
|
+
export function extractPodFailureReason(input: {
|
|
3
|
+
logs: string;
|
|
4
|
+
logTailReason?: string;
|
|
5
|
+
containerReason?: string | null;
|
|
6
|
+
jobReason?: string;
|
|
7
|
+
deadlineSeconds?: number;
|
|
8
|
+
}): string {
|
|
9
|
+
if (input.jobReason === 'DeadlineExceeded') {
|
|
10
|
+
return `DeadlineExceeded: Job 수명 상한 ${input.deadlineSeconds ?? 'unknown'}초 초과`;
|
|
11
|
+
}
|
|
12
|
+
if (input.containerReason === 'OOMKilled') return 'OOMKilled: 컨테이너 메모리 한도 초과';
|
|
13
|
+
|
|
14
|
+
const isBookkeeping = (line: string): boolean =>
|
|
15
|
+
/^ELANOUS_(?:MEM|RUN_LEDGER|USAGE_ROLLUP|POD_)/u.test(line) || /^(?:at\s+\S|\.\.\.\s+\d+\s+more\b|cleanup\s+(?:complete|done|finished)\b)/iu.test(line);
|
|
16
|
+
const isError = (line: string): boolean =>
|
|
17
|
+
/\berror\b|\bfail(?:ed|ure)?\b|exception|fatal|denied|timed?\s*out|exceeded|overloaded|unavailable|exhausted|오류|실패/iu.test(line);
|
|
18
|
+
const meaningful: Array<{ text: string; terminal: boolean }> = [];
|
|
19
|
+
for (const line of input.logs.split(/\r\n|\n|\r/u)) {
|
|
20
|
+
const trimmed = line.trim();
|
|
21
|
+
if (!trimmed || isBookkeeping(trimmed)) continue;
|
|
22
|
+
if (trimmed.startsWith('{')) {
|
|
23
|
+
let parsed: unknown;
|
|
24
|
+
try { parsed = JSON.parse(trimmed); } catch { /* An ordinary line may begin with a brace. */ }
|
|
25
|
+
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
|
|
26
|
+
const row = parsed as Record<string, unknown>;
|
|
27
|
+
if (typeof row.ok === 'boolean' && typeof row.stage === 'string') {
|
|
28
|
+
meaningful.length = 0;
|
|
29
|
+
if (row.ok === false && typeof row.error === 'string') {
|
|
30
|
+
const errorLines = row.error.split(/\r\n|\n|\r/u).map((part) => part.trim())
|
|
31
|
+
.filter((part) => part && !isBookkeeping(part));
|
|
32
|
+
const errorLine = [...errorLines].reverse().find(isError) ?? errorLines.at(-1);
|
|
33
|
+
if (errorLine) meaningful.push({ text: errorLine, terminal: true });
|
|
34
|
+
}
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
// A JSON transfer/observation is not a plain-text failure reason.
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
meaningful.push({ text: trimmed, terminal: false });
|
|
42
|
+
}
|
|
43
|
+
const errorLine = [...meaningful].reverse().find((line) => line.terminal || isError(line.text));
|
|
44
|
+
const reason = errorLine?.text.replace(/\/home\/[^/\s]+\//gu, '~/').slice(0, 240);
|
|
45
|
+
if (reason) return reason;
|
|
46
|
+
const why = input.logTailReason?.trim() || (input.logs.trim() ? '로그에 읽을 수 있는 오류 줄 없음' : '자식 로그 비어 있음');
|
|
47
|
+
return `사유 못 읽음: ${why.replace(/\/home\/[^/\s]+\//gu, '~/').slice(0, 240)}`;
|
|
48
|
+
}
|
|
@@ -12,12 +12,15 @@ 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 {
|