跳到主要内容

Workflow 模型

workflow-code 支持 workflowconversationkanban 三种项目类型。Workflow 与 Conversation 是自包含的 TypeScript workflow;平台、CLI、server 和 Desktop 会注入全局命名空间 workflow,业务 workflow 不应该从 "workflow-code" 导入 runtime API。项目仍需在 devDependencies 中精确记录 Core 与 CLI 版本;Core 版本用于宿主兼容判断,CLI 独立发版。Kanban 是以 index.html 为入口的静态项目,不使用 runtime API 或 executor,详见 Kanban HTML 项目

项目信息与展示声明

项目源码以根 package.json 为项目信息的唯一权威来源。必填 name 是默认名称和旧客户端回退;workflowCode.projectInfo 还保存本地化名称、手工展示声明、展示平台、数据存储位置和关联项目:

{
"name": "Default Name",
"workflowCode": {
"projectInfo": {
"localizedNames": {
"zh-CN": "中文名称",
"en": "English Name"
},
"declarations": ["multilingual", "multiplatform"],
"platformSupport": {
"mode": "specific",
"platforms": ["macos", "windows", "web"]
},
"dataStorage": { "mode": "both" },
"relatedProjects": []
}
}
}

localizedNames 当前管理简体中文与英文;界面优先显示当前语言名称,没有时回退根 name,搜索同时覆盖默认、中文和英文名称。declarations 只是作者手工选择的展示标签,不根据名称或平台自动推断,也不验证项目是否真的实现了对应功能。当前内置声明为 multilingualmultiplatform

platformSupport 同样只描述项目展示平台,不决定版本在哪里运行。平台无关使用 { "mode": "agnostic" };指定平台使用 specific,并至少包含一个 macoswindowsweb。Web 展示不表示版本能在 Server executor 中运行。未知的未来语言、声明和平台 ID 会在读取和写回时保留。旧项目缺少 projectInfo 时按无声明、平台无关并只使用根 name 处理。

relatedProjects 是 Workflow、Conversation 和 Kanban 共用的项目关系声明,不是小助手或 Kanban 私有配置。每项包含 alias、目标项目 UUID,以及 grantToRelatedProject 读写规则;该授权方向表示“允许目标项目访问当前项目”。关系写入双方 package 后才确认,单边声明显示为待确认且不授予 KV 权限。完整格式、校验限制和访问语义见 KV 与会话。旧 workflowCode.kanban.dataSources 不会转换,会直接校验失败。

Runtime API

当前 workflow 全局对象来自 Core 仓库 src/runtime-api.ts,包含:

workflow.defineWorkflow
workflow.defineEntrypoint
workflow.defineExecutor
workflow.parseConversationDefaultInputArgs
workflow.createInputNode
workflow.createFileInputNode
workflow.createUserInputNode
workflow.createToolApprovalGate
workflow.createLLMNode
workflow.createLLMStreamNode
workflow.createLLMProviderFromEnv
workflow.createLLMProviderRef
workflow.createOutputNode
workflow.createDetailsOutputNode
workflow.createOutputItem
workflow.createOutputPayload
workflow.addOutputItem
workflow.createOutputItemChunk
workflow.createTimerNode
workflow.endTimer
workflow.createQuestionClassifierNode
workflow.runNode
workflow.runStreamNode
workflow.runIf
workflow.runFor
workflow.runWhile
workflow.runWorkflow
workflow.listKnowledgeDocuments
workflow.getKnowledgeDocument
workflow.createKnowledgeDocument
workflow.editKnowledgeDocument
workflow.deleteKnowledgeDocument
workflow.searchKnowledgeDocuments
workflow.readKnowledgeDocumentLines
workflow.getEnv
workflow.getRuntimePlatform
workflow.getRuntimeLocale
workflow.isCli
workflow.isWeb
workflow.isDesktop
workflow.getLLMProvider
workflow.WorkflowError

