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
@@ -7,11 +7,9 @@
7
7
  // `{id, method, params}` messages, receive `{id, result}` or
8
8
  // `{id, error}`.
9
9
  //
10
- // Scope kept narrow: navigate + screenshot + evaluate + close.
11
- // The MVP targets "open a page for the user to see + capture a
12
- // PNG" which covers the display-plus-screenshot flow the plan
13
- // describes. Full Puppeteer feature parity is explicitly out of
14
- // scope.
10
+ // Scope kept narrow: navigation, capture, evaluation and CDP input.
11
+ // Approval of mutating actions belongs to the caller, not this transport.
12
+ // Full Puppeteer feature parity is explicitly out of scope.
15
13
 
16
14
  import { CHROME_NO_KEYCHAIN_FLAGS } from './chrome-flags.js';
17
15
  import { spawn, type ChildProcess } from 'node:child_process';
@@ -126,6 +124,8 @@ export interface CdpClient {
126
124
  setScriptExecutionDisabled(value: boolean): Promise<void>;
127
125
  /** Dispatch one primary-button click at viewport coordinates. */
128
126
  click?(coordinates: { x: number; y: number }): Promise<void>;
127
+ /** Insert text at the focused element (approval is the caller's responsibility). */
128
+ input?(text: string): Promise<void>;
129
129
  /** Dispose — closes Chrome + WebSocket. */
130
130
  close(): Promise<void>;
131
131
  readonly isAlive: boolean;
@@ -513,6 +513,11 @@ async function buildClientFromTransport(
513
513
  await runOnPage('Input.dispatchMouseEvent', { type: 'mousePressed', ...params });
514
514
  await runOnPage('Input.dispatchMouseEvent', { type: 'mouseReleased', ...params });
515
515
  },
516
+ async input(text) {
517
+ // Newlines can submit a form even without an Enter key event.
518
+ if (/[\r\n]/.test(text)) throw new Error('CDP input refuses newlines');
519
+ await runOnPage('Input.insertText', { text });
520
+ },
516
521
  async close() {
517
522
  if (!alive) return;
518
523
  alive = false;
@@ -213,6 +213,17 @@ opsCmd.command('status').description('현재 상태 종합(미션·태스크·
213
213
  }
214
214
  return o.allInstances ? runOpsFleet(o.json === true, o.includeTest === true) : runOps('snapshot', o);
215
215
  });
216
+ // OPS-OVERVIEW-CLI (RFC-ops-overview-flow-health-cli-2026-10-08) — 판정 먼저 · 이유 사슬 · 절. 못 잼 = null(0 아님).
217
+ opsCmd.command('overview').description('흐름 한 장 — 저작→구현→착지가 흐르나(판정 먼저·이유 사슬) · 판·일꾼·기계·착지·루프 · 못 잼은 null · exit 0=흐름 정상/저하 3=그 밖 1=수집 실패')
218
+ .option('--json', '칸 꼴 그대로 ⊕ 머리 {verdict, reasons, nextActions, meta.elapsedMs}')
219
+ .option('--watch [seconds]', '주기 재조회(기본 30초)')
220
+ .option('--since <window>', '착지 창(<n>m|h|d · 기본 1h)', '1h')
221
+ .option('--owner <owner>', '일꾼 절 거르기 — seat:TC · TC · unassigned · all')
222
+ .option('--version <v>', '판(기본: 다음 컷)')
223
+ .action(async (o: { json?: boolean; watch?: string | boolean; since?: string; owner?: string; version?: string }) => {
224
+ const { runOpsOverview } = await import('./ops-overview-cli.js');
225
+ process.exitCode = await runOpsOverview(o);
226
+ });
216
227
  opsCmd.command('health').description('이상 판정만(blocked·errored·stale)').option('--json')
217
228
  .action((o: OpsOpts) => runOps('health', o));
218
229
  opsCmd.command('timeline').description('상태 전이 최근순 통합')
