elanous 0.2.20 → 0.2.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (339) hide show
  1. package/apps/pwa/out/404/index.html +1 -1
  2. package/apps/pwa/out/404.html +1 -1
  3. package/apps/pwa/out/_next/static/MObU8ujkMciAKr6ljAV05/_buildManifest.js +1 -0
  4. package/apps/pwa/out/_next/static/chunks/088d8e8a.8069543d082bec12.js +160 -0
  5. package/apps/pwa/out/_next/static/chunks/1109.78a7a3288c0259d9.js +148 -0
  6. package/apps/pwa/out/_next/static/chunks/115.1c6690b8604f81c4.js +32 -0
  7. package/apps/pwa/out/_next/static/chunks/119fc678.6f591cdb84ca5791.js +1 -0
  8. package/apps/pwa/out/_next/static/chunks/1262.76a3f08e67c5f816.js +309 -0
  9. package/apps/pwa/out/_next/static/chunks/{1307-77fad80cf54477c6.js → 1307-c97eba9fb72a9a5e.js} +1 -1
  10. package/apps/pwa/out/_next/static/chunks/1358d292.5c87f12cad6eaa91.js +122 -0
  11. package/apps/pwa/out/_next/static/chunks/1438.27a1a7b8f2308004.js +149 -0
  12. package/apps/pwa/out/_next/static/chunks/1442.02d85cd88f4c1eac.js +79 -0
  13. package/apps/pwa/out/_next/static/chunks/1501-1aad662d82ba43f7.js +1 -0
  14. package/apps/pwa/out/_next/static/chunks/1584.9e2dd53da6e6d4b1.js +24 -0
  15. package/apps/pwa/out/_next/static/chunks/1586.b989b0614afb77e6.js +1 -0
  16. package/apps/pwa/out/_next/static/chunks/1609.2ad355062870151f.js +1 -0
  17. package/apps/pwa/out/_next/static/chunks/1612-f6df0fc0eed7157b.js +1 -0
  18. package/apps/pwa/out/_next/static/chunks/1923.37f1d5fe7ebe3860.js +27 -0
  19. package/apps/pwa/out/_next/static/chunks/1948.5f35b39eef222a94.js +5 -0
  20. package/apps/pwa/out/_next/static/chunks/1993.3fd074a2a246234e.js +1 -0
  21. package/apps/pwa/out/_next/static/chunks/2140.d2daf9ee96e295d1.js +1 -0
  22. package/apps/pwa/out/_next/static/chunks/2212.0359851457473efc.js +1 -0
  23. package/apps/pwa/out/_next/static/chunks/224.583e562365a52f74.js +1 -0
  24. package/apps/pwa/out/_next/static/chunks/2328.0f37465646bafa7d.js +36 -0
  25. package/apps/pwa/out/_next/static/chunks/2372.bf71372184991937.js +286 -0
  26. package/apps/pwa/out/_next/static/chunks/2549-a8515c86f9e8cb96.js +1 -0
  27. package/apps/pwa/out/_next/static/chunks/2655.72cc6a97276c5acc.js +1 -0
  28. package/apps/pwa/out/_next/static/chunks/2743-ef6e8c3ced74cce5.js +1 -0
  29. package/apps/pwa/out/_next/static/chunks/2840-5458ad50c49cac6a.js +1 -0
  30. package/apps/pwa/out/_next/static/chunks/2856.f75d5baef7f49b74.js +1 -0
  31. package/apps/pwa/out/_next/static/chunks/3344.0de4b0ec5a454b7a.js +1 -0
  32. package/apps/pwa/out/_next/static/chunks/335.a13f339e97893437.js +94 -0
  33. package/apps/pwa/out/_next/static/chunks/3394.646348f34b2500be.js +1 -0
  34. package/apps/pwa/out/_next/static/chunks/3443.6276909b53d72d53.js +1 -0
  35. package/apps/pwa/out/_next/static/chunks/3495.abd4254d808bf2dc.js +1 -0
  36. package/apps/pwa/out/_next/static/chunks/3522-1de1319d028ac89c.js +25 -0
  37. package/apps/pwa/out/_next/static/chunks/3721.ba5b82db0d9a5572.js +1 -0
  38. package/apps/pwa/out/_next/static/chunks/3919.9ff7dc25afefee33.js +74 -0
  39. package/apps/pwa/out/_next/static/chunks/3998.bd9a69d9b50e9bd2.js +1 -0
  40. package/apps/pwa/out/_next/static/chunks/4003.ab6db1dc266576c9.js +1 -0
  41. package/apps/pwa/out/_next/static/chunks/4216.e8ac94b9fe6b96ab.js +1 -0
  42. package/apps/pwa/out/_next/static/chunks/4327.c44dc2b55cb43ee5.js +34 -0
  43. package/apps/pwa/out/_next/static/chunks/4401.94257d497b6d26bc.js +1 -0
  44. package/apps/pwa/out/_next/static/chunks/4559.17ca2cdbaa8d414f.js +115 -0
  45. package/apps/pwa/out/_next/static/chunks/4588.ad3364cb7fb97ada.js +86 -0
  46. package/apps/pwa/out/_next/static/chunks/4706.97e734bd4c9816ca.js +1 -0
  47. package/apps/pwa/out/_next/static/chunks/4982.6e44352f751b9a1b.js +77 -0
  48. package/apps/pwa/out/_next/static/chunks/5342.49c5c5a530e62b50.js +1 -0
  49. package/apps/pwa/out/_next/static/chunks/5360-08a80abdf3a4da99.js +1 -0
  50. package/apps/pwa/out/_next/static/chunks/5431.66c58ab1327ee0b7.js +1 -0
  51. package/apps/pwa/out/_next/static/chunks/5717.bcfcb96e701b82e0.js +90 -0
  52. package/apps/pwa/out/_next/static/chunks/5755-30d102586789bf7f.js +1 -0
  53. package/apps/pwa/out/_next/static/chunks/5957.3f1a4012388dd962.js +1 -0
  54. package/apps/pwa/out/_next/static/chunks/5977-59fedea50d13c762.js +1 -0
  55. package/apps/pwa/out/_next/static/chunks/602.9bf02ac8e9502598.js +1 -0
  56. package/apps/pwa/out/_next/static/chunks/6020.3a1b347e2d2dfacc.js +8 -0
  57. package/apps/pwa/out/_next/static/chunks/{6158.af2e93b7adff709b.js → 6158.566c890adc06a459.js} +1 -1
  58. package/apps/pwa/out/_next/static/chunks/6375.fce1d174ba6d0324.js +64 -0
  59. package/apps/pwa/out/_next/static/chunks/6546.92a828aef441271f.js +1 -0
  60. package/apps/pwa/out/_next/static/chunks/658.957fdb0a8c6c6c8e.js +287 -0
  61. package/apps/pwa/out/_next/static/chunks/6706.323590aabb8b1293.js +226 -0
  62. package/apps/pwa/out/_next/static/chunks/6747.b67947f293a4c02d.js +1 -0
  63. package/apps/pwa/out/_next/static/chunks/6767.8e6c5f6e744fcd9f.js +38 -0
  64. package/apps/pwa/out/_next/static/chunks/6868.000b24a3045c5574.js +1 -0
  65. package/apps/pwa/out/_next/static/chunks/6946.bba74e9c96050f30.js +1 -0
  66. package/apps/pwa/out/_next/static/chunks/7033.a558f1b2f6912a53.js +1 -0
  67. package/apps/pwa/out/_next/static/chunks/7065.46029c26c53eac50.js +1 -0
  68. package/apps/pwa/out/_next/static/chunks/7087.6fae577d09249bc1.js +1 -0
  69. package/apps/pwa/out/_next/static/chunks/7175-350f4ce2873bc038.js +1 -0
  70. package/apps/pwa/out/_next/static/chunks/722.de8d9801e0f6d414.js +41 -0
  71. package/apps/pwa/out/_next/static/chunks/7497.57981e9d7a75a52a.js +24 -0
  72. package/apps/pwa/out/_next/static/chunks/7630.2b9a68659053ff4e.js +193 -0
  73. package/apps/pwa/out/_next/static/chunks/7672.4af122ad892117c7.js +1 -0
  74. package/apps/pwa/out/_next/static/chunks/771.6ab889a233e7dae7.js +1 -0
  75. package/apps/pwa/out/_next/static/chunks/7899.597af4a9996c907f.js +1 -0
  76. package/apps/pwa/out/_next/static/chunks/79b78388.663188133449d707.js +1 -0
  77. package/apps/pwa/out/_next/static/chunks/8096.f7da0776cfde6300.js +1 -0
  78. package/apps/pwa/out/_next/static/chunks/8194.484f51e273d9abd2.js +217 -0
  79. package/apps/pwa/out/_next/static/chunks/8769.815ca69879f608b4.js +104 -0
  80. package/apps/pwa/out/_next/static/chunks/8857-f1762140ef9a96b1.js +1 -0
  81. package/apps/pwa/out/_next/static/chunks/88729267.9c6c000157c83f2f.js +62 -0
  82. package/apps/pwa/out/_next/static/chunks/8f835b4f.048eeb02299e3e12.js +1 -0
  83. package/apps/pwa/out/_next/static/chunks/9068.5f2764fccf86c1b2.js +93 -0
  84. package/apps/pwa/out/_next/static/chunks/9247-7581825d511a64c0.js +1 -0
  85. package/apps/pwa/out/_next/static/chunks/9451.ebfb0dcc9c02250f.js +23 -0
  86. package/apps/pwa/out/_next/static/chunks/9497-b0e758d1cd4ae8d7.js +1 -0
  87. package/apps/pwa/out/_next/static/chunks/9559.e648369b1d57cf9a.js +62 -0
  88. package/apps/pwa/out/_next/static/chunks/9832.2ab2e818013574c7.js +1 -0
  89. package/apps/pwa/out/_next/static/chunks/a9f2b54d-40c16bd0efdb04b8.js +1 -0
  90. package/apps/pwa/out/_next/static/chunks/app/approvals/page-937c92292a4ab4f0.js +1 -0
  91. package/apps/pwa/out/_next/static/chunks/app/autopilot/{page-85b316bb4235980b.js → page-d781be3e3a9cfd74.js} +1 -1
  92. package/apps/pwa/out/_next/static/chunks/app/board/{page-9b2b26a33cb2cd42.js → page-b0a80997903852d8.js} +1 -1
  93. package/apps/pwa/out/_next/static/chunks/app/botlab/{page-c9955fde67b1bcb5.js → page-2b1263165630d9c8.js} +1 -1
  94. package/apps/pwa/out/_next/static/chunks/app/bots/{page-7a852956c74015a3.js → page-ea998f7f5d156bea.js} +1 -1
  95. package/apps/pwa/out/_next/static/chunks/app/ceo/page-33c328b64ca508f8.js +1 -0
  96. package/apps/pwa/out/_next/static/chunks/app/chat/{page-4ba4abe096b6c2a8.js → page-e2d6bed48ac20120.js} +1 -1
  97. package/apps/pwa/out/_next/static/chunks/app/consult/{page-575d4a8b4f7becb2.js → page-111cabe4d1fd69ae.js} +1 -1
  98. package/apps/pwa/out/_next/static/chunks/app/control/{page-f5d56ff5f45ad164.js → page-544dd4bd4847eeb7.js} +1 -1
  99. package/apps/pwa/out/_next/static/chunks/app/decisions/{page-2260e9eef0357421.js → page-3ee191319bf97d87.js} +1 -1
  100. package/apps/pwa/out/_next/static/chunks/app/design-check/page-e4bcb2c503dab04b.js +1 -0
  101. package/apps/pwa/out/_next/static/chunks/app/editor/page-300897dcbbd5ed76.js +1 -0
  102. package/apps/pwa/out/_next/static/chunks/app/exec/{page-466bf0ee16ad1432.js → page-a449f1ed472b81c2.js} +1 -1
  103. package/apps/pwa/out/_next/static/chunks/app/field/{page-a23bc71713bcc08f.js → page-9045094d6d3b5cd0.js} +1 -1
  104. package/apps/pwa/out/_next/static/chunks/app/inside/page-c06673596849ef45.js +1 -0
  105. package/apps/pwa/out/_next/static/chunks/app/intake/page-103fa3191c1b2e08.js +1 -0
  106. package/apps/pwa/out/_next/static/chunks/app/layout-9f81233f29dcd409.js +1 -0
  107. package/apps/pwa/out/_next/static/chunks/app/live/page-be8b4615c919e36c.js +1 -0
  108. package/apps/pwa/out/_next/static/chunks/app/live-run/page-c685b104e0dfa609.js +1 -0
  109. package/apps/pwa/out/_next/static/chunks/app/loops/page-8d693331cfbb6add.js +1 -0
  110. package/apps/pwa/out/_next/static/chunks/app/market/page-cef4f30a4c22fae1.js +1 -0
  111. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-66ea1b54fc83fa15.js +1 -0
  112. package/apps/pwa/out/_next/static/chunks/app/morning/{page-ec102c9b8b691e37.js → page-7a12f61be4a579d1.js} +1 -1
  113. package/apps/pwa/out/_next/static/chunks/app/not-found-c85b0589591a99c9.js +1 -0
  114. package/apps/pwa/out/_next/static/chunks/app/observatory/page-725a4da02dd129eb.js +1 -0
  115. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/{page-f6055495e9ed7ea8.js → page-8cc707bd09a497d9.js} +1 -1
  116. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-90b55be1f15ce0e8.js +1 -0
  117. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-cb16606e08be65db.js +1 -0
  118. package/apps/pwa/out/_next/static/chunks/app/outputs/page-16dc64b74ed6a40a.js +1 -0
  119. package/apps/pwa/out/_next/static/chunks/app/page-21bec4afeb2f0917.js +1 -0
  120. package/apps/pwa/out/_next/static/chunks/app/reflection/{page-db03ae3589a81c9e.js → page-dc39ee7a36d26385.js} +1 -1
  121. package/apps/pwa/out/_next/static/chunks/app/scheduler/{page-4a6463b97f0ad94f.js → page-84ae759f76053672.js} +1 -1
  122. package/apps/pwa/out/_next/static/chunks/app/sessions/{page-75bffecc9aa1004e.js → page-3db58ddb61fe2be9.js} +1 -1
  123. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-c5315e30f3528dc4.js +1 -0
  124. package/apps/pwa/out/_next/static/chunks/app/settings/{page-31d5b875760bb62d.js → page-2fe368efb6de3b6a.js} +1 -1
  125. package/apps/pwa/out/_next/static/chunks/app/setup/done/{page-c26fa10aac6c21c4.js → page-8d04132dd0ac5e10.js} +1 -1
  126. package/apps/pwa/out/_next/static/chunks/app/setup/{page-68793c6e10331d66.js → page-f23484de467c9ea3.js} +1 -1
  127. package/apps/pwa/out/_next/static/chunks/app/share/{page-46d5201d2207a511.js → page-c69762987b751174.js} +1 -1
  128. package/apps/pwa/out/_next/static/chunks/app/showroom/page-5456692bda39aafb.js +1 -0
  129. package/apps/pwa/out/_next/static/chunks/app/tasks/{page-be1c4e8697f22360.js → page-d75f7cd65845ce98.js} +1 -1
  130. package/apps/pwa/out/_next/static/chunks/app/term/page-5603f5e568b25ce3.js +1 -0
  131. package/apps/pwa/out/_next/static/chunks/app/today/page-d2cc0f4e89f42028.js +1 -0
  132. package/apps/pwa/out/_next/static/chunks/app/trace/{page-0fd895fab9b62c1b.js → page-a6eb73b8a4840f1c.js} +1 -1
  133. package/apps/pwa/out/_next/static/chunks/app/vault/page-c0a3f4d10edb83fa.js +1 -0
  134. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/{page-c6e4364d9de8f717.js → page-6c0b88068c143f15.js} +1 -1
  135. package/apps/pwa/out/_next/static/chunks/app/workflows/{page-0acc8e198f7b0dfb.js → page-7f8d7fcdf058a845.js} +1 -1
  136. package/apps/pwa/out/_next/static/chunks/app/workspace/{page-34882c84f84021c5.js → page-22163556cd38bc88.js} +1 -1
  137. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-141436fd26dd56fc.js +1 -0
  138. package/apps/pwa/out/_next/static/chunks/d70879db.c66c56a95a91efc6.js +66 -0
  139. package/apps/pwa/out/_next/static/chunks/f8626309.a4e5d362b1d55e59.js +1 -0
  140. package/apps/pwa/out/_next/static/chunks/webpack-f2b4016b9b981bc8.js +1 -0
  141. package/apps/pwa/out/_next/static/css/{d3427996da11e480.css → b9d8151e48998417.css} +1 -1
  142. package/apps/pwa/out/_next/static/css/e81df11490b4413b.css +1 -0
  143. package/apps/pwa/out/approvals/index.html +1 -1
  144. package/apps/pwa/out/approvals/index.txt +14 -14
  145. package/apps/pwa/out/autopilot/index.html +1 -1
  146. package/apps/pwa/out/autopilot/index.txt +14 -14
  147. package/apps/pwa/out/board/index.html +1 -1
  148. package/apps/pwa/out/board/index.txt +14 -14
  149. package/apps/pwa/out/botlab/index.html +1 -1
  150. package/apps/pwa/out/botlab/index.txt +14 -14
  151. package/apps/pwa/out/bots/index.html +1 -1
  152. package/apps/pwa/out/bots/index.txt +14 -14
  153. package/apps/pwa/out/ceo/index.html +1 -1
  154. package/apps/pwa/out/ceo/index.txt +14 -14
  155. package/apps/pwa/out/chat/index.html +1 -1
  156. package/apps/pwa/out/chat/index.txt +14 -14
  157. package/apps/pwa/out/consult/index.html +1 -1
  158. package/apps/pwa/out/consult/index.txt +14 -14
  159. package/apps/pwa/out/control/index.html +1 -1
  160. package/apps/pwa/out/control/index.txt +14 -14
  161. package/apps/pwa/out/decisions/index.html +1 -1
  162. package/apps/pwa/out/decisions/index.txt +14 -14
  163. package/apps/pwa/out/design-check/index.html +1 -1
  164. package/apps/pwa/out/design-check/index.txt +14 -14
  165. package/apps/pwa/out/editor/index.html +1 -1
  166. package/apps/pwa/out/editor/index.txt +15 -15
  167. package/apps/pwa/out/exec/index.html +1 -1
  168. package/apps/pwa/out/exec/index.txt +14 -14
  169. package/apps/pwa/out/field/index.html +1 -1
  170. package/apps/pwa/out/field/index.txt +14 -14
  171. package/apps/pwa/out/healthz +1 -1
  172. package/apps/pwa/out/index.html +1 -1
  173. package/apps/pwa/out/index.txt +14 -14
  174. package/apps/pwa/out/inside/index.html +1 -1
  175. package/apps/pwa/out/inside/index.txt +15 -15
  176. package/apps/pwa/out/intake/index.html +1 -1
  177. package/apps/pwa/out/intake/index.txt +14 -14
  178. package/apps/pwa/out/live/index.html +1 -1
  179. package/apps/pwa/out/live/index.txt +14 -14
  180. package/apps/pwa/out/live-run/index.html +1 -0
  181. package/apps/pwa/out/live-run/index.txt +35 -0
  182. package/apps/pwa/out/loops/index.html +1 -1
  183. package/apps/pwa/out/loops/index.txt +14 -14
  184. package/apps/pwa/out/market/index.html +1 -1
  185. package/apps/pwa/out/market/index.txt +14 -14
  186. package/apps/pwa/out/missions/placeholder/index.html +1 -1
  187. package/apps/pwa/out/missions/placeholder/index.txt +14 -14
  188. package/apps/pwa/out/morning/index.html +1 -1
  189. package/apps/pwa/out/morning/index.txt +14 -14
  190. package/apps/pwa/out/observatory/index.html +1 -1
  191. package/apps/pwa/out/observatory/index.txt +14 -14
  192. package/apps/pwa/out/ops/checklist/index.html +1 -1
  193. package/apps/pwa/out/ops/checklist/index.txt +14 -14
  194. package/apps/pwa/out/ops/release/index.html +1 -1
  195. package/apps/pwa/out/ops/release/index.txt +14 -14
  196. package/apps/pwa/out/ops/seats/index.html +1 -1
  197. package/apps/pwa/out/ops/seats/index.txt +14 -14
  198. package/apps/pwa/out/outputs/index.html +1 -1
  199. package/apps/pwa/out/outputs/index.txt +14 -14
  200. package/apps/pwa/out/reflection/index.html +1 -1
  201. package/apps/pwa/out/reflection/index.txt +15 -15
  202. package/apps/pwa/out/scheduler/index.html +1 -1
  203. package/apps/pwa/out/scheduler/index.txt +14 -14
  204. package/apps/pwa/out/sessions/index.html +1 -1
  205. package/apps/pwa/out/sessions/index.txt +14 -14
  206. package/apps/pwa/out/settings/devices/index.html +1 -1
  207. package/apps/pwa/out/settings/devices/index.txt +14 -14
  208. package/apps/pwa/out/settings/index.html +1 -1
  209. package/apps/pwa/out/settings/index.txt +14 -14
  210. package/apps/pwa/out/setup/done/index.html +1 -1
  211. package/apps/pwa/out/setup/done/index.txt +14 -14
  212. package/apps/pwa/out/setup/index.html +1 -1
  213. package/apps/pwa/out/setup/index.txt +14 -14
  214. package/apps/pwa/out/share/index.html +1 -1
  215. package/apps/pwa/out/share/index.txt +14 -14
  216. package/apps/pwa/out/showroom/index.html +1 -1
  217. package/apps/pwa/out/showroom/index.txt +14 -14
  218. package/apps/pwa/out/tasks/index.html +1 -1
  219. package/apps/pwa/out/tasks/index.txt +14 -14
  220. package/apps/pwa/out/term/index.html +1 -1
  221. package/apps/pwa/out/term/index.txt +14 -14
  222. package/apps/pwa/out/today/index.html +1 -1
  223. package/apps/pwa/out/today/index.txt +14 -14
  224. package/apps/pwa/out/trace/index.html +1 -1
  225. package/apps/pwa/out/trace/index.txt +14 -14
  226. package/apps/pwa/out/vault/index.html +1 -1
  227. package/apps/pwa/out/vault/index.txt +14 -14
  228. package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
  229. package/apps/pwa/out/workflows/chat-ui/index.txt +14 -14
  230. package/apps/pwa/out/workflows/index.html +1 -1
  231. package/apps/pwa/out/workflows/index.txt +14 -14
  232. package/apps/pwa/out/workspace/index.html +1 -1
  233. package/apps/pwa/out/workspace/index.txt +14 -14
  234. package/apps/pwa/out/worktrees/index.html +1 -1
  235. package/apps/pwa/out/worktrees/index.txt +14 -14
  236. package/graphs/drafts/recipes.yaml +4 -4
  237. package/graphs/implement-loop.yaml +277 -5
  238. package/package.json +1 -1
  239. package/scripts/coord-channel-watch.sh +31 -4
  240. package/scripts/docs-lint.ts +4 -1
  241. package/scripts/marketing/generate-ax-brief.ts +18 -1
  242. package/scripts/measure-nl-routing-corpus.ts +11 -3
  243. package/scripts/release-loop/gate-node.ts +5 -5
  244. package/scripts/release-loop/shard-plan.ts +50 -5
  245. package/scripts/rhythm/daily.ts +2 -1
  246. package/src/browser-cdp/client.ts +10 -5
  247. package/src/cli/ops-cli.ts +11 -0
  248. package/src/cli/ops-overview-cli.ts +50 -0
  249. package/src/cli/pod-cli.ts +1 -0
  250. package/src/cli/tasks-cli.ts +106 -0
  251. package/src/dashboard/index.ts +40 -4
  252. package/src/esc-abort-gate.ts +3 -1
  253. package/src/graph-runner/graph-cli.ts +57 -0
  254. package/src/graph-runner/graph-run-api.ts +13 -0
  255. package/src/graph-wizard/archetypes.ts +88 -0
  256. package/src/graph-wizard/generate.ts +545 -0
  257. package/src/graph-wizard/saved-steps.ts +120 -0
  258. package/src/graph-wizard/steps.ts +193 -0
  259. package/src/harness/harness-cli-command.ts +68 -12
  260. package/src/index.ts +7 -4
  261. package/src/intake-plane/digest.ts +18 -11
  262. package/src/maturity/feature-maturity.ts +2 -1
  263. package/src/nexus/api/draft-metrics.ts +114 -0
  264. package/src/nexus/api/graph-wizard-api.ts +53 -0
  265. package/src/nexus/api/graphs-api.ts +50 -12
  266. package/src/nexus/api/harness-traversal.ts +205 -0
  267. package/src/nexus/api/http-server.ts +31 -0
  268. package/src/nexus/outbound/router.ts +28 -8
  269. package/src/onboarding/first-run.ts +3 -0
  270. package/src/ops/overview-collect.ts +345 -0
  271. package/src/ops/overview-seat-era.ts +90 -0
  272. package/src/ops/overview.ts +223 -0
  273. package/src/release-loop/gate-shards.ts +7 -2
  274. package/src/self-dev/ask-launch-flow.ts +4 -2
  275. package/src/self-dev/ask-launch-io.ts +3 -1
  276. package/src/self-dev/graph-journey-nodes.ts +157 -0
  277. package/src/self-dev/graph-journey-route.ts +105 -0
  278. package/src/self-dev/launch-preflight.ts +43 -3
  279. package/src/self-dev/release-path-guard.ts +7 -4
  280. package/src/self-implement/frozen-merges.ts +23 -0
  281. package/src/self-implement/goal-author.ts +14 -0
  282. package/src/self-implement/heal-intake.ts +3 -0
  283. package/src/self-implement/orchestrator.ts +16 -1
  284. package/src/self-implement/seams.ts +3 -0
  285. package/src/self-implement/self-implement-runtime.ts +12 -0
  286. package/src/session-runtime/index.ts +5 -3
  287. package/src/shell-env-bootstrap.ts +17 -3
  288. package/src/task-agent/card-evidence.ts +6 -0
  289. package/src/task-agent/parent-host.ts +315 -0
  290. package/src/task-agent/task-hand.ts +9 -2
  291. package/src/task-orchestrator/surfaces/pod-command-job.ts +41 -6
  292. package/src/task-orchestrator/surfaces/pod-gate-deps.ts +123 -0
  293. package/src/task-orchestrator/surfaces/self-implement-pod.ts +39 -6
  294. package/src/tool-runtime/browser-runtime.ts +87 -3
  295. package/src/tool-runtime/index.ts +1 -0
  296. package/src/user-config.ts +20 -4
  297. package/src/version/packed-revision.json +1 -1
  298. package/apps/pwa/out/_next/static/Bam1YziK2iH9bN4K0utGk/_buildManifest.js +0 -1
  299. package/apps/pwa/out/_next/static/chunks/1612-f60c0c93ec773f81.js +0 -1
  300. package/apps/pwa/out/_next/static/chunks/1844-9ec4b1d12f6e80eb.js +0 -1
  301. package/apps/pwa/out/_next/static/chunks/1866-e7a6999f25548b30.js +0 -1
  302. package/apps/pwa/out/_next/static/chunks/1948.15f356b15a625597.js +0 -5
  303. package/apps/pwa/out/_next/static/chunks/2116-6429c188eb846ab8.js +0 -1
  304. package/apps/pwa/out/_next/static/chunks/224-e7be8f762fe74986.js +0 -1
  305. package/apps/pwa/out/_next/static/chunks/2549-89c518a4c8ed4d00.js +0 -1
  306. package/apps/pwa/out/_next/static/chunks/2957-96ffab3d521fb08f.js +0 -1
  307. package/apps/pwa/out/_next/static/chunks/5360-be1f1d8f9fcc9755.js +0 -1
  308. package/apps/pwa/out/_next/static/chunks/5755-ea7ed88eb956f47b.js +0 -1
  309. package/apps/pwa/out/_next/static/chunks/5977-94ed6f78e9814b2b.js +0 -1
  310. package/apps/pwa/out/_next/static/chunks/7748-6e51bfe927019f5c.js +0 -25
  311. package/apps/pwa/out/_next/static/chunks/9247-c1753a059e7bac08.js +0 -1
  312. package/apps/pwa/out/_next/static/chunks/a9f2b54d-33c0689b70ed8cf3.js +0 -1
  313. package/apps/pwa/out/_next/static/chunks/app/approvals/page-c855c24de7db74fe.js +0 -1
  314. package/apps/pwa/out/_next/static/chunks/app/ceo/page-c5a5c621be0e04ae.js +0 -1
  315. package/apps/pwa/out/_next/static/chunks/app/design-check/page-7c058069c5e7fa63.js +0 -1
  316. package/apps/pwa/out/_next/static/chunks/app/editor/page-dc5da0b8a2edb8a4.js +0 -1
  317. package/apps/pwa/out/_next/static/chunks/app/inside/page-ed4a6f799f005d09.js +0 -1
  318. package/apps/pwa/out/_next/static/chunks/app/intake/page-9cd565005b93aca6.js +0 -1
  319. package/apps/pwa/out/_next/static/chunks/app/layout-55f0f40c6db95f15.js +0 -1
  320. package/apps/pwa/out/_next/static/chunks/app/live/page-474cb5888ea0e8f3.js +0 -1
  321. package/apps/pwa/out/_next/static/chunks/app/loops/page-35bfec2e631d3eae.js +0 -1
  322. package/apps/pwa/out/_next/static/chunks/app/market/page-9ca21f9ad3208a61.js +0 -1
  323. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-f9ccb1b1adbea14d.js +0 -1
  324. package/apps/pwa/out/_next/static/chunks/app/not-found-a9c2cfa8dda922a5.js +0 -1
  325. package/apps/pwa/out/_next/static/chunks/app/observatory/page-6da35144878feb6d.js +0 -1
  326. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-25204694fa9984a5.js +0 -1
  327. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-a60e035f151d84d3.js +0 -1
  328. package/apps/pwa/out/_next/static/chunks/app/outputs/page-189c587f36874538.js +0 -1
  329. package/apps/pwa/out/_next/static/chunks/app/page-c56634bde705a65d.js +0 -1
  330. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-a8d2fe0c669e0de4.js +0 -1
  331. package/apps/pwa/out/_next/static/chunks/app/showroom/page-d81c07a7b4f618ae.js +0 -1
  332. package/apps/pwa/out/_next/static/chunks/app/term/page-cf0a4b4abead71ea.js +0 -1
  333. package/apps/pwa/out/_next/static/chunks/app/today/page-a4717e2c4bf7a28f.js +0 -1
  334. package/apps/pwa/out/_next/static/chunks/app/vault/page-8b6c1454a8e8f306.js +0 -1
  335. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-0506a1e0e4bdb2f5.js +0 -1
  336. package/apps/pwa/out/_next/static/chunks/webpack-c260571513bf43c2.js +0 -1
  337. package/apps/pwa/out/_next/static/css/c23d959d3ee15e57.css +0 -1
  338. /package/apps/pwa/out/_next/static/{Bam1YziK2iH9bN4K0utGk → MObU8ujkMciAKr6ljAV05}/_ssgManifest.js +0 -0
  339. /package/skills/project-onboarding/{skill.md → SKILL.md} +0 -0