workflow.getRuntimeLocale() 返回当前 CLI、Desktop 或 Server 宿主选择的 "zh-CN""en"。没有宿主语言标记时默认 "zh-CN";明确支持国际化的项目应把用户可见输出、错误、日期和数字格式统一跟随该值,而不是自行探测浏览器或机器语言。静态 package、入口和参数清单保持项目默认语言。

完整方法、参数和运行对象请看 Core API

Workflow

Workflow 是业务流程本体,由 workflow.defineWorkflow<Input, Output>() 定义:

export const exampleWorkflow = workflow.defineWorkflow<Input, OutputPayload>({
name: "example",
async run(input, context) {
const parsed = await workflow.runNode(inputNode, input, context);
const result = await workflow.runStreamNode(outputNode, parsed, context);
return result.output;
},
});

Workflow 的 run 接收 inputcontext,并返回最终 payload。执行期间如果抛出异常,executor 会把它序列化到执行报告中。

Node

Node 是最小可执行单元,分为普通节点、流式节点、分支节点和循环节点:

const inputNode = workflow.createInputNode<Input, Parsed>({
name: "input",
parse(input) {
return { message: input.message.trim() };
},
});

const outputNode = workflow.createOutputNode<Parsed, OutputPayload>({
name: "output",
async *stream(input) {
yield input.message;
},
format(stream) {
return workflow.createOutputPayload({
items: stream.items,
});
},
});

输出 payload 的当前协议是 WorkflowOutputPayload.items: WorkflowOutputItem[]。每个 item 都是一个可独立展示和折叠的消息块,包含 idtitlecontent、可选 icon / iconPresetcontentTypecollapsedmetadatacontentType 只决定渲染方式:markdowntextjsonaudio;对象和数组应直接放进 content,不要提前 stringify 成 Markdown code block。audio content 使用 { src, mimeType, name, size, durationMs?, format? }src 可为安全 data:audio/...;base64,...http(s) URL。

workflow.createDetailsOutputNode 是创建独立可折叠 output item 的便捷节点,会把 { title, content, icon } 转成 items: [{ title, content, icon, contentType, collapsed: true }],并仍按 output 标准节点被 CLI、Desktop 和 server 收集。workflow.createTimerNode 会创建标准名为 timer 的普通节点,用于启动业务计时;workflow.endTimer(timer, context, { status, message }) 显式结束计时并写入 report.timers,未结束的 running timer 会在 workflow 收尾前自动标记为 cleared,避免 UI 残留运行态。workflow.runNode 返回节点输出;workflow.runStreamNode 返回 { chunks, output },其中 chunks 是原始流式 chunk 数组,outputfinalizeformat 生成的最终 payload。流式 createOutputNode({ stream, format })format 接收累积后的 { text, items, chunks };字符串 chunk 会自动写入一个默认 Markdown item,结构化输出可使用 workflow.createOutputItemChunk(...) 发送 output_item_startoutput_item_deltaoutput_item_endoutput_item_delta 除了合并 content,还可以携带 item patch 更新 titleiconcollapsedcontentTypemetadata.status,用于展示工具调用从 running 到 success / failed 的状态变化。workflow.runIf 会按顺序检查 if / else-if / else 分支,命中后执行对应 run,并用 branch-node 运行事件记录所选分支。workflow.runForworkflow.runWhile 会把循环本身记录为可追踪节点,让循环体在 Desktop Diagram 中保持可见。

workflow.createUserInputNode 会在 workflow 中插入一个可持久恢复的人工输入点。节点的 params 使用与当前 Workflow entrypoint(Conversation 则为 executor)相同的字段定义,Desktop、server embed 和 server API 会用同一套 Run workspace 表单展示;外部 agent 动态产生问题时,可以通过无副作用的 resolveRequest(input, context) 为本次请求生成标题、说明、字段和默认值。首次执行时如果没有提交值,runtime 发出 user_input_requested 事件,report 写入 pendingUserInput,run 状态变为 waiting_for_input;提交答案后,同一个 runId 会带着 resolvedUserInputs 恢复执行,并发出 user_input_resolved。Workflow 恢复固定使用 run 快照中的精确目标和原入口,不能切换入口。CLI 不读取 stdin,遇到该节点时会输出等待报告,需要通过 workflow-code json --run-id <id> --resolved-user-inputs-json '<json>' 注入答案继续。

