elanous 0.2.20 → 0.2.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/apps/pwa/out/404/index.html +1 -1
- package/apps/pwa/out/404.html +1 -1
- package/apps/pwa/out/_next/static/aBi6EyMtLsrl7J8eH4kF7/_buildManifest.js +1 -0
- package/apps/pwa/out/_next/static/chunks/088d8e8a.8069543d082bec12.js +160 -0
- package/apps/pwa/out/_next/static/chunks/1109.78a7a3288c0259d9.js +148 -0
- package/apps/pwa/out/_next/static/chunks/115.1c6690b8604f81c4.js +32 -0
- package/apps/pwa/out/_next/static/chunks/119fc678.6f591cdb84ca5791.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1262.76a3f08e67c5f816.js +309 -0
- package/apps/pwa/out/_next/static/chunks/1307-08b716476f1d50af.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1358d292.5c87f12cad6eaa91.js +122 -0
- package/apps/pwa/out/_next/static/chunks/1438.27a1a7b8f2308004.js +149 -0
- package/apps/pwa/out/_next/static/chunks/1442.02d85cd88f4c1eac.js +79 -0
- package/apps/pwa/out/_next/static/chunks/1501-1aad662d82ba43f7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1584.9e2dd53da6e6d4b1.js +24 -0
- package/apps/pwa/out/_next/static/chunks/1586.b989b0614afb77e6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1609.2ad355062870151f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1612-f6df0fc0eed7157b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1923.37f1d5fe7ebe3860.js +27 -0
- package/apps/pwa/out/_next/static/chunks/1948.5f35b39eef222a94.js +5 -0
- package/apps/pwa/out/_next/static/chunks/1993.3fd074a2a246234e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2140.d2daf9ee96e295d1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2212.0359851457473efc.js +1 -0
- package/apps/pwa/out/_next/static/chunks/224.583e562365a52f74.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2328.0f37465646bafa7d.js +36 -0
- package/apps/pwa/out/_next/static/chunks/2372.bf71372184991937.js +286 -0
- package/apps/pwa/out/_next/static/chunks/{2489-ff55a35b202c0f94.js → 2489-d238d3e2eb5b2bf5.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/2549-a8515c86f9e8cb96.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2655.72cc6a97276c5acc.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2743-ef6e8c3ced74cce5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2840-5458ad50c49cac6a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2856.f75d5baef7f49b74.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3344.0de4b0ec5a454b7a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/335.a13f339e97893437.js +94 -0
- package/apps/pwa/out/_next/static/chunks/3394.646348f34b2500be.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3443.6276909b53d72d53.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3495.abd4254d808bf2dc.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3522-1de1319d028ac89c.js +25 -0
- package/apps/pwa/out/_next/static/chunks/3721.ba5b82db0d9a5572.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3857-5985236d9307f676.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3919.9ff7dc25afefee33.js +74 -0
- package/apps/pwa/out/_next/static/chunks/3998.bd9a69d9b50e9bd2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4003.ab6db1dc266576c9.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4216.e8ac94b9fe6b96ab.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4327.c44dc2b55cb43ee5.js +34 -0
- package/apps/pwa/out/_next/static/chunks/4401.94257d497b6d26bc.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4559.17ca2cdbaa8d414f.js +115 -0
- package/apps/pwa/out/_next/static/chunks/4588.ad3364cb7fb97ada.js +86 -0
- package/apps/pwa/out/_next/static/chunks/4706.97e734bd4c9816ca.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4982.6e44352f751b9a1b.js +77 -0
- package/apps/pwa/out/_next/static/chunks/5342.49c5c5a530e62b50.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5360-08a80abdf3a4da99.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5431.66c58ab1327ee0b7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5633-d6852385000738ef.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5717.bcfcb96e701b82e0.js +90 -0
- package/apps/pwa/out/_next/static/chunks/5755-30d102586789bf7f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5957.3f1a4012388dd962.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5977-5b9e8a45a5b0c823.js +1 -0
- package/apps/pwa/out/_next/static/chunks/602.9bf02ac8e9502598.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6020.3a1b347e2d2dfacc.js +8 -0
- package/apps/pwa/out/_next/static/chunks/{6158.af2e93b7adff709b.js → 6158.566c890adc06a459.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/6375.fce1d174ba6d0324.js +64 -0
- package/apps/pwa/out/_next/static/chunks/6546.92a828aef441271f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/658.957fdb0a8c6c6c8e.js +287 -0
- package/apps/pwa/out/_next/static/chunks/6706.323590aabb8b1293.js +226 -0
- package/apps/pwa/out/_next/static/chunks/6747.b67947f293a4c02d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6767.8e6c5f6e744fcd9f.js +38 -0
- package/apps/pwa/out/_next/static/chunks/6868.000b24a3045c5574.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6946.bba74e9c96050f30.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7033.a558f1b2f6912a53.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7065.46029c26c53eac50.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7087.6fae577d09249bc1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7175-350f4ce2873bc038.js +1 -0
- package/apps/pwa/out/_next/static/chunks/722.de8d9801e0f6d414.js +41 -0
- package/apps/pwa/out/_next/static/chunks/7497.57981e9d7a75a52a.js +24 -0
- package/apps/pwa/out/_next/static/chunks/7630.2b9a68659053ff4e.js +193 -0
- package/apps/pwa/out/_next/static/chunks/7672.4af122ad892117c7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/771.6ab889a233e7dae7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7899.597af4a9996c907f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/79b78388.663188133449d707.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8096.f7da0776cfde6300.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8194.484f51e273d9abd2.js +217 -0
- package/apps/pwa/out/_next/static/chunks/8769.815ca69879f608b4.js +104 -0
- package/apps/pwa/out/_next/static/chunks/8857-aa1cc4ac90af4d40.js +1 -0
- package/apps/pwa/out/_next/static/chunks/88729267.9c6c000157c83f2f.js +62 -0
- package/apps/pwa/out/_next/static/chunks/8f835b4f.048eeb02299e3e12.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9068.5f2764fccf86c1b2.js +93 -0
- package/apps/pwa/out/_next/static/chunks/9247-55bcfbb0e8057f59.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9451.ebfb0dcc9c02250f.js +23 -0
- package/apps/pwa/out/_next/static/chunks/9559.e648369b1d57cf9a.js +62 -0
- package/apps/pwa/out/_next/static/chunks/9832.2ab2e818013574c7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/a9f2b54d-40c16bd0efdb04b8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/approvals/{page-c855c24de7db74fe.js → page-88459e0903da2803.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/autopilot/{page-85b316bb4235980b.js → page-d85c43f46f58716b.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/board/{page-9b2b26a33cb2cd42.js → page-4e645600579ae952.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/botlab/{page-c9955fde67b1bcb5.js → page-87edbfd19117823b.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/bots/page-7f0c2b7801ac6a54.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ceo/page-bf1f030a9d1adffd.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/chat/{page-4ba4abe096b6c2a8.js → page-cbaf66b1e1804adf.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/consult/{page-575d4a8b4f7becb2.js → page-80ed511e02c81412.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/control/{page-f5d56ff5f45ad164.js → page-9f5610caaa06178c.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/decisions/{page-2260e9eef0357421.js → page-ec8f3575e339de74.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-86d753e460f1a989.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/editor/page-4166a7d51cf0a57d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/exec/{page-466bf0ee16ad1432.js → page-2a19c5cd3b562580.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/field/{page-a23bc71713bcc08f.js → page-b6a09cde2e07521a.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/inside/page-f7eae1518ca0c353.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/intake/page-456a8573ad811381.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/layout-8462d6a543cbb2d9.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/live/page-2974cdfb87c0f9c9.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/live-run/page-f927ffa25f3c86d1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/loops/{page-35bfec2e631d3eae.js → page-abd86cd832aaa011.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/market/page-79daf68f6256adaa.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/{page-f9ccb1b1adbea14d.js → page-8c3f168fc3f91939.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/morning/{page-ec102c9b8b691e37.js → page-786fb4504d0f2fa9.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/not-found-75209f14597dcb8f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-bf30affe3a85af7e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-1187a44b7515434b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-3d0910185a66ed54.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-f401ee1dd5a26e25.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/outputs/page-5dfe7604fd842784.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/page-76cf838050a8d681.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/reflection/{page-db03ae3589a81c9e.js → page-7ec8e289b4c14df6.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/scheduler/{page-4a6463b97f0ad94f.js → page-8c8ac17d52be2a79.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/sessions/{page-75bffecc9aa1004e.js → page-d359ab048d63a9d9.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-b8b345c880bf78d1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/{page-31d5b875760bb62d.js → page-f151cc10ad026940.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-9875f103057da433.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/page-9789328af9a9aa62.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/share/{page-46d5201d2207a511.js → page-bf495a1e284f4e00.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-a263d94bca4be22a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-3962eab8ec58d16e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/term/{page-cf0a4b4abead71ea.js → page-9abcfac07e02a542.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/today/page-bee49f927c333abb.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/trace/page-bac2d56dcb02d955.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/vault/page-937cbcc13176295c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-d09495594f7f480d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/{page-0acc8e198f7b0dfb.js → page-69cb93d1a2e57e0e.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/workspace/{page-34882c84f84021c5.js → page-72ec543928d67c3e.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-82906eeda0186a7b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/d70879db.c66c56a95a91efc6.js +66 -0
- package/apps/pwa/out/_next/static/chunks/f8626309.a4e5d362b1d55e59.js +1 -0
- package/apps/pwa/out/_next/static/chunks/webpack-f2b4016b9b981bc8.js +1 -0
- package/apps/pwa/out/_next/static/css/{d3427996da11e480.css → b9d8151e48998417.css} +1 -1
- package/apps/pwa/out/_next/static/css/f2bd5257e6af88d8.css +1 -0
- package/apps/pwa/out/approvals/index.html +1 -1
- package/apps/pwa/out/approvals/index.txt +14 -14
- package/apps/pwa/out/autopilot/index.html +1 -1
- package/apps/pwa/out/autopilot/index.txt +14 -14
- package/apps/pwa/out/board/index.html +1 -1
- package/apps/pwa/out/board/index.txt +14 -14
- package/apps/pwa/out/botlab/index.html +1 -1
- package/apps/pwa/out/botlab/index.txt +14 -14
- package/apps/pwa/out/bots/index.html +1 -1
- package/apps/pwa/out/bots/index.txt +14 -14
- package/apps/pwa/out/ceo/index.html +1 -1
- package/apps/pwa/out/ceo/index.txt +14 -14
- package/apps/pwa/out/chat/index.html +1 -1
- package/apps/pwa/out/chat/index.txt +14 -14
- package/apps/pwa/out/consult/index.html +1 -1
- package/apps/pwa/out/consult/index.txt +14 -14
- package/apps/pwa/out/control/index.html +1 -1
- package/apps/pwa/out/control/index.txt +14 -14
- package/apps/pwa/out/decisions/index.html +1 -1
- package/apps/pwa/out/decisions/index.txt +14 -14
- package/apps/pwa/out/design-check/index.html +1 -1
- package/apps/pwa/out/design-check/index.txt +14 -14
- package/apps/pwa/out/editor/index.html +1 -1
- package/apps/pwa/out/editor/index.txt +15 -15
- package/apps/pwa/out/exec/index.html +1 -1
- package/apps/pwa/out/exec/index.txt +14 -14
- package/apps/pwa/out/field/index.html +1 -1
- package/apps/pwa/out/field/index.txt +14 -14
- package/apps/pwa/out/healthz +1 -1
- package/apps/pwa/out/index.html +1 -1
- package/apps/pwa/out/index.txt +14 -14
- package/apps/pwa/out/inside/index.html +1 -1
- package/apps/pwa/out/inside/index.txt +15 -15
- package/apps/pwa/out/intake/index.html +1 -1
- package/apps/pwa/out/intake/index.txt +14 -14
- package/apps/pwa/out/live/index.html +1 -1
- package/apps/pwa/out/live/index.txt +14 -14
- package/apps/pwa/out/live-run/index.html +1 -0
- package/apps/pwa/out/live-run/index.txt +35 -0
- package/apps/pwa/out/loops/index.html +1 -1
- package/apps/pwa/out/loops/index.txt +14 -14
- package/apps/pwa/out/market/index.html +1 -1
- package/apps/pwa/out/market/index.txt +14 -14
- package/apps/pwa/out/missions/placeholder/index.html +1 -1
- package/apps/pwa/out/missions/placeholder/index.txt +14 -14
- package/apps/pwa/out/morning/index.html +1 -1
- package/apps/pwa/out/morning/index.txt +14 -14
- package/apps/pwa/out/observatory/index.html +1 -1
- package/apps/pwa/out/observatory/index.txt +14 -14
- package/apps/pwa/out/ops/checklist/index.html +1 -1
- package/apps/pwa/out/ops/checklist/index.txt +14 -14
- package/apps/pwa/out/ops/release/index.html +1 -1
- package/apps/pwa/out/ops/release/index.txt +14 -14
- package/apps/pwa/out/ops/seats/index.html +1 -1
- package/apps/pwa/out/ops/seats/index.txt +14 -14
- package/apps/pwa/out/outputs/index.html +1 -1
- package/apps/pwa/out/outputs/index.txt +14 -14
- package/apps/pwa/out/reflection/index.html +1 -1
- package/apps/pwa/out/reflection/index.txt +15 -15
- package/apps/pwa/out/scheduler/index.html +1 -1
- package/apps/pwa/out/scheduler/index.txt +14 -14
- package/apps/pwa/out/sessions/index.html +1 -1
- package/apps/pwa/out/sessions/index.txt +14 -14
- package/apps/pwa/out/settings/devices/index.html +1 -1
- package/apps/pwa/out/settings/devices/index.txt +14 -14
- package/apps/pwa/out/settings/index.html +1 -1
- package/apps/pwa/out/settings/index.txt +14 -14
- package/apps/pwa/out/setup/done/index.html +1 -1
- package/apps/pwa/out/setup/done/index.txt +14 -14
- package/apps/pwa/out/setup/index.html +1 -1
- package/apps/pwa/out/setup/index.txt +14 -14
- package/apps/pwa/out/share/index.html +1 -1
- package/apps/pwa/out/share/index.txt +14 -14
- package/apps/pwa/out/showroom/index.html +1 -1
- package/apps/pwa/out/showroom/index.txt +14 -14
- package/apps/pwa/out/tasks/index.html +1 -1
- package/apps/pwa/out/tasks/index.txt +14 -14
- package/apps/pwa/out/term/index.html +1 -1
- package/apps/pwa/out/term/index.txt +14 -14
- package/apps/pwa/out/today/index.html +1 -1
- package/apps/pwa/out/today/index.txt +14 -14
- package/apps/pwa/out/trace/index.html +1 -1
- package/apps/pwa/out/trace/index.txt +14 -14
- package/apps/pwa/out/vault/index.html +1 -1
- package/apps/pwa/out/vault/index.txt +14 -14
- package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
- package/apps/pwa/out/workflows/chat-ui/index.txt +14 -14
- package/apps/pwa/out/workflows/index.html +1 -1
- package/apps/pwa/out/workflows/index.txt +14 -14
- package/apps/pwa/out/workspace/index.html +1 -1
- package/apps/pwa/out/workspace/index.txt +14 -14
- package/apps/pwa/out/worktrees/index.html +1 -1
- package/apps/pwa/out/worktrees/index.txt +14 -14
- package/graphs/catalog/catalog.yaml +1 -1
- package/graphs/drafts/recipes.yaml +6 -6
- package/graphs/implement-loop.yaml +277 -5
- package/graphs/test-diet/recipes.yaml +3 -0
- package/graphs/test-diet/test-diet.yaml +3 -1
- package/package.json +1 -1
- package/scripts/coord-channel-watch.sh +31 -4
- package/scripts/docs-lint.ts +4 -1
- package/scripts/f-session-monitor.ts +69 -0
- package/scripts/hq/standby-snapshot.ts +7 -3
- package/scripts/knowledge-compress.ts +11 -0
- package/scripts/marketing/generate-ax-brief.ts +18 -1
- package/scripts/measure-nl-routing-corpus.ts +11 -3
- package/scripts/nightly-docops.ts +90 -3
- package/scripts/release-loop/gate-node.ts +5 -5
- package/scripts/release-loop/shard-plan.ts +50 -5
- package/scripts/research-coo-admin-dates.ts +39 -0
- package/scripts/rhythm/daily.ts +2 -1
- package/scripts/skills/public-check.ts +7 -2
- package/scripts/smart-merge/resync-shadow.ts +5 -3
- package/scripts/test-diet/lib.ts +81 -1
- package/scripts/test-diet/node.ts +48 -2
- package/scripts/verify-coo-admin-evidence.ts +83 -0
- package/src/acp/client.ts +4 -2
- package/src/agent/run-context.ts +4 -0
- package/src/agent-env/profile.ts +25 -5
- package/src/agent-mission/driver.ts +26 -12
- package/src/agent-mission/supervisor-metrics.ts +17 -2
- package/src/agent-substrate/acp-reviewer.ts +12 -5
- package/src/agent-substrate/pr-reviewer.ts +11 -4
- package/src/agent-substrate/review-intent.ts +102 -3
- package/src/autopilot/build/llm-conflict-merge.ts +22 -8
- package/src/autopilot/pr-manager.ts +18 -8
- package/src/away/release-watch.ts +47 -8
- package/src/briefing/brief-items.ts +104 -11
- package/src/browser-cdp/client.ts +10 -5
- package/src/chat/index.ts +1 -0
- package/src/cli/brief-cli.ts +20 -2
- package/src/cli/config-classify.ts +83 -0
- package/src/cli/control-cli.ts +14 -0
- package/src/cli/decisions-cli.ts +26 -16
- package/src/cli/logs-model-input.ts +1 -1
- package/src/cli/ops-cli.ts +11 -0
- package/src/cli/ops-overview-cli.ts +50 -0
- package/src/cli/pod-cli.ts +1 -0
- package/src/cli/pr-cli.ts +65 -8
- package/src/cli/release-cli.ts +21 -4
- package/src/cli/tasks-cli.ts +214 -1
- package/src/connectors/types.ts +6 -6
- package/src/context-bus/context-now-surfaces.ts +18 -0
- package/src/context-bus/coord-events.ts +32 -4
- package/src/context-bus/long-term-memory.ts +23 -6
- package/src/context-bus/nightly-condense.ts +169 -21
- package/src/control/control-status.ts +87 -0
- package/src/control-plane/light-member.ts +5 -3
- package/src/control-plane/member.ts +172 -7
- package/src/core-turn/run-goal-loop.ts +22 -1
- package/src/dashboard/index.ts +40 -4
- package/src/dashboard/slash-runtime/dashboard-handlers.ts +9 -0
- package/src/decisions/decision-cards.ts +20 -1
- package/src/decisions/decision-ledger.ts +11 -8
- package/src/decisions/web-push-decision-cards.ts +19 -3
- package/src/discord-self-message.ts +2 -2
- package/src/domains/coo-admin-dates.evidence.json +876 -0
- package/src/domains/coo-admin-tool.ts +48 -14
- package/src/domains/ops-status.ts +53 -0
- package/src/domains/outbound-alert.ts +24 -1
- package/src/esc-abort-gate.ts +3 -1
- package/src/exec-requests/planner.ts +6 -0
- package/src/graph-kinds/registry.ts +6 -6
- package/src/graph-runner/graph-cli.ts +91 -2
- package/src/graph-runner/graph-run-api.ts +20 -0
- package/src/graph-runner/graph-tick.ts +73 -12
- package/src/graph-runner/runner.ts +43 -11
- package/src/graph-unify/wf-to-graph.ts +79 -0
- package/src/graph-wizard/archetypes.ts +88 -0
- package/src/graph-wizard/generate.ts +577 -0
- package/src/graph-wizard/saved-steps.ts +120 -0
- package/src/graph-wizard/steps.ts +206 -0
- package/src/harness/harness-cli-command.ts +186 -32
- package/src/harness/harness-queue.ts +58 -14
- package/src/harness/launch-advice.ts +23 -0
- package/src/index.ts +43 -11
- package/src/intake-plane/digest.ts +18 -11
- package/src/knowledge/conclusion-compress.ts +93 -0
- package/src/knowledge/know-find.ts +9 -2
- package/src/knowledge/query.ts +20 -0
- package/src/llm.ts +26 -16
- package/src/loops/neighbor-runtime.ts +88 -0
- package/src/loops/orchestrator/author-depth.ts +29 -11
- package/src/loops/orchestrator/overlap.ts +7 -1
- package/src/loops/orchestrator/resync-shadow.ts +1 -1
- package/src/loops/orchestrator/tick.ts +35 -2
- package/src/loops/orchestrator/traffic.ts +2 -0
- package/src/loops/registry.ts +5 -0
- package/src/market/publish.ts +17 -1
- package/src/maturity/feature-maturity.ts +4 -2
- package/src/mss/logging/redaction.ts +17 -0
- package/src/nexus/api/draft-metrics.ts +128 -0
- package/src/nexus/api/graph-peer-edit.ts +77 -0
- package/src/nexus/api/graph-wizard-api.ts +61 -0
- package/src/nexus/api/graphs-api.ts +193 -17
- package/src/nexus/api/harness-traversal.ts +205 -0
- package/src/nexus/api/http-server.ts +122 -6
- package/src/nexus/api/plugins-market.ts +24 -0
- package/src/nexus/index.ts +3 -2
- package/src/nexus/outbound/router.ts +28 -8
- package/src/oauth/claude-pty-maturity.ts +24 -0
- package/src/oauth/fallback-chain.ts +27 -14
- package/src/onboarding/first-run.ts +3 -0
- package/src/ops/overview-collect.ts +345 -0
- package/src/ops/overview-seat-era.ts +90 -0
- package/src/ops/overview.ts +223 -0
- package/src/plugins/maker/market-bundle.ts +9 -0
- package/src/plugins/maker/plugin-maker.ts +9 -3
- package/src/plugins/maker/wizard-generate.ts +73 -9
- package/src/policy/llm-policy.ts +11 -6
- package/src/release-loop/checklist-board.ts +27 -0
- package/src/release-loop/checklist.ts +38 -0
- package/src/release-loop/gate-shards.ts +7 -2
- package/src/seat-address/seat-address.ts +1 -0
- package/src/seat-loop/seat-loop.ts +174 -20
- package/src/seat-loop/seat-report.ts +29 -1
- package/src/self-dev/ask-launch-flow.ts +4 -2
- package/src/self-dev/ask-launch-io.ts +3 -1
- package/src/self-dev/dev-cli.ts +90 -8
- package/src/self-dev/dev-pipeline.ts +20 -1
- package/src/self-dev/draft-sweep.ts +45 -8
- package/src/self-dev/graph-journey-nodes.ts +157 -0
- package/src/self-dev/graph-journey-route.ts +105 -0
- package/src/self-dev/launch-preflight.ts +43 -3
- package/src/self-dev/orchestrate.ts +3 -0
- package/src/self-dev/release-path-guard.ts +7 -4
- package/src/self-dev/review-provider-fallback.ts +83 -0
- package/src/self-dev/run-supervisor.ts +5 -1
- package/src/self-implement/frozen-merges.ts +23 -0
- package/src/self-implement/goal-author.ts +14 -0
- package/src/self-implement/harness-policy-gates.ts +7 -5
- package/src/self-implement/heal-intake.ts +3 -0
- package/src/self-implement/host-regate.ts +28 -14
- package/src/self-implement/l8-merge-queue.ts +80 -8
- package/src/self-implement/orchestrator.ts +84 -28
- package/src/self-implement/seams.ts +8 -3
- package/src/self-implement/self-implement-cli.ts +4 -0
- package/src/self-implement/self-implement-runtime.ts +12 -0
- package/src/session-runtime/index.ts +5 -3
- package/src/shell-env-bootstrap.ts +35 -6
- package/src/steward/triage.ts +25 -8
- package/src/storage/file-lock.ts +33 -4
- package/src/task-agent/card-evidence.ts +6 -0
- package/src/task-agent/completion-evidence.ts +31 -3
- package/src/task-agent/cover.ts +61 -0
- package/src/task-agent/live-moves.ts +126 -3
- package/src/task-agent/parent-host.ts +315 -0
- package/src/task-agent/shadow.ts +19 -4
- package/src/task-agent/task-hand.ts +11 -2
- package/src/task-orchestrator/surfaces/pod-command-job.ts +41 -6
- package/src/task-orchestrator/surfaces/pod-gate-deps.ts +123 -0
- package/src/task-orchestrator/surfaces/pod-pool.ts +138 -15
- package/src/task-orchestrator/surfaces/self-implement-pod.ts +39 -6
- package/src/task-orchestrator/surfaces/self-implement.ts +7 -0
- package/src/telegram-commands.ts +8 -0
- package/src/telegram-project-command.ts +6 -0
- package/src/telegram.ts +2 -2
- package/src/tool-runtime/browser-runtime.ts +87 -3
- package/src/tool-runtime/index.ts +1 -0
- package/src/typecheck-ratchet.ts +2 -1
- package/src/user-config.ts +55 -8
- package/src/version/packed-revision.json +1 -1
- package/src/workflow-runtime/executor.ts +24 -2
- package/src/workflow-runtime/triggers/registry.ts +27 -0
- package/src/workflow-runtime/triggers/schedule-source.ts +30 -3
- package/apps/pwa/out/_next/static/Bam1YziK2iH9bN4K0utGk/_buildManifest.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1307-77fad80cf54477c6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1612-f60c0c93ec773f81.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1844-9ec4b1d12f6e80eb.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1866-e7a6999f25548b30.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1948.15f356b15a625597.js +0 -5
- package/apps/pwa/out/_next/static/chunks/2116-6429c188eb846ab8.js +0 -1
- package/apps/pwa/out/_next/static/chunks/224-e7be8f762fe74986.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2549-89c518a4c8ed4d00.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2957-96ffab3d521fb08f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3857-995ea9f5ef33d71e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5360-be1f1d8f9fcc9755.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5755-ea7ed88eb956f47b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5977-94ed6f78e9814b2b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7748-6e51bfe927019f5c.js +0 -25
- package/apps/pwa/out/_next/static/chunks/9247-c1753a059e7bac08.js +0 -1
- package/apps/pwa/out/_next/static/chunks/a9f2b54d-33c0689b70ed8cf3.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/bots/page-7a852956c74015a3.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ceo/page-c5a5c621be0e04ae.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-7c058069c5e7fa63.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/editor/page-dc5da0b8a2edb8a4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/inside/page-ed4a6f799f005d09.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/intake/page-9cd565005b93aca6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/layout-55f0f40c6db95f15.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/live/page-474cb5888ea0e8f3.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/market/page-9ca21f9ad3208a61.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/not-found-a9c2cfa8dda922a5.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-6da35144878feb6d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-f6055495e9ed7ea8.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-25204694fa9984a5.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-a60e035f151d84d3.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/outputs/page-189c587f36874538.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/page-c56634bde705a65d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-a8d2fe0c669e0de4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-c26fa10aac6c21c4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/page-68793c6e10331d66.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-d81c07a7b4f618ae.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-be1c4e8697f22360.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/today/page-a4717e2c4bf7a28f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/trace/page-0fd895fab9b62c1b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/vault/page-8b6c1454a8e8f306.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-c6e4364d9de8f717.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-0506a1e0e4bdb2f5.js +0 -1
- package/apps/pwa/out/_next/static/chunks/webpack-c260571513bf43c2.js +0 -1
- package/apps/pwa/out/_next/static/css/c23d959d3ee15e57.css +0 -1
- /package/apps/pwa/out/_next/static/{Bam1YziK2iH9bN4K0utGk → aBi6EyMtLsrl7J8eH4kF7}/_ssgManifest.js +0 -0
- /package/skills/project-onboarding/{skill.md → SKILL.md} +0 -0
|
@@ -0,0 +1,577 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GRAPH-WIZARD — 말 한 줄(또는 대화 한 턴)에서 그래프 YAML 을 짓는다.
|
|
3
|
+
*
|
|
4
|
+
* 목표: «플러그인 마법사처럼 그래프도 말로 말하면 컨셉으로 생겨나게 · 채팅으로 쭉 치면 노드와 간선이 쭉 만들어진다».
|
|
5
|
+
*
|
|
6
|
+
* 흐름: ① 가장 가까운 기존 템플릿(graphs/**)을 고른다(키워드 점수 — LLM 이 목록에서 다시 고를 수 있다)
|
|
7
|
+
* ② LLM 이 «알려진 노드 종류·스키마»만으로 YAML 을 쓴다(편집이면 currentYaml 을 고쳐 «전체»를 돌려준다)
|
|
8
|
+
* ③ API 와 «같은» 검증기(validateGraphYaml)로 재고, 실패면 문제를 되먹여 최대 3회.
|
|
9
|
+
* ⛔ 저장하지 않는다 — 저장은 편집기의 기존 POST /v1/graphs 몫이다.
|
|
10
|
+
*/
|
|
11
|
+
import { createHash } from 'node:crypto';
|
|
12
|
+
import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs';
|
|
13
|
+
import { join } from 'node:path';
|
|
14
|
+
import { parse as parseYaml } from 'yaml';
|
|
15
|
+
import { debug } from '../debug/log.js';
|
|
16
|
+
import { listNodeKinds, type GraphKind } from '../graph-kinds/registry.js';
|
|
17
|
+
import { validateGraphYaml } from '../nexus/api/graph-kinds.js';
|
|
18
|
+
import { defaultGraphsDir } from '../self-implement/graph-templates.js';
|
|
19
|
+
import { WORKFLOW_SYNTH_SYSTEM_PROMPT } from '../workflow-synth/system-prompt.js';
|
|
20
|
+
import { WIZARD_ARCHETYPES } from './archetypes.js';
|
|
21
|
+
import { listInstalledPacks, queryInstalledPack } from '../knowledge/query.js';
|
|
22
|
+
import { parsePackIdString } from '../knowledge/kgs/pack.js';
|
|
23
|
+
import { WIZARD_MAX_RETRIES, WIZARD_STEPS, WIZARD_STEP_IDS, recipesYamlFor, type WizardNodeStep } from './steps.js';
|
|
24
|
+
|
|
25
|
+
export interface GraphWizardTurn { role: 'user' | 'assistant'; text: string }
|
|
26
|
+
|
|
27
|
+
export interface GraphWizardRequest {
|
|
28
|
+
prompt: string;
|
|
29
|
+
kind?: GraphKind;
|
|
30
|
+
/** 있으면 prompt 는 «이 그래프를 고치라»는 지시다 — 결과는 고친 «전체» YAML. */
|
|
31
|
+
currentYaml?: string;
|
|
32
|
+
history?: GraphWizardTurn[];
|
|
33
|
+
/** Installed pack selected for a knowledge (RAG) node. */
|
|
34
|
+
packId?: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface GraphWizardResult {
|
|
38
|
+
ok: boolean;
|
|
39
|
+
id: string;
|
|
40
|
+
yaml: string;
|
|
41
|
+
base?: string;
|
|
42
|
+
/** 왜 그 기반을 골랐나 — 키워드 · LLM 재선택. */
|
|
43
|
+
baseReason?: string;
|
|
44
|
+
issues: string[];
|
|
45
|
+
attempts: number;
|
|
46
|
+
summary: string;
|
|
47
|
+
/** 노드 id → 한국어 이름 (YAML 노드 줄 주석에서 읽는다). */
|
|
48
|
+
labels?: Record<string, string>;
|
|
49
|
+
/** 노드 id → 서재 단계(·인자). 하니스만. */
|
|
50
|
+
steps?: Record<string, WizardNodeStep>;
|
|
51
|
+
/** 이 그래프 옆에 둘 recipes.yaml — `elanous graph run` 이 cmd/approval 을 여기서 찾는다. 하니스만. */
|
|
52
|
+
recipes?: string;
|
|
53
|
+
/** 실행 가능성 증명: 그 recipes 로 `graph run --dry-run` 을 걸어 본 결과. 하니스만. */
|
|
54
|
+
dryRun?: { status: string; path: string[] };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export interface GraphWizardDeps {
|
|
58
|
+
/** LLM 한 번 부르기. 기본 = 역할 `graph-grow` 해석 ⊕ 설정 폴백 체인(streamLLM). */
|
|
59
|
+
callLLM?: (prompt: string) => Promise<string>;
|
|
60
|
+
/** 템플릿 뿌리(기본 graphs/). */
|
|
61
|
+
graphsDir?: string;
|
|
62
|
+
/** 이미 쓰인 graph_id(핵심 ⊕ 내 그래프). 기본 = graphs/** ⊕ <state>/graphs. */
|
|
63
|
+
existingIds?: () => ReadonlySet<string>;
|
|
64
|
+
maxAttempts?: number;
|
|
65
|
+
/** Installed pack inventory and retrieval seams for isolated generation. */
|
|
66
|
+
packs?: typeof listInstalledPacks;
|
|
67
|
+
searchPack?: typeof queryInstalledPack;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export const GRAPH_WIZARD_MAX_ATTEMPTS = 3;
|
|
71
|
+
|
|
72
|
+
/** Inventory used by the editor's pack picker and the wizard's validation. */
|
|
73
|
+
export function wizardInstalledPacks(deps: GraphWizardDeps = {}): Array<{ id: string; title: string }> {
|
|
74
|
+
return (deps.packs ?? listInstalledPacks)();
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** 요청 자체가 틀렸다(400) — LLM 실패(502)와 가른다. */
|
|
78
|
+
export class GraphWizardInputError extends Error { override name = 'GraphWizardInputError'; }
|
|
79
|
+
const GRAPH_ID = /^[a-z0-9-]+$/;
|
|
80
|
+
|
|
81
|
+
interface TemplateEntry { id: string; description: string; text: string; runnable: boolean; keywords?: string[] }
|
|
82
|
+
|
|
83
|
+
function walkYaml(dir: string, depth = 0): string[] {
|
|
84
|
+
if (depth > 3 || !existsSync(dir)) return [];
|
|
85
|
+
let names: string[];
|
|
86
|
+
try { names = readdirSync(dir); } catch { return []; }
|
|
87
|
+
return names.flatMap((name) => {
|
|
88
|
+
if (name.startsWith('.') || name === 'overlays' || name === 'catalog') return [];
|
|
89
|
+
const path = join(dir, name);
|
|
90
|
+
let dirLike = false;
|
|
91
|
+
try { dirLike = statSync(path).isDirectory(); } catch { return []; }
|
|
92
|
+
if (dirLike) return walkYaml(path, depth + 1);
|
|
93
|
+
return /\.ya?ml$/.test(name) && !/^recipes\.ya?ml$/.test(name) ? [path] : [];
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function graphIdOf(text: string): string | undefined {
|
|
98
|
+
try {
|
|
99
|
+
const raw = parseYaml(text) as { graph_id?: unknown; name?: unknown } | null;
|
|
100
|
+
if (raw && typeof raw === 'object') {
|
|
101
|
+
if (typeof raw.graph_id === 'string') return raw.graph_id;
|
|
102
|
+
if (typeof raw.name === 'string') return raw.name;
|
|
103
|
+
}
|
|
104
|
+
} catch { /* not a graph */ }
|
|
105
|
+
return undefined;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** 검증을 통과하는 하니스 템플릿만 «기반» 후보가 된다 — 깨진 표본을 보여주면 깨진 것을 배운다. */
|
|
109
|
+
export function listWizardTemplates(dir = defaultGraphsDir()): TemplateEntry[] {
|
|
110
|
+
// 원형이 맨 앞 — 사람이 말로 시키는 일의 뼈대(운영 템플릿은 개발·출시 루프라 말과 낱말이 안 겹친다).
|
|
111
|
+
const out: TemplateEntry[] = WIZARD_ARCHETYPES.map((a) => ({ id: a.id, description: a.description, text: a.text, runnable: true, keywords: a.keywords }));
|
|
112
|
+
const seen = new Set<string>(out.map((t) => t.id));
|
|
113
|
+
for (const file of walkYaml(dir)) {
|
|
114
|
+
let text: string;
|
|
115
|
+
try { text = readFileSync(file, 'utf8'); } catch { continue; }
|
|
116
|
+
let raw: Record<string, unknown> | null;
|
|
117
|
+
try { raw = parseYaml(text) as Record<string, unknown> | null; } catch { continue; }
|
|
118
|
+
if (!raw || typeof raw.graph_id !== 'string' || seen.has(raw.graph_id)) continue;
|
|
119
|
+
if (!validateGraphYaml('harness', text).ok) continue;
|
|
120
|
+
const loop = (raw.loop ?? {}) as { title?: unknown; description?: unknown };
|
|
121
|
+
const description = [loop.title, loop.description].filter((v): v is string => typeof v === 'string').join(' — ') || raw.graph_id;
|
|
122
|
+
const terminals = Array.isArray(raw.terminal_nodes) ? raw.terminal_nodes : [];
|
|
123
|
+
seen.add(raw.graph_id);
|
|
124
|
+
out.push({ id: raw.graph_id, description: description.slice(0, 160), text, runnable: terminals.every((t) => t === 'done' || t === 'failed') });
|
|
125
|
+
}
|
|
126
|
+
return out;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function grams(text: string): Set<string> {
|
|
130
|
+
const lower = text.toLowerCase();
|
|
131
|
+
const out = new Set<string>();
|
|
132
|
+
for (const word of lower.split(/[^\p{L}\p{N}]+/u).filter(Boolean)) {
|
|
133
|
+
if (/^[a-z0-9]+$/.test(word)) { if (word.length > 2) out.add(word); continue; }
|
|
134
|
+
for (let i = 0; i + 1 < word.length; i += 1) out.add(word.slice(i, i + 2));
|
|
135
|
+
}
|
|
136
|
+
return out;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export interface BasePick { template: TemplateEntry; reason: string }
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* 원형 키워드(낱말이 말 안에 있으면 3점) ⊕ 설명 한글 2-gram 겹침(1점)으로 가장 가까운 «실행 가능한» 템플릿.
|
|
143
|
+
* 동점이면 원형이 앞선다(목록 순서). 0점이면 «모아서 요약해 보내기» 원형. 이유를 함께 낸다.
|
|
144
|
+
*/
|
|
145
|
+
export function pickBaseTemplate(prompt: string, templates: readonly TemplateEntry[]): BasePick | undefined {
|
|
146
|
+
const lower = prompt.toLowerCase();
|
|
147
|
+
const wanted = grams(prompt);
|
|
148
|
+
let best: BasePick | undefined;
|
|
149
|
+
let bestScore = 0;
|
|
150
|
+
for (const template of templates) {
|
|
151
|
+
if (!template.runnable) continue;
|
|
152
|
+
const hits = (template.keywords ?? []).filter((k) => lower.includes(k));
|
|
153
|
+
const have = grams(`${template.id} ${template.description}`);
|
|
154
|
+
let overlap = 0;
|
|
155
|
+
for (const g of wanted) if (have.has(g)) overlap += 1;
|
|
156
|
+
const score = hits.length * 3 + overlap;
|
|
157
|
+
if (score > bestScore) {
|
|
158
|
+
bestScore = score;
|
|
159
|
+
best = { template, reason: hits.length ? `키워드 ${hits.slice(0, 5).join('·')}` : `설명 낱말 겹침 ${overlap}` };
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
if (best) return best;
|
|
163
|
+
const fallback = templates.find((t) => t.id === WIZARD_ARCHETYPES[0]!.id) ?? templates.find((t) => t.runnable);
|
|
164
|
+
return fallback ? { template: fallback, reason: '겹치는 낱말이 없어 기본 원형' } : undefined;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export function existingGraphIds(dirs: readonly string[]): Set<string> {
|
|
168
|
+
const ids = new Set<string>();
|
|
169
|
+
for (const dir of dirs) {
|
|
170
|
+
for (const file of walkYaml(dir)) {
|
|
171
|
+
try { const id = graphIdOf(readFileSync(file, 'utf8')); if (id) ids.add(id); } catch { /* unreadable */ }
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
return ids;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/** 슬러그 ⊕ 짧은 해시. 기존 id 와 겹치면 접미로 비켜 간다 — 덮어쓰지 않는다. */
|
|
178
|
+
export function wizardGraphId(prompt: string, hint: string | undefined, taken: ReadonlySet<string>): string {
|
|
179
|
+
const slugify = (s: string) => s.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '').split('-').filter(Boolean).slice(0, 5).join('-').slice(0, 40).replace(/-+$/, '');
|
|
180
|
+
const stem = slugify(hint ?? '') || slugify(prompt) || 'graph';
|
|
181
|
+
const hash = createHash('sha256').update(prompt).digest('hex').slice(0, 6);
|
|
182
|
+
let id = `${stem}-${hash}`;
|
|
183
|
+
for (let n = 2; taken.has(id); n += 1) id = `${stem}-${hash}-${n}`;
|
|
184
|
+
return id;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
export const HARNESS_EXAMPLE = `graph_id: example-digest
|
|
188
|
+
loop:
|
|
189
|
+
title: 예시 — 뉴스를 모아 요약하고 승인 뒤 보낸다
|
|
190
|
+
description: 뉴스를 검색해 요약하고 품질을 본 뒤 사람 승인을 받아 텔레그램으로 보낸다
|
|
191
|
+
trigger:
|
|
192
|
+
cron: "0 8 * * *"
|
|
193
|
+
version: 1
|
|
194
|
+
entry_node: collect
|
|
195
|
+
terminal_nodes: [done, failed]
|
|
196
|
+
nodes:
|
|
197
|
+
- { node_id: collect, kind: agent, recipe: 'cmd:collect', max_visits: 1 } # 뉴스 수집 | web-search | 오늘 AI 뉴스
|
|
198
|
+
- { node_id: summarize, kind: agent, recipe: 'cmd:summarize', max_visits: 2 } # 요약 | summarize | 핵심 5줄 한국어 요약
|
|
199
|
+
- { node_id: check, kind: judge, recipe: 'cmd:check', max_visits: 2 } # 요약 품질 판정 | check | 사실에 맞고 5줄 이내
|
|
200
|
+
- { node_id: approve, kind: hitl, recipe: 'approval:approve', max_visits: 1 } # 발송 승인 | approval
|
|
201
|
+
- { node_id: send, kind: agent, recipe: 'cmd:send', max_visits: 1 } # 텔레그램 발송 | telegram-send
|
|
202
|
+
- { node_id: done, kind: gate, recipe: none, max_visits: 1 } # 완료
|
|
203
|
+
- { node_id: failed, kind: gate, recipe: none, max_visits: 1 } # 실패
|
|
204
|
+
edges:
|
|
205
|
+
- { from: collect, on: outcome, map: { ok: summarize, fail: failed } }
|
|
206
|
+
- { from: summarize, on: outcome, map: { ok: check, fail: failed } }
|
|
207
|
+
- { from: check, on: outcome, map: { ok: approve, rework: summarize, fail: failed } }
|
|
208
|
+
- { from: approve, on: outcome, map: { ok: send, fail: failed } }
|
|
209
|
+
- { from: send, on: outcome, map: { ok: done, fail: failed } }
|
|
210
|
+
`;
|
|
211
|
+
|
|
212
|
+
export const WORKFLOW_EXAMPLE = `name: example-digest
|
|
213
|
+
description: 자료를 모아 요약하고 승인 뒤 보낸다
|
|
214
|
+
nodes:
|
|
215
|
+
- id: collect
|
|
216
|
+
bash: "echo collect"
|
|
217
|
+
- id: summarize
|
|
218
|
+
prompt: "다음을 다섯 줄로 요약하라: $collect.output"
|
|
219
|
+
depends_on: [collect]
|
|
220
|
+
- id: approve
|
|
221
|
+
approval:
|
|
222
|
+
message: "요약을 보낼까요?"
|
|
223
|
+
depends_on: [summarize]
|
|
224
|
+
- id: send
|
|
225
|
+
bash: "echo send"
|
|
226
|
+
depends_on: [approve]
|
|
227
|
+
`;
|
|
228
|
+
|
|
229
|
+
function harnessRules(kinds: string): string {
|
|
230
|
+
return [
|
|
231
|
+
'스키마(하니스 실행 그래프 · YAML):',
|
|
232
|
+
'- 최상위: graph_id, loop{title, description, trigger{cron|events}}, version: 1, entry_node, terminal_nodes: [done, failed], nodes, edges.',
|
|
233
|
+
`- nodes[]: { node_id(kebab-case 영문), kind, recipe, max_visits(1 이상 정수) }. kind 는 이것만: ${kinds}.`,
|
|
234
|
+
"- recipe 는 셋 중 하나뿐: 'none'(done·failed 같은 끝 노드), 'cmd:<kebab-id>'(실행 단계), 'approval:<kebab-id>'(사람 승인 — kind: hitl).",
|
|
235
|
+
'- 끝 노드는 정확히 done 과 failed 두 개(kind: gate, recipe: none). 다른 끝 노드를 만들지 마라.',
|
|
236
|
+
'- edges[]: { from, on: outcome, map: { ok: <다음>, fail: failed, <다른 결과>: <노드> } } — 모든 노드가 entry 에서 닿아야 하고, done·failed 외 노드는 나가는 간선이 있어야 한다.',
|
|
237
|
+
'- 되돌아가는 고리(재작업)는 map 의 다른 결과로 앞 노드를 가리키고, 그 노드의 max_visits 를 2~3 으로.',
|
|
238
|
+
'- 다른 키(contract, progress 등)는 쓰지 마라.',
|
|
239
|
+
'',
|
|
240
|
+
'노드 줄 형식(필수): 노드는 한 줄 흐름식 `- { node_id: …, kind: …, recipe: …, max_visits: … }` 으로 쓰고, 줄 끝 주석에 `# <한국어 이름> | <단계> | <인자>` 를 단다.',
|
|
241
|
+
'- 한국어 이름은 사용자가 화면에서 보는 노드 이름이다(예: 뉴스 수집). 끝 노드는 `# 완료` · `# 실패`. 승인 노드는 `# <이름> | approval`.',
|
|
242
|
+
"- cmd: 노드의 <단계> 는 아래 서재 중 하나다. 맞는 것이 없을 때만 custom(외부 연동 · 실행 시 «미구현»으로 실패). 인자에 `|`·`#` 을 쓰지 마라.",
|
|
243
|
+
...WIZARD_STEPS.map((step) => ` · ${step.id} — ${step.use} (결과: ${step.outcomes.join('/')})`),
|
|
244
|
+
'- 간선 map 키는 그 단계가 내는 결과를 쓴다(예: check → ok/rework, retry-gate → retry/give-up, gh-pr-review → ok/must-fix).',
|
|
245
|
+
'- «실패하면 다시 시도하고 N번 넘으면 알려줘» 패턴(간선을 늘리지 않는다): 재시도는 «단계 안»에서 한다 — 각 cmd 노드 주석 끝에 `| retry=N` 을 붙인다(인자가 없으면 `# 이름 | 단계 | | retry=N`). 재시도 노드·되돌이 간선을 만들지 마라. 알림 노드(notify-me) «하나»만 더하고, 실행 노드들의 `fail: failed` 를 `fail: <알림 노드>` 로 바꾸고, 알림 노드는 `{ ok: failed, fail: failed }`.',
|
|
246
|
+
].join('\n');
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/** 워크플로 어휘는 `elanous wf synth` 의 시스템 프롬프트를 «그대로» 재사용한다(노드 변형·본문 모양·예시). 출력 형식만 이 마법사 것을 쓴다. */
|
|
250
|
+
function workflowRules(kinds: string): string {
|
|
251
|
+
return [
|
|
252
|
+
'--- wf synth 어휘(재사용 · 그 안의 «# Output» 절은 무시하고 아래 출력 형식을 따른다) ---',
|
|
253
|
+
WORKFLOW_SYNTH_SYSTEM_PROMPT.trim(),
|
|
254
|
+
'--- 끝 ---',
|
|
255
|
+
'스키마(워크플로 · YAML):',
|
|
256
|
+
'- 최상위: name(kebab-case), description, nodes.',
|
|
257
|
+
`- nodes[]: { id, <종류 키 하나>, depends_on?: [id…] }. 종류: ${kinds}. 흔한 것 = prompt: "<지시>", bash: "<명령>", approval: { message }.`,
|
|
258
|
+
'- 순환 금지 · depends_on 은 선언된 id 만.',
|
|
259
|
+
].join('\n');
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
function extractYaml(text: string): string {
|
|
263
|
+
const fence = /```(?:ya?ml)?\s*\n([\s\S]*?)```/i.exec(text);
|
|
264
|
+
const body = fence ? fence[1]! : text.split('\n').filter((line) => !/^\s*(BASE|SLUG|SUMMARY)\s*:/.test(line)).join('\n');
|
|
265
|
+
return body.trim() + '\n';
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function lineValue(text: string, key: string): string | undefined {
|
|
269
|
+
const match = new RegExp(`^\\s*${key}\\s*:\\s*(.+)$`, 'mi').exec(text.split('```')[0] ?? '');
|
|
270
|
+
return match?.[1]?.trim().replace(/^['"`]|['"`]$/g, '') || undefined;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/** 선언된 id 줄만 바꾼다 — 나머지 바이트(주석 포함)는 그대로. */
|
|
274
|
+
function forceId(yaml: string, kind: GraphKind, id: string): string {
|
|
275
|
+
const key = kind === 'harness' ? 'graph_id' : 'name';
|
|
276
|
+
const re = new RegExp(`^([ \\t]*${key}:[ \\t]*)([^\\n#]*?)([ \\t]*(?:#.*)?)$`, 'm');
|
|
277
|
+
return re.test(yaml) ? yaml.replace(re, `$1${id}$3`) : `${key}: ${id}\n${yaml}`;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function fmtIssue(issue: unknown): string {
|
|
281
|
+
if (typeof issue === 'string') return issue;
|
|
282
|
+
if (issue && typeof issue === 'object') {
|
|
283
|
+
const { path, message } = issue as { path?: unknown; message?: unknown };
|
|
284
|
+
if (typeof message === 'string') return typeof path === 'string' && path ? `${path}: ${message}` : message;
|
|
285
|
+
}
|
|
286
|
+
return JSON.stringify(issue);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/** API 검증기 ⊕ (하니스) `elanous graph run` 이 실제로 받는 모양 — 통과해도 못 도는 그래프를 «ok» 라 하지 않는다. */
|
|
290
|
+
export function validateWizardYaml(kind: GraphKind, yaml: string): string[] {
|
|
291
|
+
const checked = validateGraphYaml(kind, yaml);
|
|
292
|
+
const issues = checked.errors.map(fmtIssue);
|
|
293
|
+
if (kind !== 'harness' || issues.length > 0) return issues;
|
|
294
|
+
const raw = parseYaml(yaml) as { terminal_nodes?: unknown; nodes?: Array<{ node_id?: unknown; kind?: unknown; recipe?: unknown }> };
|
|
295
|
+
const terminals = Array.isArray(raw.terminal_nodes) ? raw.terminal_nodes : [];
|
|
296
|
+
for (const t of terminals) if (t !== 'done' && t !== 'failed') issues.push(`terminal_nodes: '${String(t)}' — 끝 노드는 done·failed 만 쓴다`);
|
|
297
|
+
for (const node of raw.nodes ?? []) {
|
|
298
|
+
const recipe = typeof node.recipe === 'string' ? node.recipe : '';
|
|
299
|
+
if (recipe !== 'none' && !/^(cmd|approval):[a-z0-9][a-z0-9-]*$/.test(recipe)) {
|
|
300
|
+
issues.push(`nodes/${String(node.node_id)}/recipe: '${recipe}' — none · cmd:<id> · approval:<id> 중 하나여야 한다`);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
return issues;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/** 노드 줄 끝 주석 `# 이름 | 단계 | 인자` 를 읽는다. 흐름식 노드 줄에서만. */
|
|
307
|
+
export function parseNodeAnnotations(yaml: string): Record<string, WizardNodeStep> {
|
|
308
|
+
const out: Record<string, WizardNodeStep> = {};
|
|
309
|
+
for (const line of yaml.split('\n')) {
|
|
310
|
+
const match = /node_id:\s*['"]?([a-z0-9][a-z0-9-]*)['"]?[^#]*\}\s*#\s*(.+)$/.exec(line);
|
|
311
|
+
if (!match) continue;
|
|
312
|
+
const parts = match[2]!.split('|').map((part) => part.trim());
|
|
313
|
+
let retries: number | undefined;
|
|
314
|
+
const retryAt = parts.findIndex((part, index) => index >= 2 && /^retry\s*=\s*\d+$/i.test(part));
|
|
315
|
+
if (retryAt >= 0) retries = Number.parseInt(parts.splice(retryAt, 1)[0]!.split('=')[1]!, 10);
|
|
316
|
+
const [label, step, ...rest] = parts;
|
|
317
|
+
const arg = rest.join('|').trim();
|
|
318
|
+
out[match[1]!] = { ...(label ? { label } : {}), ...(step ? { step } : {}), ...(arg ? { arg } : {}), ...(retries ? { retries } : {}) };
|
|
319
|
+
}
|
|
320
|
+
return out;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/** cmd 노드마다 서재 단계가 있는지 — 없으면 «이름만 있는» 노드라 실제로 못 돈다. */
|
|
324
|
+
export function stepIssues(yaml: string, steps: Record<string, WizardNodeStep>): string[] {
|
|
325
|
+
const raw = parseYaml(yaml) as {
|
|
326
|
+
nodes?: Array<{ node_id?: unknown; kind?: unknown; recipe?: unknown }>;
|
|
327
|
+
edges?: Array<{ from?: unknown; to?: unknown; map?: Record<string, unknown> }>;
|
|
328
|
+
};
|
|
329
|
+
const issues: string[] = [];
|
|
330
|
+
const recipeOwner = new Map<string, string>();
|
|
331
|
+
const kindOf = new Map((raw.nodes ?? []).map((n) => [String(n.node_id), String(n.kind)]));
|
|
332
|
+
for (const node of raw.nodes ?? []) {
|
|
333
|
+
const id = String(node.node_id);
|
|
334
|
+
const recipe = typeof node.recipe === 'string' ? node.recipe : '';
|
|
335
|
+
const meta = steps[id];
|
|
336
|
+
// recipes.yaml 은 recipe 이름으로 한 줄 — 두 노드가 같은 이름을 쓰면 둘째 노드의 단계·인자가 사라진다.
|
|
337
|
+
if (recipe !== 'none' && recipeOwner.has(recipe)) issues.push(`nodes/${id}: recipe '${recipe}' 를 ${recipeOwner.get(recipe)} 도 쓴다 — 노드마다 고유한 recipe 이름(보통 cmd:<node_id>)`);
|
|
338
|
+
else recipeOwner.set(recipe, id);
|
|
339
|
+
if ((meta?.retries ?? 0) > WIZARD_MAX_RETRIES) issues.push(`nodes/${id}: retry=${meta!.retries} — 최대 ${WIZARD_MAX_RETRIES}`);
|
|
340
|
+
// 머지는 사람 승인 «뒤»에만 — 들어오는 간선이 전부 hitl 노드에서 와야 한다.
|
|
341
|
+
if (meta?.step === 'gh-pr-merge') {
|
|
342
|
+
const sources = (raw.edges ?? []).filter((e) => e.to === id || Object.values(e.map ?? {}).includes(id)).map((e) => String(e.from));
|
|
343
|
+
if (sources.length === 0 || sources.some((from) => kindOf.get(from) !== 'hitl')) issues.push(`nodes/${id}: PR 머지 앞에는 사람 승인(kind: hitl) 노드만 올 수 있다`);
|
|
344
|
+
}
|
|
345
|
+
if (!meta?.label) issues.push(`nodes/${id}: 줄 끝 주석 \`# <한국어 이름> | …\` 이 없다`);
|
|
346
|
+
if (recipe.startsWith('cmd:') && !(meta?.step && WIZARD_STEP_IDS.has(meta.step))) {
|
|
347
|
+
issues.push(`nodes/${id}: cmd 노드의 단계가 서재(${[...WIZARD_STEP_IDS].join(', ')})에 없다 — 주석 \`# 이름 | <단계> | <인자>\``);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
return issues;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
/** 실제 recipes 로 `graph run --dry-run` 을 걸어 «도는가»를 잰다(명령 실행 0). */
|
|
354
|
+
export async function dryRunWizardGraph(yaml: string, recipes: string): Promise<{ status: string; path: string[]; error?: string }> {
|
|
355
|
+
const { mkdtempSync, writeFileSync, rmSync } = await import('node:fs');
|
|
356
|
+
const { tmpdir } = await import('node:os');
|
|
357
|
+
const { runGraph } = await import('../graph-runner/runner.js');
|
|
358
|
+
const dir = mkdtempSync(join(tmpdir(), 'elanous-graph-wizard-'));
|
|
359
|
+
try {
|
|
360
|
+
writeFileSync(join(dir, 'graph.yaml'), yaml);
|
|
361
|
+
writeFileSync(join(dir, 'recipes.yaml'), recipes);
|
|
362
|
+
const state = await runGraph(join(dir, 'graph.yaml'), { dryRun: true, deps: { root: join(dir, 'state'), log: () => {} } });
|
|
363
|
+
return { status: state.status, path: state.path };
|
|
364
|
+
} catch (error) {
|
|
365
|
+
return { status: 'error', path: [], error: error instanceof Error ? error.message : String(error) };
|
|
366
|
+
} finally { rmSync(dir, { recursive: true, force: true }); }
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
interface Shape { nodes: string[]; labels: Record<string, string>; retries: Record<string, number>; edges: Map<string, string> }
|
|
370
|
+
function shapeOf(kind: GraphKind, yaml: string | undefined): Shape {
|
|
371
|
+
const empty: Shape = { nodes: [], labels: {}, retries: {}, edges: new Map() };
|
|
372
|
+
if (!yaml) return empty;
|
|
373
|
+
try {
|
|
374
|
+
const raw = parseYaml(yaml) as Record<string, unknown>;
|
|
375
|
+
const nodes = Array.isArray(raw.nodes) ? raw.nodes as Array<Record<string, unknown>> : [];
|
|
376
|
+
if (kind === 'workflow') {
|
|
377
|
+
const edges = new Map<string, string>();
|
|
378
|
+
for (const n of nodes) for (const d of Array.isArray(n.depends_on) ? n.depends_on : []) edges.set(`${String(d)}>${String(n.id)}`, String(n.id));
|
|
379
|
+
return { ...empty, nodes: nodes.map((n) => String(n.id)), edges };
|
|
380
|
+
}
|
|
381
|
+
// 간선 = (from, 결과) → to. 같은 칸의 목적지만 바뀌면 «재연결»이지 새 간선이 아니다.
|
|
382
|
+
const edges = new Map<string, string>();
|
|
383
|
+
for (const e of Array.isArray(raw.edges) ? raw.edges as Array<Record<string, unknown>> : []) {
|
|
384
|
+
if (typeof e.to === 'string') { edges.set(`${String(e.from)}:*`, e.to); continue; }
|
|
385
|
+
const map = e.map && typeof e.map === 'object' ? e.map as Record<string, unknown> : {};
|
|
386
|
+
for (const [outcome, to] of Object.entries(map)) edges.set(`${String(e.from)}:${outcome}`, String(to));
|
|
387
|
+
}
|
|
388
|
+
const notes = parseNodeAnnotations(yaml);
|
|
389
|
+
return {
|
|
390
|
+
nodes: nodes.map((n) => String(n.node_id)), edges,
|
|
391
|
+
labels: Object.fromEntries(Object.entries(notes).filter(([, v]) => v.label).map(([k, v]) => [k, v.label!])),
|
|
392
|
+
retries: Object.fromEntries(Object.entries(notes).filter(([, v]) => v.retries).map(([k, v]) => [k, v.retries!])),
|
|
393
|
+
};
|
|
394
|
+
} catch { return empty; }
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
/** 결정적 변경 요약 — LLM 의 한 줄 요약이 없거나 틀려도 «무엇이 바뀌었나»는 센 값으로 남는다. 노드 이름은 한국어 라벨. */
|
|
398
|
+
export function summarizeChange(kind: GraphKind, before: string | undefined, after: string): string {
|
|
399
|
+
const a = shapeOf(kind, before);
|
|
400
|
+
const b = shapeOf(kind, after);
|
|
401
|
+
const name = (id: string) => b.labels[id] ?? a.labels[id] ?? id;
|
|
402
|
+
if (!before) return `새 그래프: 노드 ${b.nodes.length}개 (${b.nodes.map(name).join(' → ')}) · 간선 ${b.edges.size}개`;
|
|
403
|
+
const added = b.nodes.filter((n) => !a.nodes.includes(n));
|
|
404
|
+
const removed = a.nodes.filter((n) => !b.nodes.includes(n));
|
|
405
|
+
let edgesAdded = 0;
|
|
406
|
+
let rewired = 0;
|
|
407
|
+
for (const [key, to] of b.edges) {
|
|
408
|
+
if (!a.edges.has(key)) edgesAdded += 1;
|
|
409
|
+
else if (a.edges.get(key) !== to) rewired += 1;
|
|
410
|
+
}
|
|
411
|
+
const edgesRemoved = [...a.edges.keys()].filter((key) => !b.edges.has(key)).length;
|
|
412
|
+
const retryChanged = b.nodes.filter((n) => (b.retries[n] ?? 0) !== (a.retries[n] ?? 0));
|
|
413
|
+
const parts: string[] = [];
|
|
414
|
+
if (added.length) parts.push(`노드 ${added.length}개 추가: ${added.map(name).join(' → ')}`);
|
|
415
|
+
if (removed.length) parts.push(`노드 ${removed.length}개 삭제: ${removed.map(name).join(', ')}`);
|
|
416
|
+
if (edgesAdded) parts.push(`간선 ${edgesAdded}개 추가`);
|
|
417
|
+
if (edgesRemoved) parts.push(`간선 ${edgesRemoved}개 삭제`);
|
|
418
|
+
if (rewired) parts.push(`간선 ${rewired}개 재연결`);
|
|
419
|
+
if (retryChanged.length) parts.push(`재시도 설정 ${retryChanged.length}개 노드(${retryChanged.map((n) => `${name(n)} ${b.retries[n] ?? 0}회`).join(', ')})`);
|
|
420
|
+
return parts.length ? parts.join(' · ') : '구조 변경 없음';
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
async function defaultCallLLM(prompt: string): Promise<string> {
|
|
424
|
+
const [{ PROVIDERS, streamLLM }, { getUserConfig }, { graphGrowthLlmOptions }] = await Promise.all([
|
|
425
|
+
import('../llm.js'), import('../user-config.js'), import('../graph-runner/graph-grow-llm.js'),
|
|
426
|
+
]);
|
|
427
|
+
const selected = graphGrowthLlmOptions(getUserConfig());
|
|
428
|
+
return streamLLM([{ role: 'user', content: prompt }], () => {}, {
|
|
429
|
+
...(selected.model ? { model: selected.model, provider: PROVIDERS[selected.provider!] } : {}),
|
|
430
|
+
reasoningEffort: 'low',
|
|
431
|
+
});
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
function buildPrompt(input: {
|
|
435
|
+
req: GraphWizardRequest; kind: GraphKind; id: string; base?: TemplateEntry; templates: readonly TemplateEntry[];
|
|
436
|
+
previous?: { yaml: string; issues: string[] };
|
|
437
|
+
evidence?: string;
|
|
438
|
+
}): string {
|
|
439
|
+
const { req, kind, id, base, templates, previous, evidence } = input;
|
|
440
|
+
const kinds = listNodeKinds(kind).map((k) => k.kind).join(', ');
|
|
441
|
+
const editing = typeof req.currentYaml === 'string' && req.currentYaml.trim() !== '';
|
|
442
|
+
const lines: string[] = [
|
|
443
|
+
'너는 엘라누스 «그래프 마법사»다. 사용자의 말을 실행 그래프 YAML 로 짓는다.',
|
|
444
|
+
kind === 'harness' ? harnessRules(kinds) : workflowRules(kinds),
|
|
445
|
+
`- ${kind === 'harness' ? 'graph_id' : 'name'} 는 정확히 "${id}" 로 쓴다.`,
|
|
446
|
+
'- 노드 id 는 사용자의 업무 단계를 드러내는 짧은 영문 kebab-case(예: collect-news, summarize, send-telegram). 단계 수는 3~10개.',
|
|
447
|
+
'- 사용자가 말한 것만 짓는다. 승인(hitl)·품질 판정(check)·재시도·알림은 사용자가 말했을 때만 넣는다 — 예외는 PR 머지·결제·공개 게시(외부 공개) 앞의 승인뿐이다. 텔레그램 발송·나에게 알림 앞에는 말하지 않은 승인을 넣지 마라.',
|
|
448
|
+
'',
|
|
449
|
+
'예시(사용자가 «품질을 보고, 내가 승인하면 보내»라고 «말했을 때»의 모양 — 말하지 않은 단계는 넣지 않는다):',
|
|
450
|
+
'```yaml', kind === 'harness' ? HARNESS_EXAMPLE.trim() : WORKFLOW_EXAMPLE.trim(), '```',
|
|
451
|
+
];
|
|
452
|
+
if (kind === 'harness' && !editing) {
|
|
453
|
+
lines.push('', '기존 템플릿 목록(id — 설명):', ...templates.filter((t) => t.runnable).slice(0, 30).map((t) => `- ${t.id} — ${t.description}`));
|
|
454
|
+
if (base) lines.push('', `가장 가까운 템플릿(${base.id}) — 구조를 참고하라:`, '```yaml', base.text.trim(), '```');
|
|
455
|
+
}
|
|
456
|
+
if (req.history?.length) {
|
|
457
|
+
lines.push('', '지금까지의 대화:', ...req.history.slice(-10).map((t) => `${t.role === 'user' ? '사용자' : '마법사'}: ${t.text.slice(0, 500)}`));
|
|
458
|
+
}
|
|
459
|
+
if (editing) {
|
|
460
|
+
lines.push('', '지금 그래프(이것을 고친다 — 바뀌지 않는 노드의 id 는 그대로 둔다):', '```yaml', req.currentYaml!.trim(), '```',
|
|
461
|
+
'', `사용자의 수정 지시: ${req.prompt}`,
|
|
462
|
+
'최소 변경 규칙: 지시가 요구하는 노드·간선만 더하거나 바꾼다. 나머지 노드 줄(id·kind·recipe·주석)과 간선은 글자 그대로 둔다. id 를 바꾸지 마라. loop.title·description 은 바뀐 내용만 반영한다.',
|
|
463
|
+
'고친 «전체» YAML 을 돌려준다.');
|
|
464
|
+
} else {
|
|
465
|
+
lines.push('', `사용자의 말: ${req.prompt}`);
|
|
466
|
+
}
|
|
467
|
+
if (evidence) lines.push('', '선택한 설치 팩의 검색 근거:', evidence,
|
|
468
|
+
`지식(RAG) 노드를 cmd 단계로 넣고 # 지식 검색 | knowledge-rag | ${req.packId} 주석을 붙인다. 다음 산출 단계는 이 노드의 근거를 사용한다. 실행 입력 input.query 가 있으면 질문으로 검색하고 없으면 설치 팩 전체 색인에서 가져온다. 근거에 없는 사실을 만들지 마라.`);
|
|
469
|
+
if (previous) {
|
|
470
|
+
lines.push('', '직전 시도가 검증에 실패했다. 아래 문제를 «전부» 고쳐 전체 YAML 을 다시 써라:', ...previous.issues.map((i) => `- ${i}`),
|
|
471
|
+
'직전 YAML:', '```yaml', previous.yaml.trim(), '```');
|
|
472
|
+
}
|
|
473
|
+
lines.push('', '출력 형식(이 순서 · 다른 설명 금지):',
|
|
474
|
+
...(editing || kind !== 'harness' ? [] : ['BASE: <참고한 템플릿 id 또는 none>']),
|
|
475
|
+
'SLUG: <이 그래프를 나타내는 영문 kebab-case 2~4 낱말>',
|
|
476
|
+
'SUMMARY: <무엇을 만들었/바꿨는지 한국어 한 줄 — 예: 노드 2개 추가: 요약 → 텔레그램 발송 · 간선 2개>',
|
|
477
|
+
'```yaml', '<전체 YAML>', '```');
|
|
478
|
+
return lines.join('\n');
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
export async function generateGraphFromPrompt(req: GraphWizardRequest, deps: GraphWizardDeps = {}): Promise<GraphWizardResult> {
|
|
482
|
+
const kind: GraphKind = req.kind === 'workflow' ? 'workflow' : 'harness';
|
|
483
|
+
const prompt = typeof req.prompt === 'string' ? req.prompt.trim() : '';
|
|
484
|
+
if (!prompt) throw new GraphWizardInputError('prompt is required');
|
|
485
|
+
if (req.packId !== undefined && kind !== 'harness') throw new GraphWizardInputError('pack selection requires a harness graph');
|
|
486
|
+
let evidence: string | undefined;
|
|
487
|
+
if (req.packId !== undefined) {
|
|
488
|
+
if (!parsePackIdString(req.packId) || !wizardInstalledPacks(deps).some(pack => pack.id === req.packId))
|
|
489
|
+
throw new GraphWizardInputError(`pack not installed: ${req.packId}`);
|
|
490
|
+
const cards = (deps.searchPack ?? queryInstalledPack)(req.packId, prompt);
|
|
491
|
+
if (!cards.length) throw new GraphWizardInputError(`no knowledge found in ${req.packId} for: ${prompt}`);
|
|
492
|
+
evidence = cards.slice(0, 5).map(card => `[${card.ref}] ${card.title}: ${card.body.slice(0, 1200)}`).join('\n');
|
|
493
|
+
}
|
|
494
|
+
const editing = typeof req.currentYaml === 'string' && req.currentYaml.trim() !== '';
|
|
495
|
+
const graphsDir = deps.graphsDir ?? defaultGraphsDir();
|
|
496
|
+
const templates = kind === 'harness' ? listWizardTemplates(graphsDir) : [];
|
|
497
|
+
const pick = kind === 'harness' && !editing ? pickBaseTemplate(prompt, templates) : undefined;
|
|
498
|
+
const keywordBase = pick?.template;
|
|
499
|
+
let baseReason = pick?.reason;
|
|
500
|
+
const taken = deps.existingIds?.() ?? existingGraphIds([graphsDir, join((await import('../autopilot/state-paths.js')).elanousStateRoot(), 'graphs')]);
|
|
501
|
+
// 편집이면 지금 그래프의 id 를 지킨다 — 캔버스와 저장 경로가 그 id 를 쥐고 있다.
|
|
502
|
+
const currentId = editing ? graphIdOf(req.currentYaml!) : undefined;
|
|
503
|
+
// 편집인데 id 를 지킬 수 없으면(없음·형식 밖) 새 id 로 바꿔 «다른 그래프»를 돌려주지 않고 거절한다.
|
|
504
|
+
if (editing && !(currentId && GRAPH_ID.test(currentId))) {
|
|
505
|
+
debug.log('graph.wizard', 'rejected', { reason: 'current-id-invalid', kind });
|
|
506
|
+
throw new GraphWizardInputError(`currentYaml has no valid ${kind === 'harness' ? 'graph_id' : 'name'} ([a-z0-9-])`);
|
|
507
|
+
}
|
|
508
|
+
let id = editing ? currentId! : wizardGraphId(prompt, undefined, taken);
|
|
509
|
+
const callLLM = deps.callLLM ?? defaultCallLLM;
|
|
510
|
+
const maxAttempts = Math.max(1, deps.maxAttempts ?? GRAPH_WIZARD_MAX_ATTEMPTS);
|
|
511
|
+
let base = keywordBase?.id;
|
|
512
|
+
let yaml = '';
|
|
513
|
+
let issues: string[] = [];
|
|
514
|
+
let llmSummary: string | undefined;
|
|
515
|
+
let attempts = 0;
|
|
516
|
+
let steps: Record<string, WizardNodeStep> = {};
|
|
517
|
+
let recipes: string | undefined;
|
|
518
|
+
let dryRun: { status: string; path: string[] } | undefined;
|
|
519
|
+
const startedAt = Date.now();
|
|
520
|
+
debug.log('graph.wizard', 'start', { kind, editing, base, id, prompt: prompt.slice(0, 300), historyTurns: req.history?.length ?? 0 });
|
|
521
|
+
for (attempts = 1; attempts <= maxAttempts; attempts += 1) {
|
|
522
|
+
let text: string;
|
|
523
|
+
try {
|
|
524
|
+
text = await callLLM(buildPrompt({ req: { ...req, prompt }, kind, id, base: keywordBase, templates, evidence,
|
|
525
|
+
...(attempts > 1 ? { previous: { yaml, issues } } : {}) }));
|
|
526
|
+
} catch (error) {
|
|
527
|
+
debug.log('graph.wizard', 'error', { id, kind, attempt: attempts, base, message: error instanceof Error ? error.message : String(error) });
|
|
528
|
+
throw error;
|
|
529
|
+
}
|
|
530
|
+
if (attempts === 1 && !editing) {
|
|
531
|
+
const picked = lineValue(text, 'BASE');
|
|
532
|
+
if (picked && picked !== base && templates.some((t) => t.id === picked)) { base = picked; baseReason = `LLM 재선택 (키워드 후보 ${keywordBase?.id ?? '-'})`; }
|
|
533
|
+
const slug = lineValue(text, 'SLUG');
|
|
534
|
+
if (slug && !currentId) id = wizardGraphId(prompt, slug, taken);
|
|
535
|
+
}
|
|
536
|
+
llmSummary = lineValue(text, 'SUMMARY') ?? llmSummary;
|
|
537
|
+
yaml = forceId(extractYaml(text), kind, id);
|
|
538
|
+
try { issues = validateWizardYaml(kind, yaml); }
|
|
539
|
+
catch (error) { issues = [`검증 실패: ${error instanceof Error ? error.message : String(error)}`]; }
|
|
540
|
+
steps = kind === 'harness' ? parseNodeAnnotations(yaml) : {};
|
|
541
|
+
if (req.packId && issues.length === 0) {
|
|
542
|
+
const raw = parseYaml(yaml) as { nodes?: Array<{ node_id: string; recipe: string }>; edges?: Array<{ from: string; map?: Record<string, string> }> };
|
|
543
|
+
const rag = (raw.nodes ?? []).find(node => node.recipe.startsWith('cmd:') && steps[node.node_id]?.step === 'knowledge-rag' && steps[node.node_id]?.arg === req.packId);
|
|
544
|
+
if (!rag || !(raw.edges ?? []).some(edge => edge.from === rag.node_id &&
|
|
545
|
+
typeof edge.map?.ok === 'string' && !['done', 'failed'].includes(edge.map.ok) &&
|
|
546
|
+
(raw.nodes ?? []).some(node => node.node_id === edge.map!.ok && node.recipe.startsWith('cmd:'))))
|
|
547
|
+
issues.push(`선택한 팩 ${req.packId} 의 knowledge-rag 노드를 산출 단계에 연결하라`);
|
|
548
|
+
}
|
|
549
|
+
recipes = undefined;
|
|
550
|
+
dryRun = undefined;
|
|
551
|
+
if (kind === 'harness' && issues.length === 0) {
|
|
552
|
+
issues = stepIssues(yaml, steps);
|
|
553
|
+
const nodes = ((parseYaml(yaml) as { nodes?: Array<{ node_id?: unknown; recipe?: unknown }> }).nodes ?? [])
|
|
554
|
+
.map((n) => ({ nodeId: String(n.node_id), recipe: typeof n.recipe === 'string' ? n.recipe : '' }));
|
|
555
|
+
recipes = recipesYamlFor(nodes, steps);
|
|
556
|
+
const walked = await dryRunWizardGraph(yaml, recipes);
|
|
557
|
+
dryRun = { status: walked.status, path: walked.path };
|
|
558
|
+
if (walked.status !== 'done') issues.push(`graph run --dry-run: ${walked.status}${walked.error ? ` — ${walked.error}` : ''} (경로 ${walked.path.join(' → ')})`);
|
|
559
|
+
}
|
|
560
|
+
debug.log('graph.wizard', 'attempt', { id, attempt: attempts, base, ok: issues.length === 0, issues: issues.slice(0, 10) });
|
|
561
|
+
if (issues.length === 0) break;
|
|
562
|
+
}
|
|
563
|
+
attempts = Math.min(attempts, maxAttempts);
|
|
564
|
+
const ok = issues.length === 0;
|
|
565
|
+
const counted = summarizeChange(kind, editing ? req.currentYaml : undefined, yaml);
|
|
566
|
+
const summary = llmSummary ? `${llmSummary} (${counted})` : counted;
|
|
567
|
+
debug.log('graph.wizard', ok ? 'validated' : 'failed', { id, kind, base, attempts, editing, issues: issues.slice(0, 10), summary, dryRun: dryRun?.status, ms: Date.now() - startedAt });
|
|
568
|
+
const labels = Object.fromEntries(Object.entries(steps).filter(([, v]) => v.label).map(([k, v]) => [k, v.label!]));
|
|
569
|
+
return { ok, id, yaml, ...(base ? { base } : {}), ...(base && baseReason ? { baseReason } : {}), issues, attempts, summary, labels, steps, ...(recipes ? { recipes } : {}), ...(dryRun ? { dryRun } : {}) };
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
/** v1 이름 유지 — 생성 YAML 의 노드 주석(단계·인자)으로 실제 recipes.yaml 을 낸다(v1 의 «미구현» 자리표시를 대체). */
|
|
573
|
+
export function stubRecipesFor(yaml: string): string {
|
|
574
|
+
const raw = parseYaml(yaml) as { nodes?: Array<{ node_id?: unknown; recipe?: unknown }> };
|
|
575
|
+
const nodes = (raw.nodes ?? []).map((n) => ({ nodeId: String(n.node_id), recipe: typeof n.recipe === 'string' ? n.recipe : '' }));
|
|
576
|
+
return recipesYamlFor(nodes, parseNodeAnnotations(yaml));
|
|
577
|
+
}
|