elanous 0.2.22 → 0.2.24
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/NOTICE +7 -0
- package/README.md +32 -22
- package/apps/pwa/out/404/index.html +1 -1
- package/apps/pwa/out/404.html +1 -1
- package/apps/pwa/out/_next/static/{aBi6EyMtLsrl7J8eH4kF7 → Q-7iofoxOEv1WX18S35of}/_buildManifest.js +1 -1
- package/apps/pwa/out/_next/static/chunks/{119fc678.6f591cdb84ca5791.js → 0673cc54.8e2ef1c23e49e190.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{a9f2b54d-40c16bd0efdb04b8.js → 0a09e062-34f747a17d8d8c2e.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{f8626309.a4e5d362b1d55e59.js → 0e3acd34.d42b2ef87fdf4234.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/1168.a0fb3d9db76c00bd.js +77 -0
- package/apps/pwa/out/_next/static/chunks/{7175-350f4ce2873bc038.js → 1325-dc3f3c21eb181cb4.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/1357.a843c41d2cd7ec31.js +34 -0
- package/apps/pwa/out/_next/static/chunks/1430.53f49882b17607c2.js +24 -0
- package/apps/pwa/out/_next/static/chunks/1459-be6509b3419d8a9f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{6767.8e6c5f6e744fcd9f.js → 1505.5826671109111e75.js} +2 -2
- package/apps/pwa/out/_next/static/chunks/153.0a4e3cd516930491.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1572.7bc291352e241a45.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1593-d07ad068c120dfb0.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1608.b7c900baa56cab32.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1615-4d8156febaac4b35.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1646.e03f0fbc26bc72f6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{7327-2d69fb321ba73389.js → 1669-da843ed73aa301f5.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/176.13ba0659e3e96fd4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1990.9c60f495ef33c070.js +36 -0
- package/apps/pwa/out/_next/static/chunks/2042e469.f86158dd5c1e51a1.js +122 -0
- package/apps/pwa/out/_next/static/chunks/2528.0af5c0e1fad88df6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2543-28ec6aeb80a5e1e1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2561.43c99d6ee914ee6a.js +62 -0
- package/apps/pwa/out/_next/static/chunks/2638.b39ab2f674caa1a5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/272-0eed0fb0aab716b6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{7899.597af4a9996c907f.js → 2721.bc9a8f36bd85c07a.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/2778.67e0c1d484189ccf.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2841.10ff309f6a3168af.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2886-9639f97d523d752a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2978.57fb87ce21cc5a85.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3664.15f968c39d15bd28.js +149 -0
- package/apps/pwa/out/_next/static/chunks/3786-935eb3fcde90e496.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3813.df4a01ae89c6f63f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3961-aaae04ed1d32622f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3992.f79c70480e8c4077.js +309 -0
- package/apps/pwa/out/_next/static/chunks/40-413e8641de9ed1e5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4054-521f11a503daed64.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4315.e7356f34c7d8ec79.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4489-bf629de6f04f7681.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4540.557733cc88c6a82b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/462.0275b4338c5b6337.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4658-7ed9266098a787e4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4945-0f4046cfc3228ea3.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{d70879db.c66c56a95a91efc6.js → 4c5e95ce.83e9b16add6fcd8c.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/5105-18b4165c5d3a86b0.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5205-6992210fe31f45f4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5287-72898b126d722457.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{6158.566c890adc06a459.js → 5313.1697309c532b9856.js} +2 -2
- package/apps/pwa/out/_next/static/chunks/{79b78388.663188133449d707.js → 53746282.1be36038d1abd949.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/5474.991b90688c3cb321.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5544-871146192d095594.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5578.ac963634373b28c6.js +286 -0
- package/apps/pwa/out/_next/static/chunks/5935.d5e8ca3e096772e8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5967-c0a80f8ffe5d4050.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{1268.0840ff91e083fcbd.js → 5974.360f8441eb0ac1ec.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/6006.84e8b418a7a0db97.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6013-1267ba7454d6764b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6038-1e0f7ee1086da4cf.js +1 -0
- package/apps/pwa/out/_next/static/chunks/617.709dd0019fe99796.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6261.fb9bf94947094636.js +74 -0
- package/apps/pwa/out/_next/static/chunks/6411.1966866fae31ffa9.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6481-b829d99aa8d158f5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6541.2b7daa043100d0fa.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6571-926ddf024a6ad5df.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6597.2d7a6d77ab485b27.js +64 -0
- package/apps/pwa/out/_next/static/chunks/67.5dd2dc543c2fa3de.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{9832.2ab2e818013574c7.js → 6702.ebbd165d5e4a4a14.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/6777-312496b0d774719b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/681.0116f7036cfb4e8d.js +32 -0
- package/apps/pwa/out/_next/static/chunks/6871.30b2bf23b3126e36.js +24 -0
- package/apps/pwa/out/_next/static/chunks/6882.737d366d54e29d83.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7035.54c2c535164b1c9b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7086-8bfa7e8f796a03d9.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7126.5e5599f251726b67.js +93 -0
- package/apps/pwa/out/_next/static/chunks/7203-47ff0740a293e284.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7268.017f49dafb5f0ff3.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7355.5d65a87e9567ae08.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7392.4018c2a62177f296.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{6110.1297d4093fdfbb0d.js → 740.90b31555290236a9.js} +2 -2
- package/apps/pwa/out/_next/static/chunks/7471.3a3d1be50559cfb8.js +148 -0
- package/apps/pwa/out/_next/static/chunks/7500.73489a345cef2201.js +226 -0
- package/apps/pwa/out/_next/static/chunks/{9070.067c106528ec1ae6.js → 7518.49161e8f5814abdb.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/7565.c2f72a15c1ed0396.js +27 -0
- package/apps/pwa/out/_next/static/chunks/7651.6d1c984d3d8587a4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7713.7ab863ed59b2d661.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7793.d7cd2026767ddd54.js +115 -0
- package/apps/pwa/out/_next/static/chunks/7837.c7c267e5dceadb11.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7891.09266382daf70d1c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7905.b230b9d06cb4ad5d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7990-cbf6a01bcbdcb7e6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8119-819c549e0c197786.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8186-74ea19a3700d04a8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{8194.484f51e273d9abd2.js → 8264.00e434fbd02e9311.js} +16 -16
- package/apps/pwa/out/_next/static/chunks/8267-e7b3262b735ae106.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8386.86518e55b75f7cf5.js +8 -0
- package/apps/pwa/out/_next/static/chunks/{088d8e8a.8069543d082bec12.js → 83cfc5c8.349535543c1548b6.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/8437.ba6eeeb29590042f.js +23 -0
- package/apps/pwa/out/_next/static/chunks/{1948.5f35b39eef222a94.js → 8438.ee7f0df3b3f6fcb4.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/8469-1500be57e582296f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8483-7164cecdaf178ca6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{4588.ad3364cb7fb97ada.js → 8532.a86e99acf9b761cb.js} +2 -2
- package/apps/pwa/out/_next/static/chunks/8569.b853a8daa8f31d12.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8722-4c940f02c496ad36.js +1 -0
- package/apps/pwa/out/_next/static/chunks/887-41d0dcf5af48c628.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8925-2abfb48af477fb7f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{88729267.9c6c000157c83f2f.js → 89849432.9eaa2f4c9c010965.js} +3 -3
- package/apps/pwa/out/_next/static/chunks/9039.da86361496797522.js +90 -0
- package/apps/pwa/out/_next/static/chunks/905.d5745a25884754d4.js +94 -0
- package/apps/pwa/out/_next/static/chunks/9089-d909f3d4478b0628.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9096.dc5c7a267f83790c.js +41 -0
- package/apps/pwa/out/_next/static/chunks/93.a567257b6bdb7fe4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9320.419acb9665b46597.js +287 -0
- package/apps/pwa/out/_next/static/chunks/9356.b8ceda6634612ff8.js +79 -0
- package/apps/pwa/out/_next/static/chunks/94.e9e1043b35cd3eed.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9400-7498ea90b2e6ee8f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{3522-1de1319d028ac89c.js → 9502-32c5dc22e8f120f5.js} +3 -3
- package/apps/pwa/out/_next/static/chunks/9930-58644af9c813d6e1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9932.367cf4725fedd6e2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9935.1628b904cdc8ce0e.js +104 -0
- package/apps/pwa/out/_next/static/chunks/{7630.2b9a68659053ff4e.js → 9976.8f28864317639fe6.js} +16 -16
- package/apps/pwa/out/_next/static/chunks/app/_not-found/page-a2aa8709d193ee0d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-3b9fc39362473ae7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/autopilot/page-f2b62c19ca68fef8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/board/page-d7602d489edf5f37.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/botlab/page-24282bf9f90031a5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/bots/page-8b7c5ab6b5bc8d64.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ceo/page-4eb84d454f8c3415.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/chat/page-b2e494bdad57fcb5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/consult/{page-80ed511e02c81412.js → page-c0ab229673a8b0eb.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/control/page-3e2b45f3c36dbac4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/decisions/page-c22aa511b28743ad.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-26345242e4049f55.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/editor/page-bf88c0f558d8ef54.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/exec/page-52fec523e5b5d3ad.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/field/page-67723dfdcde92b31.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/healthz/route-a2aa8709d193ee0d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/inside/page-22725e2570f1126b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/intake/page-1891335950d68b70.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/layout-3267af1ebdc73cf5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/live/page-c314b75c11234998.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/live-run/page-9f9c85415f61782b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/loops/page-c1d4276059085587.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/market/page-833aab587f26ad4c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-8526c17ae38e6a50.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/morning/page-f998637a8402115c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/not-found-b7bb53828b17b335.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-1b1850de0257e46d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-6f23159fedb41f67.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-1d809a87139dd024.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-da9e570234d481fd.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/outputs/page-b92caddb5c715f30.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/page-8ce3a38e210b810a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-57ffeeb6019440fd.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-abbb417bf514630b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-2c2b3f29cec1efa4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-9369279f72fc7b51.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/page-64861f14039499c3.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-ddc4c93efd8ab94b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/layout-a2aa8709d193ee0d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/page-901a1391b81053bc.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/share/page-68d9bbaac9841a58.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-b5c0c996f329371e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-89881ccbcf728978.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/term/page-0e87a803b3d7165e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/today/page-58d4e5cb30ef4871.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/trace/page-02ce4c3b5f39e4d2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/vault/page-e620d1bb8eadcec2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-acf868c64d77f950.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-54fa605fa6dba457.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-bc79f0833533fa84.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-d5c4398eea03d8f3.js +1 -0
- package/apps/pwa/out/_next/static/chunks/b4e12462-5fab4a813a63dadd.js +1 -0
- package/apps/pwa/out/_next/static/chunks/cd39fa05.fac4545f42a3ef70.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{f6b7c9ba.64a895b8b2fedbdc.js → ce383d63.d2ede338788ed18e.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{framework-5c169757551134ed.js → framework-b2763b10dc6b3836.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/main-app-27893e93b0ec193d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/main-f280047194e1daf8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/pages/_app-e90eff2d0b70564c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/pages/_error-70f2d654b3435d34.js +1 -0
- package/apps/pwa/out/_next/static/chunks/webpack-4794f053bd5e4230.js +1 -0
- package/apps/pwa/out/_next/static/css/72becfd42807b50d.css +1 -0
- package/apps/pwa/out/_next/static/css/d0a48dc099ed86cf.css +1 -0
- package/apps/pwa/out/approvals/index.html +1 -1
- package/apps/pwa/out/approvals/index.txt +23 -23
- package/apps/pwa/out/autopilot/index.html +1 -1
- package/apps/pwa/out/autopilot/index.txt +23 -23
- package/apps/pwa/out/board/index.html +1 -1
- package/apps/pwa/out/board/index.txt +23 -23
- package/apps/pwa/out/botlab/index.html +1 -1
- package/apps/pwa/out/botlab/index.txt +22 -22
- package/apps/pwa/out/bots/index.html +1 -1
- package/apps/pwa/out/bots/index.txt +22 -22
- package/apps/pwa/out/ceo/index.html +1 -1
- package/apps/pwa/out/ceo/index.txt +23 -23
- package/apps/pwa/out/chat/index.html +1 -1
- package/apps/pwa/out/chat/index.txt +22 -22
- package/apps/pwa/out/consult/index.html +1 -1
- package/apps/pwa/out/consult/index.txt +23 -23
- package/apps/pwa/out/control/index.html +1 -1
- package/apps/pwa/out/control/index.txt +23 -23
- package/apps/pwa/out/decisions/index.html +1 -1
- package/apps/pwa/out/decisions/index.txt +22 -22
- package/apps/pwa/out/design-check/index.html +1 -1
- package/apps/pwa/out/design-check/index.txt +23 -23
- package/apps/pwa/out/editor/index.html +1 -1
- package/apps/pwa/out/editor/index.txt +24 -23
- package/apps/pwa/out/exec/index.html +1 -1
- package/apps/pwa/out/exec/index.txt +23 -23
- package/apps/pwa/out/field/index.html +1 -1
- package/apps/pwa/out/field/index.txt +23 -23
- package/apps/pwa/out/healthz +1 -1
- package/apps/pwa/out/index.html +1 -1
- package/apps/pwa/out/index.txt +22 -22
- package/apps/pwa/out/inside/index.html +1 -1
- package/apps/pwa/out/inside/index.txt +22 -22
- package/apps/pwa/out/intake/index.html +1 -1
- package/apps/pwa/out/intake/index.txt +23 -23
- package/apps/pwa/out/live/index.html +1 -1
- package/apps/pwa/out/live/index.txt +23 -23
- package/apps/pwa/out/live-run/index.html +1 -1
- package/apps/pwa/out/live-run/index.txt +24 -23
- package/apps/pwa/out/loops/index.html +1 -1
- package/apps/pwa/out/loops/index.txt +22 -22
- package/apps/pwa/out/market/index.html +1 -1
- package/apps/pwa/out/market/index.txt +22 -22
- package/apps/pwa/out/missions/placeholder/index.html +1 -1
- package/apps/pwa/out/missions/placeholder/index.txt +22 -22
- package/apps/pwa/out/morning/index.html +1 -1
- package/apps/pwa/out/morning/index.txt +23 -23
- package/apps/pwa/out/observatory/index.html +1 -1
- package/apps/pwa/out/observatory/index.txt +23 -23
- package/apps/pwa/out/ops/checklist/index.html +1 -1
- package/apps/pwa/out/ops/checklist/index.txt +22 -22
- package/apps/pwa/out/ops/release/index.html +1 -1
- package/apps/pwa/out/ops/release/index.txt +22 -22
- package/apps/pwa/out/ops/seats/index.html +1 -1
- package/apps/pwa/out/ops/seats/index.txt +22 -22
- package/apps/pwa/out/outputs/index.html +1 -1
- package/apps/pwa/out/outputs/index.txt +22 -22
- package/apps/pwa/out/reflection/index.html +1 -1
- package/apps/pwa/out/reflection/index.txt +23 -23
- package/apps/pwa/out/scheduler/index.html +1 -1
- package/apps/pwa/out/scheduler/index.txt +23 -23
- package/apps/pwa/out/sessions/index.html +1 -1
- package/apps/pwa/out/sessions/index.txt +22 -22
- package/apps/pwa/out/settings/devices/index.html +1 -1
- package/apps/pwa/out/settings/devices/index.txt +23 -23
- package/apps/pwa/out/settings/index.html +1 -1
- package/apps/pwa/out/settings/index.txt +23 -23
- package/apps/pwa/out/setup/done/index.html +1 -1
- package/apps/pwa/out/setup/done/index.txt +23 -23
- package/apps/pwa/out/setup/index.html +1 -1
- package/apps/pwa/out/setup/index.txt +23 -23
- package/apps/pwa/out/share/index.html +1 -1
- package/apps/pwa/out/share/index.txt +23 -23
- package/apps/pwa/out/showroom/index.html +1 -1
- package/apps/pwa/out/showroom/index.txt +22 -22
- package/apps/pwa/out/tasks/index.html +1 -1
- package/apps/pwa/out/tasks/index.txt +23 -23
- package/apps/pwa/out/term/index.html +1 -1
- package/apps/pwa/out/term/index.txt +23 -23
- package/apps/pwa/out/today/index.html +1 -1
- package/apps/pwa/out/today/index.txt +23 -23
- package/apps/pwa/out/trace/index.html +1 -1
- package/apps/pwa/out/trace/index.txt +23 -23
- package/apps/pwa/out/vault/index.html +1 -1
- package/apps/pwa/out/vault/index.txt +23 -23
- package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
- package/apps/pwa/out/workflows/chat-ui/index.txt +23 -23
- package/apps/pwa/out/workflows/index.html +1 -1
- package/apps/pwa/out/workflows/index.txt +23 -23
- package/apps/pwa/out/workspace/index.html +1 -1
- package/apps/pwa/out/workspace/index.txt +23 -23
- package/apps/pwa/out/worktrees/index.html +1 -1
- package/apps/pwa/out/worktrees/index.txt +23 -23
- package/catalog/models/anthropic/claude-haiku-5-5.yaml +32 -0
- package/catalog/models/anthropic/claude-opus-5-5.yaml +5 -0
- package/catalog/models/anthropic/claude-sonnet-5-5.yaml +31 -0
- package/catalog/models/anthropic/claude-sonnet-5.yaml +8 -2
- package/catalog/models/openai/gpt-6-astra.yaml +6 -0
- package/catalog/models/openai/gpt-6-luna.yaml +6 -0
- package/catalog/models/openai/gpt-6-sol.yaml +6 -0
- package/catalog/models/openrouter/deepseek-v4-pro-0813.yaml +39 -0
- package/catalog/models/openrouter/deepseek-v4.1-flash.yaml +38 -0
- package/catalog/models/openrouter/glm-5.3-flash.yaml +35 -0
- package/catalog/providers/openrouter.yaml +2 -0
- package/graphs/implement-loop.yaml +31 -0
- package/graphs/log-horizon/log-horizon-watch.yaml +25 -0
- package/graphs/log-horizon/recipes.yaml +3 -0
- package/graphs/ops/harness-gc.yaml +17 -0
- package/graphs/ops/recipes.yaml +3 -0
- package/graphs/overlays/README.md +1 -1
- package/graphs/overlays/stop-heal-ladder.yaml +105 -0
- package/graphs/release/recipes.yaml +1 -1
- package/graphs/release/release-loop.yaml +6 -2
- package/graphs/release-rehearsal/recipes.yaml +12 -0
- package/graphs/release-rehearsal/release-rehearsal.yaml +17 -0
- package/package.json +4 -1
- package/scripts/add-api-key.sh +1 -1
- package/scripts/bench-report.ts +82 -3
- package/scripts/ci-typecheck-changed.ts +80 -14
- package/scripts/fleet/bedrock-pod-secret.ts +122 -0
- package/scripts/fleet/node-setup-linux.sh +97 -0
- package/scripts/fleet/node-setup.sh +11 -0
- package/scripts/fleet/zai-pod-secret.ts +86 -0
- package/scripts/hq/hq-fence-wrapper.sh +148 -0
- package/scripts/land-global-checks.ts +3 -0
- package/scripts/lib/typecheck-ratchet.ts +51 -0
- package/scripts/measure-log-diet-2.ts +213 -0
- package/scripts/measure-tsc-affected-roots.ts +289 -0
- package/scripts/openai-relay-server.ts +5 -1
- package/scripts/release-loop/docs-node.ts +258 -43
- package/scripts/release-loop/gate-executor.ts +28 -0
- package/scripts/release-loop/gate-method.ts +59 -0
- package/scripts/release-loop/gate-node.ts +335 -33
- package/scripts/release-loop/gate-wall.ts +208 -0
- package/scripts/release-loop/known-issues-node.ts +53 -8
- package/scripts/release-loop/publish-at.ts +14 -0
- package/scripts/release-loop/publish-node.ts +17 -2
- package/scripts/release-loop/resume-release.ts +40 -4
- package/scripts/release-loop/resume.ts +94 -0
- package/scripts/release-loop/shard-plan.ts +3 -11
- package/scripts/release-loop/tui-node-checks.ts +22 -0
- package/scripts/release-loop/unattended-release.ts +29 -7
- package/scripts/rhythm/daily.ts +51 -2
- package/scripts/rhythm/weekly.ts +3 -2
- package/scripts/tsc-baseline-cache.ts +147 -0
- package/scripts/tsc-main-safety-net.ts +189 -0
- package/scripts/usage-rollup.ts +64 -8
- package/src/acp/codex-plugins.ts +48 -6
- package/src/agent/monad-agent-turn.ts +67 -5
- package/src/agent/run-context.ts +20 -0
- package/src/agent/tool-profile.ts +28 -2
- package/src/agent-mission/acp-judge.ts +8 -2
- package/src/agent-mission/capabilities-cli.ts +16 -1
- package/src/agent-mission/driver.ts +19 -4
- package/src/agent-mission/pr-review-watch.ts +6 -2
- package/src/agent-mission/resource-ladder.ts +8 -3
- package/src/agent-mission/review-loop.ts +41 -7
- package/src/agent-substrate/acp-implementer.ts +139 -0
- package/src/agent-substrate/acp-reviewer.ts +17 -8
- package/src/agent-substrate/pr-reviewer.ts +191 -28
- package/src/agent-substrate/review-intent.ts +6 -4
- package/src/agent-substrate/self-review-cli.ts +155 -21
- package/src/agent-substrate/shard-boundary-intent.ts +92 -26
- package/src/ask-user-question/pending-questions.ts +8 -2
- package/src/autopilot/build/llm-conflict-merge.ts +216 -12
- package/src/autopilot/codebase-grounding.ts +44 -0
- package/src/autopilot/llm-control-brain.ts +13 -1
- package/src/autopilot/mission-codebase-gate.ts +8 -77
- package/src/autopilot/mission-critique.ts +18 -5
- package/src/autopilot/mission-feasibility-negotiate.ts +11 -10
- package/src/autopilot/mission-trace.ts +12 -1
- package/src/autopilot/pr-manager.ts +12 -3
- package/src/autopilot/system-lookback-run.ts +16 -10
- package/src/bench/bench.ts +183 -0
- package/src/boot/acp-server.ts +4 -7
- package/src/boot/acp-socket-path.ts +7 -0
- package/src/briefing/brief-items.ts +21 -13
- package/src/budget/fetchers/grok.ts +15 -5
- package/src/budget/llm-cost.ts +37 -5
- package/src/budget/types.ts +8 -2
- package/src/budget/unified-usage.ts +88 -6
- package/src/chat/index.ts +12 -3
- package/src/cli/agent-cli.ts +20 -0
- package/src/cli/bench-cli.ts +34 -0
- package/src/cli/brief-cli.ts +27 -21
- package/src/cli/cli-user-error.ts +9 -2
- package/src/cli/guardian-cli.ts +26 -1
- package/src/cli/image-cli.ts +101 -0
- package/src/cli/log-event-names.ts +7 -0
- package/src/cli/logs-cli.ts +1 -0
- package/src/cli/mcp-login.ts +111 -37
- package/src/cli/plugin-cli.ts +21 -1
- package/src/cli/pod-cli.ts +26 -0
- package/src/cli/pr-cli.ts +90 -5
- package/src/cli/pr-land-overlap-evidence.ts +279 -0
- package/src/cli/release-cli.ts +63 -12
- package/src/cli/rfc-status.ts +52 -2
- package/src/cli/self-update.ts +277 -10
- package/src/cli/skills-cli.ts +12 -3
- package/src/cli/tasks-cli.ts +241 -30
- package/src/cli/usage-cli.ts +91 -8
- package/src/context-bus/context-first.ts +51 -2
- package/src/context-bus/context-now-surfaces.ts +13 -2
- package/src/context-bus/long-term-memory.ts +3 -1
- package/src/context-bus/nightly-condense.ts +3 -2
- package/src/core-turn/run-goal-loop.ts +87 -2
- package/src/dashboard/ad-run-setup-bindings.ts +279 -0
- package/src/dashboard/chat-fold.ts +20 -0
- package/src/dashboard/first-screen-band.ts +151 -1
- package/src/dashboard/index.ts +45 -281
- package/src/dashboard/slash-runtime/dashboard-handlers.ts +5 -3
- package/src/dashboard/stale-install-check.ts +24 -2
- package/src/dashboard/turn-stream-formatter.ts +25 -0
- package/src/debug/log-diet.ts +190 -0
- package/src/debug/log.ts +70 -17
- package/src/display/coordinator.ts +25 -2
- package/src/domains/coo-admin-tool.ts +27 -0
- package/src/domains/cron-match.ts +1 -0
- package/src/domains/outbound-alert.ts +17 -6
- package/src/domains/release-tool.ts +158 -10
- package/src/domains/schedule-registry.ts +10 -7
- package/src/domains/schedule-runner.ts +1 -0
- package/src/elanous-config-dir.ts +2 -2
- package/src/elanous-daemon.ts +3 -3
- package/src/error-report/report.ts +21 -4
- package/src/execution-loop/launch-gate.ts +1 -1
- package/src/expression/i18n/messages.en.ts +5 -0
- package/src/expression/i18n/messages.ja.ts +5 -0
- package/src/expression/i18n/messages.ko.ts +5 -0
- package/src/expression/i18n/messages.zh.ts +5 -0
- package/src/expression/i18n/types.ts +8 -0
- package/src/github/pr-labels.ts +2 -0
- package/src/graph-runner/graph-cli.ts +42 -1
- package/src/graph-runner/runner.ts +218 -8
- package/src/grok/usage.ts +23 -2
- package/src/grounding/ref-index.ts +253 -0
- package/src/harness/dispatch-backpressure.ts +267 -0
- package/src/harness/harness-clean.ts +199 -78
- package/src/harness/harness-cli-command.ts +229 -35
- package/src/harness/harness-gc.ts +331 -0
- package/src/harness/harness-seams.ts +2 -20
- package/src/harness/harness-stop.ts +4 -2
- package/src/harness/harness-substrate-default.ts +214 -8
- package/src/harness/launch-registry.ts +6 -0
- package/src/harness/nested-elanous-policy.ts +4 -2
- package/src/harness/worktree-branch-prefix.ts +9 -0
- package/src/hq/fence-wrapper.ts +7 -0
- package/src/hq/fence.ts +187 -0
- package/src/hq/hq.ts +33 -2
- package/src/hq/lease.ts +13 -0
- package/src/hq/seats.ts +133 -21
- package/src/images/image-ledger.ts +179 -0
- package/src/index.ts +250 -69
- package/src/instance/child-scope.ts +32 -1
- package/src/instance/dev-project.ts +133 -0
- package/src/instance/resolve.ts +24 -0
- package/src/instance/spawn-elanous.ts +45 -0
- package/src/intake-plane/collect-github.ts +19 -1
- package/src/intake-plane/digest.ts +45 -20
- package/src/intake-plane/lens.ts +36 -8
- package/src/intake-plane/ref-prefetch.ts +110 -0
- package/src/intelligence-map/model-alias.ts +6 -0
- package/src/intelligence-map/model-catalog.ts +113 -0
- package/src/knowledge/kgs/pack.ts +5 -2
- package/src/knowledge/kgs/sqlite-store.ts +15 -0
- package/src/knowledge/tools/knowledge-query.ts +20 -2
- package/src/knowledge/write.ts +234 -2
- package/src/llm/bedrock-eventstream.ts +215 -0
- package/src/llm/bedrock-provider.ts +248 -0
- package/src/llm/bedrock.ts +491 -0
- package/src/llm/openrouter-byok-cost.ts +96 -0
- package/src/llm/openrouter-deepseek.ts +151 -0
- package/src/llm/openrouter-upstream-pins.config.ts +25 -0
- package/src/llm/provider-credentials.ts +3 -1
- package/src/llm/zai-coding-plan.ts +700 -0
- package/src/llm.ts +383 -41
- package/src/loops/budget.ts +150 -0
- package/src/loops/checker.ts +148 -9
- package/src/loops/orchestrator/resync-shadow.ts +34 -7
- package/src/loops/orchestrator/seat-share.ts +6 -49
- package/src/loops/orchestrator/traffic.ts +5 -81
- package/src/market/signed-index.ts +42 -0
- package/src/maturity/feature-maturity.ts +2 -2
- package/src/mcp/mcp-oauth.ts +136 -17
- package/src/model-tier/llm-tier-map.ts +52 -4
- package/src/models/prompts.ts +23 -1
- package/src/mss/logging/horizon-watch.ts +168 -0
- package/src/mss/logging/log-store.ts +262 -26
- package/src/native-tool-catalog.ts +1 -1
- package/src/nexus/api/consult-requests.ts +7 -10
- package/src/nexus/api/discord-trigger-bot.ts +6 -3
- package/src/nexus/api/error-reports.ts +30 -8
- package/src/nexus/api/http-server.ts +8 -0
- package/src/nexus/api/loop-resources.ts +42 -0
- package/src/nexus/api/missions.ts +73 -2
- package/src/nexus/api/ops-api.ts +18 -0
- package/src/nexus/api/outbound-report.ts +8 -3
- package/src/nexus/api/telegram-trigger-bot.ts +6 -3
- package/src/nexus/index.ts +17 -75
- package/src/nexus/outbound/format.ts +2 -0
- package/src/nexus/outbound/router.ts +198 -13
- package/src/nexus/tools-kind.ts +78 -0
- package/src/oauth/codex-account-rotation.ts +55 -20
- package/src/oauth/codex-account-store.ts +451 -23
- package/src/oauth/fallback-chain.ts +75 -15
- package/src/onboarding/first-run.ts +37 -18
- package/src/onboarding/web-first.ts +20 -2
- package/src/onboarding.ts +2 -1
- package/src/ops/machine-heartbeat.ts +185 -0
- package/src/ops/overview-cache.ts +175 -0
- package/src/ops/overview.ts +8 -0
- package/src/plugins/adapters/claude-package.ts +4 -12
- package/src/plugins/core/capability-policy.ts +141 -0
- package/src/plugins/core/host.ts +21 -8
- package/src/plugins/core/manifest.ts +19 -2
- package/src/plugins/core/trust-store.ts +15 -0
- package/src/plugins/core/types.ts +12 -0
- package/src/plugins/install/market-fetch.ts +68 -16
- package/src/plugins/install/plugin-install.ts +187 -16
- package/src/plugins/maker/plugin-maker.ts +25 -20
- package/src/plugins/maker/wizard-generate.ts +16 -2
- package/src/policy/llm-policy.ts +14 -1
- package/src/process/pid-liveness.ts +1 -1
- package/src/prompt-cache/openai.ts +13 -6
- package/src/prompt-cache/openrouter.ts +94 -0
- package/src/prompt-cache/types.ts +5 -0
- package/src/prompt-enhance/enhance.ts +69 -5
- package/src/prompt-library/family-prompt-coverage.ts +1 -0
- package/src/prompt-library/universal-preamble.ts +2 -2
- package/src/registry/discovery/openrouter-live-resolve.ts +122 -0
- package/src/registry/loader.ts +6 -1
- package/src/release-loop/checklist.ts +24 -9
- package/src/release-loop/feature-store.ts +3 -0
- package/src/release-loop/gate-shards.ts +48 -4
- package/src/release-loop/landed-to-green.ts +81 -0
- package/src/release-loop/light-rc.ts +11 -1
- package/src/release-loop/merged-pr-checklist.ts +10 -0
- package/src/release-loop/placement.ts +107 -23
- package/src/release-loop/rehearsal.ts +317 -0
- package/src/release-loop/release-schedule.ts +13 -1
- package/src/release-loop/rubric-types.ts +1 -0
- package/src/release-loop/rubric.ts +2 -2
- package/src/requirements/requirement-ledger.ts +536 -0
- package/src/retro/daily.ts +26 -12
- package/src/seat-loop/seat-loop.ts +66 -11
- package/src/seat-loop/traffic-signal.ts +44 -0
- package/src/self-dev/ask-launch-flow.ts +2 -2
- package/src/self-dev/dev-cli.ts +109 -11
- package/src/self-dev/dev-pipeline.ts +11 -8
- package/src/self-dev/draft-residue.ts +475 -0
- package/src/self-dev/draft-sweep.ts +254 -10
- package/src/self-dev/draft-triage-rules.ts +27 -1
- package/src/self-dev/graph-journey-route.ts +6 -0
- package/src/self-dev/launch-preflight-text.ts +549 -0
- package/src/self-dev/launch-preflight.ts +10 -545
- package/src/self-dev/orchestrate-cli.ts +82 -5
- package/src/self-dev/orchestrate-summary-line.ts +13 -0
- package/src/self-dev/orchestrate.ts +49 -141
- package/src/self-dev/review-provider-fallback.ts +115 -19
- package/src/self-dev/run-store.ts +1 -3
- package/src/self-dev/run-supervisor.ts +190 -36
- package/src/self-dev/run-types.ts +191 -0
- package/src/self-dev/shard-aggregate.ts +61 -13
- package/src/self-dev/shard-plan.ts +5 -1
- package/src/self-dev/shard-run.ts +23 -5
- package/src/self-dev/supervisor-verdict-edges.ts +3 -4
- package/src/self-implement/blocked-draft-policy.ts +3 -19
- package/src/self-implement/budget-gate.ts +53 -13
- package/src/self-implement/child-context-composition.ts +139 -0
- package/src/self-implement/child-llm-preference.ts +13 -5
- package/src/self-implement/claude-acp-implement.ts +67 -0
- package/src/self-implement/draft-on-stop-mode.ts +18 -0
- package/src/self-implement/flex-signals.ts +59 -0
- package/src/self-implement/follow-up-goals.ts +114 -38
- package/src/self-implement/frozen-merges.ts +8 -3
- package/src/self-implement/gate-baseline-reap.ts +157 -0
- package/src/self-implement/gate-baseline.ts +78 -16
- package/src/self-implement/gate-cli.ts +12 -2
- package/src/self-implement/gate-remote.ts +17 -6
- package/src/self-implement/goal-author-decompose.ts +10 -2
- package/src/self-implement/goal-author.ts +48 -13
- package/src/self-implement/goal-digest.ts +19 -0
- package/src/self-implement/graph-overlay-condition.ts +32 -0
- package/src/self-implement/graph-overlay-yaml.ts +41 -7
- package/src/self-implement/graph-templates.ts +33 -1
- package/src/self-implement/graph-yaml.ts +56 -0
- package/src/self-implement/headless-elanous-driver.ts +11 -9
- package/src/self-implement/heal-intake.ts +5 -1
- package/src/self-implement/host-regate.ts +120 -29
- package/src/self-implement/l8-merge-queue.ts +67 -5
- package/src/self-implement/merge-train.ts +131 -0
- package/src/self-implement/off-diff-evidence.ts +9 -0
- package/src/self-implement/orchestrator.ts +271 -25
- package/src/self-implement/review-finding-recurrence.ts +28 -0
- package/src/self-implement/rework-policy.ts +2 -1
- package/src/self-implement/round-summary-card.ts +63 -0
- package/src/self-implement/run-ledger.ts +4 -3
- package/src/self-implement/run-step-projection.ts +147 -0
- package/src/self-implement/run-stop.ts +1 -1
- package/src/self-implement/running-runs.ts +75 -5
- package/src/self-implement/seams.ts +83 -4
- package/src/self-implement/self-implement-cli.ts +12 -5
- package/src/self-implement/stop-autoheal.ts +69 -2
- package/src/self-implement/train-gate-remote.ts +158 -0
- package/src/session-runtime/index.ts +10 -1
- package/src/session-runtime/retry-api.ts +11 -1
- package/src/session-runtime/retry-policy.ts +2 -1
- package/src/skills/gift-install.ts +119 -0
- package/src/skills/runner.ts +59 -2
- package/src/skills/skill-feedback.ts +31 -1
- package/src/skills/skill-tier.ts +1 -0
- package/src/smart-wizard/capability-index.ts +231 -0
- package/src/smart-wizard/rung.ts +74 -0
- package/src/steward/launch.ts +22 -10
- package/src/steward/triage-budget.ts +62 -0
- package/src/steward/triage.ts +87 -24
- package/src/storage/file-lock.ts +8 -1
- package/src/task-agent/actions.ts +66 -5
- package/src/task-agent/card-evidence.ts +59 -13
- package/src/task-agent/completion-evidence.ts +5 -0
- package/src/task-agent/cover.ts +101 -3
- package/src/task-agent/judge.ts +20 -3
- package/src/task-agent/land-order-ledger.ts +95 -0
- package/src/task-agent/land-worktree.ts +201 -0
- package/src/task-agent/live-move-history.ts +219 -0
- package/src/task-agent/live-moves.ts +451 -34
- package/src/task-agent/parent-host.ts +117 -8
- package/src/task-agent/rejudge-on-head.ts +352 -0
- package/src/task-agent/review-prior-fix.ts +146 -0
- package/src/task-agent/review-result-capture.ts +218 -0
- package/src/task-agent/run-adopt.ts +136 -0
- package/src/task-agent/shadow.ts +154 -14
- package/src/task-agent/task-hand.ts +186 -14
- package/src/task-orchestrator/surfaces/pod-account-broker.ts +38 -5
- package/src/task-orchestrator/surfaces/pod-bedrock-child.ts +74 -0
- package/src/task-orchestrator/surfaces/pod-child-default.ts +50 -0
- package/src/task-orchestrator/surfaces/pod-child-providers.ts +42 -0
- package/src/task-orchestrator/surfaces/pod-command-job.ts +6 -3
- package/src/task-orchestrator/surfaces/pod-failure-reason.ts +94 -7
- package/src/task-orchestrator/surfaces/pod-image-source.ts +212 -0
- package/src/task-orchestrator/surfaces/pod-lease.ts +110 -17
- package/src/task-orchestrator/surfaces/pod-ledger-collect.ts +32 -3
- package/src/task-orchestrator/surfaces/pod-pool.ts +86 -18
- package/src/task-orchestrator/surfaces/pod-zai-child.ts +101 -0
- package/src/task-orchestrator/surfaces/self-implement-pod.ts +463 -50
- package/src/task-orchestrator/surfaces/self-implement.ts +25 -3
- package/src/telegram-service.ts +5 -1
- package/src/telegram.ts +17 -4
- package/src/time/format.ts +13 -4
- package/src/tool-runtime/goal-author-runtime.ts +1 -0
- package/src/tool-runtime/self-cognition-catalog.ts +23 -0
- package/src/tool-runtime/self-cognition-runtimes.ts +2 -24
- package/src/typecheck-ratchet.ts +1 -0
- package/src/user-config.ts +180 -32
- package/src/ux-sim/slash-menu-frames.ts +168 -0
- package/src/version/packed-revision.json +1 -1
- package/src/web-terminal/preview-tap-registry.ts +5 -2
- package/src/workflow-runtime/triggers/registry.ts +59 -0
- package/src/workflow-runtime/triggers/scheduler.ts +1 -0
- package/src/workflow-runtime/triggers/webhook-source.ts +35 -5
- package/apps/pwa/out/_next/static/chunks/1109.78a7a3288c0259d9.js +0 -148
- package/apps/pwa/out/_next/static/chunks/115.1c6690b8604f81c4.js +0 -32
- package/apps/pwa/out/_next/static/chunks/1262.76a3f08e67c5f816.js +0 -309
- package/apps/pwa/out/_next/static/chunks/1307-08b716476f1d50af.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1358d292.5c87f12cad6eaa91.js +0 -122
- package/apps/pwa/out/_next/static/chunks/1438.27a1a7b8f2308004.js +0 -149
- package/apps/pwa/out/_next/static/chunks/1442.02d85cd88f4c1eac.js +0 -79
- package/apps/pwa/out/_next/static/chunks/1501-1aad662d82ba43f7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1584.9e2dd53da6e6d4b1.js +0 -24
- package/apps/pwa/out/_next/static/chunks/1586.b989b0614afb77e6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1609.2ad355062870151f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1612-f6df0fc0eed7157b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1826-5d279da64bbbc2de.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1923.37f1d5fe7ebe3860.js +0 -27
- package/apps/pwa/out/_next/static/chunks/195-fb1d7690bf30bab4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1986-33e13d648a9df765.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1993.3fd074a2a246234e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/211-9c9090c59e2c5907.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2140.d2daf9ee96e295d1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2196-4700653d12a44b40.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2212.0359851457473efc.js +0 -1
- package/apps/pwa/out/_next/static/chunks/224.583e562365a52f74.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2328.0f37465646bafa7d.js +0 -36
- package/apps/pwa/out/_next/static/chunks/2372.bf71372184991937.js +0 -286
- package/apps/pwa/out/_next/static/chunks/2489-d238d3e2eb5b2bf5.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2537-6c289376caa88ca1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2549-a8515c86f9e8cb96.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2655.72cc6a97276c5acc.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2699-325cc923ba7b8631.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2743-ef6e8c3ced74cce5.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2840-5458ad50c49cac6a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2856.f75d5baef7f49b74.js +0 -1
- package/apps/pwa/out/_next/static/chunks/311.e685738f92e6e2ec.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3194-807b212fb8eae065.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3324-d1d2979821dfa845.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3344.0de4b0ec5a454b7a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/335.a13f339e97893437.js +0 -94
- package/apps/pwa/out/_next/static/chunks/3394.646348f34b2500be.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3443.6276909b53d72d53.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3495.abd4254d808bf2dc.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3558-bbb5ab553a0471d3.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3721.ba5b82db0d9a5572.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3857-5985236d9307f676.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3916-3d02dd1fc257dd3c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3919.9ff7dc25afefee33.js +0 -74
- package/apps/pwa/out/_next/static/chunks/3998.bd9a69d9b50e9bd2.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4003.ab6db1dc266576c9.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4144-fad095b48a1862ee.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4216.e8ac94b9fe6b96ab.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4327.c44dc2b55cb43ee5.js +0 -34
- package/apps/pwa/out/_next/static/chunks/4401.94257d497b6d26bc.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4559.17ca2cdbaa8d414f.js +0 -115
- package/apps/pwa/out/_next/static/chunks/4565-3d7e3fdeb43686a8.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4706.97e734bd4c9816ca.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4795-f372490b178583fc.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4982.6e44352f751b9a1b.js +0 -77
- package/apps/pwa/out/_next/static/chunks/5342.49c5c5a530e62b50.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5360-08a80abdf3a4da99.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5431.66c58ab1327ee0b7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5633-d6852385000738ef.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5717.bcfcb96e701b82e0.js +0 -90
- package/apps/pwa/out/_next/static/chunks/5755-30d102586789bf7f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5957.3f1a4012388dd962.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5977-5b9e8a45a5b0c823.js +0 -1
- package/apps/pwa/out/_next/static/chunks/602.9bf02ac8e9502598.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6020.3a1b347e2d2dfacc.js +0 -8
- package/apps/pwa/out/_next/static/chunks/6375.fce1d174ba6d0324.js +0 -64
- package/apps/pwa/out/_next/static/chunks/6546.92a828aef441271f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/658.957fdb0a8c6c6c8e.js +0 -287
- package/apps/pwa/out/_next/static/chunks/6706.323590aabb8b1293.js +0 -226
- package/apps/pwa/out/_next/static/chunks/6747.b67947f293a4c02d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6868.000b24a3045c5574.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6877-74399f69c7da1433.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6946.bba74e9c96050f30.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7033.a558f1b2f6912a53.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7065.46029c26c53eac50.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7087.6fae577d09249bc1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/722.de8d9801e0f6d414.js +0 -41
- package/apps/pwa/out/_next/static/chunks/726.83532d69452ee24b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7290-f09c928a06c52e2d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7497.57981e9d7a75a52a.js +0 -24
- package/apps/pwa/out/_next/static/chunks/7672.4af122ad892117c7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/771.6ab889a233e7dae7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7797-df5aa0381fdc078f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8060-cc008cc6fedfa818.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8085-d065d0393c1760f5.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8096.f7da0776cfde6300.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8401-35e94e9091f5eb8d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8682-7061dd4fa7bd62ed.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8695-846034ce44852c70.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8769.815ca69879f608b4.js +0 -104
- package/apps/pwa/out/_next/static/chunks/8857-aa1cc4ac90af4d40.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8f835b4f.048eeb02299e3e12.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9068.5f2764fccf86c1b2.js +0 -93
- package/apps/pwa/out/_next/static/chunks/9103-60fbef33e66f0a46.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9247-55bcfbb0e8057f59.js +0 -1
- package/apps/pwa/out/_next/static/chunks/935.4ddaa99fc1d1126c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9451.ebfb0dcc9c02250f.js +0 -23
- package/apps/pwa/out/_next/static/chunks/9559.e648369b1d57cf9a.js +0 -62
- package/apps/pwa/out/_next/static/chunks/app/_not-found/page-c1a96669158471ec.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-88459e0903da2803.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/autopilot/page-d85c43f46f58716b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/board/page-4e645600579ae952.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/botlab/page-87edbfd19117823b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/bots/page-7f0c2b7801ac6a54.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ceo/page-bf1f030a9d1adffd.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/chat/page-cbaf66b1e1804adf.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/control/page-9f5610caaa06178c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/decisions/page-ec8f3575e339de74.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-86d753e460f1a989.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/editor/page-4166a7d51cf0a57d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/exec/page-2a19c5cd3b562580.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/field/page-b6a09cde2e07521a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/healthz/route-c1a96669158471ec.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/inside/page-f7eae1518ca0c353.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/intake/page-456a8573ad811381.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/layout-8462d6a543cbb2d9.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/live/page-2974cdfb87c0f9c9.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/live-run/page-f927ffa25f3c86d1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/loops/page-abd86cd832aaa011.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/market/page-79daf68f6256adaa.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-8c3f168fc3f91939.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/morning/page-786fb4504d0f2fa9.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/not-found-75209f14597dcb8f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-bf30affe3a85af7e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-1187a44b7515434b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-3d0910185a66ed54.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-f401ee1dd5a26e25.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/outputs/page-5dfe7604fd842784.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/page-76cf838050a8d681.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-7ec8e289b4c14df6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-8c8ac17d52be2a79.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-d359ab048d63a9d9.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-b8b345c880bf78d1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/page-f151cc10ad026940.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-9875f103057da433.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/layout-c1a96669158471ec.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/page-9789328af9a9aa62.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/share/page-bf495a1e284f4e00.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-a263d94bca4be22a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-3962eab8ec58d16e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/term/page-9abcfac07e02a542.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/today/page-bee49f927c333abb.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/trace/page-bac2d56dcb02d955.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/vault/page-937cbcc13176295c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-d09495594f7f480d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-69cb93d1a2e57e0e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-72ec543928d67c3e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-82906eeda0186a7b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/f6231284-45726550aba08fca.js +0 -1
- package/apps/pwa/out/_next/static/chunks/main-59fe95fed40c7a01.js +0 -1
- package/apps/pwa/out/_next/static/chunks/main-app-4ca74dbbee7057c6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/pages/_app-13a0a886df4d97a1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/pages/_error-6af8591cd3a3f038.js +0 -1
- package/apps/pwa/out/_next/static/chunks/webpack-f2b4016b9b981bc8.js +0 -1
- package/apps/pwa/out/_next/static/css/f2bd5257e6af88d8.css +0 -1
- /package/apps/pwa/out/_next/static/{aBi6EyMtLsrl7J8eH4kF7 → Q-7iofoxOEv1WX18S35of}/_ssgManifest.js +0 -0
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
//
|
|
3
3
|
// Codex children drop parent env under shell_environment_policy inherit=core, so a
|
|
4
4
|
// depth marker that only lives in the environment never arrives. Depth is therefore
|
|
5
|
-
// carried on the codex argv (`-c shell_environment_policy.set.ELANOUS_NESTED_DEPTH
|
|
5
|
+
// carried on the codex argv (`-c shell_environment_policy.set.ELANOUS_NESTED_DEPTH="<n>"` — TOML 문자열).
|
|
6
6
|
//
|
|
7
7
|
// Unset ELANOUS_NESTED_DEPTH is depth 0 (the outer launcher). A missing marker is
|
|
8
8
|
// not "depth unknown" and does not refuse an ordinary mission spawn.
|
|
@@ -50,7 +50,9 @@ export function backendCarriesNestedDepth(backendName: string): boolean {
|
|
|
50
50
|
export function nestedDepthCodexArgs(backendName: string, childDepth: number): string[] {
|
|
51
51
|
if (backendName !== 'codex') return [];
|
|
52
52
|
if (!Number.isInteger(childDepth) || childDepth < 0) return [];
|
|
53
|
-
|
|
53
|
+
// 🩸 CODEX-NESTED-DEPTH-QUOTE(2026-10-09): `-c` 값은 TOML 이고 shell_environment_policy.set 은 «문자열» 맵이다.
|
|
54
|
+
// 맨 정수(`=1`)는 codex 0.159.3 에서 «failed to load bootstrap configuration» 으로 자식을 바로 죽인다 → 따옴표로 문자열.
|
|
55
|
+
return ['-c', `${CODEX_NESTED_DEPTH_CONFIG_KEY}="${childDepth}"`];
|
|
54
56
|
}
|
|
55
57
|
|
|
56
58
|
/**
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
|
+
import { branchGoalId } from '../cli/pr-lineage.js';
|
|
2
3
|
|
|
3
4
|
/** Prefix for disposable worktree branches created by self-implement and inspected by harness clean.
|
|
4
5
|
* A successfully merged run cleans up its own worktree.
|
|
@@ -31,3 +32,11 @@ export function plannedSelfImplBranch(feature: string, goalId?: string, runId?:
|
|
|
31
32
|
const digest = createHash('sha256').update(id).digest('hex').slice(0, 8);
|
|
32
33
|
return `${WORKTREE_BRANCH_PREFIX}goalid-${id}-${readable}-${digest}${runSuffix}`;
|
|
33
34
|
}
|
|
35
|
+
|
|
36
|
+
/** 같은 골의 재실행이 공유하는 «안정한» 계보 키. `plannedSelfImplBranch` 가 싣는 `goalid-<id>-` 구간만 읽는다 —
|
|
37
|
+
* feature 문구·다이제스트·런 접미(`-r<runId>`)는 재실행마다 달라지므로 키에 넣지 않는다.
|
|
38
|
+
* ⛔ 골 식별자가 없는 옛 브랜치는 `null` — 읽기용 슬러그는 서로 다른 골이 같은 값을 낼 수 있어(`grounded-N-…`) 키가 될 수 없다. */
|
|
39
|
+
export function branchLineageKey(branch: string): string | null {
|
|
40
|
+
const id = branchGoalId(branch);
|
|
41
|
+
return id ? `goalid:${id.toLowerCase()}` : null;
|
|
42
|
+
}
|
package/src/hq/fence-wrapper.ts
CHANGED
|
@@ -69,6 +69,13 @@ if [ $# -lt 1 ] || [ -z "$1" ]; then
|
|
|
69
69
|
fi
|
|
70
70
|
ROLE=$1
|
|
71
71
|
shift
|
|
72
|
+
# FENCE-LIGHT: only when this host's heartbeat left a lease-cache file and the installed package ships the light fence.
|
|
73
|
+
# No cache file = exactly the CLI path below. The sourced script always exits; returning is a fail-closed error.
|
|
74
|
+
if [ -f "$CONFIG_DIR/hq/lease-cache" ] && [ -f scripts/hq/hq-fence-wrapper.sh ]; then
|
|
75
|
+
. scripts/hq/hq-fence-wrapper.sh
|
|
76
|
+
log_line "light fence returned without a decision (role=$ROLE)"
|
|
77
|
+
exit 1
|
|
78
|
+
fi
|
|
72
79
|
"$BUN" "$ENTRY" --config-dir "$CONFIG_DIR" hq fence --role "$ROLE" -- /bin/sh -c "$*"
|
|
73
80
|
rc=$?
|
|
74
81
|
if [ "$rc" -ne 0 ]; then alert "hq-fence: fence failed (rc=$rc, role=$ROLE)"; fi
|
package/src/hq/fence.ts
ADDED
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import { spawnSync } from 'node:child_process';
|
|
2
|
+
import { readFileSync, readlinkSync, readdirSync } from 'node:fs';
|
|
3
|
+
import { cronMatches } from '../domains/cron-match.js';
|
|
4
|
+
import { resolveTimeZone } from '../time/format.js';
|
|
5
|
+
|
|
6
|
+
export interface CliProcess { pid: number; ppid: number; ageSeconds: number; cpuSeconds: number; command: string }
|
|
7
|
+
export interface OrphanCandidate { process: CliProcess; tree: CliProcess[]; periodMinutes: number }
|
|
8
|
+
export interface FenceGcDeps {
|
|
9
|
+
snapshot?: () => CliProcess[];
|
|
10
|
+
run?: (binary: string, args: string[]) => string;
|
|
11
|
+
sleep?: (ms: number) => Promise<void>;
|
|
12
|
+
kill?: (pid: number) => void;
|
|
13
|
+
timeZone?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function cliFingerprint(command: string): string {
|
|
17
|
+
return command.replace(/^\s*(?:\S*\/)?bun\s+(?:\S*\/)?elanous\.mjs\s+/, 'elanous ')
|
|
18
|
+
.replace(/^\s*(?:\S*\/)?(?:elanous\.mjs|eln)\s+/, 'elanous ')
|
|
19
|
+
.trim();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function seconds(text: string): number {
|
|
23
|
+
const parts = text.split(':').map(Number);
|
|
24
|
+
return parts.reduce((total, part) => total * 60 + part, 0);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** ps gives command after the four numeric fields; never split command on whitespace. */
|
|
28
|
+
export function processSnapshot(): CliProcess[] {
|
|
29
|
+
const result = spawnSync('ps', ['-axo', 'pid=,ppid=,etime=,time=,command='], { encoding: 'utf8', timeout: 10_000, maxBuffer: 4 * 1024 * 1024 });
|
|
30
|
+
if (result.status !== 0) throw new Error(`ps failed: ${result.stderr}`);
|
|
31
|
+
return result.stdout.split('\n').flatMap(line => {
|
|
32
|
+
const fields = /^\s*(\d+)\s+(\d+)\s+([\d:-]+)\s+([\d:.]+)\s+(.+)$/.exec(line);
|
|
33
|
+
if (!fields) return [];
|
|
34
|
+
const elapsed = fields[3]!.split('-');
|
|
35
|
+
const ageSeconds = seconds(elapsed.at(-1)!) + (elapsed.length === 2 ? Number(elapsed[0]) * 86_400 : 0);
|
|
36
|
+
const cpuSeconds = seconds(fields[4]!);
|
|
37
|
+
if (!Number.isFinite(ageSeconds) || !Number.isFinite(cpuSeconds)) return [];
|
|
38
|
+
return [{ pid: Number(fields[1]), ppid: Number(fields[2]), ageSeconds, cpuSeconds, command: fields[5]! }];
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Only installed elanous one-shot CLI launches, not shells, daemons, or an incidental flag inside a quoted payload. */
|
|
43
|
+
export function oneShotCommand(command: string): boolean {
|
|
44
|
+
const invocation = /^(?:(?:\S*\/)?bun\s+(?:\S*\/)?elanous\.mjs|(?:\S*\/)?(?:elanous|elanous\.mjs|eln))\s+/.exec(command);
|
|
45
|
+
if (!invocation) return false;
|
|
46
|
+
const args = command.slice(invocation[0].length).split(/\s+--\s+|\s+-c\s+/)[0] ?? '';
|
|
47
|
+
const payload = /\s+--\s+(.+)$/.exec(command)?.[1] ?? '';
|
|
48
|
+
const shellBody = /\s+--\s+(?:\S*\/)?(?:sh|bash)\s+-c\s+(.+)$/.exec(command)?.[1];
|
|
49
|
+
if (shellBody !== undefined) {
|
|
50
|
+
if (!/^['"]?(?:\S*\/)?bun\s+(?:\S*\/)?elanous\.mjs\s+/.test(shellBody)
|
|
51
|
+
|| /[;|`&]/.test(shellBody) || /\bnexus\s+run\b/.test(shellBody)
|
|
52
|
+
|| !/(?:^|\s)(?:--once|tick|--refresh|--notify)(?:\s|['"]|$)/.test(shellBody)) return false;
|
|
53
|
+
}
|
|
54
|
+
// Test-universe and help invocations are never collected — on the fence itself, in its payload, or in a wrapped shell body.
|
|
55
|
+
for (const part of [args, payload, shellBody ?? '']) {
|
|
56
|
+
if (/(?:^|\s|['"])--test(?:\s|=|['"]|$)/.test(part) || /(?:^|\s)(?:--help|-h)(?:\s|['"]|$)/.test(part)) return false;
|
|
57
|
+
}
|
|
58
|
+
// The daemon is never a one-shot, whatever flag rides along (`nexus run --once`), directly or as a fence payload.
|
|
59
|
+
if (/(?:^|\s)nexus\s+run\b/.test(args) || /(?:^|\s)nexus\s+run\b/.test(payload) || /&&|\|\||[;|`&]/.test(payload)) return false;
|
|
60
|
+
if (/^hq\s+fence\s+--role\s+(?:cron|seat-loop|conatus|release-run)\b/.test(args) && payload) {
|
|
61
|
+
return /(?:^|\s)(?:--once|tick|--refresh|--notify)(?:\s|['"]|$)/.test(payload);
|
|
62
|
+
}
|
|
63
|
+
return /(?:^|\s)hq\s+fence\s+--role\s+(?:cron|seat-loop|conatus|release-run)\b/.test(args)
|
|
64
|
+
|| /(?:^|\s)--once(?:\s|$)/.test(args)
|
|
65
|
+
|| /(?:^|\s)(?:tick|--refresh|--notify)(?:\s|$)/.test(args);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** Unknown/mismatched schedules are not assigned a made-up period. Match the launched action (including fence payload). */
|
|
69
|
+
export function scheduledPeriodMinutes(command: string, schedules: readonly { command: string; cron: string }[], now: Date, timeZone = resolveTimeZone().timeZone): number | null {
|
|
70
|
+
const current = cliFingerprint(command);
|
|
71
|
+
const matches = schedules.filter(job => {
|
|
72
|
+
const action = job.command.trim().replace(/^cd\s+\S+\s*&&\s*/, '').replace(/\s+>>?\s+\S+(?:\s+2>&1)?\s*$/, '');
|
|
73
|
+
if (action.length < 16) return false;
|
|
74
|
+
if (current === cliFingerprint(action)) return true;
|
|
75
|
+
// The installed hq-fence shell wrapper execs the CLI with an explicit role and -- payload.
|
|
76
|
+
// Accept only a single, unambiguous quoted command; never infer a period from a substring.
|
|
77
|
+
const wrapper = /^(?:\S*\/)?hq-fence\s+(cron|seat-loop|conatus|release-run)\s+'([^']+)'$/.exec(action);
|
|
78
|
+
if (!wrapper) return false;
|
|
79
|
+
return current === `elanous hq fence --role ${wrapper[1]} -- ${wrapper[2]}`;
|
|
80
|
+
});
|
|
81
|
+
if (matches.length !== 1) return null;
|
|
82
|
+
const cron = matches[0]!.cron;
|
|
83
|
+
if (cron.trim().split(/\s+/).length !== 5) return null;
|
|
84
|
+
const start = Math.floor(now.getTime() / 60_000) * 60_000;
|
|
85
|
+
// A 7-day minute scan per process is costly with many hung trees; one tick asks the same (cron, zone, minute) often.
|
|
86
|
+
const memoKey = `${cron}\0${timeZone}\0${start}`;
|
|
87
|
+
if (periodMemo.has(memoKey)) return periodMemo.get(memoKey)!;
|
|
88
|
+
const period = cronPeriodMinutes(cron, start, timeZone);
|
|
89
|
+
if (periodMemo.size > 256) periodMemo.clear();
|
|
90
|
+
periodMemo.set(memoKey, period);
|
|
91
|
+
return period;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const periodMemo = new Map<string, number | null>();
|
|
95
|
+
|
|
96
|
+
function cronPeriodMinutes(cron: string, start: number, timeZone: string): number | null {
|
|
97
|
+
const fires: number[] = [];
|
|
98
|
+
try {
|
|
99
|
+
// Every fire in the next 7 days, not just the first few: a weekday-hours cron (`0 9-17 * * 1-5`) is hourly
|
|
100
|
+
// for three gaps and then pauses for a night or a weekend — that is no single period.
|
|
101
|
+
for (let i = 0; i < 7 * 24 * 60; i++) {
|
|
102
|
+
const at = start + i * 60_000;
|
|
103
|
+
if (cronMatches(cron, new Date(at), { timeZone })) fires.push(at);
|
|
104
|
+
}
|
|
105
|
+
} catch { return null; }
|
|
106
|
+
// An irregular schedule (any unequal gap within the window, e.g. `0 9,18 * * *`, weekday hours) has no single period
|
|
107
|
+
// to multiply — unknown, never collected. Fewer than three fires in 7 days (weekly) is unknown too (safe side).
|
|
108
|
+
if (fires.length < 3) return null;
|
|
109
|
+
const gap = fires[1]! - fires[0]!;
|
|
110
|
+
for (let i = 2; i < fires.length; i++) if (fires[i]! - fires[i - 1]! !== gap) return null;
|
|
111
|
+
return gap / 60_000;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export function orphanCandidates(first: readonly CliProcess[], second: readonly CliProcess[], schedules: readonly { command: string; cron: string }[], now: Date, timeZone?: string): OrphanCandidate[] {
|
|
115
|
+
const byPid = new Map(second.map(row => [row.pid, row]));
|
|
116
|
+
const initial = new Map(first.map(row => [row.pid, row]));
|
|
117
|
+
const candidates: OrphanCandidate[] = [];
|
|
118
|
+
// The collector itself and every ancestor of it (the guardian tick's cron/fence parents) are never signalled.
|
|
119
|
+
const self = new Set<number>([process.pid]);
|
|
120
|
+
for (let pid: number | undefined = byPid.get(process.pid)?.ppid; pid !== undefined && pid > 1 && !self.has(pid); pid = byPid.get(pid)?.ppid) self.add(pid);
|
|
121
|
+
for (const root of first) {
|
|
122
|
+
if (root.pid === process.pid || !oneShotCommand(root.command)) continue;
|
|
123
|
+
const current = byPid.get(root.pid);
|
|
124
|
+
if (!current || current.command !== root.command || current.ppid !== root.ppid || current.cpuSeconds !== root.cpuSeconds) continue;
|
|
125
|
+
// Identity: a reused PID restarts its age; the same process can only have grown older between snapshots.
|
|
126
|
+
if (current.ageSeconds < root.ageSeconds) continue;
|
|
127
|
+
const periodMinutes = scheduledPeriodMinutes(root.command, schedules, now, timeZone);
|
|
128
|
+
if (!periodMinutes || root.ageSeconds <= periodMinutes * 3 * 60) continue;
|
|
129
|
+
// An old ancestor with an actively executing payload is not an orphan.
|
|
130
|
+
if (second.some(row => row.ppid === root.pid && !initial.has(row.pid))) continue;
|
|
131
|
+
const tree = [current];
|
|
132
|
+
for (let i = 0; i < tree.length; i++) {
|
|
133
|
+
for (const child of second) if (child.ppid === tree[i]!.pid && !tree.some(row => row.pid === child.pid)) tree.push(child);
|
|
134
|
+
}
|
|
135
|
+
if (tree.some(row => {
|
|
136
|
+
const before = initial.get(row.pid);
|
|
137
|
+
return !before || before.command !== row.command || before.cpuSeconds !== row.cpuSeconds || before.ppid !== row.ppid
|
|
138
|
+
|| Math.abs((row.ageSeconds - before.ageSeconds) - (current.ageSeconds - root.ageSeconds)) > 2;
|
|
139
|
+
})) continue;
|
|
140
|
+
if (tree.some(row => self.has(row.pid))) continue;
|
|
141
|
+
candidates.push({ process: current, tree, periodMinutes });
|
|
142
|
+
}
|
|
143
|
+
return candidates.filter(candidate => !candidates.some(parent => parent !== candidate && parent.tree.some(row => row.pid === candidate.process.pid)));
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export function gcTools(deps: FenceGcDeps = {}) {
|
|
147
|
+
return {
|
|
148
|
+
snapshot: deps.snapshot ?? processSnapshot,
|
|
149
|
+
sleep: deps.sleep ?? ((ms: number) => new Promise<void>(resolve => setTimeout(resolve, ms))),
|
|
150
|
+
run: deps.run ?? ((binary: string, args: string[]) => {
|
|
151
|
+
// Linux has no macOS sample. Over one second, sample each thread's wait channel and state (readable by the
|
|
152
|
+
// process owner); the kernel stack is root-only on most hosts, so it is added only when readable.
|
|
153
|
+
if (binary === 'sample' && process.platform === 'linux') {
|
|
154
|
+
const pid = args[0];
|
|
155
|
+
const samples: string[] = [];
|
|
156
|
+
const deadline = Date.now() + 1_000;
|
|
157
|
+
do {
|
|
158
|
+
for (const tid of readdirSync(`/proc/${pid}/task`)) {
|
|
159
|
+
const base = `/proc/${pid}/task/${tid}`;
|
|
160
|
+
const state = /^State:\s*(.+)$/m.exec(readFileSync(`${base}/status`, 'utf8'))?.[1] ?? '?';
|
|
161
|
+
let stack = '';
|
|
162
|
+
// Root-only on most kernels: carried when readable, otherwise the evidence says so (not a collection gate).
|
|
163
|
+
try { stack = `\n${readFileSync(`${base}/stack`, 'utf8')}`; } catch (error) { stack = ` kernel-stack=unreadable(${(error as NodeJS.ErrnoException).code ?? 'error'})`; }
|
|
164
|
+
samples.push(`tid=${tid} state=${state} wchan=${readFileSync(`${base}/wchan`, 'utf8') || '0'}${stack}`);
|
|
165
|
+
}
|
|
166
|
+
if (Date.now() < deadline) Bun.sleepSync(Math.min(100, deadline - Date.now()));
|
|
167
|
+
} while (Date.now() < deadline);
|
|
168
|
+
if (samples.length === 0) throw new Error(`sample: no threads for PID ${pid}`);
|
|
169
|
+
return samples.join('\n');
|
|
170
|
+
}
|
|
171
|
+
// macOS `sample` by absolute path: a PATH shim (e.g. pyenv's `sample`) must not stand in for the profiler.
|
|
172
|
+
const resolved = binary === 'sample' && process.platform === 'darwin' ? '/usr/bin/sample' : binary;
|
|
173
|
+
// sample symbolicates a large Bun binary after its 1s window — allow time and room, or it would never succeed.
|
|
174
|
+
const result = spawnSync(resolved, args, { encoding: 'utf8', timeout: binary === 'sample' ? 30_000 : 10_000, maxBuffer: 8 * 1024 * 1024 });
|
|
175
|
+
if (result.status !== 0) throw new Error(`${binary}: ${result.error ?? result.stderr ?? 'failed'}`);
|
|
176
|
+
return result.stdout;
|
|
177
|
+
}),
|
|
178
|
+
kill: deps.kill ?? ((pid: number) => process.kill(pid, 'SIGTERM')),
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/** The target's own Bun binary — read from the target PID, so a PID that died (or was reused) fails closed. */
|
|
183
|
+
export function bunVersion(pid: number, run: (binary: string, args: string[]) => string): string {
|
|
184
|
+
const bin = process.platform === 'linux' ? readlinkSync(`/proc/${pid}/exe`) : run('ps', ['-o', 'comm=', '-p', String(pid)]).trim();
|
|
185
|
+
if (!/(?:^|\/)bun(?:$|\s)/.test(bin)) throw new Error(`PID ${pid} is not a Bun process`);
|
|
186
|
+
return run(bin, ['--version']).trim();
|
|
187
|
+
}
|
package/src/hq/hq.ts
CHANGED
|
@@ -14,8 +14,8 @@ import { fenceOutcomeDir, recordFenceOutcome } from './fence-outcomes.js';
|
|
|
14
14
|
import { promoteHq, proposeHqPromotion, writeOpSeatRequest } from './promote.js';
|
|
15
15
|
import type { HqOpRequestWriter, PromoteResult } from './promote.js';
|
|
16
16
|
import {
|
|
17
|
-
DEFAULT_TTL_SECONDS, decideAcquire, decideArbiterCheck, decideFence, decideRelease, decideRenew, defaultSshRunner,
|
|
18
|
-
fileLeaseStore, leaseExpired, parseLease, probeReachable, recordView, serializeLease, sshLeaseStore, sshReachable, localShellRunner,
|
|
17
|
+
DEFAULT_TTL_SECONDS, LOCAL_LEASE_CACHE_SECONDS, decideAcquire, decideArbiterCheck, decideFence, decideRelease, decideRenew, defaultSshRunner,
|
|
18
|
+
fileLeaseStore, leaseExpired, parseLease, probeReachable, recordView, serializeLease, serializeLocalLeaseCache, sshLeaseStore, sshReachable, localShellRunner,
|
|
19
19
|
type HostProbe, type LeaseRecord, type LeaseStore, type LocalHqState, type SshRunner,
|
|
20
20
|
} from './lease.js';
|
|
21
21
|
|
|
@@ -163,6 +163,30 @@ function writeLocal(path: string, state: LocalHqState): void {
|
|
|
163
163
|
renameSync(tmp, path);
|
|
164
164
|
}
|
|
165
165
|
|
|
166
|
+
/** FENCE-LIGHT host-local hint (`<config>/hq/lease-cache`), not authority: only a successful holder renewal writes it. */
|
|
167
|
+
const leaseCachePath = (r: Resolved): string => join(dirname(r.localPath), 'lease-cache');
|
|
168
|
+
function dropHeartbeatCache(r: Resolved): void {
|
|
169
|
+
try { rmSync(leaseCachePath(r), { force: true }); }
|
|
170
|
+
catch (error) { observe(r, 'hq.lease', 'cache-invalidate-failed', { error: String(error).slice(0, 200) }); }
|
|
171
|
+
}
|
|
172
|
+
function writeHeartbeatCache(r: Resolved, record: LeaseRecord): void {
|
|
173
|
+
const path = leaseCachePath(r);
|
|
174
|
+
try {
|
|
175
|
+
mkdirSync(dirname(path), { recursive: true, mode: 0o700 });
|
|
176
|
+
const confirmedAt = r.now();
|
|
177
|
+
const expiresAt = Math.min(confirmedAt + LOCAL_LEASE_CACHE_SECONDS, record.renewedAt + record.ttlSeconds);
|
|
178
|
+
if (expiresAt <= confirmedAt) return;
|
|
179
|
+
const tempDir = mkdtempSync(`${path}.`);
|
|
180
|
+
try {
|
|
181
|
+
const tmp = join(tempDir, 'cache');
|
|
182
|
+
writeFileSync(tmp, serializeLocalLeaseCache({ holder: record.holder, generation: record.generation, expiresAt, host: r.me, machine: hostname().replace(/\.local$/, ''), confirmedAt }), { mode: 0o600 });
|
|
183
|
+
renameSync(tmp, path);
|
|
184
|
+
} finally { rmSync(tempDir, { recursive: true, force: true }); }
|
|
185
|
+
} catch (error) {
|
|
186
|
+
observe(r, 'hq.lease', 'cache-write-failed', { error: String(error).slice(0, 200) });
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
166
190
|
/** Highest lease generation this host ever observed (null = never). */
|
|
167
191
|
/** The seen-generation marker this host keeps (hq.seenGenerationFile · default ~/.elanous-hq/seen-generation). */
|
|
168
192
|
export function hqSeenGenerationPath(config: Pick<HqConfig, 'seenGenerationFile'>): string {
|
|
@@ -241,6 +265,7 @@ export function hqLease(action: LeaseAction, deps: HqDeps = {}, opts: { host?: s
|
|
|
241
265
|
const result = mutate(r, (record, now) => action === 'acquire' ? decideAcquire(record, r.me, now, r.ttl)
|
|
242
266
|
: action === 'renew' ? decideRenew(record, r.me, local.generation, now) : decideRelease(record, r.me, expected));
|
|
243
267
|
noteSeen(r, result.record);
|
|
268
|
+
if (result.ok) dropHeartbeatCache(r);
|
|
244
269
|
if (result.ok && action !== 'release') writeLocal(r.localPath, { holder: result.record.holder, generation: result.record.generation, confirmedAt: r.now(), ttlSeconds: result.record.ttlSeconds });
|
|
245
270
|
if (result.ok && action === 'release') writeLocal(r.localPath, { ...local, holder: undefined, confirmedAt: undefined });
|
|
246
271
|
observe(r, 'hq.lease', result.ok ? (action === 'acquire' ? 'acquired' : action === 'renew' ? 'renewed' : 'released') : `${action}-refused`,
|
|
@@ -255,6 +280,8 @@ export function hqLease(action: LeaseAction, deps: HqDeps = {}, opts: { host?: s
|
|
|
255
280
|
export function hqHeartbeat(deps: HqDeps = {}) {
|
|
256
281
|
const r = resolve(deps);
|
|
257
282
|
const local = readLocal(r.localPath);
|
|
283
|
+
// Never extend a prior positive hint through an unreachable/refused heartbeat.
|
|
284
|
+
dropHeartbeatCache(r);
|
|
258
285
|
let read: { now: number; raw: string | null };
|
|
259
286
|
try { read = r.store.read(); } catch (error) {
|
|
260
287
|
const standbyUp = local.holder === r.me ? sshReachable(r.standby, r.ssh) : false;
|
|
@@ -273,6 +300,7 @@ export function hqHeartbeat(deps: HqDeps = {}) {
|
|
|
273
300
|
if (renewed.ok) {
|
|
274
301
|
noteSeen(r, renewed.record);
|
|
275
302
|
writeLocal(r.localPath, { holder: r.me, generation: renewed.record.generation, confirmedAt: r.now(), ttlSeconds: renewed.record.ttlSeconds });
|
|
303
|
+
if (record.renewedAt !== 0 && !leaseExpired(renewed.record, r.now())) writeHeartbeatCache(r, renewed.record);
|
|
276
304
|
observe(r, 'hq.lease', 'renewed', { generation: renewed.record.generation });
|
|
277
305
|
return { outcome: 'renewed' as const, generation: renewed.record.generation };
|
|
278
306
|
}
|
|
@@ -282,6 +310,7 @@ export function hqHeartbeat(deps: HqDeps = {}) {
|
|
|
282
310
|
// Not the holder: report the view, keep the granted generation, stay standby.
|
|
283
311
|
const seen = probeReachable(record.holder, r.probe);
|
|
284
312
|
const reachable = seen.reachable;
|
|
313
|
+
// FENCE-LIGHT: a standby never writes the cache (the shell fence only fast-paths a fresh «this host holds» line).
|
|
285
314
|
mutate(r, (current) => current ? { ok: true, next: recordView(current, r.me, reachable, read.now) } : { ok: false, reason: 'no lease' });
|
|
286
315
|
if (leaseExpired(record, read.now)) observe(r, 'hq.lease', 'expired-seen', { holder: record.holder, generation: record.generation });
|
|
287
316
|
writeLocal(r.localPath, { holder: record.holder, generation: record.generation, ttlSeconds: record.ttlSeconds });
|
|
@@ -365,6 +394,8 @@ export function hqFenceDecision(role: HqFenceRole, deps: HqDeps = {}) {
|
|
|
365
394
|
failOpen: role !== 'ledger-cli' && r.failOpen.has(role),
|
|
366
395
|
...(seenGeneration !== null ? { seenGeneration } : {}),
|
|
367
396
|
});
|
|
397
|
+
// FENCE-LIGHT: any CLI «do not run» on this host retires a lingering fast-path hint at once.
|
|
398
|
+
if (!decided.run) dropHeartbeatCache(r);
|
|
368
399
|
if (decided.run && record !== 'unreachable' && record) writeLocal(r.localPath, { holder: r.me, generation: record.generation, confirmedAt: r.now(), ttlSeconds: record.ttlSeconds });
|
|
369
400
|
observe(r, 'hq.fence', decided.run ? 'allowed' : 'skipped', { role, reason: decided.reason, holder: decided.holder, generation: decided.generation });
|
|
370
401
|
return decided;
|
package/src/hq/lease.ts
CHANGED
|
@@ -36,6 +36,19 @@ export function parseLease(raw: string | null): LeaseRecord | null {
|
|
|
36
36
|
export const serializeLease = (record: LeaseRecord): string => `${JSON.stringify(record)}\n`;
|
|
37
37
|
export const leaseExpired = (record: LeaseRecord, now: number): boolean => record.renewedAt + record.ttlSeconds < now;
|
|
38
38
|
|
|
39
|
+
/** Host-local, short-lived shell fence snapshot; never the arbiter's CAS lease ledger. */
|
|
40
|
+
export interface LocalLeaseCache { holder: string; generation: number; expiresAt: number; host: string; machine: string; confirmedAt: number }
|
|
41
|
+
export const LOCAL_LEASE_CACHE_SECONDS = 660; // heartbeat cadence 600s + 60s scheduling slack; still below the 1500s lease TTL
|
|
42
|
+
/** A single whitespace-delimited line so /bin/sh can read it without bun or JSON tools. */
|
|
43
|
+
export function serializeLocalLeaseCache(cache: LocalLeaseCache): string {
|
|
44
|
+
if (![cache.holder, cache.host, cache.machine].every(value => /^[A-Za-z0-9_.-]+$/.test(value))
|
|
45
|
+
|| !Number.isSafeInteger(cache.generation) || cache.generation < 1
|
|
46
|
+
|| !Number.isSafeInteger(cache.expiresAt) || cache.expiresAt < 1
|
|
47
|
+
|| !Number.isSafeInteger(cache.confirmedAt) || cache.confirmedAt < 1
|
|
48
|
+
|| cache.expiresAt <= cache.confirmedAt || cache.expiresAt - cache.confirmedAt > LOCAL_LEASE_CACHE_SECONDS) throw new Error('invalid local lease cache');
|
|
49
|
+
return `${cache.holder} ${cache.generation} ${cache.expiresAt} ${cache.host} ${cache.machine} ${cache.confirmedAt}\n`;
|
|
50
|
+
}
|
|
51
|
+
|
|
39
52
|
/** acquire: only when there is no lease or it has expired; a new holder gets the next generation. */
|
|
40
53
|
export function decideAcquire(record: LeaseRecord | null, me: string, now: number, ttlSeconds = DEFAULT_TTL_SECONDS):
|
|
41
54
|
{ ok: true; next: LeaseRecord } | { ok: false; reason: string } {
|
package/src/hq/seats.ts
CHANGED
|
@@ -2,13 +2,16 @@
|
|
|
2
2
|
import { existsSync, lstatSync, mkdirSync, readdirSync, readFileSync, realpathSync, renameSync, rmSync, rmdirSync, writeFileSync } from 'node:fs';
|
|
3
3
|
import { basename, dirname, isAbsolute, join, resolve } from 'node:path';
|
|
4
4
|
import { runGitCommand } from '../git-fs/runner.js';
|
|
5
|
+
import { runGhCliWithResult, type GhCliResult } from '../git-fs/gh-cli.js';
|
|
6
|
+
import { registeredSeats } from '../seat-address/seat-address.js';
|
|
7
|
+
import { debug } from '../debug/log.js';
|
|
5
8
|
|
|
6
9
|
type HqSeatRole = 'OP' | 'MK' | 'TC' | 'UX';
|
|
7
10
|
const ROLES: readonly string[] = ['OP', 'MK', 'TC', 'UX'];
|
|
8
11
|
interface HouseLedger {
|
|
9
12
|
home: { kind: 'home'; path: string; source: 'cli-resolved' };
|
|
10
|
-
// `reclaiming`
|
|
11
|
-
sandboxes: Record<string, { kind: 'sandbox'; path: string; owner: HqSeatRole; reclaiming?: string }>;
|
|
13
|
+
// `reclaiming` records the original work branch tip; checkoutTip is needed if done detached a different branch.
|
|
14
|
+
sandboxes: Record<string, { kind: 'sandbox'; path: string; owner: HqSeatRole; reclaiming?: string; checkoutTip?: string; checkoutBranch?: string }>;
|
|
12
15
|
}
|
|
13
16
|
|
|
14
17
|
function hqRoot(home = process.env.HOME): string {
|
|
@@ -30,6 +33,26 @@ function repository(root: string): string {
|
|
|
30
33
|
}
|
|
31
34
|
|
|
32
35
|
function ledgerPath(root: string): string { return join(root, 'house.json'); }
|
|
36
|
+
|
|
37
|
+
/** Read-only launch lookup: the registered home and detached seat must exist; never create or refresh a view. */
|
|
38
|
+
export function resolveHqLaunchSeat(role: string, home: string, options: { root?: string } = {}): string {
|
|
39
|
+
if (!registeredSeats().some(seat => seat.id === role)) throw new Error(`unregistered seat: ${role}`);
|
|
40
|
+
const root = resolve(options.root ?? hqRoot());
|
|
41
|
+
const registeredHome = readLedger(root).home.path;
|
|
42
|
+
if (realpathSync(home) !== realpathSync(registeredHome)) throw new Error(`house.json home mismatch: registered ${registeredHome}; requested ${home}`);
|
|
43
|
+
const path = join(root, 'seats', role);
|
|
44
|
+
assertRegisteredWorktree(repository(root), path);
|
|
45
|
+
const head = runGitCommand(path, ['symbolic-ref', '--quiet', 'HEAD'], { encoding: 'utf8' });
|
|
46
|
+
if (head.status === 0) throw new Error(`not a detached seat: ${path}`);
|
|
47
|
+
if (head.status !== 1) throw new Error(`git symbolic-ref --quiet HEAD: ${head.stderr.trim() || `exit ${head.status}`}`);
|
|
48
|
+
return path;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Installed-CLI argv: absolute runtime + absolute CLI entry, so a launch never resolves `bin/elanous.mjs` against its cwd. */
|
|
52
|
+
export function installedCliArgv(args: readonly string[] = []): string[] {
|
|
53
|
+
return [process.execPath, resolve(import.meta.dir, '../../bin/elanous.mjs'), ...args];
|
|
54
|
+
}
|
|
55
|
+
|
|
33
56
|
function readLedger(root: string): HouseLedger {
|
|
34
57
|
const ledger = JSON.parse(readFileSync(ledgerPath(root), 'utf8')) as HouseLedger;
|
|
35
58
|
if (!ledger.home?.path || !ledger.sandboxes || typeof ledger.sandboxes !== 'object') throw new Error('invalid house.json; refusing to guess houses');
|
|
@@ -110,6 +133,68 @@ function mergedIntoMain(repo: string, commit: string): boolean {
|
|
|
110
133
|
if (merged.status !== 0 && merged.status !== 1) throw new Error(`cannot verify merge: ${merged.stderr.trim()}`);
|
|
111
134
|
return merged.status === 0;
|
|
112
135
|
}
|
|
136
|
+
|
|
137
|
+
export type MergedPrProof = { status: 'pr-merged'; number: number } | { status: 'not-merged' | 'unknown' };
|
|
138
|
+
const MERGED_PR_QUERY_LIMIT = 1000;
|
|
139
|
+
|
|
140
|
+
/** Read-only PR evidence from the HQ mirror's origin, never the caller's current repository. */
|
|
141
|
+
export function proveMergedPr(repo: string, branch: string, tip: string, gh: (args: string[]) => GhCliResult = runGhCliWithResult, base?: string): MergedPrProof {
|
|
142
|
+
if (!repo || !branch || !tip) return { status: 'unknown' };
|
|
143
|
+
let result: GhCliResult;
|
|
144
|
+
try {
|
|
145
|
+
// Read the unexpanded URL: `git remote get-url` applies url.*.insteadOf, possibly turning a
|
|
146
|
+
// hosted origin into a local mirror and losing the repository identity gh must query.
|
|
147
|
+
const origin = git(repo, 'config', '--local', '--get', 'remote.origin.url');
|
|
148
|
+
const url = /^(?:https?:\/\/|ssh:\/\/)(?:[^@/]+@)?([^/]+)\/([^/]+)\/([^/]+?)(?:\.git)?\/?$/.exec(origin);
|
|
149
|
+
const scp = /^(?:[^@]+@)?([^:/]+):([^/]+)\/([^/]+?)(?:\.git)?$/.exec(origin);
|
|
150
|
+
if (!url && !scp) return { status: 'unknown' };
|
|
151
|
+
const target = url ? `${url[1]}/${url[2]}/${url[3]}` : `${scp![1]}/${scp![2]}/${scp![3]}`;
|
|
152
|
+
result = gh(['pr', 'list', '--state', 'merged', '--head', branch, ...(base ? ['--base', base] : []), '--json', 'number,headRefOid', '--limit', String(MERGED_PR_QUERY_LIMIT), '--repo', target]);
|
|
153
|
+
} catch { return { status: 'unknown' }; }
|
|
154
|
+
if (!result.ok || result.maybeTruncated) return { status: 'unknown' };
|
|
155
|
+
let parsed: unknown;
|
|
156
|
+
try { parsed = JSON.parse(result.stdout.toString('utf8')); }
|
|
157
|
+
catch { return { status: 'unknown' }; }
|
|
158
|
+
if (!Array.isArray(parsed) || !parsed.every((pr): pr is { number: number; headRefOid: string } =>
|
|
159
|
+
pr !== null && typeof pr === 'object' && Number.isSafeInteger(pr.number) && pr.number > 0
|
|
160
|
+
&& typeof pr.headRefOid === 'string' && pr.headRefOid.length > 0)) return { status: 'unknown' };
|
|
161
|
+
const matching = parsed.find(pr => pr.headRefOid === tip);
|
|
162
|
+
return matching ? { status: 'pr-merged', number: matching.number }
|
|
163
|
+
: { status: parsed.length >= MERGED_PR_QUERY_LIMIT ? 'unknown' : 'not-merged' };
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Attached tips use their branch; a detached HEAD is checked separately from the registered work tip.
|
|
167
|
+
function landedTip(repo: string, branch: string, tip: string, gh: (args: string[]) => GhCliResult): 'ancestor' | 'pr-merged' | 'tree-equal' | null {
|
|
168
|
+
if (mergedIntoMain(repo, tip)) return 'ancestor';
|
|
169
|
+
if (proveMergedPr(repo, branch, tip, gh, 'main').status === 'pr-merged') return 'pr-merged';
|
|
170
|
+
return treeEqualToMain(repo, tip) ? 'tree-equal' : null;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function judgeLandedTip(repo: string, branch: string, tip: string, gh: (args: string[]) => GhCliResult): boolean {
|
|
174
|
+
const rule = landedTip(repo, branch, tip, gh);
|
|
175
|
+
try { debug.log('hq.work', 'done-judged', { branch, tip, rule: rule ?? 'not-merged' }); } catch { /* observation must not change the verdict */ }
|
|
176
|
+
return rule !== null;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Squash changes commit identity. Check the changed paths together against a fetched main
|
|
180
|
+
// snapshot, not merely the latest tree: a later main commit may have edited those paths again.
|
|
181
|
+
function treeEqualToMain(repo: string, tip: string): boolean {
|
|
182
|
+
const main = 'refs/remotes/origin/main';
|
|
183
|
+
const base = git(repo, 'merge-base', tip, main);
|
|
184
|
+
const changed = runGitCommand(repo, ['diff', '--name-only', '-z', base, tip], { encoding: 'utf8' });
|
|
185
|
+
if (changed.status !== 0) throw new Error(`cannot verify changed files: ${changed.stderr.trim() || `exit ${changed.status}`}`);
|
|
186
|
+
const files = changed.stdout.split('\0').filter(Boolean);
|
|
187
|
+
if (files.length === 0) return false;
|
|
188
|
+
const equalAt = (commit: string): boolean => {
|
|
189
|
+
const equal = runGitCommand(repo, ['diff', '--quiet', tip, commit, '--', ...files], { encoding: 'utf8' });
|
|
190
|
+
if (equal.status !== 0 && equal.status !== 1) throw new Error(`cannot verify tree equality: ${equal.stderr.trim() || `exit ${equal.status}`}`);
|
|
191
|
+
return equal.status === 0;
|
|
192
|
+
};
|
|
193
|
+
if (equalAt(main)) return true;
|
|
194
|
+
// Only first-parent main snapshots can establish a previous landing; a side branch alone cannot.
|
|
195
|
+
const history = git(repo, 'log', '--first-parent', '--format=%H', `${base}..${main}`, '--', ...files);
|
|
196
|
+
return history.split('\n').filter(Boolean).some(equalAt);
|
|
197
|
+
}
|
|
113
198
|
// Git lock files are created exclusively; ours carry our pid. A lock left by a killed `work done` is never
|
|
114
199
|
// removed automatically (between our check and a delete, a live git could have re-taken it): the error names it.
|
|
115
200
|
function takeLock(lock: string): void {
|
|
@@ -231,7 +316,7 @@ export function hqSeat(value: string, options: { root?: string; refresh?: boolea
|
|
|
231
316
|
});
|
|
232
317
|
}
|
|
233
318
|
|
|
234
|
-
export function hqWork(action: 'new' | 'done', value: string, nameValue: string, options: { root?: string } = {}): { path: string; outcome: 'created' | 'removed' } {
|
|
319
|
+
export function hqWork(action: 'new' | 'done', value: string, nameValue: string, options: { root?: string; gh?: (args: string[]) => GhCliResult } = {}): { path: string; outcome: 'created' | 'removed' } {
|
|
235
320
|
const role = roleOf(value);
|
|
236
321
|
const name = workName(nameValue);
|
|
237
322
|
const root = resolve(options.root ?? hqRoot());
|
|
@@ -289,15 +374,19 @@ export function hqWork(action: 'new' | 'done', value: string, nameValue: string,
|
|
|
289
374
|
const ref = runGitCommand(repo, ['rev-parse', '--verify', '--quiet', `refs/heads/${branch}`], { encoding: 'utf8' });
|
|
290
375
|
if (ref.status !== 0) {
|
|
291
376
|
// Without the branch, the only merge proof is the tip an interrupted done recorded before removing.
|
|
292
|
-
const
|
|
293
|
-
|
|
377
|
+
const recordedEntry = readLedger(root).sandboxes[path];
|
|
378
|
+
const recorded = recordedEntry?.reclaiming;
|
|
379
|
+
const checkout = recordedEntry?.checkoutTip;
|
|
380
|
+
if (!recorded || !judgeLandedTip(repo, branch, recorded, options.gh ?? runGhCliWithResult)
|
|
381
|
+
|| (checkout && (!recordedEntry.checkoutBranch || !judgeLandedTip(repo, recordedEntry.checkoutBranch, checkout, options.gh ?? runGhCliWithResult)))) {
|
|
382
|
+
throw new Error(`cannot verify merge: branch ${branch} is missing; house.json entry kept: ${path}`);
|
|
383
|
+
}
|
|
294
384
|
updateLedgerLocked(root, ledger => { delete ledger.sandboxes[path]; });
|
|
295
385
|
removeStaleWorktreeAdmin(repo, own);
|
|
296
386
|
return;
|
|
297
387
|
}
|
|
298
388
|
const tip = ref.stdout.trim();
|
|
299
|
-
|
|
300
|
-
if (merged.status !== 0) throw new Error(merged.status === 1 ? `work is not merged into origin/main: ${branch}` : `cannot verify merge: ${merged.stderr}`);
|
|
389
|
+
if (!judgeLandedTip(repo, branch, tip, options.gh ?? runGhCliWithResult)) throw new Error(`work is not merged into origin/main: ${branch}`);
|
|
301
390
|
// Same order as a normal done: the entry goes first, so a save that fails again keeps the branch (the proof).
|
|
302
391
|
const entry = readLedger(root).sandboxes[path];
|
|
303
392
|
updateLedgerLocked(root, ledger => { delete ledger.sandboxes[path]; });
|
|
@@ -321,34 +410,53 @@ export function hqWork(action: 'new' | 'done', value: string, nameValue: string,
|
|
|
321
410
|
// Resume: an interrupted done left the work detached at its recorded merged tip, with the branch either
|
|
322
411
|
// still at that tip (stopped before the delete) or already deleted (stopped before the removal).
|
|
323
412
|
const branchPresent = branchTip.status === 0;
|
|
324
|
-
const
|
|
325
|
-
|
|
413
|
+
const head = git(path, 'rev-parse', 'HEAD');
|
|
414
|
+
const resuming = attached.status === 1 && entry.reclaiming !== undefined
|
|
415
|
+
&& head === (entry.checkoutTip ?? entry.reclaiming)
|
|
326
416
|
&& (!branchPresent || branchTip.stdout.trim() === entry.reclaiming);
|
|
327
|
-
if (!resuming && (attached.status !== 0
|
|
328
|
-
const
|
|
417
|
+
if (!resuming && (attached.status !== 0 && attached.status !== 1 || !branchPresent)) throw new Error(`work branch mismatch: ${path}`);
|
|
418
|
+
const ownTip = resuming ? entry.reclaiming! : branchTip.stdout.trim();
|
|
419
|
+
const detached = !resuming && attached.status === 1;
|
|
420
|
+
const detachedCheckout = detached || (resuming && entry.checkoutBranch === branch && entry.checkoutTip !== undefined && entry.checkoutTip !== ownTip);
|
|
421
|
+
const checkoutBranch = resuming ? entry.checkoutBranch ?? branch : detached ? branch : attached.stdout.trim();
|
|
422
|
+
const checkoutRef = `refs/heads/${checkoutBranch}`;
|
|
423
|
+
const differentBranch = checkoutBranch !== branch;
|
|
424
|
+
const tip = detached ? head : resuming && entry.checkoutTip ? entry.checkoutTip : differentBranch
|
|
425
|
+
? git(repo, 'rev-parse', '--verify', checkoutRef) : ownTip;
|
|
426
|
+
if (!resuming && !detached && differentBranch && head !== tip) throw new Error(`work branch moved before the merge check: ${checkoutBranch}`);
|
|
329
427
|
// The ancestor check is against origin/main, not bare HEAD (which may still point to master).
|
|
330
|
-
if (!
|
|
331
|
-
|
|
332
|
-
|
|
428
|
+
if (!judgeLandedTip(repo, checkoutBranch, tip, options.gh ?? runGhCliWithResult)) throw new Error(`work is not merged into origin/main: ${checkoutBranch}`);
|
|
429
|
+
if ((differentBranch || detachedCheckout) && tip !== ownTip && !judgeLandedTip(repo, branch, ownTip, options.gh ?? runGhCliWithResult)) throw new Error(`work is not merged into origin/main: ${branch}`);
|
|
430
|
+
if (differentBranch && !resuming) {
|
|
431
|
+
const current = runGitCommand(repo, ['rev-parse', '--verify', checkoutRef], { encoding: 'utf8' });
|
|
432
|
+
if (current.status !== 0 || current.stdout.trim() !== tip || git(path, 'rev-parse', 'HEAD') !== tip) throw new Error(`work branch moved before removal: ${checkoutBranch}`);
|
|
433
|
+
}
|
|
434
|
+
// Removal order keeps every interruption resumable: record the proven checkout tip and original branch tip
|
|
435
|
+
// → detach → hold the work's git locks → compare-and-delete the original branch → remove the worktree
|
|
436
|
+
// → drop the registration last. A different attached branch is proven but its ref is never deleted;
|
|
437
|
+
// a pre-existing detached HEAD is checked independently and never reattached on a failed removal.
|
|
333
438
|
// Holding this worktree's HEAD.lock and index.lock makes git refuse every commit and `add` in it until the
|
|
334
439
|
// removal ends (`worktree remove` itself does not need them), so no commit can land between the last
|
|
335
440
|
// check and the removal. The locks live in the worktree's admin dir, which the removal deletes.
|
|
336
|
-
updateLedgerLocked(root, ledger => { ledger.sandboxes[path] = { ...entry, reclaiming:
|
|
441
|
+
updateLedgerLocked(root, ledger => { ledger.sandboxes[path] = { ...entry, reclaiming: ownTip,
|
|
442
|
+
...(differentBranch || (detachedCheckout && tip !== ownTip) ? { checkoutTip: tip, checkoutBranch } : {}) }; });
|
|
337
443
|
const admin = git(path, 'rev-parse', '--absolute-git-dir');
|
|
338
444
|
const held: string[] = [];
|
|
339
445
|
const release = (): void => { for (const lock of held.splice(0)) rmSync(lock, { force: true }); };
|
|
340
446
|
const restore = (): string => {
|
|
341
447
|
release();
|
|
342
448
|
const head = runGitCommand(path, ['rev-parse', '--verify', 'HEAD'], { encoding: 'utf8' });
|
|
343
|
-
runGitCommand(repo, ['update-ref', ref, head.status === 0 ? head.stdout.trim() : tip, ''], { encoding: 'utf8' });
|
|
344
|
-
const reattached =
|
|
345
|
-
|
|
346
|
-
|
|
449
|
+
runGitCommand(repo, ['update-ref', ref, differentBranch || detachedCheckout ? ownTip : head.status === 0 ? head.stdout.trim() : tip, ''], { encoding: 'utf8' });
|
|
450
|
+
const reattached = detachedCheckout ? head.status === 0 && head.stdout.trim() === tip
|
|
451
|
+
: runGitCommand(path, ['symbolic-ref', 'HEAD', checkoutRef], { encoding: 'utf8' }).status === 0;
|
|
452
|
+
// If HEAD could not be restored (e.g. a stale HEAD.lock), keep `reclaiming` so the next done resumes.
|
|
453
|
+
const restored = reattached ? { kind: entry.kind, path: entry.path, owner: entry.owner } : { ...entry, reclaiming: ownTip,
|
|
454
|
+
...(differentBranch || (detachedCheckout && tip !== ownTip) ? { checkoutTip: tip, checkoutBranch } : {}) };
|
|
347
455
|
try { updateLedgerLocked(root, ledger => { ledger.sandboxes[path] = restored; }); return reattached ? 'registration restored' : 'left resumable'; }
|
|
348
456
|
catch (error) { return `REGISTRATION NOT RESTORED (fix ${path} in house.json by hand): ${error instanceof Error ? error.message : String(error)}`; }
|
|
349
457
|
};
|
|
350
458
|
const fail = (message: string): never => { throw new Error(`${message}; nothing removed: ${path} · ${restore()}`); };
|
|
351
|
-
if (!resuming) git(path, 'checkout', '--quiet', '--detach');
|
|
459
|
+
if (!resuming && !detached) git(path, 'checkout', '--quiet', '--detach');
|
|
352
460
|
for (const name of ['HEAD.lock', 'index.lock']) {
|
|
353
461
|
const lock = join(admin, name);
|
|
354
462
|
try { takeLock(lock); held.push(lock); }
|
|
@@ -356,10 +464,14 @@ export function hqWork(action: 'new' | 'done', value: string, nameValue: string,
|
|
|
356
464
|
}
|
|
357
465
|
if (git(path, 'rev-parse', 'HEAD') !== tip) fail('work gained a commit after the merge check');
|
|
358
466
|
if (branchPresent) {
|
|
359
|
-
const deleted = runGitCommand(repo, ['update-ref', '-d', ref,
|
|
467
|
+
const deleted = runGitCommand(repo, ['update-ref', '-d', ref, ownTip], { encoding: 'utf8' });
|
|
360
468
|
if (deleted.status !== 0) fail(`work branch moved after the merge check: ${deleted.stderr.trim()}`);
|
|
361
469
|
}
|
|
362
470
|
if (!clean(path)) fail('work changed during done');
|
|
471
|
+
if (differentBranch) {
|
|
472
|
+
const current = runGitCommand(repo, ['rev-parse', '--verify', checkoutRef], { encoding: 'utf8' });
|
|
473
|
+
if (current.status !== 0 || current.stdout.trim() !== tip) fail(`work branch moved after the merge check: ${checkoutBranch}`);
|
|
474
|
+
}
|
|
363
475
|
// Without --force, git also refuses a worktree that became dirty after the check above.
|
|
364
476
|
const removed = runGitCommand(repo, ['worktree', 'remove', path], { encoding: 'utf8' });
|
|
365
477
|
if (removed.status !== 0) fail(`git worktree remove failed: ${removed.stderr.trim()}`);
|