workflow.createToolApprovalGate 使用同一套等待/恢复协议审批工具调用。Workflow 先在当前 entrypoint 的 tools 中声明工具,Conversation 则使用 executor tools,再在业务代码中调用 gate;工具未注册会返回 input_validation,被禁用会返回 tool_permission_denied,需要人工确认时会生成 tool-approval 请求并把 run 暂停为 waiting_for_input

Branch

workflow.runIf 适合把业务条件写成可追踪的 workflow 分支,而不是只写在普通 TypeScript if 里:

const plan = await workflow.runIf(seed, context, {
name: "Weapon branch",
branches: [
{
label: "high intensity assault",
condition: (mission) => mission.intensity >= 0.66,
run: (mission) => ({ ...mission, weapon: "ak47" }),
},
{
label: "stealth close quarter",
condition: (mission) => mission.stealth >= 0.58,
run: (mission) => ({ ...mission, weapon: "mp7" }),
},
],
else: {
label: "balanced rifle",
run: (mission) => ({ ...mission, weapon: "m4a1" }),
},
});

branches 数组中的第一个元素在 Diagram 中显示为 if,后续元素显示为 else-ifelse 会显示成 fallback 分支。分支的 run 里可以继续调用 workflow.runNodeworkflow.runStreamNode,也可以嵌套另一个 workflow.runIf 形成多层分支。

Loop

workflow.runForworkflow.runWhile 适合把业务循环写成可追踪的 workflow 循环,而不是只写在普通 TypeScript for / while 里:

const rounded = await workflow.runFor(seed, context, {
name: "Round loop",
maxIterations: 6,
items: (state) => Array.from({ length: state.rounds }, (_, index) => index + 1),
async run(state, value, index, loopContext) {
return workflow.runNode(addRoundNode, { ...state, value, index }, loopContext);
},
});

const boosted = await workflow.runWhile(rounded, context, {
name: "Boost until target",
maxIterations: 8,
condition: (state) => state.total < state.target,
async run(state, loopContext, iteration) {
return workflow.runIf(state, loopContext, {
name: "Choose boost strategy",
branches: [
{
label: "burst",
condition: (current) => current.target - current.total >= 7,
async run(current, branchContext) {
return workflow.runFor(current, branchContext, {
name: "Burst passes",
maxIterations: 3,
items: () => [4, 3],
async run(passState, value, index, passContext) {
return workflow.runNode(boostNode, { ...passState, value, index, iteration }, passContext);
},
});
},
},
],
else: {
label: "finish",
async run(current, branchContext) {
return workflow.runNode(boostNode, { ...current, iteration }, branchContext);
},
},
});
},
});

runFor 会按 items 生成的 iterable 顺序执行,默认最多 1000 次;runWhile 会在每轮开始前检查 condition,默认最多 100 次。两者的 run 都应返回下一轮状态。完成事件的 executionInfo 会记录 loopTypeiterationsiterationCountmaxIterationscompleted

Diagram 和节点协议

Desktop diagram 使用两类信息展示 workflow:

  • 静态结构来自 TypeScript 源码分析。所有已知 workflow.* runtime API 调用会写入 definitions.apiCalls,并以静态 API 节点展示,例如 workflow.createInputNodeworkflow.defineWorkflowworkflow.getEnv。这些节点帮助理解源码使用了哪些平台能力,但不会单独执行,也不会串进 Input -> Result 执行路径。
  • 运行状态来自 workflow.runNode / workflow.runStreamNode / workflow.runIf / workflow.runFor / workflow.runWhile / workflow.runWorkflownodeHooks 报告。只有真正被这些 API 执行的节点会显示运行中、成功、失败、耗时和错误。