@@ -16,7 +16,8 @@
16
16
  // { "type": "discord", "webhookUrl": "https://discord.com/api/webhooks/..." },
17
17
  // { "type": "pushcut", "webhookUrl": "https://api.pushcut.io/.../notifications/..." }
18
18
  // ],
19
- // "routes": { "alert": ["telegram", "pushcut"], "report": ["telegram", "discord"] }
19
+ // "routes": { "alert": ["telegram", "pushcut"], "report": ["telegram", "discord"] },
20
+ // "awayRoutes": { "alert": ["discord"] }
20
21
  // }
21
22
  //
22
23
  // Backward compat: when `outbound` is absent/malformed the resolved
@@ -27,6 +28,7 @@
27
28
  // that boolean before falling back to direct Telegram).
28
29
 
29
30
  import { createHash } from 'node:crypto';
31
+ import { readPresence } from '../../away/presence.js';
30
32
  import { debug } from '../../debug/log.js';
31
33
  import { sendTelegramReport } from '../../telegram-report.js';
32
34
  import { getPushcutClient } from '../../pushcut/client.js';
@@ -58,6 +60,8 @@ export interface OutboundFanoutConfig {
58
60
  * `routes.default`, then to ALL configured channels. An explicitly
59
61
  * empty route (`"heartbeat": []`) suppresses that kind. */
60
62
  routes?: Record<string, string[]>;
63
+ /** 외출 중 kind → channel types. 미지정 kind는 awayRoutes.default, 이것도 없으면 기존 routes를 유지한다. */
64
+ awayRoutes?: Record<string, string[]>;
61
65
  }
