@yanglaofish/fishtools 0.4.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 (255) 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/kernel-generation.md +97 -0
  6. package/manual/docs/modules.md +57 -0
  7. package/manual/docs/plugin-development.md +120 -10
  8. package/manual/docs/releases.md +12 -0
  9. package/manual/docs/research/2026-10-11-plugin-kernel-capabilities.md +6 -6
  10. package/manual/docs/roadmap.md +14 -3
  11. package/manual/docs/superpowers/plans/2026-10-09-workflow-mvp.md +5 -5
  12. package/manual/docs/superpowers/plans/2026-10-11-public-plugin-fixes.md +2 -2
  13. package/manual/docs/superpowers/plans/2026-10-12-kernel-generation.md +22 -0
  14. package/manual/docs/superpowers/plans/2026-10-12-modular-kernel.md +40 -0
  15. package/manual/docs/superpowers/reviews/2026-10-12-kernel-generation-review.md +27 -0
  16. package/manual/docs/superpowers/reviews/2026-10-12-modular-kernel-review.md +32 -0
  17. package/manual/docs/superpowers/reviews/2026-10-13-kernel-runtime-release-review.md +19 -0
  18. package/manual/docs/superpowers/specs/2026-10-12-action-network-policy-design.md +52 -0
  19. package/manual/docs/superpowers/specs/2026-10-12-kernel-generation-design.md +38 -0
  20. package/manual/docs/superpowers/specs/2026-10-12-modular-kernel-design.md +70 -0
  21. package/manual/docs/superpowers/specs/2026-10-12-plugin-readiness-design.md +21 -0
  22. package/manual/docs/superpowers/specs/2026-10-12-runtime-maintenance-granularity-design.md +35 -0
  23. package/manual/docs/superpowers/specs/2026-10-12-workflow-variation-design.md +19 -0
  24. package/manual/docs/verification.md +42 -2
  25. package/manual/packages/core/README.md +44 -0
  26. package/manual/packages/core/package.json +38 -0
  27. package/manual/packages/core/src/application.js +242 -85
  28. package/manual/packages/core/src/bundle.js +2 -0
  29. package/manual/packages/core/src/development-watch-worker.js +69 -0
  30. package/manual/packages/core/src/development.js +146 -0
  31. package/manual/packages/core/src/environments.js +2 -0
  32. package/manual/packages/core/src/export-snapshot.js +42 -0
  33. package/manual/packages/core/src/plugin-update.js +108 -0
  34. package/manual/packages/core/src/process.js +2 -0
  35. package/manual/packages/core/src/resources.js +31 -0
  36. package/manual/packages/core/src/version.js +2 -0
  37. package/manual/packages/fst-cli/LICENSE +21 -0
  38. package/manual/packages/fst-cli/README.md +54 -0
  39. package/{node_modules/@mytools/core → manual/packages/fst-cli}/bin/fst.js +55 -23
  40. package/manual/packages/fst-cli/package.json +49 -0
  41. package/manual/packages/fst-cli/src/development.js +53 -0
  42. package/{node_modules/@mytools/core → manual/packages/fst-cli}/src/terminal/model.js +1 -1
  43. package/{node_modules/@mytools/core → manual/packages/fst-cli}/src/terminal/navigation.js +1 -1
  44. package/{node_modules/@mytools/core → manual/packages/fst-cli}/src/terminal/presentation.js +9 -1
  45. package/{node_modules/@mytools/core → manual/packages/fst-cli}/src/terminal/run-feed.js +31 -5
  46. package/{node_modules/@mytools/core → manual/packages/fst-cli}/src/terminal/run-viewer.js +9 -7
  47. package/{node_modules/@mytools/core → manual/packages/fst-cli}/src/terminal/schema.js +1 -1
  48. package/manual/packages/{core → fst-cli}/src/terminal/session.js +137 -9
  49. package/manual/packages/{core → fst-cli}/src/terminal/view.js +20 -3
  50. package/{node_modules/@mytools/core → manual/packages/fst-cli}/src/terminal/workflow-menu.js +17 -2
  51. package/manual/packages/fst-devtools/LICENSE +21 -0
  52. package/manual/packages/fst-devtools/README.md +28 -0
  53. package/manual/packages/fst-devtools/package.json +29 -0
  54. package/{node_modules/@mytools/core → manual/packages/fst-devtools}/src/documentation.js +3 -1
  55. package/manual/packages/fst-devtools/src/plugin-quality.js +136 -0
  56. package/{node_modules/@mytools/core → manual/packages/fst-devtools}/src/plugin-tools.js +24 -5
  57. package/manual/packages/fst-logs/LICENSE +21 -0
  58. package/manual/packages/fst-logs/README.md +56 -0
  59. package/manual/packages/fst-logs/package.json +24 -0
  60. package/manual/packages/fst-logs/src/log-bounds.js +91 -0
  61. package/manual/packages/fst-logs/src/run-logs.js +239 -0
  62. package/manual/packages/fst-platform/LICENSE +21 -0
  63. package/manual/packages/fst-platform/README.md +67 -0
  64. package/manual/packages/fst-platform/package.json +33 -0
  65. package/manual/packages/fst-platform/src/bundle.js +17 -0
  66. package/manual/packages/fst-platform/src/environments.js +67 -0
  67. package/manual/packages/fst-platform/src/file-probe-engine.js +183 -0
  68. package/manual/packages/fst-platform/src/file-probe-worker.js +43 -0
  69. package/manual/packages/fst-platform/src/file-probe.js +6 -0
  70. package/manual/packages/fst-platform/src/json.js +5 -0
  71. package/manual/packages/fst-platform/src/network.js +199 -0
  72. package/manual/packages/fst-platform/src/npm.js +11 -0
  73. package/manual/packages/fst-platform/src/process.js +110 -0
  74. package/manual/packages/fst-plugin/LICENSE +21 -0
  75. package/manual/packages/fst-plugin/README.md +29 -0
  76. package/manual/packages/fst-plugin/package.json +29 -0
  77. package/{node_modules/@mytools/core → manual/packages/fst-plugin}/src/contracts.js +30 -3
  78. package/manual/packages/fst-plugin/src/host.js +232 -0
  79. package/manual/packages/fst-plugin/src/loader.js +21 -0
  80. package/manual/packages/fst-plugin/src/privileges.js +91 -0
  81. package/{node_modules/@mytools/core → manual/packages/fst-plugin}/src/runtime.js +20 -4
  82. package/manual/packages/fst-workflow/LICENSE +21 -0
  83. package/manual/packages/fst-workflow/README.md +28 -0
  84. package/manual/packages/fst-workflow/package.json +24 -0
  85. package/{node_modules/@mytools/core → manual/packages/fst-workflow}/src/control.js +3 -2
  86. package/manual/packages/{core → fst-workflow}/src/engine.js +2 -2
  87. package/manual/packages/fst-workspace/LICENSE +21 -0
  88. package/manual/packages/fst-workspace/README.md +32 -0
  89. package/manual/packages/fst-workspace/package.json +34 -0
  90. package/{node_modules/@mytools/core → manual/packages/fst-workspace}/src/bundle-workspace.js +1 -1
  91. package/manual/packages/fst-workspace/src/index.js +3 -0
  92. package/{node_modules/@mytools/core → manual/packages/fst-workspace}/src/plugins.js +6 -36
  93. package/{node_modules/@mytools/core → manual/packages/fst-workspace}/src/workflows.js +40 -7
  94. package/manual/packages/pack/LICENSE +21 -0
  95. package/manual/packages/pack/README.md +31 -0
  96. package/manual/packages/pack/archive.js +15 -0
  97. package/manual/packages/pack/dependencies.js +105 -0
  98. package/manual/packages/pack/environments.js +50 -0
  99. package/manual/packages/pack/index.js +103 -0
  100. package/manual/packages/pack/package.json +29 -0
  101. package/manual/packages/pack/runtime.js +61 -0
  102. package/manual/packages/sdk/README.md +31 -0
  103. package/manual/packages/sdk/conditions.js +1 -0
  104. package/manual/packages/sdk/package.json +23 -1
  105. package/manual/plugins/web/README.md +29 -0
  106. package/manual/tests/application.test.js +6 -4
  107. package/node_modules/@mytools/core/README.md +44 -0
  108. package/node_modules/@mytools/core/package.json +17 -17
  109. package/node_modules/@mytools/core/src/application.js +242 -85
  110. package/node_modules/@mytools/core/src/bundle.js +2 -17
  111. package/node_modules/@mytools/core/src/development-watch-worker.js +69 -0
  112. package/node_modules/@mytools/core/src/development.js +146 -0
  113. package/node_modules/@mytools/core/src/environments.js +2 -67
  114. package/node_modules/@mytools/core/src/export-snapshot.js +42 -0
  115. package/node_modules/@mytools/core/src/plugin-update.js +108 -0
  116. package/node_modules/@mytools/core/src/process.js +2 -80
  117. package/node_modules/@mytools/core/src/version.js +1 -1
  118. package/node_modules/@mytools/fst-cli/LICENSE +21 -0
  119. package/node_modules/@mytools/fst-cli/README.md +54 -0
  120. package/node_modules/@mytools/fst-cli/bin/fst.js +124 -0
  121. package/node_modules/@mytools/fst-cli/package.json +49 -0
  122. package/node_modules/@mytools/fst-cli/src/cli-run.js +12 -0
  123. package/node_modules/@mytools/fst-cli/src/development.js +53 -0
  124. package/node_modules/@mytools/fst-cli/src/terminal/conditions.js +32 -0
  125. package/node_modules/@mytools/fst-cli/src/terminal/log-viewer.js +45 -0
  126. package/node_modules/@mytools/fst-cli/src/terminal/model.js +104 -0
  127. package/node_modules/@mytools/fst-cli/src/terminal/navigation.js +17 -0
  128. package/node_modules/@mytools/fst-cli/src/terminal/presentation.js +50 -0
  129. package/{manual/packages/core → node_modules/@mytools/fst-cli}/src/terminal/run-feed.js +31 -5
  130. package/node_modules/@mytools/fst-cli/src/terminal/run-presentation.js +60 -0
  131. package/node_modules/@mytools/fst-cli/src/terminal/run-viewer.js +106 -0
  132. package/node_modules/@mytools/fst-cli/src/terminal/schema.js +52 -0
  133. package/node_modules/@mytools/{core → fst-cli}/src/terminal/session.js +137 -9
  134. package/node_modules/@mytools/fst-cli/src/terminal/theme.js +29 -0
  135. package/node_modules/@mytools/{core → fst-cli}/src/terminal/view.js +20 -3
  136. package/node_modules/@mytools/fst-cli/src/terminal/workflow-menu.js +65 -0
  137. package/node_modules/@mytools/fst-devtools/LICENSE +21 -0
  138. package/node_modules/@mytools/fst-devtools/README.md +28 -0
  139. package/node_modules/@mytools/fst-devtools/package.json +29 -0
  140. package/node_modules/@mytools/fst-devtools/src/documentation.js +67 -0
  141. package/node_modules/@mytools/fst-devtools/src/plugin-quality.js +136 -0
  142. package/node_modules/@mytools/fst-devtools/src/plugin-tools.js +152 -0
  143. package/node_modules/@mytools/fst-logs/LICENSE +21 -0
  144. package/node_modules/@mytools/fst-logs/README.md +56 -0
  145. package/node_modules/@mytools/fst-logs/package.json +24 -0
  146. package/node_modules/@mytools/fst-logs/src/log-bounds.js +91 -0
  147. package/node_modules/@mytools/fst-logs/src/markdown-log.js +55 -0
  148. package/node_modules/@mytools/fst-logs/src/run-logs.js +239 -0
  149. package/node_modules/@mytools/fst-platform/LICENSE +21 -0
  150. package/node_modules/@mytools/fst-platform/README.md +67 -0
  151. package/node_modules/@mytools/fst-platform/package.json +33 -0
  152. package/node_modules/@mytools/fst-platform/src/bundle.js +17 -0
  153. package/node_modules/@mytools/fst-platform/src/environments.js +67 -0
  154. package/node_modules/@mytools/fst-platform/src/file-probe-engine.js +183 -0
  155. package/node_modules/@mytools/fst-platform/src/file-probe-worker.js +43 -0
  156. package/node_modules/@mytools/fst-platform/src/file-probe.js +6 -0
  157. package/node_modules/@mytools/fst-platform/src/json.js +5 -0
  158. package/node_modules/@mytools/fst-platform/src/network.js +199 -0
  159. package/node_modules/@mytools/fst-platform/src/npm.js +11 -0
  160. package/node_modules/@mytools/fst-platform/src/process.js +110 -0
  161. package/node_modules/@mytools/fst-plugin/LICENSE +21 -0
  162. package/node_modules/@mytools/fst-plugin/README.md +29 -0
  163. package/node_modules/@mytools/fst-plugin/package.json +29 -0
  164. package/node_modules/@mytools/fst-plugin/src/contracts.js +86 -0
  165. package/node_modules/@mytools/fst-plugin/src/host.js +232 -0
  166. package/node_modules/@mytools/fst-plugin/src/loader.js +21 -0
  167. package/node_modules/@mytools/fst-plugin/src/privileges.js +91 -0
  168. package/{manual/packages/core → node_modules/@mytools/fst-plugin}/src/runtime.js +20 -4
  169. package/node_modules/@mytools/fst-workflow/LICENSE +21 -0
  170. package/node_modules/@mytools/fst-workflow/README.md +28 -0
  171. package/node_modules/@mytools/fst-workflow/package.json +24 -0
  172. package/node_modules/@mytools/fst-workflow/src/control.js +64 -0
  173. package/node_modules/@mytools/{core → fst-workflow}/src/engine.js +2 -2
  174. package/node_modules/@mytools/fst-workspace/LICENSE +21 -0
  175. package/node_modules/@mytools/fst-workspace/README.md +32 -0
  176. package/node_modules/@mytools/fst-workspace/package.json +34 -0
  177. package/node_modules/@mytools/fst-workspace/src/bundle-workspace.js +39 -0
  178. package/node_modules/@mytools/fst-workspace/src/index.js +3 -0
  179. package/node_modules/@mytools/fst-workspace/src/plugin-installations.js +84 -0
  180. package/node_modules/@mytools/fst-workspace/src/plugins.js +35 -0
  181. package/node_modules/@mytools/fst-workspace/src/project.js +29 -0
  182. package/node_modules/@mytools/fst-workspace/src/workflows.js +150 -0
  183. package/node_modules/@mytools/fst-workspace/templates/control-demo.yaml +47 -0
  184. package/node_modules/@mytools/fst-workspace/templates/hello.yaml +35 -0
  185. package/node_modules/@mytools/fst-workspace/templates/python-check.yaml +26 -0
  186. package/node_modules/@mytools/fst-workspace/templates/system-check.yaml +11 -0
  187. package/node_modules/@mytools/pack/README.md +31 -0
  188. package/node_modules/@mytools/pack/dependencies.js +7 -2
  189. package/node_modules/@mytools/pack/environments.js +1 -1
  190. package/node_modules/@mytools/pack/index.js +33 -25
  191. package/node_modules/@mytools/pack/package.json +29 -1
  192. package/node_modules/@mytools/sdk/README.md +31 -0
  193. package/node_modules/@mytools/sdk/conditions.js +1 -0
  194. package/node_modules/@mytools/sdk/diagnostics.js +27 -0
  195. package/node_modules/@mytools/sdk/package.json +23 -1
  196. package/node_modules/@mytools/sdk/schema-path.js +31 -0
  197. package/node_modules/@mytools/sdk/validation.js +78 -0
  198. package/node_modules/@mytools/web/README.md +29 -0
  199. package/node_modules/@mytools/web/package.json +7 -0
  200. package/node_modules/@mytools/web/public/THIRD_PARTY_NOTICES.txt +1398 -1199
  201. package/node_modules/@mytools/web/public/assets/index-BaVzHycP.js +70 -0
  202. package/node_modules/@mytools/web/public/assets/index-DA8Ae92t.css +1 -0
  203. package/node_modules/@mytools/web/public/index.html +2 -2
  204. package/node_modules/@mytools/web/server.js +24 -17
  205. package/node_modules/@mytools/web/src/model.js +84 -0
  206. package/node_modules/acorn/CHANGELOG.md +1012 -0
  207. package/node_modules/acorn/LICENSE +21 -0
  208. package/node_modules/acorn/README.md +309 -0
  209. package/node_modules/acorn/bin/acorn +4 -0
  210. package/node_modules/acorn/dist/acorn.d.mts +897 -0
  211. package/node_modules/acorn/dist/acorn.d.ts +897 -0
  212. package/node_modules/acorn/dist/acorn.js +6370 -0
  213. package/node_modules/acorn/dist/acorn.mjs +6341 -0
  214. package/node_modules/acorn/dist/bin.js +90 -0
  215. package/node_modules/acorn/package.json +50 -0
  216. package/node_modules/{minipass-flush/node_modules/minipass → tar}/node_modules/yallist/README.md +8 -7
  217. package/node_modules/tar/node_modules/yallist/package.json +68 -0
  218. package/node_modules/yallist/README.md +7 -8
  219. package/node_modules/yallist/package.json +12 -51
  220. package/package.json +131 -115
  221. package/manual/packages/core/src/host.js +0 -139
  222. package/node_modules/@mytools/core/src/host.js +0 -139
  223. package/node_modules/@mytools/core/src/run-logs.js +0 -63
  224. package/node_modules/@mytools/web/public/assets/index-DT2B4tMV.js +0 -70
  225. package/node_modules/@mytools/web/public/assets/index-vuZvrUTT.css +0 -1
  226. package/node_modules/minipass-flush/node_modules/minipass/node_modules/yallist/package.json +0 -29
  227. /package/{node_modules/@mytools/core → manual/packages/fst-cli}/src/cli-run.js +0 -0
  228. /package/{node_modules/@mytools/core → manual/packages/fst-cli}/src/terminal/conditions.js +0 -0
  229. /package/{node_modules/@mytools/core → manual/packages/fst-cli}/src/terminal/log-viewer.js +0 -0
  230. /package/{node_modules/@mytools/core → manual/packages/fst-cli}/src/terminal/run-presentation.js +0 -0
  231. /package/{node_modules/@mytools/core → manual/packages/fst-cli}/src/terminal/theme.js +0 -0
  232. /package/{node_modules/@mytools/core → manual/packages/fst-logs}/src/markdown-log.js +0 -0
  233. /package/{node_modules/@mytools/core → manual/packages/fst-workspace}/src/plugin-installations.js +0 -0
  234. /package/{node_modules/@mytools/core → manual/packages/fst-workspace}/src/project.js +0 -0
  235. /package/{node_modules/@mytools/core → manual/packages/fst-workspace}/templates/control-demo.yaml +0 -0
  236. /package/{node_modules/@mytools/core → manual/packages/fst-workspace}/templates/hello.yaml +0 -0
  237. /package/{node_modules/@mytools/core → manual/packages/fst-workspace}/templates/python-check.yaml +0 -0
  238. /package/{node_modules/@mytools/core → manual/packages/fst-workspace}/templates/system-check.yaml +0 -0
  239. /package/{node_modules/@mytools/core/src → manual/packages/sdk}/diagnostics.js +0 -0
  240. /package/{node_modules/@mytools/core/src → manual/packages/sdk}/schema-path.js +0 -0
  241. /package/{node_modules/@mytools/core/src → manual/packages/sdk}/validation.js +0 -0
  242. /package/node_modules/{yallist → tar/node_modules/yallist}/LICENSE.md +0 -0
  243. /package/node_modules/{yallist → tar/node_modules/yallist}/dist/commonjs/index.d.ts +0 -0
  244. /package/node_modules/{yallist → tar/node_modules/yallist}/dist/commonjs/index.d.ts.map +0 -0
  245. /package/node_modules/{yallist → tar/node_modules/yallist}/dist/commonjs/index.js +0 -0
  246. /package/node_modules/{yallist → tar/node_modules/yallist}/dist/commonjs/index.js.map +0 -0
  247. /package/node_modules/{yallist → tar/node_modules/yallist}/dist/commonjs/package.json +0 -0
  248. /package/node_modules/{yallist → tar/node_modules/yallist}/dist/esm/index.d.ts +0 -0
  249. /package/node_modules/{yallist → tar/node_modules/yallist}/dist/esm/index.d.ts.map +0 -0
  250. /package/node_modules/{yallist → tar/node_modules/yallist}/dist/esm/index.js +0 -0
  251. /package/node_modules/{yallist → tar/node_modules/yallist}/dist/esm/index.js.map +0 -0
  252. /package/node_modules/{yallist → tar/node_modules/yallist}/dist/esm/package.json +0 -0
  253. /package/node_modules/{minipass-flush/node_modules/minipass/node_modules/yallist → yallist}/LICENSE +0 -0
  254. /package/node_modules/{minipass-flush/node_modules/minipass/node_modules/yallist → yallist}/iterator.js +0 -0
  255. /package/node_modules/{minipass-flush/node_modules/minipass/node_modules/yallist → yallist}/yallist.js +0 -0
