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.
- package/CHANGELOG.md +49 -0
- package/README-CN.md +4 -2
- package/README.md +23 -4
- package/package.json +17 -2
- package/src/generator.js +19 -6
- package/src/index.js +531 -38
- package/src/prompts.js +4 -4
- package/src/ui/dist/assets/AgentsRoute-BGGs90wH.js +7 -0
- package/src/ui/dist/assets/Assistant-Bold-gm-uSS1B.woff2 +0 -0
- package/src/ui/dist/assets/Assistant-Medium-DrcxCXg3.woff2 +0 -0
- package/src/ui/dist/assets/Assistant-Regular-DVxZuzxb.woff2 +0 -0
- package/src/ui/dist/assets/Assistant-SemiBold-SCI4bEL9.woff2 +0 -0
- package/src/ui/dist/assets/Combination-DhBLEmof.js +42 -0
- package/src/ui/dist/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
- package/src/ui/dist/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
- package/src/ui/dist/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
- package/src/ui/dist/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
- package/src/ui/dist/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
- package/src/ui/dist/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
- package/src/ui/dist/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
- package/src/ui/dist/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
- package/src/ui/dist/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
- package/src/ui/dist/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
- package/src/ui/dist/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
- package/src/ui/dist/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
- package/src/ui/dist/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
- package/src/ui/dist/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
- package/src/ui/dist/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
- package/src/ui/dist/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
- package/src/ui/dist/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
- package/src/ui/dist/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
- package/src/ui/dist/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
- package/src/ui/dist/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
- package/src/ui/dist/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
- package/src/ui/dist/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
- package/src/ui/dist/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
- package/src/ui/dist/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
- package/src/ui/dist/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
- package/src/ui/dist/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
- package/src/ui/dist/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
- package/src/ui/dist/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
- package/src/ui/dist/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
- package/src/ui/dist/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
- package/src/ui/dist/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
- package/src/ui/dist/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
- package/src/ui/dist/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
- package/src/ui/dist/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
- package/src/ui/dist/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
- package/src/ui/dist/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
- package/src/ui/dist/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
- package/src/ui/dist/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
- package/src/ui/dist/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
- package/src/ui/dist/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
- package/src/ui/dist/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
- package/src/ui/dist/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
- package/src/ui/dist/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
- package/src/ui/dist/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
- package/src/ui/dist/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
- package/src/ui/dist/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
- package/src/ui/dist/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
- package/src/ui/dist/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
- package/src/ui/dist/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
- package/src/ui/dist/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
- package/src/ui/dist/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
- package/src/ui/dist/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
- package/src/ui/dist/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
- package/src/ui/dist/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
- package/src/ui/dist/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
- package/src/ui/dist/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
- package/src/ui/dist/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
- package/src/ui/dist/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
- package/src/ui/dist/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
- package/src/ui/dist/assets/RolesRoute-DnodCSz0.js +2 -0
- package/src/ui/dist/assets/RuntimePicker-CucD8TGn.js +7 -0
- package/src/ui/dist/assets/SettingsRoute-DquMWjac.js +2 -0
- package/src/ui/dist/assets/TaskList-Bxc0Uktx.js +7 -0
- package/src/ui/dist/assets/TerminalDrawer-CDusYltc.js +36 -0
- package/src/ui/dist/assets/WorkflowRoute-BN8yHmUs.css +1 -0
- package/src/ui/dist/assets/WorkflowRoute-D-0wVDpY.js +334 -0
- package/src/ui/dist/assets/abnfDiagram-VRR7QNED-zkRKkaxO.js +2 -0
- package/src/ui/dist/assets/apl-B4CMkyY2.js +2 -0
- package/src/ui/dist/assets/ar-SA-G6X2FPQ2-BiHaArx5.js +11 -0
- package/src/ui/dist/assets/arc-CBnLtOFO.js +2 -0
- package/src/ui/dist/assets/architectureDiagram-ZJ3FMSHR-DWXRXsh1.js +37 -0
- package/src/ui/dist/assets/asciiarmor-Df11BRmG.js +2 -0
- package/src/ui/dist/assets/asn1-EdZsLKOL.js +2 -0
- package/src/ui/dist/assets/asterisk-B-8jnY81.js +2 -0
- package/src/ui/dist/assets/az-AZ-76LH7QW2-CiT05voO.js +2 -0
- package/src/ui/dist/assets/bg-BG-XCXSNQG7-L-UM9tNf.js +6 -0
- package/src/ui/dist/assets/blockDiagram-677ZJIJ3-BIyh3Bry.js +133 -0
- package/src/ui/dist/assets/bn-BD-2XOGV67Q-NdgbA0bC.js +6 -0
- package/src/ui/dist/assets/brainfuck-C4LP7Hcl.js +2 -0
- package/src/ui/dist/assets/c4Diagram-LMCZKHZV-CA54vWWM.js +11 -0
- package/src/ui/dist/assets/ca-ES-6MX7JW3Y-BKZZsQ5J.js +9 -0
- package/src/ui/dist/assets/channel-Da_-zcAu.js +2 -0
- package/src/ui/dist/assets/chunk-2Q5K7J3B-DE4cxr1v.js +2 -0
- package/src/ui/dist/assets/chunk-32BRIVSS-i7FwbEU9.js +2 -0
- package/src/ui/dist/assets/chunk-5VM5RSS4-BhIcOYSu.js +16 -0
- package/src/ui/dist/assets/chunk-EX3LRPZG-Ci9zZRIs.js +232 -0
- package/src/ui/dist/assets/chunk-JWPE2WC7-BFdkcz-G.js +2 -0
- package/src/ui/dist/assets/chunk-MOJQB5TN-Dzmapl_S.js +89 -0
- package/src/ui/dist/assets/chunk-RYQCIY6F-RTmp7FOy.js +2 -0
- package/src/ui/dist/assets/chunk-V7JOEXUC-DInU3v3D.js +207 -0
- package/src/ui/dist/assets/chunk-VR4S4FIN-BHoyGBQO.js +2 -0
- package/src/ui/dist/assets/chunk-XXDRQBXY-DfeLZNZx.js +2 -0
- package/src/ui/dist/assets/classDiagram-OUVF2IWQ-FKbUS0bu.js +2 -0
- package/src/ui/dist/assets/classDiagram-v2-EOCWNBFH-FKbUS0bu.js +2 -0
- package/src/ui/dist/assets/clike-B9uivgTg.js +2 -0
- package/src/ui/dist/assets/clojure-BMjYHr_A.js +2 -0
- package/src/ui/dist/assets/cmake-BQqOBYOt.js +2 -0
- package/src/ui/dist/assets/cobol-CWcv1MsR.js +2 -0
- package/src/ui/dist/assets/coffeescript-S37ZYGWr.js +2 -0
- package/src/ui/dist/assets/commonlisp-DBKNyK5s.js +2 -0
- package/src/ui/dist/assets/cose-bilkent-JH36ORCC-_5PUJ615.js +2 -0
- package/src/ui/dist/assets/crystal-SjHAIU92.js +2 -0
- package/src/ui/dist/assets/cs-CZ-2BRQDIVT-DnkfnhDU.js +12 -0
- package/src/ui/dist/assets/css-BnMrqG3P.js +2 -0
- package/src/ui/dist/assets/cynefin-VYW2F7L2-CmkcFv-b.js +179 -0
- package/src/ui/dist/assets/cynefinDiagram-TSTJHNR4-D72VZn8H.js +63 -0
- package/src/ui/dist/assets/cypher-C_CwsFkJ.js +2 -0
- package/src/ui/dist/assets/cytoscape.esm-DTSO7Bv0.js +332 -0
- package/src/ui/dist/assets/d-pRatUO7H.js +2 -0
- package/src/ui/dist/assets/da-DK-5WZEPLOC-C54tQUgd.js +6 -0
- package/src/ui/dist/assets/dagre-VKFMJZFB-0BsAkrmo.js +5 -0
- package/src/ui/dist/assets/de-DE-XR44H4JA-DGfZlR10.js +9 -0
- package/src/ui/dist/assets/defaultLocale-DX6XiGOO.js +2 -0
- package/src/ui/dist/assets/diagram-FQU43EPY-C1ZJDOca.js +4 -0
- package/src/ui/dist/assets/diagram-G47NLZAW-D3pr3FSP.js +25 -0
- package/src/ui/dist/assets/diagram-NH7WQ7WH-Bwry0NV-.js +25 -0
- package/src/ui/dist/assets/diagram-OA4YK3LP-L6g09cmV.js +31 -0
- package/src/ui/dist/assets/diagram-WEI45ONY-Bh1puYVX.js +42 -0
- package/src/ui/dist/assets/diff-DbItnlRl.js +2 -0
- package/src/ui/dist/assets/directory-open-01563666-DWU9wJ6I.js +2 -0
- package/src/ui/dist/assets/directory-open-4ed118d0-CunoC1EB.js +2 -0
- package/src/ui/dist/assets/dockerfile-BKs6k2Af.js +2 -0
- package/src/ui/dist/assets/dtd-DF_7sFjM.js +2 -0
- package/src/ui/dist/assets/dylan-DwRh75JA.js +2 -0
- package/src/ui/dist/assets/ebnf-CDyGwa7X.js +2 -0
- package/src/ui/dist/assets/ebnfDiagram-CCIWWBDH-BC_mBRR5.js +2 -0
- package/src/ui/dist/assets/ecl-Cabwm37j.js +2 -0
- package/src/ui/dist/assets/eiffel-CnydiIhH.js +2 -0
- package/src/ui/dist/assets/el-GR-BZB4AONW-DsnwWweL.js +11 -0
- package/src/ui/dist/assets/elm-vLlmbW-K.js +2 -0
- package/src/ui/dist/assets/erDiagram-Q63AITRT-Bkxam36l.js +86 -0
- package/src/ui/dist/assets/erlang-BNw1qcRV.js +2 -0
- package/src/ui/dist/assets/es-ES-U4NZUMDT-CRxBcQgC.js +10 -0
- package/src/ui/dist/assets/eu-ES-A7QVB2H4-CTd_E7YR.js +12 -0
- package/src/ui/dist/assets/excalidraw-default-bMWIgNCy.svg +1 -0
- package/src/ui/dist/assets/fa-IR-HGAKTJCU-DO56k7eG.js +9 -0
- package/src/ui/dist/assets/factor-kuTfRLto.js +2 -0
- package/src/ui/dist/assets/fcl-Kvtd6kyn.js +2 -0
- package/src/ui/dist/assets/fi-FI-Z5N7JZ37-DEDCF9WB.js +7 -0
- package/src/ui/dist/assets/file-open-002ab408-DIuFHtCF.js +2 -0
- package/src/ui/dist/assets/file-open-7c801643-684qeFg4.js +2 -0
- package/src/ui/dist/assets/file-save-3189631c-C1wFhQhH.js +2 -0
- package/src/ui/dist/assets/file-save-745eba88-Bb9F9Kg7.js +2 -0
- package/src/ui/dist/assets/flowDiagram-23GEKE2U-C3SAEao7.js +157 -0
- package/src/ui/dist/assets/forth-Ffai-XNe.js +2 -0
- package/src/ui/dist/assets/fortran-DYz_wnZ1.js +2 -0
- package/src/ui/dist/assets/fr-FR-RHASNOE6-DE2nPyDt.js +10 -0
- package/src/ui/dist/assets/ganttDiagram-NO4QXBWP-U-J_Xi-_.js +293 -0
- package/src/ui/dist/assets/gas-Bneqetm1.js +2 -0
- package/src/ui/dist/assets/gherkin-heZmZLOM.js +2 -0
- package/src/ui/dist/assets/gitGraphDiagram-IHSO6WYX-BADVbD-a.js +107 -0
- package/src/ui/dist/assets/gl-ES-HMX3MZ6V-mCqVgQHC.js +11 -0
- package/src/ui/dist/assets/graph-C9eacEi8.js +2 -0
- package/src/ui/dist/assets/groovy-D9Dt4D0W.js +2 -0
- package/src/ui/dist/assets/haskell-Cw1EW3IL.js +2 -0
- package/src/ui/dist/assets/haxe-H-WmDvRZ.js +2 -0
- package/src/ui/dist/assets/he-IL-6SHJWFNN-KnaLrCzw.js +11 -0
- package/src/ui/dist/assets/hi-IN-IWLTKZ5I-wuqiaaWt.js +5 -0
- package/src/ui/dist/assets/http-DBlCnlav.js +2 -0
- package/src/ui/dist/assets/hu-HU-A5ZG7DT2-DLg_zm22.js +8 -0
- package/src/ui/dist/assets/id-ID-SAP4L64H-MB2mXqcR.js +11 -0
- package/src/ui/dist/assets/idl-BEugSyMb.js +2 -0
- package/src/ui/dist/assets/image-blob-reduce.esm-HwCm7IZ0.js +8 -0
- package/src/ui/dist/assets/index-39SJtrt0.js +2 -0
- package/src/ui/dist/assets/index-BQ0LIrW-.js +2 -0
- package/src/ui/dist/assets/index-BcAUh2Nm.css +1 -0
- package/src/ui/dist/assets/index-BcThOK-i.js +331 -0
- package/src/ui/dist/assets/index-BnxV3k_S.js +3 -0
- package/src/ui/dist/assets/index-C7MZBHbT.js +117 -0
- package/src/ui/dist/assets/index-CCvKI2rM.js +2 -0
- package/src/ui/dist/assets/index-CZvmcCab.js +8 -0
- package/src/ui/dist/assets/index-Cp3hLKNl.js +2 -0
- package/src/ui/dist/assets/index-D7ayFxcx.js +2 -0
- package/src/ui/dist/assets/index-DAYxSQGU.js +2 -0
- package/src/ui/dist/assets/index-DD-HeMV0.css +1 -0
- package/src/ui/dist/assets/index-DPMxd3Qn.js +2 -0
- package/src/ui/dist/assets/index-DSe2jx3k.js +4 -0
- package/src/ui/dist/assets/index-DUirCkgW.js +2 -0
- package/src/ui/dist/assets/index-DaKPQ2hN.js +159 -0
- package/src/ui/dist/assets/index-DzpuR0Jg.js +2 -0
- package/src/ui/dist/assets/index-POndwsCI.js +2 -0
- package/src/ui/dist/assets/index-R8NM9I_9.js +2 -0
- package/src/ui/dist/assets/index-TCDuDhBj.js +2 -0
- package/src/ui/dist/assets/index-TckzASdM.js +2 -0
- package/src/ui/dist/assets/index-Yi5ytFFr.js +2 -0
- package/src/ui/dist/assets/infoDiagram-FWYZ7A6U-CiGBlFqH.js +3 -0
- package/src/ui/dist/assets/init-Gi6I4Gst.js +2 -0
- package/src/ui/dist/assets/ishikawaDiagram-FXEZZL3T-B1aheRbz.js +71 -0
- package/src/ui/dist/assets/it-IT-JPQ66NNP-DT3HSU7X.js +12 -0
- package/src/ui/dist/assets/ja-JP-DBVTYXUO-CsfJjjWt.js +9 -0
- package/src/ui/dist/assets/javascript-iXu5QeM3.js +2 -0
- package/src/ui/dist/assets/journeyDiagram-5HDEW3XC-DtnJPxub.js +140 -0
- package/src/ui/dist/assets/julia-DuME0IfC.js +2 -0
- package/src/ui/dist/assets/kaa-6HZHGXH3-DQz-1-Gn.js +2 -0
- package/src/ui/dist/assets/kab-KAB-ZGHBKWFO-CaH-LSoK.js +9 -0
- package/src/ui/dist/assets/kanban-definition-HUTT4EX6-CZatSm3e.js +90 -0
- package/src/ui/dist/assets/katex-C5jXJg4s.js +258 -0
- package/src/ui/dist/assets/kk-KZ-P5N5QNE5-BMXmcm55.js +2 -0
- package/src/ui/dist/assets/km-KH-HSX4SM5Z-ZeoJNBpN.js +12 -0
- package/src/ui/dist/assets/ko-KR-MTYHY66A-BeaLRKph.js +10 -0
- package/src/ui/dist/assets/ku-TR-6OUDTVRD-B_28KrkR.js +10 -0
- package/src/ui/dist/assets/layout-DEXfKzaS.js +2 -0
- package/src/ui/dist/assets/linear-CpEDnQRC.js +2 -0
- package/src/ui/dist/assets/livescript-BwQOo05w.js +2 -0
- package/src/ui/dist/assets/lt-LT-XHIRWOB4-BxDcnUhp.js +4 -0
- package/src/ui/dist/assets/lua-VAEuO923.js +2 -0
- package/src/ui/dist/assets/lv-LV-5QDEKY6T-DpKDl1_y.js +8 -0
- package/src/ui/dist/assets/map-Czzmt4hB.js +2 -0
- package/src/ui/dist/assets/mathematica-DTrFuWx2.js +2 -0
- package/src/ui/dist/assets/mbox-CNhZ1qSd.js +2 -0
- package/src/ui/dist/assets/mermaidLoader-CBO31t0J.js +305 -0
- package/src/ui/dist/assets/mermaidLoader-CEK31ho9.css +1 -0
- package/src/ui/dist/assets/mindmap-definition-LN4V7U3C-CQ0TccEr.js +97 -0
- package/src/ui/dist/assets/mirc-CjQqDB4T.js +2 -0
- package/src/ui/dist/assets/mllike-CXdrOF99.js +2 -0
- package/src/ui/dist/assets/modelica-Dc1JOy9r.js +2 -0
- package/src/ui/dist/assets/mr-IN-CRQNXWMA-BKCZT58p.js +14 -0
- package/src/ui/dist/assets/mscgen-BA5vi2Kp.js +2 -0
- package/src/ui/dist/assets/mumps-BT43cFF4.js +2 -0
- package/src/ui/dist/assets/my-MM-5M5IBNSE-D34k0G9T.js +2 -0
- package/src/ui/dist/assets/nb-NO-T6EIAALU-DXr_KW8J.js +11 -0
- package/src/ui/dist/assets/nginx-DdIZxoE0.js +2 -0
- package/src/ui/dist/assets/nl-NL-IS3SIHDZ-C1L0aVDR.js +9 -0
- package/src/ui/dist/assets/nn-NO-6E72VCQL-CLWVSxbZ.js +9 -0
- package/src/ui/dist/assets/nsis-LdVXkNf5.js +2 -0
- package/src/ui/dist/assets/ntriples-BfvgReVJ.js +2 -0
- package/src/ui/dist/assets/oc-FR-POXYY2M6-nf8_J3eR.js +9 -0
- package/src/ui/dist/assets/octave-Ck1zUtKM.js +2 -0
- package/src/ui/dist/assets/ordinal-Cboi1Yqb.js +2 -0
- package/src/ui/dist/assets/oz-BzwKVEFT.js +2 -0
- package/src/ui/dist/assets/pa-IN-N4M65BXN-CAMJ-xd6.js +5 -0
- package/src/ui/dist/assets/pascal--L3eBynH.js +2 -0
- package/src/ui/dist/assets/pegDiagram-2B236MQR-Bj2RcmRd.js +2 -0
- package/src/ui/dist/assets/percentages-BXMCSKIN-s9yWFdr6.js +160 -0
- package/src/ui/dist/assets/perl-CdXCOZ3F.js +2 -0
- package/src/ui/dist/assets/pica-BGv_AhAB.js +8 -0
- package/src/ui/dist/assets/pieDiagram-ENE6RG2P-obpzz8dC.js +40 -0
- package/src/ui/dist/assets/pig-CevX1Tat.js +2 -0
- package/src/ui/dist/assets/pl-PL-T2D74RX3-DKJ5H85v.js +10 -0
- package/src/ui/dist/assets/powershell-CFHJl5sT.js +2 -0
- package/src/ui/dist/assets/properties-C78fOPTZ.js +2 -0
- package/src/ui/dist/assets/protobuf-ChK-085T.js +2 -0
- package/src/ui/dist/assets/{proxy-D0vUbds5.js → proxy-BSmwef4H.js} +2 -2
- package/src/ui/dist/assets/pt-BR-5N22H2LF-B5kow-kN.js +10 -0
- package/src/ui/dist/assets/pt-PT-UZXXM6DQ-B2G34fso.js +10 -0
- package/src/ui/dist/assets/pug-DeIclll2.js +2 -0
- package/src/ui/dist/assets/puppet-DMA9R1ak.js +2 -0
- package/src/ui/dist/assets/python-BuPzkPfP.js +2 -0
- package/src/ui/dist/assets/q-pXgVlZs6.js +2 -0
- package/src/ui/dist/assets/quadrantDiagram-ABIIQ3AL-Cl-T7NkJ.js +8 -0
- package/src/ui/dist/assets/r-B6wPVr8A.js +2 -0
- package/src/ui/dist/assets/railroadDiagram-RFXS5EU6-DMq82RHg.js +2 -0
- package/src/ui/dist/assets/{refresh-cw-JNmExijd.js → refresh-cw-CbFd7guV.js} +2 -2
- package/src/ui/dist/assets/requirementDiagram-TGXJPOKE-tGInMDgU.js +85 -0
- package/src/ui/dist/assets/ro-RO-JPDTUUEW-BwLdZW91.js +12 -0
- package/src/ui/dist/assets/roundRect-0PYZxl1G.js +2 -0
- package/src/ui/dist/assets/rpm-CTu-6PCP.js +2 -0
- package/src/ui/dist/assets/ru-RU-B4JR7IUQ-DpdAgIJc.js +10 -0
- package/src/ui/dist/assets/ruby-B2Rjki9n.js +2 -0
- package/src/ui/dist/assets/runtimeBrand-BoKPimsz.js +2 -0
- package/src/ui/dist/assets/sankeyDiagram-HTMAVEWB-C7X-ET4m.js +41 -0
- package/src/ui/dist/assets/sas-B4kiWyti.js +2 -0
- package/src/ui/dist/assets/save-D1CXHwBw.js +12 -0
- package/src/ui/dist/assets/scheme-C41bIUwD.js +2 -0
- package/src/ui/dist/assets/sequenceDiagram-DBY2YBRQ-Bn24i_nY.js +163 -0
- package/src/ui/dist/assets/settings-2-DtSNAAcU.js +22 -0
- package/src/ui/dist/assets/shell-CjFT_Tl9.js +2 -0
- package/src/ui/dist/assets/shield-check-BVbJ8T_N.js +7 -0
- package/src/ui/dist/assets/si-LK-N5RQ5JYF-naJaUy5u.js +2 -0
- package/src/ui/dist/assets/sieve-C3Gn_uJK.js +2 -0
- package/src/ui/dist/assets/simple-mode-GW_nhZxv.js +2 -0
- package/src/ui/dist/assets/sizeCapture-X5ZJPWSS-DAQHfm82.js +2 -0
- package/src/ui/dist/assets/sk-SK-C5VTKIMK-1NPz1J_d.js +7 -0
- package/src/ui/dist/assets/sl-SI-NN7IZMDC-D8RfmNQw.js +7 -0
- package/src/ui/dist/assets/smalltalk-CnHTOXQT.js +2 -0
- package/src/ui/dist/assets/solr-DehyRSwq.js +2 -0
- package/src/ui/dist/assets/sparql-DkYu6x3z.js +2 -0
- package/src/ui/dist/assets/spreadsheet-BCZA_wO0.js +2 -0
- package/src/ui/dist/assets/sql-D0XecflT.js +2 -0
- package/src/ui/dist/assets/stateDiagram-2N3HPSRC-Ck48D1ck.js +2 -0
- package/src/ui/dist/assets/stateDiagram-v2-6OUMAXLB-CdfRCJAJ.js +2 -0
- package/src/ui/dist/assets/stex-C3f8Ysf7.js +2 -0
- package/src/ui/dist/assets/style-C3QI7VPf.css +1 -0
- package/src/ui/dist/assets/stylus-B533Al4x.js +2 -0
- package/src/ui/dist/assets/subset-shared.chunk-eAtLHmbi.js +85 -0
- package/src/ui/dist/assets/subset-worker.chunk-DeHCTnTk.js +2 -0
- package/src/ui/dist/assets/sv-SE-XGPEYMSR-BeOYCU-8.js +11 -0
- package/src/ui/dist/assets/swift-BzpIVaGY.js +2 -0
- package/src/ui/dist/assets/swimlanes-5IMT3BWC-DOy22UjP.js +3 -0
- package/src/ui/dist/assets/swimlanesDiagram-G3AALYLV-sicPbVOg.js +9 -0
- package/src/ui/dist/assets/ta-IN-2NMHFXQM-CtS68WDd.js +10 -0
- package/src/ui/dist/assets/tcl-DVfN8rqt.js +2 -0
- package/src/ui/dist/assets/{terminal-BBQvnOAK.js → terminal-DEby5Y_P.js} +2 -2
- package/src/ui/dist/assets/terminalControl-BkFeDVXo.js +134 -0
- package/src/ui/dist/assets/textile-CnDTJFAw.js +2 -0
- package/src/ui/dist/assets/th-TH-HPSO5L25-DCHY9-Co.js +3 -0
- package/src/ui/dist/assets/tiddlywiki-DO-Gjzrf.js +2 -0
- package/src/ui/dist/assets/tiki-DGYXhP31.js +2 -0
- package/src/ui/dist/assets/timeline-definition-FHXFAJF6-DVkG72qO.js +121 -0
- package/src/ui/dist/assets/toml-Bm5Em-hy.js +2 -0
- package/src/ui/dist/assets/tr-TR-DEFEU3FU-y5-vjTv8.js +8 -0
- package/src/ui/dist/assets/troff-wAsdV37c.js +2 -0
- package/src/ui/dist/assets/ttcn-CfJYG6tj.js +2 -0
- package/src/ui/dist/assets/ttcn-cfg-B9xdYoR4.js +2 -0
- package/src/ui/dist/assets/turtle-B1tBg_DP.js +2 -0
- package/src/ui/dist/assets/uk-UA-QMV73CPH-BRbR5It6.js +7 -0
- package/src/ui/dist/assets/useReducedMotion-D07XP52Q.js +12 -0
- package/src/ui/dist/assets/vb-CmGdzxic.js +2 -0
- package/src/ui/dist/assets/vbscript-BuJXcnF6.js +2 -0
- package/src/ui/dist/assets/velocity-D8B20fx6.js +2 -0
- package/src/ui/dist/assets/vennDiagram-L72KCM5P-DwMwBlf-.js +35 -0
- package/src/ui/dist/assets/verilog-C6RDOZhf.js +2 -0
- package/src/ui/dist/assets/vhdl-lSbBsy5d.js +2 -0
- package/src/ui/dist/assets/vi-VN-M7AON7JQ-s6wSBv2s.js +6 -0
- package/src/ui/dist/assets/wardleyDiagram-EHGQE667-CNuuvzEg.js +79 -0
- package/src/ui/dist/assets/webidl-ZXfAyPTL.js +2 -0
- package/src/ui/dist/assets/{x-DoIOGAJz.js → x-B84wq78k.js} +2 -2
- package/src/ui/dist/assets/xquery-DzFWVndE.js +2 -0
- package/src/ui/dist/assets/xychartDiagram-FW5EYKEG-dUpZxmn6.js +8 -0
- package/src/ui/dist/assets/yacas-BJ4BC0dw.js +2 -0
- package/src/ui/dist/assets/z80-Hz9HOZM7.js +2 -0
- package/src/ui/dist/assets/zh-CN-LNUGB5OW-Cu_-POQN.js +11 -0
- package/src/ui/dist/assets/zh-HK-E62DVLB3-BCx4W4qZ.js +2 -0
- package/src/ui/dist/assets/zh-TW-RAJ6MFWO-02wx_Mrj.js +10 -0
- package/src/ui/dist/icons/excalidraw.svg +1 -0
- package/src/ui/dist/icons/markdown.svg +1 -0
- package/src/ui/dist/index.html +2 -2
- package/src/ui/package.json +10 -0
- package/src/ui/playwright.config.ts +36 -0
- package/src/ui/pnpm-workspace.yaml +2 -0
- package/src/ui/public/icons/excalidraw.svg +1 -0
- package/src/ui/public/icons/markdown.svg +1 -0
- package/src/ui/tsconfig.json +2 -1
- package/src/ui/vite.config.ts +1 -0
- package/src/wf-ui-server/__tests__/a2a-store.test.mjs +432 -15
- package/src/wf-ui-server/__tests__/action-registry-loader.test.mjs +156 -0
- package/src/wf-ui-server/__tests__/claude-stream-json.test.mjs +397 -0
- package/src/wf-ui-server/__tests__/codex-appserver.test.mjs +392 -0
- package/src/wf-ui-server/__tests__/component-node-api.test.mjs +881 -0
- package/src/wf-ui-server/__tests__/component-node-store.test.mjs +401 -0
- package/src/wf-ui-server/__tests__/control-plane-acceptance.test.mjs +2027 -0
- package/src/wf-ui-server/__tests__/control-plane-cli-smoke.test.mjs +1014 -0
- package/src/wf-ui-server/__tests__/dispatch-lifecycle.test.mjs +330 -0
- package/src/wf-ui-server/__tests__/effort-compatibility-red-092.test.mjs +151 -0
- package/src/wf-ui-server/__tests__/graph-layout-agent-tree.test.mjs +254 -0
- package/src/wf-ui-server/__tests__/graph-layout-tidy.test.mjs +260 -0
- package/src/wf-ui-server/__tests__/graph-layout.test.mjs +84 -0
- package/src/wf-ui-server/__tests__/node-config-api.test.mjs +384 -0
- package/src/wf-ui-server/__tests__/opencode-server.test.mjs +526 -0
- package/src/wf-ui-server/__tests__/peer-capsule.test.mjs +2 -2
- package/src/wf-ui-server/__tests__/pty-adapter.test.mjs +71 -1
- package/src/wf-ui-server/__tests__/runtime-config.test.mjs +3 -3
- package/src/wf-ui-server/__tests__/runtime-detector.test.mjs +21 -0
- package/src/wf-ui-server/__tests__/runtime-review.test.mjs +828 -0
- package/src/wf-ui-server/__tests__/server.integration.test.mjs +1463 -11
- package/src/wf-ui-server/__tests__/session-cleanup.test.mjs +159 -0
- package/src/wf-ui-server/__tests__/session-registry.test.mjs +52 -3
- package/src/wf-ui-server/__tests__/settings.test.mjs +12 -2
- package/src/wf-ui-server/__tests__/spawn-gate.test.mjs +78 -0
- package/src/wf-ui-server/__tests__/task-group-parser.test.mjs +120 -0
- package/src/wf-ui-server/__tests__/task-group-tag.test.mjs +264 -0
- package/src/wf-ui-server/__tests__/task-parser.test.mjs +49 -3
- package/src/wf-ui-server/__tests__/terminal-context-hardening.test.mjs +122 -0
- package/src/wf-ui-server/__tests__/terminal-context.test.mjs +164 -0
- package/src/wf-ui-server/__tests__/terminal-store.test.mjs +4 -4
- package/src/wf-ui-server/__tests__/wf-browser-api.test.mjs +599 -0
- package/src/wf-ui-server/__tests__/wf-browser-store.test.mjs +184 -0
- package/src/wf-ui-server/__tests__/workflow-action-registry.test.mjs +282 -0
- package/src/wf-ui-server/__tests__/workflow-agent-communication.test.mjs +411 -0
- package/src/wf-ui-server/__tests__/workflow-agent-context.test.mjs +323 -0
- package/src/wf-ui-server/__tests__/workflow-agent-cooperation-permissions.test.mjs +318 -0
- package/src/wf-ui-server/__tests__/workflow-agent-messages-structured.test.mjs +458 -0
- package/src/wf-ui-server/__tests__/workflow-agent-routing.test.mjs +434 -0
- package/src/wf-ui-server/__tests__/workflow-agent-runtime.test.mjs +324 -0
- package/src/wf-ui-server/__tests__/workflow-api-integration.test.mjs +263 -0
- package/src/wf-ui-server/__tests__/workflow-api-phase3.test.mjs +1213 -0
- package/src/wf-ui-server/__tests__/workflow-claude-materialize.test.mjs +209 -0
- package/src/wf-ui-server/__tests__/workflow-cli-registry-manuals.test.mjs +173 -0
- package/src/wf-ui-server/__tests__/workflow-cli-surface.test.mjs +176 -0
- package/src/wf-ui-server/__tests__/workflow-codex-resume-id.test.mjs +556 -0
- package/src/wf-ui-server/__tests__/workflow-cooperation-enforcement.test.mjs +553 -0
- package/src/wf-ui-server/__tests__/workflow-depth-resume-dock.test.mjs +846 -0
- package/src/wf-ui-server/__tests__/workflow-display-node.test.mjs +150 -0
- package/src/wf-ui-server/__tests__/workflow-edge-update.test.mjs +296 -0
- package/src/wf-ui-server/__tests__/workflow-file-cache-lock-watch.test.mjs +380 -0
- package/src/wf-ui-server/__tests__/workflow-file-formats.test.mjs +994 -0
- package/src/wf-ui-server/__tests__/workflow-file-node-actions.test.mjs +294 -0
- package/src/wf-ui-server/__tests__/workflow-goal-actions.test.mjs +357 -0
- package/src/wf-ui-server/__tests__/workflow-goal-task-autoclose.test.mjs +256 -0
- package/src/wf-ui-server/__tests__/workflow-graceful-stop.test.mjs +108 -0
- package/src/wf-ui-server/__tests__/workflow-graph-undo.test.mjs +524 -0
- package/src/wf-ui-server/__tests__/workflow-layout.test.mjs +732 -0
- package/src/wf-ui-server/__tests__/workflow-magnetic-topology.test.mjs +222 -0
- package/src/wf-ui-server/__tests__/workflow-markdown-lock.test.mjs +359 -0
- package/src/wf-ui-server/__tests__/workflow-mcp-hub.test.mjs +174 -0
- package/src/wf-ui-server/__tests__/workflow-node-manual-schema.test.mjs +241 -0
- package/src/wf-ui-server/__tests__/workflow-node-runtime.test.mjs +1991 -0
- package/src/wf-ui-server/__tests__/workflow-node-store-utils.test.mjs +125 -0
- package/src/wf-ui-server/__tests__/workflow-registry-drift.test.mjs +296 -0
- package/src/wf-ui-server/__tests__/workflow-resource-handle-four-side.test.mjs +217 -0
- package/src/wf-ui-server/__tests__/workflow-role-profile.test.mjs +234 -0
- package/src/wf-ui-server/__tests__/workflow-set-model.test.mjs +261 -0
- package/src/wf-ui-server/__tests__/workflow-skill-group-context-enabled.test.mjs +131 -0
- package/src/wf-ui-server/__tests__/workflow-skill-group-enable-disable.test.mjs +186 -0
- package/src/wf-ui-server/__tests__/workflow-skills-hub.test.mjs +125 -0
- package/src/wf-ui-server/__tests__/workflow-state-reconcile.test.mjs +139 -0
- package/src/wf-ui-server/__tests__/workflow-subagent-strategy-matrix.test.mjs +529 -0
- package/src/wf-ui-server/__tests__/workflow-team-flow.test.mjs +278 -0
- package/src/wf-ui-server/__tests__/workflow-terminal-control-live.test.mjs +294 -0
- package/src/wf-ui-server/__tests__/workflow-terminal-control.test.mjs +631 -0
- package/src/wf-ui-server/__tests__/workflow-timer-wakeup.test.mjs +318 -0
- package/src/wf-ui-server/__tests__/workflow-unified-resume.test.mjs +537 -0
- package/src/wf-ui-server/__tests__/workspace-api-hardening.test.mjs +187 -0
- package/src/wf-ui-server/__tests__/workspace-api.test.mjs +382 -0
- package/src/wf-ui-server/__tests__/workspace-reveal.test.mjs +138 -0
- package/src/wf-ui-server/__tests__/workspace-store-hardening.test.mjs +255 -0
- package/src/wf-ui-server/__tests__/workspace-store.test.mjs +208 -0
- package/src/wf-ui-server/__tests__/ws-events.integration.test.mjs +52 -25
- package/src/wf-ui-server/__tests__/ws-terminal.integration.test.mjs +12 -6
- package/src/wf-ui-server/__tests__/ws-wf-browser.integration.test.mjs +488 -0
- package/src/wf-ui-server/a2a-store.mjs +3007 -89
- package/src/wf-ui-server/action-registry.mjs +277 -0
- package/src/wf-ui-server/bridge-store.mjs +276 -0
- package/src/wf-ui-server/chat-driver.mjs +220 -0
- package/src/wf-ui-server/chat-store.mjs +109 -0
- package/src/wf-ui-server/claude-transcript-materializer.mjs +235 -0
- package/src/wf-ui-server/codex-update-prompt.mjs +53 -0
- package/src/wf-ui-server/component-node-store.mjs +689 -0
- package/src/wf-ui-server/composition-api.mjs +436 -0
- package/src/wf-ui-server/control-plane-token.mjs +17 -0
- package/src/wf-ui-server/dispatch-contract.mjs +150 -0
- package/src/wf-ui-server/drivers/claude-stream-json.mjs +371 -0
- package/src/wf-ui-server/drivers/codex-appserver.mjs +513 -0
- package/src/wf-ui-server/drivers/opencode-server.mjs +486 -0
- package/src/wf-ui-server/excalidraw-svg.mjs +141 -0
- package/src/wf-ui-server/file-format-adapters.mjs +511 -0
- package/src/wf-ui-server/file-watcher.mjs +215 -0
- package/src/wf-ui-server/graph-layout.mjs +778 -0
- package/src/wf-ui-server/harness-env.mjs +46 -0
- package/src/wf-ui-server/json-store-utils.mjs +48 -0
- package/src/wf-ui-server/model-capability.mjs +773 -0
- package/src/wf-ui-server/node-config-store.mjs +233 -0
- package/src/wf-ui-server/node-store-utils.mjs +88 -0
- package/src/wf-ui-server/peer-capsule.mjs +56 -1
- package/src/wf-ui-server/pty-adapter.mjs +513 -11
- package/src/wf-ui-server/reveal-foreground.mjs +69 -0
- package/src/wf-ui-server/runtime-detector.mjs +52 -6
- package/src/wf-ui-server/server.mjs +5613 -403
- package/src/wf-ui-server/session-cleanup.mjs +363 -0
- package/src/wf-ui-server/session-registry.mjs +179 -3
- package/src/wf-ui-server/settings.mjs +25 -0
- package/src/wf-ui-server/spawn-gate.mjs +46 -0
- package/src/wf-ui-server/task-parser.mjs +88 -3
- package/src/wf-ui-server/terminal-store.mjs +225 -11
- package/src/wf-ui-server/timer-wakeup-scheduler.mjs +226 -0
- package/src/wf-ui-server/wf-browser-store.mjs +733 -0
- package/src/wf-ui-server/workflow-agent-context.mjs +1731 -0
- package/src/wf-ui-server/workflow-capability-node-store.mjs +676 -0
- package/src/wf-ui-server/workflow-event-node-store.mjs +623 -0
- package/src/wf-ui-server/workflow-goal-node-store.mjs +328 -0
- package/src/wf-ui-server/workflow-graph-store.mjs +623 -0
- package/src/wf-ui-server/workflow-mcp-hub.mjs +286 -0
- package/src/wf-ui-server/workflow-node-runtime.mjs +1767 -0
- package/src/wf-ui-server/workflow-node-settings-store.mjs +339 -0
- package/src/wf-ui-server/workflow-node-types/agent-node.mjs +920 -0
- package/src/wf-ui-server/workflow-node-types/display-node.mjs +151 -0
- package/src/wf-ui-server/workflow-node-types/excalidraw-node.mjs +145 -0
- package/src/wf-ui-server/workflow-node-types/file-node.mjs +484 -0
- package/src/wf-ui-server/workflow-node-types/github-trigger-node.mjs +207 -0
- package/src/wf-ui-server/workflow-node-types/goal-node.mjs +491 -0
- package/src/wf-ui-server/workflow-node-types/markdown-node.mjs +225 -0
- package/src/wf-ui-server/workflow-node-types/mcp-connector-node.mjs +71 -0
- package/src/wf-ui-server/workflow-node-types/role-profile-store.mjs +191 -0
- package/src/wf-ui-server/workflow-node-types/skill-group-node.mjs +98 -0
- package/src/wf-ui-server/workflow-node-types/timer-node.mjs +607 -0
- package/src/wf-ui-server/workflow-ontology.mjs +681 -0
- package/src/wf-ui-server/workflow-operation-store.mjs +115 -0
- package/src/wf-ui-server/workflow-skills-hub.mjs +366 -0
- package/src/wf-ui-server/workflow-skills-market.mjs +723 -0
- package/src/wf-ui-server/workspace-store.mjs +951 -0
- package/src/wf-ui-server/ws-chat.mjs +299 -0
- package/src/wf-ui-server/ws-events.mjs +45 -20
- package/src/wf-ui-server/ws-terminal.mjs +165 -22
- package/src/wf-ui-server/ws-wf-browser.mjs +419 -0
- package/templates/common/.claude/agents/review-manager.md +33 -29
- package/templates/common/.claude/commands/wf-browser.md +1 -1
- package/templates/common/.claude/commands/wf-help.md +5 -3
- package/templates/common/.claude/commands/wf-init.md +44 -0
- package/templates/common/.claude/commands/wf-learn.md +34 -8
- package/templates/common/.claude/commands/wf-max.md +19 -9
- package/templates/common/.claude/commands/wf-review.md +3 -3
- package/templates/common/.claude/commands/wf-search.md +55 -0
- package/templates/common/.claude/commands/wf-task-archive.md +5 -4
- package/templates/common/.claude/commands/wf-task-list.md +6 -3
- package/templates/common/.claude/commands/wf-task-record.md +10 -2
- package/templates/common/.claude/commands/wf-ui.md +12 -5
- package/templates/common/.claude/commands/wf-update.md +4 -0
- package/templates/common/.claude/commands/wf.md +12 -1
- package/templates/common/.claude/rules/ecc/common.md +10 -1
- package/templates/common/.claude/skills/wf/SKILL.md +36 -0
- package/templates/common/.claude/skills/wf-browser/SKILL.md +427 -122
- package/templates/common/.claude/skills/wf-browser/references/BROWSER_CONTROL_PATTERNS.md +43 -0
- package/templates/common/.claude/skills/wf-init/SKILL.md +33 -0
- package/templates/common/.claude/skills/wf-learn/SKILL.md +106 -26
- package/templates/common/.claude/skills/wf-max/SKILL.md +45 -11
- package/templates/common/.claude/skills/wf-review/SKILL.md +26 -54
- package/templates/common/.claude/skills/wf-search/SKILL.md +122 -0
- package/templates/common/.claude/skills/wf-search/references/evidence-contract.md +77 -0
- package/templates/common/.claude/skills/wf-search/references/policies/compare.md +38 -0
- package/templates/common/.claude/skills/wf-search/references/policies/fact.md +39 -0
- package/templates/common/.claude/skills/wf-search/references/policies/troubleshoot.md +39 -0
- package/templates/common/.claude/skills/wf-search/references/policies/verify.md +44 -0
- package/templates/common/.claude/skills/wf-search/references/source-routing.md +40 -0
- package/templates/common/.claude/skills/wf-search/references/tool-adapters.md +38 -0
- package/templates/common/.claude/skills/wf-ui/SKILL.md +146 -12
- package/templates/common/.claude/skills/wf-update/SKILL.md +5 -1
- package/templates/common/.harness-version +188 -85
- package/templates/common/.opencode/agents/review-manager.md +31 -27
- package/templates/common/.opencode/commands/wf-browser.md +1 -1
- package/templates/common/.opencode/commands/wf-help.md +5 -3
- package/templates/common/.opencode/commands/wf-init.md +44 -0
- package/templates/common/.opencode/commands/wf-learn.md +34 -8
- package/templates/common/.opencode/commands/wf-max.md +19 -9
- package/templates/common/.opencode/commands/wf-review.md +3 -3
- package/templates/common/.opencode/commands/wf-search.md +55 -0
- package/templates/common/.opencode/commands/wf-task-archive.md +5 -7
- package/templates/common/.opencode/commands/wf-task-list.md +6 -6
- package/templates/common/.opencode/commands/wf-task-record.md +10 -5
- package/templates/common/.opencode/commands/wf-ui.md +12 -5
- package/templates/common/.opencode/commands/wf-update.md +4 -0
- package/templates/common/.opencode/commands/wf.md +12 -1
- package/templates/common/CLAUDE.md +61 -3
- package/templates/common/Harness/MEMORY.md +8 -5
- package/templates/common/Harness/README.md +14 -10
- package/templates/common/Harness/a2a/action-registry.json +4397 -0
- package/templates/common/Harness/a2a/role-graph.json +16 -3
- package/templates/common/Harness/a2a/skills/terminal-control.json +45 -1
- package/templates/common/Harness/a2a/skills/wf-ui-map.json +50 -0
- package/templates/common/Harness/a2a/skills/workflow-agent-node.json +143 -0
- package/templates/common/Harness/a2a/skills/workflow-context.json +95 -0
- package/templates/common/Harness/a2a/skills/workflow-diagram-node.json +60 -0
- package/templates/common/Harness/a2a/skills/workflow-display-node.json +69 -0
- package/templates/common/Harness/a2a/skills/workflow-file-node.json +92 -0
- package/templates/common/Harness/a2a/skills/workflow-goal-node.json +76 -0
- package/templates/common/Harness/a2a/skills/workflow-markdown-node.json +79 -0
- package/templates/common/Harness/a2a/skills/workflow-mcp-connector-node.json +59 -0
- package/templates/common/Harness/a2a/skills/workflow-node-actions.json +105 -0
- package/templates/common/Harness/a2a/skills/workflow-node-map.json +71 -0
- package/templates/common/Harness/a2a/skills/workflow-ontology.json +54 -0
- package/templates/common/Harness/a2a/skills/workflow-resource-node.json +68 -0
- package/templates/common/Harness/a2a/skills/workflow-skill-group-node.json +56 -0
- package/templates/common/Harness/a2a/skills/workflow-timer-node.json +81 -0
- package/templates/common/Harness/a2a/workflows.json +33 -0
- package/templates/common/Harness/ownership.manifest.json +263 -2
- package/templates/common/Harness/project/architecture.md +499 -0
- package/templates/common/Harness/research/README.md +20 -0
- package/templates/common/Harness/scripts/README.md +70 -6
- package/templates/common/Harness/scripts/a2a-terminal.mjs +12 -4
- package/templates/common/Harness/scripts/context-budget.mjs +7 -3
- package/templates/common/Harness/scripts/lib/memory-context.mjs +400 -0
- package/templates/common/Harness/scripts/lib/path-scope.mjs +77 -0
- package/templates/common/Harness/scripts/lib/research-policy.mjs +99 -0
- package/templates/common/Harness/scripts/lib/wf-search.mjs +189 -0
- package/templates/common/Harness/scripts/memory-context.mjs +63 -0
- package/templates/common/Harness/scripts/research-policy.mjs +27 -0
- package/templates/common/Harness/scripts/task-context.mjs +547 -0
- package/templates/common/Harness/scripts/task-group-index.mjs +242 -0
- package/templates/common/Harness/scripts/task-state.mjs +659 -95
- package/templates/common/Harness/scripts/validate-harness.mjs +70 -28
- package/templates/common/Harness/scripts/wf-auto-update-prompt.mjs +24 -0
- package/templates/common/Harness/scripts/wf-remove.mjs +10 -0
- package/templates/common/Harness/scripts/wf-search.mjs +10 -0
- package/templates/common/Harness/scripts/wf-ui-control.mjs +3144 -0
- package/templates/common/Harness/scripts/wf-update-check.mjs +274 -51
- package/templates/common/Harness/scripts/wf-update-runner.mjs +53 -16
- package/templates/common/Harness/settings.json +10 -0
- package/templates/common/Harness/specs/guides/SETUP.md +4 -3
- package/templates/common/Harness/specs/protocols/HARNESS_BRIDGE.md +429 -48
- package/templates/common/Harness/specs/protocols/MEMORY_PROTOCOL.md +24 -0
- package/templates/common/Harness/specs/protocols/TASK_ARCHIVE.md +14 -11
- package/templates/common/Harness/specs/runtime/command-surface.json +28 -0
- package/templates/common/Harness/specs/runtime/context-loading.md +6 -0
- package/templates/common/Harness/specs/runtime/dispatch.md +9 -1
- package/templates/common/Harness/specs/runtime/subagents.md +20 -5
- package/templates/common/Harness/specs/workflows/WF-AUTO-SPARK.md +19 -0
- package/templates/common/Harness/specs/workflows/WF-AUTO.md +21 -0
- package/templates/common/Harness/specs/workflows/WF-KERNEL.md +4 -2
- package/templates/common/Harness/specs/workflows/WF-MAX.md +39 -12
- package/templates/common/Harness/specs/workflows/WF-STATE.md +62 -14
- package/templates/common/Harness/specs/workflows/WF.md +32 -0
- package/templates/common/Harness/tasks/INDEX.json +7 -0
- package/templates/common/Harness/tasks/INDEX.md +12 -0
- package/templates/common/Harness/tasks/_template/PLAN.md +6 -0
- package/templates/common/Harness/tasks/_template/PROGRESS.md +5 -0
- package/templates/common/Harness/tasks/_template/STATE.json +22 -0
- package/templates/common/Harness/templates/TEST_PLAN.template.md +5 -4
- package/templates/common/Harness/templates/UI_CONTRACT.template.md +7 -5
- package/templates/common/Harness/wf-browser/README.md +69 -0
- package/templates/common/README.md +17 -0
- package/src/ui/dist/assets/AgentsRoute-CuSKYmFu.js +0 -17
- package/src/ui/dist/assets/AgentsRoute-CuSKYmFu.js.map +0 -1
- package/src/ui/dist/assets/RolesRoute-DOzTYVRm.js +0 -7
- package/src/ui/dist/assets/RolesRoute-DOzTYVRm.js.map +0 -1
- package/src/ui/dist/assets/SettingsRoute-DPU7jSQm.js +0 -12
- package/src/ui/dist/assets/SettingsRoute-DPU7jSQm.js.map +0 -1
- package/src/ui/dist/assets/TaskList-BN4r8s1i.js +0 -50
- package/src/ui/dist/assets/TaskList-BN4r8s1i.js.map +0 -1
- package/src/ui/dist/assets/TerminalDrawer-Cej4PxsH.js +0 -74
- package/src/ui/dist/assets/TerminalDrawer-Cej4PxsH.js.map +0 -1
- package/src/ui/dist/assets/WorkflowRoute-BnuhLJ6X.css +0 -1
- package/src/ui/dist/assets/WorkflowRoute-C-clKe-j.js +0 -39
- package/src/ui/dist/assets/WorkflowRoute-C-clKe-j.js.map +0 -1
- package/src/ui/dist/assets/index-BOVYWntB.js +0 -117
- package/src/ui/dist/assets/index-BOVYWntB.js.map +0 -1
- package/src/ui/dist/assets/index-DaUObq0H.css +0 -1
- package/src/ui/dist/assets/maximize-2-LNrr_rKr.js +0 -7
- package/src/ui/dist/assets/maximize-2-LNrr_rKr.js.map +0 -1
- package/src/ui/dist/assets/plus-QuTJyoT3.js +0 -7
- package/src/ui/dist/assets/plus-QuTJyoT3.js.map +0 -1
- package/src/ui/dist/assets/proxy-D0vUbds5.js.map +0 -1
- package/src/ui/dist/assets/refresh-cw-JNmExijd.js.map +0 -1
- package/src/ui/dist/assets/terminal-BBQvnOAK.js.map +0 -1
- package/src/ui/dist/assets/useReducedMotion-BqmH0Tf7.js +0 -7
- package/src/ui/dist/assets/useReducedMotion-BqmH0Tf7.js.map +0 -1
- package/src/ui/dist/assets/x-DoIOGAJz.js.map +0 -1
- package/src/ui/pnpm-lock.yaml +0 -1582
- package/src/ui/src/App.tsx +0 -86
- package/src/ui/src/api.ts +0 -93
- package/src/ui/src/components/AgentsRoute.tsx +0 -503
- package/src/ui/src/components/Footer.tsx +0 -69
- package/src/ui/src/components/Header.tsx +0 -175
- package/src/ui/src/components/LoadingView.tsx +0 -22
- package/src/ui/src/components/RolesRoute.tsx +0 -169
- package/src/ui/src/components/SettingsRoute.tsx +0 -166
- package/src/ui/src/components/TaskList.tsx +0 -388
- package/src/ui/src/components/TerminalDrawer.tsx +0 -611
- package/src/ui/src/components/WorkflowRoute.tsx +0 -670
- package/src/ui/src/hooks/useReducedMotion.ts +0 -17
- package/src/ui/src/hooks/useServerConnection.ts +0 -114
- package/src/ui/src/index.css +0 -281
- package/src/ui/src/main.tsx +0 -11
- package/src/ui/src/types.ts +0 -108
- /package/src/ui/dist/assets/{TerminalDrawer-6GBZ9nXN.css → terminalControl-6GBZ9nXN.css} +0 -0
|
@@ -0,0 +1,3144 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import http from 'node:http';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { spawn } from 'node:child_process';
|
|
6
|
+
import { fileURLToPath } from 'node:url';
|
|
7
|
+
|
|
8
|
+
function parseArgs(argv) {
|
|
9
|
+
const [command, ...rest] = argv;
|
|
10
|
+
const flags = {};
|
|
11
|
+
const args = [];
|
|
12
|
+
for (let i = 0; i < rest.length; i += 1) {
|
|
13
|
+
const arg = rest[i];
|
|
14
|
+
if (!arg.startsWith('--')) {
|
|
15
|
+
args.push(arg);
|
|
16
|
+
continue;
|
|
17
|
+
}
|
|
18
|
+
const key = arg.slice(2);
|
|
19
|
+
const value = rest[i + 1] && !rest[i + 1].startsWith('--') ? rest[++i] : 'true';
|
|
20
|
+
flags[key] = value;
|
|
21
|
+
}
|
|
22
|
+
return { command, flags, args };
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function print(data) {
|
|
26
|
+
process.stdout.write(`${JSON.stringify(data, null, 2)}\n`);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// ── Unified machine-readable command registry ───────────────────────────────
|
|
30
|
+
// Single source of truth for both plain-text `--help` and `help --json`.
|
|
31
|
+
// One entry per command; aliases resolve to their canonical entry. Each flag
|
|
32
|
+
// may carry an optional `value` placeholder used only when rendering
|
|
33
|
+
// plain-text help. `help --json` builds its list from BOTH the registry and
|
|
34
|
+
// the dispatch map, so a command added to COMMAND_DISPATCH without a registry
|
|
35
|
+
// entry shows up as `{ name, summary: '(undocumented)' }` instead of silently
|
|
36
|
+
// missing from the machine-readable surface.
|
|
37
|
+
const COMMAND_REGISTRY = [
|
|
38
|
+
{
|
|
39
|
+
name: 'help',
|
|
40
|
+
aliases: [],
|
|
41
|
+
summary: 'Show help for a command, or dump the machine-readable command registry with --json.',
|
|
42
|
+
example: 'wf-ui-control.mjs help send-input --json',
|
|
43
|
+
flags: [
|
|
44
|
+
{ flag: 'json', description: 'print the full registry (or one command entry) as JSON' },
|
|
45
|
+
{ flag: 'cmd', value: '<command>', description: 'command to show help for (or pass it positionally)' },
|
|
46
|
+
{ flag: 'command', value: '<command>', description: 'command to show help for (or pass it positionally)' },
|
|
47
|
+
],
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
name: 'self',
|
|
51
|
+
aliases: [],
|
|
52
|
+
summary: 'Print the acting agent identity read from the Harness environment.',
|
|
53
|
+
example: 'wf-ui-control.mjs self',
|
|
54
|
+
flags: [],
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
name: 'snapshot',
|
|
58
|
+
aliases: [],
|
|
59
|
+
summary: 'Read the full workflow snapshot (graph + sessions) from the backend, or from local files when no backend URL is set.',
|
|
60
|
+
example: 'wf-ui-control.mjs snapshot --url <url>',
|
|
61
|
+
flags: [
|
|
62
|
+
{ flag: 'url', value: '<url>', description: 'wf-ui backend URL (or HARNESS_WF_UI_URL)' },
|
|
63
|
+
{ flag: 'token', value: '<token>', description: 'auth token' },
|
|
64
|
+
{ flag: 'read-token', value: '<token>', description: 'read-only auth token' },
|
|
65
|
+
{ flag: 'project', value: '<path>', description: 'project root (default: .)' },
|
|
66
|
+
],
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
name: 'describe',
|
|
70
|
+
aliases: [],
|
|
71
|
+
summary: 'Render a human-oriented summary of the workflow snapshot (self, counts, nodes, connected edges).',
|
|
72
|
+
example: 'wf-ui-control.mjs describe',
|
|
73
|
+
flags: [
|
|
74
|
+
{ flag: 'url', value: '<url>', description: 'wf-ui backend URL (or HARNESS_WF_UI_URL)' },
|
|
75
|
+
{ flag: 'token', value: '<token>', description: 'auth token' },
|
|
76
|
+
{ flag: 'read-token', value: '<token>', description: 'read-only auth token' },
|
|
77
|
+
{ flag: 'project', value: '<path>', description: 'project root (default: .)' },
|
|
78
|
+
],
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
name: 'create-agent',
|
|
82
|
+
aliases: [],
|
|
83
|
+
summary: 'Create a workflow agent node (PTY session) through the wf-ui backend.',
|
|
84
|
+
example: 'wf-ui-control.mjs create-agent --role implementer --initial-prompt "Implement X"',
|
|
85
|
+
flags: [
|
|
86
|
+
{ flag: 'agent-kind', value: '<main|subagent>', description: 'agent kind (default: subagent)' },
|
|
87
|
+
{ flag: 'runtime', value: '<runtime>', description: 'peer runtime (default: claude)' },
|
|
88
|
+
{ flag: 'role', value: '<roleTitle>', description: 'agent role title' },
|
|
89
|
+
{ flag: 'objective', value: '<text>', description: 'agent objective' },
|
|
90
|
+
{ flag: 'initial-prompt', value: '<text>', description: 'Start the agent with this task as its first prompt (auto-submitted by the runtime TUI, no keystroke injection needed)' },
|
|
91
|
+
{ flag: 'mode', value: '<workflowMode>', description: 'workflow mode (default: wf)' },
|
|
92
|
+
{ flag: 'subagent-mode', value: '<built-in-subagents|wf-node-subagents>', description: 'subagent mode (default: built-in-subagents; legacy alias: wf-subagents)' },
|
|
93
|
+
{ flag: 'model', value: '<model>', description: 'model id' },
|
|
94
|
+
{ flag: 'dispatch-id', value: '<dispatchId>', description: 'opt into canonical dispatch creation (required with all dispatch fields)' },
|
|
95
|
+
{ flag: 'task', value: '<taskId>', description: 'dispatch task id (required when --dispatch-id is supplied)' },
|
|
96
|
+
{ flag: 'effort', value: '<low|medium|high|xhigh>', description: 'dispatch model effort (required when --dispatch-id is supplied)' },
|
|
97
|
+
{ flag: 'transport', value: '<pty|chat>', description: 'dispatch transport (required when --dispatch-id is supplied)' },
|
|
98
|
+
{ flag: 'context-pack-path', value: '<path>', description: 'dispatch context pack path, relative to the project root' },
|
|
99
|
+
{ flag: 'context-refs', value: '<json-array>', description: 'dispatch node references as a JSON array' },
|
|
100
|
+
{ flag: 'context-ref', value: '<nodeId>', description: 'dispatch node reference shorthand (one nodeId)' },
|
|
101
|
+
{ flag: 'request-id', value: '<requestId>', description: 'dispatch correlation request id' },
|
|
102
|
+
{ flag: 'reply-to', value: '<replyTo>', description: 'dispatch reply correlation id' },
|
|
103
|
+
{ flag: 'provider', value: '<provider>', description: 'model provider' },
|
|
104
|
+
{ flag: 'cwd', value: '<path>', description: 'working directory (default: project root)' },
|
|
105
|
+
{ flag: 'parent', value: '<sessionId>', description: 'parent agent session id' },
|
|
106
|
+
{ flag: 'parent-node', value: '<nodeId>', description: 'parent workflow node id' },
|
|
107
|
+
{ flag: 'defer-pty-spawn', value: '<true|false>', description: 'defer PTY spawn' },
|
|
108
|
+
{ flag: 'url', value: '<url>', description: 'wf-ui backend URL (or HARNESS_WF_UI_URL)' },
|
|
109
|
+
{ flag: 'token', value: '<token>', description: 'auth token' },
|
|
110
|
+
{ flag: 'actor-kind', value: '<kind>', description: 'actor agent kind (create-agent requires main)' },
|
|
111
|
+
{ flag: 'project', value: '<path>', description: 'project root (default: .)' },
|
|
112
|
+
],
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
name: 'ensure-backend',
|
|
116
|
+
aliases: [],
|
|
117
|
+
summary: 'Start or reuse the project wf-ui backend without opening a browser.',
|
|
118
|
+
example: 'wf-ui-control.mjs ensure-backend --project . --no-open --json',
|
|
119
|
+
flags: [
|
|
120
|
+
{ flag: 'project', value: '<path>', description: 'project root (default: .)' },
|
|
121
|
+
{ flag: 'host', value: '<127.0.0.1>', description: 'loopback host (default: 127.0.0.1)' },
|
|
122
|
+
{ flag: 'port', value: '<0-65535>', description: 'backend port (default: 0)' },
|
|
123
|
+
{ flag: 'no-open', description: 'never open a browser' },
|
|
124
|
+
{ flag: 'json', description: 'print one machine-readable result' },
|
|
125
|
+
],
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
name: 'dispatch-progress',
|
|
129
|
+
aliases: [],
|
|
130
|
+
summary: 'Report explicit progress for a dispatched runtime by dispatchId.',
|
|
131
|
+
example: 'wf-ui-control.mjs dispatch-progress --dispatch-id <id> --text "Writing files"',
|
|
132
|
+
flags: [
|
|
133
|
+
{ flag: 'dispatch-id', value: '<id>', description: 'dispatch identifier (or HARNESS_DISPATCH_ID)' },
|
|
134
|
+
{ flag: 'text', value: '<message>', description: 'non-empty progress message' },
|
|
135
|
+
{ flag: 'task', value: '<taskId>', description: 'task id (or HARNESS_PEER_TASK_ID)' },
|
|
136
|
+
{ flag: 'session', value: '<sessionId>', description: 'target session id (or HARNESS_PEER_SESSION_ID)' },
|
|
137
|
+
{ flag: 'request-id', value: '<id>', description: 'typed envelope requestId' },
|
|
138
|
+
{ flag: 'reply-to', value: '<id>', description: 'typed envelope replyTo' },
|
|
139
|
+
{ flag: 'url', value: '<url>', description: 'wf-ui backend URL (or HARNESS_WF_UI_URL)' },
|
|
140
|
+
{ flag: 'token', value: '<token>', description: 'control-plane token' },
|
|
141
|
+
],
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
name: 'dispatch-result',
|
|
145
|
+
aliases: [],
|
|
146
|
+
summary: 'Report an explicit terminal result for a dispatched runtime by dispatchId.',
|
|
147
|
+
example: 'wf-ui-control.mjs dispatch-result --dispatch-id <id> --status succeeded --result "{\\"files\\":[\\"out.txt\\"]}"',
|
|
148
|
+
flags: [
|
|
149
|
+
{ flag: 'dispatch-id', value: '<id>', description: 'dispatch identifier (or HARNESS_DISPATCH_ID)' },
|
|
150
|
+
{ flag: 'status', value: '<succeeded|failed|cancelled>', description: 'required terminal status' },
|
|
151
|
+
{ flag: 'result', value: '<json|string>', description: 'structured result payload or text' },
|
|
152
|
+
{ flag: 'payload', value: '<json|string>', description: 'alias for --result' },
|
|
153
|
+
{ flag: 'output', value: '<text>', description: 'result text when --result is omitted' },
|
|
154
|
+
{ flag: 'task', value: '<taskId>', description: 'task id (or HARNESS_PEER_TASK_ID)' },
|
|
155
|
+
{ flag: 'session', value: '<sessionId>', description: 'target session id (or HARNESS_PEER_SESSION_ID)' },
|
|
156
|
+
{ flag: 'request-id', value: '<id>', description: 'typed envelope requestId' },
|
|
157
|
+
{ flag: 'reply-to', value: '<id>', description: 'typed envelope replyTo' },
|
|
158
|
+
{ flag: 'url', value: '<url>', description: 'wf-ui backend URL (or HARNESS_WF_UI_URL)' },
|
|
159
|
+
{ flag: 'token', value: '<token>', description: 'control-plane token' },
|
|
160
|
+
],
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
name: 'find-agent',
|
|
164
|
+
aliases: [],
|
|
165
|
+
summary: 'Find an existing agent node by role, runtime, provider, capability, or title.',
|
|
166
|
+
example: 'wf-ui-control.mjs find-agent --role implementer --connect true',
|
|
167
|
+
flags: [
|
|
168
|
+
{ flag: 'role', value: '<role>', description: 'role title to match' },
|
|
169
|
+
{ flag: 'runtime', value: '<runtime>', description: 'peer runtime to match' },
|
|
170
|
+
{ flag: 'provider', value: '<provider>', description: 'model provider to match' },
|
|
171
|
+
{ flag: 'capability', value: '<capability>', description: 'capability to match' },
|
|
172
|
+
{ flag: 'title', value: '<title>', description: 'node title to match' },
|
|
173
|
+
{ flag: 'from', value: '<nodeId>', description: 'query from this node (default: HARNESS_WORKFLOW_NODE_ID)' },
|
|
174
|
+
{ flag: 'connect', description: 'auto-connect the found agent to the actor (alias: --auto-connect)' },
|
|
175
|
+
{ flag: 'auto-connect', description: 'auto-connect the found agent to the actor' },
|
|
176
|
+
{ flag: 'url', value: '<url>', description: 'wf-ui backend URL (or HARNESS_WF_UI_URL)' },
|
|
177
|
+
{ flag: 'token', value: '<token>', description: 'auth token' },
|
|
178
|
+
{ flag: 'read-token', value: '<token>', description: 'read-only auth token' },
|
|
179
|
+
{ flag: 'project', value: '<path>', description: 'project root (default: .)' },
|
|
180
|
+
],
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
name: 'agent-role-profile',
|
|
184
|
+
aliases: [],
|
|
185
|
+
summary: 'Read the role profile (json + markdown) of an agent node.',
|
|
186
|
+
example: 'wf-ui-control.mjs agent-role-profile --node <agentNodeId>',
|
|
187
|
+
flags: [
|
|
188
|
+
{ flag: 'node', value: '<agentNodeId>', description: 'agent node id (default: HARNESS_WORKFLOW_NODE_ID)' },
|
|
189
|
+
{ flag: 'url', value: '<url>', description: 'wf-ui backend URL (or HARNESS_WF_UI_URL)' },
|
|
190
|
+
{ flag: 'token', value: '<token>', description: 'auth token' },
|
|
191
|
+
{ flag: 'read-token', value: '<token>', description: 'read-only auth token' },
|
|
192
|
+
{ flag: 'project', value: '<path>', description: 'project root (default: .)' },
|
|
193
|
+
],
|
|
194
|
+
},
|
|
195
|
+
{
|
|
196
|
+
name: 'send-input',
|
|
197
|
+
aliases: [],
|
|
198
|
+
summary: 'Write terminal input to a managed session PTY (appends submit enter unless --raw or the text ends with a newline).',
|
|
199
|
+
example: 'wf-ui-control.mjs send-input --session <sessionId> --text "/model"',
|
|
200
|
+
flags: [
|
|
201
|
+
{ flag: 'session', value: '<sessionId>', description: 'target session id' },
|
|
202
|
+
{ flag: 'text', value: '<text>', description: 'input text (appends \\r unless --raw or already newline-terminated)' },
|
|
203
|
+
{ flag: 'raw', description: 'send the text exactly as given, without appending enter' },
|
|
204
|
+
{ flag: 'from', value: '<sessionId>', description: 'actor session id (alias: --from-session; default: HARNESS_PEER_SESSION_ID)' },
|
|
205
|
+
{ flag: 'from-session', value: '<sessionId>', description: 'actor session id' },
|
|
206
|
+
{ flag: 'from-node', value: '<nodeId>', description: 'actor workflow node id (default: HARNESS_WORKFLOW_NODE_ID)' },
|
|
207
|
+
{ flag: 'url', value: '<url>', description: 'wf-ui backend URL (or HARNESS_WF_UI_URL)' },
|
|
208
|
+
{ flag: 'token', value: '<token>', description: 'auth token' },
|
|
209
|
+
{ flag: 'actor-kind', value: '<kind>', description: 'actor agent kind (send-input requires main)' },
|
|
210
|
+
],
|
|
211
|
+
},
|
|
212
|
+
{
|
|
213
|
+
name: 'send-key',
|
|
214
|
+
aliases: ['key'],
|
|
215
|
+
summary: 'Send a single named keystroke as raw bytes to a managed session PTY — e.g. "send-key down" to navigate TUI pickers such as the codex /model picker after typing /model.',
|
|
216
|
+
example: 'wf-ui-control.mjs send-key down --session <sessionId> --url <url>',
|
|
217
|
+
flags: [
|
|
218
|
+
{ flag: 'key', value: '<up|down|left|right|enter|esc|tab|backspace>', description: 'key to send (or pass positionally: send-key up)' },
|
|
219
|
+
{ flag: 'session', value: '<sessionId>', description: 'target session id' },
|
|
220
|
+
{ flag: 'node', value: '<graphNodeIdOrSessionId>', description: 'resolve the target session from the workflow graph map (used when --session is omitted)' },
|
|
221
|
+
{ flag: 'from', value: '<sessionId>', description: 'actor session id (alias: --from-session; default: HARNESS_PEER_SESSION_ID)' },
|
|
222
|
+
{ flag: 'from-session', value: '<sessionId>', description: 'actor session id' },
|
|
223
|
+
{ flag: 'from-node', value: '<nodeId>', description: 'actor workflow node id (default: HARNESS_WORKFLOW_NODE_ID)' },
|
|
224
|
+
{ flag: 'url', value: '<url>', description: 'wf-ui backend URL (or HARNESS_WF_UI_URL)' },
|
|
225
|
+
{ flag: 'token', value: '<token>', description: 'auth token' },
|
|
226
|
+
{ flag: 'actor-kind', value: '<kind>', description: 'actor agent kind (send-key requires main)' },
|
|
227
|
+
],
|
|
228
|
+
},
|
|
229
|
+
{
|
|
230
|
+
name: 'delegate-agent',
|
|
231
|
+
aliases: [],
|
|
232
|
+
summary: 'Send terminal input to another agent node through the typed agent.sendInput action.',
|
|
233
|
+
example: 'wf-ui-control.mjs delegate-agent --node <targetNodeId> --text "Report status"',
|
|
234
|
+
flags: [
|
|
235
|
+
{ flag: 'node', value: '<graphNodeIdOrSessionId>', description: 'target agent node' },
|
|
236
|
+
{ flag: 'text', value: '<text>', description: 'input text (appends \\r unless --raw or already newline-terminated; alias: --input)' },
|
|
237
|
+
{ flag: 'raw', description: 'send the text exactly as given, without submit enter' },
|
|
238
|
+
{ flag: 'actor', value: '<nodeId>', description: 'actor node id (alias: --actor-node)' },
|
|
239
|
+
{ flag: 'actor-node', value: '<nodeId>', description: 'actor node id' },
|
|
240
|
+
{ flag: 'actor-session', value: '<sessionId>', description: 'actor session id' },
|
|
241
|
+
{ flag: 'actor-kind', value: '<kind>', description: 'actor agent kind' },
|
|
242
|
+
{ flag: 'from', value: '<sessionId>', description: 'actor session id (alias: --from-session)' },
|
|
243
|
+
{ flag: 'from-session', value: '<sessionId>', description: 'actor session id' },
|
|
244
|
+
{ flag: 'url', value: '<url>', description: 'wf-ui backend URL (or HARNESS_WF_UI_URL)' },
|
|
245
|
+
{ flag: 'token', value: '<token>', description: 'auth token' },
|
|
246
|
+
{ flag: 'project', value: '<path>', description: 'project root (default: .)' },
|
|
247
|
+
],
|
|
248
|
+
},
|
|
249
|
+
{
|
|
250
|
+
name: 'send-agent-message',
|
|
251
|
+
aliases: ['message-agent'],
|
|
252
|
+
summary: 'Send a structured 1-to-1 message to another agent node through agent.sendMessage.',
|
|
253
|
+
example: 'wf-ui-control.mjs send-agent-message --node <senderNodeId> --to <targetNodeId> --text "Hello" --request-id req-1',
|
|
254
|
+
flags: [
|
|
255
|
+
{ flag: 'node', value: '<senderNodeId>', description: 'sender agent node id (default: HARNESS_WORKFLOW_NODE_ID)' },
|
|
256
|
+
{ flag: 'to', value: '<agentNodeIdOrSessionId>', description: 'target agent' },
|
|
257
|
+
{ flag: 'text', value: '<text>', description: 'message text (aliases: --message, --input, --data)' },
|
|
258
|
+
{ flag: 'topic', value: '<topic>', description: 'message topic' },
|
|
259
|
+
{ flag: 'thread', value: '<threadId>', description: 'thread id (alias: --thread-id)' },
|
|
260
|
+
{ flag: 'thread-id', value: '<threadId>', description: 'thread id' },
|
|
261
|
+
{ flag: 'reply-to', value: '<messageId>', description: 'reply to a message id' },
|
|
262
|
+
{ flag: 'request-id', value: '<requestId>', description: 'correlation request id' },
|
|
263
|
+
{ flag: 'raw', description: 'mark the message as raw (no envelope processing)' },
|
|
264
|
+
{ flag: 'actor', value: '<nodeId>', description: 'actor node id (alias: --actor-node)' },
|
|
265
|
+
{ flag: 'actor-node', value: '<nodeId>', description: 'actor node id' },
|
|
266
|
+
{ flag: 'actor-session', value: '<sessionId>', description: 'actor session id' },
|
|
267
|
+
{ flag: 'actor-kind', value: '<kind>', description: 'actor agent kind' },
|
|
268
|
+
{ flag: 'from', value: '<sessionId>', description: 'actor session id (alias: --from-session)' },
|
|
269
|
+
{ flag: 'from-session', value: '<sessionId>', description: 'actor session id' },
|
|
270
|
+
{ flag: 'url', value: '<url>', description: 'wf-ui backend URL (or HARNESS_WF_UI_URL)' },
|
|
271
|
+
{ flag: 'token', value: '<token>', description: 'auth token' },
|
|
272
|
+
{ flag: 'project', value: '<path>', description: 'project root (default: .)' },
|
|
273
|
+
],
|
|
274
|
+
},
|
|
275
|
+
{
|
|
276
|
+
name: 'broadcast-agent-message',
|
|
277
|
+
aliases: ['broadcast-agent'],
|
|
278
|
+
summary: 'Send one structured message to many agent nodes through agent.broadcastMessage.',
|
|
279
|
+
example: 'wf-ui-control.mjs broadcast-agent-message --node <senderNodeId> --to a,b,c --text "All hands"',
|
|
280
|
+
flags: [
|
|
281
|
+
{ flag: 'node', value: '<senderNodeId>', description: 'sender agent node id (default: HARNESS_WORKFLOW_NODE_ID)' },
|
|
282
|
+
{ flag: 'to', value: '<agentNodeIdOrSessionId,...>', description: 'targets (comma/newline/semicolon separated)' },
|
|
283
|
+
{ flag: 'text', value: '<text>', description: 'message text (aliases: --message, --input, --data)' },
|
|
284
|
+
{ flag: 'topic', value: '<topic>', description: 'message topic' },
|
|
285
|
+
{ flag: 'thread', value: '<threadId>', description: 'thread id (alias: --thread-id)' },
|
|
286
|
+
{ flag: 'thread-id', value: '<threadId>', description: 'thread id' },
|
|
287
|
+
{ flag: 'request-id', value: '<requestId>', description: 'correlation request id' },
|
|
288
|
+
{ flag: 'raw', description: 'mark the message as raw (no envelope processing)' },
|
|
289
|
+
{ flag: 'actor', value: '<nodeId>', description: 'actor node id (alias: --actor-node)' },
|
|
290
|
+
{ flag: 'actor-node', value: '<nodeId>', description: 'actor node id' },
|
|
291
|
+
{ flag: 'actor-session', value: '<sessionId>', description: 'actor session id' },
|
|
292
|
+
{ flag: 'actor-kind', value: '<kind>', description: 'actor agent kind' },
|
|
293
|
+
{ flag: 'from', value: '<sessionId>', description: 'actor session id (alias: --from-session)' },
|
|
294
|
+
{ flag: 'from-session', value: '<sessionId>', description: 'actor session id' },
|
|
295
|
+
{ flag: 'url', value: '<url>', description: 'wf-ui backend URL (or HARNESS_WF_UI_URL)' },
|
|
296
|
+
{ flag: 'token', value: '<token>', description: 'auth token' },
|
|
297
|
+
{ flag: 'project', value: '<path>', description: 'project root (default: .)' },
|
|
298
|
+
],
|
|
299
|
+
},
|
|
300
|
+
{
|
|
301
|
+
name: 'read-agent-messages',
|
|
302
|
+
aliases: ['agent-messages'],
|
|
303
|
+
summary: 'Read the recorded conversation between two agents through agent.readMessages.',
|
|
304
|
+
example: 'wf-ui-control.mjs read-agent-messages --node <senderNodeId> --peer <peerNodeId>',
|
|
305
|
+
flags: [
|
|
306
|
+
{ flag: 'node', value: '<senderNodeId>', description: 'sender agent node id (default: HARNESS_WORKFLOW_NODE_ID)' },
|
|
307
|
+
{ flag: 'peer', value: '<agentNodeIdOrSessionId>', description: 'peer agent to read the conversation with' },
|
|
308
|
+
{ flag: 'tail', value: '<n>', description: 'last n entries' },
|
|
309
|
+
{ flag: 'limit', value: '<n>', description: 'max entries to return' },
|
|
310
|
+
{ flag: 'actor', value: '<nodeId>', description: 'actor node id (alias: --actor-node)' },
|
|
311
|
+
{ flag: 'actor-node', value: '<nodeId>', description: 'actor node id' },
|
|
312
|
+
{ flag: 'actor-session', value: '<sessionId>', description: 'actor session id' },
|
|
313
|
+
{ flag: 'actor-kind', value: '<kind>', description: 'actor agent kind' },
|
|
314
|
+
{ flag: 'url', value: '<url>', description: 'wf-ui backend URL (or HARNESS_WF_UI_URL)' },
|
|
315
|
+
{ flag: 'token', value: '<token>', description: 'auth token' },
|
|
316
|
+
{ flag: 'project', value: '<path>', description: 'project root (default: .)' },
|
|
317
|
+
],
|
|
318
|
+
},
|
|
319
|
+
{
|
|
320
|
+
name: 'read-agent',
|
|
321
|
+
aliases: [],
|
|
322
|
+
summary: 'Read agent output, transcript, or context through agent.readOutput/readTranscript/readContext.',
|
|
323
|
+
example: 'wf-ui-control.mjs read-agent --node <agentNodeId> --action transcript --tail 50',
|
|
324
|
+
flags: [
|
|
325
|
+
{ flag: 'node', value: '<graphNodeIdOrSessionId>', description: 'target agent node' },
|
|
326
|
+
{ flag: 'action', value: '<output|transcript|context>', description: 'what to read (default: output)' },
|
|
327
|
+
{ flag: 'tail', value: '<n>', description: 'last n entries' },
|
|
328
|
+
{ flag: 'from-seq', value: '<n>', description: 'start sequence number' },
|
|
329
|
+
{ flag: 'to-seq', value: '<n>', description: 'end sequence number' },
|
|
330
|
+
{ flag: 'payload', value: '<json>', description: 'extra payload as a JSON object' },
|
|
331
|
+
{ flag: 'actor', value: '<nodeId>', description: 'actor node id (alias: --actor-node)' },
|
|
332
|
+
{ flag: 'actor-node', value: '<nodeId>', description: 'actor node id' },
|
|
333
|
+
{ flag: 'actor-session', value: '<sessionId>', description: 'actor session id' },
|
|
334
|
+
{ flag: 'actor-kind', value: '<kind>', description: 'actor agent kind' },
|
|
335
|
+
{ flag: 'url', value: '<url>', description: 'wf-ui backend URL (or HARNESS_WF_UI_URL)' },
|
|
336
|
+
{ flag: 'token', value: '<token>', description: 'auth token' },
|
|
337
|
+
{ flag: 'project', value: '<path>', description: 'project root (default: .)' },
|
|
338
|
+
],
|
|
339
|
+
},
|
|
340
|
+
{
|
|
341
|
+
name: 'bridge-messages',
|
|
342
|
+
aliases: [],
|
|
343
|
+
summary: 'List recorded bridge messages between two sessions.',
|
|
344
|
+
example: 'wf-ui-control.mjs bridge-messages --from <sessionId> --to <sessionId>',
|
|
345
|
+
flags: [
|
|
346
|
+
{ flag: 'from', value: '<sessionId>', description: 'sender session id (alias: --from-session)' },
|
|
347
|
+
{ flag: 'from-session', value: '<sessionId>', description: 'sender session id' },
|
|
348
|
+
{ flag: 'to', value: '<sessionId>', description: 'recipient session id (alias: --to-session, --session)' },
|
|
349
|
+
{ flag: 'to-session', value: '<sessionId>', description: 'recipient session id' },
|
|
350
|
+
{ flag: 'session', value: '<sessionId>', description: 'recipient session id' },
|
|
351
|
+
{ flag: 'limit', value: '<n>', description: 'max entries (alias: --tail; default: 200)' },
|
|
352
|
+
{ flag: 'tail', value: '<n>', description: 'max entries' },
|
|
353
|
+
{ flag: 'url', value: '<url>', description: 'wf-ui backend URL (or HARNESS_WF_UI_URL)' },
|
|
354
|
+
{ flag: 'token', value: '<token>', description: 'auth token' },
|
|
355
|
+
{ flag: 'project', value: '<path>', description: 'project root (default: .)' },
|
|
356
|
+
],
|
|
357
|
+
},
|
|
358
|
+
{
|
|
359
|
+
name: 'node-map',
|
|
360
|
+
aliases: ['workflow-node-map'],
|
|
361
|
+
summary: 'Run a typed workflow graph action (readGraph, createNode, connectNodes, disconnectNodes, updateEdge, moveNode, deleteNode, deleteNodes, attachDock, detachDock, setDockSide, layout, undo, redo) for the acting agent node.',
|
|
362
|
+
example: 'wf-ui-control.mjs node-map --action readGraph --actor <graphNodeId>',
|
|
363
|
+
flags: [
|
|
364
|
+
{ flag: 'action', value: '<readGraph|createNode|connectNodes|disconnectNodes|updateEdge|moveNode|deleteNode|deleteNodes|attachDock|detachDock|setDockSide|layout|undo|redo>', description: 'graph action to run (aliases: --command, --do)' },
|
|
365
|
+
{ flag: 'scope', value: '<graph>', description: 'undo/redo: scope selector (default: graph)' },
|
|
366
|
+
{ flag: 'actor', value: '<graphNodeIdOrSessionId>', description: 'actor node id (aliases: --actor-node, --actorNodeId)' },
|
|
367
|
+
{ flag: 'payload', value: '<json>', description: 'extra payload as a JSON object (aliases: --body, --json)' },
|
|
368
|
+
{ flag: 'type', value: '<nodeType>', description: 'createNode: node type' },
|
|
369
|
+
{ flag: 'title', value: '<title>', description: 'createNode: node title' },
|
|
370
|
+
{ flag: 'node', value: '<nodeId>', description: 'createNode/moveNode/deleteNode: target node id' },
|
|
371
|
+
{ flag: 'x', value: '<number>', description: 'createNode/moveNode: x position' },
|
|
372
|
+
{ flag: 'y', value: '<number>', description: 'createNode/moveNode: y position' },
|
|
373
|
+
{ flag: 'from', value: '<nodeOrSession>', description: 'connectNodes: source (default: actor)' },
|
|
374
|
+
{ flag: 'to', value: '<nodeOrSession>', description: 'connectNodes: target (aliases: --target, --session, --target-node)' },
|
|
375
|
+
{ flag: 'relation', value: '<relation>', description: 'connectNodes/updateEdge: edge relation (connectNodes default: delegation)' },
|
|
376
|
+
{ flag: 'source-handle', value: '<handle>', description: 'connectNodes/updateEdge: source handle' },
|
|
377
|
+
{ flag: 'target-handle', value: '<handle>', description: 'connectNodes/updateEdge: target handle' },
|
|
378
|
+
{ flag: 'direction', value: '<direction>', description: 'connectNodes/updateEdge: edge direction' },
|
|
379
|
+
{ flag: 'edge', value: '<edgeId>', description: 'disconnectNodes: edge id to remove; updateEdge: edge id to update (or pass --from/--to as the edge pair)' },
|
|
380
|
+
{ flag: 'anchor', value: '<nodeId>', description: 'attachDock/detachDock/setDockSide: anchor node id (aliases: --anchor-id, --anchorId)' },
|
|
381
|
+
{ flag: 'dragged', value: '<nodeId>', description: 'attachDock/detachDock/setDockSide: dragged node id (aliases: --dragged-id, --draggedId)' },
|
|
382
|
+
{ flag: 'side', value: '<left|right|top|bottom>', description: 'attachDock/setDockSide: dock side (default: right)' },
|
|
383
|
+
{ flag: 'dock-id', value: '<dockId>', description: 'detachDock: dock link id to remove (aliases: --dock, --dockId)' },
|
|
384
|
+
{ flag: 'nodes', value: '<nodeId,...>', description: 'deleteNodes: target node ids' },
|
|
385
|
+
{ flag: 'all', description: 'deleteNodes: delete all non-actor nodes' },
|
|
386
|
+
{ flag: 'force', description: 'deleteNodes/deleteNode: force deletion' },
|
|
387
|
+
{ flag: 'allow-live-agent-delete', description: 'deleteNodes/deleteNode: allow deleting live agent nodes' },
|
|
388
|
+
{ flag: 'layout-mode', value: '<grid|tree>', description: 'layout: layout mode (default: tree; aliases: --mode)' },
|
|
389
|
+
{ flag: 'mode', value: '<grid|tree>', description: 'layout: alias for --layout-mode' },
|
|
390
|
+
{ flag: 'origin-x', value: '<number>', description: 'layout: origin x (default: 260)' },
|
|
391
|
+
{ flag: 'origin-y', value: '<number>', description: 'layout: origin y (default: 220)' },
|
|
392
|
+
{ flag: 'gap-x', value: '<number>', description: 'layout: horizontal gap (default: 420)' },
|
|
393
|
+
{ flag: 'gap-y', value: '<number>', description: 'layout: vertical gap (default: 140)' },
|
|
394
|
+
{ flag: 'url', value: '<url>', description: 'wf-ui backend URL (or HARNESS_WF_UI_URL)' },
|
|
395
|
+
{ flag: 'token', value: '<token>', description: 'auth token' },
|
|
396
|
+
{ flag: 'project', value: '<path>', description: 'project root (default: .)' },
|
|
397
|
+
],
|
|
398
|
+
},
|
|
399
|
+
{
|
|
400
|
+
name: 'workflow-ontology',
|
|
401
|
+
aliases: [],
|
|
402
|
+
summary: 'Read the workflow ontology (node types, actions, affordances) from the backend.',
|
|
403
|
+
example: 'wf-ui-control.mjs workflow-ontology --url <url>',
|
|
404
|
+
flags: [
|
|
405
|
+
{ flag: 'url', value: '<url>', description: 'wf-ui backend URL (or HARNESS_WF_UI_URL)' },
|
|
406
|
+
{ flag: 'token', value: '<token>', description: 'auth token' },
|
|
407
|
+
{ flag: 'read-token', value: '<token>', description: 'read-only auth token' },
|
|
408
|
+
{ flag: 'project', value: '<path>', description: 'project root (default: .)' },
|
|
409
|
+
],
|
|
410
|
+
},
|
|
411
|
+
{
|
|
412
|
+
name: 'workflow-context',
|
|
413
|
+
aliases: [],
|
|
414
|
+
summary: 'Read the workflow context of a node: identity, connected peers, node manuals, available actions.',
|
|
415
|
+
example: 'wf-ui-control.mjs workflow-context --node <graphNodeId>',
|
|
416
|
+
flags: [
|
|
417
|
+
{ flag: 'node', value: '<graphNodeIdOrSessionId>', description: 'target node (default: HARNESS_WORKFLOW_NODE_ID)' },
|
|
418
|
+
{ flag: 'url', value: '<url>', description: 'wf-ui backend URL (or HARNESS_WF_UI_URL)' },
|
|
419
|
+
{ flag: 'token', value: '<token>', description: 'auth token' },
|
|
420
|
+
{ flag: 'read-token', value: '<token>', description: 'read-only auth token' },
|
|
421
|
+
{ flag: 'project', value: '<path>', description: 'project root (default: .)' },
|
|
422
|
+
],
|
|
423
|
+
},
|
|
424
|
+
{
|
|
425
|
+
name: 'read-node',
|
|
426
|
+
aliases: ['readnode', 'read-snapshot'],
|
|
427
|
+
summary: 'Read a single workflow node by id.',
|
|
428
|
+
example: 'wf-ui-control.mjs read-node --node <graphNodeId>',
|
|
429
|
+
flags: [
|
|
430
|
+
{ flag: 'node', value: '<graphNodeIdOrSessionId>', description: 'target node (default: HARNESS_WORKFLOW_NODE_ID)' },
|
|
431
|
+
{ flag: 'url', value: '<url>', description: 'wf-ui backend URL (or HARNESS_WF_UI_URL)' },
|
|
432
|
+
{ flag: 'token', value: '<token>', description: 'auth token' },
|
|
433
|
+
{ flag: 'read-token', value: '<token>', description: 'read-only auth token' },
|
|
434
|
+
{ flag: 'project', value: '<path>', description: 'project root (default: .)' },
|
|
435
|
+
],
|
|
436
|
+
},
|
|
437
|
+
{
|
|
438
|
+
name: 'workflow-node-action',
|
|
439
|
+
aliases: ['workflow-node-actions'],
|
|
440
|
+
summary: 'Run a typed node action (markdown.*, goal.*, agent.*, timer.*, ...) on any workflow node.',
|
|
441
|
+
example: 'wf-ui-control.mjs workflow-node-action --node <nodeId> --action markdown.read',
|
|
442
|
+
flags: [
|
|
443
|
+
{ flag: 'node', value: '<graphNodeIdOrSessionId>', description: 'target node' },
|
|
444
|
+
{ flag: 'action', value: '<type.action>', description: 'action to run (aliases: --command, --do)' },
|
|
445
|
+
{ flag: 'payload', value: '<json>', description: 'action payload as a JSON object (aliases: --body, --json)' },
|
|
446
|
+
{ flag: 'actor', value: '<nodeId>', description: 'actor node id (alias: --actor-node)' },
|
|
447
|
+
{ flag: 'actor-node', value: '<nodeId>', description: 'actor node id' },
|
|
448
|
+
{ flag: 'actor-session', value: '<sessionId>', description: 'actor session id' },
|
|
449
|
+
{ flag: 'actor-kind', value: '<kind>', description: 'actor agent kind' },
|
|
450
|
+
{ flag: 'resume', value: '<true|false>', description: 'agent.start/agent.restart: resume the previous agent session (default: restart=true; start=true when a previous session exists on the node)' },
|
|
451
|
+
{ flag: 'url', value: '<url>', description: 'wf-ui backend URL (or HARNESS_WF_UI_URL)' },
|
|
452
|
+
{ flag: 'token', value: '<token>', description: 'auth token' },
|
|
453
|
+
{ flag: 'project', value: '<path>', description: 'project root (default: .)' },
|
|
454
|
+
],
|
|
455
|
+
},
|
|
456
|
+
{
|
|
457
|
+
name: 'delete-node',
|
|
458
|
+
aliases: [],
|
|
459
|
+
summary: 'Delete a single workflow node through agent.deleteNode.',
|
|
460
|
+
example: 'wf-ui-control.mjs delete-node --node <graphNodeIdOrSessionId> --actor <actorNodeId>',
|
|
461
|
+
flags: [
|
|
462
|
+
{ flag: 'node', value: '<graphNodeIdOrSessionId>', description: 'node to delete' },
|
|
463
|
+
{ flag: 'actor', value: '<graphNodeIdOrSessionId>', description: 'actor node id' },
|
|
464
|
+
{ flag: 'force', description: 'force deletion' },
|
|
465
|
+
{ flag: 'allow-live-agent-delete', description: 'allow deleting live agent nodes' },
|
|
466
|
+
{ flag: 'url', value: '<url>', description: 'wf-ui backend URL (or HARNESS_WF_UI_URL)' },
|
|
467
|
+
{ flag: 'token', value: '<token>', description: 'auth token' },
|
|
468
|
+
{ flag: 'project', value: '<path>', description: 'project root (default: .)' },
|
|
469
|
+
],
|
|
470
|
+
},
|
|
471
|
+
{
|
|
472
|
+
name: 'connect',
|
|
473
|
+
aliases: [],
|
|
474
|
+
summary: 'Connect two workflow nodes with a typed edge through agent.connectNodes.',
|
|
475
|
+
example: 'wf-ui-control.mjs connect --actor <actorNodeId> --from <nodeId> --to <nodeId>',
|
|
476
|
+
flags: [
|
|
477
|
+
{ flag: 'actor', value: '<graphNodeIdOrSessionId>', description: 'actor node id' },
|
|
478
|
+
{ flag: 'from', value: '<nodeOrSession>', description: 'source node (default: actor)' },
|
|
479
|
+
{ flag: 'to', value: '<nodeOrSession>', description: 'target node (aliases: --target, --session, --target-node)' },
|
|
480
|
+
{ flag: 'relation', value: '<relation>', description: 'edge relation (default: delegation)' },
|
|
481
|
+
{ flag: 'source-handle', value: '<handle>', description: 'source handle' },
|
|
482
|
+
{ flag: 'target-handle', value: '<handle>', description: 'target handle' },
|
|
483
|
+
{ flag: 'direction', value: '<direction>', description: 'edge direction' },
|
|
484
|
+
{ flag: 'payload', value: '<json>', description: 'extra payload as a JSON object' },
|
|
485
|
+
{ flag: 'url', value: '<url>', description: 'wf-ui backend URL (or HARNESS_WF_UI_URL)' },
|
|
486
|
+
{ flag: 'token', value: '<token>', description: 'auth token' },
|
|
487
|
+
{ flag: 'project', value: '<path>', description: 'project root (default: .)' },
|
|
488
|
+
],
|
|
489
|
+
},
|
|
490
|
+
{
|
|
491
|
+
name: 'tail',
|
|
492
|
+
aliases: [],
|
|
493
|
+
summary: 'Tail the terminal transcript of a local session from terminal.jsonl.',
|
|
494
|
+
example: 'wf-ui-control.mjs tail --session <sessionId> --lines 100',
|
|
495
|
+
flags: [
|
|
496
|
+
{ flag: 'session', value: '<sessionId>', description: 'session id (default: HARNESS_PEER_SESSION_ID)' },
|
|
497
|
+
{ flag: 'lines', value: '<n>', description: 'lines to tail (default: 80, alias: --tail)' },
|
|
498
|
+
{ flag: 'tail', value: '<n>', description: 'lines to tail' },
|
|
499
|
+
{ flag: 'project', value: '<path>', description: 'project root (default: .)' },
|
|
500
|
+
],
|
|
501
|
+
},
|
|
502
|
+
{
|
|
503
|
+
name: 'manuals',
|
|
504
|
+
aliases: ['manual'],
|
|
505
|
+
summary: 'Print the workflow node manual for a node type (prose sections plus generated command lines from the shared action registry), or list available manual types with --list.',
|
|
506
|
+
example: 'wf-ui-control.mjs manuals timer',
|
|
507
|
+
flags: [
|
|
508
|
+
{ flag: 'list', description: 'list all available manual types (agent, markdown, excalidraw, file, timer, goal, skill-group, mcp-connector, github-trigger, diagram)' },
|
|
509
|
+
{ flag: 'type', value: '<nodeType>', description: 'node type to read the manual for (or pass it positionally: manuals timer)' },
|
|
510
|
+
{ flag: 'project', value: '<path>', description: 'project root (default: .)' },
|
|
511
|
+
],
|
|
512
|
+
},
|
|
513
|
+
{
|
|
514
|
+
name: 'browser-runs',
|
|
515
|
+
aliases: [],
|
|
516
|
+
summary: 'List wf-browser runs.',
|
|
517
|
+
example: 'wf-ui-control.mjs browser-runs --url <url>',
|
|
518
|
+
flags: [
|
|
519
|
+
{ flag: 'limit', value: '<n>', description: 'max runs (default: 20)' },
|
|
520
|
+
{ flag: 'url', value: '<url>', description: 'wf-ui backend URL (or HARNESS_WF_UI_URL)' },
|
|
521
|
+
{ flag: 'token', value: '<token>', description: 'auth token' },
|
|
522
|
+
],
|
|
523
|
+
},
|
|
524
|
+
{
|
|
525
|
+
name: 'browser-run',
|
|
526
|
+
aliases: [],
|
|
527
|
+
summary: 'Create a wf-browser run.',
|
|
528
|
+
example: 'wf-ui-control.mjs browser-run --objective "Verify the UI"',
|
|
529
|
+
flags: [
|
|
530
|
+
{ flag: 'run', value: '<runId>', description: 'run id (alias: --runId)' },
|
|
531
|
+
{ flag: 'mode', value: '<mode>', description: 'run mode (default: mixed)' },
|
|
532
|
+
{ flag: 'url', value: '<url>', description: 'wf-ui backend URL (or HARNESS_WF_UI_URL)' },
|
|
533
|
+
{ flag: 'token', value: '<token>', description: 'auth token' },
|
|
534
|
+
],
|
|
535
|
+
},
|
|
536
|
+
{
|
|
537
|
+
name: 'browser-window',
|
|
538
|
+
aliases: [],
|
|
539
|
+
summary: 'Create a wf-browser window under a run.',
|
|
540
|
+
example: 'wf-ui-control.mjs browser-window --run <runId>',
|
|
541
|
+
flags: [
|
|
542
|
+
{ flag: 'run', value: '<runId>', description: 'run id (alias: --runId)' },
|
|
543
|
+
{ flag: 'window', value: '<windowId>', description: 'window id (alias: --windowId)' },
|
|
544
|
+
{ flag: 'url', value: '<url>', description: 'wf-ui backend URL (or HARNESS_WF_UI_URL)' },
|
|
545
|
+
{ flag: 'token', value: '<token>', description: 'auth token' },
|
|
546
|
+
],
|
|
547
|
+
},
|
|
548
|
+
{
|
|
549
|
+
name: 'browser-windows',
|
|
550
|
+
aliases: [],
|
|
551
|
+
summary: 'List windows of a wf-browser run.',
|
|
552
|
+
example: 'wf-ui-control.mjs browser-windows --run <runId>',
|
|
553
|
+
flags: [
|
|
554
|
+
{ flag: 'run', value: '<runId>', description: 'run id (alias: --runId)' },
|
|
555
|
+
{ flag: 'url', value: '<url>', description: 'wf-ui backend URL (or HARNESS_WF_UI_URL)' },
|
|
556
|
+
{ flag: 'token', value: '<token>', description: 'auth token' },
|
|
557
|
+
],
|
|
558
|
+
},
|
|
559
|
+
{
|
|
560
|
+
name: 'browser-lease',
|
|
561
|
+
aliases: [],
|
|
562
|
+
summary: 'Acquire a control lease for a wf-browser window.',
|
|
563
|
+
example: 'wf-ui-control.mjs browser-lease --run <runId> --window <windowId>',
|
|
564
|
+
flags: [
|
|
565
|
+
{ flag: 'run', value: '<runId>', description: 'run id (alias: --runId)' },
|
|
566
|
+
{ flag: 'window', value: '<windowId>', description: 'window id (alias: --windowId)' },
|
|
567
|
+
{ flag: 'lease', value: '<leaseId>', description: 'lease id (alias: --leaseId)' },
|
|
568
|
+
{ flag: 'url', value: '<url>', description: 'wf-ui backend URL (or HARNESS_WF_UI_URL)' },
|
|
569
|
+
{ flag: 'token', value: '<token>', description: 'auth token' },
|
|
570
|
+
],
|
|
571
|
+
},
|
|
572
|
+
{
|
|
573
|
+
name: 'browser-url',
|
|
574
|
+
aliases: [],
|
|
575
|
+
summary: 'Resolve the debug launch URL for a wf-browser window.',
|
|
576
|
+
example: 'wf-ui-control.mjs browser-url --run <runId> --window <windowId> --open true',
|
|
577
|
+
flags: [
|
|
578
|
+
{ flag: 'run', value: '<runId>', description: 'run id (alias: --runId)' },
|
|
579
|
+
{ flag: 'window', value: '<windowId>', description: 'window id (alias: --windowId)' },
|
|
580
|
+
{ flag: 'open', description: 'also open the URL in the local browser' },
|
|
581
|
+
{ flag: 'url', value: '<url>', description: 'wf-ui backend URL (or HARNESS_WF_UI_URL)' },
|
|
582
|
+
{ flag: 'token', value: '<token>', description: 'auth token' },
|
|
583
|
+
],
|
|
584
|
+
},
|
|
585
|
+
{
|
|
586
|
+
name: 'browser-allocate',
|
|
587
|
+
aliases: [],
|
|
588
|
+
summary: 'Allocate a wf-browser run/window/lease and optionally open the browser window.',
|
|
589
|
+
example: 'wf-ui-control.mjs browser-allocate --open true --wait true',
|
|
590
|
+
flags: [
|
|
591
|
+
{ flag: 'run', value: '<runId>', description: 'run id (alias: --runId; created if omitted)' },
|
|
592
|
+
{ flag: 'window', value: '<windowId>', description: 'window id (alias: --windowId)' },
|
|
593
|
+
{ flag: 'open', description: 'open the browser window after allocation' },
|
|
594
|
+
{ flag: 'wait', description: 'wait until the window connects to the backend' },
|
|
595
|
+
{ flag: 'url', value: '<url>', description: 'wf-ui backend URL (or HARNESS_WF_UI_URL)' },
|
|
596
|
+
{ flag: 'token', value: '<token>', description: 'auth token' },
|
|
597
|
+
],
|
|
598
|
+
},
|
|
599
|
+
{
|
|
600
|
+
name: 'browser-allocate-many',
|
|
601
|
+
aliases: [],
|
|
602
|
+
summary: 'Allocate multiple wf-browser windows in one batch.',
|
|
603
|
+
example: 'wf-ui-control.mjs browser-allocate-many --count 3 --open true',
|
|
604
|
+
flags: [
|
|
605
|
+
{ flag: 'count', value: '<n>', description: 'number of windows (default: 1, max: 50)' },
|
|
606
|
+
{ flag: 'agents', value: '<agentId,...>', description: 'agent ids per window' },
|
|
607
|
+
{ flag: 'routes', value: '<route,...>', description: 'routes per window' },
|
|
608
|
+
{ flag: 'open', description: 'open the browser windows after allocation' },
|
|
609
|
+
{ flag: 'url', value: '<url>', description: 'wf-ui backend URL (or HARNESS_WF_UI_URL)' },
|
|
610
|
+
{ flag: 'token', value: '<token>', description: 'auth token' },
|
|
611
|
+
],
|
|
612
|
+
},
|
|
613
|
+
{
|
|
614
|
+
name: 'browser-open',
|
|
615
|
+
aliases: [],
|
|
616
|
+
summary: 'Open a browser window at the launch URL (default or isolated context).',
|
|
617
|
+
example: 'wf-ui-control.mjs browser-open --run <runId> --window <windowId> --context isolated',
|
|
618
|
+
flags: [
|
|
619
|
+
{ flag: 'run', value: '<runId>', description: 'run id (alias: --runId)' },
|
|
620
|
+
{ flag: 'window', value: '<windowId>', description: 'window id (alias: --windowId)' },
|
|
621
|
+
{ flag: 'context', value: '<default|isolated>', description: 'browser context (default: isolated)' },
|
|
622
|
+
{ flag: 'browser-command', value: '<path>', description: 'browser executable path (alias: --browser-path)' },
|
|
623
|
+
{ flag: 'dry-run', description: 'prepare without launching' },
|
|
624
|
+
{ flag: 'url', value: '<url>', description: 'wf-ui backend URL (or HARNESS_WF_UI_URL)' },
|
|
625
|
+
{ flag: 'token', value: '<token>', description: 'auth token' },
|
|
626
|
+
],
|
|
627
|
+
},
|
|
628
|
+
{
|
|
629
|
+
name: 'browser-launches',
|
|
630
|
+
aliases: [],
|
|
631
|
+
summary: 'List recorded browser launches for a wf-browser window.',
|
|
632
|
+
example: 'wf-ui-control.mjs browser-launches --run <runId> --window <windowId>',
|
|
633
|
+
flags: [
|
|
634
|
+
{ flag: 'run', value: '<runId>', description: 'run id (alias: --runId)' },
|
|
635
|
+
{ flag: 'window', value: '<windowId>', description: 'window id (alias: --windowId)' },
|
|
636
|
+
{ flag: 'url', value: '<url>', description: 'wf-ui backend URL (or HARNESS_WF_UI_URL)' },
|
|
637
|
+
{ flag: 'token', value: '<token>', description: 'auth token' },
|
|
638
|
+
],
|
|
639
|
+
},
|
|
640
|
+
{
|
|
641
|
+
name: 'browser-close',
|
|
642
|
+
aliases: [],
|
|
643
|
+
summary: 'Close selected browser launches and optionally remove their profiles.',
|
|
644
|
+
example: 'wf-ui-control.mjs browser-close --run <runId> --window <windowId> --force true',
|
|
645
|
+
flags: [
|
|
646
|
+
{ flag: 'run', value: '<runId>', description: 'run id (alias: --runId)' },
|
|
647
|
+
{ flag: 'window', value: '<windowId>', description: 'window id (alias: --windowId)' },
|
|
648
|
+
{ flag: 'lease', value: '<leaseId>', description: 'lease id (alias: --leaseId)' },
|
|
649
|
+
{ flag: 'force', description: 'force-kill the browser process' },
|
|
650
|
+
{ flag: 'remove-profile', description: 'also remove the browser profile directory' },
|
|
651
|
+
{ flag: 'dry-run', description: 'report without killing' },
|
|
652
|
+
{ flag: 'url', value: '<url>', description: 'wf-ui backend URL (or HARNESS_WF_UI_URL)' },
|
|
653
|
+
{ flag: 'token', value: '<token>', description: 'auth token' },
|
|
654
|
+
],
|
|
655
|
+
},
|
|
656
|
+
{
|
|
657
|
+
name: 'browser-wait',
|
|
658
|
+
aliases: [],
|
|
659
|
+
summary: 'Wait until a wf-browser window connects to the backend.',
|
|
660
|
+
example: 'wf-ui-control.mjs browser-wait --run <runId> --window <windowId> --timeout 20000',
|
|
661
|
+
flags: [
|
|
662
|
+
{ flag: 'run', value: '<runId>', description: 'run id (alias: --runId)' },
|
|
663
|
+
{ flag: 'window', value: '<windowId>', description: 'window id (alias: --windowId)' },
|
|
664
|
+
{ flag: 'timeout', value: '<ms>', description: 'wait timeout (default: 10000)' },
|
|
665
|
+
{ flag: 'url', value: '<url>', description: 'wf-ui backend URL (or HARNESS_WF_UI_URL)' },
|
|
666
|
+
{ flag: 'token', value: '<token>', description: 'auth token' },
|
|
667
|
+
],
|
|
668
|
+
},
|
|
669
|
+
{
|
|
670
|
+
name: 'browser-release',
|
|
671
|
+
aliases: [],
|
|
672
|
+
summary: 'Release a wf-browser window lease, optionally closing the browser.',
|
|
673
|
+
example: 'wf-ui-control.mjs browser-release --run <runId> --window <windowId> --lease <leaseId> --close true',
|
|
674
|
+
flags: [
|
|
675
|
+
{ flag: 'run', value: '<runId>', description: 'run id (alias: --runId)' },
|
|
676
|
+
{ flag: 'window', value: '<windowId>', description: 'window id (alias: --windowId)' },
|
|
677
|
+
{ flag: 'lease', value: '<leaseId>', description: 'lease id (alias: --leaseId)' },
|
|
678
|
+
{ flag: 'close', description: 'also close the browser window' },
|
|
679
|
+
{ flag: 'url', value: '<url>', description: 'wf-ui backend URL (or HARNESS_WF_UI_URL)' },
|
|
680
|
+
{ flag: 'token', value: '<token>', description: 'auth token' },
|
|
681
|
+
],
|
|
682
|
+
},
|
|
683
|
+
{
|
|
684
|
+
name: 'browser-artifacts',
|
|
685
|
+
aliases: [],
|
|
686
|
+
summary: 'List artifacts stored for a wf-browser window.',
|
|
687
|
+
example: 'wf-ui-control.mjs browser-artifacts --run <runId> --window <windowId>',
|
|
688
|
+
flags: [
|
|
689
|
+
{ flag: 'run', value: '<runId>', description: 'run id (alias: --runId)' },
|
|
690
|
+
{ flag: 'window', value: '<windowId>', description: 'window id (alias: --windowId)' },
|
|
691
|
+
{ flag: 'url', value: '<url>', description: 'wf-ui backend URL (or HARNESS_WF_UI_URL)' },
|
|
692
|
+
{ flag: 'token', value: '<token>', description: 'auth token' },
|
|
693
|
+
],
|
|
694
|
+
},
|
|
695
|
+
{
|
|
696
|
+
name: 'browser-connections',
|
|
697
|
+
aliases: [],
|
|
698
|
+
summary: 'List live wf-browser window connections.',
|
|
699
|
+
example: 'wf-ui-control.mjs browser-connections --url <url>',
|
|
700
|
+
flags: [
|
|
701
|
+
{ flag: 'url', value: '<url>', description: 'wf-ui backend URL (or HARNESS_WF_UI_URL)' },
|
|
702
|
+
{ flag: 'token', value: '<token>', description: 'auth token' },
|
|
703
|
+
],
|
|
704
|
+
},
|
|
705
|
+
{
|
|
706
|
+
name: 'browser-cleanup',
|
|
707
|
+
aliases: [],
|
|
708
|
+
summary: 'Run wf-browser cleanup for old runs and browser profiles.',
|
|
709
|
+
example: 'wf-ui-control.mjs browser-cleanup --apply true --max-age-days 7',
|
|
710
|
+
flags: [
|
|
711
|
+
{ flag: 'apply', description: 'actually apply cleanup (default: dry run)' },
|
|
712
|
+
{ flag: 'keep-latest', value: '<n>', description: 'keep the n latest runs' },
|
|
713
|
+
{ flag: 'max-age-days', value: '<n>', description: 'remove runs older than n days' },
|
|
714
|
+
{ flag: 'url', value: '<url>', description: 'wf-ui backend URL (or HARNESS_WF_UI_URL)' },
|
|
715
|
+
{ flag: 'token', value: '<token>', description: 'auth token' },
|
|
716
|
+
],
|
|
717
|
+
},
|
|
718
|
+
{
|
|
719
|
+
name: 'browser-command',
|
|
720
|
+
aliases: [],
|
|
721
|
+
summary: 'Run a single observe.*/act.* browser primitive against a window.',
|
|
722
|
+
example: 'wf-ui-control.mjs browser-command --run <runId> --window <windowId> --lease <leaseId> --primitive observe.uiTree',
|
|
723
|
+
flags: [
|
|
724
|
+
{ flag: 'run', value: '<runId>', description: 'run id (alias: --runId)' },
|
|
725
|
+
{ flag: 'window', value: '<windowId>', description: 'window id (alias: --windowId)' },
|
|
726
|
+
{ flag: 'lease', value: '<leaseId>', description: 'lease id (alias: --leaseId)' },
|
|
727
|
+
{ flag: 'primitive', value: '<observe.*|act.*>', description: 'browser primitive to run' },
|
|
728
|
+
{ flag: 'payload', value: '<json>', description: 'primitive payload as a JSON object' },
|
|
729
|
+
{ flag: 'url', value: '<url>', description: 'wf-ui backend URL (or HARNESS_WF_UI_URL)' },
|
|
730
|
+
{ flag: 'token', value: '<token>', description: 'auth token' },
|
|
731
|
+
],
|
|
732
|
+
},
|
|
733
|
+
{
|
|
734
|
+
name: 'browser-snapshot',
|
|
735
|
+
aliases: [],
|
|
736
|
+
summary: 'Run observe.* primitives and aggregate their artifacts into a snapshot.',
|
|
737
|
+
example: 'wf-ui-control.mjs browser-snapshot --run <runId> --window <windowId> --lease <leaseId>',
|
|
738
|
+
flags: [
|
|
739
|
+
{ flag: 'run', value: '<runId>', description: 'run id (alias: --runId)' },
|
|
740
|
+
{ flag: 'window', value: '<windowId>', description: 'window id (alias: --windowId)' },
|
|
741
|
+
{ flag: 'lease', value: '<leaseId>', description: 'lease id (alias: --leaseId)' },
|
|
742
|
+
{ flag: 'primitives', value: '<observe.*,...>', description: 'primitives to run (default: standard observe set)' },
|
|
743
|
+
{ flag: 'strict', description: 'fail the whole snapshot when one primitive fails' },
|
|
744
|
+
{ flag: 'url', value: '<url>', description: 'wf-ui backend URL (or HARNESS_WF_UI_URL)' },
|
|
745
|
+
{ flag: 'token', value: '<token>', description: 'auth token' },
|
|
746
|
+
],
|
|
747
|
+
},
|
|
748
|
+
];
|
|
749
|
+
|
|
750
|
+
function registryEntryFor(commandName) {
|
|
751
|
+
if (!commandName) return null;
|
|
752
|
+
return COMMAND_REGISTRY.find(entry =>
|
|
753
|
+
entry.name === commandName || (entry.aliases || []).includes(commandName)
|
|
754
|
+
) || null;
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
function helpTextFor(commandName) {
|
|
758
|
+
const entry = registryEntryFor(commandName);
|
|
759
|
+
if (!entry) {
|
|
760
|
+
return 'Usage: wf-ui-control.mjs <command> [flags]\nPass --help after a command for command-specific flags.';
|
|
761
|
+
}
|
|
762
|
+
const lines = [
|
|
763
|
+
`Usage: wf-ui-control.mjs ${entry.name} [flags]`,
|
|
764
|
+
'',
|
|
765
|
+
entry.summary,
|
|
766
|
+
];
|
|
767
|
+
if (entry.aliases && entry.aliases.length) {
|
|
768
|
+
lines.push('', `Aliases: ${entry.aliases.join(', ')}`);
|
|
769
|
+
}
|
|
770
|
+
if (entry.flags && entry.flags.length) {
|
|
771
|
+
lines.push('', 'Flags:');
|
|
772
|
+
for (const flag of entry.flags) {
|
|
773
|
+
const rendered = ` --${flag.flag}${flag.value ? ` ${flag.value}` : ''}`;
|
|
774
|
+
lines.push(rendered.length >= 44 ? `${rendered} ${flag.description}` : `${rendered.padEnd(44)}${flag.description}`);
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
if (entry.example) {
|
|
778
|
+
lines.push('', `Example: ${entry.example}`);
|
|
779
|
+
}
|
|
780
|
+
return lines.join('\n');
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
// `help --json` output: the full registry `{ commands: [...], actions: [...] }`
|
|
784
|
+
// when no command is named, or a single command entry. The command list is
|
|
785
|
+
// built from BOTH the registry (names + aliases) and the dispatch map, so
|
|
786
|
+
// every dispatchable name appears exactly once; dispatch names without a
|
|
787
|
+
// registry entry fall back to `{ name, summary: '(undocumented)' }`. The
|
|
788
|
+
// `actions` array merges the shared action registry
|
|
789
|
+
// (Harness/a2a/action-registry.json): every action with a cli.command is
|
|
790
|
+
// mapped to `{ id, command, summary, flags }`. When the shared registry file
|
|
791
|
+
// is missing or invalid the `actions` key is omitted and `actionsFallback:
|
|
792
|
+
// true` marks the absence so consumers can distinguish it from an empty list
|
|
793
|
+
// (COMMAND_REGISTRY remains the command surface either way).
|
|
794
|
+
function registryCommandsJson() {
|
|
795
|
+
const entriesByKey = new Map();
|
|
796
|
+
for (const entry of COMMAND_REGISTRY) {
|
|
797
|
+
entriesByKey.set(entry.name, entry);
|
|
798
|
+
for (const alias of entry.aliases || []) entriesByKey.set(alias, entry);
|
|
799
|
+
}
|
|
800
|
+
const commands = [];
|
|
801
|
+
const seen = new Set();
|
|
802
|
+
for (const name of ['help', ...Object.keys(COMMAND_DISPATCH)]) {
|
|
803
|
+
const entry = entriesByKey.get(name);
|
|
804
|
+
if (entry) {
|
|
805
|
+
if (seen.has(entry.name)) continue;
|
|
806
|
+
seen.add(entry.name);
|
|
807
|
+
commands.push(entry);
|
|
808
|
+
} else {
|
|
809
|
+
commands.push({ name, summary: '(undocumented)' });
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
return commands;
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
// Actions from the shared action registry with a cli.command set, mapped to
|
|
816
|
+
// { id, command, summary, flags }. Returns null when the registry file is
|
|
817
|
+
// missing or invalid so callers can emit the actionsFallback marker.
|
|
818
|
+
function registryActionsJson(projectRoot) {
|
|
819
|
+
const registry = readJson(path.join(projectRoot, 'Harness', 'a2a', 'action-registry.json'), null);
|
|
820
|
+
if (!registry || !Array.isArray(registry.actions)) return null;
|
|
821
|
+
return registry.actions
|
|
822
|
+
.filter(action => action && action.cli && action.cli.command)
|
|
823
|
+
.map(action => ({
|
|
824
|
+
id: action.id,
|
|
825
|
+
command: action.cli.command,
|
|
826
|
+
summary: action.summary || '',
|
|
827
|
+
flags: Array.isArray(action.cli.flags) ? action.cli.flags : [],
|
|
828
|
+
}));
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
function helpJsonFor(commandName, projectRoot) {
|
|
832
|
+
if (commandName) return registryEntryFor(commandName) || { name: commandName, summary: '(undocumented)' };
|
|
833
|
+
const output = { commands: registryCommandsJson() };
|
|
834
|
+
const actions = registryActionsJson(projectRoot);
|
|
835
|
+
if (actions === null) output.actionsFallback = true;
|
|
836
|
+
else output.actions = actions;
|
|
837
|
+
return output;
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
// Dispatch map: every command (and alias) the CLI accepts. This is the
|
|
841
|
+
// machine-readable source of "known dispatch names" used by `help --json`.
|
|
842
|
+
const COMMAND_DISPATCH = {
|
|
843
|
+
self: () => selfContext(),
|
|
844
|
+
snapshot: (projectRoot, flags) => snapshot(projectRoot, flags),
|
|
845
|
+
describe: async (projectRoot, flags) => describeSnapshot(await snapshot(projectRoot, flags)),
|
|
846
|
+
'create-agent': (projectRoot, flags) => createAgent(projectRoot, flags),
|
|
847
|
+
'ensure-backend': (projectRoot, flags) => ensureBackend(projectRoot, flags),
|
|
848
|
+
'dispatch-progress': (_projectRoot, flags) => dispatchReport(flags, 'progress'),
|
|
849
|
+
'dispatch-result': (_projectRoot, flags) => dispatchReport(flags, 'result'),
|
|
850
|
+
'find-agent': (_projectRoot, flags) => findAgent(flags),
|
|
851
|
+
'agent-role-profile': (projectRoot, flags) => agentRoleProfile(projectRoot, flags),
|
|
852
|
+
'send-input': (_projectRoot, flags) => sendInput(flags),
|
|
853
|
+
'send-key': (projectRoot, flags, args) => sendKey(projectRoot, flags, args),
|
|
854
|
+
key: (projectRoot, flags, args) => sendKey(projectRoot, flags, args),
|
|
855
|
+
'delegate-agent': (_projectRoot, flags) => delegateAgent(flags),
|
|
856
|
+
'send-agent-message': (_projectRoot, flags) => sendAgentMessage(flags),
|
|
857
|
+
'message-agent': (_projectRoot, flags) => sendAgentMessage(flags),
|
|
858
|
+
'broadcast-agent-message': (_projectRoot, flags) => broadcastAgentMessage(flags),
|
|
859
|
+
'broadcast-agent': (_projectRoot, flags) => broadcastAgentMessage(flags),
|
|
860
|
+
'read-agent-messages': (_projectRoot, flags) => readAgentMessages(flags),
|
|
861
|
+
'agent-messages': (_projectRoot, flags) => readAgentMessages(flags),
|
|
862
|
+
'read-agent': (_projectRoot, flags) => readAgent(flags),
|
|
863
|
+
'bridge-messages': (_projectRoot, flags) => bridgeMessages(flags),
|
|
864
|
+
'browser-runs': (_projectRoot, flags) => browserRuns(flags),
|
|
865
|
+
'browser-run': (_projectRoot, flags) => browserRun(flags),
|
|
866
|
+
'browser-window': (_projectRoot, flags) => browserWindow(flags),
|
|
867
|
+
'browser-windows': (_projectRoot, flags) => browserWindows(flags),
|
|
868
|
+
'browser-lease': (_projectRoot, flags) => browserLease(flags),
|
|
869
|
+
'browser-url': (_projectRoot, flags) => browserUrl(flags),
|
|
870
|
+
'browser-allocate': (_projectRoot, flags) => browserAllocate(flags),
|
|
871
|
+
'browser-allocate-many': (_projectRoot, flags) => browserAllocateMany(flags),
|
|
872
|
+
'browser-open': (projectRoot, flags) => browserOpen({ ...flags, project: projectRoot }),
|
|
873
|
+
'browser-launches': (projectRoot, flags) => browserLaunches({ ...flags, project: projectRoot }),
|
|
874
|
+
'browser-close': (projectRoot, flags) => browserClose({ ...flags, project: projectRoot }),
|
|
875
|
+
'browser-wait': (_projectRoot, flags) => browserWait(flags),
|
|
876
|
+
'browser-release': (_projectRoot, flags) => browserRelease(flags),
|
|
877
|
+
'browser-artifacts': (_projectRoot, flags) => browserArtifacts(flags),
|
|
878
|
+
'browser-connections': (_projectRoot, flags) => browserConnections(flags),
|
|
879
|
+
'browser-cleanup': (_projectRoot, flags) => browserCleanup(flags),
|
|
880
|
+
'browser-command': (_projectRoot, flags) => browserCommand(flags),
|
|
881
|
+
'browser-snapshot': (_projectRoot, flags) => browserSnapshot(flags),
|
|
882
|
+
'node-map': (projectRoot, flags) => nodeMap(projectRoot, flags),
|
|
883
|
+
'workflow-node-map': (projectRoot, flags) => nodeMap(projectRoot, flags),
|
|
884
|
+
'workflow-ontology': (_projectRoot, flags) => workflowOntology(flags),
|
|
885
|
+
'workflow-context': (_projectRoot, flags) => workflowContext(flags),
|
|
886
|
+
'read-node': (_projectRoot, flags) => readNode(flags),
|
|
887
|
+
readnode: (_projectRoot, flags) => readNode(flags),
|
|
888
|
+
'read-snapshot': (_projectRoot, flags) => readNode(flags),
|
|
889
|
+
'workflow-node-action': (_projectRoot, flags) => workflowNodeAction(flags),
|
|
890
|
+
'workflow-node-actions': (_projectRoot, flags) => workflowNodeAction(flags),
|
|
891
|
+
'delete-node': (projectRoot, flags) => deleteNode(projectRoot, flags),
|
|
892
|
+
connect: (projectRoot, flags) => connectNodes(projectRoot, flags),
|
|
893
|
+
tail: (projectRoot, flags) => tail(projectRoot, flags),
|
|
894
|
+
manuals: (projectRoot, flags, args) => manuals(projectRoot, flags, args),
|
|
895
|
+
manual: (projectRoot, flags, args) => manuals(projectRoot, flags, args),
|
|
896
|
+
};
|
|
897
|
+
|
|
898
|
+
function readJson(filePath, fallback = null) {
|
|
899
|
+
try {
|
|
900
|
+
if (!filePath || !fs.existsSync(filePath)) return fallback;
|
|
901
|
+
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
902
|
+
} catch {
|
|
903
|
+
return fallback;
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
function readJsonl(filePath) {
|
|
908
|
+
try {
|
|
909
|
+
if (!fs.existsSync(filePath)) return [];
|
|
910
|
+
return fs.readFileSync(filePath, 'utf8')
|
|
911
|
+
.split(/\r?\n/)
|
|
912
|
+
.filter(Boolean)
|
|
913
|
+
.map(line => JSON.parse(line));
|
|
914
|
+
} catch {
|
|
915
|
+
return [];
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
function allSessionDirs(projectRoot) {
|
|
920
|
+
const out = [];
|
|
921
|
+
const pushSessions = (root, taskId = null) => {
|
|
922
|
+
let sessions = [];
|
|
923
|
+
try { sessions = fs.readdirSync(root, { withFileTypes: true }); } catch { return; }
|
|
924
|
+
for (const session of sessions) {
|
|
925
|
+
if (session.isDirectory()) out.push({ taskId, sessionId: session.name, dir: path.join(root, session.name) });
|
|
926
|
+
}
|
|
927
|
+
};
|
|
928
|
+
const tasksRoot = path.join(projectRoot, 'Harness', 'tasks');
|
|
929
|
+
let tasks = [];
|
|
930
|
+
try { tasks = fs.readdirSync(tasksRoot, { withFileTypes: true }); } catch { tasks = []; }
|
|
931
|
+
for (const task of tasks) {
|
|
932
|
+
if (task.isDirectory() && !task.name.startsWith('_')) {
|
|
933
|
+
pushSessions(path.join(tasksRoot, task.name, 'sessions'), task.name);
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
pushSessions(path.join(projectRoot, 'Harness', 'a2a', 'sessions'), null);
|
|
937
|
+
return out;
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
function localSnapshot(projectRoot) {
|
|
941
|
+
const mapPath = process.env.HARNESS_WORKFLOW_MAP || path.join(projectRoot, 'Harness', 'a2a', 'workflow-map.json');
|
|
942
|
+
const graph = readJson(mapPath, { schemaVersion: 1, version: 1, nodes: [], edges: [] });
|
|
943
|
+
const sessions = allSessionDirs(projectRoot)
|
|
944
|
+
.map(({ dir }) => readJson(path.join(dir, 'STATE.json')))
|
|
945
|
+
.filter(Boolean)
|
|
946
|
+
.sort((a, b) => String(b.updatedAt || '').localeCompare(String(a.updatedAt || '')));
|
|
947
|
+
return {
|
|
948
|
+
source: 'local-files',
|
|
949
|
+
self: selfContext(),
|
|
950
|
+
graph,
|
|
951
|
+
nodes: graph.nodes || [],
|
|
952
|
+
edges: graph.edges || [],
|
|
953
|
+
sessions,
|
|
954
|
+
};
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
function selfContext() {
|
|
958
|
+
return {
|
|
959
|
+
sessionId: process.env.HARNESS_PEER_SESSION_ID || '',
|
|
960
|
+
peerRuntime: process.env.HARNESS_PEER_RUNTIME || '',
|
|
961
|
+
agentKind: process.env.HARNESS_AGENT_KIND || '',
|
|
962
|
+
workflowMode: process.env.HARNESS_WORKFLOW_MODE || '',
|
|
963
|
+
nodeId: process.env.HARNESS_WORKFLOW_NODE_ID || '',
|
|
964
|
+
mapPath: process.env.HARNESS_WORKFLOW_MAP || '',
|
|
965
|
+
hasControlPlaneUrl: Boolean(process.env.HARNESS_WF_UI_URL || process.env.WF_UI_URL),
|
|
966
|
+
};
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
function controlPlane(flags) {
|
|
970
|
+
return {
|
|
971
|
+
url: flags.url || process.env.HARNESS_WF_UI_URL || process.env.WF_UI_URL || '',
|
|
972
|
+
token: flags.token || process.env.HARNESS_WF_UI_TOKEN || process.env.WF_UI_TOKEN || '',
|
|
973
|
+
readToken: flags['read-token'] || process.env.HARNESS_WF_UI_READ_TOKEN || process.env.WF_UI_READ_TOKEN || '',
|
|
974
|
+
};
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
function parseDispatchReportValue(value, label) {
|
|
978
|
+
if (value === undefined || value === null || value === '') return null;
|
|
979
|
+
try { return JSON.parse(value); } catch {
|
|
980
|
+
if (typeof value === 'string') return value;
|
|
981
|
+
throw new Error(`${label} must be valid JSON or text.`);
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
async function dispatchReport(flags, kind) {
|
|
986
|
+
const cp = controlPlane(flags);
|
|
987
|
+
if (!cp.url) throw new Error(`Missing HARNESS_WF_UI_URL for dispatch-${kind}.`);
|
|
988
|
+
const serviceDispatchId = process.env.HARNESS_DISPATCH_ID || process.env.CLAUDE_DISPATCH_ID || '';
|
|
989
|
+
const requestedDispatchId = flags.dispatchId || flags['dispatch-id'] || '';
|
|
990
|
+
if (serviceDispatchId && requestedDispatchId && serviceDispatchId !== requestedDispatchId) {
|
|
991
|
+
throw new Error('The dispatch id does not match the service-injected worker identity.');
|
|
992
|
+
}
|
|
993
|
+
const dispatchId = serviceDispatchId || requestedDispatchId;
|
|
994
|
+
if (!dispatchId) throw new Error(`Missing --dispatch-id <id> or HARNESS_DISPATCH_ID for dispatch-${kind}.`);
|
|
995
|
+
const serviceTaskId = process.env.HARNESS_PEER_TASK_ID || process.env.CLAUDE_PEER_TASK_ID || '';
|
|
996
|
+
const requestedTaskId = flags.task || flags.taskId || flags['task-id'] || '';
|
|
997
|
+
const taskId = serviceTaskId || requestedTaskId;
|
|
998
|
+
const serviceSessionId = process.env.HARNESS_PEER_SESSION_ID || '';
|
|
999
|
+
const requestedSessionId = flags.session || flags.sessionId || flags['session-id'] || '';
|
|
1000
|
+
if (serviceSessionId && requestedSessionId && serviceSessionId !== requestedSessionId) {
|
|
1001
|
+
throw new Error('The session id does not match the service-injected worker identity.');
|
|
1002
|
+
}
|
|
1003
|
+
const sessionId = serviceSessionId || requestedSessionId;
|
|
1004
|
+
const serviceRequestId = process.env.HARNESS_DISPATCH_REQUEST_ID || '';
|
|
1005
|
+
const requestedRequestId = flags.requestId || flags['request-id'] || '';
|
|
1006
|
+
if (serviceRequestId && requestedRequestId && serviceRequestId !== requestedRequestId) {
|
|
1007
|
+
throw new Error('The request id does not match the canonical dispatch envelope.');
|
|
1008
|
+
}
|
|
1009
|
+
const requestId = serviceRequestId || requestedRequestId;
|
|
1010
|
+
const serviceReplyTo = process.env.HARNESS_DISPATCH_REPLY_TO || '';
|
|
1011
|
+
const requestedReplyTo = flags.replyTo || flags['reply-to'] || '';
|
|
1012
|
+
if (serviceReplyTo && requestedReplyTo && serviceReplyTo !== requestedReplyTo) {
|
|
1013
|
+
throw new Error('The replyTo id does not match the canonical dispatch envelope.');
|
|
1014
|
+
}
|
|
1015
|
+
const replyTo = serviceReplyTo || requestedReplyTo;
|
|
1016
|
+
const body = {
|
|
1017
|
+
dispatchId,
|
|
1018
|
+
...(taskId ? { taskId } : {}),
|
|
1019
|
+
...(sessionId ? { sessionId } : {}),
|
|
1020
|
+
...(requestId ? { requestId } : {}),
|
|
1021
|
+
...(replyTo ? { replyTo } : {}),
|
|
1022
|
+
};
|
|
1023
|
+
let route;
|
|
1024
|
+
if (kind === 'progress') {
|
|
1025
|
+
const progress = flags.text || flags.message || flags.progress || '';
|
|
1026
|
+
if (!String(progress).trim()) throw new Error('dispatch-progress requires --text "...".');
|
|
1027
|
+
route = 'progress';
|
|
1028
|
+
body.progress = String(progress);
|
|
1029
|
+
} else {
|
|
1030
|
+
const status = String(flags.status || '').trim();
|
|
1031
|
+
if (!status) throw new Error('dispatch-result requires explicit --status <succeeded|failed|cancelled>.');
|
|
1032
|
+
route = 'result';
|
|
1033
|
+
body.status = status;
|
|
1034
|
+
const rawResult = flags.result !== undefined
|
|
1035
|
+
? flags.result
|
|
1036
|
+
: (flags.output !== undefined ? flags.output : flags.payload);
|
|
1037
|
+
if (rawResult !== undefined) {
|
|
1038
|
+
body.result = flags.result !== undefined || flags.payload !== undefined
|
|
1039
|
+
? parseDispatchReportValue(rawResult, '--result')
|
|
1040
|
+
: String(rawResult);
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
return apiJson(cp.url, cp.token || cp.readToken, `/api/dispatches/${encodeURIComponent(dispatchId)}/${route}`, {
|
|
1044
|
+
method: 'POST',
|
|
1045
|
+
body,
|
|
1046
|
+
actorSessionId: sessionId,
|
|
1047
|
+
actorNodeId: process.env.HARNESS_WORKFLOW_NODE_ID || '',
|
|
1048
|
+
actorType: sessionId || process.env.HARNESS_WORKFLOW_NODE_ID ? 'agent' : '',
|
|
1049
|
+
actorKind: process.env.HARNESS_AGENT_KIND || '',
|
|
1050
|
+
workerCapability: process.env.HARNESS_WORKER_CAPABILITY || '',
|
|
1051
|
+
});
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1054
|
+
function backendEnsurePaths(projectRoot) {
|
|
1055
|
+
const dir = path.join(projectRoot, 'Harness', '.temp', 'wf-ui-ensure');
|
|
1056
|
+
return {
|
|
1057
|
+
dir,
|
|
1058
|
+
state: path.join(dir, 'backend.json'),
|
|
1059
|
+
lock: path.join(dir, 'backend.lock'),
|
|
1060
|
+
ready: path.join(dir, 'ready.json'),
|
|
1061
|
+
};
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
function ensureProcessAlive(pid) {
|
|
1065
|
+
const numeric = Number(pid);
|
|
1066
|
+
if (!Number.isInteger(numeric) || numeric <= 0) return false;
|
|
1067
|
+
try { process.kill(numeric, 0); return true; } catch { return false; }
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
async function backendHealthy(record) {
|
|
1071
|
+
if (!record?.url || !ensureProcessAlive(record.pid)) return false;
|
|
1072
|
+
try {
|
|
1073
|
+
const response = await fetch(new URL('/api/health', record.url));
|
|
1074
|
+
const body = await response.json();
|
|
1075
|
+
return response.ok && body?.status === 'ok' && path.resolve(String(body.projectRoot || record.projectRoot)) === record.projectRoot;
|
|
1076
|
+
} catch {
|
|
1077
|
+
return false;
|
|
1078
|
+
}
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1081
|
+
async function acquireBackendEnsureLock(lockPath) {
|
|
1082
|
+
fs.mkdirSync(path.dirname(lockPath), { recursive: true });
|
|
1083
|
+
const deadline = Date.now() + 15000;
|
|
1084
|
+
while (Date.now() < deadline) {
|
|
1085
|
+
try {
|
|
1086
|
+
const fd = fs.openSync(lockPath, 'wx');
|
|
1087
|
+
fs.writeFileSync(fd, `${process.pid}\n`, 'utf8');
|
|
1088
|
+
return fd;
|
|
1089
|
+
} catch (error) {
|
|
1090
|
+
if (error?.code !== 'EEXIST') throw error;
|
|
1091
|
+
try {
|
|
1092
|
+
const ageMs = Date.now() - fs.statSync(lockPath).mtimeMs;
|
|
1093
|
+
if (ageMs > 30000) fs.rmSync(lockPath, { force: true });
|
|
1094
|
+
} catch { /* another ensure may be replacing the lock */ }
|
|
1095
|
+
await new Promise(resolve => setTimeout(resolve, 40));
|
|
1096
|
+
}
|
|
1097
|
+
}
|
|
1098
|
+
throw new Error(`Timed out waiting for backend ensure lock: ${lockPath}`);
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1101
|
+
function releaseBackendEnsureLock(lockPath, fd) {
|
|
1102
|
+
try { fs.closeSync(fd); } catch {}
|
|
1103
|
+
try { fs.rmSync(lockPath, { force: true }); } catch {}
|
|
1104
|
+
}
|
|
1105
|
+
|
|
1106
|
+
function runtimeEntryForEnsure() {
|
|
1107
|
+
const scriptDir = path.dirname(fileURLToPath(import.meta.url));
|
|
1108
|
+
const candidates = [
|
|
1109
|
+
process.env.HARNESS_RUNTIME_ENTRY,
|
|
1110
|
+
process.env.HARNESS_RUNTIME_ROOT ? path.join(process.env.HARNESS_RUNTIME_ROOT, 'src', 'index.js') : '',
|
|
1111
|
+
path.resolve(scriptDir, '..', '..', 'src', 'index.js'),
|
|
1112
|
+
].filter(Boolean).map(item => path.resolve(item));
|
|
1113
|
+
return candidates.find(item => existingFile(item)) || '';
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1116
|
+
function runtimeLaunchForEnsure() {
|
|
1117
|
+
const entry = runtimeEntryForEnsure();
|
|
1118
|
+
if (entry) return { command: process.execPath, args: [entry] };
|
|
1119
|
+
// A global thin bridge has no project-local src/. Use the installed package
|
|
1120
|
+
// binary, which owns the package-relative src/wf-ui-server implementation.
|
|
1121
|
+
const cli = 'create-harness-vibe-coding';
|
|
1122
|
+
if (process.platform === 'win32') return { command: 'cmd.exe', args: ['/d', '/c', `${cli}.cmd`] };
|
|
1123
|
+
return { command: cli, args: [] };
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
function waitForBackendReady(readyPath, child) {
|
|
1127
|
+
const deadline = Date.now() + 15000;
|
|
1128
|
+
return new Promise((resolve, reject) => {
|
|
1129
|
+
let childExit = null;
|
|
1130
|
+
let childError = null;
|
|
1131
|
+
child.once('exit', (code, signal) => { childExit = { code, signal }; });
|
|
1132
|
+
child.once('error', (error) => { childError = error; });
|
|
1133
|
+
const poll = () => {
|
|
1134
|
+
try {
|
|
1135
|
+
if (fs.existsSync(readyPath)) {
|
|
1136
|
+
const value = JSON.parse(fs.readFileSync(readyPath, 'utf8'));
|
|
1137
|
+
if (value?.url && value?.pid) { resolve(value); return; }
|
|
1138
|
+
}
|
|
1139
|
+
} catch { /* child may still be writing the handoff */ }
|
|
1140
|
+
if (childError) {
|
|
1141
|
+
reject(new Error(`backend launch failed: ${childError.message}`));
|
|
1142
|
+
return;
|
|
1143
|
+
}
|
|
1144
|
+
if (childExit) {
|
|
1145
|
+
reject(new Error(`backend exited before startup (${childExit.signal || childExit.code})`));
|
|
1146
|
+
return;
|
|
1147
|
+
}
|
|
1148
|
+
if (Date.now() >= deadline) { reject(new Error('backend did not report a URL within 15s')); return; }
|
|
1149
|
+
setTimeout(poll, 60);
|
|
1150
|
+
};
|
|
1151
|
+
poll();
|
|
1152
|
+
});
|
|
1153
|
+
}
|
|
1154
|
+
|
|
1155
|
+
async function ensureBackend(projectRoot, flags) {
|
|
1156
|
+
const canonicalRoot = path.resolve(projectRoot || process.cwd());
|
|
1157
|
+
const host = flags.host || '127.0.0.1';
|
|
1158
|
+
if (host !== '127.0.0.1') throw new Error('ensure-backend host must be 127.0.0.1');
|
|
1159
|
+
const port = Number(flags.port || 0);
|
|
1160
|
+
if (!Number.isInteger(port) || port < 0 || port > 65535) throw new Error('ensure-backend port must be an integer from 0 to 65535');
|
|
1161
|
+
const paths = backendEnsurePaths(canonicalRoot);
|
|
1162
|
+
const fd = await acquireBackendEnsureLock(paths.lock);
|
|
1163
|
+
try {
|
|
1164
|
+
const current = readJson(paths.state, null);
|
|
1165
|
+
if (await backendHealthy(current)) return { ...current, openBrowser: false };
|
|
1166
|
+
const launch = runtimeLaunchForEnsure();
|
|
1167
|
+
try { fs.rmSync(paths.ready, { force: true }); } catch {}
|
|
1168
|
+
const child = spawn(launch.command, [...launch.args, 'wf-ui', '--project', canonicalRoot, '--host', host, '--port', String(port), '--no-open'], {
|
|
1169
|
+
cwd: canonicalRoot,
|
|
1170
|
+
detached: true,
|
|
1171
|
+
env: { ...process.env, HARNESS_WF_UI_READY_FILE: paths.ready },
|
|
1172
|
+
stdio: 'ignore',
|
|
1173
|
+
windowsHide: true,
|
|
1174
|
+
});
|
|
1175
|
+
child.unref();
|
|
1176
|
+
const started = await waitForBackendReady(paths.ready, child);
|
|
1177
|
+
const result = { ok: true, url: started.url, pid: Number(started.pid), projectRoot: canonicalRoot, openBrowser: false, startedAt: started.startedAt };
|
|
1178
|
+
fs.mkdirSync(paths.dir, { recursive: true });
|
|
1179
|
+
fs.writeFileSync(paths.state, `${JSON.stringify(result, null, 2)}\n`, 'utf8');
|
|
1180
|
+
return result;
|
|
1181
|
+
} finally {
|
|
1182
|
+
releaseBackendEnsureLock(paths.lock, fd);
|
|
1183
|
+
}
|
|
1184
|
+
}
|
|
1185
|
+
|
|
1186
|
+
function apiJson(baseUrl, token, route, {
|
|
1187
|
+
method = 'GET',
|
|
1188
|
+
body = null,
|
|
1189
|
+
actorSessionId = '',
|
|
1190
|
+
actorNodeId = '',
|
|
1191
|
+
actorType = '',
|
|
1192
|
+
actorKind = '',
|
|
1193
|
+
workerCapability = '',
|
|
1194
|
+
} = {}) {
|
|
1195
|
+
return new Promise((resolve, reject) => {
|
|
1196
|
+
const url = new URL(route, baseUrl);
|
|
1197
|
+
const payload = body === null ? null : JSON.stringify(body);
|
|
1198
|
+
const req = http.request({
|
|
1199
|
+
hostname: url.hostname,
|
|
1200
|
+
port: url.port,
|
|
1201
|
+
path: `${url.pathname}${url.search}`,
|
|
1202
|
+
method,
|
|
1203
|
+
headers: {
|
|
1204
|
+
...(payload ? { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(payload) } : {}),
|
|
1205
|
+
...(token ? { Authorization: `Bearer ${token}` } : {}),
|
|
1206
|
+
...(actorSessionId ? { 'X-Harness-Session-Id': actorSessionId } : {}),
|
|
1207
|
+
...(actorNodeId ? { 'X-Harness-Workflow-Node-Id': actorNodeId } : {}),
|
|
1208
|
+
...(actorType ? { 'X-Harness-Actor-Type': actorType } : {}),
|
|
1209
|
+
...(actorKind ? { 'X-Harness-Actor-Kind': actorKind } : {}),
|
|
1210
|
+
...(workerCapability ? { 'X-Harness-Worker-Capability': workerCapability } : {}),
|
|
1211
|
+
},
|
|
1212
|
+
}, (res) => {
|
|
1213
|
+
let data = '';
|
|
1214
|
+
res.on('data', chunk => { data += chunk; });
|
|
1215
|
+
res.on('end', () => {
|
|
1216
|
+
let parsed = data;
|
|
1217
|
+
try { parsed = data ? JSON.parse(data) : null; } catch {}
|
|
1218
|
+
if (res.statusCode < 200 || res.statusCode >= 300) {
|
|
1219
|
+
reject(new Error(`HTTP ${res.statusCode}: ${typeof parsed === 'string' ? parsed : JSON.stringify(parsed)}`));
|
|
1220
|
+
return;
|
|
1221
|
+
}
|
|
1222
|
+
resolve(parsed);
|
|
1223
|
+
});
|
|
1224
|
+
});
|
|
1225
|
+
req.on('error', reject);
|
|
1226
|
+
if (payload) req.write(payload);
|
|
1227
|
+
req.end();
|
|
1228
|
+
});
|
|
1229
|
+
}
|
|
1230
|
+
|
|
1231
|
+
async function snapshot(projectRoot, flags) {
|
|
1232
|
+
const cp = controlPlane(flags);
|
|
1233
|
+
if (cp.url) {
|
|
1234
|
+
return apiJson(cp.url, cp.token || cp.readToken, '/api/a2a/snapshot', {
|
|
1235
|
+
actorSessionId: process.env.HARNESS_PEER_SESSION_ID || '',
|
|
1236
|
+
});
|
|
1237
|
+
}
|
|
1238
|
+
return localSnapshot(projectRoot);
|
|
1239
|
+
}
|
|
1240
|
+
|
|
1241
|
+
function describeSnapshot(data) {
|
|
1242
|
+
const self = data.self || selfContext();
|
|
1243
|
+
const nodes = data.nodes || data.graph?.nodes || [];
|
|
1244
|
+
const edges = data.edges || data.graph?.edges || [];
|
|
1245
|
+
const sessions = data.sessions || [];
|
|
1246
|
+
const selfNodeIds = new Set([
|
|
1247
|
+
self.nodeId,
|
|
1248
|
+
self.sessionId ? `session-${self.sessionId}` : '',
|
|
1249
|
+
].filter(Boolean));
|
|
1250
|
+
for (const node of nodes) {
|
|
1251
|
+
if (node.sessionId === self.sessionId || node.graphNodeId === self.nodeId || node.nodeId === self.nodeId) {
|
|
1252
|
+
selfNodeIds.add(node.id || node.nodeId || node.graphNodeId);
|
|
1253
|
+
if (node.graphNodeId) selfNodeIds.add(node.graphNodeId);
|
|
1254
|
+
}
|
|
1255
|
+
}
|
|
1256
|
+
const connectedEdges = edges.filter(edge =>
|
|
1257
|
+
selfNodeIds.has(edge.from) || selfNodeIds.has(edge.to) || selfNodeIds.has(edge.source) || selfNodeIds.has(edge.target)
|
|
1258
|
+
);
|
|
1259
|
+
return {
|
|
1260
|
+
self,
|
|
1261
|
+
counts: {
|
|
1262
|
+
nodes: nodes.length,
|
|
1263
|
+
edges: edges.length,
|
|
1264
|
+
sessions: sessions.length,
|
|
1265
|
+
runningSessions: sessions.filter(session => session.status === 'running' || session.status === 'starting').length,
|
|
1266
|
+
},
|
|
1267
|
+
nodes: nodes.map(node => ({
|
|
1268
|
+
id: node.id || node.nodeId,
|
|
1269
|
+
sessionId: node.sessionId,
|
|
1270
|
+
label: node.label,
|
|
1271
|
+
agentKind: node.agentKind,
|
|
1272
|
+
runtime: node.runtime,
|
|
1273
|
+
status: node.status,
|
|
1274
|
+
role: node.role,
|
|
1275
|
+
})),
|
|
1276
|
+
connectedEdges,
|
|
1277
|
+
};
|
|
1278
|
+
}
|
|
1279
|
+
|
|
1280
|
+
function mainAgentKindFromIdentity(identity) {
|
|
1281
|
+
if (!identity || typeof identity !== 'object') return false;
|
|
1282
|
+
if (identity.isMainAgent === true) return true;
|
|
1283
|
+
const agentKind = String(identity.agentKind || '').toLowerCase();
|
|
1284
|
+
if (agentKind === 'main') return true;
|
|
1285
|
+
const role = String(identity.role || '').toLowerCase();
|
|
1286
|
+
return role === 'main' || role.includes('ceo');
|
|
1287
|
+
}
|
|
1288
|
+
|
|
1289
|
+
async function assertMainAgent(flags, action = 'create-agent') {
|
|
1290
|
+
const actorKind = flags['actor-kind'] || flags.actorKind || process.env.HARNESS_AGENT_KIND || '';
|
|
1291
|
+
if (actorKind === 'main') return;
|
|
1292
|
+
if (actorKind && actorKind !== 'main') {
|
|
1293
|
+
throw new Error(`${action} is only available to Main Agent nodes. Subagents can read the workflow map but cannot create or control nodes.`);
|
|
1294
|
+
}
|
|
1295
|
+
// No local agent kind: resolve the actor from the backend so the gate matches
|
|
1296
|
+
// the backend isMainAgentGraphNode semantics (agentKind=main or role main/ceo).
|
|
1297
|
+
const cp = controlPlane(flags);
|
|
1298
|
+
if (!cp.url) {
|
|
1299
|
+
throw new Error(`${action} is only available to Main Agent nodes. Pass --actor-kind main, set HARNESS_AGENT_KIND=main, or set HARNESS_WF_UI_URL so the actor node can be resolved from the backend.`);
|
|
1300
|
+
}
|
|
1301
|
+
const actorId = nodeMapActorId(flags);
|
|
1302
|
+
let context;
|
|
1303
|
+
try {
|
|
1304
|
+
context = await apiJson(cp.url, cp.token || cp.readToken, `/api/workflow/context/${encodeURIComponent(actorId)}`);
|
|
1305
|
+
} catch {
|
|
1306
|
+
// Fail closed: an unreachable backend must not weaken the main-agent gate.
|
|
1307
|
+
throw new Error(`${action} is only available to Main Agent nodes. Subagents can read the workflow map but cannot create or control nodes. (failed to resolve actor ${actorId} from the backend)`);
|
|
1308
|
+
}
|
|
1309
|
+
if (!mainAgentKindFromIdentity(context?.context?.identity)) {
|
|
1310
|
+
throw new Error(`${action} is only available to Main Agent nodes. Subagents can read the workflow map but cannot create or control nodes.`);
|
|
1311
|
+
}
|
|
1312
|
+
}
|
|
1313
|
+
|
|
1314
|
+
async function createAgent(projectRoot, flags) {
|
|
1315
|
+
assertMainAgent(flags, 'create-agent');
|
|
1316
|
+
const cp = controlPlane(flags);
|
|
1317
|
+
if (!cp.url) throw new Error('Missing HARNESS_WF_UI_URL for agent control.');
|
|
1318
|
+
const agentKind = flags['agent-kind'] || 'subagent';
|
|
1319
|
+
const runtime = flags.runtime || process.env.HARNESS_PEER_RUNTIME || 'claude';
|
|
1320
|
+
const dispatchFlagKeys = [
|
|
1321
|
+
'dispatchId', 'dispatch-id', 'task', 'taskId', 'task-id', 'effort', 'transport',
|
|
1322
|
+
'contextPackPath', 'context-pack-path', 'contextRefs', 'context-refs',
|
|
1323
|
+
'contextRef', 'context-ref', 'requestId', 'request-id', 'replyTo', 'reply-to',
|
|
1324
|
+
];
|
|
1325
|
+
const hasDispatchFlag = dispatchFlagKeys.some(key => Object.prototype.hasOwnProperty.call(flags, key));
|
|
1326
|
+
const dispatchId = flags.dispatchId || flags['dispatch-id'] || process.env.HARNESS_DISPATCH_ID || '';
|
|
1327
|
+
const dispatchMode = Boolean(dispatchId || hasDispatchFlag);
|
|
1328
|
+
let dispatchFields = null;
|
|
1329
|
+
if (dispatchMode) {
|
|
1330
|
+
const taskId = flags.task || flags.taskId || flags['task-id'] || '';
|
|
1331
|
+
const dispatchRuntime = flags.runtime || process.env.HARNESS_PEER_RUNTIME || '';
|
|
1332
|
+
const model = flags.model || '';
|
|
1333
|
+
const effort = flags.effort || '';
|
|
1334
|
+
const transport = flags.transport || '';
|
|
1335
|
+
const missing = [
|
|
1336
|
+
['--dispatch-id', dispatchId],
|
|
1337
|
+
['--task', taskId],
|
|
1338
|
+
['--runtime', dispatchRuntime],
|
|
1339
|
+
['--model', model],
|
|
1340
|
+
['--effort', effort],
|
|
1341
|
+
['--transport', transport],
|
|
1342
|
+
].filter(([, value]) => !String(value || '').trim()).map(([name]) => name);
|
|
1343
|
+
if (missing.length) {
|
|
1344
|
+
throw new Error(`create-agent dispatch requires ${missing.join(', ')}.`);
|
|
1345
|
+
}
|
|
1346
|
+
|
|
1347
|
+
const refsJson = flags.contextRefs || flags['context-refs'];
|
|
1348
|
+
const refShorthand = flags.contextRef || flags['context-ref'];
|
|
1349
|
+
let contextRefs;
|
|
1350
|
+
if (refsJson !== undefined) {
|
|
1351
|
+
try { contextRefs = JSON.parse(refsJson); } catch {
|
|
1352
|
+
throw new Error('create-agent dispatch --context-refs must be a JSON array of node references.');
|
|
1353
|
+
}
|
|
1354
|
+
if (!Array.isArray(contextRefs)) {
|
|
1355
|
+
throw new Error('create-agent dispatch --context-refs must be a JSON array of node references.');
|
|
1356
|
+
}
|
|
1357
|
+
} else if (refShorthand !== undefined) {
|
|
1358
|
+
contextRefs = [refShorthand];
|
|
1359
|
+
}
|
|
1360
|
+
dispatchFields = {
|
|
1361
|
+
dispatchId,
|
|
1362
|
+
taskId,
|
|
1363
|
+
runtime: dispatchRuntime,
|
|
1364
|
+
model,
|
|
1365
|
+
effort,
|
|
1366
|
+
transport,
|
|
1367
|
+
projectRoot,
|
|
1368
|
+
...(flags.contextPackPath || flags['context-pack-path'] ? { contextPackPath: flags.contextPackPath || flags['context-pack-path'] } : {}),
|
|
1369
|
+
...(contextRefs !== undefined ? { contextRefs } : {}),
|
|
1370
|
+
...(flags.requestId || flags['request-id'] ? { requestId: flags.requestId || flags['request-id'] } : {}),
|
|
1371
|
+
...(flags.replyTo || flags['reply-to'] ? { replyTo: flags.replyTo || flags['reply-to'] } : {}),
|
|
1372
|
+
};
|
|
1373
|
+
}
|
|
1374
|
+
const rawSubagentMode = flags['subagent-mode'] || flags.subagentMode || 'built-in-subagents';
|
|
1375
|
+
// Backward compat: legacy 'wf-subagents' callers resolve to the canonical
|
|
1376
|
+
// 'wf-node-subagents' id; unspecified defaults to built-in-subagents.
|
|
1377
|
+
const subagentMode = rawSubagentMode === 'wf-subagents' ? 'wf-node-subagents' : rawSubagentMode;
|
|
1378
|
+
const body = {
|
|
1379
|
+
runtime,
|
|
1380
|
+
agentKind,
|
|
1381
|
+
role: flags.role || (agentKind === 'main' ? 'Main Agent' : 'Subagent'),
|
|
1382
|
+
objective: flags.objective || 'Spawned by Harness Workflow Main Agent',
|
|
1383
|
+
...(flags['initial-prompt'] || flags.initialPrompt ? { initialPrompt: flags['initial-prompt'] || flags.initialPrompt } : {}),
|
|
1384
|
+
workflowMode: flags.mode || process.env.HARNESS_WORKFLOW_MODE || 'wf',
|
|
1385
|
+
subagentMode,
|
|
1386
|
+
cwd: flags.cwd || projectRoot,
|
|
1387
|
+
model: flags.model || '',
|
|
1388
|
+
provider: flags.provider || '',
|
|
1389
|
+
parentAgentId: flags.parent || process.env.HARNESS_PEER_SESSION_ID || null,
|
|
1390
|
+
parentNodeId: flags['parent-node'] || process.env.HARNESS_WORKFLOW_NODE_ID || null,
|
|
1391
|
+
launchPolicy: {
|
|
1392
|
+
sandboxMode: 'danger-full-access',
|
|
1393
|
+
approvalPolicy: 'never',
|
|
1394
|
+
},
|
|
1395
|
+
...(dispatchFields || {}),
|
|
1396
|
+
};
|
|
1397
|
+
if (trueFlag(flags.deferPtySpawn) || trueFlag(flags['defer-pty-spawn']) || trueFlag(flags.defer)) {
|
|
1398
|
+
body.deferPtySpawn = true;
|
|
1399
|
+
}
|
|
1400
|
+
return apiJson(cp.url, cp.token, '/api/sessions', { method: 'POST', body });
|
|
1401
|
+
}
|
|
1402
|
+
|
|
1403
|
+
async function sendInput(flags) {
|
|
1404
|
+
assertMainAgent(flags, 'send-input');
|
|
1405
|
+
const cp = controlPlane(flags);
|
|
1406
|
+
if (!cp.url) throw new Error('Missing HARNESS_WF_UI_URL for terminal input.');
|
|
1407
|
+
if (!flags.session) throw new Error('Missing --session <sessionId>.');
|
|
1408
|
+
const raw = flags.raw === 'true';
|
|
1409
|
+
const text = flags.text || '';
|
|
1410
|
+
const data = raw || /[\r\n]$/.test(text) ? text : `${text}\r`;
|
|
1411
|
+
const actorSessionId = flags.from || flags['from-session'] || process.env.HARNESS_PEER_SESSION_ID || '';
|
|
1412
|
+
return apiJson(cp.url, cp.token, `/api/sessions/${encodeURIComponent(flags.session)}/input`, {
|
|
1413
|
+
method: 'POST',
|
|
1414
|
+
actorSessionId,
|
|
1415
|
+
body: {
|
|
1416
|
+
data,
|
|
1417
|
+
fromSessionId: actorSessionId,
|
|
1418
|
+
fromNodeId: flags['from-node'] || process.env.HARNESS_WORKFLOW_NODE_ID || '',
|
|
1419
|
+
source: 'wf-ui-control.send-input',
|
|
1420
|
+
},
|
|
1421
|
+
});
|
|
1422
|
+
}
|
|
1423
|
+
|
|
1424
|
+
// Named keystrokes mapped to the raw bytes a terminal emulator expects.
|
|
1425
|
+
const SEND_KEY_BYTES = {
|
|
1426
|
+
up: '\x1b[A',
|
|
1427
|
+
down: '\x1b[B',
|
|
1428
|
+
left: '\x1b[D',
|
|
1429
|
+
right: '\x1b[C',
|
|
1430
|
+
enter: '\r',
|
|
1431
|
+
esc: '\x1b',
|
|
1432
|
+
tab: '\t',
|
|
1433
|
+
backspace: '\x7f',
|
|
1434
|
+
};
|
|
1435
|
+
|
|
1436
|
+
function sendKeyBytes(key) {
|
|
1437
|
+
const normalized = String(key || '').trim().toLowerCase();
|
|
1438
|
+
if (!Object.prototype.hasOwnProperty.call(SEND_KEY_BYTES, normalized)) {
|
|
1439
|
+
throw new Error(`Unknown key "${String(key)}". Expected one of: ${Object.keys(SEND_KEY_BYTES).join(', ')}.`);
|
|
1440
|
+
}
|
|
1441
|
+
return SEND_KEY_BYTES[normalized];
|
|
1442
|
+
}
|
|
1443
|
+
|
|
1444
|
+
// Resolve the target session for send-key: an explicit --session wins;
|
|
1445
|
+
// otherwise resolve a --node id to its managed session through the local
|
|
1446
|
+
// workflow graph map so node ids and session ids both work.
|
|
1447
|
+
function sendKeyTargetSessionId(projectRoot, flags) {
|
|
1448
|
+
if (flags.session) return flags.session;
|
|
1449
|
+
const nodeId = flags.node || flags['target-node'] || process.env.HARNESS_WORKFLOW_NODE_ID || '';
|
|
1450
|
+
if (!nodeId) throw new Error('Missing --session <sessionId> (or --node <graphNodeIdOrSessionId>) for send-key.');
|
|
1451
|
+
const mapPath = process.env.HARNESS_WORKFLOW_MAP || path.join(projectRoot, 'Harness', 'a2a', 'workflow-map.json');
|
|
1452
|
+
const graph = readJson(mapPath, { nodes: [] });
|
|
1453
|
+
const node = (graph.nodes || []).find(item =>
|
|
1454
|
+
(item.nodeId || item.id) === nodeId || item.sessionId === nodeId
|
|
1455
|
+
);
|
|
1456
|
+
const sessionId = node?.sessionId || '';
|
|
1457
|
+
if (!sessionId) {
|
|
1458
|
+
throw new Error(`send-key: --node ${nodeId} does not resolve to a managed session in the workflow map.`);
|
|
1459
|
+
}
|
|
1460
|
+
return sessionId;
|
|
1461
|
+
}
|
|
1462
|
+
|
|
1463
|
+
async function sendKey(projectRoot, flags, args = []) {
|
|
1464
|
+
assertMainAgent(flags, 'send-key');
|
|
1465
|
+
const cp = controlPlane(flags);
|
|
1466
|
+
if (!cp.url) throw new Error('Missing HARNESS_WF_UI_URL for terminal input.');
|
|
1467
|
+
const key = flags.key || args[0] || '';
|
|
1468
|
+
const data = sendKeyBytes(key);
|
|
1469
|
+
const sessionId = sendKeyTargetSessionId(projectRoot, flags);
|
|
1470
|
+
const actorSessionId = flags.from || flags['from-session'] || process.env.HARNESS_PEER_SESSION_ID || '';
|
|
1471
|
+
return apiJson(cp.url, cp.token, `/api/sessions/${encodeURIComponent(sessionId)}/input`, {
|
|
1472
|
+
method: 'POST',
|
|
1473
|
+
actorSessionId,
|
|
1474
|
+
body: {
|
|
1475
|
+
data,
|
|
1476
|
+
raw: true,
|
|
1477
|
+
fromSessionId: actorSessionId,
|
|
1478
|
+
fromNodeId: flags['from-node'] || process.env.HARNESS_WORKFLOW_NODE_ID || '',
|
|
1479
|
+
source: 'wf-ui-control.send-key',
|
|
1480
|
+
},
|
|
1481
|
+
});
|
|
1482
|
+
}
|
|
1483
|
+
|
|
1484
|
+
async function bridgeMessages(flags) {
|
|
1485
|
+
const cp = controlPlane(flags);
|
|
1486
|
+
if (!cp.url) throw new Error('Missing HARNESS_WF_UI_URL for bridge messages.');
|
|
1487
|
+
const fromSessionId = flags.from || flags['from-session'] || process.env.HARNESS_PEER_SESSION_ID || '';
|
|
1488
|
+
const toSessionId = flags.to || flags.session || flags['to-session'] || '';
|
|
1489
|
+
if (!fromSessionId) throw new Error('Missing --from <sessionId> or HARNESS_PEER_SESSION_ID.');
|
|
1490
|
+
if (!toSessionId) throw new Error('Missing --to <sessionId> or --session <sessionId>.');
|
|
1491
|
+
const limit = flags.limit || flags.tail || 200;
|
|
1492
|
+
return apiJson(cp.url, cp.token, `/api/a2a/bridge-messages?fromSessionId=${encodeURIComponent(fromSessionId)}&toSessionId=${encodeURIComponent(toSessionId)}&limit=${encodeURIComponent(limit)}`);
|
|
1493
|
+
}
|
|
1494
|
+
|
|
1495
|
+
function nodeMapActorId(flags) {
|
|
1496
|
+
const actor = flags.actor
|
|
1497
|
+
|| flags['actor-node']
|
|
1498
|
+
|| flags.actorNodeId
|
|
1499
|
+
|| process.env.HARNESS_WORKFLOW_NODE_ID
|
|
1500
|
+
|| process.env.HARNESS_PEER_SESSION_ID
|
|
1501
|
+
|| '';
|
|
1502
|
+
if (!actor) throw new Error('Missing --actor <graphNodeIdOrSessionId> or HARNESS_WORKFLOW_NODE_ID for node-map control.');
|
|
1503
|
+
return actor;
|
|
1504
|
+
}
|
|
1505
|
+
|
|
1506
|
+
async function nodeMapControlPlane(flags) {
|
|
1507
|
+
await assertMainAgent(flags, 'node-map');
|
|
1508
|
+
const cp = controlPlane(flags);
|
|
1509
|
+
if (!cp.url) throw new Error('Missing HARNESS_WF_UI_URL for node-map control.');
|
|
1510
|
+
return cp;
|
|
1511
|
+
}
|
|
1512
|
+
|
|
1513
|
+
function workflowApiControlPlane(flags, action) {
|
|
1514
|
+
const cp = controlPlane(flags);
|
|
1515
|
+
if (!cp.url) throw new Error(`Missing HARNESS_WF_UI_URL for ${action}.`);
|
|
1516
|
+
return cp;
|
|
1517
|
+
}
|
|
1518
|
+
|
|
1519
|
+
function workflowTargetNodeId(flags, action) {
|
|
1520
|
+
const targetNodeId = flags.node
|
|
1521
|
+
|| flags.target
|
|
1522
|
+
|| flags.id
|
|
1523
|
+
|| flags.session
|
|
1524
|
+
|| flags['target-node']
|
|
1525
|
+
|| process.env.HARNESS_WORKFLOW_NODE_ID
|
|
1526
|
+
|| process.env.HARNESS_PEER_SESSION_ID
|
|
1527
|
+
|| '';
|
|
1528
|
+
if (!targetNodeId) throw new Error(`Missing --node <graphNodeIdOrSessionId> for ${action}.`);
|
|
1529
|
+
return targetNodeId;
|
|
1530
|
+
}
|
|
1531
|
+
|
|
1532
|
+
function parseJsonObjectFlag(value, label) {
|
|
1533
|
+
if (!value) return {};
|
|
1534
|
+
try {
|
|
1535
|
+
const parsed = JSON.parse(value);
|
|
1536
|
+
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) return parsed;
|
|
1537
|
+
} catch {
|
|
1538
|
+
throw new Error(`${label} must be a JSON object.`);
|
|
1539
|
+
}
|
|
1540
|
+
throw new Error(`${label} must be a JSON object.`);
|
|
1541
|
+
}
|
|
1542
|
+
|
|
1543
|
+
function optionalNumber(value) {
|
|
1544
|
+
if (value === undefined || value === null || value === '') return undefined;
|
|
1545
|
+
const parsed = Number(value);
|
|
1546
|
+
return Number.isFinite(parsed) ? parsed : undefined;
|
|
1547
|
+
}
|
|
1548
|
+
|
|
1549
|
+
function normalizeNodeMapAction(value) {
|
|
1550
|
+
const raw = String(value || '').trim().replace(/^agent\./, '');
|
|
1551
|
+
const compact = raw.replace(/[-_\s]+/g, '').toLowerCase();
|
|
1552
|
+
const aliases = {
|
|
1553
|
+
read: 'readGraph',
|
|
1554
|
+
readgraph: 'readGraph',
|
|
1555
|
+
describe: 'readGraph',
|
|
1556
|
+
snapshot: 'readGraph',
|
|
1557
|
+
create: 'createNode',
|
|
1558
|
+
createnode: 'createNode',
|
|
1559
|
+
add: 'createNode',
|
|
1560
|
+
addnode: 'createNode',
|
|
1561
|
+
connect: 'connectNodes',
|
|
1562
|
+
connectnode: 'connectNodes',
|
|
1563
|
+
connectnodes: 'connectNodes',
|
|
1564
|
+
disconnect: 'disconnectNodes',
|
|
1565
|
+
disconnectnode: 'disconnectNodes',
|
|
1566
|
+
disconnectnodes: 'disconnectNodes',
|
|
1567
|
+
update: 'updateEdge',
|
|
1568
|
+
updateedge: 'updateEdge',
|
|
1569
|
+
move: 'moveNode',
|
|
1570
|
+
movenode: 'moveNode',
|
|
1571
|
+
delete: 'deleteNode',
|
|
1572
|
+
deletenode: 'deleteNode',
|
|
1573
|
+
deleteone: 'deleteNode',
|
|
1574
|
+
deletenodes: 'deleteNodes',
|
|
1575
|
+
deleteall: 'deleteNodes',
|
|
1576
|
+
clear: 'deleteNodes',
|
|
1577
|
+
clearmap: 'deleteNodes',
|
|
1578
|
+
layout: 'layout',
|
|
1579
|
+
relayout: 'layout',
|
|
1580
|
+
arrange: 'layout',
|
|
1581
|
+
attach: 'attachDock',
|
|
1582
|
+
attachdock: 'attachDock',
|
|
1583
|
+
dock: 'attachDock',
|
|
1584
|
+
detach: 'detachDock',
|
|
1585
|
+
detachdock: 'detachDock',
|
|
1586
|
+
undock: 'detachDock',
|
|
1587
|
+
setside: 'setDockSide',
|
|
1588
|
+
setdockside: 'setDockSide',
|
|
1589
|
+
dockside: 'setDockSide',
|
|
1590
|
+
undo: 'undo',
|
|
1591
|
+
undograph: 'undo',
|
|
1592
|
+
redo: 'redo',
|
|
1593
|
+
redograph: 'redo',
|
|
1594
|
+
};
|
|
1595
|
+
const action = aliases[compact];
|
|
1596
|
+
if (!action) {
|
|
1597
|
+
throw new Error('Missing or unknown node-map --action. Use readGraph, createNode, connectNodes, disconnectNodes, updateEdge, moveNode, deleteNode, deleteNodes, attachDock, detachDock, setDockSide, layout, undo, or redo.');
|
|
1598
|
+
}
|
|
1599
|
+
return action;
|
|
1600
|
+
}
|
|
1601
|
+
|
|
1602
|
+
function nodeMapPayloadForAction(actionName, flags, actorNodeId) {
|
|
1603
|
+
const payload = {
|
|
1604
|
+
...parseJsonObjectFlag(flags.payload || flags.body || flags.json, '--payload'),
|
|
1605
|
+
actorNodeId,
|
|
1606
|
+
};
|
|
1607
|
+
if (actionName === 'createNode') {
|
|
1608
|
+
if (flags.type) payload.type = flags.type;
|
|
1609
|
+
if (flags.title) payload.title = flags.title;
|
|
1610
|
+
if (flags.node && !payload.nodeId) payload.nodeId = flags.node;
|
|
1611
|
+
const x = optionalNumber(flags.x);
|
|
1612
|
+
const y = optionalNumber(flags.y);
|
|
1613
|
+
if (x !== undefined || y !== undefined) {
|
|
1614
|
+
payload.position = {
|
|
1615
|
+
...(payload.position && typeof payload.position === 'object' ? payload.position : {}),
|
|
1616
|
+
...(x !== undefined ? { x } : {}),
|
|
1617
|
+
...(y !== undefined ? { y } : {}),
|
|
1618
|
+
};
|
|
1619
|
+
}
|
|
1620
|
+
}
|
|
1621
|
+
if (actionName === 'connectNodes') {
|
|
1622
|
+
payload.from = flags.from || flags.source || flags['source-node'] || payload.from || payload.source || actorNodeId;
|
|
1623
|
+
payload.to = flags.to || flags.target || flags.session || flags['target-node'] || payload.to || payload.target;
|
|
1624
|
+
if (flags.relation) payload.relation = flags.relation;
|
|
1625
|
+
if (flags['source-handle']) payload.sourceHandle = flags['source-handle'];
|
|
1626
|
+
if (flags['target-handle']) payload.targetHandle = flags['target-handle'];
|
|
1627
|
+
if (flags.direction) payload.direction = flags.direction;
|
|
1628
|
+
if (!payload.from || !payload.to) throw new Error('node-map connectNodes requires --to <nodeOrSession>; --from defaults to the actor.');
|
|
1629
|
+
}
|
|
1630
|
+
if (actionName === 'disconnectNodes') {
|
|
1631
|
+
payload.edgeId = flags.edge || flags['edge-id'] || flags.id || payload.edgeId || payload.id;
|
|
1632
|
+
if (!payload.edgeId) throw new Error('node-map disconnectNodes requires --edge <edgeId>.');
|
|
1633
|
+
}
|
|
1634
|
+
if (actionName === 'updateEdge') {
|
|
1635
|
+
payload.edgeId = flags.edge || flags['edge-id'] || flags.id || payload.edgeId || payload.id;
|
|
1636
|
+
if (!payload.edgeId && flags.from && flags.to) {
|
|
1637
|
+
payload.edgeId = `${flags.from}->${flags.to}`;
|
|
1638
|
+
}
|
|
1639
|
+
if (flags.relation) payload.relation = flags.relation;
|
|
1640
|
+
if (flags.direction) payload.direction = flags.direction;
|
|
1641
|
+
if (flags['source-handle']) payload.sourceHandle = flags['source-handle'];
|
|
1642
|
+
if (flags['target-handle']) payload.targetHandle = flags['target-handle'];
|
|
1643
|
+
if (!payload.edgeId) {
|
|
1644
|
+
throw new Error('node-map updateEdge requires --edge <edgeId> (or --from <node> --to <node>).');
|
|
1645
|
+
}
|
|
1646
|
+
if (
|
|
1647
|
+
payload.relation === undefined
|
|
1648
|
+
&& payload.direction === undefined
|
|
1649
|
+
&& payload.sourceHandle === undefined
|
|
1650
|
+
&& payload.targetHandle === undefined
|
|
1651
|
+
) {
|
|
1652
|
+
throw new Error('node-map updateEdge requires at least one of --relation, --direction, --source-handle, --target-handle.');
|
|
1653
|
+
}
|
|
1654
|
+
}
|
|
1655
|
+
if (actionName === 'moveNode') {
|
|
1656
|
+
payload.targetNodeId = flags.node || flags.target || flags.id || payload.targetNodeId || payload.nodeId || payload.id;
|
|
1657
|
+
const x = optionalNumber(flags.x);
|
|
1658
|
+
const y = optionalNumber(flags.y);
|
|
1659
|
+
payload.position = {
|
|
1660
|
+
...(payload.position && typeof payload.position === 'object' ? payload.position : {}),
|
|
1661
|
+
...(x !== undefined ? { x } : {}),
|
|
1662
|
+
...(y !== undefined ? { y } : {}),
|
|
1663
|
+
};
|
|
1664
|
+
if (!payload.targetNodeId) throw new Error('node-map moveNode requires --node <nodeId>.');
|
|
1665
|
+
if (payload.position.x === undefined || payload.position.y === undefined) throw new Error('node-map moveNode requires --x <number> and --y <number>.');
|
|
1666
|
+
}
|
|
1667
|
+
if (actionName === 'deleteNode') {
|
|
1668
|
+
payload.targetNodeId = flags.node || flags.target || flags.id || payload.targetNodeId || payload.nodeId || payload.id;
|
|
1669
|
+
if (trueFlag(flags.force)) payload.force = true;
|
|
1670
|
+
if (trueFlag(flags['allow-live-agent-delete'])) payload.allowLiveAgentDelete = true;
|
|
1671
|
+
if (!payload.targetNodeId) throw new Error('node-map deleteNode requires --node <nodeIdOrSessionId>.');
|
|
1672
|
+
}
|
|
1673
|
+
if (actionName === 'deleteNodes') {
|
|
1674
|
+
const targetNodeIds = [
|
|
1675
|
+
...listFlag(flags.nodes || flags['node-ids'] || flags.ids),
|
|
1676
|
+
...listFlag(flags.node || ''),
|
|
1677
|
+
];
|
|
1678
|
+
if (targetNodeIds.length > 0) payload.targetNodeIds = targetNodeIds;
|
|
1679
|
+
if (trueFlag(flags.all) || String(flags.all || '').toLowerCase() === 'all') payload.all = true;
|
|
1680
|
+
if (trueFlag(flags.force)) payload.force = true;
|
|
1681
|
+
if (trueFlag(flags['allow-live-agent-delete'])) payload.allowLiveAgentDelete = true;
|
|
1682
|
+
if (!payload.all && !payload.targetNodeIds?.length && !payload.nodeIds?.length && !payload.ids?.length) {
|
|
1683
|
+
throw new Error('node-map deleteNodes requires --all true or --nodes <nodeId,...>.');
|
|
1684
|
+
}
|
|
1685
|
+
}
|
|
1686
|
+
if (actionName === 'layout') {
|
|
1687
|
+
const mode = flags['layout-mode'] || flags.mode || payload.mode;
|
|
1688
|
+
if (mode) payload.mode = mode;
|
|
1689
|
+
const originX = optionalNumber(flags['origin-x']);
|
|
1690
|
+
const originY = optionalNumber(flags['origin-y']);
|
|
1691
|
+
const gapX = optionalNumber(flags['gap-x']);
|
|
1692
|
+
const gapY = optionalNumber(flags['gap-y']);
|
|
1693
|
+
if (originX !== undefined) payload.originX = originX;
|
|
1694
|
+
if (originY !== undefined) payload.originY = originY;
|
|
1695
|
+
if (gapX !== undefined) payload.gapX = gapX;
|
|
1696
|
+
if (gapY !== undefined) payload.gapY = gapY;
|
|
1697
|
+
}
|
|
1698
|
+
if (actionName === 'attachDock' || actionName === 'detachDock' || actionName === 'setDockSide') {
|
|
1699
|
+
payload.anchorId = flags.anchor || flags['anchor-id'] || payload.anchorId;
|
|
1700
|
+
payload.draggedId = flags.dragged || flags['dragged-id'] || payload.draggedId;
|
|
1701
|
+
if (flags.side) payload.side = flags.side;
|
|
1702
|
+
const dockId = flags.dock || flags['dock-id'] || payload.dockId;
|
|
1703
|
+
if (dockId) payload.dockId = dockId;
|
|
1704
|
+
if (!payload.dockId && (!payload.anchorId || !payload.draggedId)) {
|
|
1705
|
+
throw new Error(`node-map ${actionName} requires --anchor <nodeId> and --dragged <nodeId> (or --dock-id <id> for detachDock).`);
|
|
1706
|
+
}
|
|
1707
|
+
if (payload.anchorId === payload.draggedId) {
|
|
1708
|
+
throw new Error(`node-map ${actionName} requires distinct anchor and dragged nodes.`);
|
|
1709
|
+
}
|
|
1710
|
+
if (actionName === 'setDockSide' && !payload.side) {
|
|
1711
|
+
throw new Error('node-map setDockSide requires --side <left|right|top|bottom>.');
|
|
1712
|
+
}
|
|
1713
|
+
}
|
|
1714
|
+
return payload;
|
|
1715
|
+
}
|
|
1716
|
+
|
|
1717
|
+
async function agentGraphAction(flags, actionName, payload = {}) {
|
|
1718
|
+
const cp = await nodeMapControlPlane(flags);
|
|
1719
|
+
const actorNodeId = nodeMapActorId(flags);
|
|
1720
|
+
return apiJson(cp.url, cp.token, `/api/workflow/nodes/${encodeURIComponent(actorNodeId)}/actions/agent.${actionName}`, {
|
|
1721
|
+
method: 'POST',
|
|
1722
|
+
body: {
|
|
1723
|
+
actorNodeId,
|
|
1724
|
+
...payload,
|
|
1725
|
+
},
|
|
1726
|
+
});
|
|
1727
|
+
}
|
|
1728
|
+
|
|
1729
|
+
// P5: graph.undo / graph.redo are typed graph actions (not agent.* actions);
|
|
1730
|
+
// the CLI posts them to the graph-actions endpoint with {actorNodeId, scope}.
|
|
1731
|
+
async function graphHistoryAction(flags, actionName) {
|
|
1732
|
+
const cp = await nodeMapControlPlane(flags);
|
|
1733
|
+
const actorNodeId = nodeMapActorId(flags);
|
|
1734
|
+
return apiJson(cp.url, cp.token, `/api/workflow/nodes/${encodeURIComponent(actorNodeId)}/actions/graph.${actionName}`, {
|
|
1735
|
+
method: 'POST',
|
|
1736
|
+
body: {
|
|
1737
|
+
actorNodeId,
|
|
1738
|
+
scope: flags.scope || 'graph',
|
|
1739
|
+
},
|
|
1740
|
+
});
|
|
1741
|
+
}
|
|
1742
|
+
|
|
1743
|
+
async function nodeMap(projectRoot, flags) {
|
|
1744
|
+
const actionName = normalizeNodeMapAction(flags.action || flags.command || flags.do);
|
|
1745
|
+
const actorNodeId = nodeMapActorId(flags);
|
|
1746
|
+
const payload = nodeMapPayloadForAction(actionName, flags, actorNodeId);
|
|
1747
|
+
if (actionName === 'createNode' && !payload.cwd) payload.cwd = projectRoot;
|
|
1748
|
+
if (actionName === 'undo' || actionName === 'redo') {
|
|
1749
|
+
return graphHistoryAction(flags, actionName);
|
|
1750
|
+
}
|
|
1751
|
+
return agentGraphAction(flags, actionName, payload);
|
|
1752
|
+
}
|
|
1753
|
+
|
|
1754
|
+
async function workflowContext(flags) {
|
|
1755
|
+
const cp = controlPlane(flags);
|
|
1756
|
+
const token = cp.token || cp.readToken;
|
|
1757
|
+
if (!cp.url) throw new Error('Missing HARNESS_WF_UI_URL for workflow-context.');
|
|
1758
|
+
const targetNodeId = workflowTargetNodeId(flags, 'workflow-context');
|
|
1759
|
+
const result = await apiJson(cp.url, token, `/api/workflow/context/${encodeURIComponent(targetNodeId)}`, {
|
|
1760
|
+
actorSessionId: process.env.HARNESS_PEER_SESSION_ID || '',
|
|
1761
|
+
});
|
|
1762
|
+
const subagentMode = result?.context?.subagentMode
|
|
1763
|
+
|| result?.node?.subagentMode
|
|
1764
|
+
|| result?.node?.settings?.values?.subagentMode
|
|
1765
|
+
|| '';
|
|
1766
|
+
return subagentMode ? { ...result, subagentMode } : result;
|
|
1767
|
+
}
|
|
1768
|
+
|
|
1769
|
+
async function readNode(flags) {
|
|
1770
|
+
const cp = controlPlane(flags);
|
|
1771
|
+
const token = cp.token || cp.readToken;
|
|
1772
|
+
if (!cp.url) throw new Error('Missing HARNESS_WF_UI_URL for read-node.');
|
|
1773
|
+
const targetNodeId = workflowTargetNodeId(flags, 'read-node');
|
|
1774
|
+
return apiJson(cp.url, token, `/api/workflow/nodes/${encodeURIComponent(targetNodeId)}`, {
|
|
1775
|
+
actorSessionId: process.env.HARNESS_PEER_SESSION_ID || '',
|
|
1776
|
+
});
|
|
1777
|
+
}
|
|
1778
|
+
|
|
1779
|
+
async function workflowOntology(flags) {
|
|
1780
|
+
const cp = controlPlane(flags);
|
|
1781
|
+
const token = cp.token || cp.readToken;
|
|
1782
|
+
if (!cp.url) throw new Error('Missing HARNESS_WF_UI_URL for workflow-ontology.');
|
|
1783
|
+
return apiJson(cp.url, token, '/api/workflow/ontology', {
|
|
1784
|
+
actorSessionId: process.env.HARNESS_PEER_SESSION_ID || '',
|
|
1785
|
+
});
|
|
1786
|
+
}
|
|
1787
|
+
|
|
1788
|
+
async function workflowNodeAction(flags) {
|
|
1789
|
+
const cp = workflowApiControlPlane(flags, 'workflow-node-action');
|
|
1790
|
+
const targetNodeId = workflowTargetNodeId(flags, 'workflow-node-action');
|
|
1791
|
+
const actionName = String(flags.action || flags.command || flags.do || '').trim();
|
|
1792
|
+
if (!actionName) throw new Error('Missing --action <type.action> for workflow-node-action.');
|
|
1793
|
+
const payload = parseJsonObjectFlag(flags.payload || flags.body || flags.json, '--payload');
|
|
1794
|
+
const actorNodeId = flags.actor
|
|
1795
|
+
|| flags['actor-node']
|
|
1796
|
+
|| flags.actorNodeId
|
|
1797
|
+
|| process.env.HARNESS_WORKFLOW_NODE_ID
|
|
1798
|
+
|| '';
|
|
1799
|
+
const actorSessionId = flags['actor-session']
|
|
1800
|
+
|| flags.actorSessionId
|
|
1801
|
+
|| process.env.HARNESS_PEER_SESSION_ID
|
|
1802
|
+
|| '';
|
|
1803
|
+
const actorKind = flags['actor-kind']
|
|
1804
|
+
|| flags.actorKind
|
|
1805
|
+
|| process.env.HARNESS_AGENT_KIND
|
|
1806
|
+
|| '';
|
|
1807
|
+
if (actorNodeId && payload.actorNodeId === undefined) payload.actorNodeId = actorNodeId;
|
|
1808
|
+
if (actorSessionId && payload.actorSessionId === undefined) payload.actorSessionId = actorSessionId;
|
|
1809
|
+
if (actorKind && payload.actorKind === undefined) payload.actorKind = actorKind;
|
|
1810
|
+
if ((actorNodeId || actorSessionId || actorKind) && payload.actorType === undefined) payload.actorType = 'agent';
|
|
1811
|
+
if (flags.resume !== undefined && payload.resume === undefined) payload.resume = trueFlag(flags.resume);
|
|
1812
|
+
return apiJson(cp.url, cp.token, `/api/workflow/nodes/${encodeURIComponent(targetNodeId)}/actions/${encodeURIComponent(actionName)}`, {
|
|
1813
|
+
method: 'POST',
|
|
1814
|
+
body: payload,
|
|
1815
|
+
actorSessionId,
|
|
1816
|
+
actorNodeId,
|
|
1817
|
+
actorKind,
|
|
1818
|
+
actorType: payload.actorType || ((actorNodeId || actorSessionId || actorKind) ? 'agent' : ''),
|
|
1819
|
+
});
|
|
1820
|
+
}
|
|
1821
|
+
|
|
1822
|
+
async function delegateAgent(flags) {
|
|
1823
|
+
const cp = workflowApiControlPlane(flags, 'delegate-agent');
|
|
1824
|
+
const targetNodeId = workflowTargetNodeId(flags, 'delegate-agent');
|
|
1825
|
+
const actorNodeId = flags.actor
|
|
1826
|
+
|| flags['actor-node']
|
|
1827
|
+
|| flags.actorNodeId
|
|
1828
|
+
|| process.env.HARNESS_WORKFLOW_NODE_ID
|
|
1829
|
+
|| '';
|
|
1830
|
+
const actorSessionId = flags['actor-session']
|
|
1831
|
+
|| flags.actorSessionId
|
|
1832
|
+
|| flags.from
|
|
1833
|
+
|| flags['from-session']
|
|
1834
|
+
|| process.env.HARNESS_PEER_SESSION_ID
|
|
1835
|
+
|| '';
|
|
1836
|
+
const actorKind = flags['actor-kind']
|
|
1837
|
+
|| flags.actorKind
|
|
1838
|
+
|| process.env.HARNESS_AGENT_KIND
|
|
1839
|
+
|| '';
|
|
1840
|
+
const raw = flags.raw === 'true';
|
|
1841
|
+
const text = flags.text || flags.input || '';
|
|
1842
|
+
if (!text) throw new Error('Missing --text "..." for delegate-agent.');
|
|
1843
|
+
const data = raw || /[\r\n]$/.test(text) ? text : `${text}\r`;
|
|
1844
|
+
return apiJson(cp.url, cp.token, `/api/workflow/nodes/${encodeURIComponent(targetNodeId)}/actions/agent.sendInput`, {
|
|
1845
|
+
method: 'POST',
|
|
1846
|
+
body: {
|
|
1847
|
+
data,
|
|
1848
|
+
submit: !raw,
|
|
1849
|
+
fromSessionId: actorSessionId,
|
|
1850
|
+
fromNodeId: actorNodeId,
|
|
1851
|
+
source: 'wf-ui-control.delegate-agent',
|
|
1852
|
+
},
|
|
1853
|
+
actorSessionId,
|
|
1854
|
+
actorNodeId,
|
|
1855
|
+
actorKind,
|
|
1856
|
+
actorType: actorNodeId || actorSessionId || actorKind ? 'agent' : '',
|
|
1857
|
+
});
|
|
1858
|
+
}
|
|
1859
|
+
|
|
1860
|
+
async function readAgent(flags) {
|
|
1861
|
+
const action = String(flags.action || flags.do || 'readOutput').trim();
|
|
1862
|
+
const aliases = {
|
|
1863
|
+
output: 'agent.readOutput',
|
|
1864
|
+
readoutput: 'agent.readOutput',
|
|
1865
|
+
transcript: 'agent.readTranscript',
|
|
1866
|
+
readtranscript: 'agent.readTranscript',
|
|
1867
|
+
context: 'agent.readContext',
|
|
1868
|
+
readcontext: 'agent.readContext',
|
|
1869
|
+
};
|
|
1870
|
+
const normalized = aliases[action.replace(/[-_\s.]+/g, '').toLowerCase()] || action;
|
|
1871
|
+
const actionName = normalized.startsWith('agent.') ? normalized : `agent.${normalized}`;
|
|
1872
|
+
return workflowNodeAction({
|
|
1873
|
+
...flags,
|
|
1874
|
+
action: actionName,
|
|
1875
|
+
payload: flags.payload || flags.body || flags.json || JSON.stringify({
|
|
1876
|
+
...(flags.tail ? { tail: Number(flags.tail) } : {}),
|
|
1877
|
+
...(flags.fromSeq ? { fromSeq: Number(flags.fromSeq) } : {}),
|
|
1878
|
+
...(flags.toSeq ? { toSeq: Number(flags.toSeq) } : {}),
|
|
1879
|
+
}),
|
|
1880
|
+
});
|
|
1881
|
+
}
|
|
1882
|
+
|
|
1883
|
+
function workflowActorFields(flags) {
|
|
1884
|
+
const actorNodeId = flags.actor
|
|
1885
|
+
|| flags['actor-node']
|
|
1886
|
+
|| flags.actorNodeId
|
|
1887
|
+
|| process.env.HARNESS_WORKFLOW_NODE_ID
|
|
1888
|
+
|| '';
|
|
1889
|
+
const actorSessionId = flags['actor-session']
|
|
1890
|
+
|| flags.actorSessionId
|
|
1891
|
+
|| flags.from
|
|
1892
|
+
|| flags['from-session']
|
|
1893
|
+
|| process.env.HARNESS_PEER_SESSION_ID
|
|
1894
|
+
|| '';
|
|
1895
|
+
const actorKind = flags['actor-kind']
|
|
1896
|
+
|| flags.actorKind
|
|
1897
|
+
|| process.env.HARNESS_AGENT_KIND
|
|
1898
|
+
|| '';
|
|
1899
|
+
return { actorNodeId, actorSessionId, actorKind };
|
|
1900
|
+
}
|
|
1901
|
+
|
|
1902
|
+
function workflowSenderNodeId(flags, action) {
|
|
1903
|
+
const senderNodeId = flags.node
|
|
1904
|
+
|| flags.sender
|
|
1905
|
+
|| flags.fromNode
|
|
1906
|
+
|| flags['from-node']
|
|
1907
|
+
|| process.env.HARNESS_WORKFLOW_NODE_ID
|
|
1908
|
+
|| '';
|
|
1909
|
+
if (!senderNodeId) throw new Error(`Missing --node <senderAgentNodeId> or HARNESS_WORKFLOW_NODE_ID for ${action}.`);
|
|
1910
|
+
return senderNodeId;
|
|
1911
|
+
}
|
|
1912
|
+
|
|
1913
|
+
function agentMessagePayload(flags, mode) {
|
|
1914
|
+
const text = flags.text || flags.message || flags.input || flags.data || '';
|
|
1915
|
+
if (!text) throw new Error(`Missing --text "..." for ${mode}.`);
|
|
1916
|
+
const payload = {
|
|
1917
|
+
text,
|
|
1918
|
+
...(flags.topic ? { topic: flags.topic } : {}),
|
|
1919
|
+
...(flags.thread ? { threadId: flags.thread } : {}),
|
|
1920
|
+
...(flags['thread-id'] ? { threadId: flags['thread-id'] } : {}),
|
|
1921
|
+
...(flags.replyTo ? { replyTo: flags.replyTo } : {}),
|
|
1922
|
+
...(flags['reply-to'] ? { replyTo: flags['reply-to'] } : {}),
|
|
1923
|
+
...(flags.requestId ? { requestId: flags.requestId } : {}),
|
|
1924
|
+
...(flags['request-id'] ? { requestId: flags['request-id'] } : {}),
|
|
1925
|
+
...(flags.raw ? { raw: trueFlag(flags.raw) } : {}),
|
|
1926
|
+
};
|
|
1927
|
+
const targets = listFlag(flags.to || flags.target || flags.targets || flags.recipients || '');
|
|
1928
|
+
if (targets.length === 1) payload.to = targets[0];
|
|
1929
|
+
else if (targets.length > 1) payload.to = targets;
|
|
1930
|
+
return payload;
|
|
1931
|
+
}
|
|
1932
|
+
|
|
1933
|
+
async function agentMessageAction(flags, action, mode) {
|
|
1934
|
+
const cp = workflowApiControlPlane(flags, mode);
|
|
1935
|
+
const senderNodeId = workflowSenderNodeId(flags, mode);
|
|
1936
|
+
const actor = workflowActorFields(flags);
|
|
1937
|
+
const payload = {
|
|
1938
|
+
...agentMessagePayload(flags, mode),
|
|
1939
|
+
...(actor.actorNodeId ? { actorNodeId: actor.actorNodeId } : {}),
|
|
1940
|
+
...(actor.actorSessionId ? { actorSessionId: actor.actorSessionId } : {}),
|
|
1941
|
+
...(actor.actorKind ? { actorKind: actor.actorKind } : {}),
|
|
1942
|
+
actorType: actor.actorNodeId || actor.actorSessionId || actor.actorKind ? 'agent' : '',
|
|
1943
|
+
source: `wf-ui-control.${mode}`,
|
|
1944
|
+
};
|
|
1945
|
+
return apiJson(cp.url, cp.token, `/api/workflow/nodes/${encodeURIComponent(senderNodeId)}/actions/${action}`, {
|
|
1946
|
+
method: 'POST',
|
|
1947
|
+
body: payload,
|
|
1948
|
+
actorSessionId: actor.actorSessionId,
|
|
1949
|
+
actorNodeId: actor.actorNodeId,
|
|
1950
|
+
actorKind: actor.actorKind,
|
|
1951
|
+
actorType: payload.actorType,
|
|
1952
|
+
});
|
|
1953
|
+
}
|
|
1954
|
+
|
|
1955
|
+
async function sendAgentMessage(flags) {
|
|
1956
|
+
if (!(flags.to || flags.target)) throw new Error('Missing --to <agentNodeIdOrSessionId> for send-agent-message.');
|
|
1957
|
+
return agentMessageAction(flags, 'agent.sendMessage', 'send-agent-message');
|
|
1958
|
+
}
|
|
1959
|
+
|
|
1960
|
+
async function broadcastAgentMessage(flags) {
|
|
1961
|
+
return agentMessageAction(flags, 'agent.broadcastMessage', 'broadcast-agent-message');
|
|
1962
|
+
}
|
|
1963
|
+
|
|
1964
|
+
async function readAgentMessages(flags) {
|
|
1965
|
+
const cp = workflowApiControlPlane(flags, 'read-agent-messages');
|
|
1966
|
+
const senderNodeId = workflowSenderNodeId(flags, 'read-agent-messages');
|
|
1967
|
+
const peer = flags.peer || flags.to || flags.from || flags.target || '';
|
|
1968
|
+
if (!peer) throw new Error('Missing --peer <agentNodeIdOrSessionId> for read-agent-messages.');
|
|
1969
|
+
const actor = workflowActorFields(flags);
|
|
1970
|
+
const payload = {
|
|
1971
|
+
peer,
|
|
1972
|
+
...(flags.tail ? { tail: Number(flags.tail) } : {}),
|
|
1973
|
+
...(flags.limit ? { limit: Number(flags.limit) } : {}),
|
|
1974
|
+
...(actor.actorNodeId ? { actorNodeId: actor.actorNodeId } : {}),
|
|
1975
|
+
...(actor.actorSessionId ? { actorSessionId: actor.actorSessionId } : {}),
|
|
1976
|
+
...(actor.actorKind ? { actorKind: actor.actorKind } : {}),
|
|
1977
|
+
actorType: actor.actorNodeId || actor.actorSessionId || actor.actorKind ? 'agent' : '',
|
|
1978
|
+
};
|
|
1979
|
+
return apiJson(cp.url, cp.token, `/api/workflow/nodes/${encodeURIComponent(senderNodeId)}/actions/agent.readMessages`, {
|
|
1980
|
+
method: 'POST',
|
|
1981
|
+
body: payload,
|
|
1982
|
+
actorSessionId: actor.actorSessionId,
|
|
1983
|
+
actorNodeId: actor.actorNodeId,
|
|
1984
|
+
actorKind: actor.actorKind,
|
|
1985
|
+
actorType: payload.actorType,
|
|
1986
|
+
});
|
|
1987
|
+
}
|
|
1988
|
+
|
|
1989
|
+
function wfBrowserControlPlane(flags) {
|
|
1990
|
+
const cp = controlPlane(flags);
|
|
1991
|
+
if (!cp.url) throw new Error('Missing HARNESS_WF_UI_URL for wf-browser control.');
|
|
1992
|
+
return cp;
|
|
1993
|
+
}
|
|
1994
|
+
|
|
1995
|
+
const DEFAULT_BROWSER_SNAPSHOT_PRIMITIVES = [
|
|
1996
|
+
'observe.route',
|
|
1997
|
+
'observe.capabilities',
|
|
1998
|
+
'observe.uiTree',
|
|
1999
|
+
'observe.state',
|
|
2000
|
+
'observe.logs',
|
|
2001
|
+
'observe.network',
|
|
2002
|
+
'observe.replay',
|
|
2003
|
+
'observe.diff',
|
|
2004
|
+
];
|
|
2005
|
+
|
|
2006
|
+
function trueFlag(value) {
|
|
2007
|
+
return value === true || value === 'true' || value === '1' || value === 'yes';
|
|
2008
|
+
}
|
|
2009
|
+
|
|
2010
|
+
function listFlag(value) {
|
|
2011
|
+
return String(value || '')
|
|
2012
|
+
.split(/[,\n;]/)
|
|
2013
|
+
.map(item => item.trim())
|
|
2014
|
+
.filter(Boolean);
|
|
2015
|
+
}
|
|
2016
|
+
|
|
2017
|
+
function pickIndexed(values, index, fallback = '') {
|
|
2018
|
+
if (!values.length) return fallback;
|
|
2019
|
+
return values[index] || values[values.length - 1] || fallback;
|
|
2020
|
+
}
|
|
2021
|
+
|
|
2022
|
+
function boundedNumber(value, fallback, min, max) {
|
|
2023
|
+
const parsed = Number(value);
|
|
2024
|
+
const numeric = Number.isFinite(parsed) ? parsed : fallback;
|
|
2025
|
+
return Math.min(Math.max(numeric, min), max);
|
|
2026
|
+
}
|
|
2027
|
+
|
|
2028
|
+
function safePathSegment(value, fallback = 'item') {
|
|
2029
|
+
const text = String(value || fallback)
|
|
2030
|
+
.replace(/[^A-Za-z0-9._-]+/g, '-')
|
|
2031
|
+
.replace(/^\.+/, '')
|
|
2032
|
+
.replace(/^-+|-+$/g, '');
|
|
2033
|
+
return text || fallback;
|
|
2034
|
+
}
|
|
2035
|
+
|
|
2036
|
+
function sleep(ms) {
|
|
2037
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
2038
|
+
}
|
|
2039
|
+
|
|
2040
|
+
function openLocalUrl(url) {
|
|
2041
|
+
if (!url) return false;
|
|
2042
|
+
let command;
|
|
2043
|
+
let args;
|
|
2044
|
+
if (process.platform === 'win32') {
|
|
2045
|
+
command = 'cmd';
|
|
2046
|
+
args = ['/c', 'start', '', url];
|
|
2047
|
+
} else if (process.platform === 'darwin') {
|
|
2048
|
+
command = 'open';
|
|
2049
|
+
args = [url];
|
|
2050
|
+
} else {
|
|
2051
|
+
command = 'xdg-open';
|
|
2052
|
+
args = [url];
|
|
2053
|
+
}
|
|
2054
|
+
const child = spawn(command, args, {
|
|
2055
|
+
detached: true,
|
|
2056
|
+
stdio: 'ignore',
|
|
2057
|
+
windowsHide: true,
|
|
2058
|
+
});
|
|
2059
|
+
child.unref();
|
|
2060
|
+
return true;
|
|
2061
|
+
}
|
|
2062
|
+
|
|
2063
|
+
function existingFile(candidate) {
|
|
2064
|
+
try {
|
|
2065
|
+
return candidate && fs.existsSync(candidate) && fs.statSync(candidate).isFile();
|
|
2066
|
+
} catch {
|
|
2067
|
+
return false;
|
|
2068
|
+
}
|
|
2069
|
+
}
|
|
2070
|
+
|
|
2071
|
+
function browserCandidates(kind) {
|
|
2072
|
+
const browser = String(kind || 'chrome').toLowerCase();
|
|
2073
|
+
const candidates = [];
|
|
2074
|
+
const push = (value) => {
|
|
2075
|
+
if (value && !candidates.includes(value)) candidates.push(value);
|
|
2076
|
+
};
|
|
2077
|
+
if (process.platform === 'win32') {
|
|
2078
|
+
const programFiles = [
|
|
2079
|
+
process.env.PROGRAMFILES,
|
|
2080
|
+
process.env['PROGRAMFILES(X86)'],
|
|
2081
|
+
process.env['ProgramFiles(x86)'],
|
|
2082
|
+
process.env.LOCALAPPDATA,
|
|
2083
|
+
].filter(Boolean);
|
|
2084
|
+
if (browser === 'edge' || browser === 'msedge') {
|
|
2085
|
+
for (const root of programFiles) push(path.join(root, 'Microsoft', 'Edge', 'Application', 'msedge.exe'));
|
|
2086
|
+
} else if (browser === 'chromium') {
|
|
2087
|
+
for (const root of programFiles) push(path.join(root, 'Chromium', 'Application', 'chrome.exe'));
|
|
2088
|
+
} else {
|
|
2089
|
+
for (const root of programFiles) push(path.join(root, 'Google', 'Chrome', 'Application', 'chrome.exe'));
|
|
2090
|
+
for (const root of programFiles) push(path.join(root, 'Microsoft', 'Edge', 'Application', 'msedge.exe'));
|
|
2091
|
+
}
|
|
2092
|
+
} else if (process.platform === 'darwin') {
|
|
2093
|
+
if (browser === 'edge' || browser === 'msedge') {
|
|
2094
|
+
push('/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge');
|
|
2095
|
+
} else if (browser === 'chromium') {
|
|
2096
|
+
push('/Applications/Chromium.app/Contents/MacOS/Chromium');
|
|
2097
|
+
} else {
|
|
2098
|
+
push('/Applications/Google Chrome.app/Contents/MacOS/Google Chrome');
|
|
2099
|
+
push('/Applications/Chromium.app/Contents/MacOS/Chromium');
|
|
2100
|
+
push('/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge');
|
|
2101
|
+
}
|
|
2102
|
+
} else {
|
|
2103
|
+
if (browser === 'edge' || browser === 'msedge') {
|
|
2104
|
+
push('/usr/bin/microsoft-edge');
|
|
2105
|
+
push('/usr/bin/microsoft-edge-stable');
|
|
2106
|
+
} else if (browser === 'chromium') {
|
|
2107
|
+
push('/usr/bin/chromium');
|
|
2108
|
+
push('/usr/bin/chromium-browser');
|
|
2109
|
+
} else {
|
|
2110
|
+
push('/usr/bin/google-chrome');
|
|
2111
|
+
push('/usr/bin/google-chrome-stable');
|
|
2112
|
+
push('/usr/bin/chromium');
|
|
2113
|
+
push('/usr/bin/chromium-browser');
|
|
2114
|
+
push('/usr/bin/microsoft-edge');
|
|
2115
|
+
push('/usr/bin/microsoft-edge-stable');
|
|
2116
|
+
}
|
|
2117
|
+
}
|
|
2118
|
+
return candidates;
|
|
2119
|
+
}
|
|
2120
|
+
|
|
2121
|
+
function resolveBrowserExecutable(flags) {
|
|
2122
|
+
const explicit = flags.browserCommand || flags['browser-command'] || flags.browserPath || flags['browser-path'] || process.env.HARNESS_WF_BROWSER_BROWSER || '';
|
|
2123
|
+
if (explicit) return { command: explicit, source: 'explicit', candidates: [] };
|
|
2124
|
+
const candidates = browserCandidates(flags.browser || 'chrome');
|
|
2125
|
+
const found = candidates.find(existingFile);
|
|
2126
|
+
return {
|
|
2127
|
+
command: found || '',
|
|
2128
|
+
source: found ? 'detected' : 'missing',
|
|
2129
|
+
candidates,
|
|
2130
|
+
};
|
|
2131
|
+
}
|
|
2132
|
+
|
|
2133
|
+
function browserProfileDir(projectRoot, runId, windowId, flags) {
|
|
2134
|
+
const explicit = flags.profileDir || flags['profile-dir'] || '';
|
|
2135
|
+
if (explicit) return path.resolve(projectRoot, explicit);
|
|
2136
|
+
return path.join(
|
|
2137
|
+
projectRoot,
|
|
2138
|
+
'Harness',
|
|
2139
|
+
'wf-browser',
|
|
2140
|
+
'tmp',
|
|
2141
|
+
'browser-profiles',
|
|
2142
|
+
safePathSegment(runId, 'run'),
|
|
2143
|
+
safePathSegment(windowId, 'window')
|
|
2144
|
+
);
|
|
2145
|
+
}
|
|
2146
|
+
|
|
2147
|
+
function browserOpenArgs({ launchUrl, profileDir, context, width, height, flags }) {
|
|
2148
|
+
const args = [
|
|
2149
|
+
'--new-window',
|
|
2150
|
+
'--no-first-run',
|
|
2151
|
+
'--disable-default-apps',
|
|
2152
|
+
'--disable-background-mode',
|
|
2153
|
+
];
|
|
2154
|
+
if (context === 'isolated') args.push(`--user-data-dir=${profileDir}`);
|
|
2155
|
+
if (width && height) args.push(`--window-size=${width},${height}`);
|
|
2156
|
+
if (trueFlag(flags.app)) args.push(`--app=${launchUrl}`);
|
|
2157
|
+
else args.push(launchUrl);
|
|
2158
|
+
return args;
|
|
2159
|
+
}
|
|
2160
|
+
|
|
2161
|
+
function shouldUseBrowserOpen(flags) {
|
|
2162
|
+
return Boolean(
|
|
2163
|
+
flags.context
|
|
2164
|
+
|| flags['browser-context']
|
|
2165
|
+
|| flags.browser
|
|
2166
|
+
|| flags.browserCommand
|
|
2167
|
+
|| flags['browser-command']
|
|
2168
|
+
|| flags.browserPath
|
|
2169
|
+
|| flags['browser-path']
|
|
2170
|
+
|| flags.profileDir
|
|
2171
|
+
|| flags['profile-dir']
|
|
2172
|
+
|| trueFlag(flags.isolated)
|
|
2173
|
+
|| trueFlag(flags.dryRun)
|
|
2174
|
+
);
|
|
2175
|
+
}
|
|
2176
|
+
|
|
2177
|
+
function nowIso() {
|
|
2178
|
+
return new Date().toISOString();
|
|
2179
|
+
}
|
|
2180
|
+
|
|
2181
|
+
function wfBrowserWindowRuntimeDir(projectRoot, runId, windowId) {
|
|
2182
|
+
return path.join(
|
|
2183
|
+
projectRoot,
|
|
2184
|
+
'Harness',
|
|
2185
|
+
'wf-browser',
|
|
2186
|
+
'runs',
|
|
2187
|
+
safePathSegment(runId, 'run'),
|
|
2188
|
+
'windows',
|
|
2189
|
+
safePathSegment(windowId, 'window')
|
|
2190
|
+
);
|
|
2191
|
+
}
|
|
2192
|
+
|
|
2193
|
+
function browserLaunchesPath(projectRoot, runId, windowId) {
|
|
2194
|
+
return path.join(wfBrowserWindowRuntimeDir(projectRoot, runId, windowId), 'browser-launches.json');
|
|
2195
|
+
}
|
|
2196
|
+
|
|
2197
|
+
function readBrowserLaunchState(projectRoot, runId, windowId) {
|
|
2198
|
+
const data = readJson(browserLaunchesPath(projectRoot, runId, windowId), { schemaVersion: 1, launches: [] });
|
|
2199
|
+
return {
|
|
2200
|
+
schemaVersion: 1,
|
|
2201
|
+
runId,
|
|
2202
|
+
windowId,
|
|
2203
|
+
updatedAt: data.updatedAt || '',
|
|
2204
|
+
launches: Array.isArray(data.launches) ? data.launches : [],
|
|
2205
|
+
};
|
|
2206
|
+
}
|
|
2207
|
+
|
|
2208
|
+
function writeBrowserLaunchState(projectRoot, runId, windowId, launches) {
|
|
2209
|
+
const filePath = browserLaunchesPath(projectRoot, runId, windowId);
|
|
2210
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
2211
|
+
const state = {
|
|
2212
|
+
schemaVersion: 1,
|
|
2213
|
+
runId,
|
|
2214
|
+
windowId,
|
|
2215
|
+
updatedAt: nowIso(),
|
|
2216
|
+
launches,
|
|
2217
|
+
};
|
|
2218
|
+
fs.writeFileSync(filePath, `${JSON.stringify(state, null, 2)}\n`, 'utf8');
|
|
2219
|
+
return { state, path: path.relative(projectRoot, filePath).replace(/\\/g, '/') };
|
|
2220
|
+
}
|
|
2221
|
+
|
|
2222
|
+
function upsertBrowserLaunch(projectRoot, launch) {
|
|
2223
|
+
const state = readBrowserLaunchState(projectRoot, launch.runId, launch.windowId);
|
|
2224
|
+
const launches = state.launches.filter(item => item.launchId !== launch.launchId);
|
|
2225
|
+
launches.push(launch);
|
|
2226
|
+
launches.sort((a, b) => String(b.startedAt || '').localeCompare(String(a.startedAt || '')));
|
|
2227
|
+
return writeBrowserLaunchState(projectRoot, launch.runId, launch.windowId, launches);
|
|
2228
|
+
}
|
|
2229
|
+
|
|
2230
|
+
function processAlive(pid) {
|
|
2231
|
+
const numericPid = Number(pid || 0);
|
|
2232
|
+
if (!Number.isInteger(numericPid) || numericPid <= 0) return false;
|
|
2233
|
+
try {
|
|
2234
|
+
process.kill(numericPid, 0);
|
|
2235
|
+
return true;
|
|
2236
|
+
} catch (err) {
|
|
2237
|
+
return err?.code === 'EPERM';
|
|
2238
|
+
}
|
|
2239
|
+
}
|
|
2240
|
+
|
|
2241
|
+
function closeProcess(pid, { dryRun = false, force = false } = {}) {
|
|
2242
|
+
const numericPid = Number(pid || 0);
|
|
2243
|
+
if (!Number.isInteger(numericPid) || numericPid <= 0) {
|
|
2244
|
+
return { attempted: false, status: 'no-pid', pid: null, aliveBefore: false, aliveAfter: false };
|
|
2245
|
+
}
|
|
2246
|
+
const aliveBefore = processAlive(numericPid);
|
|
2247
|
+
if (dryRun) {
|
|
2248
|
+
return { attempted: false, status: 'dry-run', pid: numericPid, aliveBefore, aliveAfter: aliveBefore };
|
|
2249
|
+
}
|
|
2250
|
+
if (!aliveBefore) {
|
|
2251
|
+
return { attempted: false, status: 'already-exited', pid: numericPid, aliveBefore, aliveAfter: false };
|
|
2252
|
+
}
|
|
2253
|
+
try {
|
|
2254
|
+
process.kill(numericPid, force ? 'SIGKILL' : 'SIGTERM');
|
|
2255
|
+
} catch (err) {
|
|
2256
|
+
return {
|
|
2257
|
+
attempted: true,
|
|
2258
|
+
status: 'failed',
|
|
2259
|
+
pid: numericPid,
|
|
2260
|
+
aliveBefore,
|
|
2261
|
+
aliveAfter: processAlive(numericPid),
|
|
2262
|
+
error: err?.message || 'process kill failed',
|
|
2263
|
+
};
|
|
2264
|
+
}
|
|
2265
|
+
return {
|
|
2266
|
+
attempted: true,
|
|
2267
|
+
status: 'closed',
|
|
2268
|
+
pid: numericPid,
|
|
2269
|
+
aliveBefore,
|
|
2270
|
+
aliveAfter: processAlive(numericPid),
|
|
2271
|
+
};
|
|
2272
|
+
}
|
|
2273
|
+
|
|
2274
|
+
function removeBrowserProfile(projectRoot, profileDir, { dryRun = false } = {}) {
|
|
2275
|
+
if (!profileDir) return { attempted: false, removed: false, status: 'no-profile-dir', path: '' };
|
|
2276
|
+
const profilesRoot = path.resolve(projectRoot, 'Harness', 'wf-browser', 'tmp', 'browser-profiles');
|
|
2277
|
+
const target = path.resolve(profileDir);
|
|
2278
|
+
const withinProfiles = target.startsWith(`${profilesRoot}${path.sep}`);
|
|
2279
|
+
if (!withinProfiles) {
|
|
2280
|
+
return { attempted: false, removed: false, status: 'outside-profile-root', path: target };
|
|
2281
|
+
}
|
|
2282
|
+
const exists = fs.existsSync(target);
|
|
2283
|
+
if (dryRun) return { attempted: false, removed: false, status: 'dry-run', path: target, exists };
|
|
2284
|
+
if (!exists) return { attempted: false, removed: false, status: 'missing', path: target, exists: false };
|
|
2285
|
+
fs.rmSync(target, { recursive: true, force: true });
|
|
2286
|
+
return { attempted: true, removed: true, status: 'removed', path: target, exists: true };
|
|
2287
|
+
}
|
|
2288
|
+
|
|
2289
|
+
function launchIdFromFlags(flags, windowId) {
|
|
2290
|
+
return flags.launch || flags.launchId || flags.id || `launch-${Date.now().toString(36)}-${safePathSegment(windowId, 'window')}`;
|
|
2291
|
+
}
|
|
2292
|
+
|
|
2293
|
+
function shouldRemoveBrowserProfile(flags) {
|
|
2294
|
+
return trueFlag(flags.removeProfile)
|
|
2295
|
+
|| trueFlag(flags['remove-profile'])
|
|
2296
|
+
|| trueFlag(flags.cleanupProfile)
|
|
2297
|
+
|| trueFlag(flags['cleanup-profile']);
|
|
2298
|
+
}
|
|
2299
|
+
|
|
2300
|
+
function browserLaunchWithRuntime(launch) {
|
|
2301
|
+
return {
|
|
2302
|
+
...launch,
|
|
2303
|
+
alive: processAlive(launch.pid),
|
|
2304
|
+
};
|
|
2305
|
+
}
|
|
2306
|
+
|
|
2307
|
+
function selectBrowserLaunches(launches, flags) {
|
|
2308
|
+
const requested = flags.launch || flags.launchId || flags.id || '';
|
|
2309
|
+
if (requested) return launches.filter(item => item.launchId === requested);
|
|
2310
|
+
if (trueFlag(flags.all)) return launches;
|
|
2311
|
+
const reusable = launches.find(item => !new Set(['closed', 'removed']).has(String(item.status || '')));
|
|
2312
|
+
return reusable ? [reusable] : [];
|
|
2313
|
+
}
|
|
2314
|
+
|
|
2315
|
+
async function browserRuns(flags) {
|
|
2316
|
+
const cp = wfBrowserControlPlane(flags);
|
|
2317
|
+
const limit = flags.limit || 20;
|
|
2318
|
+
return apiJson(cp.url, cp.token, `/api/wf-browser/runs?limit=${encodeURIComponent(limit)}`);
|
|
2319
|
+
}
|
|
2320
|
+
|
|
2321
|
+
async function browserRun(flags) {
|
|
2322
|
+
const cp = wfBrowserControlPlane(flags);
|
|
2323
|
+
return apiJson(cp.url, cp.token, '/api/wf-browser/runs', {
|
|
2324
|
+
method: 'POST',
|
|
2325
|
+
body: {
|
|
2326
|
+
runId: flags.run || flags.runId || undefined,
|
|
2327
|
+
mode: flags.mode || 'mixed',
|
|
2328
|
+
agentId: flags.agent || flags.agentId || process.env.HARNESS_PEER_SESSION_ID || 'agent-unknown',
|
|
2329
|
+
sessionId: flags.session || process.env.HARNESS_PEER_SESSION_ID || '',
|
|
2330
|
+
taskId: flags.task || flags.taskId || '',
|
|
2331
|
+
route: flags.route || '',
|
|
2332
|
+
objective: flags.objective || '',
|
|
2333
|
+
readinessBefore: flags['readiness-before'] || '',
|
|
2334
|
+
readinessAfter: flags['readiness-after'] || '',
|
|
2335
|
+
},
|
|
2336
|
+
});
|
|
2337
|
+
}
|
|
2338
|
+
|
|
2339
|
+
async function browserWindow(flags) {
|
|
2340
|
+
const cp = wfBrowserControlPlane(flags);
|
|
2341
|
+
const runId = flags.run || flags.runId || process.env.HARNESS_WF_BROWSER_RUN_ID || '';
|
|
2342
|
+
if (!runId) throw new Error('Missing --run <runId> or HARNESS_WF_BROWSER_RUN_ID.');
|
|
2343
|
+
return apiJson(cp.url, cp.token, `/api/wf-browser/runs/${encodeURIComponent(runId)}/windows`, {
|
|
2344
|
+
method: 'POST',
|
|
2345
|
+
body: {
|
|
2346
|
+
windowId: flags.window || flags.windowId || undefined,
|
|
2347
|
+
agentId: flags.agent || flags.agentId || process.env.HARNESS_PEER_SESSION_ID || 'agent-unknown',
|
|
2348
|
+
sessionId: flags.session || process.env.HARNESS_PEER_SESSION_ID || '',
|
|
2349
|
+
route: flags.route || '',
|
|
2350
|
+
viewport: {
|
|
2351
|
+
width: flags.width ? Number(flags.width) : undefined,
|
|
2352
|
+
height: flags.height ? Number(flags.height) : undefined,
|
|
2353
|
+
},
|
|
2354
|
+
},
|
|
2355
|
+
});
|
|
2356
|
+
}
|
|
2357
|
+
|
|
2358
|
+
async function browserWindows(flags) {
|
|
2359
|
+
const cp = wfBrowserControlPlane(flags);
|
|
2360
|
+
const runId = flags.run || flags.runId || process.env.HARNESS_WF_BROWSER_RUN_ID || '';
|
|
2361
|
+
if (!runId) throw new Error('Missing --run <runId> or HARNESS_WF_BROWSER_RUN_ID.');
|
|
2362
|
+
return apiJson(cp.url, cp.token, `/api/wf-browser/runs/${encodeURIComponent(runId)}/windows`);
|
|
2363
|
+
}
|
|
2364
|
+
|
|
2365
|
+
async function browserLease(flags) {
|
|
2366
|
+
const cp = wfBrowserControlPlane(flags);
|
|
2367
|
+
const runId = flags.run || flags.runId || process.env.HARNESS_WF_BROWSER_RUN_ID || '';
|
|
2368
|
+
const windowId = flags.window || flags.windowId || process.env.HARNESS_WF_BROWSER_WINDOW_ID || '';
|
|
2369
|
+
if (!runId) throw new Error('Missing --run <runId> or HARNESS_WF_BROWSER_RUN_ID.');
|
|
2370
|
+
if (!windowId) throw new Error('Missing --window <windowId> or HARNESS_WF_BROWSER_WINDOW_ID.');
|
|
2371
|
+
return apiJson(cp.url, cp.token, `/api/wf-browser/runs/${encodeURIComponent(runId)}/windows/${encodeURIComponent(windowId)}/lease`, {
|
|
2372
|
+
method: 'POST',
|
|
2373
|
+
body: {
|
|
2374
|
+
leaseId: flags.lease || flags.leaseId || undefined,
|
|
2375
|
+
type: flags.type || 'control',
|
|
2376
|
+
agentId: flags.agent || flags.agentId || process.env.HARNESS_PEER_SESSION_ID || 'agent-unknown',
|
|
2377
|
+
sessionId: flags.session || process.env.HARNESS_PEER_SESSION_ID || '',
|
|
2378
|
+
reason: flags.reason || '',
|
|
2379
|
+
ttlMs: flags.ttlMs ? Number(flags.ttlMs) : undefined,
|
|
2380
|
+
},
|
|
2381
|
+
});
|
|
2382
|
+
}
|
|
2383
|
+
|
|
2384
|
+
async function browserUrl(flags) {
|
|
2385
|
+
const cp = wfBrowserControlPlane(flags);
|
|
2386
|
+
const runId = flags.run || flags.runId || process.env.HARNESS_WF_BROWSER_RUN_ID || '';
|
|
2387
|
+
const windowId = flags.window || flags.windowId || process.env.HARNESS_WF_BROWSER_WINDOW_ID || '';
|
|
2388
|
+
if (!runId) throw new Error('Missing --run <runId> or HARNESS_WF_BROWSER_RUN_ID.');
|
|
2389
|
+
if (!windowId) throw new Error('Missing --window <windowId> or HARNESS_WF_BROWSER_WINDOW_ID.');
|
|
2390
|
+
const params = new URLSearchParams();
|
|
2391
|
+
const leaseId = flags.lease || flags.leaseId || process.env.HARNESS_WF_BROWSER_LEASE_ID || '';
|
|
2392
|
+
if (leaseId) params.set('leaseId', leaseId);
|
|
2393
|
+
if (flags.agent || flags.agentId) params.set('agentId', flags.agent || flags.agentId);
|
|
2394
|
+
if (flags.route) params.set('route', flags.route);
|
|
2395
|
+
if (flags.debug === 'false' || flags.debug === '0') params.set('debug', '0');
|
|
2396
|
+
const query = params.toString();
|
|
2397
|
+
const result = await apiJson(cp.url, cp.token, `/api/wf-browser/runs/${encodeURIComponent(runId)}/windows/${encodeURIComponent(windowId)}/launch-url${query ? `?${query}` : ''}`);
|
|
2398
|
+
return {
|
|
2399
|
+
...result,
|
|
2400
|
+
opened: trueFlag(flags.open) ? openLocalUrl(result.launchUrl) : false,
|
|
2401
|
+
};
|
|
2402
|
+
}
|
|
2403
|
+
|
|
2404
|
+
async function browserOpen(flags, options = {}) {
|
|
2405
|
+
const cp = wfBrowserControlPlane(flags);
|
|
2406
|
+
const runId = flags.run || flags.runId || process.env.HARNESS_WF_BROWSER_RUN_ID || '';
|
|
2407
|
+
const windowId = flags.window || flags.windowId || process.env.HARNESS_WF_BROWSER_WINDOW_ID || '';
|
|
2408
|
+
if (!runId) throw new Error('Missing --run <runId> or HARNESS_WF_BROWSER_RUN_ID.');
|
|
2409
|
+
if (!windowId) throw new Error('Missing --window <windowId> or HARNESS_WF_BROWSER_WINDOW_ID.');
|
|
2410
|
+
const projectRoot = path.resolve(flags.project || process.cwd());
|
|
2411
|
+
const directCommand = options.directCommand !== false;
|
|
2412
|
+
const context = String(flags.context || flags['browser-context'] || (trueFlag(flags.isolated) || directCommand ? 'isolated' : 'default')).toLowerCase();
|
|
2413
|
+
if (!new Set(['default', 'isolated']).has(context)) {
|
|
2414
|
+
throw new Error('Invalid --context; expected default or isolated.');
|
|
2415
|
+
}
|
|
2416
|
+
const urlResult = flags.url
|
|
2417
|
+
? { ok: true, launchUrl: flags.url, runId, windowId, leaseId: flags.lease || flags.leaseId || '' }
|
|
2418
|
+
: await browserUrl({ ...flags, run: runId, window: windowId, open: 'false' });
|
|
2419
|
+
const launchUrl = urlResult.launchUrl;
|
|
2420
|
+
const dryRun = trueFlag(flags.dryRun) || trueFlag(flags['dry-run']);
|
|
2421
|
+
const width = flags.width ? Number(flags.width) : undefined;
|
|
2422
|
+
const height = flags.height ? Number(flags.height) : undefined;
|
|
2423
|
+
const launchId = launchIdFromFlags(flags, windowId);
|
|
2424
|
+
const startedAt = new Date().toISOString();
|
|
2425
|
+
let opened = false;
|
|
2426
|
+
let pid = null;
|
|
2427
|
+
let command = '';
|
|
2428
|
+
let args = [];
|
|
2429
|
+
let profileDir = '';
|
|
2430
|
+
let resolver = { source: 'system-default', candidates: [] };
|
|
2431
|
+
|
|
2432
|
+
if (context === 'default' || String(flags.browser || '').toLowerCase() === 'default') {
|
|
2433
|
+
if (!dryRun) opened = openLocalUrl(launchUrl);
|
|
2434
|
+
} else {
|
|
2435
|
+
profileDir = browserProfileDir(projectRoot, runId, windowId, flags);
|
|
2436
|
+
fs.mkdirSync(profileDir, { recursive: true });
|
|
2437
|
+
resolver = resolveBrowserExecutable(flags);
|
|
2438
|
+
command = resolver.command;
|
|
2439
|
+
if (!command) {
|
|
2440
|
+
throw new Error(`Unable to locate Chrome/Edge/Chromium for isolated browser context. Pass --browser-command <path>. Checked: ${resolver.candidates.join(', ')}`);
|
|
2441
|
+
}
|
|
2442
|
+
args = browserOpenArgs({ launchUrl, profileDir, context, width, height, flags });
|
|
2443
|
+
if (!dryRun) {
|
|
2444
|
+
const child = spawn(command, args, {
|
|
2445
|
+
detached: true,
|
|
2446
|
+
stdio: 'ignore',
|
|
2447
|
+
windowsHide: true,
|
|
2448
|
+
});
|
|
2449
|
+
child.unref();
|
|
2450
|
+
opened = true;
|
|
2451
|
+
pid = child.pid || null;
|
|
2452
|
+
}
|
|
2453
|
+
}
|
|
2454
|
+
|
|
2455
|
+
const launch = {
|
|
2456
|
+
ok: true,
|
|
2457
|
+
launchId,
|
|
2458
|
+
runId,
|
|
2459
|
+
windowId,
|
|
2460
|
+
leaseId: urlResult.leaseId || flags.lease || flags.leaseId || '',
|
|
2461
|
+
agentId: flags.agent || flags.agentId || '',
|
|
2462
|
+
context,
|
|
2463
|
+
isolated: context === 'isolated',
|
|
2464
|
+
launchUrl,
|
|
2465
|
+
opened,
|
|
2466
|
+
dryRun,
|
|
2467
|
+
status: dryRun ? 'prepared' : (opened ? 'open' : 'not-opened'),
|
|
2468
|
+
pid,
|
|
2469
|
+
command,
|
|
2470
|
+
args,
|
|
2471
|
+
profileDir,
|
|
2472
|
+
resolver,
|
|
2473
|
+
startedAt,
|
|
2474
|
+
completedAt: new Date().toISOString(),
|
|
2475
|
+
};
|
|
2476
|
+
if (flags['no-artifact'] !== 'true') {
|
|
2477
|
+
const artifact = await apiJson(cp.url, cp.token, `/api/wf-browser/runs/${encodeURIComponent(runId)}/windows/${encodeURIComponent(windowId)}/artifacts`, {
|
|
2478
|
+
method: 'POST',
|
|
2479
|
+
body: {
|
|
2480
|
+
type: 'analysis',
|
|
2481
|
+
name: `browser-launch-${Date.now().toString(36)}.json`,
|
|
2482
|
+
label: 'browser.open',
|
|
2483
|
+
json: launch,
|
|
2484
|
+
},
|
|
2485
|
+
});
|
|
2486
|
+
launch.artifact = artifact.artifact || null;
|
|
2487
|
+
}
|
|
2488
|
+
const launchState = upsertBrowserLaunch(projectRoot, launch);
|
|
2489
|
+
launch.statePath = launchState.path;
|
|
2490
|
+
if (trueFlag(flags.wait) && !dryRun) {
|
|
2491
|
+
launch.connection = (await browserWait({
|
|
2492
|
+
...flags,
|
|
2493
|
+
run: runId,
|
|
2494
|
+
window: windowId,
|
|
2495
|
+
agent: launch.agentId,
|
|
2496
|
+
})).connection;
|
|
2497
|
+
const updatedLaunchState = upsertBrowserLaunch(projectRoot, launch);
|
|
2498
|
+
launch.statePath = updatedLaunchState.path;
|
|
2499
|
+
}
|
|
2500
|
+
return launch;
|
|
2501
|
+
}
|
|
2502
|
+
|
|
2503
|
+
function matchingWfBrowserConnection(connections, { runId, windowId, agentId = '' } = {}) {
|
|
2504
|
+
return (connections || []).find(item =>
|
|
2505
|
+
item.runId === runId
|
|
2506
|
+
&& item.windowId === windowId
|
|
2507
|
+
&& (!agentId || item.agentId === agentId)
|
|
2508
|
+
) || null;
|
|
2509
|
+
}
|
|
2510
|
+
|
|
2511
|
+
async function browserWait(flags) {
|
|
2512
|
+
const runId = flags.run || flags.runId || process.env.HARNESS_WF_BROWSER_RUN_ID || '';
|
|
2513
|
+
const windowId = flags.window || flags.windowId || process.env.HARNESS_WF_BROWSER_WINDOW_ID || '';
|
|
2514
|
+
if (!runId) throw new Error('Missing --run <runId> or HARNESS_WF_BROWSER_RUN_ID.');
|
|
2515
|
+
if (!windowId) throw new Error('Missing --window <windowId> or HARNESS_WF_BROWSER_WINDOW_ID.');
|
|
2516
|
+
const agentId = flags.agent || flags.agentId || '';
|
|
2517
|
+
const timeoutMs = boundedNumber(flags.timeout || flags.timeoutMs || flags['wait-timeout'] || flags.waitTimeout, 10000, 250, 120000);
|
|
2518
|
+
const intervalMs = boundedNumber(flags.interval || flags.intervalMs, 250, 50, 5000);
|
|
2519
|
+
const startedAtMs = Date.now();
|
|
2520
|
+
let attempts = 0;
|
|
2521
|
+
let lastConnections = [];
|
|
2522
|
+
while (Date.now() - startedAtMs <= timeoutMs) {
|
|
2523
|
+
attempts += 1;
|
|
2524
|
+
const result = await browserConnections(flags);
|
|
2525
|
+
lastConnections = result.connections || [];
|
|
2526
|
+
const connection = matchingWfBrowserConnection(lastConnections, { runId, windowId, agentId });
|
|
2527
|
+
if (connection) {
|
|
2528
|
+
return {
|
|
2529
|
+
ok: true,
|
|
2530
|
+
runId,
|
|
2531
|
+
windowId,
|
|
2532
|
+
agentId,
|
|
2533
|
+
connection,
|
|
2534
|
+
attempts,
|
|
2535
|
+
waitedMs: Date.now() - startedAtMs,
|
|
2536
|
+
};
|
|
2537
|
+
}
|
|
2538
|
+
await sleep(intervalMs);
|
|
2539
|
+
}
|
|
2540
|
+
throw new Error(`wf-browser window did not connect within ${timeoutMs}ms: ${runId}/${windowId}${agentId ? ` agent=${agentId}` : ''}`);
|
|
2541
|
+
}
|
|
2542
|
+
|
|
2543
|
+
async function browserAllocate(flags) {
|
|
2544
|
+
const cp = wfBrowserControlPlane(flags);
|
|
2545
|
+
let runId = flags.run || flags.runId || process.env.HARNESS_WF_BROWSER_RUN_ID || '';
|
|
2546
|
+
let run = null;
|
|
2547
|
+
if (!runId) {
|
|
2548
|
+
const createdRun = await browserRun(flags);
|
|
2549
|
+
run = createdRun.run;
|
|
2550
|
+
runId = run.runId;
|
|
2551
|
+
}
|
|
2552
|
+
const windowResult = await apiJson(cp.url, cp.token, `/api/wf-browser/runs/${encodeURIComponent(runId)}/windows`, {
|
|
2553
|
+
method: 'POST',
|
|
2554
|
+
body: {
|
|
2555
|
+
windowId: flags.window || flags.windowId || undefined,
|
|
2556
|
+
agentId: flags.agent || flags.agentId || process.env.HARNESS_PEER_SESSION_ID || 'agent-unknown',
|
|
2557
|
+
sessionId: flags.session || process.env.HARNESS_PEER_SESSION_ID || '',
|
|
2558
|
+
route: flags.route || '',
|
|
2559
|
+
viewport: {
|
|
2560
|
+
width: flags.width ? Number(flags.width) : undefined,
|
|
2561
|
+
height: flags.height ? Number(flags.height) : undefined,
|
|
2562
|
+
},
|
|
2563
|
+
},
|
|
2564
|
+
});
|
|
2565
|
+
const leaseResult = await apiJson(cp.url, cp.token, `/api/wf-browser/runs/${encodeURIComponent(runId)}/windows/${encodeURIComponent(windowResult.window.windowId)}/lease`, {
|
|
2566
|
+
method: 'POST',
|
|
2567
|
+
body: {
|
|
2568
|
+
type: flags.type || 'control',
|
|
2569
|
+
agentId: flags.agent || flags.agentId || process.env.HARNESS_PEER_SESSION_ID || 'agent-unknown',
|
|
2570
|
+
sessionId: flags.session || process.env.HARNESS_PEER_SESSION_ID || '',
|
|
2571
|
+
reason: flags.reason || 'allocated by wf-ui-control',
|
|
2572
|
+
ttlMs: flags.ttlMs ? Number(flags.ttlMs) : undefined,
|
|
2573
|
+
},
|
|
2574
|
+
});
|
|
2575
|
+
const urlResult = await browserUrl({
|
|
2576
|
+
...flags,
|
|
2577
|
+
run: runId,
|
|
2578
|
+
window: windowResult.window.windowId,
|
|
2579
|
+
lease: leaseResult.lease.leaseId,
|
|
2580
|
+
open: 'false',
|
|
2581
|
+
});
|
|
2582
|
+
let browserLaunch = null;
|
|
2583
|
+
let opened = false;
|
|
2584
|
+
if (trueFlag(flags.open)) {
|
|
2585
|
+
if (shouldUseBrowserOpen(flags)) {
|
|
2586
|
+
browserLaunch = await browserOpen({
|
|
2587
|
+
...flags,
|
|
2588
|
+
run: runId,
|
|
2589
|
+
window: windowResult.window.windowId,
|
|
2590
|
+
lease: leaseResult.lease.leaseId,
|
|
2591
|
+
agent: leaseResult.lease.agentId,
|
|
2592
|
+
url: urlResult.launchUrl,
|
|
2593
|
+
}, { directCommand: false });
|
|
2594
|
+
opened = browserLaunch.opened;
|
|
2595
|
+
} else {
|
|
2596
|
+
opened = openLocalUrl(urlResult.launchUrl);
|
|
2597
|
+
}
|
|
2598
|
+
}
|
|
2599
|
+
return {
|
|
2600
|
+
ok: true,
|
|
2601
|
+
run: run || { runId },
|
|
2602
|
+
window: windowResult.window,
|
|
2603
|
+
lease: leaseResult.lease,
|
|
2604
|
+
debugUrlParams: urlResult.debugUrlParams,
|
|
2605
|
+
launchUrl: urlResult.launchUrl,
|
|
2606
|
+
opened,
|
|
2607
|
+
browserLaunch,
|
|
2608
|
+
connection: trueFlag(flags.wait) && !trueFlag(flags.dryRun) && !trueFlag(flags['dry-run']) ? (await browserWait({
|
|
2609
|
+
...flags,
|
|
2610
|
+
run: runId,
|
|
2611
|
+
window: windowResult.window.windowId,
|
|
2612
|
+
agent: leaseResult.lease.agentId,
|
|
2613
|
+
})).connection : null,
|
|
2614
|
+
};
|
|
2615
|
+
}
|
|
2616
|
+
|
|
2617
|
+
async function browserAllocateMany(flags) {
|
|
2618
|
+
const agents = listFlag(flags.agents || flags.agentIds);
|
|
2619
|
+
const routes = listFlag(flags.routes);
|
|
2620
|
+
const sessions = listFlag(flags.sessions || flags.sessionIds);
|
|
2621
|
+
const windows = listFlag(flags.windows || flags.windowIds);
|
|
2622
|
+
const requestedCount = Number(flags.count || Math.max(agents.length, routes.length, sessions.length, windows.length, 1));
|
|
2623
|
+
const count = Math.min(Math.max(Number.isFinite(requestedCount) ? requestedCount : 1, 1), 50);
|
|
2624
|
+
let runId = flags.run || flags.runId || process.env.HARNESS_WF_BROWSER_RUN_ID || '';
|
|
2625
|
+
let run = null;
|
|
2626
|
+
if (!runId) {
|
|
2627
|
+
const createdRun = await browserRun({
|
|
2628
|
+
...flags,
|
|
2629
|
+
agent: pickIndexed(agents, 0, flags.agent || flags.agentId || process.env.HARNESS_PEER_SESSION_ID || 'agent-unknown'),
|
|
2630
|
+
session: pickIndexed(sessions, 0, flags.session || process.env.HARNESS_PEER_SESSION_ID || ''),
|
|
2631
|
+
route: pickIndexed(routes, 0, flags.route || ''),
|
|
2632
|
+
});
|
|
2633
|
+
run = createdRun.run;
|
|
2634
|
+
runId = run.runId;
|
|
2635
|
+
}
|
|
2636
|
+
|
|
2637
|
+
const allocations = [];
|
|
2638
|
+
for (let index = 0; index < count; index += 1) {
|
|
2639
|
+
const agentId = pickIndexed(agents, index, flags.agent || flags.agentId || `${process.env.HARNESS_PEER_SESSION_ID || 'agent'}-${index + 1}`);
|
|
2640
|
+
const sessionId = pickIndexed(sessions, index, flags.session || process.env.HARNESS_PEER_SESSION_ID || '');
|
|
2641
|
+
const route = pickIndexed(routes, index, flags.route || '');
|
|
2642
|
+
const windowId = pickIndexed(windows, index, '');
|
|
2643
|
+
const allocation = await browserAllocate({
|
|
2644
|
+
...flags,
|
|
2645
|
+
run: runId,
|
|
2646
|
+
window: windowId || undefined,
|
|
2647
|
+
agent: agentId,
|
|
2648
|
+
session: sessionId,
|
|
2649
|
+
route,
|
|
2650
|
+
reason: flags.reason || `batch allocation ${index + 1}/${count}`,
|
|
2651
|
+
});
|
|
2652
|
+
allocations.push({
|
|
2653
|
+
index,
|
|
2654
|
+
agentId,
|
|
2655
|
+
sessionId,
|
|
2656
|
+
route,
|
|
2657
|
+
run: allocation.run,
|
|
2658
|
+
window: allocation.window,
|
|
2659
|
+
lease: allocation.lease,
|
|
2660
|
+
debugUrlParams: allocation.debugUrlParams,
|
|
2661
|
+
launchUrl: allocation.launchUrl,
|
|
2662
|
+
opened: allocation.opened,
|
|
2663
|
+
browserLaunch: allocation.browserLaunch,
|
|
2664
|
+
connection: allocation.connection,
|
|
2665
|
+
});
|
|
2666
|
+
}
|
|
2667
|
+
|
|
2668
|
+
return {
|
|
2669
|
+
ok: true,
|
|
2670
|
+
run: run || { runId },
|
|
2671
|
+
count: allocations.length,
|
|
2672
|
+
allocations,
|
|
2673
|
+
windows: allocations.map(item => item.window),
|
|
2674
|
+
leases: allocations.map(item => item.lease),
|
|
2675
|
+
launchUrls: allocations.map(item => item.launchUrl),
|
|
2676
|
+
};
|
|
2677
|
+
}
|
|
2678
|
+
|
|
2679
|
+
async function browserLaunches(flags) {
|
|
2680
|
+
const projectRoot = path.resolve(flags.project || process.cwd());
|
|
2681
|
+
const runId = flags.run || flags.runId || process.env.HARNESS_WF_BROWSER_RUN_ID || '';
|
|
2682
|
+
const windowId = flags.window || flags.windowId || process.env.HARNESS_WF_BROWSER_WINDOW_ID || '';
|
|
2683
|
+
if (!runId) throw new Error('Missing --run <runId> or HARNESS_WF_BROWSER_RUN_ID.');
|
|
2684
|
+
if (!windowId) throw new Error('Missing --window <windowId> or HARNESS_WF_BROWSER_WINDOW_ID.');
|
|
2685
|
+
const state = readBrowserLaunchState(projectRoot, runId, windowId);
|
|
2686
|
+
return {
|
|
2687
|
+
ok: true,
|
|
2688
|
+
runId,
|
|
2689
|
+
windowId,
|
|
2690
|
+
updatedAt: state.updatedAt,
|
|
2691
|
+
path: path.relative(projectRoot, browserLaunchesPath(projectRoot, runId, windowId)).replace(/\\/g, '/'),
|
|
2692
|
+
count: state.launches.length,
|
|
2693
|
+
launches: state.launches.map(browserLaunchWithRuntime),
|
|
2694
|
+
};
|
|
2695
|
+
}
|
|
2696
|
+
|
|
2697
|
+
async function browserClose(flags) {
|
|
2698
|
+
const cp = wfBrowserControlPlane(flags);
|
|
2699
|
+
const projectRoot = path.resolve(flags.project || process.cwd());
|
|
2700
|
+
const runId = flags.run || flags.runId || process.env.HARNESS_WF_BROWSER_RUN_ID || '';
|
|
2701
|
+
const windowId = flags.window || flags.windowId || process.env.HARNESS_WF_BROWSER_WINDOW_ID || '';
|
|
2702
|
+
if (!runId) throw new Error('Missing --run <runId> or HARNESS_WF_BROWSER_RUN_ID.');
|
|
2703
|
+
if (!windowId) throw new Error('Missing --window <windowId> or HARNESS_WF_BROWSER_WINDOW_ID.');
|
|
2704
|
+
const dryRun = trueFlag(flags.dryRun) || trueFlag(flags['dry-run']);
|
|
2705
|
+
const closeReason = flags.reason || 'closed by wf-ui-control';
|
|
2706
|
+
const state = readBrowserLaunchState(projectRoot, runId, windowId);
|
|
2707
|
+
const selected = selectBrowserLaunches(state.launches, flags);
|
|
2708
|
+
if (!selected.length) {
|
|
2709
|
+
return {
|
|
2710
|
+
ok: true,
|
|
2711
|
+
runId,
|
|
2712
|
+
windowId,
|
|
2713
|
+
path: path.relative(projectRoot, browserLaunchesPath(projectRoot, runId, windowId)).replace(/\\/g, '/'),
|
|
2714
|
+
count: 0,
|
|
2715
|
+
selectedLaunchIds: [],
|
|
2716
|
+
closed: [],
|
|
2717
|
+
warning: 'No matching browser launch found.',
|
|
2718
|
+
};
|
|
2719
|
+
}
|
|
2720
|
+
|
|
2721
|
+
const selectedLaunchIds = new Set(selected.map(item => item.launchId));
|
|
2722
|
+
const closed = [];
|
|
2723
|
+
const launches = state.launches.map((launch) => {
|
|
2724
|
+
if (!selectedLaunchIds.has(launch.launchId)) return launch;
|
|
2725
|
+
const closeResult = closeProcess(launch.pid, {
|
|
2726
|
+
dryRun,
|
|
2727
|
+
force: trueFlag(flags.force),
|
|
2728
|
+
});
|
|
2729
|
+
const profileCleanup = shouldRemoveBrowserProfile(flags)
|
|
2730
|
+
? removeBrowserProfile(projectRoot, launch.profileDir, { dryRun })
|
|
2731
|
+
: null;
|
|
2732
|
+
let status = launch.status || 'unknown';
|
|
2733
|
+
if (dryRun) status = 'close-dry-run';
|
|
2734
|
+
else if (closeResult.status === 'failed') status = 'close-failed';
|
|
2735
|
+
else if (profileCleanup?.status === 'outside-profile-root') status = 'cleanup-blocked';
|
|
2736
|
+
else status = 'closed';
|
|
2737
|
+
const updated = {
|
|
2738
|
+
...launch,
|
|
2739
|
+
status,
|
|
2740
|
+
closeReason,
|
|
2741
|
+
closeResult,
|
|
2742
|
+
profileCleanup,
|
|
2743
|
+
closedAt: dryRun ? '' : nowIso(),
|
|
2744
|
+
updatedAt: nowIso(),
|
|
2745
|
+
};
|
|
2746
|
+
closed.push(browserLaunchWithRuntime(updated));
|
|
2747
|
+
return updated;
|
|
2748
|
+
});
|
|
2749
|
+
const written = writeBrowserLaunchState(projectRoot, runId, windowId, launches);
|
|
2750
|
+
const result = {
|
|
2751
|
+
ok: true,
|
|
2752
|
+
runId,
|
|
2753
|
+
windowId,
|
|
2754
|
+
path: written.path,
|
|
2755
|
+
count: closed.length,
|
|
2756
|
+
selectedLaunchIds: Array.from(selectedLaunchIds),
|
|
2757
|
+
closed,
|
|
2758
|
+
};
|
|
2759
|
+
if (flags['no-artifact'] !== 'true' && cp.url) {
|
|
2760
|
+
const artifact = await apiJson(cp.url, cp.token, `/api/wf-browser/runs/${encodeURIComponent(runId)}/windows/${encodeURIComponent(windowId)}/artifacts`, {
|
|
2761
|
+
method: 'POST',
|
|
2762
|
+
body: {
|
|
2763
|
+
type: 'analysis',
|
|
2764
|
+
name: `browser-close-${Date.now().toString(36)}.json`,
|
|
2765
|
+
label: 'browser.close',
|
|
2766
|
+
json: result,
|
|
2767
|
+
},
|
|
2768
|
+
});
|
|
2769
|
+
result.artifact = artifact.artifact || null;
|
|
2770
|
+
}
|
|
2771
|
+
return result;
|
|
2772
|
+
}
|
|
2773
|
+
|
|
2774
|
+
async function browserRelease(flags) {
|
|
2775
|
+
const cp = wfBrowserControlPlane(flags);
|
|
2776
|
+
const runId = flags.run || flags.runId || process.env.HARNESS_WF_BROWSER_RUN_ID || '';
|
|
2777
|
+
const windowId = flags.window || flags.windowId || process.env.HARNESS_WF_BROWSER_WINDOW_ID || '';
|
|
2778
|
+
const leaseId = flags.lease || flags.leaseId || process.env.HARNESS_WF_BROWSER_LEASE_ID || '';
|
|
2779
|
+
if (!runId) throw new Error('Missing --run <runId> or HARNESS_WF_BROWSER_RUN_ID.');
|
|
2780
|
+
if (!windowId) throw new Error('Missing --window <windowId> or HARNESS_WF_BROWSER_WINDOW_ID.');
|
|
2781
|
+
if (!leaseId) throw new Error('Missing --lease <leaseId> or HARNESS_WF_BROWSER_LEASE_ID.');
|
|
2782
|
+
const released = await apiJson(cp.url, cp.token, `/api/wf-browser/runs/${encodeURIComponent(runId)}/windows/${encodeURIComponent(windowId)}/lease/${encodeURIComponent(leaseId)}/release`, {
|
|
2783
|
+
method: 'POST',
|
|
2784
|
+
body: { reason: flags.reason || 'released' },
|
|
2785
|
+
});
|
|
2786
|
+
if (!trueFlag(flags.close)) return released;
|
|
2787
|
+
const browserCloseResult = await browserClose({
|
|
2788
|
+
...flags,
|
|
2789
|
+
run: runId,
|
|
2790
|
+
window: windowId,
|
|
2791
|
+
lease: leaseId,
|
|
2792
|
+
reason: flags.reason || 'released',
|
|
2793
|
+
});
|
|
2794
|
+
return {
|
|
2795
|
+
...released,
|
|
2796
|
+
browserClose: browserCloseResult,
|
|
2797
|
+
};
|
|
2798
|
+
}
|
|
2799
|
+
|
|
2800
|
+
async function browserArtifacts(flags) {
|
|
2801
|
+
const cp = wfBrowserControlPlane(flags);
|
|
2802
|
+
const runId = flags.run || flags.runId || process.env.HARNESS_WF_BROWSER_RUN_ID || '';
|
|
2803
|
+
const windowId = flags.window || flags.windowId || process.env.HARNESS_WF_BROWSER_WINDOW_ID || '';
|
|
2804
|
+
if (!runId) throw new Error('Missing --run <runId> or HARNESS_WF_BROWSER_RUN_ID.');
|
|
2805
|
+
if (!windowId) throw new Error('Missing --window <windowId> or HARNESS_WF_BROWSER_WINDOW_ID.');
|
|
2806
|
+
return apiJson(cp.url, cp.token, `/api/wf-browser/runs/${encodeURIComponent(runId)}/windows/${encodeURIComponent(windowId)}/artifacts`);
|
|
2807
|
+
}
|
|
2808
|
+
|
|
2809
|
+
async function browserConnections(flags) {
|
|
2810
|
+
const cp = wfBrowserControlPlane(flags);
|
|
2811
|
+
return apiJson(cp.url, cp.token, '/api/wf-browser/connections');
|
|
2812
|
+
}
|
|
2813
|
+
|
|
2814
|
+
async function browserCleanup(flags) {
|
|
2815
|
+
const cp = wfBrowserControlPlane(flags);
|
|
2816
|
+
return apiJson(cp.url, cp.token, '/api/wf-browser/cleanup', {
|
|
2817
|
+
method: 'POST',
|
|
2818
|
+
body: {
|
|
2819
|
+
apply: trueFlag(flags.apply),
|
|
2820
|
+
keepLatest: flags.keepLatest || flags['keep-latest'] ? Number(flags.keepLatest || flags['keep-latest']) : undefined,
|
|
2821
|
+
maxAgeDays: flags.maxAgeDays || flags['max-age-days'] ? Number(flags.maxAgeDays || flags['max-age-days']) : undefined,
|
|
2822
|
+
},
|
|
2823
|
+
});
|
|
2824
|
+
}
|
|
2825
|
+
|
|
2826
|
+
function parseJsonFlag(value, fallback = {}) {
|
|
2827
|
+
if (!value) return fallback;
|
|
2828
|
+
try { return JSON.parse(value); } catch {
|
|
2829
|
+
throw new Error(`Invalid JSON flag: ${value}`);
|
|
2830
|
+
}
|
|
2831
|
+
}
|
|
2832
|
+
|
|
2833
|
+
function browserCommandPayload(flags) {
|
|
2834
|
+
const payload = parseJsonFlag(flags.payload || flags.json, {});
|
|
2835
|
+
const target = {
|
|
2836
|
+
...(payload.target && typeof payload.target === 'object' ? payload.target : {}),
|
|
2837
|
+
...(flags.selector ? { selector: flags.selector } : {}),
|
|
2838
|
+
...(flags.testid ? { testId: flags.testid } : {}),
|
|
2839
|
+
...(flags['test-id'] ? { testId: flags['test-id'] } : {}),
|
|
2840
|
+
...(flags.role ? { role: flags.role } : {}),
|
|
2841
|
+
...(flags.name ? { name: flags.name } : {}),
|
|
2842
|
+
...(flags.textTarget ? { text: flags.textTarget } : {}),
|
|
2843
|
+
...(flags['text-target'] ? { text: flags['text-target'] } : {}),
|
|
2844
|
+
};
|
|
2845
|
+
const to = {
|
|
2846
|
+
...(payload.to && typeof payload.to === 'object' ? payload.to : {}),
|
|
2847
|
+
...(payload.destination && typeof payload.destination === 'object' ? payload.destination : {}),
|
|
2848
|
+
...(flags.toSelector ? { selector: flags.toSelector } : {}),
|
|
2849
|
+
...(flags['to-selector'] ? { selector: flags['to-selector'] } : {}),
|
|
2850
|
+
...(flags.toTestid ? { testId: flags.toTestid } : {}),
|
|
2851
|
+
...(flags.toTestId ? { testId: flags.toTestId } : {}),
|
|
2852
|
+
...(flags['to-testid'] ? { testId: flags['to-testid'] } : {}),
|
|
2853
|
+
...(flags['to-test-id'] ? { testId: flags['to-test-id'] } : {}),
|
|
2854
|
+
...(flags.toRole ? { role: flags.toRole } : {}),
|
|
2855
|
+
...(flags['to-role'] ? { role: flags['to-role'] } : {}),
|
|
2856
|
+
...(flags.toName ? { name: flags.toName } : {}),
|
|
2857
|
+
...(flags['to-name'] ? { name: flags['to-name'] } : {}),
|
|
2858
|
+
};
|
|
2859
|
+
const body = {
|
|
2860
|
+
...payload,
|
|
2861
|
+
...(Object.keys(target).length ? { target } : {}),
|
|
2862
|
+
...(Object.keys(to).length ? { to } : {}),
|
|
2863
|
+
...(flags.text ? { text: flags.text } : {}),
|
|
2864
|
+
...(flags.key ? { key: flags.key } : {}),
|
|
2865
|
+
...(flags.dx ? { dx: Number(flags.dx) } : {}),
|
|
2866
|
+
...(flags.dy ? { dy: Number(flags.dy) } : {}),
|
|
2867
|
+
...(flags.toX ? { toX: Number(flags.toX) } : {}),
|
|
2868
|
+
...(flags['to-x'] ? { toX: Number(flags['to-x']) } : {}),
|
|
2869
|
+
...(flags.toY ? { toY: Number(flags.toY) } : {}),
|
|
2870
|
+
...(flags['to-y'] ? { toY: Number(flags['to-y']) } : {}),
|
|
2871
|
+
...(flags.steps ? { steps: Number(flags.steps) } : {}),
|
|
2872
|
+
...(flags.timeout ? { timeoutMs: Number(flags.timeout) } : {}),
|
|
2873
|
+
...(flags.timeoutMs ? { timeoutMs: Number(flags.timeoutMs) } : {}),
|
|
2874
|
+
...(flags.limit ? { limit: Number(flags.limit) } : {}),
|
|
2875
|
+
...(flags.maxNodes ? { maxNodes: Number(flags.maxNodes) } : {}),
|
|
2876
|
+
...(flags['max-nodes'] ? { maxNodes: Number(flags['max-nodes']) } : {}),
|
|
2877
|
+
...(flags.prefix ? { prefix: flags.prefix } : {}),
|
|
2878
|
+
...(flags.replace === 'true' ? { replace: true } : {}),
|
|
2879
|
+
};
|
|
2880
|
+
return body;
|
|
2881
|
+
}
|
|
2882
|
+
|
|
2883
|
+
async function browserCommand(flags) {
|
|
2884
|
+
const cp = wfBrowserControlPlane(flags);
|
|
2885
|
+
const runId = flags.run || flags.runId || process.env.HARNESS_WF_BROWSER_RUN_ID || '';
|
|
2886
|
+
const windowId = flags.window || flags.windowId || process.env.HARNESS_WF_BROWSER_WINDOW_ID || '';
|
|
2887
|
+
const leaseId = flags.lease || flags.leaseId || process.env.HARNESS_WF_BROWSER_LEASE_ID || '';
|
|
2888
|
+
const primitive = flags.primitive || '';
|
|
2889
|
+
if (!runId) throw new Error('Missing --run <runId> or HARNESS_WF_BROWSER_RUN_ID.');
|
|
2890
|
+
if (!windowId) throw new Error('Missing --window <windowId> or HARNESS_WF_BROWSER_WINDOW_ID.');
|
|
2891
|
+
if (!leaseId) throw new Error('Missing --lease <leaseId> or HARNESS_WF_BROWSER_LEASE_ID.');
|
|
2892
|
+
if (!primitive) throw new Error('Missing --primitive observe.* or act.*.');
|
|
2893
|
+
return apiJson(cp.url, cp.token, `/api/wf-browser/runs/${encodeURIComponent(runId)}/windows/${encodeURIComponent(windowId)}/commands`, {
|
|
2894
|
+
method: 'POST',
|
|
2895
|
+
body: {
|
|
2896
|
+
commandId: flags.command || flags.commandId || undefined,
|
|
2897
|
+
primitive,
|
|
2898
|
+
agentId: flags.agent || flags.agentId || process.env.HARNESS_PEER_SESSION_ID || 'agent-unknown',
|
|
2899
|
+
sessionId: flags.session || process.env.HARNESS_PEER_SESSION_ID || '',
|
|
2900
|
+
leaseId,
|
|
2901
|
+
timeoutMs: flags.timeout ? Number(flags.timeout) : undefined,
|
|
2902
|
+
payload: browserCommandPayload(flags),
|
|
2903
|
+
storeArtifact: flags['no-artifact'] === 'true' ? false : undefined,
|
|
2904
|
+
},
|
|
2905
|
+
});
|
|
2906
|
+
}
|
|
2907
|
+
|
|
2908
|
+
function browserSnapshotPrimitives(flags) {
|
|
2909
|
+
const values = listFlag(flags.primitives || flags.primitive);
|
|
2910
|
+
const primitives = values.length ? values : DEFAULT_BROWSER_SNAPSHOT_PRIMITIVES;
|
|
2911
|
+
const invalid = primitives.filter(primitive => !primitive.startsWith('observe.'));
|
|
2912
|
+
if (invalid.length) {
|
|
2913
|
+
throw new Error(`browser-snapshot only accepts observe.* primitives: ${invalid.join(', ')}`);
|
|
2914
|
+
}
|
|
2915
|
+
return primitives;
|
|
2916
|
+
}
|
|
2917
|
+
|
|
2918
|
+
async function browserSnapshot(flags) {
|
|
2919
|
+
const runId = flags.run || flags.runId || process.env.HARNESS_WF_BROWSER_RUN_ID || '';
|
|
2920
|
+
const windowId = flags.window || flags.windowId || process.env.HARNESS_WF_BROWSER_WINDOW_ID || '';
|
|
2921
|
+
const leaseId = flags.lease || flags.leaseId || process.env.HARNESS_WF_BROWSER_LEASE_ID || '';
|
|
2922
|
+
if (!runId) throw new Error('Missing --run <runId> or HARNESS_WF_BROWSER_RUN_ID.');
|
|
2923
|
+
if (!windowId) throw new Error('Missing --window <windowId> or HARNESS_WF_BROWSER_WINDOW_ID.');
|
|
2924
|
+
if (!leaseId) throw new Error('Missing --lease <leaseId> or HARNESS_WF_BROWSER_LEASE_ID.');
|
|
2925
|
+
|
|
2926
|
+
const primitives = browserSnapshotPrimitives(flags);
|
|
2927
|
+
const agentId = flags.agent || flags.agentId || process.env.HARNESS_PEER_SESSION_ID || 'agent-unknown';
|
|
2928
|
+
const startedAt = new Date().toISOString();
|
|
2929
|
+
let connection = null;
|
|
2930
|
+
try {
|
|
2931
|
+
const connections = await browserConnections(flags);
|
|
2932
|
+
connection = (connections.connections || []).find(item => item.runId === runId && item.windowId === windowId) || null;
|
|
2933
|
+
} catch { /* command loop reports disconnected windows */ }
|
|
2934
|
+
|
|
2935
|
+
const results = [];
|
|
2936
|
+
const baseCommandId = flags.command || flags.commandId || '';
|
|
2937
|
+
for (let index = 0; index < primitives.length; index += 1) {
|
|
2938
|
+
const primitive = primitives[index];
|
|
2939
|
+
const itemStartedAt = Date.now();
|
|
2940
|
+
try {
|
|
2941
|
+
const output = await browserCommand({
|
|
2942
|
+
...flags,
|
|
2943
|
+
primitive,
|
|
2944
|
+
run: runId,
|
|
2945
|
+
window: windowId,
|
|
2946
|
+
lease: leaseId,
|
|
2947
|
+
agent: agentId,
|
|
2948
|
+
command: baseCommandId ? `${baseCommandId}-${String(index + 1).padStart(2, '0')}` : undefined,
|
|
2949
|
+
commandId: undefined,
|
|
2950
|
+
});
|
|
2951
|
+
results.push({
|
|
2952
|
+
primitive,
|
|
2953
|
+
ok: true,
|
|
2954
|
+
status: output.command?.status || 'ok',
|
|
2955
|
+
commandId: output.command?.commandId || '',
|
|
2956
|
+
artifact: output.artifact || null,
|
|
2957
|
+
durationMs: Date.now() - itemStartedAt,
|
|
2958
|
+
});
|
|
2959
|
+
} catch (err) {
|
|
2960
|
+
const failed = {
|
|
2961
|
+
primitive,
|
|
2962
|
+
ok: false,
|
|
2963
|
+
status: 'failed',
|
|
2964
|
+
error: err?.message || 'Snapshot primitive failed',
|
|
2965
|
+
durationMs: Date.now() - itemStartedAt,
|
|
2966
|
+
};
|
|
2967
|
+
results.push(failed);
|
|
2968
|
+
if (trueFlag(flags.strict)) throw err;
|
|
2969
|
+
}
|
|
2970
|
+
}
|
|
2971
|
+
|
|
2972
|
+
const artifacts = results.map(item => item.artifact).filter(Boolean);
|
|
2973
|
+
return {
|
|
2974
|
+
ok: results.every(item => item.ok),
|
|
2975
|
+
runId,
|
|
2976
|
+
windowId,
|
|
2977
|
+
leaseId,
|
|
2978
|
+
agentId,
|
|
2979
|
+
startedAt,
|
|
2980
|
+
completedAt: new Date().toISOString(),
|
|
2981
|
+
connection,
|
|
2982
|
+
primitives,
|
|
2983
|
+
results,
|
|
2984
|
+
artifacts,
|
|
2985
|
+
};
|
|
2986
|
+
}
|
|
2987
|
+
|
|
2988
|
+
async function findAgent(flags) {
|
|
2989
|
+
const cp = workflowApiControlPlane(flags, 'find-agent');
|
|
2990
|
+
const params = new URLSearchParams();
|
|
2991
|
+
for (const key of ['role', 'runtime', 'provider', 'capability', 'title']) {
|
|
2992
|
+
if (flags[key]) params.set(key, String(flags[key]));
|
|
2993
|
+
}
|
|
2994
|
+
const from = flags.from || flags['from-node'] || process.env.HARNESS_WORKFLOW_NODE_ID || '';
|
|
2995
|
+
if (from) params.set('from', from);
|
|
2996
|
+
if (trueFlag(flags.connect) || trueFlag(flags['auto-connect'])) params.set('autoConnect', '1');
|
|
2997
|
+
const qs = params.toString();
|
|
2998
|
+
return apiJson(cp.url, cp.token || cp.readToken, `/api/workflow/agents/find${qs ? `?${qs}` : ''}`, {
|
|
2999
|
+
actorNodeId: process.env.HARNESS_WORKFLOW_NODE_ID || '',
|
|
3000
|
+
actorSessionId: process.env.HARNESS_PEER_SESSION_ID || '',
|
|
3001
|
+
});
|
|
3002
|
+
}
|
|
3003
|
+
|
|
3004
|
+
async function agentRoleProfile(projectRoot, flags) {
|
|
3005
|
+
const nodeId = flags.node || flags.id || flags.session
|
|
3006
|
+
|| process.env.HARNESS_WORKFLOW_NODE_ID || '';
|
|
3007
|
+
if (!nodeId) throw new Error('Missing --node <agentNodeId> for agent-role-profile.');
|
|
3008
|
+
const cp = controlPlane(flags);
|
|
3009
|
+
if (cp.url) {
|
|
3010
|
+
return apiJson(cp.url, cp.token || cp.readToken, `/api/workflow/agents/profile?nodeId=${encodeURIComponent(nodeId)}`, {
|
|
3011
|
+
actorNodeId: process.env.HARNESS_WORKFLOW_NODE_ID || '',
|
|
3012
|
+
actorSessionId: process.env.HARNESS_PEER_SESSION_ID || '',
|
|
3013
|
+
});
|
|
3014
|
+
}
|
|
3015
|
+
const jsonPath = path.join(projectRoot, 'Harness', 'a2a', 'agent-roles', `${nodeId}.json`);
|
|
3016
|
+
const mdPath = path.join(projectRoot, 'Harness', 'a2a', 'agent-roles', `${nodeId}.md`);
|
|
3017
|
+
const profile = readJson(jsonPath, null);
|
|
3018
|
+
const markdown = fs.existsSync(mdPath) ? fs.readFileSync(mdPath, 'utf8') : '';
|
|
3019
|
+
return { ok: true, nodeId, profile, markdown };
|
|
3020
|
+
}
|
|
3021
|
+
|
|
3022
|
+
async function deleteNode(projectRoot, flags) {
|
|
3023
|
+
return nodeMap(projectRoot, { ...flags, action: 'deleteNode' });
|
|
3024
|
+
}
|
|
3025
|
+
|
|
3026
|
+
async function connectNodes(projectRoot, flags) {
|
|
3027
|
+
return nodeMap(projectRoot, { ...flags, action: 'connectNodes' });
|
|
3028
|
+
}
|
|
3029
|
+
|
|
3030
|
+
function tail(projectRoot, flags) {
|
|
3031
|
+
const sessionId = flags.session || process.env.HARNESS_PEER_SESSION_ID;
|
|
3032
|
+
const found = allSessionDirs(projectRoot).find(session => session.sessionId === sessionId);
|
|
3033
|
+
if (!found) return { sessionId, entries: [] };
|
|
3034
|
+
const lines = Math.min(Math.max(Number(flags.lines || flags.tail || 80), 1), 1000);
|
|
3035
|
+
return {
|
|
3036
|
+
taskId: found.taskId,
|
|
3037
|
+
sessionId,
|
|
3038
|
+
entries: readJsonl(path.join(found.dir, 'terminal.jsonl')).slice(-lines),
|
|
3039
|
+
};
|
|
3040
|
+
}
|
|
3041
|
+
|
|
3042
|
+
// ── Node manuals ─────────────────────────────────────────────────────────────
|
|
3043
|
+
// `manuals <nodeType>` prints the node manual JSON
|
|
3044
|
+
// (Harness/a2a/skills/workflow-<type>-node.json) verbatim plus a generated
|
|
3045
|
+
// `commands` section derived from the shared action registry
|
|
3046
|
+
// (actionsForNodeType), so the prose manual and the invocable action surface
|
|
3047
|
+
// arrive together. `excalidraw` resolves to the diagram manual
|
|
3048
|
+
// (workflow-diagram-node.json) exactly like the backend context layer's
|
|
3049
|
+
// EXCALIDRAW_MANUAL_ALIAS; the registry stores excalidraw actions under
|
|
3050
|
+
// nodeType 'excalidraw', so command generation works for both spellings.
|
|
3051
|
+
const MANUAL_TYPES = [
|
|
3052
|
+
'agent',
|
|
3053
|
+
'markdown',
|
|
3054
|
+
'excalidraw',
|
|
3055
|
+
'file',
|
|
3056
|
+
'display',
|
|
3057
|
+
'timer',
|
|
3058
|
+
'goal',
|
|
3059
|
+
'skill-group',
|
|
3060
|
+
'mcp-connector',
|
|
3061
|
+
'github-trigger',
|
|
3062
|
+
'diagram',
|
|
3063
|
+
];
|
|
3064
|
+
const MANUAL_TYPE_ALIAS = { excalidraw: 'diagram' };
|
|
3065
|
+
|
|
3066
|
+
function resolveManualType(value) {
|
|
3067
|
+
const raw = String(value || '').trim();
|
|
3068
|
+
const type = raw.toLowerCase();
|
|
3069
|
+
if (!type) {
|
|
3070
|
+
throw new Error(`manuals requires a node type. Valid types: ${MANUAL_TYPES.join('|')}.`);
|
|
3071
|
+
}
|
|
3072
|
+
if (MANUAL_TYPE_ALIAS[type]) return MANUAL_TYPE_ALIAS[type];
|
|
3073
|
+
if (!MANUAL_TYPES.includes(type)) {
|
|
3074
|
+
throw new Error(`Unknown manual type "${raw}". Valid types: ${MANUAL_TYPES.join('|')}.`);
|
|
3075
|
+
}
|
|
3076
|
+
return type;
|
|
3077
|
+
}
|
|
3078
|
+
|
|
3079
|
+
// Registry actions for a node type rendered as `id — summary` lines with the
|
|
3080
|
+
// action example appended when present (mirrors backend actionsForNodeType).
|
|
3081
|
+
function actionsForNodeTypeLines(registry, nodeType) {
|
|
3082
|
+
if (!registry || !Array.isArray(registry.actions)) return [];
|
|
3083
|
+
return registry.actions
|
|
3084
|
+
.filter(action => action && action.nodeType === nodeType)
|
|
3085
|
+
.map(action => {
|
|
3086
|
+
const summary = String(action.summary || '').trim().replace(/\.+$/, '');
|
|
3087
|
+
const base = `${action.id} — ${summary}`;
|
|
3088
|
+
return action.example ? `${base}. Example: ${action.example}` : base;
|
|
3089
|
+
});
|
|
3090
|
+
}
|
|
3091
|
+
|
|
3092
|
+
function manuals(projectRoot, flags, args = []) {
|
|
3093
|
+
if (trueFlag(flags.list)) {
|
|
3094
|
+
return { ok: true, types: MANUAL_TYPES, alias: MANUAL_TYPE_ALIAS };
|
|
3095
|
+
}
|
|
3096
|
+
const type = resolveManualType(args[0] || flags.type || '');
|
|
3097
|
+
const manualPath = path.join(projectRoot, 'Harness', 'a2a', 'skills', `workflow-${type}-node.json`);
|
|
3098
|
+
const manual = readJson(manualPath, null);
|
|
3099
|
+
if (!manual) {
|
|
3100
|
+
throw new Error(`Manual not found for node type "${type}" at ${manualPath} (expected Harness/a2a/skills/workflow-${type}-node.json). Valid types: ${MANUAL_TYPES.join('|')}.`);
|
|
3101
|
+
}
|
|
3102
|
+
const registry = readJson(path.join(projectRoot, 'Harness', 'a2a', 'action-registry.json'), null);
|
|
3103
|
+
// The manual declares the node type it documents (the diagram manual
|
|
3104
|
+
// declares 'excalidraw'), so action generation follows the manual's own
|
|
3105
|
+
// nodeType — excalidraw/diagram spellings both resolve to the excalidraw
|
|
3106
|
+
// actions stored under that type in the registry.
|
|
3107
|
+
const actionType = manual.nodeType || type;
|
|
3108
|
+
return {
|
|
3109
|
+
ok: true,
|
|
3110
|
+
type,
|
|
3111
|
+
manualPath: manual.source || `Harness/a2a/skills/workflow-${type}-node.json`,
|
|
3112
|
+
manual,
|
|
3113
|
+
commands: actionsForNodeTypeLines(registry, actionType),
|
|
3114
|
+
};
|
|
3115
|
+
}
|
|
3116
|
+
|
|
3117
|
+
async function main() {
|
|
3118
|
+
const { command, flags, args } = parseArgs(process.argv.slice(2));
|
|
3119
|
+
const projectRoot = path.resolve(flags.project || process.cwd());
|
|
3120
|
+
if (command === 'help' || command === '--help' || trueFlag(flags.help)) {
|
|
3121
|
+
const helpCommand = command === 'help' || command === '--help' ? (flags.cmd || flags.command || args[0] || '') : command;
|
|
3122
|
+
if (trueFlag(flags.json)) {
|
|
3123
|
+
process.stdout.write(`${JSON.stringify(helpJsonFor(helpCommand, projectRoot), null, 2)}\n`);
|
|
3124
|
+
return;
|
|
3125
|
+
}
|
|
3126
|
+
process.stdout.write(`${helpTextFor(helpCommand)}\n`);
|
|
3127
|
+
return;
|
|
3128
|
+
}
|
|
3129
|
+
const handler = COMMAND_DISPATCH[command];
|
|
3130
|
+
if (handler) {
|
|
3131
|
+
const result = await handler(projectRoot, flags, args);
|
|
3132
|
+
if (command === 'ensure-backend' && trueFlag(flags.json)) {
|
|
3133
|
+
process.stdout.write(`${JSON.stringify(result)}\n`);
|
|
3134
|
+
return;
|
|
3135
|
+
}
|
|
3136
|
+
return print(result);
|
|
3137
|
+
}
|
|
3138
|
+
throw new Error('Usage: wf-ui-control.mjs self|snapshot|describe|create-agent|find-agent|agent-role-profile|send-input|send-key|key|delegate-agent|send-agent-message|broadcast-agent-message|read-agent-messages|read-agent|bridge-messages|browser-runs|browser-run|browser-window|browser-windows|browser-lease|browser-url|browser-allocate|browser-allocate-many|browser-open|browser-launches|browser-close|browser-wait|browser-release|browser-artifacts|browser-connections|browser-cleanup|browser-command|browser-snapshot|workflow-node-map|workflow-ontology|workflow-context|read-node|workflow-node-action|node-map|connect|delete-node|tail|manuals|manual [--project .]\nPass --help after a command for command-specific flags.');
|
|
3139
|
+
}
|
|
3140
|
+
|
|
3141
|
+
main().catch((err) => {
|
|
3142
|
+
process.stderr.write(`${err.message}\n`);
|
|
3143
|
+
process.exit(1);
|
|
3144
|
+
});
|