elanous 0.2.20 → 0.2.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/apps/pwa/out/404/index.html +1 -1
- package/apps/pwa/out/404.html +1 -1
- package/apps/pwa/out/_next/static/aBi6EyMtLsrl7J8eH4kF7/_buildManifest.js +1 -0
- package/apps/pwa/out/_next/static/chunks/088d8e8a.8069543d082bec12.js +160 -0
- package/apps/pwa/out/_next/static/chunks/1109.78a7a3288c0259d9.js +148 -0
- package/apps/pwa/out/_next/static/chunks/115.1c6690b8604f81c4.js +32 -0
- package/apps/pwa/out/_next/static/chunks/119fc678.6f591cdb84ca5791.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1262.76a3f08e67c5f816.js +309 -0
- package/apps/pwa/out/_next/static/chunks/1307-08b716476f1d50af.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1358d292.5c87f12cad6eaa91.js +122 -0
- package/apps/pwa/out/_next/static/chunks/1438.27a1a7b8f2308004.js +149 -0
- package/apps/pwa/out/_next/static/chunks/1442.02d85cd88f4c1eac.js +79 -0
- package/apps/pwa/out/_next/static/chunks/1501-1aad662d82ba43f7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1584.9e2dd53da6e6d4b1.js +24 -0
- package/apps/pwa/out/_next/static/chunks/1586.b989b0614afb77e6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1609.2ad355062870151f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1612-f6df0fc0eed7157b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1923.37f1d5fe7ebe3860.js +27 -0
- package/apps/pwa/out/_next/static/chunks/1948.5f35b39eef222a94.js +5 -0
- package/apps/pwa/out/_next/static/chunks/1993.3fd074a2a246234e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2140.d2daf9ee96e295d1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2212.0359851457473efc.js +1 -0
- package/apps/pwa/out/_next/static/chunks/224.583e562365a52f74.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2328.0f37465646bafa7d.js +36 -0
- package/apps/pwa/out/_next/static/chunks/2372.bf71372184991937.js +286 -0
- package/apps/pwa/out/_next/static/chunks/{2489-ff55a35b202c0f94.js → 2489-d238d3e2eb5b2bf5.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/2549-a8515c86f9e8cb96.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2655.72cc6a97276c5acc.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2743-ef6e8c3ced74cce5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2840-5458ad50c49cac6a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2856.f75d5baef7f49b74.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3344.0de4b0ec5a454b7a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/335.a13f339e97893437.js +94 -0
- package/apps/pwa/out/_next/static/chunks/3394.646348f34b2500be.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3443.6276909b53d72d53.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3495.abd4254d808bf2dc.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3522-1de1319d028ac89c.js +25 -0
- package/apps/pwa/out/_next/static/chunks/3721.ba5b82db0d9a5572.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3857-5985236d9307f676.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3919.9ff7dc25afefee33.js +74 -0
- package/apps/pwa/out/_next/static/chunks/3998.bd9a69d9b50e9bd2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4003.ab6db1dc266576c9.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4216.e8ac94b9fe6b96ab.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4327.c44dc2b55cb43ee5.js +34 -0
- package/apps/pwa/out/_next/static/chunks/4401.94257d497b6d26bc.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4559.17ca2cdbaa8d414f.js +115 -0
- package/apps/pwa/out/_next/static/chunks/4588.ad3364cb7fb97ada.js +86 -0
- package/apps/pwa/out/_next/static/chunks/4706.97e734bd4c9816ca.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4982.6e44352f751b9a1b.js +77 -0
- package/apps/pwa/out/_next/static/chunks/5342.49c5c5a530e62b50.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5360-08a80abdf3a4da99.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5431.66c58ab1327ee0b7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5633-d6852385000738ef.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5717.bcfcb96e701b82e0.js +90 -0
- package/apps/pwa/out/_next/static/chunks/5755-30d102586789bf7f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5957.3f1a4012388dd962.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5977-5b9e8a45a5b0c823.js +1 -0
- package/apps/pwa/out/_next/static/chunks/602.9bf02ac8e9502598.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6020.3a1b347e2d2dfacc.js +8 -0
- package/apps/pwa/out/_next/static/chunks/{6158.af2e93b7adff709b.js → 6158.566c890adc06a459.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/6375.fce1d174ba6d0324.js +64 -0
- package/apps/pwa/out/_next/static/chunks/6546.92a828aef441271f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/658.957fdb0a8c6c6c8e.js +287 -0
- package/apps/pwa/out/_next/static/chunks/6706.323590aabb8b1293.js +226 -0
- package/apps/pwa/out/_next/static/chunks/6747.b67947f293a4c02d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6767.8e6c5f6e744fcd9f.js +38 -0
- package/apps/pwa/out/_next/static/chunks/6868.000b24a3045c5574.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6946.bba74e9c96050f30.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7033.a558f1b2f6912a53.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7065.46029c26c53eac50.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7087.6fae577d09249bc1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7175-350f4ce2873bc038.js +1 -0
- package/apps/pwa/out/_next/static/chunks/722.de8d9801e0f6d414.js +41 -0
- package/apps/pwa/out/_next/static/chunks/7497.57981e9d7a75a52a.js +24 -0
- package/apps/pwa/out/_next/static/chunks/7630.2b9a68659053ff4e.js +193 -0
- package/apps/pwa/out/_next/static/chunks/7672.4af122ad892117c7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/771.6ab889a233e7dae7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7899.597af4a9996c907f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/79b78388.663188133449d707.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8096.f7da0776cfde6300.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8194.484f51e273d9abd2.js +217 -0
- package/apps/pwa/out/_next/static/chunks/8769.815ca69879f608b4.js +104 -0
- package/apps/pwa/out/_next/static/chunks/8857-aa1cc4ac90af4d40.js +1 -0
- package/apps/pwa/out/_next/static/chunks/88729267.9c6c000157c83f2f.js +62 -0
- package/apps/pwa/out/_next/static/chunks/8f835b4f.048eeb02299e3e12.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9068.5f2764fccf86c1b2.js +93 -0
- package/apps/pwa/out/_next/static/chunks/9247-55bcfbb0e8057f59.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9451.ebfb0dcc9c02250f.js +23 -0
- package/apps/pwa/out/_next/static/chunks/9559.e648369b1d57cf9a.js +62 -0
- package/apps/pwa/out/_next/static/chunks/9832.2ab2e818013574c7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/a9f2b54d-40c16bd0efdb04b8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/approvals/{page-c855c24de7db74fe.js → page-88459e0903da2803.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/autopilot/{page-85b316bb4235980b.js → page-d85c43f46f58716b.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/board/{page-9b2b26a33cb2cd42.js → page-4e645600579ae952.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/botlab/{page-c9955fde67b1bcb5.js → page-87edbfd19117823b.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/bots/page-7f0c2b7801ac6a54.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ceo/page-bf1f030a9d1adffd.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/chat/{page-4ba4abe096b6c2a8.js → page-cbaf66b1e1804adf.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/consult/{page-575d4a8b4f7becb2.js → page-80ed511e02c81412.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/control/{page-f5d56ff5f45ad164.js → page-9f5610caaa06178c.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/decisions/{page-2260e9eef0357421.js → page-ec8f3575e339de74.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-86d753e460f1a989.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/editor/page-4166a7d51cf0a57d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/exec/{page-466bf0ee16ad1432.js → page-2a19c5cd3b562580.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/field/{page-a23bc71713bcc08f.js → page-b6a09cde2e07521a.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/inside/page-f7eae1518ca0c353.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/intake/page-456a8573ad811381.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/layout-8462d6a543cbb2d9.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/live/page-2974cdfb87c0f9c9.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/live-run/page-f927ffa25f3c86d1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/loops/{page-35bfec2e631d3eae.js → page-abd86cd832aaa011.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/market/page-79daf68f6256adaa.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/{page-f9ccb1b1adbea14d.js → page-8c3f168fc3f91939.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/morning/{page-ec102c9b8b691e37.js → page-786fb4504d0f2fa9.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/not-found-75209f14597dcb8f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-bf30affe3a85af7e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-1187a44b7515434b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-3d0910185a66ed54.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-f401ee1dd5a26e25.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/outputs/page-5dfe7604fd842784.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/page-76cf838050a8d681.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/reflection/{page-db03ae3589a81c9e.js → page-7ec8e289b4c14df6.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/scheduler/{page-4a6463b97f0ad94f.js → page-8c8ac17d52be2a79.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/sessions/{page-75bffecc9aa1004e.js → page-d359ab048d63a9d9.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-b8b345c880bf78d1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/{page-31d5b875760bb62d.js → page-f151cc10ad026940.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-9875f103057da433.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/page-9789328af9a9aa62.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/share/{page-46d5201d2207a511.js → page-bf495a1e284f4e00.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-a263d94bca4be22a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-3962eab8ec58d16e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/term/{page-cf0a4b4abead71ea.js → page-9abcfac07e02a542.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/today/page-bee49f927c333abb.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/trace/page-bac2d56dcb02d955.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/vault/page-937cbcc13176295c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-d09495594f7f480d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/{page-0acc8e198f7b0dfb.js → page-69cb93d1a2e57e0e.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/workspace/{page-34882c84f84021c5.js → page-72ec543928d67c3e.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-82906eeda0186a7b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/d70879db.c66c56a95a91efc6.js +66 -0
- package/apps/pwa/out/_next/static/chunks/f8626309.a4e5d362b1d55e59.js +1 -0
- package/apps/pwa/out/_next/static/chunks/webpack-f2b4016b9b981bc8.js +1 -0
- package/apps/pwa/out/_next/static/css/{d3427996da11e480.css → b9d8151e48998417.css} +1 -1
- package/apps/pwa/out/_next/static/css/f2bd5257e6af88d8.css +1 -0
- package/apps/pwa/out/approvals/index.html +1 -1
- package/apps/pwa/out/approvals/index.txt +14 -14
- package/apps/pwa/out/autopilot/index.html +1 -1
- package/apps/pwa/out/autopilot/index.txt +14 -14
- package/apps/pwa/out/board/index.html +1 -1
- package/apps/pwa/out/board/index.txt +14 -14
- package/apps/pwa/out/botlab/index.html +1 -1
- package/apps/pwa/out/botlab/index.txt +14 -14
- package/apps/pwa/out/bots/index.html +1 -1
- package/apps/pwa/out/bots/index.txt +14 -14
- package/apps/pwa/out/ceo/index.html +1 -1
- package/apps/pwa/out/ceo/index.txt +14 -14
- package/apps/pwa/out/chat/index.html +1 -1
- package/apps/pwa/out/chat/index.txt +14 -14
- package/apps/pwa/out/consult/index.html +1 -1
- package/apps/pwa/out/consult/index.txt +14 -14
- package/apps/pwa/out/control/index.html +1 -1
- package/apps/pwa/out/control/index.txt +14 -14
- package/apps/pwa/out/decisions/index.html +1 -1
- package/apps/pwa/out/decisions/index.txt +14 -14
- package/apps/pwa/out/design-check/index.html +1 -1
- package/apps/pwa/out/design-check/index.txt +14 -14
- package/apps/pwa/out/editor/index.html +1 -1
- package/apps/pwa/out/editor/index.txt +15 -15
- package/apps/pwa/out/exec/index.html +1 -1
- package/apps/pwa/out/exec/index.txt +14 -14
- package/apps/pwa/out/field/index.html +1 -1
- package/apps/pwa/out/field/index.txt +14 -14
- package/apps/pwa/out/healthz +1 -1
- package/apps/pwa/out/index.html +1 -1
- package/apps/pwa/out/index.txt +14 -14
- package/apps/pwa/out/inside/index.html +1 -1
- package/apps/pwa/out/inside/index.txt +15 -15
- package/apps/pwa/out/intake/index.html +1 -1
- package/apps/pwa/out/intake/index.txt +14 -14
- package/apps/pwa/out/live/index.html +1 -1
- package/apps/pwa/out/live/index.txt +14 -14
- package/apps/pwa/out/live-run/index.html +1 -0
- package/apps/pwa/out/live-run/index.txt +35 -0
- package/apps/pwa/out/loops/index.html +1 -1
- package/apps/pwa/out/loops/index.txt +14 -14
- package/apps/pwa/out/market/index.html +1 -1
- package/apps/pwa/out/market/index.txt +14 -14
- package/apps/pwa/out/missions/placeholder/index.html +1 -1
- package/apps/pwa/out/missions/placeholder/index.txt +14 -14
- package/apps/pwa/out/morning/index.html +1 -1
- package/apps/pwa/out/morning/index.txt +14 -14
- package/apps/pwa/out/observatory/index.html +1 -1
- package/apps/pwa/out/observatory/index.txt +14 -14
- package/apps/pwa/out/ops/checklist/index.html +1 -1
- package/apps/pwa/out/ops/checklist/index.txt +14 -14
- package/apps/pwa/out/ops/release/index.html +1 -1
- package/apps/pwa/out/ops/release/index.txt +14 -14
- package/apps/pwa/out/ops/seats/index.html +1 -1
- package/apps/pwa/out/ops/seats/index.txt +14 -14
- package/apps/pwa/out/outputs/index.html +1 -1
- package/apps/pwa/out/outputs/index.txt +14 -14
- package/apps/pwa/out/reflection/index.html +1 -1
- package/apps/pwa/out/reflection/index.txt +15 -15
- package/apps/pwa/out/scheduler/index.html +1 -1
- package/apps/pwa/out/scheduler/index.txt +14 -14
- package/apps/pwa/out/sessions/index.html +1 -1
- package/apps/pwa/out/sessions/index.txt +14 -14
- package/apps/pwa/out/settings/devices/index.html +1 -1
- package/apps/pwa/out/settings/devices/index.txt +14 -14
- package/apps/pwa/out/settings/index.html +1 -1
- package/apps/pwa/out/settings/index.txt +14 -14
- package/apps/pwa/out/setup/done/index.html +1 -1
- package/apps/pwa/out/setup/done/index.txt +14 -14
- package/apps/pwa/out/setup/index.html +1 -1
- package/apps/pwa/out/setup/index.txt +14 -14
- package/apps/pwa/out/share/index.html +1 -1
- package/apps/pwa/out/share/index.txt +14 -14
- package/apps/pwa/out/showroom/index.html +1 -1
- package/apps/pwa/out/showroom/index.txt +14 -14
- package/apps/pwa/out/tasks/index.html +1 -1
- package/apps/pwa/out/tasks/index.txt +14 -14
- package/apps/pwa/out/term/index.html +1 -1
- package/apps/pwa/out/term/index.txt +14 -14
- package/apps/pwa/out/today/index.html +1 -1
- package/apps/pwa/out/today/index.txt +14 -14
- package/apps/pwa/out/trace/index.html +1 -1
- package/apps/pwa/out/trace/index.txt +14 -14
- package/apps/pwa/out/vault/index.html +1 -1
- package/apps/pwa/out/vault/index.txt +14 -14
- package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
- package/apps/pwa/out/workflows/chat-ui/index.txt +14 -14
- package/apps/pwa/out/workflows/index.html +1 -1
- package/apps/pwa/out/workflows/index.txt +14 -14
- package/apps/pwa/out/workspace/index.html +1 -1
- package/apps/pwa/out/workspace/index.txt +14 -14
- package/apps/pwa/out/worktrees/index.html +1 -1
- package/apps/pwa/out/worktrees/index.txt +14 -14
- package/graphs/catalog/catalog.yaml +1 -1
- package/graphs/drafts/recipes.yaml +6 -6
- package/graphs/implement-loop.yaml +277 -5
- package/graphs/test-diet/recipes.yaml +3 -0
- package/graphs/test-diet/test-diet.yaml +3 -1
- package/package.json +1 -1
- package/scripts/coord-channel-watch.sh +31 -4
- package/scripts/docs-lint.ts +4 -1
- package/scripts/f-session-monitor.ts +69 -0
- package/scripts/hq/standby-snapshot.ts +7 -3
- package/scripts/knowledge-compress.ts +11 -0
- package/scripts/marketing/generate-ax-brief.ts +18 -1
- package/scripts/measure-nl-routing-corpus.ts +11 -3
- package/scripts/nightly-docops.ts +90 -3
- package/scripts/release-loop/gate-node.ts +5 -5
- package/scripts/release-loop/shard-plan.ts +50 -5
- package/scripts/research-coo-admin-dates.ts +39 -0
- package/scripts/rhythm/daily.ts +2 -1
- package/scripts/skills/public-check.ts +7 -2
- package/scripts/smart-merge/resync-shadow.ts +5 -3
- package/scripts/test-diet/lib.ts +81 -1
- package/scripts/test-diet/node.ts +48 -2
- package/scripts/verify-coo-admin-evidence.ts +83 -0
- package/src/acp/client.ts +4 -2
- package/src/agent/run-context.ts +4 -0
- package/src/agent-env/profile.ts +25 -5
- package/src/agent-mission/driver.ts +26 -12
- package/src/agent-mission/supervisor-metrics.ts +17 -2
- package/src/agent-substrate/acp-reviewer.ts +12 -5
- package/src/agent-substrate/pr-reviewer.ts +11 -4
- package/src/agent-substrate/review-intent.ts +102 -3
- package/src/autopilot/build/llm-conflict-merge.ts +22 -8
- package/src/autopilot/pr-manager.ts +18 -8
- package/src/away/release-watch.ts +47 -8
- package/src/briefing/brief-items.ts +104 -11
- package/src/browser-cdp/client.ts +10 -5
- package/src/chat/index.ts +1 -0
- package/src/cli/brief-cli.ts +20 -2
- package/src/cli/config-classify.ts +83 -0
- package/src/cli/control-cli.ts +14 -0
- package/src/cli/decisions-cli.ts +26 -16
- package/src/cli/logs-model-input.ts +1 -1
- package/src/cli/ops-cli.ts +11 -0
- package/src/cli/ops-overview-cli.ts +50 -0
- package/src/cli/pod-cli.ts +1 -0
- package/src/cli/pr-cli.ts +65 -8
- package/src/cli/release-cli.ts +21 -4
- package/src/cli/tasks-cli.ts +214 -1
- package/src/connectors/types.ts +6 -6
- package/src/context-bus/context-now-surfaces.ts +18 -0
- package/src/context-bus/coord-events.ts +32 -4
- package/src/context-bus/long-term-memory.ts +23 -6
- package/src/context-bus/nightly-condense.ts +169 -21
- package/src/control/control-status.ts +87 -0
- package/src/control-plane/light-member.ts +5 -3
- package/src/control-plane/member.ts +172 -7
- package/src/core-turn/run-goal-loop.ts +22 -1
- package/src/dashboard/index.ts +40 -4
- package/src/dashboard/slash-runtime/dashboard-handlers.ts +9 -0
- package/src/decisions/decision-cards.ts +20 -1
- package/src/decisions/decision-ledger.ts +11 -8
- package/src/decisions/web-push-decision-cards.ts +19 -3
- package/src/discord-self-message.ts +2 -2
- package/src/domains/coo-admin-dates.evidence.json +876 -0
- package/src/domains/coo-admin-tool.ts +48 -14
- package/src/domains/ops-status.ts +53 -0
- package/src/domains/outbound-alert.ts +24 -1
- package/src/esc-abort-gate.ts +3 -1
- package/src/exec-requests/planner.ts +6 -0
- package/src/graph-kinds/registry.ts +6 -6
- package/src/graph-runner/graph-cli.ts +91 -2
- package/src/graph-runner/graph-run-api.ts +20 -0
- package/src/graph-runner/graph-tick.ts +73 -12
- package/src/graph-runner/runner.ts +43 -11
- package/src/graph-unify/wf-to-graph.ts +79 -0
- package/src/graph-wizard/archetypes.ts +88 -0
- package/src/graph-wizard/generate.ts +577 -0
- package/src/graph-wizard/saved-steps.ts +120 -0
- package/src/graph-wizard/steps.ts +206 -0
- package/src/harness/harness-cli-command.ts +186 -32
- package/src/harness/harness-queue.ts +58 -14
- package/src/harness/launch-advice.ts +23 -0
- package/src/index.ts +43 -11
- package/src/intake-plane/digest.ts +18 -11
- package/src/knowledge/conclusion-compress.ts +93 -0
- package/src/knowledge/know-find.ts +9 -2
- package/src/knowledge/query.ts +20 -0
- package/src/llm.ts +26 -16
- package/src/loops/neighbor-runtime.ts +88 -0
- package/src/loops/orchestrator/author-depth.ts +29 -11
- package/src/loops/orchestrator/overlap.ts +7 -1
- package/src/loops/orchestrator/resync-shadow.ts +1 -1
- package/src/loops/orchestrator/tick.ts +35 -2
- package/src/loops/orchestrator/traffic.ts +2 -0
- package/src/loops/registry.ts +5 -0
- package/src/market/publish.ts +17 -1
- package/src/maturity/feature-maturity.ts +4 -2
- package/src/mss/logging/redaction.ts +17 -0
- package/src/nexus/api/draft-metrics.ts +128 -0
- package/src/nexus/api/graph-peer-edit.ts +77 -0
- package/src/nexus/api/graph-wizard-api.ts +61 -0
- package/src/nexus/api/graphs-api.ts +193 -17
- package/src/nexus/api/harness-traversal.ts +205 -0
- package/src/nexus/api/http-server.ts +122 -6
- package/src/nexus/api/plugins-market.ts +24 -0
- package/src/nexus/index.ts +3 -2
- package/src/nexus/outbound/router.ts +28 -8
- package/src/oauth/claude-pty-maturity.ts +24 -0
- package/src/oauth/fallback-chain.ts +27 -14
- package/src/onboarding/first-run.ts +3 -0
- package/src/ops/overview-collect.ts +345 -0
- package/src/ops/overview-seat-era.ts +90 -0
- package/src/ops/overview.ts +223 -0
- package/src/plugins/maker/market-bundle.ts +9 -0
- package/src/plugins/maker/plugin-maker.ts +9 -3
- package/src/plugins/maker/wizard-generate.ts +73 -9
- package/src/policy/llm-policy.ts +11 -6
- package/src/release-loop/checklist-board.ts +27 -0
- package/src/release-loop/checklist.ts +38 -0
- package/src/release-loop/gate-shards.ts +7 -2
- package/src/seat-address/seat-address.ts +1 -0
- package/src/seat-loop/seat-loop.ts +174 -20
- package/src/seat-loop/seat-report.ts +29 -1
- package/src/self-dev/ask-launch-flow.ts +4 -2
- package/src/self-dev/ask-launch-io.ts +3 -1
- package/src/self-dev/dev-cli.ts +90 -8
- package/src/self-dev/dev-pipeline.ts +20 -1
- package/src/self-dev/draft-sweep.ts +45 -8
- package/src/self-dev/graph-journey-nodes.ts +157 -0
- package/src/self-dev/graph-journey-route.ts +105 -0
- package/src/self-dev/launch-preflight.ts +43 -3
- package/src/self-dev/orchestrate.ts +3 -0
- package/src/self-dev/release-path-guard.ts +7 -4
- package/src/self-dev/review-provider-fallback.ts +83 -0
- package/src/self-dev/run-supervisor.ts +5 -1
- package/src/self-implement/frozen-merges.ts +23 -0
- package/src/self-implement/goal-author.ts +14 -0
- package/src/self-implement/harness-policy-gates.ts +7 -5
- package/src/self-implement/heal-intake.ts +3 -0
- package/src/self-implement/host-regate.ts +28 -14
- package/src/self-implement/l8-merge-queue.ts +80 -8
- package/src/self-implement/orchestrator.ts +84 -28
- package/src/self-implement/seams.ts +8 -3
- package/src/self-implement/self-implement-cli.ts +4 -0
- package/src/self-implement/self-implement-runtime.ts +12 -0
- package/src/session-runtime/index.ts +5 -3
- package/src/shell-env-bootstrap.ts +35 -6
- package/src/steward/triage.ts +25 -8
- package/src/storage/file-lock.ts +33 -4
- package/src/task-agent/card-evidence.ts +6 -0
- package/src/task-agent/completion-evidence.ts +31 -3
- package/src/task-agent/cover.ts +61 -0
- package/src/task-agent/live-moves.ts +126 -3
- package/src/task-agent/parent-host.ts +315 -0
- package/src/task-agent/shadow.ts +19 -4
- package/src/task-agent/task-hand.ts +11 -2
- package/src/task-orchestrator/surfaces/pod-command-job.ts +41 -6
- package/src/task-orchestrator/surfaces/pod-gate-deps.ts +123 -0
- package/src/task-orchestrator/surfaces/pod-pool.ts +138 -15
- package/src/task-orchestrator/surfaces/self-implement-pod.ts +39 -6
- package/src/task-orchestrator/surfaces/self-implement.ts +7 -0
- package/src/telegram-commands.ts +8 -0
- package/src/telegram-project-command.ts +6 -0
- package/src/telegram.ts +2 -2
- package/src/tool-runtime/browser-runtime.ts +87 -3
- package/src/tool-runtime/index.ts +1 -0
- package/src/typecheck-ratchet.ts +2 -1
- package/src/user-config.ts +55 -8
- package/src/version/packed-revision.json +1 -1
- package/src/workflow-runtime/executor.ts +24 -2
- package/src/workflow-runtime/triggers/registry.ts +27 -0
- package/src/workflow-runtime/triggers/schedule-source.ts +30 -3
- package/apps/pwa/out/_next/static/Bam1YziK2iH9bN4K0utGk/_buildManifest.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1307-77fad80cf54477c6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1612-f60c0c93ec773f81.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1844-9ec4b1d12f6e80eb.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1866-e7a6999f25548b30.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1948.15f356b15a625597.js +0 -5
- package/apps/pwa/out/_next/static/chunks/2116-6429c188eb846ab8.js +0 -1
- package/apps/pwa/out/_next/static/chunks/224-e7be8f762fe74986.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2549-89c518a4c8ed4d00.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2957-96ffab3d521fb08f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3857-995ea9f5ef33d71e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5360-be1f1d8f9fcc9755.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5755-ea7ed88eb956f47b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5977-94ed6f78e9814b2b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7748-6e51bfe927019f5c.js +0 -25
- package/apps/pwa/out/_next/static/chunks/9247-c1753a059e7bac08.js +0 -1
- package/apps/pwa/out/_next/static/chunks/a9f2b54d-33c0689b70ed8cf3.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/bots/page-7a852956c74015a3.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ceo/page-c5a5c621be0e04ae.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-7c058069c5e7fa63.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/editor/page-dc5da0b8a2edb8a4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/inside/page-ed4a6f799f005d09.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/intake/page-9cd565005b93aca6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/layout-55f0f40c6db95f15.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/live/page-474cb5888ea0e8f3.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/market/page-9ca21f9ad3208a61.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/not-found-a9c2cfa8dda922a5.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-6da35144878feb6d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-f6055495e9ed7ea8.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-25204694fa9984a5.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-a60e035f151d84d3.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/outputs/page-189c587f36874538.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/page-c56634bde705a65d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-a8d2fe0c669e0de4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-c26fa10aac6c21c4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/page-68793c6e10331d66.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-d81c07a7b4f618ae.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-be1c4e8697f22360.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/today/page-a4717e2c4bf7a28f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/trace/page-0fd895fab9b62c1b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/vault/page-8b6c1454a8e8f306.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-c6e4364d9de8f717.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-0506a1e0e4bdb2f5.js +0 -1
- package/apps/pwa/out/_next/static/chunks/webpack-c260571513bf43c2.js +0 -1
- package/apps/pwa/out/_next/static/css/c23d959d3ee15e57.css +0 -1
- /package/apps/pwa/out/_next/static/{Bam1YziK2iH9bN4K0utGk → aBi6EyMtLsrl7J8eH4kF7}/_ssgManifest.js +0 -0
- /package/skills/project-onboarding/{skill.md → SKILL.md} +0 -0
|
@@ -1,14 +1,58 @@
|
|
|
1
|
-
import { readFileSync, readdirSync, mkdirSync, writeFileSync, renameSync, existsSync } from 'node:fs';
|
|
1
|
+
import { readFileSync, readdirSync, mkdirSync, writeFileSync, renameSync, existsSync, unlinkSync } from 'node:fs';
|
|
2
|
+
import { gzipSync, gunzipSync } from 'node:zlib';
|
|
2
3
|
import { dirname, join, resolve } from 'node:path';
|
|
3
4
|
import { streamLLM } from '../llm.js';
|
|
4
|
-
import { condenseContextDay, type MemoryItem, type MemorySource, type MemorySummary } from './long-term-memory.js';
|
|
5
|
-
import {
|
|
5
|
+
import { condenseContextDay, guardianRetireAged, readCondenseEvents, type MemoryItem, type MemorySource, type MemorySummary } from './long-term-memory.js';
|
|
6
|
+
import type { CoordEvent } from './coord-events.js';
|
|
6
7
|
import { DecisionLedger, type DecisionEntry } from '../decisions/decision-ledger.js';
|
|
7
8
|
import { acquireLockAsync } from '../storage/file-lock.js';
|
|
8
9
|
|
|
9
10
|
type Stage = 'collect' | 'condense' | 'record' | 'conflict-candidates';
|
|
10
11
|
type Input = { day?: string; events?: CoordEvent[]; decisions?: DecisionEntry[]; summaries?: Record<string, MemorySummary> };
|
|
11
|
-
type Context = { graphId?: string; nodeId?: string; input: Input | null; outputs: Record<string, { day?: string; events?: CoordEvent[]; decisions?: DecisionEntry[]; items?: MemoryItem[] } | null> };
|
|
12
|
+
type Context = { graphId?: string; nodeId?: string; input: Input | null; outputs: Record<string, { day?: string; events?: CoordEvent[]; decisions?: DecisionEntry[]; items?: MemoryItem[]; memoryItems?: MemoryItem[] } | null> };
|
|
13
|
+
type NightlyTask = (source: MemorySource, assignee: 'nightly-task-agent', recipe: 'sleep-review') => Promise<MemorySummary>;
|
|
14
|
+
type GuardianTask = (items: readonly MemoryItem[], assignee: 'guardian', now: Date) => MemoryItem[] | Promise<MemoryItem[]>;
|
|
15
|
+
type NightlyAssignment = { owner: 'coordinator'; assignee: 'nightly-task-agent'; contract: 'librarian'; recipe: 'sleep-review'; source: MemorySource; summary?: MemorySummary };
|
|
16
|
+
type GuardianAssignment = { owner: 'guardian'; assignee: 'guardian'; recipe: 'retire-aged'; items: readonly MemoryItem[]; now: string }
|
|
17
|
+
| { owner: 'guardian'; assignee: 'guardian'; recipe: 'store-snapshot'; root: string; project: string; seat: string; value: MemoryItem[] }
|
|
18
|
+
| { owner: 'guardian'; assignee: 'guardian'; recipe: 'store-candidates'; root: string; value: unknown[] };
|
|
19
|
+
type Assignment = NightlyAssignment | GuardianAssignment;
|
|
20
|
+
|
|
21
|
+
/** Execute an assigned recipe in a separate, short-lived task worker, never in the coordinator's stage process. */
|
|
22
|
+
async function dispatchAssignment<T>(assignment: Assignment): Promise<{ result: T; workerPid: number }> {
|
|
23
|
+
const worker = Bun.spawn([process.execPath, import.meta.path, '--assigned-task'], {
|
|
24
|
+
stdin: 'pipe', stdout: 'pipe', stderr: 'pipe',
|
|
25
|
+
});
|
|
26
|
+
worker.stdin.write(JSON.stringify(assignment));
|
|
27
|
+
worker.stdin.end();
|
|
28
|
+
const [stdout, stderr, exit] = await Promise.all([
|
|
29
|
+
new Response(worker.stdout).text(), new Response(worker.stderr).text(), worker.exited,
|
|
30
|
+
]);
|
|
31
|
+
if (exit !== 0) throw new Error(`nightly ${assignment.recipe} task failed: ${stderr.trim()}`);
|
|
32
|
+
return { result: JSON.parse(stdout) as T, workerPid: worker.pid };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async function executeAssignment(assignment: Assignment): Promise<MemorySummary | MemoryItem[] | { stored: true }> {
|
|
36
|
+
if (assignment.owner === 'coordinator' && assignment.assignee === 'nightly-task-agent'
|
|
37
|
+
&& assignment.contract === 'librarian' && assignment.recipe === 'sleep-review') {
|
|
38
|
+
return summarize(assignment.source, assignment.summary ? { [assignment.source.source]: assignment.summary } : undefined, assignment.recipe);
|
|
39
|
+
}
|
|
40
|
+
if (assignment.owner === 'guardian' && assignment.assignee === 'guardian') {
|
|
41
|
+
if (assignment.recipe === 'retire-aged') return guardianRetireAged(assignment.items, new Date(assignment.now));
|
|
42
|
+
if (assignment.recipe === 'store-snapshot') {
|
|
43
|
+
if (!assignment.value.every(item => item.project === assignment.project && item.seat === assignment.seat)) {
|
|
44
|
+
throw new Error('guardian memory group mismatch');
|
|
45
|
+
}
|
|
46
|
+
guardianSave(snapshotPath(assignment.root, assignment.project, assignment.seat), assignment.value);
|
|
47
|
+
return { stored: true };
|
|
48
|
+
}
|
|
49
|
+
if (assignment.recipe === 'store-candidates') {
|
|
50
|
+
guardianSave(join(assignment.root, 'context-memory', 'conflict-candidates.json'), assignment.value);
|
|
51
|
+
return { stored: true };
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
throw new Error('invalid nightly assignment');
|
|
55
|
+
}
|
|
12
56
|
|
|
13
57
|
const dayBefore = (date: Date) => new Date(date.getTime() - 86_400_000).toISOString().slice(0, 10);
|
|
14
58
|
const fileName = (value: string) => encodeURIComponent(value).replace(/\./g, '%2E');
|
|
@@ -25,6 +69,39 @@ function save(file: string, value: unknown): void {
|
|
|
25
69
|
renameSync(temp, file);
|
|
26
70
|
}
|
|
27
71
|
|
|
72
|
+
const GUARDIAN_SIZE_BUDGET_BYTES = 16 * 1024 * 1024;
|
|
73
|
+
|
|
74
|
+
/** Guardian-owned snapshot hygiene. The JSON ledger stays byte-for-byte in its existing format. */
|
|
75
|
+
function guardianSave(file: string, value: unknown): void {
|
|
76
|
+
const text = JSON.stringify(value, null, 2) + '\n';
|
|
77
|
+
const previous = existsSync(file) ? readFileSync(file) : undefined;
|
|
78
|
+
const backup = `${file}.gz`;
|
|
79
|
+
if (previous) {
|
|
80
|
+
// Write the new backup beside the old one and verify it on disk before it replaces the old backup,
|
|
81
|
+
// so a failed or corrupt backup write never destroys the previous backup.
|
|
82
|
+
const backupTemp = `${backup}.${process.pid}.tmp`;
|
|
83
|
+
try {
|
|
84
|
+
writeFileSync(backupTemp, gzipSync(previous));
|
|
85
|
+
if (!gunzipSync(readFileSync(backupTemp)).equals(previous)) throw new Error('guardian backup integrity check failed');
|
|
86
|
+
renameSync(backupTemp, backup);
|
|
87
|
+
} catch (error) {
|
|
88
|
+
if (existsSync(backupTemp)) unlinkSync(backupTemp);
|
|
89
|
+
throw error;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
try {
|
|
93
|
+
if (Buffer.byteLength(text) > GUARDIAN_SIZE_BUDGET_BYTES) throw new Error('guardian memory size budget exceeded');
|
|
94
|
+
save(file, value);
|
|
95
|
+
if (readFileSync(file, 'utf8') !== text) throw new Error('guardian memory integrity check failed');
|
|
96
|
+
} catch (error) {
|
|
97
|
+
if (previous) writeFileSync(file, gunzipSync(readFileSync(backup)));
|
|
98
|
+
else if (existsSync(file)) unlinkSync(file);
|
|
99
|
+
const temp = `${file}.${process.pid}.tmp`;
|
|
100
|
+
if (existsSync(temp)) unlinkSync(temp);
|
|
101
|
+
throw error;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
28
105
|
async function withCurrentDay<T>(root: string, day: string, write: () => T): Promise<T> {
|
|
29
106
|
const dir = join(root, 'context-memory');
|
|
30
107
|
mkdirSync(dir, { recursive: true });
|
|
@@ -39,7 +116,7 @@ async function withCurrentDay<T>(root: string, day: string, write: () => T): Pro
|
|
|
39
116
|
if (!lock.stillHeld()) throw new Error('nightly memory write lock lost');
|
|
40
117
|
// Reserve the day before touching either output, so a failed writer cannot let an older run overwrite it.
|
|
41
118
|
if (!latest || latest < day) save(marker, day);
|
|
42
|
-
return write();
|
|
119
|
+
return await write();
|
|
43
120
|
} finally { lock.release(); }
|
|
44
121
|
}
|
|
45
122
|
|
|
@@ -64,20 +141,38 @@ function storedItems(root: string): MemoryItem[] {
|
|
|
64
141
|
return items;
|
|
65
142
|
}
|
|
66
143
|
|
|
67
|
-
|
|
144
|
+
/** A sleep-review rejection: counted as `not-promoted` by the condenser, never as a malformed summary. */
|
|
145
|
+
function notPromoted(value: Record<string, unknown>): MemorySummary {
|
|
146
|
+
const text = (key: string) => typeof value[key] === 'string' ? value[key] as string : '';
|
|
147
|
+
return { promote: false, project: text('project'), topic: text('topic'), summary: text('summary') };
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export function summaryInstruction(recipe: 'standard' | 'sleep-review' = 'standard'): string {
|
|
151
|
+
const system = 'Summarize this one-line context event as a compact project/seat memory item. Return ONLY JSON: {"project":"...","topic":"...","summary":"...","claim":{"key":"...","value":"..."}}. Omit claim when the event makes no comparable factual assertion. Never invent a project, topic or claim.';
|
|
152
|
+
if (recipe === 'standard') return system;
|
|
153
|
+
// SLEEP1 builds on this librarian recipe; the guardian owns storage hygiene, not promotion judgments.
|
|
154
|
+
return `${system.replace('"project":"..."', '"promote":false,"project":"..."')} You are the nightly task agent (librarian). Sleep review (librarian contract): apply the coordinator promotion policy. Return a boolean "promote" for every source. Set promote=true only for sourced, durable project/seat facts or decisions; set promote=false for chatter or speculative claims. A correction with a later timestamp wins; return a comparable claim only for an explicit factual assertion so opposing claims can become OP conflict candidates. Do not perform guardian hygiene (retention, compression, size, backup, integrity or rollback).`;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export async function summarize(source: MemorySource, injected?: Record<string, MemorySummary>, recipe: 'standard' | 'sleep-review' = 'standard'): Promise<MemorySummary> {
|
|
68
158
|
if (injected) {
|
|
69
159
|
const result = injected[source.source];
|
|
70
160
|
if (!result) throw new Error(`missing test summary: ${source.source}`);
|
|
161
|
+
if (recipe === 'sleep-review' && typeof result.promote !== 'boolean') throw new Error('invalid memory summary');
|
|
162
|
+
if (recipe === 'sleep-review' && result.promote === false) return notPromoted(result as unknown as Record<string, unknown>);
|
|
71
163
|
return result;
|
|
72
164
|
}
|
|
73
165
|
const response = await streamLLM([
|
|
74
|
-
{ role: 'system', content:
|
|
166
|
+
{ role: 'system', content: summaryInstruction(recipe) },
|
|
75
167
|
{ role: 'user', content: JSON.stringify(source) },
|
|
76
168
|
], () => {}, { maxTokens: 350, usageRole: 'research' });
|
|
77
169
|
const parsed: unknown = JSON.parse(response.replace(/^```(?:json)?\s*|\s*```$/g, '').trim());
|
|
78
170
|
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) throw new Error('invalid memory summary');
|
|
79
171
|
const value = parsed as Record<string, unknown>;
|
|
172
|
+
// A librarian rejection needs no project/topic/summary — chatter often has none to give.
|
|
173
|
+
if (recipe === 'sleep-review' && value.promote === false) return notPromoted(value);
|
|
80
174
|
if (typeof value.project !== 'string' || typeof value.topic !== 'string' || typeof value.summary !== 'string' ||
|
|
175
|
+
(recipe === 'sleep-review' && typeof value.promote !== 'boolean') ||
|
|
81
176
|
(value.claim !== undefined && (!value.claim || typeof value.claim !== 'object' || Array.isArray(value.claim) ||
|
|
82
177
|
typeof (value.claim as Record<string, unknown>).key !== 'string' || typeof (value.claim as Record<string, unknown>).value !== 'string'))) {
|
|
83
178
|
throw new Error('invalid memory summary');
|
|
@@ -85,8 +180,14 @@ export async function summarize(source: MemorySource, injected?: Record<string,
|
|
|
85
180
|
return value as unknown as MemorySummary;
|
|
86
181
|
}
|
|
87
182
|
|
|
183
|
+
const defaultGuardianTask: GuardianTask = (items, assignee, now) => {
|
|
184
|
+
if (assignee !== 'guardian') throw new Error('invalid guardian assignee');
|
|
185
|
+
return guardianRetireAged(items, now);
|
|
186
|
+
};
|
|
187
|
+
|
|
88
188
|
/** Stage outputs travel only through the graph runner's per-run contexts. No decision ledger write occurs here. */
|
|
89
|
-
export async function runNightlyStage(stage: Stage, context: Context, root: string, dryRun: boolean, now = new Date()
|
|
189
|
+
export async function runNightlyStage(stage: Stage, context: Context, root: string, dryRun: boolean, now = new Date(),
|
|
190
|
+
nightlyTask?: NightlyTask, guardianTask: GuardianTask = defaultGuardianTask): Promise<Record<string, unknown>> {
|
|
90
191
|
const input = context.input ?? {};
|
|
91
192
|
if (dryRun && stage !== 'collect' && (!input.events || !input.decisions || !input.summaries)) {
|
|
92
193
|
throw new Error('dry-run requires fake events, decisions and summaries');
|
|
@@ -103,51 +204,98 @@ export async function runNightlyStage(stage: Stage, context: Context, root: stri
|
|
|
103
204
|
if (dryRun && (!input.events || !input.decisions || !input.summaries)) {
|
|
104
205
|
throw new Error('dry-run requires fake events, decisions and summaries');
|
|
105
206
|
}
|
|
106
|
-
const events = input.events ??
|
|
107
|
-
const decisions = input.decisions ?? (dryRun ? [] : new DecisionLedger().list({ status: 'all' })
|
|
207
|
+
const events = input.events ?? readCondenseEvents(new Date(Date.parse(`${day}T00:00:00Z`) - 86_400_000).toISOString());
|
|
208
|
+
const decisions = input.decisions ?? (dryRun ? [] : new DecisionLedger({ stateDir: root }).list({ status: 'all' })
|
|
209
|
+
.filter(decision => decision.raisedBy.agent !== 'context-condense'));
|
|
108
210
|
return { outcome: 'ok', day, events, decisions };
|
|
109
211
|
}
|
|
110
212
|
const collected = context.outputs.collect;
|
|
111
213
|
if (!collected?.day || !collected.events || !collected.decisions) throw new Error('collect output missing');
|
|
112
214
|
if (stage === 'condense') {
|
|
215
|
+
const assignments: Array<{ owner: 'coordinator'; assignee: 'nightly-task-agent'; contract: 'librarian'; recipe: 'sleep-review'; workerPid: number }> = [];
|
|
113
216
|
const items = await condenseContextDay(collected.day, dryRun ? [] : storedItems(root).filter(item => Date.parse(item.updatedAt) < Date.parse(`${collected.day}T00:00:00Z`)), {
|
|
114
217
|
events: () => collected.events!, decisions: () => collected.decisions!,
|
|
115
|
-
summarize: source =>
|
|
116
|
-
|
|
117
|
-
|
|
218
|
+
summarize: async source => {
|
|
219
|
+
if (dryRun) return summarize(source, input.summaries, 'sleep-review');
|
|
220
|
+
if (nightlyTask) return nightlyTask(source, 'nightly-task-agent', 'sleep-review');
|
|
221
|
+
const { result, workerPid } = await dispatchAssignment<MemorySummary>({ owner: 'coordinator', assignee: 'nightly-task-agent',
|
|
222
|
+
contract: 'librarian', recipe: 'sleep-review', source, ...(input.summaries ? { summary: input.summaries[source.source] } : {}) });
|
|
223
|
+
assignments.push({ owner: 'coordinator', assignee: 'nightly-task-agent', contract: 'librarian', recipe: 'sleep-review', workerPid });
|
|
224
|
+
return result;
|
|
225
|
+
},
|
|
226
|
+
}, now, { promotion: 'sleep-review', retention: 'guardian' });
|
|
227
|
+
return { outcome: 'ok', items, assignments };
|
|
118
228
|
}
|
|
119
229
|
const items = context.outputs.condense?.items;
|
|
120
230
|
if (!items) throw new Error('condense output missing');
|
|
121
231
|
if (stage === 'record') {
|
|
122
232
|
const grouped = new Map<string, MemoryItem[]>();
|
|
123
|
-
|
|
233
|
+
// The guardian task owns retirement; the librarian never mutates the stored status.
|
|
234
|
+
const guardianRun = guardianTask === defaultGuardianTask && !dryRun
|
|
235
|
+
? await dispatchAssignment<MemoryItem[]>({ owner: 'guardian', assignee: 'guardian', recipe: 'retire-aged', items, now: now.toISOString() })
|
|
236
|
+
: { result: await guardianTask(items, 'guardian', now), workerPid: undefined };
|
|
237
|
+
const memoryItems = guardianRun.result;
|
|
238
|
+
for (const item of memoryItems) {
|
|
124
239
|
const file = snapshotPath(root, item.project, item.seat);
|
|
125
240
|
grouped.set(file, [...grouped.get(file) ?? [], item]);
|
|
126
241
|
}
|
|
127
|
-
if (!dryRun) await withCurrentDay(root, day, () => {
|
|
128
|
-
|
|
242
|
+
if (!dryRun) await withCurrentDay(root, day, async () => {
|
|
243
|
+
// The record is all-or-nothing: each guardian write rolls back its own file, and if a later group
|
|
244
|
+
// fails (size budget, integrity, worker error) the files this record already wrote are restored too.
|
|
245
|
+
const written: Array<{ file: string; previous: Buffer | undefined; previousBackup: Buffer | undefined }> = [];
|
|
246
|
+
const snapshot = (path: string) => existsSync(path) ? readFileSync(path) : undefined;
|
|
247
|
+
try {
|
|
248
|
+
for (const [file, group] of grouped) {
|
|
249
|
+
const first = group[0]!;
|
|
250
|
+
written.push({ file, previous: snapshot(file), previousBackup: snapshot(`${file}.gz`) });
|
|
251
|
+
const stored = await dispatchAssignment<{ stored: true }>({ owner: 'guardian', assignee: 'guardian', recipe: 'store-snapshot',
|
|
252
|
+
root, project: first.project, seat: first.seat, value: group });
|
|
253
|
+
if (!stored.result.stored) throw new Error('guardian snapshot not stored');
|
|
254
|
+
}
|
|
255
|
+
} catch (error) {
|
|
256
|
+
// Restore both the snapshot and its backup to what they were before this record started.
|
|
257
|
+
for (const { file, previous, previousBackup } of written.reverse()) {
|
|
258
|
+
for (const [path, before] of [[file, previous], [`${file}.gz`, previousBackup]] as const) {
|
|
259
|
+
if (before) writeFileSync(path, before);
|
|
260
|
+
else if (existsSync(path)) unlinkSync(path);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
throw error;
|
|
264
|
+
}
|
|
129
265
|
});
|
|
130
|
-
return { outcome: 'ok', files: [...grouped.keys()], items: items.length, memoryItems
|
|
266
|
+
return { outcome: 'ok', files: [...grouped.keys()], items: items.length, memoryItems, dryRun,
|
|
267
|
+
guardian: { assignee: 'guardian', recipe: 'retire-aged', retired: memoryItems.filter(item => item.status === 'retired').length,
|
|
268
|
+
workerPid: guardianRun.workerPid } };
|
|
131
269
|
}
|
|
132
270
|
if (stage === 'conflict-candidates') {
|
|
133
271
|
if (!context.outputs.record) throw new Error('record output missing');
|
|
134
|
-
const
|
|
272
|
+
const recorded = context.outputs.record.memoryItems;
|
|
273
|
+
if (!recorded) throw new Error('guardian record output missing');
|
|
274
|
+
const candidates = recorded.filter(item => item.conflict && item.status === 'active').map(item => ({
|
|
135
275
|
project: item.project, topic: item.topic, owner: item.conflict!.owner,
|
|
136
276
|
sources: item.conflict!.sources, status: 'candidate' as const,
|
|
137
277
|
}));
|
|
138
|
-
if (!dryRun) await withCurrentDay(root, day, () =>
|
|
278
|
+
if (!dryRun) await withCurrentDay(root, day, async () => {
|
|
279
|
+
const stored = await dispatchAssignment<{ stored: true }>({ owner: 'guardian', assignee: 'guardian', recipe: 'store-candidates',
|
|
280
|
+
root, value: candidates });
|
|
281
|
+
if (!stored.result.stored) throw new Error('guardian candidates not stored');
|
|
282
|
+
});
|
|
139
283
|
return { outcome: 'ok', candidates, published: 0 };
|
|
140
284
|
}
|
|
141
285
|
throw new Error(`unknown stage: ${stage}`);
|
|
142
286
|
}
|
|
143
287
|
|
|
144
|
-
if (import.meta.main) {
|
|
288
|
+
if (import.meta.main && process.argv[2] === '--assigned-task') {
|
|
289
|
+
const assignment = await new Response(Bun.stdin.stream()).json() as Assignment;
|
|
290
|
+
console.log(JSON.stringify(await executeAssignment(assignment)));
|
|
291
|
+
} else if (import.meta.main) {
|
|
145
292
|
const stage = process.argv[2] as Stage;
|
|
146
293
|
const path = process.env.ELANOUS_GRAPH_CONTEXT;
|
|
147
294
|
if (!path || !['collect', 'condense', 'record', 'conflict-candidates'].includes(stage)) throw new Error('invalid nightly graph stage');
|
|
148
295
|
const context = JSON.parse(readFileSync(path, 'utf8')) as Context;
|
|
149
296
|
if (context.graphId !== 'nightly-condense' || context.nodeId !== stage) throw new Error('nightly graph context mismatch');
|
|
150
297
|
const statePath = resolve(path, '..', '..', '..', '..');
|
|
151
|
-
const result = await runNightlyStage(stage, context, statePath, process.env.ELANOUS_GRAPH_DRY_RUN === '1'
|
|
298
|
+
const result = await runNightlyStage(stage, context, statePath, process.env.ELANOUS_GRAPH_DRY_RUN === '1',
|
|
299
|
+
new Date());
|
|
152
300
|
console.log(JSON.stringify(result));
|
|
153
301
|
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { readHqLease } from '../hq/hq.js';
|
|
2
|
+
import { fileLeaseStore } from '../hq/lease.js';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { effectiveInstanceRoot } from '../instance/resolve.js';
|
|
5
|
+
import { getElanousConfigDirOverride } from '../elanous-config-dir.js';
|
|
6
|
+
import { readLandingFreeze } from '../release-loop/landing-freeze.js';
|
|
7
|
+
import { entriesFromRegistry, checkLoops, countLoopStates } from '../loops/checker.js';
|
|
8
|
+
import { computeDrift, discoverUniverses } from '../cli/config-drift.js';
|
|
9
|
+
import { debug } from '../debug/log.js';
|
|
10
|
+
|
|
11
|
+
export type ControlStatusState = 'ok' | 'warn' | 'unmeasured';
|
|
12
|
+
export type ControlStatusRowName = '집' | '판' | '자원' | '자격' | '동결·몫' | '루프' | 'config';
|
|
13
|
+
export interface ControlStatusRow {
|
|
14
|
+
row: ControlStatusRowName;
|
|
15
|
+
state: ControlStatusState;
|
|
16
|
+
text: string;
|
|
17
|
+
reason?: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface ControlStatusDeps {
|
|
21
|
+
readLease?: () => ReturnType<typeof readHqLease> | Promise<ReturnType<typeof readHqLease>>;
|
|
22
|
+
readVersion?: () => ControlStatusRow | Promise<ControlStatusRow>;
|
|
23
|
+
readResources?: () => ControlStatusRow | Promise<ControlStatusRow>;
|
|
24
|
+
readCredentials?: () => ControlStatusRow | Promise<ControlStatusRow>;
|
|
25
|
+
readFreeze?: () => ReturnType<typeof readLandingFreeze> | Promise<ReturnType<typeof readLandingFreeze>>;
|
|
26
|
+
readLoops?: () => ReturnType<typeof entriesFromRegistry> | Promise<ReturnType<typeof entriesFromRegistry>>;
|
|
27
|
+
readDrift?: () => ReturnType<typeof computeDrift> | Promise<ReturnType<typeof computeDrift>>;
|
|
28
|
+
now?: () => Date;
|
|
29
|
+
log?: typeof debug.log;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const missing = (row: ControlStatusRowName, reason: string): ControlStatusRow => ({ row, state: 'unmeasured', text: '못 쟀다', reason });
|
|
33
|
+
|
|
34
|
+
/** Read-only snapshot: each source fails independently; no lease renewal, freeze mutation or config sync. */
|
|
35
|
+
export async function collectControlStatus(deps: ControlStatusDeps = {}): Promise<ControlStatusRow[]> {
|
|
36
|
+
const rows: ControlStatusRow[] = [];
|
|
37
|
+
const add = async (row: ControlStatusRowName, read: () => ControlStatusRow | Promise<ControlStatusRow>) => {
|
|
38
|
+
let result: ControlStatusRow;
|
|
39
|
+
try { result = await read(); }
|
|
40
|
+
catch (error) {
|
|
41
|
+
// Source errors may contain file contents or credentials; report the failure class, not the raw message.
|
|
42
|
+
result = missing(row, error instanceof Error ? `읽기 실패(${error.name})` : '읽기 실패');
|
|
43
|
+
}
|
|
44
|
+
rows.push(result);
|
|
45
|
+
try { (deps.log ?? debug.log.bind(debug))('control.status', 'row', { row, state: result.state }); }
|
|
46
|
+
catch { /* logging must not mask another row */ }
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
await add('집', async () => {
|
|
50
|
+
const readLease = deps.readLease ?? (() => readHqLease(getElanousConfigDirOverride() !== undefined
|
|
51
|
+
? { store: fileLeaseStore(join(effectiveInstanceRoot(), 'hq', 'lease.json')), hostPath: join(effectiveInstanceRoot(), 'hq', 'host') }
|
|
52
|
+
: {}));
|
|
53
|
+
const { record, ageSeconds, expired } = await readLease();
|
|
54
|
+
if (!record) return missing('집', '본부 임대 기록 없음');
|
|
55
|
+
return { row: '집', state: expired ? 'warn' : 'ok',
|
|
56
|
+
text: `본부 ${record.holder} · 세대 ${record.generation} · ${ageSeconds}초 전 · ${expired ? '만료' : '유효'}` };
|
|
57
|
+
});
|
|
58
|
+
await add('판', deps.readVersion ?? (() => missing('판', '기계별 install.json·데몬 판·공개 판 원천 미연결')));
|
|
59
|
+
await add('자원', deps.readResources ?? (() => missing('자원', '기계별 Pod 실행/대기/실패·풀 상한·호스트 부하 원천 미연결')));
|
|
60
|
+
await add('자격', deps.readCredentials ?? (() => missing('자격', '계정별 만료·쿼터·갱신자 원천 미연결')));
|
|
61
|
+
await add('동결·몫', async () => {
|
|
62
|
+
const freeze = await (deps.readFreeze ?? readLandingFreeze)();
|
|
63
|
+
return { row: '동결·몫', state: freeze ? 'warn' : 'unmeasured',
|
|
64
|
+
text: freeze ? `동결 켬 · ${freeze.reason} · 끝 ${freeze.until ?? '미지정'} · 자리별 몫 못 쟀다` : '동결 끔 · 자리별 몫 못 쟀다',
|
|
65
|
+
reason: '자리별 몫/도는 수 원천 미연결' };
|
|
66
|
+
});
|
|
67
|
+
await add('루프', async () => {
|
|
68
|
+
const { entries, scope } = await (deps.readLoops ?? entriesFromRegistry)();
|
|
69
|
+
if (entries.length === 0) return missing('루프', `${scope} 루프 목록에 측정 대상 없음`);
|
|
70
|
+
const results = checkLoops(entries, (deps.now ?? (() => new Date()))());
|
|
71
|
+
const counts = countLoopStates(results);
|
|
72
|
+
if (counts.unknown + counts.unregistered + counts.off === results.length) return missing('루프', '활성 등록 루프의 마지막 실행/예정 시각을 잴 수 없음');
|
|
73
|
+
const failing = results.filter(result => result.state === 'failing').map(result => result.id);
|
|
74
|
+
const late = results.filter(result => result.state === 'late').map(result => result.id);
|
|
75
|
+
return { row: '루프', state: failing.length || late.length || counts.unknown || counts.unregistered || counts.off ? 'warn' : 'ok',
|
|
76
|
+
text: `failing ${counts.failing}${failing.length ? ` (${failing.join(', ')})` : ''} · late ${counts.late}${late.length ? ` (${late.join(', ')})` : ''} · unknown ${counts.unknown} · unregistered ${counts.unregistered} · off ${counts.off}` };
|
|
77
|
+
});
|
|
78
|
+
await add('config', async () => {
|
|
79
|
+
const report = await (deps.readDrift ?? (() => computeDrift(discoverUniverses(getElanousConfigDirOverride() !== undefined
|
|
80
|
+
? { prodRoot: effectiveInstanceRoot(), registryPath: null, treeScanRoots: [] } : {}))))();
|
|
81
|
+
if (report.prodStatus !== 'ok' || !report.registry.ok || report.universes.unreadable > 0 || report.universes.compared === 0) {
|
|
82
|
+
return missing('config', `드리프트 비교 불가(집 ${report.prodStatus} · 레지스트리 ${report.registry.ok ? '읽음' : '못 읽음'} · 비교 ${report.universes.compared} · 못 읽은 우주 ${report.universes.unreadable})`);
|
|
83
|
+
}
|
|
84
|
+
return { row: 'config', state: report.rows.length ? 'warn' : 'ok', text: `드리프트 키 ${report.rows.length}` };
|
|
85
|
+
});
|
|
86
|
+
return rows;
|
|
87
|
+
}
|
|
@@ -67,12 +67,14 @@ export async function runLightMember(opts: LightMemberOptions): Promise<LightMem
|
|
|
67
67
|
const root = opts.root ?? effectiveInstanceRoot();
|
|
68
68
|
const configured = readMachineProfile(root);
|
|
69
69
|
if (configured) assertJoinedMachineId(configured.id, root);
|
|
70
|
-
|
|
70
|
+
// measureLoad is async (harness parents · disk I/O); a pending Promise would serialize as `{}` and blank `load`.
|
|
71
|
+
const pageoutBaseline: { value: number } = { value: Number.NaN };
|
|
72
|
+
const machineAttrs = async () => {
|
|
71
73
|
const profile = readMachineProfile(root);
|
|
72
74
|
if (profile && profile.id !== machine) {
|
|
73
75
|
throw new Error(`machine id ${profile.id} differs from joined machine ${machine}`);
|
|
74
76
|
}
|
|
75
|
-
return { load: measureLoad(now), ...(profile ? { duties: profile.duties, seats: profile.seats } : {}) };
|
|
77
|
+
return { load: await measureLoad(now, pageoutBaseline), ...(profile ? { duties: profile.duties, seats: profile.seats } : {}) };
|
|
76
78
|
};
|
|
77
79
|
const interval = opts.intervalMs ?? DEFAULT_INTERVAL_MS;
|
|
78
80
|
if (!Number.isFinite(interval) || interval <= 0 || interval > MAX_RETRY_MS) throw new Error('invalid member heartbeat interval');
|
|
@@ -119,7 +121,7 @@ export async function runLightMember(opts: LightMemberOptions): Promise<LightMem
|
|
|
119
121
|
while (!signal.aborted) {
|
|
120
122
|
const resources: LightMemberResult['resources'] = [];
|
|
121
123
|
try {
|
|
122
|
-
const attrs = machineAttrs();
|
|
124
|
+
const attrs = await machineAttrs();
|
|
123
125
|
await post(machineId, registered.has(machineId) ? { attrs } : {
|
|
124
126
|
id: machineId, kind: 'machine', machine, name: machine, owner: '',
|
|
125
127
|
attrs, observedAt: now(), ttlMs: TTL_MS,
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { execFile } from 'node:child_process';
|
|
2
|
+
import { readFileSync, promises as fs } from 'node:fs';
|
|
3
|
+
import { promisify } from 'node:util';
|
|
4
|
+
import { cpus, freemem, loadavg, platform, totalmem } from 'node:os';
|
|
3
5
|
import { join } from 'node:path';
|
|
4
6
|
import { debug } from '../debug/log.js';
|
|
5
7
|
import { effectiveInstanceRoot } from '../instance/resolve.js';
|
|
@@ -12,13 +14,173 @@ const MAX_RETRY_MS = 300_000;
|
|
|
12
14
|
// Ordinary ticks start within the 10-minute TTL; first failure retries within 30 seconds.
|
|
13
15
|
const MAX_INTERVAL_MS = MAX_RETRY_MS;
|
|
14
16
|
const REQUEST_TIMEOUT_MS = 5_000;
|
|
17
|
+
const PROCESS_PROBE_TIMEOUT_MS = 2_000;
|
|
18
|
+
const execFileAsync = promisify(execFile);
|
|
15
19
|
|
|
16
|
-
|
|
20
|
+
type Measurement<T> = { value: T; source: string; observedAt: number; status: 'ok' }
|
|
21
|
+
| { value: null; source: string; observedAt: number; status: 'unmeasured'; reason: string };
|
|
22
|
+
|
|
23
|
+
function unmeasured<T>(source: string, observedAt: number, reason: string): Measurement<T> {
|
|
24
|
+
return { value: null, source, observedAt, status: 'unmeasured', reason };
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface HarnessParentSummary {
|
|
28
|
+
count: Measurement<number>;
|
|
29
|
+
oldestAgeSeconds: Measurement<number>;
|
|
30
|
+
versionMismatchCount: Measurement<number>;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Pod image commit is fixed at process startup; file paths and mutable package versions are not process identities.
|
|
34
|
+
const STARTUP_IMAGE_COMMIT = process.env.ELANOUS_IMAGE_COMMIT?.trim();
|
|
35
|
+
const HARNESS_PARENT_COMMAND = /^(?:(?:\S*\/)?(?:bun|node)\s+)?(?:\S*\/)?elanous\.mjs\s+harness\s+(?:say|ask)(?:\s|$)/;
|
|
36
|
+
|
|
37
|
+
export function parseHarnessParentPs(text: string): HarnessProcessList {
|
|
38
|
+
if (!text.trim()) return { status: 'incomplete', stage: 'ps-parse' };
|
|
39
|
+
const records: Array<{ pid: number; command: string; elapsedSeconds: number }> = [];
|
|
40
|
+
let incomplete = false;
|
|
41
|
+
for (const line of text.split('\n')) {
|
|
42
|
+
const candidate = line.trim().match(/^\d+\s+\S+\s+(.+)$/)?.[1];
|
|
43
|
+
if (!candidate || !HARNESS_PARENT_COMMAND.test(candidate)) continue;
|
|
44
|
+
const match = line.trim().match(/^(\d+)\s+(?:(\d+)-)?(?:(\d+):)?(\d+):(\d+)\s+(.+)$/);
|
|
45
|
+
if (!match) { incomplete = true; continue; }
|
|
46
|
+
const pid = Number(match[1]);
|
|
47
|
+
const days = Number(match[2] ?? 0);
|
|
48
|
+
const hours = Number(match[3] ?? 0);
|
|
49
|
+
const minutes = Number(match[4]);
|
|
50
|
+
const seconds = Number(match[5]);
|
|
51
|
+
if (!Number.isSafeInteger(pid) || pid <= 0 || minutes >= 60 || seconds >= 60 || (match[3] !== undefined && hours >= 24)) { incomplete = true; continue; }
|
|
52
|
+
records.push({ pid, elapsedSeconds: days * 86_400 + hours * 3_600 + minutes * 60 + seconds, command: match[6]! });
|
|
53
|
+
}
|
|
54
|
+
return incomplete ? { status: 'incomplete', stage: 'ps-parse' } : { status: 'ok', records };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
interface HarnessProcessList {
|
|
58
|
+
status: 'ok' | 'incomplete' | 'failed';
|
|
59
|
+
stage?: string;
|
|
60
|
+
records?: readonly { pid: number; command: string; elapsedSeconds: number; version?: string }[];
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async function readHarnessProcessVersion(pid: number): Promise<string | undefined> {
|
|
64
|
+
if (platform() !== 'linux') return undefined;
|
|
65
|
+
try {
|
|
66
|
+
const env = await fs.readFile(`/proc/${pid}/environ`, { encoding: 'utf8', signal: AbortSignal.timeout(PROCESS_PROBE_TIMEOUT_MS) });
|
|
67
|
+
const versions = env.split('\0').filter(entry => entry.startsWith('ELANOUS_IMAGE_COMMIT='))
|
|
68
|
+
.map(entry => entry.slice('ELANOUS_IMAGE_COMMIT='.length).trim());
|
|
69
|
+
return versions.length === 1 && versions[0] ? versions[0] : undefined;
|
|
70
|
+
} catch { return undefined; }
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export async function listHarnessParents(
|
|
74
|
+
run: typeof execFileAsync = execFileAsync,
|
|
75
|
+
readVersion: (pid: number) => Promise<string | undefined> = readHarnessProcessVersion,
|
|
76
|
+
): Promise<HarnessProcessList> {
|
|
77
|
+
try {
|
|
78
|
+
const { stdout } = await run('ps', ['-axo', 'pid=,etime=,command='], {
|
|
79
|
+
encoding: 'utf8', timeout: PROCESS_PROBE_TIMEOUT_MS, maxBuffer: 4 * 1024 * 1024,
|
|
80
|
+
});
|
|
81
|
+
const parsed = parseHarnessParentPs(stdout);
|
|
82
|
+
if (parsed.status !== 'ok' || !parsed.records) return parsed;
|
|
83
|
+
const records = await Promise.all(parsed.records.map(async record => {
|
|
84
|
+
const version = await readVersion(record.pid).catch(() => undefined);
|
|
85
|
+
return { ...record, ...(version ? { version } : {}) };
|
|
86
|
+
}));
|
|
87
|
+
return { status: 'ok', records };
|
|
88
|
+
} catch { return { status: 'failed', stage: 'ps-exec' }; }
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** A process command/path is not the version loaded into a running process. Only startup-stamped identities count. */
|
|
92
|
+
export function measureHarnessParents(
|
|
93
|
+
observedAt = Date.now(),
|
|
94
|
+
observation: HarnessProcessList = { status: 'failed', stage: 'ps-exec' },
|
|
95
|
+
currentVersion?: string,
|
|
96
|
+
): HarnessParentSummary {
|
|
97
|
+
const source = 'ps:harness-processes';
|
|
98
|
+
if (observation.status !== 'ok' || !observation.records) {
|
|
99
|
+
const reason = observation.status === 'incomplete' ? 'ps-parse incomplete' : (observation.stage ?? 'ps-exec');
|
|
100
|
+
return { count: unmeasured(source, observedAt, reason), oldestAgeSeconds: unmeasured(source, observedAt, reason),
|
|
101
|
+
versionMismatchCount: unmeasured(source, observedAt, reason) };
|
|
102
|
+
}
|
|
103
|
+
const ok = (value: number): Measurement<number> => ({ value, source, observedAt, status: 'ok' });
|
|
104
|
+
const records = observation.records.filter(record => HARNESS_PARENT_COMMAND.test(record.command));
|
|
105
|
+
const count = ok(records.length);
|
|
106
|
+
const oldestAgeSeconds = records.length === 0
|
|
107
|
+
? unmeasured<number>(source, observedAt, 'no-parents')
|
|
108
|
+
: records.every(record => Number.isFinite(record.elapsedSeconds) && record.elapsedSeconds >= 0)
|
|
109
|
+
? ok(Math.max(...records.map(record => record.elapsedSeconds)))
|
|
110
|
+
: unmeasured<number>(source, observedAt, 'elapsed-unavailable');
|
|
111
|
+
const versionMismatchCount = records.length === 0 ? ok(0)
|
|
112
|
+
: currentVersion && records.every(record => typeof record.version === 'string' && record.version.length > 0)
|
|
113
|
+
? ok(records.filter(record => record.version !== currentVersion).length)
|
|
114
|
+
: unmeasured<number>(source, observedAt, 'version-unavailable');
|
|
115
|
+
return { count, oldestAgeSeconds, versionMismatchCount };
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export interface DiskIoPressure {
|
|
119
|
+
pageouts: Measurement<number>;
|
|
120
|
+
io: Measurement<{ someAvg10: number; fullAvg10: number }>;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export async function measureDiskIoPressure(
|
|
124
|
+
observedAt = Date.now(),
|
|
125
|
+
deps: { platform?: NodeJS.Platform; vmStat?: () => Promise<string> | string; readPressure?: () => Promise<string> | string } = {},
|
|
126
|
+
pageoutBaseline?: { value: number },
|
|
127
|
+
): Promise<DiskIoPressure> {
|
|
128
|
+
const os = deps.platform ?? platform();
|
|
129
|
+
let pageouts: Measurement<number> = unmeasured('vm_stat:pageouts', observedAt, 'platform');
|
|
130
|
+
if (os === 'darwin') {
|
|
131
|
+
try {
|
|
132
|
+
const text = await (deps.vmStat ?? (async () => (await execFileAsync('vm_stat', [], {
|
|
133
|
+
encoding: 'utf8', timeout: PROCESS_PROBE_TIMEOUT_MS, maxBuffer: 1024 * 1024,
|
|
134
|
+
})).stdout))();
|
|
135
|
+
// Real macOS vm_stat prints `Pageouts:`; older/other formats print `Pages paged out:`.
|
|
136
|
+
const value = text.match(/^(?:Pageouts|Pages paged out):\s*([\d,]+)\s*\.?\s*$/m)?.[1];
|
|
137
|
+
if (value && Number.isSafeInteger(Number(value.replaceAll(',', '')))) {
|
|
138
|
+
const total = Number(value.replaceAll(',', ''));
|
|
139
|
+
pageouts = pageoutBaseline
|
|
140
|
+
? Number.isFinite(pageoutBaseline.value) && total >= pageoutBaseline.value
|
|
141
|
+
? { value: total - pageoutBaseline.value, source: 'vm_stat:pageouts', observedAt, status: 'ok' }
|
|
142
|
+
: unmeasured('vm_stat:pageouts', observedAt, 'baseline-unavailable')
|
|
143
|
+
: { value: total, source: 'vm_stat:pageouts', observedAt, status: 'ok' };
|
|
144
|
+
if (pageoutBaseline) pageoutBaseline.value = total;
|
|
145
|
+
}
|
|
146
|
+
else pageouts = unmeasured('vm_stat:pageouts', observedAt, 'invalid-output');
|
|
147
|
+
} catch { pageouts = unmeasured('vm_stat:pageouts', observedAt, 'vm_stat-failed'); }
|
|
148
|
+
}
|
|
149
|
+
let io: DiskIoPressure['io'] = unmeasured('/proc/pressure/io', observedAt, 'platform');
|
|
150
|
+
if (os === 'linux') {
|
|
151
|
+
try {
|
|
152
|
+
const text = await (deps.readPressure ?? (() => fs.readFile('/proc/pressure/io', {
|
|
153
|
+
encoding: 'utf8', signal: AbortSignal.timeout(PROCESS_PROBE_TIMEOUT_MS),
|
|
154
|
+
})))();
|
|
155
|
+
const some = text.match(/^some\s+avg10=(\d+(?:\.\d+)?)(?:\s|$)/m)?.[1];
|
|
156
|
+
const full = text.match(/^full\s+avg10=(\d+(?:\.\d+)?)(?:\s|$)/m)?.[1];
|
|
157
|
+
if (some !== undefined && full !== undefined && Number.isFinite(Number(some)) && Number.isFinite(Number(full)))
|
|
158
|
+
io = { value: { someAvg10: Number(some), fullAvg10: Number(full) }, source: '/proc/pressure/io', observedAt, status: 'ok' };
|
|
159
|
+
else io = unmeasured('/proc/pressure/io', observedAt, 'invalid-output');
|
|
160
|
+
} catch { io = unmeasured('/proc/pressure/io', observedAt, 'pressure-read-failed'); }
|
|
161
|
+
}
|
|
162
|
+
return { pageouts, io };
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export async function measureLoad(
|
|
166
|
+
now: () => number = Date.now,
|
|
167
|
+
pageoutBaseline?: { value: number },
|
|
168
|
+
deps: { listParents?: () => Promise<HarnessProcessList>; diskIo?: (at: number, baseline?: { value: number }) => Promise<DiskIoPressure>; currentVersion?: string } = {},
|
|
169
|
+
): Promise<{
|
|
17
170
|
loadAvg: number[]; cpuCount: number; freeMem: number; totalMem: number; observedAt: number;
|
|
18
|
-
|
|
171
|
+
harnessParents: HarnessParentSummary; diskIo: DiskIoPressure;
|
|
172
|
+
}> {
|
|
173
|
+
const observedAt = now();
|
|
174
|
+
const [parents, diskIo] = await Promise.all([
|
|
175
|
+
(deps.listParents ?? listHarnessParents)().catch((): HarnessProcessList => ({ status: 'failed', stage: 'ps-exec' })),
|
|
176
|
+
(deps.diskIo ?? ((at, baseline) => measureDiskIoPressure(at, {}, baseline)))(observedAt, pageoutBaseline)
|
|
177
|
+
.catch((): DiskIoPressure => ({ pageouts: unmeasured<number>('vm_stat:pageouts', observedAt, 'probe-failed'),
|
|
178
|
+
io: unmeasured<{ someAvg10: number; fullAvg10: number }>('/proc/pressure/io', observedAt, 'probe-failed') })),
|
|
179
|
+
]);
|
|
19
180
|
return {
|
|
20
181
|
loadAvg: loadavg(), cpuCount: cpus().length,
|
|
21
|
-
freeMem: freemem(), totalMem: totalmem(), observedAt
|
|
182
|
+
freeMem: freemem(), totalMem: totalmem(), observedAt,
|
|
183
|
+
harnessParents: measureHarnessParents(observedAt, parents, deps.currentVersion ?? STARTUP_IMAGE_COMMIT), diskIo,
|
|
22
184
|
};
|
|
23
185
|
}
|
|
24
186
|
|
|
@@ -50,6 +212,7 @@ export interface MemberHeartbeatOptions {
|
|
|
50
212
|
intervalMs?: number;
|
|
51
213
|
fetch?: (input: URL | RequestInfo, init?: RequestInit) => Promise<Response>;
|
|
52
214
|
now?: () => number;
|
|
215
|
+
measure?: (now: () => number, pageoutBaseline: { value: number }) => Promise<Awaited<ReturnType<typeof measureLoad>>>;
|
|
53
216
|
}
|
|
54
217
|
|
|
55
218
|
/** 넥서스 멤버의 기계 신원 — OS hostname 이 아니라 임대·관제가 쓰는 기계 식별자(`resolveMachineName`)를 쓴다.
|
|
@@ -81,6 +244,8 @@ export function startMemberHeartbeat(opts: MemberHeartbeatOptions): () => void {
|
|
|
81
244
|
let failures = 0;
|
|
82
245
|
let lastFailure: string | undefined;
|
|
83
246
|
const machineId = opts.machine.id;
|
|
247
|
+
const pageoutBaseline: { value: number } = { value: Number.NaN };
|
|
248
|
+
const machineLoad = () => (opts.measure ?? measureLoad)(now, pageoutBaseline);
|
|
84
249
|
// 자원의 `machine` 칸 = 기계 «이름»(가벼운 멤버·임대와 같은 값). 기계 범위 토큰은 이 칸을 토큰의 기계와 대조한다(server.ts requireMachine).
|
|
85
250
|
const machineName = opts.machine.name;
|
|
86
251
|
|
|
@@ -106,7 +271,7 @@ export function startMemberHeartbeat(opts: MemberHeartbeatOptions): () => void {
|
|
|
106
271
|
const register = async (): Promise<void> => {
|
|
107
272
|
await post('/v1/resources/register', {
|
|
108
273
|
...opts.machine, kind: 'machine', machine: machineName, owner: '',
|
|
109
|
-
attrs: { ...opts.machine.attrs, load:
|
|
274
|
+
attrs: { ...opts.machine.attrs, load: await machineLoad() }, observedAt: now(), ttlMs: MAX_RETRY_MS * 2,
|
|
110
275
|
});
|
|
111
276
|
await post('/v1/resources/register', {
|
|
112
277
|
...opts.instance, kind: 'instance', machine: machineName, owner: '',
|
|
@@ -125,7 +290,7 @@ export function startMemberHeartbeat(opts: MemberHeartbeatOptions): () => void {
|
|
|
125
290
|
try {
|
|
126
291
|
if (!registered) await register();
|
|
127
292
|
else {
|
|
128
|
-
await post(`/v1/resources/${encodeURIComponent(machineId)}/heartbeat`, { attrs: { load:
|
|
293
|
+
await post(`/v1/resources/${encodeURIComponent(machineId)}/heartbeat`, { attrs: { load: await machineLoad() } });
|
|
129
294
|
heartbeatsSinceInstanceRefresh++;
|
|
130
295
|
if (heartbeatsSinceInstanceRefresh * interval >= MAX_RETRY_MS) {
|
|
131
296
|
await post(`/v1/resources/${encodeURIComponent(opts.instance.id)}/heartbeat`, {});
|