@@ -0,0 +1,50 @@
1
+ import { debug } from '../debug/log.js';
2
+ import { collectOverview, parseSince } from '../ops/overview-collect.js';
3
+ import { computeVerdict, renderOverview, unmeasuredCells, verdictExitCode, VERDICT_LABEL } from '../ops/overview.js';
4
+ import { writeStdoutJson } from './stdout-json.js';
5
+
6
+ export interface OpsOverviewOptions { json?: boolean; watch?: string | boolean; since?: string; owner?: string; version?: string }
7
+
8
+ /** 한 번 모아서 내고 exit code 를 돌려준다(RFC §A4: flowing·degraded 0 · 그 밖 3 · 수집 실패 1). */
9
+ async function once(o: OpsOverviewOptions, previous: string | null): Promise<{ code: number; verdict: string | null }> {
10
+ const started = performance.now();
11
+ let since: { ms: number; label: string };
12
+ try { since = parseSince(o.since); } catch (error) { console.error(String((error as Error).message)); return { code: 1, verdict: null }; }
13
+ try {
14
+ const snapshot = await collectOverview({ sinceMs: since.ms, sinceLabel: since.label, ...(o.owner ? { owner: o.owner } : {}), ...(o.version ? { version: o.version } : {}) });
15
+ const result = computeVerdict(snapshot);
16
+ const elapsedMs = Math.round(performance.now() - started);
17
+ const unmeasured = unmeasuredCells(snapshot);
18
+ debug.log('ops.overview', 'computed', { verdict: result.verdict, elapsedMs, unmeasured });
19
+ debug.flush();
20
+ if (o.json) {
21
+ await writeStdoutJson(JSON.stringify({ verdict: result.verdict, verdictLabel: VERDICT_LABEL[result.verdict], reasons: result.reasons, nextActions: result.nextActions,
22
+ meta: { elapsedMs, source: 'local', unmeasured }, ...snapshot }, null, 2) + '\n');
23
+ } else {
24
+ const text = renderOverview(snapshot, result, { elapsedMs });
25
+ if (previous !== null && previous !== result.verdict) console.log(`🔔 판정 바뀜: ${previous} → ${result.verdict}`);
26
+ console.log(text);
27
+ }
28
+ return { code: verdictExitCode(result.verdict), verdict: result.verdict };
29
+ } catch (error) {
30
+ debug.log('ops.overview', 'failed', { reason: String(error) });
31
+ debug.flush();
32
+ console.error(`ops overview 수집 실패: ${String(error)}`);
33
+ return { code: 1, verdict: null };
34
+ }
35
+ }
36
+
37
+ export async function runOpsOverview(o: OpsOverviewOptions): Promise<number> {
38
+ // 독립 CLI 프로세스는 데몬 StoreSink 를 물려받지 않는다 — 등록 없이는 debug.log 가 logs.db 에 안 닿는다(관측 안 한 것).
39
+ try { await (await import('../domains/standalone-log-sink.js')).registerStandaloneLogSink('cli'); } catch { /* fail-open */ }
40
+ if (o.watch === undefined || o.watch === false) return (await once(o, null)).code;
41
+ const seconds = o.watch === true ? 30 : Number(o.watch);
42
+ if (!Number.isFinite(seconds) || seconds < 5) { console.error('--watch 는 5초 이상'); return 1; }
43
+ let previous: string | null = null;
44
+ for (;;) {
45
+ if (!o.json) process.stdout.write('\x1b[2J\x1b[H');
46
+ const r = await once(o, previous);
47
+ previous = r.verdict ?? previous;
48
+ await new Promise((res) => setTimeout(res, seconds * 1000));
49
+ }
50
+ }
@@ -151,6 +151,7 @@ export function registerPodCommands(program: Command, deps: PodCliDeps = {}): vo
151
151
  try {
152
152
  const result = await run({
153
153
  command: commandArgs,
154
+ returnLogs: true,
154
155
  ...(opts.pool ? { pool: opts.pool } : {}),
155
156
  ...(skills.length ? { skills } : {}),
156
157
  ...(opts.llm === 'grok' ? { llm: 'grok' as const } : {}),
@@ -34,6 +34,10 @@ export interface TasksCliDeps {
34
34
  releaseBoard?: () => BoardNode[];
35
35
  /** `tasks show`·`advance` 의 런 근거 읽기(시험 주입) — 기본 = 런 원장(이 우주 ⊕ 연합) ⊕ 호스트 logs 의 Pod job-finished. */
36
36
  cardRunEvidence?: CardRunEvidenceDeps;
37
+ /** HARNESS-PARENT-ON-MSB1 — 원격 부모 카드의 원장 거울(시험 주입). 기본 = ssh 로 `<prodRoot>/remote-parents/<host>/run-ledger`. */
38
+ remoteLedgerMirror?: (card: TaskCard) => void;
39
+ /** `tasks parents` 의 낡은 부모 보고 의존(시험 주입). */
40
+ staleParents?: import('../task-agent/parent-host.js').StaleParentDeps;
37
41
  }
38
42
 
39
43
  /** `<조각id>=<값>` 반복 인자. */
@@ -97,6 +101,60 @@ export interface DefaultTaskLauncherDeps {
97
101
  writeStop?: import('../self-implement/run-ledger.js').RunLedgerWriter;
98
102
  /** 이미 stop 이 있나(시험 주입). 기본 = 이 우주 런 원장 ⊕ 이 프로세스 기록. */
99
103
  hasStop?: (runId: string) => boolean;
104
+ /** HARNESS-PARENT-ON-MSB1 — 원격 부모 대상(시험 주입). undefined = 설정·env 에서 푼다 · null = 끔. */
105
+ parentHost?: import('../task-agent/parent-host.js').ParentHostConfig | null;
106
+ /** 원격 ssh(시험 주입). */
107
+ ssh?: import('../task-agent/parent-host.js').SshRunner;
108
+ /** HQ 발사 동결(holdLaunches) 여부(시험 주입). */
109
+ launchHoldActive?: () => boolean;
110
+ /** HQ 판(시험 주입). */
111
+ localVersion?: string;
112
+ /** 원격 → 로컬 되돌림 알림(시험 주입). 기본 = stderr 한 줄. */
113
+ notice?: (line: string) => void;
114
+ }
115
+
116
+ /**
117
+ * HARNESS-PARENT-ON-MSB1 — 켜져 있고 발사 전 점검이 통과하면 원격(ssh)에서 부모를 띄운다(PR 까지만 · 병합은 HQ).
118
+ * 막히면 null — 호출부가 종전 로컬 발사로 간다(관측: task-agent parent-host-fallback).
119
+ */
120
+ async function tryRemoteParent(args: string[], cwd: string | undefined, context: TaskLaunchContext, deps: DefaultTaskLauncherDeps): Promise<TaskLaunchReceipt | null> {
121
+ const parentHostModule = await import('../task-agent/parent-host.js');
122
+ let target = deps.parentHost;
123
+ if (target === undefined) {
124
+ const { getUserConfig } = await import('../user-config.js');
125
+ let configured: Parameters<typeof parentHostModule.resolveParentHost>[0];
126
+ try { configured = getUserConfig().taskAgent?.parentHost; } catch { configured = undefined; }
127
+ target = parentHostModule.resolveParentHost(configured);
128
+ }
129
+ if (!target) return null;
130
+ const notice = deps.notice ?? ((line: string) => { try { process.stderr.write(`${line}\n`); } catch { /* ignore */ } });
131
+ const fallback = (gaps: string[], facts: Record<string, string> = {}): null => {
132
+ try { debug.log('task-agent', 'parent-host-fallback', { host: target!.host, runId: context.runId, gaps, facts }); } catch { /* fail-soft */ }
133
+ notice(`[parent-host] ${target!.host} 로 못 띄움 — 로컬 발사로 간다: ${gaps.join(' · ')}`);
134
+ return null;
135
+ };
136
+ if (cwd) return fallback(['project-target-is-local']);
137
+ let launchHoldActive = false;
138
+ try {
139
+ if (deps.launchHoldActive) launchHoldActive = deps.launchHoldActive();
140
+ else {
141
+ const { readLandingFreeze } = await import('../release-loop/landing-freeze.js');
142
+ const { prodInstanceRoot } = await import('../instance/resolve.js');
143
+ launchHoldActive = readLandingFreeze(prodInstanceRoot())?.holdLaunches === true;
144
+ }
145
+ } catch { launchHoldActive = true; /* 못 읽으면 막힌 것으로(fail-closed) */ }
146
+ const preflight = parentHostModule.preflightParentHost({ target, localVersion: deps.localVersion ?? parentHostModule.localElanousVersion(), launchHoldActive, ...(deps.ssh ? { ssh: deps.ssh } : {}) });
147
+ if (!preflight.ok) return fallback(preflight.gaps, preflight.facts);
148
+ try {
149
+ const launched = parentHostModule.launchRemoteParent(target, args, context.env, deps.ssh);
150
+ try { debug.log('task-agent', 'parent-host-launched', { host: target.host, runId: context.runId, pid: launched.pid, log: launched.log }); } catch { /* fail-soft */ }
151
+ return { runId: context.runId, parentHost: { host: target.host, ...(target.configDir ? { configDir: target.configDir } : {}), pid: launched.pid, log: launched.log } };
152
+ } catch (error) {
153
+ // «확실히 안 떴다»일 때만 로컬로 — 불확실(ssh 끊김 등)은 던져 카드를 launch-failed 로 남긴다(중복 발사 방지).
154
+ if (error instanceof parentHostModule.RemoteParentNotLaunchedError) return fallback([error.message]);
155
+ try { debug.log('task-agent', 'parent-host-launch-uncertain', { host: target.host, runId: context.runId, error: error instanceof Error ? error.message : String(error) }); } catch { /* fail-soft */ }
156
+ throw error;
157
+ }
100
158
  }
101
159
 
102
160
  /** stderr 파일의 첫 비어 있지 않은 줄(최대 4KB 만 읽는다 — 원 로그 꼬리를 싣지 않는다). */
@@ -112,6 +170,10 @@ function firstStderrLine(path: string): string {
112
170
  }
113
171
 
114
172
  export async function defaultTaskLauncher(args: string[], cwd?: string, context?: TaskLaunchContext, deps: DefaultTaskLauncherDeps = {}): Promise<void | TaskLaunchReceipt> {
173
+ if (context) {
174
+ const remote = await tryRemoteParent(args, cwd, context, deps);
175
+ if (remote) return remote;
176
+ }
115
177
  const spawn = deps.spawn ?? ((await import('node:child_process')).spawn as unknown as DetachedSpawn);
116
178
  const { getElanousConfigDirOverride } = await import('../elanous-config-dir.js');
117
179
  const { prodInstanceRoot } = await import('../instance/resolve.js');
@@ -208,6 +270,21 @@ export function spawnDetachedConfirmed(spawnFn: DetachedSpawn, command: string,
208
270
  });
209
271
  }
210
272
 
273
+ /** HARNESS-PARENT-ON-MSB1 — 카드가 원격 부모면 그 런 원장(⊕ Pod 자식)을 HQ 거울로 당긴다. 실패는 관측만(«없음»으로 읽히지 않게). */
274
+ async function mirrorRemoteCardLedger(cardId: string, deps: TasksCliDeps): Promise<void> {
275
+ let card: TaskCard | undefined;
276
+ try { card = readTaskCard(cardId, deps.taskStatePath); } catch { return; }
277
+ if (!card?.parentHost || !card.runId) return;
278
+ try {
279
+ if (deps.remoteLedgerMirror) { deps.remoteLedgerMirror(card); return; }
280
+ const { mirrorRemoteRunLedgers } = await import('../task-agent/parent-host.js');
281
+ const { prodInstanceRoot } = await import('../instance/resolve.js');
282
+ mirrorRemoteRunLedgers(card.parentHost, card.runId, prodInstanceRoot());
283
+ } catch (error) {
284
+ try { debug.log('task-agent', 'remote-ledger-mirror-failed', { card: cardId, host: card.parentHost.host, runId: card.runId, error: error instanceof Error ? error.message : String(error) }); } catch { /* fail-soft */ }
285
+ }
286
+ }
287
+
211
288
  function moveLine(card: TaskCard): string {
212
289
  const move = nextMoveFor(card);
213
290
  return `${move.kind}${move.command ? ` — elanous ${move.command.map(shellQuote).join(' ')}` : ''} (${move.reason})`;
@@ -442,6 +519,32 @@ export function registerTasksCommands(program: Command, deps: TasksCliDeps = {})
442
519
  }
443
520
  });
