elanous 0.2.29 → 0.2.31
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/apps/pwa/out/404/index.html +1 -1
- package/apps/pwa/out/404.html +1 -1
- package/apps/pwa/out/_next/static/{cIpv5nU9AycwYWZI9H589 → LSXfY0u9QtfDbejY0Ukf_}/_buildManifest.js +1 -1
- package/apps/pwa/out/_next/static/chunks/{058a98ba.7eb28d5e6dc03565.js → 0bb0ff36.b8d418980951e5ba.js} +7 -7
- package/apps/pwa/out/_next/static/chunks/1034-8b639cee57b8e4a8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1085-6c2d35526f11f840.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1220.1ff7d5cdc34b1ae5.js +27 -0
- package/apps/pwa/out/_next/static/chunks/1247.5884ec81b39fe2e7.js +287 -0
- package/apps/pwa/out/_next/static/chunks/1302.87681521a7a4661e.js +62 -0
- package/apps/pwa/out/_next/static/chunks/1324-59dae3002413623b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{6013-2f769370d8b4c2c6.js → 1335-6731f392c35216eb.js} +3 -3
- package/apps/pwa/out/_next/static/chunks/1343-f40b5870ac1eda5a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/146-b6cd56be11dda017.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{8332.6f0f49df772e163b.js → 1654.32de4b324981a188.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/1834-50625904996a8fa8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1849.1ce38efa6c527d9a.js +149 -0
- package/apps/pwa/out/_next/static/chunks/{8795.32573a185af18c7b.js → 1913.7cc6fb6055b1ed3f.js} +2 -2
- package/apps/pwa/out/_next/static/chunks/{419.736c0d6f426249a7.js → 2001.6c7e0fd02190886c.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/2030.41fae98c7e907304.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2099-08b2f2dfba742c9b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2112-e4efc21a6505d5bc.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2134.ecee03a550ee7250.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{6767.1fcfc9b8be83975f.js → 2213.2ccbada92c33ea0f.js} +16 -16
- package/apps/pwa/out/_next/static/chunks/2236.66061832181cb745.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2422.b0bd272e7020d7e6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2534.e85ae21ad0dac829.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{4759.1f76713aefb87db5.js → 2871.0bca6137343b3f76.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{6776.06ff1e3b464cc607.js → 2898.fd781bc7cf01a7b7.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{1782-59db1b5aa06df38a.js → 2940-619bfd8ef337c611.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{8471.8d2a8c74f09c36ff.js → 2985.39ccdecd36848b12.js} +4 -4
- package/apps/pwa/out/_next/static/chunks/3040.92ff9ffc9f971cb9.js +115 -0
- package/apps/pwa/out/_next/static/chunks/3144.6861096c7b4edef7.js +64 -0
- package/apps/pwa/out/_next/static/chunks/{bc324b7c.02a09dd93f6a399b.js → 3267c0e6.d34854e7b3984a6e.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/3278.2e5d15b53a4091c8.js +2 -0
- package/apps/pwa/out/_next/static/chunks/{7725-4f83d5541fbdb81b.js → 3541-ce3a5424ed5ad7f1.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/3583.a05e88cc8d594628.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3771.e928edf2867d28c0.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3902.e1e71aaac850a045.js +34 -0
- package/apps/pwa/out/_next/static/chunks/4063-03284b5de59965f3.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{46bd8bea.2f4b4099172fcdb7.js → 409f1e57.d0649fc47691ebfe.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/4111.2bfedbb233ad206b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{1142.65d002789ab3af7d.js → 4174.92ed04922709ac9e.js} +2 -2
- package/apps/pwa/out/_next/static/chunks/4300-c7ec4eeabbb5f3f7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4483.646d5065199e1b55.js +36 -0
- package/apps/pwa/out/_next/static/chunks/4519.28199ff26b2fd80d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4581.ef2443578054544f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4608-8d894e9cbe4f08bf.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4639.ae48a01d1bb50a93.js +286 -0
- package/apps/pwa/out/_next/static/chunks/4739.08742ac668aada67.js +77 -0
- package/apps/pwa/out/_next/static/chunks/4745.67d6f99c245af3f7.js +226 -0
- package/apps/pwa/out/_next/static/chunks/4882-16cef71d6a1a32be.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4999.c064a4000c2a9e94.js +79 -0
- package/apps/pwa/out/_next/static/chunks/5173-233188df2f42c626.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5260-69f35419bca263e7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5272.b92d1b72a15c25fe.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5304.04d24756f242a094.js +148 -0
- package/apps/pwa/out/_next/static/chunks/5307.60f2209e809bd627.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5415-997980f828667736.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{3396-97b97928c49190c9.js → 5458-16cd7a2b51b74c8c.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{3403.7199205e65124486.js → 5525.86debf231e181813.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{9143.ff14585db1d275ff.js → 5543.a438b8ab5a13ea47.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/5572-42c9084f27ca761e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5661-80470aac963d9f36.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5723.421338408b5c30c4.js +74 -0
- package/apps/pwa/out/_next/static/chunks/5809.64c3664267c50bce.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6000.64bc21ac8627f2f5.js +94 -0
- package/apps/pwa/out/_next/static/chunks/6013.39459b9c68eabf5f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/607-06466dff012b03ef.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{8398.73a19b39715eda04.js → 6092.a7f99e68b188ca66.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/60fb96c9.360b01d8d4927b7b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{5591.a35e7a217734a8b9.js → 6157.0c9af021de4fb31b.js} +2 -2
- package/apps/pwa/out/_next/static/chunks/6192.a190b989b81e9743.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6323.8672b4817a306928.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{7375.050ea0d72968e49f.js → 6433.fc573af22bbc9ab7.js} +17 -17
- package/apps/pwa/out/_next/static/chunks/6444.7d1fdcb9b09ae71c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6517.4147878ac06d584d.js +90 -0
- package/apps/pwa/out/_next/static/chunks/6532.e34302010b2f0b68.js +32 -0
- package/apps/pwa/out/_next/static/chunks/6544cb48.a87fe20ddf137be2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6546.74bc4ed82f19016d.js +104 -0
- package/apps/pwa/out/_next/static/chunks/6684-e7523ce7c2fad075.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6726.ebdc82feb161431e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6739.00fb6f892e5e392b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6745.934c4921bf15513a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6767.ea607cf0c20a25af.js +24 -0
- package/apps/pwa/out/_next/static/chunks/6843.67bfd020da0926c5.js +93 -0
- package/apps/pwa/out/_next/static/chunks/6948.1062d4594652b5bf.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7138-eaf841fc1dc03463.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7174-cda29ee13d7790e7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7304-3c07583f4e405f4f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7371.80c05c653b08e907.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7391.ebae168025c91b6a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{1852.7f7b53991fcb99ef.js → 7502.ccd5b8065545edd0.js} +2 -2
- package/apps/pwa/out/_next/static/chunks/7557.e325503a090a4310.js +8 -0
- package/apps/pwa/out/_next/static/chunks/7569-9ce9432c7f10989f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{6534.1ab00f370db7b105.js → 7862.30a8d6482bb238eb.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/7886-55854585fadeded1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7947-d0759430f8042240.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{285adb1a.5654e605dddf86d2.js → 7a1887c4.f36b88fba31da8fd.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/8018.a32663f04a6fc5f9.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8097-92f87bd108322f66.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8215-1661812b6e9ad71f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8260-19c921a51ca8ec3e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8440.3a5cf5c85aa76db8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8519-4599be2249161e68.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8547.b028b90c5c01ace6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8587-cbe02a953a7f4990.js +1 -0
- package/apps/pwa/out/_next/static/chunks/877-2b977259c4b15655.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9002.e5658224e3dc1b6f.js +24 -0
- package/apps/pwa/out/_next/static/chunks/9019-0a4b40c50c7314fb.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{1516.dfaceff3fd2d0e0d.js → 9138.b6e80031afb1d973.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/9188-d36e4fc869ddc166.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{9907.49be0fcadaf214b5.js → 9221.7d22eaf353a2d4e7.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/9230.44f5039e796e4ab4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{8767-2f6a634f5abeaa01.js → 9279-d28c5b7754d010ae.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/939.336acce73e98482c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{3160-95c96f4d2a21dd26.js → 9398-6aed28f0a96d0d6f.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/9407.e49124dedf0f5e72.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9610.c07cce4bfcc14ed7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9667-ce78c2ce91050ff9.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{328bb3ab-6628a1114fe3fb3b.js → 997ce8a5-2ad501927003b41b.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{6853.cc5174a9dee59593.js → 9989.3694300e5f000b14.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/_not-found/page-339fabb3499da0e1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-bf3634a117322df7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/autopilot/page-3535a4a69f1fafaa.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/board/page-b9f51447f757fbf2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/botlab/{page-a5635eba0d73b84e.js → page-2ebb8fd59b9461f9.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/bots/page-a2fc4e49a4fa4ba8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ceo/page-5a503541353e60a8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/chat/page-6025101384e361a5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/consult/page-f4b630cc8b5273cd.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/control/page-a645f00a06483e9a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/decisions/page-61ac22cbab1ee4ec.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-e9bab7413b028b28.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/editor/page-c992eafe028b642e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/exec/page-e547e6ce55f1fb33.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/field/page-d27e1b9850fd54bf.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/healthz/route-339fabb3499da0e1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/inside/page-805ac2e137b7a121.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/intake/page-0fec29c7ba01894b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/layout-339a07410cd6f36b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/live/page-349f2752d8cd37ad.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/live-run/page-d5c6dcf31c25cecc.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/loops/page-2fd93ed8c9f16580.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/market/page-9b564dbff0fefc35.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-62f4475d3c38fb9b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/morning/page-814ba51385225776.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/not-found-397d479626e5a459.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-af1bba4e3251b728.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-3ad3be5e7d3fdce9.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-f219ccdbc0bf27df.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-4da93ad35d4a8d28.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/outputs/page-456666766e5ce6fc.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/page-7404e377e8adb8c5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-14456240ed2a36ad.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-3a658d5fa6d16a25.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-0157de6e85f8bb6b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-0a07363b130182a1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/page-841d73d4ac44ed49.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-6f43a3d1146e6377.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/layout-339fabb3499da0e1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/{page-29900ed0011b2c83.js → page-521ee23ac8c0abfa.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/share/page-a9992ee285efaa83.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-3d48e85fa89ec04f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-d2ced71132fac9b2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/term/page-f08358bdf251adb9.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/today/page-233c71cd5f68f0d7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/trace/page-70151d78de2f375e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/vault/page-dcac33a3e2cb72c0.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/{page-9f92737b8766009a.js → page-149dc1caba411bb9.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-0448145ccd5c45c1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-9cb0c444903da65d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-17e504c396e0e389.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{425cfc4d.fb91158e3971c39f.js → b313b242.cb1265eea908db61.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/e5012ffe.f727e23c231f4342.js +122 -0
- package/apps/pwa/out/_next/static/chunks/ea695e3c-1f1d70d740cf3031.js +1 -0
- package/apps/pwa/out/_next/static/chunks/ebc7d829.264a0bcb6c750b50.js +1 -0
- package/apps/pwa/out/_next/static/chunks/framework-906af9bd029e166c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/main-a24847f9f684d469.js +1 -0
- package/apps/pwa/out/_next/static/chunks/main-app-7bdcd8aa7c8397d3.js +1 -0
- package/apps/pwa/out/_next/static/chunks/pages/_app-6ba46a5066fc226d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/pages/_error-a8dc6d07591f2a25.js +1 -0
- package/apps/pwa/out/_next/static/chunks/webpack-f13df28c15c55eee.js +1 -0
- package/apps/pwa/out/_next/static/css/{72becfd42807b50d.css → 3f5fc6f96e2cf7b2.css} +1 -1
- 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 +23 -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 +23 -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/bin/elanous.mjs +7 -3
- package/graphs/context/nightly-condense.yaml +1 -0
- package/graphs/intake/intake-daily.yaml +8 -1
- package/graphs/intake/recipes.yaml +5 -0
- package/graphs/lecture/lecture-note.yaml +2 -2
- package/graphs/ops/harness-gc.yaml +1 -0
- package/graphs/release/recipes.yaml +3 -0
- package/graphs/release/release-loop.yaml +9 -5
- package/graphs/units/recipes.yaml +3 -0
- package/graphs/units/release-gate-recheck.yaml +15 -0
- package/package.json +2 -1
- package/scripts/check-native-tool-wiring.ts +3 -2
- package/scripts/ci-android-unit-tests.ts +16 -0
- package/scripts/ci-typecheck-changed.ts +37 -9
- package/scripts/coord-channel-watch.sh +22 -16
- package/scripts/coord-role-pack.ts +81 -2
- package/scripts/cron-run.ts +96 -3
- package/scripts/detect-test-interference.ts +38 -5
- package/scripts/gate-pr-cache.ts +65 -0
- package/scripts/gh-surface/capture.ts +126 -0
- package/scripts/gh-surface/gh +40 -0
- package/scripts/import-graph-scc.ts +38 -0
- package/scripts/install.ps1 +19 -0
- package/scripts/intake-social.ts +181 -0
- package/scripts/lecture-note/node.ts +71 -4
- package/scripts/lib/android-toolchain.ts +32 -0
- package/scripts/lib/deterministic-env.ts +62 -0
- package/scripts/lib/gate-test-env.ts +19 -0
- package/scripts/measure-goal-grounding-corpus.ts +2 -1
- package/scripts/ratchets/test-interference-patterns.allowlist.txt +257 -0
- package/scripts/ratchets/test-interference-patterns.ts +367 -0
- package/scripts/release-loop/cut-branch.ts +4 -3
- package/scripts/release-loop/export-check-node.ts +0 -1
- package/scripts/release-loop/fixtures/warm-failure-assertion/sample.junit.xml +8 -0
- package/scripts/release-loop/fixtures/warm-job-029/sample.json +1 -0
- package/scripts/release-loop/fixtures/warm-job-029/sample.junit.xml +10 -0
- package/scripts/release-loop/gate-closure.ts +12 -8
- package/scripts/release-loop/gate-diff.ts +1 -1
- package/scripts/release-loop/gate-host-mirror.ts +66 -0
- package/scripts/release-loop/gate-node.ts +523 -75
- package/scripts/release-loop/gate-partial.ts +106 -9
- package/scripts/release-loop/gate-pr-cache.ts +66 -0
- package/scripts/release-loop/gate-prewarm-plan.ts +66 -0
- package/scripts/release-loop/gate-r2-verdict.ts +189 -0
- package/scripts/release-loop/gate-recheck-plan-node.ts +52 -0
- package/scripts/release-loop/gate-warm-artifacts.ts +253 -0
- package/scripts/release-loop/import-graph-scc-run.ts +54 -0
- package/scripts/release-loop/import-graph-scc.ts +215 -0
- package/scripts/release-loop/node-verdict.ts +2 -0
- package/scripts/release-loop/probe-pod-host-dirs-live.ts +113 -0
- package/scripts/release-loop/resume-release.ts +33 -4
- package/scripts/release-loop/resume.ts +13 -5
- package/scripts/release-loop/spawn-lane.ts +2 -19
- package/scripts/release-loop/tui-sim-node.ts +3 -2
- package/scripts/release-loop/unattended-release.ts +16 -0
- package/scripts/test-deterministic.ts +5 -61
- package/scripts/train-reach-grouping.ts +48 -0
- package/scripts/tsc-incremental-cache.ts +86 -0
- package/scripts/tsc-incremental-diagnostics.ts +76 -0
- package/scripts/tsc-main-safety-net.ts +2 -2
- package/src/acp/client.ts +4 -1
- package/src/acp/core-turn-bridge.ts +50 -5
- package/src/ad-pipeline/run.ts +4 -2
- package/src/agent/runner.ts +169 -3
- package/src/agent/subagent-callable.ts +3 -2
- package/src/agent-mission/review-loop.ts +6 -3
- package/src/agent-substrate/acp-implementer.ts +14 -2
- package/src/agent-substrate/pr-reviewer.ts +12 -0
- package/src/agent-substrate/self-review-cli.ts +30 -4
- package/src/autopilot/pr-manager.ts +62 -7
- package/src/briefing/brief-items.ts +24 -22
- package/src/capture/tui-self-report.ts +15 -0
- package/src/chat/chat-cli.ts +10 -2
- package/src/chat/index.ts +77 -11
- package/src/chat/input-control-key.ts +5 -3
- package/src/chat/tool-interrupt-behavior.ts +38 -0
- package/src/chat/tool-render/dispatch.ts +5 -1
- package/src/chat/turn-typeahead.ts +75 -20
- package/src/cli/__fixtures__/agent-help-before/agent.txt +4 -2
- package/src/cli/agent-cli.ts +41 -13
- package/src/cli/config-audit.ts +123 -0
- package/src/cli/decisions-cli.ts +80 -6
- package/src/cli/log-event-names.ts +7 -0
- package/src/cli/mcp-login.ts +21 -5
- package/src/cli/msg-cli.ts +88 -0
- package/src/cli/pr-cli.ts +214 -57
- package/src/cli/pr-land-heavy-remote.ts +2 -2
- package/src/cli/pr-land-overlap-evidence.ts +63 -9
- package/src/cli/pr-land-pod-shadow-job.ts +83 -0
- package/src/cli/pr-land-pod-shadow.ts +16 -3
- package/src/cli/pr-land-virtual-rebase.ts +108 -0
- package/src/cli/release-cli.ts +80 -14
- package/src/cli/seat-cli.ts +138 -3
- package/src/cli/self-update.ts +5 -2
- package/src/cli/tasks-cli.ts +97 -19
- package/src/context-bus/context-first.ts +13 -0
- package/src/context-bus/context-now.ts +13 -2
- package/src/context-bus/ops-events.ts +75 -0
- package/src/control-plane/lease-holder.ts +12 -8
- package/src/control-plane/ledger.ts +163 -4
- package/src/control-plane/server.ts +91 -11
- package/src/core-turn/run-core-turn.ts +1 -0
- package/src/core-turn/run-goal-loop.ts +222 -3
- package/src/core-turn/seat-turn-cap.ts +79 -0
- package/src/core-turn/short-question.ts +38 -6
- package/src/core-turn/types.ts +3 -0
- package/src/dashboard/acp-boot.ts +3 -0
- package/src/dashboard/index.ts +224 -615
- package/src/dashboard/input/chat-main-entry-runtime.ts +1 -1
- package/src/dashboard/input/chat-main-plain-turn-runtime.ts +4 -1
- package/src/dashboard/input/chat-main-session.ts +8 -1
- package/src/dashboard/input/chat-main-turn.ts +12 -3
- package/src/dashboard/input/compact-prepass-recovery.ts +33 -0
- package/src/dashboard/input/external-input-host.ts +26 -0
- package/src/dashboard/input/internal-turn.ts +261 -0
- package/src/dashboard/input/seat-autowake.ts +318 -0
- package/src/dashboard/input/turn-steer-bridge.ts +70 -0
- package/src/dashboard/loop-scheduler.ts +72 -0
- package/src/dashboard/seat-boot-preamble.ts +18 -0
- package/src/dashboard/skill-runtime.ts +2 -0
- package/src/dashboard/slash-runtime/dashboard-handlers.ts +126 -1181
- package/src/dashboard/slash-runtime/help-from-registry.ts +2 -1
- package/src/dashboard/slash-runtime/index.ts +13 -0
- package/src/dashboard/turn-busy-teardown.ts +50 -0
- package/src/decisions/decision-ledger.ts +45 -4
- package/src/discord-self-message.ts +4 -4
- package/src/domains/release-tool.ts +18 -1
- package/src/field-note/transcribe.ts +17 -0
- package/src/github/remote-call.ts +81 -0
- package/src/goals/registry.ts +46 -6
- package/src/goals/types.ts +7 -1
- package/src/graph-runner/runner.ts +1 -0
- package/src/grok/credential.ts +26 -0
- package/src/harness/harness-clean.ts +2 -1
- package/src/harness/harness-cli-command.ts +52 -5
- package/src/harness/harness-gc.ts +2 -1
- package/src/harness/harness-incidents.ts +57 -20
- package/src/harness/harness-pod-dispatch.ts +87 -10
- package/src/harness/harness-queue.ts +53 -4
- package/src/harness/harness-stop.ts +4 -3
- package/src/harness/harness-substrate-default.ts +78 -7
- package/src/harness/harness-worktree-auto.ts +2 -1
- package/src/harness/harness-worktrees.ts +3 -2
- package/src/hooks/dispatch.ts +2 -1
- package/src/index.ts +152 -25
- package/src/input/external-input-queue.ts +33 -1
- package/src/intake-plane/slash.ts +14 -3
- package/src/keybindings.ts +4 -1
- package/src/llm/tool-loop-steered-rejection.ts +33 -0
- package/src/llm.ts +138 -19
- package/src/loops/consensus.ts +348 -0
- package/src/loops/msg-wake.ts +206 -4
- package/src/loops/observe.ts +19 -3
- package/src/loops/orchestrator/tick.ts +64 -10
- package/src/loops/orchestrator/traffic.ts +64 -1
- package/src/loops/registry.ts +30 -0
- package/src/market/signed-index.ts +14 -0
- package/src/mcp/mcp-oauth.ts +54 -5
- package/src/models/prompts.ts +28 -0
- package/src/msg/msg-sse-client.ts +223 -0
- package/src/msg/msg-store.ts +86 -2
- package/src/mss/logging/log-store.ts +225 -1
- package/src/nexus/DAEMON-THIN-inventory-2026-10-15.md +122 -0
- package/src/nexus/api/http-server.ts +54 -15
- package/src/nexus/api/loop-resources.ts +19 -3
- package/src/nexus/api/ops-api.ts +11 -0
- package/src/nexus/daemon-thin-inventory.md +37 -0
- package/src/nexus/index.ts +70 -0
- package/src/nexus/state/state.ts +1 -0
- package/src/oauth/store.ts +32 -7
- package/src/onboarding/first-run.ts +28 -3
- package/src/ops/overview-cache.ts +16 -4
- package/src/ops/resources.ts +82 -0
- package/src/plugins/maker/connector-maker.ts +41 -0
- package/src/plugins/maker/skill-maker.ts +42 -0
- package/src/pr-tool/census.ts +288 -0
- package/src/prompt-library/chat-conciseness.ts +13 -0
- package/src/prompt-library/universal-preamble.ts +10 -5
- package/src/release-loop/gate-host-reservation.ts +97 -0
- package/src/release-loop/landing-freeze.ts +5 -0
- package/src/release-loop/light-rc.ts +5 -2
- package/src/release-loop/placement.ts +92 -1
- package/src/release-loop/rehearsal-shard-plan.ts +149 -0
- package/src/release-loop/rehearsal.ts +73 -17
- package/src/repl/index.ts +20 -2
- package/src/seat-loop/seat-knowhow.ts +282 -0
- package/src/seat-loop/seat-loop.ts +41 -2
- package/src/seat-loop/traffic-signal.ts +3 -0
- package/src/self-dev/ask-launch-flow.ts +24 -3
- package/src/self-dev/decompose-compose-graph.ts +28 -27
- package/src/self-dev/decompose-proposal.ts +53 -38
- package/src/self-dev/decompose.ts +17 -0
- package/src/self-dev/dev-pipeline.ts +31 -2
- package/src/self-dev/draft-residue.ts +24 -9
- package/src/self-dev/draft-sweep.ts +59 -4
- package/src/self-dev/external-call-zero.ts +77 -0
- package/src/self-dev/launch-preflight-text.ts +6 -0
- package/src/self-dev/launch-preflight.ts +25 -3
- package/src/self-dev/local-supervisor-judge.ts +185 -0
- package/src/self-dev/run-supervisor.ts +229 -19
- package/src/self-dev/run-types.ts +20 -0
- package/src/self-dev/shard-aggregate.ts +13 -2
- package/src/self-dev/shard-plan.ts +47 -0
- package/src/self-dev/shard-run.ts +112 -10
- package/src/self-dev/spawn-lane-files.ts +19 -0
- package/src/self-implement/app-api-budget.ts +51 -11
- package/src/self-implement/config-gate.ts +2 -1
- package/src/self-implement/expose-gate.ts +2 -1
- package/src/self-implement/gate-artifacts.ts +405 -0
- package/src/self-implement/gate-cli.ts +201 -16
- package/src/self-implement/gate-remote.ts +110 -25
- package/src/self-implement/gate-scenario.ts +85 -0
- package/src/self-implement/goal-author.ts +160 -8
- package/src/self-implement/graph-authority.ts +38 -6
- package/src/self-implement/graph-overlay-condition.ts +92 -1
- package/src/self-implement/heal-triage.ts +79 -0
- package/src/self-implement/host-regate.ts +61 -3
- package/src/self-implement/orchestrator.ts +14 -123
- package/src/self-implement/pr-title.ts +123 -0
- package/src/self-implement/run-checkpoint.ts +235 -0
- package/src/self-implement/run-ledger.ts +155 -10
- package/src/self-implement/run-stop.ts +4 -3
- package/src/self-implement/run-ttl.ts +28 -4
- package/src/self-implement/seams.ts +2 -0
- package/src/self-implement/shadow-stage.ts +7 -6
- package/src/self-implement/stop-autoheal.ts +2 -1
- package/src/self-implement/train-gate-remote.ts +37 -8
- package/src/self-implement/train-reach-grouping.ts +157 -0
- package/src/self-implement/train-scenario-layers.ts +133 -0
- package/src/self-implement/uncommitted-work.ts +2 -0
- package/src/session/pending-input.ts +19 -0
- package/src/session/session-start.ts +224 -0
- package/src/session-runtime/parallel-dispatch.ts +22 -5
- package/src/skills/tools/dev-harness.ts +6 -4
- package/src/skills/tools/shell.ts +1 -1
- package/src/steward/resync-tick.ts +114 -23
- package/src/steward/ta-repair-event-collect.ts +158 -0
- package/src/task-agent/card-evidence.ts +25 -1
- package/src/task-agent/green-sweep.ts +8 -3
- package/src/task-agent/judge.ts +76 -0
- package/src/task-agent/land-queue.ts +79 -0
- package/src/task-agent/live-moves.ts +30 -9
- package/src/task-agent/postland-card.ts +5 -0
- package/src/task-agent/prelaunch-already-done.ts +98 -0
- package/src/task-agent/real-launch-guard.ts +37 -0
- package/src/task-agent/rejudge-on-head.ts +89 -12
- package/src/task-agent/remote-rejudge-bind.ts +28 -8
- package/src/task-agent/repair-cycle.ts +45 -7
- package/src/task-agent/repair-worker.ts +7 -0
- package/src/task-agent/run-identity.ts +200 -0
- package/src/task-agent/shadow.ts +50 -3
- package/src/task-agent/task-hand.ts +158 -5
- package/src/task-agent/train-landing.ts +77 -0
- package/src/task-agent/work-order.ts +104 -0
- package/src/task-orchestrator/surfaces/pod-command-job.ts +88 -28
- package/src/task-orchestrator/surfaces/pod-failure-reason.ts +88 -19
- package/src/task-orchestrator/surfaces/pod-host-acp-review.ts +2 -1
- package/src/task-orchestrator/surfaces/pod-image-source.ts +2 -1
- package/src/task-orchestrator/surfaces/pod-install-slots.ts +121 -9
- package/src/task-orchestrator/surfaces/pod-lease.ts +20 -6
- package/src/task-orchestrator/surfaces/pod-lifecycle-stages.ts +169 -0
- package/src/task-orchestrator/surfaces/pod-mirror-seed.ts +27 -0
- package/src/task-orchestrator/surfaces/pod-result-marker-wait.ts +33 -0
- package/src/task-orchestrator/surfaces/pod-reuse-cleanup.ts +63 -0
- package/src/task-orchestrator/surfaces/pod-source-receive.ts +19 -3
- package/src/task-orchestrator/surfaces/pod-warm-switch.ts +141 -0
- package/src/task-orchestrator/surfaces/pod-warm-ta-shadow.ts +85 -0
- package/src/task-orchestrator/surfaces/pod-warm-worker-protocol.ts +153 -0
- package/src/task-orchestrator/surfaces/pod-warm-worker-runtime.ts +208 -0
- package/src/task-orchestrator/surfaces/self-implement-pod.ts +132 -15
- package/src/telegram.ts +2 -4
- package/src/tui/input/pickers.ts +732 -0
- package/src/tui/slash/mission.ts +492 -0
- package/src/tui/slash/term.ts +877 -0
- package/src/tui-client/dashboard-session.ts +9 -0
- package/src/tui.ts +48 -0
- package/src/user-config/gemini-server-tools.ts +9 -0
- package/src/user-config.ts +53 -16
- package/src/util/spawn-sync-output.ts +7 -0
- package/src/ux-sim/fake-bin.ts +154 -0
- package/src/ux-sim/fake-clock.ts +36 -0
- package/src/ux-sim/headless-tui.ts +72 -0
- package/src/ux-sim/in-process-cli.ts +2 -1
- package/src/ux-sim/temp-git-remote.ts +82 -0
- package/src/version/packed-revision.json +1 -1
- package/src/video-pipeline/recipes/vlog.ts +25 -7
- package/apps/pwa/out/_next/static/chunks/1015-9cba22a5a4554797.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1084-4e66af7376a18db7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1106.3a6410b7b0c70ad4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1127.32fc642f70423506.js +0 -8
- package/apps/pwa/out/_next/static/chunks/1189-d9b8e1995a831fae.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1224.6da98c7ad0eb7dd8.js +0 -34
- package/apps/pwa/out/_next/static/chunks/1370.4e3e2558f111fd5d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1514-b7da377fdc4eb17a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1577.3c24c32584e10d70.js +0 -286
- package/apps/pwa/out/_next/static/chunks/161.130a569855c4d68c.js +0 -24
- package/apps/pwa/out/_next/static/chunks/1864.16da9556e78a9236.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1b6f6b2a.aff090122be3726e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2341.e8a9135e5b910dd5.js +0 -36
- package/apps/pwa/out/_next/static/chunks/257.10afdbe3520f2fc2.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2658.87550e692dc493f6.js +0 -64
- package/apps/pwa/out/_next/static/chunks/2673.85e2f02efae7a85f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2766-bee623b37472ed63.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2794.6d836872b0bf43e2.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2806.eb57ced1a528ed39.js +0 -1
- package/apps/pwa/out/_next/static/chunks/283.7a121c4910e6dad7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2870.1dc29aa5fc01f09f.js +0 -115
- package/apps/pwa/out/_next/static/chunks/293.fd38e77ce5d48103.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2993-5952a1cd3fd5d441.js +0 -1
- package/apps/pwa/out/_next/static/chunks/32-a523427c5bd5ed00.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3280.af38002a6424a905.js +0 -90
- package/apps/pwa/out/_next/static/chunks/3292-5ddffdb2f517b44f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3523.d3231daaf7aba993.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3763.577de3181e1e6ea0.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4138-cc56b0938ef72783.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4255-8350c43d307a8875.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4352.a4954693499c2714.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4421-f2dcbc6fd80ec013.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4513-51b94aeee37c9905.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4928-c3b72f99aa588699.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4986-f9fc0283fb325219.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5014.671a7b9f69c2b7b1.js +0 -94
- package/apps/pwa/out/_next/static/chunks/5137.d398e24710b3feb6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5298.0bea74885cabe754.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5315.74c53a39739b11c4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5317-8b1df8d02105cd07.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5358-33051f41a2b239af.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5388.a2ec02501f714ad5.js +0 -62
- package/apps/pwa/out/_next/static/chunks/5395.e6ac948a2d15058d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5409-f282bf533c151849.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5466.d0f4aba9d52acfa8.js +0 -27
- package/apps/pwa/out/_next/static/chunks/5564.3d28b353470fe305.js +0 -104
- package/apps/pwa/out/_next/static/chunks/569.b0cbfb4dee4a167a.js +0 -93
- package/apps/pwa/out/_next/static/chunks/5835-72465b73a8bb8d74.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5941.045d6093139e1f7a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6228.47cabb83564dfe54.js +0 -24
- package/apps/pwa/out/_next/static/chunks/6457-8d1993002a87d0fe.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6513.ef54cfae52351982.js +0 -2
- package/apps/pwa/out/_next/static/chunks/6582.13d245bf87df69f1.js +0 -148
- package/apps/pwa/out/_next/static/chunks/6655.c33f6acc2c61bb89.js +0 -149
- package/apps/pwa/out/_next/static/chunks/6731-9d7195af289c8d4c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6733.0be05b1db9279fc5.js +0 -79
- package/apps/pwa/out/_next/static/chunks/6742-90937146253a8b9e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/687-816858f77a396a4e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6888.72c7b8cfb02a7e61.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6993.c17c828ac3ef3253.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7076.9f27ef666b0cefdd.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7113.1403c5d451804e8c.js +0 -287
- package/apps/pwa/out/_next/static/chunks/7169.9dd603c726c2cd7c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7247-e3ec21f761f86710.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7261-a47b72f96f5199df.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7335-5cda81ac90310077.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7356-c2e4af090546e2d4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/738-59518742078fda71.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7385.a7375f10db73243a.js +0 -77
- package/apps/pwa/out/_next/static/chunks/7445.cf1cf2fe109bcd96.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7505.a2057432ab6ef99a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7515.31f45fac1f9c70cb.js +0 -74
- package/apps/pwa/out/_next/static/chunks/7658-d4ce8c72a9548a91.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8107-0b1ae46bc5bcb788.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8132.bfea2f016ce8d992.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8672.e299832bc7d18ee2.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8735.f1ef798a5f260f6f.js +0 -226
- package/apps/pwa/out/_next/static/chunks/8774-88f67f7d8d53d299.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8782.252e5a405d86e4ed.js +0 -32
- package/apps/pwa/out/_next/static/chunks/909-9ed7aa0dba804316.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9100.22b0301256827007.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9140-dde77575570bc71e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9292-a7ad9a01c7044b93.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9517.00627dc51b02821c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9725.dcbb585652d8e9c5.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9761.a3ebf5c389c24ddc.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9972.834d2b2b7d48f1ed.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9995-1a90486f5ae01546.js +0 -1
- package/apps/pwa/out/_next/static/chunks/a435ad95.577add051b4facb2.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/_not-found/page-83372d8b5863ebf7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-d69256d7613c140a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/autopilot/page-352c5e9ba3f9b858.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/board/page-69f1d725524201a8.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/bots/page-ad9181d84285e4ea.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ceo/page-86feb08080261a2f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/chat/page-ffa15987e4756d03.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/consult/page-11a3ab44ac327cc1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/control/page-6309b11c4264d24d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/decisions/page-8800472079612e87.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-d387bf3f2301882a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/editor/page-048f88d8c8658da6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/exec/page-6c14f9739b7f6880.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/field/page-2834f44f64e98d74.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/healthz/route-83372d8b5863ebf7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/inside/page-a13df6f88e824887.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/intake/page-8a4397248754cf2b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/layout-daf0bd329360a336.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/live/page-f7ef1c28764cb476.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/live-run/page-61e74f28b9353dac.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/loops/page-8aaa6dedc4c10dfb.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/market/page-294602ba649be829.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-5fa5f41ed3fce323.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/morning/page-33bf667a5fb297bf.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/not-found-5ae3cbd6e5dba515.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-eb867d16ad6fdede.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-1ab789fa68682f7e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-6fd41b5b6ff13a71.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-fb3d5f7642c99fba.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/outputs/page-70f705b2b5ed42ca.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/page-c9c93b358a7667b5.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-1563c556b81c96ed.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-ce89c20c45b3a673.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-423ae63d786f4ee9.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-591a5378a4c4447c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/page-a250496b19a5824b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-c36a9277febc5911.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/layout-83372d8b5863ebf7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/share/page-a5de57edc6d7f942.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-16bdd4c9f40498df.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-75e946e6e71cc6d8.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/term/page-9eb56166774eb256.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/today/page-1c5f9ef35dbb22c5.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/trace/page-657ff9985e508498.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/vault/page-22dc72499da68fb2.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-094ad334e40f8169.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-8bcd379852ff6287.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-6b04413f280cd344.js +0 -1
- package/apps/pwa/out/_next/static/chunks/de70ab42-d28d47437208d458.js +0 -1
- package/apps/pwa/out/_next/static/chunks/f800c5e3.46dbd7f2af8c3fce.js +0 -1
- package/apps/pwa/out/_next/static/chunks/f8bc19ad.47579f6d684b0760.js +0 -122
- package/apps/pwa/out/_next/static/chunks/framework-ca9ac4609fb9fed8.js +0 -1
- package/apps/pwa/out/_next/static/chunks/main-app-0dd08d32d15a030f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/main-f0399d33955c4e01.js +0 -1
- package/apps/pwa/out/_next/static/chunks/pages/_app-d42caca6f8ac2211.js +0 -1
- package/apps/pwa/out/_next/static/chunks/pages/_error-42e713c13aa41ca8.js +0 -1
- package/apps/pwa/out/_next/static/chunks/webpack-8fbf8f61a377aa16.js +0 -1
- /package/apps/pwa/out/_next/static/{cIpv5nU9AycwYWZI9H589 → LSXfY0u9QtfDbejY0Ukf_}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
// ── 우편 SSE 구독 클라이언트 — 받는 몸이 «듣는» 쪽 ──
|
|
2
|
+
//
|
|
3
|
+
// 서버(`msg-sse-server.ts` · `eln msg serve --sse`)는 원장을 빨리 밀어 줄 뿐이고 원장(SQLite)이 정본이다.
|
|
4
|
+
// 이 클라이언트는 한 주소를 구독해 글마다 `onMessage` 를 부른다.
|
|
5
|
+
// · 커서(Last-Event-ID)는 주소별 state-dir 파일에 보존하고 «onMessage 가 성공한 뒤에만» 전진한다
|
|
6
|
+
// — 실패하면 같은 글이 다시 온다(at-least-once · 중복은 소비자가 provenance 키로 거른다).
|
|
7
|
+
// · SSE 가 끊기면 지수 백오프(상한)로 다시 붙고, 기다리는 동안은 원장을 직접 폴링해 빠짐을 막는다.
|
|
8
|
+
// · 우체국 주소는 인자(`url`)로만 받는다(env·config·하드코딩 0) — 없으면 원장 폴링이 운영 기본이다.
|
|
9
|
+
// 서버가 없거나 토큰이 거절되어도 같은 길로 원장 폴링에 내려앉는다 — `fallback-poll` 로그.
|
|
10
|
+
// · stop() 뒤에는 콜백 0.
|
|
11
|
+
// 관측: debug.log('msg.sse-client', connect|reconnect|replay|fallback-poll|deliver-failed|stop, …) — 원장을 직접 읽는 구간마다 fallback-poll
|
|
12
|
+
|
|
13
|
+
import { randomUUID } from 'node:crypto';
|
|
14
|
+
import { mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
|
|
15
|
+
import { dirname, join } from 'node:path';
|
|
16
|
+
|
|
17
|
+
import { ensureAuthToken } from '../auth/acp-token.js';
|
|
18
|
+
import { elanousStateRoot } from '../autopilot/state-paths.js';
|
|
19
|
+
import { debug } from '../debug/log.js';
|
|
20
|
+
import { canonicalSeatId, openMsgStoreReadOnly, type Message, type MsgStore } from './msg-store.js';
|
|
21
|
+
|
|
22
|
+
export interface MsgStreamOptions {
|
|
23
|
+
address: string;
|
|
24
|
+
/** Resolve to advance the cursor; reject to receive the same message again. */
|
|
25
|
+
onMessage: (message: Message) => void | Promise<void>;
|
|
26
|
+
signal?: AbortSignal;
|
|
27
|
+
/** Default `<state-dir>/msg/sse-cursor/<ADDRESS>.json`. */
|
|
28
|
+
cursorPath?: string;
|
|
29
|
+
/**
|
|
30
|
+
* Post office base URL — only by argument (OP #3237: no env, config key or hard-coded port on the
|
|
31
|
+
* operating path). Absent = poll the ledger, which alone still delivers (wakes) every message.
|
|
32
|
+
* Discovery through the control resource ledger is a follow-up goal (ASK-MSG-SSE-LEDGER-DISCOVERY).
|
|
33
|
+
*/
|
|
34
|
+
url?: string;
|
|
35
|
+
token?: () => string;
|
|
36
|
+
/** Ledger reader for the polling fallback (default: read-only ledger in this state-dir). */
|
|
37
|
+
openStore?: () => MsgStore;
|
|
38
|
+
fetch?: typeof fetch;
|
|
39
|
+
/** Reconnect backoff: doubles from `initialMs` to `maxMs`, reset after a successful connect. */
|
|
40
|
+
backoff?: { initialMs?: number; maxMs?: number };
|
|
41
|
+
pollMs?: number;
|
|
42
|
+
/** Reconnect when the stream is silent this long (the server sends a keepalive every second). */
|
|
43
|
+
idleTimeoutMs?: number;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface MsgStreamHandle {
|
|
47
|
+
/** Stop delivering; resolves once the loop has exited. No callback starts after stop() is called. */
|
|
48
|
+
stop: () => Promise<void>;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const LOG = 'msg.sse-client';
|
|
52
|
+
|
|
53
|
+
function defaultCursorPath(address: string): string {
|
|
54
|
+
return join(elanousStateRoot(), 'msg', 'sse-cursor', `${address}.json`);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** The saved cursor for `address` — a file written for another address is ignored (start from 0), never borrowed. */
|
|
58
|
+
function readCursor(path: string, address: string): number | undefined {
|
|
59
|
+
let saved: { address?: unknown; lastEventId?: unknown };
|
|
60
|
+
try { saved = JSON.parse(readFileSync(path, 'utf8')) as typeof saved; } catch { return undefined; }
|
|
61
|
+
if (saved.address !== address) {
|
|
62
|
+
debug.log(LOG, 'cursor-address-mismatch', { address, fileAddress: typeof saved.address === 'string' ? saved.address : null, path });
|
|
63
|
+
return undefined;
|
|
64
|
+
}
|
|
65
|
+
const value = saved.lastEventId;
|
|
66
|
+
return typeof value === 'number' && Number.isSafeInteger(value) && value >= 0 ? value : undefined;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function writeCursor(path: string, address: string, lastEventId: number): void {
|
|
70
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
71
|
+
const tmp = `${path}.${process.pid}.${randomUUID()}.tmp`;
|
|
72
|
+
writeFileSync(tmp, JSON.stringify({ address, lastEventId, at: new Date().toISOString() }));
|
|
73
|
+
renameSync(tmp, path);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
class StopError extends Error {}
|
|
77
|
+
|
|
78
|
+
export function subscribeMsgStream(options: MsgStreamOptions): MsgStreamHandle {
|
|
79
|
+
const address = canonicalSeatId(options.address);
|
|
80
|
+
if (address === 'ALL') throw new Error('ALL is a topic, not a recipient');
|
|
81
|
+
const cursorPath = options.cursorPath ?? defaultCursorPath(address);
|
|
82
|
+
const url = options.url;
|
|
83
|
+
const doFetch = options.fetch ?? fetch;
|
|
84
|
+
const initialBackoff = options.backoff?.initialMs ?? 500;
|
|
85
|
+
const maxBackoff = options.backoff?.maxMs ?? 30_000;
|
|
86
|
+
const pollMs = options.pollMs ?? 1000;
|
|
87
|
+
const idleTimeoutMs = options.idleTimeoutMs ?? 10_000;
|
|
88
|
+
const controller = new AbortController();
|
|
89
|
+
const onOuterAbort = () => controller.abort();
|
|
90
|
+
options.signal?.addEventListener('abort', onOuterAbort, { once: true });
|
|
91
|
+
if (options.signal?.aborted) controller.abort();
|
|
92
|
+
const stopped = () => controller.signal.aborted;
|
|
93
|
+
let cursor = readCursor(cursorPath, address) ?? 0;
|
|
94
|
+
|
|
95
|
+
const sleep = (ms: number) => new Promise<void>(resolve => {
|
|
96
|
+
if (stopped()) { resolve(); return; }
|
|
97
|
+
const timer = setTimeout(done, ms);
|
|
98
|
+
function done() { clearTimeout(timer); controller.signal.removeEventListener('abort', done); resolve(); }
|
|
99
|
+
controller.signal.addEventListener('abort', done, { once: true });
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
/** Deliver in order; the cursor moves only after onMessage resolves. Returns false when a delivery failed. */
|
|
103
|
+
const deliver = async (message: Message, via: 'sse' | 'poll'): Promise<boolean> => {
|
|
104
|
+
if (message.id <= cursor) return true;
|
|
105
|
+
if (stopped()) throw new StopError();
|
|
106
|
+
try { await options.onMessage(message); }
|
|
107
|
+
catch (error) {
|
|
108
|
+
debug.log(LOG, 'deliver-failed', { address, msgId: message.id, via, cursor, error: error instanceof Error ? error.message : String(error) });
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
cursor = message.id;
|
|
112
|
+
try { writeCursor(cursorPath, address, cursor); }
|
|
113
|
+
catch (error) { debug.log(LOG, 'cursor-write-failed', { address, cursor, error: error instanceof Error ? error.message : String(error) }); }
|
|
114
|
+
return true;
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
/** Drain the ledger directly — the outage path. Stops at the first failed delivery. */
|
|
118
|
+
const pollOnce = async (): Promise<void> => {
|
|
119
|
+
let store: MsgStore;
|
|
120
|
+
try { store = (options.openStore ?? (() => openMsgStoreReadOnly()))(); }
|
|
121
|
+
catch (error) { debug.log(LOG, 'poll-unavailable', { address, error: error instanceof Error ? error.message : String(error) }); return; }
|
|
122
|
+
try {
|
|
123
|
+
for (;;) {
|
|
124
|
+
const batch = store.listByRecipient(address, cursor, 100);
|
|
125
|
+
for (const message of batch) if (!await deliver(message, 'poll')) return;
|
|
126
|
+
if (batch.length < 100) return;
|
|
127
|
+
}
|
|
128
|
+
} finally { store.close(); }
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
/** One SSE session; resolves when it ends (server gone, idle, delivery failure). Throws for 401. */
|
|
132
|
+
const streamOnce = async (base: string, attempt: number): Promise<{ connected: boolean }> => {
|
|
133
|
+
const target = new URL('/stream', base);
|
|
134
|
+
target.searchParams.set('to', address);
|
|
135
|
+
target.searchParams.set('since', String(cursor));
|
|
136
|
+
const session = new AbortController();
|
|
137
|
+
const onStop = () => session.abort();
|
|
138
|
+
controller.signal.addEventListener('abort', onStop, { once: true });
|
|
139
|
+
try {
|
|
140
|
+
const token = (options.token ?? (() => ensureAuthToken().token))();
|
|
141
|
+
// 재접속은 «실제로 다시 요청하는 순간»에 남긴다 — 토큰을 못 얻어 요청이 없으면 기록도 없다(첫 연결은 connect 만).
|
|
142
|
+
if (attempt > 0) debug.log(LOG, 'reconnect', { address, attempts: attempt, cursor });
|
|
143
|
+
// 헤더 대기에도 같은 무음 제한 — 연결만 받고 응답을 안 주는 서버에 매달려 폴링이 멈추지 않게.
|
|
144
|
+
let connectTimer: ReturnType<typeof setTimeout> | undefined;
|
|
145
|
+
const response = await Promise.race([
|
|
146
|
+
doFetch(target, { headers: { Authorization: `Bearer ${token}`, 'Last-Event-ID': String(cursor) }, signal: session.signal }),
|
|
147
|
+
new Promise<never>((_, reject) => { connectTimer = setTimeout(() => { session.abort(); reject(new Error('SSE connect timeout')); }, idleTimeoutMs); }),
|
|
148
|
+
]).finally(() => clearTimeout(connectTimer));
|
|
149
|
+
if (response.status === 401) throw Object.assign(new Error('SSE unauthorized (401)'), { unauthorized: true });
|
|
150
|
+
if (!response.ok || !response.body) throw new Error(`SSE unavailable (${response.status})`);
|
|
151
|
+
debug.log(LOG, 'connect', { address, url: target.origin, lastEventId: cursor });
|
|
152
|
+
if (cursor > 0) debug.log(LOG, 'replay', { address, from: cursor });
|
|
153
|
+
const reader = response.body.getReader();
|
|
154
|
+
const decoder = new TextDecoder();
|
|
155
|
+
let pending = '';
|
|
156
|
+
while (!stopped()) {
|
|
157
|
+
let idle: ReturnType<typeof setTimeout> | undefined;
|
|
158
|
+
const chunk = await Promise.race([
|
|
159
|
+
reader.read(),
|
|
160
|
+
new Promise<never>((_, reject) => { idle = setTimeout(() => reject(new Error('SSE idle timeout')), idleTimeoutMs); }),
|
|
161
|
+
]).finally(() => clearTimeout(idle));
|
|
162
|
+
if (chunk.done) break;
|
|
163
|
+
pending = (pending + decoder.decode(chunk.value, { stream: true })).replace(/\r\n/g, '\n');
|
|
164
|
+
if (pending.length > 1_000_000) throw new Error('SSE event exceeds limit');
|
|
165
|
+
let boundary: number;
|
|
166
|
+
while ((boundary = pending.indexOf('\n\n')) >= 0) {
|
|
167
|
+
const frame = pending.slice(0, boundary);
|
|
168
|
+
pending = pending.slice(boundary + 2);
|
|
169
|
+
const data = frame.split('\n').filter(line => line.startsWith('data: ')).map(line => line.slice(6)).join('\n');
|
|
170
|
+
if (!data) continue;
|
|
171
|
+
// A failed delivery drops this session; the reconnect replays from the unchanged cursor.
|
|
172
|
+
if (!await deliver(JSON.parse(data) as Message, 'sse')) return { connected: true };
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
return { connected: true };
|
|
176
|
+
} finally {
|
|
177
|
+
controller.signal.removeEventListener('abort', onStop);
|
|
178
|
+
session.abort();
|
|
179
|
+
}
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
/** Poll the ledger until `until` — one `fallback-poll` per interval. */
|
|
183
|
+
const pollInterval = async (reason: string, until: number, attempts: number): Promise<void> => {
|
|
184
|
+
debug.log(LOG, 'fallback-poll', { address, reason, cursor, attempts, ...(Number.isFinite(until) ? { untilMs: until - Date.now() } : {}) });
|
|
185
|
+
do {
|
|
186
|
+
try { await pollOnce(); }
|
|
187
|
+
catch (error) { if (error instanceof StopError) return; debug.log(LOG, 'poll-failed', { address, error: error instanceof Error ? error.message : String(error) }); }
|
|
188
|
+
await sleep(Math.min(pollMs, Math.max(0, until - Date.now())));
|
|
189
|
+
} while (!stopped() && Date.now() < until);
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
const loop = (async () => {
|
|
193
|
+
// 주소가 없으면 처음부터 원장 폴링 한 구간(끝 없음) — 운영 기본.
|
|
194
|
+
if (!url) { await pollInterval('no-url', Number.POSITIVE_INFINITY, 0); return; }
|
|
195
|
+
let backoff = initialBackoff;
|
|
196
|
+
let attempts = 0;
|
|
197
|
+
while (!stopped()) {
|
|
198
|
+
let reason: string;
|
|
199
|
+
try {
|
|
200
|
+
const { connected } = await streamOnce(url, attempts);
|
|
201
|
+
if (connected) backoff = initialBackoff;
|
|
202
|
+
reason = 'stream-ended';
|
|
203
|
+
} catch (error) {
|
|
204
|
+
if (error instanceof StopError || stopped()) break;
|
|
205
|
+
reason = (error as { unauthorized?: boolean }).unauthorized ? 'unauthorized' : error instanceof Error ? error.message : String(error);
|
|
206
|
+
}
|
|
207
|
+
if (stopped()) break;
|
|
208
|
+
attempts++;
|
|
209
|
+
// 끊긴 동안(서버 없음·토큰 거절·무응답·재접속 대기)은 원장을 직접 폴링한다.
|
|
210
|
+
await pollInterval(reason, Date.now() + backoff, attempts);
|
|
211
|
+
backoff = Math.min(maxBackoff, backoff * 2);
|
|
212
|
+
}
|
|
213
|
+
})();
|
|
214
|
+
|
|
215
|
+
return {
|
|
216
|
+
stop: async () => {
|
|
217
|
+
if (!stopped()) debug.log(LOG, 'stop', { address, cursor });
|
|
218
|
+
controller.abort();
|
|
219
|
+
options.signal?.removeEventListener('abort', onOuterAbort);
|
|
220
|
+
await loop.catch(() => undefined);
|
|
221
|
+
},
|
|
222
|
+
};
|
|
223
|
+
}
|
package/src/msg/msg-store.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Database } from 'bun:sqlite';
|
|
2
|
+
import { randomUUID } from 'node:crypto';
|
|
2
3
|
import { existsSync, mkdirSync } from 'node:fs';
|
|
3
4
|
import { dirname, join } from 'node:path';
|
|
4
5
|
import { elanousStateRoot } from '../autopilot/state-paths.js';
|
|
@@ -64,6 +65,15 @@ function validCursor(cursor: number): number {
|
|
|
64
65
|
return cursor;
|
|
65
66
|
}
|
|
66
67
|
|
|
68
|
+
/** Unread count ⊕ newest unread ID for one recipient (same predicate as `unreadCountOn`). */
|
|
69
|
+
function unreadSummaryOn(db: Database, to: string): { count: number; maxId: number | null } {
|
|
70
|
+
const row = db.query(`SELECT COUNT(*) AS count, MAX(m.id) AS max_id FROM msg_messages m
|
|
71
|
+
WHERE m.id > COALESCE((SELECT last_id FROM msg_cursors WHERE recipient = ?), 0)
|
|
72
|
+
AND (m.recipient = ? OR (m.recipient = 'ALL' AND m.sender <> ?))`)
|
|
73
|
+
.get(to, to, to) as { count: number; max_id: number | null };
|
|
74
|
+
return { count: row.count, maxId: row.max_id ?? null };
|
|
75
|
+
}
|
|
76
|
+
|
|
67
77
|
function unreadCountOn(db: Database, to: string): number {
|
|
68
78
|
return (db.query(`SELECT COUNT(*) AS count FROM msg_messages m
|
|
69
79
|
WHERE m.id > COALESCE((SELECT last_id FROM msg_cursors WHERE recipient = ?), 0)
|
|
@@ -126,6 +136,14 @@ export class MsgStore {
|
|
|
126
136
|
CREATE TABLE IF NOT EXISTS msg_provenance (
|
|
127
137
|
provenance_key TEXT PRIMARY KEY,
|
|
128
138
|
message_id INTEGER NOT NULL REFERENCES msg_messages(id)
|
|
139
|
+
);
|
|
140
|
+
CREATE TABLE IF NOT EXISTS msg_ta_delivery (
|
|
141
|
+
request_id INTEGER PRIMARY KEY REFERENCES msg_messages(id),
|
|
142
|
+
status TEXT NOT NULL CHECK(status IN ('pending', 'delivered'))
|
|
143
|
+
);
|
|
144
|
+
CREATE TABLE IF NOT EXISTS msg_ta_claim (
|
|
145
|
+
request_id INTEGER PRIMARY KEY REFERENCES msg_messages(id),
|
|
146
|
+
token TEXT NOT NULL
|
|
129
147
|
);`);
|
|
130
148
|
// Serialize the schema check and ALTER across processes opening an old ledger at once.
|
|
131
149
|
if (!options.readOnly) this.db.transaction(() => {
|
|
@@ -140,9 +158,9 @@ export class MsgStore {
|
|
|
140
158
|
return this.insertMessage(input);
|
|
141
159
|
}
|
|
142
160
|
|
|
143
|
-
/** Append once for a coordination comment
|
|
161
|
+
/** Append once for a coordination comment, task failure, or decided notification. */
|
|
144
162
|
appendWithProvenance(input: MessageEnvelope, provenanceKey: string): Message {
|
|
145
|
-
if (typeof provenanceKey !== 'string' || !/^(?:coord|ta-failure):[^:]+:[^:]+$/.test(provenanceKey)) {
|
|
163
|
+
if (typeof provenanceKey !== 'string' || !/^(?:coord|ta-failure|decision):[^:]+:[^:]+$/.test(provenanceKey)) {
|
|
146
164
|
throw new Error('invalid message provenance key');
|
|
147
165
|
}
|
|
148
166
|
const msg = validateMessageEnvelope(input);
|
|
@@ -172,6 +190,62 @@ export class MsgStore {
|
|
|
172
190
|
|
|
173
191
|
post(input: MessageEnvelope): Message { return this.append(input); }
|
|
174
192
|
|
|
193
|
+
/** Delivery state is separate from the single linked reply; an uncertain send keeps its claim. */
|
|
194
|
+
taDeliveryStatus(requestId: number): 'pending' | 'delivered' | null {
|
|
195
|
+
return (this.db.query('SELECT status FROM msg_ta_delivery WHERE request_id = ?').get(requestId) as { status: 'pending' | 'delivered' } | null)?.status ?? null;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
setTaDeliveryStatus(requestId: number, status: 'pending' | 'delivered'): void {
|
|
199
|
+
this.db.query(`INSERT INTO msg_ta_delivery(request_id, status) VALUES (?, ?)
|
|
200
|
+
ON CONFLICT(request_id) DO UPDATE SET status = excluded.status`).run(requestId, status);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/** A short DB transaction grants one sender the external delivery. An abandoned claim stays ambiguous. */
|
|
204
|
+
claimTaDelivery(requestId: number): string | null {
|
|
205
|
+
return this.db.transaction(() => {
|
|
206
|
+
if (this.taDeliveryStatus(requestId) !== 'pending') return null;
|
|
207
|
+
const previous = this.db.query('SELECT 1 FROM msg_ta_claim WHERE request_id = ?').get(requestId);
|
|
208
|
+
if (previous) return null;
|
|
209
|
+
const token = randomUUID();
|
|
210
|
+
this.db.query('INSERT INTO msg_ta_claim(request_id, token) VALUES (?, ?)').run(requestId, token);
|
|
211
|
+
return token;
|
|
212
|
+
}).immediate();
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
taDeliveryClaimState(requestId: number): 'none' | 'held' {
|
|
216
|
+
return this.db.query('SELECT 1 FROM msg_ta_claim WHERE request_id = ?').get(requestId) ? 'held' : 'none';
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/** Only the active sender may settle or report its own attempt; stale failures cannot overwrite success. */
|
|
220
|
+
settleTaDelivery(requestId: number, token: string, replyBody: string, delivered: boolean): boolean {
|
|
221
|
+
return this.db.transaction(() => {
|
|
222
|
+
const owner = this.db.query('SELECT token FROM msg_ta_claim WHERE request_id = ?').get(requestId) as { token: string } | null;
|
|
223
|
+
if (owner?.token !== token || this.taDeliveryStatus(requestId) === 'delivered') return false;
|
|
224
|
+
if (delivered) this.setTaDeliveryStatus(requestId, 'delivered');
|
|
225
|
+
this.updateTaReply(requestId, replyBody);
|
|
226
|
+
this.db.query('DELETE FROM msg_ta_claim WHERE request_id = ? AND token = ?').run(requestId, token);
|
|
227
|
+
return true;
|
|
228
|
+
}).immediate();
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/** Keep the claim after any transport/settlement exception: publication may already have happened. */
|
|
232
|
+
markTaDeliveryUnknown(requestId: number, token: string, replyBody: string): boolean {
|
|
233
|
+
return this.db.transaction(() => {
|
|
234
|
+
const owner = this.db.query('SELECT token FROM msg_ta_claim WHERE request_id = ?').get(requestId) as { token: string } | null;
|
|
235
|
+
if (owner?.token !== token || this.taDeliveryStatus(requestId) === 'delivered') return false;
|
|
236
|
+
this.updateTaReply(requestId, replyBody);
|
|
237
|
+
return true;
|
|
238
|
+
}).immediate();
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/** Settle only the TA reply reserved for this request; never create a second reply. */
|
|
242
|
+
updateTaReply(requestId: number, body: string): void {
|
|
243
|
+
const changed = this.db.query(`UPDATE msg_messages SET body = ? WHERE id = (
|
|
244
|
+
SELECT id FROM msg_messages WHERE reply_to = ? AND sender = 'TA' ORDER BY id ASC LIMIT 1
|
|
245
|
+
) AND sender = 'TA'`).run(body, requestId);
|
|
246
|
+
if (changed.changes !== 1) throw new Error(`TA reply missing: #${requestId}`);
|
|
247
|
+
}
|
|
248
|
+
|
|
175
249
|
/** Messages strictly after the supplied cursor, ordered by durable insertion ID. */
|
|
176
250
|
listByRecipient(recipient: string, after: number = 0, limit: number = 100): Message[] {
|
|
177
251
|
const to = canonicalSeatId(recipient);
|
|
@@ -279,3 +353,13 @@ export function readUnreadCount(recipient: string, path: string = defaultMsgStor
|
|
|
279
353
|
try { return unreadCountOn(db, to); }
|
|
280
354
|
finally { db.close(); }
|
|
281
355
|
}
|
|
356
|
+
|
|
357
|
+
/** Observe one recipient's unread count ⊕ newest unread ID (read-only · no cursor change). null = ledger absent («모름»). */
|
|
358
|
+
export function readUnreadSummary(recipient: string, path: string = defaultMsgStorePath()): { count: number; maxId: number | null } | null {
|
|
359
|
+
const to = canonicalSeatId(recipient);
|
|
360
|
+
if (to === 'ALL') throw new Error('ALL is a topic, not a recipient');
|
|
361
|
+
if (!existsSync(path)) return null;
|
|
362
|
+
const db = new Database(path, { readonly: true });
|
|
363
|
+
try { return unreadSummaryOn(db, to); }
|
|
364
|
+
finally { db.close(); }
|
|
365
|
+
}
|
|
@@ -19,8 +19,11 @@
|
|
|
19
19
|
import { Database } from 'bun:sqlite';
|
|
20
20
|
import { existsSync, mkdirSync, readFileSync, readdirSync, renameSync, rmdirSync, rmSync, writeFileSync } from 'node:fs';
|
|
21
21
|
import { randomUUID } from 'node:crypto';
|
|
22
|
+
import { spawnSync } from 'node:child_process';
|
|
23
|
+
import { readlinkSync, statSync } from 'node:fs';
|
|
24
|
+
import { fileURLToPath } from 'node:url';
|
|
22
25
|
import { elanousStateRoot } from '../../autopilot/state-paths.js';
|
|
23
|
-
import { dirname, join } from 'node:path';
|
|
26
|
+
import { dirname, join, resolve } from 'node:path';
|
|
24
27
|
|
|
25
28
|
import { LOG_LEVEL_ORDER, type LogLevel, type LogRecord } from './record.js';
|
|
26
29
|
import type { LogSink } from './sink.js';
|
|
@@ -978,6 +981,224 @@ function ownerDead(owner: RetentionLockOwner): boolean {
|
|
|
978
981
|
}
|
|
979
982
|
}
|
|
980
983
|
|
|
984
|
+
// ── ⓑ 수호자: «설치 sha 보다 옛 판으로 logs.db 를 연 프로세스» 경보 (LOGS-RETENTION-46MIN) ──
|
|
985
|
+
//
|
|
986
|
+
// 사고(10-12 · 운영 보존 46분): 하한 이전 판을 쥔 옛 크론 프로세스들이 6시간마다
|
|
987
|
+
// «오래된 행 50%» 삭제를 겹쳐 돌려 보존 창을 무너뜨렸다. retention 주기마다 lsof
|
|
988
|
+
// 상당(/proc/<pid>/fd 스캔)으로 logs.db(-wal/-shm 포함)를 연 프로세스를 찾고, lstart
|
|
989
|
+
// 상당(이 파일의 processStart() = /proc/<pid>/stat starttime)으로 «현재 설치 sha 의
|
|
990
|
+
// 설치 시점보다 먼저 기동»한 프로세스(=옛 판 코드를 쥔 채 살아 있는 것)를 경보한다.
|
|
991
|
+
// 마커는 git ref/HEAD 파일의 mtime(=이 sha 가 체크아웃된 시점)이지 모듈 파일 mtime 이
|
|
992
|
+
// 아니다(must-fix — 그 파일이 변경되지 않은 채 새 판을 설치하면 옛 판을 놓친다). 전부 fail-soft.
|
|
993
|
+
|
|
994
|
+
export interface LogDbOpenerInfo {
|
|
995
|
+
pid: number;
|
|
996
|
+
/** /proc/<pid>/stat starttime(clock ticks) — lstart 상당(pid 재사용 판별용). */
|
|
997
|
+
start: string | null;
|
|
998
|
+
/** 기동 절대시각(ms) — /proc/stat btime + ticks/100(리눅스 CLK_TCK=100 환산). */
|
|
999
|
+
startedAtMs: number | null;
|
|
1000
|
+
cmdline: string[] | null;
|
|
1001
|
+
cwd: string | null;
|
|
1002
|
+
/** 열린 프로세스의 checkout ref. 설치 후 다른 checkout에서 기동한 옛 판도 구별한다. */
|
|
1003
|
+
revisionSha?: string | null;
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
/** procfs 부팅 시각(ms). 읽기 실패 시 null(=환산 불가 → 경보하지 않는다, fail-safe). */
|
|
1007
|
+
function procBootTimeMs(): number | null {
|
|
1008
|
+
try {
|
|
1009
|
+
for (const line of readFileSync('/proc/stat', 'utf8').split('\n')) {
|
|
1010
|
+
if (line.startsWith('btime ')) return Number(line.slice(6).trim()) * 1000;
|
|
1011
|
+
}
|
|
1012
|
+
} catch { /* fail-soft */ }
|
|
1013
|
+
return null;
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
/** lsof 상당 — logs.db(와 -wal/-shm)를 연 fd 를 든 프로세스 목록. 개별 pid 실패는 조용히 건너뛴다. */
|
|
1017
|
+
export function scanLogDbOpeners(dbPath: string, opts: { selfPid?: number } = {}): LogDbOpenerInfo[] {
|
|
1018
|
+
const selfPid = opts.selfPid ?? process.pid;
|
|
1019
|
+
const wanted = new Set([dbPath, `${dbPath}-wal`, `${dbPath}-shm`]);
|
|
1020
|
+
const bootMs = procBootTimeMs();
|
|
1021
|
+
const openers: LogDbOpenerInfo[] = [];
|
|
1022
|
+
let pids: string[];
|
|
1023
|
+
try { pids = readdirSync('/proc').filter((name) => /^[0-9]+$/.test(name)); } catch { return openers; }
|
|
1024
|
+
for (const name of pids) {
|
|
1025
|
+
const pid = Number(name);
|
|
1026
|
+
if (pid === selfPid) continue;
|
|
1027
|
+
let holds = false;
|
|
1028
|
+
try {
|
|
1029
|
+
for (const fdName of readdirSync(`/proc/${pid}/fd`)) {
|
|
1030
|
+
let target: string;
|
|
1031
|
+
try { target = readlinkSync(`/proc/${pid}/fd/${fdName}`); } catch { continue; }
|
|
1032
|
+
if (wanted.has(target)) { holds = true; break; }
|
|
1033
|
+
}
|
|
1034
|
+
} catch { continue; }
|
|
1035
|
+
if (!holds) continue;
|
|
1036
|
+
const start = processStart(pid);
|
|
1037
|
+
const ticks = start === null ? null : Number(start);
|
|
1038
|
+
let cmdline: string[] | null = null;
|
|
1039
|
+
try { cmdline = readFileSync(`/proc/${pid}/cmdline`, 'utf8').split('\0').filter(Boolean); } catch { /* fail-soft */ }
|
|
1040
|
+
let cwd: string | null = null;
|
|
1041
|
+
try { cwd = readlinkSync(`/proc/${pid}/cwd`); } catch { /* fail-soft */ }
|
|
1042
|
+
// cwd는 새 checkout일 수 있다. 실행한 스크립트 경로가 가리키는 판을 먼저 읽는다.
|
|
1043
|
+
// 절대 경로뿐 아니라 cwd 상대 스크립트도 처리한다. 찾지 못할 때만 cwd로 폴백.
|
|
1044
|
+
const scriptSha = cmdline?.slice(1).filter((arg) => /\.(?:[cm]?[jt]s|sh)$/.test(arg))
|
|
1045
|
+
.map((arg) => resolveCheckoutRevisionFromCwd(dirname(resolve(cwd ?? '/', arg))))
|
|
1046
|
+
.find((sha) => sha !== null) ?? null;
|
|
1047
|
+
const revisionSha = scriptSha ?? (cwd === null ? null : resolveCheckoutRevisionFromCwd(cwd));
|
|
1048
|
+
openers.push({ pid, start, startedAtMs: ticks !== null && bootMs !== null ? bootMs + (ticks / 100) * 1000 : null, cmdline, cwd, revisionSha });
|
|
1049
|
+
}
|
|
1050
|
+
return openers;
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
export interface InstalledRevisionMarker {
|
|
1054
|
+
/** 이 체크아웃의 HEAD sha. 실패 시 null. */
|
|
1055
|
+
sha: string | null;
|
|
1056
|
+
/** 현재 sha 가 이 체크아웃에 설치된 시점(ms) — git ref/HEAD 파일의 mtime. 실패 시 null. */
|
|
1057
|
+
installedAtMs: number | null;
|
|
1058
|
+
/** 마커로 읽은 파일 경로 — 경보 payload 에 싣는 용도. */
|
|
1059
|
+
marker: string | null;
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
/** /proc/<pid>/cwd에서 위로 올라가 첫 Git checkout의 현재 ref를 읽는다. */
|
|
1063
|
+
function resolveCheckoutRevisionFromCwd(cwd: string): string | null {
|
|
1064
|
+
let current = cwd;
|
|
1065
|
+
while (true) {
|
|
1066
|
+
if (existsSync(join(current, '.git'))) return resolveInstalledRevisionMarker(current).sha;
|
|
1067
|
+
const parent = dirname(current);
|
|
1068
|
+
if (parent === current) return null;
|
|
1069
|
+
current = parent;
|
|
1070
|
+
}
|
|
1071
|
+
}
|
|
1072
|
+
|
|
1073
|
+
/** 설치 판 마커 — HEAD sha 와 «그 sha 가 설치된 시점»을 함께 돌린다(worktree 의 .git 파일·
|
|
1074
|
+
* commondir 도 해석). must-fix(리뷰): 옛 판 판정 기준은 모듈 파일 mtime 이 아니라 «설치
|
|
1075
|
+
* 시점»이다 — 같은 파일이 변경되지 않은 채 새 판을 설치하면 모듈 mtime 은 옛 채로 있어
|
|
1076
|
+
* 옛 판 오프너를 놓친다. 설치 시점의 파일시스템 근거: 브랜치 체크아웃은 ref 파일 mtime
|
|
1077
|
+
* (=pull·merge·reset·commit 로 ref 가 움직인 시점), detached 전환은 HEAD 파일 mtime —
|
|
1078
|
+
* 둘 중 늦은 쪽. 전부 실패 시 null(fail-safe — 경보하지 않는다). */
|
|
1079
|
+
export function resolveInstalledRevisionMarker(root = resolve(dirname(fileURLToPath(import.meta.url)), '../../..')): InstalledRevisionMarker {
|
|
1080
|
+
const none: InstalledRevisionMarker = { sha: null, installedAtMs: null, marker: null };
|
|
1081
|
+
try {
|
|
1082
|
+
const dotGit = join(root, '.git');
|
|
1083
|
+
let gitDir: string;
|
|
1084
|
+
if (statSync(dotGit).isDirectory()) gitDir = dotGit;
|
|
1085
|
+
else {
|
|
1086
|
+
const firstLine = readFileSync(dotGit, 'utf8').trim();
|
|
1087
|
+
if (!firstLine.startsWith('gitdir:')) return none;
|
|
1088
|
+
gitDir = resolve(root, firstLine.slice(7).trim());
|
|
1089
|
+
}
|
|
1090
|
+
const headPath = join(gitDir, 'HEAD');
|
|
1091
|
+
let head: string;
|
|
1092
|
+
try { head = readFileSync(headPath, 'utf8').trim(); } catch { return none; }
|
|
1093
|
+
if (!head.startsWith('ref: ')) {
|
|
1094
|
+
// detached — HEAD 파일이 sha 를 직접 쥔다: 그 mtime = 체크아웃(=설치) 시점.
|
|
1095
|
+
if (!/^[0-9a-f]{40}$/.test(head)) return none;
|
|
1096
|
+
try { return { sha: head, installedAtMs: statSync(headPath).mtimeMs, marker: headPath }; } catch { return none; }
|
|
1097
|
+
}
|
|
1098
|
+
const ref = head.slice(5).trim();
|
|
1099
|
+
const commonDir = existsSync(join(gitDir, 'commondir'))
|
|
1100
|
+
? resolve(gitDir, readFileSync(join(gitDir, 'commondir'), 'utf8').trim())
|
|
1101
|
+
: gitDir;
|
|
1102
|
+
const looseRef = [join(gitDir, ref), join(commonDir, ref)].find(existsSync);
|
|
1103
|
+
const markerPath = looseRef ?? join(commonDir, 'packed-refs');
|
|
1104
|
+
const sha = looseRef
|
|
1105
|
+
? readFileSync(looseRef, 'utf8').trim()
|
|
1106
|
+
: readFileSync(markerPath, 'utf8').split('\n')
|
|
1107
|
+
.find((line) => line.endsWith(` ${ref}`) && /^[0-9a-f]{40} /.test(line))?.slice(0, 40) ?? '';
|
|
1108
|
+
if (!/^[0-9a-f]{40}$/.test(sha)) return none;
|
|
1109
|
+
// packed-refs mtime은 재패킹만 해도 움직이므로 설치 시각으로 쓰면 오경보가 난다.
|
|
1110
|
+
// packed ref의 체크아웃 시각은 HEAD mtime만 사용한다. loose ref는 ref 갱신과
|
|
1111
|
+
// 체크아웃 중 늦은 시각을 사용한다.
|
|
1112
|
+
let installedAtMs: number | null = null;
|
|
1113
|
+
try { installedAtMs = looseRef
|
|
1114
|
+
? Math.max(statSync(markerPath).mtimeMs, statSync(headPath).mtimeMs)
|
|
1115
|
+
: statSync(headPath).mtimeMs; } catch { /* fail-soft */ }
|
|
1116
|
+
return { sha, installedAtMs, marker: markerPath };
|
|
1117
|
+
} catch { return none; }
|
|
1118
|
+
}
|
|
1119
|
+
|
|
1120
|
+
/** 설치 sha — 이 체크아웃의 HEAD. 실패 시 null. */
|
|
1121
|
+
export function resolveInstalledSha(): string | null {
|
|
1122
|
+
return resolveInstalledRevisionMarker().sha;
|
|
1123
|
+
}
|
|
1124
|
+
|
|
1125
|
+
export interface StaleOpenerCheck {
|
|
1126
|
+
selfPid: number;
|
|
1127
|
+
/** 현재 설치 sha 가 설치된 시점(ms) — 이보다 먼저 기동한 오프너는 옛 판이다(must-fix: 모듈 mtime 아님). */
|
|
1128
|
+
installedAtMs: number | null;
|
|
1129
|
+
/** 경보 문자열에 싣는 현재 설치 sha. */
|
|
1130
|
+
installedSha?: string | null;
|
|
1131
|
+
revisionRelation?: 'older' | 'newer' | 'different' | 'same';
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1134
|
+
/** 설치 checkout의 Git 객체 그래프에서 선후를 확인한다. 알 수 없으면 다른 판으로 남긴다. */
|
|
1135
|
+
function revisionRelation(openerSha: string | null | undefined, installedSha: string | null): StaleOpenerCheck['revisionRelation'] {
|
|
1136
|
+
if (!openerSha || !installedSha) return 'different';
|
|
1137
|
+
if (openerSha === installedSha) return 'same';
|
|
1138
|
+
const root = resolve(dirname(fileURLToPath(import.meta.url)), '../../..');
|
|
1139
|
+
const ancestor = (from: string, to: string): boolean =>
|
|
1140
|
+
spawnSync('git', ['-C', root, 'merge-base', '--is-ancestor', from, to], { timeout: 2_000, stdio: 'ignore' }).status === 0;
|
|
1141
|
+
if (ancestor(openerSha, installedSha)) return 'older';
|
|
1142
|
+
if (ancestor(installedSha, openerSha)) return 'newer';
|
|
1143
|
+
return 'different';
|
|
1144
|
+
}
|
|
1145
|
+
|
|
1146
|
+
/** 순수 — 오프너 하나가 «옛 판»인가? 이유 문자열, 정상이면 null. 측정 결측은 정상 취급(fail-safe). */
|
|
1147
|
+
export function staleOpenerReason(opener: LogDbOpenerInfo, check: StaleOpenerCheck): string | null {
|
|
1148
|
+
if (opener.pid === check.selfPid) return null;
|
|
1149
|
+
if (check.revisionRelation === 'newer' || check.revisionRelation === 'different') return null;
|
|
1150
|
+
if (check.revisionRelation === 'older') {
|
|
1151
|
+
return `checkout revision ${opener.revisionSha} precedes installed revision ${check.installedSha}`;
|
|
1152
|
+
}
|
|
1153
|
+
if (check.installedAtMs === null || opener.startedAtMs === null) return null;
|
|
1154
|
+
if (opener.startedAtMs >= check.installedAtMs) return null;
|
|
1155
|
+
return `started ${new Date(opener.startedAtMs).toISOString()} before installed revision ${check.installedSha ?? 'unknown'} (installed at ${new Date(check.installedAtMs).toISOString()})`;
|
|
1156
|
+
}
|
|
1157
|
+
|
|
1158
|
+
export interface StaleOpenerAlert {
|
|
1159
|
+
pid: number;
|
|
1160
|
+
start: string | null;
|
|
1161
|
+
startedAt: string | null;
|
|
1162
|
+
reason: string;
|
|
1163
|
+
}
|
|
1164
|
+
|
|
1165
|
+
/** 수호자 본체 — 옛 판 오프너를 찾아 `log-store.retention`/`stale-opener-alert` 경보 관측을
|
|
1166
|
+
* 남긴다. caller: runStampedRetention(보존 주기마다 1회 · registerLogStoreSink 가 스케줄).
|
|
1167
|
+
* 전 경로 fail-soft — 수호자가 retention 을 절대 깨지 않는다. */
|
|
1168
|
+
export function alertStaleLogDbOpeners(
|
|
1169
|
+
dbPath: string,
|
|
1170
|
+
deps: { openers?: () => LogDbOpenerInfo[]; installedAtMs?: number | null } = {},
|
|
1171
|
+
): StaleOpenerAlert[] {
|
|
1172
|
+
const alerts: StaleOpenerAlert[] = [];
|
|
1173
|
+
try {
|
|
1174
|
+
// must-fix: 옛 판 판정 마커는 모듈 파일 mtime 이 아니라 «설치 sha 의 설치 시점»이다.
|
|
1175
|
+
const marker = resolveInstalledRevisionMarker();
|
|
1176
|
+
const installedAtMs = deps.installedAtMs ?? marker.installedAtMs;
|
|
1177
|
+
const openers = (deps.openers ?? (() => scanLogDbOpeners(dbPath)))();
|
|
1178
|
+
for (const opener of openers) {
|
|
1179
|
+
const relation = opener.revisionSha && marker.sha
|
|
1180
|
+
? revisionRelation(opener.revisionSha, marker.sha) : undefined;
|
|
1181
|
+
if (relation === 'different') {
|
|
1182
|
+
debug.log('log-store.retention', 'different-opener-alert', {
|
|
1183
|
+
pid: opener.pid, db: dbPath, openerSha: opener.revisionSha, installedSha: marker.sha,
|
|
1184
|
+
reason: 'checkout revision ancestry unavailable or divergent',
|
|
1185
|
+
}, { level: 'warn' });
|
|
1186
|
+
continue;
|
|
1187
|
+
}
|
|
1188
|
+
const reason = staleOpenerReason(opener, { selfPid: process.pid, installedAtMs, installedSha: marker.sha, revisionRelation: relation });
|
|
1189
|
+
if (!reason) continue;
|
|
1190
|
+
const startedAt = opener.startedAtMs === null ? null : new Date(opener.startedAtMs).toISOString();
|
|
1191
|
+
alerts.push({ pid: opener.pid, start: opener.start, startedAt, reason });
|
|
1192
|
+
debug.log('log-store.retention', 'stale-opener-alert', {
|
|
1193
|
+
pid: opener.pid, start: opener.start, startedAt, reason,
|
|
1194
|
+
db: dbPath, installedSha: marker.sha, openerSha: opener.revisionSha ?? null, installedAtMs,
|
|
1195
|
+
marker: marker.marker, via: 'procfs-fd+stat',
|
|
1196
|
+
}, { level: 'warn' });
|
|
1197
|
+
}
|
|
1198
|
+
} catch { /* fail-soft — 수호자가 retention 을 깨지지 않게 한다 */ }
|
|
1199
|
+
return alerts;
|
|
1200
|
+
}
|
|
1201
|
+
|
|
981
1202
|
/** Only the guard row can authorize removal. A pre-publication temp file belongs to its row's token. */
|
|
982
1203
|
function removeRetentionLockDirectory(lockPath: string, token: string, interrupted: boolean): void {
|
|
983
1204
|
if (!existsSync(lockPath)) return;
|
|
@@ -1071,6 +1292,9 @@ function runStampedRetention(store: LogStore, retention: LogStoreRetention, owne
|
|
|
1071
1292
|
if (!release) return;
|
|
1072
1293
|
try {
|
|
1073
1294
|
if (!owner && isFresh()) return;
|
|
1295
|
+
// ⓑ 수호자 — 설치 판보다 먼저 기동한(=옛 판) logs.db 오프너 경보(lsof⊕lstart 상당,
|
|
1296
|
+
// fail-soft). 보존 주기(6h · owner=nexus)마다 함께 돈다.
|
|
1297
|
+
alertStaleLogDbOpeners(store.path);
|
|
1074
1298
|
store.enforceRetention(retention);
|
|
1075
1299
|
try { store.reclaimFreePages(); }
|
|
1076
1300
|
catch (error) { debug.log('log-store.retention', 'reclaim-failed', { reason: error instanceof Error ? error.message : String(error) }); }
|