defineExecutor
workflow.defineExecutor<Input, Output>() 定义项目的执行协议。Workflow 使用它声明默认入口和多个独立 entrypoints;Conversation 继续把 workflow、参数和输入构造直接声明在 executor 上。
签名
workflow.defineExecutor<Input, Output>(
definition: WorkflowExecutorDefinition<Input, Output>,
): WorkflowExecutorDefinition<Input, Output>
参数
| 参数 | 类型 | 说明 |
|---|---|---|
definition | WorkflowExecutorDefinition<Input, Output> | 平台发现和执行 workflow 的入口配置。 |
Workflow executor
| 字段 | 类型 | 说明 |
|---|---|---|
projectType | "workflow" | Workflow 项目的类型标记。必填。 |
defaultEntrypoint | string | 省略入口选择时使用的入口 ID。必须是静态字符串并命中 entrypoints。 |
entrypoints | WorkflowEntrypointDefinition[] | 非空静态入口数组;每项必须直接调用 workflow.defineEntrypoint()。 |
Workflow 的 workflow、params、createInput、resolveParams、createContext、requiredProviders、tools、tokenUsage 和 registry 全部属于具体 entrypoint,不能放在 executor 顶层。入口按声明顺序展示;选择任一入口会直接创建该入口的 run,不会先执行默认入口。
Workflow 可以在 package.json.workflowCode.schedulePresets 中声明建议计划。每项静态引用一个 entrypoint,并包含 IANA 时区、时间窗口或五字段 Cron,以及默认参数值。该目录会进入 Structure 报告,但宿主必须显示确认界面,不能因导入或发布项目自动创建计划。Conversation 和 Kanban 声明此字段会被 Structure 拒绝。完整格式与运行边界参见 Workflow 定时运行。
Conversation executor
| 字段 | 类型 | 说明 |
|---|---|---|
projectType | "conversation" | Conversation 项目的类型标记。必填。 |
workflow | WorkflowDefinition<Input, Output> | Conversation 的唯一 workflow。必填。 |
createInput | (context) => Input | 把 args、环境和会话 ID 转成业务输入。必填。 |
resolveParams | (context) => WorkflowParamResolveResult | 可选参数联动回调。 |
createContext | (context) => Partial<WorkflowContext> | 增补 providers、metadata 和 hooks。 |
requiredProviders | ExecutorProviderName[] | 声明必需 provider。 |
params | WorkflowParamDefinition[] | Conversation 的结构化参数。 |
tools | WorkflowToolPermissionDefinition[] | Conversation 可调用的工具和默认策略。 |
conversation | WorkflowConversationDefinition | 配置默认输入框、附件和输入队列。 |
tokenUsage | WorkflowTokenUsageDefinition | 开启 token 用量统计。 |
registry | WorkflowNodeRegistryEntry[] | 注册可单独调试的节点。 |
Conversation 不声明 defaultEntrypoint 或 entrypoints。CLI、Desktop 和 Server 收到非空入口选择时会返回错误,不会忽略该值。
WorkflowExecutorContext
| 字段 | 类型 | 说明 |
|---|---|---|
workflowName | string | 当前 workflow 名称。 |
workflowDir | string | 当前 workflow 目录。 |
entrypointId | string | undefined | Workflow 当前所选入口 ID;Conversation 为 undefined。 |
entrypointTitle | string | undefined | Workflow 当前所选入口标题;Conversation 为 undefined。 |
args | string[] | 原始 CLI 风格参数。 |
paramValues | Record<string, string | string[]> | 按 params 解析后的结构化值。单值保持字符串,多选为字符串数组。 |
env | NodeJS.ProcessEnv | 环境变量集合。 |
abortSignal | AbortSignal | 中断信号;Desktop/server 取消运行时会传递到 workflow 和支持 signal 的 SDK/provider 调用。 |
conversationId | string | undefined | 会话 ID。 |
toolPermissionPolicies | WorkflowToolPermissionPolicyMap | undefined | 宿主传入的工具策略覆盖值。workflow 通常不直接读取该字段,而是使用 context.toolPermissions 或 workflow.createToolApprovalGate()。 |
WorkflowParamDefinition
| 字段 | 类型 | 说明 |
|---|---|---|
name | string | 参数名。必填。 |
flag | string | 长参数,例如 --message。 |
shortFlag | string | 短参数,例如 -m。 |
type | WorkflowParamType | 参数类型。必填。 |
required | boolean | 是否必填。 |
description | string | 参数说明。 |
defaultValue | string | 默认值。 |
multiple | boolean | 是否允许多个值。 |
control | "text" | "textarea" | "radio" | "select" | "checkboxes" | "date" | "time" | "datetime" | "date-range" | Desktop/server Run workspace 的表单控件提示;日期相关控件仍使用 type: "string"。 |
panel | "main" | "auxiliary" | "quick" | 表单展示区域;auxiliary 收进 Advanced,quick 进入 composer 的组合快捷选择器。 |
resolveOnInput | boolean | 是否在主输入内容变化时执行 resolveParams。默认 false;只有联动确实依赖输入文本时才设为 true。 |
options | { value, label?, description? }[] | 枚举选项;有选项时表单会渲染单选、下拉或多选控件。 |
accept | string[] | 文件类型限制。 |
format | "json" | file param 使用 JSON 引用格式。 |
WorkflowParamStatePatch
| 字段 | 类型 | 说明 |
|---|---|---|
value | string | string[] | null | 替换当前参数值;null 表示显式清空。 |
visible | boolean | 控制参数是否显示。 |
disabled | boolean | 控制参数是否可操作。 |
resolvedOptions | { value, label?, description? }[] | null | 数组替换参数当前的完整选项列表;null 恢复静态 options。 |
options | Record<string, { visible?, disabled? }> | 修改当前选项列表中单个值的显示或禁用状态。 |
WorkflowNodeRegistryEntry
| 字段 | 类型 | 说明 |
|---|---|---|
name | string | registry 中的调试节点名。必填。 |
node | NodeDefinition | StreamNodeDefinition | 实际执行的节点对象。必填。 |
metadata | WorkflowNodeMetadataInput | 覆盖节点展示元信息。 |
stream | boolean | 标记节点是否按流式节点执行。 |
createInput | (context) => Input | 单节点调试时的输入构造函数。 |
执行流程
| 阶段 | 说明 |
|---|---|
| 平台选择配置 | Workflow 按显式 ID 或 defaultEntrypoint 选择一个入口;Conversation 使用 executor 本身。 |
| 创建上下文 | runtime 根据 args、env、workflowName、入口和 conversationId 创建 WorkflowExecutorContext。 |
createInput | 调用所选入口或 Conversation executor 的输入构造。 |
createContext | 调用同一执行配置的可选上下文扩展。 |
workflow run | 只执行所选入口的 workflow,或 Conversation 的唯一 workflow。 |
快捷参数和联动
panel: "quick" 适用于运行前需要频繁调整的枚举参数。Workflow 把这些字段写在具体 entrypoint 中,Conversation 写在 executor 中。composer 底部只显示一个组合摘要入口:存在 model 与 reasoningEffort、effort 或 variant 时优先显示“模型 · Effort”,否则回退到其它已选快捷值。
打开入口后,一级菜单按声明顺序列出全部可见快捷参数及当前值,选择参数后再进入二级单选或多选列表。Provider、Permission、Sandbox 等字段仍可配置,不会因摘要只突出模型与 Effort 而省略。宽屏会把二级列表放在一级菜单相邻侧,窄屏则在同一弹层中钻取并提供返回操作。
“重置为默认值”会一次恢复全部快捷参数并只触发一次 resolveParams 联动,不会清空 Main、Advanced、Raw 中的未知参数,也不会清空 Conversation 尚未发送的正文或附件。所有选择最终仍写入真实 args 和 paramValues。
params: [
{
name: "tone",
flag: "--tone",
type: "string",
panel: "quick",
resolveOnInput: true,
control: "select",
defaultValue: "balanced",
options: [
{ value: "balanced", label: "Balanced" },
{ value: "precise", label: "Precise" },
{ value: "creative", label: "Creative" },
],
},
{
name: "sources",
flag: "--sources",
type: "string",
panel: "quick",
control: "checkboxes",
options: [
{ value: "web", label: "Web" },
{ value: "docs", label: "Docs" },
],
},
],
resolveParams({ draftText, paramValues }) {
const docsQuestion = draftText.includes("文档");
return {
params: {
tone: {
value: docsQuestion ? "precise" : paramValues.tone,
options: { creative: { disabled: docsQuestion } },
},
sources: { value: docsQuestion ? ["docs"] : paramValues.sources },
},
};
},
createInput({ paramValues }) {
return {
tone: String(paramValues.tone),
sources: Array.isArray(paramValues.sources) ? paramValues.sources : [],
};
},
resolveParams 会在初始化以及 main、Advanced、Quick 中任意 Form 参数值变化时执行。主输入内容变化只有在至少一个参数设置 resolveOnInput: true 时才会触发,避免普通消息输入造成无关的远程求值。它应保持无写入副作用和幂等,不执行写文件、写 KV 或修改外部状态;只读动态目录应传入回调提供的 abortSignal。补丁中的 visible / disabled 不会隐式清空已有值;需要清空时显式返回 value: null。初始化在 Raw 模式返回值补丁时,宿主只更新对应已声明参数,保留其它 Raw 参数及其值。
resolvedOptions 用于动态替换枚举:返回数组会完整替换参数的静态 options,返回 null 恢复静态选项,省略则保留当前动态选项。数组中的 value 必须是非空唯一字符串,label 和 description 必须是字符串。替换选项时旧 option 状态会清除;宿主会统一把动态选项应用到 Quick 与 Advanced,并区分 loading、empty、error。conversation 宿主按 conversation ID 重新求值,已经过期的异步响应不会覆盖当前会话。
日期参数
日期相关控件使用固定的本地时间字符串,不进行时区转换:
control | 字符串格式 | 行为 |
|---|---|---|
date | YYYY-MM-DD | 选择单个日期后立即写回。 |
time | HH:mm:ss | 24 小时制,包含秒。 |
datetime | YYYY-MM-DDTHH:mm:ss | 选择日期和时间后通过“确定”写回。 |
date-range | YYYY-MM-DD/YYYY-MM-DD | 闭区间;只在起止日期完整且结束不早于开始时写回。 |
这些控件只支持 type: "string" 和 panel: "main" / panel: "auxiliary",不能声明 options、multiple: true 或 panel: "quick";TypeScript、静态结构分析和 runtime 都会拒绝非法组合。日期参数也不能通过 resolveParams 返回 resolvedOptions 或 option 状态补丁。
空值保持 "";可选参数可以清除,必填参数不显示清除操作。非空无效默认值、Raw 参数或 resolveParams 返回值会原样进入表单并显示字段错误,不会被静默修正;修正前 Run、Send 或 Submit 保持禁用,不会把已知无效值传给 createInput。合法值的 args 与 paramValues 仍按原字符串传递。
点击日历标题会打开紧凑的年份、月份双列滚动选择器;滚动过程只更新草稿,选择“完成”后才切换日历,“取消”保持原月份。年份限制为 0001 到 9999,滚轮只加载当前年份附近的有限选项。在宽屏界面中,date-range 会显示两个可独立切换年月的日历;重新打开时分别定位到已选的开始月份和结束月份,起止在同一个月时右侧显示下一个月。切换其中一个日历不会改变另一个日历当前展示的月份。窄屏界面使用单个日历,日期范围的值格式和完整范围提交规则保持不变。
params: [
{ name: "publishDate", flag: "--publish-date", type: "string", control: "date" },
{ name: "startsAt", flag: "--starts-at", type: "string", control: "datetime", panel: "auxiliary" },
{ name: "period", flag: "--period", type: "string", control: "date-range" },
],
resolveParams({ paramValues, trigger }) {
const publishDate = typeof paramValues.publishDate === "string" ? paramValues.publishDate : "";
const period = typeof paramValues.period === "string" ? paramValues.period : "";
const preferPeriod = trigger.kind === "param" && trigger.paramName === "period";
const usePeriod = period !== "" && (preferPeriod || publishDate === "");
const useDate = publishDate !== "" && !usePeriod;
return {
params: {
publishDate: usePeriod
? { visible: false, value: null }
: { visible: true },
period: useDate
? { visible: false, value: null }
: { visible: true },
},
};
},
createInput({ paramValues }) {
return {
publishDate: String(paramValues.publishDate ?? ""),
startsAt: String(paramValues.startsAt ?? ""),
period: String(paramValues.period ?? ""),
};
},
该互斥示例在选择单日后隐藏并清空范围,选择范围后隐藏并清空单日;清除当前值后两个字段重新显示。初始化时若外部 args 同时提供两者,会确定性地保留单日并清空范围。只返回 visible: false 会保留旧值,并继续把它放入 args 和 paramValues,不适合互斥参数。
每个 executor 都必须静态声明 projectType。projectType: "conversation" 会自动启用 conversation ID、上下文和会话存储,workflow 代码可以调用 context.conversation.setTitle(title) 给 Desktop 或 server embed 提供可读会话标题。projectType: "workflow" 禁止声明 conversation,其会话上下文保持禁用,并必须声明静态 defaultEntrypoint 与 entrypoints。缺失、未知类型或遗留 conversation.enabled 都会作为迁移错误拒绝加载,不会回退成 Workflow。
workflow.defineExecutor(...) 必须接收内联对象字面量,并把返回值作为模块的 executor 命名导出或默认导出。不能先把配置保存到变量后再传入,也不能继续使用顶层 workflow、createInput、projectType 等旧式分散导出;静态结构分析不会执行配置代码,runtime 也不会再把这些字段拼装成 executor。
如果 conversation workflow 想复用 Desktop、server embed 和公开 embed 的共享默认输入框,可以在 conversation 下声明:
export const executor = workflow.defineExecutor<Input, Output>({
projectType: "conversation",
workflow: conversationWorkflow,
conversation: {
inputQueue: {
enabled: true,
},
defaultInput: {
enabled: true,
attachments: {
images: true,
files: true,
},
},
},
createInput({ args }) {
return workflow.parseConversationDefaultInputArgs(args);
},
});
defaultInput 只是宿主 UI 协议,不会自动改写 WorkflowContext。workflow 仍需在 createInput({ args }) 中显式读取固定参数名:
- 文本:
--message - 图片附件:
--images - 普通文件附件:
--files
推荐直接使用 workflow.parseConversationDefaultInputArgs(args):
createInput({ args }) {
const defaults = workflow.parseConversationDefaultInputArgs(args);
return {
message: defaults.message,
images: defaults.images,
files: defaults.files,
};
}
--images / --files 复用现有 file reference 协议,可以是 managed file JSON refs,也可以是本地 path。宿主负责把粘贴、拖拽或上传的 File 先转成 managed refs,再写回这些固定参数。
如果还声明了 conversation.inputQueue: { enabled: true },Desktop 和 server/web embed
会在当前会话运行中允许继续提交新输入。新增输入不会立刻执行,而是进入共享输入队列:
- 队列只在 Desktop 和 Web 宿主中生效,CLI 不使用这个能力。
- 宿主会持久化等待中的输入;Desktop 重启或 embed 刷新后,队列仍保留。
- 恢复后的队列不会自动续跑,需要先手动发送一条新输入,后续排队项才会按顺序继续执行。
- 当前 run 完成、失败或取消后,宿主会清理该会话的 active 运行标记;后续新输入恢复为普通发送,不会继续进入等待队列。
- 队列项支持引导、编辑、删除和拖动排序;引导会先中断当前运行,再立即执行目标队列项。
开启 tokenUsage.enabled 后,runtime 会把 token 用量写入 KV,并把本次 run 聚合写入运行 report。聚合包含 inputTokens、outputTokens、totalTokens、reasoningTokens、cachedInputTokens、cacheCreationTokens 和 calls。display 省略时默认等于 enabled,Desktop 和 server embed 只在 display === true 且有数据时展示 badge;badge 默认显示总量,hover/focus 分别展示 Input、Output、Reasoning、Cache read、Cache write 和 Calls。
工具权限
tools 用于注册 workflow 业务工具的默认权限。每个工具包含稳定 name,以及可选 label、description、enabled 和 autoApprove。enabled 默认 true,autoApprove 默认 false。Desktop 和 server embed 可以在运行前缓存用户选择并传入覆盖值;runtime 只接受已注册工具的 enabled/autoApprove 覆盖,未知工具会被忽略。
workflow 代码可以使用 workflow.createToolApprovalGate() 包装工具函数。工具未注册时会以 input_validation 失败;工具被禁用时会以 tool_permission_denied 拒绝;工具启用但未自动批准时,会复用 workflow.createUserInputNode(...) 的等待/恢复协议生成 tool-approval 请求,批准后继续同一个 run,拒绝后返回 tool_permission_denied。
示例
export const executor = workflow.defineExecutor<Input, Output>({
projectType: "workflow",
defaultEntrypoint: "main",
entrypoints: [
workflow.defineEntrypoint<Input, Output>({
id: "main",
title: "完整流程",
workflow: exampleWorkflow,
tokenUsage: { enabled: true, display: true },
tools: [
{
name: "search",
label: "Search",
description: "调用外部搜索工具。",
enabled: true,
autoApprove: false,
},
],
params: [
{
name: "message",
flag: "--message",
type: "string",
required: true,
control: "textarea",
description: "输入文本。",
},
],
createInput({ args }) {
return { message: readMessage(args) };
},
}),
],
});
注意事项
| 规则 | 说明 |
|---|---|
createInput 是必填 | 每个 Workflow entrypoint 和 Conversation executor 都必须声明。 |
paramValues 是正式结构化值 | createInput 可直接读取参数值;args 继续用于需要完整 CLI 参数或未知参数的场景。 |
| Workflow 运行配置属于 entrypoint | 顶层旧字段会给出迁移错误,不再保留兼容别名。 |
tools 必须静态声明 | Desktop/server embed 只能展示所选入口或 Conversation executor 静态分析到的工具;工具名必须稳定。 |
tokenUsage.enabled 默认关闭 | 未配置时不会写 KV,也不会展示用量。关闭时 context.tokenUsage.report() 是 no-op。 |
registry 用于单节点调试 | Workflow 只搜索所选入口的 registry;没有注册的节点不能通过 debug API 单独运行。 |