@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.
- package/README.md +46 -18
- package/manual/LICENSE +21 -0
- package/manual/README.md +173 -0
- package/manual/THIRD_PARTY_NOTICES.md +32 -0
- package/manual/docs/examples/action-plugin/LICENSE +21 -0
- package/manual/docs/examples/action-plugin/README.md +38 -0
- package/manual/docs/examples/action-plugin/index.js +18 -0
- package/manual/docs/examples/action-plugin/input.json +1 -0
- package/manual/docs/examples/action-plugin/package.json +9 -0
- package/manual/docs/plans/2026-10-10-workbench-ui.md +27 -0
- package/{docs → manual/docs}/plugin-development.md +151 -19
- package/{docs → manual/docs}/releases.md +13 -1
- package/manual/docs/roadmap.md +85 -0
- package/manual/docs/superpowers/plans/2026-10-09-workflow-mvp.md +115 -0
- package/manual/docs/superpowers/plans/2026-10-10-cordis-services.md +34 -0
- package/manual/docs/superpowers/plans/2026-10-10-fst-terminal.md +33 -0
- package/manual/docs/superpowers/plans/2026-10-10-portable-plugin-contract.md +35 -0
- package/manual/docs/superpowers/plans/2026-10-11-control-observation.md +77 -0
- package/manual/docs/superpowers/specs/2026-10-09-workflow-cli-design.md +377 -0
- package/manual/docs/superpowers/specs/2026-10-10-cordis-services-design.md +88 -0
- package/manual/docs/superpowers/specs/2026-10-10-portable-plugin-contract.md +31 -0
- package/manual/docs/superpowers/specs/2026-10-10-run-logs.md +28 -0
- package/manual/docs/superpowers/specs/2026-10-11-control-observation.md +37 -0
- package/{docs → manual/docs}/verification.md +14 -1
- package/manual/examples/service-plugins/README.md +20 -0
- package/manual/examples/service-plugins/consumer/LICENSE +21 -0
- package/manual/examples/service-plugins/consumer/index.js +14 -0
- package/manual/examples/service-plugins/consumer/package.json +1 -0
- package/manual/examples/service-plugins/provider/LICENSE +21 -0
- package/manual/examples/service-plugins/provider/index.js +19 -0
- package/manual/examples/service-plugins/provider/package.json +1 -0
- package/manual/packages/core/LICENSE +21 -0
- package/manual/packages/core/src/host.js +122 -0
- package/manual/packages/sdk/LICENSE +21 -0
- package/manual/packages/sdk/index.js +14 -0
- package/manual/packages/sdk/package.json +1 -0
- package/manual/plugins/environment/LICENSE +21 -0
- package/manual/plugins/environment/index.js +92 -0
- package/manual/plugins/environment/package.json +1 -0
- package/manual/plugins/examples/LICENSE +21 -0
- package/manual/plugins/examples/index.js +23 -0
- package/manual/plugins/examples/package.json +1 -0
- package/manual/plugins/examples/scripts/echo.py +5 -0
- package/manual/tests/application.test.js +45 -0
- package/node_modules/@mytools/core/bin/fst.js +12 -4
- package/node_modules/@mytools/core/src/application.js +9 -4
- package/node_modules/@mytools/core/src/cli-run.js +6 -0
- package/node_modules/@mytools/core/src/contracts.js +15 -2
- package/node_modules/@mytools/core/src/control.js +63 -0
- package/node_modules/@mytools/core/src/documentation.js +65 -0
- package/node_modules/@mytools/core/src/engine.js +80 -26
- package/node_modules/@mytools/core/src/host.js +13 -2
- package/node_modules/@mytools/core/src/markdown-log.js +8 -5
- package/node_modules/@mytools/core/src/process.js +35 -14
- package/node_modules/@mytools/core/src/project.js +1 -1
- package/node_modules/@mytools/core/src/run-logs.js +1 -1
- package/node_modules/@mytools/core/src/terminal/conditions.js +32 -0
- package/node_modules/@mytools/core/src/terminal/log-viewer.js +7 -8
- package/node_modules/@mytools/core/src/terminal/model.js +32 -3
- package/node_modules/@mytools/core/src/terminal/presentation.js +7 -9
- package/node_modules/@mytools/core/src/terminal/run-viewer.js +86 -0
- package/node_modules/@mytools/core/src/terminal/session.js +109 -22
- package/node_modules/@mytools/core/src/terminal/theme.js +28 -0
- package/node_modules/@mytools/core/src/terminal/view.js +74 -19
- package/node_modules/@mytools/core/src/validation.js +15 -4
- package/node_modules/@mytools/core/src/version.js +1 -1
- package/node_modules/@mytools/core/src/workflows.js +30 -19
- package/node_modules/@mytools/core/templates/control-demo.yaml +47 -0
- package/node_modules/@mytools/environment/index.js +14 -2
- package/node_modules/@mytools/pack/index.js +2 -0
- package/node_modules/@mytools/web/public/THIRD_PARTY_NOTICES.txt +215 -216
- package/node_modules/@mytools/web/public/assets/index-DT2B4tMV.js +70 -0
- package/node_modules/@mytools/web/public/assets/{index-BeV6IqTW.css → index-vuZvrUTT.css} +1 -1
- package/node_modules/@mytools/web/public/index.html +2 -2
- package/package.json +2 -2
- package/node_modules/@mytools/web/public/assets/index-TgsBnZgG.js +0 -70
- /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
|
-
|
|
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)
|
|
70
|
-
const
|
|
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
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
const
|
|
79
|
-
if(
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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
|
-
|
|
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);
|