workflow.runIf 会作为 run-if 分支节点展示,Desktop 会使用分支图标和语义色与 LLM、输出、provider、question-classifier 区分。完成事件的 executionInfo 会记录 selectedBranchselectedBranchIndexdescription,用于 Trace 查看实际走到了哪条分支。静态结构分析优先识别对象字面量形式的 branches / else;动态拼装分支仍可运行,但 Diagram 无法完整展开所有静态路径。

workflow.runForworkflow.runWhile 会作为 run-for / run-while 循环节点展示。Desktop Diagram 会用带语义色的矩形背景容器包裹循环体 children,循环头节点仍可点击查看 itemsconditionmaxIterations 和运行时迭代信息。循环体可以继续嵌套 workflow.runIfworkflow.runForworkflow.runWhile,用于表达 while 内部分支和多层循环。静态结构分析优先识别对象字面量形式的 loop options;动态拼装 options 仍可运行,但 Diagram 无法完整展开循环体。

workflow.runWorkflow 会作为 run-workflow 调用节点展示。子 workflow 不会创建独立 run report,子节点事件会进入父 trace;顶层 executor 入口不会额外插入这个包裹节点。默认 conversationMode: "shared"kvMode: "shared"outputVisibility: "visible",只传 name 不会改变会话或输出语义;当父 workflow 传入 conversationMode: "shared-readonly" 时,子 workflow 可以读取父会话快照,但不会追加 transcript 或修改标题。子 workflow 会写专属 KV 状态时,可同时传入 kvMode: "isolated"

Entrypoint 不作为 Diagram 中的嵌套节点,也不表达父子关系。项目对外只暴露一层独立入口;需要多级业务编排时继续嵌套 workflow.runWorkflow(),默认最大深度为 16

内置节点工厂返回的普通节点继承 BaseNode,流式节点继承 BaseStreamNode;它们共同提供 namestandardNamemetadatagetExecutionInfo() 等公共信息。Diagram 不直接绑定 BaseNode 类,而是读取 NodeDefinition / StreamNodeDefinition 执行协议和运行报告,因此未来自定义节点只要符合协议,也能被执行与记录。

Provider

当前 executor 的内置 provider 名称只有 "llm"。LLM provider 通过 createLLMProviderRef 延迟解析:

const llmProvider = workflow.createLLMProviderRef("default", () => {
return workflow.createLLMProviderFromEnv();
});

业务节点依赖统一 provider 接口,不直接绑定具体厂商。createLLMProviderFromEnv 会从 workflow runtime 环境读取 LLM_TYPELLM_API_KEYLLM_BASE_URLLLM_MODEL

Executor

Executor 是平台发现和运行项目的入口目录。Workflow 项目声明一个默认入口和多个可独立启动的 entrypoint:

export const executor = workflow.defineExecutor<Input, OutputPayload>({
projectType: "workflow",
defaultEntrypoint: "main",
entrypoints: [
workflow.defineEntrypoint<Input, OutputPayload>({
id: "main",
title: "完整流程",
workflow: exampleWorkflow,
params: [
{
name: "message",
flag: "--message",
type: "string",
required: true,
description: "输入文本。",
},
],
createInput({ args }) {
return parseArgs(args);
},
}),
],
});

点击或请求任一 entrypoint 都只创建该入口的 run,不先执行 main。入口 ID 项目内唯一,使用 /^[a-z][a-z0-9_-]{0,63}$/entrypoints 必须是非空静态数组,defaultEntrypoint 必须命中其中一项。各入口独立声明 workflowparamsresolveParamscreateInput、provider、tools、token usage、registry 和 context factory。旧 Workflow 顶层执行字段不再接受。

