createUserInputNode

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

签名

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

options

字段类型说明
namestring节点名,默认 "user-input"。会用于 report、requestId 和 Diagram。
titlestring等待表单标题。未传时使用 name
descriptionstring等待表单说明。
paramsWorkflowParamDefinition[]表单字段,字段定义与 executor.params 一致。
defaultValuesRecord<string, unknown>表单默认值,按 param name 匹配。
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
提交答案App、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。

示例

interface ApprovalPayload extends WorkflowPayload {
  approved: boolean;
  note: 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" },
  ],
  defaultValues: { approved: false },
  format(payload) {
    return {
      errCode: 0,
      errMessage: "",
      approved: payload.values.approved === true,
      note: typeof payload.values.note === "string" ? payload.values.note : "",
    };
  },
});

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);
  },
});