@yanglaofish/fishtools 0.1.4 → 0.2.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 (77) hide show
  1. package/README.md +46 -18
  2. package/manual/LICENSE +21 -0
  3. package/manual/README.md +173 -0
  4. package/manual/THIRD_PARTY_NOTICES.md +32 -0
  5. package/manual/docs/examples/action-plugin/LICENSE +21 -0
  6. package/manual/docs/examples/action-plugin/README.md +38 -0
  7. package/manual/docs/examples/action-plugin/index.js +18 -0
  8. package/manual/docs/examples/action-plugin/input.json +1 -0
  9. package/manual/docs/examples/action-plugin/package.json +9 -0
  10. package/manual/docs/plans/2026-10-10-workbench-ui.md +27 -0
  11. package/{docs → manual/docs}/plugin-development.md +151 -19
  12. package/{docs → manual/docs}/releases.md +13 -1
  13. package/manual/docs/roadmap.md +85 -0
  14. package/manual/docs/superpowers/plans/2026-10-09-workflow-mvp.md +115 -0
  15. package/manual/docs/superpowers/plans/2026-10-10-cordis-services.md +34 -0
  16. package/manual/docs/superpowers/plans/2026-10-10-fst-terminal.md +33 -0
  17. package/manual/docs/superpowers/plans/2026-10-10-portable-plugin-contract.md +35 -0
  18. package/manual/docs/superpowers/plans/2026-10-11-control-observation.md +77 -0
  19. package/manual/docs/superpowers/specs/2026-10-09-workflow-cli-design.md +377 -0
  20. package/manual/docs/superpowers/specs/2026-10-10-cordis-services-design.md +88 -0
  21. package/manual/docs/superpowers/specs/2026-10-10-portable-plugin-contract.md +31 -0
  22. package/manual/docs/superpowers/specs/2026-10-10-run-logs.md +28 -0
  23. package/manual/docs/superpowers/specs/2026-10-11-control-observation.md +37 -0
  24. package/{docs → manual/docs}/verification.md +14 -1
  25. package/manual/examples/service-plugins/README.md +20 -0
  26. package/manual/examples/service-plugins/consumer/LICENSE +21 -0
  27. package/manual/examples/service-plugins/consumer/index.js +14 -0
  28. package/manual/examples/service-plugins/consumer/package.json +1 -0
  29. package/manual/examples/service-plugins/provider/LICENSE +21 -0
  30. package/manual/examples/service-plugins/provider/index.js +19 -0
  31. package/manual/examples/service-plugins/provider/package.json +1 -0
  32. package/manual/packages/core/LICENSE +21 -0
  33. package/manual/packages/core/src/host.js +122 -0
  34. package/manual/packages/sdk/LICENSE +21 -0
  35. package/manual/packages/sdk/index.js +14 -0
  36. package/manual/packages/sdk/package.json +1 -0
  37. package/manual/plugins/environment/LICENSE +21 -0
  38. package/manual/plugins/environment/index.js +92 -0
  39. package/manual/plugins/environment/package.json +1 -0
  40. package/manual/plugins/examples/LICENSE +21 -0
  41. package/manual/plugins/examples/index.js +23 -0
  42. package/manual/plugins/examples/package.json +1 -0
  43. package/manual/plugins/examples/scripts/echo.py +5 -0
  44. package/manual/tests/application.test.js +45 -0
  45. package/node_modules/@mytools/core/bin/fst.js +12 -4
  46. package/node_modules/@mytools/core/src/application.js +9 -4
  47. package/node_modules/@mytools/core/src/cli-run.js +6 -0
  48. package/node_modules/@mytools/core/src/contracts.js +15 -2
  49. package/node_modules/@mytools/core/src/control.js +63 -0
  50. package/node_modules/@mytools/core/src/documentation.js +65 -0
  51. package/node_modules/@mytools/core/src/engine.js +80 -26
  52. package/node_modules/@mytools/core/src/host.js +13 -2
  53. package/node_modules/@mytools/core/src/markdown-log.js +8 -5
  54. package/node_modules/@mytools/core/src/process.js +35 -14
  55. package/node_modules/@mytools/core/src/project.js +1 -1
  56. package/node_modules/@mytools/core/src/run-logs.js +1 -1
  57. package/node_modules/@mytools/core/src/terminal/conditions.js +32 -0
  58. package/node_modules/@mytools/core/src/terminal/log-viewer.js +7 -8
  59. package/node_modules/@mytools/core/src/terminal/model.js +32 -3
  60. package/node_modules/@mytools/core/src/terminal/presentation.js +7 -9
  61. package/node_modules/@mytools/core/src/terminal/run-viewer.js +86 -0
  62. package/node_modules/@mytools/core/src/terminal/session.js +109 -22
  63. package/node_modules/@mytools/core/src/terminal/theme.js +28 -0
  64. package/node_modules/@mytools/core/src/terminal/view.js +74 -19
  65. package/node_modules/@mytools/core/src/validation.js +15 -4
  66. package/node_modules/@mytools/core/src/version.js +1 -1
  67. package/node_modules/@mytools/core/src/workflows.js +30 -19
  68. package/node_modules/@mytools/core/templates/control-demo.yaml +47 -0
  69. package/node_modules/@mytools/environment/index.js +14 -2
  70. package/node_modules/@mytools/pack/index.js +2 -0
  71. package/node_modules/@mytools/web/public/THIRD_PARTY_NOTICES.txt +215 -216
  72. package/node_modules/@mytools/web/public/assets/index-DT2B4tMV.js +70 -0
  73. package/node_modules/@mytools/web/public/assets/{index-BeV6IqTW.css → index-vuZvrUTT.css} +1 -1
  74. package/node_modules/@mytools/web/public/index.html +2 -2
  75. package/package.json +2 -2
  76. package/node_modules/@mytools/web/public/assets/index-TgsBnZgG.js +0 -70
  77. /package/{docs → manual/docs}/deployment.md +0 -0
