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,123 @@
|
|
|
1
|
+
// GATE-IMAGE-DEPS (0.2.21 · GATE-SPEED) — the release-gate shards' dependencies come baked in the harness image.
|
|
2
|
+
//
|
|
3
|
+
// Measured on the 0.2.20 cut gate (~/.elanous/release/0.2.20/gate-logs/cut · 24 root shards · pod-<n>.log ⊕ .junit.xml):
|
|
4
|
+
// install-slot wait median 156 s (max 509 s) ⊕ `bun install` root+pwa median 104 s (max 160 s) = median ≈ 260 s of a
|
|
5
|
+
// ≈ 1200 s shard spent before the first test — and the shards that waited longest were the ones the 1200 s deadline cut
|
|
6
|
+
// (pod-1 · 3 · 11 · 21 · 22 waited 327–509 s and ran only 215–436 s of tests). Alone the install takes seconds; 24 at
|
|
7
|
+
// once thrash the node (GATE-INSTALL-CACHE). Not installing at all removes both the wait and the install.
|
|
8
|
+
//
|
|
9
|
+
// How: `docker/harness/build.sh` stages each install tree's lockfile ⊕ the install-relevant projection of its package.json
|
|
10
|
+
// ({@link GATE_DEPS_PROJECTION_JS}) into `gate-deps/`; the Dockerfile installs them once into {@link GATE_DEPS_DIR}
|
|
11
|
+
// (`--frozen-lockfile`). The layer's inputs change only when dependencies change, so the per-commit rebuild reuses it from
|
|
12
|
+
// the build cache. A shard links `node_modules` to the baked tree only when its own lockfile and projection are
|
|
13
|
+
// byte-identical to the baked ones; anything else — no baked tree (old image), a different lockfile, a different
|
|
14
|
+
// projection, an existing node_modules, a failed link — falls back to the old path (install slot ⊕ `bun install`).
|
|
15
|
+
// The link is a symlink: copying (or hard-linking across overlay layers) is the very I/O the install slots were throttling.
|
|
16
|
+
// Module resolution follows the real path, and the baked layout mirrors the repo (`<dir>/node_modules`,
|
|
17
|
+
// `<dir>/apps/pwa/node_modules`), so the parent lookup from a package inside the pwa tree is the same as before.
|
|
18
|
+
|
|
19
|
+
import { createHash } from 'node:crypto';
|
|
20
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
21
|
+
import { join } from 'node:path';
|
|
22
|
+
|
|
23
|
+
/** Baked dependency root inside the full harness image (mirrors the repo layout). */
|
|
24
|
+
export const GATE_DEPS_DIR = '/opt/elanous-gate-deps';
|
|
25
|
+
/** The install trees, relative to the repo root, in install order (same as the old `bun install && (cd apps/pwa && bun install)`). */
|
|
26
|
+
export const GATE_DEPS_TREES = ['.', 'apps/pwa'] as const;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* The part of a package.json that decides what `bun install` produces: dependency maps, overrides, trusted/patched
|
|
30
|
+
* dependencies and the install lifecycle scripts. `version`, `description` and every other script are dropped, so a
|
|
31
|
+
* release bump or a new script does not invalidate the baked layer. Run as `bun -e <this> <package.json>` (stdout).
|
|
32
|
+
* ⛔ build.sh (bake) and the shard (check) run this same text — changing it simply makes the next shards fall back once.
|
|
33
|
+
*/
|
|
34
|
+
export const GATE_DEPS_PROJECTION_JS = [
|
|
35
|
+
"const p=JSON.parse(require('fs').readFileSync(process.argv[1],'utf8'));const o={};",
|
|
36
|
+
"for(const k of ['name','dependencies','devDependencies','optionalDependencies','peerDependencies','trustedDependencies','overrides','resolutions','patchedDependencies','workspaces'])if(p[k]!==undefined)o[k]=p[k];",
|
|
37
|
+
"const s={};for(const k of ['preinstall','install','postinstall','prepare'])if(p.scripts&&p.scripts[k]!==undefined)s[k]=p.scripts[k];",
|
|
38
|
+
"if(Object.keys(s).length)o.scripts=s;process.stdout.write(JSON.stringify(o,null,2)+'\\n');",
|
|
39
|
+
].join('');
|
|
40
|
+
|
|
41
|
+
/** Same projection in-process (tests · build staging). */
|
|
42
|
+
export function projectDepsManifest(manifest: Record<string, unknown>): Record<string, unknown> {
|
|
43
|
+
const out: Record<string, unknown> = {};
|
|
44
|
+
for (const key of ['name', 'dependencies', 'devDependencies', 'optionalDependencies', 'peerDependencies', 'trustedDependencies', 'overrides', 'resolutions', 'patchedDependencies', 'workspaces']) {
|
|
45
|
+
if (manifest[key] !== undefined) out[key] = manifest[key];
|
|
46
|
+
}
|
|
47
|
+
const scripts = manifest.scripts as Record<string, unknown> | undefined;
|
|
48
|
+
const lifecycle: Record<string, unknown> = {};
|
|
49
|
+
for (const key of ['preinstall', 'install', 'postinstall', 'prepare']) if (scripts && scripts[key] !== undefined) lifecycle[key] = scripts[key];
|
|
50
|
+
if (Object.keys(lifecycle).length) out.scripts = lifecycle;
|
|
51
|
+
return out;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* build.sh staging: `<dest>/<tree>/bun.lock` (verbatim) ⊕ `<dest>/<tree>/package.json` (the projection) for every install
|
|
56
|
+
* tree. The digest (sha256 of the staged bytes, 12 hex) becomes the image label `elanous.gate-deps`.
|
|
57
|
+
*/
|
|
58
|
+
export function stageGateDeps(root: string, dest: string): { digest: string; trees: string[] } {
|
|
59
|
+
const hash = createHash('sha256');
|
|
60
|
+
const trees: string[] = [];
|
|
61
|
+
mkdirSync(dest, { recursive: true });
|
|
62
|
+
for (const tree of GATE_DEPS_TREES) {
|
|
63
|
+
const lock = join(root, tree, 'bun.lock');
|
|
64
|
+
const manifest = join(root, tree, 'package.json');
|
|
65
|
+
if (!existsSync(lock) || !existsSync(manifest)) continue;
|
|
66
|
+
const out = join(dest, tree);
|
|
67
|
+
mkdirSync(out, { recursive: true });
|
|
68
|
+
const lockBytes = readFileSync(lock);
|
|
69
|
+
const projected = `${JSON.stringify(projectDepsManifest(JSON.parse(readFileSync(manifest, 'utf8')) as Record<string, unknown>), null, 2)}\n`;
|
|
70
|
+
writeFileSync(join(out, 'bun.lock'), lockBytes, { mode: 0o644 });
|
|
71
|
+
writeFileSync(join(out, 'package.json'), projected, { mode: 0o644 });
|
|
72
|
+
hash.update(`${tree}\0`).update(lockBytes).update('\0').update(projected).update('\0');
|
|
73
|
+
trees.push(tree);
|
|
74
|
+
}
|
|
75
|
+
return { digest: hash.digest('hex').slice(0, 12), trees };
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const sq = (value: string) => `'${value.replace(/'/g, `'\\''`)}'`;
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Bash that installs the shard's dependencies (cwd = the repo clone) and sets `irc` (0 = ready). Each tree is linked to
|
|
82
|
+
* the baked tree when it matches, else installed — only the installs take an install slot (`install_slot_acquire` /
|
|
83
|
+
* `install_slot_release` from `installSlotScript` must already be defined when `installSlots > 0`). Every decision is a
|
|
84
|
+
* `[gate] deps <tree> baked|miss (<why>)` line in `log` (the shard's install.log).
|
|
85
|
+
*/
|
|
86
|
+
export function gateDepsInstallScript(o: { log: string; installSlots: number; depsDir?: string }): string {
|
|
87
|
+
const deps = sq(o.depsDir ?? GATE_DEPS_DIR);
|
|
88
|
+
const log = o.log;
|
|
89
|
+
return [
|
|
90
|
+
'gate_deps_link() {',
|
|
91
|
+
` local t="$1" d=${deps} want`,
|
|
92
|
+
' [ "$t" = . ] || d="$d/$t"',
|
|
93
|
+
' if [ ! -d "$d/node_modules" ]; then echo "[gate] deps $t miss (no baked deps)"; return 1; fi',
|
|
94
|
+
' if [ -e "$t/node_modules" ] || [ -L "$t/node_modules" ]; then echo "[gate] deps $t miss (node_modules exists)"; return 1; fi',
|
|
95
|
+
' if ! cmp -s "$t/bun.lock" "$d/bun.lock"; then echo "[gate] deps $t miss (bun.lock differs)"; return 1; fi',
|
|
96
|
+
` want=$(bun -e ${sq(GATE_DEPS_PROJECTION_JS)} "$t/package.json" 2>/dev/null) || { echo "[gate] deps $t miss (package.json unreadable)"; return 1; }`,
|
|
97
|
+
' if [ "$want" != "$(cat "$d/package.json" 2>/dev/null)" ]; then echo "[gate] deps $t miss (package.json differs)"; return 1; fi',
|
|
98
|
+
' if ! ln -s "$d/node_modules" "$t/node_modules"; then echo "[gate] deps $t miss (link failed)"; return 1; fi',
|
|
99
|
+
' echo "[gate] deps $t baked"',
|
|
100
|
+
'}',
|
|
101
|
+
'irc=0; gate_deps_need=',
|
|
102
|
+
`for t in ${GATE_DEPS_TREES.join(' ')}; do gate_deps_link "$t" >> ${log} 2>&1 || gate_deps_need="$gate_deps_need $t"; done`,
|
|
103
|
+
'if [ -n "$gate_deps_need" ]; then',
|
|
104
|
+
...(o.installSlots > 0 ? [` install_slot_acquire >> ${log} 2>&1`] : []),
|
|
105
|
+
` for t in $gate_deps_need; do (cd "$t" && bun install) >> ${log} 2>&1; irc=$?; [ "$irc" -eq 0 ] || break; done`,
|
|
106
|
+
...(o.installSlots > 0 ? [' install_slot_release'] : []),
|
|
107
|
+
'fi',
|
|
108
|
+
].join('\n');
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** Per-tree decisions from a shard's install log (`baked` · `miss:<why>`), for the gate's `pod-shard` observation. */
|
|
112
|
+
export function parseGateDeps(log: string): Record<string, string> {
|
|
113
|
+
const out: Record<string, string> = {};
|
|
114
|
+
for (const match of log.matchAll(/^\[gate\] deps (\S+) (baked|miss \(([^)]*)\))$/gm)) out[match[1]!] = match[2] === 'baked' ? 'baked' : `miss:${match[3]}`;
|
|
115
|
+
return out;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (import.meta.main) {
|
|
119
|
+
const [cmd, dest] = process.argv.slice(2);
|
|
120
|
+
if (cmd !== 'stage' || !dest) { console.error('usage: pod-gate-deps.ts stage <dir>'); process.exit(2); }
|
|
121
|
+
const staged = stageGateDeps(process.cwd(), dest);
|
|
122
|
+
console.log(`digest=${staged.digest} trees=${staged.trees.join(',') || '-'}`);
|
|
123
|
+
}
|
|
@@ -16,13 +16,14 @@
|
|
|
16
16
|
* 풀을 안 주면 종전처럼 «현재 컨텍스트» 하나다(동작 불변).
|
|
17
17
|
*/
|
|
18
18
|
import { spawn, spawnSync } from 'node:child_process';
|
|
19
|
-
import { existsSync, mkdtempSync, rmSync } from 'node:fs';
|
|
19
|
+
import { existsSync, mkdirSync, mkdtempSync, rmSync, utimesSync } from 'node:fs';
|
|
20
20
|
import { tmpdir } from 'node:os';
|
|
21
21
|
import { join, resolve } from 'node:path';
|
|
22
22
|
import { debug } from '../../debug/log.js';
|
|
23
23
|
import { PodLeaseAdmission, type PodLeasePredecessor, type PodLeaseRelease } from '../../pod-lease/admission.js';
|
|
24
24
|
import { predecessorState, type PredecessorState } from '../../pod-lease/dependency-state.js';
|
|
25
|
-
import { HostPoolLease, leaseHasPendingPod } from '../../pod-lease/host-lease.js';
|
|
25
|
+
import { HostPoolLease, hostLeaseBaseDir, leaseHasPendingPod } from '../../pod-lease/host-lease.js';
|
|
26
|
+
import { acquireLockSync } from '../../storage/file-lock.js';
|
|
26
27
|
import { harnessCpuSlotsBesideGate, measurePoolLease, recommendConcurrency, type PodLeaseMember, type PoolDnsProbe, type PoolLeaseRecommendation } from './pod-lease.js';
|
|
27
28
|
|
|
28
29
|
export interface PodPoolMember {
|
|
@@ -473,13 +474,64 @@ type LocalImageInspect = (image: string) => { status: number | null; stdout: str
|
|
|
473
474
|
/** `docker save | ssh docker load` 한 번. 시험이 실제 전송 대신 호출 횟수만 센다. */
|
|
474
475
|
type ImageShip = (member: PodPoolMember, image: string) => { status: number | null; stderr: string };
|
|
475
476
|
|
|
477
|
+
const IMAGE_SHIP_TIMEOUT_MS = 1_800_000;
|
|
478
|
+
const REMOTE_RUN_TIMEOUT_MS = 900_000;
|
|
479
|
+
|
|
480
|
+
/**
|
|
481
|
+
* Runs a shell pipeline in its «own process group» and kills the whole group, not just bash (flood 10-08:
|
|
482
|
+
* 186 orphaned `docker save | gzip | ssh` pipes after runs timed out or died).
|
|
483
|
+
* - `perl setpgrp` gives the pipeline a group of its own, so `kill 0` cannot reach the caller.
|
|
484
|
+
* - The pipeline runs in the background and bash `wait`s, because bash defers a trap until a «foreground»
|
|
485
|
+
* command finishes — a TERM from the spawnSync timeout would otherwise wait out `docker save`.
|
|
486
|
+
* - A watchdog in the group polls the parent pid: if the caller dies (kill -9 included), the group goes too.
|
|
487
|
+
* - On a normal finish the group is swept as well (watchdog and its `sleep`).
|
|
488
|
+
* ⚠️ The background pipeline's stdin is /dev/null (non-interactive bash) — fine for `docker save`.
|
|
489
|
+
*/
|
|
490
|
+
export function runProcessGroupPipeline(pipeline: string, opts: { timeoutMs: number; parentPid?: number; watchIntervalSec?: number }): { status: number | null; stderr: string; timedOut: boolean } {
|
|
491
|
+
const script = [
|
|
492
|
+
`trap 'trap "" TERM HUP INT; kill -TERM 0 2>/dev/null; exit 143' TERM HUP INT`,
|
|
493
|
+
`( while kill -0 "$ELANOUS_PIPE_PARENT" 2>/dev/null; do sleep "$ELANOUS_PIPE_WATCH"; done; kill -TERM 0 2>/dev/null ) &`,
|
|
494
|
+
`( set -o pipefail; eval "$ELANOUS_PIPE_CMD" ) &`,
|
|
495
|
+
'wait $!',
|
|
496
|
+
'rc=$?',
|
|
497
|
+
`trap "" TERM HUP INT`,
|
|
498
|
+
'kill -TERM 0 2>/dev/null',
|
|
499
|
+
'exit $rc',
|
|
500
|
+
].join('\n');
|
|
501
|
+
const r = spawnSync('perl', ['-e', 'setpgrp(0,0) or die "setpgrp: $!"; exec @ARGV or die "exec: $!"', 'bash', '-c', script], {
|
|
502
|
+
encoding: 'utf8',
|
|
503
|
+
timeout: opts.timeoutMs,
|
|
504
|
+
killSignal: 'SIGTERM',
|
|
505
|
+
env: { ...process.env, ELANOUS_PIPE_CMD: pipeline, ELANOUS_PIPE_PARENT: String(opts.parentPid ?? process.pid), ELANOUS_PIPE_WATCH: String(opts.watchIntervalSec ?? 2) },
|
|
506
|
+
});
|
|
507
|
+
const timedOut = (r.error as NodeJS.ErrnoException | undefined)?.code === 'ETIMEDOUT';
|
|
508
|
+
return { status: r.status, stderr: (r.stderr ?? '') + (timedOut ? ` · timed out after ${opts.timeoutMs}ms (process group killed)` : r.error ? ` · ${String(r.error)}` : ''), timedOut };
|
|
509
|
+
}
|
|
510
|
+
|
|
476
511
|
function defaultImageShip(member: PodPoolMember, image: string): { status: number | null; stderr: string } {
|
|
477
|
-
const ship =
|
|
478
|
-
return { status: ship.status, stderr: ship.stderr
|
|
512
|
+
const ship = runProcessGroupPipeline(`docker save ${image} | gzip -1 | ssh -o BatchMode=yes -o ConnectTimeout=10 ${member.sshHost} 'export PATH=/opt/homebrew/bin:/usr/local/bin:$HOME/.orbstack/bin:$PATH; gunzip | docker load'`, { timeoutMs: IMAGE_SHIP_TIMEOUT_MS });
|
|
513
|
+
return { status: ship.status, stderr: ship.stderr };
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
/** This machine's image ID (`sha256:…` — `docker save | docker load` keeps it). Null when unreadable. */
|
|
517
|
+
function defaultLocalImageId(image: string): { status: number | null; stdout: string } {
|
|
518
|
+
const r = spawnSync('docker', ['image', 'inspect', image, '--format', '{{.Id}}'], { encoding: 'utf8', timeout: 30_000 });
|
|
519
|
+
return { status: r.status, stdout: r.stdout ?? '' };
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
function imageIdFrom(r: { status: number | null; stdout: string }): string | null {
|
|
523
|
+
const v = r.status === 0 ? r.stdout.trim() : '';
|
|
524
|
+
return /^sha256:[0-9a-f]{64}$/.test(v) ? v : null;
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
/** The remote node's image ID. Null when unreadable — never read as «same». */
|
|
528
|
+
export function remoteImageId(member: PodPoolMember, image: string, run: RemoteRun = defaultRemoteRun): string | null {
|
|
529
|
+
if (!member.sshHost) return null;
|
|
530
|
+
return imageIdFrom(run(member.sshHost, `docker image inspect ${image} --format '{{.Id}}'`));
|
|
479
531
|
}
|
|
480
532
|
|
|
481
533
|
export function defaultRemoteRun(host: string, script: string): { status: number | null; stdout: string; stderr: string } {
|
|
482
|
-
const r = spawnSync('ssh', ['-o', 'BatchMode=yes', '-o', 'ConnectTimeout=10', host, `export PATH=/opt/homebrew/bin:/usr/local/bin:$HOME/.orbstack/bin:$PATH; unset HTTPS_PROXY https_proxy HTTP_PROXY http_proxy ALL_PROXY all_proxy; ${script}`], { encoding: 'utf8', timeout:
|
|
534
|
+
const r = spawnSync('ssh', ['-o', 'BatchMode=yes', '-o', 'ConnectTimeout=10', host, `export PATH=/opt/homebrew/bin:/usr/local/bin:$HOME/.orbstack/bin:$PATH; unset HTTPS_PROXY https_proxy HTTP_PROXY http_proxy ALL_PROXY all_proxy; ${script}`], { encoding: 'utf8', timeout: REMOTE_RUN_TIMEOUT_MS });
|
|
483
535
|
return { status: r.status, stdout: r.stdout ?? '', stderr: (r.stderr ?? '') + (r.error ? String(r.error) : '') };
|
|
484
536
|
}
|
|
485
537
|
|
|
@@ -491,29 +543,100 @@ export function remoteImageCommit(member: PodPoolMember, image: string, run: Rem
|
|
|
491
543
|
return v && v !== '<no value>' ? v : null;
|
|
492
544
|
}
|
|
493
545
|
|
|
546
|
+
/** Cross-process lock options for one image ship per host (tests shrink them). */
|
|
547
|
+
export interface ImageShipLockOptions {
|
|
548
|
+
/** Directory for the per-host lock files. Default = the machine-wide pod lease dir (shared by every universe on this machine). */
|
|
549
|
+
dir?: string;
|
|
550
|
+
retryMs?: number;
|
|
551
|
+
/** A lock older than this is a crash leftover. Default = ship timeout ⊕ import timeout ⊕ 2 min. */
|
|
552
|
+
staleMs?: number;
|
|
553
|
+
maxTries?: number;
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
export function imageShipLockPath(host: string, dir: string = join(hostLeaseBaseDir(), 'image-ship')): string {
|
|
557
|
+
return join(dir, `${host.replace(/[^A-Za-z0-9._-]/g, '_').slice(0, 120) || 'host'}.lock`);
|
|
558
|
+
}
|
|
559
|
+
|
|
494
560
|
/**
|
|
495
561
|
* 원격 노드에 이 기계의 이미지를 보낸다(판이 다를 때만) — `docker save | ssh docker load` ⊕ `k3d image import`.
|
|
496
562
|
* ⛔ Pod 의 elanous 는 이미지 판이다(피드백: Pod 는 main 이 아니라 이미지를 돈다) — 노드마다 판이 다르면 같은 골이 노드마다 다른 코드로 돈다.
|
|
563
|
+
* 🩸 10-08 flood: many runs saw the same stale label at once and all shipped together — ssh refused them
|
|
564
|
+
* (`Session open refused by peer` · `kex_exchange_identification`). Now:
|
|
565
|
+
* ① «same image» = commit label OR (label missing) the same image ID on both sides — never ship it twice.
|
|
566
|
+
* ② one ship per host, under a cross-process lock; whoever gets the lock re-checks and skips if another process shipped.
|
|
567
|
+
* ③ the ship pipeline runs in its own process group (`runProcessGroupPipeline`).
|
|
497
568
|
*/
|
|
498
|
-
export function syncPoolImage(member: PodPoolMember, image: string, localCommit: string | null, run: RemoteRun | { run?: RemoteRun; inspect?: LocalImageInspect; transfer?: ImageShip } = defaultRemoteRun): { ok: boolean; action: 'local' | 'fresh' | 'built' | 'shipped' | 'failed'; detail: string } {
|
|
499
|
-
const opts = typeof run === 'function' ? { run } : { run: run.run ?? defaultRemoteRun, inspect: run.inspect, transfer: run.transfer };
|
|
569
|
+
export function syncPoolImage(member: PodPoolMember, image: string, localCommit: string | null, run: RemoteRun | { run?: RemoteRun; inspect?: LocalImageInspect; inspectId?: LocalImageInspect; transfer?: ImageShip; lock?: ImageShipLockOptions } = defaultRemoteRun): { ok: boolean; action: 'local' | 'fresh' | 'built' | 'shipped' | 'failed'; detail: string } {
|
|
570
|
+
const opts = typeof run === 'function' ? { run } : { run: run.run ?? defaultRemoteRun, inspect: run.inspect, inspectId: run.inspectId, transfer: run.transfer, lock: run.lock };
|
|
500
571
|
const remote = opts.run ?? defaultRemoteRun;
|
|
501
572
|
const inspect = opts.inspect ?? defaultLocalImageInspect;
|
|
573
|
+
// An injected label inspect without an ID inspect = a hermetic caller: the ID check is off (no real docker call).
|
|
574
|
+
const inspectId = opts.inspectId ?? (opts.inspect ? null : defaultLocalImageId);
|
|
502
575
|
const transfer = opts.transfer ?? defaultImageShip;
|
|
503
576
|
if (!member.sshHost) return { ok: true, action: 'local', detail: 'this machine' };
|
|
577
|
+
const host = member.sshHost;
|
|
504
578
|
const before = remoteImageCommit(member, image, remote);
|
|
505
579
|
if (localCommit && before === localCommit) return { ok: true, action: 'fresh', detail: before.slice(0, 12) };
|
|
506
580
|
// 통째 전송은 이 기계 이미지의 커밋 라벨이 목표와 같을 때만. 다르면 낡은 판을 보내지 않는다.
|
|
507
581
|
const here = localImageCommit(image, inspect);
|
|
508
582
|
if (localCommit && here !== localCommit) return { ok: false, action: 'failed', detail: '로컬 이미지 판이 다르다 · 보내지 않음' };
|
|
509
|
-
const
|
|
510
|
-
|
|
511
|
-
const
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
583
|
+
const localId = inspectId ? imageIdFrom(inspectId(image)) : null;
|
|
584
|
+
/** Fresh = the commit label matches, or the very same image (ID) is already there. Null = not fresh. */
|
|
585
|
+
const freshDetail = (label: string | null): string | null => {
|
|
586
|
+
if (localCommit && label === localCommit) return label.slice(0, 12);
|
|
587
|
+
if (localId && remoteImageId(member, image, remote) === localId) return `same image id ${localId.slice(7, 19)}`;
|
|
588
|
+
return null;
|
|
589
|
+
};
|
|
590
|
+
const sameId = freshDetail(before);
|
|
591
|
+
if (sameId) {
|
|
592
|
+
debug.log('pod.pool', 'image-ship-skipped-same-id', { host, image, localCommit, remoteCommit: before, detail: sameId });
|
|
593
|
+
return { ok: true, action: 'fresh', detail: sameId };
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
const lockOpts = opts.lock ?? {};
|
|
597
|
+
const retryMs = lockOpts.retryMs ?? 2_000;
|
|
598
|
+
const staleMs = lockOpts.staleMs ?? IMAGE_SHIP_TIMEOUT_MS + REMOTE_RUN_TIMEOUT_MS + 120_000;
|
|
599
|
+
const lockDir = lockOpts.dir ?? join(hostLeaseBaseDir(), 'image-ship');
|
|
600
|
+
const lockPath = imageShipLockPath(host, lockDir);
|
|
601
|
+
const t0 = Date.now();
|
|
602
|
+
let waited = false;
|
|
603
|
+
let release: () => void;
|
|
604
|
+
try {
|
|
605
|
+
mkdirSync(lockDir, { recursive: true });
|
|
606
|
+
release = acquireLockSync(lockPath, {
|
|
607
|
+
staleMs,
|
|
608
|
+
retryBusyMs: retryMs,
|
|
609
|
+
maxTries: lockOpts.maxTries ?? Math.ceil(staleMs / retryMs) + 10,
|
|
610
|
+
breakDeadHolder: true,
|
|
611
|
+
onWait: (holder) => { waited = true; debug.log('pod.pool', 'image-ship-lock-wait', { host, image, lockPath, holder }); },
|
|
612
|
+
});
|
|
613
|
+
} catch (error) {
|
|
614
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
615
|
+
debug.log('pod.pool', 'image-ship-lock-failed', { host, image, lockPath, reason }, { level: 'warn' });
|
|
616
|
+
return { ok: false, action: 'failed', detail: `image ship lock: ${reason}` };
|
|
617
|
+
}
|
|
618
|
+
try {
|
|
619
|
+
debug.log('pod.pool', 'image-ship-lock-acquired', { host, image, lockPath, waited, waitedMs: Date.now() - t0 });
|
|
620
|
+
// ⭐ Re-check after the lock: the holder we waited for may have shipped this very image.
|
|
621
|
+
const labelNow = waited ? remoteImageCommit(member, image, remote) : before;
|
|
622
|
+
const fresh = waited ? freshDetail(labelNow) : null;
|
|
623
|
+
if (fresh) {
|
|
624
|
+
debug.log('pod.pool', 'image-ship-skipped-fresh-after-wait', { host, image, localCommit, remoteCommit: labelNow, detail: fresh, waitedMs: Date.now() - t0 });
|
|
625
|
+
return { ok: true, action: 'fresh', detail: `${fresh} (shipped by another process while waiting)` };
|
|
626
|
+
}
|
|
627
|
+
const ship = transfer(member, image);
|
|
628
|
+
if (ship.status !== 0) return { ok: false, action: 'failed', detail: `docker load rc=${ship.status}: ${(ship.stderr ?? '').slice(-300)}` };
|
|
629
|
+
// The import phase gets its own stale window (the lock file's age restarts here).
|
|
630
|
+
try { const now = new Date(); utimesSync(lockPath, now, now); } catch { /* lock gone · finish anyway */ }
|
|
631
|
+
const imp = remote(host, `k3d image import ${image} -c ${member.k3dCluster}`);
|
|
632
|
+
if (imp.status !== 0) return { ok: false, action: 'failed', detail: `k3d import rc=${imp.status}: ${imp.stderr.slice(-300)}` };
|
|
633
|
+
const after = remoteImageCommit(member, image, remote);
|
|
634
|
+
return after && (!localCommit || after === localCommit)
|
|
635
|
+
? { ok: true, action: 'shipped', detail: `${labelNow?.slice(0, 12) ?? '없음'} → ${after.slice(0, 12)}` }
|
|
636
|
+
: { ok: false, action: 'failed', detail: `보낸 뒤 판이 ${after ?? '없음'} — 기대 ${localCommit ?? '?'}` };
|
|
637
|
+
} finally {
|
|
638
|
+
release();
|
|
639
|
+
}
|
|
517
640
|
}
|
|
518
641
|
|
|
519
642
|
export type PoolKubectl = (args: readonly string[], input?: string) => { status: number | null; stdout: string; stderr: string };
|
|
@@ -59,6 +59,8 @@ import { homedir } from 'node:os';
|
|
|
59
59
|
import { isAbsolute, join, normalize, relative, resolve, sep } from 'node:path';
|
|
60
60
|
import { findGitDir } from '../../git-fs/locate.js';
|
|
61
61
|
import { debug } from '../../debug/log.js';
|
|
62
|
+
import { enterJourneyNode, exitJourneyNode, passJourneyNode } from '../../self-dev/graph-journey-nodes.js';
|
|
63
|
+
import { routeJourneyEdge } from '../../self-dev/graph-journey-route.js';
|
|
62
64
|
import { selectLiveDetail } from '../../live/detail-switch.js';
|
|
63
65
|
import { authStorePath } from '../../oauth/store.js';
|
|
64
66
|
import { grokAuthFilePath, isGrokSubscriptionExpiring, refreshGrokSubscriptionToken, resolveGrokCredential } from '../../grok/credential.js';
|
|
@@ -1066,9 +1068,27 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
|
|
|
1066
1068
|
: { exitCode: null, output: '', error: { code: 'aborted', message: 'aborted before a pool slot opened' } };
|
|
1067
1069
|
const unfitDone = await unfitFailure();
|
|
1068
1070
|
if (unfitDone) return unfitDone;
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1071
|
+
// HARNESS-FULL-GRAPH — Pod 입장(입장 관문 ⊕ 멤버 슬롯)은 그래프 노드 pool-admit 이다.
|
|
1072
|
+
// 2판: decompose →(substrate=pod) pool-admit 간선은 «여기»(Pod 입장 직전 · 실제 단계 경계)에서 남긴다.
|
|
1073
|
+
routeJourneyEdge('decompose', 'pod', { substrate: 'pod' });
|
|
1074
|
+
enterJourneyNode('pool-admit', { provenance: 'self-implement-pod', data: { spaceId: input.spaceId } });
|
|
1075
|
+
// ⛔ 감싸는 함수(async 래퍼)를 쓰지 않는다 — 마이크로태스크가 늘면 Job apply 순서가 밀린다(시험이 잡았다). 같은 자리 try/catch 로.
|
|
1076
|
+
const podAdmitError = (error: unknown): void => {
|
|
1077
|
+
exitJourneyNode('pool-admit', { provenance: 'self-implement-pod', outcome: 'error', data: { spaceId: input.spaceId, error: error instanceof Error ? error.message : String(error) } });
|
|
1078
|
+
};
|
|
1079
|
+
let admitted: boolean;
|
|
1080
|
+
try { admitted = await acquireAdmission(); } catch (error) { podAdmitError(error); throw error; }
|
|
1081
|
+
if (!admitted) {
|
|
1082
|
+
exitJourneyNode('pool-admit', { provenance: 'self-implement-pod', outcome: 'refused', data: { spaceId: input.spaceId, reason: 'admission' } });
|
|
1083
|
+
return admissionFailure();
|
|
1084
|
+
}
|
|
1085
|
+
let member: PodPoolMember | null;
|
|
1086
|
+
try { member = await acquireSlot(); } catch (error) { podAdmitError(error); throw error; }
|
|
1087
|
+
if (options.pool && !member) {
|
|
1088
|
+
exitJourneyNode('pool-admit', { provenance: 'self-implement-pod', outcome: 'refused', data: { spaceId: input.spaceId, reason: 'no-slot' } });
|
|
1089
|
+
return { exitCode: null, output: '', error: { code: 'aborted', message: 'aborted before a pool slot opened' } };
|
|
1090
|
+
}
|
|
1091
|
+
exitJourneyNode('pool-admit', { provenance: 'self-implement-pod', outcome: 'admitted', data: { spaceId: input.spaceId, ...(member ? { context: member.context } : {}) } });
|
|
1072
1092
|
const currentContext = member ? null : baseKubectl(['config', 'current-context']);
|
|
1073
1093
|
let context = member?.context ?? (currentContext?.status === 0 ? currentContext.stdout.trim() : '');
|
|
1074
1094
|
const kubectl: Kubectl = (args, stdin) => baseKubectl(['--context', context, ...args], stdin);
|
|
@@ -1135,10 +1155,17 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
|
|
|
1135
1155
|
};
|
|
1136
1156
|
const candidates = grok ? [] : plannedCodexAccounts ?? [plannedAccount];
|
|
1137
1157
|
const usable: Array<{ name: string; credential: ReturnType<typeof accountCredentials> }> = [];
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1158
|
+
enterJourneyNode('credential-refresh', { provenance: 'self-implement-pod', data: { spaceId: input.spaceId, candidates: candidates.length } });
|
|
1159
|
+
try {
|
|
1160
|
+
for (const candidate of candidates) {
|
|
1161
|
+
const credential = await podCodexCredentialWithPrerefresh(candidate, prerefreshDeps);
|
|
1162
|
+
if (credential) usable.push({ name: candidate, credential });
|
|
1163
|
+
}
|
|
1164
|
+
} catch (error) {
|
|
1165
|
+
exitJourneyNode('credential-refresh', { provenance: 'self-implement-pod', outcome: 'error', data: { spaceId: input.spaceId, error: error instanceof Error ? error.message : String(error) } });
|
|
1166
|
+
throw error;
|
|
1141
1167
|
}
|
|
1168
|
+
exitJourneyNode('credential-refresh', { provenance: 'self-implement-pod', outcome: grok || usable.length > 0 ? 'usable' : 'exhausted', data: { spaceId: input.spaceId, usable: usable.length } });
|
|
1142
1169
|
if (!grok && usable.length === 0) throw new Error(`openai-codex 후보(${candidates.join(', ')}) access token 이 전부 3시간 안에 만료 — 선갱신도 못 했다(본부 임대 없음 또는 갱신 실패 · pod.credential-prerefresh 를 보라)`);
|
|
1143
1170
|
const account = grok ? plannedAccount : usable[0]!.name;
|
|
1144
1171
|
if (account !== plannedAccount) debug.log('self-implement.pod', 'account-skipped-expiring', { spaceId: input.spaceId, from: plannedAccount, to: account });
|
|
@@ -1725,6 +1752,12 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
|
|
|
1725
1752
|
// SCHED-CONTRACT: a failed child must say why outside the Pod, not «no error diagnostic».
|
|
1726
1753
|
const podReason = podExitCode === 1 ? extractPodFailureReason({ logs, logTailReason, containerReason, jobReason: failedReason || undefined, deadlineSeconds, result: disposition ? { stage: disposition.stage, prUrl: disposition.prUrl ?? null, prNumber: disposition.prNumber ?? null } : null }) : undefined;
|
|
1727
1754
|
if (podReason) debug.log('self-implement.pod', 'failure-reason', { job: name, reason: podReason });
|
|
1755
|
+
if (podExitCode === 1) {
|
|
1756
|
+
// HARNESS-FULL-GRAPH — 실패 쪽 두 노드: 결과 해석(result-parse) → 수확(salvage, Pod Job 이 push 한 줄로 판정).
|
|
1757
|
+
passJourneyNode('result-parse', { provenance: 'self-implement-pod', outcome: podReason ? 'parsed' : 'no-reason', data: { job: name } });
|
|
1758
|
+
const salvageOutcome = /^ELANOUS_POD_SALVAGE \S+ \S+$/m.test(logs) ? 'pushed' : logs.includes('ELANOUS_POD_SALVAGE_NONE') ? 'none' : 'unknown';
|
|
1759
|
+
passJourneyNode('salvage', { provenance: 'self-implement-pod', outcome: salvageOutcome, data: { job: name } });
|
|
1760
|
+
}
|
|
1728
1761
|
return {
|
|
1729
1762
|
exitCode: podExitCode,
|
|
1730
1763
|
output: tail,
|
|
@@ -55,6 +55,8 @@ export interface SelfImplementDisposition {
|
|
|
55
55
|
prNumber?: number;
|
|
56
56
|
/** Head SHA checked by the Pod before host regate. */
|
|
57
57
|
checkedHeadCommit?: string;
|
|
58
|
+
/** Review verdict only when the child bound a real pass to a clean, unchanged worktree head. */
|
|
59
|
+
selfReview?: { verdict: 'pass'; head: string };
|
|
58
60
|
hostRegate?: { passed: boolean; failures: Array<{ step: string; detail: string }>; os: string };
|
|
59
61
|
merged?: boolean;
|
|
60
62
|
detail?: string;
|
|
@@ -469,6 +471,11 @@ export function parseSelfImplementJson(stdout: string): SelfImplementDisposition
|
|
|
469
471
|
...(typeof o.prUrl === 'string' ? { prUrl: o.prUrl } : {}),
|
|
470
472
|
...(typeof o.prNumber === 'number' ? { prNumber: o.prNumber } : {}),
|
|
471
473
|
...(typeof o.checkedHeadCommit === 'string' ? { checkedHeadCommit: o.checkedHeadCommit } : {}),
|
|
474
|
+
...(o.reviewedHeadCommit && typeof o.reviewedHeadCommit === 'string' && /^[0-9a-f]{40}$/i.test(o.reviewedHeadCommit)
|
|
475
|
+
&& o.review && typeof o.review === 'object' && !Array.isArray(o.review)
|
|
476
|
+
&& (o.review as { reviewed?: unknown; verdict?: unknown }).reviewed === true
|
|
477
|
+
&& (o.review as { verdict?: unknown }).verdict === 'pass'
|
|
478
|
+
? { selfReview: { verdict: 'pass' as const, head: o.reviewedHeadCommit } } : {}),
|
|
472
479
|
...(typeof o.hostRegate === 'object' && o.hostRegate !== null ? { hostRegate: o.hostRegate as SelfImplementDisposition['hostRegate'] } : {}),
|
|
473
480
|
...(typeof o.merged === 'boolean' ? { merged: o.merged } : {}),
|
|
474
481
|
...(typeof o.detail === 'string' ? { detail: o.detail } : {}),
|
package/src/telegram-commands.ts
CHANGED
|
@@ -56,6 +56,8 @@ import { telegramDecisionOwner } from './decisions/telegram-decision-cards.js';
|
|
|
56
56
|
import { CardStore } from './task-cards/card-store.js';
|
|
57
57
|
import type { ContextNowDeps } from './context-bus/context-now.js';
|
|
58
58
|
import type { RunningRunsResult } from './self-implement/running-runs.js';
|
|
59
|
+
import { checklistBoardSlash } from './release-loop/checklist-board.js';
|
|
60
|
+
import type { Checklist } from './release-loop/checklist.js';
|
|
59
61
|
|
|
60
62
|
/** How many prior user/assistant turns to pass into `executeSkill` as
|
|
61
63
|
* the `## Recent conversation` block. Mirrors the dashboard default
|
|
@@ -348,6 +350,7 @@ export function defaultTelegramCommands(
|
|
|
348
350
|
coreCommands: readonly SlashCommand[] = SLASH_COMMANDS,
|
|
349
351
|
maturity: { tuiSlash: Readonly<Record<string, Maturity>>; telegramCommand: Readonly<Record<string, Maturity>>; discordCommand: Readonly<Record<string, Maturity>> } = FEATURE_MATURITY,
|
|
350
352
|
runningRunsQuery?: () => RunningRunsResult,
|
|
353
|
+
checklistRead?: (version: string) => Checklist,
|
|
351
354
|
): TgSlashCommand[] {
|
|
352
355
|
const handled: TgSlashCommand[] = [
|
|
353
356
|
{
|
|
@@ -376,6 +379,11 @@ export function defaultTelegramCommands(
|
|
|
376
379
|
description: 'Running harness runs — same output as TUI /harness runs',
|
|
377
380
|
handler: async (args) => telegramRunsSlash(args, runningRunsQuery),
|
|
378
381
|
},
|
|
382
|
+
{
|
|
383
|
+
name: 'board',
|
|
384
|
+
description: '판 체크리스트 — /board [판]',
|
|
385
|
+
handler: async (args) => checklistBoardSlash(args, checklistRead),
|
|
386
|
+
},
|
|
379
387
|
{
|
|
380
388
|
name: 'release',
|
|
381
389
|
description: '발행 현황 — 지금 런·노드·막힘을 한 통으로',
|
|
@@ -121,6 +121,12 @@ export function attachTelegramProjectButtons(
|
|
|
121
121
|
await bot.answerCallbackQuery(q.id, { text: '개인 대화에서만 고를 수 있습니다' });
|
|
122
122
|
return;
|
|
123
123
|
}
|
|
124
|
+
// Reject callbacks outside Telegram's 64-byte limit and empty project ids.
|
|
125
|
+
if (Buffer.byteLength(q.data, 'utf8') > 64 || q.data.length === 4) {
|
|
126
|
+
debug.log('telegram.project', 'button-rejected', { reason: 'invalid-callback-data', chatId: q.chatId });
|
|
127
|
+
await bot.answerCallbackQuery(q.id, { text: '프로젝트를 다시 골라 주세요' });
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
124
130
|
const chatId = q.chatId;
|
|
125
131
|
const session = deps.findSession(chatId, q.threadId, bot.botId);
|
|
126
132
|
if (!session) {
|
package/src/telegram.ts
CHANGED
|
@@ -81,7 +81,7 @@ import { maybeHandleCardPhoto, type CardPhotoDeps } from './telegram-card-follow
|
|
|
81
81
|
import { attachTelegramProjectButtons } from './telegram-project-command.js';
|
|
82
82
|
import { attachTelegramFabricPlan } from './telegram-fabric-plan.js';
|
|
83
83
|
import { contextNow as readContextNow, type ContextNowDeps } from './context-bus/context-now.js';
|
|
84
|
-
import {
|
|
84
|
+
import { renderContextFirstNow } from './context-bus/context-now-surfaces.js';
|
|
85
85
|
import { createContextFirstGate, renderContextFirst } from './context-bus/context-first.js';
|
|
86
86
|
|
|
87
87
|
export type TgAttachmentKind = 'photo' | 'voice' | 'audio' | 'document';
|
|
@@ -437,7 +437,7 @@ export class TelegramBot {
|
|
|
437
437
|
const cfg = this.slashContext?.userConfig.telegram;
|
|
438
438
|
if (cfg?.contextFirst === false) return;
|
|
439
439
|
try {
|
|
440
|
-
await this.sendMessage(ctx.chatId, renderContextFirst(this.readContextNow, this.contextNowDeps,
|
|
440
|
+
await this.sendMessage(ctx.chatId, renderContextFirst(this.readContextNow, this.contextNowDeps, renderContextFirstNow), { replyTo: ctx.messageId, threadId: ctx.threadId });
|
|
441
441
|
} catch (err) {
|
|
442
442
|
this.log(`telegram context-first failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
443
443
|
}
|
|
@@ -16,6 +16,12 @@
|
|
|
16
16
|
// D1). It holds a lazy singleton per elanous process: first call
|
|
17
17
|
// spawns Chrome, subsequent calls reuse; the client lives until the
|
|
18
18
|
// process exits or `closeBrowserRuntime()` is invoked.
|
|
19
|
+
// BrowserRead remains read-only. An explicitly requested BrowserNavigate
|
|
20
|
+
// input asks a person through the HITL confirmation channels only and fails
|
|
21
|
+
// closed without approval. The shell-command approver is never consulted: an
|
|
22
|
+
// unattended surface may auto-allow shell patterns, which must not type into a page.
|
|
23
|
+
// Click still runs through performBrowserAction in src/harness/browser-act.ts,
|
|
24
|
+
// where armed, reversibility and host-boundary checks precede CdpClient.click.
|
|
19
25
|
|
|
20
26
|
import type { CdpClient } from '../browser-cdp/client.js';
|
|
21
27
|
import { createCdpClient, CdpUnavailable } from '../browser-cdp/client.js';
|
|
@@ -24,6 +30,9 @@ import { defaultControlSignalBus } from '../input/control-signal.js';
|
|
|
24
30
|
import type { LLMToolSpec } from '../llm.js';
|
|
25
31
|
import { debug } from '../debug/log.js';
|
|
26
32
|
import { getHarnessRunId } from '../harness/harness-space.js';
|
|
33
|
+
import { decideTypeAction } from '../harness/browser-act-type.js';
|
|
34
|
+
import { requestConfirmation } from '../hitl/confirm.js';
|
|
35
|
+
import type { TypeTarget } from '../harness/bot-type-request.js';
|
|
27
36
|
import type { ToolRuntime, ToolRuntimeContext } from './types.js';
|
|
28
37
|
|
|
29
38
|
// ─── Dependency injection ───────────────────────────────────────
|
|
@@ -116,6 +125,8 @@ export async function closeBrowserRuntime(): Promise<void> {
|
|
|
116
125
|
export interface BrowserNavigateArgs {
|
|
117
126
|
/** Absolute URL. Scheme required. */
|
|
118
127
|
url: string;
|
|
128
|
+
/** Input requires a selected target and explicit approval; no form submission. */
|
|
129
|
+
input?: { selector: string; text: string };
|
|
119
130
|
/** When true, block until Page.loadEventFired (or timeoutMs).
|
|
120
131
|
* Default true. */
|
|
121
132
|
waitForLoad?: boolean;
|
|
@@ -147,6 +158,16 @@ export function buildBrowserNavigateTool(): LLMToolSpec {
|
|
|
147
158
|
type: 'string',
|
|
148
159
|
description: 'Absolute URL including scheme (https://...). about:blank also accepted.',
|
|
149
160
|
},
|
|
161
|
+
input: {
|
|
162
|
+
type: 'object',
|
|
163
|
+
description: 'Type into a selected page field only after explicit approval. Never submits a form.',
|
|
164
|
+
properties: {
|
|
165
|
+
selector: { type: 'string', description: 'CSS selector of the input field.' },
|
|
166
|
+
text: { type: 'string', description: 'Text without newlines (at most 200 characters).' },
|
|
167
|
+
},
|
|
168
|
+
required: ['selector', 'text'],
|
|
169
|
+
additionalProperties: false,
|
|
170
|
+
},
|
|
150
171
|
waitForLoad: {
|
|
151
172
|
type: 'boolean',
|
|
152
173
|
description: 'Wait for Page.loadEventFired before returning. Default true.',
|
|
@@ -165,9 +186,33 @@ export function buildBrowserNavigateTool(): LLMToolSpec {
|
|
|
165
186
|
const DEFAULT_NAV_TIMEOUT_MS = 10_000;
|
|
166
187
|
const MAX_NAV_TIMEOUT_MS = 60_000;
|
|
167
188
|
|
|
168
|
-
export async function dispatchBrowserNavigate(args: BrowserNavigateArgs): Promise<BrowserNavigateResult> {
|
|
189
|
+
export async function dispatchBrowserNavigate(args: BrowserNavigateArgs, ctx?: ToolRuntimeContext): Promise<BrowserNavigateResult> {
|
|
169
190
|
ensureBrowserControlSignalConsumer();
|
|
170
191
|
const url = String(args.url ?? '').trim();
|
|
192
|
+
const input = args.input;
|
|
193
|
+
// Input is never an implicit side effect of navigation. Approval is scoped
|
|
194
|
+
// to this URL, selector and text, before connecting or dispatching CDP.
|
|
195
|
+
if (input !== undefined) {
|
|
196
|
+
if (typeof input.selector !== 'string' || !input.selector.trim()
|
|
197
|
+
|| typeof input.text !== 'string' || !input.text.trim()
|
|
198
|
+
|| /[\r\n]/.test(input.text) || input.text.length > 200) {
|
|
199
|
+
return { output: '# BrowserNavigate: invalid input', finalUrl: '', title: '', loadMs: 0 };
|
|
200
|
+
}
|
|
201
|
+
let httpUrl = false;
|
|
202
|
+
try { httpUrl = ['http:', 'https:'].includes(new URL(url).protocol); } catch { /* invalid URL */ }
|
|
203
|
+
if (!httpUrl) return { output: '# BrowserNavigate: invalid input URL', finalUrl: '', title: '', loadMs: 0 };
|
|
204
|
+
let approved = false;
|
|
205
|
+
try {
|
|
206
|
+
// Human confirmation only — `ctx.approver` is the shell-command approver (TC review of #24990).
|
|
207
|
+
approved = (await requestConfirmation({
|
|
208
|
+
prompt: `Browser input on ${url}`,
|
|
209
|
+
detail: `Selector: ${input.selector}\nText: ${input.text}`,
|
|
210
|
+
...(ctx?.confirmChannels ? { channels: ctx.confirmChannels } : {}),
|
|
211
|
+
onTimeout: () => false,
|
|
212
|
+
})).answer === true;
|
|
213
|
+
} catch { /* approval failure is denial */ }
|
|
214
|
+
if (!approved) return { output: '# BrowserNavigate: input not approved', finalUrl: '', title: '', loadMs: 0 };
|
|
215
|
+
}
|
|
171
216
|
if (!url) {
|
|
172
217
|
observeBrowserAction({ action: 'navigate', url: '', target: 'url', ok: false, error: 'url is required' });
|
|
173
218
|
return { output: '# BrowserNavigate: error\nurl is required', finalUrl: '', title: '', loadMs: 0 };
|
|
@@ -233,6 +278,45 @@ export async function dispatchBrowserNavigate(args: BrowserNavigateArgs): Promis
|
|
|
233
278
|
timedOut = r.timedOut;
|
|
234
279
|
}
|
|
235
280
|
|
|
281
|
+
if (input !== undefined) {
|
|
282
|
+
try {
|
|
283
|
+
if (!client.input) throw new Error('CDP input unavailable');
|
|
284
|
+
const currentUrl = await client.evaluate('document.location.href');
|
|
285
|
+
if (currentUrl !== new URL(url).href) throw new Error('CDP input refused: page URL changed after approval');
|
|
286
|
+
const inspected = await client.evaluate(`(() => {
|
|
287
|
+
const element = document.querySelector(${JSON.stringify(input.selector)});
|
|
288
|
+
if (!(element instanceof HTMLElement)) return null;
|
|
289
|
+
return { tag: element.tagName.toLowerCase(),
|
|
290
|
+
type: element instanceof HTMLInputElement ? element.type : null,
|
|
291
|
+
name: element.getAttribute('name'), id: element.getAttribute('id'),
|
|
292
|
+
contentEditable: element.isContentEditable, inForm: !!element.closest('form') };
|
|
293
|
+
})()`);
|
|
294
|
+
const candidate = inspected && typeof inspected === 'object' ? inspected as Record<string, unknown> : null;
|
|
295
|
+
const target: TypeTarget | null = candidate
|
|
296
|
+
&& typeof candidate.tag === 'string'
|
|
297
|
+
&& (candidate.type === null || typeof candidate.type === 'string')
|
|
298
|
+
&& (candidate.name === null || typeof candidate.name === 'string')
|
|
299
|
+
&& (candidate.id === null || typeof candidate.id === 'string')
|
|
300
|
+
&& typeof candidate.contentEditable === 'boolean' && typeof candidate.inForm === 'boolean'
|
|
301
|
+
? candidate as TypeTarget : null;
|
|
302
|
+
const verdict = decideTypeAction({ url, selector: input.selector, text: input.text,
|
|
303
|
+
// The caller's approval is for this exact URL; it cannot authorize a different host.
|
|
304
|
+
actionHosts: [new URL(url).hostname], armed: true, target });
|
|
305
|
+
if (!verdict.allowed) throw new Error(verdict.reason);
|
|
306
|
+
const focused = await client.evaluate(`(() => {
|
|
307
|
+
const element = document.querySelector(${JSON.stringify(input.selector)});
|
|
308
|
+
if (!(element instanceof HTMLElement)) return false;
|
|
309
|
+
element.focus();
|
|
310
|
+
return document.activeElement === element;
|
|
311
|
+
})()`);
|
|
312
|
+
if (focused !== true) throw new Error('CDP input target could not be focused');
|
|
313
|
+
await client.input(input.text);
|
|
314
|
+
} catch (err) {
|
|
315
|
+
return { output: `# BrowserNavigate: input error\n${err instanceof Error ? err.message : String(err)}`,
|
|
316
|
+
finalUrl: url, title: '', loadMs: Date.now() - started };
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
236
320
|
const loadMs = Date.now() - started;
|
|
237
321
|
|
|
238
322
|
// Best-effort metadata. Errors here don't abort the tool.
|
|
@@ -255,8 +339,8 @@ export async function dispatchBrowserNavigate(args: BrowserNavigateArgs): Promis
|
|
|
255
339
|
export const browserNavigateRuntime: ToolRuntime<BrowserNavigateArgs, BrowserNavigateResult> = {
|
|
256
340
|
id: 'browser_navigate',
|
|
257
341
|
spec: buildBrowserNavigateTool(),
|
|
258
|
-
async run(req,
|
|
259
|
-
return dispatchBrowserNavigate(req);
|
|
342
|
+
async run(req, ctx: ToolRuntimeContext) {
|
|
343
|
+
return dispatchBrowserNavigate(req, ctx);
|
|
260
344
|
},
|
|
261
345
|
};
|
|
262
346
|
|
|
@@ -177,6 +177,7 @@ export { setWorktreeRuntimeDeps } from './git-worktree-runtimes.js';
|
|
|
177
177
|
export {
|
|
178
178
|
setSelfImplementApprover,
|
|
179
179
|
setSelfImplementRuntimeDeps,
|
|
180
|
+
setSelfImplementSoftStopSpaceObserver,
|
|
180
181
|
} from '../self-implement/self-implement-runtime.js';
|
|
181
182
|
export {
|
|
182
183
|
setTerminalSessionsGetter,
|