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,13 +1,17 @@
|
|
|
1
1
|
import { existsSync, mkdtempSync, mkdirSync, readFileSync, readdirSync, renameSync, rmSync, statSync, writeFileSync } from 'node:fs';
|
|
2
2
|
import { tmpdir } from 'node:os';
|
|
3
3
|
import { join } from 'node:path';
|
|
4
|
+
import { createHash, randomUUID, timingSafeEqual } from 'node:crypto';
|
|
4
5
|
import { parse as parseYaml } from 'yaml';
|
|
5
6
|
import { elanousStateRoot } from '../../autopilot/state-paths.js';
|
|
6
7
|
import { debug } from '../../debug/log.js';
|
|
7
8
|
import { defaultGraphsDir, loadGraphTemplatesFrom } from '../../self-implement/graph-templates.js';
|
|
8
9
|
import { loadGraphTemplates } from '../../self-implement/graph-yaml.js';
|
|
9
10
|
import { validateGraphYaml } from './graph-kinds.js';
|
|
11
|
+
import { parseWizardSteps, readWizardSteps, withWizardSteps } from '../../graph-wizard/saved-steps.js';
|
|
12
|
+
import type { WizardNodeStep } from '../../graph-wizard/steps.js';
|
|
10
13
|
import { jsonResponse } from './json-response.js';
|
|
14
|
+
import { clearPeerEdit, peerEditRefusalBody, peerEditRunGate, readPeerEdit, writePeerEdit } from './graph-peer-edit.js';
|
|
11
15
|
|
|
12
16
|
/** Declared graph ids only. A user string is never joined onto a filesystem path. */
|
|
13
17
|
const GRAPH_ID = /^[a-z0-9-]+$/;
|
|
@@ -42,6 +46,93 @@ function mineGraphsDir(): string {
|
|
|
42
46
|
return join(elanousStateRoot(), 'graphs');
|
|
43
47
|
}
|
|
44
48
|
|
|
49
|
+
export type GraphPermission = 'view' | 'edit';
|
|
50
|
+
type GraphGrant = { recipient: string; permission: GraphPermission };
|
|
51
|
+
const TOKEN_PATTERN = /^eg_[A-Za-z0-9_-]{32,}$/;
|
|
52
|
+
|
|
53
|
+
function accessFile(dir: string, id: string): string {
|
|
54
|
+
return join(dir, '.access', `${id}.json`);
|
|
55
|
+
}
|
|
56
|
+
function tokenHash(token: string): string {
|
|
57
|
+
return createHash('sha256').update(token).digest('hex');
|
|
58
|
+
}
|
|
59
|
+
function accessGrants(dir: string, id: string): GraphGrant[] {
|
|
60
|
+
let text: string;
|
|
61
|
+
try { text = readFileSync(accessFile(dir, id), 'utf8'); }
|
|
62
|
+
catch (error) {
|
|
63
|
+
if ((error as NodeJS.ErrnoException).code === 'ENOENT') return [];
|
|
64
|
+
throw error;
|
|
65
|
+
}
|
|
66
|
+
const parsed: unknown = JSON.parse(text);
|
|
67
|
+
if (!Array.isArray(parsed) || !parsed.every((entry): entry is GraphGrant => entry !== null && typeof entry === 'object'
|
|
68
|
+
&& typeof entry.recipient === 'string' && /^[a-f0-9]{64}$/.test(entry.recipient)
|
|
69
|
+
&& (entry.permission === 'view' || entry.permission === 'edit'))) throw new Error('invalid graph access store');
|
|
70
|
+
return parsed;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** Only a hash is persisted; no owner bearer or raw recipient credential goes into a graph/YAML. */
|
|
74
|
+
export function handleGraphAccess(pathname: string, req: Request, deps: GraphsApiDeps = {}): Response {
|
|
75
|
+
const match = /^\/v1\/graphs\/([^/]+)\/access$/.exec(pathname);
|
|
76
|
+
const id = match ? resolveId(match[1]!) : null;
|
|
77
|
+
if (!id) return jsonResponse({ error: 'bad_request' }, 400);
|
|
78
|
+
const owned = mineOwnership(id, deps);
|
|
79
|
+
if (owned instanceof Response) return owned;
|
|
80
|
+
if (req.method !== 'GET') return jsonResponse({ error: 'method-not-allowed' }, 405);
|
|
81
|
+
try { return jsonResponse({ grants: accessGrants(owned.mineDir, id) }); }
|
|
82
|
+
catch (error) { return saveFailed(id, error); }
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export async function handleGraphAccessPut(pathname: string, req: Request, deps: GraphsApiDeps = {}): Promise<Response> {
|
|
86
|
+
const match = /^\/v1\/graphs\/([^/]+)\/access$/.exec(pathname);
|
|
87
|
+
const id = match ? resolveId(match[1]!) : null;
|
|
88
|
+
if (!id) return jsonResponse({ error: 'bad_request' }, 400);
|
|
89
|
+
const owned = mineOwnership(id, deps);
|
|
90
|
+
if (owned instanceof Response) return owned;
|
|
91
|
+
// Detail GET resolves a core id first; do not grant a token for a shadowed mine id.
|
|
92
|
+
if (indexByDeclaredId(deps.coreDir ?? defaultGraphsDir()).has(id)) return jsonResponse({ error: 'core-id-conflict', id }, 409);
|
|
93
|
+
let body: { recipient?: unknown; permission?: unknown };
|
|
94
|
+
try { body = await req.json() as typeof body; }
|
|
95
|
+
catch { return jsonResponse({ error: 'bad_request' }, 400); }
|
|
96
|
+
if (!body || typeof body.recipient !== 'string' || !TOKEN_PATTERN.test(body.recipient)
|
|
97
|
+
|| (body.permission !== 'view' && body.permission !== 'edit')) {
|
|
98
|
+
return jsonResponse({ error: 'bad_request', reason: 'recipient access token and view/edit permission required' }, 400);
|
|
99
|
+
}
|
|
100
|
+
const recipient = tokenHash(body.recipient);
|
|
101
|
+
const dir = join(owned.mineDir, '.access');
|
|
102
|
+
try {
|
|
103
|
+
const grants = accessGrants(owned.mineDir, id).filter((grant) => grant.recipient !== recipient);
|
|
104
|
+
grants.push({ recipient, permission: body.permission });
|
|
105
|
+
mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
106
|
+
const target = accessFile(owned.mineDir, id);
|
|
107
|
+
const temp = `${target}.${process.pid}.${randomUUID()}.tmp`;
|
|
108
|
+
try {
|
|
109
|
+
writeFileSync(temp, JSON.stringify(grants), { mode: 0o600 });
|
|
110
|
+
renameSync(temp, target);
|
|
111
|
+
} finally { rmSync(temp, { force: true }); }
|
|
112
|
+
} catch (error) { return saveFailed(id, error); }
|
|
113
|
+
return jsonResponse({ recipient, permission: body.permission });
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** The peer token works only on this graph and the requested read/edit operation. */
|
|
117
|
+
export function graphAccessForRequest(pathname: string, method: string, token: string | null, deps: GraphsApiDeps = {}): boolean {
|
|
118
|
+
const match = /^\/v1\/graphs\/([^/]+)(?:\/(yaml))?$/.exec(pathname);
|
|
119
|
+
const id = match ? resolveId(match[1]!) : null;
|
|
120
|
+
if (!id || !token || !TOKEN_PATTERN.test(token)) return false;
|
|
121
|
+
if (method !== 'GET' && !(method === 'PUT' && match?.[2] === 'yaml')) return false;
|
|
122
|
+
try {
|
|
123
|
+
if (!indexByDeclaredId(deps.mineDir ?? mineGraphsDir()).has(id)) return false;
|
|
124
|
+
if (indexByDeclaredId(deps.coreDir ?? defaultGraphsDir()).has(id)) return false;
|
|
125
|
+
const wanted = tokenHash(token);
|
|
126
|
+
return accessGrants(deps.mineDir ?? mineGraphsDir(), id).some((grant) => {
|
|
127
|
+
const stored = Buffer.from(grant.recipient, 'hex');
|
|
128
|
+
return timingSafeEqual(stored, Buffer.from(wanted, 'hex')) && (method === 'GET' || grant.permission === 'edit');
|
|
129
|
+
});
|
|
130
|
+
} catch (error) {
|
|
131
|
+
debug.log('nexus.graphs', 'access-read-failed', { id, reason: String(error) }, { level: 'warn' });
|
|
132
|
+
return false;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
45
136
|
function resolveId(raw: string): string | null {
|
|
46
137
|
let id: string;
|
|
47
138
|
try { id = decodeURIComponent(raw); }
|
|
@@ -109,7 +200,12 @@ function detailOf(id: string, source: 'core' | 'mine', editable: boolean, dir: s
|
|
|
109
200
|
editable,
|
|
110
201
|
entry_node: template.entryNode,
|
|
111
202
|
terminal_nodes: template.terminalNodes,
|
|
112
|
-
|
|
203
|
+
// GRAPH-GROUPS — a node's `group:` tag (when the loader carries it) rides along so the PWA can box/fold units.
|
|
204
|
+
nodes: template.nodes.map((node) => {
|
|
205
|
+
const { nodeId, kind, recipe, maxVisits } = node;
|
|
206
|
+
const group = (node as { group?: unknown }).group;
|
|
207
|
+
return { node_id: nodeId, kind, recipe, max_visits: maxVisits, ...(typeof group === 'string' && group.trim() ? { group: group.trim() } : {}) };
|
|
208
|
+
}),
|
|
113
209
|
edges: spec.edges.map(({ from, to, on, map }) => ({
|
|
114
210
|
from,
|
|
115
211
|
...(to === undefined ? {} : { to }),
|
|
@@ -178,7 +274,8 @@ export function handleGraphsGet(pathname: string, dir = defaultGraphsDir(), deps
|
|
|
178
274
|
const coreHit = core.ok ? indexByDeclaredId(coreDir).get(id) : undefined;
|
|
179
275
|
const hit = mineHit ?? coreHit;
|
|
180
276
|
if (!hit) return jsonResponse({ error: 'not-found', id }, 404);
|
|
181
|
-
|
|
277
|
+
const steps = mineHit ? readWizardSteps(mineDir, id, hit.text) : null;
|
|
278
|
+
return jsonResponse({ id, source: mineHit ? 'mine' : 'core', editable: Boolean(mineHit), yaml: hit.text, ...(steps ? { steps } : {}) });
|
|
182
279
|
}
|
|
183
280
|
|
|
184
281
|
const match = /^\/v1\/graphs\/([^/]+)$/.exec(pathname);
|
|
@@ -195,7 +292,10 @@ export function handleGraphsGet(pathname: string, dir = defaultGraphsDir(), deps
|
|
|
195
292
|
}
|
|
196
293
|
|
|
197
294
|
/** PUT writes only under the state-root graphs directory. Core YAML is never opened for write. */
|
|
198
|
-
|
|
295
|
+
/** Who authorized a save: the owner, or a peer token holding an «edit» grant (raw token; only a hash prefix is kept). */
|
|
296
|
+
export type GraphSaveActor = { peerToken: string } | null;
|
|
297
|
+
|
|
298
|
+
export async function handleGraphsPut(pathname: string, req: Request, deps: GraphsApiDeps = {}, actor: GraphSaveActor = null): Promise<Response> {
|
|
199
299
|
const match = /^\/v1\/graphs\/([^/]+)\/yaml$/.exec(pathname);
|
|
200
300
|
if (!match) return jsonResponse({ error: 'not-found' }, 404);
|
|
201
301
|
const id = resolveId(match[1]!);
|
|
@@ -209,18 +309,33 @@ export async function handleGraphsPut(pathname: string, req: Request, deps: Grap
|
|
|
209
309
|
const mineOwns = indexByDeclaredId(mineDir).has(id);
|
|
210
310
|
if (coreOwns && !mineOwns) return jsonResponse({ error: 'core-read-only', id }, 403);
|
|
211
311
|
if (!mineOwns) return jsonResponse({ error: 'not-found', id }, 404);
|
|
212
|
-
let body: { yaml?: unknown };
|
|
213
|
-
try { body = await req.json() as { yaml?: unknown }; }
|
|
312
|
+
let body: { yaml?: unknown; steps?: unknown };
|
|
313
|
+
try { body = await req.json() as { yaml?: unknown; steps?: unknown }; }
|
|
214
314
|
catch { return jsonResponse({ error: 'bad_request', reason: 'json body required' }, 400); }
|
|
215
315
|
if (typeof body?.yaml !== 'string') return jsonResponse({ error: 'bad_request', reason: 'yaml string required' }, 400);
|
|
216
316
|
const rejected = rejectGraphYaml(body.yaml, id);
|
|
217
317
|
if (rejected) return rejected;
|
|
318
|
+
const steps = stepsOf(body.steps, body.yaml);
|
|
319
|
+
if (steps instanceof Response) return steps;
|
|
218
320
|
const existing = indexByDeclaredId(mineDir).get(id)!;
|
|
219
321
|
// Writes go only to <mine>/<id>.yaml; a hand-placed file under another name is not edited over HTTP.
|
|
220
322
|
if (existing.file !== `${id}.yaml`) return jsonResponse({ error: 'file-name-mismatch', id, file: existing.file }, 409);
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
323
|
+
let written: { version: string; previous: string };
|
|
324
|
+
const editedBy = actor ? `peer:${tokenHash(actor.peerToken).slice(0, 8)}` : null;
|
|
325
|
+
try {
|
|
326
|
+
written = withWizardSteps(mineDir, id, steps, () => {
|
|
327
|
+
// Peer save: the marker goes down «before» the bytes, so a crash in between still refuses the run.
|
|
328
|
+
if (editedBy) writePeerEdit(mineDir, id, { editedBy, version: 'pending', at: new Date().toISOString() });
|
|
329
|
+
const result = writeWithVersion(mineDir, id, existing, body.yaml as string);
|
|
330
|
+
if (editedBy) writePeerEdit(mineDir, id, { editedBy, version: result.version, at: new Date().toISOString() });
|
|
331
|
+
else clearPeerEdit(mineDir, id);
|
|
332
|
+
return result;
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
catch (error) { return saveFailed(id, error); }
|
|
336
|
+
const { version, previous } = written;
|
|
337
|
+
debug.log('nexus.graphs', 'saved', { id, version, previous, steps: steps ? Object.keys(steps).length : 0, ...(editedBy ? { editedBy } : {}) });
|
|
338
|
+
return jsonResponse({ id, source: 'mine', editable: true, saved: true, version, previous, ...(editedBy ? { editedBy } : {}) });
|
|
224
339
|
}
|
|
225
340
|
|
|
226
341
|
const VERSION_ID = /^\d{4}-\d{2}-\d{2}T\d{2}-\d{2}-\d{2}-\d{3}Z(?:-\d{4})?$/;
|
|
@@ -331,10 +446,27 @@ export async function handleGraphsRevert(pathname: string, req: Request, deps: G
|
|
|
331
446
|
return jsonResponse({ id, source: 'mine', editable: true, reverted: true, version, previous, restoredFrom: target });
|
|
332
447
|
}
|
|
333
448
|
|
|
449
|
+
/** GRAPH-WIZARD-SAVE-RECIPES — optional `steps` on save (the wizard's node → library step map). Absent = keep
|
|
450
|
+
* whatever sidecar the graph has; present = validated against the step library and stored as a sidecar. The
|
|
451
|
+
* run builds commands from it server-side (src/graph-wizard/saved-steps.ts) — no client command text is kept. */
|
|
452
|
+
function stepsOf(raw: unknown, yaml: string): Record<string, WizardNodeStep> | null | Response {
|
|
453
|
+
// Only an absent field means «keep what is there»; an explicit null or any other non-object is a bad request.
|
|
454
|
+
if (raw === undefined) return null;
|
|
455
|
+
const parsed = parseWizardSteps(raw, yaml);
|
|
456
|
+
if (!parsed.ok) return jsonResponse({ error: 'bad_request', reason: parsed.reason }, 400);
|
|
457
|
+
return parsed.steps;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
function saveFailed(id: string, error: unknown): Response {
|
|
461
|
+
const reason = error instanceof Error ? error.message.slice(0, 200) : String(error);
|
|
462
|
+
debug.log('nexus.graphs', 'save-failed', { id, reason }, { level: 'warn' });
|
|
463
|
+
return jsonResponse({ error: 'save-failed', id, reason }, 500);
|
|
464
|
+
}
|
|
465
|
+
|
|
334
466
|
/** POST /v1/graphs {id, yaml} — a brand-new «mine» graph (blank canvas → save). Records version v1. */
|
|
335
467
|
export async function handleGraphsCreate(req: Request, deps: GraphsApiDeps = {}): Promise<Response> {
|
|
336
|
-
let body: { id?: unknown; yaml?: unknown };
|
|
337
|
-
try { body = await req.json() as { id?: unknown; yaml?: unknown }; }
|
|
468
|
+
let body: { id?: unknown; yaml?: unknown; steps?: unknown };
|
|
469
|
+
try { body = await req.json() as { id?: unknown; yaml?: unknown; steps?: unknown }; }
|
|
338
470
|
catch { return jsonResponse({ error: 'bad_request', reason: 'json body required' }, 400); }
|
|
339
471
|
const id = typeof body?.id === 'string' ? resolveId(body.id) : null;
|
|
340
472
|
if (!id) return jsonResponse({ error: 'bad_request', reason: 'id must match [a-z0-9-]' }, 400);
|
|
@@ -347,10 +479,18 @@ export async function handleGraphsCreate(req: Request, deps: GraphsApiDeps = {})
|
|
|
347
479
|
if (indexByDeclaredId(mineDir).has(id) || existsSync(join(mineDir, `${id}.yaml`))) return jsonResponse({ error: 'exists', id }, 409);
|
|
348
480
|
const rejected = rejectGraphYaml(body.yaml, id);
|
|
349
481
|
if (rejected) return rejected;
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
482
|
+
const steps = stepsOf(body.steps, body.yaml);
|
|
483
|
+
if (steps instanceof Response) return steps;
|
|
484
|
+
let version: string;
|
|
485
|
+
try {
|
|
486
|
+
version = withWizardSteps(mineDir, id, steps, () => {
|
|
487
|
+
mkdirSync(mineDir, { recursive: true });
|
|
488
|
+
writeFileSync(join(mineDir, `${id}.yaml`), body.yaml as string, { flag: 'wx' });
|
|
489
|
+
try { return recordVersion(mineDir, id, body.yaml as string); }
|
|
490
|
+
catch (error) { rmSync(join(mineDir, `${id}.yaml`), { force: true }); throw error; }
|
|
491
|
+
});
|
|
492
|
+
} catch (error) { return saveFailed(id, error); }
|
|
493
|
+
debug.log('nexus.graphs', 'saved', { id, version, created: true, steps: steps ? Object.keys(steps).length : 0 });
|
|
354
494
|
return jsonResponse({ id, source: 'mine', editable: true, saved: true, version, previous: null }, 201);
|
|
355
495
|
}
|
|
356
496
|
|
|
@@ -370,8 +510,14 @@ export async function handleGraphsClone(pathname: string, req: Request, deps: Gr
|
|
|
370
510
|
if (reservedId) return reservedId;
|
|
371
511
|
const coreDir = deps.coreDir ?? defaultGraphsDir();
|
|
372
512
|
const mineDir = deps.mineDir ?? mineGraphsDir();
|
|
373
|
-
const
|
|
513
|
+
const fromCore = indexByDeclaredId(coreDir).get(id);
|
|
514
|
+
const source = fromCore ?? indexByDeclaredId(mineDir).get(id);
|
|
374
515
|
if (!source) return jsonResponse({ error: 'not-found', id }, 404);
|
|
516
|
+
// A clone would carry a peer's unapproved change into a graph with no marker — approve first.
|
|
517
|
+
if (!fromCore) {
|
|
518
|
+
const gate = peerEditRunGate(mineDir, id);
|
|
519
|
+
if (!gate.ok) return jsonResponse(peerEditRefusalBody(id, gate), 409);
|
|
520
|
+
}
|
|
375
521
|
if (indexByDeclaredId(mineDir).has(body.newId) || indexByDeclaredId(coreDir).has(body.newId)) {
|
|
376
522
|
return jsonResponse({ error: 'conflict', id: body.newId }, 409);
|
|
377
523
|
}
|
|
@@ -384,9 +530,39 @@ export async function handleGraphsClone(pathname: string, req: Request, deps: Gr
|
|
|
384
530
|
return jsonResponse({ id: body.newId, source: 'mine', editable: true, clonedFrom: id }, 201);
|
|
385
531
|
}
|
|
386
532
|
|
|
387
|
-
|
|
533
|
+
/** POST /v1/graphs/<id>/approve {version} — owner only (the route never accepts a peer token). Approves the
|
|
534
|
+
* exact peer version the run refusal named; a newer peer save is a new version and needs a new approval. */
|
|
535
|
+
export async function handleGraphsApprove(pathname: string, req: Request, deps: GraphsApiDeps = {}): Promise<Response> {
|
|
536
|
+
const match = /^\/v1\/graphs\/([^/]+)\/approve$/.exec(pathname);
|
|
537
|
+
if (!match) return jsonResponse({ error: 'not-found' }, 404);
|
|
538
|
+
const id = resolveId(match[1]!);
|
|
539
|
+
if (!id) return jsonResponse({ error: 'bad_request', reason: 'invalid graph id' }, 400);
|
|
540
|
+
const owned = mineOwnership(id, deps);
|
|
541
|
+
if (owned instanceof Response) return owned;
|
|
542
|
+
let body: { version?: unknown };
|
|
543
|
+
try { body = await req.json() as { version?: unknown }; }
|
|
544
|
+
catch { return jsonResponse({ error: 'bad_request', reason: 'json body required' }, 400); }
|
|
545
|
+
if (typeof body?.version !== 'string' || !body.version) return jsonResponse({ error: 'bad_request', reason: 'version string required' }, 400);
|
|
546
|
+
let record;
|
|
547
|
+
try { record = readPeerEdit(owned.mineDir, id); }
|
|
548
|
+
catch (error) { return saveFailed(id, error); }
|
|
549
|
+
if (!record) return jsonResponse({ id, approved: true, version: body.version, pending: false });
|
|
550
|
+
if (record.version !== body.version) {
|
|
551
|
+
return jsonResponse({ error: 'version-mismatch', id, version: record.version, reason: '승인하려는 판이 최신 상대 변경과 다르다 — 다시 확인하라' }, 409);
|
|
552
|
+
}
|
|
553
|
+
const approved = { version: record.version, at: new Date().toISOString() };
|
|
554
|
+
try { writePeerEdit(owned.mineDir, id, { ...record, approved }); }
|
|
555
|
+
catch (error) { return saveFailed(id, error); }
|
|
556
|
+
debug.log('nexus.graphs', 'peer-edit-approved', { id, version: record.version, editedBy: record.editedBy });
|
|
557
|
+
return jsonResponse({ id, approved: true, version: record.version, editedBy: record.editedBy });
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
export async function handleGraphsMutation(pathname: string, req: Request, deps: GraphsApiDeps = {}, actor: GraphSaveActor = null): Promise<Response | null> {
|
|
561
|
+
// Only PUT …/yaml is open to a peer token; every other mutation is the owner's alone.
|
|
562
|
+
if (actor && !(req.method === 'PUT' && /^\/v1\/graphs\/[^/]+\/yaml$/.test(pathname))) return jsonResponse({ error: 'unauthorized' }, 401);
|
|
388
563
|
if (req.method === 'POST' && pathname === '/v1/graphs') return handleGraphsCreate(req, deps);
|
|
389
|
-
if (req.method === 'PUT' && /^\/v1\/graphs\/[^/]+\/yaml$/.test(pathname)) return handleGraphsPut(pathname, req, deps);
|
|
564
|
+
if (req.method === 'PUT' && /^\/v1\/graphs\/[^/]+\/yaml$/.test(pathname)) return handleGraphsPut(pathname, req, deps, actor);
|
|
565
|
+
if (req.method === 'POST' && /^\/v1\/graphs\/[^/]+\/approve$/.test(pathname)) return handleGraphsApprove(pathname, req, deps);
|
|
390
566
|
if (req.method === 'POST' && /^\/v1\/graphs\/[^/]+\/clone$/.test(pathname)) return handleGraphsClone(pathname, req, deps);
|
|
391
567
|
if (req.method === 'POST' && /^\/v1\/graphs\/[^/]+\/revert$/.test(pathname)) return handleGraphsRevert(pathname, req, deps);
|
|
392
568
|
return null;
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
// HARNESS-RUN-LIVE-GRAPH (0.2.21) — one harness run's «which node, which outcome, when» for the PWA
|
|
2
|
+
// live/replay graph page (`/live-run/?run=<runId>`).
|
|
3
|
+
//
|
|
4
|
+
// Canonical source = the run's own ledger `~/.elanous/run-ledger/<runId>.jsonl` (federated: pod ledgers synced
|
|
5
|
+
// home are found through the same directories as the running-runs query):
|
|
6
|
+
// · `pipeline-node-entry` — one per node visit (node · round · graphId)
|
|
7
|
+
// · `graph-visit-budget` — the visit count the runtime itself counted
|
|
8
|
+
// · per-node outcome events — implemented · gated · reviewed(+review-reflect) · rework-budget · pre-pr-sync ·
|
|
9
|
+
// gate.postsync · pr-opened · merge-decision
|
|
10
|
+
// · `run-status` — terminal status of the whole run
|
|
11
|
+
// Durations are derived (gap to the next node entry, or to the terminal status); nothing writes a node «end».
|
|
12
|
+
//
|
|
13
|
+
// Node ids are never hardcoded for routing: any node the graph declares and the runtime enters (including the journey
|
|
14
|
+
// nodes HARNESS-FULL-GRAPH adds — entrance, queue, author, dispatch, landing, failure side) shows up as a step. A
|
|
15
|
+
// generic `pipeline-node-exit {node, outcome, detail?}` wins over the per-node heuristics below when present.
|
|
16
|
+
//
|
|
17
|
+
// ⛔ No goal body: the title is the first line of `start.feature`, cut to 80 chars. No artifact paths, no tokens.
|
|
18
|
+
|
|
19
|
+
import { loadFederatedRunLedger, loadRunLedger, runLedgerDir, type RunLedgerEntry } from '../../self-implement/run-ledger.js';
|
|
20
|
+
import { redactSecretText } from '../../debug/log.js';
|
|
21
|
+
|
|
22
|
+
export interface TraversalStepWire {
|
|
23
|
+
node: string;
|
|
24
|
+
round: number | null;
|
|
25
|
+
/** `pass` · `fail` · null (still running). */
|
|
26
|
+
outcome: string | null;
|
|
27
|
+
at: string;
|
|
28
|
+
durationMs: number | null;
|
|
29
|
+
visit: number;
|
|
30
|
+
maxVisits: number | null;
|
|
31
|
+
detail: string | null;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface RunTraversalWire {
|
|
35
|
+
runId: string;
|
|
36
|
+
graphId: string | null;
|
|
37
|
+
title: string | null;
|
|
38
|
+
/** `running` until the ledger carries a terminal `run-status`. */
|
|
39
|
+
status: string;
|
|
40
|
+
stage: string | null;
|
|
41
|
+
startedAt: string | null;
|
|
42
|
+
endedAt: string | null;
|
|
43
|
+
prNumber: number | null;
|
|
44
|
+
substrate: string | null;
|
|
45
|
+
steps: TraversalStepWire[];
|
|
46
|
+
ledgerEvents: number;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const str = (value: unknown): string | null => typeof value === 'string' && value.trim() ? value : null;
|
|
50
|
+
const num = (value: unknown): number | null => typeof value === 'number' && Number.isFinite(value) ? value : null;
|
|
51
|
+
const ms = (iso: string | undefined | null): number => iso ? Date.parse(iso) : Number.NaN;
|
|
52
|
+
|
|
53
|
+
const TERMINAL_FAIL = new Set(['failed', 'abandoned', 'cancelled', 'error', 'aborted', 'stopped']);
|
|
54
|
+
|
|
55
|
+
/** A display title from a goal's first line: path-like tokens shrink to their basename (`a/b/c.tsx` → `c.tsx`),
|
|
56
|
+
* then the line is redacted and cut to 80 chars. Shared with the PWA picker (`apps/pwa/src/lib/live-run-view.ts`). */
|
|
57
|
+
export function displayTitle(text: string | null | undefined, max = 80): string | null {
|
|
58
|
+
const line = text?.split('\n').map((part) => part.trim()).find(Boolean) ?? '';
|
|
59
|
+
const shrunk = line.replace(/\s*\[truncated[^\]]*\]\s*$/, '').replace(/(?:~|\.{0,2})?\/?(?:[\w.@-]+\/)+([\w.@-]+)/g, '$1');
|
|
60
|
+
const cut = shrunk.length > max ? `${shrunk.slice(0, max - 1)}…` : shrunk;
|
|
61
|
+
return cut ? redactSecretText(cut) : null;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function titleOf(entries: readonly RunLedgerEntry[]): string | null {
|
|
65
|
+
return displayTitle(str(entries.find((entry) => entry.event === 'start')?.data.feature));
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** Outcome + one-line detail of a node visit, read from the events between its entry and the next entry. */
|
|
69
|
+
function judgeVisit(node: string, window: readonly RunLedgerEntry[], lookback: readonly RunLedgerEntry[] = []): { outcome: 'pass' | 'fail' | null; detail: string | null } {
|
|
70
|
+
const last = (event: string) => [...window].reverse().find((entry) => entry.event === event)?.data;
|
|
71
|
+
// Some decisions are written just before the node they belong to is entered (merge-decision lands in the regate
|
|
72
|
+
// window, right before open-pr) — look back to the previous node entry for those.
|
|
73
|
+
const near = (event: string) => last(event) ?? [...lookback].reverse().find((entry) => entry.event === event)?.data;
|
|
74
|
+
const exit = [...window].reverse().find((entry) => entry.event === 'pipeline-node-exit' && entry.data.node === node)?.data;
|
|
75
|
+
const exitOutcome = str(exit?.outcome);
|
|
76
|
+
if (exitOutcome) {
|
|
77
|
+
const failed = TERMINAL_FAIL.has(exitOutcome) || exitOutcome === 'fail';
|
|
78
|
+
const exitDetail = str(exit?.detail)?.replace(/\s+/g, ' ').trim();
|
|
79
|
+
return { outcome: failed ? 'fail' : 'pass', detail: exitDetail ? redactSecretText(exitDetail.slice(0, 120)) : exitOutcome };
|
|
80
|
+
}
|
|
81
|
+
switch (node) {
|
|
82
|
+
case 'implement': {
|
|
83
|
+
const done = last('implemented');
|
|
84
|
+
if (!done) return { outcome: null, detail: null };
|
|
85
|
+
const evidence = last('off-diff-evidence');
|
|
86
|
+
const covered = num(evidence?.coveredEvidence); const required = num(evidence?.requiredEvidence);
|
|
87
|
+
const proof = covered !== null && required !== null ? ` · 증거 ${covered}/${required}` : '';
|
|
88
|
+
return done.ok === false ? { outcome: 'fail', detail: '구현 실패' } : { outcome: 'pass', detail: `구현 완료${proof}` };
|
|
89
|
+
}
|
|
90
|
+
case 'gate': {
|
|
91
|
+
const gated = last('gated');
|
|
92
|
+
if (!gated) return { outcome: null, detail: null };
|
|
93
|
+
return gated.passed === true ? { outcome: 'pass', detail: '게이트 통과' } : { outcome: 'fail', detail: '게이트 실패' };
|
|
94
|
+
}
|
|
95
|
+
case 'regate': {
|
|
96
|
+
const gated = last('gate.postsync');
|
|
97
|
+
if (!gated) return { outcome: null, detail: null };
|
|
98
|
+
return gated.passed === true ? { outcome: 'pass', detail: 'main 동기화 뒤 재게이트 통과' } : { outcome: 'fail', detail: '재게이트 실패' };
|
|
99
|
+
}
|
|
100
|
+
case 'review': {
|
|
101
|
+
const review = last('reviewed');
|
|
102
|
+
if (!review) return { outcome: null, detail: null };
|
|
103
|
+
const verdict = str(review.verdict) ?? '?';
|
|
104
|
+
const mustFix = num(review.mustFix);
|
|
105
|
+
const converged = window.some((entry) => entry.event === 'review-reflect' && entry.data.converged === true);
|
|
106
|
+
const base = `리뷰 ${verdict}${mustFix !== null ? ` · 꼭 고칠 것 ${mustFix}` : ''}`;
|
|
107
|
+
if (converged) return { outcome: 'pass', detail: `${base} · 반박 수렴` };
|
|
108
|
+
return { outcome: verdict === 'fail' ? 'fail' : 'pass', detail: base };
|
|
109
|
+
}
|
|
110
|
+
case 'rework': {
|
|
111
|
+
const budget = last('rework-budget');
|
|
112
|
+
const verdict = str(budget?.verdict);
|
|
113
|
+
return { outcome: 'pass', detail: verdict ? `재작업 예산 ${verdict}` : '재작업' };
|
|
114
|
+
}
|
|
115
|
+
case 'main-sync': {
|
|
116
|
+
const sync = last('pre-pr-sync');
|
|
117
|
+
if (!sync) return { outcome: null, detail: null };
|
|
118
|
+
const status = str(sync.status) ?? '?';
|
|
119
|
+
return { outcome: status === 'merged' || status === 'up-to-date' ? 'pass' : 'fail', detail: `main 동기화 ${status}` };
|
|
120
|
+
}
|
|
121
|
+
case 'open-pr': {
|
|
122
|
+
const opened = last('pr-opened');
|
|
123
|
+
const decision = str(near('merge-decision')?.decision);
|
|
124
|
+
if (!opened) return { outcome: null, detail: decision ? `병합 결정 ${decision}` : null };
|
|
125
|
+
const number = num(opened.number);
|
|
126
|
+
return { outcome: 'pass', detail: `PR${number !== null ? ` #${number}` : ''} 열림${decision ? ` · 병합 결정 ${decision}` : ''}` };
|
|
127
|
+
}
|
|
128
|
+
default:
|
|
129
|
+
return { outcome: null, detail: null };
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export function buildRunTraversal(runId: string, entries: readonly RunLedgerEntry[]): RunTraversalWire {
|
|
134
|
+
const sorted = [...entries].map((entry, index) => ({ entry, index }))
|
|
135
|
+
.sort((a, b) => (ms(a.entry.timestamp) - ms(b.entry.timestamp)) || a.index - b.index).map(({ entry }) => entry);
|
|
136
|
+
const terminal = [...sorted].reverse().find((entry) => entry.event === 'run-status' && str(entry.data.runStatus));
|
|
137
|
+
const status = terminal ? str(terminal.data.runStatus)! : 'running';
|
|
138
|
+
const entryIndexes = sorted.map((entry, index) => entry.event === 'pipeline-node-entry' && str(entry.data.node) ? index : -1).filter((index) => index >= 0);
|
|
139
|
+
const visits = new Map<string, number>();
|
|
140
|
+
const steps: TraversalStepWire[] = entryIndexes.map((at, position) => {
|
|
141
|
+
const entry = sorted[at]!;
|
|
142
|
+
const node = str(entry.data.node)!;
|
|
143
|
+
const nextAt = entryIndexes[position + 1];
|
|
144
|
+
const window = sorted.slice(at + 1, nextAt ?? sorted.length);
|
|
145
|
+
const budget = [...sorted.slice(Math.max(0, at - 3), at)].reverse()
|
|
146
|
+
.find((candidate) => candidate.event === 'graph-visit-budget' && candidate.data.node === node);
|
|
147
|
+
const visit = num(budget?.data.visits) ?? (visits.get(node) ?? 0) + 1;
|
|
148
|
+
visits.set(node, visit);
|
|
149
|
+
const prevAt = position > 0 ? entryIndexes[position - 1]! : -1;
|
|
150
|
+
let { outcome, detail } = judgeVisit(node, window, sorted.slice(prevAt + 1, at));
|
|
151
|
+
const isLast = nextAt === undefined;
|
|
152
|
+
if (!isLast && outcome === null) outcome = 'pass';
|
|
153
|
+
if (isLast && terminal) outcome = TERMINAL_FAIL.has(status) ? 'fail' : (outcome ?? 'pass');
|
|
154
|
+
const endIso = nextAt !== undefined ? sorted[nextAt]!.timestamp : terminal?.timestamp;
|
|
155
|
+
const duration = ms(endIso) - ms(entry.timestamp);
|
|
156
|
+
return {
|
|
157
|
+
node,
|
|
158
|
+
round: num(entry.data.round),
|
|
159
|
+
outcome,
|
|
160
|
+
at: entry.timestamp ?? '',
|
|
161
|
+
durationMs: Number.isFinite(duration) && duration >= 0 ? duration : null,
|
|
162
|
+
visit,
|
|
163
|
+
maxVisits: num(budget?.data.maxVisits),
|
|
164
|
+
detail,
|
|
165
|
+
};
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
const contract = sorted.find((entry) => entry.event === 'graph-run-contract-resolved')?.data;
|
|
169
|
+
const pr = [...sorted].reverse().find((entry) => entry.event === 'pr-opened')?.data;
|
|
170
|
+
return {
|
|
171
|
+
runId,
|
|
172
|
+
graphId: str(contract?.graphId) ?? str(sorted.find((entry) => entry.event === 'pipeline-node-entry')?.data.graphId),
|
|
173
|
+
title: titleOf(sorted),
|
|
174
|
+
status,
|
|
175
|
+
stage: terminal ? str(terminal.data.stage) : null,
|
|
176
|
+
startedAt: sorted[0]?.timestamp ?? null,
|
|
177
|
+
endedAt: terminal?.timestamp ?? null,
|
|
178
|
+
prNumber: num(pr?.number),
|
|
179
|
+
substrate: str(contract?.actualSubstrate),
|
|
180
|
+
steps,
|
|
181
|
+
ledgerEvents: sorted.length,
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
export interface TraversalDeps {
|
|
186
|
+
loadLedger?: (runId: string) => RunLedgerEntry[] | null;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const USAGE = 'usage: GET /v1/harness/run-traversal?runId=<runId>';
|
|
190
|
+
|
|
191
|
+
/** GET /v1/harness/run-traversal?runId= — ordered node steps of one harness run. */
|
|
192
|
+
export function handleHarnessRunTraversalGet(req: Request, deps: TraversalDeps = {}): Response {
|
|
193
|
+
const runId = new URL(req.url).searchParams.get('runId')?.trim() ?? '';
|
|
194
|
+
if (!runId || /[\\/]|\.\./.test(runId)) return Response.json({ error: USAGE }, { status: 400 });
|
|
195
|
+
let entries: RunLedgerEntry[] | null;
|
|
196
|
+
try {
|
|
197
|
+
entries = deps.loadLedger
|
|
198
|
+
? deps.loadLedger(runId)
|
|
199
|
+
: loadRunLedger(runId, runLedgerDir()) ?? loadFederatedRunLedger(runId);
|
|
200
|
+
} catch (error) {
|
|
201
|
+
return Response.json({ error: 'ledger-unreadable', reason: error instanceof Error ? error.message.slice(0, 200) : 'unknown' }, { status: 422 });
|
|
202
|
+
}
|
|
203
|
+
if (!entries) return Response.json({ error: 'run-not-found', runId }, { status: 404 });
|
|
204
|
+
return Response.json(buildRunTraversal(runId, entries));
|
|
205
|
+
}
|