CC Workflow Studio Schema-Driven 属性面板迁移实战:Phase 1 中 Prompt / Branch Session / Codex / Group 节点面板的重构剖析
CC Workflow Studio Schema-Driven 属性面板迁移实战:Phase 1 中 Prompt / Branch Session / Codex / Group 节点面板的重构剖析
CC Workflow Studio 正在通过多阶段的 changeset 迭代,将各节点类型的手写属性面板逐步迁移到一套以核心 zod schema(PropertySchema)为单一事实来源(single source of truth)的 schema-driven 渲染模型上。本文以 .changeset/schema-driven-property-panel-phase1.md 记录的 Phase 1 为核心,深入讲解 Prompt、Branch Session、Codex、Group 四类节点的属性面板如何迁移到 core 包内的 zod schema 与注册表条目,并剖析渲染器新增的 i18n-convention 选项标签与分节默认展开能力,最后展示 PromptNodeData / BranchSessionNodeData 数据接口在 core 中的去重合并。读完本文,你将掌握该 schema 模型的声明语法、渲染器的工作机制,以及为任意新节点类型接入这套模型所需遵循的完整操作步骤。
一、Phase 1 变更速览:从 changeset 到源码
Phase 1 的变更记录(.changeset/schema-driven-property-panel-phase1.md)是标准的 changesets 格式,头部声明了两个包的版本影响:
---
'@cc-wf-studio/core': minor
'cc-wf-studio': patch
---
正文摘要将其拆解为三块核心工作:
- 将 Prompt、Branch Session、Codex、Group 四类节点的属性面板迁移到 core 的 zod schema(对应
prompt/branch-session/codex/group四个 schema 文件及其注册表条目); - 为渲染器新增 i18n-convention 选项标签(option label 通过
<labelKey>.option.<value>约定解析)与 分节默认展开(sectionDefaultOpen)能力; - 将
PromptNodeData/BranchSessionNodeData从 webview 侧去重合并到 core 类型定义中。
三者分别对应仓库中的三类文件:core 的 schema 定义、webview 的通用渲染器与面板配置、以及 core 的 workflow 类型。下面逐一展开。
二、Schema 模型地基:FieldMeta、PropertySchema 与注册表
要理解 Phase 1,必须先掌握 Phase 0 打下的基础(见 .changeset/schema-driven-property-panel-phase0.md):core 包中定义了一套 zod 属性字段原语,位于 packages/core/src/schema/field.ts。
每个字段由 field() 辅助函数声明,把 zod 运行时校验与领域元数据(FieldMeta)合并在一个 PropertyField 对象中:
export interface PropertyField<T extends z.ZodTypeAny = z.ZodTypeAny> {
zod: T;
meta: FieldMeta;
}
export function field<T extends z.ZodTypeAny>(zod: T, meta: FieldMeta): PropertyField<T> {
return { zod, meta };
}
FieldMeta 是渲染与导出共享的元数据载体,Phase 1 用到的关键成员包括:
| 元数据成员 | 类型 | 作用 |
|---|---|---|
targets |
FieldTargets |
该字段适用的导出目标('all' 或显式目标列表),决定 UI 显示范围与导出忽略告警 |
labelKey |
string |
字段标签的 i18n key,由 webview 解析 |
control |
FieldControl |
UI 控件类型:text / textarea / select / checkbox / radio / color / tools / objectArray;undefined 表示仅数据字段(校验与目标作用域生效,但不由通用面板渲染) |
options |
readonly string[] |
select/radio 的可选值,单一来源供 UI 使用 |
helpKey / placeholderKey |
string |
控件下方的帮助行与占位文案 key |
sectionKey |
string |
将字段归入某个 CollapsibleSection 分组 |
visibleWhen |
谓词函数 | 当前节点数据不满足条件时隐藏字段 |
readonlyWhen |
谓词函数 | 满足条件时字段只读(如内置子代理预设控制 model/tools) |
editInEditor |
boolean |
textarea 控件配套"在外部编辑器中编辑"按钮 |
allowCustom |
boolean |
select 控件追加"自定义"选项并切换为自由文本输入 |
itemFields / itemReadonlyWhen |
objectArray 专用 |
数组项行内的列定义与只读判定 |
FieldMeta 的范围护栏(scope guardrails)非常明确:字段间的副作用(如 outputPorts 同步)交给节点 schema 旁导出的纯函数 derive*Update;布局与样式提示靠声明顺序;对话框与导航交给 webview 面板配置中的 Header/Footer 插槽;跨字段校验用 zod 的 refine。
节点的完整属性 schema 是一个按字段名键控的 PropertySchema(Record<string, PropertyField>),全部节点 schema 汇总到注册表 packages/core/src/schema/node-schema-registry.ts 的 NODE_PROPERTY_SCHEMAS 中。Phase 1 落地后,该注册表已包含全部 12 类可编辑节点:
export const NODE_PROPERTY_SCHEMAS: Partial<Record<NodeType, PropertySchema>> = {
[NodeType.SubAgent]: subAgentPropertySchema,
[NodeType.Prompt]: promptPropertySchema, // ← Phase 1
[NodeType.BranchSession]: branchSessionPropertySchema, // ← Phase 1
[NodeType.Codex]: codexPropertySchema, // ← Phase 1
[NodeType.Group]: groupPropertySchema, // ← Phase 1
[NodeType.AskUserQuestion]: askUserQuestionPropertySchema,
[NodeType.Branch]: branchPropertySchema,
[NodeType.IfElse]: ifElsePropertySchema,
[NodeType.Switch]: switchPropertySchema,
[NodeType.Skill]: skillPropertySchema,
[NodeType.Mcp]: mcpPropertySchema,
[NodeType.SubAgentFlow]: subAgentFlowPropertySchema,
};
未进入注册表的 Start/End 节点(无可编辑字段)继续保留非 schema 渲染。注册表注释明确了它的三位消费者:webview 属性面板(渲染)、导出告警推导(warnings.ts)、工作流校验器(validate-workflow.ts),三者读取的是同一份定义,从机制上杜绝了 UI 与校验/告警之间的漂移。
三、Phase 1 的四个新节点 schema 逐一拆解
3.1 Prompt 节点:promptPropertySchema
源码位于 packages/core/src/schema/nodes/prompt-schema.ts,镜像 PromptNodeData(见 packages/core/src/types/workflow-definition.ts):
export const promptPropertySchema = {
label: field(z.string().optional(), {
targets: 'all',
labelKey: 'prompt.field.label',
control: 'text',
placeholderKey: 'prompt.field.label.placeholder',
}),
prompt: field(z.string(), {
targets: 'all',
labelKey: 'prompt.field.prompt',
control: 'textarea',
editInEditor: true,
placeholderKey: 'prompt.field.prompt.placeholder',
helpKey: 'prompt.field.prompt.help',
}),
variables: field(z.record(z.string(), z.string()).optional(), {
targets: 'all',
labelKey: 'prompt.field.variables',
}),
} satisfies Record<string, PropertyField>;
值得注意的设计点是 variables:它是运行时填充的映射,刻意不声明 control,因此属于"仅数据字段"——通用面板不渲染它,webview 的 Prompt 面板改为在 Footer 插槽中根据 prompt 文本实时提取 {{variableName}} 并展示"检测到 N 个变量"(见 prompt-panel.tsx 的 DetectedVariables 组件)。这样把"数据校验/目标作用域"(schema 负责)与"展示方式"(webview 插槽负责)彻底解耦。
3.2 Branch Session 节点:branchSessionPropertySchema
源码位于 packages/core/src/schema/nodes/branch-session-schema.ts,镜像 BranchSessionNodeData:
export const branchSessionPropertySchema = {
label: field(z.string(), {
targets: 'all',
labelKey: 'branchSession.field.label',
control: 'text',
placeholderKey: 'branchSession.field.label.placeholder',
}),
workDescription: field(z.string().optional(), {
targets: 'all',
labelKey: 'branchSession.field.workDescription',
control: 'textarea',
editInEditor: true,
placeholderKey: 'branchSession.field.workDescription.placeholder',
helpKey: 'branchSession.field.workDescription.help',
}),
} satisfies Record<string, PropertyField>;
Branch Session 是 Claude Code 独占的"人机协作检查点"节点类型(见 packages/core/src/schema/claude-code-only.ts 的 CLAUDE_CODE_ONLY_NODE_TYPES),节点级目标是 claudeCode,因此字段级 targets 一律为 'all'(在该节点类型内部不再细分)。其面板配置(branch-session-panel.tsx)在 Footer 插槽中静态展示 "Claude Code only" 提示条,提醒用户该节点无法导出到其他运行时。
3.3 Codex 节点:codexPropertySchema
源码位于 packages/core/src/schema/nodes/codex-schema.ts,是 Phase 1 中字段最多、也最能体现"schema 表达边界"的 schema。它镜像 CodexNodeData(workflow-definition.ts),并导出两组预定义常量供 UI 与校验共用:
export const CODEX_PREDEFINED_MODELS = [
'gpt-5.2-codex',
'gpt-5.2',
'gpt-5.1-codex-max',
'gpt-5.1-codex-mini',
] as const;
export const CODEX_SANDBOX_MODES = ['read-only', 'workspace-write', 'danger-full-access'] as const;
export const codexPropertySchema = {
label: field(z.string().optional(), {
targets: 'all',
labelKey: 'codex.field.label',
control: 'text',
helpKey: 'codex.field.label.help',
}),
promptMode: field(z.enum(['fixed', 'ai-generated']), {
targets: 'all',
labelKey: 'codex.field.promptMode',
control: 'radio',
options: ['fixed', 'ai-generated'],
}),
prompt: field(z.string(), {
targets: 'all',
labelKey: 'codex.field.prompt',
control: 'textarea',
editInEditor: true,
placeholderKey: 'codex.field.prompt.placeholder',
}),
model: field(z.string(), {
targets: 'all',
labelKey: 'codex.field.model',
control: 'select',
options: CODEX_PREDEFINED_MODELS,
allowCustom: true,
}),
reasoningEffort: field(z.enum(['low', 'medium', 'high']), {
targets: 'all',
labelKey: 'codex.field.reasoningEffort',
control: 'select',
options: ['low', 'medium', 'high'],
}),
skipGitRepoCheck: field(z.boolean().optional(), {
targets: 'all',
labelKey: 'codex.field.skipGitRepoCheck',
control: 'checkbox',
}),
sandbox: field(z.enum(CODEX_SANDBOX_MODES).optional(), {
targets: 'all',
labelKey: 'codex.field.sandbox',
sectionKey: 'advanced',
}),
} satisfies Record<string, PropertyField>;
这里有几个典型细节:
label在 schema 中可选、接口中必填:面板在标签被清空时会置为undefined,画布则回退显示节点 id(保留的旧行为)。schema 的可选性可以比接口更宽松,这正是 drift guard 注释所允许的;sandbox是数据驱动 UI 的范例:schema 中仅声明为数据字段(无control),其"启用复选框 + 模式下拉"的组合控件由 webview 的customControls注入(见下文第五节),并按sectionKey: 'advanced'归入"高级选项"分组;model的allowCustom: true:webview 的SelectControl会追加一个"自定义"选项,选中后切换为自由文本输入框,支持gpt-6.0-codex这类自定义模型名。
3.4 Group 节点:groupPropertySchema
源码位于 packages/core/src/schema/nodes/group-schema.ts。Group 是纯布局容器,label 是唯一可编辑属性;子节点列表/导航保留在 webview 的 Footer 插槽中:
export const groupPropertySchema = {
label: field(z.string(), {
targets: 'all',
labelKey: 'group.field.label',
control: 'text',
placeholderKey: 'group.field.label.placeholder',
}),
} satisfies Record<string, PropertyField>;
3.5 每个 schema 的编译期 drift guard
四个新 schema 文件末尾都导出了两个类型级守卫,这是 Phase 1 引入的防漂移机制(AssertAssignable 定义见 packages/core/src/schema/field.ts):
// 字段名守卫:schema 推断出的字段名必须是节点数据接口字段名的子集
export type PromptSchemaFieldNamesGuard = AssertAssignable<
keyof z.infer<typeof promptZodObject>,
keyof PromptNodeData
>;
// 值类型守卫:schema 推断出的对象形状必须可赋值给 Partial<PromptNodeData>
export type PromptSchemaValueTypesGuard = AssertAssignable<
z.infer<typeof promptZodObject>,
Partial<PromptNodeData>
>;
一旦 schema 的字段名或值类型与 PromptNodeData 等公共接口发生分歧(例如拼错了一个枚举值),tsc 会在编译期直接报错,而不是等到运行时才暴露——这是"声明一次、处处复用"模型的正确性基石。
四、渲染器能力升级:i18n-convention 选项标签与分节默认展开
Phase 1 changeset 明确提及"add i18n-convention-based option labels and per-section default-open to the renderer",这两项能力都落在 webview 的通用渲染器 SchemaPropertyPanel.tsx 及配套组件中。
4.1 i18n-convention 选项标签
SelectControl(SelectControl.tsx)按约定解析选项文案:若存在 <labelKey>.option.<value> 这条翻译,则使用它;否则原样显示产品术语(如模型名):
const optionLabel = (opt: string) => stOptional(`${meta.labelKey}.option.${opt}`) ?? opt;
这在简体中文翻译文件中(zh-CN.ts)有完整对应,例如:
'codex.field.reasoningEffort.option.low': '低',
'codex.field.reasoningEffort.option.medium': '中',
'codex.field.reasoningEffort.option.high': '高',
这意味着 schema 只需声明 options: <a href="https://link.gitcode.com/i/b850bcbe9f47c62dbc4397dd22aa122e" target="_blank">'low', 'medium', 'high'],五种语言的 webview 翻译文件各维护一条 option 文案即可,无需在 schema 中硬编码任何显示字符串。i18n key 的解析通过 [schema-i18n.ts 的 useSchemaTranslation 包装实现:必填 key 缺失时回退到原始 key(便于开发期暴露),可选 key 缺失时返回 undefined 以跳过渲染。
4.2 分节默认展开(sectionDefaultOpen)
sandbox 字段归入 advanced 分组后,面板需要一个"智能默认":没有设置 sandbox 时收起高级选项,设置了才展开。NodePanelConfig(types.ts)新增 sectionDefaultOpen,Codex 面板配置(codex-panel.tsx)实现为:
export const codexPanelConfig: NodePanelConfig = {
schema: NODE_PROPERTY_SCHEMAS[NodeType.Codex]!,
mode: 'edit',
i18nNamespace: 'codex',
customControls: { label: ..., promptMode: ..., skipGitRepoCheck: ..., sandbox: ... },
sectionDefaultOpen: {
advanced: (data) => !!data.sandbox,
},
};
SchemaPropertyPanel 渲染分组时按 sectionDefaultOpen?.[sectionKey]?.(data) ?? true 计算初始展开状态(默认展开),并以 node.id + sectionKey 作为 CollapsibleSection 的 key,保证切换节点时组件重挂载、重新计算默认展开态:
<CollapsibleSection
key={`${node.id}-${item.sectionKey}`}
title={st(`${i18nNamespace}.section.${item.sectionKey}`)}
hint={stOptional(`${i18nNamespace}.section.${item.sectionKey}.hint`)}
defaultOpen={sectionDefaultOpen?.[item.sectionKey]?.(data) ?? true}
>
分组逻辑(buildRenderItems)按 schema 声明顺序遍历字段:共享同一 sectionKey 的字段在 key 首次出现的位置聚合成一个折叠区,未分组字段保持顶层平铺。分节标题解析为 <i18nNamespace>.section.<sectionKey>(可选 .hint 提示),例如 'codex.section.advanced': '高级选项'。
五、webview 侧接线:面板配置注册表与插槽机制
Phase 1 的四个面板配置统一注册在 node-panels.ts 的 NODE_PANELS 映射中(该文件就是"节点类型 → schema 面板配置"的 webview 侧注册表;尚未迁移的节点保留旧 PropertyOverlay)。迁移完成的节点由 SchemaPropertyPanel 统一渲染,其工作流程如下(SchemaPropertyPanel.tsx):
- 从配置取
schema,按声明顺序构建渲染项(顶层字段 + 分组); - 每个字段先过滤数据字段(无
control且无自定义控件则跳过)与visibleWhen不满足的字段; - 渲染
Header插槽 → 字段区 →Footer插槽; - 每次字段变更构造 patch,先经节点可选的
derive纯函数归一化(如 outputPorts 同步),再调用updateNodeData持久化。
const handleChange = (fieldName: string, value: unknown) => {
const patch: Record<string, unknown> = { [fieldName]: value };
updateNodeData(node.id, derive ? derive(data, patch) : patch);
};
NodePanelConfig(types.ts)的完整契约如下:
| 配置项 | 说明 |
|---|---|
schema |
core 注册表中的该节点属性 schema |
derive |
可选纯归一化函数,每个字段 patch 持久化前执行 |
mode |
'edit'(渲染交互控件)或 'readonly'(全部只读展示,如 Skill/MCP/SubAgent 摘要面板) |
i18nNamespace |
分节标题前缀 <ns>.section.<sectionKey>(+ .hint) |
Header / Footer |
字段区上方/下方的插槽组件(徽标、编辑对话框、导航等) |
customControls |
按字段名键控的组件覆写:无 control 的字段整体替换渲染;有 control 的字段仅替换输入控件 |
sectionDefaultOpen |
每个 sectionKey 的初始展开谓词,默认展开 |
customControls 与 schema 的组合是这套模型的"逃生舱":schema 表达不了的行为(复合控件、条件帮助文案、失焦清空等)用普通 React 注入,而字段是否可见仍由 visibleWhen 决定——自定义控件只改变渲染方式,不改变可见性。
5.1 Codex 面板的四个自定义控件
codex-panel.tsx 注入的四个控件正是 Phase 1 的"边界案例"教科书:
CodexLabelControl:输入框失焦且内容为空时onChange(undefined),让画布回退到节点 id;CodexPromptModeControl:RadioControl外包一层,当值为ai-generated时追加条件帮助文案;CodexSkipGitControl:等宽字体的--skip-git-repo-check标记 + 红色警告文案;CodexSandboxControl:启用复选框 +CODEX_SANDBOX_MODES模式下拉的复合控件(对应advanced分组,分组默认收起、设置 sandbox 后才展开)。
5.2 Prompt 面板的变量检测 Footer
prompt-panel.tsx 在 Footer 插槽中渲染 DetectedVariables:用 extractVariables 从 prompt 文本中解析 {{variableName}},以等宽字体列表展示并提示"这些变量将在执行时被替换"。这一设计呼应了 schema 中 variables 作为数据字段(无 control)的定位——检测逻辑与 schema 解耦,可以独立演进。
六、数据接口去重:PromptNodeData 与 BranchSessionNodeData 移入 core
changeset 的最后一项工作是"de-duplicate PromptNodeData/BranchSessionNodeData into core"。在 packages/core/src/types/workflow-definition.ts 中,两个接口目前定义如下:
export interface PromptNodeData {
label?: string;
prompt: string;
variables?: Record<string, string>;
}
export interface BranchSessionNodeData {
/** Display label for the branch-session checkpoint */
label: string;
/** Optional scope of the joint human+AI work, injected into generated instructions */
workDescription?: string;
/** Number of output ports (fixed at 1) */
outputPorts: 1;
}
去重的意义在于:core 包统一导出这些类型(见 packages/core/src/index.ts 的 export * from './types/workflow-definition.js'),webview 与 CLI 都从同一处导入,配合第三节的编译期 drift guard,schema、类型接口、渲染面板三者的字段集合被约束为始终一致。此后任何"新增一个 Prompt 节点字段"的改动都变成标准流程:改 schema → 改接口 → 补五个 webview 翻译文件的 label/placeholder/help → (如需要 AI 生成)更新 resources/workflow-schema.json。
七、从 Phase 1 看全貌:四个阶段的完整迁移轨迹
Phase 1 并非孤立变更,它是 .changeset 目录下 schema-driven-property-panel 系列四阶段之一:
| 阶段 | changeset 文件 | 主要内容 |
|---|---|---|
| Phase 0 | schema-driven-property-panel-phase0.md | 扩展 FieldMeta(sections、visibleWhen/readonlyWhen、help/placeholder、edit-in-editor、custom select),建立节点属性 schema 注册表,用通用 SchemaPropertyPanel 渲染 SubAgent 面板 |
| Phase 1(本文) | schema-driven-property-panel-phase1.md | 迁移 Prompt / Branch Session / Codex / Group 四类面板;新增 i18n-convention 选项标签与分节默认展开;PromptNodeData / BranchSessionNodeData 去重入 core |
| Phase 2 | schema-driven-property-panel-phase2.md | 迁移 AskUserQuestion / Branch / IfElse / Switch;引入纯 derive*Update 函数做 outputPorts 同步;新增 objectArray 控件(边界从 zod 读取);数组项 ID 回填改为加载时归一化(ensureNodeDataItemIds) |
| Phase 3 | schema-driven-property-panel-phase3.md | 迁移 Skill / MCP / Sub-Agent Flow(只读摘要保留编辑对话框为插槽);导出忽略字段告警推广为注册表驱动的 collectIgnoredFieldWarnings |
| Phase 4 | schema-driven-property-panel-phase4.md | 将注册表驱动的 zod 校验接入 validateAIGeneratedWorkflow(仅校验已存在的字段,visibleWhen 未激活字段跳过);移除被取代的 7 个手写枚举检查;为全部节点 schema 增加编译期 drift guard |
八、注册表驱动的三驾马车:渲染、告警与校验
值得强调的是,schema 注册表不仅是 Phase 1 面板的渲染依据,它同时驱动着另外两条链路,这正是"单一事实来源"的完整含义:
8.1 导出忽略字段告警
packages/core/src/schema/warnings.ts 的 collectIgnoredFieldWarnings 遍历工作流每个节点,用 queries.ts 的 getIgnoredFields 找出"已设置但不适用于当前导出目标"的字段并生成可读告警:
Node "code-reviewer" (codex): field "model" (=gpt-5.2-codex) is ignored when exporting to ADK.
告警消息完全由 schema 的 targets 推导,因此 UI 隐藏的字段与导出器丢弃的字段永远一致(旧 API collectIgnoredSubAgentWarnings 已标记 @deprecated,保留仅为兼容)。queries.ts 还提供 getFieldsForTarget、clearFieldsNotAppliedToTarget(切换导出目标时清空不再适用的 Claude Code 专属字段)等查询辅助。
8.2 AI 生成工作流的运行时校验
Phase 4 把同一注册表接入了 packages/core/src/utils/validate-workflow.ts 的 validateNodeSchemaFields:对每个已注册 schema 的节点,逐个校验存在值的字段(value === undefined 跳过),visibleWhen 谓词不成立的字段视为未激活直接跳过,然后用字段 zod safeParse 校验。这取代了原先 7 个手写枚举检查,且天然规避了 AskUserQuestion 的 options 冲突:AI 建议模式下空数组合法,手动模式下才受 .min(2).max(4) 约束。
8.3 数组边界复用
packages/core/src/schema/field.ts 的 getArrayBounds 直接从 zod 类型的 checks 中读取 min_length / max_length / length_equals,让 UI 的 add/remove 边界与校验器声明的边界永不重复维护;length(n) 会得到 min === max === n,此时 UI 完全隐藏增删按钮。
九、实操指南:为节点类型接入 schema 面板的完整步骤
综合 Phase 1 及后续阶段的实践,为任意新节点类型接入这套模型的标准流程如下:
- 在 core 声明 schema:在
packages/core/src/schema/nodes/新建<type>-schema.ts,用field()声明每个属性(zod 类型 + FieldMeta),导出propertySchema与toZodObject派生的zodObject; - 添加编译期 drift guard:导出
SchemaFieldNamesGuard/SchemaValueTypesGuard两个AssertAssignable类型,确保 schema 与节点数据接口同步演进; - 注册:在 node-schema-registry.ts 的
NODE_PROPERTY_SCHEMAS中加入映射;如需 outputPorts 同步等副作用,同时注册纯函数到NODE_DERIVE_FNS; - 配置面板:在
packages/vscode/src/webview/src/components/property/panels/新建<type>-panel.tsx导出NodePanelConfig(引用 core 的 schema,按需配置 Header/Footer/customControls/sectionDefaultOpen),并登记到 node-panels.ts; - 补 i18n 文案:在 webview 五个语言文件(en.ts、ja.ts、ko.ts、zh-CN.ts、zh-TW.ts)中补充
<ns>.field.<name>标签与可选的 placeholder/help,以及<labelKey>.option.<value>选项文案、<ns>.section.<sectionKey>分节标题;extension 侧的 i18n 文件位于 packages/vscode/src/extension/i18n/translations/; - (可选)支持 AI 编写:如需 AI agent 也能生成该字段,同时在
resources/workflow-schema.json中补充描述(按设计该 AI 编写指南是手工维护的,不从 zod schema 自动生成); - 更新 changeset:按变更包类型(
@cc-wf-studio/core通常为minor)记录变更说明。
Phase 1 本身通过 @cc-wf-studio/core: minor(新增了四个 schema 的公共 API)与 cc-wf-studio: patch(webview 渲染器增强与类型去重,向后兼容)的版本声明,向我们展示了 changesets 在 monorepo 中对跨包重构的典型记录方式——这也是阅读 .changeset 目录时理解各阶段变更边界的正确姿势。
十、结语:为什么 schema 驱动是属性面板的终局形态
回顾 Phase 1 的全部落地代码,可以总结这套模型的三层价值:
- 单一事实来源:zod 校验、UI 渲染、导出告警、AI 工作流校验四者共享同一份
PropertySchema,字段级目标作用域(targets)让"UI 隐藏"与"导出丢弃"在机制上不可能脱节; - 声明式且可扩展:schema 表达不了的复合交互(Codex sandbox 组合控件、Prompt 变量检测、Skill/MCP 摘要对话框)通过 Header/Footer 插槽与
customControls逃生舱保留全部 React 灵活性; - 编译期与运行时双重保障:
AssertAssignabledrift guard 在tsc阶段拦截 schema/接口分歧,注册表驱动的safeParse校验在运行时兜底,配合ensureNodeDataItemIds的加载时归一化,共同保证了存量工作流文件与 AI 生成工作流的兼容性。
对想要贡献 CC Workflow Studio 的开发者来说,Phase 1 之后的新节点面板开发已经从"手写一整块 React 表单"简化为"声明一份 zod schema + 配置面板插槽",而 packages/core/src/schema/ 与 packages/vscode/src/webview/src/components/property/ 两个目录就是这套模型最完整的参考实现。