444
521
 
522
+ tasks.command('parents')
523
+ .description('낡은 하니스 부모 보고(읽기 전용 · 죽이지 않는다) — 나이 ≥ N 시간이고 런 원장이 끝났거나(run-status) Pod 이 없는 `harness say|ask` 부모')
524
+ .option('--older-than <hours>', '나이 하한(시간)', '6')
525
+ .option('--no-pods', 'Pod 목록을 재지 않는다(원장 근거만)')
526
+ .option('--json', 'JSON 출력')
527
+ .action(async (opts: { olderThan: string; pods?: boolean; json?: boolean }) => {
528
+ const hours = Number(opts.olderThan);
529
+ if (!Number.isFinite(hours) || hours < 0) { out(`--older-than 는 0 이상 수다: ${opts.olderThan}`); process.exitCode = 1; return; }
530
+ const { staleHarnessParents } = await import('../task-agent/parent-host.js');
531
+ const injected = deps.staleParents ?? {};
532
+ const loadLedger = injected.loadLedger ?? ((runId: string) => loadRunLedgerSync(runId));
533
+ let podRuns = injected.podRuns;
534
+ let labelOf = injected.labelOf;
535
+ if (!podRuns && opts.pods !== false) {
536
+ const queue = await import('../harness/harness-queue.js');
537
+ podRuns = () => queue.readHarnessQueuePodRuns();
538
+ labelOf = queue.queueRunLabelValue;
539
+ }
540
+ const report = staleHarnessParents(hours, { ...injected, loadLedger, ...(podRuns ? { podRuns } : {}), ...(labelOf ? { labelOf } : {}) });
541
+ try { debug.log('task-agent', 'stale-parents', { olderThanHours: hours, scanned: report.scanned, stale: report.rows.length, podRunsMeasured: report.podRunsMeasured }); } catch { /* fail-soft */ }
542
+ if (opts.json) { out(JSON.stringify({ olderThanHours: hours, ...report })); return; }
543
+ out(`하니스 부모 ${report.scanned} · 나이 ≥ ${hours}h 중 낡음 후보 ${report.rows.length}${report.podRunsMeasured ? '' : ' · ⚠ Pod 목록 못 잼(원장 근거만)'} — 읽기 전용(죽이지 않는다)`);
544
+ out('pid\t나이h\t자리\t판\t런\t사유');
545
+ for (const row of report.rows) out([row.pid, row.ageHours, row.seat ?? '-', row.version ?? '-', row.runId ?? '-', row.reasons.join(',')].join('\t'));
546
+ });
547
+
445
548
  tasks.command('board')
