@yanglaofish/fishtools 0.3.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (247) hide show
  1. package/README.md +33 -5
  2. package/bin/fst.js +1 -1
  3. package/manual/README.md +33 -5
  4. package/manual/docs/deployment.md +3 -3
  5. package/manual/docs/examples/script-plugin/LICENSE +21 -0
  6. package/manual/docs/examples/script-plugin/README.md +64 -0
  7. package/manual/docs/examples/script-plugin/index.js +39 -0
  8. package/manual/docs/examples/script-plugin/package.json +9 -0
  9. package/manual/docs/examples/script-plugin/scripts/benchmark.py +23 -0
  10. package/manual/docs/kernel-generation.md +97 -0
  11. package/manual/docs/modules.md +57 -0
  12. package/manual/docs/plugin-development.md +163 -12
  13. package/manual/docs/releases.md +23 -0
  14. package/manual/docs/research/2026-10-11-plugin-kernel-capabilities.md +6 -6
  15. package/manual/docs/roadmap.md +16 -3
  16. package/manual/docs/superpowers/plans/2026-10-09-workflow-mvp.md +5 -5
  17. package/manual/docs/superpowers/plans/2026-10-11-public-plugin-fixes.md +2 -2
  18. package/manual/docs/superpowers/plans/2026-10-12-kernel-generation.md +22 -0
  19. package/manual/docs/superpowers/plans/2026-10-12-modular-kernel.md +40 -0
  20. package/manual/docs/superpowers/plans/2026-10-12-plugin-developer-experience.md +50 -0
  21. package/manual/docs/superpowers/reviews/2026-10-12-kernel-generation-review.md +27 -0
  22. package/manual/docs/superpowers/reviews/2026-10-12-modular-kernel-review.md +32 -0
  23. package/manual/docs/superpowers/reviews/2026-10-12-plugin-release-review.md +20 -0
  24. package/manual/docs/superpowers/reviews/2026-10-13-kernel-runtime-release-review.md +19 -0
  25. package/manual/docs/superpowers/specs/2026-10-12-action-network-policy-design.md +52 -0
  26. package/manual/docs/superpowers/specs/2026-10-12-kernel-generation-design.md +38 -0
  27. package/manual/docs/superpowers/specs/2026-10-12-modular-kernel-design.md +70 -0
  28. package/manual/docs/superpowers/specs/2026-10-12-plugin-developer-experience.md +22 -0
  29. package/manual/docs/superpowers/specs/2026-10-12-plugin-readiness-design.md +21 -0
  30. package/manual/docs/superpowers/specs/2026-10-12-runtime-maintenance-granularity-design.md +35 -0
  31. package/manual/docs/superpowers/specs/2026-10-12-workflow-variation-design.md +19 -0
  32. package/manual/docs/verification.md +58 -1
  33. package/manual/packages/core/README.md +44 -0
  34. package/manual/packages/core/package.json +38 -0
  35. package/manual/packages/core/src/application.js +268 -87
  36. package/manual/packages/core/src/bundle.js +2 -0
  37. package/manual/packages/core/src/development-watch-worker.js +69 -0
  38. package/manual/packages/core/src/development.js +146 -0
  39. package/manual/packages/core/src/environments.js +2 -0
  40. package/manual/packages/core/src/export-snapshot.js +42 -0
  41. package/manual/packages/core/src/plugin-update.js +108 -0
  42. package/manual/packages/core/src/process.js +2 -0
  43. package/manual/packages/core/src/resources.js +31 -0
  44. package/manual/packages/core/src/version.js +2 -0
  45. package/manual/packages/fst-cli/LICENSE +21 -0
  46. package/manual/packages/fst-cli/README.md +54 -0
  47. package/manual/packages/fst-cli/bin/fst.js +124 -0
  48. package/manual/packages/fst-cli/package.json +49 -0
  49. package/manual/packages/fst-cli/src/development.js +53 -0
  50. package/{node_modules/@mytools/core → manual/packages/fst-cli}/src/terminal/model.js +1 -1
  51. package/{node_modules/@mytools/core → manual/packages/fst-cli}/src/terminal/navigation.js +1 -1
  52. package/{node_modules/@mytools/core → manual/packages/fst-cli}/src/terminal/presentation.js +9 -1
  53. package/{node_modules/@mytools/core → manual/packages/fst-cli}/src/terminal/run-feed.js +31 -5
  54. package/{node_modules/@mytools/core → manual/packages/fst-cli}/src/terminal/run-viewer.js +9 -7
  55. package/{node_modules/@mytools/core → manual/packages/fst-cli}/src/terminal/schema.js +1 -1
  56. package/manual/packages/{core → fst-cli}/src/terminal/session.js +137 -9
  57. package/manual/packages/{core → fst-cli}/src/terminal/view.js +20 -3
  58. package/{node_modules/@mytools/core → manual/packages/fst-cli}/src/terminal/workflow-menu.js +17 -2
  59. package/manual/packages/fst-devtools/LICENSE +21 -0
  60. package/manual/packages/fst-devtools/README.md +28 -0
  61. package/manual/packages/fst-devtools/package.json +29 -0
  62. package/{node_modules/@mytools/core → manual/packages/fst-devtools}/src/documentation.js +3 -1
  63. package/manual/packages/fst-devtools/src/plugin-quality.js +136 -0
  64. package/manual/packages/fst-devtools/src/plugin-tools.js +152 -0
  65. package/manual/packages/fst-logs/LICENSE +21 -0
  66. package/manual/packages/fst-logs/README.md +56 -0
  67. package/manual/packages/fst-logs/package.json +24 -0
  68. package/manual/packages/fst-logs/src/log-bounds.js +91 -0
  69. package/manual/packages/fst-logs/src/run-logs.js +239 -0
  70. package/manual/packages/fst-platform/LICENSE +21 -0
  71. package/manual/packages/fst-platform/README.md +67 -0
  72. package/manual/packages/fst-platform/package.json +33 -0
  73. package/manual/packages/fst-platform/src/bundle.js +17 -0
  74. package/manual/packages/fst-platform/src/environments.js +67 -0
  75. package/manual/packages/fst-platform/src/file-probe-engine.js +183 -0
  76. package/manual/packages/fst-platform/src/file-probe-worker.js +43 -0
  77. package/manual/packages/fst-platform/src/file-probe.js +6 -0
  78. package/manual/packages/fst-platform/src/json.js +5 -0
  79. package/manual/packages/fst-platform/src/network.js +199 -0
  80. package/manual/packages/fst-platform/src/npm.js +11 -0
  81. package/manual/packages/fst-platform/src/process.js +110 -0
  82. package/manual/packages/fst-plugin/LICENSE +21 -0
  83. package/manual/packages/fst-plugin/README.md +29 -0
  84. package/manual/packages/fst-plugin/package.json +29 -0
  85. package/manual/packages/fst-plugin/src/contracts.js +86 -0
  86. package/manual/packages/fst-plugin/src/host.js +232 -0
  87. package/manual/packages/fst-plugin/src/loader.js +21 -0
  88. package/manual/packages/fst-plugin/src/privileges.js +91 -0
  89. package/manual/packages/{core → fst-plugin}/src/runtime.js +20 -4
  90. package/manual/packages/fst-workflow/LICENSE +21 -0
  91. package/manual/packages/fst-workflow/README.md +28 -0
  92. package/manual/packages/fst-workflow/package.json +24 -0
  93. package/{node_modules/@mytools/core → manual/packages/fst-workflow}/src/control.js +3 -2
  94. package/manual/packages/{core → fst-workflow}/src/engine.js +2 -2
  95. package/manual/packages/fst-workspace/LICENSE +21 -0
  96. package/manual/packages/fst-workspace/README.md +32 -0
  97. package/manual/packages/fst-workspace/package.json +34 -0
  98. package/{node_modules/@mytools/core → manual/packages/fst-workspace}/src/bundle-workspace.js +1 -1
  99. package/manual/packages/fst-workspace/src/index.js +3 -0
  100. package/manual/packages/fst-workspace/src/plugin-installations.js +84 -0
  101. package/manual/packages/fst-workspace/src/plugins.js +35 -0
  102. package/{node_modules/@mytools/core → manual/packages/fst-workspace}/src/workflows.js +41 -21
  103. package/manual/packages/pack/LICENSE +21 -0
  104. package/manual/packages/pack/README.md +31 -0
  105. package/manual/packages/pack/archive.js +15 -0
  106. package/manual/packages/pack/dependencies.js +105 -0
  107. package/manual/packages/pack/environments.js +50 -0
  108. package/manual/packages/pack/index.js +103 -0
  109. package/manual/packages/pack/package.json +29 -0
  110. package/manual/packages/pack/runtime.js +61 -0
  111. package/manual/packages/sdk/README.md +31 -0
  112. package/manual/packages/sdk/conditions.js +1 -0
  113. package/manual/packages/sdk/package.json +23 -1
  114. package/manual/packages/sdk/schema-path.js +31 -0
  115. package/{node_modules/@mytools/core/src → manual/packages/sdk}/validation.js +22 -9
  116. package/manual/plugins/web/README.md +29 -0
  117. package/manual/tests/application.test.js +6 -4
  118. package/node_modules/@mytools/core/README.md +44 -0
  119. package/node_modules/@mytools/core/package.json +17 -15
  120. package/node_modules/@mytools/core/src/application.js +268 -87
  121. package/node_modules/@mytools/core/src/bundle.js +2 -17
  122. package/node_modules/@mytools/core/src/development-watch-worker.js +69 -0
  123. package/node_modules/@mytools/core/src/development.js +146 -0
  124. package/node_modules/@mytools/core/src/environments.js +2 -29
  125. package/node_modules/@mytools/core/src/export-snapshot.js +42 -0
  126. package/node_modules/@mytools/core/src/plugin-update.js +108 -0
  127. package/node_modules/@mytools/core/src/process.js +2 -71
  128. package/node_modules/@mytools/core/src/version.js +1 -1
  129. package/node_modules/@mytools/fst-cli/LICENSE +21 -0
  130. package/node_modules/@mytools/fst-cli/README.md +54 -0
  131. package/node_modules/@mytools/fst-cli/bin/fst.js +124 -0
  132. package/node_modules/@mytools/fst-cli/package.json +49 -0
  133. package/node_modules/@mytools/fst-cli/src/cli-run.js +12 -0
  134. package/node_modules/@mytools/fst-cli/src/development.js +53 -0
  135. package/node_modules/@mytools/fst-cli/src/terminal/conditions.js +32 -0
  136. package/node_modules/@mytools/fst-cli/src/terminal/log-viewer.js +45 -0
  137. package/node_modules/@mytools/fst-cli/src/terminal/model.js +104 -0
  138. package/node_modules/@mytools/fst-cli/src/terminal/navigation.js +17 -0
  139. package/node_modules/@mytools/fst-cli/src/terminal/presentation.js +50 -0
  140. package/{manual/packages/core → node_modules/@mytools/fst-cli}/src/terminal/run-feed.js +31 -5
  141. package/node_modules/@mytools/fst-cli/src/terminal/run-presentation.js +60 -0
  142. package/node_modules/@mytools/fst-cli/src/terminal/run-viewer.js +106 -0
  143. package/node_modules/@mytools/fst-cli/src/terminal/schema.js +52 -0
  144. package/node_modules/@mytools/{core → fst-cli}/src/terminal/session.js +137 -9
  145. package/node_modules/@mytools/fst-cli/src/terminal/theme.js +29 -0
  146. package/node_modules/@mytools/{core → fst-cli}/src/terminal/view.js +20 -3
  147. package/node_modules/@mytools/fst-cli/src/terminal/workflow-menu.js +65 -0
  148. package/node_modules/@mytools/fst-devtools/LICENSE +21 -0
  149. package/node_modules/@mytools/fst-devtools/README.md +28 -0
  150. package/node_modules/@mytools/fst-devtools/package.json +29 -0
  151. package/node_modules/@mytools/fst-devtools/src/documentation.js +67 -0
  152. package/node_modules/@mytools/fst-devtools/src/plugin-quality.js +136 -0
  153. package/node_modules/@mytools/fst-devtools/src/plugin-tools.js +152 -0
  154. package/node_modules/@mytools/fst-logs/LICENSE +21 -0
  155. package/node_modules/@mytools/fst-logs/README.md +56 -0
  156. package/node_modules/@mytools/fst-logs/package.json +24 -0
  157. package/node_modules/@mytools/fst-logs/src/log-bounds.js +91 -0
  158. package/node_modules/@mytools/fst-logs/src/markdown-log.js +55 -0
  159. package/node_modules/@mytools/fst-logs/src/run-logs.js +239 -0
  160. package/node_modules/@mytools/fst-platform/LICENSE +21 -0
  161. package/node_modules/@mytools/fst-platform/README.md +67 -0
  162. package/node_modules/@mytools/fst-platform/package.json +33 -0
  163. package/node_modules/@mytools/fst-platform/src/bundle.js +17 -0
  164. package/node_modules/@mytools/fst-platform/src/environments.js +67 -0
  165. package/node_modules/@mytools/fst-platform/src/file-probe-engine.js +183 -0
  166. package/node_modules/@mytools/fst-platform/src/file-probe-worker.js +43 -0
  167. package/node_modules/@mytools/fst-platform/src/file-probe.js +6 -0
  168. package/node_modules/@mytools/fst-platform/src/json.js +5 -0
  169. package/node_modules/@mytools/fst-platform/src/network.js +199 -0
  170. package/node_modules/@mytools/fst-platform/src/npm.js +11 -0
  171. package/node_modules/@mytools/fst-platform/src/process.js +110 -0
  172. package/node_modules/@mytools/fst-plugin/LICENSE +21 -0
  173. package/node_modules/@mytools/fst-plugin/README.md +29 -0
  174. package/node_modules/@mytools/fst-plugin/package.json +29 -0
  175. package/node_modules/@mytools/fst-plugin/src/contracts.js +86 -0
  176. package/node_modules/@mytools/fst-plugin/src/host.js +232 -0
  177. package/node_modules/@mytools/fst-plugin/src/loader.js +21 -0
  178. package/node_modules/@mytools/fst-plugin/src/privileges.js +91 -0
  179. package/node_modules/@mytools/{core → fst-plugin}/src/runtime.js +20 -4
  180. package/node_modules/@mytools/fst-workflow/LICENSE +21 -0
  181. package/node_modules/@mytools/fst-workflow/README.md +28 -0
  182. package/node_modules/@mytools/fst-workflow/package.json +24 -0
  183. package/node_modules/@mytools/fst-workflow/src/control.js +64 -0
  184. package/node_modules/@mytools/{core → fst-workflow}/src/engine.js +2 -2
  185. package/node_modules/@mytools/fst-workspace/LICENSE +21 -0
  186. package/node_modules/@mytools/fst-workspace/README.md +32 -0
  187. package/node_modules/@mytools/fst-workspace/package.json +34 -0
  188. package/node_modules/@mytools/fst-workspace/src/bundle-workspace.js +39 -0
  189. package/node_modules/@mytools/fst-workspace/src/index.js +3 -0
  190. package/node_modules/@mytools/fst-workspace/src/plugin-installations.js +84 -0
  191. package/node_modules/@mytools/fst-workspace/src/plugins.js +35 -0
  192. package/node_modules/@mytools/fst-workspace/src/project.js +29 -0
  193. package/node_modules/@mytools/fst-workspace/src/workflows.js +150 -0
  194. package/node_modules/@mytools/fst-workspace/templates/control-demo.yaml +47 -0
  195. package/node_modules/@mytools/fst-workspace/templates/hello.yaml +35 -0
  196. package/node_modules/@mytools/fst-workspace/templates/python-check.yaml +26 -0
  197. package/node_modules/@mytools/fst-workspace/templates/system-check.yaml +11 -0
  198. package/node_modules/@mytools/pack/README.md +31 -0
  199. package/node_modules/@mytools/pack/dependencies.js +15 -3
  200. package/node_modules/@mytools/pack/environments.js +1 -1
  201. package/node_modules/@mytools/pack/index.js +33 -25
  202. package/node_modules/@mytools/pack/package.json +29 -1
  203. package/node_modules/@mytools/sdk/README.md +31 -0
  204. package/node_modules/@mytools/sdk/conditions.js +1 -0
  205. package/node_modules/@mytools/sdk/diagnostics.js +27 -0
  206. package/node_modules/@mytools/sdk/package.json +23 -1
  207. package/node_modules/@mytools/sdk/schema-path.js +31 -0
  208. package/node_modules/@mytools/sdk/validation.js +78 -0
  209. package/node_modules/@mytools/web/README.md +29 -0
  210. package/node_modules/@mytools/web/package.json +7 -0
  211. package/node_modules/@mytools/web/public/THIRD_PARTY_NOTICES.txt +1275 -1076
  212. package/node_modules/@mytools/web/public/assets/index-BaVzHycP.js +70 -0
  213. package/node_modules/@mytools/web/public/assets/index-DA8Ae92t.css +1 -0
  214. package/node_modules/@mytools/web/public/index.html +2 -2
  215. package/node_modules/@mytools/web/server.js +24 -17
  216. package/node_modules/@mytools/web/src/model.js +84 -0
  217. package/node_modules/acorn/CHANGELOG.md +1012 -0
  218. package/node_modules/acorn/LICENSE +21 -0
  219. package/node_modules/acorn/README.md +309 -0
  220. package/node_modules/acorn/bin/acorn +4 -0
  221. package/node_modules/acorn/dist/acorn.d.mts +897 -0
  222. package/node_modules/acorn/dist/acorn.d.ts +897 -0
  223. package/node_modules/acorn/dist/acorn.js +6370 -0
  224. package/node_modules/acorn/dist/acorn.mjs +6341 -0
  225. package/node_modules/acorn/dist/bin.js +90 -0
  226. package/node_modules/acorn/package.json +50 -0
  227. package/package.json +117 -101
  228. package/manual/packages/core/src/host.js +0 -124
  229. package/node_modules/@mytools/core/bin/fst.js +0 -70
  230. package/node_modules/@mytools/core/src/contracts.js +0 -41
  231. package/node_modules/@mytools/core/src/host.js +0 -124
  232. package/node_modules/@mytools/core/src/plugins.js +0 -55
  233. package/node_modules/@mytools/core/src/run-logs.js +0 -63
  234. package/node_modules/@mytools/web/public/assets/index-DT2B4tMV.js +0 -70
  235. package/node_modules/@mytools/web/public/assets/index-vuZvrUTT.css +0 -1
  236. /package/{node_modules/@mytools/core → manual/packages/fst-cli}/src/cli-run.js +0 -0
  237. /package/{node_modules/@mytools/core → manual/packages/fst-cli}/src/terminal/conditions.js +0 -0
  238. /package/{node_modules/@mytools/core → manual/packages/fst-cli}/src/terminal/log-viewer.js +0 -0
  239. /package/{node_modules/@mytools/core → manual/packages/fst-cli}/src/terminal/run-presentation.js +0 -0
  240. /package/{node_modules/@mytools/core → manual/packages/fst-cli}/src/terminal/theme.js +0 -0
  241. /package/{node_modules/@mytools/core → manual/packages/fst-logs}/src/markdown-log.js +0 -0
  242. /package/{node_modules/@mytools/core → manual/packages/fst-workspace}/src/project.js +0 -0
  243. /package/{node_modules/@mytools/core → manual/packages/fst-workspace}/templates/control-demo.yaml +0 -0
  244. /package/{node_modules/@mytools/core → manual/packages/fst-workspace}/templates/hello.yaml +0 -0
  245. /package/{node_modules/@mytools/core → manual/packages/fst-workspace}/templates/python-check.yaml +0 -0
  246. /package/{node_modules/@mytools/core → manual/packages/fst-workspace}/templates/system-check.yaml +0 -0
  247. /package/{node_modules/@mytools/core/src → manual/packages/sdk}/diagnostics.js +0 -0
