elanous 0.2.20 → 0.2.22
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/aBi6EyMtLsrl7J8eH4kF7/_buildManifest.js +1 -0
- package/apps/pwa/out/_next/static/chunks/088d8e8a.8069543d082bec12.js +160 -0
- package/apps/pwa/out/_next/static/chunks/1109.78a7a3288c0259d9.js +148 -0
- package/apps/pwa/out/_next/static/chunks/115.1c6690b8604f81c4.js +32 -0
- package/apps/pwa/out/_next/static/chunks/119fc678.6f591cdb84ca5791.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1262.76a3f08e67c5f816.js +309 -0
- package/apps/pwa/out/_next/static/chunks/1307-08b716476f1d50af.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1358d292.5c87f12cad6eaa91.js +122 -0
- package/apps/pwa/out/_next/static/chunks/1438.27a1a7b8f2308004.js +149 -0
- package/apps/pwa/out/_next/static/chunks/1442.02d85cd88f4c1eac.js +79 -0
- package/apps/pwa/out/_next/static/chunks/1501-1aad662d82ba43f7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1584.9e2dd53da6e6d4b1.js +24 -0
- package/apps/pwa/out/_next/static/chunks/1586.b989b0614afb77e6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1609.2ad355062870151f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1612-f6df0fc0eed7157b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1923.37f1d5fe7ebe3860.js +27 -0
- package/apps/pwa/out/_next/static/chunks/1948.5f35b39eef222a94.js +5 -0
- package/apps/pwa/out/_next/static/chunks/1993.3fd074a2a246234e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2140.d2daf9ee96e295d1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2212.0359851457473efc.js +1 -0
- package/apps/pwa/out/_next/static/chunks/224.583e562365a52f74.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2328.0f37465646bafa7d.js +36 -0
- package/apps/pwa/out/_next/static/chunks/2372.bf71372184991937.js +286 -0
- package/apps/pwa/out/_next/static/chunks/{2489-ff55a35b202c0f94.js → 2489-d238d3e2eb5b2bf5.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/2549-a8515c86f9e8cb96.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2655.72cc6a97276c5acc.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2743-ef6e8c3ced74cce5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2840-5458ad50c49cac6a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2856.f75d5baef7f49b74.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3344.0de4b0ec5a454b7a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/335.a13f339e97893437.js +94 -0
- package/apps/pwa/out/_next/static/chunks/3394.646348f34b2500be.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3443.6276909b53d72d53.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3495.abd4254d808bf2dc.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3522-1de1319d028ac89c.js +25 -0
- package/apps/pwa/out/_next/static/chunks/3721.ba5b82db0d9a5572.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3857-5985236d9307f676.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3919.9ff7dc25afefee33.js +74 -0
- package/apps/pwa/out/_next/static/chunks/3998.bd9a69d9b50e9bd2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4003.ab6db1dc266576c9.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4216.e8ac94b9fe6b96ab.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4327.c44dc2b55cb43ee5.js +34 -0
- package/apps/pwa/out/_next/static/chunks/4401.94257d497b6d26bc.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4559.17ca2cdbaa8d414f.js +115 -0
- package/apps/pwa/out/_next/static/chunks/4588.ad3364cb7fb97ada.js +86 -0
- package/apps/pwa/out/_next/static/chunks/4706.97e734bd4c9816ca.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4982.6e44352f751b9a1b.js +77 -0
- package/apps/pwa/out/_next/static/chunks/5342.49c5c5a530e62b50.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5360-08a80abdf3a4da99.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5431.66c58ab1327ee0b7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5633-d6852385000738ef.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5717.bcfcb96e701b82e0.js +90 -0
- package/apps/pwa/out/_next/static/chunks/5755-30d102586789bf7f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5957.3f1a4012388dd962.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5977-5b9e8a45a5b0c823.js +1 -0
- package/apps/pwa/out/_next/static/chunks/602.9bf02ac8e9502598.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6020.3a1b347e2d2dfacc.js +8 -0
- package/apps/pwa/out/_next/static/chunks/{6158.af2e93b7adff709b.js → 6158.566c890adc06a459.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/6375.fce1d174ba6d0324.js +64 -0
- package/apps/pwa/out/_next/static/chunks/6546.92a828aef441271f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/658.957fdb0a8c6c6c8e.js +287 -0
- package/apps/pwa/out/_next/static/chunks/6706.323590aabb8b1293.js +226 -0
- package/apps/pwa/out/_next/static/chunks/6747.b67947f293a4c02d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6767.8e6c5f6e744fcd9f.js +38 -0
- package/apps/pwa/out/_next/static/chunks/6868.000b24a3045c5574.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6946.bba74e9c96050f30.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7033.a558f1b2f6912a53.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7065.46029c26c53eac50.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7087.6fae577d09249bc1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7175-350f4ce2873bc038.js +1 -0
- package/apps/pwa/out/_next/static/chunks/722.de8d9801e0f6d414.js +41 -0
- package/apps/pwa/out/_next/static/chunks/7497.57981e9d7a75a52a.js +24 -0
- package/apps/pwa/out/_next/static/chunks/7630.2b9a68659053ff4e.js +193 -0
- package/apps/pwa/out/_next/static/chunks/7672.4af122ad892117c7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/771.6ab889a233e7dae7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7899.597af4a9996c907f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/79b78388.663188133449d707.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8096.f7da0776cfde6300.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8194.484f51e273d9abd2.js +217 -0
- package/apps/pwa/out/_next/static/chunks/8769.815ca69879f608b4.js +104 -0
- package/apps/pwa/out/_next/static/chunks/8857-aa1cc4ac90af4d40.js +1 -0
- package/apps/pwa/out/_next/static/chunks/88729267.9c6c000157c83f2f.js +62 -0
- package/apps/pwa/out/_next/static/chunks/8f835b4f.048eeb02299e3e12.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9068.5f2764fccf86c1b2.js +93 -0
- package/apps/pwa/out/_next/static/chunks/9247-55bcfbb0e8057f59.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9451.ebfb0dcc9c02250f.js +23 -0
- package/apps/pwa/out/_next/static/chunks/9559.e648369b1d57cf9a.js +62 -0
- package/apps/pwa/out/_next/static/chunks/9832.2ab2e818013574c7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/a9f2b54d-40c16bd0efdb04b8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/approvals/{page-c855c24de7db74fe.js → page-88459e0903da2803.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/autopilot/{page-85b316bb4235980b.js → page-d85c43f46f58716b.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/board/{page-9b2b26a33cb2cd42.js → page-4e645600579ae952.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/botlab/{page-c9955fde67b1bcb5.js → page-87edbfd19117823b.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/bots/page-7f0c2b7801ac6a54.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ceo/page-bf1f030a9d1adffd.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/chat/{page-4ba4abe096b6c2a8.js → page-cbaf66b1e1804adf.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/consult/{page-575d4a8b4f7becb2.js → page-80ed511e02c81412.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/control/{page-f5d56ff5f45ad164.js → page-9f5610caaa06178c.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/decisions/{page-2260e9eef0357421.js → page-ec8f3575e339de74.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-86d753e460f1a989.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/editor/page-4166a7d51cf0a57d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/exec/{page-466bf0ee16ad1432.js → page-2a19c5cd3b562580.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/field/{page-a23bc71713bcc08f.js → page-b6a09cde2e07521a.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/inside/page-f7eae1518ca0c353.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/intake/page-456a8573ad811381.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/layout-8462d6a543cbb2d9.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/live/page-2974cdfb87c0f9c9.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/live-run/page-f927ffa25f3c86d1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/loops/{page-35bfec2e631d3eae.js → page-abd86cd832aaa011.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/market/page-79daf68f6256adaa.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/{page-f9ccb1b1adbea14d.js → page-8c3f168fc3f91939.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/morning/{page-ec102c9b8b691e37.js → page-786fb4504d0f2fa9.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/not-found-75209f14597dcb8f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-bf30affe3a85af7e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-1187a44b7515434b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-3d0910185a66ed54.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-f401ee1dd5a26e25.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/outputs/page-5dfe7604fd842784.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/page-76cf838050a8d681.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/reflection/{page-db03ae3589a81c9e.js → page-7ec8e289b4c14df6.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/scheduler/{page-4a6463b97f0ad94f.js → page-8c8ac17d52be2a79.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/sessions/{page-75bffecc9aa1004e.js → page-d359ab048d63a9d9.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-b8b345c880bf78d1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/{page-31d5b875760bb62d.js → page-f151cc10ad026940.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-9875f103057da433.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/page-9789328af9a9aa62.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/share/{page-46d5201d2207a511.js → page-bf495a1e284f4e00.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-a263d94bca4be22a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-3962eab8ec58d16e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/term/{page-cf0a4b4abead71ea.js → page-9abcfac07e02a542.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/today/page-bee49f927c333abb.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/trace/page-bac2d56dcb02d955.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/vault/page-937cbcc13176295c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-d09495594f7f480d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/{page-0acc8e198f7b0dfb.js → page-69cb93d1a2e57e0e.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/workspace/{page-34882c84f84021c5.js → page-72ec543928d67c3e.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-82906eeda0186a7b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/d70879db.c66c56a95a91efc6.js +66 -0
- package/apps/pwa/out/_next/static/chunks/f8626309.a4e5d362b1d55e59.js +1 -0
- package/apps/pwa/out/_next/static/chunks/webpack-f2b4016b9b981bc8.js +1 -0
- package/apps/pwa/out/_next/static/css/{d3427996da11e480.css → b9d8151e48998417.css} +1 -1
- package/apps/pwa/out/_next/static/css/f2bd5257e6af88d8.css +1 -0
- package/apps/pwa/out/approvals/index.html +1 -1
- package/apps/pwa/out/approvals/index.txt +14 -14
- package/apps/pwa/out/autopilot/index.html +1 -1
- package/apps/pwa/out/autopilot/index.txt +14 -14
- package/apps/pwa/out/board/index.html +1 -1
- package/apps/pwa/out/board/index.txt +14 -14
- package/apps/pwa/out/botlab/index.html +1 -1
- package/apps/pwa/out/botlab/index.txt +14 -14
- package/apps/pwa/out/bots/index.html +1 -1
- package/apps/pwa/out/bots/index.txt +14 -14
- package/apps/pwa/out/ceo/index.html +1 -1
- package/apps/pwa/out/ceo/index.txt +14 -14
- package/apps/pwa/out/chat/index.html +1 -1
- package/apps/pwa/out/chat/index.txt +14 -14
- package/apps/pwa/out/consult/index.html +1 -1
- package/apps/pwa/out/consult/index.txt +14 -14
- package/apps/pwa/out/control/index.html +1 -1
- package/apps/pwa/out/control/index.txt +14 -14
- package/apps/pwa/out/decisions/index.html +1 -1
- package/apps/pwa/out/decisions/index.txt +14 -14
- package/apps/pwa/out/design-check/index.html +1 -1
- package/apps/pwa/out/design-check/index.txt +14 -14
- package/apps/pwa/out/editor/index.html +1 -1
- package/apps/pwa/out/editor/index.txt +15 -15
- package/apps/pwa/out/exec/index.html +1 -1
- package/apps/pwa/out/exec/index.txt +14 -14
- package/apps/pwa/out/field/index.html +1 -1
- package/apps/pwa/out/field/index.txt +14 -14
- package/apps/pwa/out/healthz +1 -1
- package/apps/pwa/out/index.html +1 -1
- package/apps/pwa/out/index.txt +14 -14
- package/apps/pwa/out/inside/index.html +1 -1
- package/apps/pwa/out/inside/index.txt +15 -15
- package/apps/pwa/out/intake/index.html +1 -1
- package/apps/pwa/out/intake/index.txt +14 -14
- package/apps/pwa/out/live/index.html +1 -1
- package/apps/pwa/out/live/index.txt +14 -14
- package/apps/pwa/out/live-run/index.html +1 -0
- package/apps/pwa/out/live-run/index.txt +35 -0
- package/apps/pwa/out/loops/index.html +1 -1
- package/apps/pwa/out/loops/index.txt +14 -14
- package/apps/pwa/out/market/index.html +1 -1
- package/apps/pwa/out/market/index.txt +14 -14
- package/apps/pwa/out/missions/placeholder/index.html +1 -1
- package/apps/pwa/out/missions/placeholder/index.txt +14 -14
- package/apps/pwa/out/morning/index.html +1 -1
- package/apps/pwa/out/morning/index.txt +14 -14
- package/apps/pwa/out/observatory/index.html +1 -1
- package/apps/pwa/out/observatory/index.txt +14 -14
- package/apps/pwa/out/ops/checklist/index.html +1 -1
- package/apps/pwa/out/ops/checklist/index.txt +14 -14
- package/apps/pwa/out/ops/release/index.html +1 -1
- package/apps/pwa/out/ops/release/index.txt +14 -14
- package/apps/pwa/out/ops/seats/index.html +1 -1
- package/apps/pwa/out/ops/seats/index.txt +14 -14
- package/apps/pwa/out/outputs/index.html +1 -1
- package/apps/pwa/out/outputs/index.txt +14 -14
- package/apps/pwa/out/reflection/index.html +1 -1
- package/apps/pwa/out/reflection/index.txt +15 -15
- package/apps/pwa/out/scheduler/index.html +1 -1
- package/apps/pwa/out/scheduler/index.txt +14 -14
- package/apps/pwa/out/sessions/index.html +1 -1
- package/apps/pwa/out/sessions/index.txt +14 -14
- package/apps/pwa/out/settings/devices/index.html +1 -1
- package/apps/pwa/out/settings/devices/index.txt +14 -14
- package/apps/pwa/out/settings/index.html +1 -1
- package/apps/pwa/out/settings/index.txt +14 -14
- package/apps/pwa/out/setup/done/index.html +1 -1
- package/apps/pwa/out/setup/done/index.txt +14 -14
- package/apps/pwa/out/setup/index.html +1 -1
- package/apps/pwa/out/setup/index.txt +14 -14
- package/apps/pwa/out/share/index.html +1 -1
- package/apps/pwa/out/share/index.txt +14 -14
- package/apps/pwa/out/showroom/index.html +1 -1
- package/apps/pwa/out/showroom/index.txt +14 -14
- package/apps/pwa/out/tasks/index.html +1 -1
- package/apps/pwa/out/tasks/index.txt +14 -14
- package/apps/pwa/out/term/index.html +1 -1
- package/apps/pwa/out/term/index.txt +14 -14
- package/apps/pwa/out/today/index.html +1 -1
- package/apps/pwa/out/today/index.txt +14 -14
- package/apps/pwa/out/trace/index.html +1 -1
- package/apps/pwa/out/trace/index.txt +14 -14
- package/apps/pwa/out/vault/index.html +1 -1
- package/apps/pwa/out/vault/index.txt +14 -14
- package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
- package/apps/pwa/out/workflows/chat-ui/index.txt +14 -14
- package/apps/pwa/out/workflows/index.html +1 -1
- package/apps/pwa/out/workflows/index.txt +14 -14
- package/apps/pwa/out/workspace/index.html +1 -1
- package/apps/pwa/out/workspace/index.txt +14 -14
- package/apps/pwa/out/worktrees/index.html +1 -1
- package/apps/pwa/out/worktrees/index.txt +14 -14
- package/graphs/catalog/catalog.yaml +1 -1
- package/graphs/drafts/recipes.yaml +6 -6
- package/graphs/implement-loop.yaml +277 -5
- package/graphs/test-diet/recipes.yaml +3 -0
- package/graphs/test-diet/test-diet.yaml +3 -1
- package/package.json +1 -1
- package/scripts/coord-channel-watch.sh +31 -4
- package/scripts/docs-lint.ts +4 -1
- package/scripts/f-session-monitor.ts +69 -0
- package/scripts/hq/standby-snapshot.ts +7 -3
- package/scripts/knowledge-compress.ts +11 -0
- package/scripts/marketing/generate-ax-brief.ts +18 -1
- package/scripts/measure-nl-routing-corpus.ts +11 -3
- package/scripts/nightly-docops.ts +90 -3
- package/scripts/release-loop/gate-node.ts +5 -5
- package/scripts/release-loop/shard-plan.ts +50 -5
- package/scripts/research-coo-admin-dates.ts +39 -0
- package/scripts/rhythm/daily.ts +2 -1
- package/scripts/skills/public-check.ts +7 -2
- package/scripts/smart-merge/resync-shadow.ts +5 -3
- package/scripts/test-diet/lib.ts +81 -1
- package/scripts/test-diet/node.ts +48 -2
- package/scripts/verify-coo-admin-evidence.ts +83 -0
- package/src/acp/client.ts +4 -2
- package/src/agent/run-context.ts +4 -0
- package/src/agent-env/profile.ts +25 -5
- package/src/agent-mission/driver.ts +26 -12
- package/src/agent-mission/supervisor-metrics.ts +17 -2
- package/src/agent-substrate/acp-reviewer.ts +12 -5
- package/src/agent-substrate/pr-reviewer.ts +11 -4
- package/src/agent-substrate/review-intent.ts +102 -3
- package/src/autopilot/build/llm-conflict-merge.ts +22 -8
- package/src/autopilot/pr-manager.ts +18 -8
- package/src/away/release-watch.ts +47 -8
- package/src/briefing/brief-items.ts +104 -11
- package/src/browser-cdp/client.ts +10 -5
- package/src/chat/index.ts +1 -0
- package/src/cli/brief-cli.ts +20 -2
- package/src/cli/config-classify.ts +83 -0
- package/src/cli/control-cli.ts +14 -0
- package/src/cli/decisions-cli.ts +26 -16
- package/src/cli/logs-model-input.ts +1 -1
- package/src/cli/ops-cli.ts +11 -0
- package/src/cli/ops-overview-cli.ts +50 -0
- package/src/cli/pod-cli.ts +1 -0
- package/src/cli/pr-cli.ts +65 -8
- package/src/cli/release-cli.ts +21 -4
- package/src/cli/tasks-cli.ts +214 -1
- package/src/connectors/types.ts +6 -6
- package/src/context-bus/context-now-surfaces.ts +18 -0
- package/src/context-bus/coord-events.ts +32 -4
- package/src/context-bus/long-term-memory.ts +23 -6
- package/src/context-bus/nightly-condense.ts +169 -21
- package/src/control/control-status.ts +87 -0
- package/src/control-plane/light-member.ts +5 -3
- package/src/control-plane/member.ts +172 -7
- package/src/core-turn/run-goal-loop.ts +22 -1
- package/src/dashboard/index.ts +40 -4
- package/src/dashboard/slash-runtime/dashboard-handlers.ts +9 -0
- package/src/decisions/decision-cards.ts +20 -1
- package/src/decisions/decision-ledger.ts +11 -8
- package/src/decisions/web-push-decision-cards.ts +19 -3
- package/src/discord-self-message.ts +2 -2
- package/src/domains/coo-admin-dates.evidence.json +876 -0
- package/src/domains/coo-admin-tool.ts +48 -14
- package/src/domains/ops-status.ts +53 -0
- package/src/domains/outbound-alert.ts +24 -1
- package/src/esc-abort-gate.ts +3 -1
- package/src/exec-requests/planner.ts +6 -0
- package/src/graph-kinds/registry.ts +6 -6
- package/src/graph-runner/graph-cli.ts +91 -2
- package/src/graph-runner/graph-run-api.ts +20 -0
- package/src/graph-runner/graph-tick.ts +73 -12
- package/src/graph-runner/runner.ts +43 -11
- package/src/graph-unify/wf-to-graph.ts +79 -0
- package/src/graph-wizard/archetypes.ts +88 -0
- package/src/graph-wizard/generate.ts +577 -0
- package/src/graph-wizard/saved-steps.ts +120 -0
- package/src/graph-wizard/steps.ts +206 -0
- package/src/harness/harness-cli-command.ts +186 -32
- package/src/harness/harness-queue.ts +58 -14
- package/src/harness/launch-advice.ts +23 -0
- package/src/index.ts +43 -11
- package/src/intake-plane/digest.ts +18 -11
- package/src/knowledge/conclusion-compress.ts +93 -0
- package/src/knowledge/know-find.ts +9 -2
- package/src/knowledge/query.ts +20 -0
- package/src/llm.ts +26 -16
- package/src/loops/neighbor-runtime.ts +88 -0
- package/src/loops/orchestrator/author-depth.ts +29 -11
- package/src/loops/orchestrator/overlap.ts +7 -1
- package/src/loops/orchestrator/resync-shadow.ts +1 -1
- package/src/loops/orchestrator/tick.ts +35 -2
- package/src/loops/orchestrator/traffic.ts +2 -0
- package/src/loops/registry.ts +5 -0
- package/src/market/publish.ts +17 -1
- package/src/maturity/feature-maturity.ts +4 -2
- package/src/mss/logging/redaction.ts +17 -0
- package/src/nexus/api/draft-metrics.ts +128 -0
- package/src/nexus/api/graph-peer-edit.ts +77 -0
- package/src/nexus/api/graph-wizard-api.ts +61 -0
- package/src/nexus/api/graphs-api.ts +193 -17
- package/src/nexus/api/harness-traversal.ts +205 -0
- package/src/nexus/api/http-server.ts +122 -6
- package/src/nexus/api/plugins-market.ts +24 -0
- package/src/nexus/index.ts +3 -2
- package/src/nexus/outbound/router.ts +28 -8
- package/src/oauth/claude-pty-maturity.ts +24 -0
- package/src/oauth/fallback-chain.ts +27 -14
- package/src/onboarding/first-run.ts +3 -0
- package/src/ops/overview-collect.ts +345 -0
- package/src/ops/overview-seat-era.ts +90 -0
- package/src/ops/overview.ts +223 -0
- package/src/plugins/maker/market-bundle.ts +9 -0
- package/src/plugins/maker/plugin-maker.ts +9 -3
- package/src/plugins/maker/wizard-generate.ts +73 -9
- package/src/policy/llm-policy.ts +11 -6
- package/src/release-loop/checklist-board.ts +27 -0
- package/src/release-loop/checklist.ts +38 -0
- package/src/release-loop/gate-shards.ts +7 -2
- package/src/seat-address/seat-address.ts +1 -0
- package/src/seat-loop/seat-loop.ts +174 -20
- package/src/seat-loop/seat-report.ts +29 -1
- package/src/self-dev/ask-launch-flow.ts +4 -2
- package/src/self-dev/ask-launch-io.ts +3 -1
- package/src/self-dev/dev-cli.ts +90 -8
- package/src/self-dev/dev-pipeline.ts +20 -1
- package/src/self-dev/draft-sweep.ts +45 -8
- package/src/self-dev/graph-journey-nodes.ts +157 -0
- package/src/self-dev/graph-journey-route.ts +105 -0
- package/src/self-dev/launch-preflight.ts +43 -3
- package/src/self-dev/orchestrate.ts +3 -0
- package/src/self-dev/release-path-guard.ts +7 -4
- package/src/self-dev/review-provider-fallback.ts +83 -0
- package/src/self-dev/run-supervisor.ts +5 -1
- package/src/self-implement/frozen-merges.ts +23 -0
- package/src/self-implement/goal-author.ts +14 -0
- package/src/self-implement/harness-policy-gates.ts +7 -5
- package/src/self-implement/heal-intake.ts +3 -0
- package/src/self-implement/host-regate.ts +28 -14
- package/src/self-implement/l8-merge-queue.ts +80 -8
- package/src/self-implement/orchestrator.ts +84 -28
- package/src/self-implement/seams.ts +8 -3
- package/src/self-implement/self-implement-cli.ts +4 -0
- package/src/self-implement/self-implement-runtime.ts +12 -0
- package/src/session-runtime/index.ts +5 -3
- package/src/shell-env-bootstrap.ts +35 -6
- package/src/steward/triage.ts +25 -8
- package/src/storage/file-lock.ts +33 -4
- package/src/task-agent/card-evidence.ts +6 -0
- package/src/task-agent/completion-evidence.ts +31 -3
- package/src/task-agent/cover.ts +61 -0
- package/src/task-agent/live-moves.ts +126 -3
- package/src/task-agent/parent-host.ts +315 -0
- package/src/task-agent/shadow.ts +19 -4
- package/src/task-agent/task-hand.ts +11 -2
- package/src/task-orchestrator/surfaces/pod-command-job.ts +41 -6
- package/src/task-orchestrator/surfaces/pod-gate-deps.ts +123 -0
- package/src/task-orchestrator/surfaces/pod-pool.ts +138 -15
- package/src/task-orchestrator/surfaces/self-implement-pod.ts +39 -6
- package/src/task-orchestrator/surfaces/self-implement.ts +7 -0
- package/src/telegram-commands.ts +8 -0
- package/src/telegram-project-command.ts +6 -0
- package/src/telegram.ts +2 -2
- package/src/tool-runtime/browser-runtime.ts +87 -3
- package/src/tool-runtime/index.ts +1 -0
- package/src/typecheck-ratchet.ts +2 -1
- package/src/user-config.ts +55 -8
- package/src/version/packed-revision.json +1 -1
- package/src/workflow-runtime/executor.ts +24 -2
- package/src/workflow-runtime/triggers/registry.ts +27 -0
- package/src/workflow-runtime/triggers/schedule-source.ts +30 -3
- package/apps/pwa/out/_next/static/Bam1YziK2iH9bN4K0utGk/_buildManifest.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1307-77fad80cf54477c6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1612-f60c0c93ec773f81.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1844-9ec4b1d12f6e80eb.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1866-e7a6999f25548b30.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1948.15f356b15a625597.js +0 -5
- package/apps/pwa/out/_next/static/chunks/2116-6429c188eb846ab8.js +0 -1
- package/apps/pwa/out/_next/static/chunks/224-e7be8f762fe74986.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2549-89c518a4c8ed4d00.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2957-96ffab3d521fb08f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3857-995ea9f5ef33d71e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5360-be1f1d8f9fcc9755.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5755-ea7ed88eb956f47b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5977-94ed6f78e9814b2b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7748-6e51bfe927019f5c.js +0 -25
- package/apps/pwa/out/_next/static/chunks/9247-c1753a059e7bac08.js +0 -1
- package/apps/pwa/out/_next/static/chunks/a9f2b54d-33c0689b70ed8cf3.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/bots/page-7a852956c74015a3.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ceo/page-c5a5c621be0e04ae.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-7c058069c5e7fa63.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/editor/page-dc5da0b8a2edb8a4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/inside/page-ed4a6f799f005d09.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/intake/page-9cd565005b93aca6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/layout-55f0f40c6db95f15.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/live/page-474cb5888ea0e8f3.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/market/page-9ca21f9ad3208a61.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/not-found-a9c2cfa8dda922a5.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-6da35144878feb6d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-f6055495e9ed7ea8.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-25204694fa9984a5.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-a60e035f151d84d3.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/outputs/page-189c587f36874538.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/page-c56634bde705a65d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-a8d2fe0c669e0de4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-c26fa10aac6c21c4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/page-68793c6e10331d66.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-d81c07a7b4f618ae.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-be1c4e8697f22360.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/today/page-a4717e2c4bf7a28f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/trace/page-0fd895fab9b62c1b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/vault/page-8b6c1454a8e8f306.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-c6e4364d9de8f717.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-0506a1e0e4bdb2f5.js +0 -1
- package/apps/pwa/out/_next/static/chunks/webpack-c260571513bf43c2.js +0 -1
- package/apps/pwa/out/_next/static/css/c23d959d3ee15e57.css +0 -1
- /package/apps/pwa/out/_next/static/{Bam1YziK2iH9bN4K0utGk → aBi6EyMtLsrl7J8eH4kF7}/_ssgManifest.js +0 -0
- /package/skills/project-onboarding/{skill.md → SKILL.md} +0 -0
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HARNESS-PARENT-ON-MSB1 — `tasks hand --live` 의 하니스 «부모»를 다른 호스트(ssh)에서 띄우는 첫 조각 ⊕ 낡은 부모 보고.
|
|
3
|
+
*
|
|
4
|
+
* 왜 «첫 조각»인가 (2026-10-08 실측):
|
|
5
|
+
* - 부모가 호스트에서 기대는 것이 전부 «호스트 로컬 파일»이다 — 착지 동결(`landing-freeze.json` ⊕ `landing-merges/` 표지
|
|
6
|
+
* 무용), 풀 예약(`HostPoolLease`), 런 원장(`run-ledger/`), logs.db. 원격 부모가 `--merge-by-host` 로 병합하면 HQ 의
|
|
7
|
+
* 동결·컷 배수(drain)를 «못 본 채» 병합한다.
|
|
8
|
+
* - 그래서 원격 부모는 «PR 까지만» 간다: `--merge-by-host` → `--no-auto-merge`. 병합 권한은 HQ 에 남는다(동결 안전).
|
|
9
|
+
* - 발사 전 점검(preflight)이 하나라도 막히면 원격으로 띄우지 않고 «종전 로컬 발사»로 돌아간다(fail-closed · 관측 남김).
|
|
10
|
+
* - 관측: 카드에 `parentHost` 를 적고, `tasks show` 가 원격 런 원장을 ssh 로 HQ 의 거울 디렉터리로 당겨 읽는다.
|
|
11
|
+
*
|
|
12
|
+
* 기본 OFF — `taskAgent.parentHost` 가 없고 `ELANOUS_TA_PARENT_HOST` 도 없으면 아무것도 바뀌지 않는다.
|
|
13
|
+
*/
|
|
14
|
+
import { spawnSync } from 'node:child_process';
|
|
15
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, renameSync, rmSync, writeFileSync } from 'node:fs';
|
|
16
|
+
import { join, resolve } from 'node:path';
|
|
17
|
+
import { debug } from '../debug/log.js';
|
|
18
|
+
import { shellQuote } from './task-hand.js';
|
|
19
|
+
|
|
20
|
+
/** `taskAgent.parentHost` 설정 칸. */
|
|
21
|
+
export interface ParentHostConfig {
|
|
22
|
+
/** ssh 대상(예: `node-b`). */
|
|
23
|
+
host: string;
|
|
24
|
+
/** 원격에서 부모를 띄울 git 작업 트리(절대 경로 · 원격 기준). */
|
|
25
|
+
cwd: string;
|
|
26
|
+
/** 원격 elanous 진입점 — 기본 `$HOME/.local/share/elanous/bin/elanous`(설치본). */
|
|
27
|
+
elanous?: string;
|
|
28
|
+
/** 원격 config-dir — 기본 `$HOME/.elanous`. */
|
|
29
|
+
configDir?: string;
|
|
30
|
+
/** 원격 부모에 줄 `--pod-pool`(원격 kube context 이름이 HQ 와 다를 때). */
|
|
31
|
+
podPool?: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export const PARENT_HOST_ENV = 'ELANOUS_TA_PARENT_HOST';
|
|
35
|
+
const DEFAULT_REMOTE_ELANOUS = '$HOME/.local/share/elanous/bin/elanous';
|
|
36
|
+
const DEFAULT_REMOTE_CONFIG_DIR = '$HOME/.elanous';
|
|
37
|
+
const HOST_RE = /^[A-Za-z0-9][A-Za-z0-9._@-]{0,127}$/;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* 켜졌나 — env 가 이긴다(`off`·`0`·빈 값이면 끔). env 가 호스트를 주면 나머지 칸(cwd 등)은 설정에서 받는다.
|
|
41
|
+
* cwd 가 없으면 켜지지 않는다(어디서 띄울지 모르는 원격 발사는 하지 않는다).
|
|
42
|
+
*/
|
|
43
|
+
export function resolveParentHost(config: Partial<ParentHostConfig> | undefined, env: NodeJS.ProcessEnv = process.env): ParentHostConfig | null {
|
|
44
|
+
const fromEnv = env[PARENT_HOST_ENV];
|
|
45
|
+
if (fromEnv !== undefined && ['', '0', 'off', 'false', 'local'].includes(fromEnv.trim().toLowerCase())) return null;
|
|
46
|
+
const host = (fromEnv?.trim() || config?.host || '').trim();
|
|
47
|
+
if (!host || !HOST_RE.test(host)) return null;
|
|
48
|
+
const cwd = config?.cwd?.trim();
|
|
49
|
+
if (!cwd || !cwd.startsWith('/')) return null;
|
|
50
|
+
return { host, cwd, ...(config?.elanous ? { elanous: config.elanous } : {}), ...(config?.configDir ? { configDir: config.configDir } : {}), ...(config?.podPool ? { podPool: config.podPool } : {}) };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** `$HOME/…` 은 원격 셸이 풀도록 따옴표 밖에 둔다 — 나머지는 shellQuote. */
|
|
54
|
+
function remotePath(path: string): string {
|
|
55
|
+
return path.startsWith('$HOME/') ? `"$HOME"/${shellQuote(path.slice('$HOME/'.length))}` : shellQuote(path);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** 원격 부모 인자 — HQ 병합 권한을 넘기지 않는다: `--merge-by-host` → `--no-auto-merge` ⊕ (있으면) `--pod-pool`. */
|
|
59
|
+
export function remoteParentArgs(args: readonly string[], podPool?: string): string[] {
|
|
60
|
+
const out = args.map((arg) => arg === '--merge-by-host' ? '--no-auto-merge' : arg);
|
|
61
|
+
if (!out.includes('--no-auto-merge') && out[0] === 'harness') out.splice(2, 0, '--no-auto-merge');
|
|
62
|
+
if (podPool && !out.includes('--pod-pool')) out.splice(2, 0, '--pod-pool', podPool);
|
|
63
|
+
return out;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** 원격 로그 파일(원격 기준) — 부모 stdout/stderr. */
|
|
67
|
+
export function remoteParentLogPath(target: ParentHostConfig, runId: string): string {
|
|
68
|
+
return `${target.configDir ?? DEFAULT_REMOTE_CONFIG_DIR}/remote-parents/${runId}.log`;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** ssh 로 넘길 원격 셸 한 줄 — 떼어 띄우고 pid 만 찍는다. */
|
|
72
|
+
export function remoteLaunchScript(target: ParentHostConfig, args: readonly string[], env: Record<string, string>): string {
|
|
73
|
+
const elanous = target.elanous ?? DEFAULT_REMOTE_ELANOUS;
|
|
74
|
+
const configDir = target.configDir ?? DEFAULT_REMOTE_CONFIG_DIR;
|
|
75
|
+
const log = remoteParentLogPath(target, env.ELANOUS_RUN_ID ?? 'unknown');
|
|
76
|
+
const envPart = Object.entries(env).filter(([key]) => /^[A-Z_][A-Z0-9_]*$/.test(key)).map(([key, value]) => `${key}=${shellQuote(value)}`).join(' ');
|
|
77
|
+
const argv = [remotePath(elanous), '--config-dir', remotePath(configDir), ...remoteParentArgs(args, target.podPool).map(shellQuote)].join(' ');
|
|
78
|
+
// 준비가 실패하면 «확실히 안 띄웠다»(not-launched=…)를 찍고 끝낸다 — 그때만 호출부가 로컬로 돌아간다.
|
|
79
|
+
// 띄운 뒤에는 짧게 살아 있는지 보고 pid 를 찍는다(조기 종료면 early-exit — 역시 «안 돈다»가 확실).
|
|
80
|
+
return `mkdir -p ${remotePath(`${configDir}/remote-parents`)} || { echo "not-launched=mkdir"; exit 3; }; `
|
|
81
|
+
+ `cd ${shellQuote(target.cwd)} || { echo "not-launched=cwd"; exit 3; }; `
|
|
82
|
+
+ `${envPart ? `env ${envPart} ` : ''}nohup ${argv} >> ${remotePath(log)} 2>&1 < /dev/null & p=$!; sleep 2; `
|
|
83
|
+
+ `if kill -0 "$p" 2>/dev/null; then echo "pid=$p"; else echo "not-launched=early-exit"; tail -3 ${remotePath(log)}; exit 4; fi`;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** ssh 실행기(시험 주입). */
|
|
87
|
+
export type SshRunner = (host: string, script: string, timeoutMs: number) => { status: number | null; stdout: string; stderr: string };
|
|
88
|
+
|
|
89
|
+
export const defaultSshRunner: SshRunner = (host, script, timeoutMs) => {
|
|
90
|
+
const result = spawnSync('ssh', ['-o', 'BatchMode=yes', '-o', 'ConnectTimeout=8', '-o', 'ControlPath=none', host, script], { encoding: 'utf8', timeout: timeoutMs, maxBuffer: 32 * 1024 * 1024 });
|
|
91
|
+
return { status: result.status, stdout: result.stdout ?? '', stderr: result.stderr ?? (result.error ? String(result.error) : '') };
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
/** 발사 전 점검 결과 — `gaps` 가 비어야 원격으로 띄운다. */
|
|
95
|
+
export interface ParentHostPreflight { ok: boolean; gaps: string[]; facts: Record<string, string> }
|
|
96
|
+
|
|
97
|
+
/** 원격 사실 한 번에 — key=value 줄. */
|
|
98
|
+
export function preflightScript(target: ParentHostConfig): string {
|
|
99
|
+
const elanous = remotePath(target.elanous ?? DEFAULT_REMOTE_ELANOUS);
|
|
100
|
+
const configDir = remotePath(target.configDir ?? DEFAULT_REMOTE_CONFIG_DIR);
|
|
101
|
+
return [
|
|
102
|
+
`echo "version=$(${elanous} --version 2>/dev/null | tail -1 | cut -d' ' -f1)"`,
|
|
103
|
+
`test -f ${configDir}/config.json && echo config=1`,
|
|
104
|
+
`grep -Eq '"podPool"[[:space:]]*:[[:space:]]*"[^"[:space:]]+' ${configDir}/config.json 2>/dev/null && echo podPool=1`,
|
|
105
|
+
`(cd ${shellQuote(target.cwd)} 2>/dev/null && git rev-parse --is-inside-work-tree >/dev/null 2>&1) && echo repo=1`,
|
|
106
|
+
`command -v kubectl >/dev/null 2>&1 && echo kubectl=1`,
|
|
107
|
+
`true`,
|
|
108
|
+
].join('; ');
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export interface PreflightInput {
|
|
112
|
+
target: ParentHostConfig;
|
|
113
|
+
/** HQ 의 elanous 판(첫 낱말). */
|
|
114
|
+
localVersion: string;
|
|
115
|
+
/** HQ 착지 동결이 «발사도 막는» 상태인가 — 원격 부모는 그 동결을 못 본다. */
|
|
116
|
+
launchHoldActive: boolean;
|
|
117
|
+
ssh?: SshRunner;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** 순수 판정 — 원격 사실 → 막는 칸 목록. */
|
|
121
|
+
export function judgeParentHostFacts(facts: Record<string, string>, input: Omit<PreflightInput, 'ssh' | 'target'> & { podPoolOverride?: boolean }): string[] {
|
|
122
|
+
const gaps: string[] = [];
|
|
123
|
+
if (facts.unreachable) gaps.push(`unreachable: ${facts.unreachable}`);
|
|
124
|
+
else {
|
|
125
|
+
if (!facts.version) gaps.push('remote-elanous-missing');
|
|
126
|
+
else if (facts.version !== input.localVersion) gaps.push(`version-mismatch: remote ${facts.version} ≠ HQ ${input.localVersion}`);
|
|
127
|
+
if (facts.config !== '1') gaps.push('remote-config-missing');
|
|
128
|
+
if (facts.podPool !== '1' && !input.podPoolOverride) gaps.push('remote-pod-pool-unset');
|
|
129
|
+
if (facts.repo !== '1') gaps.push('remote-repo-missing');
|
|
130
|
+
if (facts.kubectl !== '1') gaps.push('remote-kubectl-missing');
|
|
131
|
+
}
|
|
132
|
+
if (input.launchHoldActive) gaps.push('hq-launch-freeze-active');
|
|
133
|
+
return gaps;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export function preflightParentHost(input: PreflightInput): ParentHostPreflight {
|
|
137
|
+
const ssh = input.ssh ?? defaultSshRunner;
|
|
138
|
+
const facts: Record<string, string> = {};
|
|
139
|
+
const result = ssh(input.target.host, preflightScript(input.target), 20_000);
|
|
140
|
+
if (result.status !== 0) facts.unreachable = (result.stderr.trim().split('\n').pop() || `rc=${result.status}`).slice(0, 200);
|
|
141
|
+
else for (const line of result.stdout.split('\n')) {
|
|
142
|
+
const at = line.indexOf('=');
|
|
143
|
+
if (at > 0) facts[line.slice(0, at).trim()] = line.slice(at + 1).trim();
|
|
144
|
+
}
|
|
145
|
+
const gaps = judgeParentHostFacts(facts, { localVersion: input.localVersion, launchHoldActive: input.launchHoldActive, podPoolOverride: Boolean(input.target.podPool) });
|
|
146
|
+
return { ok: gaps.length === 0, gaps, facts };
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/** 원격이 «확실히 안 띄웠다»고 답했다 — 호출부가 로컬로 돌아가도 중복 발사가 아니다. */
|
|
150
|
+
export class RemoteParentNotLaunchedError extends Error {
|
|
151
|
+
constructor(host: string, reason: string) { super(`원격 부모 안 뜸(${host}): ${reason}`); this.name = 'RemoteParentNotLaunchedError'; }
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* 원격 발사 — pid 를 받으면 성공. 원격이 `not-launched=…` 로 답하면 `RemoteParentNotLaunchedError`(로컬로 가도 된다).
|
|
156
|
+
* 그 밖(ssh 끊김·시간 초과·알 수 없는 답)은 «떴는지 모른다» — 일반 Error 로 던져 카드가 launch-failed 로 남게 한다
|
|
157
|
+
* (⛔ 로컬로 또 띄우면 원격에 이미 뜬 부모와 중복이 될 수 있다).
|
|
158
|
+
*/
|
|
159
|
+
export function launchRemoteParent(target: ParentHostConfig, args: readonly string[], env: Record<string, string>, ssh: SshRunner = defaultSshRunner): { pid: number; log: string } {
|
|
160
|
+
const result = ssh(target.host, remoteLaunchScript(target, args, env), 30_000);
|
|
161
|
+
const log = remoteParentLogPath(target, env.ELANOUS_RUN_ID ?? 'unknown');
|
|
162
|
+
const pid = Number(/^pid=(\d+)$/m.exec(result.stdout)?.[1]);
|
|
163
|
+
if (result.status === 0 && Number.isSafeInteger(pid) && pid > 0) return { pid, log };
|
|
164
|
+
const refused = /^not-launched=(\S+)/m.exec(result.stdout)?.[1];
|
|
165
|
+
if (refused && (result.status === 3 || result.status === 4)) {
|
|
166
|
+
const tail = result.stdout.split('\n').filter((line) => line.trim() && !line.startsWith('not-launched=')).slice(-1)[0] ?? '';
|
|
167
|
+
throw new RemoteParentNotLaunchedError(target.host, `${refused}${tail ? `: ${tail.slice(0, 160)}` : ''}`);
|
|
168
|
+
}
|
|
169
|
+
throw new Error(`원격 부모 발사 결과 불확실(${target.host} · rc=${result.status}) — 중복을 피해 로컬로 다시 띄우지 않는다 · 원격에서 ELANOUS_RUN_ID=${env.ELANOUS_RUN_ID ?? '?'} 를 확인: ${(result.stderr.trim().split('\n').pop() || '').slice(0, 160)}`);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// ── 관측: 원격 런 원장 거울 ─────────────────────────────────────────────────────────────
|
|
173
|
+
|
|
174
|
+
const RUN_ID_RE = /^run-[A-Za-z0-9_-]{4,64}$/;
|
|
175
|
+
|
|
176
|
+
/** HQ 쪽 거울 디렉터리 — `<prodRoot>/remote-parents/<host>/run-ledger`. */
|
|
177
|
+
export function remoteLedgerMirrorDir(prodRoot: string, host: string): string {
|
|
178
|
+
return join(prodRoot, 'remote-parents', host.replace(/[^A-Za-z0-9._-]/g, '_'), 'run-ledger');
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/** 거울 디렉터리 전부(카드 원장 읽기가 연합 목록 뒤에 붙인다). */
|
|
182
|
+
export function remoteLedgerMirrorDirs(prodRoot: string): string[] {
|
|
183
|
+
const base = join(prodRoot, 'remote-parents');
|
|
184
|
+
if (!existsSync(base)) return [];
|
|
185
|
+
try {
|
|
186
|
+
return readdirSync(base).map((host) => join(base, host, 'run-ledger')).filter((dir) => existsSync(dir));
|
|
187
|
+
} catch { return []; }
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* 원격 런 원장(부모 ⊕ 그 원장이 가리키는 Pod 자식)을 HQ 거울로 당긴다 — 읽기 전용(원격에 쓰지 않는다).
|
|
192
|
+
* 돌려주는 값 = 당긴 런 id. 원격을 못 읽으면 던진다(호출부가 «없음»이 아니라 실패로 적는다).
|
|
193
|
+
*/
|
|
194
|
+
export function mirrorRemoteRunLedgers(target: Pick<ParentHostConfig, 'host' | 'configDir'>, runId: string, prodRoot: string, ssh: SshRunner = defaultSshRunner): string[] {
|
|
195
|
+
if (!RUN_ID_RE.test(runId)) throw new Error(`invalid runId: ${runId}`);
|
|
196
|
+
const dir = remoteLedgerMirrorDir(prodRoot, target.host);
|
|
197
|
+
mkdirSync(dir, { recursive: true });
|
|
198
|
+
const remoteDir = `${target.configDir ?? DEFAULT_REMOTE_CONFIG_DIR}/run-ledger`;
|
|
199
|
+
const pulled: string[] = [];
|
|
200
|
+
const pull = (id: string): string | null => {
|
|
201
|
+
const file = remotePath(`${remoteDir}/${id}.jsonl`);
|
|
202
|
+
// 부재만 ABSENT — 권한·읽기 오류는 cat 의 비0 종료로 올라와 던진다(«없음»으로 바꾸지 않는다).
|
|
203
|
+
const result = ssh(target.host, `if [ -e ${file} ]; then cat ${file}; else echo __ELANOUS_ABSENT__; fi`, 20_000);
|
|
204
|
+
if (result.status !== 0) throw new Error(`원격 원장 읽기 실패(${target.host} ${id}): ${(result.stderr.trim().split('\n').pop() || `rc=${result.status}`).slice(0, 200)}`);
|
|
205
|
+
const path = join(dir, `${id}.jsonl`);
|
|
206
|
+
if (result.stdout.trim() === '__ELANOUS_ABSENT__') {
|
|
207
|
+
// 원격에서 사라졌으면 낡은 거울도 걷는다 — 지난 사진을 지금 근거로 읽지 않게.
|
|
208
|
+
rmSync(path, { force: true });
|
|
209
|
+
return null;
|
|
210
|
+
}
|
|
211
|
+
const tmp = `${path}.${process.pid}.tmp`;
|
|
212
|
+
writeFileSync(tmp, result.stdout);
|
|
213
|
+
renameSync(tmp, path);
|
|
214
|
+
pulled.push(id);
|
|
215
|
+
return result.stdout;
|
|
216
|
+
};
|
|
217
|
+
const host = pull(runId);
|
|
218
|
+
if (host) {
|
|
219
|
+
const children = new Set<string>();
|
|
220
|
+
for (const line of host.split('\n')) {
|
|
221
|
+
try {
|
|
222
|
+
const entry = JSON.parse(line) as { event?: string; data?: { childRunId?: unknown } };
|
|
223
|
+
if (entry.event === 'pod-child-run' && typeof entry.data?.childRunId === 'string' && RUN_ID_RE.test(entry.data.childRunId) && entry.data.childRunId !== runId) children.add(entry.data.childRunId);
|
|
224
|
+
} catch { /* 깨진 줄은 원장 읽기가 다룬다 */ }
|
|
225
|
+
}
|
|
226
|
+
for (const child of children) pull(child);
|
|
227
|
+
}
|
|
228
|
+
try { debug.log('task-agent', 'remote-ledger-mirrored', { host: target.host, runId, pulled }); } catch { /* fail-soft */ }
|
|
229
|
+
return pulled;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// ── 낡은 부모 보고(읽기 전용 · 죽이지 않는다) ─────────────────────────────────────────────
|
|
233
|
+
|
|
234
|
+
export interface HarnessParentProcess { pid: number; ageHours: number; runId: string | null; version: string | null; seat: string | null; kind: 'say' | 'ask' | 'queue-child' }
|
|
235
|
+
|
|
236
|
+
/** macOS/Linux `etime` — `[[dd-]hh:]mm:ss`. */
|
|
237
|
+
export function parseEtime(value: string): number | null {
|
|
238
|
+
const match = /^(?:(\d+)-)?(?:(\d+):)?(\d+):(\d+)$/.exec(value.trim());
|
|
239
|
+
if (!match) return null;
|
|
240
|
+
const [, d, h, m, s] = match;
|
|
241
|
+
return Number(d ?? 0) * 86400 + Number(h ?? 0) * 3600 + Number(m) * 60 + Number(s);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/** `ps -axwwE -o pid=,etime=,command=` 출력 → 하니스 부모들. 런 id 는 env 꼬리의 «마지막» `ELANOUS_RUN_ID=`. */
|
|
245
|
+
export function parseHarnessParents(psOutput: string): HarnessParentProcess[] {
|
|
246
|
+
const rows: HarnessParentProcess[] = [];
|
|
247
|
+
for (const line of psOutput.split('\n')) {
|
|
248
|
+
const match = /^\s*(\d+)\s+(\S+)\s+(.*)$/.exec(line);
|
|
249
|
+
if (!match) continue;
|
|
250
|
+
const command = match[3]!;
|
|
251
|
+
const head = command.slice(0, 600);
|
|
252
|
+
const kind = /harness-queue-child\.ts/.test(head) ? 'queue-child' : /elanous\.mjs\b.*\bharness (say|ask)\b/.exec(head)?.[1] as 'say' | 'ask' | undefined;
|
|
253
|
+
if (!kind) continue;
|
|
254
|
+
const seconds = parseEtime(match[2]!);
|
|
255
|
+
if (seconds === null) continue;
|
|
256
|
+
const runIds = [...command.matchAll(/(?:^|\s)ELANOUS_RUN_ID=(run-[A-Za-z0-9_-]{4,64})(?=\s|$)/g)].map((m) => m[1]!);
|
|
257
|
+
rows.push({
|
|
258
|
+
pid: Number(match[1]),
|
|
259
|
+
ageHours: Math.round((seconds / 3600) * 10) / 10,
|
|
260
|
+
runId: runIds.at(-1) ?? null,
|
|
261
|
+
version: /\/versions\/([^/\s]+)\//.exec(head)?.[1] ?? null,
|
|
262
|
+
seat: /--seat (\S+)/.exec(head)?.[1] ?? null,
|
|
263
|
+
kind,
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
return rows;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
export interface StaleParentRow extends HarnessParentProcess { reasons: string[]; runStatus: string | null }
|
|
270
|
+
|
|
271
|
+
export interface StaleParentDeps {
|
|
272
|
+
ps?: () => string;
|
|
273
|
+
/** 런 원장(없으면 null · 못 읽으면 던진다). */
|
|
274
|
+
loadLedger?: (runId: string) => Array<{ event: string; data?: Record<string, unknown> }> | null;
|
|
275
|
+
/** 살아 있는 Pod Job 의 `elanous.run`/`elanous.child-run` 라벨 — null 이면 «못 쟀다»(Pod 근거 없음). */
|
|
276
|
+
podRuns?: () => ReadonlySet<string> | null;
|
|
277
|
+
labelOf?: (runId: string) => string;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* 나이 ≥ olderThanHours 인 부모 중 «원장이 끝났다»(run-status 줄) 또는 «Pod 이 없다»(Pod 목록을 쟀고 부모·자식 라벨이 없다)인 것.
|
|
282
|
+
* 읽기만 한다 — 죽이지 않는다. 런 id 를 모르는 부모는 `run-id-unknown` 으로 따로 적는다(낡음 판정이 아니다).
|
|
283
|
+
*/
|
|
284
|
+
export function staleHarnessParents(olderThanHours: number, deps: StaleParentDeps = {}): { rows: StaleParentRow[]; scanned: number; podRunsMeasured: boolean } {
|
|
285
|
+
const ps = deps.ps ?? (() => spawnSync('ps', ['-axwwE', '-o', 'pid=,etime=,command='], { encoding: 'utf8', maxBuffer: 64 * 1024 * 1024 }).stdout ?? '');
|
|
286
|
+
const parents = parseHarnessParents(ps());
|
|
287
|
+
let podRuns: ReadonlySet<string> | null = null;
|
|
288
|
+
try { podRuns = deps.podRuns ? deps.podRuns() : null; } catch { podRuns = null; }
|
|
289
|
+
const label = deps.labelOf ?? ((id: string) => id);
|
|
290
|
+
const rows: StaleParentRow[] = [];
|
|
291
|
+
for (const parent of parents) {
|
|
292
|
+
if (parent.ageHours < olderThanHours) continue;
|
|
293
|
+
const reasons: string[] = [];
|
|
294
|
+
let runStatus: string | null = null;
|
|
295
|
+
if (!parent.runId) reasons.push('run-id-unknown');
|
|
296
|
+
else {
|
|
297
|
+
let ledger: ReturnType<NonNullable<StaleParentDeps['loadLedger']>> = null;
|
|
298
|
+
try { ledger = deps.loadLedger ? deps.loadLedger(parent.runId) : null; } catch (error) { reasons.push(`ledger-unreadable: ${(error instanceof Error ? error.message : String(error)).slice(0, 80)}`); }
|
|
299
|
+
const status = [...(ledger ?? [])].reverse().find((entry) => entry.event === 'run-status' && typeof entry.data?.runStatus === 'string');
|
|
300
|
+
if (status) { runStatus = String(status.data!.runStatus); reasons.push(`ledger-terminal(${runStatus})`); }
|
|
301
|
+
if (podRuns) {
|
|
302
|
+
const ids = [parent.runId, ...(ledger ?? []).filter((entry) => entry.event === 'pod-child-run' && typeof entry.data?.childRunId === 'string').map((entry) => entry.data!.childRunId as string)];
|
|
303
|
+
if (!ids.some((id) => podRuns!.has(label(id)))) reasons.push('pod-gone');
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
if (reasons.length) rows.push({ ...parent, reasons, runStatus });
|
|
307
|
+
}
|
|
308
|
+
rows.sort((a, b) => b.ageHours - a.ageHours);
|
|
309
|
+
return { rows, scanned: parents.length, podRunsMeasured: podRuns !== null };
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/** HQ 의 elanous 판 — 이 패키지의 package.json(첫 낱말 = `--version` 첫 낱말과 같은 모양). */
|
|
313
|
+
export function localElanousVersion(): string {
|
|
314
|
+
try { return String((JSON.parse(readFileSync(resolve(import.meta.dir, '../../package.json'), 'utf8')) as { version?: string }).version ?? ''); } catch { return ''; }
|
|
315
|
+
}
|
package/src/task-agent/shadow.ts
CHANGED
|
@@ -16,12 +16,16 @@ import { readTaskAgentState, readTaskCard, taskAgentStatePath, type CompletionKi
|
|
|
16
16
|
import type { SupervisorJobResult, SupervisorStopReason } from '../self-dev/run-supervisor.js';
|
|
17
17
|
import { executeNextAction, type DeliveryKind, type NextAction } from './actions.js';
|
|
18
18
|
import { judgeNextMove, type TaskJudgeInput, type TaskJudgement } from './judge.js';
|
|
19
|
-
import { configuredTaskAgentLiveMoves, executeLiveGreenProposal, executeLiveReview, type LiveMoveDeps, type LiveMoveResult, type TaskAgentLiveMove } from './live-moves.js';
|
|
19
|
+
import { configuredTaskAgentLiveMoves, executeLiveGreenProposal, executeLiveLand, executeLiveReview, type LiveMoveDeps, type LiveMoveResult, type TaskAgentLiveMove } from './live-moves.js';
|
|
20
20
|
|
|
21
21
|
export interface TaskAgentShadowInput {
|
|
22
22
|
runId: string | null;
|
|
23
23
|
stopReason: SupervisorStopReason;
|
|
24
24
|
results: readonly SupervisorJobResult[];
|
|
25
|
+
/** One judgement cycle can attempt at most one land, even across PRs. */
|
|
26
|
+
cycleId?: string;
|
|
27
|
+
/** Reviewed SHA is mandatory for land; a bare pass cannot authorize merging. */
|
|
28
|
+
selfReview?: { verdict: 'pass' | 'fail'; head: string };
|
|
25
29
|
}
|
|
26
30
|
|
|
27
31
|
export interface TaskAgentShadowMove {
|
|
@@ -47,7 +51,7 @@ export interface TaskAgentShadowDeps {
|
|
|
47
51
|
readCard?: (taskId: string) => TaskCard | undefined;
|
|
48
52
|
/** Explicit live invocation only; supervisor's default hook always uses shadow. */
|
|
49
53
|
mode?: 'shadow' | 'live';
|
|
50
|
-
/** TA-JUDGE-LIVE-SAFE — 실제로 할
|
|
54
|
+
/** TA-JUDGE-LIVE-SAFE — 실제로 할 수(`review`·`propose-green`·`propose-land`). 없으면 설정 `taskAgent.liveMoves` 를 읽는다. */
|
|
51
55
|
liveMoves?: ReadonlySet<TaskAgentLiveMove>;
|
|
52
56
|
/** live 수의 부작용(리뷰 띄우기·PR 머리 조회·상태 파일) — 시험은 주입한다. */
|
|
53
57
|
live?: LiveMoveDeps;
|
|
@@ -161,6 +165,7 @@ export async function recordTaskAgentShadowMove(input: TaskAgentShadowInput, dep
|
|
|
161
165
|
}
|
|
162
166
|
const kind = card?.completion;
|
|
163
167
|
const judgeInput = shadowJudgeInput(input.stopReason, input.results, kind);
|
|
168
|
+
if (input.selfReview) judgeInput.review = input.selfReview.verdict;
|
|
164
169
|
const delivery = successful?.worktreePath && card?.project?.target && isDeliveryKind(kind)
|
|
165
170
|
? { kind, worktreePath: successful.worktreePath, projectTarget: card.project.target, files: selectDeliveryCandidates(successful.worktreePath, kind) }
|
|
166
171
|
: undefined;
|
|
@@ -209,8 +214,8 @@ export async function recordTaskAgentShadowMove(input: TaskAgentShadowInput, dep
|
|
|
209
214
|
move.reason = wouldDo ?? judgement.reason;
|
|
210
215
|
move.wouldDo = null;
|
|
211
216
|
}
|
|
212
|
-
const liveKind: TaskAgentLiveMove | null = move.move === 'review' || move.move === 'propose-green' ? move.move : null;
|
|
213
|
-
// 판단마다 해석한다 —
|
|
217
|
+
const liveKind: TaskAgentLiveMove | null = move.move === 'review' || move.move === 'propose-green' || move.move === 'propose-land' ? move.move : null;
|
|
218
|
+
// 판단마다 해석한다 — 허용되지 않은 수를 판단할 때도 모르는 항목 경고가 남게.
|
|
214
219
|
const liveMoves = deps.liveMoves ?? await configuredTaskAgentLiveMoves();
|
|
215
220
|
const live = liveKind !== null && liveMoves.has(liveKind);
|
|
216
221
|
// 카드 id 는 모든 판단 관측에 싣는다(그림자 일치율을 카드로 잇는다) — 읽기 전용 조회.
|
|
@@ -237,6 +242,16 @@ export async function recordTaskAgentShadowMove(input: TaskAgentShadowInput, dep
|
|
|
237
242
|
...(prResult.prUrl ? { prUrl: prResult.prUrl } : {}),
|
|
238
243
|
} } : {}),
|
|
239
244
|
}, { ...(deps.log ? { log: deps.log } : {}), ...deps.live });
|
|
245
|
+
} else if (liveKind === 'propose-land') {
|
|
246
|
+
liveMove = await executeLiveLand(boundCard, prResult?.prNumber,
|
|
247
|
+
{ runId: input.runId, cycleId: input.cycleId, review: input.selfReview,
|
|
248
|
+
...(prResult?.worktreePath ? { cwd: prResult.worktreePath } : {}),
|
|
249
|
+
...(prResult && !prResult.worktreePath ? { produced: {
|
|
250
|
+
...(prResult.checkedHeadCommit !== undefined ? { headCommit: prResult.checkedHeadCommit } : {}),
|
|
251
|
+
...(prResult.branch ? { branch: prResult.branch } : {}),
|
|
252
|
+
...(prResult.prUrl ? { prUrl: prResult.prUrl } : {}),
|
|
253
|
+
} } : {}),
|
|
254
|
+
}, { ...(deps.log ? { log: deps.log } : {}), ...deps.live });
|
|
240
255
|
} else {
|
|
241
256
|
const evidence: Record<string, string> = {};
|
|
242
257
|
if (boundCard && !delivery && judgeInput.prState === 'MERGED' && prResult) evidence[boundCard.id] = `#${prResult.prNumber}`;
|
|
@@ -77,10 +77,14 @@ export interface TaskCard {
|
|
|
77
77
|
launchId?: string;
|
|
78
78
|
/** Pod 런이면 PR 을 연 자식 런 id(런 묶기가 PR 과 함께 적는다). */
|
|
79
79
|
runChildId?: string;
|
|
80
|
+
/** HARNESS-PARENT-ON-MSB1 — 부모가 도는 원격 호스트(없으면 HQ 로컬). 원격 부모는 PR 까지만 간다(병합은 HQ). */
|
|
81
|
+
parentHost?: { host: string; configDir?: string; pid?: number; log?: string };
|
|
80
82
|
/** 미션 카드만 — 모든 조각 착지 → 칸 green «제안»(체크리스트는 손대지 않는다 · OP/주인이 뒤집는다). */
|
|
81
83
|
greenProposal?: { at: string; checklistId: string | null; evidence: Record<string, string> };
|
|
82
84
|
/** TA-JUDGE-LIVE-SAFE — live `review` 수가 리뷰를 요청한 PR 머리들(머리마다 한 번 · 띄우기 실패면 error · 이력은 지우지 않는다). */
|
|
83
85
|
reviewRequests?: Array<{ pr: number; head: string; at: string; error?: string }>;
|
|
86
|
+
/** Live land attempts, claimed before invocation, once per PR head even if the command fails. */
|
|
87
|
+
landAttempts?: Array<{ pr: number; head: string; at: string; ok?: boolean; detail?: string }>;
|
|
84
88
|
}
|
|
85
89
|
|
|
86
90
|
/** 런 원장에서 묶은 PR — url 은 원장(pr-opened · Pod job-finished)에 있을 때만(지어내지 않는다). */
|
|
@@ -193,7 +197,11 @@ export function shellQuote(arg: string): string {
|
|
|
193
197
|
/** 발사기에 넘기는 연결 — env(`ELANOUS_RUN_ID`)를 자식 환경에 얹으면 하니스 런이 `runId` 로 돈다(`resolveRunIdentity` 상속). launchId 는 카드에만 남는 발사 토큰이다. */
|
|
194
198
|
export interface TaskLaunchContext { runId: string; launchId: string; env: Record<string, string> }
|
|
195
199
|
/** 발사기가 «이 런 id 로 띄웠다»고 돌려주는 영수증 — 없으면 카드에 런 id 를 적지 않는다(추측하지 않는다). */
|
|
196
|
-
export interface TaskLaunchReceipt {
|
|
200
|
+
export interface TaskLaunchReceipt {
|
|
201
|
+
runId?: string;
|
|
202
|
+
/** HARNESS-PARENT-ON-MSB1 — 부모를 원격 호스트에서 띄웠다(ssh). 카드에 적어 `tasks show` 가 그 호스트의 원장을 당긴다. */
|
|
203
|
+
parentHost?: { host: string; configDir?: string; pid?: number; log?: string };
|
|
204
|
+
}
|
|
197
205
|
export type TaskLauncher = (args: string[], cwd?: string, context?: TaskLaunchContext) => void | TaskLaunchReceipt | Promise<void | TaskLaunchReceipt>;
|
|
198
206
|
|
|
199
207
|
export interface HandTaskOptions {
|
|
@@ -328,8 +336,9 @@ export async function handTask(opts: HandTaskOptions): Promise<HandTaskResult> {
|
|
|
328
336
|
const launched: TaskCard = {
|
|
329
337
|
...card, status: 'launched',
|
|
330
338
|
...(boundRunId ? { runId: boundRunId, launchId } : {}),
|
|
339
|
+
...(receipt && receipt.parentHost && typeof receipt.parentHost.host === 'string' ? { parentHost: receipt.parentHost } : {}),
|
|
331
340
|
history: [
|
|
332
|
-
{ at: launchedAt, event: 'launch', detail: move.command!.join(' ') },
|
|
341
|
+
{ at: launchedAt, event: 'launch', detail: `${receipt && receipt.parentHost ? `[parent@${receipt.parentHost.host} · PR 까지만] ` : ''}${move.command!.join(' ')}` },
|
|
333
342
|
...(boundRunId ? [{ at: launchedAt, event: 'run-bound', detail: boundRunId, runId: boundRunId }] : []),
|
|
334
343
|
],
|
|
335
344
|
};
|
|
@@ -13,6 +13,7 @@ import { getUserConfig } from '../../user-config.js';
|
|
|
13
13
|
import { debug } from '../../debug/log.js';
|
|
14
14
|
import { effectiveInstanceRoot } from '../../instance/resolve.js';
|
|
15
15
|
import { collectPodArtifacts } from './pod-artifact-return.js';
|
|
16
|
+
import { getDefaultLogStore, type LogStore } from '../../mss/logging/log-store.js';
|
|
16
17
|
import { podBunCacheVolume } from './pod-bun-cache.js';
|
|
17
18
|
import { podHostDirsInit, podInstallSlotsVolume } from './pod-install-slots.js';
|
|
18
19
|
import { type PodPoolMember, PodPoolScheduler, resolvePodPoolSpec, parsePodPool, checkPodPool, syncPoolImages, type PoolKubectl } from './pod-pool.js';
|
|
@@ -136,6 +137,19 @@ for i in $(seq 1 60); do
|
|
|
136
137
|
done
|
|
137
138
|
echo "[gate] ISOLATION NOT ENFORCED within 30s"; exit 1`;
|
|
138
139
|
|
|
140
|
+
/** Export the command's log store into the existing outbox return channel without changing the command exit status. */
|
|
141
|
+
const COMMAND_LOG_EXPORT = `if mkdir -p "$HOME/outbox/pod-logs" && elanous --test="$HOME/.elanous-test" logs --instance prod --since 12h --limit 20000 --json --json-data > "$HOME/outbox/pod-logs/logs.jsonl"; then
|
|
142
|
+
logs_size=$(( $(wc -c < "$HOME/outbox/pod-logs/logs.jsonl") ))
|
|
143
|
+
if [ "$logs_size" -gt 5242880 ]; then
|
|
144
|
+
tail -c 5242880 "$HOME/outbox/pod-logs/logs.jsonl" | tail -n +2 > "$HOME/outbox/pod-logs/logs.jsonl.tail" && mv -f "$HOME/outbox/pod-logs/logs.jsonl.tail" "$HOME/outbox/pod-logs/logs.jsonl"
|
|
145
|
+
printf 'ELANOUS_POD_LOGS_TRUNCATED %s %s\\n' "$logs_size" "$(( $(wc -c < "$HOME/outbox/pod-logs/logs.jsonl") ))"
|
|
146
|
+
fi
|
|
147
|
+
else
|
|
148
|
+
logs_rc=$?
|
|
149
|
+
rm -f "$HOME/outbox/pod-logs/logs.jsonl"
|
|
150
|
+
printf 'ELANOUS_POD_LOGS_UNAVAILABLE export-exit-%s\\n' "$logs_rc"
|
|
151
|
+
fi`;
|
|
152
|
+
|
|
139
153
|
/** 기존 Job 과 같은 `~/outbox` 조각 형식(`ELANOUS_POD_ARTIFACT`). 원장 회수는 하지 않는다. */
|
|
140
154
|
const ARTIFACT_EMIT = `set -o pipefail
|
|
141
155
|
artifact_bytes=0
|
|
@@ -172,6 +186,8 @@ export interface PodCommandJobInput {
|
|
|
172
186
|
repoUrl: string;
|
|
173
187
|
command: readonly string[];
|
|
174
188
|
skills: readonly string[];
|
|
189
|
+
/** pod run opt-in: return this command's local log store with its outbox artifacts. */
|
|
190
|
+
returnLogs?: boolean;
|
|
175
191
|
llm?: 'grok';
|
|
176
192
|
/** 저장소를 clone 한다 — 비공개 저장소라 GitHub 토큰이 Secret 으로 간다(명시 opt-in). 기본은 clone 없이 `~/work` 에서 이미지의 `elanous` 로 돈다. */
|
|
177
193
|
clone?: boolean;
|
|
@@ -222,6 +238,8 @@ export function podCommandJobManifest(o: PodCommandJobInput): Record<string, unk
|
|
|
222
238
|
`set -- ${quotedArgs}`,
|
|
223
239
|
'"$@"',
|
|
224
240
|
'rc=$?',
|
|
241
|
+
// A command may not end in LF; keep artifact markers on their own line for the existing parser.
|
|
242
|
+
...(o.returnLogs ? ["printf '\\n'", COMMAND_LOG_EXPORT] : []),
|
|
225
243
|
ARTIFACT_EMIT,
|
|
226
244
|
'exit $rc',
|
|
227
245
|
].join('\n');
|
|
@@ -325,6 +343,8 @@ export interface PodCommandResult {
|
|
|
325
343
|
|
|
326
344
|
export interface RunPodCommandOptions {
|
|
327
345
|
command: readonly string[];
|
|
346
|
+
/** pod run opt-in; other runPodCommand callers keep their existing Job script. */
|
|
347
|
+
returnLogs?: boolean;
|
|
328
348
|
pool?: string;
|
|
329
349
|
skills?: readonly string[];
|
|
330
350
|
llm?: 'grok';
|
|
@@ -357,6 +377,8 @@ export interface RunPodCommandOptions {
|
|
|
357
377
|
readSkillEnv?: (skills: readonly string[]) => Record<string, string>;
|
|
358
378
|
grokCredentials?: () => { grokAuth?: string; grokApiKey?: string; ghToken: string };
|
|
359
379
|
artifactsRoot?: string;
|
|
380
|
+
/** Test seam for the launching instance's log store. */
|
|
381
|
+
logStore?: LogStore | null;
|
|
360
382
|
sleep?: (ms: number) => Promise<void>;
|
|
361
383
|
log?: (category: string, event: string, data: Record<string, unknown>) => void;
|
|
362
384
|
env?: NodeJS.ProcessEnv;
|
|
@@ -454,7 +476,8 @@ export async function runPodCommand(options: RunPodCommandOptions): Promise<PodC
|
|
|
454
476
|
const context = member?.context ?? (currentContext?.status === 0 ? currentContext.stdout.trim() : '');
|
|
455
477
|
const kubectl: Kubectl = (args, stdin) => baseKubectl(context ? ['--context', context, ...args] : [...args], stdin);
|
|
456
478
|
const cleanupSecret = () => { kubectl(['-n', namespace, 'delete', 'secret', '-l', own, '--ignore-not-found']); };
|
|
457
|
-
const
|
|
479
|
+
const artifactRoot = options.artifactsRoot ?? `${effectiveInstanceRoot()}/pod-artifacts`;
|
|
480
|
+
const artifactsDir = `${artifactRoot}/${name}`;
|
|
458
481
|
try {
|
|
459
482
|
if (!context && !options.kubectl) throw new Error('pod command: kubectl context 를 확인할 수 없다');
|
|
460
483
|
let grokAuth: string | undefined;
|
|
@@ -499,7 +522,7 @@ export async function runPodCommand(options: RunPodCommandOptions): Promise<PodC
|
|
|
499
522
|
const manifest = podCommandJobManifest({
|
|
500
523
|
name, namespace, launch, image: imageRef ?? image,
|
|
501
524
|
...(imageRef ? { imagePullPolicy: 'IfNotPresent' as const } : {}),
|
|
502
|
-
repoUrl, command, skills, ...(llm ? { llm } : {}), ...(options.clone ? { clone: true } : {}),
|
|
525
|
+
repoUrl, command, skills, ...(options.returnLogs ? { returnLogs: true } : {}), ...(llm ? { llm } : {}), ...(options.clone ? { clone: true } : {}),
|
|
503
526
|
...(options.source ? { source: options.source } : {}), ...(hostMirror ? { hostMirror } : {}), ...(bunCache ? { bunCache } : {}), ...(installSlots ? { installSlots } : {}), ...(memoryLimit ? { memoryLimit } : {}), ...(options.cpu ? { cpu: options.cpu } : {}), deadlineSeconds,
|
|
504
527
|
...(options.runId ? { runId: options.runId } : {}),
|
|
505
528
|
});
|
|
@@ -519,19 +542,31 @@ export async function runPodCommand(options: RunPodCommandOptions): Promise<PodC
|
|
|
519
542
|
const logsResult = kubectl(['-n', namespace, 'logs', `job/${name}`, '-c', 'child']);
|
|
520
543
|
const logsFailed = logsResult.status !== 0;
|
|
521
544
|
const logs = logsFailed ? '' : logsResult.stdout;
|
|
522
|
-
const artifactDir =
|
|
545
|
+
const artifactDir = artifactRoot;
|
|
523
546
|
let artifacts: PodArtifactCollection;
|
|
524
547
|
if (logsFailed) {
|
|
525
548
|
const reason = logsResult.stderr.trim() || `kubectl logs exit ${logsResult.status}`;
|
|
526
549
|
artifacts = { files: null, names: [], error: reason };
|
|
527
550
|
log('pod.command', 'artifact-collect-failed', { job: name, reason });
|
|
528
551
|
} else {
|
|
552
|
+
const store = options.returnLogs ? (options.logStore === undefined ? getDefaultLogStore() : options.logStore) : null;
|
|
529
553
|
collectPodArtifacts(logs, {
|
|
530
|
-
dir: artifactDir,
|
|
531
|
-
|
|
532
|
-
log: (c, e, d) => log(c, e, d),
|
|
554
|
+
dir: artifactDir, job: name, log: (c, e, d) => log(c, e, d),
|
|
555
|
+
...(options.returnLogs ? { logStore: store } : {}),
|
|
533
556
|
});
|
|
534
557
|
const names = artifactNames(logs);
|
|
558
|
+
// A plain command need not write JSONL. Make its child output queryable without changing the store schema.
|
|
559
|
+
if (options.returnLogs && store) {
|
|
560
|
+
try {
|
|
561
|
+
const output = logs.split(/\r?\n/).filter((line) => !line.startsWith('ELANOUS_POD_ARTIFACT ')).join('\n').trim();
|
|
562
|
+
if (output) store.insertPodBatch(name, [{ line: -1, surface: 'pod', rec: {
|
|
563
|
+
ts: new Date().toISOString(), category: 'pod.command', event: 'child-output', level: 'info',
|
|
564
|
+
data: { job: name, output: tailBytes(output, options.childLogMaxBytes ?? podChildLogMaxBytes()), origin: 'pod', podJob: name },
|
|
565
|
+
} }]);
|
|
566
|
+
} catch (error) {
|
|
567
|
+
log('pod.command', 'child-output-import-failed', { job: name, reason: error instanceof Error ? error.message : String(error) });
|
|
568
|
+
}
|
|
569
|
+
}
|
|
535
570
|
artifacts = { files: names.length, names, error: null };
|
|
536
571
|
}
|
|
537
572
|
// 표지가 없어도 알리는 경로는 실제로 존재해야 한다. 자식 로그 전문은 child.log (상한 넘으면 끝부분).
|