446
549
  .description('보드 — 프로젝트 › 목표 › 이정표 › 과제. 과제 카드(project·goal·milestone)와 내부 팩(릴리스 체크리스트 = elanous › 판 › 칸)을 합쳐 읽는다(쓰지 않는다)')
447
550
  .option('--project <id>', '이 프로젝트만')
@@ -569,6 +672,8 @@ export function registerTasksCommands(program: Command, deps: TasksCliDeps = {})
569
672
  // 런 id 가 있으면 원장에서 골 id · PR 을 묶는다(찾았을 때만 적는다 · 실패는 그대로 보인다).
570
673
  const bindable = card?.pieces ? card.pieces : card ? [card.id] : [];
571
674
  for (const cardId of bindable) {
675
+ // HARNESS-PARENT-ON-MSB1: 원격 부모 런은 원장이 그 호스트에 있다 — HQ 거울로 먼저 당긴다(읽기 전용).
676
+ await mirrorRemoteCardLedger(cardId, deps);
572
677
  try { await refreshCardRunBinding(cardId, deps.taskStatePath ?? taskStatePathDefault(), deps.cardRunEvidence ?? {}); } catch (error) {
573
678
  // 근거 없음으로 보인다 — 원장 부재와 갈리게 실패는 남긴다.
574
679
  try { debug.log('task-agent', 'card-bind-failed', { card: cardId, via: 'show', error: error instanceof Error ? error.message : String(error) }); } catch { /* fail-soft */ }
@@ -608,6 +713,7 @@ export function registerTasksCommands(program: Command, deps: TasksCliDeps = {})
608
713
  if (opts.json) out(JSON.stringify({ card, nextMove: nextMoveFor(card), salvageBranch: salvage.salvageBranch, ...(salvageUnread ? { salvageBranchUnread: true } : {}) }));
609
714
  else {
610
715
  for (const line of taskCardLines(card)) out(line);
716
+ if (card.parentHost) out(`부모 호스트: ${card.parentHost.host}${card.parentHost.pid ? ` (pid ${card.parentHost.pid})` : ''} · PR 까지만 — 병합은 HQ${card.parentHost.log ? ` · 로그 ${card.parentHost.host}:${card.parentHost.log}` : ''}`);
611
717
  if (salvage.salvageBranch) out(`수확 가지: ${salvage.salvageBranch} (draft PR 대신)`);
612
718
  else if (salvageUnread) out('수확 가지: 못 읽음(원장 읽기 실패)');
613
719
  }
@@ -769,6 +769,15 @@ export function clearEscAbortToolState(toolState: EscAbortToolState): void {
769
769
  toolState.activeToolNamesByCallId.clear();
770
770
  }
771
771
 
772
+ export function activeEscHarnessSpace(
773
+ toolState: EscAbortToolState,
774
+ spacesByCallId: ReadonlyMap<string, string>,
775
+ ): string | null {
776
+ const active = [...toolState.activeToolNamesByCallId].filter(([, name]) =>
777
+ (SELF_IMPLEMENT_TOOL_NAMES as readonly string[]).includes(name));
778
+ return active.length === 1 ? spacesByCallId.get(active[0]![0]) ?? null : null;
779
+ }
780
+
772
781
  /** Reset per-turn ESC tool tracking before a new stream runtime begins. */
773
782
  export function createDashboardTurnStreamRuntimeEscBoundary(toolState: EscAbortToolState): void {
774
783
  clearEscAbortToolState(toolState);
@@ -1324,6 +1333,8 @@ import {
1324
1333
  PULSE_HALF_PERIOD_MS,
1325
1334
  } from '../agent-activity-hud.js';
1326
1335
  import { DASHBOARD_CLARIFICATION_REQUEST, consumeStreamingTopModalEscape, createEscAbortGate, createHarnessEscHint, routeStreamingEscapeKey } from '../esc-abort-gate.js';
1336
+ import { enqueueSoftStop, readSoftStopRequestStatus } from '../harness/control-inbox.js';
1337
+ import { setSelfImplementSoftStopSpaceObserver } from '../self-implement/self-implement-runtime.js';
1327
1338
  import { createViEditor, type ViEditorHandle } from '../vi-editor.js';
1328
1339
  import { launchEditor, canLaunchEditor } from '../editor-launcher.js';
1329
1340
  import { createSshPickerModal } from '../ssh/ssh-picker-modal.js';
@@ -6700,6 +6711,13 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
6700
6711
  * this function is invoked.
6701
6712
  */
6702
6713
  const escAbortToolState = createEscAbortToolState();
6714
+ const escHarnessSpacesByCallId = new Map<string, string>();
6715
+ setSelfImplementSoftStopSpaceObserver((callId, spaceId) => {
6716
+ const toolName = escAbortToolState.activeToolNamesByCallId.get(callId);
6717
+ if (toolName && (SELF_IMPLEMENT_TOOL_NAMES as readonly string[]).includes(toolName)) {
6718
+ escHarnessSpacesByCallId.set(callId, spaceId);
6719
+ }
6720
+ });
6703
6721
  let clarificationModalId: string | null = null;
6704
6722
 
6705
6723
  const attachChatStreamingKeys = (abortCtrl: AbortController): () => void => {
@@ -6713,6 +6731,8 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
6713
6731
  // so the user can make an informed call.
6714
6732
  let abortGateModalId: string | null = null;
6715
6733
  const showHarnessEscHint = createHarnessEscHint((line) => pushChatLine(line));
6734
+ let stoppedHarnessSpace: string | null = null;
6735
+ const activeHarnessSpace = (): string | null => activeEscHarnessSpace(escAbortToolState, escHarnessSpacesByCallId);
6716
6736
  const escGate = createEscAbortGate({
6717
6737
  abortCtrl,
6718
6738
  getRunningCount: () => getEscAbortRunningCount(escAbortToolState),
@@ -6726,14 +6746,25 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
6726
6746
  requestRedraw: () => draw(),
6727
6747
  getTheme: () => currentThemeTokens(),
6728
6748
  getWaitingTargetNames: () => getEscAbortWaitingToolNames(escAbortToolState),
6749
+ shouldAbortImmediately: () => activeHarnessSpace() !== null,
6729
6750
  onAbortPending: ({ targets }) => {
6730
- if (!showHarnessEscHint(targets)) {
6751
+ const spaceId = activeHarnessSpace();
6752
+ if (spaceId) {
6753
+ try {
6754
+ enqueueSoftStop(spaceId);
6755
+ if (readSoftStopRequestStatus(spaceId).status !== 'present') throw new Error('soft-stop marker not persisted');
6756
+ stoppedHarnessSpace = spaceId;
6757
+ pushChatLine(' ⏹ 멈췄다 — 해당 하니스 런에 중단 요청을 보냈습니다.');
6758
+ } catch {
6759
+ pushChatLine(' ⚠ 하니스 런 중단 요청을 보내지 못했습니다. /harness runs 에서 확인해 주세요.');
6760
+ }
6761
+ } else if (!showHarnessEscHint(targets)) {
6731
6762
  pushChatLine(` ⏳ 중단 요청됨 — ${formatEscAbortWaitingTargets(targets)} 종료를 기다리는 중입니다.`);
6732
6763
  }
6733
6764
  chatScrollOffset = -1;
6734
6765
  },
6735
6766
  onAbortRepeat: ({ repeat, targets }) => {
6736
- if (!showHarnessEscHint(targets)) {
6767
+ if (!stoppedHarnessSpace && !showHarnessEscHint(targets)) {
6737
6768
  pushChatLine(` ⏳ ESC 재시도 ${repeat}회 — ${formatEscAbortWaitingTargets(targets)} 종료를 기다리는 중입니다.`);
6738
6769
  }
6739
6770
  chatScrollOffset = -1;
@@ -7251,7 +7282,7 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
7251
7282
  return () => {
7252
7283
  cleanup();
7253
7284
  escGate.dispose();
7254
- settleEscAbortPendingLines(chatLines, chatLinesStartIndex);
7285
+ if (!stoppedHarnessSpace) settleEscAbortPendingLines(chatLines, chatLinesStartIndex);
7255
7286
  userScrolledDuringStream = false;
7256
7287
  streamingInFlight = false;
7257
7288
  };
@@ -15674,6 +15705,7 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
15674
15705
  pushChatLine,
15675
15706
  draw,
15676
15707
  signal: acpTurnRef.abortCtrl?.signal ?? new AbortController().signal,
15708
+ toolCallId: ctx?.callId,
15677
15709
  // ⛔⭐ 폴백은 `''` 가 아니라 `undefined` 다 — 「안 넘김」과 「말했는데 어휘가 없음」은 «다른 값»이다.
15678
15710
  // harnessMentionState(session-runtime 소비처)는 `undefined → 'absent'` · `'' → 'not-matched'` 로
15679
15711
  // 가른다. `?? ''` 로 접으면 그 둘이 같은 값이 되고, 그것이 2026-08-06 에 「TUI 가 하니스에게
@@ -15695,9 +15727,10 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
15695
15727
  ptyDashboardOn:
15696
15728
  uc.shell.allowDashboardPty === true && (acpPtyAvailable?.() ?? false),
15697
15729
  getToolRuntime: (toolName) => runtime.getToolRuntime(toolName),
15698
- dispatchToolRuntime: (toolName, input) =>
15730
+ dispatchToolRuntime: (toolName, input, toolCallId) =>
15699
15731
  runtime.dispatchToolByName(toolName, input, {
15700
15732
  surface: 'tui',
15733
+ toolCallId,
15701
15734
  signal: acpTurnRef.abortCtrl?.signal,
15702
15735
  // ⭐ Agent-family context. Without these two the sub-agent runs
15703
15736
  // text-only (see getTools above). `agentDispatchTool` routes the
@@ -19171,6 +19204,7 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
19171
19204
  createTurnStreamRuntime: (initialAssistantStart) => {
19172
19205
  latestTurnUsage = undefined;
19173
19206
  createDashboardTurnStreamRuntimeEscBoundary(escAbortToolState);
19207
+ escHarnessSpacesByCallId.clear();
19174
19208
  liveTypeaheadEchoes.length = 0;
19175
19209
  const toolRendering = {
19176
19210
  ...getUserConfig().chat.rendering.tool,
@@ -19225,6 +19259,7 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
19225
19259
  const toolCall = call as DashboardTurnStreamCall;
19226
19260
  turnStreamRuntime.onToolResult(toolCall);
19227
19261
  settleEscAbortToolCall(escAbortToolState, toolCall);
19262
+ escHarnessSpacesByCallId.delete(toolCall.id);
19228
19263
  },
19229
19264
  };
19230
19265
  },
@@ -19524,6 +19559,7 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
19524
19559
  closeDashboardTui();
19525
19560
  throw err;
19526
19561
  } finally {
19562
+ setSelfImplementSoftStopSpaceObserver(null);
19527
19563
  if (tuiSeatAskTimer) clearInterval(tuiSeatAskTimer);
19528
19564
  }
19529
19565
  }
@@ -55,6 +55,8 @@ export interface EscAbortGateDeps {
55
55
  getTheme?: () => ThemeTokens | null | undefined;
56
56
  /** Names shown/logged while a turn abort request is waiting for running work to unwind. */
57
57
  getWaitingTargetNames?: () => readonly string[];
58
+ /** Only an identified foreground harness run may bypass the sub-agent confirmation. */
59
+ shouldAbortImmediately?: () => boolean;
58
60
  /** Called before abortTurnOnly so the UI can paint a visible pending state immediately. */
59
61
  onAbortPending?: (event: { running: number; repeat: number; targets: readonly string[] }) => void;
60
62
  /** Called when another Escape arrives after the first abort request. */
@@ -211,7 +213,7 @@ export function createEscAbortGate(deps: EscAbortGateDeps): EscAbortGate {
211
213
  return;
212
214
  }
213
215
  const running = deps.getRunningCount();
214
- if (running <= 0) {
216
+ if (running <= 0 || deps.shouldAbortImmediately?.()) {
215
217
  debug.log('esc.abort', 'abort-immediately', { running, repeat: Math.max(0, escapePresses - 1) });
216
218
  abortPendingTurn(running);
217
219
  return;
@@ -94,6 +94,63 @@ export function registerGraphCommands(program: Command, runsDeps: { root?: strin
94
94
  process.exitCode = 1;
95
95
  }
96
96
  });
97
+ graph.command('wizard <prompt>')
98
+ .description('말 한 줄로 그래프 YAML 을 짓고 같은 검증기 ⊕ graph run --dry-run 으로 확인한다(저장하지 않는다) — --from 이면 그 그래프를 고친다')
99
+ .option('--kind <kind>', 'harness | workflow', 'harness')
100
+ .option('--from <file>', '고칠 그래프 YAML (편집 경로)')
101
+ .option('--out <file>', 'YAML 을 이 파일에 쓰고, 하니스면 같은 폴더에 recipes.yaml 도 쓴다(있으면 덮지 않는다)')
102
+ .option('--json', '결과를 JSON 으로')
103
+ .option('--run', '생성된 그래프의 dry-run 경로를 보여 준다(--dry-run 과 함께만)')
104
+ .option('--dry-run', '--run 과 함께: 명령을 실행하지 않고 경로만 걷는다')
105
+ .action(async (prompt: string, opts: { kind: string; from?: string; out?: string; json?: boolean; run?: boolean; dryRun?: boolean }) => {
106
+ try {
107
+ if (opts.kind !== 'harness' && opts.kind !== 'workflow') throw new Error('--kind must be harness or workflow');
108
+ if (opts.run && !opts.dryRun) throw new Error('--run is only supported with --dry-run (run the saved graph with `elanous graph run <file>`)');
109
+ if (opts.run && opts.kind !== 'harness') throw new Error('--run --dry-run walks harness graphs only');
110
+ await (await import('../domains/standalone-log-sink.js')).registerStandaloneLogSink('graph');
111
+ const { generateGraphFromPrompt } = await import('../graph-wizard/generate.js');
112
+ const currentYaml = opts.from ? readFileSync(opts.from, 'utf8') : undefined;
113
+ const started = Date.now();
114
+ const result = await generateGraphFromPrompt({ prompt, kind: opts.kind, ...(currentYaml ? { currentYaml } : {}) });
115
+ const ms = Date.now() - started;
116
+ let recipesNote: string | undefined;
117
+ if (opts.out) {
118
+ const { writeFileSync } = await import('node:fs');
119
+ const { dirname } = await import('node:path');
120
+ writeFileSync(opts.out, result.yaml);
121
+ if (result.recipes) {
122
+ const recipesPath = join(dirname(resolve(opts.out)), 'recipes.yaml');
123
+ if (existsSync(recipesPath) && readFileSync(recipesPath, 'utf8') !== result.recipes) recipesNote = `recipes.yaml 이 이미 있어 덮지 않았다: ${recipesPath}`;
124
+ else { writeFileSync(recipesPath, result.recipes); recipesNote = `recipes ${recipesPath}`; }
125
+ }
126
+ }
127
+ if (opts.json) {
128
+ await writeStdoutJson(JSON.stringify({ ...result, ms, ...(recipesNote ? { recipesNote } : {}) }) + '\n');
129
+ } else {
130
+ if (!opts.out) process.stdout.write(result.yaml);
131
+ console.log(`# ${result.ok ? 'ok' : 'invalid'} · id ${result.id} · base ${result.base ?? '-'} · attempts ${result.attempts} · ${(ms / 1000).toFixed(1)}s${opts.out ? ` · saved ${opts.out}` : ''}`);
132
+ console.log(`# ${result.summary}`);
133
+ if (recipesNote) console.log(`# ${recipesNote}`);
134
+ for (const issue of result.issues) console.log(`# issue: ${issue}`);
135
+ if (result.dryRun && (opts.run || !result.ok)) console.log(`# dry-run ${result.dryRun.status}: ${result.dryRun.path.join(' → ')}`);
136
+ }
137
+ if (!result.ok) process.exitCode = 1;
138
+ } catch (error) {
139
+ console.error(`graph wizard: ${error instanceof Error ? error.message : String(error)}`);
140
+ process.exitCode = 1;
141
+ }
142
+ });
143
+ graph.command('step <step>')
144
+ .description('그래프 마법사 단계 하나를 실행한다(graph run 의 cmd 노드가 부른다 · 마지막 줄 JSON {outcome})')
145
+ .option('--arg <text>', '단계 인자')
146
+ .option('--retries <n>', '실패하면 이 횟수만큼 다시 시도', '0')
147
+ .action(async (step: string, opts: { arg?: string; retries: string }) => {
148
+ const { runWizardStepWithRetries } = await import('../graph-wizard/steps.js');
149
+ const result = runWizardStepWithRetries(step, opts.arg, Number.parseInt(opts.retries, 10) || 0);
150
+ if (result.error) console.error(result.error);
151
+ console.log(JSON.stringify({ outcome: result.outcome, tries: result.tries, ...(result.text ? { text: result.text.slice(0, 20_000) } : {}), ...(result.error ? { error: result.error } : {}) }));
152
+ if (result.outcome === 'fail') process.exitCode = 1;
153
+ });
97
154
  graph.command('tick <file>')
98
155
  .description('Advance one graph run, resuming a pending decision or starting only when requested')
99
156
  .option('--start', 'Start a new run when the graph is idle')
@@ -13,6 +13,8 @@ import { effectiveInstanceRoot } from '../instance/resolve.js';
13
13
  import { jsonResponse } from '../nexus/api/json-response.js';
14
14
  import { defaultGraphsDir } from '../self-implement/graph-templates.js';
15
15
  import { runGraph, type GraphRunState } from './runner.js';
16
+ import { readWizardSteps, wizardRecipesFor } from '../graph-wizard/saved-steps.js';
17
+ import { stringify as stringifyYaml } from 'yaml';
16
18
 
17
19
  const GRAPH_ID = /^[a-z0-9-]+$/;
18
20
  const RUN_ID = /^ed-[a-z0-9]+-[a-f0-9]{6}$/;
@@ -148,6 +150,17 @@ export function handleGraphRunStart(rawId: string, deps: GraphRunApiDeps = {}):
148
150
  recipeText = readFileSync(recipesFile, 'utf8');
149
151
  const parsed: unknown = parseYaml(recipeText);
150
152
  allowed = new Set(parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? Object.keys(parsed) : []);
153
+ // GRAPH-WIZARD-SAVE-RECIPES — a wizard graph's own steps (saved as a validated sidecar) become recipes here,
154
+ // generated by the server from the step library; they take precedence for the nodes that carry them.
155
+ const steps = readWizardSteps(mineDirOf(deps), id, readFileSync(mine.file, 'utf8'));
156
+ const generated = steps ? wizardRecipesFor(readFileSync(mine.file, 'utf8'), steps) : {};
157
+ if (Object.keys(generated).length) {
158
+ const base = parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed as Record<string, unknown> : {};
159
+ const merged = { ...base, ...generated };
160
+ recipeText = stringifyYaml(merged);
161
+ allowed = new Set(Object.keys(merged));
162
+ debug.log('graphs.run', 'wizard-recipes', { id, nodes: Object.keys(generated) });
163
+ }
151
164
  } catch {
152
165
  return jsonResponse({ error: 'recipes-unavailable' }, 503);
153
166
  }
@@ -0,0 +1,88 @@
1
+ /**
2
+ * 마법사 «원형» — 사람이 말로 자주 시키는 일의 뼈대 셋. graphs/** 의 운영 템플릿(개발·출시 루프)은
3
+ * «모아서 요약해 보내기» 같은 말과 낱말이 안 겹쳐 엉뚱한 기반(lecture-note)을 골랐다(10-08 라이브 실측).
4
+ * 원형은 기반 후보 «맨 앞»에 서고, 키워드가 겹치는 만큼 점수를 얻는다.
5
+ */
6
+ export interface WizardArchetype { id: string; description: string; keywords: string[]; text: string }
7
+
8
+ export const WIZARD_ARCHETYPES: readonly WizardArchetype[] = [
9
+ {
10
+ id: 'wizard-collect-summarize-send',
11
+ description: '모아서 요약해 보내기 — 검색·수집 → 요약 → 텔레그램/알림 발송 (브리핑·다이제스트·동향)',
12
+ keywords: ['뉴스', '모아', '수집', '검색', '찾아', '요약', '정리', '보내', '알려', '텔레그램', '브리핑', '동향', '소식', 'news', 'digest', 'summary'],
13
+ text: `graph_id: wizard-collect-summarize-send
14
+ loop:
15
+ title: 모아서 요약해 보내기
16
+ description: 검색으로 모은 내용을 요약해 텔레그램으로 보낸다
17
+ trigger:
18
+ cron: "0 8 * * *"
19
+ version: 1
20
+ entry_node: collect
21
+ terminal_nodes: [done, failed]
22
+ nodes:
23
+ - { node_id: collect, kind: agent, recipe: 'cmd:collect', max_visits: 1 } # 수집 | web-search | 검색어
24
+ - { node_id: summarize, kind: agent, recipe: 'cmd:summarize', max_visits: 1 } # 요약 | summarize | 핵심 5줄 한국어
25
+ - { node_id: send, kind: agent, recipe: 'cmd:send', max_visits: 1 } # 텔레그램 발송 | telegram-send
26
+ - { node_id: done, kind: gate, recipe: none, max_visits: 1 } # 완료
27
+ - { node_id: failed, kind: gate, recipe: none, max_visits: 1 } # 실패
28
+ edges:
29
+ - { from: collect, on: outcome, map: { ok: summarize, fail: failed } }
30
+ - { from: summarize, on: outcome, map: { ok: send, fail: failed } }
31
+ - { from: send, on: outcome, map: { ok: done, fail: failed } }
32
+ `,
33
+ },
34
+ {
35
+ id: 'wizard-review-merge',
36
+ description: 'PR 리뷰 후 머지 — 리뷰 → must-fix 없으면 승인 뒤 머지 (있으면 멈추고 알림)',
37
+ keywords: ['pr', '리뷰', '머지', '병합', '풀리퀘', 'must', 'fix', 'review', 'merge'],
38
+ text: `graph_id: wizard-review-merge
39
+ loop:
40
+ title: PR 리뷰 후 머지
41
+ description: PR 을 리뷰하고 must-fix 가 없으면 머지한다
42
+ trigger:
43
+ events: [pr-opened]
44
+ version: 1
45
+ entry_node: review
46
+ terminal_nodes: [done, failed]
47
+ nodes:
48
+ - { node_id: review, kind: judge, recipe: 'cmd:review', max_visits: 1 } # PR 리뷰 | gh-pr-review
49
+ - { node_id: approve-merge, kind: hitl, recipe: 'approval:approve-merge', max_visits: 1 } # 머지 승인 | approval
50
+ - { node_id: merge, kind: git, recipe: 'cmd:merge', max_visits: 1 } # PR 머지 | gh-pr-merge
51
+ - { node_id: notify, kind: agent, recipe: 'cmd:notify', max_visits: 1 } # must-fix 알림 | notify-me | PR 에 must-fix 가 있어 머지하지 않았다
52
+ - { node_id: done, kind: gate, recipe: none, max_visits: 1 } # 완료
53
+ - { node_id: failed, kind: gate, recipe: none, max_visits: 1 } # 실패
54
+ edges:
55
+ - { from: review, on: outcome, map: { ok: approve-merge, must-fix: notify, fail: failed } }
56
+ - { from: approve-merge, on: outcome, map: { ok: merge, fail: failed } }
57
+ - { from: merge, on: outcome, map: { ok: done, fail: failed } }
58
+ - { from: notify, on: outcome, map: { ok: done, fail: failed } }
59
+ `,
60
+ },
61
+ {
62
+ id: 'wizard-research-approve-publish',
63
+ description: '조사해 정리하고 승인 뒤 게시 — 조사 → 표/초안 정리 → 사람 승인 → 게시',
64
+ keywords: ['조사', '가격', '경쟁', '표', '정리', '승인', '올려', '게시', '노션', '초안', '블로그', '보고서'],
65
+ text: `graph_id: wizard-research-approve-publish
66
+ loop:
67
+ title: 조사해 정리하고 승인 뒤 게시
68
+ description: 조사한 내용을 표로 정리하고 사람 승인 뒤 게시한다
69
+ trigger:
70
+ events: [manual]
71
+ version: 1
72
+ entry_node: research
73
+ terminal_nodes: [done, failed]
74
+ nodes:
75
+ - { node_id: research, kind: agent, recipe: 'cmd:research', max_visits: 1 } # 조사 | web-search | 조사 주제
76
+ - { node_id: tabulate, kind: agent, recipe: 'cmd:tabulate', max_visits: 1 } # 표로 정리 | llm | 결과를 마크다운 표로 정리
77
+ - { node_id: approve, kind: hitl, recipe: 'approval:approve', max_visits: 1 } # 게시 승인 | approval
78
+ - { node_id: publish, kind: agent, recipe: 'cmd:publish', max_visits: 1 } # 게시 | custom | 게시처에 올리기
79
+ - { node_id: done, kind: gate, recipe: none, max_visits: 1 } # 완료
80
+ - { node_id: failed, kind: gate, recipe: none, max_visits: 1 } # 실패
81
+ edges:
82
+ - { from: research, on: outcome, map: { ok: tabulate, fail: failed } }
83
+ - { from: tabulate, on: outcome, map: { ok: approve, fail: failed } }
84
+ - { from: approve, on: outcome, map: { ok: publish, fail: failed } }
85
+ - { from: publish, on: outcome, map: { ok: done, fail: failed } }
86
+ `,
87
+ },
88
+ ];