From f93f473a0ea898785921e3c4fb16114549fd2783 Mon Sep 17 00:00:00 2001 From: weichen Date: Thu, 1 Oct 2026 15:55:52 +0000 Subject: [PATCH] Add script steps and critic fields to workflow definitions Why: The workflow executor needs a shared contract for subagent profiles, plugin scripts, and per-step plus whole-run retry caps. Evidence: go test ./... passed, including the new workflow_def golden. Impact: New fields are omitempty, so existing workflow JSON still decodes. Kind script is additive. Refs: feature/agent-harness-p4 Co-authored-by: Cursor --- conformance_test.go | 20 +++++++++ plugin.go | 13 +++++- testdata/golden/workflow_def.json | 69 +++++++++++++++++++++++++++++++ ts/types.ts | 30 ++++++++++++++ 4 files changed, 131 insertions(+), 1 deletion(-) create mode 100644 testdata/golden/workflow_def.json diff --git a/conformance_test.go b/conformance_test.go index 27c3cce..031a03f 100644 --- a/conformance_test.go +++ b/conformance_test.go @@ -207,6 +207,26 @@ func TestGoldenIngest(t *testing.T) { }) } +func TestGoldenWorkflowDef(t *testing.T) { + golden(t, "workflow_def", WorkflowDef{ + ID: "demo.brief", Title: "简报", Description: "按主题写一份简报", + IntentID: "demo.brief", MaxRetries: 6, + Steps: []WorkflowStep{ + {ID: "length", Title: "篇幅", Kind: "hitl", Hitl: &HITLCardTemplate{ + Question: "篇幅?", Kind: HITLKindChoice, AllowFreeInput: false, + Choices: []HITLChoice{{ID: "short", Label: "短", Description: "一页", Impact: "更快"}}, + }}, + {ID: "draft", Title: "起草", Kind: "prompt", Agent: "research", + Prompt: "写一份关于 {{slots.topic}} 的简报。用户说:{{input}}。上一步:{{steps.length.output}}", + Critic: &StepCritic{Criteria: "必须点明主题", MaxRetries: 1}}, + {ID: "lookup", Title: "查时间", Kind: "tool", Tool: "time_now", Args: json.RawMessage(`{"tz":"UTC"}`)}, + {ID: "save", Title: "存稿", Kind: "artifact", Artifact: "brief.md", Prompt: "{{steps.draft.output}}"}, + {ID: "note", Title: "通知", Kind: "card", Card: &ActivityCardTemplate{Kind: CardKindResult, Title: "简报已写好"}}, + {ID: "run", Title: "脚本", Kind: "script", Script: "scripts/demo.py"}, + }, + }) +} + func TestGoldenPlugin(t *testing.T) { golden(t, "plugin_manifest", PluginManifest{ ID: "example", Name: "示例插件", Version: "0.1.0", APIVersion: PluginAPIVersion, diff --git a/plugin.go b/plugin.go index 708deb7..3c920c8 100644 --- a/plugin.go +++ b/plugin.go @@ -52,24 +52,35 @@ type PluginPanel struct { } // WorkflowDef:工作流即数据(4.6)。Step 可产 HITL 卡片、调工具、产出工件。 +// 模板占位符:{{input}}、{{slots.}}、{{steps..output}}。 type WorkflowDef struct { ID string `json:"id"` Title string `json:"title"` Description string `json:"description,omitempty"` IntentID string `json:"intentId,omitempty"` // 绑定意图(命中即启动) + MaxRetries int `json:"maxRetries,omitempty"` // 整条工作流的 critic 重试上限 Steps []WorkflowStep `json:"steps"` } +// StepCritic:单步验收。无法解析的判定按不通过处理。 +type StepCritic struct { + Criteria string `json:"criteria"` + MaxRetries int `json:"maxRetries,omitempty"` // 本步重试上限;0 表示缺省 1 次 +} + type WorkflowStep struct { ID string `json:"id"` Title string `json:"title"` - Kind string `json:"kind"` // prompt | tool | hitl | artifact | card + Kind string `json:"kind"` // prompt | tool | hitl | artifact | card | script Prompt string `json:"prompt,omitempty"` // prompt: 发给模型的指令模板 + Agent string `json:"agent,omitempty"` // prompt: 子代理 profile,缺省 general Tool string `json:"tool,omitempty"` // tool: 工具名 Args json.RawMessage `json:"args,omitempty"` // tool: 参数模板 Hitl *HITLCardTemplate `json:"hitl,omitempty"` // hitl: 卡片模板 Artifact string `json:"artifact,omitempty"` // artifact: 产出工件名模板 Card *ActivityCardTemplate `json:"card,omitempty"` // card: 产收件箱任务卡 + Script string `json:"script,omitempty"` // script: 插件目录内的相对路径 + Critic *StepCritic `json:"critic,omitempty"` } // ActivityCardTemplate:工作流步骤产卡模板(kind=card)——步骤执行时生成 diff --git a/testdata/golden/workflow_def.json b/testdata/golden/workflow_def.json new file mode 100644 index 0000000..755a5df --- /dev/null +++ b/testdata/golden/workflow_def.json @@ -0,0 +1,69 @@ +{ + "id": "demo.brief", + "title": "简报", + "description": "按主题写一份简报", + "intentId": "demo.brief", + "maxRetries": 6, + "steps": [ + { + "id": "length", + "title": "篇幅", + "kind": "hitl", + "hitl": { + "question": "篇幅?", + "kind": "choice", + "choices": [ + { + "id": "short", + "label": "短", + "description": "一页", + "impact": "更快" + } + ], + "allowFreeInput": false + } + }, + { + "id": "draft", + "title": "起草", + "kind": "prompt", + "prompt": "写一份关于 {{slots.topic}} 的简报。用户说:{{input}}。上一步:{{steps.length.output}}", + "agent": "research", + "critic": { + "criteria": "必须点明主题", + "maxRetries": 1 + } + }, + { + "id": "lookup", + "title": "查时间", + "kind": "tool", + "tool": "time_now", + "args": { + "tz": "UTC" + } + }, + { + "id": "save", + "title": "存稿", + "kind": "artifact", + "prompt": "{{steps.draft.output}}", + "artifact": "brief.md" + }, + { + "id": "note", + "title": "通知", + "kind": "card", + "card": { + "kind": "result", + "title": "简报已写好" + } + }, + { + "id": "run", + "title": "脚本", + "kind": "script", + "script": "scripts/demo.py" + } + ] +} diff --git a/ts/types.ts b/ts/types.ts index 50c83d7..ef43c74 100644 --- a/ts/types.ts +++ b/ts/types.ts @@ -261,6 +261,36 @@ export interface PluginPanel { id: string; title: string; icon?: string; body: UINode[]; refreshTool?: string; } +/** 模板占位符:{{input}}、{{slots.}}、{{steps..output}}。 */ +export interface WorkflowDef { + id: string; title: string; description?: string; intentId?: string; + /** 整条工作流的 critic 重试上限。 */ + maxRetries?: number; + steps: WorkflowStep[]; +} + +export interface StepCritic { + criteria: string; + /** 本步重试上限;缺省 1 次。 */ + maxRetries?: number; +} + +export interface WorkflowStep { + id: string; title: string; + kind: 'prompt' | 'tool' | 'hitl' | 'artifact' | 'card' | 'script'; + prompt?: string; + /** prompt 步骤的子代理 profile,缺省 general。 */ + agent?: string; + tool?: string; + args?: unknown; + hitl?: { question: string; kind: HITLKind; choices?: HITLChoice[]; allowFreeInput: boolean; freeInputHint?: string }; + artifact?: string; + card?: { kind: CardKind; title: string; tone?: string; body?: UINode[]; actions?: { id: string; label: string; style?: 'primary' | 'default' | 'danger'; command?: string }[] }; + /** script 步骤在插件目录内的相对路径。 */ + script?: string; + critic?: StepCritic; +} + export interface PluginWorkspace { id: string; title: string; icon?: string; shortcut?: string; badgeTool?: string; body: UINode[]; refreshTool?: string; -- 2.54.0