@@ -0,0 +1,232 @@
1
+ import { Context } from 'cordis';
2
+ import semver from 'semver';
3
+ import { SDK_VERSION, FlowError } from '@mytools/sdk';
4
+ import { validate, emptyObject } from '@mytools/sdk/schema';
5
+ import {validateDeclarations,validateCommands} from './contracts.js';
6
+ import {prepareEnvironments,platformWarnings} from '@mytools/fst-platform/environments';
7
+ import path from 'node:path';
8
+ import {mkdir} from 'node:fs/promises';
9
+ import {createHash} from 'node:crypto';
10
+ import {createRuntime} from './runtime.js';
11
+ import {pluginError} from '@mytools/sdk/diagnostics';
12
+ import {validateNetworkPolicy,resolveNetworkPolicy,createHttpClient,buildProcessEnv} from '@mytools/fst-platform/network';
13
+ import {resolvePrivilegeRequirement,resolvePrivilegePreflight,checkStaticPrivilege,runPrivilegePreflight} from './privileges.js';
14
+
15
+ export async function createHost({ plugins = [], config = {}, services = {}, project,network,environmentOverrides={},runtimeEnabled=true,allowUnknownPrivileges=false } = {}) {
16
+ const currentNetwork=()=>typeof network==='function'?network():network;
17
+ validateNetworkPolicy(currentNetwork());
18
+ const ctx = new Context();
19
+ const actions = new Map();
20
+ const definitions = new Map();
21
+ const extensions = new Map();
22
+ const serviceNames = new Set();
23
+ const commandNames=new Set(),commands=new Map();
24
+ function bindContext(context){
25
+ const {application:ignored,...capabilities}=services;
26
+ const bound={...capabilities,...context},execute=bound.runProcess;
27
+ // 面向用户的输出与诊断日志共用持久化通道,只区分默认可见性。
28
+ bound.print=(message,data)=>Promise.resolve(bound.log?.(message,data,{audience:'user'}));
29
+ if(typeof execute==='function')bound.runProcess=(file,args,options={})=>{
30
+ const signal=context.signal&&options.signal?AbortSignal.any([context.signal,options.signal]):context.signal??options.signal;
31
+ return execute(file,args,{...options,signal,onOutput:async({stream,message})=>{
32
+ await bound.log?.(message,undefined,{stream});
33
+ await options.onOutput?.({stream,message});
34
+ }});
35
+ };
36
+ return bound;
37
+ }
38
+ async function pluginContext(plugin,action,context){
39
+ const bound=bindContext(context);
40
+ const projectDir=project?.directory??services.application?.projectDir;
41
+ if(bound.fs){
42
+ const probe=bound.fs,resolve=file=>typeof file==='string'&&!/^[/\\]{2}/.test(file)?path.resolve(projectDir??process.cwd(),file):file;
43
+ const options=value=>({...value,signal:context.signal&&value?.signal?AbortSignal.any([context.signal,value.signal]):context.signal??value?.signal});
44
+ bound.fs={stat:(file,value)=>probe.stat(resolve(file),options(value)),readDirectory:(file,value)=>probe.readDirectory(resolve(file),options(value))};
45
+ }
46
+ // 固定长度摘要避免 scoped 包名分隔符、大小写碰撞及合法长包名超出文件名上限。
47
+ const readOnly=project?.readOnly??services.application?.project?.readOnly;
48
+ const stateDir=projectDir&&!readOnly?path.join(projectDir,'.flow','plugin-state',createHash('sha256').update(plugin.id).digest('hex')):undefined;
49
+ if(stateDir)await mkdir(stateDir,{recursive:true});
50
+ for(const message of platformWarnings(plugin))await bound.log?.(message,{code:'PLATFORM_MISMATCH'},{audience:'user'});
51
+ const environments=await prepareEnvironments(plugin,action,environmentOverrides[plugin.id],{cwd:projectDir??process.cwd()});
52
+ context.signal?.throwIfAborted();
53
+ const policy=context.networkPolicy??currentNetwork();
54
+ const actionName=action?.id?.slice(plugin.id.length+1);
55
+ const resolved=resolveNetworkPolicy(policy,{pluginId:plugin.id,actionId:actionName});
56
+ if(policy?.default||policy?.plugins?.[plugin.id])await bound.log?.('网络代理规则',{mode:resolved.mode,...(resolved.profile?{profile:resolved.profile}:{})});
57
+ const http=createHttpClient(resolved,{signal:context.signal});
58
+ if(typeof bound.runProcess==='function'){
59
+ const execute=bound.runProcess;
60
+ bound.runProcess=(file,args,options={})=>execute(file,args,{...options,env:buildProcessEnv(resolved,{overrides:options.env})});
61
+ }
62
+ bound.http={fetch:(input,options)=>http.fetch(input,options)};
63
+ return {capabilities:{...bound,projectDir,stateDir,environments},close:()=>http.close()};
64
+ }
65
+ async function checkPrivilege(plugin,actionName,input,context,fs){
66
+ const requirement=resolvePrivilegeRequirement(plugin,actionName);
67
+ if(!requirement.declared)return requirement;
68
+ const staticResult=checkStaticPrivilege(requirement);
69
+ let result=staticResult;
70
+ if(staticResult.status==='unknown')result=await runPrivilegePreflight(resolvePrivilegePreflight(plugin,actionName),input,{signal:context.signal,fs});
71
+ context.signal?.throwIfAborted();
72
+ if(result.status==='blocked')throw new FlowError(`权限不足:${result.reason??requirement.reason??plugin.id}`,{code:'INSUFFICIENT_PRIVILEGE',details:{plugin:plugin.id,action:actionName,requirement,result}});
73
+ if(result.status==='unknown'&&!context.allowUnknownPrivileges)throw new FlowError(`权限尚未确认:${result.reason??requirement.reason??plugin.id}`,{code:'PRIVILEGE_UNVERIFIED',details:{plugin:plugin.id,action:actionName,requirement,result}});
74
+ if(result.status==='unknown')await context.log?.('权限状态未确认,按本次选择继续运行',{plugin:plugin.id,action:actionName,reason:result.reason},{audience:'user'});
75
+ return requirement;
76
+ }
77
+ for (const entry of plugins) {
78
+ const plugin = entry.plugin ?? entry;
79
+ validateDeclarations(plugin);validateCommands(plugin,commandNames);
80
+ if (!plugin.id || definitions.has(plugin.id)) throw new FlowError(`插件标识缺失或重复:${plugin.id}`, {code:'VALIDATION'});
81
+ if (!semver.satisfies(SDK_VERSION, plugin.sdk ?? '^0.1.0')) throw new FlowError(`插件 ${plugin.id} 与 SDK ${SDK_VERSION} 不兼容`, {code:'VALIDATION'});
82
+ for(const [name,action] of Object.entries(plugin.actions??{})){
83
+ if(typeof action?.run!=='function')throw new FlowError(`动作 ${plugin.id}:${name} 缺少执行函数`);
84
+ }
85
+ for(const [name,extension] of Object.entries(plugin.services??{})){
86
+ if(serviceNames.has(name))throw new FlowError(`服务重复:${name}`);
87
+ if(typeof extension!=='function')throw new FlowError(`服务 ${name} 缺少执行函数`);
88
+ serviceNames.add(name);
89
+ }
90
+ definitions.set(plugin.id, {...plugin, root:entry.root});
91
+ }
92
+ const settingsById=new Map();
93
+ for (const plugin of definitions.values()) {
94
+ for (const [id, range] of Object.entries(plugin.dependencies ?? {})) {
95
+ if (!definitions.has(id) || !semver.satisfies(definitions.get(id).version,range)) throw new FlowError(`插件 ${plugin.id} 缺少兼容依赖 ${id}@${range}`,{code:'VALIDATION'});
96
+ }
97
+ const settings = validate(plugin.config, config[plugin.id] ?? {}, `${plugin.id} 配置`);
98
+ settingsById.set(plugin.id,settings);
99
+ }
100
+ const runtime=createRuntime(ctx,definitions,{enabled:runtimeEnabled});
101
+ let unusable=false;
102
+ const assertReadable=()=>{
103
+ if(unusable)throw new FlowError('插件更新和恢复均失败,请重启应用',{code:'PLUGIN_RUNTIME_UNUSABLE'});
104
+ };
105
+ const assertHealthy=()=>{runtime.assertOpen();assertReadable();};
106
+ function registerPlugin(plugin,settings){
107
+ const registered=plugin.runtime?{...plugin,runtime:{...plugin.runtime,apply:async(scope,value)=>{
108
+ await checkPrivilege(plugin,undefined,{}, {allowUnknownPrivileges},services.fs);
109
+ return plugin.runtime.apply(scope,value);
110
+ }}}:plugin;
111
+ runtime.register(registered,settings,scope=>{
112
+ for(const [name,command] of Object.entries(plugin.commands??{})){
113
+ const action=command.action?plugin.actions[command.action]:command;
114
+ commands.set(name,{...command,name,plugin:plugin.id,input:action.input,output:action.output,description:command.description??action.description??action.title??name,config:settings});
115
+ scope.on('dispose',()=>commands.delete(name));
116
+ }
117
+ for (const [name, action] of Object.entries(plugin.actions ?? {})) {
118
+ const id = `${plugin.id}:${name}`;
119
+ if (typeof action.run !== 'function') throw new FlowError(`动作 ${id} 缺少执行函数`);
120
+ actions.set(id, {...action, id, plugin:plugin.id, config:settings, root:plugin.root});
121
+ scope.on('dispose', () => actions.delete(id));
122
+ }
123
+ for (const [name, extension] of Object.entries(plugin.services ?? {})) {
124
+ if (extensions.has(name)) throw new FlowError(`服务重复:${name}`);
125
+ extensions.set(name, {run:extension,plugin:plugin.id});
126
+ scope.on('dispose',()=>extensions.delete(name));
127
+ }
128
+ });
129
+ }
130
+ try{
131
+ for (const plugin of definitions.values())registerPlugin(plugin,settingsById.get(plugin.id));
132
+ await runtime.start();
133
+ }catch(error){
134
+ try{await runtime.close();}catch(cleanup){if(cleanup.message!==error.message)throw new AggregateError([error,cleanup],`${error.message};${cleanup.message}`);}
135
+ throw error;
136
+ }
137
+ return {
138
+ definitions, actions,
139
+ assertHealthy,
140
+ invalidate(){unusable=true;},
141
+ get warnings(){return [...definitions.values()].flatMap(plugin=>platformWarnings(plugin).map(message=>({plugin:plugin.id,message})));},
142
+ get hasRuntime(){return runtime.hasRuntime;},
143
+ runtimeStatus:()=>runtime.status(),
144
+ async replace({plugins:nextEntries,config:nextConfig={},affected=[]}){
145
+ assertHealthy();
146
+ const selected=new Set(affected);
147
+ // 候选宿主只做声明、依赖、配置和服务图校验,不激活候选 runtime。
148
+ const candidate=await createHost({plugins:nextEntries,config:nextConfig,services,project,network,environmentOverrides,runtimeEnabled:false,allowUnknownPrivileges});
149
+ const next=new Map(candidate.definitions);
150
+ await candidate.close();
151
+ const oldDefinitions=new Map([...definitions].filter(([id])=>selected.has(id)));
152
+ const oldSettings=new Map([...settingsById].filter(([id])=>selected.has(id)));
153
+ const switchDefinitions=async(targets,settings)=>{
154
+ await runtime.remove(selected);
155
+ for(const id of selected){definitions.delete(id);settingsById.delete(id);}
156
+ for(const [id,plugin] of targets)if(selected.has(id)){
157
+ definitions.set(id,plugin);
158
+ settingsById.set(id,settings.get(id));
159
+ }
160
+ runtime.reindex();
161
+ for(const [id,plugin] of definitions)if(selected.has(id))registerPlugin(plugin,settingsById.get(id));
162
+ await runtime.ready();
163
+ };
164
+ const nextSettings=new Map();
165
+ for(const [id,plugin] of next)if(selected.has(id))nextSettings.set(id,validate(plugin.config,nextConfig[id]??{},`${id} 配置`));
166
+ try{await switchDefinitions(next,nextSettings);}
167
+ catch(error){
168
+ try{
169
+ runtime.clearErrors();
170
+ await switchDefinitions(oldDefinitions,oldSettings);
171
+ }catch(recovery){unusable=true;throw new AggregateError([error,recovery],`插件更新失败且恢复失败:${error.message};${recovery.message}`);}
172
+ throw error;
173
+ }
174
+ },
175
+ commands(){assertReadable();return [...commands.values()].map(({run,preflight,config,...metadata})=>structuredClone(metadata));},
176
+ async invokeCommand(name,input,context={}){
177
+ assertHealthy();
178
+ const command=commands.get(name);if(!command)throw new FlowError(`命令不存在:/${name}`);
179
+ if(command.action)return this.invoke(`${command.plugin}:${command.action}`,input,context);
180
+ if(!context.ui)throw new FlowError(`交互命令 /${name} 需要在 TUI 中调用`);
181
+ const plugin=definitions.get(command.plugin);
182
+ try{
183
+ const getService=runtime.bindServices(plugin.id);
184
+ context.signal?.throwIfAborted();
185
+ const checked=validate(command.input??emptyObject,input,`/${name} 输入`);
186
+ const bound=await pluginContext(plugin,undefined,context);
187
+ let result;
188
+ try{
189
+ await checkPrivilege(plugin,undefined,checked,context,bound.capabilities.fs);
190
+ result=await command.run(checked,{...bound.capabilities,getService,config:command.config,resourceDir:plugin.root});
191
+ }finally{await bound.close();}
192
+ context.signal?.throwIfAborted();
193
+ return validate(command.output,JSON.parse(JSON.stringify(result??{})),`/${name} 输出`,{useDefaults:false});
194
+ }catch(cause){throw pluginError(cause,{plugin:plugin.id,command:`/${name}`});}
195
+ },
196
+ catalog() { assertReadable();return [...actions.values()].map(({run,preflight,config,root,...metadata})=>structuredClone(metadata)); },
197
+ async invoke(id, input, context = {}) {
198
+ assertHealthy();
199
+ const action = actions.get(id);
200
+ if (!action) throw new FlowError(`动作不存在:${id}`, {code:'VALIDATION'});
201
+ try{
202
+ const getService=runtime.bindServices(action.plugin);
203
+ context.signal?.throwIfAborted();
204
+ const checked=validate(action.input ?? emptyObject,input,`${id} 输入`);
205
+ const plugin=definitions.get(action.plugin),bound=await pluginContext(plugin,action,context);
206
+ let result;
207
+ try{
208
+ await checkPrivilege(plugin,id.slice(plugin.id.length+1),checked,context,bound.capabilities.fs);
209
+ result=await action.run(checked,{...bound.capabilities,getService, config:action.config, resourceDir:action.root});
210
+ }finally{await bound.close();}
211
+ context.signal?.throwIfAborted();
212
+ // 通过 JSON 边界验证返回值,避免函数、循环引用等进入工作流状态。
213
+ const encoded = JSON.stringify(result);
214
+ if (encoded === undefined) throw new FlowError(`${id} 输出必须可 JSON 序列化`);
215
+ return validate(action.output, JSON.parse(encoded),`${id} 输出`,{useDefaults:false});
216
+ }catch(cause){throw pluginError(cause,{plugin:action.plugin,action:id});}
217
+ },
218
+ async service(name,options) {
219
+ assertHealthy();
220
+ if (!extensions.has(name)) throw new FlowError(`未安装提供 ${name} 的插件`);
221
+ const extension=extensions.get(name);runtime.assertActive(extension.plugin);
222
+ const plugin=definitions.get(extension.plugin),bound=await pluginContext(plugin,undefined,{});
223
+ try{
224
+ await checkPrivilege(plugin,undefined,{}, {allowUnknownPrivileges},bound.capabilities.fs);
225
+ const result=await extension.run({...services,...bound.capabilities,application:services.application},options);
226
+ if(typeof result?.close!=='function'){await bound.close();return result;}
227
+ return {...result,close:async()=>{try{await result.close();}finally{await bound.close();}}};
228
+ }catch(error){await bound.close();throw error;}
229
+ },
230
+ close:()=>runtime.close(),
231
+ };
232
+ }
@@ -0,0 +1,21 @@
1
+ import {createRequire} from 'node:module';
2
+ import {pathToFileURL} from 'node:url';
3
+ import {access} from 'node:fs/promises';
4
+ import path from 'node:path';
5
+ import {readJson} from '@mytools/fst-platform/json';
6
+ export async function loadPlugin(spec,base) {
7
+ const req=createRequire(path.join(base,'package.json'));
8
+ let entry;
9
+ if(path.isAbsolute(spec) || spec.startsWith('.')) {
10
+ const root=path.resolve(base,spec), pkg=await readJson(path.join(root,'package.json'));
11
+ entry=path.resolve(root,pkg.main??'index.js');
12
+ } else {
13
+ try {entry=req.resolve(spec);} catch {entry=createRequire(import.meta.url).resolve(spec);}
14
+ }
15
+ let root=path.dirname(entry);
16
+ while(root!==path.dirname(root)) {
17
+ try {await access(path.join(root,'package.json'));break;} catch {root=path.dirname(root);}
18
+ }
19
+ const module=await import(pathToFileURL(entry).href);
20
+ return {plugin:module.default,root};
21
+ }
@@ -0,0 +1,91 @@
1
+ // 权限声明按平台逐项覆盖;返回纯数据,避免把插件函数带入元数据。
2
+ export function resolvePrivilegeRequirement(plugin,actionName,platform=process.platform){
3
+ const action=actionName===undefined?undefined:plugin.actions?.[actionName];
4
+ const actionRequirement=action?.requirements?.privilege?.[platform];
5
+ const pluginRequirement=plugin.requirements?.privilege?.[platform];
6
+ const requirement=actionRequirement??pluginRequirement;
7
+ if(!requirement)return {declared:false,platform,source:null,level:null};
8
+ const result={declared:true,platform,source:actionRequirement?'action':'plugin',level:requirement.level};
9
+ if(requirement.reason!==undefined)result.reason=requirement.reason;
10
+ if(requirement.remediation!==undefined)result.remediation=requirement.remediation;
11
+ return result;
12
+ }
13
+
14
+ // 动作探针优先,插件探针可为动作提供默认实现。
15
+ export function resolvePrivilegePreflight(plugin,actionName){
16
+ return (actionName===undefined?undefined:plugin.actions?.[actionName]?.preflight)??plugin.preflight;
17
+ }
18
+
19
+ export function checkStaticPrivilege(requirement,options={}){
20
+ if(!requirement?.declared)return {status:'unknown',reason:'未声明权限要求'};
21
+ if(requirement.level==='user')return {status:'ok'};
22
+ if(requirement.level==='root'&&requirement.platform==='linux'){
23
+ const uid=Object.hasOwn(options,'uid')?options.uid:process.getuid?.();
24
+ if(uid===0)return {status:'ok'};
25
+ if(Number.isInteger(uid)&&uid>0)return {status:'blocked',reason:requirement.reason,remediation:requirement.remediation};
26
+ }
27
+ if(requirement.level==='administrator'&&requirement.platform==='win32'){
28
+ if(options.isAdministrator===true)return {status:'ok'};
29
+ if(options.isAdministrator===false)return {status:'blocked',reason:requirement.reason,remediation:requirement.remediation};
30
+ }
31
+ return {status:'unknown',reason:requirement.reason,remediation:requirement.remediation};
32
+ }
33
+
34
+ function normalizeResult(result){
35
+ if(!result||typeof result!=='object'||Array.isArray(result)||!['ok','blocked','unknown'].includes(result.status))return {status:'unknown',reason:'权限预检返回无效结果'};
36
+ const normalized={status:result.status};
37
+ for(const field of ['reason','remediation']){
38
+ if(result[field]===undefined)continue;
39
+ if(typeof result[field]!=='string'||!result[field].trim())return {status:'unknown',reason:'权限预检返回无效结果'};
40
+ normalized[field]=result[field];
41
+ }
42
+ return normalized;
43
+ }
44
+
45
+ function readOnlyProbe(fs,signal){
46
+ if(!fs)return undefined;
47
+ const result={};
48
+ for(const name of ['stat','readDirectory']){
49
+ if(typeof fs[name]!=='function')continue;
50
+ result[name]=(file,options={})=>{
51
+ const combined=options?.signal?AbortSignal.any([signal,options.signal]):signal;
52
+ return fs[name](file,{...options,signal:combined});
53
+ };
54
+ }
55
+ return Object.freeze(result);
56
+ }
57
+
58
+ function preflightFailure(error){
59
+ let detail;
60
+ try{detail=String(error?.message??error);}catch{detail='未知错误';}
61
+ return {status:'unknown',reason:`权限预检失败:${detail}`};
62
+ }
63
+
64
+ // 探针错误与超时都收敛为待确认;同步死循环无法由计时器强制中断。
65
+ export async function runPrivilegePreflight(preflight,input,{platform=process.platform,signal,fs,timeoutMs=5000}={}){
66
+ if(typeof preflight!=='function')return {status:'unknown',reason:'未提供权限预检'};
67
+ if(signal?.aborted)return {status:'unknown',reason:'权限预检已取消'};
68
+ const controller=new AbortController();
69
+ const duration=Number.isFinite(timeoutMs)&&timeoutMs>0?timeoutMs:5000;
70
+ let timer,abortHandler;
71
+ const interrupted=new Promise(resolve=>{
72
+ timer=setTimeout(()=>{
73
+ controller.abort(new Error('权限预检超时'));
74
+ resolve({status:'unknown',reason:'权限预检超时'});
75
+ },duration);
76
+ if(signal){
77
+ abortHandler=()=>{
78
+ controller.abort(signal.reason);
79
+ resolve({status:'unknown',reason:'权限预检已取消'});
80
+ };
81
+ signal.addEventListener('abort',abortHandler,{once:true});
82
+ if(signal.aborted)abortHandler();
83
+ }
84
+ });
85
+ const work=Promise.resolve().then(()=>preflight(input,{platform,signal:controller.signal,fs:readOnlyProbe(fs,controller.signal)})).then(normalizeResult).catch(preflightFailure);
86
+ try{return await Promise.race([work,interrupted]);}
87
+ finally{
88
+ clearTimeout(timer);
89
+ if(signal&&abortHandler)signal.removeEventListener('abort',abortHandler);
90
+ }
91
+ }
@@ -32,7 +32,9 @@ export function validateRuntimes(definitions,ctx){
32
32
  }
