create-harness-vibe-coding 0.8.19 → 0.9.3

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 (656) hide show
  1. package/CHANGELOG.md +49 -0
  2. package/README-CN.md +4 -2
  3. package/README.md +23 -4
  4. package/package.json +17 -2
  5. package/src/generator.js +19 -6
  6. package/src/index.js +531 -38
  7. package/src/prompts.js +4 -4
  8. package/src/ui/dist/assets/AgentsRoute-BGGs90wH.js +7 -0
  9. package/src/ui/dist/assets/Assistant-Bold-gm-uSS1B.woff2 +0 -0
  10. package/src/ui/dist/assets/Assistant-Medium-DrcxCXg3.woff2 +0 -0
  11. package/src/ui/dist/assets/Assistant-Regular-DVxZuzxb.woff2 +0 -0
  12. package/src/ui/dist/assets/Assistant-SemiBold-SCI4bEL9.woff2 +0 -0
  13. package/src/ui/dist/assets/Combination-DhBLEmof.js +42 -0
  14. package/src/ui/dist/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
  15. package/src/ui/dist/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
  16. package/src/ui/dist/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
  17. package/src/ui/dist/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
  18. package/src/ui/dist/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
  19. package/src/ui/dist/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
  20. package/src/ui/dist/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
  21. package/src/ui/dist/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
  22. package/src/ui/dist/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
  23. package/src/ui/dist/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
  24. package/src/ui/dist/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
  25. package/src/ui/dist/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
  26. package/src/ui/dist/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
  27. package/src/ui/dist/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
  28. package/src/ui/dist/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
  29. package/src/ui/dist/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
  30. package/src/ui/dist/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
  31. package/src/ui/dist/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
  32. package/src/ui/dist/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
  33. package/src/ui/dist/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
  34. package/src/ui/dist/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
  35. package/src/ui/dist/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
  36. package/src/ui/dist/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
  37. package/src/ui/dist/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
  38. package/src/ui/dist/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
  39. package/src/ui/dist/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
  40. package/src/ui/dist/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
  41. package/src/ui/dist/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
  42. package/src/ui/dist/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
  43. package/src/ui/dist/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
  44. package/src/ui/dist/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
  45. package/src/ui/dist/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
  46. package/src/ui/dist/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
  47. package/src/ui/dist/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
  48. package/src/ui/dist/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
  49. package/src/ui/dist/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
  50. package/src/ui/dist/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
  51. package/src/ui/dist/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
  52. package/src/ui/dist/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
  53. package/src/ui/dist/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
  54. package/src/ui/dist/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
  55. package/src/ui/dist/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
  56. package/src/ui/dist/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
  57. package/src/ui/dist/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
  58. package/src/ui/dist/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
  59. package/src/ui/dist/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
  60. package/src/ui/dist/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
  61. package/src/ui/dist/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
  62. package/src/ui/dist/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
  63. package/src/ui/dist/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
  64. package/src/ui/dist/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
  65. package/src/ui/dist/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
  66. package/src/ui/dist/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
  67. package/src/ui/dist/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
  68. package/src/ui/dist/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
  69. package/src/ui/dist/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
  70. package/src/ui/dist/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
  71. package/src/ui/dist/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
  72. package/src/ui/dist/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
  73. package/src/ui/dist/assets/RolesRoute-DnodCSz0.js +2 -0
  74. package/src/ui/dist/assets/RuntimePicker-CucD8TGn.js +7 -0
  75. package/src/ui/dist/assets/SettingsRoute-DquMWjac.js +2 -0
  76. package/src/ui/dist/assets/TaskList-Bxc0Uktx.js +7 -0
  77. package/src/ui/dist/assets/TerminalDrawer-CDusYltc.js +36 -0
  78. package/src/ui/dist/assets/WorkflowRoute-BN8yHmUs.css +1 -0
  79. package/src/ui/dist/assets/WorkflowRoute-D-0wVDpY.js +334 -0
  80. package/src/ui/dist/assets/abnfDiagram-VRR7QNED-zkRKkaxO.js +2 -0
  81. package/src/ui/dist/assets/apl-B4CMkyY2.js +2 -0
  82. package/src/ui/dist/assets/ar-SA-G6X2FPQ2-BiHaArx5.js +11 -0
  83. package/src/ui/dist/assets/arc-CBnLtOFO.js +2 -0
  84. package/src/ui/dist/assets/architectureDiagram-ZJ3FMSHR-DWXRXsh1.js +37 -0
  85. package/src/ui/dist/assets/asciiarmor-Df11BRmG.js +2 -0
  86. package/src/ui/dist/assets/asn1-EdZsLKOL.js +2 -0
  87. package/src/ui/dist/assets/asterisk-B-8jnY81.js +2 -0
  88. package/src/ui/dist/assets/az-AZ-76LH7QW2-CiT05voO.js +2 -0
  89. package/src/ui/dist/assets/bg-BG-XCXSNQG7-L-UM9tNf.js +6 -0
  90. package/src/ui/dist/assets/blockDiagram-677ZJIJ3-BIyh3Bry.js +133 -0
  91. package/src/ui/dist/assets/bn-BD-2XOGV67Q-NdgbA0bC.js +6 -0
  92. package/src/ui/dist/assets/brainfuck-C4LP7Hcl.js +2 -0
  93. package/src/ui/dist/assets/c4Diagram-LMCZKHZV-CA54vWWM.js +11 -0
  94. package/src/ui/dist/assets/ca-ES-6MX7JW3Y-BKZZsQ5J.js +9 -0
  95. package/src/ui/dist/assets/channel-Da_-zcAu.js +2 -0
  96. package/src/ui/dist/assets/chunk-2Q5K7J3B-DE4cxr1v.js +2 -0
  97. package/src/ui/dist/assets/chunk-32BRIVSS-i7FwbEU9.js +2 -0
  98. package/src/ui/dist/assets/chunk-5VM5RSS4-BhIcOYSu.js +16 -0
  99. package/src/ui/dist/assets/chunk-EX3LRPZG-Ci9zZRIs.js +232 -0
  100. package/src/ui/dist/assets/chunk-JWPE2WC7-BFdkcz-G.js +2 -0
  101. package/src/ui/dist/assets/chunk-MOJQB5TN-Dzmapl_S.js +89 -0
  102. package/src/ui/dist/assets/chunk-RYQCIY6F-RTmp7FOy.js +2 -0
  103. package/src/ui/dist/assets/chunk-V7JOEXUC-DInU3v3D.js +207 -0
  104. package/src/ui/dist/assets/chunk-VR4S4FIN-BHoyGBQO.js +2 -0
  105. package/src/ui/dist/assets/chunk-XXDRQBXY-DfeLZNZx.js +2 -0
  106. package/src/ui/dist/assets/classDiagram-OUVF2IWQ-FKbUS0bu.js +2 -0
  107. package/src/ui/dist/assets/classDiagram-v2-EOCWNBFH-FKbUS0bu.js +2 -0
  108. package/src/ui/dist/assets/clike-B9uivgTg.js +2 -0
  109. package/src/ui/dist/assets/clojure-BMjYHr_A.js +2 -0
  110. package/src/ui/dist/assets/cmake-BQqOBYOt.js +2 -0
  111. package/src/ui/dist/assets/cobol-CWcv1MsR.js +2 -0
  112. package/src/ui/dist/assets/coffeescript-S37ZYGWr.js +2 -0
  113. package/src/ui/dist/assets/commonlisp-DBKNyK5s.js +2 -0
  114. package/src/ui/dist/assets/cose-bilkent-JH36ORCC-_5PUJ615.js +2 -0
  115. package/src/ui/dist/assets/crystal-SjHAIU92.js +2 -0
  116. package/src/ui/dist/assets/cs-CZ-2BRQDIVT-DnkfnhDU.js +12 -0
  117. package/src/ui/dist/assets/css-BnMrqG3P.js +2 -0
  118. package/src/ui/dist/assets/cynefin-VYW2F7L2-CmkcFv-b.js +179 -0
  119. package/src/ui/dist/assets/cynefinDiagram-TSTJHNR4-D72VZn8H.js +63 -0
  120. package/src/ui/dist/assets/cypher-C_CwsFkJ.js +2 -0
  121. package/src/ui/dist/assets/cytoscape.esm-DTSO7Bv0.js +332 -0
  122. package/src/ui/dist/assets/d-pRatUO7H.js +2 -0
  123. package/src/ui/dist/assets/da-DK-5WZEPLOC-C54tQUgd.js +6 -0
  124. package/src/ui/dist/assets/dagre-VKFMJZFB-0BsAkrmo.js +5 -0
  125. package/src/ui/dist/assets/de-DE-XR44H4JA-DGfZlR10.js +9 -0
  126. package/src/ui/dist/assets/defaultLocale-DX6XiGOO.js +2 -0
  127. package/src/ui/dist/assets/diagram-FQU43EPY-C1ZJDOca.js +4 -0
  128. package/src/ui/dist/assets/diagram-G47NLZAW-D3pr3FSP.js +25 -0
  129. package/src/ui/dist/assets/diagram-NH7WQ7WH-Bwry0NV-.js +25 -0
  130. package/src/ui/dist/assets/diagram-OA4YK3LP-L6g09cmV.js +31 -0
  131. package/src/ui/dist/assets/diagram-WEI45ONY-Bh1puYVX.js +42 -0
  132. package/src/ui/dist/assets/diff-DbItnlRl.js +2 -0
  133. package/src/ui/dist/assets/directory-open-01563666-DWU9wJ6I.js +2 -0
  134. package/src/ui/dist/assets/directory-open-4ed118d0-CunoC1EB.js +2 -0
  135. package/src/ui/dist/assets/dockerfile-BKs6k2Af.js +2 -0
  136. package/src/ui/dist/assets/dtd-DF_7sFjM.js +2 -0
  137. package/src/ui/dist/assets/dylan-DwRh75JA.js +2 -0
  138. package/src/ui/dist/assets/ebnf-CDyGwa7X.js +2 -0
  139. package/src/ui/dist/assets/ebnfDiagram-CCIWWBDH-BC_mBRR5.js +2 -0
  140. package/src/ui/dist/assets/ecl-Cabwm37j.js +2 -0
  141. package/src/ui/dist/assets/eiffel-CnydiIhH.js +2 -0
  142. package/src/ui/dist/assets/el-GR-BZB4AONW-DsnwWweL.js +11 -0
  143. package/src/ui/dist/assets/elm-vLlmbW-K.js +2 -0
  144. package/src/ui/dist/assets/erDiagram-Q63AITRT-Bkxam36l.js +86 -0
  145. package/src/ui/dist/assets/erlang-BNw1qcRV.js +2 -0
  146. package/src/ui/dist/assets/es-ES-U4NZUMDT-CRxBcQgC.js +10 -0
  147. package/src/ui/dist/assets/eu-ES-A7QVB2H4-CTd_E7YR.js +12 -0
  148. package/src/ui/dist/assets/excalidraw-default-bMWIgNCy.svg +1 -0
  149. package/src/ui/dist/assets/fa-IR-HGAKTJCU-DO56k7eG.js +9 -0
  150. package/src/ui/dist/assets/factor-kuTfRLto.js +2 -0
  151. package/src/ui/dist/assets/fcl-Kvtd6kyn.js +2 -0
  152. package/src/ui/dist/assets/fi-FI-Z5N7JZ37-DEDCF9WB.js +7 -0
  153. package/src/ui/dist/assets/file-open-002ab408-DIuFHtCF.js +2 -0
  154. package/src/ui/dist/assets/file-open-7c801643-684qeFg4.js +2 -0
  155. package/src/ui/dist/assets/file-save-3189631c-C1wFhQhH.js +2 -0
  156. package/src/ui/dist/assets/file-save-745eba88-Bb9F9Kg7.js +2 -0
  157. package/src/ui/dist/assets/flowDiagram-23GEKE2U-C3SAEao7.js +157 -0
  158. package/src/ui/dist/assets/forth-Ffai-XNe.js +2 -0
  159. package/src/ui/dist/assets/fortran-DYz_wnZ1.js +2 -0
  160. package/src/ui/dist/assets/fr-FR-RHASNOE6-DE2nPyDt.js +10 -0
  161. package/src/ui/dist/assets/ganttDiagram-NO4QXBWP-U-J_Xi-_.js +293 -0
  162. package/src/ui/dist/assets/gas-Bneqetm1.js +2 -0
  163. package/src/ui/dist/assets/gherkin-heZmZLOM.js +2 -0
  164. package/src/ui/dist/assets/gitGraphDiagram-IHSO6WYX-BADVbD-a.js +107 -0
  165. package/src/ui/dist/assets/gl-ES-HMX3MZ6V-mCqVgQHC.js +11 -0
  166. package/src/ui/dist/assets/graph-C9eacEi8.js +2 -0
  167. package/src/ui/dist/assets/groovy-D9Dt4D0W.js +2 -0
  168. package/src/ui/dist/assets/haskell-Cw1EW3IL.js +2 -0
  169. package/src/ui/dist/assets/haxe-H-WmDvRZ.js +2 -0
  170. package/src/ui/dist/assets/he-IL-6SHJWFNN-KnaLrCzw.js +11 -0
  171. package/src/ui/dist/assets/hi-IN-IWLTKZ5I-wuqiaaWt.js +5 -0
  172. package/src/ui/dist/assets/http-DBlCnlav.js +2 -0
  173. package/src/ui/dist/assets/hu-HU-A5ZG7DT2-DLg_zm22.js +8 -0
  174. package/src/ui/dist/assets/id-ID-SAP4L64H-MB2mXqcR.js +11 -0
  175. package/src/ui/dist/assets/idl-BEugSyMb.js +2 -0
  176. package/src/ui/dist/assets/image-blob-reduce.esm-HwCm7IZ0.js +8 -0
  177. package/src/ui/dist/assets/index-39SJtrt0.js +2 -0
  178. package/src/ui/dist/assets/index-BQ0LIrW-.js +2 -0
  179. package/src/ui/dist/assets/index-BcAUh2Nm.css +1 -0
  180. package/src/ui/dist/assets/index-BcThOK-i.js +331 -0
  181. package/src/ui/dist/assets/index-BnxV3k_S.js +3 -0
  182. package/src/ui/dist/assets/index-C7MZBHbT.js +117 -0
  183. package/src/ui/dist/assets/index-CCvKI2rM.js +2 -0
  184. package/src/ui/dist/assets/index-CZvmcCab.js +8 -0
  185. package/src/ui/dist/assets/index-Cp3hLKNl.js +2 -0
  186. package/src/ui/dist/assets/index-D7ayFxcx.js +2 -0
  187. package/src/ui/dist/assets/index-DAYxSQGU.js +2 -0
  188. package/src/ui/dist/assets/index-DD-HeMV0.css +1 -0
  189. package/src/ui/dist/assets/index-DPMxd3Qn.js +2 -0
  190. package/src/ui/dist/assets/index-DSe2jx3k.js +4 -0
  191. package/src/ui/dist/assets/index-DUirCkgW.js +2 -0
  192. package/src/ui/dist/assets/index-DaKPQ2hN.js +159 -0
  193. package/src/ui/dist/assets/index-DzpuR0Jg.js +2 -0
  194. package/src/ui/dist/assets/index-POndwsCI.js +2 -0
  195. package/src/ui/dist/assets/index-R8NM9I_9.js +2 -0
  196. package/src/ui/dist/assets/index-TCDuDhBj.js +2 -0
  197. package/src/ui/dist/assets/index-TckzASdM.js +2 -0
  198. package/src/ui/dist/assets/index-Yi5ytFFr.js +2 -0
  199. package/src/ui/dist/assets/infoDiagram-FWYZ7A6U-CiGBlFqH.js +3 -0
  200. package/src/ui/dist/assets/init-Gi6I4Gst.js +2 -0
  201. package/src/ui/dist/assets/ishikawaDiagram-FXEZZL3T-B1aheRbz.js +71 -0
  202. package/src/ui/dist/assets/it-IT-JPQ66NNP-DT3HSU7X.js +12 -0
  203. package/src/ui/dist/assets/ja-JP-DBVTYXUO-CsfJjjWt.js +9 -0
  204. package/src/ui/dist/assets/javascript-iXu5QeM3.js +2 -0
  205. package/src/ui/dist/assets/journeyDiagram-5HDEW3XC-DtnJPxub.js +140 -0
  206. package/src/ui/dist/assets/julia-DuME0IfC.js +2 -0
  207. package/src/ui/dist/assets/kaa-6HZHGXH3-DQz-1-Gn.js +2 -0
  208. package/src/ui/dist/assets/kab-KAB-ZGHBKWFO-CaH-LSoK.js +9 -0
  209. package/src/ui/dist/assets/kanban-definition-HUTT4EX6-CZatSm3e.js +90 -0
  210. package/src/ui/dist/assets/katex-C5jXJg4s.js +258 -0
  211. package/src/ui/dist/assets/kk-KZ-P5N5QNE5-BMXmcm55.js +2 -0
  212. package/src/ui/dist/assets/km-KH-HSX4SM5Z-ZeoJNBpN.js +12 -0
  213. package/src/ui/dist/assets/ko-KR-MTYHY66A-BeaLRKph.js +10 -0
  214. package/src/ui/dist/assets/ku-TR-6OUDTVRD-B_28KrkR.js +10 -0
  215. package/src/ui/dist/assets/layout-DEXfKzaS.js +2 -0
  216. package/src/ui/dist/assets/linear-CpEDnQRC.js +2 -0
  217. package/src/ui/dist/assets/livescript-BwQOo05w.js +2 -0
  218. package/src/ui/dist/assets/lt-LT-XHIRWOB4-BxDcnUhp.js +4 -0
  219. package/src/ui/dist/assets/lua-VAEuO923.js +2 -0
  220. package/src/ui/dist/assets/lv-LV-5QDEKY6T-DpKDl1_y.js +8 -0
  221. package/src/ui/dist/assets/map-Czzmt4hB.js +2 -0
  222. package/src/ui/dist/assets/mathematica-DTrFuWx2.js +2 -0
  223. package/src/ui/dist/assets/mbox-CNhZ1qSd.js +2 -0
  224. package/src/ui/dist/assets/mermaidLoader-CBO31t0J.js +305 -0
  225. package/src/ui/dist/assets/mermaidLoader-CEK31ho9.css +1 -0
  226. package/src/ui/dist/assets/mindmap-definition-LN4V7U3C-CQ0TccEr.js +97 -0
  227. package/src/ui/dist/assets/mirc-CjQqDB4T.js +2 -0
  228. package/src/ui/dist/assets/mllike-CXdrOF99.js +2 -0
  229. package/src/ui/dist/assets/modelica-Dc1JOy9r.js +2 -0
  230. package/src/ui/dist/assets/mr-IN-CRQNXWMA-BKCZT58p.js +14 -0
  231. package/src/ui/dist/assets/mscgen-BA5vi2Kp.js +2 -0
  232. package/src/ui/dist/assets/mumps-BT43cFF4.js +2 -0
  233. package/src/ui/dist/assets/my-MM-5M5IBNSE-D34k0G9T.js +2 -0
  234. package/src/ui/dist/assets/nb-NO-T6EIAALU-DXr_KW8J.js +11 -0
  235. package/src/ui/dist/assets/nginx-DdIZxoE0.js +2 -0
  236. package/src/ui/dist/assets/nl-NL-IS3SIHDZ-C1L0aVDR.js +9 -0
  237. package/src/ui/dist/assets/nn-NO-6E72VCQL-CLWVSxbZ.js +9 -0
  238. package/src/ui/dist/assets/nsis-LdVXkNf5.js +2 -0
  239. package/src/ui/dist/assets/ntriples-BfvgReVJ.js +2 -0
  240. package/src/ui/dist/assets/oc-FR-POXYY2M6-nf8_J3eR.js +9 -0
  241. package/src/ui/dist/assets/octave-Ck1zUtKM.js +2 -0
  242. package/src/ui/dist/assets/ordinal-Cboi1Yqb.js +2 -0
  243. package/src/ui/dist/assets/oz-BzwKVEFT.js +2 -0
  244. package/src/ui/dist/assets/pa-IN-N4M65BXN-CAMJ-xd6.js +5 -0
  245. package/src/ui/dist/assets/pascal--L3eBynH.js +2 -0
  246. package/src/ui/dist/assets/pegDiagram-2B236MQR-Bj2RcmRd.js +2 -0
  247. package/src/ui/dist/assets/percentages-BXMCSKIN-s9yWFdr6.js +160 -0
  248. package/src/ui/dist/assets/perl-CdXCOZ3F.js +2 -0
  249. package/src/ui/dist/assets/pica-BGv_AhAB.js +8 -0
  250. package/src/ui/dist/assets/pieDiagram-ENE6RG2P-obpzz8dC.js +40 -0
  251. package/src/ui/dist/assets/pig-CevX1Tat.js +2 -0
  252. package/src/ui/dist/assets/pl-PL-T2D74RX3-DKJ5H85v.js +10 -0
  253. package/src/ui/dist/assets/powershell-CFHJl5sT.js +2 -0
  254. package/src/ui/dist/assets/properties-C78fOPTZ.js +2 -0
  255. package/src/ui/dist/assets/protobuf-ChK-085T.js +2 -0
  256. package/src/ui/dist/assets/{proxy-D0vUbds5.js → proxy-BSmwef4H.js} +2 -2
  257. package/src/ui/dist/assets/pt-BR-5N22H2LF-B5kow-kN.js +10 -0
  258. package/src/ui/dist/assets/pt-PT-UZXXM6DQ-B2G34fso.js +10 -0
  259. package/src/ui/dist/assets/pug-DeIclll2.js +2 -0
  260. package/src/ui/dist/assets/puppet-DMA9R1ak.js +2 -0
  261. package/src/ui/dist/assets/python-BuPzkPfP.js +2 -0
  262. package/src/ui/dist/assets/q-pXgVlZs6.js +2 -0
  263. package/src/ui/dist/assets/quadrantDiagram-ABIIQ3AL-Cl-T7NkJ.js +8 -0
  264. package/src/ui/dist/assets/r-B6wPVr8A.js +2 -0
  265. package/src/ui/dist/assets/railroadDiagram-RFXS5EU6-DMq82RHg.js +2 -0
  266. package/src/ui/dist/assets/{refresh-cw-JNmExijd.js → refresh-cw-CbFd7guV.js} +2 -2
  267. package/src/ui/dist/assets/requirementDiagram-TGXJPOKE-tGInMDgU.js +85 -0
  268. package/src/ui/dist/assets/ro-RO-JPDTUUEW-BwLdZW91.js +12 -0
  269. package/src/ui/dist/assets/roundRect-0PYZxl1G.js +2 -0
  270. package/src/ui/dist/assets/rpm-CTu-6PCP.js +2 -0
  271. package/src/ui/dist/assets/ru-RU-B4JR7IUQ-DpdAgIJc.js +10 -0
  272. package/src/ui/dist/assets/ruby-B2Rjki9n.js +2 -0
  273. package/src/ui/dist/assets/runtimeBrand-BoKPimsz.js +2 -0
  274. package/src/ui/dist/assets/sankeyDiagram-HTMAVEWB-C7X-ET4m.js +41 -0
  275. package/src/ui/dist/assets/sas-B4kiWyti.js +2 -0
  276. package/src/ui/dist/assets/save-D1CXHwBw.js +12 -0
  277. package/src/ui/dist/assets/scheme-C41bIUwD.js +2 -0
  278. package/src/ui/dist/assets/sequenceDiagram-DBY2YBRQ-Bn24i_nY.js +163 -0
  279. package/src/ui/dist/assets/settings-2-DtSNAAcU.js +22 -0
  280. package/src/ui/dist/assets/shell-CjFT_Tl9.js +2 -0
  281. package/src/ui/dist/assets/shield-check-BVbJ8T_N.js +7 -0
  282. package/src/ui/dist/assets/si-LK-N5RQ5JYF-naJaUy5u.js +2 -0
  283. package/src/ui/dist/assets/sieve-C3Gn_uJK.js +2 -0
  284. package/src/ui/dist/assets/simple-mode-GW_nhZxv.js +2 -0
  285. package/src/ui/dist/assets/sizeCapture-X5ZJPWSS-DAQHfm82.js +2 -0
  286. package/src/ui/dist/assets/sk-SK-C5VTKIMK-1NPz1J_d.js +7 -0
  287. package/src/ui/dist/assets/sl-SI-NN7IZMDC-D8RfmNQw.js +7 -0
  288. package/src/ui/dist/assets/smalltalk-CnHTOXQT.js +2 -0
  289. package/src/ui/dist/assets/solr-DehyRSwq.js +2 -0
  290. package/src/ui/dist/assets/sparql-DkYu6x3z.js +2 -0
  291. package/src/ui/dist/assets/spreadsheet-BCZA_wO0.js +2 -0
  292. package/src/ui/dist/assets/sql-D0XecflT.js +2 -0
  293. package/src/ui/dist/assets/stateDiagram-2N3HPSRC-Ck48D1ck.js +2 -0
  294. package/src/ui/dist/assets/stateDiagram-v2-6OUMAXLB-CdfRCJAJ.js +2 -0
  295. package/src/ui/dist/assets/stex-C3f8Ysf7.js +2 -0
  296. package/src/ui/dist/assets/style-C3QI7VPf.css +1 -0
  297. package/src/ui/dist/assets/stylus-B533Al4x.js +2 -0
  298. package/src/ui/dist/assets/subset-shared.chunk-eAtLHmbi.js +85 -0
  299. package/src/ui/dist/assets/subset-worker.chunk-DeHCTnTk.js +2 -0
  300. package/src/ui/dist/assets/sv-SE-XGPEYMSR-BeOYCU-8.js +11 -0
  301. package/src/ui/dist/assets/swift-BzpIVaGY.js +2 -0
  302. package/src/ui/dist/assets/swimlanes-5IMT3BWC-DOy22UjP.js +3 -0
  303. package/src/ui/dist/assets/swimlanesDiagram-G3AALYLV-sicPbVOg.js +9 -0
  304. package/src/ui/dist/assets/ta-IN-2NMHFXQM-CtS68WDd.js +10 -0
  305. package/src/ui/dist/assets/tcl-DVfN8rqt.js +2 -0
  306. package/src/ui/dist/assets/{terminal-BBQvnOAK.js → terminal-DEby5Y_P.js} +2 -2
  307. package/src/ui/dist/assets/terminalControl-BkFeDVXo.js +134 -0
  308. package/src/ui/dist/assets/textile-CnDTJFAw.js +2 -0
  309. package/src/ui/dist/assets/th-TH-HPSO5L25-DCHY9-Co.js +3 -0
  310. package/src/ui/dist/assets/tiddlywiki-DO-Gjzrf.js +2 -0
  311. package/src/ui/dist/assets/tiki-DGYXhP31.js +2 -0
  312. package/src/ui/dist/assets/timeline-definition-FHXFAJF6-DVkG72qO.js +121 -0
  313. package/src/ui/dist/assets/toml-Bm5Em-hy.js +2 -0
  314. package/src/ui/dist/assets/tr-TR-DEFEU3FU-y5-vjTv8.js +8 -0
  315. package/src/ui/dist/assets/troff-wAsdV37c.js +2 -0
  316. package/src/ui/dist/assets/ttcn-CfJYG6tj.js +2 -0
  317. package/src/ui/dist/assets/ttcn-cfg-B9xdYoR4.js +2 -0
  318. package/src/ui/dist/assets/turtle-B1tBg_DP.js +2 -0
  319. package/src/ui/dist/assets/uk-UA-QMV73CPH-BRbR5It6.js +7 -0
  320. package/src/ui/dist/assets/useReducedMotion-D07XP52Q.js +12 -0
  321. package/src/ui/dist/assets/vb-CmGdzxic.js +2 -0
  322. package/src/ui/dist/assets/vbscript-BuJXcnF6.js +2 -0
  323. package/src/ui/dist/assets/velocity-D8B20fx6.js +2 -0
  324. package/src/ui/dist/assets/vennDiagram-L72KCM5P-DwMwBlf-.js +35 -0
  325. package/src/ui/dist/assets/verilog-C6RDOZhf.js +2 -0
  326. package/src/ui/dist/assets/vhdl-lSbBsy5d.js +2 -0
  327. package/src/ui/dist/assets/vi-VN-M7AON7JQ-s6wSBv2s.js +6 -0
  328. package/src/ui/dist/assets/wardleyDiagram-EHGQE667-CNuuvzEg.js +79 -0
  329. package/src/ui/dist/assets/webidl-ZXfAyPTL.js +2 -0
  330. package/src/ui/dist/assets/{x-DoIOGAJz.js → x-B84wq78k.js} +2 -2
  331. package/src/ui/dist/assets/xquery-DzFWVndE.js +2 -0
  332. package/src/ui/dist/assets/xychartDiagram-FW5EYKEG-dUpZxmn6.js +8 -0
  333. package/src/ui/dist/assets/yacas-BJ4BC0dw.js +2 -0
  334. package/src/ui/dist/assets/z80-Hz9HOZM7.js +2 -0
  335. package/src/ui/dist/assets/zh-CN-LNUGB5OW-Cu_-POQN.js +11 -0
  336. package/src/ui/dist/assets/zh-HK-E62DVLB3-BCx4W4qZ.js +2 -0
  337. package/src/ui/dist/assets/zh-TW-RAJ6MFWO-02wx_Mrj.js +10 -0
  338. package/src/ui/dist/icons/excalidraw.svg +1 -0
  339. package/src/ui/dist/icons/markdown.svg +1 -0
  340. package/src/ui/dist/index.html +2 -2
  341. package/src/ui/package.json +10 -0
  342. package/src/ui/playwright.config.ts +36 -0
  343. package/src/ui/pnpm-workspace.yaml +2 -0
  344. package/src/ui/public/icons/excalidraw.svg +1 -0
  345. package/src/ui/public/icons/markdown.svg +1 -0
  346. package/src/ui/tsconfig.json +2 -1
  347. package/src/ui/vite.config.ts +1 -0
  348. package/src/wf-ui-server/__tests__/a2a-store.test.mjs +432 -15
  349. package/src/wf-ui-server/__tests__/action-registry-loader.test.mjs +156 -0
  350. package/src/wf-ui-server/__tests__/claude-stream-json.test.mjs +397 -0
  351. package/src/wf-ui-server/__tests__/codex-appserver.test.mjs +392 -0
  352. package/src/wf-ui-server/__tests__/component-node-api.test.mjs +881 -0
  353. package/src/wf-ui-server/__tests__/component-node-store.test.mjs +401 -0
  354. package/src/wf-ui-server/__tests__/control-plane-acceptance.test.mjs +2027 -0
  355. package/src/wf-ui-server/__tests__/control-plane-cli-smoke.test.mjs +1014 -0
  356. package/src/wf-ui-server/__tests__/dispatch-lifecycle.test.mjs +330 -0
  357. package/src/wf-ui-server/__tests__/effort-compatibility-red-092.test.mjs +151 -0
  358. package/src/wf-ui-server/__tests__/graph-layout-agent-tree.test.mjs +254 -0
  359. package/src/wf-ui-server/__tests__/graph-layout-tidy.test.mjs +260 -0
  360. package/src/wf-ui-server/__tests__/graph-layout.test.mjs +84 -0
  361. package/src/wf-ui-server/__tests__/node-config-api.test.mjs +384 -0
  362. package/src/wf-ui-server/__tests__/opencode-server.test.mjs +526 -0
  363. package/src/wf-ui-server/__tests__/peer-capsule.test.mjs +2 -2
  364. package/src/wf-ui-server/__tests__/pty-adapter.test.mjs +71 -1
  365. package/src/wf-ui-server/__tests__/runtime-config.test.mjs +3 -3
  366. package/src/wf-ui-server/__tests__/runtime-detector.test.mjs +21 -0
  367. package/src/wf-ui-server/__tests__/runtime-review.test.mjs +828 -0
  368. package/src/wf-ui-server/__tests__/server.integration.test.mjs +1463 -11
  369. package/src/wf-ui-server/__tests__/session-cleanup.test.mjs +159 -0
  370. package/src/wf-ui-server/__tests__/session-registry.test.mjs +52 -3
  371. package/src/wf-ui-server/__tests__/settings.test.mjs +12 -2
  372. package/src/wf-ui-server/__tests__/spawn-gate.test.mjs +78 -0
  373. package/src/wf-ui-server/__tests__/task-group-parser.test.mjs +120 -0
  374. package/src/wf-ui-server/__tests__/task-group-tag.test.mjs +264 -0
  375. package/src/wf-ui-server/__tests__/task-parser.test.mjs +49 -3
  376. package/src/wf-ui-server/__tests__/terminal-context-hardening.test.mjs +122 -0
  377. package/src/wf-ui-server/__tests__/terminal-context.test.mjs +164 -0
  378. package/src/wf-ui-server/__tests__/terminal-store.test.mjs +4 -4
  379. package/src/wf-ui-server/__tests__/wf-browser-api.test.mjs +599 -0
  380. package/src/wf-ui-server/__tests__/wf-browser-store.test.mjs +184 -0
  381. package/src/wf-ui-server/__tests__/workflow-action-registry.test.mjs +282 -0
  382. package/src/wf-ui-server/__tests__/workflow-agent-communication.test.mjs +411 -0
  383. package/src/wf-ui-server/__tests__/workflow-agent-context.test.mjs +323 -0
  384. package/src/wf-ui-server/__tests__/workflow-agent-cooperation-permissions.test.mjs +318 -0
  385. package/src/wf-ui-server/__tests__/workflow-agent-messages-structured.test.mjs +458 -0
  386. package/src/wf-ui-server/__tests__/workflow-agent-routing.test.mjs +434 -0
  387. package/src/wf-ui-server/__tests__/workflow-agent-runtime.test.mjs +324 -0
  388. package/src/wf-ui-server/__tests__/workflow-api-integration.test.mjs +263 -0
  389. package/src/wf-ui-server/__tests__/workflow-api-phase3.test.mjs +1213 -0
  390. package/src/wf-ui-server/__tests__/workflow-claude-materialize.test.mjs +209 -0
  391. package/src/wf-ui-server/__tests__/workflow-cli-registry-manuals.test.mjs +173 -0
  392. package/src/wf-ui-server/__tests__/workflow-cli-surface.test.mjs +176 -0
  393. package/src/wf-ui-server/__tests__/workflow-codex-resume-id.test.mjs +556 -0
  394. package/src/wf-ui-server/__tests__/workflow-cooperation-enforcement.test.mjs +553 -0
  395. package/src/wf-ui-server/__tests__/workflow-depth-resume-dock.test.mjs +846 -0
  396. package/src/wf-ui-server/__tests__/workflow-display-node.test.mjs +150 -0
  397. package/src/wf-ui-server/__tests__/workflow-edge-update.test.mjs +296 -0
  398. package/src/wf-ui-server/__tests__/workflow-file-cache-lock-watch.test.mjs +380 -0
  399. package/src/wf-ui-server/__tests__/workflow-file-formats.test.mjs +994 -0
  400. package/src/wf-ui-server/__tests__/workflow-file-node-actions.test.mjs +294 -0
  401. package/src/wf-ui-server/__tests__/workflow-goal-actions.test.mjs +357 -0
  402. package/src/wf-ui-server/__tests__/workflow-goal-task-autoclose.test.mjs +256 -0
  403. package/src/wf-ui-server/__tests__/workflow-graceful-stop.test.mjs +108 -0
  404. package/src/wf-ui-server/__tests__/workflow-graph-undo.test.mjs +524 -0
  405. package/src/wf-ui-server/__tests__/workflow-layout.test.mjs +732 -0
  406. package/src/wf-ui-server/__tests__/workflow-magnetic-topology.test.mjs +222 -0
  407. package/src/wf-ui-server/__tests__/workflow-markdown-lock.test.mjs +359 -0
  408. package/src/wf-ui-server/__tests__/workflow-mcp-hub.test.mjs +174 -0
  409. package/src/wf-ui-server/__tests__/workflow-node-manual-schema.test.mjs +241 -0
  410. package/src/wf-ui-server/__tests__/workflow-node-runtime.test.mjs +1991 -0
  411. package/src/wf-ui-server/__tests__/workflow-node-store-utils.test.mjs +125 -0
  412. package/src/wf-ui-server/__tests__/workflow-registry-drift.test.mjs +296 -0
  413. package/src/wf-ui-server/__tests__/workflow-resource-handle-four-side.test.mjs +217 -0
  414. package/src/wf-ui-server/__tests__/workflow-role-profile.test.mjs +234 -0
  415. package/src/wf-ui-server/__tests__/workflow-set-model.test.mjs +261 -0
  416. package/src/wf-ui-server/__tests__/workflow-skill-group-context-enabled.test.mjs +131 -0
  417. package/src/wf-ui-server/__tests__/workflow-skill-group-enable-disable.test.mjs +186 -0
  418. package/src/wf-ui-server/__tests__/workflow-skills-hub.test.mjs +125 -0
  419. package/src/wf-ui-server/__tests__/workflow-state-reconcile.test.mjs +139 -0
  420. package/src/wf-ui-server/__tests__/workflow-subagent-strategy-matrix.test.mjs +529 -0
  421. package/src/wf-ui-server/__tests__/workflow-team-flow.test.mjs +278 -0
  422. package/src/wf-ui-server/__tests__/workflow-terminal-control-live.test.mjs +294 -0
  423. package/src/wf-ui-server/__tests__/workflow-terminal-control.test.mjs +631 -0
  424. package/src/wf-ui-server/__tests__/workflow-timer-wakeup.test.mjs +318 -0
  425. package/src/wf-ui-server/__tests__/workflow-unified-resume.test.mjs +537 -0
  426. package/src/wf-ui-server/__tests__/workspace-api-hardening.test.mjs +187 -0
  427. package/src/wf-ui-server/__tests__/workspace-api.test.mjs +382 -0
  428. package/src/wf-ui-server/__tests__/workspace-reveal.test.mjs +138 -0
  429. package/src/wf-ui-server/__tests__/workspace-store-hardening.test.mjs +255 -0
  430. package/src/wf-ui-server/__tests__/workspace-store.test.mjs +208 -0
  431. package/src/wf-ui-server/__tests__/ws-events.integration.test.mjs +52 -25
  432. package/src/wf-ui-server/__tests__/ws-terminal.integration.test.mjs +12 -6
  433. package/src/wf-ui-server/__tests__/ws-wf-browser.integration.test.mjs +488 -0
  434. package/src/wf-ui-server/a2a-store.mjs +3007 -89
  435. package/src/wf-ui-server/action-registry.mjs +277 -0
  436. package/src/wf-ui-server/bridge-store.mjs +276 -0
  437. package/src/wf-ui-server/chat-driver.mjs +220 -0
  438. package/src/wf-ui-server/chat-store.mjs +109 -0
  439. package/src/wf-ui-server/claude-transcript-materializer.mjs +235 -0
  440. package/src/wf-ui-server/codex-update-prompt.mjs +53 -0
  441. package/src/wf-ui-server/component-node-store.mjs +689 -0
  442. package/src/wf-ui-server/composition-api.mjs +436 -0
  443. package/src/wf-ui-server/control-plane-token.mjs +17 -0
  444. package/src/wf-ui-server/dispatch-contract.mjs +150 -0
  445. package/src/wf-ui-server/drivers/claude-stream-json.mjs +371 -0
  446. package/src/wf-ui-server/drivers/codex-appserver.mjs +513 -0
  447. package/src/wf-ui-server/drivers/opencode-server.mjs +486 -0
  448. package/src/wf-ui-server/excalidraw-svg.mjs +141 -0
  449. package/src/wf-ui-server/file-format-adapters.mjs +511 -0
  450. package/src/wf-ui-server/file-watcher.mjs +215 -0
  451. package/src/wf-ui-server/graph-layout.mjs +778 -0
  452. package/src/wf-ui-server/harness-env.mjs +46 -0
  453. package/src/wf-ui-server/json-store-utils.mjs +48 -0
  454. package/src/wf-ui-server/model-capability.mjs +773 -0
  455. package/src/wf-ui-server/node-config-store.mjs +233 -0
  456. package/src/wf-ui-server/node-store-utils.mjs +88 -0
  457. package/src/wf-ui-server/peer-capsule.mjs +56 -1
  458. package/src/wf-ui-server/pty-adapter.mjs +513 -11
  459. package/src/wf-ui-server/reveal-foreground.mjs +69 -0
  460. package/src/wf-ui-server/runtime-detector.mjs +52 -6
  461. package/src/wf-ui-server/server.mjs +5613 -403
  462. package/src/wf-ui-server/session-cleanup.mjs +363 -0
  463. package/src/wf-ui-server/session-registry.mjs +179 -3
  464. package/src/wf-ui-server/settings.mjs +25 -0
  465. package/src/wf-ui-server/spawn-gate.mjs +46 -0
  466. package/src/wf-ui-server/task-parser.mjs +88 -3
  467. package/src/wf-ui-server/terminal-store.mjs +225 -11
  468. package/src/wf-ui-server/timer-wakeup-scheduler.mjs +226 -0
  469. package/src/wf-ui-server/wf-browser-store.mjs +733 -0
  470. package/src/wf-ui-server/workflow-agent-context.mjs +1731 -0
  471. package/src/wf-ui-server/workflow-capability-node-store.mjs +676 -0
  472. package/src/wf-ui-server/workflow-event-node-store.mjs +623 -0
  473. package/src/wf-ui-server/workflow-goal-node-store.mjs +328 -0
  474. package/src/wf-ui-server/workflow-graph-store.mjs +623 -0
  475. package/src/wf-ui-server/workflow-mcp-hub.mjs +286 -0
  476. package/src/wf-ui-server/workflow-node-runtime.mjs +1767 -0
  477. package/src/wf-ui-server/workflow-node-settings-store.mjs +339 -0
  478. package/src/wf-ui-server/workflow-node-types/agent-node.mjs +920 -0
  479. package/src/wf-ui-server/workflow-node-types/display-node.mjs +151 -0
  480. package/src/wf-ui-server/workflow-node-types/excalidraw-node.mjs +145 -0
  481. package/src/wf-ui-server/workflow-node-types/file-node.mjs +484 -0
  482. package/src/wf-ui-server/workflow-node-types/github-trigger-node.mjs +207 -0
  483. package/src/wf-ui-server/workflow-node-types/goal-node.mjs +491 -0
  484. package/src/wf-ui-server/workflow-node-types/markdown-node.mjs +225 -0
  485. package/src/wf-ui-server/workflow-node-types/mcp-connector-node.mjs +71 -0
  486. package/src/wf-ui-server/workflow-node-types/role-profile-store.mjs +191 -0
  487. package/src/wf-ui-server/workflow-node-types/skill-group-node.mjs +98 -0
  488. package/src/wf-ui-server/workflow-node-types/timer-node.mjs +607 -0
  489. package/src/wf-ui-server/workflow-ontology.mjs +681 -0
  490. package/src/wf-ui-server/workflow-operation-store.mjs +115 -0
  491. package/src/wf-ui-server/workflow-skills-hub.mjs +366 -0
  492. package/src/wf-ui-server/workflow-skills-market.mjs +723 -0
  493. package/src/wf-ui-server/workspace-store.mjs +951 -0
  494. package/src/wf-ui-server/ws-chat.mjs +299 -0
  495. package/src/wf-ui-server/ws-events.mjs +45 -20
  496. package/src/wf-ui-server/ws-terminal.mjs +165 -22
  497. package/src/wf-ui-server/ws-wf-browser.mjs +419 -0
  498. package/templates/common/.claude/agents/review-manager.md +33 -29
  499. package/templates/common/.claude/commands/wf-browser.md +1 -1
  500. package/templates/common/.claude/commands/wf-help.md +5 -3
  501. package/templates/common/.claude/commands/wf-init.md +44 -0
  502. package/templates/common/.claude/commands/wf-learn.md +34 -8
  503. package/templates/common/.claude/commands/wf-max.md +19 -9
  504. package/templates/common/.claude/commands/wf-review.md +3 -3
  505. package/templates/common/.claude/commands/wf-search.md +55 -0
  506. package/templates/common/.claude/commands/wf-task-archive.md +5 -4
  507. package/templates/common/.claude/commands/wf-task-list.md +6 -3
  508. package/templates/common/.claude/commands/wf-task-record.md +10 -2
  509. package/templates/common/.claude/commands/wf-ui.md +12 -5
  510. package/templates/common/.claude/commands/wf-update.md +4 -0
  511. package/templates/common/.claude/commands/wf.md +12 -1
  512. package/templates/common/.claude/rules/ecc/common.md +10 -1
  513. package/templates/common/.claude/skills/wf/SKILL.md +36 -0
  514. package/templates/common/.claude/skills/wf-browser/SKILL.md +427 -122
  515. package/templates/common/.claude/skills/wf-browser/references/BROWSER_CONTROL_PATTERNS.md +43 -0
  516. package/templates/common/.claude/skills/wf-init/SKILL.md +33 -0
  517. package/templates/common/.claude/skills/wf-learn/SKILL.md +106 -26
  518. package/templates/common/.claude/skills/wf-max/SKILL.md +45 -11
  519. package/templates/common/.claude/skills/wf-review/SKILL.md +26 -54
  520. package/templates/common/.claude/skills/wf-search/SKILL.md +122 -0
  521. package/templates/common/.claude/skills/wf-search/references/evidence-contract.md +77 -0
  522. package/templates/common/.claude/skills/wf-search/references/policies/compare.md +38 -0
  523. package/templates/common/.claude/skills/wf-search/references/policies/fact.md +39 -0
  524. package/templates/common/.claude/skills/wf-search/references/policies/troubleshoot.md +39 -0
  525. package/templates/common/.claude/skills/wf-search/references/policies/verify.md +44 -0
  526. package/templates/common/.claude/skills/wf-search/references/source-routing.md +40 -0
  527. package/templates/common/.claude/skills/wf-search/references/tool-adapters.md +38 -0
  528. package/templates/common/.claude/skills/wf-ui/SKILL.md +146 -12
  529. package/templates/common/.claude/skills/wf-update/SKILL.md +5 -1
  530. package/templates/common/.harness-version +188 -85
  531. package/templates/common/.opencode/agents/review-manager.md +31 -27
  532. package/templates/common/.opencode/commands/wf-browser.md +1 -1
  533. package/templates/common/.opencode/commands/wf-help.md +5 -3
  534. package/templates/common/.opencode/commands/wf-init.md +44 -0
  535. package/templates/common/.opencode/commands/wf-learn.md +34 -8
  536. package/templates/common/.opencode/commands/wf-max.md +19 -9
  537. package/templates/common/.opencode/commands/wf-review.md +3 -3
  538. package/templates/common/.opencode/commands/wf-search.md +55 -0
  539. package/templates/common/.opencode/commands/wf-task-archive.md +5 -7
  540. package/templates/common/.opencode/commands/wf-task-list.md +6 -6
  541. package/templates/common/.opencode/commands/wf-task-record.md +10 -5
  542. package/templates/common/.opencode/commands/wf-ui.md +12 -5
  543. package/templates/common/.opencode/commands/wf-update.md +4 -0
  544. package/templates/common/.opencode/commands/wf.md +12 -1
  545. package/templates/common/CLAUDE.md +61 -3
  546. package/templates/common/Harness/MEMORY.md +8 -5
  547. package/templates/common/Harness/README.md +14 -10
  548. package/templates/common/Harness/a2a/action-registry.json +4397 -0
  549. package/templates/common/Harness/a2a/role-graph.json +16 -3
  550. package/templates/common/Harness/a2a/skills/terminal-control.json +45 -1
  551. package/templates/common/Harness/a2a/skills/wf-ui-map.json +50 -0
  552. package/templates/common/Harness/a2a/skills/workflow-agent-node.json +143 -0
  553. package/templates/common/Harness/a2a/skills/workflow-context.json +95 -0
  554. package/templates/common/Harness/a2a/skills/workflow-diagram-node.json +60 -0
  555. package/templates/common/Harness/a2a/skills/workflow-display-node.json +69 -0
  556. package/templates/common/Harness/a2a/skills/workflow-file-node.json +92 -0
  557. package/templates/common/Harness/a2a/skills/workflow-goal-node.json +76 -0
  558. package/templates/common/Harness/a2a/skills/workflow-markdown-node.json +79 -0
  559. package/templates/common/Harness/a2a/skills/workflow-mcp-connector-node.json +59 -0
  560. package/templates/common/Harness/a2a/skills/workflow-node-actions.json +105 -0
  561. package/templates/common/Harness/a2a/skills/workflow-node-map.json +71 -0
  562. package/templates/common/Harness/a2a/skills/workflow-ontology.json +54 -0
  563. package/templates/common/Harness/a2a/skills/workflow-resource-node.json +68 -0
  564. package/templates/common/Harness/a2a/skills/workflow-skill-group-node.json +56 -0
  565. package/templates/common/Harness/a2a/skills/workflow-timer-node.json +81 -0
  566. package/templates/common/Harness/a2a/workflows.json +33 -0
  567. package/templates/common/Harness/ownership.manifest.json +263 -2
  568. package/templates/common/Harness/project/architecture.md +499 -0
  569. package/templates/common/Harness/research/README.md +20 -0
  570. package/templates/common/Harness/scripts/README.md +70 -6
  571. package/templates/common/Harness/scripts/a2a-terminal.mjs +12 -4
  572. package/templates/common/Harness/scripts/context-budget.mjs +7 -3
  573. package/templates/common/Harness/scripts/lib/memory-context.mjs +400 -0
  574. package/templates/common/Harness/scripts/lib/path-scope.mjs +77 -0
  575. package/templates/common/Harness/scripts/lib/research-policy.mjs +99 -0
  576. package/templates/common/Harness/scripts/lib/wf-search.mjs +189 -0
  577. package/templates/common/Harness/scripts/memory-context.mjs +63 -0
  578. package/templates/common/Harness/scripts/research-policy.mjs +27 -0
  579. package/templates/common/Harness/scripts/task-context.mjs +547 -0
  580. package/templates/common/Harness/scripts/task-group-index.mjs +242 -0
  581. package/templates/common/Harness/scripts/task-state.mjs +659 -95
  582. package/templates/common/Harness/scripts/validate-harness.mjs +70 -28
  583. package/templates/common/Harness/scripts/wf-auto-update-prompt.mjs +24 -0
  584. package/templates/common/Harness/scripts/wf-remove.mjs +10 -0
  585. package/templates/common/Harness/scripts/wf-search.mjs +10 -0
  586. package/templates/common/Harness/scripts/wf-ui-control.mjs +3144 -0
  587. package/templates/common/Harness/scripts/wf-update-check.mjs +274 -51
  588. package/templates/common/Harness/scripts/wf-update-runner.mjs +53 -16
  589. package/templates/common/Harness/settings.json +10 -0
  590. package/templates/common/Harness/specs/guides/SETUP.md +4 -3
  591. package/templates/common/Harness/specs/protocols/HARNESS_BRIDGE.md +429 -48
  592. package/templates/common/Harness/specs/protocols/MEMORY_PROTOCOL.md +24 -0
  593. package/templates/common/Harness/specs/protocols/TASK_ARCHIVE.md +14 -11
  594. package/templates/common/Harness/specs/runtime/command-surface.json +28 -0
  595. package/templates/common/Harness/specs/runtime/context-loading.md +6 -0
  596. package/templates/common/Harness/specs/runtime/dispatch.md +9 -1
  597. package/templates/common/Harness/specs/runtime/subagents.md +20 -5
  598. package/templates/common/Harness/specs/workflows/WF-AUTO-SPARK.md +19 -0
  599. package/templates/common/Harness/specs/workflows/WF-AUTO.md +21 -0
  600. package/templates/common/Harness/specs/workflows/WF-KERNEL.md +4 -2
  601. package/templates/common/Harness/specs/workflows/WF-MAX.md +39 -12
  602. package/templates/common/Harness/specs/workflows/WF-STATE.md +62 -14
  603. package/templates/common/Harness/specs/workflows/WF.md +32 -0
  604. package/templates/common/Harness/tasks/INDEX.json +7 -0
  605. package/templates/common/Harness/tasks/INDEX.md +12 -0
  606. package/templates/common/Harness/tasks/_template/PLAN.md +6 -0
  607. package/templates/common/Harness/tasks/_template/PROGRESS.md +5 -0
  608. package/templates/common/Harness/tasks/_template/STATE.json +22 -0
  609. package/templates/common/Harness/templates/TEST_PLAN.template.md +5 -4
  610. package/templates/common/Harness/templates/UI_CONTRACT.template.md +7 -5
  611. package/templates/common/Harness/wf-browser/README.md +69 -0
  612. package/templates/common/README.md +17 -0
  613. package/src/ui/dist/assets/AgentsRoute-CuSKYmFu.js +0 -17
  614. package/src/ui/dist/assets/AgentsRoute-CuSKYmFu.js.map +0 -1
  615. package/src/ui/dist/assets/RolesRoute-DOzTYVRm.js +0 -7
  616. package/src/ui/dist/assets/RolesRoute-DOzTYVRm.js.map +0 -1
  617. package/src/ui/dist/assets/SettingsRoute-DPU7jSQm.js +0 -12
  618. package/src/ui/dist/assets/SettingsRoute-DPU7jSQm.js.map +0 -1
  619. package/src/ui/dist/assets/TaskList-BN4r8s1i.js +0 -50
  620. package/src/ui/dist/assets/TaskList-BN4r8s1i.js.map +0 -1
  621. package/src/ui/dist/assets/TerminalDrawer-Cej4PxsH.js +0 -74
  622. package/src/ui/dist/assets/TerminalDrawer-Cej4PxsH.js.map +0 -1
  623. package/src/ui/dist/assets/WorkflowRoute-BnuhLJ6X.css +0 -1
  624. package/src/ui/dist/assets/WorkflowRoute-C-clKe-j.js +0 -39
  625. package/src/ui/dist/assets/WorkflowRoute-C-clKe-j.js.map +0 -1
  626. package/src/ui/dist/assets/index-BOVYWntB.js +0 -117
  627. package/src/ui/dist/assets/index-BOVYWntB.js.map +0 -1
  628. package/src/ui/dist/assets/index-DaUObq0H.css +0 -1
  629. package/src/ui/dist/assets/maximize-2-LNrr_rKr.js +0 -7
  630. package/src/ui/dist/assets/maximize-2-LNrr_rKr.js.map +0 -1
  631. package/src/ui/dist/assets/plus-QuTJyoT3.js +0 -7
  632. package/src/ui/dist/assets/plus-QuTJyoT3.js.map +0 -1
  633. package/src/ui/dist/assets/proxy-D0vUbds5.js.map +0 -1
  634. package/src/ui/dist/assets/refresh-cw-JNmExijd.js.map +0 -1
  635. package/src/ui/dist/assets/terminal-BBQvnOAK.js.map +0 -1
  636. package/src/ui/dist/assets/useReducedMotion-BqmH0Tf7.js +0 -7
  637. package/src/ui/dist/assets/useReducedMotion-BqmH0Tf7.js.map +0 -1
  638. package/src/ui/dist/assets/x-DoIOGAJz.js.map +0 -1
  639. package/src/ui/pnpm-lock.yaml +0 -1582
  640. package/src/ui/src/App.tsx +0 -86
  641. package/src/ui/src/api.ts +0 -93
  642. package/src/ui/src/components/AgentsRoute.tsx +0 -503
  643. package/src/ui/src/components/Footer.tsx +0 -69
  644. package/src/ui/src/components/Header.tsx +0 -175
  645. package/src/ui/src/components/LoadingView.tsx +0 -22
  646. package/src/ui/src/components/RolesRoute.tsx +0 -169
  647. package/src/ui/src/components/SettingsRoute.tsx +0 -166
  648. package/src/ui/src/components/TaskList.tsx +0 -388
  649. package/src/ui/src/components/TerminalDrawer.tsx +0 -611
  650. package/src/ui/src/components/WorkflowRoute.tsx +0 -670
  651. package/src/ui/src/hooks/useReducedMotion.ts +0 -17
  652. package/src/ui/src/hooks/useServerConnection.ts +0 -114
  653. package/src/ui/src/index.css +0 -281
  654. package/src/ui/src/main.tsx +0 -11
  655. package/src/ui/src/types.ts +0 -108
  656. /package/src/ui/dist/assets/{TerminalDrawer-6GBZ9nXN.css → terminalControl-6GBZ9nXN.css} +0 -0