62
66
 
63
67
  export interface ChannelResult {
@@ -118,14 +122,17 @@ export function normalizeOutbound(raw: unknown): OutboundFanoutConfig | undefine
118
122
  channels.push({ type, ...(webhookUrl ? { webhookUrl } : {}), ...(notification ? { notification } : {}) });
119
123
  }
120
124
  if (channels.length === 0) return undefined;
121
- let routes: Record<string, string[]> | undefined;
122
- if (o.routes && typeof o.routes === 'object' && !Array.isArray(o.routes)) {
123
- routes = {};
124
- for (const [kind, v] of Object.entries(o.routes as Record<string, unknown>)) {
125
+ const parseRoutes = (value: unknown): Record<string, string[]> | undefined => {
126
+ if (!value || typeof value !== 'object' || Array.isArray(value)) return undefined;
127
+ const routes: Record<string, string[]> = {};
128
+ for (const [kind, v] of Object.entries(value as Record<string, unknown>)) {
125
129
  if (Array.isArray(v)) routes[kind] = v.filter((t): t is string => typeof t === 'string');
126
130
  }
127
- }
128
- return { channels, ...(routes ? { routes } : {}) };
131
+ return routes;
132
+ };
133
+ const routes = parseRoutes(o.routes);
134
+ const awayRoutes = parseRoutes(o.awayRoutes);
135
+ return { channels, ...(routes ? { routes } : {}), ...(awayRoutes ? { awayRoutes } : {}) };
129
136
  }