项目可以通过 package.json.workflowCode.schedulePresets 为某个入口声明建议定时计划。Structure 会校验预设 ID、入口、时区、触发规则和 JSON 参数,并把目录交给 Desktop 与 Server Web;预设不会自动启用。参见 Workflow 定时运行

Conversation executor 保持单入口,把 workflowparamscreateInput 等字段直接放在 executor 上,不声明或接受 entrypoint。Executor 的 projectType 只允许 "workflow""conversation"。Kanban 在 package.json.workflowCode.projectType 中声明 "kanban",不能伪造 executor。

createInput 会接收 workflowNameworkflowDirargsparamValuesenvabortSignal 和可选 conversationId。Workflow 还会收到所选 entrypointIdentrypointTitle;相同字段会写入 WorkflowContext.metadata、report 和运行记录。Desktop、CLI、server 和外部 API 最终都通过所选执行配置把参数转换为 workflow input;取消本地或 server 运行时,abortSignal 会传播到 workflow context,便于长耗时 provider/SDK 调用及时停止。

resolveParams 可以按当前参数返回 valuevisibledisabled 和单个 option 状态补丁,也可以用 resolvedOptions 数组替换参数当前的完整选项列表,或用 null 恢复静态选项。初始化以及 main、Advanced、Quick 的 Form 参数变化都会触发求值。visible: false 不会隐式清空旧值;互斥参数应同时返回 value: null,避免隐藏值继续进入 argsparamValues。动态列表适合读取服务器提供的 provider、model 或 variant 目录;读取必须可取消、限时,且不能在参数求值期间写入运行数据。

Registry

Registry 用于单节点调试和结构展示。Workflow 的 registry 属于具体 entrypoint,并且必须是静态数组字面量:

export const executor = workflow.defineExecutor<Input, OutputPayload>({
projectType: "workflow",
defaultEntrypoint: "main",
entrypoints: [
workflow.defineEntrypoint({
id: "main",
title: "完整流程",
workflow: exampleWorkflow,
registry: [
{
name: "output",
node: outputNode,
stream: true,
metadata: { type: "output", title: "Output", version: "1.0.0" },
createInput({ args }) {
return parseArgs(args);
},
},
],
createInput({ args }) {
return parseArgs(args);
},
}),
],
});

server 的 /api/workflows/{name}/debug/nodes/{nodeName} 会使用所选 entrypoint registry 中的节点配置运行单节点调试;省略入口时使用默认入口,不会跨入口搜索同名节点。Conversation 继续使用 executor 顶层 registry。

包结构

workflow package 精确声明 Core 与 CLI 的 dev dependency,同时按需声明真实第三方依赖。CLI、Desktop 和 server 会提供外层运行时:

{
"id": "1f221460-4fc7-48e2-bd23-651dee826692",
"name": "@workflow-code/hello-workflow",
"version": "0.1.0",
"private": true,
"type": "module",
"devDependencies": {
"@workflow-code/cli": "0.2.0",
"workflow-code": "0.2.0"
},
"workflowCode": {
"projectInfo": {
"dataStorage": {
"mode": "both"
},
"relatedProjects": []
}
}
}

workflow-code 必须是精确 SemVer,不能写 ^0.2.xworkspace:* 或其它 range。Server、Desktop 与 CLI 在运行前用项目要求的 Core 版本计算兼容性,但只比较 major.minor:同线任意 patch 或精确预发布版本可直接运行,major 或 minor 变化需要升级宿主。@workflow-code/cli 同样固定精确版本但独立发版,不替代 Core 运行兼容判断。Core 0.2 项目还必须显式声明 workflowCode.projectInfo.dataStorage.mode

Kanban 的最小 package 使用 workflowCode.projectType: "kanban",目录只要求 package.json + index.html;参数化项目再增加可选 kanban.json。Kanban package 同样精确记录 Core 与 CLI 版本,以便项目管理工具保持统一依赖模型;Kanban 本身不执行 Core runtime。