elanous 0.2.20 → 0.2.21
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/MObU8ujkMciAKr6ljAV05/_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-77fad80cf54477c6.js → 1307-c97eba9fb72a9a5e.js} +1 -1
- 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/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/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/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-59fedea50d13c762.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-f1762140ef9a96b1.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-7581825d511a64c0.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9451.ebfb0dcc9c02250f.js +23 -0
- package/apps/pwa/out/_next/static/chunks/9497-b0e758d1cd4ae8d7.js +1 -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-937c92292a4ab4f0.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/autopilot/{page-85b316bb4235980b.js → page-d781be3e3a9cfd74.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/board/{page-9b2b26a33cb2cd42.js → page-b0a80997903852d8.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/botlab/{page-c9955fde67b1bcb5.js → page-2b1263165630d9c8.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/bots/{page-7a852956c74015a3.js → page-ea998f7f5d156bea.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/ceo/page-33c328b64ca508f8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/chat/{page-4ba4abe096b6c2a8.js → page-e2d6bed48ac20120.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/consult/{page-575d4a8b4f7becb2.js → page-111cabe4d1fd69ae.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/control/{page-f5d56ff5f45ad164.js → page-544dd4bd4847eeb7.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/decisions/{page-2260e9eef0357421.js → page-3ee191319bf97d87.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-e4bcb2c503dab04b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/editor/page-300897dcbbd5ed76.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/exec/{page-466bf0ee16ad1432.js → page-a449f1ed472b81c2.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/field/{page-a23bc71713bcc08f.js → page-9045094d6d3b5cd0.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/inside/page-c06673596849ef45.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/intake/page-103fa3191c1b2e08.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/layout-9f81233f29dcd409.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/live/page-be8b4615c919e36c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/live-run/page-c685b104e0dfa609.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/loops/page-8d693331cfbb6add.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/market/page-cef4f30a4c22fae1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-66ea1b54fc83fa15.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/morning/{page-ec102c9b8b691e37.js → page-7a12f61be4a579d1.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/not-found-c85b0589591a99c9.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-725a4da02dd129eb.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/{page-f6055495e9ed7ea8.js → page-8cc707bd09a497d9.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-90b55be1f15ce0e8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-cb16606e08be65db.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/outputs/page-16dc64b74ed6a40a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/page-21bec4afeb2f0917.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/reflection/{page-db03ae3589a81c9e.js → page-dc39ee7a36d26385.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/scheduler/{page-4a6463b97f0ad94f.js → page-84ae759f76053672.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/sessions/{page-75bffecc9aa1004e.js → page-3db58ddb61fe2be9.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-c5315e30f3528dc4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/{page-31d5b875760bb62d.js → page-2fe368efb6de3b6a.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/done/{page-c26fa10aac6c21c4.js → page-8d04132dd0ac5e10.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/{page-68793c6e10331d66.js → page-f23484de467c9ea3.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/share/{page-46d5201d2207a511.js → page-c69762987b751174.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-5456692bda39aafb.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/tasks/{page-be1c4e8697f22360.js → page-d75f7cd65845ce98.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/term/page-5603f5e568b25ce3.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/today/page-d2cc0f4e89f42028.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/trace/{page-0fd895fab9b62c1b.js → page-a6eb73b8a4840f1c.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/vault/page-c0a3f4d10edb83fa.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/{page-c6e4364d9de8f717.js → page-6c0b88068c143f15.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/{page-0acc8e198f7b0dfb.js → page-7f8d7fcdf058a845.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/workspace/{page-34882c84f84021c5.js → page-22163556cd38bc88.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-141436fd26dd56fc.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/e81df11490b4413b.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/drafts/recipes.yaml +4 -4
- package/graphs/implement-loop.yaml +277 -5
- package/package.json +1 -1
- package/scripts/coord-channel-watch.sh +31 -4
- package/scripts/docs-lint.ts +4 -1
- package/scripts/marketing/generate-ax-brief.ts +18 -1
- package/scripts/measure-nl-routing-corpus.ts +11 -3
- package/scripts/release-loop/gate-node.ts +5 -5
- package/scripts/release-loop/shard-plan.ts +50 -5
- package/scripts/rhythm/daily.ts +2 -1
- package/src/browser-cdp/client.ts +10 -5
- 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/tasks-cli.ts +106 -0
- package/src/dashboard/index.ts +40 -4
- package/src/esc-abort-gate.ts +3 -1
- package/src/graph-runner/graph-cli.ts +57 -0
- package/src/graph-runner/graph-run-api.ts +13 -0
- package/src/graph-wizard/archetypes.ts +88 -0
- package/src/graph-wizard/generate.ts +545 -0
- package/src/graph-wizard/saved-steps.ts +120 -0
- package/src/graph-wizard/steps.ts +193 -0
- package/src/harness/harness-cli-command.ts +68 -12
- package/src/index.ts +7 -4
- package/src/intake-plane/digest.ts +18 -11
- package/src/maturity/feature-maturity.ts +2 -1
- package/src/nexus/api/draft-metrics.ts +114 -0
- package/src/nexus/api/graph-wizard-api.ts +53 -0
- package/src/nexus/api/graphs-api.ts +50 -12
- package/src/nexus/api/harness-traversal.ts +205 -0
- package/src/nexus/api/http-server.ts +31 -0
- package/src/nexus/outbound/router.ts +28 -8
- 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/release-loop/gate-shards.ts +7 -2
- package/src/self-dev/ask-launch-flow.ts +4 -2
- package/src/self-dev/ask-launch-io.ts +3 -1
- 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/release-path-guard.ts +7 -4
- package/src/self-implement/frozen-merges.ts +23 -0
- package/src/self-implement/goal-author.ts +14 -0
- package/src/self-implement/heal-intake.ts +3 -0
- package/src/self-implement/orchestrator.ts +16 -1
- package/src/self-implement/seams.ts +3 -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 +17 -3
- package/src/task-agent/card-evidence.ts +6 -0
- package/src/task-agent/parent-host.ts +315 -0
- package/src/task-agent/task-hand.ts +9 -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/self-implement-pod.ts +39 -6
- package/src/tool-runtime/browser-runtime.ts +87 -3
- package/src/tool-runtime/index.ts +1 -0
- package/src/user-config.ts +20 -4
- package/src/version/packed-revision.json +1 -1
- package/apps/pwa/out/_next/static/Bam1YziK2iH9bN4K0utGk/_buildManifest.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/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/approvals/page-c855c24de7db74fe.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/loops/page-35bfec2e631d3eae.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/missions/[id]/page-f9ccb1b1adbea14d.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/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/showroom/page-d81c07a7b4f618ae.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/term/page-cf0a4b4abead71ea.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/vault/page-8b6c1454a8e8f306.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 → MObU8ujkMciAKr6ljAV05}/_ssgManifest.js +0 -0
- /package/skills/project-onboarding/{skill.md → SKILL.md} +0 -0
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { join } from 'node:path';
|
|
2
|
+
import { debug } from '../../debug/log.js';
|
|
3
|
+
import type { DraftMetrics } from '../../self-dev/draft-sweep.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* DRAFT-METRIC — CEO view source. Opening the page must never trigger a GitHub read:
|
|
7
|
+
* `read()` answers from memory and at most one background collection runs at a time,
|
|
8
|
+
* no more often than the TTL (or the failure back-off). The collection itself is
|
|
9
|
+
* `elanous harness drafts metrics --json` in a child process, which reads PR evidence
|
|
10
|
+
* through the batched GraphQL path (#24918) — one call per 40 drafts, no per-draft comments call.
|
|
11
|
+
*/
|
|
12
|
+
export type DraftMetricsState = 'measuring' | 'ready' | 'unavailable';
|
|
13
|
+
|
|
14
|
+
export interface DraftMetricsSnapshot {
|
|
15
|
+
/** measuring = no value yet and a collection is running · unavailable = no value and the last collection failed. */
|
|
16
|
+
readonly state: DraftMetricsState;
|
|
17
|
+
readonly metrics: DraftMetrics | null;
|
|
18
|
+
/** When the served value was collected (ISO). null without a value. */
|
|
19
|
+
readonly measuredAt: string | null;
|
|
20
|
+
/** A background collection is in flight. */
|
|
21
|
+
readonly refreshing: boolean;
|
|
22
|
+
/** Last collection failure; with a value it means the value is older than intended. */
|
|
23
|
+
readonly reason: string | null;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface DraftMetricsSourceOptions {
|
|
27
|
+
readonly collect?: () => Promise<DraftMetrics>;
|
|
28
|
+
readonly ttlMs?: number;
|
|
29
|
+
readonly failureRetryMs?: number;
|
|
30
|
+
readonly now?: () => number;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export const DRAFT_METRICS_TTL_MS = 10 * 60_000;
|
|
34
|
+
export const DRAFT_METRICS_FAILURE_RETRY_MS = 2 * 60_000;
|
|
35
|
+
const COLLECT_TIMEOUT_MS = 5 * 60_000;
|
|
36
|
+
|
|
37
|
+
function isMetrics(value: unknown): value is DraftMetrics {
|
|
38
|
+
if (!value || typeof value !== 'object') return false;
|
|
39
|
+
const row = value as Record<string, unknown>;
|
|
40
|
+
const count = (key: string) => Number.isInteger(row[key]) && (row[key] as number) >= 0;
|
|
41
|
+
const nullableNumber = (key: string) => row[key] === null || (typeof row[key] === 'number' && Number.isFinite(row[key]));
|
|
42
|
+
return count('inventory') && count('needsOwner') && count('converted48h') && count('cohort48h')
|
|
43
|
+
&& nullableNumber('oldestAgeHours') && nullableNumber('conversion48h');
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Parses the CLI's single JSON line; anything else is a failed collection, never a zero. */
|
|
47
|
+
export function parseDraftMetricsOutput(stdout: string): DraftMetrics {
|
|
48
|
+
const line = stdout.trim().split('\n').reverse().find((text) => text.trim().startsWith('{'));
|
|
49
|
+
if (!line) throw new Error('draft metrics: no JSON output');
|
|
50
|
+
const parsed: unknown = JSON.parse(line);
|
|
51
|
+
if (!isMetrics(parsed)) throw new Error('draft metrics: malformed output');
|
|
52
|
+
const { inventory, oldestAgeHours, needsOwner, converted48h, cohort48h, conversion48h } = parsed;
|
|
53
|
+
return { inventory, oldestAgeHours, needsOwner, converted48h, cohort48h, conversion48h };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async function collectViaCli(): Promise<DraftMetrics> {
|
|
57
|
+
const root = join(import.meta.dir, '../../..');
|
|
58
|
+
const proc = Bun.spawn(['bun', join(root, 'bin/elanous.mjs'), 'harness', 'drafts', 'metrics', '--json'], {
|
|
59
|
+
cwd: root, stdout: 'pipe', stderr: 'pipe',
|
|
60
|
+
});
|
|
61
|
+
const timer = setTimeout(() => proc.kill(), COLLECT_TIMEOUT_MS);
|
|
62
|
+
try {
|
|
63
|
+
const [stdout, stderr, exitCode] = await Promise.all([new Response(proc.stdout).text(), new Response(proc.stderr).text(), proc.exited]);
|
|
64
|
+
if (exitCode !== 0) throw new Error(stderr.trim().split('\n').at(-1) || `draft metrics exited ${exitCode}`);
|
|
65
|
+
return parseDraftMetricsOutput(stdout);
|
|
66
|
+
} finally { clearTimeout(timer); }
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export interface DraftMetricsSource {
|
|
70
|
+
read(): DraftMetricsSnapshot;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function createDraftMetricsSource(options: DraftMetricsSourceOptions = {}): DraftMetricsSource {
|
|
74
|
+
const collect = options.collect ?? collectViaCli;
|
|
75
|
+
const ttl = options.ttlMs ?? DRAFT_METRICS_TTL_MS;
|
|
76
|
+
const retry = options.failureRetryMs ?? DRAFT_METRICS_FAILURE_RETRY_MS;
|
|
77
|
+
const now = options.now ?? Date.now;
|
|
78
|
+
let value: { metrics: DraftMetrics; at: number } | null = null;
|
|
79
|
+
let failure: { reason: string; at: number } | null = null;
|
|
80
|
+
let inFlight: Promise<void> | null = null;
|
|
81
|
+
|
|
82
|
+
const refresh = (): void => {
|
|
83
|
+
if (inFlight) return;
|
|
84
|
+
const started = now();
|
|
85
|
+
inFlight = (async () => {
|
|
86
|
+
try {
|
|
87
|
+
const metrics = await collect();
|
|
88
|
+
value = { metrics, at: now() };
|
|
89
|
+
failure = null;
|
|
90
|
+
try { debug.log('nexus.draft-metrics', 'collected', { ms: now() - started, inventory: metrics.inventory }); } catch { /* fail-soft */ }
|
|
91
|
+
} catch (error) {
|
|
92
|
+
failure = { reason: error instanceof Error ? error.message : String(error), at: now() };
|
|
93
|
+
try { debug.log('nexus.draft-metrics', 'failed', { ms: now() - started, reason: failure.reason }); } catch { /* fail-soft */ }
|
|
94
|
+
} finally { inFlight = null; }
|
|
95
|
+
})();
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
return {
|
|
99
|
+
read() {
|
|
100
|
+
const clock = now();
|
|
101
|
+
const due = failure && (!value || failure.at >= value.at)
|
|
102
|
+
? clock - failure.at >= retry
|
|
103
|
+
: !value || clock - value.at >= ttl;
|
|
104
|
+
if (due) refresh();
|
|
105
|
+
return {
|
|
106
|
+
state: value ? 'ready' : failure && !inFlight ? 'unavailable' : 'measuring',
|
|
107
|
+
metrics: value?.metrics ?? null,
|
|
108
|
+
measuredAt: value ? new Date(value.at).toISOString() : null,
|
|
109
|
+
refreshing: inFlight !== null,
|
|
110
|
+
reason: failure?.reason ?? null,
|
|
111
|
+
};
|
|
112
|
+
},
|
|
113
|
+
};
|
|
114
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { debug } from '../../debug/log.js';
|
|
2
|
+
import { GraphWizardInputError, generateGraphFromPrompt, type GraphWizardDeps, type GraphWizardTurn } from '../../graph-wizard/generate.js';
|
|
3
|
+
import { jsonResponse } from './json-response.js';
|
|
4
|
+
|
|
5
|
+
const MAX_PROMPT = 4000;
|
|
6
|
+
const MAX_YAML = 200_000;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* POST /v1/graphs/wizard {prompt, kind?, currentYaml?, history?}
|
|
10
|
+
* → 200 {ok:true, id, yaml, base, baseReason, issues:[], attempts, summary, labels, steps, recipes, dryRun} · 422 {ok:false, …} · 400 빈 prompt.
|
|
11
|
+
* (labels·steps·recipes·dryRun·baseReason 는 v2 추가 필드 — 기존 필드는 그대로.)
|
|
12
|
+
* currentYaml 이 빈 문자열이면 «빈 캔버스»로 보고 새 그래프를 짓는다(의도) · 내용이 있는데 id 가 없거나 형식 밖이면 400.
|
|
13
|
+
* 저장하지 않는다 — 저장은 POST /v1/graphs(새 그래프) 또는 PUT /v1/graphs/<id>/yaml(내 그래프).
|
|
14
|
+
*/
|
|
15
|
+
export async function handleGraphWizardPost(req: Request, deps: GraphWizardDeps = {}): Promise<Response> {
|
|
16
|
+
let body: Record<string, unknown>;
|
|
17
|
+
try {
|
|
18
|
+
const parsed: unknown = await req.json();
|
|
19
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) throw new Error('object');
|
|
20
|
+
body = parsed as Record<string, unknown>;
|
|
21
|
+
} catch { return jsonResponse({ error: 'bad_request', reason: 'json body required' }, 400); }
|
|
22
|
+
const prompt = typeof body.prompt === 'string' ? body.prompt.trim() : '';
|
|
23
|
+
if (!prompt) return jsonResponse({ error: 'bad_request', reason: 'prompt required' }, 400);
|
|
24
|
+
if (prompt.length > MAX_PROMPT) return jsonResponse({ error: 'bad_request', reason: `prompt longer than ${MAX_PROMPT}` }, 400);
|
|
25
|
+
if (body.kind !== undefined && body.kind !== 'harness' && body.kind !== 'workflow') {
|
|
26
|
+
return jsonResponse({ error: 'bad_request', reason: 'kind must be harness or workflow' }, 400);
|
|
27
|
+
}
|
|
28
|
+
if (body.currentYaml !== undefined && (typeof body.currentYaml !== 'string' || body.currentYaml.length > MAX_YAML)) {
|
|
29
|
+
return jsonResponse({ error: 'bad_request', reason: 'currentYaml must be a string' }, 400);
|
|
30
|
+
}
|
|
31
|
+
let history: GraphWizardTurn[] | undefined;
|
|
32
|
+
if (body.history !== undefined) {
|
|
33
|
+
if (!Array.isArray(body.history) || !body.history.every((t) => t && typeof t === 'object' &&
|
|
34
|
+
((t as GraphWizardTurn).role === 'user' || (t as GraphWizardTurn).role === 'assistant') && typeof (t as GraphWizardTurn).text === 'string')) {
|
|
35
|
+
return jsonResponse({ error: 'bad_request', reason: 'history must be [{role, text}]' }, 400);
|
|
36
|
+
}
|
|
37
|
+
history = body.history as GraphWizardTurn[];
|
|
38
|
+
}
|
|
39
|
+
try {
|
|
40
|
+
const result = await generateGraphFromPrompt({
|
|
41
|
+
prompt,
|
|
42
|
+
...(body.kind ? { kind: body.kind as 'harness' | 'workflow' } : {}),
|
|
43
|
+
...(typeof body.currentYaml === 'string' && body.currentYaml.trim() ? { currentYaml: body.currentYaml } : {}),
|
|
44
|
+
...(history ? { history } : {}),
|
|
45
|
+
}, deps);
|
|
46
|
+
return jsonResponse({ ...result, base: result.base ?? null }, result.ok ? 200 : 422);
|
|
47
|
+
} catch (error) {
|
|
48
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
49
|
+
if (error instanceof GraphWizardInputError) return jsonResponse({ error: 'bad_request', reason: message }, 400);
|
|
50
|
+
debug.log('graph.wizard', 'error', { message });
|
|
51
|
+
return jsonResponse({ error: 'wizard-failed', reason: message }, 502);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -7,6 +7,8 @@ import { debug } from '../../debug/log.js';
|
|
|
7
7
|
import { defaultGraphsDir, loadGraphTemplatesFrom } from '../../self-implement/graph-templates.js';
|
|
8
8
|
import { loadGraphTemplates } from '../../self-implement/graph-yaml.js';
|
|
9
9
|
import { validateGraphYaml } from './graph-kinds.js';
|
|
10
|
+
import { parseWizardSteps, readWizardSteps, withWizardSteps } from '../../graph-wizard/saved-steps.js';
|
|
11
|
+
import type { WizardNodeStep } from '../../graph-wizard/steps.js';
|
|
10
12
|
import { jsonResponse } from './json-response.js';
|
|
11
13
|
|
|
12
14
|
/** Declared graph ids only. A user string is never joined onto a filesystem path. */
|
|
@@ -109,7 +111,12 @@ function detailOf(id: string, source: 'core' | 'mine', editable: boolean, dir: s
|
|
|
109
111
|
editable,
|
|
110
112
|
entry_node: template.entryNode,
|
|
111
113
|
terminal_nodes: template.terminalNodes,
|
|
112
|
-
|
|
114
|
+
// GRAPH-GROUPS — a node's `group:` tag (when the loader carries it) rides along so the PWA can box/fold units.
|
|
115
|
+
nodes: template.nodes.map((node) => {
|
|
116
|
+
const { nodeId, kind, recipe, maxVisits } = node;
|
|
117
|
+
const group = (node as { group?: unknown }).group;
|
|
118
|
+
return { node_id: nodeId, kind, recipe, max_visits: maxVisits, ...(typeof group === 'string' && group.trim() ? { group: group.trim() } : {}) };
|
|
119
|
+
}),
|
|
113
120
|
edges: spec.edges.map(({ from, to, on, map }) => ({
|
|
114
121
|
from,
|
|
115
122
|
...(to === undefined ? {} : { to }),
|
|
@@ -178,7 +185,8 @@ export function handleGraphsGet(pathname: string, dir = defaultGraphsDir(), deps
|
|
|
178
185
|
const coreHit = core.ok ? indexByDeclaredId(coreDir).get(id) : undefined;
|
|
179
186
|
const hit = mineHit ?? coreHit;
|
|
180
187
|
if (!hit) return jsonResponse({ error: 'not-found', id }, 404);
|
|
181
|
-
|
|
188
|
+
const steps = mineHit ? readWizardSteps(mineDir, id, hit.text) : null;
|
|
189
|
+
return jsonResponse({ id, source: mineHit ? 'mine' : 'core', editable: Boolean(mineHit), yaml: hit.text, ...(steps ? { steps } : {}) });
|
|
182
190
|
}
|
|
183
191
|
|
|
184
192
|
const match = /^\/v1\/graphs\/([^/]+)$/.exec(pathname);
|
|
@@ -209,17 +217,22 @@ export async function handleGraphsPut(pathname: string, req: Request, deps: Grap
|
|
|
209
217
|
const mineOwns = indexByDeclaredId(mineDir).has(id);
|
|
210
218
|
if (coreOwns && !mineOwns) return jsonResponse({ error: 'core-read-only', id }, 403);
|
|
211
219
|
if (!mineOwns) return jsonResponse({ error: 'not-found', id }, 404);
|
|
212
|
-
let body: { yaml?: unknown };
|
|
213
|
-
try { body = await req.json() as { yaml?: unknown }; }
|
|
220
|
+
let body: { yaml?: unknown; steps?: unknown };
|
|
221
|
+
try { body = await req.json() as { yaml?: unknown; steps?: unknown }; }
|
|
214
222
|
catch { return jsonResponse({ error: 'bad_request', reason: 'json body required' }, 400); }
|
|
215
223
|
if (typeof body?.yaml !== 'string') return jsonResponse({ error: 'bad_request', reason: 'yaml string required' }, 400);
|
|
216
224
|
const rejected = rejectGraphYaml(body.yaml, id);
|
|
217
225
|
if (rejected) return rejected;
|
|
226
|
+
const steps = stepsOf(body.steps, body.yaml);
|
|
227
|
+
if (steps instanceof Response) return steps;
|
|
218
228
|
const existing = indexByDeclaredId(mineDir).get(id)!;
|
|
219
229
|
// Writes go only to <mine>/<id>.yaml; a hand-placed file under another name is not edited over HTTP.
|
|
220
230
|
if (existing.file !== `${id}.yaml`) return jsonResponse({ error: 'file-name-mismatch', id, file: existing.file }, 409);
|
|
221
|
-
|
|
222
|
-
|
|
231
|
+
let written: { version: string; previous: string };
|
|
232
|
+
try { written = withWizardSteps(mineDir, id, steps, () => writeWithVersion(mineDir, id, existing, body.yaml as string)); }
|
|
233
|
+
catch (error) { return saveFailed(id, error); }
|
|
234
|
+
const { version, previous } = written;
|
|
235
|
+
debug.log('nexus.graphs', 'saved', { id, version, previous, steps: steps ? Object.keys(steps).length : 0 });
|
|
223
236
|
return jsonResponse({ id, source: 'mine', editable: true, saved: true, version, previous });
|
|
224
237
|
}
|
|
225
238
|
|
|
@@ -331,10 +344,27 @@ export async function handleGraphsRevert(pathname: string, req: Request, deps: G
|
|
|
331
344
|
return jsonResponse({ id, source: 'mine', editable: true, reverted: true, version, previous, restoredFrom: target });
|
|
332
345
|
}
|
|
333
346
|
|
|
347
|
+
/** GRAPH-WIZARD-SAVE-RECIPES — optional `steps` on save (the wizard's node → library step map). Absent = keep
|
|
348
|
+
* whatever sidecar the graph has; present = validated against the step library and stored as a sidecar. The
|
|
349
|
+
* run builds commands from it server-side (src/graph-wizard/saved-steps.ts) — no client command text is kept. */
|
|
350
|
+
function stepsOf(raw: unknown, yaml: string): Record<string, WizardNodeStep> | null | Response {
|
|
351
|
+
// Only an absent field means «keep what is there»; an explicit null or any other non-object is a bad request.
|
|
352
|
+
if (raw === undefined) return null;
|
|
353
|
+
const parsed = parseWizardSteps(raw, yaml);
|
|
354
|
+
if (!parsed.ok) return jsonResponse({ error: 'bad_request', reason: parsed.reason }, 400);
|
|
355
|
+
return parsed.steps;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
function saveFailed(id: string, error: unknown): Response {
|
|
359
|
+
const reason = error instanceof Error ? error.message.slice(0, 200) : String(error);
|
|
360
|
+
debug.log('nexus.graphs', 'save-failed', { id, reason }, { level: 'warn' });
|
|
361
|
+
return jsonResponse({ error: 'save-failed', id, reason }, 500);
|
|
362
|
+
}
|
|
363
|
+
|
|
334
364
|
/** POST /v1/graphs {id, yaml} — a brand-new «mine» graph (blank canvas → save). Records version v1. */
|
|
335
365
|
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 }; }
|
|
366
|
+
let body: { id?: unknown; yaml?: unknown; steps?: unknown };
|
|
367
|
+
try { body = await req.json() as { id?: unknown; yaml?: unknown; steps?: unknown }; }
|
|
338
368
|
catch { return jsonResponse({ error: 'bad_request', reason: 'json body required' }, 400); }
|
|
339
369
|
const id = typeof body?.id === 'string' ? resolveId(body.id) : null;
|
|
340
370
|
if (!id) return jsonResponse({ error: 'bad_request', reason: 'id must match [a-z0-9-]' }, 400);
|
|
@@ -347,10 +377,18 @@ export async function handleGraphsCreate(req: Request, deps: GraphsApiDeps = {})
|
|
|
347
377
|
if (indexByDeclaredId(mineDir).has(id) || existsSync(join(mineDir, `${id}.yaml`))) return jsonResponse({ error: 'exists', id }, 409);
|
|
348
378
|
const rejected = rejectGraphYaml(body.yaml, id);
|
|
349
379
|
if (rejected) return rejected;
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
380
|
+
const steps = stepsOf(body.steps, body.yaml);
|
|
381
|
+
if (steps instanceof Response) return steps;
|
|
382
|
+
let version: string;
|
|
383
|
+
try {
|
|
384
|
+
version = withWizardSteps(mineDir, id, steps, () => {
|
|
385
|
+
mkdirSync(mineDir, { recursive: true });
|
|
386
|
+
writeFileSync(join(mineDir, `${id}.yaml`), body.yaml as string, { flag: 'wx' });
|
|
387
|
+
try { return recordVersion(mineDir, id, body.yaml as string); }
|
|
388
|
+
catch (error) { rmSync(join(mineDir, `${id}.yaml`), { force: true }); throw error; }
|
|
389
|
+
});
|
|
390
|
+
} catch (error) { return saveFailed(id, error); }
|
|
391
|
+
debug.log('nexus.graphs', 'saved', { id, version, created: true, steps: steps ? Object.keys(steps).length : 0 });
|
|
354
392
|
return jsonResponse({ id, source: 'mine', editable: true, saved: true, version, previous: null }, 201);
|
|
355
393
|
}
|
|
356
394
|
|
|
@@ -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
|
+
}
|
|
@@ -23,6 +23,7 @@ import { isAbsolute } from 'node:path';
|
|
|
23
23
|
import { createProject, listProjects } from '../../project/project-store.js';
|
|
24
24
|
import { handleOutputsGet, type OutputsDeps } from './outputs-route.js';
|
|
25
25
|
import { buildGridData, type GridDeps } from './grid.js';
|
|
26
|
+
import { createDraftMetricsSource, type DraftMetricsSource } from './draft-metrics.js';
|
|
26
27
|
import { DECISIONS_PATH, handleDecisions, type DecisionsRouteDeps } from './decisions-route.js';
|
|
27
28
|
import { OPS_BOARD_PATH, handleOpsBoard, type OpsBoardDeps } from './ops-board-route.js';
|
|
28
29
|
import { CARD_FOLLOWUP_PATH, createCardFollowupJobs, type CardFollowupJobs } from './card-followup-route.js';
|
|
@@ -31,6 +32,7 @@ import { compareTokenConstTime } from '../../acp/transport/auth.js';
|
|
|
31
32
|
import { ensureAuthToken } from '../../auth/acp-token.js';
|
|
32
33
|
import type { NexusState } from '../state/state.js';
|
|
33
34
|
import { DEFAULT_NEXUS_HTTP_PORT } from '../default-port.js';
|
|
35
|
+
|
|
34
36
|
export { DEFAULT_NEXUS_HTTP_PORT } from '../default-port.js';
|
|
35
37
|
import type { TabRegistry } from '../state/tab-registry.js';
|
|
36
38
|
import type { FeedbackEnvelope } from '../../feedback/envelope.js';
|
|
@@ -164,6 +166,7 @@ import {
|
|
|
164
166
|
} from './vault-api.js';
|
|
165
167
|
import { handleBuildsGet, parseBuildsPath } from './builds-api.js';
|
|
166
168
|
import { handleHarnessAskPost, handleHarnessAskStatusGet, handleHarnessRunEventsGet, handleHarnessRunScreenGet, handleHarnessRunsGet, handleHarnessStopPost } from './harness-api.js';
|
|
169
|
+
import { handleHarnessRunTraversalGet } from './harness-traversal.js';
|
|
167
170
|
import { handleLoopEdgesGet } from './loop-edges.js';
|
|
168
171
|
import { handleFabricPlans, type FabricPlansRouteOpts } from './fabric-plans.js';
|
|
169
172
|
import { dispatchPersonaRoute } from './personas.js';
|
|
@@ -367,6 +370,7 @@ import { handleTriggersSnapshot } from './triggers.js';
|
|
|
367
370
|
import { handleWorkflowTemplatesList } from './workflow-templates.js';
|
|
368
371
|
import { handleGraphVersionsGet, handleGraphsGet, handleGraphsMutation } from './graphs-api.js';
|
|
369
372
|
import { handleGraphKindsGet, handleGraphsValidatePost } from './graph-kinds.js';
|
|
373
|
+
import { handleGraphWizardPost } from './graph-wizard-api.js';
|
|
370
374
|
import { syncInstalledPluginNodes } from '../../graph-kinds/installed-plugin-nodes.js';
|
|
371
375
|
import { elanousStateRoot } from '../../autopilot/state-paths.js';
|
|
372
376
|
import { handlePluginsIndexGet, handlePluginsGet, handlePluginsMarketRefresh, handlePluginsInstall, handlePluginsRemove } from './plugins-market.js';
|
|
@@ -426,6 +430,9 @@ import {
|
|
|
426
430
|
parseShowroomLayoutPath,
|
|
427
431
|
} from './showroom-layouts.js';
|
|
428
432
|
|
|
433
|
+
/** Process-wide DRAFT-METRIC cache (one background collection per TTL, shared by every page load). */
|
|
434
|
+
let sharedDraftMetrics: DraftMetricsSource | undefined;
|
|
435
|
+
|
|
429
436
|
export const NEXUS_HTTP_PORT_RANGE = 16; // try 31415..31430
|
|
430
437
|
/** Short timeout for the pre-bind localhost occupancy probe. */
|
|
431
438
|
export const NEXUS_PORT_PROBE_TIMEOUT_MS = 400;
|
|
@@ -480,11 +487,15 @@ export type NexusWsBridgeInit = Omit<WsBridgeOpts, 'hostname' | 'port'>;
|
|
|
480
487
|
export interface NexusHttpServerOpts {
|
|
481
488
|
/** Optional fabric-plan ledger and decomposition seam; production uses the instance root and existing fabric decomposer. */
|
|
482
489
|
fabricPlans?: FabricPlansRouteOpts;
|
|
490
|
+
/** GRAPH-WIZARD 시험 주입(LLM·템플릿 뿌리). 운영은 비워 둔다. */
|
|
491
|
+
graphWizard?: import('../../graph-wizard/generate.js').GraphWizardDeps;
|
|
483
492
|
/** Optional installer state root for isolated API consumers and tests. */
|
|
484
493
|
pluginStateRoot?: string;
|
|
485
494
|
outputs?: OutputsDeps;
|
|
486
495
|
/** Read-only grid measurement seam; omitted in production for live HQ and Pod pool reads. */
|
|
487
496
|
grid?: GridDeps;
|
|
497
|
+
/** DRAFT-METRIC cached source seam; omitted in production for the process-wide TTL cache. */
|
|
498
|
+
draftMetrics?: DraftMetricsSource;
|
|
488
499
|
decisions?: Pick<DecisionsRouteDeps, 'ledger'>;
|
|
489
500
|
opsBoard?: Pick<OpsBoardDeps, 'openStore'>;
|
|
490
501
|
cardFollowup?: CardFollowupJobs;
|
|
@@ -1360,6 +1371,13 @@ export async function routeRequest(
|
|
|
1360
1371
|
return handleHarnessRunEventsGet(req, opts.metaApi);
|
|
1361
1372
|
}
|
|
1362
1373
|
|
|
1374
|
+
// HARNESS-RUN-LIVE-GRAPH — one run's ordered node steps ⊕ journey band (PWA /live-run).
|
|
1375
|
+
if (pathname === '/v1/harness/run-traversal' && method === 'GET') {
|
|
1376
|
+
if (!opts.metaApi) return jsonResponse({ error: 'meta-api-runtime-not-wired' }, 503);
|
|
1377
|
+
if (!checkAuth(req, opts.metaApi)) return jsonResponse({ error: 'unauthorized' }, 401);
|
|
1378
|
+
return handleHarnessRunTraversalGet(req);
|
|
1379
|
+
}
|
|
1380
|
+
|
|
1363
1381
|
// Design direction is a write: check owner auth before the handler reads the body.
|
|
1364
1382
|
// Keep this POST ahead of the mutation fallback, and never make it a public route.
|
|
1365
1383
|
if (method === 'POST' && pathname === DESIGN_DIRECTION_PATH) {
|
|
@@ -1389,6 +1407,13 @@ export async function routeRequest(
|
|
|
1389
1407
|
return handleGraphsValidatePost(req, opts.metaApi);
|
|
1390
1408
|
}
|
|
1391
1409
|
|
|
1410
|
+
// GRAPH-WIZARD — 말 → 그래프 YAML(저장 안 함). 다른 /v1/graphs 쓰기와 같은 인증.
|
|
1411
|
+
if (method === 'POST' && pathname === '/v1/graphs/wizard') {
|
|
1412
|
+
if (!opts.metaApi) return jsonResponse({ error: 'meta-api-not-wired' }, 503);
|
|
1413
|
+
if (!checkAuth(req, opts.metaApi)) return jsonResponse({ error: 'unauthorized' }, 401);
|
|
1414
|
+
return handleGraphWizardPost(req, opts.graphWizard);
|
|
1415
|
+
}
|
|
1416
|
+
|
|
1392
1417
|
if (pathname === '/v1/fabric/decompose' || pathname.startsWith('/v1/fabric/plans/')) {
|
|
1393
1418
|
if (!opts.metaApi || !checkAuth(req, opts.metaApi)) return jsonResponse({ error: 'unauthorized' }, 401);
|
|
1394
1419
|
return handleFabricPlans(req, opts.fabricPlans);
|
|
@@ -1437,6 +1462,12 @@ export async function routeRequest(
|
|
|
1437
1462
|
if (method !== 'GET') return jsonResponse({ error: 'method-not-allowed', method }, 405);
|
|
1438
1463
|
return jsonResponse(buildGridData(opts.grid));
|
|
1439
1464
|
}
|
|
1465
|
+
// DRAFT-METRIC: answers from memory; a stale/cold cache starts one background collection (never per request).
|
|
1466
|
+
if (pathname === '/v1/drafts/metrics') {
|
|
1467
|
+
if (!opts.metaApi || !checkAuth(req, opts.metaApi)) return jsonResponse({ error: 'unauthorized' }, 401);
|
|
1468
|
+
if (method !== 'GET') return jsonResponse({ error: 'method-not-allowed', method }, 405);
|
|
1469
|
+
return jsonResponse((opts.draftMetrics ?? (sharedDraftMetrics ??= createDraftMetricsSource())).read());
|
|
1470
|
+
}
|
|
1440
1471
|
if (method === 'GET' && pathname === '/v1/outputs') {
|
|
1441
1472
|
if (!opts.metaApi || !checkAuth(req, opts.metaApi)) return jsonResponse({ error: 'unauthorized' }, 401);
|
|
1442
1473
|
return handleOutputsGet(req, opts.outputs);
|