130
137
 
131
138
  /** Resolve the channel list for one message kind. No/empty config →
@@ -219,7 +226,20 @@ export async function routeOutbound(cfg: UserConfig, msg: OutboundMsg, deps: Rou
219
226
  } catch { /* 원장 조회 실패 — 억제 없이 진행 */ }
220
227
  }
221
228
 
222
- const list = resolveChannels(outbound, msg.kind);
229
+ // 상태 파일이 없거나 읽기에 실패하면 기존 kind 경로를 그대로 사용한다.
230
+ let list = resolveChannels(outbound, msg.kind);
231
+ if (outbound?.awayRoutes) {
232
+ try {
233
+ if (readPresence().away) {
234
+ const awayRoute = outbound.awayRoutes[msg.kind] ?? outbound.awayRoutes.default;
235
+ // kind 도 default 도 없으면 기존 경로로 · 명시적인 빈 배열은 기존 routes 와 같은 규칙(채널 0)을 따른다.
236
+ if (awayRoute !== undefined) {
237
+ list = outbound.channels.filter(ch => awayRoute.includes(ch.type));
238
+ try { debug.log('outbound.send', 'away-route', { kind: msg.kind, channels: list.map(ch => ch.type) }); } catch { /* 관측 실패는 발송 결과에 영향 없음 */ }
239
+ }
240
+ }
241
+ } catch { /* 상태 조회 실패 — 기존 경로 유지 */ }
242
+ }
223
243
  const channels = await Promise.all(list.map(async (ch): Promise<ChannelResult> => {
224
244
  try {
225
245
  if (ch.type === 'telegram') return await deliverTelegram(cfg, outMsg, deps);
@@ -1,6 +1,7 @@
1
1
  import * as readline from 'node:readline/promises';
2
2
  import { detectProviders, type DetectedProvider } from '../llm/provider-detect.js';
3
3
  import { debug } from '../debug/log.js';
4
+ import { unattendedNextStepLine } from './entry-hints.js';
4
5
  import { resolveProviderCredential } from '../llm/provider-credentials.js';
5
6
  import {
6
7
  getUserConfig, saveUserConfig, userConfigPath,
@@ -57,6 +58,8 @@ export async function runFirstRun(deps: FirstRunDeps = {}): Promise<{
57
58
  if (!provider) {
58
59
  if ((deps.isTTY ?? process.stdin.isTTY) !== true) {
59
60
  print('eln setup llm 으로 고르세요');
61
+ // FIRST-CHAT-NONTTY — bare `elanous` with no TTY gets the same one-line next step as the other entrances.
62
+ print(unattendedNextStepLine());
60
63
  observe('needs-llm', 0);
61
64
  return { outcome: 'needs-llm', inputs: 0 };
62
65
  }
@@ -0,0 +1,345 @@
1
+ /**
2
+ * OPS-OVERVIEW-CLI 첫 조각 — 수집기(한 시점에 모은다 · RFC §A1 «같은 스냅숏»).
3
+ * ⛔ ssh 0 · kubectl 0 · GitHub 호출 0(호출 경로). 칸마다 실패는 그 칸만 «못 잼».
4
+ */
5
+ import { execFileSync } from 'node:child_process';
6
+ import { readFileSync, readdirSync, statSync } from 'node:fs';
7
+ import { hostname } from 'node:os';
8
+ import { join } from 'node:path';
9
+ import { effectiveInstanceRoot } from '../instance/resolve.js';
10
+ import type { Checklist, ChecklistItem } from '../release-loop/checklist.js';
11
+ import type { ReleaseSchedule } from '../release-loop/release-schedule.js';
12
+ import type { ResourceView } from '../control-plane/ledger.js';
13
+ import type { TaskCard } from '../task-agent/task-hand.js';
14
+ import {
15
+ okCell, unmeasured, type Cell, type CapacityInfo, type ChecklistCounts, type LaunchableInfo, type LoopRow, type MachineRow,
16
+ type OverviewSnapshot, type OwnerTaskRow, type ParentsInfo, type PodMemberRow, type ReleaseRunInfo, type ScheduleInfo,
17
+ } from './overview.js';
18
+ import { SEAT_ERA_SOURCE, readDispatcherBeats, readDispatcherCapacity, readSeatEraHanded, seatEraFeederDir } from './overview-seat-era.js';
19
+
20
+ const GB = 1024 ** 3;
21
+ /** 하트비트 주기 30 s × 2 (RFC §A2 «낡음»). */
22
+ const HEARTBEAT_STALE_S = 60;
23
+ /** 디스패처 주기 ≈ 5.5 분 × 2. */
24
+ const DISPATCHER_STALE_S = 11 * 60;
25
+ const STALE_PARENT_S = 6 * 3600;
26
+ const TARGET_PATH = /대상 경로:/;
27
+
28
+ export interface CollectOptions {
29
+ version?: string;
30
+ sinceMs: number;
31
+ sinceLabel: string;
32
+ owner?: string;
33
+ now?: number;
34
+ root?: string;
35
+ seatEraDir?: string;
36
+ }
37
+
38
+ const iso = (ms: number): string => new Date(ms).toISOString();
39
+ const errText = (error: unknown): string => (error instanceof Error ? error.message : String(error)).split('\n')[0]!.slice(0, 160);
40
+
41
+ function pickVersion(explicit: string | undefined, schedules: ReleaseSchedule[], now: number): string | null {
42
+ if (explicit) return explicit;
43
+ const upcoming = schedules.filter((s) => Date.parse(s.cutAt) > now).sort((a, b) => a.cutAt.localeCompare(b.cutAt));
44
+ return upcoming[0]?.version ?? null;
45
+ }
46
+
47
+ export function countChecklist(items: readonly ChecklistItem[]): ChecklistCounts {
48
+ const c: ChecklistCounts = { green: 0, yellow: 0, red: 0, done: 0, p0Yellow: 0 };
49
+ for (const it of items) {
50
+ c[it.status]++;
51
+ if (it.status === 'yellow' && it.priority === 'P0') c.p0Yellow++;
52
+ }
53
+ return c;
54
+ }
55
+
56
+ /**
57
+ * ⚠️ 임시 «발사 가능» 술어 — 오케스트레이터가 아직 술어를 export 하지 않는다(OVW-DISPATCH-LEDGER).
58
+ * 노랑 ∧ 제목에 «대상 경로:» ∧ 아직 안 넘김(TASK-AGENT checklistId ⊕ 자리 시대 handed) ∧ 선행 모두 초록/끝.
59
+ */
60
+ export function launchableCells(checklists: readonly Checklist[], handed: ReadonlySet<string>): LaunchableInfo {
61
+ const status = new Map<string, ChecklistItem['status']>();
62
+ for (const cl of checklists) for (const it of cl.items) if (!status.has(it.id)) status.set(it.id, it.status);
63
+ const ids: string[] = [];
64
+ const skipped = { noTargetPath: 0, predecessorOpen: 0, alreadyHanded: 0 };
65
+ for (const cl of checklists) for (const it of cl.items) {
66
+ if (it.status !== 'yellow') continue;
67
+ if (handed.has(it.id)) { skipped.alreadyHanded++; continue; }
68
+ if (!TARGET_PATH.test(it.title)) { skipped.noTargetPath++; continue; }
69
+ const open = (it.predecessors ?? []).some((p) => { const st = status.get(p); return st !== undefined && st !== 'green' && st !== 'done'; });
70
+ if (open) { skipped.predecessorOpen++; continue; }
71
+ ids.push(it.id);
72
+ }
73
+ return { count: ids.length, ids: ids.slice(0, 20), skipped, interim: true };
74
+ }
75
+
76
+ export function ownerRows(cards: readonly TaskCard[], sinceIso: string, ownerFilter?: string): Record<string, OwnerTaskRow> {
77
+ const rows: Record<string, OwnerTaskRow> = {};
78
+ for (const card of cards) {
79
+ if (!card || typeof card.createdAt !== 'string' || card.createdAt < sinceIso) continue;
80
+ const owner = card.seat ? `seat:${card.seat}` : 'unassigned';
81
+ if (ownerFilter && ownerFilter !== 'all' && owner !== ownerFilter && owner !== `seat:${ownerFilter}`) continue;
82
+ const row = rows[owner] ??= { handed: 0, launched: 0, prBound: 0, failed: 0, launchFailed: 0 };
83
+ if (card.status === 'handed') row.handed++;
84
+ else if (card.status === 'launched') row.launched++;
85
+ else if (card.status === 'failed') row.failed++;
86
+ else if (card.status === 'launch-failed') row.launchFailed++;
87
+ if ((card.history ?? []).some((h) => h.event === 'pr-bound')) row.prBound++;
88
+ }
89
+ return rows;
90
+ }
91
+
92
+ /** `[[dd-]hh:]mm:ss` → 초. */
93
+ export function parseEtime(etime: string): number | null {
94
+ const m = /^(?:(\d+)-)?(?:(\d+):)?(\d+):(\d+)$/.exec(etime.trim());
95
+ if (!m) return null;
96
+ return Number(m[1] ?? 0) * 86400 + Number(m[2] ?? 0) * 3600 + Number(m[3]) * 60 + Number(m[4]);
97
+ }
98
+
99
+ const HARNESS_PARENT = /elanous\.mjs .*\bharness (?:say|ask)\b/;
100
+
101
+ /** `ps -axo etime=,command=` 출력 → 하니스 부모 요약(RFC §S «재는 명령»과 같은 술어). */
102
+ export function summarizeParents(psOut: string): ParentsInfo {
103
+ let count = 0; let stale = 0; let oldest = -1;
104
+ for (const line of psOut.split('\n')) {
105
+ const m = /^\s*(\S+)\s+(.*)$/.exec(line);
106
+ if (!m || !HARNESS_PARENT.test(m[2]!)) continue;
107
+ const s = parseEtime(m[1]!);
108
+ count++;
109
+ if (s !== null) { if (s > STALE_PARENT_S) stale++; if (s > oldest) oldest = s; }
110
+ }
111
+ return { count, staleOver6h: stale, oldestHours: oldest < 0 ? null : Math.round(oldest / 360) / 10 };
112
+ }
113
+
114
+ function collectRelease(root: string, version: string | null, schedules: ReleaseSchedule[] | Error, now: number,
115
+ readRuns: (dir: string) => Array<{ runId: string; status: string; startedAt: string; version: string | null; nodes: Array<{ nodeId: string; ok: boolean | null; startedAt?: string }> }> | 'unavailable',
116
+ readList: (v: string) => Checklist): OverviewSnapshot['release'] {
117
+ const versionCell: Cell<string> = version ? okCell(version, 'release schedule(다음 컷)', iso(now)) : unmeasured('release schedule', schedules instanceof Error ? `스케줄 못 읽음: ${errText(schedules)}` : '앞으로의 컷이 없음 — --version 으로 지정');
118
+ let schedule: Cell<ScheduleInfo | 'none'>;
119
+ if (schedules instanceof Error) schedule = unmeasured('release schedule', errText(schedules));
120
+ else if (!version) schedule = okCell('none', 'release schedule', iso(now));
121
+ else {
122
+ const row = schedules.find((s) => s.version === version);
123
+ schedule = okCell(row ? {
124
+ cutAt: row.cutAt, landBy: row.landBy, publishAt: row.publishAt ?? null,
125
+ freezeNow: !!(row.freezeFrom && row.freezeUntil && Date.parse(row.freezeFrom) <= now && now < Date.parse(row.freezeUntil)),
126
+ } : 'none', 'release schedule', iso(now));
127
+ }
128
+ let checklist: Cell<ChecklistCounts>;
129
+ if (!version) checklist = unmeasured('release checklist', '판 미정');
130
+ else {
131
+ try { checklist = okCell(countChecklist(readList(version).items), `release checklist(${version})`, iso(now)); }
132
+ catch (error) { checklist = unmeasured(`release checklist(${version})`, errText(error)); }
133
+ }
134
+ const dir = join(root, 'graph-runs', 'release-loop');
135
+ let run: Cell<ReleaseRunInfo | 'none'>;
136
+ try {
137
+ const runs = readRuns(dir);
138
+ if (runs === 'unavailable') run = unmeasured(dir, 'runs-unavailable');
139
+ else if (runs.length === 0) run = okCell('none', dir, iso(now));
140
+ else {
141
+ const r = runs[0]!;
142
+ const current = r.nodes.find((n) => n.ok === null) ?? null;
143
+ let pidAliveFlag: boolean | null = null;
144
+ try {
145
+ const raw = JSON.parse(readFileSync(join(dir, `${r.runId}.json`), 'utf8')) as { pid?: unknown };
146
+ if (typeof raw.pid === 'number') { try { process.kill(raw.pid, 0); pidAliveFlag = true; } catch (e) { pidAliveFlag = (e as NodeJS.ErrnoException).code === 'EPERM'; } }
147
+ } catch { /* pid 모름 */ }
148
+ const node = current?.nodeId ?? null;
149
+ const waitingOn: ReleaseRunInfo['waitingOn'] = r.status !== 'running' ? 'none'
150
+ : node && /approv/i.test(node) ? 'approval'
151
+ : pidAliveFlag === false ? 'process-gone'
152
+ : node && /gate/i.test(node) ? 'gate' : 'none';
153
+ const startedMs = Date.parse(current?.startedAt ?? r.startedAt);
154
+ run = okCell({ runId: r.runId, status: r.status, currentNode: node, startedAt: r.startedAt, waitingOn,
155
+ elapsedMinutes: Number.isNaN(startedMs) ? null : Math.round((now - startedMs) / 60000) }, `release run ledger(${r.version ?? '?'})`, iso(now));
156
+ }
157
+ } catch (error) { run = unmeasured(dir, errText(error)); }
158
+ return { version: versionCell, run, schedule, checklist };
159
+ }
160
+
161
+ function collectMachines(root: string, ledgerRows: ResourceView[] | Error, poolSpec: string | null, psOut: string | Error, now: number): OverviewSnapshot['machines'] {
162
+ const expected = new Set<string>();
163
+ const pods: PodMemberRow[] = [];
164
+ if (poolSpec) {
165
+ for (const part of poolSpec.split(',').map((p) => p.trim()).filter(Boolean)) {
166
+ const m = /^([^@:#]+)(?:@([^:#]+))?(?::(\d+))?/.exec(part);
167
+ if (!m) continue;
168
+ if (m[2]) expected.add(m[2]);
169
+ pods.push({ context: m[1]!, capacity: m[3] === undefined ? 2 : Number(m[3]),
170
+ occupied: unmeasured('pod pool', '호출 경로 kubectl 금지 — 멤버별 점유는 데몬 백그라운드 측정기(OVW-API) 대기') });
171
+ }
172
+ }
173
+ const hosts: MachineRow[] = [];
174
+ if (ledgerRows instanceof Error) {
175
+ for (const name of expected) hosts.push({ name, load: unmeasured('control ledger', errText(ledgerRows)) });
176
+ } else {
177
+ const machines = ledgerRows.filter((r) => r.kind === 'machine');
178
+ for (const r of machines) expected.add(r.machine);
179
+ for (const name of [...expected].sort()) {
180
+ const row = machines.find((r) => r.machine === name);
181
+ const load = (row?.attrs as { load?: { loadAvg?: number[]; cpuCount?: number; freeMem?: number; totalMem?: number; observedAt?: number } } | undefined)?.load;
182
+ if (!row || !load || !Array.isArray(load.loadAvg)) { hosts.push({ name, load: unmeasured('control ledger heartbeat', '하트비트 없음') }); continue; }
183
+ const observed = typeof load.observedAt === 'number' ? load.observedAt : row.observedAt;
184
+ const [l1, l5, l15] = load.loadAvg;
185
+ const num = (x: unknown): x is number => typeof x === 'number' && Number.isFinite(x);
186
+ if (!num(l1) || !num(l5) || !num(l15) || !num(load.cpuCount) || load.cpuCount <= 0 || !num(load.freeMem) || !num(load.totalMem)) {
187
+ hosts.push({ name, load: unmeasured('control ledger heartbeat', '하트비트 항목 빠짐(loadAvg·cpuCount·freeMem·totalMem)', iso(observed)) });
188
+ continue;
189
+ }
190
+ const age = Math.max(0, Math.round((now - observed) / 1000));
191
+ const value = { load1: l1, load5: l5, load15: l15, cpuCount: load.cpuCount,
192
+ freeGb: load.freeMem / GB, totalGb: load.totalMem / GB, ageSeconds: age };
193
+ hosts.push({ name, load: age > HEARTBEAT_STALE_S
194
+ ? unmeasured('control ledger heartbeat', `낡음(${age}초)`, iso(observed))
195
+ : okCell(value, 'control ledger heartbeat', iso(observed)) });
196
+ }
197
+ }
198
+ const parents: Cell<ParentsInfo> = psOut instanceof Error
199
+ ? unmeasured(`ps(${hostname()})`, `ps-exec 실패: ${errText(psOut)}`)
200
+ : okCell(summarizeParents(psOut), `ps(${hostname()} 로컬만 — 다른 기계는 OVW-MACHINE-HB)`, iso(now));
201
+ void root;
202
+ return { hosts, pods, parents };
203
+ }
204
+
205
+ export interface CollectDeps {
206
+ listSchedules?: () => ReleaseSchedule[];
207
+ listChecklist?: (v: string) => Checklist;
208
+ readRuns?: (dir: string) => ReturnType<Parameters<typeof collectRelease>[4]>;
209
+ readCards?: () => TaskCard[];
210
+ ledger?: () => ResourceView[];
211
+ poolSpec?: () => string | null;
212
+ ps?: () => string;
213
+ gitLog?: (sinceIso: string) => { lines: string[]; observedAt: string } ;
214
+ }
215
+
216
+ async function defaultDeps(root: string): Promise<Required<CollectDeps>> {
217
+ const [{ listSchedules }, { listChecklist }, { readReleaseRuns }, { readTaskAgentState, taskAgentStatePath }, { ResourceLedger }, { getUserConfig }] = await Promise.all([
218
+ import('../release-loop/release-schedule.js'), import('../release-loop/checklist.js'), import('../nexus/api/ops-api.js'),
219
+ import('../task-agent/task-hand.js'), import('../control-plane/ledger.js'), import('../user-config.js'),
220
+ ]);
221
+ return {
222
+ listSchedules: () => listSchedules(),
223
+ listChecklist: (v) => listChecklist(v),
224
+ readRuns: (dir) => readReleaseRuns(null, dir),
225
+ readCards: () => {
226
+ const state = readTaskAgentState<{ tasks?: Record<string, TaskCard> | TaskCard[] }>(taskAgentStatePath());
227
+ const t = state.tasks ?? {};
228
+ return Array.isArray(t) ? t : Object.values(t);
229
+ },
230
+ ledger: () => new ResourceLedger(root).list({ kind: 'machine' }),
231
+ poolSpec: () => {
232
+ const c = getUserConfig() as { harness?: { podPool?: string }; pod?: { pool?: string } };
233
+ return process.env.ELANOUS_POD_POOL?.trim() || c.harness?.podPool || c.pod?.pool || null;
234
+ },
235
+ ps: () => execFileSync('ps', ['-axo', 'etime=,command='], { encoding: 'utf8', timeout: 1_500, maxBuffer: 32 * 1024 * 1024, stdio: ['ignore', 'pipe', 'ignore'] }),
236
+ gitLog: (sinceIso) => {
237
+ const c = getUserConfig() as { harness?: { defaultRepo?: string } };
238
+ const repo = c.harness?.defaultRepo;
239
+ if (!repo) throw new Error('harness.defaultRepo 없음');
240
+ let fetched: number;
241
+ try { fetched = statSync(join(repo, '.git', 'FETCH_HEAD')).mtimeMs; } catch { throw new Error(`FETCH_HEAD 없음(${repo})`); }
242
+ const out = execFileSync('git', ['-C', repo, 'log', 'refs/remotes/origin/main', `--since=${sinceIso}`, '--first-parent', '--format=%cI%x09%s'],
243
+ { encoding: 'utf8', timeout: 1_000, stdio: ['ignore', 'pipe', 'ignore'] });
244
+ return { lines: out.split('\n').filter(Boolean), observedAt: iso(fetched) };
245
+ },
246
+ };
247
+ }
248
+
249
+ /** 착지 원천(git origin/main)의 fetch 가 이보다 낡으면 «못 잼»(낡음). */
250
+ const LANDINGS_STALE_S = 30 * 60;
251
+
252
+ export async function collectOverview(opts: CollectOptions, injected: CollectDeps = {}): Promise<OverviewSnapshot> {
253
+ const now = opts.now ?? Date.now();
254
+ const root = opts.root ?? effectiveInstanceRoot();
255
+ const deps = { ...(await defaultDeps(root)), ...injected };
256
+ const seatDir = opts.seatEraDir ?? seatEraFeederDir();
257
+ const sinceIso = iso(now - opts.sinceMs);
258
+
259
+ let schedules: ReleaseSchedule[] | Error;
260
+ try { schedules = deps.listSchedules(); } catch (error) { schedules = error instanceof Error ? error : new Error(String(error)); }
261
+ const version = pickVersion(opts.version, schedules instanceof Error ? [] : schedules, now);
262
+ const release = collectRelease(root, version, schedules, now, deps.readRuns, deps.listChecklist);
263
+
264
+ // 일꾼 — 오늘(KST 0시) 이후 카드.
265
+ const kstMidnight = iso(Math.floor((now + 9 * 3600_000) / 86400_000) * 86400_000 - 9 * 3600_000);
266
+ let cards: TaskCard[] | Error;
267
+ try { cards = deps.readCards(); } catch (error) { cards = error instanceof Error ? error : new Error(String(error)); }
268
+ const byOwner: Cell<Record<string, OwnerTaskRow>> = cards instanceof Error
269
+ ? unmeasured('task-agent ledger', errText(cards))
270
+ : okCell(ownerRows(cards, kstMidnight, opts.owner), `task-agent ledger(오늘 KST · owner=seat 키)`, iso(now));
271
+ const done = unmeasured<number>('task-agent ledger', '착지 판정은 PR 조회가 필요 — owner 모델(OVW-OWNER)·card-evidence 연결 전');
272
+
273
+ // 발사 가능 — 열린 판(앞으로의 컷) 전부.
274
+ let launchable: Cell<LaunchableInfo>;
275
+ if (schedules instanceof Error) launchable = unmeasured('release checklist', `스케줄 못 읽음: ${errText(schedules)}`);
276
+ else if (cards instanceof Error) launchable = unmeasured('task-agent ledger', `넘김 목록 못 읽음: ${errText(cards)}`);
277
+ else {
278
+ try {
279
+ const open = schedules.filter((s) => Date.parse(s.cutAt) > now).map((s) => s.version);
280
+ if (version && !open.includes(version)) open.push(version);
281
+ const lists = open.map((v) => deps.listChecklist(v));
282
+ const handed = new Set<string>([...cards.map((c) => c.checklistId).filter((x): x is string => typeof x === 'string'), ...readSeatEraHanded(seatDir)]);
283
+ launchable = okCell(launchableCells(lists, handed), `release checklist(${open.join(',')}) ⊕ task-agent ⊕ ${SEAT_ERA_SOURCE} handed · 임시 술어`, iso(now));
284
+ } catch (error) { launchable = unmeasured('release checklist', errText(error)); }
285
+ }
286
+
287
+ // 빈 자리 — 자리 시대 디스패처 로그(임시) · 호출 경로 kubectl 0.
288
+ const capLine = readDispatcherCapacity(seatDir, now);
289
+ let capacity: Cell<CapacityInfo>;
290
+ if ('error' in capLine) capacity = unmeasured(SEAT_ERA_SOURCE, capLine.error);
291
+ else if (capLine.ageSeconds > DISPATCHER_STALE_S) capacity = unmeasured(SEAT_ERA_SOURCE, `낡음(${Math.round(capLine.ageSeconds / 60)}분)`, capLine.at);
292
+ else {
293
+ const free = Math.max(0, capLine.podTarget - capLine.podUsed) + Math.max(0, capLine.localMax - capLine.localUsed);
294
+ capacity = okCell({ podUsed: capLine.podUsed, podTarget: capLine.podTarget, localUsed: capLine.localUsed, localMax: capLine.localMax, free }, `${SEAT_ERA_SOURCE} dispatcher-OP.out`, capLine.at);
295
+ }
296
+ const tasks = { byOwner, done, launchable, capacity };
297
+
298
+ let ledgerRows: ResourceView[] | Error;
299
+ try { ledgerRows = deps.ledger(); } catch (error) { ledgerRows = error instanceof Error ? error : new Error(String(error)); }
300
+ let poolSpec: string | null = null;
301
+ try { poolSpec = deps.poolSpec(); } catch { poolSpec = null; }
302
+ let psOut: string | Error;
303
+ try { psOut = deps.ps(); } catch (error) { psOut = error instanceof Error ? error : new Error(String(error)); }
304
+ const machines = collectMachines(root, ledgerRows, poolSpec, psOut, now);
305
+
306
+ let landings: Cell<{ count: number; recent: string[] }>;
307
+ try {
308
+ const { lines, observedAt } = deps.gitLog(sinceIso);
309
+ const age = Math.round((now - Date.parse(observedAt)) / 1000);
310
+ landings = age > LANDINGS_STALE_S
311
+ ? unmeasured('git origin/main(로컬 fetch)', `fetch 낡음(${Math.round(age / 60)}분)`, observedAt)
312
+ : okCell({ count: lines.length, recent: lines.slice(0, 5).map((l) => l.split('\t')[1] ?? l) }, `git origin/main(로컬 fetch ${Math.round(age / 60)}분 전)`, observedAt);
313
+ } catch (error) { landings = unmeasured('git origin/main(로컬 fetch)', errText(error)); }
314
+
315
+ const loops: LoopRow[] = [];
316
+ const productBeat = (name: string, paths: string[]): void => {
317
+ let newest = -1;
318
+ for (const p of paths) {
319
+ try {
320
+ const st = statSync(p);
321
+ if (st.isDirectory()) { for (const f of readdirSync(p)) { try { newest = Math.max(newest, statSync(join(p, f)).mtimeMs); } catch { /* skip */ } } }
322
+ else newest = Math.max(newest, st.mtimeMs);
323
+ } catch { /* 없음 */ }
324
+ }
325
+ loops.push({ name, era: 'product', staleAfterSeconds: null,
326
+ beat: newest < 0 ? unmeasured(paths.join(','), '원장 없음(계측 없음 — «죽었다» 아님)')
327
+ : okCell({ lastAt: iso(newest), ageSeconds: Math.max(0, Math.round((now - newest) / 1000)), alive: null }, `${paths.join(',')} mtime`, iso(newest)) });
328
+ };
329
+ productBeat('orchestrator', [join(root, 'orchestrator')]);
330
+ productBeat('steward', [join(root, 'steward', 'readiness.jsonl'), join(root, 'steward', 'streak.json')]);
331
+ for (const b of readDispatcherBeats(seatDir, now)) {
332
+ loops.push({ name: `dispatcher-${b.seat}`, era: 'seat-era', staleAfterSeconds: b.seat === 'OP' ? DISPATCHER_STALE_S : null,
333
+ beat: okCell({ lastAt: b.lastAt, ageSeconds: b.ageSeconds, alive: b.alive }, `${SEAT_ERA_SOURCE} dispatcher-${b.seat}.pid`, b.lastAt) });
334
+ }
335
+
336
+ return { generatedAt: iso(now), instanceRoot: root, since: opts.sinceLabel, release, tasks, machines, landings, loops };
337
+ }
338
+
339
+ export function parseSince(raw: string | undefined): { ms: number; label: string } {
340
+ const v = (raw ?? '1h').trim();
341
+ const m = /^(\d+)\s*(m|h|d)$/.exec(v);
342
+ if (!m) throw new Error(`--since 형식: <n>m|h|d (받음: ${v})`);
343
+ const n = Number(m[1]);
344
+ return { ms: n * (m[2] === 'm' ? 60_000 : m[2] === 'h' ? 3_600_000 : 86_400_000), label: `최근 ${v}` };
345
+ }
@@ -0,0 +1,90 @@
1
+ /**
2
+ * ⚠️ 자리 시대 원천(제품 «밖») — OPS-OVERVIEW-CLI 첫 조각의 «임시» 읽기 전용 리더.
3
+ *
4
+ * RFC §A3 는 overview 의 원천을 제품 원장·API 로만 둔다. 그런데 디스패처의 박동·Pod 사용 수·넘김 목록은
5
+ * 아직 `~/elanous-hq/seat-state/OP/feeder/` 의 PID 파일·stdout 로그에만 있다(OVW-DISPATCH-LEDGER 가 원장으로 옮긴다).
6
+ * 그때까지 이 파일 «하나»에 가둬 두고, 출력에는 항상 «자리 시대 원천» 표지를 붙인다. 옮겨지면 이 파일을 지운다.
7
+ * ⛔ 쓰지 않는다 · ⛔ ssh 하지 않는다 · 파일이 없으면 «못 잼»(0 아님).
8
+ */
9
+ import { readdirSync, readFileSync, statSync } from 'node:fs';
10
+ import { homedir } from 'node:os';
11
+ import { join } from 'node:path';
12
+
13
+ export const SEAT_ERA_SOURCE = '자리 시대 원천(feeder)';
14
+
15
+ export function seatEraFeederDir(home: string = homedir()): string {
16
+ return join(home, 'elanous-hq', 'seat-state', 'OP', 'feeder');
17
+ }
18
+
19
+ export interface DispatcherBeat { seat: string; pid: number | null; alive: boolean | null; lastAt: string; ageSeconds: number }
20
+
21
+ /** `dispatcher-<자리>.pid` 마다 하나 — 박동 시각은 `.out` 이 있으면 그 mtime, 없으면 PID 파일 mtime. */
22
+ export function readDispatcherBeats(dir: string, now: number, isAlive: (pid: number) => boolean = pidAlive): DispatcherBeat[] {
23
+ let names: string[];
24
+ try { names = readdirSync(dir); } catch { return []; }
25
+ const out: DispatcherBeat[] = [];
26
+ for (const name of names) {
27
+ const m = /^dispatcher-([A-Za-z]+)\.pid$/.exec(name);
28
+ if (!m) continue;
29
+ const seat = m[1]!;
30
+ let pid: number | null = null;
31
+ try { const n = Number(readFileSync(join(dir, name), 'utf8').trim()); pid = Number.isInteger(n) && n > 0 ? n : null; } catch { /* 못 읽음 */ }
32
+ let mtime: number;
33
+ try { mtime = statSync(join(dir, `dispatcher-${seat}.out`)).mtimeMs; } catch {
34
+ try { mtime = statSync(join(dir, name)).mtimeMs; } catch { continue; }
35
+ }
36
+ out.push({ seat, pid, alive: pid === null ? null : isAlive(pid), lastAt: new Date(mtime).toISOString(), ageSeconds: Math.max(0, Math.round((now - mtime) / 1000)) });
37
+ }
38
+ return out.sort((a, b) => a.seat.localeCompare(b.seat));
39
+ }
40
+
41
+ export function pidAlive(pid: number): boolean {
42
+ try { process.kill(pid, 0); return true; } catch (error) { return (error as NodeJS.ErrnoException).code === 'EPERM'; }
43
+ }
44
+
45
+ export interface DispatcherCapacityLine { at: string; podUsed: number; podTarget: number; localUsed: number; localMax: number }
46
+
47
+ /** 디스패처 로그의 마지막 «Pod a/b · 로컬 c/d» 줄. 줄 머리 `HH:MM:SS` 는 로그 파일 mtime 의 날짜(KST)로 읽는다. */
48
+ export function parseDispatcherCapacity(text: string): Omit<DispatcherCapacityLine, 'at'> & { hhmmss: string } | null {
49
+ const lines = text.split('\n');
50
+ for (let i = lines.length - 1; i >= 0; i--) {
51
+ const m = /^(\d{2}:\d{2}:\d{2}) .*Pod (\d+)(?:\/(\d+))? · 로컬 (\d+)(?:\/(\d+))?/.exec(lines[i]!);
52
+ if (!m || m[3] === undefined) continue;
53
+ return { hhmmss: m[1]!, podUsed: Number(m[2]), podTarget: Number(m[3]), localUsed: Number(m[4]), localMax: m[5] === undefined ? 0 : Number(m[5]) };
54
+ }
55
+ return null;
56
+ }
57
+
58
+ export function readDispatcherCapacity(dir: string, now: number = Date.now()): (DispatcherCapacityLine & { ageSeconds: number }) | { error: string } {
59
+ const path = join(dir, 'dispatcher-OP.out');
60
+ let text: string; let mtime: number;
61
+ try { text = readFileSync(path, 'utf8'); mtime = statSync(path).mtimeMs; } catch (error) { return { error: `디스패처 로그 없음(${(error as NodeJS.ErrnoException).code ?? 'read'})` }; }
62
+ const parsed = parseDispatcherCapacity(text);
63
+ if (!parsed) return { error: '디스패처 로그에 «Pod a/b» 줄 없음' };
64
+ const at = lineTime(parsed.hhmmss, mtime);
65
+ return { at: new Date(at).toISOString(), ageSeconds: Math.max(0, Math.round((now - at) / 1000)),
66
+ podUsed: parsed.podUsed, podTarget: parsed.podTarget, localUsed: parsed.localUsed, localMax: parsed.localMax };
67
+ }
68
+
69
+ /**
70
+ * 줄 머리 `HH:MM:SS`(KST)의 절대 시각 — 날짜는 로그 mtime 의 KST 날짜이고, 그 시각이 mtime 보다 뒤면 전날이다.
71
+ * ⛔ mtime 을 관측 시각으로 쓰지 않는다 — 용량 줄 뒤에 다른 줄이 붙으면 낡은 값이 새것처럼 보인다.
72
+ */
73
+ export function lineTime(hhmmss: string, mtimeMs: number): number {
74
+ const [h, m, s] = hhmmss.split(':').map(Number) as [number, number, number];
75
+ const KST = 9 * 3600_000;
76
+ const dayStartKst = Math.floor((mtimeMs + KST) / 86400_000) * 86400_000 - KST;
77
+ let t = dayStartKst + ((h * 60 + m) * 60 + s) * 1000;
78
+ if (t > mtimeMs + 1000) t -= 86400_000;
79
+ return t;
80
+ }
81
+
82
+ /** 디스패처가 손으로 쌓는 넘김 목록(`handed.txt`) — «없음»(ENOENT)만 빈 집합, 그 밖의 읽기 실패는 던진다(넘김 여부를 모르면 발사 가능을 셀 수 없다). */
83
+ export function readSeatEraHanded(dir: string): Set<string> {
84
+ let text: string;
85
+ try { text = readFileSync(join(dir, 'handed.txt'), 'utf8'); } catch (error) {
86
+ if ((error as NodeJS.ErrnoException).code === 'ENOENT') return new Set();
87
+ throw new Error(`handed.txt 못 읽음(${(error as NodeJS.ErrnoException).code ?? 'read'})`);
88
+ }
89
+ return new Set(text.split(/\s+/).filter(Boolean));
90
+ }