@@ -0,0 +1,1767 @@
1
+ import {
2
+ ComponentNodeError,
3
+ componentStateRefs,
4
+ createComponentNode,
5
+ deleteComponentNode,
6
+ getComponentNode,
7
+ listLiveComponentNodes,
8
+ restoreComponentNode,
9
+ updateComponentNode,
10
+ } from './component-node-store.mjs'
11
+ import {
12
+ EventNodeError,
13
+ createEventNode,
14
+ deleteEventNode,
15
+ eventStateRefs,
16
+ getEventNode,
17
+ listEventNodes,
18
+ restoreEventNode,
19
+ updateEventNode,
20
+ } from './workflow-event-node-store.mjs'
21
+ import {
22
+ CapabilityNodeError,
23
+ capabilityStateRefs,
24
+ createCapabilityNode,
25
+ deleteCapabilityNode,
26
+ getCapabilityNode,
27
+ listCapabilityNodes,
28
+ restoreCapabilityNode,
29
+ updateCapabilityNode,
30
+ } from './workflow-capability-node-store.mjs'
31
+ import {
32
+ GoalNodeError,
33
+ getGoalNode,
34
+ listGoalNodes,
35
+ } from './workflow-goal-node-store.mjs'
36
+ import { assertSingleGoalPerGroup, loadWorkflowGraphMap, removeWorkflowGraphNode, restoreWorkflowGraphNode, writeWorkflowGraphMap } from './a2a-store.mjs'
37
+ import {
38
+ connectNodes as connectWorkflowNodes,
39
+ disconnectNodes as disconnectWorkflowNodes,
40
+ getGraphSnapshot,
41
+ moveNode as moveWorkflowNode,
42
+ recordGraphOp,
43
+ resolveWorkflowNodeId,
44
+ } from './workflow-graph-store.mjs'
45
+ import { appendWorkflowOperation } from './workflow-operation-store.mjs'
46
+ import { workspaceMimeForPath } from './workspace-store.mjs'
47
+ import {
48
+ deleteNodeSettings,
49
+ getSettingsSchema,
50
+ initNodeSettings,
51
+ readNodeSettings,
52
+ writeNodeSettings,
53
+ } from './workflow-node-settings-store.mjs'
54
+ import * as MarkdownNode from './workflow-node-types/markdown-node.mjs'
55
+ import * as DisplayNode from './workflow-node-types/display-node.mjs'
56
+ import * as ExcalidrawNode from './workflow-node-types/excalidraw-node.mjs'
57
+ import * as FileNode from './workflow-node-types/file-node.mjs'
58
+ import * as AgentNode from './workflow-node-types/agent-node.mjs'
59
+ import * as TimerNode from './workflow-node-types/timer-node.mjs'
60
+ import * as GithubTriggerNode from './workflow-node-types/github-trigger-node.mjs'
61
+ import * as SkillGroupNode from './workflow-node-types/skill-group-node.mjs'
62
+ import * as McpConnectorNode from './workflow-node-types/mcp-connector-node.mjs'
63
+ import * as GoalNode from './workflow-node-types/goal-node.mjs'
64
+ import { listMcpHub } from './workflow-mcp-hub.mjs'
65
+ import { buildNodeOntologyContext } from './workflow-ontology.mjs'
66
+ import {
67
+ compositionIdFor,
68
+ getIdempotentResult,
69
+ recordCompositionTransition,
70
+ rememberIdempotentResult,
71
+ } from './composition-api.mjs'
72
+ import {
73
+ agentNodeId,
74
+ buildAgentContext,
75
+ buildAgentSnapshot,
76
+ buildCapabilities,
77
+ findAgentGraphNode,
78
+ goalStateRefsForGraph,
79
+ handlesFor,
80
+ hasTimerControlEdge,
81
+ isDeletedGraphNode,
82
+ isLiveAgentGraphNode,
83
+ isMainAgentGraphNode,
84
+ } from './workflow-agent-context.mjs'
85
+
86
+ export {
87
+ ComponentNodeError,
88
+ createComponentNode,
89
+ deleteComponentNode,
90
+ getComponentNode,
91
+ listComponentNodes,
92
+ listLiveComponentNodes,
93
+ componentStateRefs,
94
+ restoreComponentNode,
95
+ updateComponentNode,
96
+ } from './component-node-store.mjs'
97
+ export {
98
+ EventNodeError,
99
+ createEventNode,
100
+ deleteEventNode,
101
+ getEventNode,
102
+ listEventNodes,
103
+ restoreEventNode,
104
+ updateEventNode,
105
+ } from './workflow-event-node-store.mjs'
106
+ export {
107
+ CapabilityNodeError,
108
+ createCapabilityNode,
109
+ deleteCapabilityNode,
110
+ getCapabilityNode,
111
+ listCapabilityNodes,
112
+ restoreCapabilityNode,
113
+ updateCapabilityNode,
114
+ } from './workflow-capability-node-store.mjs'
115
+ export {
116
+ GoalNodeError,
117
+ getGoalNode,
118
+ goalStateRefs,
119
+ listGoalNodes,
120
+ } from './workflow-goal-node-store.mjs'
121
+ export { loadWorkflowGraphMap, restoreWorkflowGraphNode, writeWorkflowGraphMap } from './a2a-store.mjs'
122
+ export { workflowOntology } from './workflow-ontology.mjs'
123
+ export {
124
+ deleteNodeSettings,
125
+ getSettingsSchema,
126
+ readNodeSettings,
127
+ writeNodeSettings,
128
+ } from './workflow-node-settings-store.mjs'
129
+
130
+ // Action routing: "<type>.<suffix>" dispatches to the matching adapter module
131
+ // (markdown.*, excalidraw.*, file.*, display.*). Adapter handlers must export
132
+ // the action suffix as a function and are called as handler(projectRoot,
133
+ // nodeId, payload).
134
+ const ACTION_ADAPTERS = {
135
+ markdown: MarkdownNode,
136
+ excalidraw: ExcalidrawNode,
137
+ file: FileNode,
138
+ display: DisplayNode,
139
+ agent: AgentNode,
140
+ timer: TimerNode,
141
+ 'github-trigger': GithubTriggerNode,
142
+ 'skill-group': SkillGroupNode,
143
+ 'mcp-connector': McpConnectorNode,
144
+ goal: GoalNode,
145
+ }
146
+
147
+ const EVENT_NODE_TYPES = new Set(['timer', 'github-trigger'])
148
+ const AGENT_GRAPH_ACTIONS = new Set(['createNode', 'connectNodes', 'disconnectNodes', 'moveNode', 'deleteNode', 'deleteNodes', 'readGraph'])
149
+ function normalizeSettings(type, settings) {
150
+ const source = settings && typeof settings === 'object' ? settings : {}
151
+ return {
152
+ schemaId: `${type}-settings`,
153
+ values: source.values && typeof source.values === 'object' && !Array.isArray(source.values) ? source.values : {},
154
+ revision: Number(source.revision || 0),
155
+ }
156
+ }
157
+
158
+ function buildContentRef(node, state, metadata = null) {
159
+ if (node.type === 'goal') {
160
+ return state.contentRef || {
161
+ kind: 'task-capsule',
162
+ taskId: node.config?.taskId || '',
163
+ }
164
+ }
165
+ if (EVENT_NODE_TYPES.has(node.type)) {
166
+ return {
167
+ kind: 'event-node-state',
168
+ statePath: node.statePath,
169
+ revision: node.revision,
170
+ eventKind: node.type,
171
+ }
172
+ }
173
+ if (node.type === 'skill-group' || node.type === 'mcp-connector') {
174
+ return {
175
+ kind: 'capability-node-state',
176
+ statePath: node.statePath,
177
+ revision: node.revision,
178
+ capabilityKind: node.type,
179
+ }
180
+ }
181
+ if (node.type === 'file') {
182
+ const file = state.file || {}
183
+ return {
184
+ kind: file.source === 'user-file' ? 'user-file' : 'workspace-file',
185
+ source: file.source || 'workspace',
186
+ path: file.path || '',
187
+ mime: file.mime || metadata?.mime || workspaceMimeForPath(file.path || ''),
188
+ size: metadata && metadata.exists ? Number(metadata.size || 0) : Number(file.size || 0),
189
+ etag: metadata?.etag ?? null,
190
+ endpoints: {
191
+ meta: '/api/workspace/meta',
192
+ bytes: '/api/workspace/file',
193
+ text: '/api/workspace/text',
194
+ },
195
+ }
196
+ }
197
+ if (node.type === 'markdown') {
198
+ return {
199
+ kind: 'component-state',
200
+ statePath: node.statePath,
201
+ revision: node.revision,
202
+ field: 'markdown',
203
+ mime: 'text/markdown',
204
+ }
205
+ }
206
+ if (node.type === 'excalidraw') {
207
+ return {
208
+ kind: 'component-state',
209
+ statePath: node.statePath,
210
+ revision: node.revision,
211
+ field: 'scene',
212
+ mime: 'application/vnd.excalidraw+json',
213
+ }
214
+ }
215
+ return {
216
+ kind: 'component-state',
217
+ statePath: node.statePath,
218
+ revision: node.revision,
219
+ }
220
+ }
221
+
222
+ function canonicalAgentActorNodeId(graph, actorNodeId) {
223
+ const value = String(actorNodeId || '').trim()
224
+ if (!value) return ''
225
+ const graphNode = findAgentGraphNode(graph, value)
226
+ return graphNode ? agentNodeId(graphNode) : ''
227
+ }
228
+
229
+ function cleanActorString(value) {
230
+ return String(value || '').trim()
231
+ }
232
+
233
+ function actorValue(...values) {
234
+ for (const value of values) {
235
+ const text = cleanActorString(value)
236
+ if (text) return text
237
+ }
238
+ return ''
239
+ }
240
+
241
+ function agentLikeActorType(value) {
242
+ const text = cleanActorString(value).toLowerCase()
243
+ return text === 'agent'
244
+ || text === 'main'
245
+ || text === 'subagent'
246
+ || text === 'ceo'
247
+ || text.endsWith('-agent')
248
+ || text.endsWith('_agent')
249
+ }
250
+
251
+ function resolveAgentActionActor(graph, payload = {}, options = {}, ErrorClass = ComponentNodeError) {
252
+ const actorType = actorValue(payload.actorType, options.actorType)
253
+ const actorKind = actorValue(payload.actorKind, options.actorKind)
254
+ const actorNodeCandidate = actorValue(
255
+ payload.actorNodeId,
256
+ payload.fromNodeId,
257
+ payload.sourceNodeId,
258
+ options.actorNodeId,
259
+ )
260
+ const actorSessionCandidate = actorValue(
261
+ payload.actorSessionId,
262
+ payload.fromSessionId,
263
+ payload.sourceSessionId,
264
+ options.actorSessionId,
265
+ )
266
+ const agentAuthored = Boolean(actorNodeCandidate || actorSessionCandidate)
267
+ || agentLikeActorType(actorType)
268
+ || agentLikeActorType(actorKind)
269
+ if (!agentAuthored) {
270
+ return { agentAuthored: false, payload }
271
+ }
272
+ if (!actorNodeCandidate && !actorSessionCandidate) {
273
+ throw new ErrorClass('Agent-authored node action requires actorNodeId or actorSessionId', {
274
+ statusCode: 403,
275
+ code: 'AGENT_ACTOR_REQUIRED',
276
+ })
277
+ }
278
+
279
+ const byNode = actorNodeCandidate ? findAgentGraphNode(graph, actorNodeCandidate) : null
280
+ const bySession = actorSessionCandidate ? findAgentGraphNode(graph, actorSessionCandidate) : null
281
+ if (actorNodeCandidate && !byNode) {
282
+ throw new ErrorClass('Agent action actor is not a workflow Agent node', {
283
+ statusCode: 403,
284
+ code: 'AGENT_ACTOR_NOT_FOUND',
285
+ })
286
+ }
287
+ if (actorSessionCandidate && !bySession) {
288
+ throw new ErrorClass('Agent action session is not bound to a workflow Agent node', {
289
+ statusCode: 403,
290
+ code: 'AGENT_ACTOR_NOT_FOUND',
291
+ })
292
+ }
293
+ if (byNode && bySession && agentNodeId(byNode) !== agentNodeId(bySession)) {
294
+ throw new ErrorClass('Agent action actor does not match actorSessionId', {
295
+ statusCode: 403,
296
+ code: 'AGENT_ACTOR_MISMATCH',
297
+ })
298
+ }
299
+
300
+ const graphNode = byNode || bySession
301
+ const canonicalActorNodeId = agentNodeId(graphNode)
302
+ return {
303
+ agentAuthored: true,
304
+ graphNode,
305
+ actorNodeId: canonicalActorNodeId,
306
+ actorSessionId: graphNode.sessionId || actorSessionCandidate,
307
+ payload: {
308
+ ...payload,
309
+ actorType: 'agent',
310
+ actorNodeId: canonicalActorNodeId,
311
+ actorSessionId: graphNode.sessionId || actorSessionCandidate,
312
+ },
313
+ }
314
+ }
315
+
316
+ // Timer internal actions (spec §6.1, M4): timer.fire / timer.tick /
317
+ // timer.dispatchWakeup are backend-internal. The bounded scheduler calls the
318
+ // timer-node functions directly (timer-wakeup-scheduler.mjs), never this
319
+ // HTTP-reachable surface. The HTTP action route always passes actor options,
320
+ // so any options-carrying invocation must prove a main/controller agent or
321
+ // the explicit internal scheduler marker; unknown actors are denied (F1/F3).
322
+ const TIMER_INTERNAL_ACTIONS = new Set(['fire', 'tick', 'dispatchWakeup'])
323
+
324
+ function isMainOrControllerActorKind(value) {
325
+ const kind = cleanActorString(value).toLowerCase()
326
+ return kind === 'main' || kind === 'controller' || kind === 'ceo' || kind === 'main-agent'
327
+ }
328
+
329
+ function assertTimerInternalActionAllowed(projectRoot, nodeId, suffix, payload, options) {
330
+ if (!TIMER_INTERNAL_ACTIONS.has(String(suffix || '').trim())) return
331
+ // Bare in-process invocations (no options, or the function-default empty
332
+ // object) are trusted internal callers — the scheduler and unit tests call
333
+ // the timer-node functions directly. Only the HTTP route produces actor
334
+ // options (workflowActionActorOptions always returns the four actor keys),
335
+ // so an options object with any key is HTTP-originated and must prove its
336
+ // actor (F1).
337
+ if (options === undefined || options === null) return
338
+ if (options !== null && typeof options === 'object' && Object.keys(options).length === 0) return
339
+ // Explicit internal marker: set only by backend-internal dispatch paths;
340
+ // workflowActionActorOptions() never copies it from HTTP input (F1).
341
+ if (options.internal === true) return
342
+ const declaredKind = actorValue(payload?.actorKind, options?.actorKind)
343
+ const nodeCandidate = actorValue(
344
+ payload?.actorNodeId,
345
+ payload?.fromNodeId,
346
+ payload?.sourceNodeId,
347
+ options?.actorNodeId,
348
+ )
349
+ const sessionCandidate = actorValue(
350
+ payload?.actorSessionId,
351
+ payload?.fromSessionId,
352
+ payload?.sourceSessionId,
353
+ options?.actorSessionId,
354
+ )
355
+ // Main/controller agent kind declared WITHOUT a node/session is accepted as
356
+ // the scheduler-facing controller; with a node/session the node must resolve
357
+ // below (a bogus main claim then fails AGENT_ACTOR_NOT_FOUND).
358
+ if (isMainOrControllerActorKind(declaredKind) && !nodeCandidate && !sessionCandidate) return
359
+ const graph = loadWorkflowGraphMap(projectRoot)
360
+ const actor = resolveAgentActionActor(graph, payload || {}, options || {}, EventNodeError)
361
+ if (actor.agentAuthored) {
362
+ if (actor.graphNode && isMainAgentGraphNode(actor.graphNode)) return
363
+ if (isMainOrControllerActorKind(actor.graphNode?.agentKind)) return
364
+ }
365
+ throw new EventNodeError(
366
+ `Timer internal action ${nodeId} ${suffix} requires the main/controller agent or the internal scheduler`,
367
+ {
368
+ statusCode: 403,
369
+ code: 'TIMER_INTERNAL_ACTION_DENIED',
370
+ }
371
+ )
372
+ }
373
+
374
+ function hasGoalActionEdge(graph, actorNodeId, goalNodeId) {
375
+ const actor = canonicalAgentActorNodeId(graph, actorNodeId)
376
+ if (!actor || !goalNodeId) return false
377
+ return workflowRuntimeEdges(graph).some((edge) => {
378
+ const from = edge.from || edge.source
379
+ const to = edge.to || edge.target
380
+ const endpointsMatch = (from === actor && to === goalNodeId) || (from === goalNodeId && to === actor)
381
+ return endpointsMatch
382
+ && normalizeEdgeDirection(edge.direction) === 'bidirectional'
383
+ && isGoalRelation(edge.relation)
384
+ })
385
+ }
386
+
387
+ function isGoalRelation(value) {
388
+ const relation = String(value || '').trim()
389
+ return relation === 'goal' || relation.endsWith('/goal')
390
+ }
391
+
392
+ // Canonical node shape builder
393
+ function buildNodeSnapshot(node, state, settings) {
394
+ return {
395
+ nodeId: node.nodeId,
396
+ kind: node.type, // markdown | excalidraw | file
397
+ version: node.revision,
398
+ lifecycle: 'ready',
399
+ status: { state: 'ready', updatedAt: new Date().toISOString() },
400
+ graph: {
401
+ position: node.position,
402
+ handles: handlesFor(node.type),
403
+ connections: [], // filled by getNodeContext
404
+ },
405
+ stateRef: { path: node.statePath, revision: node.revision },
406
+ contentRef: buildContentRef(node, state),
407
+ settings: normalizeSettings(node.type, settings),
408
+ capabilities: buildCapabilities(node.type, state),
409
+ ui: {
410
+ previewKind: node.type,
411
+ settingsPanel: `${node.type}-settings`,
412
+ testId: `workflow-${node.type}-node`,
413
+ labels: { title: node.title },
414
+ },
415
+ }
416
+ }
417
+
418
+ function buildEventNodeSnapshot(node, state, settings) {
419
+ return {
420
+ nodeId: node.nodeId,
421
+ kind: node.type,
422
+ version: node.revision,
423
+ revision: node.revision,
424
+ lifecycle: 'event-source',
425
+ status: { state: 'ready', updatedAt: new Date().toISOString() },
426
+ graph: {
427
+ position: node.position,
428
+ handles: handlesFor(node.type),
429
+ connections: [],
430
+ },
431
+ stateRef: { path: node.statePath, revision: node.revision },
432
+ contentRef: buildContentRef(node, state),
433
+ settings: normalizeSettings(node.type, settings),
434
+ capabilities: buildCapabilities(node.type, state),
435
+ ui: {
436
+ previewKind: node.type,
437
+ settingsPanel: `${node.type}-settings`,
438
+ testId: 'workflow-event-node',
439
+ labels: { title: node.title },
440
+ },
441
+ }
442
+ }
443
+
444
+ function buildCapabilityNodeSnapshot(node, state, settings) {
445
+ return {
446
+ nodeId: node.nodeId,
447
+ kind: node.type,
448
+ version: node.revision,
449
+ lifecycle: 'capability-provider',
450
+ status: { state: 'ready', updatedAt: new Date().toISOString() },
451
+ graph: {
452
+ position: node.position,
453
+ handles: handlesFor(node.type),
454
+ connections: [],
455
+ },
456
+ stateRef: { path: node.statePath, revision: node.revision },
457
+ contentRef: buildContentRef(node, state),
458
+ settings: normalizeSettings(node.type, settings),
459
+ capabilities: buildCapabilities(node.type, state),
460
+ ui: {
461
+ previewKind: node.type,
462
+ settingsPanel: `${node.type}-settings`,
463
+ testId: 'workflow-capability-node',
464
+ labels: { title: node.title },
465
+ },
466
+ }
467
+ }
468
+
469
+ function buildGoalNodeSnapshot(node, state, settings) {
470
+ const title = state.title || node.title
471
+ return {
472
+ nodeId: node.nodeId,
473
+ kind: 'goal',
474
+ version: node.revision,
475
+ lifecycle: 'goal-anchor',
476
+ status: { state: state.status || 'active', updatedAt: new Date().toISOString() },
477
+ graph: {
478
+ position: node.position,
479
+ handles: handlesFor('goal'),
480
+ connections: [],
481
+ },
482
+ stateRef: { path: node.statePath, revision: node.revision },
483
+ contentRef: buildContentRef({ ...node, type: 'goal' }, state),
484
+ settings: normalizeSettings('goal', settings),
485
+ capabilities: buildCapabilities('goal', state),
486
+ taskId: state.taskId,
487
+ title,
488
+ objective: state.objective,
489
+ acceptance: state.acceptance,
490
+ planItems: state.planItems,
491
+ progress: state.progress,
492
+ nextAction: state.nextAction,
493
+ confirmation: state.confirmation,
494
+ wdt: state.wdt,
495
+ ui: {
496
+ previewKind: 'goal',
497
+ settingsPanel: 'goal-settings',
498
+ testId: 'workflow-goal-node',
499
+ labels: { title },
500
+ },
501
+ }
502
+ }
503
+
504
+ function initSettings(projectRoot, nodeId, type) {
505
+ initNodeSettings(projectRoot, nodeId, type)
506
+ }
507
+
508
+ function asComponentNodeError(error) {
509
+ if (error instanceof ComponentNodeError) return error
510
+ if (error instanceof EventNodeError) return error
511
+ if (error instanceof CapabilityNodeError) return error
512
+ if (error instanceof GoalNodeError) return error
513
+ return new ComponentNodeError(
514
+ error && error.message ? error.message : 'Workflow node runtime error',
515
+ { statusCode: 500, code: 'NODE_RUNTIME_ERROR' }
516
+ )
517
+ }
518
+
519
+ function buildConnections(nodeId, graph) {
520
+ return workflowRuntimeEdges(graph)
521
+ .map(edge => connectionForEdge(edge, nodeId))
522
+ .filter(Boolean)
523
+ }
524
+
525
+ function workflowRuntimeEdges(graph = {}) {
526
+ const graphEdges = Array.isArray(graph.edges) ? graph.edges : []
527
+ const dockEdges = (Array.isArray(graph.capsuleDockLinks) ? graph.capsuleDockLinks : [])
528
+ .flatMap((link) => (
529
+ Array.isArray(link?.connections) ? link.connections : []
530
+ ).map((connection) => {
531
+ const from = String(connection?.from || connection?.source || '').trim()
532
+ const to = String(connection?.to || connection?.target || '').trim()
533
+ if (!from || !to || from === to) return null
534
+ return {
535
+ id: String(connection.id || `dock:${link.id || 'capsule'}:${from}->${to}`).trim(),
536
+ kind: 'capsule-dock-link',
537
+ from,
538
+ to,
539
+ source: from,
540
+ target: to,
541
+ relation: String(connection.relation || 'wf-bridge').trim() || 'wf-bridge',
542
+ direction: normalizeEdgeDirection(connection.direction),
543
+ sourceHandle: connection.sourceHandle || null,
544
+ targetHandle: connection.targetHandle || null,
545
+ dockLinkId: link.id || '',
546
+ }
547
+ }).filter(Boolean))
548
+ const seen = new Set()
549
+ return [...graphEdges, ...dockEdges].filter((edge) => {
550
+ const key = workflowRuntimeEdgeSemanticKey(edge)
551
+ || String(edge.id || `${edge.from || edge.source}->${edge.to || edge.target}`).trim()
552
+ if (!key) return false
553
+ if (seen.has(key)) return false
554
+ seen.add(key)
555
+ return true
556
+ })
557
+ }
558
+
559
+ function normalizeEdgeDirection(value) {
560
+ return String(value || '').trim() === 'source-to-target' ? 'source-to-target' : 'bidirectional'
561
+ }
562
+
563
+ function workflowRuntimeEdgeSemanticKey(edge = {}) {
564
+ const from = String(edge.from || edge.source || '').trim()
565
+ const to = String(edge.to || edge.target || '').trim()
566
+ if (!from || !to) return ''
567
+ const relation = String(edge.relation || 'wf-bridge').trim() || 'wf-bridge'
568
+ const direction = normalizeEdgeDirection(edge.direction)
569
+ const sourceHandle = String(edge.sourceHandle || '').trim()
570
+ const targetHandle = String(edge.targetHandle || '').trim()
571
+ if (direction !== 'source-to-target') {
572
+ return [
573
+ direction,
574
+ relation,
575
+ ...[`${from}:${sourceHandle}`, `${to}:${targetHandle}`].sort(),
576
+ ].join('|')
577
+ }
578
+ return [direction, from, sourceHandle, relation, to, targetHandle].join('|')
579
+ }
580
+
581
+ function connectionForEdge(edge, nodeId) {
582
+ const from = edge.from || edge.source
583
+ const to = edge.to || edge.target
584
+ const isSource = from === nodeId
585
+ const isTarget = to === nodeId
586
+ if (!isSource && !isTarget) return null
587
+ return {
588
+ edgeId: edge.id || `${from}->${to}`,
589
+ peerNodeId: isSource ? to : from,
590
+ endpointRole: isSource ? 'source' : 'target',
591
+ localHandle: isSource ? edge.sourceHandle || null : edge.targetHandle || null,
592
+ peerHandle: isSource ? edge.targetHandle || null : edge.sourceHandle || null,
593
+ sourceHandle: edge.sourceHandle || null,
594
+ targetHandle: edge.targetHandle || null,
595
+ relation: edge.relation || 'wf-bridge',
596
+ direction: normalizeEdgeDirection(edge.direction),
597
+ }
598
+ }
599
+
600
+ function graphNodeKey(node) {
601
+ return node?.nodeId || node?.id || (node?.sessionId ? `session-${node.sessionId}` : '')
602
+ }
603
+
604
+ function resolveAgentDeleteTargetIds(projectRoot, graph, payload = {}) {
605
+ if (payload?.all === true) {
606
+ return (graph.nodes || [])
607
+ .map(node => graphNodeKey(node))
608
+ .filter(Boolean)
609
+ }
610
+
611
+ const rawTargets = []
612
+ for (const value of [
613
+ payload?.targetNodeIds,
614
+ payload?.nodeIds,
615
+ payload?.ids,
616
+ ]) {
617
+ if (Array.isArray(value)) rawTargets.push(...value)
618
+ }
619
+ for (const value of [
620
+ payload?.targetNodeId,
621
+ payload?.nodeId,
622
+ payload?.target,
623
+ payload?.id,
624
+ ]) {
625
+ if (value) rawTargets.push(value)
626
+ }
627
+ const unique = []
628
+ const seen = new Set()
629
+ for (const raw of rawTargets) {
630
+ const resolved = resolveWorkflowNodeId(projectRoot, graph, raw)
631
+ if (seen.has(resolved)) continue
632
+ seen.add(resolved)
633
+ unique.push(resolved)
634
+ }
635
+ if (unique.length === 0) {
636
+ throw new ComponentNodeError('Agent delete action requires targetNodeId, targetNodeIds, or all: true', {
637
+ statusCode: 400,
638
+ code: 'DELETE_TARGET_REQUIRED',
639
+ })
640
+ }
641
+ return unique
642
+ }
643
+
644
+ function assertMainAgentGraphActor(projectRoot, key) {
645
+ const graph = loadWorkflowGraphMap(projectRoot)
646
+ const graphNode = findAgentGraphNode(graph, key)
647
+ if (!graphNode) {
648
+ throw new ComponentNodeError('Agent graph actor not found', {
649
+ statusCode: 404,
650
+ code: 'AGENT_GRAPH_ACTOR_NOT_FOUND',
651
+ })
652
+ }
653
+ if (!isMainAgentGraphNode(graphNode)) {
654
+ throw new ComponentNodeError('Agent graph control requires a Main Agent node', {
655
+ statusCode: 403,
656
+ code: 'MAIN_AGENT_REQUIRED',
657
+ })
658
+ }
659
+ return { graph, graphNode, actorNodeId: agentNodeId(graphNode) }
660
+ }
661
+
662
+ // Typed detail fields forwarded from plain backend errors (e.g. the
663
+ // goal_already_bound shape thrown by assertSingleGoalPerGroup) so the HTTP
664
+ // surface returns the same spec shape as the direct edge route (F12).
665
+ const GRAPH_ACTION_TYPED_FIELDS = ['existingGoalNodeId', 'timerNodeId', 'remaining', 'currentRevision', 'expectedRevision', 'holder', 'expiresAt']
666
+
667
+ function graphActionError(error) {
668
+ if (error instanceof ComponentNodeError) return error
669
+ const wrapped = new ComponentNodeError(error?.message || 'Agent graph action failed', {
670
+ statusCode: error?.statusCode || 400,
671
+ code: error?.code || 'AGENT_GRAPH_ACTION_FAILED',
672
+ })
673
+ for (const field of GRAPH_ACTION_TYPED_FIELDS) {
674
+ const value = error?.[field]
675
+ if (value !== undefined && value !== null) wrapped[field] = value
676
+ }
677
+ return wrapped
678
+ }
679
+
680
+ function graphActionActor(graphNode, actorNodeId) {
681
+ return {
682
+ type: 'agent',
683
+ nodeId: actorNodeId,
684
+ sessionId: graphNode.sessionId || null,
685
+ agentKind: graphNode.agentKind || null,
686
+ label: graphNode.label || graphNode.runtime || 'Agent',
687
+ }
688
+ }
689
+
690
+ function appendAgentGraphOperation(projectRoot, { actionName, graphNode, actorNodeId, targetNodeIds = [], edgeIds = [], summary, status = 'completed' }) {
691
+ return appendWorkflowOperation(projectRoot, {
692
+ kind: actionName,
693
+ actor: graphActionActor(graphNode, actorNodeId),
694
+ targetNodeIds,
695
+ edgeIds,
696
+ status,
697
+ summary,
698
+ })
699
+ }
700
+
701
+ async function executeAgentGraphAction(projectRoot, nodeId, actionName, suffix, payload = {}) {
702
+ const { graphNode, actorNodeId } = assertMainAgentGraphActor(projectRoot, payload?.actorNodeId || nodeId)
703
+ try {
704
+ if (suffix === 'createNode') {
705
+ const nodePayload = payload?.node && typeof payload.node === 'object' && !Array.isArray(payload.node)
706
+ ? payload.node
707
+ : payload
708
+ const created = await createNode(projectRoot, nodePayload)
709
+ const targetNodeId = created?.node?.nodeId || created?.node?.id || nodePayload.nodeId
710
+ const operation = appendAgentGraphOperation(projectRoot, {
711
+ actionName,
712
+ graphNode,
713
+ actorNodeId,
714
+ targetNodeIds: targetNodeId ? [targetNodeId] : [],
715
+ summary: `created ${targetNodeId || 'node'}`,
716
+ })
717
+ const graph = loadWorkflowGraphMap(projectRoot)
718
+ const snapshot = buildAgentSnapshot(projectRoot, graphNode, graph)
719
+ return { ok: true, action: actionName, node: snapshot, result: { ...created, operation }, operation }
720
+ }
721
+
722
+ if (suffix === 'connectNodes') {
723
+ const from = payload?.from || payload?.source || payload?.sourceNodeId || actorNodeId
724
+ const to = payload?.to || payload?.target || payload?.targetNodeId
725
+ // Single-Goal rule (spec §6.2, AC-015, F12): the main-agent graph-action
726
+ // connect path must enforce the same candidate-inclusive guard as the
727
+ // HTTP edge route (server.mjs /api/workflow/edges), otherwise
728
+ // agent.connectNodes could bind a second Goal into a Timer+Agent group
729
+ // that already has one. Throws the goal_already_bound spec shape (409).
730
+ const endpoints = [String(from || '').trim(), String(to || '').trim()]
731
+ for (const endpoint of endpoints) {
732
+ if (!endpoint || !/^goal-/.test(endpoint)) continue
733
+ const peer = endpoints.find(candidate => candidate && candidate !== endpoint) || ''
734
+ const extraDockLinks = peer
735
+ ? [{ id: `sim:${endpoint}->${peer}`, nodeIds: [endpoint, peer] }]
736
+ : []
737
+ assertSingleGoalPerGroup(projectRoot, endpoint, { extraDockLinks })
738
+ }
739
+ const connected = connectWorkflowNodes(projectRoot, {
740
+ from,
741
+ to,
742
+ relation: payload?.relation,
743
+ sourceHandle: payload?.sourceHandle,
744
+ targetHandle: payload?.targetHandle,
745
+ direction: payload?.direction,
746
+ }, { action: 'agent.connectNodes', actor: graphActionActor(graphNode, actorNodeId) })
747
+ const operation = appendAgentGraphOperation(projectRoot, {
748
+ actionName,
749
+ graphNode,
750
+ actorNodeId,
751
+ targetNodeIds: [connected.edge.from || from, connected.edge.to || to],
752
+ edgeIds: [connected.edge.id],
753
+ summary: `connected ${connected.edge.from}->${connected.edge.to}`,
754
+ })
755
+ const graph = loadWorkflowGraphMap(projectRoot)
756
+ const snapshot = buildAgentSnapshot(projectRoot, graphNode, graph)
757
+ return { ok: true, action: actionName, node: snapshot, result: { ...connected, operation }, operation }
758
+ }
759
+
760
+ if (suffix === 'disconnectNodes') {
761
+ const edgeId = payload?.edgeId || payload?.id
762
+ const beforeGraph = loadWorkflowGraphMap(projectRoot)
763
+ const existing = (beforeGraph.edges || []).find(edge => edge.id === edgeId)
764
+ const disconnected = disconnectWorkflowNodes(projectRoot, edgeId, { action: 'agent.disconnectNodes', actor: graphActionActor(graphNode, actorNodeId) })
765
+ const operation = appendAgentGraphOperation(projectRoot, {
766
+ actionName,
767
+ graphNode,
768
+ actorNodeId,
769
+ targetNodeIds: existing ? [existing.from || existing.source, existing.to || existing.target] : [],
770
+ edgeIds: [edgeId],
771
+ summary: `disconnected ${edgeId}`,
772
+ })
773
+ const graph = loadWorkflowGraphMap(projectRoot)
774
+ const snapshot = buildAgentSnapshot(projectRoot, graphNode, graph)
775
+ return { ok: true, action: actionName, node: snapshot, result: { ...disconnected, operation }, operation }
776
+ }
777
+
778
+ if (suffix === 'moveNode') {
779
+ const graph = loadWorkflowGraphMap(projectRoot)
780
+ const targetNodeId = resolveWorkflowNodeId(projectRoot, graph, payload?.targetNodeId || payload?.nodeId || payload?.id)
781
+ const moved = moveWorkflowNode(projectRoot, targetNodeId, payload?.position || payload, { action: 'agent.moveNode', actor: graphActionActor(graphNode, actorNodeId) })
782
+ const freshGraph = loadWorkflowGraphMap(projectRoot)
783
+ const edgeIds = (freshGraph.edges || [])
784
+ .filter(edge => {
785
+ const from = edge.from || edge.source
786
+ const to = edge.to || edge.target
787
+ return (from === actorNodeId && to === targetNodeId) || (from === targetNodeId && to === actorNodeId)
788
+ })
789
+ .map(edge => edge.id || `${edge.from || edge.source}->${edge.to || edge.target}`)
790
+ const operation = appendAgentGraphOperation(projectRoot, {
791
+ actionName,
792
+ graphNode,
793
+ actorNodeId,
794
+ targetNodeIds: [targetNodeId],
795
+ edgeIds,
796
+ summary: `moved ${targetNodeId}`,
797
+ })
798
+ const snapshot = buildAgentSnapshot(projectRoot, graphNode, freshGraph)
799
+ return { ok: true, action: actionName, node: snapshot, result: { ...moved, operation }, operation }
800
+ }
801
+
802
+ if (suffix === 'deleteNode' || suffix === 'deleteNodes') {
803
+ const beforeGraph = loadWorkflowGraphMap(projectRoot)
804
+ const requestedTargetIds = resolveAgentDeleteTargetIds(projectRoot, beforeGraph, payload)
805
+ const deletedNodeIds = []
806
+ const skippedNodeIds = []
807
+ const errors = []
808
+
809
+ for (const targetNodeId of requestedTargetIds) {
810
+ if (targetNodeId === actorNodeId) {
811
+ if (payload?.all === true || suffix === 'deleteNodes') {
812
+ skippedNodeIds.push(targetNodeId)
813
+ continue
814
+ }
815
+ throw new ComponentNodeError('Agent cannot delete its own graph node while executing graph control', {
816
+ statusCode: 400,
817
+ code: 'CANNOT_DELETE_SELF',
818
+ })
819
+ }
820
+ const currentGraph = loadWorkflowGraphMap(projectRoot)
821
+ const targetGraphNode = (currentGraph.nodes || [])
822
+ .find(node => graphNodeKey(node) === targetNodeId || node.sessionId === targetNodeId || (node.sessionId && `session-${node.sessionId}` === targetNodeId))
823
+ if (!targetGraphNode || isDeletedGraphNode(currentGraph, targetNodeId)) {
824
+ skippedNodeIds.push(targetNodeId)
825
+ continue
826
+ }
827
+ if (isLiveAgentGraphNode(targetGraphNode) && payload?.allowLiveAgentDelete !== true && payload?.force !== true) {
828
+ if (payload?.all === true || suffix === 'deleteNodes') {
829
+ skippedNodeIds.push(targetNodeId)
830
+ continue
831
+ }
832
+ throw new ComponentNodeError('Stop the target Agent before deleting its graph node', {
833
+ statusCode: 409,
834
+ code: 'LIVE_AGENT_DELETE_REQUIRES_STOP',
835
+ })
836
+ }
837
+ try {
838
+ await deleteNode(projectRoot, targetNodeId, { action: 'agent.deleteNode', actor: graphActionActor(graphNode, actorNodeId) })
839
+ deletedNodeIds.push(targetNodeId)
840
+ } catch (deleteError) {
841
+ if (payload?.all === true || suffix === 'deleteNodes') {
842
+ errors.push({
843
+ nodeId: targetNodeId,
844
+ code: deleteError?.code || 'DELETE_FAILED',
845
+ message: deleteError?.message || 'Delete failed',
846
+ })
847
+ continue
848
+ }
849
+ throw deleteError
850
+ }
851
+ }
852
+
853
+ const freshGraph = loadWorkflowGraphMap(projectRoot)
854
+ const operation = appendAgentGraphOperation(projectRoot, {
855
+ actionName,
856
+ graphNode,
857
+ actorNodeId,
858
+ targetNodeIds: [...deletedNodeIds, ...skippedNodeIds],
859
+ edgeIds: [],
860
+ summary: `deleted ${deletedNodeIds.length} node(s), skipped ${skippedNodeIds.length}`,
861
+ status: errors.length > 0 && deletedNodeIds.length === 0 ? 'failed' : 'completed',
862
+ })
863
+ const snapshot = buildAgentSnapshot(projectRoot, graphNode, freshGraph)
864
+ return {
865
+ ok: errors.length === 0,
866
+ action: actionName,
867
+ node: snapshot,
868
+ result: { deletedNodeIds, skippedNodeIds, errors, operation },
869
+ operation,
870
+ }
871
+ }
872
+
873
+ if (suffix === 'readGraph') {
874
+ const graph = getGraphSnapshot(projectRoot)
875
+ const operation = appendAgentGraphOperation(projectRoot, {
876
+ actionName,
877
+ graphNode,
878
+ actorNodeId,
879
+ targetNodeIds: [],
880
+ edgeIds: [],
881
+ summary: 'read graph',
882
+ })
883
+ const snapshot = buildAgentSnapshot(projectRoot, graphNode, graph)
884
+ return { ok: true, action: actionName, node: snapshot, result: { graph, operation }, operation }
885
+ }
886
+
887
+ throw new ComponentNodeError(`Unknown workflow node action: ${actionName}`, {
888
+ statusCode: 404,
889
+ code: 'UNKNOWN_ACTION',
890
+ })
891
+ } catch (error) {
892
+ throw graphActionError(error)
893
+ }
894
+ }
895
+
896
+ export async function listNodes(projectRoot) {
897
+ try {
898
+ const graph = loadWorkflowGraphMap(projectRoot)
899
+ // Component nodes
900
+ const componentNodes = listLiveComponentNodes(projectRoot).map((node) => {
901
+ const current = getComponentNode(projectRoot, node.nodeId)
902
+ const settings = readNodeSettings(projectRoot, node.nodeId)
903
+ const snapshot = buildNodeSnapshot(current.node, current.state, settings)
904
+ snapshot.graph.connections = buildConnections(current.node.nodeId, graph)
905
+ return snapshot
906
+ })
907
+ const eventNodes = listEventNodes(projectRoot).map((node) => {
908
+ const current = getEventNode(projectRoot, node.nodeId)
909
+ const settings = readNodeSettings(projectRoot, node.nodeId, node.type)
910
+ const snapshot = buildEventNodeSnapshot(current.node, current.state, settings)
911
+ snapshot.graph.connections = buildConnections(current.node.nodeId, graph)
912
+ return snapshot
913
+ })
914
+ const capabilityNodes = listCapabilityNodes(projectRoot).map((node) => {
915
+ const current = getCapabilityNode(projectRoot, node.nodeId)
916
+ const settings = readNodeSettings(projectRoot, node.nodeId, node.type)
917
+ const snapshot = buildCapabilityNodeSnapshot(current.node, current.state, settings)
918
+ snapshot.graph.connections = buildConnections(current.node.nodeId, graph)
919
+ return snapshot
920
+ })
921
+ const goalNodes = listGoalNodes(projectRoot).map((node) => {
922
+ const current = getGoalNode(projectRoot, node.nodeId)
923
+ const settings = readNodeSettings(projectRoot, node.nodeId, 'goal')
924
+ const snapshot = buildGoalNodeSnapshot(current.node, current.state, settings)
925
+ snapshot.graph.position = graph.positions?.[current.node.nodeId] || snapshot.graph.position
926
+ snapshot.graph.connections = buildConnections(current.node.nodeId, graph)
927
+ return snapshot
928
+ }).filter(node => !isDeletedGraphNode(graph, node.nodeId))
929
+ // Agent nodes from graph-map
930
+ const agentNodes = (graph.nodes || [])
931
+ .filter(n => n.sessionId && n.kind !== 'component-node')
932
+ .map(n => buildAgentSnapshot(projectRoot, n, graph))
933
+ return { ok: true, nodes: [...componentNodes, ...eventNodes, ...capabilityNodes, ...goalNodes, ...agentNodes] }
934
+ } catch (error) {
935
+ throw asComponentNodeError(error)
936
+ }
937
+ }
938
+
939
+ export async function getNode(projectRoot, nodeId) {
940
+ try {
941
+ // Try component node first
942
+ try {
943
+ const current = getComponentNode(projectRoot, nodeId)
944
+ const settings = readNodeSettings(projectRoot, current.node.nodeId)
945
+ const snapshot = buildNodeSnapshot(current.node, current.state, settings)
946
+ const graph = loadWorkflowGraphMap(projectRoot)
947
+ snapshot.graph.connections = buildConnections(current.node.nodeId, graph)
948
+ return { ok: true, node: snapshot, state: current.state, revision: current.revision }
949
+ } catch (e) {
950
+ if (e.code === 'NOT_FOUND' || e.code === 'BAD_REQUEST') {
951
+ try {
952
+ const current = getEventNode(projectRoot, nodeId)
953
+ const settings = readNodeSettings(projectRoot, current.node.nodeId, current.node.type)
954
+ const snapshot = buildEventNodeSnapshot(current.node, current.state, settings)
955
+ const graph = loadWorkflowGraphMap(projectRoot)
956
+ snapshot.graph.connections = buildConnections(current.node.nodeId, graph)
957
+ return { ok: true, node: snapshot, state: current.state, revision: current.revision }
958
+ } catch (eventError) {
959
+ if (!(eventError.code === 'NOT_FOUND' || eventError.code === 'BAD_REQUEST')) {
960
+ throw eventError
961
+ }
962
+ }
963
+ try {
964
+ const current = getCapabilityNode(projectRoot, nodeId)
965
+ const settings = readNodeSettings(projectRoot, current.node.nodeId, current.node.type)
966
+ const snapshot = buildCapabilityNodeSnapshot(current.node, current.state, settings)
967
+ const graph = loadWorkflowGraphMap(projectRoot)
968
+ snapshot.graph.connections = buildConnections(current.node.nodeId, graph)
969
+ return { ok: true, node: snapshot, state: current.state, revision: current.revision }
970
+ } catch (capabilityError) {
971
+ if (!(capabilityError.code === 'NOT_FOUND' || capabilityError.code === 'BAD_REQUEST')) {
972
+ throw capabilityError
973
+ }
974
+ }
975
+ try {
976
+ const current = getGoalNode(projectRoot, nodeId)
977
+ const graph = loadWorkflowGraphMap(projectRoot)
978
+ if (isDeletedGraphNode(graph, current.node.nodeId)) {
979
+ throw new GoalNodeError(`Goal node not found: ${current.node.nodeId}`, {
980
+ statusCode: 404,
981
+ code: 'NOT_FOUND',
982
+ })
983
+ }
984
+ const settings = readNodeSettings(projectRoot, current.node.nodeId, 'goal')
985
+ const snapshot = buildGoalNodeSnapshot(current.node, current.state, settings)
986
+ snapshot.graph.position = graph.positions?.[current.node.nodeId] || snapshot.graph.position
987
+ snapshot.graph.connections = buildConnections(current.node.nodeId, graph)
988
+ return { ok: true, node: snapshot, state: current.state, revision: current.revision }
989
+ } catch (goalError) {
990
+ if (isDeletedGraphNode(loadWorkflowGraphMap(projectRoot), nodeId)) {
991
+ throw goalError
992
+ }
993
+ if (!(goalError.code === 'NOT_FOUND' || goalError.code === 'BAD_REQUEST')) {
994
+ throw goalError
995
+ }
996
+ }
997
+ // Try agent node in graph-map (agent ids are not component-* ids)
998
+ const graph = loadWorkflowGraphMap(projectRoot)
999
+ const graphNode = findAgentGraphNode(graph, nodeId)
1000
+ if (graphNode) {
1001
+ return { ok: true, node: buildAgentSnapshot(projectRoot, graphNode, graph), revision: graph.version }
1002
+ }
1003
+ }
1004
+ throw e
1005
+ }
1006
+ } catch (error) {
1007
+ throw asComponentNodeError(error)
1008
+ }
1009
+ }
1010
+
1011
+ // P5: record a createNode op AFTER the node exists (its record is the forward
1012
+ // slice; the inverse carries a _removed marker so undo deletes the node again).
1013
+ function recordCreatedNodeGraphOp(projectRoot, created, stateField, payload = {}) {
1014
+ if (!created?.node?.nodeId) return
1015
+ const actorNodeId = String(payload?.actorNodeId || '').trim()
1016
+ const record = {
1017
+ ...created.node,
1018
+ position: created.node.position,
1019
+ [stateField]: created.state,
1020
+ }
1021
+ const recorded = recordGraphOp(projectRoot, {
1022
+ action: 'agent.createNode',
1023
+ actor: actorNodeId
1024
+ ? { kind: 'agent', nodeId: actorNodeId, sessionId: String(payload?.actorSessionId || '') }
1025
+ : { kind: 'human', nodeId: '', sessionId: '' },
1026
+ inverse: { nodes: [{ ...record, _removed: true }] },
1027
+ forward: { nodes: [record] },
1028
+ })
1029
+ if (!recorded) return
1030
+ const fresh = loadWorkflowGraphMap(projectRoot)
1031
+ writeWorkflowGraphMap(projectRoot, {
1032
+ ...fresh,
1033
+ version: fresh.version + 1,
1034
+ undoStack: recorded.undoStack,
1035
+ redoStack: recorded.redoStack,
1036
+ }, { overrideHistory: true })
1037
+ }
1038
+
1039
+ export async function createNode(projectRoot, payload = {}) {
1040
+ try {
1041
+ const eventType = String(payload.type || '').trim().toLowerCase()
1042
+ const nodeType = eventType
1043
+ const { experimental, ...stablePayload } = payload && typeof payload === 'object' && !Array.isArray(payload) ? payload : {}
1044
+ if (eventType === 'github-trigger' && experimental !== true) {
1045
+ throw new EventNodeError('github-trigger is planned and requires experimental: true to create', {
1046
+ statusCode: 409,
1047
+ code: 'PLANNED_NODE_REQUIRES_EXPERIMENTAL',
1048
+ })
1049
+ }
1050
+ if (EVENT_NODE_TYPES.has(eventType)) {
1051
+ const created = createEventNode(projectRoot, { ...stablePayload, type: eventType })
1052
+ initSettings(projectRoot, created.node.nodeId, created.node.type)
1053
+ recordCreatedNodeGraphOp(projectRoot, created, 'eventState', payload)
1054
+ const settings = readNodeSettings(projectRoot, created.node.nodeId, created.node.type)
1055
+ const snapshot = buildEventNodeSnapshot(created.node, created.state, settings)
1056
+ const graph = loadWorkflowGraphMap(projectRoot)
1057
+ snapshot.graph.connections = buildConnections(created.node.nodeId, graph)
1058
+ return { ok: true, node: snapshot, state: created.state, revision: created.revision }
1059
+ }
1060
+ if (String(payload.type || '').trim().toLowerCase() === 'skill-group') {
1061
+ const created = createCapabilityNode(projectRoot, { ...payload, type: 'skill-group' })
1062
+ initSettings(projectRoot, created.node.nodeId, created.node.type)
1063
+ recordCreatedNodeGraphOp(projectRoot, created, 'capabilityState', payload)
1064
+ const settings = readNodeSettings(projectRoot, created.node.nodeId, created.node.type)
1065
+ const snapshot = buildCapabilityNodeSnapshot(created.node, created.state, settings)
1066
+ const graph = loadWorkflowGraphMap(projectRoot)
1067
+ snapshot.graph.connections = buildConnections(created.node.nodeId, graph)
1068
+ return { ok: true, node: snapshot, state: created.state, revision: created.revision }
1069
+ }
1070
+ if (String(payload.type || '').trim().toLowerCase() === 'mcp-connector') {
1071
+ const mcpServerId = String(payload.mcpServerId || payload.serverId || '').trim()
1072
+ let server = payload.server
1073
+ if (mcpServerId) {
1074
+ const hub = listMcpHub(projectRoot, { scope: 'project' })
1075
+ server = hub.servers.find(item => item.id === mcpServerId)
1076
+ if (!server) {
1077
+ throw new CapabilityNodeError(`MCP server metadata not found: ${mcpServerId}`, {
1078
+ statusCode: 404,
1079
+ code: 'MCP_SERVER_NOT_FOUND',
1080
+ })
1081
+ }
1082
+ }
1083
+ const created = createCapabilityNode(projectRoot, {
1084
+ ...payload,
1085
+ type: 'mcp-connector',
1086
+ title: payload.title || (server ? `${server.title || server.name} MCP` : 'MCP Connector'),
1087
+ sourceGroup: payload.sourceGroup || (server?.sources?.[0]
1088
+ ? {
1089
+ id: `source:${server.sources[0].rootId || 'mcp'}`,
1090
+ label: server.sources[0].label || 'MCP config',
1091
+ kind: 'mcp-source',
1092
+ }
1093
+ : { id: 'mcp-hub', label: 'MCP Hub', kind: 'mcp-hub' }),
1094
+ servers: server ? [server] : payload.servers,
1095
+ })
1096
+ initSettings(projectRoot, created.node.nodeId, created.node.type)
1097
+ recordCreatedNodeGraphOp(projectRoot, created, 'capabilityState', payload)
1098
+ const settings = readNodeSettings(projectRoot, created.node.nodeId, created.node.type)
1099
+ const snapshot = buildCapabilityNodeSnapshot(created.node, created.state, settings)
1100
+ const graph = loadWorkflowGraphMap(projectRoot)
1101
+ snapshot.graph.connections = buildConnections(created.node.nodeId, graph)
1102
+ return { ok: true, node: snapshot, state: created.state, revision: created.revision }
1103
+ }
1104
+ if (nodeType === 'goal') {
1105
+ const goal = listGoalNodes(projectRoot)[0]
1106
+ if (!goal) {
1107
+ throw new GoalNodeError('Goal node creation requires an active task capsule', {
1108
+ statusCode: 409,
1109
+ code: 'NO_ACTIVE_GOAL_TASK',
1110
+ })
1111
+ }
1112
+ const graph = loadWorkflowGraphMap(projectRoot)
1113
+ if (isDeletedGraphNode(graph, goal.nodeId)) {
1114
+ restoreWorkflowGraphNode(projectRoot, goal.nodeId)
1115
+ }
1116
+ if (stablePayload.position && typeof stablePayload.position === 'object' && !Array.isArray(stablePayload.position)) {
1117
+ moveWorkflowNode(projectRoot, goal.nodeId, stablePayload.position)
1118
+ }
1119
+ initSettings(projectRoot, goal.nodeId, 'goal')
1120
+ const current = getGoalNode(projectRoot, goal.nodeId)
1121
+ const settings = readNodeSettings(projectRoot, current.node.nodeId, 'goal')
1122
+ const snapshot = buildGoalNodeSnapshot(current.node, current.state, settings)
1123
+ const freshGraph = loadWorkflowGraphMap(projectRoot)
1124
+ snapshot.graph.position = freshGraph.positions?.[current.node.nodeId] || snapshot.graph.position
1125
+ snapshot.graph.connections = buildConnections(current.node.nodeId, freshGraph)
1126
+ return { ok: true, node: snapshot, state: current.state, revision: current.revision }
1127
+ }
1128
+ const created = createComponentNode(projectRoot, payload)
1129
+ initSettings(projectRoot, created.node.nodeId, created.node.type)
1130
+ recordCreatedNodeGraphOp(projectRoot, created, 'componentState', payload)
1131
+ const settings = readNodeSettings(projectRoot, created.node.nodeId)
1132
+ const snapshot = buildNodeSnapshot(created.node, created.state, settings)
1133
+ const graph = loadWorkflowGraphMap(projectRoot)
1134
+ snapshot.graph.connections = buildConnections(created.node.nodeId, graph)
1135
+ return { ok: true, node: snapshot, state: created.state, revision: created.revision }
1136
+ } catch (error) {
1137
+ throw asComponentNodeError(error)
1138
+ }
1139
+ }
1140
+
1141
+ export async function updateNodeState(projectRoot, nodeId, payload = {}) {
1142
+ try {
1143
+ try {
1144
+ const updated = updateComponentNode(projectRoot, nodeId, payload)
1145
+ const settings = readNodeSettings(projectRoot, updated.node.nodeId)
1146
+ const snapshot = buildNodeSnapshot(updated.node, updated.state, settings)
1147
+ const graph = loadWorkflowGraphMap(projectRoot)
1148
+ snapshot.graph.connections = buildConnections(updated.node.nodeId, graph)
1149
+ return { ok: true, node: snapshot, state: updated.state, revision: updated.revision }
1150
+ } catch (e) {
1151
+ if (!(e.code === 'NOT_FOUND' || e.code === 'BAD_REQUEST')) throw e
1152
+ try {
1153
+ const updated = updateEventNode(projectRoot, nodeId, payload)
1154
+ const settings = readNodeSettings(projectRoot, updated.node.nodeId, updated.node.type)
1155
+ const snapshot = buildEventNodeSnapshot(updated.node, updated.state, settings)
1156
+ const graph = loadWorkflowGraphMap(projectRoot)
1157
+ snapshot.graph.connections = buildConnections(updated.node.nodeId, graph)
1158
+ return { ok: true, node: snapshot, state: updated.state, revision: updated.revision }
1159
+ } catch (eventError) {
1160
+ if (!(eventError.code === 'NOT_FOUND' || eventError.code === 'BAD_REQUEST')) throw eventError
1161
+ const updated = updateCapabilityNode(projectRoot, nodeId, payload)
1162
+ const settings = readNodeSettings(projectRoot, updated.node.nodeId, updated.node.type)
1163
+ const snapshot = buildCapabilityNodeSnapshot(updated.node, updated.state, settings)
1164
+ const graph = loadWorkflowGraphMap(projectRoot)
1165
+ snapshot.graph.connections = buildConnections(updated.node.nodeId, graph)
1166
+ return { ok: true, node: snapshot, state: updated.state, revision: updated.revision }
1167
+ }
1168
+ }
1169
+ } catch (error) {
1170
+ throw asComponentNodeError(error)
1171
+ }
1172
+ }
1173
+
1174
+ export async function updateNodeSettings(projectRoot, nodeId, payload = {}) {
1175
+ try {
1176
+ const graph = loadWorkflowGraphMap(projectRoot)
1177
+ const graphNode = findAgentGraphNode(graph, nodeId)
1178
+ if (graphNode) {
1179
+ const settingsNodeId = agentNodeId(graphNode)
1180
+ writeNodeSettings(projectRoot, settingsNodeId, payload, 'agent')
1181
+ const freshGraph = loadWorkflowGraphMap(projectRoot)
1182
+ const freshGraphNode = findAgentGraphNode(freshGraph, settingsNodeId) || graphNode
1183
+ const snapshot = buildAgentSnapshot(projectRoot, freshGraphNode, freshGraph)
1184
+ return { ok: true, node: snapshot, settings: snapshot.settings }
1185
+ }
1186
+ try {
1187
+ const current = getEventNode(projectRoot, nodeId)
1188
+ writeNodeSettings(projectRoot, current.node.nodeId, payload, current.node.type)
1189
+ const settings = readNodeSettings(projectRoot, current.node.nodeId, current.node.type)
1190
+ const snapshot = buildEventNodeSnapshot(current.node, current.state, settings)
1191
+ snapshot.graph.connections = buildConnections(current.node.nodeId, graph)
1192
+ return { ok: true, node: snapshot, settings }
1193
+ } catch (eventError) {
1194
+ if (!(eventError.code === 'NOT_FOUND' || eventError.code === 'BAD_REQUEST')) {
1195
+ throw eventError
1196
+ }
1197
+ }
1198
+ try {
1199
+ const current = getCapabilityNode(projectRoot, nodeId)
1200
+ writeNodeSettings(projectRoot, current.node.nodeId, payload, current.node.type)
1201
+ const settings = readNodeSettings(projectRoot, current.node.nodeId, current.node.type)
1202
+ const snapshot = buildCapabilityNodeSnapshot(current.node, current.state, settings)
1203
+ snapshot.graph.connections = buildConnections(current.node.nodeId, graph)
1204
+ return { ok: true, node: snapshot, settings }
1205
+ } catch (capabilityError) {
1206
+ if (!(capabilityError.code === 'NOT_FOUND' || capabilityError.code === 'BAD_REQUEST')) {
1207
+ throw capabilityError
1208
+ }
1209
+ }
1210
+ try {
1211
+ const current = getGoalNode(projectRoot, nodeId)
1212
+ if (isDeletedGraphNode(graph, current.node.nodeId)) {
1213
+ throw new GoalNodeError(`Goal node not found: ${current.node.nodeId}`, {
1214
+ statusCode: 404,
1215
+ code: 'NOT_FOUND',
1216
+ })
1217
+ }
1218
+ writeNodeSettings(projectRoot, current.node.nodeId, payload, 'goal')
1219
+ const settings = readNodeSettings(projectRoot, current.node.nodeId, 'goal')
1220
+ const snapshot = buildGoalNodeSnapshot(current.node, current.state, settings)
1221
+ snapshot.graph.position = graph.positions?.[current.node.nodeId] || snapshot.graph.position
1222
+ snapshot.graph.connections = buildConnections(current.node.nodeId, graph)
1223
+ return { ok: true, node: snapshot, settings }
1224
+ } catch (goalError) {
1225
+ if (isDeletedGraphNode(graph, nodeId)) {
1226
+ throw goalError
1227
+ }
1228
+ if (!(goalError.code === 'NOT_FOUND' || goalError.code === 'BAD_REQUEST')) {
1229
+ throw goalError
1230
+ }
1231
+ }
1232
+ writeNodeSettings(projectRoot, nodeId, payload)
1233
+ const current = getComponentNode(projectRoot, nodeId)
1234
+ const settings = readNodeSettings(projectRoot, current.node.nodeId)
1235
+ const snapshot = buildNodeSnapshot(current.node, current.state, settings)
1236
+ snapshot.graph.connections = buildConnections(current.node.nodeId, graph)
1237
+ return { ok: true, node: snapshot, settings }
1238
+ } catch (error) {
1239
+ throw asComponentNodeError(error)
1240
+ }
1241
+ }
1242
+
1243
+ export async function executeNodeAction(projectRoot, nodeId, action, payload = {}, options = {}) {
1244
+ try {
1245
+ const actionName = String(action || '').trim()
1246
+ if (actionName === 'node.delete') {
1247
+ return deleteNode(projectRoot, nodeId, { action: 'node.delete', actor: { kind: 'human', nodeId: '', sessionId: '' } })
1248
+ }
1249
+ if (actionName === 'node.restore') {
1250
+ return restoreNode(projectRoot, nodeId, payload)
1251
+ }
1252
+ const separator = actionName.indexOf('.')
1253
+ const prefix = separator === -1 ? actionName : actionName.slice(0, separator)
1254
+ const suffix = separator === -1 ? '' : actionName.slice(separator + 1)
1255
+ if (prefix === 'timer' && payload && typeof payload === 'object' && payload.idempotencyKey) {
1256
+ const replay = getIdempotentResult(projectRoot, {
1257
+ nodeId,
1258
+ action: actionName,
1259
+ idempotencyKey: payload.idempotencyKey,
1260
+ })
1261
+ if (replay) return replay
1262
+ }
1263
+ if (prefix === 'agent' && AGENT_GRAPH_ACTIONS.has(suffix)) {
1264
+ return executeAgentGraphAction(projectRoot, nodeId, actionName, suffix, payload)
1265
+ }
1266
+ if (prefix === 'agent' && suffix === 'readContext') {
1267
+ const canonical = await getNodeContext(projectRoot, nodeId)
1268
+ return {
1269
+ ok: true,
1270
+ action: actionName,
1271
+ node: canonical.node,
1272
+ result: canonical.context,
1273
+ context: canonical.context,
1274
+ }
1275
+ }
1276
+ const adapter = ACTION_ADAPTERS[prefix]
1277
+ const handler = adapter && typeof adapter[suffix] === 'function' ? adapter[suffix] : null
1278
+ if (!handler) {
1279
+ throw new ComponentNodeError(`Unknown workflow node action: ${actionName}`, {
1280
+ statusCode: 404,
1281
+ code: 'UNKNOWN_ACTION',
1282
+ })
1283
+ }
1284
+
1285
+ // CHECK: is this a component node or agent node?
1286
+ const isAgentAction = prefix === 'agent'
1287
+
1288
+ if (isAgentAction) {
1289
+ // Agent nodes live in the graph-map. The handler gets (nodeId, projectRoot, payload).
1290
+ const result = await handler(nodeId, projectRoot, payload)
1291
+ // Build agent snapshot
1292
+ const graph = loadWorkflowGraphMap(projectRoot)
1293
+ const graphNode = findAgentGraphNode(graph, nodeId)
1294
+ const snapshot = graphNode ? buildAgentSnapshot(projectRoot, graphNode, graph) : null
1295
+ return { ok: result?.ok === false ? false : true, action: actionName, node: snapshot, result }
1296
+ }
1297
+
1298
+ if (EVENT_NODE_TYPES.has(prefix)) {
1299
+ const current = getEventNode(projectRoot, nodeId)
1300
+ if (prefix === 'timer' && current.node.type !== 'timer') {
1301
+ throw new EventNodeError(`Node is not a timer node: ${current.node.nodeId}`, {
1302
+ statusCode: 409,
1303
+ code: 'TYPE_MISMATCH',
1304
+ })
1305
+ }
1306
+ let actionPayload = payload
1307
+ let timerActor = null
1308
+ if (current.node.type === 'timer') {
1309
+ // F1/F3: timer.fire / timer.tick / timer.dispatchWakeup stay
1310
+ // backend-internal — the HTTP surface denies every actor except the
1311
+ // main/controller agent and the internal scheduler marker.
1312
+ assertTimerInternalActionAllowed(projectRoot, nodeId, suffix, payload, options)
1313
+ if (typeof TimerNode.isTimerControlAction === 'function' && TimerNode.isTimerControlAction(suffix)) {
1314
+ const graph = loadWorkflowGraphMap(projectRoot)
1315
+ const actor = resolveAgentActionActor(graph, payload, options, EventNodeError)
1316
+ timerActor = actor
1317
+ actionPayload = actor.payload
1318
+ if (actor.agentAuthored && !hasTimerControlEdge(graph, actor.actorNodeId, current.node.nodeId)) {
1319
+ throw new EventNodeError('Timer control action requires a source-to-target control edge from actor to timer', {
1320
+ statusCode: 403,
1321
+ code: 'CONTROL_EDGE_REQUIRED',
1322
+ })
1323
+ }
1324
+ }
1325
+ if (payload.expectedRevision !== undefined && payload.expectedRevision !== null) {
1326
+ const expectedRevision = Number(payload.expectedRevision)
1327
+ if (!Number.isInteger(expectedRevision) || expectedRevision !== Number(current.node.revision)) {
1328
+ const error = new EventNodeError('Stale event node revision', {
1329
+ statusCode: 409,
1330
+ code: 'STALE_REVISION',
1331
+ })
1332
+ error.currentRevision = Number(current.node.revision)
1333
+ error.expectedRevision = expectedRevision
1334
+ throw error
1335
+ }
1336
+ }
1337
+ }
1338
+ const result = await handler(current.node.nodeId, projectRoot, actionPayload)
1339
+ const fresh = getEventNode(projectRoot, current.node.nodeId)
1340
+ const settings = readNodeSettings(projectRoot, current.node.nodeId, current.node.type)
1341
+ const snapshot = buildEventNodeSnapshot(fresh.node, fresh.state, settings)
1342
+ const graph = loadWorkflowGraphMap(projectRoot)
1343
+ snapshot.graph.connections = buildConnections(fresh.node.nodeId, graph)
1344
+ // Top-level `state` carries the full updated event-node state (heartbeat.base.nextDueAt etc.)
1345
+ // so the frontend can reconcile instantly from the action response, per the
1346
+ // WorkflowRuntimeNodeResponse contract. `result` remains the raw adapter result.
1347
+ let actionResult = result
1348
+ let transition = result?.transition || null
1349
+ if (current.node.type === 'timer' && suffix === 'fire' && !transition) {
1350
+ const beforeState = current.state?.enabled && current.state?.heartbeat?.base?.enabled ? 'scheduled' : 'idle'
1351
+ const fireTransition = recordCompositionTransition(projectRoot, {
1352
+ compositionId: compositionIdFor(projectRoot),
1353
+ from: beforeState,
1354
+ to: 'fired',
1355
+ cause: actionName,
1356
+ eventId: result?.event?.id || null,
1357
+ wakeupId: null,
1358
+ timerNodeId: current.node.nodeId,
1359
+ targetNodeId: null,
1360
+ edgeId: null,
1361
+ graphVersion: Number(graph.version || 1),
1362
+ stateRevision: Number(fresh.node.revision),
1363
+ contextRefs: [],
1364
+ })
1365
+ transition = fireTransition
1366
+ actionResult = { ...(result || {}), transition: fireTransition }
1367
+ }
1368
+ if (current.node.type === 'timer' && TimerNode.isTimerControlAction(suffix) && !transition) {
1369
+ const beforeState = current.state?.enabled && current.state?.heartbeat?.base?.enabled ? 'scheduled' : 'idle'
1370
+ const afterState = fresh.state?.enabled && fresh.state?.heartbeat?.base?.enabled ? 'scheduled' : 'idle'
1371
+ const graph = loadWorkflowGraphMap(projectRoot)
1372
+ const controlEdge = timerActor?.actorNodeId
1373
+ ? (graph.edges || []).find(edge => String(edge.from || edge.source || '') === timerActor.actorNodeId
1374
+ && String(edge.to || edge.target || '') === current.node.nodeId
1375
+ && String(edge.relation || '') === 'control')
1376
+ : null
1377
+ transition = recordCompositionTransition(projectRoot, {
1378
+ compositionId: compositionIdFor(projectRoot),
1379
+ from: beforeState,
1380
+ to: afterState,
1381
+ cause: actionName,
1382
+ eventId: null,
1383
+ wakeupId: null,
1384
+ timerNodeId: current.node.nodeId,
1385
+ targetNodeId: timerActor?.actorNodeId || null,
1386
+ edgeId: controlEdge?.id || null,
1387
+ graphVersion: Number(graph.version || 1),
1388
+ stateRevision: Number(fresh.node.revision),
1389
+ contextRefs: [],
1390
+ })
1391
+ actionResult = { ...(result || {}), transition }
1392
+ }
1393
+ const response = { ok: true, action: actionName, node: snapshot, state: fresh.state, revision: fresh.node.revision, result: actionResult }
1394
+ if (current.node.type === 'timer' && payload && payload.idempotencyKey) {
1395
+ rememberIdempotentResult(projectRoot, {
1396
+ nodeId: current.node.nodeId,
1397
+ action: actionName,
1398
+ idempotencyKey: payload.idempotencyKey,
1399
+ }, response)
1400
+ }
1401
+ return response
1402
+ }
1403
+
1404
+ if (prefix === 'goal') {
1405
+ const current = getGoalNode(projectRoot, nodeId)
1406
+ const graph = loadWorkflowGraphMap(projectRoot)
1407
+ if (isDeletedGraphNode(graph, current.node.nodeId)) {
1408
+ throw new GoalNodeError(`Goal node not found: ${current.node.nodeId}`, {
1409
+ statusCode: 404,
1410
+ code: 'NOT_FOUND',
1411
+ })
1412
+ }
1413
+ const actor = suffix === 'read'
1414
+ ? { agentAuthored: false, payload }
1415
+ : resolveAgentActionActor(graph, payload, options, GoalNodeError)
1416
+ if (actor.agentAuthored && !hasGoalActionEdge(graph, actor.actorNodeId, current.node.nodeId)) {
1417
+ throw new GoalNodeError('Goal action requires a bidirectional goal edge from actor to goal', {
1418
+ statusCode: 403,
1419
+ code: 'GOAL_EDGE_REQUIRED',
1420
+ })
1421
+ }
1422
+ const result = await handler(current.node.nodeId, projectRoot, actor.payload)
1423
+ const fresh = getGoalNode(projectRoot, current.node.nodeId)
1424
+ const settings = readNodeSettings(projectRoot, current.node.nodeId, 'goal')
1425
+ const snapshot = buildGoalNodeSnapshot(fresh.node, fresh.state, settings)
1426
+ const freshGraph = loadWorkflowGraphMap(projectRoot)
1427
+ snapshot.graph.position = freshGraph.positions?.[fresh.node.nodeId] || snapshot.graph.position
1428
+ snapshot.graph.connections = buildConnections(fresh.node.nodeId, freshGraph)
1429
+ return { ok: true, action: actionName, node: snapshot, result }
1430
+ }
1431
+
1432
+ if (prefix === 'skill-group' || prefix === 'mcp-connector') {
1433
+ const current = getCapabilityNode(projectRoot, nodeId)
1434
+ const result = await handler(current.node.nodeId, projectRoot, payload)
1435
+ const fresh = getCapabilityNode(projectRoot, current.node.nodeId)
1436
+ const settings = readNodeSettings(projectRoot, current.node.nodeId, current.node.type)
1437
+ const snapshot = buildCapabilityNodeSnapshot(fresh.node, fresh.state, settings)
1438
+ const graph = loadWorkflowGraphMap(projectRoot)
1439
+ snapshot.graph.connections = buildConnections(fresh.node.nodeId, graph)
1440
+ return { ok: true, action: actionName, node: snapshot, result }
1441
+ }
1442
+
1443
+ // Component node path (existing logic)
1444
+ const current = getComponentNode(projectRoot, nodeId)
1445
+ const result = await handler(current.node.nodeId, projectRoot, payload)
1446
+ const fresh = getComponentNode(projectRoot, current.node.nodeId)
1447
+ const settings = readNodeSettings(projectRoot, current.node.nodeId)
1448
+ const snapshot = buildNodeSnapshot(fresh.node, fresh.state, settings)
1449
+ const graph = loadWorkflowGraphMap(projectRoot)
1450
+ snapshot.graph.connections = buildConnections(fresh.node.nodeId, graph)
1451
+ return { ok: true, action: actionName, node: snapshot, result }
1452
+ } catch (error) {
1453
+ throw asComponentNodeError(error)
1454
+ }
1455
+ }
1456
+
1457
+ // P5: capture the delete-op slice (full node record + state + affected edges)
1458
+ // BEFORE the graph node is removed so undo can restore the node and its edges.
1459
+ function recordDeleteNodeGraphOp(projectRoot, { node, state = null, stateField = null, graph, action, actor }) {
1460
+ const nodeId = String(node?.nodeId || node?.id || '').trim()
1461
+ if (!nodeId) return null
1462
+ const record = {
1463
+ ...node,
1464
+ position: graph?.positions?.[nodeId] || node?.position,
1465
+ }
1466
+ if (stateField && state !== undefined && state !== null) record[stateField] = state
1467
+ const affectedEdges = (Array.isArray(graph?.edges) ? graph.edges : []).filter(edge => {
1468
+ const from = String(edge?.from || edge?.source || '')
1469
+ const to = String(edge?.to || edge?.target || '')
1470
+ return from === nodeId || to === nodeId
1471
+ || Boolean(node?.sessionId && (from === node.sessionId || to === node.sessionId))
1472
+ })
1473
+ return recordGraphOp(projectRoot, {
1474
+ action: action || 'agent.deleteNode',
1475
+ actor,
1476
+ inverse: { nodes: [record], edges: affectedEdges },
1477
+ forward: { nodes: [{ ...record, _removed: true }], edges: affectedEdges.map(edge => ({ ...edge, _removed: true })) },
1478
+ })
1479
+ }
1480
+
1481
+ export async function deleteNode(projectRoot, nodeId, historyOptions = {}) {
1482
+ try {
1483
+ const historyAction = historyOptions.action || 'deleteNode'
1484
+ const historyActor = historyOptions.actor || { kind: 'human', nodeId: '', sessionId: '' }
1485
+ try {
1486
+ let current = null
1487
+ try {
1488
+ current = getComponentNode(projectRoot, nodeId)
1489
+ } catch (error) {
1490
+ if (!(error.code === 'NOT_FOUND' || error.code === 'BAD_REQUEST' || error.code === 'STATE_MISMATCH')) throw error
1491
+ }
1492
+ const graphBeforeDelete = loadWorkflowGraphMap(projectRoot)
1493
+ const removed = deleteComponentNode(projectRoot, nodeId)
1494
+ deleteNodeSettings(projectRoot, nodeId)
1495
+ const recorded = recordDeleteNodeGraphOp(projectRoot, {
1496
+ node: current?.node || removed.removed,
1497
+ state: current?.state || null,
1498
+ stateField: 'componentState',
1499
+ graph: graphBeforeDelete,
1500
+ action: historyAction,
1501
+ actor: historyActor,
1502
+ })
1503
+ const graphResult = removeWorkflowGraphNode(projectRoot, nodeId, {
1504
+ node: current?.node || removed.removed,
1505
+ edges: graphBeforeDelete.edges,
1506
+ ...(recorded ? { undoStack: recorded.undoStack, redoStack: recorded.redoStack } : {}),
1507
+ })
1508
+ return { ok: true, nodeId: removed.nodeId, removed: removed.removed, recovery: { node: current?.node || removed.removed, state: current?.state || null, graph: graphResult.removed } }
1509
+ } catch (e) {
1510
+ if (!(e.code === 'NOT_FOUND' || e.code === 'BAD_REQUEST')) throw e
1511
+ try {
1512
+ const current = getEventNode(projectRoot, nodeId)
1513
+ const graphBeforeDelete = loadWorkflowGraphMap(projectRoot)
1514
+ const removed = deleteEventNode(projectRoot, nodeId)
1515
+ deleteNodeSettings(projectRoot, nodeId, removed.removed?.type || 'timer')
1516
+ const recorded = recordDeleteNodeGraphOp(projectRoot, {
1517
+ node: current.node,
1518
+ state: current.state,
1519
+ stateField: 'eventState',
1520
+ graph: graphBeforeDelete,
1521
+ action: historyAction,
1522
+ actor: historyActor,
1523
+ })
1524
+ const graphResult = removeWorkflowGraphNode(projectRoot, nodeId, {
1525
+ node: current.node,
1526
+ edges: graphBeforeDelete.edges,
1527
+ ...(recorded ? { undoStack: recorded.undoStack, redoStack: recorded.redoStack } : {}),
1528
+ })
1529
+ return { ok: true, nodeId: removed.nodeId, removed: removed.removed, recovery: { node: current.node, state: current.state, graph: graphResult.removed } }
1530
+ } catch (eventError) {
1531
+ if (!(eventError.code === 'NOT_FOUND' || eventError.code === 'BAD_REQUEST')) throw eventError
1532
+ try {
1533
+ const current = getCapabilityNode(projectRoot, nodeId)
1534
+ const graphBeforeDelete = loadWorkflowGraphMap(projectRoot)
1535
+ const removed = deleteCapabilityNode(projectRoot, nodeId)
1536
+ deleteNodeSettings(projectRoot, nodeId, removed.removed?.type || 'skill-group')
1537
+ const recorded = recordDeleteNodeGraphOp(projectRoot, {
1538
+ node: current.node,
1539
+ state: current.state,
1540
+ stateField: 'capabilityState',
1541
+ graph: graphBeforeDelete,
1542
+ action: historyAction,
1543
+ actor: historyActor,
1544
+ })
1545
+ const graphResult = removeWorkflowGraphNode(projectRoot, nodeId, {
1546
+ node: current.node,
1547
+ edges: graphBeforeDelete.edges,
1548
+ ...(recorded ? { undoStack: recorded.undoStack, redoStack: recorded.redoStack } : {}),
1549
+ })
1550
+ return { ok: true, nodeId: removed.nodeId, removed: removed.removed, recovery: { node: current.node, state: current.state, graph: graphResult.removed } }
1551
+ } catch (capabilityError) {
1552
+ if (!(capabilityError.code === 'NOT_FOUND' || capabilityError.code === 'BAD_REQUEST')) throw capabilityError
1553
+ try {
1554
+ const current = getGoalNode(projectRoot, nodeId)
1555
+ deleteNodeSettings(projectRoot, current.node.nodeId, 'goal')
1556
+ const recorded = recordDeleteNodeGraphOp(projectRoot, {
1557
+ node: current.node,
1558
+ graph: loadWorkflowGraphMap(projectRoot),
1559
+ action: historyAction,
1560
+ actor: historyActor,
1561
+ })
1562
+ const graphResult = removeWorkflowGraphNode(projectRoot, current.node.nodeId, {
1563
+ node: current.node,
1564
+ ...(recorded ? { undoStack: recorded.undoStack, redoStack: recorded.redoStack } : {}),
1565
+ })
1566
+ return { ok: true, nodeId: current.node.nodeId, removed: current.node, graph: graphResult.graph, recovery: { node: current.node, state: current.state, graph: graphResult.removed } }
1567
+ } catch (goalError) {
1568
+ if (!(goalError.code === 'NOT_FOUND' || goalError.code === 'BAD_REQUEST')) throw goalError
1569
+ const graph = loadWorkflowGraphMap(projectRoot)
1570
+ const agentNode = findAgentGraphNode(graph, nodeId)
1571
+ if (!agentNode) throw capabilityError
1572
+ if (isLiveAgentGraphNode(agentNode)) {
1573
+ throw new ComponentNodeError('Stop the target Agent before deleting its graph node', {
1574
+ statusCode: 409,
1575
+ code: 'LIVE_AGENT_DELETE_REQUIRES_STOP',
1576
+ })
1577
+ }
1578
+ const recorded = recordDeleteNodeGraphOp(projectRoot, {
1579
+ node: agentNode,
1580
+ graph,
1581
+ action: historyAction,
1582
+ actor: historyActor,
1583
+ })
1584
+ const graphResult = removeWorkflowGraphNode(projectRoot, nodeId, {
1585
+ node: agentNode,
1586
+ ...(recorded ? { undoStack: recorded.undoStack, redoStack: recorded.redoStack } : {}),
1587
+ })
1588
+ return { ok: true, nodeId: graphNodeKey(agentNode), removed: agentNode, graph: graphResult.graph, recovery: { node: agentNode, graph: graphResult.removed } }
1589
+ }
1590
+ }
1591
+ }
1592
+ }
1593
+ } catch (error) {
1594
+ throw asComponentNodeError(error)
1595
+ }
1596
+ }
1597
+
1598
+ export async function restoreNode(projectRoot, nodeId, payload = {}) {
1599
+ try {
1600
+ const recovery = payload?.recovery && typeof payload.recovery === 'object'
1601
+ ? payload.recovery
1602
+ : payload;
1603
+ const snapshot = recovery?.node || recovery;
1604
+ const graphId = String(recovery?.graphId || snapshot?.graphNodeId || snapshot?.nodeId || nodeId || '').trim();
1605
+ const nodeKind = String(snapshot?.type || snapshot?.componentType || snapshot?.kind || '').trim().toLowerCase();
1606
+ if (nodeKind === 'markdown' || nodeKind === 'excalidraw' || nodeKind === 'file') {
1607
+ restoreComponentNode(projectRoot, { node: { ...snapshot, nodeId: snapshot.nodeId || nodeId, type: nodeKind }, state: snapshot.componentState });
1608
+ } else if (nodeKind === 'timer' || nodeKind === 'github-trigger') {
1609
+ restoreEventNode(projectRoot, { node: { ...snapshot, nodeId: snapshot.nodeId || nodeId, type: nodeKind }, state: snapshot.eventState });
1610
+ } else if (nodeKind === 'skill-group' || nodeKind === 'mcp-connector') {
1611
+ restoreCapabilityNode(projectRoot, { node: { ...snapshot, nodeId: snapshot.nodeId || nodeId, type: nodeKind }, state: snapshot.capabilityState });
1612
+ }
1613
+ restoreWorkflowGraphNode(projectRoot, graphId);
1614
+ const restored = await getNode(projectRoot, graphId);
1615
+ return { ok: true, action: 'node.restore', node: restored.node, result: restored };
1616
+ } catch (error) {
1617
+ throw asComponentNodeError(error);
1618
+ }
1619
+ }
1620
+
1621
+ export async function getNodeContext(projectRoot, nodeId) {
1622
+ try {
1623
+ let current
1624
+ try {
1625
+ current = getComponentNode(projectRoot, nodeId)
1626
+ } catch (error) {
1627
+ if (error?.code === 'NOT_FOUND' || error?.code === 'BAD_REQUEST') {
1628
+ const graph = loadWorkflowGraphMap(projectRoot)
1629
+ const graphNode = findAgentGraphNode(graph, nodeId)
1630
+ if (graphNode) return buildAgentContext(projectRoot, graphNode, graph)
1631
+ try {
1632
+ const eventNode = getEventNode(projectRoot, nodeId)
1633
+ const settings = readNodeSettings(projectRoot, eventNode.node.nodeId, eventNode.node.type)
1634
+ const snapshot = buildEventNodeSnapshot(eventNode.node, eventNode.state, settings)
1635
+ const connections = buildConnections(eventNode.node.nodeId, graph)
1636
+ const ontology = buildNodeOntologyContext({
1637
+ nodeId: eventNode.node.nodeId,
1638
+ nodeType: eventNode.node.type,
1639
+ connections,
1640
+ })
1641
+ snapshot.graph.connections = connections
1642
+ return {
1643
+ ok: true,
1644
+ node: snapshot,
1645
+ context: {
1646
+ nodeId: eventNode.node.nodeId,
1647
+ graphVersion: Number(graph.version || 1),
1648
+ connectedPeers: connections.map(connection => ({ nodeId: connection.peerNodeId })),
1649
+ eventStateRefs: eventStateRefs(projectRoot),
1650
+ ontology,
1651
+ affordances: ontology.affordances,
1652
+ },
1653
+ }
1654
+ } catch (eventError) {
1655
+ if (!(eventError.code === 'NOT_FOUND' || eventError.code === 'BAD_REQUEST')) {
1656
+ throw eventError
1657
+ }
1658
+ }
1659
+ try {
1660
+ const capabilityNode = getCapabilityNode(projectRoot, nodeId)
1661
+ const settings = readNodeSettings(projectRoot, capabilityNode.node.nodeId, capabilityNode.node.type)
1662
+ const snapshot = buildCapabilityNodeSnapshot(capabilityNode.node, capabilityNode.state, settings)
1663
+ const connections = buildConnections(capabilityNode.node.nodeId, graph)
1664
+ const ontology = buildNodeOntologyContext({
1665
+ nodeId: capabilityNode.node.nodeId,
1666
+ nodeType: capabilityNode.node.type,
1667
+ connections,
1668
+ })
1669
+ snapshot.graph.connections = connections
1670
+ return {
1671
+ ok: true,
1672
+ node: snapshot,
1673
+ context: {
1674
+ nodeId: capabilityNode.node.nodeId,
1675
+ graphVersion: Number(graph.version || 1),
1676
+ connectedPeers: connections.map(connection => ({ nodeId: connection.peerNodeId })),
1677
+ capabilityStateRefs: capabilityStateRefs(projectRoot),
1678
+ ontology,
1679
+ affordances: ontology.affordances,
1680
+ },
1681
+ }
1682
+ } catch (capabilityError) {
1683
+ if (!(capabilityError.code === 'NOT_FOUND' || capabilityError.code === 'BAD_REQUEST')) {
1684
+ throw capabilityError
1685
+ }
1686
+ }
1687
+ try {
1688
+ const goalNode = getGoalNode(projectRoot, nodeId)
1689
+ if (isDeletedGraphNode(graph, goalNode.node.nodeId)) {
1690
+ throw new GoalNodeError(`Goal node not found: ${goalNode.node.nodeId}`, {
1691
+ statusCode: 404,
1692
+ code: 'NOT_FOUND',
1693
+ })
1694
+ }
1695
+ const settings = readNodeSettings(projectRoot, goalNode.node.nodeId, 'goal')
1696
+ const snapshot = buildGoalNodeSnapshot(goalNode.node, goalNode.state, settings)
1697
+ const connections = buildConnections(goalNode.node.nodeId, graph)
1698
+ const ontology = buildNodeOntologyContext({
1699
+ nodeId: goalNode.node.nodeId,
1700
+ nodeType: 'goal',
1701
+ connections,
1702
+ })
1703
+ snapshot.graph.position = graph.positions?.[goalNode.node.nodeId] || snapshot.graph.position
1704
+ snapshot.graph.connections = connections
1705
+ return {
1706
+ ok: true,
1707
+ node: snapshot,
1708
+ context: {
1709
+ nodeId: goalNode.node.nodeId,
1710
+ graphVersion: Number(graph.version || 1),
1711
+ connectedPeers: connections.map(connection => ({ nodeId: connection.peerNodeId })),
1712
+ goalStateRefs: goalStateRefsForGraph(projectRoot, graph),
1713
+ ontology,
1714
+ affordances: ontology.affordances,
1715
+ },
1716
+ }
1717
+ } catch (goalError) {
1718
+ if (isDeletedGraphNode(graph, nodeId)) {
1719
+ throw goalError
1720
+ }
1721
+ if (!(goalError.code === 'NOT_FOUND' || goalError.code === 'BAD_REQUEST')) {
1722
+ throw goalError
1723
+ }
1724
+ }
1725
+ }
1726
+ throw error
1727
+ }
1728
+ const settings = readNodeSettings(projectRoot, current.node.nodeId)
1729
+ const snapshot = buildNodeSnapshot(current.node, current.state, settings)
1730
+ const graph = loadWorkflowGraphMap(projectRoot)
1731
+ const connections = buildConnections(current.node.nodeId, graph)
1732
+ const peerIds = connections.map(connection => connection.peerNodeId)
1733
+ snapshot.graph.connections = connections
1734
+ const refs = componentStateRefs(projectRoot)
1735
+ const ontology = buildNodeOntologyContext({
1736
+ nodeId: current.node.nodeId,
1737
+ nodeType: current.node.type,
1738
+ connections,
1739
+ })
1740
+ const connectedPeers = [...new Set(peerIds)].map((peerNodeId) => {
1741
+ const ref = refs[peerNodeId]
1742
+ return ref
1743
+ ? {
1744
+ nodeId: peerNodeId,
1745
+ type: ref.type,
1746
+ title: ref.title,
1747
+ stateRef: { path: ref.statePath, revision: ref.revision },
1748
+ ...(ref.file ? { file: ref.file } : {}),
1749
+ }
1750
+ : { nodeId: peerNodeId }
1751
+ })
1752
+ return {
1753
+ ok: true,
1754
+ node: snapshot,
1755
+ context: {
1756
+ nodeId: current.node.nodeId,
1757
+ graphVersion: Number(graph.version || 1),
1758
+ connectedPeers,
1759
+ componentStateRefs: refs,
1760
+ ontology,
1761
+ affordances: ontology.affordances,
1762
+ },
1763
+ }
1764
+ } catch (error) {
1765
+ throw asComponentNodeError(error)
1766
+ }
1767
+ }