elanous 0.2.20 → 0.2.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (456) 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/aBi6EyMtLsrl7J8eH4kF7/_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-08b716476f1d50af.js +1 -0
  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/{2489-ff55a35b202c0f94.js → 2489-d238d3e2eb5b2bf5.js} +1 -1
  27. package/apps/pwa/out/_next/static/chunks/2549-a8515c86f9e8cb96.js +1 -0
  28. package/apps/pwa/out/_next/static/chunks/2655.72cc6a97276c5acc.js +1 -0
  29. package/apps/pwa/out/_next/static/chunks/2743-ef6e8c3ced74cce5.js +1 -0
  30. package/apps/pwa/out/_next/static/chunks/2840-5458ad50c49cac6a.js +1 -0
  31. package/apps/pwa/out/_next/static/chunks/2856.f75d5baef7f49b74.js +1 -0
  32. package/apps/pwa/out/_next/static/chunks/3344.0de4b0ec5a454b7a.js +1 -0
  33. package/apps/pwa/out/_next/static/chunks/335.a13f339e97893437.js +94 -0
  34. package/apps/pwa/out/_next/static/chunks/3394.646348f34b2500be.js +1 -0
  35. package/apps/pwa/out/_next/static/chunks/3443.6276909b53d72d53.js +1 -0
  36. package/apps/pwa/out/_next/static/chunks/3495.abd4254d808bf2dc.js +1 -0
  37. package/apps/pwa/out/_next/static/chunks/3522-1de1319d028ac89c.js +25 -0
  38. package/apps/pwa/out/_next/static/chunks/3721.ba5b82db0d9a5572.js +1 -0
  39. package/apps/pwa/out/_next/static/chunks/3857-5985236d9307f676.js +1 -0
  40. package/apps/pwa/out/_next/static/chunks/3919.9ff7dc25afefee33.js +74 -0
  41. package/apps/pwa/out/_next/static/chunks/3998.bd9a69d9b50e9bd2.js +1 -0
  42. package/apps/pwa/out/_next/static/chunks/4003.ab6db1dc266576c9.js +1 -0
  43. package/apps/pwa/out/_next/static/chunks/4216.e8ac94b9fe6b96ab.js +1 -0
  44. package/apps/pwa/out/_next/static/chunks/4327.c44dc2b55cb43ee5.js +34 -0
  45. package/apps/pwa/out/_next/static/chunks/4401.94257d497b6d26bc.js +1 -0
  46. package/apps/pwa/out/_next/static/chunks/4559.17ca2cdbaa8d414f.js +115 -0
  47. package/apps/pwa/out/_next/static/chunks/4588.ad3364cb7fb97ada.js +86 -0
  48. package/apps/pwa/out/_next/static/chunks/4706.97e734bd4c9816ca.js +1 -0
  49. package/apps/pwa/out/_next/static/chunks/4982.6e44352f751b9a1b.js +77 -0
  50. package/apps/pwa/out/_next/static/chunks/5342.49c5c5a530e62b50.js +1 -0
  51. package/apps/pwa/out/_next/static/chunks/5360-08a80abdf3a4da99.js +1 -0
  52. package/apps/pwa/out/_next/static/chunks/5431.66c58ab1327ee0b7.js +1 -0
  53. package/apps/pwa/out/_next/static/chunks/5633-d6852385000738ef.js +1 -0
  54. package/apps/pwa/out/_next/static/chunks/5717.bcfcb96e701b82e0.js +90 -0
  55. package/apps/pwa/out/_next/static/chunks/5755-30d102586789bf7f.js +1 -0
  56. package/apps/pwa/out/_next/static/chunks/5957.3f1a4012388dd962.js +1 -0
  57. package/apps/pwa/out/_next/static/chunks/5977-5b9e8a45a5b0c823.js +1 -0
  58. package/apps/pwa/out/_next/static/chunks/602.9bf02ac8e9502598.js +1 -0
  59. package/apps/pwa/out/_next/static/chunks/6020.3a1b347e2d2dfacc.js +8 -0
  60. package/apps/pwa/out/_next/static/chunks/{6158.af2e93b7adff709b.js → 6158.566c890adc06a459.js} +1 -1
  61. package/apps/pwa/out/_next/static/chunks/6375.fce1d174ba6d0324.js +64 -0
  62. package/apps/pwa/out/_next/static/chunks/6546.92a828aef441271f.js +1 -0
  63. package/apps/pwa/out/_next/static/chunks/658.957fdb0a8c6c6c8e.js +287 -0
  64. package/apps/pwa/out/_next/static/chunks/6706.323590aabb8b1293.js +226 -0
  65. package/apps/pwa/out/_next/static/chunks/6747.b67947f293a4c02d.js +1 -0
  66. package/apps/pwa/out/_next/static/chunks/6767.8e6c5f6e744fcd9f.js +38 -0
  67. package/apps/pwa/out/_next/static/chunks/6868.000b24a3045c5574.js +1 -0
  68. package/apps/pwa/out/_next/static/chunks/6946.bba74e9c96050f30.js +1 -0
  69. package/apps/pwa/out/_next/static/chunks/7033.a558f1b2f6912a53.js +1 -0
  70. package/apps/pwa/out/_next/static/chunks/7065.46029c26c53eac50.js +1 -0
  71. package/apps/pwa/out/_next/static/chunks/7087.6fae577d09249bc1.js +1 -0
  72. package/apps/pwa/out/_next/static/chunks/7175-350f4ce2873bc038.js +1 -0
  73. package/apps/pwa/out/_next/static/chunks/722.de8d9801e0f6d414.js +41 -0
  74. package/apps/pwa/out/_next/static/chunks/7497.57981e9d7a75a52a.js +24 -0
  75. package/apps/pwa/out/_next/static/chunks/7630.2b9a68659053ff4e.js +193 -0
  76. package/apps/pwa/out/_next/static/chunks/7672.4af122ad892117c7.js +1 -0
  77. package/apps/pwa/out/_next/static/chunks/771.6ab889a233e7dae7.js +1 -0
  78. package/apps/pwa/out/_next/static/chunks/7899.597af4a9996c907f.js +1 -0
  79. package/apps/pwa/out/_next/static/chunks/79b78388.663188133449d707.js +1 -0
  80. package/apps/pwa/out/_next/static/chunks/8096.f7da0776cfde6300.js +1 -0
  81. package/apps/pwa/out/_next/static/chunks/8194.484f51e273d9abd2.js +217 -0
  82. package/apps/pwa/out/_next/static/chunks/8769.815ca69879f608b4.js +104 -0
  83. package/apps/pwa/out/_next/static/chunks/8857-aa1cc4ac90af4d40.js +1 -0
  84. package/apps/pwa/out/_next/static/chunks/88729267.9c6c000157c83f2f.js +62 -0
  85. package/apps/pwa/out/_next/static/chunks/8f835b4f.048eeb02299e3e12.js +1 -0
  86. package/apps/pwa/out/_next/static/chunks/9068.5f2764fccf86c1b2.js +93 -0
  87. package/apps/pwa/out/_next/static/chunks/9247-55bcfbb0e8057f59.js +1 -0
  88. package/apps/pwa/out/_next/static/chunks/9451.ebfb0dcc9c02250f.js +23 -0
  89. package/apps/pwa/out/_next/static/chunks/9559.e648369b1d57cf9a.js +62 -0
  90. package/apps/pwa/out/_next/static/chunks/9832.2ab2e818013574c7.js +1 -0
  91. package/apps/pwa/out/_next/static/chunks/a9f2b54d-40c16bd0efdb04b8.js +1 -0
  92. package/apps/pwa/out/_next/static/chunks/app/approvals/{page-c855c24de7db74fe.js → page-88459e0903da2803.js} +1 -1
  93. package/apps/pwa/out/_next/static/chunks/app/autopilot/{page-85b316bb4235980b.js → page-d85c43f46f58716b.js} +1 -1
  94. package/apps/pwa/out/_next/static/chunks/app/board/{page-9b2b26a33cb2cd42.js → page-4e645600579ae952.js} +1 -1
  95. package/apps/pwa/out/_next/static/chunks/app/botlab/{page-c9955fde67b1bcb5.js → page-87edbfd19117823b.js} +1 -1
  96. package/apps/pwa/out/_next/static/chunks/app/bots/page-7f0c2b7801ac6a54.js +1 -0
  97. package/apps/pwa/out/_next/static/chunks/app/ceo/page-bf1f030a9d1adffd.js +1 -0
  98. package/apps/pwa/out/_next/static/chunks/app/chat/{page-4ba4abe096b6c2a8.js → page-cbaf66b1e1804adf.js} +1 -1
  99. package/apps/pwa/out/_next/static/chunks/app/consult/{page-575d4a8b4f7becb2.js → page-80ed511e02c81412.js} +1 -1
  100. package/apps/pwa/out/_next/static/chunks/app/control/{page-f5d56ff5f45ad164.js → page-9f5610caaa06178c.js} +1 -1
  101. package/apps/pwa/out/_next/static/chunks/app/decisions/{page-2260e9eef0357421.js → page-ec8f3575e339de74.js} +1 -1
  102. package/apps/pwa/out/_next/static/chunks/app/design-check/page-86d753e460f1a989.js +1 -0
  103. package/apps/pwa/out/_next/static/chunks/app/editor/page-4166a7d51cf0a57d.js +1 -0
  104. package/apps/pwa/out/_next/static/chunks/app/exec/{page-466bf0ee16ad1432.js → page-2a19c5cd3b562580.js} +1 -1
  105. package/apps/pwa/out/_next/static/chunks/app/field/{page-a23bc71713bcc08f.js → page-b6a09cde2e07521a.js} +1 -1
  106. package/apps/pwa/out/_next/static/chunks/app/inside/page-f7eae1518ca0c353.js +1 -0
  107. package/apps/pwa/out/_next/static/chunks/app/intake/page-456a8573ad811381.js +1 -0
  108. package/apps/pwa/out/_next/static/chunks/app/layout-8462d6a543cbb2d9.js +1 -0
  109. package/apps/pwa/out/_next/static/chunks/app/live/page-2974cdfb87c0f9c9.js +1 -0
  110. package/apps/pwa/out/_next/static/chunks/app/live-run/page-f927ffa25f3c86d1.js +1 -0
  111. package/apps/pwa/out/_next/static/chunks/app/loops/{page-35bfec2e631d3eae.js → page-abd86cd832aaa011.js} +1 -1
  112. package/apps/pwa/out/_next/static/chunks/app/market/page-79daf68f6256adaa.js +1 -0
  113. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/{page-f9ccb1b1adbea14d.js → page-8c3f168fc3f91939.js} +1 -1
  114. package/apps/pwa/out/_next/static/chunks/app/morning/{page-ec102c9b8b691e37.js → page-786fb4504d0f2fa9.js} +1 -1
  115. package/apps/pwa/out/_next/static/chunks/app/not-found-75209f14597dcb8f.js +1 -0
  116. package/apps/pwa/out/_next/static/chunks/app/observatory/page-bf30affe3a85af7e.js +1 -0
  117. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-1187a44b7515434b.js +1 -0
  118. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-3d0910185a66ed54.js +1 -0
  119. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-f401ee1dd5a26e25.js +1 -0
  120. package/apps/pwa/out/_next/static/chunks/app/outputs/page-5dfe7604fd842784.js +1 -0
  121. package/apps/pwa/out/_next/static/chunks/app/page-76cf838050a8d681.js +1 -0
  122. package/apps/pwa/out/_next/static/chunks/app/reflection/{page-db03ae3589a81c9e.js → page-7ec8e289b4c14df6.js} +1 -1
  123. package/apps/pwa/out/_next/static/chunks/app/scheduler/{page-4a6463b97f0ad94f.js → page-8c8ac17d52be2a79.js} +1 -1
  124. package/apps/pwa/out/_next/static/chunks/app/sessions/{page-75bffecc9aa1004e.js → page-d359ab048d63a9d9.js} +1 -1
  125. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-b8b345c880bf78d1.js +1 -0
  126. package/apps/pwa/out/_next/static/chunks/app/settings/{page-31d5b875760bb62d.js → page-f151cc10ad026940.js} +1 -1
  127. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-9875f103057da433.js +1 -0
  128. package/apps/pwa/out/_next/static/chunks/app/setup/page-9789328af9a9aa62.js +1 -0
  129. package/apps/pwa/out/_next/static/chunks/app/share/{page-46d5201d2207a511.js → page-bf495a1e284f4e00.js} +1 -1
  130. package/apps/pwa/out/_next/static/chunks/app/showroom/page-a263d94bca4be22a.js +1 -0
  131. package/apps/pwa/out/_next/static/chunks/app/tasks/page-3962eab8ec58d16e.js +1 -0
  132. package/apps/pwa/out/_next/static/chunks/app/term/{page-cf0a4b4abead71ea.js → page-9abcfac07e02a542.js} +1 -1
  133. package/apps/pwa/out/_next/static/chunks/app/today/page-bee49f927c333abb.js +1 -0
  134. package/apps/pwa/out/_next/static/chunks/app/trace/page-bac2d56dcb02d955.js +1 -0
  135. package/apps/pwa/out/_next/static/chunks/app/vault/page-937cbcc13176295c.js +1 -0
  136. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-d09495594f7f480d.js +1 -0
  137. package/apps/pwa/out/_next/static/chunks/app/workflows/{page-0acc8e198f7b0dfb.js → page-69cb93d1a2e57e0e.js} +1 -1
  138. package/apps/pwa/out/_next/static/chunks/app/workspace/{page-34882c84f84021c5.js → page-72ec543928d67c3e.js} +1 -1
  139. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-82906eeda0186a7b.js +1 -0
  140. package/apps/pwa/out/_next/static/chunks/d70879db.c66c56a95a91efc6.js +66 -0
  141. package/apps/pwa/out/_next/static/chunks/f8626309.a4e5d362b1d55e59.js +1 -0
  142. package/apps/pwa/out/_next/static/chunks/webpack-f2b4016b9b981bc8.js +1 -0
  143. package/apps/pwa/out/_next/static/css/{d3427996da11e480.css → b9d8151e48998417.css} +1 -1
  144. package/apps/pwa/out/_next/static/css/f2bd5257e6af88d8.css +1 -0
  145. package/apps/pwa/out/approvals/index.html +1 -1
  146. package/apps/pwa/out/approvals/index.txt +14 -14
  147. package/apps/pwa/out/autopilot/index.html +1 -1
  148. package/apps/pwa/out/autopilot/index.txt +14 -14
  149. package/apps/pwa/out/board/index.html +1 -1
  150. package/apps/pwa/out/board/index.txt +14 -14
  151. package/apps/pwa/out/botlab/index.html +1 -1
  152. package/apps/pwa/out/botlab/index.txt +14 -14
  153. package/apps/pwa/out/bots/index.html +1 -1
  154. package/apps/pwa/out/bots/index.txt +14 -14
  155. package/apps/pwa/out/ceo/index.html +1 -1
  156. package/apps/pwa/out/ceo/index.txt +14 -14
  157. package/apps/pwa/out/chat/index.html +1 -1
  158. package/apps/pwa/out/chat/index.txt +14 -14
  159. package/apps/pwa/out/consult/index.html +1 -1
  160. package/apps/pwa/out/consult/index.txt +14 -14
  161. package/apps/pwa/out/control/index.html +1 -1
  162. package/apps/pwa/out/control/index.txt +14 -14
  163. package/apps/pwa/out/decisions/index.html +1 -1
  164. package/apps/pwa/out/decisions/index.txt +14 -14
  165. package/apps/pwa/out/design-check/index.html +1 -1
  166. package/apps/pwa/out/design-check/index.txt +14 -14
  167. package/apps/pwa/out/editor/index.html +1 -1
  168. package/apps/pwa/out/editor/index.txt +15 -15
  169. package/apps/pwa/out/exec/index.html +1 -1
  170. package/apps/pwa/out/exec/index.txt +14 -14
  171. package/apps/pwa/out/field/index.html +1 -1
  172. package/apps/pwa/out/field/index.txt +14 -14
  173. package/apps/pwa/out/healthz +1 -1
  174. package/apps/pwa/out/index.html +1 -1
  175. package/apps/pwa/out/index.txt +14 -14
  176. package/apps/pwa/out/inside/index.html +1 -1
  177. package/apps/pwa/out/inside/index.txt +15 -15
  178. package/apps/pwa/out/intake/index.html +1 -1
  179. package/apps/pwa/out/intake/index.txt +14 -14
  180. package/apps/pwa/out/live/index.html +1 -1
  181. package/apps/pwa/out/live/index.txt +14 -14
  182. package/apps/pwa/out/live-run/index.html +1 -0
  183. package/apps/pwa/out/live-run/index.txt +35 -0
  184. package/apps/pwa/out/loops/index.html +1 -1
  185. package/apps/pwa/out/loops/index.txt +14 -14
  186. package/apps/pwa/out/market/index.html +1 -1
  187. package/apps/pwa/out/market/index.txt +14 -14
  188. package/apps/pwa/out/missions/placeholder/index.html +1 -1
  189. package/apps/pwa/out/missions/placeholder/index.txt +14 -14
  190. package/apps/pwa/out/morning/index.html +1 -1
  191. package/apps/pwa/out/morning/index.txt +14 -14
  192. package/apps/pwa/out/observatory/index.html +1 -1
  193. package/apps/pwa/out/observatory/index.txt +14 -14
  194. package/apps/pwa/out/ops/checklist/index.html +1 -1
  195. package/apps/pwa/out/ops/checklist/index.txt +14 -14
  196. package/apps/pwa/out/ops/release/index.html +1 -1
  197. package/apps/pwa/out/ops/release/index.txt +14 -14
  198. package/apps/pwa/out/ops/seats/index.html +1 -1
  199. package/apps/pwa/out/ops/seats/index.txt +14 -14
  200. package/apps/pwa/out/outputs/index.html +1 -1
  201. package/apps/pwa/out/outputs/index.txt +14 -14
  202. package/apps/pwa/out/reflection/index.html +1 -1
  203. package/apps/pwa/out/reflection/index.txt +15 -15
  204. package/apps/pwa/out/scheduler/index.html +1 -1
  205. package/apps/pwa/out/scheduler/index.txt +14 -14
  206. package/apps/pwa/out/sessions/index.html +1 -1
  207. package/apps/pwa/out/sessions/index.txt +14 -14
  208. package/apps/pwa/out/settings/devices/index.html +1 -1
  209. package/apps/pwa/out/settings/devices/index.txt +14 -14
  210. package/apps/pwa/out/settings/index.html +1 -1
  211. package/apps/pwa/out/settings/index.txt +14 -14
  212. package/apps/pwa/out/setup/done/index.html +1 -1
  213. package/apps/pwa/out/setup/done/index.txt +14 -14
  214. package/apps/pwa/out/setup/index.html +1 -1
  215. package/apps/pwa/out/setup/index.txt +14 -14
  216. package/apps/pwa/out/share/index.html +1 -1
  217. package/apps/pwa/out/share/index.txt +14 -14
  218. package/apps/pwa/out/showroom/index.html +1 -1
  219. package/apps/pwa/out/showroom/index.txt +14 -14
  220. package/apps/pwa/out/tasks/index.html +1 -1
  221. package/apps/pwa/out/tasks/index.txt +14 -14
  222. package/apps/pwa/out/term/index.html +1 -1
  223. package/apps/pwa/out/term/index.txt +14 -14
  224. package/apps/pwa/out/today/index.html +1 -1
  225. package/apps/pwa/out/today/index.txt +14 -14
  226. package/apps/pwa/out/trace/index.html +1 -1
  227. package/apps/pwa/out/trace/index.txt +14 -14
  228. package/apps/pwa/out/vault/index.html +1 -1
  229. package/apps/pwa/out/vault/index.txt +14 -14
  230. package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
  231. package/apps/pwa/out/workflows/chat-ui/index.txt +14 -14
  232. package/apps/pwa/out/workflows/index.html +1 -1
  233. package/apps/pwa/out/workflows/index.txt +14 -14
  234. package/apps/pwa/out/workspace/index.html +1 -1
  235. package/apps/pwa/out/workspace/index.txt +14 -14
  236. package/apps/pwa/out/worktrees/index.html +1 -1
  237. package/apps/pwa/out/worktrees/index.txt +14 -14
  238. package/graphs/catalog/catalog.yaml +1 -1
  239. package/graphs/drafts/recipes.yaml +6 -6
  240. package/graphs/implement-loop.yaml +277 -5
  241. package/graphs/test-diet/recipes.yaml +3 -0
  242. package/graphs/test-diet/test-diet.yaml +3 -1
  243. package/package.json +1 -1
  244. package/scripts/coord-channel-watch.sh +31 -4
  245. package/scripts/docs-lint.ts +4 -1
  246. package/scripts/f-session-monitor.ts +69 -0
  247. package/scripts/hq/standby-snapshot.ts +7 -3
  248. package/scripts/knowledge-compress.ts +11 -0
  249. package/scripts/marketing/generate-ax-brief.ts +18 -1
  250. package/scripts/measure-nl-routing-corpus.ts +11 -3
  251. package/scripts/nightly-docops.ts +90 -3
  252. package/scripts/release-loop/gate-node.ts +5 -5
  253. package/scripts/release-loop/shard-plan.ts +50 -5
  254. package/scripts/research-coo-admin-dates.ts +39 -0
  255. package/scripts/rhythm/daily.ts +2 -1
  256. package/scripts/skills/public-check.ts +7 -2
  257. package/scripts/smart-merge/resync-shadow.ts +5 -3
  258. package/scripts/test-diet/lib.ts +81 -1
  259. package/scripts/test-diet/node.ts +48 -2
  260. package/scripts/verify-coo-admin-evidence.ts +83 -0
  261. package/src/acp/client.ts +4 -2
  262. package/src/agent/run-context.ts +4 -0
  263. package/src/agent-env/profile.ts +25 -5
  264. package/src/agent-mission/driver.ts +26 -12
  265. package/src/agent-mission/supervisor-metrics.ts +17 -2
  266. package/src/agent-substrate/acp-reviewer.ts +12 -5
  267. package/src/agent-substrate/pr-reviewer.ts +11 -4
  268. package/src/agent-substrate/review-intent.ts +102 -3
  269. package/src/autopilot/build/llm-conflict-merge.ts +22 -8
  270. package/src/autopilot/pr-manager.ts +18 -8
  271. package/src/away/release-watch.ts +47 -8
  272. package/src/briefing/brief-items.ts +104 -11
  273. package/src/browser-cdp/client.ts +10 -5
  274. package/src/chat/index.ts +1 -0
  275. package/src/cli/brief-cli.ts +20 -2
  276. package/src/cli/config-classify.ts +83 -0
  277. package/src/cli/control-cli.ts +14 -0
  278. package/src/cli/decisions-cli.ts +26 -16
  279. package/src/cli/logs-model-input.ts +1 -1
  280. package/src/cli/ops-cli.ts +11 -0
  281. package/src/cli/ops-overview-cli.ts +50 -0
  282. package/src/cli/pod-cli.ts +1 -0
  283. package/src/cli/pr-cli.ts +65 -8
  284. package/src/cli/release-cli.ts +21 -4
  285. package/src/cli/tasks-cli.ts +214 -1
  286. package/src/connectors/types.ts +6 -6
  287. package/src/context-bus/context-now-surfaces.ts +18 -0
  288. package/src/context-bus/coord-events.ts +32 -4
  289. package/src/context-bus/long-term-memory.ts +23 -6
  290. package/src/context-bus/nightly-condense.ts +169 -21
  291. package/src/control/control-status.ts +87 -0
  292. package/src/control-plane/light-member.ts +5 -3
  293. package/src/control-plane/member.ts +172 -7
  294. package/src/core-turn/run-goal-loop.ts +22 -1
  295. package/src/dashboard/index.ts +40 -4
  296. package/src/dashboard/slash-runtime/dashboard-handlers.ts +9 -0
  297. package/src/decisions/decision-cards.ts +20 -1
  298. package/src/decisions/decision-ledger.ts +11 -8
  299. package/src/decisions/web-push-decision-cards.ts +19 -3
  300. package/src/discord-self-message.ts +2 -2
  301. package/src/domains/coo-admin-dates.evidence.json +876 -0
  302. package/src/domains/coo-admin-tool.ts +48 -14
  303. package/src/domains/ops-status.ts +53 -0
  304. package/src/domains/outbound-alert.ts +24 -1
  305. package/src/esc-abort-gate.ts +3 -1
  306. package/src/exec-requests/planner.ts +6 -0
  307. package/src/graph-kinds/registry.ts +6 -6
  308. package/src/graph-runner/graph-cli.ts +91 -2
  309. package/src/graph-runner/graph-run-api.ts +20 -0
  310. package/src/graph-runner/graph-tick.ts +73 -12
  311. package/src/graph-runner/runner.ts +43 -11
  312. package/src/graph-unify/wf-to-graph.ts +79 -0
  313. package/src/graph-wizard/archetypes.ts +88 -0
  314. package/src/graph-wizard/generate.ts +577 -0
  315. package/src/graph-wizard/saved-steps.ts +120 -0
  316. package/src/graph-wizard/steps.ts +206 -0
  317. package/src/harness/harness-cli-command.ts +186 -32
  318. package/src/harness/harness-queue.ts +58 -14
  319. package/src/harness/launch-advice.ts +23 -0
  320. package/src/index.ts +43 -11
  321. package/src/intake-plane/digest.ts +18 -11
  322. package/src/knowledge/conclusion-compress.ts +93 -0
  323. package/src/knowledge/know-find.ts +9 -2
  324. package/src/knowledge/query.ts +20 -0
  325. package/src/llm.ts +26 -16
  326. package/src/loops/neighbor-runtime.ts +88 -0
  327. package/src/loops/orchestrator/author-depth.ts +29 -11
  328. package/src/loops/orchestrator/overlap.ts +7 -1
  329. package/src/loops/orchestrator/resync-shadow.ts +1 -1
  330. package/src/loops/orchestrator/tick.ts +35 -2
  331. package/src/loops/orchestrator/traffic.ts +2 -0
  332. package/src/loops/registry.ts +5 -0
  333. package/src/market/publish.ts +17 -1
  334. package/src/maturity/feature-maturity.ts +4 -2
  335. package/src/mss/logging/redaction.ts +17 -0
  336. package/src/nexus/api/draft-metrics.ts +128 -0
  337. package/src/nexus/api/graph-peer-edit.ts +77 -0
  338. package/src/nexus/api/graph-wizard-api.ts +61 -0
  339. package/src/nexus/api/graphs-api.ts +193 -17
  340. package/src/nexus/api/harness-traversal.ts +205 -0
  341. package/src/nexus/api/http-server.ts +122 -6
  342. package/src/nexus/api/plugins-market.ts +24 -0
  343. package/src/nexus/index.ts +3 -2
  344. package/src/nexus/outbound/router.ts +28 -8
  345. package/src/oauth/claude-pty-maturity.ts +24 -0
  346. package/src/oauth/fallback-chain.ts +27 -14
  347. package/src/onboarding/first-run.ts +3 -0
  348. package/src/ops/overview-collect.ts +345 -0
  349. package/src/ops/overview-seat-era.ts +90 -0
  350. package/src/ops/overview.ts +223 -0
  351. package/src/plugins/maker/market-bundle.ts +9 -0
  352. package/src/plugins/maker/plugin-maker.ts +9 -3
  353. package/src/plugins/maker/wizard-generate.ts +73 -9
  354. package/src/policy/llm-policy.ts +11 -6
  355. package/src/release-loop/checklist-board.ts +27 -0
  356. package/src/release-loop/checklist.ts +38 -0
  357. package/src/release-loop/gate-shards.ts +7 -2
  358. package/src/seat-address/seat-address.ts +1 -0
  359. package/src/seat-loop/seat-loop.ts +174 -20
  360. package/src/seat-loop/seat-report.ts +29 -1
  361. package/src/self-dev/ask-launch-flow.ts +4 -2
  362. package/src/self-dev/ask-launch-io.ts +3 -1
  363. package/src/self-dev/dev-cli.ts +90 -8
  364. package/src/self-dev/dev-pipeline.ts +20 -1
  365. package/src/self-dev/draft-sweep.ts +45 -8
  366. package/src/self-dev/graph-journey-nodes.ts +157 -0
  367. package/src/self-dev/graph-journey-route.ts +105 -0
  368. package/src/self-dev/launch-preflight.ts +43 -3
  369. package/src/self-dev/orchestrate.ts +3 -0
  370. package/src/self-dev/release-path-guard.ts +7 -4
  371. package/src/self-dev/review-provider-fallback.ts +83 -0
  372. package/src/self-dev/run-supervisor.ts +5 -1
  373. package/src/self-implement/frozen-merges.ts +23 -0
  374. package/src/self-implement/goal-author.ts +14 -0
  375. package/src/self-implement/harness-policy-gates.ts +7 -5
  376. package/src/self-implement/heal-intake.ts +3 -0
  377. package/src/self-implement/host-regate.ts +28 -14
  378. package/src/self-implement/l8-merge-queue.ts +80 -8
  379. package/src/self-implement/orchestrator.ts +84 -28
  380. package/src/self-implement/seams.ts +8 -3
  381. package/src/self-implement/self-implement-cli.ts +4 -0
  382. package/src/self-implement/self-implement-runtime.ts +12 -0
  383. package/src/session-runtime/index.ts +5 -3
  384. package/src/shell-env-bootstrap.ts +35 -6
  385. package/src/steward/triage.ts +25 -8
  386. package/src/storage/file-lock.ts +33 -4
  387. package/src/task-agent/card-evidence.ts +6 -0
  388. package/src/task-agent/completion-evidence.ts +31 -3
  389. package/src/task-agent/cover.ts +61 -0
  390. package/src/task-agent/live-moves.ts +126 -3
  391. package/src/task-agent/parent-host.ts +315 -0
  392. package/src/task-agent/shadow.ts +19 -4
  393. package/src/task-agent/task-hand.ts +11 -2
  394. package/src/task-orchestrator/surfaces/pod-command-job.ts +41 -6
  395. package/src/task-orchestrator/surfaces/pod-gate-deps.ts +123 -0
  396. package/src/task-orchestrator/surfaces/pod-pool.ts +138 -15
  397. package/src/task-orchestrator/surfaces/self-implement-pod.ts +39 -6
  398. package/src/task-orchestrator/surfaces/self-implement.ts +7 -0
  399. package/src/telegram-commands.ts +8 -0
  400. package/src/telegram-project-command.ts +6 -0
  401. package/src/telegram.ts +2 -2
  402. package/src/tool-runtime/browser-runtime.ts +87 -3
  403. package/src/tool-runtime/index.ts +1 -0
  404. package/src/typecheck-ratchet.ts +2 -1
  405. package/src/user-config.ts +55 -8
  406. package/src/version/packed-revision.json +1 -1
  407. package/src/workflow-runtime/executor.ts +24 -2
  408. package/src/workflow-runtime/triggers/registry.ts +27 -0
  409. package/src/workflow-runtime/triggers/schedule-source.ts +30 -3
  410. package/apps/pwa/out/_next/static/Bam1YziK2iH9bN4K0utGk/_buildManifest.js +0 -1
  411. package/apps/pwa/out/_next/static/chunks/1307-77fad80cf54477c6.js +0 -1
  412. package/apps/pwa/out/_next/static/chunks/1612-f60c0c93ec773f81.js +0 -1
  413. package/apps/pwa/out/_next/static/chunks/1844-9ec4b1d12f6e80eb.js +0 -1
  414. package/apps/pwa/out/_next/static/chunks/1866-e7a6999f25548b30.js +0 -1
  415. package/apps/pwa/out/_next/static/chunks/1948.15f356b15a625597.js +0 -5
  416. package/apps/pwa/out/_next/static/chunks/2116-6429c188eb846ab8.js +0 -1
  417. package/apps/pwa/out/_next/static/chunks/224-e7be8f762fe74986.js +0 -1
  418. package/apps/pwa/out/_next/static/chunks/2549-89c518a4c8ed4d00.js +0 -1
  419. package/apps/pwa/out/_next/static/chunks/2957-96ffab3d521fb08f.js +0 -1
  420. package/apps/pwa/out/_next/static/chunks/3857-995ea9f5ef33d71e.js +0 -1
  421. package/apps/pwa/out/_next/static/chunks/5360-be1f1d8f9fcc9755.js +0 -1
  422. package/apps/pwa/out/_next/static/chunks/5755-ea7ed88eb956f47b.js +0 -1
  423. package/apps/pwa/out/_next/static/chunks/5977-94ed6f78e9814b2b.js +0 -1
  424. package/apps/pwa/out/_next/static/chunks/7748-6e51bfe927019f5c.js +0 -25
  425. package/apps/pwa/out/_next/static/chunks/9247-c1753a059e7bac08.js +0 -1
  426. package/apps/pwa/out/_next/static/chunks/a9f2b54d-33c0689b70ed8cf3.js +0 -1
  427. package/apps/pwa/out/_next/static/chunks/app/bots/page-7a852956c74015a3.js +0 -1
  428. package/apps/pwa/out/_next/static/chunks/app/ceo/page-c5a5c621be0e04ae.js +0 -1
  429. package/apps/pwa/out/_next/static/chunks/app/design-check/page-7c058069c5e7fa63.js +0 -1
  430. package/apps/pwa/out/_next/static/chunks/app/editor/page-dc5da0b8a2edb8a4.js +0 -1
  431. package/apps/pwa/out/_next/static/chunks/app/inside/page-ed4a6f799f005d09.js +0 -1
  432. package/apps/pwa/out/_next/static/chunks/app/intake/page-9cd565005b93aca6.js +0 -1
  433. package/apps/pwa/out/_next/static/chunks/app/layout-55f0f40c6db95f15.js +0 -1
  434. package/apps/pwa/out/_next/static/chunks/app/live/page-474cb5888ea0e8f3.js +0 -1
  435. package/apps/pwa/out/_next/static/chunks/app/market/page-9ca21f9ad3208a61.js +0 -1
  436. package/apps/pwa/out/_next/static/chunks/app/not-found-a9c2cfa8dda922a5.js +0 -1
  437. package/apps/pwa/out/_next/static/chunks/app/observatory/page-6da35144878feb6d.js +0 -1
  438. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-f6055495e9ed7ea8.js +0 -1
  439. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-25204694fa9984a5.js +0 -1
  440. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-a60e035f151d84d3.js +0 -1
  441. package/apps/pwa/out/_next/static/chunks/app/outputs/page-189c587f36874538.js +0 -1
  442. package/apps/pwa/out/_next/static/chunks/app/page-c56634bde705a65d.js +0 -1
  443. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-a8d2fe0c669e0de4.js +0 -1
  444. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-c26fa10aac6c21c4.js +0 -1
  445. package/apps/pwa/out/_next/static/chunks/app/setup/page-68793c6e10331d66.js +0 -1
  446. package/apps/pwa/out/_next/static/chunks/app/showroom/page-d81c07a7b4f618ae.js +0 -1
  447. package/apps/pwa/out/_next/static/chunks/app/tasks/page-be1c4e8697f22360.js +0 -1
  448. package/apps/pwa/out/_next/static/chunks/app/today/page-a4717e2c4bf7a28f.js +0 -1
  449. package/apps/pwa/out/_next/static/chunks/app/trace/page-0fd895fab9b62c1b.js +0 -1
  450. package/apps/pwa/out/_next/static/chunks/app/vault/page-8b6c1454a8e8f306.js +0 -1
  451. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-c6e4364d9de8f717.js +0 -1
  452. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-0506a1e0e4bdb2f5.js +0 -1
  453. package/apps/pwa/out/_next/static/chunks/webpack-c260571513bf43c2.js +0 -1
  454. package/apps/pwa/out/_next/static/css/c23d959d3ee15e57.css +0 -1
  455. /package/apps/pwa/out/_next/static/{Bam1YziK2iH9bN4K0utGk → aBi6EyMtLsrl7J8eH4kF7}/_ssgManifest.js +0 -0
  456. /package/skills/project-onboarding/{skill.md → SKILL.md} +0 -0
