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
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { execFile, execFileSync, spawn } from 'node:child_process';
|
|
1
|
+
import { execFile, execFileSync, spawn, spawnSync } from 'node:child_process';
|
|
2
2
|
import { promisify } from 'node:util';
|
|
3
3
|
import { dlopen, FFIType } from 'bun:ffi';
|
|
4
4
|
import { createHash } from 'node:crypto';
|
|
@@ -10,6 +10,7 @@ import { LogStore, logsDbPath } from '../mss/logging/log-store.js';
|
|
|
10
10
|
import { loadSelfDevRun } from '../self-dev/run-store.js';
|
|
11
11
|
import { normalizeRunId } from './harness-space.js';
|
|
12
12
|
import { effectiveInstanceRoot, prodInstanceRoot } from '../instance/resolve.js';
|
|
13
|
+
import { stateDirSourceForChild } from '../agent/identity-env.js';
|
|
13
14
|
import { lookupGoal, type GoalLookupOptions, type GoalLookupResult } from '../self-implement/goal-lookup.js';
|
|
14
15
|
import { archiveGoals, type GoalArchiveOptions } from '../self-implement/goal-archive.js';
|
|
15
16
|
import { detectBursts, formatIncidentBurstWarning, incidentLastLines, readRunExits, recentBurst, recordRunExit } from './harness-incidents.js';
|
|
@@ -23,13 +24,15 @@ import { resolveChildLlmEffort, resolveImplementationChildModel } from '../self-
|
|
|
23
24
|
import { resolveHarnessTarget } from '../self-implement/harness-target-options.js';
|
|
24
25
|
import { queryRunningRuns } from '../self-implement/running-runs.js';
|
|
25
26
|
import { DevPipelineError } from '../self-dev/dev-pipeline.js';
|
|
26
|
-
import { DRAFT_SWEEP_CONCURRENCY, mapBounded, runDraftSweep, sweepFailureReason, type DraftSweepAdapters, type DraftSweepResult, type SweepDraft, type SweepMergedPr, type SweepReviewGate } from '../self-dev/draft-sweep.js';
|
|
27
|
+
import { collectDraftMetrics, collectOverlapMetrics, DRAFT_SWEEP_CONCURRENCY, mapBounded, resolveDraftSweepCloseCap, runDraftSweep, sweepFailureReason, type DraftSweepAdapters, type DraftSweepResult, type OverlapMetricAdapters, type SweepDraft, type SweepMergedPr, type SweepReviewGate } from '../self-dev/draft-sweep.js';
|
|
28
|
+
import { readAskPreflightRowsSince, readSalvagedRowsSince } from '../self-dev/ask-launch-io.js';
|
|
27
29
|
import { debug } from '../debug/log.js';
|
|
28
30
|
import { installHarnessSalvageCommand, installHarnessSalvageRetentionCommand } from './harness-salvage-cli.js';
|
|
29
31
|
import { decideNestedElanousLaunch, readNestedElanousDepth } from './nested-elanous-policy.js';
|
|
30
32
|
import { dispatchTask, type DispatchTaskInput, type DispatchTaskDeps } from '../execution-loop/dispatch-task.js';
|
|
31
33
|
import { launchRequestId, preLaunchGate, type PreLaunchGateDeps } from '../execution-loop/launch-gate.js';
|
|
32
34
|
import { PR_LABELS } from '../github/pr-labels.js';
|
|
35
|
+
import { ghAutomationEnv } from '../autopilot/pr-manager.js';
|
|
33
36
|
import { CODEX_PROVIDER, GROK_PROVIDER, decideBudget, decideLaunchBudget, readBudgetInputsLive, type BudgetDecision, type BudgetInputs } from '../self-implement/budget-gate.js';
|
|
34
37
|
import { DEFAULT_FALLBACK_CHAIN } from '../oauth/fallback-chain.js';
|
|
35
38
|
import { installDeliverableVerifyCliCommand, type InstallDeliverableVerifyCliDeps } from './deliverable-verify-cli.js';
|
|
@@ -47,6 +50,8 @@ import { runHarnessPlanRfc } from './harness-plan-rfc.js';
|
|
|
47
50
|
import { classifyGarbage, isGarbageProcessTarget, type GarbageProcess } from './process-garbage.js';
|
|
48
51
|
import type { MissionSolveOutcome } from './mission-solve-loop.js';
|
|
49
52
|
import { formatAxis, formatAxisObservations, inspectAskMarkers, inspectUnpressedDecisionSignals } from '../../scripts/ask-marker-check.js';
|
|
53
|
+
import { enterJourneyNode, ensureJourneyKey, exitJourneyNode, JOURNEY_KEY_ENV, passJourneyNode, withJourneyNode } from '../self-dev/graph-journey-nodes.js';
|
|
54
|
+
import { intakeTakesQueue, readJourneyEdge, recordJourneyEdge } from '../self-dev/graph-journey-route.js';
|
|
50
55
|
|
|
51
56
|
let harnessPlanRfcForTesting: typeof runHarnessPlanRfc | undefined;
|
|
52
57
|
let harnessAskMarkerInspectorForTesting: ((ask: string) => readonly string[]) | undefined;
|
|
@@ -777,13 +782,17 @@ async function dispatchHarnessAskSay(
|
|
|
777
782
|
immediateLaunch?: HarnessQueueDeps['launch'],
|
|
778
783
|
immediateExit?: () => Promise<number>,
|
|
779
784
|
): Promise<void> {
|
|
780
|
-
|
|
785
|
+
// HARNESS-FULL-GRAPH — 여정 조인 키는 이 발사 동안만 환경에 싣는다(자식은 그동안 물려받는다).
|
|
786
|
+
const journeyPreset = Boolean(process.env[JOURNEY_KEY_ENV]?.trim());
|
|
787
|
+
try { await runInjectedHarnessHandler(async () => {
|
|
781
788
|
assertHarnessChildLlmModel(opts);
|
|
782
789
|
if (isHarnessDryRun(opts)) {
|
|
783
790
|
resolveLaunchSubstrate(opts);
|
|
784
791
|
printHarnessLaunchDryRun(dryRunPreview);
|
|
785
792
|
return;
|
|
786
793
|
}
|
|
794
|
+
ensureJourneyKey();
|
|
795
|
+
const journey = { provenance: dryRunPreview.entrance, ...(opts.goalType ? { goalType: opts.goalType as GoalType } : {}) };
|
|
787
796
|
// Resolve the launch directory once; descendants and the checkpoint retain the same identity.
|
|
788
797
|
const queued = process.env.ELANOUS_HARNESS_QUEUE_LAUNCH && process.env.ELANOUS_HARNESS_SEAT;
|
|
789
798
|
const queueSeat = queued === 'OP' || queued === 'TC' || queued === 'MK' || queued === 'UX' ? queued : undefined;
|
|
@@ -801,10 +810,21 @@ async function dispatchHarnessAskSay(
|
|
|
801
810
|
const podSeat = resolved.substrate === 'pod' && ['OP', 'TC', 'MK', 'UX'].includes(inheritedSeat ?? '')
|
|
802
811
|
? inheritedSeat as QueueSeat : undefined;
|
|
803
812
|
const stampedSeat = resolved.substrate === 'pod' ? opts.seat ?? queueSeat ?? podSeat ?? assigned : assigned;
|
|
813
|
+
// HARNESS-FULL-GRAPH 2판 — 대기열을 탈지는 «그래프»가 정한다. 코드는 상태 값(queued|direct)만 내고,
|
|
814
|
+
// 다음 노드는 YAML(⊕ 조건이 맞는 launch 오버레이)의 intake 간선이 고른다. 기본 YAML 은 오늘과 같은 길이다.
|
|
815
|
+
// ⛔ 대기열은 자리가 있어야 하고 대기열 자식·--no-queue 는 다시 줄 서지 않는다 — 그 셋은 그래프가 못 넘는다.
|
|
816
|
+
const intakeLabel = shouldQueue && stampedSeat ? 'queued' : 'direct';
|
|
817
|
+
// intake 는 판정만 하는 관측 노드 — 진입·출구를 한 자리에서.
|
|
818
|
+
passJourneyNode('intake', { ...journey, outcome: intakeLabel });
|
|
819
|
+
const intakeRoute = readJourneyEdge('intake', intakeLabel, { seat: stampedSeat ?? 'none', substrate: resolved.substrate });
|
|
820
|
+
const queueEligible = opts.queue !== false && !process.env.ELANOUS_HARNESS_QUEUE_LAUNCH && Boolean(stampedSeat);
|
|
821
|
+
const takeQueue = intakeTakesQueue(intakeLabel, intakeRoute, queueEligible);
|
|
822
|
+
// 실제로 탄 간선을 전제 검사 «뒤»에 남긴다(그래프 답과 다르면 honored=false · 이유).
|
|
823
|
+
recordJourneyEdge(intakeRoute, takeQueue ? 'queue' : 'launch-gate', intakeRoute.to === 'queue' && !takeQueue ? (opts.queue === false ? '--no-queue' : process.env.ELANOUS_HARNESS_QUEUE_LAUNCH ? 'queue-child' : 'no-seat') : undefined);
|
|
804
824
|
if (directSay && opts.queue === false) {
|
|
805
825
|
try { debug.log('harness.queue', 'bypass', { seat: stampedSeat ?? null, reason: '--no-queue' }); }
|
|
806
826
|
catch { /* Observation must not prevent an emergency launch. */ }
|
|
807
|
-
} else if (
|
|
827
|
+
} else if (takeQueue && stampedSeat) {
|
|
808
828
|
resolveNestedElanousOption(opts);
|
|
809
829
|
const flags: string[] = [];
|
|
810
830
|
const values: Array<[string, string | undefined]> = [
|
|
@@ -833,6 +853,7 @@ async function dispatchHarnessAskSay(
|
|
|
833
853
|
[opts.forceGate, '--force-gate'],
|
|
834
854
|
] as const) if (enabled) flags.push(flag);
|
|
835
855
|
let row: QueueItem;
|
|
856
|
+
enterJourneyNode('queue', journey);
|
|
836
857
|
try {
|
|
837
858
|
row = await addHarnessQueue({ seat: stampedSeat, launchCwd: process.cwd(), refuseDuplicate: true,
|
|
838
859
|
...(dryRunPreview.goalPath ? { ask: dryRunPreview.goalPath } : { say: dryRunPreview.input }),
|
|
@@ -842,12 +863,17 @@ async function dispatchHarnessAskSay(
|
|
|
842
863
|
}, queueDeps);
|
|
843
864
|
await queueDeps?.afterEnqueue?.(row);
|
|
844
865
|
} catch (error) {
|
|
845
|
-
if (!(error instanceof HarnessQueueDuplicateError))
|
|
866
|
+
if (!(error instanceof HarnessQueueDuplicateError)) {
|
|
867
|
+
exitJourneyNode('queue', { ...journey, outcome: 'error', data: { error: error instanceof Error ? error.message : String(error) } });
|
|
868
|
+
throw error;
|
|
869
|
+
}
|
|
846
870
|
console.error(`발사 거절 — ${error.message.replace(/[\r\n]+/g, ' ')}`);
|
|
871
|
+
exitJourneyNode('queue', { ...journey, outcome: 'refused' });
|
|
847
872
|
process.exitCode = 1;
|
|
848
873
|
return;
|
|
849
874
|
}
|
|
850
|
-
const result = await tickHarnessQueue({ ...queueDeps, ...(immediateLaunch ? { launch: immediateLaunch } : {}) }, row.id);
|
|
875
|
+
const result = await withJourneyNode('queue', journey, () => tickHarnessQueue({ ...queueDeps, ...(immediateLaunch ? { launch: immediateLaunch } : {}) }, row.id));
|
|
876
|
+
exitJourneyNode('queue', { ...journey, outcome: result.outcome === 'launched' && result.item?.id === row.id ? 'launched' : 'waiting', data: { queueItemId: row.id, queueTick: result.outcome } });
|
|
851
877
|
if (result.outcome === 'launched' && result.item?.id === row.id) {
|
|
852
878
|
if (immediateExit) process.exitCode = await immediateExit();
|
|
853
879
|
} else if (result.outcome === 'waiting') {
|
|
@@ -880,6 +906,8 @@ async function dispatchHarnessAskSay(
|
|
|
880
906
|
const previous = process.env.ELANOUS_HARNESS_SEAT;
|
|
881
907
|
if (stampedSeat) process.env.ELANOUS_HARNESS_SEAT = stampedSeat;
|
|
882
908
|
else delete process.env.ELANOUS_HARNESS_SEAT;
|
|
909
|
+
// 배치 노드 — 실행 칸(local | pod)이 그 출구 라벨이고, YAML 의 decompose 간선(on: substrate)이 그 값으로 갈린다.
|
|
910
|
+
passJourneyNode('dispatch', { ...journey, outcome: resolved.substrate });
|
|
883
911
|
try { await dispatch(resolved, stampedOpts); }
|
|
884
912
|
finally {
|
|
885
913
|
if (previous === undefined) delete process.env.ELANOUS_HARNESS_SEAT;
|
|
@@ -900,11 +928,16 @@ async function dispatchHarnessAskSay(
|
|
|
900
928
|
// CLI wiring tests time out on them. Gate tests inject deps or opt in with ELANOUS_LAUNCH_GATE_LIVE=1.
|
|
901
929
|
if (process.env.NODE_ENV === 'test' && Object.keys(gate).length === 0 && process.env.ELANOUS_LAUNCH_GATE_LIVE !== '1') {
|
|
902
930
|
try { debug.log('execution-loop.launch-gate', 'skipped-test-env', { goalId }); } catch { /* observation is fail-soft */ }
|
|
931
|
+
passJourneyNode('launch-gate', { ...journey, outcome: 'pass', data: { skipped: 'test-env' } });
|
|
903
932
|
await launch();
|
|
904
933
|
return;
|
|
905
934
|
}
|
|
906
|
-
|
|
907
|
-
const decision =
|
|
935
|
+
enterJourneyNode('launch-gate', journey);
|
|
936
|
+
const decision = await withJourneyNode('launch-gate', journey, async () => {
|
|
937
|
+
const budget = await (gate.readBudget ?? (() => readHarnessLaunchBudget(opts)))();
|
|
938
|
+
return preLaunchGate({ goalId, budget, forceLaunch: opts.forceGate === true }, gate);
|
|
939
|
+
});
|
|
940
|
+
exitJourneyNode('launch-gate', { ...journey, outcome: decision.action === 'proceed' ? 'pass' : 'refuse', data: { action: decision.action } });
|
|
908
941
|
if (decision.action !== 'proceed') {
|
|
909
942
|
console.error(`❌ launch gate: ${decision.action} — ${decision.reason.split(/\r?\n/, 1)[0]}`);
|
|
910
943
|
process.exitCode = 3;
|
|
@@ -916,7 +949,7 @@ async function dispatchHarnessAskSay(
|
|
|
916
949
|
];
|
|
917
950
|
if (warnings.length > 0) console.error(`⚠️ launch gate: ${warnings.join(' · ').split(/\r?\n/, 1)[0]}`);
|
|
918
951
|
await launch();
|
|
919
|
-
});
|
|
952
|
+
}); } finally { if (!journeyPreset) delete process.env[JOURNEY_KEY_ENV]; }
|
|
920
953
|
}
|
|
921
954
|
|
|
922
955
|
function normalizeHarnessPlanOptions(opts: HarnessPlanOptions): HarnessPlanOptions {
|
|
@@ -1559,6 +1592,28 @@ function readProcessCwd(pid: number): Pick<HarnessProcessRecord, 'cwd' | 'cwdSta
|
|
|
1559
1592
|
}
|
|
1560
1593
|
}
|
|
1561
1594
|
|
|
1595
|
+
/** One `lsof` for many pids (a per-pid lsof costs ~0.1–0.3 s); lsof exits 1 when some pid is gone yet prints the rest. */
|
|
1596
|
+
function batchProcessCwdReader(pids: readonly number[]): (pid: number) => Pick<HarnessProcessRecord, 'cwd' | 'cwdStatus' | 'cwdFailureReason'> {
|
|
1597
|
+
const cwds = new Map<number, string>();
|
|
1598
|
+
let failure: string | undefined;
|
|
1599
|
+
if (pids.length) {
|
|
1600
|
+
const result = spawnSync('lsof', ['-a', '-d', 'cwd', '-Fpn', '-p', pids.join(',')],
|
|
1601
|
+
{ encoding: 'utf8', timeout: 5_000, maxBuffer: 16 * 1024 * 1024, stdio: ['ignore', 'pipe', 'ignore'] });
|
|
1602
|
+
if (result.error || typeof result.stdout !== 'string') failure = observationFailureReason(result.error ?? new Error('lsof output missing'));
|
|
1603
|
+
else {
|
|
1604
|
+
let current: number | undefined;
|
|
1605
|
+
for (const line of result.stdout.split('\n')) {
|
|
1606
|
+
if (line.startsWith('p')) current = Number(line.slice(1));
|
|
1607
|
+
else if (line.startsWith('n') && current !== undefined && line.length > 1) cwds.set(current, line.slice(1).trim());
|
|
1608
|
+
}
|
|
1609
|
+
}
|
|
1610
|
+
}
|
|
1611
|
+
return (pid) => {
|
|
1612
|
+
const cwd = cwds.get(pid);
|
|
1613
|
+
return cwd ? { cwd, cwdStatus: 'observed' } : { cwdStatus: 'unknown', cwdFailureReason: failure ?? 'lsof cwd missing' };
|
|
1614
|
+
};
|
|
1615
|
+
}
|
|
1616
|
+
|
|
1562
1617
|
const OWNERSHIP_ENV_KEYS = new Set<string>(Object.values(HARNESS_PROCESS_OWNERSHIP_ENV));
|
|
1563
1618
|
|
|
1564
1619
|
function ownershipValueFromEntry(entry: string, key: string): string | undefined {
|
|
@@ -1714,10 +1769,23 @@ export function readProcessOwnership(
|
|
|
1714
1769
|
}
|
|
1715
1770
|
}
|
|
1716
1771
|
|
|
1717
|
-
|
|
1772
|
+
/** Narrows the per-process reads (`lsof` cwd ≈ 0.1 s each · `ps eww`) of `defaultListHarnessProcesses`. */
|
|
1773
|
+
export interface HarnessProcessListOptions {
|
|
1774
|
+
/** Keep only matching records; the others are dropped before any per-process read. */
|
|
1775
|
+
readonly include?: (record: HarnessProcessRecord) => boolean;
|
|
1776
|
+
/** Skip the ownership (`ps eww`) read — for callers that only need the cwd. */
|
|
1777
|
+
readonly cwdOnly?: boolean;
|
|
1778
|
+
/** Wall-clock budget for the per-process reads; records past it keep `cwdStatus: 'unknown'` (reason names the budget). */
|
|
1779
|
+
readonly deadlineMs?: number;
|
|
1780
|
+
readonly readCwd?: (pid: number) => Pick<HarnessProcessRecord, 'cwd' | 'cwdStatus' | 'cwdFailureReason'>;
|
|
1781
|
+
readonly psOutput?: () => string;
|
|
1782
|
+
readonly clock?: () => number;
|
|
1783
|
+
}
|
|
1784
|
+
|
|
1785
|
+
export function defaultListHarnessProcesses(options: HarnessProcessListOptions = {}): HarnessProcessListObservation {
|
|
1718
1786
|
let out: string;
|
|
1719
1787
|
try {
|
|
1720
|
-
out = execFileSync('ps', ['-axo', 'pid=,ppid=,pcpu=,etime=,command='], {
|
|
1788
|
+
out = options.psOutput ? options.psOutput() : execFileSync('ps', ['-axo', 'pid=,ppid=,pcpu=,etime=,command='], {
|
|
1721
1789
|
encoding: 'utf8',
|
|
1722
1790
|
timeout: 5_000,
|
|
1723
1791
|
stdio: ['ignore', 'pipe', 'ignore'],
|
|
@@ -1727,11 +1795,21 @@ export function defaultListHarnessProcesses(): HarnessProcessListObservation {
|
|
|
1727
1795
|
}
|
|
1728
1796
|
const parsed = parseHarnessProcessPsOutput(out);
|
|
1729
1797
|
if (parsed.status === 'failed') return parsed;
|
|
1730
|
-
const
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1798
|
+
const clock = options.clock ?? Date.now;
|
|
1799
|
+
const selected = options.include ? parsed.records.filter(options.include) : parsed.records;
|
|
1800
|
+
const readCwd = options.readCwd ?? (options.cwdOnly ? batchProcessCwdReader(selected.map((record) => record.pid)) : readProcessCwd);
|
|
1801
|
+
const deadline = options.deadlineMs === undefined ? Infinity : clock() + options.deadlineMs;
|
|
1802
|
+
const records = selected.map((record) => {
|
|
1803
|
+
// A host with hundreds of harness processes made the serial reads take minutes (10-08 queue tick hang).
|
|
1804
|
+
if (clock() >= deadline) {
|
|
1805
|
+
return { ...record, cwdStatus: 'unknown' as const, cwdFailureReason: `observation deadline ${options.deadlineMs}ms exceeded` };
|
|
1806
|
+
}
|
|
1807
|
+
return {
|
|
1808
|
+
...record,
|
|
1809
|
+
...readCwd(record.pid),
|
|
1810
|
+
...(options.cwdOnly ? {} : { ownership: readProcessOwnership(record.pid) }),
|
|
1811
|
+
};
|
|
1812
|
+
});
|
|
1735
1813
|
if (parsed.status === 'incomplete') {
|
|
1736
1814
|
return {
|
|
1737
1815
|
status: 'incomplete',
|
|
@@ -1847,12 +1925,30 @@ type GithubPull = {
|
|
|
1847
1925
|
|
|
1848
1926
|
type GhExecute = (args: string[]) => string;
|
|
1849
1927
|
|
|
1850
|
-
//
|
|
1851
|
-
//
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1928
|
+
// Explicit env preserves the augmented PATH under Bun (cron may start with a minimal PATH).
|
|
1929
|
+
// Authentication comes from the same automation policy used by the PR manager, not the user's keyring.
|
|
1930
|
+
// Failure details are bounded and redact the token before reaching the sweep result or debug log.
|
|
1931
|
+
function ghFailure(args: string[], error: unknown, token: string | undefined): Error {
|
|
1932
|
+
const failure = error as { status?: number | null; code?: number | string; stderr?: string | Buffer };
|
|
1933
|
+
const exit = failure.status ?? (typeof failure.code === 'number' ? failure.code : 'unknown');
|
|
1934
|
+
const stderr = String(failure.stderr ?? '');
|
|
1935
|
+
const stderrTail = (token ? stderr.replaceAll(token, '[REDACTED]') : stderr).slice(-300).replace(/[\r\n]+/g, ' ').trim();
|
|
1936
|
+
try { debug.log('harness.drafts', 'gh-failed', { args0: args[0], exit, stderrTail }); }
|
|
1937
|
+
catch { /* Logging must not hide the gh failure. */ }
|
|
1938
|
+
// gh never ran (e.g. «Executable not found: gh»): no exit or stderr to report, so keep the spawn error itself.
|
|
1939
|
+
if (typeof failure.status !== 'number' && !stderrTail && error instanceof Error) return error;
|
|
1940
|
+
return new Error(`gh ${args.slice(0, 2).join(' ')} exit=${exit}: ${stderrTail}`);
|
|
1941
|
+
}
|
|
1942
|
+
|
|
1943
|
+
const executeGh: GhExecute = (args) => {
|
|
1944
|
+
const env = ghAutomationEnv(process.env);
|
|
1945
|
+
try {
|
|
1946
|
+
return execFileSync('gh', args, {
|
|
1947
|
+
encoding: 'utf8', timeout: 30_000, maxBuffer: 16 * 1024 * 1024,
|
|
1948
|
+
stdio: ['ignore', 'pipe', 'pipe'], env,
|
|
1949
|
+
});
|
|
1950
|
+
} catch (error) { throw ghFailure(args, error, env.GH_TOKEN); }
|
|
1951
|
+
};
|
|
1856
1952
|
|
|
1857
1953
|
/** Merge-time adapter: reuse the sweep's GitHub inventory and worktree liveness without a second triage implementation. */
|
|
1858
1954
|
export async function supersedeMergedGoalDrafts(
|
|
@@ -1956,9 +2052,14 @@ function deliverResyncMemo(spaceId: string, memo: ControlMemoPayload,
|
|
|
1956
2052
|
}
|
|
1957
2053
|
|
|
1958
2054
|
const execFileAsync = promisify(execFile);
|
|
1959
|
-
const executeGhAsync = async (args: string[]): Promise<string> =>
|
|
1960
|
-
|
|
1961
|
-
|
|
2055
|
+
const executeGhAsync = async (args: string[]): Promise<string> => {
|
|
2056
|
+
const env = ghAutomationEnv(process.env);
|
|
2057
|
+
try {
|
|
2058
|
+
return (await execFileAsync('gh', args, {
|
|
2059
|
+
encoding: 'utf8', timeout: 30_000, maxBuffer: 16 * 1024 * 1024, env,
|
|
2060
|
+
})).stdout;
|
|
2061
|
+
} catch (error) { throw ghFailure(args, error, env.GH_TOKEN); }
|
|
2062
|
+
};
|
|
1962
2063
|
|
|
1963
2064
|
/** RUN-TTL adapters: open self-impl PRs, head commit time (async, bounded by the core), owning run, memo, label. */
|
|
1964
2065
|
export async function githubRunTtlAdapters(repository: string, execute: GhExecute = executeGh,
|
|
@@ -2420,15 +2521,16 @@ export function githubDraftSweepAdapters(execute: GhExecute = executeGh, git: Dr
|
|
|
2420
2521
|
},
|
|
2421
2522
|
getClaimOwner: async (repository, number) => claimCommentOwner(repository, number, execute),
|
|
2422
2523
|
getActiveClaimOwner: async (repository, number) => {
|
|
2524
|
+
// DRAFT-METRIC: the batched GraphQL comments (oldest first, proven complete) answer without a per-draft REST call.
|
|
2525
|
+
const batched = (await detailFor(repository, number))?.comments;
|
|
2526
|
+
if (batched) return activeClaimOwnerFromBodies(batched);
|
|
2423
2527
|
const pages = ghJson<Array<Array<{ body: string; created_at: string }>>>(['api', '--paginate', '--slurp',
|
|
2424
2528
|
`repos/${repository}/issues/${number}/comments?per_page=100`], execute);
|
|
2425
2529
|
if (!Array.isArray(pages) || pages.some((page) => !Array.isArray(page)
|
|
2426
2530
|
|| page.some((comment) => typeof comment.body !== 'string' || !Number.isFinite(Date.parse(comment.created_at)))))
|
|
2427
2531
|
throw new Error('Incomplete claim comments');
|
|
2428
|
-
|
|
2429
|
-
.map((comment) => comment.body)
|
|
2430
|
-
.find((body) => draftClaimOwner(body) !== undefined || body.startsWith('🔧 처리 끝 —'));
|
|
2431
|
-
return last ? draftClaimOwner(last) : undefined;
|
|
2532
|
+
return activeClaimOwnerFromBodies(pages.flat().sort((a, b) => Date.parse(a.created_at) - Date.parse(b.created_at))
|
|
2533
|
+
.map((comment) => comment.body));
|
|
2432
2534
|
},
|
|
2433
2535
|
getReviewGate: async (draft, repository) => reviewGateForDraft(draft, repository),
|
|
2434
2536
|
};
|
|
@@ -2436,12 +2538,16 @@ export function githubDraftSweepAdapters(execute: GhExecute = executeGh, git: Dr
|
|
|
2436
2538
|
|
|
2437
2539
|
export interface HarnessDraftSweepDeps {
|
|
2438
2540
|
readonly adapters?: DraftSweepAdapters;
|
|
2541
|
+
/** Optional overlap source injection; tests do not read GitHub or logs.db. */
|
|
2542
|
+
readonly overlapAdapters?: OverlapMetricAdapters;
|
|
2439
2543
|
readonly repository?: () => string;
|
|
2440
2544
|
readonly write?: (line: string) => void;
|
|
2441
2545
|
readonly execute?: GhExecute;
|
|
2442
2546
|
readonly now?: () => Date;
|
|
2443
2547
|
/** RUN-TTL step adapters. Omitted with injected `adapters` ⇒ the step is skipped (fakes never reach real gh). */
|
|
2444
2548
|
readonly runTtl?: RunTtlAdapters;
|
|
2549
|
+
/** Test seam for `tools.selfImplement.draftSweepCloseCap`; omitted ⇒ the user config. */
|
|
2550
|
+
readonly closeCapConfig?: () => unknown;
|
|
2445
2551
|
}
|
|
2446
2552
|
|
|
2447
2553
|
/** RUN-TTL step inside the draft sweep — fail-soft: its failure never changes the sweep verdict. */
|
|
@@ -2464,6 +2570,12 @@ async function draftSweepRunTtl(repository: string, deps: HarnessDraftSweepDeps,
|
|
|
2464
2570
|
}
|
|
2465
2571
|
}
|
|
2466
2572
|
|
|
2573
|
+
/** Latest claim or release among comments in chronological order (oldest first); a release means no owner. */
|
|
2574
|
+
export function activeClaimOwnerFromBodies(bodies: readonly string[]): string | undefined {
|
|
2575
|
+
const last = [...bodies].reverse().find((body) => draftClaimOwner(body) !== undefined || body.startsWith('🔧 처리 끝 —'));
|
|
2576
|
+
return last ? draftClaimOwner(last) : undefined;
|
|
2577
|
+
}
|
|
2578
|
+
|
|
2467
2579
|
function draftClaimOwner(body: string): string | undefined {
|
|
2468
2580
|
return /^🔧 처리 중 — owner ([^\n·]+?) · /m.exec(body)?.[1]?.trim();
|
|
2469
2581
|
}
|
|
@@ -2520,11 +2632,43 @@ function installHarnessDraftSweepCommand(harnessCmd: Command, deps: HarnessDraft
|
|
|
2520
2632
|
process.exitCode = 1;
|
|
2521
2633
|
}
|
|
2522
2634
|
});
|
|
2635
|
+
drafts.command('metrics').description('draft 재고·최장 나이·needs-owner·48h 전환율 (읽기 전용 · PR 근거는 GraphQL 배치)')
|
|
2636
|
+
.option('--json', 'JSON 으로 출력')
|
|
2637
|
+
.option('--repo <owner/name>', '조회할 GitHub 저장소')
|
|
2638
|
+
.action(async (opts: { json?: boolean; repo?: string }) => {
|
|
2639
|
+
try {
|
|
2640
|
+
const execute = deps.execute ?? executeGh;
|
|
2641
|
+
const repository = resolveRepositoryName({ repo: opts.repo ?? deps.repository?.(), executeGh: (args) => execute(args) });
|
|
2642
|
+
if (!DRAFT_SWEEP_REPOSITORY.test(repository) || repository.includes('..')) throw new HarnessCliInputError(`invalid --repo (expected owner/name): ${repository}`);
|
|
2643
|
+
const started = Date.now();
|
|
2644
|
+
const adapters = deps.adapters ?? githubDraftSweepAdapters(deps.execute);
|
|
2645
|
+
const now = deps.now?.() ?? new Date();
|
|
2646
|
+
const metrics = await collectDraftMetrics(repository, adapters, now);
|
|
2647
|
+
let overlap: Awaited<ReturnType<typeof collectOverlapMetrics>> | null = null;
|
|
2648
|
+
try {
|
|
2649
|
+
overlap = await collectOverlapMetrics(repository, deps.overlapAdapters ?? {
|
|
2650
|
+
...adapters,
|
|
2651
|
+
listPreflightRows: readAskPreflightRowsSince,
|
|
2652
|
+
listSalvagedRows: readSalvagedRowsSince,
|
|
2653
|
+
}, now);
|
|
2654
|
+
} catch (error) {
|
|
2655
|
+
try { debug.log('self-dev.draft-metrics', 'overlap-failed', { repository, reason: sweepFailureReason(error) }); } catch { /* fail-soft */ }
|
|
2656
|
+
}
|
|
2657
|
+
try { debug.log('self-dev.draft-metrics', 'collected', { repository, ms: Date.now() - started, ...metrics }); } catch { /* fail-soft */ }
|
|
2658
|
+
const write = deps.write ?? console.log;
|
|
2659
|
+
if (opts.json) write(JSON.stringify({ repository, ...metrics, overlap }));
|
|
2660
|
+
else write(`draft 재고 ${metrics.inventory} · 최장 나이 ${metrics.oldestAgeHours === null ? '해당 없음' : `${metrics.oldestAgeHours.toFixed(1)}h`} · needs-owner ${metrics.needsOwner} · 48h 전환율 ${metrics.conversion48h === null ? '표본 없음' : `${(metrics.conversion48h * 100).toFixed(1)}% (${metrics.converted48h}/${metrics.cohort48h})`}`);
|
|
2661
|
+
} catch (error) {
|
|
2662
|
+
console.error(humanErrorLine(error));
|
|
2663
|
+
process.exitCode = 1;
|
|
2664
|
+
}
|
|
2665
|
+
});
|
|
2523
2666
|
return drafts.command('sweep').description('draft PR 을 조사한다 (기본: 변경 없음)')
|
|
2524
2667
|
.option('--apply', '판정한 라벨 변경과 종료를 적용')
|
|
2525
2668
|
.option('--json', '판정 결과를 JSON 으로 출력')
|
|
2526
2669
|
.option('--repo <owner/name>', '조회할 GitHub 저장소')
|
|
2527
|
-
.
|
|
2670
|
+
.option('--close-cap <n>', '이번 틱 최대 종료 수 (1~100 · config tools.selfImplement.draftSweepCloseCap 보다 우선 · 기본 10)')
|
|
2671
|
+
.action(async (opts: { apply?: boolean; json?: boolean; repo?: string; closeCap?: string }) => {
|
|
2528
2672
|
try {
|
|
2529
2673
|
const write = deps.write ?? console.log;
|
|
2530
2674
|
const execute = deps.execute ?? executeGh;
|
|
@@ -2537,7 +2681,16 @@ function installHarnessDraftSweepCommand(harnessCmd: Command, deps: HarnessDraft
|
|
|
2537
2681
|
}
|
|
2538
2682
|
// One adapter instance per sweep: its open listing and ledger scan are shared with the RUN-TTL step.
|
|
2539
2683
|
const github = deps.adapters ? undefined : githubDraftSweepAdapters(deps.execute);
|
|
2540
|
-
const
|
|
2684
|
+
const configCap = deps.closeCapConfig
|
|
2685
|
+
? deps.closeCapConfig()
|
|
2686
|
+
: (() => { try { return getUserConfig().tools?.selfImplement?.draftSweepCloseCap; } catch { return undefined; } })();
|
|
2687
|
+
const cap = resolveDraftSweepCloseCap({ flag: opts.closeCap, config: configCap });
|
|
2688
|
+
for (const warning of cap.warnings) {
|
|
2689
|
+
console.error(`⚠ ${warning}`);
|
|
2690
|
+
try { debug.log('self-dev.draft-sweep', 'close-cap-rejected', { warning, closeCap: cap.closeCap, closeCapSource: cap.closeCapSource }); } catch { /* fail-soft */ }
|
|
2691
|
+
}
|
|
2692
|
+
const result: DraftSweepResult = await runDraftSweep({ repository, apply: opts.apply === true, adapters: deps.adapters ?? github!,
|
|
2693
|
+
closeCap: cap.closeCap, closeCapSource: cap.closeCapSource });
|
|
2541
2694
|
const reason = result.error ? sweepFailureReason(result.error) : undefined;
|
|
2542
2695
|
const ttlStarted = Date.now();
|
|
2543
2696
|
const runTtl = await draftSweepRunTtl(repository, deps, github);
|
|
@@ -2888,7 +3041,8 @@ export function installHarnessCliCommand(program: Command, deps: HarnessCliComma
|
|
|
2888
3041
|
harnessQueueReceiptPath(root, item.launchId!), queueDeps.launchCommand ?? join(import.meta.dir, '../../bin/elanous.mjs'),
|
|
2889
3042
|
...(resolve(root) === prodInstanceRoot() ? [] : [`--test=${root}`]), ...args], {
|
|
2890
3043
|
cwd: item.launchCwd ?? resolve(import.meta.dir, '../..'), stdio: 'inherit',
|
|
2891
|
-
env: { ...process.env, ELANOUS_STATE_DIR: root,
|
|
3044
|
+
env: { ...process.env, ELANOUS_STATE_DIR: root,
|
|
3045
|
+
ELANOUS_STATE_DIR_SOURCE: stateDirSourceForChild(root), ELANOUS_HARNESS_SEAT: item.seat,
|
|
2892
3046
|
ELANOUS_HARNESS_QUEUE_LAUNCH: item.launchId! },
|
|
2893
3047
|
});
|
|
2894
3048
|
// Capture the end at spawn time: a child that already closed (or died by a signal) must not leave the parent waiting.
|
|
@@ -4,6 +4,7 @@ import { appendFileSync, closeSync, existsSync, mkdirSync, openSync, readdirSync
|
|
|
4
4
|
import { join, resolve } from 'node:path';
|
|
5
5
|
import { Database } from 'bun:sqlite';
|
|
6
6
|
import { debug } from '../debug/log.js';
|
|
7
|
+
import { stateDirSourceForChild } from '../agent/identity-env.js';
|
|
7
8
|
import { findTreeRoot } from '../cli/test-flag.js';
|
|
8
9
|
import { readLogInstances, type LogInstanceView } from '../mss/logging/instance-registry.js';
|
|
9
10
|
import { effectiveInstanceRoot, prodInstanceRoot, releaseLedgerRoot } from '../instance/resolve.js';
|
|
@@ -20,6 +21,7 @@ import { hostLeaseCounts, leaseHasPendingPod } from '../pod-lease/host-lease.js'
|
|
|
20
21
|
import { leaseKubectl, measurePoolLease } from '../task-orchestrator/surfaces/pod-lease.js';
|
|
21
22
|
import { parsePodPool, podPoolHostLease, resolvePodPoolSpec } from '../task-orchestrator/surfaces/pod-pool.js';
|
|
22
23
|
import { writeHarnessQueueReceipt } from './harness-queue-child.js';
|
|
24
|
+
import { adviseLaunch, compareLaunchAdvice, type LaunchAdvice } from './launch-advice.js';
|
|
23
25
|
import { finishAdvice, measureFinish, recordFinishHistory, type FinishMetrics } from '../loops/orchestrator/finish-rate.js';
|
|
24
26
|
|
|
25
27
|
export type QueueSeat = 'OP' | 'TC' | 'MK' | 'UX';
|
|
@@ -73,6 +75,8 @@ export interface HarnessQueueDeps {
|
|
|
73
75
|
configPath?: string;
|
|
74
76
|
cap?: (seat: QueueSeat) => number;
|
|
75
77
|
pool?: () => QueuePool;
|
|
78
|
+
/** Shadow-only advice seam; exceptions must not affect the queue decision. */
|
|
79
|
+
advice?: typeof adviseLaunch;
|
|
76
80
|
launch?: (item: QueueItem, args: string[], root: string) => Promise<number>;
|
|
77
81
|
/** Test seam: runs after a direct CLI call enqueued its row and before that call's own tick (race tests). */
|
|
78
82
|
afterEnqueue?: (item: QueueItem) => void | Promise<void>;
|
|
@@ -385,24 +389,25 @@ export function listHarnessQueue(deps: HarnessQueueDeps = {}): QueueItem[] {
|
|
|
385
389
|
return read(harnessQueuePath(deps.root ?? effectiveInstanceRoot()));
|
|
386
390
|
}
|
|
387
391
|
|
|
388
|
-
/** A missing row is not proof of failure
|
|
389
|
-
export function harnessQueueOutcome(id: string, deps: HarnessQueueDeps = {}): 'pending' | 'succeeded' | 'retryable' | 'unknown' {
|
|
392
|
+
/** A missing row is not proof of failure; explicit removal and terminal receipts have distinct markers. */
|
|
393
|
+
export function harnessQueueOutcome(id: string, deps: HarnessQueueDeps = {}): 'pending' | 'succeeded' | 'retryable' | 'removed' | 'unknown' {
|
|
390
394
|
const root = deps.root ?? effectiveInstanceRoot();
|
|
391
395
|
const row = listHarnessQueue(deps).find((item) => item.id === id);
|
|
392
396
|
if (!row) {
|
|
393
397
|
const marker = join(root, 'harness', `${id}.outcome`);
|
|
394
398
|
if (!existsSync(marker)) return 'unknown';
|
|
395
399
|
const value = readFileSync(marker, 'utf8');
|
|
396
|
-
return value === 'succeeded' || value === 'retryable' ? value : 'unknown';
|
|
400
|
+
return value === 'succeeded' || value === 'retryable' || value === 'removed' ? value : 'unknown';
|
|
397
401
|
}
|
|
398
402
|
return queueRowOutcome(row, root);
|
|
399
403
|
}
|
|
400
404
|
|
|
401
405
|
const keyMarkerPath = (root: string, key: string) => join(root, 'harness', 'by-key', `${createHash('sha256').update(key).digest('hex')}.id`);
|
|
402
406
|
|
|
403
|
-
function recordQueueOutcome(root: string, id: string, outcome: 'succeeded' | 'retryable', key?: string): void {
|
|
407
|
+
function recordQueueOutcome(root: string, id: string, outcome: 'succeeded' | 'retryable' | 'removed', key?: string): void {
|
|
404
408
|
const path = join(root, 'harness', `${id}.outcome`);
|
|
405
|
-
if (
|
|
409
|
+
if (outcome === 'removed') writeFileSync(path, outcome, { mode: 0o600 });
|
|
410
|
+
else if (!existsSync(path)) writeFileSync(path, outcome, { flag: 'wx', mode: 0o600 });
|
|
406
411
|
// The row is about to leave the queue; keep «which id last held this key» so a caller that crashed before
|
|
407
412
|
// recording the id can still find the outcome (LOOP-LIVE1 · seat loop recovery).
|
|
408
413
|
if (key) {
|
|
@@ -491,10 +496,8 @@ export async function removeHarnessQueue(id: string, deps: HarnessQueueDeps = {}
|
|
|
491
496
|
if (state !== 'finished' && state !== 'not-started'
|
|
492
497
|
|| (deps.processes ?? readHarnessQueueProcesses)().some((row) => row.launchId === item.launchId)) return false;
|
|
493
498
|
}
|
|
494
|
-
const outcome = item
|
|
495
|
-
if (item.idempotencyKey
|
|
496
|
-
recordQueueOutcome(root, id, outcome, item.idempotencyKey);
|
|
497
|
-
}
|
|
499
|
+
const outcome = queueRowOutcome(item, root) === 'succeeded' ? 'succeeded' : 'removed';
|
|
500
|
+
if (item.idempotencyKey) recordQueueOutcome(root, id, outcome, item.idempotencyKey);
|
|
498
501
|
save(path, rows.filter((row) => row.id !== id));
|
|
499
502
|
observe('skipped', { id, seat: item.seat, reason: 'removed' }, deps);
|
|
500
503
|
return true;
|
|
@@ -699,6 +702,26 @@ export function readHarnessQueueInventory(probe: ProcessProbe = {}): QueueInvent
|
|
|
699
702
|
if (prior && prior !== record.seat) { ledgerSeats.delete(pid); ambiguousPids.add(pid); }
|
|
700
703
|
else ledgerSeats.set(pid, record.seat!);
|
|
701
704
|
}
|
|
705
|
+
// One lsof for every candidate: a per-pid lsof (~0.1–0.3 s each) made a tick on a busy host take 10 s+ (10-08). lsof exits 1
|
|
706
|
+
// when some pid is gone yet still prints the others; a pid it does not print stays unattributed (charged to all seats).
|
|
707
|
+
let batchCwds: Map<number, string> | undefined;
|
|
708
|
+
const readBatchCwds = (): Map<number, string> => {
|
|
709
|
+
const cwds = new Map<number, string>();
|
|
710
|
+
const started = Date.now();
|
|
711
|
+
const lsof = run('lsof', ['-a', '-d', 'cwd', '-Fpn', '-p', candidates.map(({ pid }) => pid).join(',')],
|
|
712
|
+
{ encoding: 'utf8', timeout: 5_000, maxBuffer: 16 * 1024 * 1024 });
|
|
713
|
+
if (!lsof.error && typeof lsof.stdout === 'string') {
|
|
714
|
+
let current: number | undefined;
|
|
715
|
+
for (const line of lsof.stdout.split('\n')) {
|
|
716
|
+
if (line.startsWith('p')) current = Number(line.slice(1));
|
|
717
|
+
else if (line.startsWith('n/') && current !== undefined) cwds.set(current, line.slice(1));
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
try { debug.log('harness.queue', 'cwd-batch', { pids: candidates.length, read: cwds.size, ms: Date.now() - started,
|
|
721
|
+
...(lsof.error ? { error: String(lsof.error).slice(0, 240) } : {}) }); }
|
|
722
|
+
catch { /* Observation cannot affect dispatch. */ }
|
|
723
|
+
return cwds;
|
|
724
|
+
};
|
|
702
725
|
const rows: QueueProcess[] = [];
|
|
703
726
|
// Attribute a run after grouping: the orchestrator may switch working trees while its launcher stays in HQ.
|
|
704
727
|
for (const root of candidates.filter((row) => !byPid.has(row.ppid))) {
|
|
@@ -736,9 +759,9 @@ export function readHarnessQueueInventory(probe: ProcessProbe = {}): QueueInvent
|
|
|
736
759
|
if (probe.cwd) cwd = probe.cwd(pid);
|
|
737
760
|
else if (platform === 'linux') cwd = readlinkSync(`/proc/${pid}/cwd`);
|
|
738
761
|
else {
|
|
739
|
-
const
|
|
740
|
-
if (
|
|
741
|
-
cwd =
|
|
762
|
+
const found = (batchCwds ??= readBatchCwds()).get(pid);
|
|
763
|
+
if (found === undefined) throw new Error('cwd unavailable');
|
|
764
|
+
cwd = found;
|
|
742
765
|
}
|
|
743
766
|
if (!cwd.startsWith('/')) continue;
|
|
744
767
|
const found = queueSeatForCwd(cwd, trees);
|
|
@@ -816,7 +839,8 @@ async function launch(item: QueueItem, args: string[], root: string, command?: s
|
|
|
816
839
|
harnessQueueReceiptPath(root, item.launchId!), command ?? join(repo, 'bin', 'elanous.mjs'),
|
|
817
840
|
...(resolve(root) === prodInstanceRoot() ? [] : [`--test=${root}`]), ...args], {
|
|
818
841
|
cwd: item.launchCwd ?? repo, detached: true, stdio: ['ignore', fd, fd],
|
|
819
|
-
env: { ...process.env, ELANOUS_STATE_DIR: root,
|
|
842
|
+
env: { ...process.env, ELANOUS_STATE_DIR: root,
|
|
843
|
+
ELANOUS_STATE_DIR_SOURCE: stateDirSourceForChild(root), ELANOUS_HARNESS_SEAT: item.seat,
|
|
820
844
|
ELANOUS_HARNESS_QUEUE_LAUNCH: item.launchId! },
|
|
821
845
|
});
|
|
822
846
|
const pid = await new Promise<number>((done, reject) => {
|
|
@@ -1187,7 +1211,13 @@ export async function tickHarnessQueue(deps: HarnessQueueDeps = {}, requestedId?
|
|
|
1187
1211
|
|
|
1188
1212
|
async function tickHarnessQueueOnce(deps: HarnessQueueDeps, requestedId: string | undefined, followUp: boolean): Promise<QueueTick> {
|
|
1189
1213
|
const root = deps.root ?? effectiveInstanceRoot();
|
|
1190
|
-
|
|
1214
|
+
const caller = 'harness-queue-tick' as const;
|
|
1215
|
+
let advice: LaunchAdvice = { verdict: 'unknown', why: 'pool not measured before queue decision' };
|
|
1216
|
+
const shadow = (pool: QueuePool | undefined): void => {
|
|
1217
|
+
try { advice = (deps.advice ?? adviseLaunch)({ pool, caller }); }
|
|
1218
|
+
catch { advice = { verdict: 'unknown', why: 'launch advice unavailable' }; }
|
|
1219
|
+
};
|
|
1220
|
+
const actual = await locked<QueueTick>(root, async (path): Promise<QueueTick> => {
|
|
1191
1221
|
// A burst follow-up already ran the author shadow and the idle request in this tick's first pass.
|
|
1192
1222
|
if (!followUp) try {
|
|
1193
1223
|
const now = (deps.now ?? (() => new Date()))();
|
|
@@ -1196,7 +1226,11 @@ async function tickHarnessQueueOnce(deps: HarnessQueueDeps, requestedId: string
|
|
|
1196
1226
|
debug.log('loops.author-depth', 'shadow-skipped-test', {});
|
|
1197
1227
|
} else {
|
|
1198
1228
|
const shadow = deps.authorShadow ?? (await import('../loops/orchestrator/author-depth.js')).runAuthorDepthShadow;
|
|
1229
|
+
const started = Date.now();
|
|
1199
1230
|
await shadow(root, now);
|
|
1231
|
+
// The shadow runs inside the queue lock; its duration is what other ticks wait on.
|
|
1232
|
+
try { debug.log('harness.queue', 'author-shadow', { ms: Date.now() - started }); }
|
|
1233
|
+
catch { /* Observation cannot affect dispatch. */ }
|
|
1200
1234
|
}
|
|
1201
1235
|
} catch (error) {
|
|
1202
1236
|
try { debug.log('loops.author-depth', 'shadow-failed', { reason: String(error) }); }
|
|
@@ -1463,6 +1497,7 @@ async function tickHarnessQueueOnce(deps: HarnessQueueDeps, requestedId: string
|
|
|
1463
1497
|
if (pool && (![pool.running, pool.pending, pool.reserved, pool.limit].every((n) => Number.isSafeInteger(n) && n >= 0) || pool.limit < 1)) {
|
|
1464
1498
|
poolReason = 'pod lease status incomplete';
|
|
1465
1499
|
}
|
|
1500
|
+
shadow(poolReason ? undefined : pool);
|
|
1466
1501
|
if (pool && !poolReason) {
|
|
1467
1502
|
const queueLaunching = current.filter((row) => row.status === 'launching' || row.status === 'launched').length;
|
|
1468
1503
|
// CHILD-UNIV-HOLD: each unreadable child universe is charged one pool slot (conservative), never a seat slot.
|
|
@@ -1545,4 +1580,13 @@ async function tickHarnessQueueOnce(deps: HarnessQueueDeps, requestedId: string
|
|
|
1545
1580
|
}
|
|
1546
1581
|
return waiting();
|
|
1547
1582
|
});
|
|
1583
|
+
// An empty queue tick has nothing to advise on; logging it would flood the advice-unknown sample.
|
|
1584
|
+
if (actual.outcome === 'skipped' && !actual.item) return actual;
|
|
1585
|
+
try {
|
|
1586
|
+
const { kind } = compareLaunchAdvice(advice, actual.outcome);
|
|
1587
|
+
debug.log('resource.advice', 'launch-advice', {
|
|
1588
|
+
caller, verdict: advice.verdict, why: advice.why, actual: actual.outcome, kind, itemId: actual.item?.id ?? null,
|
|
1589
|
+
});
|
|
1590
|
+
} catch { /* Shadow observation cannot affect the queue result. */ }
|
|
1591
|
+
return actual;
|
|
1548
1592
|
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { QueuePool, QueueTick } from './harness-queue.js';
|
|
2
|
+
|
|
3
|
+
export type LaunchAdvice = { verdict: 'launch-now' | 'wait' | 'unknown'; why: string; retryAfterHint?: string };
|
|
4
|
+
export type LaunchAdviceInput = { pool: QueuePool | null | undefined; caller: 'harness-queue-tick' | 'hand-task' | 'release-run' };
|
|
5
|
+
|
|
6
|
+
/** A pool-only suggestion; queue, seat, freeze and finish gates remain the dispatcher's decisions. */
|
|
7
|
+
export function adviseLaunch(input: LaunchAdviceInput): LaunchAdvice {
|
|
8
|
+
const pool = input.pool;
|
|
9
|
+
if (!pool || ![pool.running, pool.pending, pool.reserved, pool.limit].every(n => Number.isSafeInteger(n) && n >= 0)
|
|
10
|
+
|| pool.limit < 1) return { verdict: 'unknown', why: 'pool unavailable or incomplete' };
|
|
11
|
+
const used = pool.running + pool.pending + pool.reserved;
|
|
12
|
+
if (used >= pool.limit) return { verdict: 'wait', why: `pool ${used}/${pool.limit} occupied`, retryAfterHint: 'when a pool slot is released' };
|
|
13
|
+
return { verdict: 'launch-now', why: `pool ${used}/${pool.limit} occupied` };
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function compareLaunchAdvice(advice: LaunchAdvice, actualOutcome: QueueTick['outcome']): {
|
|
17
|
+
agree: boolean; kind: 'advice-launch-actual-wait' | 'advice-wait-actual-launch' | 'advice-unknown' | 'agree';
|
|
18
|
+
} {
|
|
19
|
+
if (advice.verdict === 'unknown' || actualOutcome === 'skipped') return { agree: false, kind: 'advice-unknown' };
|
|
20
|
+
if (advice.verdict === 'launch-now' && actualOutcome === 'waiting') return { agree: false, kind: 'advice-launch-actual-wait' };
|
|
21
|
+
if (advice.verdict === 'wait' && actualOutcome === 'launched') return { agree: false, kind: 'advice-wait-actual-launch' };
|
|
22
|
+
return { agree: true, kind: 'agree' };
|
|
23
|
+
}
|