跳到主要内容

createUserInputNode

workflow.createUserInputNode() 创建一个人机输入节点。节点执行时会先检查当前 run 是否已经有对应 requestId 的提交答案;如果没有,会向 runtime 发布 user_input_requested 事件,把 run 保存为 waiting_for_input,并在 Desktop、server embed 或 API 中展示等待表单。

表单字段复用当前 Workflow entrypoint(Conversation 则为 executor)的 WorkflowParamDefinition,不要为用户输入节点另建一套 schema。

等待表单同样支持 datetimedatetimedate-range。提交值分别保持 YYYY-MM-DDHH:mm:ss、本地 YYYY-MM-DDTHH:mm:ss 和闭区间 YYYY-MM-DD/YYYY-MM-DD 字符串,因此 workflow 可以直接从 payload.values[param.name] 读取,不会收到 Date 对象或经过时区转换的值。日期字段必须使用 type: "string",只能放在 main 或 auxiliary,不能声明枚举选项或 multiple: true;必填空值或格式错误会保留在表单中显示错误,并阻止提交直至用户修正。

签名

workflow.createUserInputNode<Input, Output extends WorkflowPayload>(
options: UserInputNodeOptions<Input, Output>,
): BaseNode<Input, Output>

options

字段类型说明
namestring节点名,默认 "user-input"。会用于 report、requestId 和 Diagram。
titlestring等待表单标题。未传时使用 name
descriptionstring等待表单说明。
paramsWorkflowParamDefinition[]表单字段,字段定义与所选 Workflow 入口的 params 一致;Conversation 使用 executor.params
defaultValuesRecord<string, unknown>表单默认值,按 param name 匹配。
resolveRequest(input, context) => UserInputRequestDefinition | Promise<UserInputRequestDefinition>按本次节点输入动态生成 titledescriptionparamsdefaultValues
createRequestId(input, context) => string自定义请求 ID。默认包含 workflow、runId 和 node name。
format(payload, input, context) => Output把默认提交 payload 转为业务 payload。
historyLimitnumber节点执行历史保留条数,默认 50
metadataWorkflowNodeMetadataInput节点展示元信息。

默认 payload

interface UserInputPayload extends WorkflowPayload {
requestId: string;
values: Record<string, unknown>;
submittedAt: string;
}

成功恢复时默认返回:

{
errCode: 0,
errMessage: "",
requestId,
values,
submittedAt
}

如果传入 format,返回值仍必须继承 WorkflowPayload,成功时使用 errCode: 0, errMessage: ""

Runtime 行为

阶段行为
首次执行没有提交值时发布 user_input_requested,报告 pendingUserInput,run 状态变为 waiting_for_input
提交答案Desktop、server 或 embed 提交 { requestId, values }
恢复执行runtime 用同一个 runId 注入 resolvedUserInputs,节点发布 user_input_resolved 并返回 payload。
非交互 CLICLI 不读取 stdin;需要使用 workflow-code json --run-id ... --resolved-user-inputs-json ... 恢复。

waiting_for_input 是非终态,但也是持久状态。server 重启后仍可从 run record 读取 pendingUserInput 并接受提交;stale running 逻辑不会把它误判成 timeout。

Workflow 恢复必须继续使用 run 记录中的精确 resolvedTarget 和原 entrypointId。恢复请求不能切换入口;如果等待期间 draft 删除了原入口,Server 返回 409,不会改用新的默认入口。旧运行记录没有入口字段时,才按该精确目标的默认入口兼容恢复。Conversation 保持单入口,同样不接受入口参数。

当问题字段由外部 agent、远程审批系统或当前节点输入动态产生时,可以保留静态 params 作为结构分析的默认定义,并使用 resolveRequest 生成本次等待表单。resolver 可以异步执行,但必须对相同输入保持确定性且不产生副作用;恢复同一个 requestId 时,返回的字段名和选项值必须保持稳定。

const questionNode = workflow.createUserInputNode<AgentQuestion>({
name: "agent-question",
params: [],
resolveRequest(question) {
return {
title: question.header,
description: question.prompt,
params: [{
name: "answer",
type: "string",
control: question.multiple ? "checkboxes" : "radio",
multiple: question.multiple,
required: true,
options: question.options,
}],
};
},
createRequestId(question) {
return `agent-question:${question.id}`;
},
});

示例

interface ApprovalPayload extends WorkflowPayload {
approved: boolean;
note: string;
reviewDate: string;
}

const approvalNode = workflow.createUserInputNode<Plan, ApprovalPayload>({
name: "approval",
title: "Review deployment",
description: "Confirm whether the workflow should continue.",
params: [
{ name: "approved", flag: "--approved", type: "boolean" },
{ name: "note", flag: "--note", type: "string", control: "textarea" },
{ name: "reviewDate", flag: "--review-date", type: "string", control: "date", required: true },
],
defaultValues: { approved: false, reviewDate: "2026-07-26" },
format(payload) {
return {
errCode: 0,
errMessage: "",
approved: payload.values.approved === true,
note: typeof payload.values.note === "string" ? payload.values.note : "",
reviewDate: typeof payload.values.reviewDate === "string" ? payload.values.reviewDate : "",
};
},
});

export const deployWorkflow = workflow.defineWorkflow<Input, OutputPayload>({
name: "deploy",
async run(input, context) {
const plan = await workflow.runNode(planNode, input, context);
const approval = await workflow.runNode(approvalNode, plan, context);
if (!approval.approved) {
return workflow.createOutputPayload({
items: [{ title: "Stopped", content: approval.note, contentType: "text" }],
});
}
return workflow.runNode(deployNode, plan, context);
},
});