@@ -19,6 +19,9 @@ export interface CollectWorkDeps {
19
19
  config?: Pick<OrchestratorLoopConfig, 'seatTrees'>;
20
20
  }
21
21
 
22
+ /** Budget for collectWork's per-process cwd reads. */
23
+ export const COLLECT_WORK_PROCESS_DEADLINE_MS = 10_000;
24
+
22
25
  const repoRoot = resolve(import.meta.dir, '../../..');
23
26
  const defaultRunGh = (args: string[]): string => execFileSync('bun', ['bin/elanous.mjs', '--test', 'gh', ...args], {
24
27
  cwd: repoRoot, encoding: 'utf8', timeout: 60_000, maxBuffer: 20 * 1024 * 1024,
@@ -63,7 +66,10 @@ function goalFiles(text: string): string[] {
63
66
  export function collectWork(deps: CollectWorkDeps = {}): CollectWorkResult {
64
67
  const prs = JSON.parse((deps.runGh ?? defaultRunGh)(['pr', 'list', '--state', 'open', '--limit', '1000', '--json', 'number,title,body,headRefName,files,isDraft,labels'])) as OpenPr[];
65
68
  if (!Array.isArray(prs)) throw new Error('PR observation is not a list');
66
- const observation = (deps.listProcesses ?? defaultListHarnessProcesses)();
69
+ // Only goal-carrying launches become work, so only they need a cwd; bounded so a host with hundreds of harness processes
70
+ // cannot hold the queue lock for minutes (10-08). Records past the budget stay cwd-unknown → their goal reads unreadable.
71
+ const observation = deps.listProcesses ? deps.listProcesses()
72
+ : defaultListHarnessProcesses({ include: record => goalPath(record.command) !== null, cwdOnly: true, deadlineMs: COLLECT_WORK_PROCESS_DEADLINE_MS });
67
73
  if (observation.status !== 'ok') throw new Error(`process observation ${observation.status}`);
68
74
  const cfg = deps.config ?? getUserConfig().loops?.orchestrator ?? ORCHESTRATOR_DEFAULTS;
69
75
  const excluded = { stalled: 0, superseded: 0, draft: 0 };
@@ -171,7 +171,7 @@ export async function runResyncShadow(max = 3, deps: ResyncShadowDeps = {}): Pro
171
171
  entry.resolvedFiles = outcome.resolvedFiles ?? [];
172
172
  if (outcome.sizeCollapse?.length || outcome.providerFailure?.length || outcome.testDeclarationLoss?.length) {
173
173
  entry.verdict = 'guard-tripped';
174
- } else if (outcome.status === 'merged' || outcome.status === 'llm-resolved') {
174
+ } else if (outcome.status === 'merged' || outcome.status === 'llm-resolved' || outcome.status === 'deterministic-resolved') {
175
175
  if (entry.testFiles.length === 0) entry.verdict = 'merge-ok-no-tests';
176
176
  else {
177
177
  const test = (deps.runTests ?? defaultRunTests)(tmp, entry.testFiles);
@@ -8,6 +8,7 @@ import { effectiveInstanceRoot } from '../../instance/resolve.js';
8
8
  import { getUserConfig, type OrchestratorLoopConfig } from '../../user-config.js';
9
9
  import { rubricPriority } from '../../release-loop/rubric.js';
10
10
  import { debug } from '../../debug/log.js';
11
+ import { emitLoopHeartbeat } from '../neighbor-runtime.js';
11
12
  import { listSelfDevRuns, runSummaryLine, selfDevRunsDir } from '../../self-dev/run-store.js';
12
13
  import { planAction, runSeatLoopOnce, type SeatDeps, type SeatItem } from '../../seat-loop/seat-loop.js';
13
14
  import { addHarnessQueue, harnessQueueOutcome, setQueuedPoolHint } from '../../harness/harness-queue.js';
@@ -48,7 +49,8 @@ export type PlacementInput = { id: string; title: string; owner: string; priorit
48
49
  export type PlaceAdapter = (cell: PlacementInput, deps?: { dryRun?: boolean; now?: Date }) => Promise<{ version?: string } | null> | { version?: string } | null;
49
50
  export type RebalanceAdapter = (version: string) => Promise<{ decisions: unknown[]; blocked: Array<{ id: string; reason: string }> }> | { decisions: unknown[]; blocked: Array<{ id: string; reason: string }> };
50
51
  export interface TickDeps {
51
- root?: string; now?: Date; mode?: Mode; runId?: string; window?: Window;
52
+ root?: string; now?: Date | (() => Date); mode?: Mode; runId?: string; window?: Window;
53
+ heartbeatIntervalMs?: number;
52
54
  cards?: () => TaskCard[]; split?: SplitAdapter; placeCell?: PlaceAdapter;
53
55
  degradation?: () => { llm: boolean; board: boolean };
54
56
  gridHosts?: readonly GridHost[];
@@ -101,6 +103,19 @@ export function kstWindow(now: Date): Window {
101
103
  const hour = Number(new Intl.DateTimeFormat('en-US', { timeZone: 'Asia/Seoul', hour: '2-digit', hourCycle: 'h23' }).format(now));
102
104
  return hour < 12 ? '08' : hour < 18 ? '12' : '18';
103
105
  }
106
+ /** KST schedule of graphs/orchestrator/orchestrator.yaml (`0 8,12,18 * * *`). */
107
+ const ORCHESTRATOR_HOURS_KST = [8, 12, 18] as const;
108
+ /** Grace past the next scheduled window before neighbors may call the orchestrator absent. */
109
+ export const ORCHESTRATOR_LEASE_GRACE_MS = 60 * 60_000;
110
+ /** The next scheduled window strictly after `now` — the orchestrator is idle, not absent, until then. */
111
+ export function nextOrchestratorWindowAt(now: Date): Date {
112
+ const kst = new Date(now.getTime() + 9 * 3_600_000);
113
+ for (let day = 0; day < 2; day++) for (const hour of ORCHESTRATOR_HOURS_KST) {
114
+ const at = Date.UTC(kst.getUTCFullYear(), kst.getUTCMonth(), kst.getUTCDate() + day, hour) - 9 * 3_600_000;
115
+ if (at > now.getTime()) return new Date(at);
116
+ }
117
+ throw new Error('unreachable: no orchestrator window within two days');
118
+ }
104
119
  function kstDay(now: Date): string {
105
120
  return new Intl.DateTimeFormat('en-CA', { timeZone: 'Asia/Seoul', year: 'numeric', month: '2-digit', day: '2-digit' }).format(now);
106
121
  }
@@ -371,7 +386,8 @@ async function optionalAdapter<T>(path: string, name: string): Promise<T | undef
371
386
 
372
387
  /** A graph command invokes one node; only the per-run file carries data across processes. */
373
388
  export async function runOrchestratorNode(node: Node, deps: TickDeps = {}): Promise<TickState> {
374
- const now = deps.now ?? new Date();
389
+ const currentTime = () => typeof deps.now === 'function' ? deps.now() : deps.now ?? new Date();
390
+ const now = currentTime();
375
391
  const window = deps.window ?? kstWindow(now);
376
392
  const root = deps.root ?? effectiveInstanceRoot();
377
393
  const runId = deps.runId ?? graphRunId() ?? `${kstDay(now)}-${window}`;
@@ -379,6 +395,16 @@ export async function runOrchestratorNode(node: Node, deps: TickDeps = {}): Prom
379
395
  const orchestratorConfig = getUserConfig().loops?.orchestrator;
380
396
  const configured = deps.mode ?? orchestratorConfig?.mode;
381
397
  const mode: Mode = configured === 'off' || configured === 'live' ? configured : 'shadow';
398
+ const pulse = (health: 'healthy' | 'degraded', at: Date): void => {
399
+ try { emitLoopHeartbeat(root, 'orchestrator', health, at,
400
+ new Date(nextOrchestratorWindowAt(at).getTime() + ORCHESTRATOR_LEASE_GRACE_MS).toISOString()); }
401
+ catch (error) { debug.log('loop.orchestrator', 'heartbeat-failed', { error: String(error).slice(0, 200) }, { level: 'warn' }); }
402
+ };
403
+ if (mode !== 'off') pulse('healthy', now);
404
+ const heartbeatTimer = mode === 'off' ? undefined : setInterval(() => pulse('healthy', currentTime()), deps.heartbeatIntervalMs ?? 60 * 60_000);
405
+ heartbeatTimer?.unref();
406
+ let nodeError: unknown;
407
+ try {
382
408
  const state: TickState = existsSync(path) ? JSON.parse(readFileSync(path, 'utf8')) as TickState : initial(runId, window, mode, kstDay(now));
383
409
  state.steps ??= [];
384
410
  if (state.window !== window || state.mode !== mode) throw new Error('orchestrator run window/mode changed');
@@ -739,6 +765,13 @@ export async function runOrchestratorNode(node: Node, deps: TickDeps = {}): Prom
739
765
  mkdirSync(dirname(path), { recursive: true });
740
766
  writeFileSync(path, JSON.stringify(state));
741
767
  return state;
768
+ } catch (error) {
769
+ nodeError = error;
770
+ throw error;
771
+ } finally {
772
+ if (heartbeatTimer) clearInterval(heartbeatTimer);
773
+ if (mode !== 'off') pulse(nodeError === undefined ? 'healthy' : 'degraded', currentTime());
774
+ }
742
775
  }
743
776
 
744
777
  /** COORD-HA — the production supply of `TickDeps.degradation` (config `loops.orchestrator.degradationSignal`). Shadow (the
@@ -50,6 +50,8 @@ function realOrResolved(path: string): string {
50
50
  }
51
51
 
52
52
  const IS_LAUNCH = /(?:^|\s)(?:\S*\/)?elanous\.mjs\s+(?:(?:--test|--config-dir\s+\S+)\s+)?harness\s+(?:ask|say)(?=\s|$)/;
53
+ /** A `harness ask|say` launch command line — the only processes `trafficTick` counts. */
54
+ export const isTrafficLaunch = (command: string): boolean => IS_LAUNCH.test(command);
53
55
 
54
56
  /** Process-to-seat resolution occurs at the boundary; this decision does no IO. */
55
57
  export function trafficTick({ processes, now, caps, lastLaunchAt, idleSince, openCells, nextRound, totalSlots, idleMinutes = 30 }: {
@@ -685,6 +685,11 @@ export function unregisteredCronLoops(entries: AllLoopEntry[], opts: LoopRegistr
685
685
  return warnings;
686
686
  }
687
687
 
688
+ /** Newest-first run history of one loop without re-listing the registry (health reads a longer window than `loop status`). */
689
+ export function loopRecentRuns(id: string, opts: LoopRegistryOptions = {}, count = 5): LoopRun[] {
690
+ return recentRuns(id, opts.stateRoot ?? effectiveInstanceRoot(), count);
691
+ }
692
+
688
693
  export function loopStatus(id: string, opts: LoopRegistryOptions = {}): LoopEntry & { recentRuns: LoopRun[] } {
689
694
  const loop = listLoops(opts).find(entry => entry.id === id);
690
695
  if (!loop) throw new Error(`loop not found: ${id}`);
@@ -91,6 +91,8 @@ export interface PublishResult {
91
91
  ok: boolean;
92
92
  sequence: number;
93
93
  published: Array<{ name: string; version: string; sha256: string; bundled: string[] }>;
94
+ /** With keepExisting: previous index entries whose name has no folder under pluginsDir, carried over unchanged. */
95
+ kept: Array<{ name: string; version: string; sha256: string }>;
94
96
  skipped: Array<{ dir: string; reason: string; hits?: InternalRefHit[] }>;
95
97
  /** Published anyway, but a reviewer should look (e.g. a third-party graph relying on core recipes). */
96
98
  warnings: Array<{ dir: string; graph: string; reason: 'third-party-core-recipe'; recipes: string[] }>;
@@ -103,6 +105,8 @@ export function publishMarket(input: {
103
105
  market: { name: string; displayName: string };
104
106
  key: { keyId: string; privateKeyPem: string };
105
107
  source?: { repoUrl: string; sha: string; basePath: string };
108
+ /** Carry over previous entries whose name is not a plugin folder under pluginsDir (entry, archive and unpacked files untouched). */
109
+ keepExisting?: boolean;
106
110
  now?: Date;
107
111
  }): PublishResult {
108
112
  let sequence = 1;
@@ -307,6 +311,18 @@ export function publishMarket(input: {
307
311
  published.push({ name: manifest.name, version: manifest.version, sha256, bundled });
308
312
  debug.log('market.publish', 'bundled', { name: manifest.name, version: manifest.version, skills: bundled });
309
313
  }
314
+ const kept: PublishResult['kept'] = [];
315
+ if (input.keepExisting && previous) {
316
+ // Folders under pluginsDir own their names (even when skipped); every other previous entry stays as it was.
317
+ const present = new Set(readdirSync(input.pluginsDir).filter(dir => lstatSync(join(input.pluginsDir, dir)).isDirectory()));
318
+ for (const item of previous.plugins) {
319
+ if (present.has(item.name)) continue;
320
+ plugins.push(item);
321
+ kept.push({ name: item.name, version: item.version, sha256: item.artifact.sha256 });
322
+ debug.log('market.publish', 'kept', { name: item.name, version: item.version });
323
+ }
324
+ plugins.sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0));
325
+ }
310
326
  const index: MarketplaceIndex = { name: input.market.name, interface: { displayName: input.market.displayName },
311
327
  sequence, generatedAt: (input.now ?? new Date()).toISOString(), plugins };
312
328
  const bytes = Buffer.from(JSON.stringify(index, null, 2) + '\n');
@@ -345,7 +361,7 @@ export function publishMarket(input: {
345
361
  signatureText: readFileSync(join(input.outDir, 'index.sig'), 'utf8'), trustedKeys });
346
362
  if (!verified.ok) throw new Error(`index verification failed: ${verified.reason}: ${verified.detail}`);
347
363
  for (const item of published) debug.log('market.publish', 'published', { name: item.name, version: item.version, sequence });
348
- return { ok: !skipped.some(item => item.reason === 'internal-reference'), sequence, published, skipped, warnings };
364
+ return { ok: !skipped.some(item => item.reason === 'internal-reference'), sequence, published, kept, skipped, warnings };
349
365
  } catch (error) {
350
366
  const rawReason = error instanceof Error ? error.message : 'unknown-error';
351
367
  const reason = input.key.privateKeyPem && rawReason.includes(input.key.privateKeyPem) ? 'invalid signing key' : rawReason;
@@ -16,10 +16,11 @@ export const FEATURE_MATURITY = {
16
16
  '/today': { pwa: 'stable' },
17
17
  '/term': { pwa: 'stable' },
18
18
  '/live': { pwa: 'stable' },
19
+ '/live-run': { pwa: 'beta' }, // HARNESS-RUN-LIVE-GRAPH (0.2.21) — 하니스 런 하나가 도는 장면
19
20
  '/market': { pwa: 'stable' },
20
21
  '/sessions': { pwa: 'stable' },
21
22
  '/settings': { pwa: 'beta' },
22
- '/editor': { pwa: 'beta' },
23
+ '/editor': { pwa: 'stable' }, // EDITOR-NAV-PUBLIC (0.2.21) — 데모·0순위 피처: 역할을 안 고른 기기에도 «만들기 › 편집기»
23
24
  '/intake': { pwa: 'beta' },
24
25
  '/trace': { pwa: 'beta' },
25
26
  '/inside': { pwa: 'beta' },
@@ -116,6 +117,7 @@ export const FEATURE_MATURITY = {
116
117
  widget: 'tool',
117
118
  log: 'tool',
118
119
  loops: 'beta',
120
+ board: 'beta',
119
121
  persona: 'beta', // G3 b — 대화별 페르소나(GOAL-BENCH B7 #23836)
120
122
  memory: 'beta',
121
123
  export: 'stable',
@@ -160,7 +162,7 @@ export const FEATURE_MATURITY = {
160
162
  ping: 'stable', provider: 'stable', sessions: 'stable', fork: 'stable', resume: 'stable',
161
163
  decisions: 'stable', work: 'stable', cancel: 'stable',
162
164
  skills: 'beta', skill: 'beta', digest: 'beta', intake: 'beta', ad: 'beta',
163
- taste: 'beta', missions: 'beta', attach: 'beta', detach: 'beta', now: 'beta', loops: 'beta', runs: 'beta', release: 'beta', away: 'beta', project: 'beta', wish: 'beta',
165
+ taste: 'beta', missions: 'beta', attach: 'beta', detach: 'beta', now: 'beta', loops: 'beta', runs: 'beta', board: 'beta', release: 'beta', away: 'beta', project: 'beta', wish: 'beta',
164
166
  brain: 'tool', cc: 'tool', cdx: 'tool', gem: 'tool', local: 'tool',
165
167
  harness: 'ops',
166
168
  // Existing commands outside the CXO four-grade menu list are owner-only.
@@ -93,3 +93,20 @@ export function redactLogRecord(rec: LogRecord, opts: RedactOpts = {}): LogRecor
93
93
  const mask = opts.mask ?? defaultMask;
94
94
  return { ...rec, data: redactValue(rec.data, keyBlock, mask, new WeakSet()) };
95
95
  }
96
+
97
+ /** Pre-bundle knowledge-pack DLP. Reasons are fixed labels, never source text or secret values.
98
+ * This is separate from the opt-in log masking path above. */
99
+ const KNOWLEDGE_PACK_DLP_RULES: ReadonlyArray<{ reason: string; pattern: RegExp }> = [
100
+ { reason: 'secret-value', pattern: /-----BEGIN (?:[A-Z0-9_-]+ )?PRIVATE KEY(?: BLOCK)?-----|\bBearer\s+[A-Za-z0-9._~+/-]{12,}={0,2}\b|\b(?:sk-[A-Za-z0-9_-]{16,}|xai-[A-Za-z0-9_-]{20,}|gh[pousr]_[A-Za-z0-9_]{12,}|github_pat_[A-Za-z0-9_]{12,}|xox[baprs]-[A-Za-z0-9-]{12,}|(?:AKIA|ASIA|ABIA|ACCA)[A-Z2-7]{16}|AIza[A-Za-z0-9_-]{35})\b|\b(?:api[_-]?key|secret[_-]?key|access[_-]?token|private[_-]?key|token)['"]?\s*[=:]\s*['"]?[A-Za-z0-9_./+~-]{16,}/iu },
101
+ // A key that names a password/secret makes ANY assigned value secret: short or punctuated values count too.
102
+ // Schema flags (`"secret": true`) are not values.
103
+ { reason: 'secret-value', pattern: /\b(?:password|passwd|pwd|passphrase|client[_-]?secret|secret)['"]?\s*[=:]\s*(?!(?:true|false|null)\b)(?:"[^"\n]+"|'[^'\n]+'|[^\s'",;{}\[\]]{4,})/iu },
104
+ { reason: 'personal-information', pattern: /\b01[016789]-?\d{3,4}-?\d{4}\b|\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b|\b\d{6}-[1-8]\d{6}\b/iu },
105
+ { reason: 'internal-identifier', pattern: /(?:사번|사내\s*식별자|내부\s*식별자|employee[_ -]?id|internal[_ -]?id)\s*(?:[:=]|\s)\s*[A-Za-z0-9][A-Za-z0-9_-]{3,}/iu },
106
+ { reason: 'sales-confidential', pattern: /영업\s*기밀|(?:sales\s+confidential|confidential\s+sales|영업\s*비밀)/iu },
107
+ ];
108
+
109
+ export function scanKnowledgePackDlp(text: string): string[] {
110
+ const reasons = KNOWLEDGE_PACK_DLP_RULES.filter(({ pattern }) => pattern.test(text)).map(({ reason }) => reason);
111
+ return [...new Set(reasons)];
112
+ }
@@ -0,0 +1,128 @@
1
+ import { join } from 'node:path';
2
+ import { debug } from '../../debug/log.js';
3
+ import type { DraftMetrics, OverlapMetrics } from '../../self-dev/draft-sweep.js';
4
+
5
+ export type CeoDraftMetrics = DraftMetrics & { readonly overlap: OverlapMetrics | null };
6
+
7
+ /**
8
+ * DRAFT-METRIC — CEO view source. Opening the page must never trigger a GitHub read:
9
+ * `read()` answers from memory and at most one background collection runs at a time,
10
+ * no more often than the TTL (or the failure back-off). The collection itself is
11
+ * `elanous harness drafts metrics --json` in a child process, which reads PR evidence
12
+ * through the batched GraphQL path (#24918) — one call per 40 drafts, no per-draft comments call.
13
+ */
14
+ export type DraftMetricsState = 'measuring' | 'ready' | 'unavailable';
15
+
16
+ export interface DraftMetricsSnapshot {
17
+ /** measuring = no value yet and a collection is running · unavailable = no value and the last collection failed. */
18
+ readonly state: DraftMetricsState;
19
+ readonly metrics: CeoDraftMetrics | null;
20
+ /** When the served value was collected (ISO). null without a value. */
21
+ readonly measuredAt: string | null;
22
+ /** A background collection is in flight. */
23
+ readonly refreshing: boolean;
24
+ /** Last collection failure; with a value it means the value is older than intended. */
25
+ readonly reason: string | null;
26
+ }
27
+
28
+ export interface DraftMetricsSourceOptions {
29
+ readonly collect?: () => Promise<CeoDraftMetrics>;
30
+ readonly ttlMs?: number;
31
+ readonly failureRetryMs?: number;
32
+ readonly now?: () => number;
33
+ }
34
+
35
+ export const DRAFT_METRICS_TTL_MS = 10 * 60_000;
36
+ export const DRAFT_METRICS_FAILURE_RETRY_MS = 2 * 60_000;
37
+ const COLLECT_TIMEOUT_MS = 5 * 60_000;
38
+
39
+ function isMetrics(value: unknown): value is DraftMetrics {
40
+ if (!value || typeof value !== 'object') return false;
41
+ const row = value as Record<string, unknown>;
42
+ const count = (key: string) => Number.isInteger(row[key]) && (row[key] as number) >= 0;
43
+ const nullableNumber = (key: string) => row[key] === null || (typeof row[key] === 'number' && Number.isFinite(row[key]));
44
+ return count('inventory') && count('needsOwner') && count('converted48h') && count('cohort48h')
45
+ && nullableNumber('oldestAgeHours') && nullableNumber('conversion48h');
46
+ }
47
+
48
+ function isOverlapMetrics(value: unknown): value is OverlapMetrics {
49
+ if (!value || typeof value !== 'object' || Array.isArray(value)) return false;
50
+ const row = value as Record<string, unknown>;
51
+ const count = (key: string) => row[key] === null || (Number.isInteger(row[key]) && (row[key] as number) >= 0);
52
+ const nullableNumber = (key: string) => row[key] === null || (typeof row[key] === 'number' && Number.isFinite(row[key]) && (row[key] as number) >= 0);
53
+ return ['launches24h', 'launched', 'unmeasured', 'linked', 'autoLanded', 'salvaged', 'salvageUnmeasured'].every(count)
54
+ && typeof row.sourceIncomplete === 'boolean' && nullableNumber('secondSiblingMedianHours')
55
+ && (row.autoRate === null || (typeof row.autoRate === 'number' && Number.isFinite(row.autoRate) && row.autoRate >= 0 && row.autoRate <= 1));
56
+ }
57
+
58
+ /** Parses the CLI's single JSON line; malformed draft values fail, malformed overlap is unmeasured. */
59
+ export function parseDraftMetricsOutput(stdout: string): CeoDraftMetrics {
60
+ const line = stdout.trim().split('\n').reverse().find((text) => text.trim().startsWith('{'));
61
+ if (!line) throw new Error('draft metrics: no JSON output');
62
+ const parsed: unknown = JSON.parse(line);
63
+ if (!isMetrics(parsed)) throw new Error('draft metrics: malformed output');
64
+ const { inventory, oldestAgeHours, needsOwner, converted48h, cohort48h, conversion48h } = parsed;
65
+ const candidate = (parsed as DraftMetrics & { overlap?: unknown }).overlap;
66
+ return { inventory, oldestAgeHours, needsOwner, converted48h, cohort48h, conversion48h,
67
+ overlap: isOverlapMetrics(candidate) ? candidate : null };
68
+ }
69
+
70
+ async function collectViaCli(): Promise<CeoDraftMetrics> {
71
+ const root = join(import.meta.dir, '../../..');
72
+ const proc = Bun.spawn(['bun', join(root, 'bin/elanous.mjs'), 'harness', 'drafts', 'metrics', '--json'], {
73
+ cwd: root, stdout: 'pipe', stderr: 'pipe',
74
+ });
75
+ const timer = setTimeout(() => proc.kill(), COLLECT_TIMEOUT_MS);
76
+ try {
77
+ const [stdout, stderr, exitCode] = await Promise.all([new Response(proc.stdout).text(), new Response(proc.stderr).text(), proc.exited]);
78
+ if (exitCode !== 0) throw new Error(stderr.trim().split('\n').at(-1) || `draft metrics exited ${exitCode}`);
79
+ return parseDraftMetricsOutput(stdout);
80
+ } finally { clearTimeout(timer); }
81
+ }
82
+
83
+ export interface DraftMetricsSource {
84
+ read(): DraftMetricsSnapshot;
85
+ }
86
+
87
+ export function createDraftMetricsSource(options: DraftMetricsSourceOptions = {}): DraftMetricsSource {
88
+ const collect = options.collect ?? collectViaCli;
89
+ const ttl = options.ttlMs ?? DRAFT_METRICS_TTL_MS;
90
+ const retry = options.failureRetryMs ?? DRAFT_METRICS_FAILURE_RETRY_MS;
91
+ const now = options.now ?? Date.now;
92
+ let value: { metrics: CeoDraftMetrics; at: number } | null = null;
93
+ let failure: { reason: string; at: number } | null = null;
94
+ let inFlight: Promise<void> | null = null;
95
+
96
+ const refresh = (): void => {
97
+ if (inFlight) return;
98
+ const started = now();
99
+ inFlight = (async () => {
100
+ try {
101
+ const metrics = await collect();
102
+ value = { metrics, at: now() };
103
+ failure = null;
104
+ try { debug.log('nexus.draft-metrics', 'collected', { ms: now() - started, inventory: metrics.inventory }); } catch { /* fail-soft */ }
105
+ } catch (error) {
106
+ failure = { reason: error instanceof Error ? error.message : String(error), at: now() };
107
+ try { debug.log('nexus.draft-metrics', 'failed', { ms: now() - started, reason: failure.reason }); } catch { /* fail-soft */ }
108
+ } finally { inFlight = null; }
109
+ })();
110
+ };
111
+
112
+ return {
113
+ read() {
114
+ const clock = now();
115
+ const due = failure && (!value || failure.at >= value.at)
116
+ ? clock - failure.at >= retry
117
+ : !value || clock - value.at >= ttl;
118
+ if (due) refresh();
119
+ return {
120
+ state: value ? 'ready' : failure && !inFlight ? 'unavailable' : 'measuring',
121
+ metrics: value?.metrics ?? null,
122
+ measuredAt: value ? new Date(value.at).toISOString() : null,
123
+ refreshing: inFlight !== null,
124
+ reason: failure?.reason ?? null,
125
+ };
126
+ },
127
+ };
128
+ }
@@ -0,0 +1,77 @@
1
+ // W9c run guard (TC security review of #25123) — a peer «edit» grant lets another token rewrite a «mine» graph.
2
+ // Running that graph would run the peer's change with this machine's privileges, so a peer save leaves a
3
+ // marker next to the graph's access grants (`<mine>/.access/<id>.peer-edit.json`) and every run entry for a
4
+ // «mine» graph refuses it until the owner approves that exact version (or re-saves it themselves).
5
+ // Fail closed: a marker that exists but cannot be read or parsed refuses the run.
6
+ import { mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from 'node:fs';
7
+ import { join } from 'node:path';
8
+ import { randomUUID } from 'node:crypto';
9
+
10
+ export const PEER_EDIT_REFUSAL = '상대가 바꾼 그래프 — 변경을 확인하고 승인해야 실행할 수 있다';
11
+
12
+ export interface PeerEditRecord {
13
+ /** `peer:<first 8 hex of the peer token's sha256>` — never the token itself. */
14
+ editedBy: string;
15
+ /** The version id the peer save produced (`pending` while the save is in flight). */
16
+ version: string;
17
+ at: string;
18
+ approved?: { version: string; at: string };
19
+ }
20
+
21
+ export type PeerEditGate =
22
+ | { ok: true }
23
+ | { ok: false; error: 'peer-edit-unapproved'; record: PeerEditRecord }
24
+ | { ok: false; error: 'peer-edit-unreadable'; reason: string };
25
+
26
+ export function peerEditFile(mineDir: string, id: string): string {
27
+ return join(mineDir, '.access', `${id}.peer-edit.json`);
28
+ }
29
+
30
+ /** null = no peer ever saved this graph. Throws when the marker exists but is unreadable or malformed. */
31
+ export function readPeerEdit(mineDir: string, id: string): PeerEditRecord | null {
32
+ let text: string;
33
+ try { text = readFileSync(peerEditFile(mineDir, id), 'utf8'); }
34
+ catch (error) {
35
+ if ((error as NodeJS.ErrnoException).code === 'ENOENT') return null;
36
+ throw error;
37
+ }
38
+ const parsed: unknown = JSON.parse(text);
39
+ const record = parsed as PeerEditRecord;
40
+ if (!parsed || typeof parsed !== 'object' || typeof record.editedBy !== 'string' || typeof record.version !== 'string'
41
+ || typeof record.at !== 'string'
42
+ || (record.approved !== undefined && (typeof record.approved !== 'object' || record.approved === null
43
+ || typeof record.approved.version !== 'string' || typeof record.approved.at !== 'string'))) {
44
+ throw new Error('invalid peer-edit marker');
45
+ }
46
+ return record;
47
+ }
48
+
49
+ export function writePeerEdit(mineDir: string, id: string, record: PeerEditRecord): void {
50
+ mkdirSync(join(mineDir, '.access'), { recursive: true, mode: 0o700 });
51
+ const target = peerEditFile(mineDir, id);
52
+ const temp = `${target}.${process.pid}.${randomUUID()}.tmp`;
53
+ try {
54
+ writeFileSync(temp, JSON.stringify(record), { mode: 0o600 });
55
+ renameSync(temp, target);
56
+ } finally { rmSync(temp, { force: true }); }
57
+ }
58
+
59
+ /** An owner-authorized save replaces the peer's bytes with bytes the owner chose. */
60
+ export function clearPeerEdit(mineDir: string, id: string): void {
61
+ rmSync(peerEditFile(mineDir, id), { force: true });
62
+ }
63
+
64
+ export function peerEditRunGate(mineDir: string, id: string): PeerEditGate {
65
+ let record: PeerEditRecord | null;
66
+ try { record = readPeerEdit(mineDir, id); }
67
+ catch (error) { return { ok: false, error: 'peer-edit-unreadable', reason: String(error).slice(0, 200) }; }
68
+ if (!record || record.approved?.version === record.version) return { ok: true };
69
+ return { ok: false, error: 'peer-edit-unapproved', record };
70
+ }
71
+
72
+ /** The 409 body every refusing run entry returns — `reason` is what the editor shows. */
73
+ export function peerEditRefusalBody(id: string, gate: Exclude<PeerEditGate, { ok: true }>): Record<string, unknown> {
74
+ return gate.error === 'peer-edit-unapproved'
75
+ ? { error: gate.error, id, reason: PEER_EDIT_REFUSAL, editedBy: gate.record.editedBy, version: gate.record.version, editedAt: gate.record.at }
76
+ : { error: gate.error, id, reason: PEER_EDIT_REFUSAL, detail: gate.reason };
77
+ }
@@ -0,0 +1,61 @@
1
+ import { debug } from '../../debug/log.js';
2
+ import { GraphWizardInputError, generateGraphFromPrompt, wizardInstalledPacks, type GraphWizardDeps, type GraphWizardTurn } from '../../graph-wizard/generate.js';
3
+ import { jsonResponse } from './json-response.js';
4
+
5
+ export function handleGraphWizardPacksGet(deps: GraphWizardDeps = {}): Response {
6
+ return jsonResponse({ packs: wizardInstalledPacks(deps) });
7
+ }
8
+
9
+ const MAX_PROMPT = 4000;
10
+ const MAX_YAML = 200_000;
11
+
12
+ /**
13
+ * POST /v1/graphs/wizard {prompt, kind?, currentYaml?, history?}
14
+ * → 200 {ok:true, id, yaml, base, baseReason, issues:[], attempts, summary, labels, steps, recipes, dryRun} · 422 {ok:false, …} · 400 빈 prompt.
15
+ * (labels·steps·recipes·dryRun·baseReason 는 v2 추가 필드 — 기존 필드는 그대로.)
16
+ * currentYaml 이 빈 문자열이면 «빈 캔버스»로 보고 새 그래프를 짓는다(의도) · 내용이 있는데 id 가 없거나 형식 밖이면 400.
17
+ * 저장하지 않는다 — 저장은 POST /v1/graphs(새 그래프) 또는 PUT /v1/graphs/<id>/yaml(내 그래프).
18
+ */
19
+ export async function handleGraphWizardPost(req: Request, deps: GraphWizardDeps = {}): Promise<Response> {
20
+ let body: Record<string, unknown>;
21
+ try {
22
+ const parsed: unknown = await req.json();
23
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) throw new Error('object');
24
+ body = parsed as Record<string, unknown>;
25
+ } catch { return jsonResponse({ error: 'bad_request', reason: 'json body required' }, 400); }
26
+ const prompt = typeof body.prompt === 'string' ? body.prompt.trim() : '';
27
+ if (!prompt) return jsonResponse({ error: 'bad_request', reason: 'prompt required' }, 400);
28
+ if (prompt.length > MAX_PROMPT) return jsonResponse({ error: 'bad_request', reason: `prompt longer than ${MAX_PROMPT}` }, 400);
29
+ if (body.kind !== undefined && body.kind !== 'harness' && body.kind !== 'workflow') {
30
+ return jsonResponse({ error: 'bad_request', reason: 'kind must be harness or workflow' }, 400);
31
+ }
32
+ if (body.currentYaml !== undefined && (typeof body.currentYaml !== 'string' || body.currentYaml.length > MAX_YAML)) {
33
+ return jsonResponse({ error: 'bad_request', reason: 'currentYaml must be a string' }, 400);
34
+ }
35
+ if (body.packId !== undefined && typeof body.packId !== 'string') {
36
+ return jsonResponse({ error: 'bad_request', reason: 'packId must be a string' }, 400);
37
+ }
38
+ let history: GraphWizardTurn[] | undefined;
39
+ if (body.history !== undefined) {
40
+ if (!Array.isArray(body.history) || !body.history.every((t) => t && typeof t === 'object' &&
41
+ ((t as GraphWizardTurn).role === 'user' || (t as GraphWizardTurn).role === 'assistant') && typeof (t as GraphWizardTurn).text === 'string')) {
42
+ return jsonResponse({ error: 'bad_request', reason: 'history must be [{role, text}]' }, 400);
43
+ }
44
+ history = body.history as GraphWizardTurn[];
45
+ }
46
+ try {
47
+ const result = await generateGraphFromPrompt({
48
+ prompt,
49
+ ...(body.kind ? { kind: body.kind as 'harness' | 'workflow' } : {}),
50
+ ...(typeof body.currentYaml === 'string' && body.currentYaml.trim() ? { currentYaml: body.currentYaml } : {}),
51
+ ...(history ? { history } : {}),
52
+ ...(typeof body.packId === 'string' ? { packId: body.packId } : {}),
53
+ }, deps);
54
+ return jsonResponse({ ...result, base: result.base ?? null }, result.ok ? 200 : 422);
55
+ } catch (error) {
56
+ const message = error instanceof Error ? error.message : String(error);
57
+ if (error instanceof GraphWizardInputError) return jsonResponse({ error: 'bad_request', reason: message }, 400);
58
+ debug.log('graph.wizard', 'error', { message });
59
+ return jsonResponse({ error: 'wizard-failed', reason: message }, 502);
60
+ }
61
+ }