@@ -3,7 +3,8 @@ import path from 'node:path';
3
3
  import { randomUUID } from 'node:crypto';
4
4
  import YAML from 'yaml';
5
5
  import { FlowError } from '@mytools/sdk';
6
- import { assertId, references, referenceParts, emptyObject } from './validation.js';
6
+ import { assertId, assertSchema, references, referenceParts, emptyObject } from './validation.js';
7
+ import {validateCondition,validateStepList} from './control.js';
7
8
 
8
9
  function fieldSchema(schema,fields,ref){
9
10
  let current=schema;
@@ -34,13 +35,7 @@ export class WorkflowStore {
34
35
  validateShape(w) {
35
36
  assertId(w?.id);
36
37
  if (w.schemaVersion!==1 || !Array.isArray(w.steps)) throw new FlowError('工作流格式错误:需要 schemaVersion: 1 与 steps 列表',{code:'VALIDATION'});
37
- const ids=new Set();
38
- for(const s of w.steps) {
39
- assertId(s.id);
40
- if(ids.has(s.id)) throw new FlowError(`步骤 ID 重复:${s.id}`,{code:'VALIDATION'});
41
- ids.add(s.id);
42
- if(Boolean(s.action)===Boolean(s.workflow) || (s.action && typeof s.action!=='string') || (s.workflow && typeof s.workflow!=='string')) throw new FlowError(`${s.id} 必须选择一个动作或子工作流`,{code:'VALIDATION'});
43
- }
38
+ validateStepList(w.steps,assertId);
44
39
  return structuredClone(w);
45
40
  }
46
41
  async save(definition) {
@@ -66,21 +61,37 @@ export class WorkflowStore {
66
61
  if(visiting.has(id)) throw new FlowError(`工作流循环引用:${[...visiting,id].join(' → ')}`,{code:'VALIDATION'});
67
62
  if(workflows.has(id)) return;
68
63
  visiting.add(id);
69
- const w=await this.get(id), previous=new Map();
70
- const check=ref=>{
64
+ const w=await this.get(id);
65
+ const checker=previous=>ref=>{
71
66
  const p=referenceParts(ref);
72
67
  if(p[0]==='steps' && !previous.has(p[1])) throw new FlowError(`${id} 引用必须指向前序步骤:${ref}`,{code:'VALIDATION'});
73
68
  return fieldSchema(p[0]==='inputs'?(w.inputs??emptyObject):previous.get(p[1]),p.slice(p[0]==='inputs'?1:3),ref);
74
69
  };
75
- for(const s of w.steps) {
76
- references(s.with??{},check);
77
- if(s.action) {
78
- const action=host.actions.get(s.action);
79
- if(!action) throw new FlowError(`${id} → ${s.id} 缺少插件动作 ${s.action}`,{code:'VALIDATION'});
80
- plugins.add(action.plugin);
81
- previous.set(s.id,action.output);
82
- } else {const child=this.childId(s.workflow);await visit(child);previous.set(s.id,outputs.get(child));}
83
- }
70
+ const resolveList=async(steps,inherited=new Map(),parallel=false)=>{
71
+ const previous=new Map(inherited),own={};
72
+ for(const s of steps){
73
+ const check=checker(parallel?inherited:previous);
74
+ if(s.when)validateCondition(s.when,check);
75
+ let output;
76
+ if(s.action){
77
+ references(s.with??{},check);
78
+ const action=host.actions.get(s.action);
79
+ if(!action)throw new FlowError(`${id} → ${s.id} 缺少插件动作 ${s.action}`,{code:'VALIDATION'});
80
+ if(action.input===undefined||action.output===undefined)throw new FlowError(`可编排动作 ${s.action} 必须声明 input 和 output schema`,{code:'VALIDATION'});
81
+ assertSchema(action.input,`${s.action} input schema`);assertSchema(action.output,`${s.action} output schema`);
82
+ plugins.add(action.plugin);output=action.output;
83
+ }else if(s.workflow){references(s.with??{},check);const child=this.childId(s.workflow);await visit(child);output=outputs.get(child);}
84
+ else if(s.branch){
85
+ validateCondition(s.branch.condition,check);
86
+ const left=await resolveList(s.branch.then,parallel?inherited:previous),right=await resolveList(s.branch.else??[],parallel?inherited:previous);
87
+ output={type:'object',additionalProperties:false,properties:{...left.own,...right.own}};
88
+ }else if(s.parallel){const group=await resolveList(s.parallel.steps,parallel?inherited:previous,true);output={type:'object',additionalProperties:false,properties:group.own};}
89
+ else output=emptyObject;
90
+ previous.set(s.id,output);own[s.id]=output;
91
+ }
92
+ return {previous,own};
93
+ };
94
+ const {previous}=await resolveList(w.steps),check=checker(previous);
84
95
  references(w.outputs??{},check);
85
96
  const infer=value=>{
86
97
  if(value?.$ref)return check(value.$ref);
@@ -0,0 +1,47 @@
1
+ schemaVersion: 1
2
+ id: control-demo
3
+ title: 条件与并行入门
4
+ description: 检查系统后,根据你的选择继续或提前结束;继续时并行处理两段文本。运行中可切换步骤和查看日志。
5
+ inputs:
6
+ type: object
7
+ properties:
8
+ proceed:
9
+ type: boolean
10
+ title: 继续执行文本处理
11
+ default: true
12
+ required: [proceed]
13
+ steps:
14
+ - id: environment
15
+ action: "@mytools/environment:report"
16
+ with: {}
17
+ - id: guard
18
+ when:
19
+ left: {$ref: steps.environment.output.ok}
20
+ op: eq
21
+ right: false
22
+ stop:
23
+ reason: 环境检查未通过,已停止后续步骤。
24
+ - id: choose
25
+ branch:
26
+ condition:
27
+ left: {$ref: inputs.proceed}
28
+ op: eq
29
+ right: true
30
+ then:
31
+ - id: batch
32
+ parallel:
33
+ concurrency: 2
34
+ steps:
35
+ - id: first
36
+ action: "@mytools/examples:uppercase"
37
+ with: {text: "Hello, Fishtools!"}
38
+ - id: second
39
+ action: "@mytools/examples:uppercase"
40
+ with: {text: Parallel workflow}
41
+ else:
42
+ - id: finish
43
+ stop:
44
+ reason: 本次选择提前结束,没有执行文本处理。
45
+ outputs:
46
+ first: {$ref: steps.choose.output.batch.first.text}
47
+ second: {$ref: steps.choose.output.batch.second.text}
@@ -17,7 +17,7 @@ for item in names:
17
17
  packages.append({'name':name,'ok':False,'error':str(e)})
18
18
  print(json.dumps({'executable':sys.executable,'version':sys.version.split()[0],'prefix':sys.prefix,'packages':packages}))`;
19
19
 
20
- export default definePlugin({
20
+ const plugin={
21
21
  id:'@mytools/environment',version:'0.1.0',title:'环境检查',description:'检查目标机器的系统、Python/pip 与昇腾 NPU 环境,缺失必需条件时停止工作流。',
22
22
  platforms:['win32','linux'],commands:{'env.check':{description:'检查系统、Python/pip 或 NPU',action:'check'}},
23
23
  actions:{
@@ -77,4 +77,16 @@ export default definePlugin({
77
77
  },
78
78
  },
79
79
  },
80
- });
80
+ };
81
+ // 报告模式将“检测完成”与“检测通过”分开;旧 check 保持失败即终止的兼容语义。
82
+ plugin.actions.report={
83
+ ...plugin.actions.check,
84
+ title:'生成环境报告',description:'返回结构化检测结果;未通过时返回 ok:false,由工作流条件决定是否继续。',
85
+ conditions:{passed:{title:'环境检查通过',path:'ok',op:'eq',value:true}},
86
+ async run(input,context){
87
+ try{return await plugin.actions.check.run(input,context);}
88
+ catch(error){if(error.code==='ENVIRONMENT_FAILED')return error.details;throw error;}
89
+ },
90
+ };
91
+ plugin.commands['env.report']={description:'检测环境并返回报告(不因检查未通过抛错)',action:'report'};
92
+ export default definePlugin(plugin);
@@ -9,6 +9,7 @@ import YAML from 'yaml';
9
9
  import { verifyBundle } from '@mytools/core/bundle';
10
10
  import {planEnvironments} from './environments.js';
11
11
  import {archiveDirectory} from './archive.js';
12
+ import {copyDocumentation} from '../core/src/documentation.js';
12
13
  export {verifyBundle};
13
14
 
14
15
  async function hashes(root,dir=root,result={}){
@@ -84,6 +85,7 @@ export async function packApplication(app,{entries,target,outDir='dist',includeW
84
85
  for(const e of environmentPlan.entries.filter(e=>e.source==='bundled'))for(const p of packages.filter(p=>p.name===e.plugin)){
85
86
  const file=['app',p.path,e.executable].join('/');executablePaths.add(file);await chmod(path.join(directory,file),0o755);
86
87
  }
88
+ await copyDocumentation(path.join(directory,'manual'));
87
89
  const manifest={format:2,bundleId,editable:!readOnly,createdAt:new Date().toISOString(),target,runtime,entries,workflows:closure.workflows.map(w=>w.id),plugins:plugins.map(p=>({id:p.id,version:p.version,platforms:p.platforms,architectures:p.architectures})),environments:environmentPlan.entries,warnings:environmentPlan.warnings,packages,files:await hashes(directory)};
88
90
  await writeFile(path.join(directory,'manifest.json'),JSON.stringify(manifest,null,2));
89
91
  const archive=await archiveDirectory(directory,target,executablePaths);