@@ -0,0 +1 @@
1
+ @font-face{font-family:Geist;font-style:normal;font-display:swap;font-weight:400;src:url(./geist-latin-400-normal-B40WzpMT.woff2)format("woff2"),url(./geist-latin-400-normal-akEymXtG.woff)format("woff")}@font-face{font-family:Geist;font-style:normal;font-display:swap;font-weight:500;src:url(./geist-latin-500-normal-CTWBw9NS.woff2)format("woff2"),url(./geist-latin-500-normal-BDXIbFrL.woff)format("woff")}@font-face{font-family:Geist;font-style:normal;font-display:swap;font-weight:600;src:url(./geist-latin-600-normal-CSETrqM2.woff2)format("woff2"),url(./geist-latin-600-normal-DsPlZH-9.woff)format("woff")}input:not([type=checkbox]),select,textarea{color:#304d3e;background:#fff;border:1px solid #cfdbd2;border-radius:6px;padding:9px 10px}input::placeholder,textarea::placeholder{color:#7b8d81}input:disabled{color:#68758c;background:0 0}fieldset{border:0;min-width:0;margin:0;padding:0}.rjsf{font-size:13px}.rjsf .form-group{margin-bottom:13px}.rjsf .control-label{color:#476253;margin-bottom:7px;font-size:13px;display:block}.rjsf input:not([type=checkbox]),.rjsf select,.rjsf textarea{width:100%;font-size:13px}.rjsf legend{margin:13px 0;padding:0;font-size:12px;font-weight:600}.rjsf .help-block,.rjsf .field-description{color:#657571;margin:5px 0 8px;font-size:12px;line-height:1.6}.rjsf .checkbox label{align-items:center;gap:7px;font-size:13px;display:flex}.rjsf input[type=checkbox]{accent-color:#137566}.rjsf .required{color:#c18678;margin-left:3px}.rjsf ul.error-detail{color:#b36359;padding-left:17px;font-size:12px}.rjsf .btn{color:#647799;font:inherit;cursor:pointer;background:#f5f7fb;border:1px solid #e0e6f0;border-radius:5px;padding:7px 11px}.rjsf .array-item{border:1px solid #e7ebf2;border-radius:6px;margin:8px 0;padding:10px}.rjsf .field-object{margin-bottom:10px}.rjsf .text-danger{color:#b46258}.rjsf .row{flex-wrap:wrap;display:flex}.rjsf .col-xs-9{width:75%}.rjsf .col-xs-3{width:25%}.rjsf .col-xs-12{width:100%}.rjsf .btn-group{gap:3px;display:flex}.rjsf .glyphicon-plus:before{content:"+"}.rjsf .glyphicon-remove:before{content:"×"}.rjsf .glyphicon-arrow-up:before{content:"↑"}.rjsf .glyphicon-arrow-down:before{content:"↓"}.rjsf .glyphicon-copy:before{content:"复制"}:root{color:#233734;font-synthesis:none;--ink:#233734;--muted:#657571;--line:#dce4e0;--accent:#137566;background:#f5f7f6;font-family:Geist,Segoe UI,Microsoft YaHei,sans-serif}*{box-sizing:border-box}body{margin:0;font-size:14px}button,input,select,textarea{font:inherit}button{cursor:pointer;transition:background .16s,border-color .16s,color .16s}button:disabled{cursor:not-allowed;opacity:.45}button:focus-visible,input:focus-visible,select:focus-visible,textarea:focus-visible,summary:focus-visible{outline-offset:3px;outline:3px solid #58ad9c}button svg{flex-shrink:0}input,select,textarea{min-width:0}p{line-height:1.75}h1,h2,h3,p{margin-top:0}button,a{-webkit-tap-highlight-color:transparent}.topbar{border-bottom:1px solid var(--line);background:#fff;justify-content:space-between;align-items:center;gap:20px;min-height:76px;padding:14px 32px;display:flex}.brand{color:var(--ink);text-align:left;background:0 0;border:0;align-items:center;gap:12px;padding:0;display:flex}.brand-mark{color:#fff;background:#153e35;border-radius:12px;place-items:center;width:42px;height:42px;display:grid}.brand strong{letter-spacing:-.6px;font-size:21px;font-weight:600}.brand strong span{letter-spacing:1px;color:var(--muted);margin-top:3px;font-size:11px;font-weight:400;display:block}.topbar-actions{align-items:center;gap:24px;display:flex}.local-label{color:var(--muted);align-items:center;gap:8px;font-size:12px;display:flex}.local-label i{background:#278772;border-radius:50%;width:6px;height:6px}.button{color:var(--ink);white-space:nowrap;background:#fff;border:1px solid #cfdad4;border-radius:8px;justify-content:center;align-items:center;gap:7px;min-height:40px;padding:10px 14px;font-size:13px;font-weight:500;display:inline-flex}.button:hover{background:#edf3f0;border-color:#aabfb4}.button.primary{color:#fff;background:var(--accent);border-color:var(--accent)}.button.primary:hover{background:#0d5b4e}.button.quiet{background:#f5f7f6;border-color:#e9eeeb}.count{color:#66796e;border-left:1px solid #d5dfd8;margin-left:3px;padding-left:9px;font-size:12px}.eyebrow{color:#58756a;letter-spacing:1px;font-size:12px;font-weight:500}.text-button{color:var(--accent);background:0 0;border:0;align-items:center;gap:6px;padding:6px 0;font-size:13px;display:inline-flex}.text-button:hover{color:#083d33}.icon{color:#61756b;background:0 0;border:0;border-radius:6px;justify-content:center;align-items:center;padding:7px;display:inline-flex}.icon:hover{color:var(--ink);background:#e7eeea}.muted{color:var(--muted);font-size:13px}.home{max-width:1240px;margin:0 auto;padding:52px 40px 28px}.welcome{border-bottom:1px solid var(--line);grid-template-columns:1.25fr 1fr;align-items:center;gap:64px;padding:4px 0 44px;display:grid}.welcome h1{letter-spacing:-1.8px;max-width:760px;margin:17px 0;font-size:clamp(32px,3.8vw,48px);font-weight:600;line-height:1.3}.welcome p{color:var(--muted);margin-bottom:0}.quick-guide{border-left:1px solid #d3dfd7;gap:23px;padding-left:26px;display:grid}.quick-guide>div{align-items:flex-start;gap:15px;display:flex}.quick-guide>div>span{color:#4f7362;border:1px solid #b6cdc0;border-radius:50%;flex-shrink:0;place-items:center;width:29px;height:29px;font-size:12px;display:grid}.quick-guide strong{font-size:14px;font-weight:600;line-height:29px}.quick-guide p{margin:3px 0 0;font-size:12px}.workflow-section{padding-top:34px}.section-heading{justify-content:space-between;align-items:center;gap:18px;margin-bottom:20px;display:flex}.section-heading h2{letter-spacing:-.4px;margin-bottom:6px;font-size:21px}.section-heading h2 span{color:#65816e;margin-left:8px;font-size:13px;font-weight:400}.section-heading p{color:var(--muted);margin-bottom:0;font-size:13px}.search{color:#7d9085;border:1px solid var(--line);background:#fff;border-radius:8px;align-items:center;gap:9px;padding:0 12px;display:flex}.search input:not([type=checkbox]){background:0 0;border:0;border-radius:0;width:100%;padding:12px 0;font-size:13px}.search input:focus-visible{outline-offset:0}.home-search{margin-bottom:20px}.home-search>span{white-space:nowrap;color:var(--muted);font-size:12px}.workflow-grid{grid-template-columns:repeat(3,minmax(0,1fr));grid-auto-flow:dense;gap:18px;display:grid}.workflow-card{border:1px solid var(--line);background:#fff;border-radius:12px;flex-direction:column;min-width:0;padding:22px 22px 0;transition:border-color .18s,box-shadow .18s;display:flex}.workflow-card:hover,.workflow-card:focus-within{border-color:#8bb7a3;box-shadow:0 5px 20px #20392e08}.workflow-card>header{justify-content:space-between;align-items:center;margin-bottom:21px;display:flex}.workflow-icon{color:#44715a;background:#edf4ee;border-radius:10px;place-items:center;width:40px;height:40px;display:grid}.step-count{color:var(--muted);font-size:12px}.workflow-card h3{overflow-wrap:anywhere;margin-bottom:9px;font-size:17px;font-weight:600}.workflow-card>p{color:var(--muted);flex:1;margin-bottom:22px;font-size:13px}.workflow-card dl{gap:8px;margin:0 0 20px;font-size:12px;display:grid}.workflow-card dl>div{gap:13px;display:flex}.workflow-card dt{color:#758b7d;flex-shrink:0}.workflow-card dd{color:#486050;overflow-wrap:anywhere;margin:0}.card-open{width:100%;color:var(--accent);background:0 0;border:0;border-top:1px solid #e9eeeb;justify-content:space-between;align-items:center;padding:15px 0;font-weight:500;display:flex}.card-open:hover{color:#073b2b}.card-secondary{border-top:1px solid #e9eeeb;justify-content:space-between;gap:16px;padding:9px 0 13px;display:flex}.card-secondary .text-button{font-size:12px}.home-empty{text-align:center;color:var(--muted);padding:45px 15px}.home-empty h3{color:var(--ink);margin:15px 0 8px;font-size:16px}.home-footer{color:#6a7e72;justify-content:space-between;gap:18px;margin-top:36px;font-size:12px;display:flex}.home-footer span{align-items:center;gap:8px;display:inline-flex}.workspace{grid-template-columns:258px minmax(0,1fr) 320px;min-height:calc(100vh - 76px);display:grid}.library{border-right:1px solid var(--line);background:#fafcfb;min-width:0;padding:27px 18px}.library-heading h2{margin-bottom:6px;font-size:18px}.library-heading p{color:var(--muted);margin-bottom:21px;font-size:12px}.tabs{border-bottom:1px solid var(--line);gap:10px;margin:20px 0 16px;display:flex}.tabs button{color:var(--muted);background:0 0;border:0;border-bottom:2px solid #0000;flex:1;padding:0 0 12px;font-size:12px}.tabs button.active{color:var(--accent);border-bottom-color:var(--accent);font-weight:600}.tabs span{margin-left:4px;font-size:11px}.library-items{flex-direction:column;gap:12px;display:flex}.capability{border:1px solid var(--line);background:#fff;border-radius:9px;min-width:0;padding:15px}.capability:hover{border-color:#9dbeac}.capability-title{color:#395548;align-items:center;gap:8px;display:flex}.capability-title strong{color:var(--ink);font-size:13px;font-weight:500}.capability p{color:var(--muted);margin:11px 0 16px;font-size:12px}.capability-footer{justify-content:space-between;align-items:center;gap:10px;display:flex}.capability-footer>span{color:#728278;overflow-wrap:anywhere;min-width:0;font-size:11px}.capability-footer button{color:#14624b;white-space:nowrap;background:#eaf3ed;border:0;border-radius:5px;align-items:center;gap:3px;padding:6px 9px;font-size:12px;display:flex}.capability-footer button:hover{background:#d6e8dc}.library-open{margin-top:8px;font-size:11px}.library-note{color:#74867a;align-items:flex-start;gap:9px;padding-top:25px;font-size:11px;line-height:1.75;display:flex}.library-note svg{flex-shrink:0;margin-top:2px}.library-empty{padding:20px 0}.canvas{min-width:0;padding:0 34px 44px}.canvas-toolbar{border-bottom:1px solid var(--line);justify-content:space-between;align-items:center;gap:12px;height:65px;display:flex}.back-button{color:#5e7566;font-size:12px}.draft-tag{color:#627b6a;font-size:11px}.draft-tag.unsaved{color:#926027}.flow-heading{padding:24px 0 20px}.flow-heading input.flow-title{letter-spacing:-.8px;width:100%;color:var(--ink);background:0 0;border:1px solid #0000;border-radius:5px;padding:6px 0;font-size:28px;font-weight:600;display:block}.flow-heading input.flow-description{width:100%;color:var(--muted);background:0 0;border:1px solid #0000;padding:6px 0;font-size:13px;display:block}.flow-heading input:hover:not(:disabled),.flow-heading input:focus-visible{border-bottom-color:#a6c3b1}.editor-guide{border-bottom:1px solid var(--line);color:#687f6e;flex-wrap:wrap;gap:10px 20px;margin-bottom:22px;padding-bottom:22px;font-size:11px;display:flex}.editor-guide span{align-items:center;gap:7px;display:inline-flex}.editor-guide b{border:1px solid #bacdc0;border-radius:50%;place-items:center;width:18px;height:18px;font-size:10px;font-weight:500;display:grid}.flow-actions{flex-wrap:wrap;justify-content:space-between;align-items:center;gap:15px;margin-bottom:20px;display:flex}.flow-meta{color:#748579;flex-wrap:wrap;align-items:center;gap:8px;font-size:11px;display:flex}.flow-meta strong{color:#3f5f4e;font-size:12px;font-weight:500}.flow-meta .saved{color:#187754;align-items:center;gap:4px;display:flex}.action-group{flex-wrap:wrap;gap:7px;display:flex}.action-group .button{padding:9px 12px;font-size:12px}.step{cursor:pointer;background:#fff;border:1px solid #d6e1d9;border-radius:10px;align-items:center;gap:10px;min-width:0;padding:18px 12px;transition:border-color .18s,background .18s;display:flex;position:relative}.step:hover{border-color:#8eb89f}.step.selected{background:#fcfefd;border-color:#198260;box-shadow:0 0 0 2px #d7eae0}.step-number{color:#58725f;background:#eff4ef;border-radius:7px;flex-shrink:0;place-items:center;width:30px;height:30px;font-size:12px;display:grid}.step-text{min-width:0;color:var(--ink);text-align:left;background:0 0;border:0;flex:1;padding:3px 0}.step-text strong{font-size:14px;font-weight:500;display:block}.step-text>span{color:var(--muted);white-space:nowrap;text-overflow:ellipsis;margin-top:5px;font-size:12px;display:block;overflow:hidden}.step-text small{color:#2b7960;margin-top:8px;font-size:11px;display:block}.step-buttons{gap:0;display:flex}.step-buttons .icon{padding:6px}.grip{cursor:grab;touch-action:none;color:#8a9d90;padding:2px}.connector{color:#a5bcae;align-items:center;height:25px;padding-left:46px;display:flex}.step.succeeded .step-number{color:#21855a;background:#e0f1e7}.step.failed{border-color:#d09c91}.step.failed .step-number{color:#ae4937;background:#f9e8e4}.status-dot{background:#d7e4da;border-radius:50%;width:5px;height:5px}.status-dot.succeeded{background:#299a65}.status-dot.failed{background:#be6654}.status-dot.started{background:#299a65;animation:1s infinite pulse}.list-hint{color:#708576;text-align:center;padding:17px 0 4px;font-size:11px}.add-step-button{color:#397557;background:0 0;border:1px dashed #afc7b7;border-radius:8px;flex-wrap:wrap;justify-content:center;align-items:center;gap:7px;width:100%;margin:18px 0;padding:15px;font-size:13px;display:flex}.add-step-button span{color:var(--muted);margin-left:5px;font-size:11px}.add-step-button:hover{background:#edf5ef}.empty-editor{text-align:center;padding:28px 12px 18px}.empty-icon{color:#6b9476;background:#e6efe7;border-radius:12px;place-items:center;width:46px;height:46px;margin:0 auto 18px;display:grid}.empty-editor h3{margin-bottom:8px;font-size:17px}.empty-editor p{color:var(--muted);font-size:13px}.inspector{border-left:1px solid var(--line);background:#fff;min-width:0}.inspector-top{border-bottom:1px solid var(--line);justify-content:space-between;padding:24px;font-size:13px;font-weight:500;display:flex}.inspector-top span{color:var(--muted);font-size:11px;font-weight:400}.inspector-heading{padding:27px 24px 5px}.inspector-heading h2{margin:10px 0;font-size:19px;line-height:1.5}.inspector-heading p{color:var(--muted);font-size:12px}.inspector-section{border-top:1px solid #e9eeeb;padding:20px 24px}.inspector-section h3{margin-bottom:15px;font-size:13px;font-weight:500}.inspector-placeholder{text-align:center;color:#8da394;padding:80px 25px}.inspector-placeholder h3{color:#607e6a;margin:20px 0 10px;font-size:15px}.inspector-placeholder p{color:var(--muted);font-size:13px}.input-sources summary{cursor:pointer;font-size:13px}.input-sources summary span{color:var(--muted);margin:6px 0 0 14px;font-size:11px;display:block}.source-field{margin:17px 0 0;display:block}.source-field>span{justify-content:space-between;gap:10px;margin-bottom:8px;font-size:12px;display:flex}.source-field small{color:var(--muted);overflow-wrap:anywhere;font-size:10px}.source-field select{width:100%;font-size:12px}.output-field{flex-wrap:wrap;justify-content:space-between;gap:4px;margin-bottom:15px;font-size:12px;display:flex}.output-field code{color:#687f6c;background:#eef3ef;border-radius:3px;padding:2px 5px;font-size:10px}.output-field small{width:100%;color:var(--muted);font-size:10px}.workflow-outputs{border-top:1px solid var(--line);color:#4e6b59;padding:20px 0;font-size:12px}.workflow-outputs summary{cursor:pointer}.workflow-outputs summary span{color:var(--muted);margin-left:8px;font-size:11px}.output-binding{align-items:center;gap:8px;margin:14px 0;display:flex}.output-binding .output-name{width:100px}.output-binding select{flex:1;width:0;min-width:0;font-size:12px}.run-panel{border-top:1px solid var(--line);margin-top:0;padding-top:23px}.run-panel>header{justify-content:space-between;align-items:center;margin-bottom:16px;display:flex}.run-panel h3{margin:10px 0 0;font-size:17px;font-weight:500}.run-panel.succeeded h3{color:#197149}.run-panel.failed h3{color:#a74734}.run-events{flex-direction:column;gap:10px;display:flex}.event{color:#387f56;align-items:center;gap:8px;font-size:12px;display:flex}.event small{color:var(--muted);margin-left:auto}.event.bad{color:#aa5140}.result{margin-top:24px;font-size:12px}.result>span{color:var(--muted)}.result-value{border-left:3px solid #8fbea3;margin-top:12px;padding:4px 0 4px 14px}.result-value strong{color:#62816b;font-size:11px;font-weight:400}.result-value p{color:#254b34;white-space:pre-wrap;overflow-wrap:anywhere;margin:6px 0 0;font-size:17px}.result pre,.run-error pre,.step-results pre{color:#425e4e;white-space:pre-wrap;overflow-wrap:anywhere;background:#edf2ee;border-radius:6px;max-height:260px;padding:14px;font-size:12px;line-height:1.7;overflow:auto}.run-error{color:#aa5140;overflow-wrap:anywhere;margin-top:15px;font-size:13px}.run-error summary,.step-results summary{cursor:pointer}.step-results{color:#5c7665;margin-top:22px;font-size:12px}.step-results strong{margin-top:17px;font-size:12px;font-weight:500;display:block}.run-id{color:#778c7c;overflow-wrap:anywhere;margin-top:18px;font-size:10px;display:block}.alert{border-radius:8px;align-items:center;gap:10px;padding:14px 18px;font-size:13px;line-height:1.7;display:flex}.alert.error{color:#a54a36;background:#fbeee9}.notice-alert{color:#286446;background:#eaf5ed}.alert>span{overflow-wrap:anywhere;flex:1}.global-alert{margin:18px 30px}.loading-state{color:var(--muted);justify-content:center;gap:12px;padding:90px 20px;display:flex}.modal{width:560px;max-width:calc(100vw - 32px);max-height:calc(100dvh - 40px);color:var(--ink);background:#fff;border:1px solid #dce5de;border-radius:16px;padding:0;box-shadow:0 24px 100px #14271d33}.modal::backdrop{-webkit-backdrop-filter:blur(3px);backdrop-filter:blur(3px);background:#172c2466}.modal[open]{animation:.16s ease-out appear}.modal-content{padding:27px}.modal-content>header{justify-content:space-between;align-items:center;gap:15px;margin-bottom:22px;display:flex}.modal h2{margin:0;font-size:20px}.modal code,.network-guide code{font-family:ui-monospace,Cascadia Code,monospace}.modal-footer{border-top:1px solid var(--line);justify-content:flex-end;gap:10px;margin-top:24px;padding-top:20px;display:flex}.modal .library-heading{display:none}.inspector-dialog{width:490px}.inspector-dialog .inspector-heading{padding:0 0 14px}.inspector-dialog .inspector-section{padding:20px 0}.plugin-list>div{border-bottom:1px solid var(--line);justify-content:space-between;align-items:center;gap:16px;padding:18px 0;display:flex}.plugin-list strong{font-size:14px}.plugin-list p{color:var(--muted);overflow-wrap:anywhere;margin:6px 0 0;font-size:12px}.plugin-list details{margin-top:8px;font-size:12px}.plugin-list summary{cursor:pointer}.plugin-list details ul{margin:8px 0 0;padding-left:19px}.plugin-list details li{overflow-wrap:anywhere;margin:5px 0}.install-form{margin-top:25px}.install-form>label{font-size:14px;font-weight:500}.install-form>div{gap:9px;display:flex}.install-form input{flex:1;min-width:0}.danger{color:#ac503c}.check-row{align-items:center;gap:10px;padding:13px 0;font-size:13px;display:flex}.check-row input{accent-color:var(--accent);width:16px;height:16px}.check-row small{color:var(--muted);margin-left:auto}.export-options{border:1px solid var(--line);border-radius:8px;margin:20px 0;padding:5px 15px}.form-label{margin-top:20px;font-size:13px;display:block}.form-label select{width:100%;margin-top:10px;display:block}.export-status{background:#eef5f0;border-radius:8px;margin-top:20px;padding:16px;font-size:13px}.export-status p{overflow-wrap:anywhere;margin:10px 0;font-size:12px}.export-status.failed{background:#fbeee9}.field-error{overflow-wrap:anywhere;font-size:12px;line-height:1.7;display:block;color:#a34b34!important}.danger-button{color:#a74734;border-color:#d8a79b}.danger-button:hover{background:#fbeee9;border-color:#c67f6c}.network-dialog{width:740px}.network-guide{border:1px solid var(--line);background:#f5f8f6;border-radius:8px;padding:14px 17px}.network-guide p{overflow-wrap:anywhere;margin:0 0 8px;font-size:12px}.network-guide p:last-child{margin-bottom:0}.network-json{resize:vertical;width:100%;min-height:230px;margin-top:9px;font:12px/1.6 ui-monospace,Cascadia Code,monospace;display:block}.network-effective{border:1px solid var(--line);border-radius:8px;margin-top:16px;padding:12px 16px;font-size:12px}.network-effective h3{margin:0 0 7px;font-size:13px}.network-effective p{margin:5px 0}.network-effective ul{max-height:160px;margin:7px 0 0;padding-left:19px;overflow:auto}.network-effective li{overflow-wrap:anywhere;margin:5px 0}.network-example{color:var(--muted);margin-top:14px;font-size:12px}.network-example summary{cursor:pointer}.network-example pre{color:#3d5949;background:#f4f7f5;border-radius:8px;max-height:230px;padding:12px;overflow:auto}.plugin-confirmation,.privilege-confirmation{color:#664a3f;background:#fff7f3;border:1px solid #e7c6b9;border-radius:8px;margin-top:20px;padding:15px;font-size:12px}.plugin-confirmation strong,.privilege-confirmation strong{font-size:13px}.plugin-confirmation p{overflow-wrap:anywhere;margin:10px 0}.plugin-confirmation ul,.privilege-confirmation ul{margin:10px 0;padding-left:20px}.plugin-confirmation li{overflow-wrap:anywhere;line-height:1.8}.privilege-confirmation li{margin-bottom:10px}.privilege-confirmation li span{overflow-wrap:anywhere;line-height:1.7;display:block}.spin{animation:1s linear infinite spin}@keyframes spin{to{transform:rotate(360deg)}}@keyframes pulse{50%{opacity:.4}}@keyframes appear{0%{opacity:.5;transform:translateY(5px)}to{opacity:1;transform:translateY(0)}}@media (width>=1600px){.workspace{grid-template-columns:280px minmax(0,1fr) 360px}.canvas{padding-left:60px;padding-right:60px}}@media (width<=1199px){.workspace{grid-template-columns:250px minmax(0,1fr)}.canvas{padding-left:26px;padding-right:26px}.home{padding:40px 30px 26px}.welcome{gap:35px}.workflow-grid{grid-template-columns:repeat(2,minmax(0,1fr))}}@media (width<=760px){.topbar{padding:14px 20px}.local-label{display:none}.home{padding:28px 20px}.welcome{grid-template-columns:1fr;gap:28px;padding-bottom:28px}.welcome h1{font-size:35px}.quick-guide{border:0;gap:18px;padding:0;display:flex}.quick-guide>div{flex:1;display:block}.quick-guide strong{margin-top:6px;font-size:12px;line-height:1.6;display:block}.quick-guide p{font-size:11px}.quick-guide>div>span{width:24px;height:24px;font-size:11px}.workspace{display:block}.workspace>.library{display:none}.canvas{padding:0 20px 30px}.home-footer{flex-direction:column;gap:8px}.step-buttons{flex-direction:column}.home-search>span{display:none}}@media (width<=480px){.workflow-grid{grid-template-columns:1fr}.workflow-card{padding:20px 20px 0}.welcome h1{font-size:32px}.welcome p{font-size:13px}.brand strong{font-size:19px}.brand strong span{display:none}.brand-mark{border-radius:9px;width:35px;height:35px}.topbar{gap:12px;min-height:64px;padding:12px 16px}.topbar .button{padding:8px 10px;font-size:12px}.topbar .count{display:none}.section-heading{align-items:flex-start}.section-heading h2{font-size:18px}.section-heading p{font-size:12px}.section-heading .button{padding:9px 11px;font-size:12px}.flow-heading input.flow-title{font-size:25px}.editor-guide{gap:10px}.editor-guide span{gap:4px;font-size:10px}.editor-guide b{width:16px;height:16px}.action-group{width:100%}.action-group .primary{flex:1}.add-step-button span{display:none}.modal-content{padding:20px}.modal h2{font-size:18px}.step{gap:7px;padding:12px 9px}.step-text>span{max-width:100%}}@media (prefers-reduced-motion:reduce){*,:before,:after{scroll-behavior:auto!important;transition:none!important;animation:none!important}}.nested-steps{border-left:2px solid #bcd3c7;min-width:0;margin:10px 0 14px 15px;padding:10px 0 8px 12px}.nested-steps>header{color:#436653;justify-content:space-between;gap:10px;margin-bottom:12px;font-size:12px;display:flex}.nested-steps>header>span{color:var(--muted)}.nested-steps .empty-editor{text-align:left;padding:8px 0;font-size:12px}.nested-steps .step{flex-wrap:wrap}.nested-steps .step-buttons{margin-left:auto}.nested-steps .step-text{flex-basis:90px}.condition-editor{min-width:0}.condition-editor label{margin:10px 0;font-size:12px;display:block}.condition-editor select,.condition-editor input,.form-label>input{width:100%;margin-top:7px;font-size:12px;display:block}.condition-child{border-left:2px solid var(--line);min-width:0;margin:12px 0 10px 4px;padding:0 0 0 10px}.condition-node,.condition-operand{min-width:0}.condition-editor .muted{font-size:11px}.step.skipped{opacity:.65}.step.stopped .step-number{color:#8b5e18;background:#f8ead2}.step.cancelled .step-number{color:#657571;background:#e5e9e7}.run-reason{color:#775721;overflow-wrap:anywhere;background:#fbf1df;border-radius:7px;padding:12px}.observation-dialog{width:850px}.observation-dialog .run-panel{border:0;padding-top:0}.observation-filter{margin:12px 0 16px;font-size:12px;display:block}.observation-filter select{width:100%;margin-top:7px;font-size:12px;display:block}.run-step-list{border:1px solid var(--line);border-radius:8px;max-height:350px;overflow:auto}.run-step{border:0;border-bottom:1px solid var(--line);text-align:left;width:100%;color:var(--ink);background:#fff;justify-content:space-between;align-items:center;gap:12px;padding:12px;font-size:12px;display:flex}.run-step:hover,.run-step.selected{background:#eaf3ed}.run-step-name{min-width:0}.run-step-name strong{font-weight:500;display:block}.run-step-name small{color:var(--muted);overflow-wrap:anywhere;margin-top:4px;display:block}.run-step>span:last-child{color:var(--muted);flex-shrink:0}.run-step.succeeded>span:last-child{color:#187754}.run-step.failed>span:last-child{color:#b24b36}.run-step.started>span:last-child{color:#137566}.observation-details pre,.live-logs{white-space:pre-wrap;overflow-wrap:anywhere;background:#edf2ee;border-radius:7px;max-height:320px;padding:14px;font-size:12px;line-height:1.7;overflow:auto}.observation-details h5{margin:12px 0 6px;font-weight:500}.live-logs{color:#deeee3;scrollbar-gutter:stable;background:#142d25;height:300px}.log-toolbar{color:var(--muted);flex-wrap:wrap;justify-content:space-between;align-items:center;gap:10px;font-size:11px;display:flex}.log-path{color:#9dc4ac;font-size:10px}.log-stderr{color:#ffb5a1}@media (width<=760px){.topbar-actions{flex-wrap:wrap;justify-content:flex-end;gap:7px}.nested-steps{margin-left:4px;padding-left:8px}.nested-steps .step-buttons{flex-direction:row}.step-list .nested-steps .nested-steps .nested-steps{margin-left:0;padding-left:5px}}
@@ -1,4 +1,4 @@
1
1
  <!doctype html>
2
- <html lang="zh-CN"><head><meta charset="UTF-8"/><meta name="viewport" content="width=device-width, initial-scale=1"/><meta name="theme-color" content="#f6f7f9"/><title>Fishtools · 工作流工作台</title> <script type="module" crossorigin src="./assets/index-DT2B4tMV.js"></script>
3
- <link rel="stylesheet" crossorigin href="./assets/index-vuZvrUTT.css">
2
+ <html lang="zh-CN"><head><meta charset="UTF-8"/><meta name="viewport" content="width=device-width, initial-scale=1"/><meta name="theme-color" content="#f6f7f9"/><title>Fishtools · 工作流工作台</title> <script type="module" crossorigin src="./assets/index-BaVzHycP.js"></script>
3
+ <link rel="stylesheet" crossorigin href="./assets/index-DA8Ae92t.css">
4
4
  </head><body><div id="root"></div></body></html>
@@ -3,43 +3,50 @@ import { randomBytes, timingSafeEqual, randomUUID } from 'node:crypto';
3
3
  import { fileURLToPath } from 'node:url';
4
4
 
5
5
  export async function startWeb(application,{host='127.0.0.1',port=8765,token=randomBytes(24).toString('hex')}={}){
6
- const web=express(),jobs=new Map();
6
+ const web=express(),jobs=new Map(),instanceId=randomUUID();
7
+ const fail=(res,status,error,code='BAD_REQUEST',details=null)=>res.status(status).json({error,code,details});
8
+ const writable=(_req,res,next)=>application.project.readOnly?fail(res,403,'只读运行包不能编辑项目','READ_ONLY'):next();
7
9
  web.disable('x-powered-by');
8
10
  web.use((_req,res,next)=>{res.setHeader('X-Content-Type-Options','nosniff');res.setHeader('Referrer-Policy','no-referrer');res.setHeader('Content-Security-Policy',"default-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; connect-src 'self'; frame-ancestors 'none'");next();});
9
11
  web.use('/api',(req,res,next)=>{
10
12
  if(req.headers.origin){
11
- try{if(new URL(req.headers.origin).host!==req.headers.host)return res.status(403).json({error:'请求来源不匹配'});}catch{return res.status(403).json({error:'请求来源无效'});}
13
+ try{if(new URL(req.headers.origin).host!==req.headers.host)return fail(res,403,'请求来源不匹配','ORIGIN_MISMATCH');}catch{return fail(res,403,'请求来源无效','ORIGIN_INVALID');}
12
14
  }
13
15
  const actual=Buffer.from(req.headers.authorization??''),expected=Buffer.from(`Bearer ${token}`);
14
- if(actual.length!==expected.length||!timingSafeEqual(actual,expected))return res.status(401).json({error:'请使用启动终端给出的完整访问地址'});
16
+ if(actual.length!==expected.length||!timingSafeEqual(actual,expected))return fail(res,401,'请使用启动终端给出的完整访问地址','AUTH_REQUIRED');
15
17
  res.setHeader('Cache-Control','no-store');next();
16
18
  });
17
19
  web.use('/api',express.json({limit:'2mb'}));
18
- web.get('/api/bootstrap',(_req,res)=>res.json({readOnly:!!application.project.readOnly,platform:process.platform,project:application.projectDir}));
20
+ web.get('/api/bootstrap',(_req,res)=>res.json({readOnly:!!application.project.readOnly,platform:process.platform,project:application.projectDir,instanceId,development:process.env.FISHTOOLS_DEV_CHILD==='1'}));
19
21
  web.get('/api/catalog',(_req,res)=>res.json(application.catalog()));
20
22
  web.get('/api/plugins',(_req,res)=>res.json(application.listPlugins()));
21
23
  web.post('/api/export-plan',async(req,res)=>res.json(await application.packPlan(req.body)));
22
- web.post('/api/plugins',async(req,res)=>res.json(await application.installPlugin(req.body.source)));
23
- web.delete('/api/plugins',async(req,res)=>res.json(await application.removePlugin(req.body.name)));
24
+ web.post('/api/plugins',writable,async(req,res)=>res.json(await application.installPlugin(req.body.source,{allowMaintenance:!!req.body.allowMaintenance})));
25
+ web.delete('/api/plugins',writable,async(req,res)=>res.json(await application.removePlugin(req.body.name,{force:!!req.body.force,allowMaintenance:!!req.body.allowMaintenance})));
26
+ web.get('/api/network',(_req,res)=>res.json(application.networkPolicy()));
27
+ web.get('/api/network/report',(_req,res)=>res.json(application.networkReport()));
28
+ web.put('/api/network',writable,async(req,res)=>res.json(await application.configureNetworkPolicy(req.body)));
24
29
  web.get('/api/workflows',async(_req,res)=>res.json(await application.listWorkflows()));
25
- web.post('/api/workflows',async(req,res)=>res.json(await application.saveWorkflow(req.body)));
26
- web.post('/api/check',async(req,res)=>{await application.checkWorkflow(req.body.id);res.json({ok:true});});
30
+ web.post('/api/workflows',writable,async(req,res)=>res.json(await application.saveWorkflow(req.body)));
31
+ web.post('/api/workflows/duplicate',writable,async(req,res)=>res.json(await application.duplicateWorkflow(req.body.definition,{id:req.body.id,title:req.body.title})));
32
+ web.delete('/api/workflows/:id',writable,async(req,res)=>res.json({backup:await application.removeWorkflow(req.params.id)}));
33
+ web.post('/api/check',async(req,res)=>{const report=await application.checkWorkflow(req.body.id);res.json({ok:true,privileges:report.privileges});});
27
34
  web.get('/api/runs',(_req,res)=>res.json(application.listRuns()));
28
- web.post('/api/runs',async(req,res)=>{const run=await application.startRun(req.body.id,req.body.input??{});res.status(202).json({id:run.id});});
29
- web.get('/api/runs/:id',async(req,res)=>{try{const r=await application.readRun(req.params.id);res.json(r);}catch(error){res.status(404).json({error:error.message});}});
35
+ web.post('/api/runs',async(req,res)=>{const run=await application.startRun(req.body.id,req.body.input??{},{allowUnknownPrivileges:!!req.body.allowUnknownPrivileges});res.status(202).json({id:run.id});});
36
+ web.get('/api/runs/:id',async(req,res)=>{try{const r=await application.readRun(req.params.id);res.json(r);}catch(error){fail(res,404,error.message,error.code??'RUN_NOT_FOUND',error.details??null);}});
37
+ web.get('/api/runs/:id/logs',async(req,res)=>res.json(await application.readLogPage(req.params.id,{cursor:req.query.cursor===undefined?0:Number(req.query.cursor),limit:req.query.limit===undefined?65536:Number(req.query.limit)})));
30
38
  web.post('/api/runs/:id/cancel',(req,res)=>res.json({cancelled:application.cancelRun(req.params.id)}));
31
- web.post('/api/exports',(req,res)=>{
32
- if(application.project.readOnly)return res.status(400).json({error:'运行包不包含重新打包工具,请在开发项目导出'});
33
- if([...jobs.values()].some(j=>j.status==='running'))return res.status(409).json({error:'已有导出任务正在运行'});
39
+ web.post('/api/exports',writable,(req,res)=>{
40
+ if([...jobs.values()].some(j=>j.status==='running'))return fail(res,409,'已有导出任务正在运行','EXPORT_BUSY');
34
41
  const id=randomUUID(),job={id,status:'running'};jobs.set(id,job);
35
42
  application.pack({...req.body,outDir:req.body.outDir??'dist'}).then(result=>{job.status='succeeded';job.result=result;},error=>{job.status='failed';job.error=error.message;});
36
43
  res.status(202).json({id});
37
44
  });
38
- web.get('/api/exports/:id',(req,res)=>{const job=jobs.get(req.params.id);if(!job)return res.sendStatus(404);res.json(job);});
39
- web.get('/api/exports/:id/download',(req,res)=>{const job=jobs.get(req.params.id);if(job?.status!=='succeeded')return res.sendStatus(404);res.download(job.result.archive);});
45
+ web.get('/api/exports/:id',(req,res)=>{const job=jobs.get(req.params.id);if(!job)return fail(res,404,'导出任务不存在','EXPORT_NOT_FOUND');res.json(job);});
46
+ web.get('/api/exports/:id/download',(req,res)=>{const job=jobs.get(req.params.id);if(job?.status!=='succeeded')return fail(res,404,'导出文件尚未生成','EXPORT_NOT_READY');res.download(job.result.archive);});
40
47
  web.use(express.static(fileURLToPath(new URL('./public/',import.meta.url)),{dotfiles:'deny',index:'index.html'}));
41
- web.use((_req,res)=>res.status(404).json({error:'未找到页面或资源'}));
42
- web.use((error,_req,res,_next)=>res.status(error.status??400).json({error:error.message,details:error.details}));
48
+ web.use((_req,res)=>fail(res,404,'未找到页面或资源','NOT_FOUND'));
49
+ web.use((error,_req,res,_next)=>fail(res,error.status??(['PLUGIN_IN_USE','MAINTENANCE_REQUIRED'].includes(error.code)?409:400),error.message,error.code??'BAD_REQUEST',error.details??null));
43
50
  const server=await new Promise((resolve,reject)=>{const server=web.listen(port,host,()=>resolve(server));server.once('error',reject);});
44
51
  const actualPort=server.address().port,displayHost=['0.0.0.0','::'].includes(host)?'127.0.0.1':host;
45
52
  return {token,port:actualPort,url:`http://${displayHost}:${actualPort}/#token=${token}`,close:()=>new Promise((resolve,reject)=>{server.closeAllConnections();server.close(e=>e?reject(e):resolve());})};
@@ -0,0 +1,84 @@
1
+ export const clone=value=>structuredClone(value);
2
+ export const unverifiedPrivileges=items=>(items??[]).filter(item=>item.requirement?.declared&&item.static?.status==='unknown');
3
+ export const emptyFlow=()=>({schemaVersion:1,id:`flow-${crypto.randomUUID().slice(0,8)}`,title:'未命名工作流',description:'',inputs:{type:'object',properties:{}},steps:[],outputs:{}});
4
+ export function fields(schema,prefix=''){
5
+ return Object.entries(schema?.properties??{}).flatMap(([key,s])=>{const name=prefix?`${prefix}.${key}`:key;return [{path:name,schema:s,title:s.title??key},...fields(s,name)];});
6
+ }
7
+ export function getAt(value,path){return path.split('.').reduce((v,k)=>v?.[k],value);}
8
+ export function setAt(value,path,data){const keys=path.split('.');let target=value;for(const k of keys.slice(0,-1))target=target[k]??={};if(data===undefined)delete target[keys.at(-1)];else target[keys.at(-1)]=data;}
9
+ export function stripRefs(value){if(value?.$ref)return undefined;if(Array.isArray(value))return value.map(stripRefs);if(value&&typeof value==='object')return Object.fromEntries(Object.entries(value).filter(([,v])=>!v?.$ref).map(([k,v])=>[k,stripRefs(v)]));return value;}
10
+ export function literalSchema(schema,bindings){
11
+ if(!schema?.properties)return schema;
12
+ const result=clone(schema);
13
+ result.properties=Object.fromEntries(Object.entries(schema.properties).filter(([key])=>!bindings?.[key]?.$ref).map(([key,value])=>[key,literalSchema(value,bindings?.[key])]));
14
+ if(schema.required)result.required=schema.required.filter(key=>Object.hasOwn(result.properties,key));
15
+ return result;
16
+ }
17
+ export function keepRefs(old,next){if(old?.$ref)return old;if(!old||typeof old!=='object')return next;const result=clone(next??(Array.isArray(old)?[]:{}));for(const [k,v] of Object.entries(old))if(v&&typeof v==='object'){const retained=keepRefs(v,result[k]);if(retained!==undefined)result[k]=retained;}return result;}
18
+ export function stepMeta(step,catalog,workflows){
19
+ if(step?.branch||step?.parallel||step?.stop){
20
+ const kind=step.branch?'条件分支':step.parallel?'并行组':'提前结束';
21
+ const children=childLists(step).flatMap(g=>g.steps);
22
+ return {title:step.title||kind,description:step.branch?'根据条件执行其中一组步骤':step.parallel?'独立步骤并发执行':'正常结束运行并保留已有结果',output:{type:'object',properties:Object.fromEntries(children.map(s=>[s.id,{...(stepMeta(s,catalog,workflows)?.output??{}),title:s.title||stepMeta(s,catalog,workflows)?.title||s.id}]))}};
23
+ }
24
+ if(step?.action)return catalog.find(a=>a.id===step.action);
25
+ const w=workflows.find(w=>`./${w.id}.yaml`===step?.workflow);
26
+ return w?{title:w.title??w.id,description:w.description,input:w.inputs,output:w.outputSchema??{type:'object',properties:Object.fromEntries(Object.keys(w.outputs??{}).map(k=>[k,{title:k}]))}}:undefined;
27
+ }
28
+ export function childLists(step){
29
+ if(step.branch)return [{key:'then',label:'条件满足时',steps:step.branch.then??[]},{key:'else',label:'条件不满足时',steps:step.branch.else??[]}];
30
+ if(step.parallel)return [{key:'steps',label:'并行执行',steps:step.parallel.steps??[]}];
31
+ return [];
32
+ }
33
+ export function findStep(flow,id){
34
+ for(const step of flow.steps??[]){if(step.id===id)return step;for(const group of childLists(step)){const found=findStep(group,id);if(found)return found;}}
35
+ }
36
+ export function updateStep(flow,id,update){
37
+ const next=clone(flow);
38
+ const visit=steps=>{const index=steps.findIndex(s=>s.id===id);if(index>=0){steps[index]=update(steps[index]);return true;}return steps.some(s=>childLists(s).some(g=>visit(g.steps)));};
39
+ visit(next.steps);return next;
40
+ }
41
+ export function visibleSteps(flow,id){
42
+ const visit=(steps,outer=[],parallel=false)=>{
43
+ for(let i=0;i<steps.length;i++){
44
+ const step=steps[i],available=[...outer,...(parallel?[]:steps.slice(0,i))];
45
+ if(step.id===id)return available;
46
+ for(const group of childLists(step)){const found=visit(group.steps,available,!!step.parallel);if(found)return found;}
47
+ }
48
+ };
49
+ return visit(flow.steps)??[];
50
+ }
51
+ export function stepSources(flow,id,catalog,workflows){return sources({steps:visibleSteps(flow,id)},Infinity,catalog,workflows);}
52
+ export function conditionSources(flow,id,catalog,workflows){return [...fields(flow.inputs).map(f=>({ref:`inputs.${f.path}`,label:`运行输入 → ${f.title}`,schema:f.schema})),...stepSources(flow,id,catalog,workflows)];}
53
+ export function conditionPresets(flow,id,catalog,workflows){
54
+ const collect=(step,prefix)=>[
55
+ ...Object.entries(stepMeta(step,catalog,workflows)?.conditions??{}).map(([key,p])=>({id:`${prefix}.${key}`,label:`${step.title||stepMeta(step,catalog,workflows)?.title||step.id} → ${p.title||key}`,condition:{left:{$ref:`${prefix}.${p.path}`},op:p.op,...(p.op==='exists'?{}:{right:p.value})}})),
56
+ ...childLists(step).flatMap(g=>g.steps.flatMap(s=>collect(s,`${prefix}.${s.id}`)))
57
+ ];
58
+ return visibleSteps(flow,id).flatMap(s=>collect(s,`steps.${s.id}.output`));
59
+ }
60
+ export function newStep(kind,item){
61
+ const id=`step-${crypto.randomUUID().slice(0,8)}`;
62
+ if(kind==='branch')return {id,branch:{condition:{left:true,op:'eq',right:true},then:[],else:[]}};
63
+ if(kind==='parallel')return {id,parallel:{steps:[],concurrency:4}};
64
+ if(kind==='stop')return {id,stop:{reason:'流程已按条件结束'}};
65
+ return {id,...(kind==='workflow'?{workflow:`./${item.id}.yaml`}:{action:item.id}),with:{}};
66
+ }
67
+ export function stepObservation(run,callPath){
68
+ const events=[...(run?.events??[]),...(run?.stepSummaries??[])].filter(e=>e.callPath===callPath).sort((a,b)=>(a.seq??0)-(b.seq??0));
69
+ const latest=events.filter(e=>e.type.startsWith('step.')).at(-1);
70
+ return {status:latest?.type.slice(5)??(run?.stepsTruncated&&run.status!=='running'?'unknown':'pending'),input:events.find(e=>e.type==='step.started'&&e.input!==undefined)?.input,output:events.findLast(e=>e.type==='step.succeeded'&&e.output!==undefined)?.output,error:events.findLast(e=>e.type==='step.failed'||e.type==='step.cancelled')?.error,reason:latest?.reason??latest?.details?.reason??latest?.message,logs:events.filter(e=>e.type==='log')};
71
+ }
72
+ export function runSteps(flow,run,catalog=[],workflows=[]){
73
+ const rows=[],known=new Set();
74
+ const visit=(steps,path,depth,seen)=>{for(const step of steps??[]){const callPath=`${path}/${step.id}`;known.add(callPath);rows.push({callPath,step,depth,title:step.title||stepMeta(step,catalog,workflows)?.title||step.action||step.workflow||step.id,...stepObservation(run,callPath)});for(const group of childLists(step))visit(group.steps,callPath,depth+1,seen);if(step.workflow){const child=workflows.find(w=>`./${w.id}.yaml`===step.workflow);if(child&&!seen.includes(child.id))visit(child.steps,callPath,depth+1,[...seen,child.id]);}}};
75
+ visit(flow.steps,run?.workflowId??flow.id,0,[flow.id]);
76
+ for(const event of [...(run?.stepSummaries??[]),...(run?.events??[])]){if(event.type.startsWith('step.')&&event.callPath&&!known.has(event.callPath)){known.add(event.callPath);rows.push({callPath:event.callPath,depth:0,title:event.stepId??event.callPath,...stepObservation(run,event.callPath)});}}
77
+ return rows;
78
+ }
79
+ export function sources(flow,index,catalog,workflows){
80
+ return flow.steps.slice(0,index).flatMap(s=>{
81
+ const meta=stepMeta(s,catalog,workflows);
82
+ return [{ref:`steps.${s.id}.output`,label:`${meta?.title??s.id} → 完整输出`,schema:meta?.output},...fields(meta?.output).map(f=>({ref:`steps.${s.id}.output.${f.path}`,label:`${meta.title} → ${f.title} (${f.path})`,schema:f.schema}))];
83
+ });
84
+ }