33
33
 
34
34
  export function createRuntime(ctx,definitions,{enabled=true}={}){
35
- const {providers,ordered}=validateRuntimes(definitions,ctx);
35
+ // 动态更新时 ctx 已含运行服务;声明检查始终以 Cordis 原生名称为准。
36
+ const reservedContext=new ctx.constructor();
37
+ let graph=validateRuntimes(definitions,reservedContext);
36
38
  const forks=new Map(),scopes=new Map(),errors=[],pending=new Set();let closing;
37
39
  const record=(id,error)=>errors.push({plugin:id,message:error?.message??String(error)});
38
40
  const track=(id,callback)=>{
@@ -52,7 +54,7 @@ export function createRuntime(ctx,definitions,{enabled=true}={}){
52
54
  const drain=async()=>{while(pending.size)await Promise.all([...pending]);};
53
55
  const failure=()=>new FlowError(`插件生命周期失败:${errors.map(e=>`${e.plugin}: ${e.message}`).join(';')}`,{code:'PLUGIN_RUNTIME',details:{errors:structuredClone(errors)}});
54
56
  const runtime={
55
- hasRuntime:[...definitions.values()].some(p=>p.runtime),
57
+ get hasRuntime(){return [...definitions.values()].some(p=>p.runtime);},
56
58
  register(plugin,settings,register){
57
59
  if(!enabled&&plugin.runtime)return;
58
60
  const fork=ctx.plugin({name:plugin.id,inject:plugin.runtime?.inject??[],apply(scope){
@@ -95,10 +97,24 @@ export function createRuntime(ctx,definitions,{enabled=true}={}){
95
97
  forks.set(plugin.id,fork);
96
98
  },
97
99
  async start(){
100
+ await ctx.start();await runtime.ready();
101
+ },
102
+ reindex(){graph=validateRuntimes(definitions,reservedContext);},
103
+ clearErrors(){errors.length=0;},
104
+ async remove(ids){
105
+ // 先撤销消费者再撤销提供者;旧作用域清理结束后才能注册新作用域。
106
+ const selected=new Set(ids);
107
+ for(const id of [...graph.ordered].reverse())if(selected.has(id)){
108
+ forks.get(id)?.dispose();forks.delete(id);await drain();
109
+ }
110
+ if(errors.length)throw failure();
111
+ },
112
+ async ready(){
113
+ // 动态注册后的 ready 监听尚未消费;Cordis start 会取出新监听并等待任务。
98
114
  await ctx.start();await drain();
99
115
  if(errors.length)throw failure();
100
116
  if(!enabled)return;
101
- for(const [name,id] of providers)if(ctx.get(name)==null)throw new FlowError(`${id} 未提供声明的服务:${name}`,{code:'PLUGIN_RUNTIME'});
117
+ for(const [name,id] of graph.providers)if(ctx.get(name)==null)throw new FlowError(`${id} 未提供声明的服务:${name}`,{code:'PLUGIN_RUNTIME'});
102
118
  for(const p of runtime.status())if(p.status!=='ready')throw new FlowError(`${p.id} 服务尚未就绪:${p.missing.join(', ')}`,{code:'PLUGIN_RUNTIME'});
103
119
  },
104
120
  status(){return [...definitions.values()].map(p=>{
@@ -127,7 +143,7 @@ export function createRuntime(ctx,definitions,{enabled=true}={}){
127
143
  close(){
128
144
  closing??=Promise.resolve().then(async()=>{
129
145
  // 消费者先关闭,等待其使用完服务;注册撤销和依赖停用由 Cordis 执行。
130
- for(const id of [...ordered].reverse()){forks.get(id)?.dispose();await drain();}
146
+ for(const id of [...graph.ordered].reverse()){forks.get(id)?.dispose();await drain();}
131
147
  await ctx.stop();await drain();if(errors.length)throw failure();
132
148
  });return closing;
133
149
  },
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 mytools contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,28 @@
1
+ # 工作流模块
2
+
3
+ 包名:`@mytools/fst-workflow`。内部 workspace 包,随 `@yanglaofish/fishtools` 统一交付。
4
+
5
+ ## 职责与边界
6
+
7
+ 步骤执行、控制流和条件语义。工作流文件和闭包解析由 workspace 负责,运行管理由 core 负责。
8
+
9
+ 内部依赖:`@mytools/sdk`。
10
+ ## 公开入口
11
+
12
+ | 导入路径 | 实现位置 |
13
+ | --- | --- |
14
+ | `@mytools/fst-workflow` | `./src/engine.js` |
15
+ | `@mytools/fst-workflow/control` | `./src/control.js` |
16
+ | `@mytools/fst-workflow/package.json` | `./package.json` |
17
+
18
+ 仅依赖 exports 声明的入口,禁止消费者跨包读取 src/bin 私有文件。入口路径由本模块维护,修改协议时同步消费方回归。
19
+
20
+ ## 维护与验证
21
+
22
+ 本模块负责上述职责的实现、文档与回归;跨模块接口调整需要同时检查生产者与消费者。具体维护人员尚未指定。
23
+
24
+ 在仓库根目录执行:
25
+
26
+ ```sh
27
+ node --test tests/modules.test.js tests/workflows.test.js tests/control-flow.test.js
28
+ ```
@@ -0,0 +1,24 @@
1
+ {
2
+ "name": "@mytools/fst-workflow",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "main": "src/engine.js",
7
+ "exports": {
8
+ ".": "./src/engine.js",
9
+ "./control": "./src/control.js",
10
+ "./package.json": "./package.json"
11
+ },
12
+ "files": [
13
+ "src",
14
+ "README.md",
15
+ "LICENSE"
16
+ ],
17
+ "license": "MIT",
18
+ "engines": {
19
+ "node": ">=24.15.0"
20
+ },
21
+ "dependencies": {
22
+ "@mytools/sdk": "0.1.0"
23
+ }
24
+ }
@@ -1,8 +1,9 @@
1
1
  import {isDeepStrictEqual} from 'node:util';
2
2
  import {FlowError} from '@mytools/sdk';
3
- import {bind,references} from './validation.js';
3
+ import {bind,references} from '@mytools/sdk/schema';
4
4
 
5
- export const conditionOperators=['eq','ne','gt','gte','lt','lte','contains','exists'];
5
+ import {conditionOperators} from '@mytools/sdk/conditions';
6
+ export {conditionOperators};
6
7
  const invalid=message=>{throw new FlowError(message,{code:'VALIDATION'});};
7
8
  export function validateCondition(condition,visit=()=>{},depth=0){
8
9
  if(depth>32||!condition||typeof condition!=='object'||Array.isArray(condition))invalid('条件必须是有限深度的结构化对象');
@@ -1,7 +1,7 @@
1
1
  import { randomUUID } from 'node:crypto';
2
2
  import { FlowError } from '@mytools/sdk';
3
- import { bind, validate, emptyObject } from './validation.js';
4
- import {errorRecord} from './diagnostics.js';
3
+ import { bind, validate, emptyObject } from '@mytools/sdk/schema';
4
+ import {errorRecord} from '@mytools/sdk/diagnostics';
5
5
  import {evaluateCondition,childLists} from './control.js';
6
6
 
7
7
  export async function runWorkflow({store,host,id,input={},signal,onEvent=()=>{},runId=randomUUID(),context={}}) {
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 mytools contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,32 @@
1
+ # 工作区模块
2
+
3
+ 包名:`@mytools/fst-workspace`。内部 workspace 包,随 `@yanglaofish/fishtools` 统一交付。
4
+
5
+ ## 职责与边界
6
+
7
+ 项目初始化、模板、工作流文件、插件安装批次、租约、开发引用及便携工作区。插件执行由 plugin 负责,系统调用由 platform 负责。
8
+
9
+ 内部依赖:`@mytools/sdk`、`@mytools/fst-platform`、`@mytools/fst-plugin`、`@mytools/fst-workflow`。
10
+ ## 公开入口
11
+
12
+ | 导入路径 | 实现位置 |
13
+ | --- | --- |
14
+ | `@mytools/fst-workspace` | `./src/index.js` |
15
+ | `@mytools/fst-workspace/workflows` | `./src/workflows.js` |
16
+ | `@mytools/fst-workspace/project` | `./src/project.js` |
17
+ | `@mytools/fst-workspace/plugins` | `./src/plugins.js` |
18
+ | `@mytools/fst-workspace/installations` | `./src/plugin-installations.js` |
19
+ | `@mytools/fst-workspace/bundles` | `./src/bundle-workspace.js` |
20
+ | `@mytools/fst-workspace/package.json` | `./package.json` |
21
+
22
+ 仅依赖 exports 声明的入口,禁止消费者跨包读取 src/bin 私有文件。入口路径由本模块维护,修改协议时同步消费方回归。
23
+
24
+ ## 维护与验证
25
+
26
+ 本模块负责上述职责的实现、文档与回归;跨模块接口调整需要同时检查生产者与消费者。具体维护人员尚未指定。
27
+
28
+ 在仓库根目录执行:
29
+
30
+ ```sh
31
+ node --test tests/modules.test.js tests/bundle-workspace.test.js tests/plugin-development-cycle.test.js tests/project-config-concurrency.test.js
32
+ ```
@@ -0,0 +1,34 @@
1
+ {
2
+ "name": "@mytools/fst-workspace",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "main": "src/index.js",
7
+ "exports": {
8
+ ".": "./src/index.js",
9
+ "./workflows": "./src/workflows.js",
10
+ "./project": "./src/project.js",
11
+ "./plugins": "./src/plugins.js",
12
+ "./installations": "./src/plugin-installations.js",
13
+ "./bundles": "./src/bundle-workspace.js",
14
+ "./package.json": "./package.json"
15
+ },
16
+ "files": [
17
+ "src",
18
+ "templates",
19
+ "README.md",
20
+ "LICENSE"
21
+ ],
22
+ "license": "MIT",
23
+ "engines": {
24
+ "node": ">=24.15.0"
25
+ },
26
+ "dependencies": {
27
+ "@mytools/sdk": "0.1.0",
28
+ "@mytools/fst-platform": "0.1.0",
29
+ "@mytools/fst-plugin": "0.1.0",
30
+ "@mytools/fst-workflow": "0.1.0",
31
+ "tar": "7.5.22",
32
+ "yaml": "2.9.1"
33
+ }
34
+ }
@@ -5,7 +5,7 @@ import {constants} from 'node:fs';
5
5
  import {randomUUID} from 'node:crypto';
6
6
  import {setTimeout} from 'node:timers/promises';
7
7
  import {FlowError} from '@mytools/sdk';
8
- import {readJson} from './plugins.js';
8
+ import {readJson} from '@mytools/fst-platform/json';
9
9
 
10
10
  export async function prepareBundleWorkspace(bundleRoot,requested){
11
11
  bundleRoot=path.resolve(bundleRoot);
@@ -0,0 +1,3 @@
1
+ export {WorkflowStore} from './workflows.js';
2
+ export {initializeProject,resolveProject} from './project.js';
3
+ export {prepareBundleWorkspace} from './bundle-workspace.js';