dsh-auto-flow 0.1.2 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1389 @@
1
+ import { z } from "zod";
2
+ //#region ../../shared/script-contract/src/types.d.ts
3
+ /** JSON 可序列化值。刻意不含 undefined —— 它建模的是能过 JSON 往返的值。 */
4
+ type JsonValue = string | number | boolean | null | JsonValue[] | {
5
+ [key: string]: JsonValue;
6
+ };
7
+ /** 脚本载荷:一个 JSON 对象,具体形状由各脚本的 payloadSchema 约束。 */
8
+ declare const ScriptPayloadSchema: z.ZodRecord<z.ZodString, z.ZodType<JsonValue, unknown, z.core.$ZodTypeInternals<JsonValue, unknown>>>;
9
+ type ScriptPayload = z.infer<typeof ScriptPayloadSchema>;
10
+ /** 显隐条件:判据键取到该值时才显示本字段。 */
11
+ /**
12
+ * 字段条件的运算符白名单(**真源在这里**,`workflow` 的 `FieldCondition` 直接用它)。
13
+ *
14
+ * 【为什么放在最底层】`workflow` 依赖 `script-contract`(产物规则的真源在这里),
15
+ * 所以共用词汇只能由下层定义、上层复用 —— 反向依赖会造出一个环。
16
+ * 与条件节点的 `CONDITION_OPERATORS` 同一套思路:宁可少几种写法,也不要一个能执行任意代码的入口。
17
+ */
18
+ declare const SCRIPT_FIELD_CONDITION_OPERATORS: readonly ["eq", "not", "gte", "lte", "gt", "lt", "startsWith", "endsWith", "includes", "regex", "exists"];
19
+ type ScriptFieldConditionOperator = (typeof SCRIPT_FIELD_CONDITION_OPERATORS)[number];
20
+ /**
21
+ * 产物装配规则:告诉工作流引擎「本脚本产出的文件路径放在返回数据的哪个键上」。
22
+ *
23
+ * 为什么需要它:工作流下游脚本节点的 payload 由「上游表单值 + 上游脚本返回值 + 上游产物文件」
24
+ * 三路合并而成(auto-flow 的 src/workflow/payload.ts),而产物文件注入哪个键由**下游脚本**声明。
25
+ * 不声明则工作流拿不到文件,只会拿到路径字符串。
26
+ */
27
+ declare const ScriptArtifactSpecSchema: z.ZodObject<{
28
+ filesKey: z.ZodOptional<z.ZodString>;
29
+ filesShape: z.ZodOptional<z.ZodEnum<{
30
+ paths: "paths";
31
+ "prompt-pairs": "prompt-pairs";
32
+ }>>;
33
+ filesPromptKey: z.ZodOptional<z.ZodString>;
34
+ }, z.core.$strip>;
35
+ type ScriptArtifactSpec = z.infer<typeof ScriptArtifactSpecSchema>;
36
+ /**
37
+ * 一次脚本运行的结果信封。
38
+ * 业务错误(脚本不存在 / 参数错误 / 执行失败)**一律落 `{ ok: false, error }`,不抛异常** ——
39
+ * 工作流脚本节点与模型工具都靠这个信封判成败。
40
+ */
41
+ declare const ScriptResultSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
42
+ ok: z.ZodLiteral<true>;
43
+ script: z.ZodString;
44
+ data: z.ZodType<JsonValue, unknown, z.core.$ZodTypeInternals<JsonValue, unknown>>;
45
+ meta: z.ZodObject<{
46
+ startedAt: z.ZodNumber;
47
+ finishedAt: z.ZodNumber;
48
+ durationMs: z.ZodNumber;
49
+ }, z.core.$strip>;
50
+ }, z.core.$strip>, z.ZodObject<{
51
+ ok: z.ZodLiteral<false>;
52
+ script: z.ZodString;
53
+ error: z.ZodObject<{
54
+ code: z.ZodString;
55
+ message: z.ZodString;
56
+ }, z.core.$strip>;
57
+ meta: z.ZodObject<{
58
+ startedAt: z.ZodNumber;
59
+ finishedAt: z.ZodNumber;
60
+ durationMs: z.ZodNumber;
61
+ }, z.core.$strip>;
62
+ }, z.core.$strip>], "ok">;
63
+ type ScriptResult = z.infer<typeof ScriptResultSchema>;
64
+ declare const ScriptRunOptionsSchema: z.ZodObject<{
65
+ baseDir: z.ZodOptional<z.ZodString>;
66
+ runId: z.ZodOptional<z.ZodString>;
67
+ browser: z.ZodOptional<z.ZodObject<{
68
+ headless: z.ZodOptional<z.ZodBoolean>;
69
+ channel: z.ZodOptional<z.ZodEnum<{
70
+ msedge: "msedge";
71
+ chrome: "chrome";
72
+ }>>;
73
+ }, z.core.$strip>>;
74
+ timeoutMs: z.ZodOptional<z.ZodNumber>;
75
+ }, z.core.$strip>;
76
+ type ScriptRunOptions = z.infer<typeof ScriptRunOptionsSchema>;
77
+ //#endregion
78
+ //#region ../../shared/workflow/src/types.d.ts
79
+ /**
80
+ * 工作流的词汇(Host / Client 共用;纯类型 + 少量常量,运行期近乎 0 字节)。
81
+ * 必须保持「浏览器安全」:只含纯 JSON 载荷与类型词汇,不 import Host 专属值。
82
+ *
83
+ * 【为什么独立成包】它原在 `dsh-auto-flow/src/types.ts` 里,与工作流的领域逻辑同包。
84
+ * 逻辑(本包 `src/` 的 10 个模块)下沉后,词汇必须跟着走 —— 否则本包要反过来
85
+ * import `dsh-auto-flow`(基座依赖业务),方向反了。
86
+ *
87
+ * 【两类词汇同处本文件,且**互相依赖**(实测,别按行号硬切)】
88
+ * - 工作流词汇:`FlowDataValue` / `FlowNode` / `RunState` / …
89
+ * - 脚本词汇:`ScriptInfo` / `ScriptParamField` / … —— 它是脚本在工作流里的**消费侧视图**,
90
+ * 与 `script-contract` 的线格式刻意不同(这里 `children` 可选,以接受旧清单 / 手工构造的字段)。
91
+ * 实测依赖是**双向的**:脚本词汇用了工作流词汇(`type: PropertyType`、`defaultValue?: FlowDataValue`),
92
+ * 而工作流的逻辑(`payload.ts` / `required-params.ts` / `validation.ts`)也用 `ScriptParamField`。
93
+ * 故两者必须同包 —— 这正是「只下沉工作流词汇、脚本词汇留原地」不成立的原因。
94
+ *
95
+ * 【⚠ 处在 @Remote 边界上】`FlowDataValue` 是**受约束的递归联合**而非 unknown:
96
+ * @Remote 边界(loadFlow / saveFlow / listScripts 等)由 typert 生成编解码,
97
+ * 不接受无约束的 unknown / any。而 typert 的 codec 要求递归类型的声明文件落在
98
+ * 「同 face 的已登记包」内(生成器 analyzer 的 `registrationForFile`)——
99
+ * 故本包带 `tsconfig.host.json` 并登记进 host 聚合,理由见该文件。
100
+ *
101
+ * 【本包刻意不含】auto-flow 自己的**对外契约登记**(错误码 `autoFlow/…`、事件 `auto-flow/…`)
102
+ * 留在业务包的 `src/types.ts` —— 那是业务事实,不是「工作流是什么」的词汇。
103
+ */
104
+ /**
105
+ * ⚠ 脚本契约的**消费侧视图**:下面 `ScriptParamField` / `SelectOption` / `ScriptInfo` 是
106
+ * `script-contract` 的线格式(`ScriptMeta` / `ScriptParamField` / `ScriptSelectOption`)
107
+ * 在编排侧的投影,**真源在 `packages/shared/script-contract/src/types.ts`**。
108
+ *
109
+ * 【为什么要投影而不是直接用真源】线格式里 `params[].children` **必填**(typert 的递归
110
+ * codec 要求),而这里要接受「旧清单 / 手工构造的字段 / 用户手写的 `scripts.json`」,
111
+ * 所以消费侧必须是可选。这条分歧登记在 `packages/shared/policy.facts.json` 的
112
+ * `contractDivergence`(符号 `ScriptParamField`)—— 改动脚本清单字段时两侧必须同步。
113
+ *
114
+ * `ScriptArtifactSpec` 不在此列:它没有分歧,直接从真源转出(见下方 import)。
115
+ */
116
+ /** 脚本参数字段(JSON 安全,来自 ag 的脚本清单;供工作流 UI 渲染表单)。 */
117
+ interface ScriptParamField {
118
+ /** 字段名(payload key)。 */
119
+ key: string;
120
+ /** 显示名。 */
121
+ label: string;
122
+ /** 控件类型。 */
123
+ type: PropertyType;
124
+ /** 是否必填。 */
125
+ required?: boolean | undefined;
126
+ /** 默认值。 */
127
+ defaultValue?: FlowDataValue | undefined;
128
+ /** 下拉选项(type=select 时)。 */
129
+ options?: SelectOption[] | undefined;
130
+ /**
131
+ * 多值(缺省 false):select → 多选;file → 多文件。
132
+ *
133
+ * ⚠ 与 `script-contract` 的 `ScriptParamField.multiple` 保持同步(契约镜像);缺了它,
134
+ * 脚本清单声明的「多文件参数」会在这一侧退化成单值。
135
+ */
136
+ multiple?: boolean | undefined;
137
+ /** 呈现格式:string → text/password/textarea/json/code/expression;datetime → datetime/date/time。 */
138
+ format?: string | undefined;
139
+ /** 占位符。 */
140
+ placeholder?: string | undefined;
141
+ /** 描述/帮助文本。 */
142
+ description?: string | undefined;
143
+ /**
144
+ * 复合子字段(type=array/object,可递归):array → 元素的字段定义;object → 各子字段。
145
+ * 空数组 = 脚本未声明结构 → 工作流表单退化成 JSON 文本编辑(而不是显示死字段)。
146
+ *
147
+ * 这里写成**可选**:ag 侧现在是必填(typert Remote codec 要求递归字段非可选),
148
+ * 但客户端对缺省/空数组的处理完全相同,保持可选可以让「旧清单 / 手工构造的字段」继续被接受。
149
+ * ⚠ 与 ag 的 ScriptParamField.children 保持同步(契约镜像)。
150
+ */
151
+ children?: ScriptParamField[] | undefined;
152
+ /** 判别联合的候选字段集(如 operation 按 action 分四套字段);表单按判据值只显示命中的那套。 */
153
+ variants?: {
154
+ when: string;
155
+ value: FlowDataValue;
156
+ children: ScriptParamField[];
157
+ }[] | undefined;
158
+ /** 单字段显隐条件(同分支内的「选了这个才填那个」;形状与 `ScriptParamField` 同源)。 */
159
+ visibleWhen?: FieldConditionSet | undefined;
160
+ }
161
+ /** 脚本来源(一个脚本库/插件):id 是限定名的第一段,label 是 UI 一级分类标题。 */
162
+ interface ScriptSourceInfo {
163
+ /** 稳定路由 id(如 `ag`)。 */
164
+ id: string;
165
+ /** 展示名(如「AG」)。 */
166
+ label: string;
167
+ }
168
+ /** 一个可运行的业务脚本(脚本库 `listScripts` 的完整清单;`ScriptMeta` 的消费侧视图)。 */
169
+ interface ScriptInfo {
170
+ /**
171
+ * 脚本身份。两种形态,看它处在链路的哪一段:
172
+ * - **provider 输出**(脚本库的 `listScripts`):**库内名**,如 `xhs/collect`;
173
+ * - **聚合后**(`ScriptProviderRegistry.entries`):**限定名** `<来源 id>/<库内名>`,如 `ag/xhs/collect`。
174
+ *
175
+ * 来源是身份的一部分 —— 两个脚本库各自都有 `xhs/collect` 时不会互相覆盖;
176
+ * 编排侧(auto-flow)由 ScriptProviderRegistry 汇总后统一加工成限定名形态。
177
+ */
178
+ name: string;
179
+ /** 展示名(如「小红书·素材收集」)。 */
180
+ label: string;
181
+ /** 描述。 */
182
+ description: string;
183
+ /** 参数清单(供工作流 UI 渲染表单)。 */
184
+ params: ScriptParamField[];
185
+ /** 产物文件装配规则(缺省不消费上游文件)。 */
186
+ artifacts?: ScriptArtifactSpec | undefined;
187
+ /** 脚本来源(UI 按它做一级分类;缺省视为未分类)。 */
188
+ source?: ScriptSourceInfo | undefined;
189
+ /**
190
+ * 该来源在**加载期**发现的诊断(可选、不阻断运行):例如用户脚本清单里某条写错、
191
+ * 入口文件不存在、参数类型非法。UI 要把它们显示出来 —— 否则用户只会看到"脚本少了几个",
192
+ * 完全无从下手。
193
+ */
194
+ diagnostics?: ScriptDiagnosticInfo[] | undefined;
195
+ }
196
+ /** 一条脚本来源的加载诊断(用户脚本特有;内置来源一般为空)。 */
197
+ interface ScriptDiagnosticInfo {
198
+ /** 出问题的脚本名(清单整体出错时为空串)。 */
199
+ name: string;
200
+ /** 稳定错误码(如 BAD_INPUT / FILE_ERROR)。 */
201
+ code: string;
202
+ /** 人类可读的原因。 */
203
+ message: string;
204
+ }
205
+ /**
206
+ * 工作流数据里的标量/结构值(JSON 安全、递归)。
207
+ * 必须是受约束的递归联合而非 unknown:@Remote 边界(loadFlow/saveFlow/listScripts 等)
208
+ * 由 typert 生成编解码,不接受无约束的 unknown/any。
209
+ */
210
+ type FlowDataValue = string | number | boolean | null | FlowDataValue[] | {
211
+ [key: string]: FlowDataValue;
212
+ };
213
+ /**
214
+ * 一条**数据条目**:一个命名字段集 + 它在本次运行内的来源条目下标。
215
+ *
216
+ * 【为什么值属于字段而不是节点】一个节点只有一对口、一个整值的模型表达不了三件事:
217
+ * 「一批数据」(只能靠迭代节点绕)、「多条入边都要」(只能靠 `primaryValue` 猜拓扑序最大的一条)、
218
+ * 「取上游某个字段」(只能手打 `{{steps.<uuid>.<字段>}}`)。item 把值放回字段上,
219
+ * 三件事各归各位。参照 n8n 的 `INodeExecutionData`(`{json, binary?}`,
220
+ * `third/n8n/packages/workflow/src/interfaces.ts:1837-1856`)——我们**不做 `binary`**:
221
+ * 文件在本仓是路径 / 附件引用,是 json 里的普通字段,没有二进制在节点间搬运的需求。
222
+ *
223
+ * 【为什么 `json` 必须是对象而不是任意值】标量产出(模型返回的文本、命令的 stdout)统一包进
224
+ * 一个声明字段(`{text}` / `{stdout}`),字段级引用与校验才有据可依。n8n 同样要求 `json` 是对象。
225
+ */
226
+ interface Item {
227
+ /** 一条数据的字段集(JSON 安全)。 */
228
+ json: Record<string, FlowDataValue>;
229
+ /**
230
+ * 本条目在**上游产出批次**里的下标(`each` 模式下由引擎填入;根节点缺省)。
231
+ *
232
+ * 用途:千条级以上唯一能回答「这条结果是从哪条来的」。**只在一次运行内有意义** ——
233
+ * 跨运行会漂移,故按业务键记的台账另立一份(见 {@link LedgerEntry}),不用它当键。
234
+ */
235
+ pairedItem?: number | undefined;
236
+ }
237
+ /** 一个输入口 / 输出口上流的东西:一批 item。 */
238
+ type PortData = readonly Item[];
239
+ /**
240
+ * 台账里一条条目的**结局**。
241
+ *
242
+ * 【为什么恰好这三值】它只回答「这条上次试过没成功」,不回答「该不该现在做」:
243
+ * - `ok`:成功过(重试时跳过);
244
+ * - `failed`:试过但失败了(重试时补做);
245
+ * - `skipped`:**这一轮没轮到它**(失败上限截断、取消,或展开时缺业务键)。
246
+ * 与 `failed` 分开是因为两者的下一步不同:`failed` 要重试,`skipped` 则可能是数据本身有问题。
247
+ */
248
+ type LedgerStatus = 'ok' | 'failed' | 'skipped';
249
+ /**
250
+ * 台账的一条记录:**某个批次的某个节点上,某条数据(按业务键)的结局**。
251
+ *
252
+ * 【为什么三个维度缺一不可】
253
+ * - 按 `key`:业务标识,是跳过式重试唯一的判据(**绝不用下标** —— 下标在重试时不可靠:
254
+ * 上游多一条数据则所有下标平移,会跳过没做过的、重做做过的,是静默错误);
255
+ * - 按 `nodeId`:同一个 key 在节点 A 成功、在节点 B 失败是两件事;
256
+ * - 按**批次**:台账挂在一次批次上而不是永久累积 —— 挂在节点上会让「同一批数据
257
+ * 永远只能跑一次,用户想重跑都没有办法」。
258
+ *
259
+ * 【为什么没有 `batchId` 字段】批次的身份**由文件名承载**:
260
+ * `<工作区>/ledger/<节点id>-<批次id>.jsonl`。一行 JSON 里再存一遍批次 id 是冗余的 ——
261
+ * 冗余就有「行里写的批次与文件名的批次不一致」这种自相矛盾的可能(手改文件时尤其),
262
+ * 而判据只能认一个。故本类型**刻意不含** `batchId`:批次来自你打开的那个文件。
263
+ *
264
+ * 【为什么是追加式】重启 / 重试会对同一 key 再写一行;读到多行时**取最后一行**,
265
+ * 与运行日志「只追加、不回改」的口径一致。
266
+ *
267
+ * 【关于 `inputFingerprint`】确认书列它为**可选**,本批**不实现采集**(非目标 5:
268
+ * 不做输入指纹的失效判定)。刻意不在这里声明它:一个永远不写入的字段会让人以为
269
+ * 「台账记了输入指纹」,而实际读到的永远是 `undefined` —— 那是比缺字段更坏的误导。
270
+ * 将来真要记时再加(加可选字段不必升版本,见 `runs/record.ts` 的判据)。
271
+ */
272
+ interface LedgerEntry {
273
+ /** 本条是哪个节点上的结局。 */
274
+ nodeId: string;
275
+ /** 业务键取值(字符串化后的稳定形态)。 */
276
+ key: string;
277
+ /** 结局。 */
278
+ status: LedgerStatus;
279
+ /** 到这个结局为止一共尝试了几次(含首次)。 */
280
+ attempts: number;
281
+ /** 本条写入时刻(epoch 毫秒)。 */
282
+ at: number;
283
+ /** 失败原因(`status === 'failed'` 时才有)。 */
284
+ error?: string | undefined;
285
+ }
286
+ /**
287
+ * 一次**批次**:一批带业务键的数据在某个节点上的一次产出。
288
+ *
289
+ * 【为什么批次是一等公民而不是步骤的一个字段】台账要挂在批次上而不是节点上(见
290
+ * {@link LedgerEntry} 的按 `batchId` 那条),故「这次运行在跑哪一批」必须是运行状态里的
291
+ * 一个显式事实,否则「第 873 条要补跑」在记录里没有位置。
292
+ *
293
+ * 【边界】一个批次 = **一个节点的输出口**上的一次产出(重试不重跑上游,故边界也到此为止)。
294
+ */
295
+ interface Batch {
296
+ /** 批次 id(本批的唯一标识,台账文件名用它)。 */
297
+ batchId: string;
298
+ /** 产出本批的节点 id。 */
299
+ nodeId: string;
300
+ /** 产出口 id。 */
301
+ port: string;
302
+ /**
303
+ * 本批实际使用的**业务键字段名**(节点 `data.keyField` 的落定值)。
304
+ *
305
+ * 缺省 = 本批没有业务键,**不支持跳过式重试**(只能整批重跑,且入口会明确报出来
306
+ * 「本批数据无业务键,不支持跳过式重试」,不装作支持、更不静默退化成下标)。
307
+ */
308
+ keyField?: string | undefined;
309
+ }
310
+ /**
311
+ * 重试路径上的**跳过策略**。
312
+ *
313
+ * - `rerun-all`(**默认**):不跳过,该做的都做 —— 与正常执行等价(只是保留同一次运行的身份);
314
+ * - `skip-succeeded`:只补做台账里 `status !== 'ok'` 的 key。
315
+ *
316
+ * 【为什么默认不跳过】默认跳过会让「我改了提示词想重跑」变成一个**用户想不通的静默行为**;
317
+ * 跳过是重试路径上用户显式要的东西,不该成为整个体系的默认。**这是我的判断**,
318
+ * 不是从别处抄的;若认为默认应当跳过,改这一个常量即可,不影响其它设计。
319
+ */
320
+ declare const RESUME_MODES: readonly ["rerun-all", "skip-succeeded"];
321
+ type ResumeMode = (typeof RESUME_MODES)[number];
322
+ /** 缺省的重试跳过策略(见 {@link ResumeMode} 的理由)。 */
323
+ declare const DEFAULT_RESUME_MODE: ResumeMode;
324
+ /** 把一批 item 折叠成一个 `FlowDataValue`(`{{节点名.字段}}` 的取值规则)。 */
325
+ type PortProjection = FlowDataValue;
326
+ /** 工作流节点的数据(JSON 安全;含 `nodeType` 注册表 key + 各属性值)。 */
327
+ type FlowNodeData = Record<string, FlowDataValue>;
328
+ /** 一个工作流节点(JSON 安全)。 */
329
+ interface FlowNode {
330
+ /** 节点 id(内部稳定标识,不再出现在用户视野里)。 */
331
+ id: string;
332
+ /**
333
+ * 节点**名字**:文档内唯一,`{{名字.字段}}` 的寻址键。
334
+ *
335
+ * 【为什么不是 id】用户写引用时看到的是名字(`{{小红书素材收集.notes}}`),
336
+ * 而不是 `{{steps.llm-3f2a….prompt}}`。代价是改名必须改写文档里的引用
337
+ * (`rewriteNodeReferences`),漏一处就断链 —— 这是本设计主动接受的取舍。
338
+ * 保留名:不得叫 `input` / `item`(它们是模板里的保留作用域),不得含 `.` 与花括号。
339
+ * 参照 n8n:节点名唯一(`workflow-structure-validation.ts:70,157`),改名重写引用(`workflow.ts:334,384`)。
340
+ */
341
+ name: string;
342
+ /** React Flow 渲染类型(节点系统统一为 'base',实际渲染由 data.nodeType 决定)。 */
343
+ type: string;
344
+ /** 画布坐标。 */
345
+ position: {
346
+ x: number;
347
+ y: number;
348
+ };
349
+ data: FlowNodeData;
350
+ /** 节点样式(如宽高,JSON 安全)。 */
351
+ style?: Record<string, FlowDataValue> | undefined;
352
+ }
353
+ /** 一条工作流连线(JSON 安全)。 */
354
+ interface FlowEdge {
355
+ /** 连线 id。 */
356
+ id: string;
357
+ /** 源节点 id。 */
358
+ source: string;
359
+ /** 目标节点 id。 */
360
+ target: string;
361
+ /** 源节点句柄(缺省为默认输出句柄)。 */
362
+ sourceHandle?: string | undefined;
363
+ /** 目标节点句柄(缺省为默认输入句柄)。 */
364
+ targetHandle?: string | undefined;
365
+ /** 连线渲染类型(有限集合,见 {@link FlowEdgeType})。 */
366
+ type?: FlowEdgeType | undefined;
367
+ }
368
+ /**
369
+ * 连线的**渲染类型**(有限集合:本项目的自定义边 + React Flow 自带的那几种)。
370
+ *
371
+ * 【为什么从 `string` 收紧成枚举】原先是任意字符串:手写文档或模型工具写一个不存在的类型,
372
+ * 渲染会静默回退成默认边 —— 一个没有主人的兜底。收紧之后未知类型在**解码**时就报错。
373
+ *
374
+ * 【为什么同时删掉 `animated` 与 `data`】两者都没有生产者:`animated` 从来没有人设为 true;
375
+ * `data` 只被一个 xyflow 示例残留(`special` 边)用过,而边的展示信息(分支语义、运行状态)
376
+ * 应当从**句柄与运行状态**派生,不该写进持久化文档。
377
+ */
378
+ type FlowEdgeType = 'custom-edge' | 'default' | 'straight' | 'step' | 'smoothstep' | 'simplebezier';
379
+ /** 一次持久化的工作流画布:节点 + 连线。 */
380
+ interface FlowGraph {
381
+ nodes: FlowNode[];
382
+ edges: FlowEdge[];
383
+ }
384
+ /**
385
+ * 当前工作流文档格式版本(显式版本化,便于未来演进与迁移)。
386
+ *
387
+ * v2 = item 流:节点有唯一 `name`、引用按名字;`RunStep` 装批的汇总与样本。
388
+ * 旧文档(v1)**响亮拒绝**,不写迁移(项目无历史债务)。
389
+ */
390
+ declare const WORKFLOW_FORMAT_VERSION = 2;
391
+ /**
392
+ * 一份持久化的工作流文档:画布 + 元数据(名称 / 描述 / 格式版本)。
393
+ * 与 FlowGraph 不同——它是 domain flows 表里「以 id 为键」的完整记录;
394
+ * 引擎与校验仍只消费其 nodes/edges 画布部分(FlowGraph)。
395
+ */
396
+ interface WorkflowDefinition {
397
+ /** 文档格式版本(当前 WORKFLOW_FORMAT_VERSION)。 */
398
+ formatVersion: number;
399
+ /** 工作流名称(展示名;缺省回退为 id)。 */
400
+ name: string;
401
+ /** 工作流描述。 */
402
+ description: string;
403
+ nodes: FlowNode[];
404
+ edges: FlowEdge[];
405
+ }
406
+ /** 一个工作流的摘要(列表用)。inputs 为表单节点暴露的输入键,供模型/命令发现可传参数。 */
407
+ interface FlowSummary {
408
+ id: string;
409
+ name: string;
410
+ description: string;
411
+ nodeCount: number;
412
+ inputs: string[];
413
+ }
414
+ /** 一个实时 LLM 模型路由(provider id + 展示名)。 */
415
+ interface WorkflowLlmRoute {
416
+ /** 路由 id(传给 llm.stream 的 provider)。 */
417
+ provider: string;
418
+ /** 展示名。 */
419
+ displayName: string;
420
+ }
421
+ /** 实时模型路由列表结果。 */
422
+ interface WorkflowListLlmRoutesResult {
423
+ routes: WorkflowLlmRoute[];
424
+ }
425
+ /** 某路由上的一个模型。 */
426
+ interface WorkflowRouteModel {
427
+ /** 模型 id(传给 llm.stream 的 model)。 */
428
+ id: string;
429
+ /** 展示名。 */
430
+ name: string;
431
+ }
432
+ /** 查询某模型路由的模型目录的请求。 */
433
+ interface WorkflowListRouteModelsRequest {
434
+ provider: string;
435
+ }
436
+ /** 某路由的模型目录结果。 */
437
+ interface WorkflowListRouteModelsResult {
438
+ models: WorkflowRouteModel[];
439
+ /** 目录加载失败时的错误文本。 */
440
+ error?: string | undefined;
441
+ }
442
+ /** 一次工作流校验的诊断项:级别、稳定错误码、可读信息,可关联节点或连线。 */
443
+ interface FlowDiagnostic {
444
+ /** 诊断级别:错误或警告。 */
445
+ level: 'error' | 'warning';
446
+ /** 稳定错误码(机器可路由)。 */
447
+ code: string;
448
+ /** 用户可读的诊断信息。 */
449
+ message: string;
450
+ /** 关联的节点 id;缺省表示与节点无关。 */
451
+ nodeId?: string | undefined;
452
+ /**
453
+ * 关联的**节点属性 key**(字段级诊断;缺省表示这条诊断不指向某一个字段)。
454
+ *
455
+ * 【为什么要有它】此前诊断只到节点级,属性面板只能把「这个节点有问题」整体提示一次,
456
+ * 用户还得自己找是哪个字段 —— 而校验其实**知道**是哪个字段(就是它报的那一项)。
457
+ * 落成字段之后,面板能把错误显示在那个字段下面(n8n 的字段旁报错同理)。
458
+ */
459
+ field?: string | undefined;
460
+ /** 关联的连线 id;缺省表示与连线无关。 */
461
+ edgeId?: string | undefined;
462
+ }
463
+ /** 工作流校验结果。 */
464
+ interface FlowValidationResult {
465
+ /** 是否全部通过(无错误级诊断)。 */
466
+ valid: boolean;
467
+ /** 全部诊断项。 */
468
+ diagnostics: FlowDiagnostic[];
469
+ }
470
+ /** 工作流产物文件(JSON 安全,供审批节点增删改查)。 */
471
+ interface WorkflowFile {
472
+ /** 绝对路径。 */
473
+ path: string;
474
+ /** 相对 workspace 根目录的路径。 */
475
+ relativePath: string;
476
+ /** 文件名。 */
477
+ name: string;
478
+ /** 字节数。 */
479
+ size: number;
480
+ /** MIME 类型。 */
481
+ contentType?: string | undefined;
482
+ /** 源 URL。 */
483
+ url?: string | undefined;
484
+ /** 业务元信息。 */
485
+ meta?: Record<string, FlowDataValue> | undefined;
486
+ }
487
+ /**
488
+ * 一个节点的一次执行尝试(重试的每一次都留一条)。
489
+ *
490
+ * 【为什么只能追加、不覆盖】「重试」的现场价值全在**上一次**:第一次报了什么错、拿到的是什么输入,
491
+ * 是判断「重试有没有意义」的唯一依据。若每次重试都覆盖,用户只能看到最后一次(最没有信息量的那次)。
492
+ * 故引擎只 `push`,从不改写既有条目。
493
+ */
494
+ interface NodeAttempt {
495
+ /** 第几次尝试,从 1 开始。 */
496
+ attempt: number;
497
+ /** 本次尝试开始的时刻(epoch 毫秒)。 */
498
+ startedAt: number;
499
+ /** 本次尝试结束的时刻(epoch 毫秒)。 */
500
+ finishedAt: number;
501
+ /** 本次尝试的墙钟耗时毫秒(`finishedAt - startedAt`)。 */
502
+ durationMs: number;
503
+ /** 本次尝试是否成功。 */
504
+ ok: boolean;
505
+ /**
506
+ * 本次尝试**收到的批**的规模(每个输入口的条目数)。
507
+ *
508
+ * 【为什么不是输入值本身】item 模型下一批可能上万条,把整批写进每一次尝试的记录会让
509
+ * 运行快照随条数线性膨胀(`NodeAttempt` 是只追加的)。这里只留规模,
510
+ * 样本与失败明细在 {@link RunStep.items} / {@link RunStep.failedItems} 上。
511
+ */
512
+ itemCounts?: Record<string, number> | undefined;
513
+ /** 失败原因(失败时)。 */
514
+ error?: string | undefined;
515
+ /** 失败时的稳定错误码。 */
516
+ errorCode?: string | undefined;
517
+ /**
518
+ * 本次失败是否**值得再试**。
519
+ * `false` = 命中豁免判据(脚本的确定性错误码、子工作流已失败、审批被驳回…),引擎不会重试。
520
+ */
521
+ retryable?: boolean | undefined;
522
+ }
523
+ /**
524
+ * 一条**节点过程日志**(追加式,顺序即发生顺序)。
525
+ *
526
+ * 【为什么不复用宿主日志】宿主日志是给维护者看的(级别 + 文本),而这里是**运行事实**:
527
+ * 它随运行记录一起落盘、能在运行历史里按节点回放。用户的诉求是「这一步到底卡在哪」,
528
+ * 那需要结构化的时间点,而不是一行行混在一起的文本。
529
+ *
530
+ * 【为什么 `data` 是自由键值对】不同节点要说的事不一样(脚本说产物数、审批说等了多久、
531
+ * 迭代说跑了多少项)。强行统一成固定字段只会让一半的字段在多数节点上恒为空。
532
+ * 引擎只写入它确知的事实,其余由各执行器按需补。
533
+ */
534
+ interface NodeLogEntry {
535
+ /** 发生时刻(epoch 毫秒)。 */
536
+ at: number;
537
+ /**
538
+ * 事件类型(稳定字符串,供 UI 与检索按类分派)。
539
+ *
540
+ * 目前引擎写入四类:`start`(开始执行)/ `attempt`(一次尝试结束)/ `retry-wait`
541
+ * (重试前的等待)/ `settled`(结算)。执行器可写自己的类别。
542
+ */
543
+ kind: string;
544
+ /** 人可读的一句话(中文原文,与既有诊断文案一致)。 */
545
+ message: string;
546
+ /** 该条日志附带的键值事实(JSON 安全)。 */
547
+ data?: Record<string, FlowDataValue> | undefined;
548
+ }
549
+ /** 单个节点的运行状态。 */
550
+ interface RunStep {
551
+ /**
552
+ * 本步骤在本次运行 trace 里的**序号**(0 起,等于 `steps` 数组下标 = 拓扑序位置)。
553
+ *
554
+ * 【为什么不靠数组顺序】序号此前只存在于数组下标里,于是任何拿到「一份步骤快照」的消费者
555
+ * (客户端排序、断点续跑恢复、运行日志的历史行)都必须先知道「这份数组是按拓扑序排的」,
556
+ * 而这个约定没有任何地方写下来。落成字段后 trace 自身可排序 —— 数组乱序、只取片段、
557
+ * 或跨版本读取时都不再依赖隐含约定。产物目录名里的 `stepDir`(`1-xhs-collect`)也是这个序号。
558
+ * 参照 dify 的 `WorkflowNodeExecution.index`:「Execution sequence number, used for
559
+ * displaying Tracing Node order」。
560
+ */
561
+ index: number;
562
+ /** 节点 id。 */
563
+ nodeId: string;
564
+ /**
565
+ * 本步骤的**批次规模**:每个输入口 / 输出口上的条目数。
566
+ *
567
+ * 【为什么用计数而不是值】一批可能上万条;记录只留规模,画布徽章与边可视化读它
568
+ * (「这条线走没走过数据」= 源的对应输出口计数 > 0)。值本身见 {@link RunStep.items}。
569
+ */
570
+ itemCounts?: {
571
+ in: Record<string, number>;
572
+ out: Record<string, number>;
573
+ } | undefined;
574
+ /**
575
+ * 各输入口的**来源节点**(按它们贡献条目的顺序展平)。
576
+ *
577
+ * 【为什么是一张表】同一输入口可以接多条入边(按序 append,见 `ledger` 之外的 `item-flow` 范围 C),
578
+ * 取谁的值不再是运行期猜出来的事实(旧模型是 `primaryValue` 取拓扑序最大的一条,
579
+ * 与 `predecessor` 一起把「这一步拿的是谁的值」推迟到事后才可见)。列出贡献者,
580
+ * 画布与运行记录都直接可见。
581
+ */
582
+ inputSources?: string[] | undefined;
583
+ /** 节点类型(nodeType 注册表 key)。 */
584
+ nodeType: string;
585
+ /** 展示名。 */
586
+ label: string;
587
+ status: 'pending' | 'running' | 'waiting' | 'success' | 'failed' | 'skipped';
588
+ /**
589
+ * 本步骤 status **最后一次变化**的时间(epoch 毫秒)。
590
+ *
591
+ * 用途:客户端据此判断「运行中 / 等待中」是真在跑还是已经卡住 —— 过阈值就显示
592
+ * 「疑似卡住(已 xx 分钟)」,而不是永远安静地转圈。
593
+ * 为什么需要:脚本步骤的 Promise 一旦不结算(例如脚本插件收尾卡在 browser.close()),
594
+ * 运行会永远停在 running,此前界面无从分辨「慢」与「卡死」。
595
+ */
596
+ statusChangedAt?: number | undefined;
597
+ /**
598
+ * 本步骤**首次进入执行**(`running`)的时刻;从未执行的步骤(被排除 / 被预算截断的 `skipped`)缺省。
599
+ *
600
+ * 与 `startedAt`/`finishedAt` 的分工:`statusChangedAt` 是「最后一次状态变化」,会随
601
+ * running→waiting→running 反复刷新;这两个是**本次执行区间的两端**,只写一次,故耗时
602
+ * (`finishedAt - startedAt`)可算 —— 先前只有脚本自报的 `durationMs`,值节点连跑多久都看不到。
603
+ */
604
+ startedAt?: number | undefined;
605
+ /** 本步骤**结算为终态**(success / failed / skipped)的时刻;仍在跑或等待时缺省。 */
606
+ finishedAt?: number | undefined;
607
+ /** 该节点对应的业务脚本名(如 xhs/collect)。 */
608
+ script?: string | undefined;
609
+ /** 该脚本步骤在 workspace 下的相对目录名(如 1-xhs-collect)。 */
610
+ stepDir?: string | undefined;
611
+ /**
612
+ * 本步骤**产出的文件路径**(脚本按 `ScriptArtifactSpec.filesKey` 声明的那个键给出来)。
613
+ *
614
+ * 【为什么落在步骤上】产物文件此前只活在 payload 装配里(下游脚本吃得到),运行记录里
615
+ * 用户看不到「这一步产出了哪些文件」,只能去翻工作目录。与 `stepDir` 同源、同一步骤一条。
616
+ * 非脚本步骤(值节点)不产出文件,故可选。
617
+ */
618
+ files?: string[] | undefined;
619
+ /**
620
+ * 本步骤产出的**条目样本**。
621
+ *
622
+ * 【有界是硬要求】`RunStep` 随运行快照反复落盘,一批上万条若整批写进来,快照大小随条数线性膨胀。
623
+ * 规则:条目数 ≤ {@link ITEM_SAMPLE_LIMIT} 时内联**全部**;超出时只放**前 200 条样本**,
624
+ * 全量走 {@link RunStep.itemsRef} 指向的追加式文件。运行面板展示的就是这一份样本。
625
+ */
626
+ items?: Item[] | undefined;
627
+ /**
628
+ * **非主输出口**的条目样本(按出口 id 分组,各口独立受 {@link ITEM_SAMPLE_LIMIT} 约束)。
629
+ *
630
+ * 【为什么需要它】{@link RunStep.items} 只记**主输出口**(`out`)的样本。而没有 `out` 口的
631
+ * 节点恰恰是「数据去了哪」最需要看清的那些:条件分支把条目**按条**路由到 `true` / `false`
632
+ * (`node-defs/condition.ts`),声明「走错误分支」的节点把失败条目送去 `error`。此前这些口
633
+ * 只有计数(`itemCounts.out`),条目一条都取不回来 —— 于是「走了 true 的那 12 条到底是什么」
634
+ * 无从回答。
635
+ *
636
+ * 【为什么只记非主输出口】主口已有 `items`,再记一份就是同一事实的两个来源(迟早漂移),
637
+ * 也让**单出口节点零开销** —— 它们(绝大多数)根本不写这个字段。
638
+ *
639
+ * 【有界且不外溢】每口最多 {@link ITEM_SAMPLE_LIMIT} 条。**它不进溢出文件**:
640
+ * `itemsRef` 的全量落盘与续跑水合仍只覆盖主输出口(见 `RunStep.itemsRef`)。
641
+ * 因此这里的样本只用于展示,不可当作「这一步的全部产出」。
642
+ *
643
+ * 【为什么不升记录版本】可选字段:旧行读不到它就是「这一次没记按口样本」
644
+ * (判据见 `runs/record.ts` 的 `RUN_RECORD_VERSION` 说明)。
645
+ */
646
+ portItems?: Record<string, Item[]> | undefined;
647
+ /**
648
+ * 本步骤的**失败条目明细**(全量)。
649
+ *
650
+ * 【为什么失败不设样本上限】失败才是用户要看的东西,也是第二批「重试只补没成功的」的依据。
651
+ */
652
+ failedItems?: {
653
+ index: number;
654
+ error: string;
655
+ }[] | undefined;
656
+ /**
657
+ * 本步骤被台账**跳过**的条目数(重试路径上「这条之前成功过」)。
658
+ *
659
+ * 【为什么记在步骤上】重试时「输入 1000 条、只做了 3 条」必须看得出差额,
660
+ * 否则用户看到输入与产出差 997 条会以为数据丢了。缺省 = 没跳过任何条目(正常执行恒缺省)。
661
+ */
662
+ skippedCount?: number | undefined;
663
+ /**
664
+ * 条目数超出样本上限时的**溢出文件**(相对 `workspaceDir`,如 `steps/3-llm-a1b2/items.jsonl`)。
665
+ *
666
+ * 【为什么要有它】记录有界之后,续跑不能再从 `RunStep` 重建上游的值 —— 全量在文件里,
667
+ * 由宿主在续跑入口读回并经 `RunSeed.initialValues` 注入(引擎不读文件)。
668
+ */
669
+ itemsRef?: string | undefined;
670
+ /** 失败原因。 */
671
+ error?: string | undefined;
672
+ /** 失败时的稳定错误码(ag ScriptResult.error.code)。 */
673
+ errorCode?: string | undefined;
674
+ /** 脚本耗时毫秒(ScriptResult.meta.durationMs)。 */
675
+ durationMs?: number | undefined;
676
+ /**
677
+ * 各次尝试的记录(**只追加**,见 {@link NodeAttempt})。
678
+ *
679
+ * 首次即成功时**不写**(零噪音);失败与实际发生过重试的节点才有。缺省 = 一次过。
680
+ */
681
+ attempts?: NodeAttempt[] | undefined;
682
+ /**
683
+ * 这一步的**过程日志**(按发生顺序追加)。
684
+ *
685
+ * 【与 `attempts` / `error` 的分工】`error` 只留最后一句失败原因,`attempts` 只记每一次
686
+ * 尝试的输入与耗时 —— 两者都答不了「它中间在干什么」(等审批等了多久、重试之间睡了多久、
687
+ * 子运行跑了几项)。这一栏就是补这个缺口:**可观测性**(对齐 langflow 的 `end_vertex` 级事件
688
+ * 与逐顶点耗时,见 `docs/analysis/ref-langflow-flowise.md`)。
689
+ *
690
+ * 【为什么是可选字段而不是升版本号】加一个**可选**字段不改变旧记录的读法(读不到就是
691
+ * 「这一次没记日志」),与 v0→v1 那次「回填必需字段」不是一类事 —— 那种才需要迁移链。
692
+ * 故 `RUN_RECORD_VERSION` 不升,见 `record.ts` 的版本说明。
693
+ */
694
+ logs?: NodeLogEntry[] | undefined;
695
+ }
696
+ /** 一次工作流运行的快照(客户端轮询用;终态后作为 settled 记录追加进运行日志)。 */
697
+ interface RunState {
698
+ runId: string;
699
+ /** 本次运行的工作流 id(归属标签)。 */
700
+ workflowId: string;
701
+ /** 本次运行的工作流名称(归属标签)。 */
702
+ name: string;
703
+ /** 发起本次运行的会话 id(归属标签);画布/外部触发时缺省。 */
704
+ sessionId?: string | undefined;
705
+ /**
706
+ * 运行级状态。
707
+ *
708
+ * `crashed` 只描述**一件事**:运行进程在「进行中」时消失了(宿主重启 / 被强杀),
709
+ * 且我们只有它的中途快照、拿不到结局。故它**只能**由「快照里是 `running`」推出 ——
710
+ * `waiting`(合法暂停,等人放行)与各终态**故意不判** crashed:把一个正等人审批的运行
711
+ * 说成「崩溃」会让人去做毫无意义的恢复操作,而把已完成的运行说成崩溃则是纯错误。
712
+ */
713
+ status: 'running' | 'waiting' | 'completed' | 'failed' | 'cancelled' | 'crashed';
714
+ /** 本次运行的产物工作区根目录(宿主文件系统绝对路径)。 */
715
+ workspaceDir: string;
716
+ steps: RunStep[];
717
+ /**
718
+ * 本次运行所跑的**批次**(见 {@link Batch});没跑批次(图里没有产出数据集)时缺省。
719
+ *
720
+ * 【为什么是单数、挂在 RunState 上】「任务 = 一组批次」是后续形态(本批不做任务系统),
721
+ * 当下一次运行跑一个批次是常态。挂在这里是**本次为需求做的结构妥协**:若任务系统成型后
722
+ * 发现归属该上移,改动面是台账的键与查询,不是台账机制本身。
723
+ */
724
+ batch?: Batch | undefined;
725
+ /**
726
+ * 本次**重试**使用的跳过策略(缺省 {@link DEFAULT_RESUME_MODE})。
727
+ *
728
+ * 【为什么记在运行状态上】它是「这次运行是怎么跑的」的一部分:只看运行记录就要能回答
729
+ * 「这条为什么没执行」。存成状态而非仅入口参数,恢复 / 崩溃扫尾之后这个事实不会丢。
730
+ */
731
+ resumeMode?: ResumeMode | undefined;
732
+ createdAt: number;
733
+ updatedAt: number;
734
+ }
735
+ /** 一次工作流运行的摘要(历史列表用,不含 steps/workspaceDir 大字段)。 */
736
+ interface RunSummary {
737
+ runId: string;
738
+ /** 本次运行的工作流 id(归属标签)。 */
739
+ workflowId: string;
740
+ /** 本次运行的工作流名称(归属标签)。 */
741
+ name: string;
742
+ /** 发起本次运行的会话 id(归属标签);画布/外部触发时缺省。 */
743
+ sessionId?: string | undefined;
744
+ status: RunState['status'];
745
+ nodeCount: number;
746
+ createdAt: number;
747
+ updatedAt: number;
748
+ }
749
+ /** 列出运行记录的请求;sessionId / workflowId 双过滤可同时使用。 */
750
+ interface WorkflowListRunsRequest {
751
+ /** 只列出该会话发起的运行。 */
752
+ sessionId?: string | undefined;
753
+ /** 只列出该工作流的运行。 */
754
+ workflowId?: string | undefined;
755
+ }
756
+ /** 审批节点的一个选项:标签 + 行为(继续 / 中止)。 */
757
+ interface ApprovalOption {
758
+ /** 选项显示名(也是审批节点的输出值)。 */
759
+ label: string;
760
+ /** 选中后的行为:continue 继续下一节点,abort 中止本次运行。 */
761
+ action: 'continue' | 'abort';
762
+ }
763
+ /**
764
+ * 属性类型 = 控件注册表的 key(只保留「基础类型」)。
765
+ * 呈现差异不再单列类型,而用 NodePropertySchema 的 format / multiple 表达:
766
+ * - string + format(text|password|textarea|json|code|expression);
767
+ * - datetime + format(datetime|date|time);
768
+ * - select + multiple(false=单选|true=多选);
769
+ * - file + multiple(false=单文件|true=多文件)。
770
+ *
771
+ * 【`file` 为什么是独立类型而不是 `string` + 一个 format】「文件」不是字符串的一种**呈现**,
772
+ * 而是另一种**取值形态**:它有独有的来源(本地路径 / URL,见 `materializeFiles`)、独有的
773
+ * 运行期获取环节(下载落盘 → 读字节 → 附件服务 → 内容部件)、以及独有的多值语义(多文件)。
774
+ * 靠 `format` 表达会让「这是个文件」这件事只剩控件层知道,定义、校验与执行器都无从判断。
775
+ */
776
+ type PropertyType = 'string' | 'number' | 'boolean' | 'select' | 'datetime' | 'array' | 'object' | 'file';
777
+ /** string 类型的呈现格式。 */
778
+ type StringFormat = 'text' | 'password' | 'textarea' | 'json' | 'code' | 'expression';
779
+ /** datetime 类型的呈现格式。datetime-seconds = YYYY-MM-DD HH:mm:ss(带秒位选择器)。 */
780
+ type DatetimeFormat = 'datetime' | 'datetime-seconds' | 'date' | 'time';
781
+ /** 属性的展示放置:inline(节点内直显)/ advanced(右上角高级属性)/ hidden(仅数据)。 */
782
+ type PropertyPlacement = 'inline' | 'advanced' | 'hidden';
783
+ /** 下拉选项(静态)。 */
784
+ interface SelectOption {
785
+ label: string;
786
+ value: FlowDataValue;
787
+ }
788
+ /**
789
+ * 节点属性定义(JSON 安全的静态子集;代码路径可在此之上追加动态选项/联动/校验)。
790
+ * 这是「节点系统」的统一词汇:代码定义与 UI 定义都产出同形的这一结构。
791
+ */
792
+ interface NodePropertySchema {
793
+ /** 属性在 data 里的 key。 */
794
+ key: string;
795
+ /** 显示名。 */
796
+ label: string;
797
+ /** 控件类型。 */
798
+ type: PropertyType;
799
+ /** 展示放置。 */
800
+ placement: PropertyPlacement;
801
+ /**
802
+ * 属性面板里的分组名词条。
803
+ *
804
+ * 【为什么在共享 schema 而不是客户端扩展】分组是**定义文件的事实**(哪些字段属于同一件事),
805
+ * 不是浏览器才知道的东西;放在共享 schema 里,宿主读同一份定义也能按组生成文档。
806
+ * 面板按「属性在 `properties` 里的声明顺序」排出组的先后。
807
+ */
808
+ group?: string | undefined;
809
+ /** 呈现格式:string → StringFormat(缺省 text),datetime → DatetimeFormat(缺省 datetime)。 */
810
+ format?: string | undefined;
811
+ /** 代码编辑器的语言提示(string + format=code 时使用,如 shell/json;缺省 plaintext)。 */
812
+ language?: string | undefined;
813
+ /** select / file 是否多值(缺省 false=单值)。select → 多选;file → 多文件。 */
814
+ multiple?: boolean | undefined;
815
+ /** 默认值(getNodeDefaults 的唯一来源)。 */
816
+ defaultValue?: FlowDataValue | undefined;
817
+ /** 描述/帮助文本。 */
818
+ description?: string | undefined;
819
+ /** 是否必填。 */
820
+ required?: boolean | undefined;
821
+ /** 是否禁用(true = 不在画布表单/预览显示;定义与已填值保留,可随时重新启用)。 */
822
+ disabled?: boolean | undefined;
823
+ /** 占位符。 */
824
+ placeholder?: string | undefined;
825
+ /** 静态选项(type=select 时使用)。 */
826
+ options?: SelectOption[] | undefined;
827
+ /** 复合子属性(type=array/object 时使用;可递归)。 */
828
+ children?: NodePropertySchema[] | undefined;
829
+ /**
830
+ * 判别联合的候选子字段集(脚本参数里 z.discriminatedUnion 的产物):
831
+ * `when` 指向的键取当前值,命中哪个 variant 就只显示那套 children;未命中回退并集。
832
+ * 来自脚本清单的 paramsToFields,手工定义节点用 children 即可。
833
+ */
834
+ variants?: {
835
+ when: string;
836
+ value: FlowDataValue;
837
+ children: NodePropertySchema[];
838
+ }[] | undefined;
839
+ /**
840
+ * 单字段显隐条件:满足时才显示该字段(`undefined` = 恒显示)。
841
+ *
842
+ * 单个条件写一个对象即可;多条写数组(**全部满足**才显示)。
843
+ */
844
+ visibleWhen?: FieldConditionSet | undefined;
845
+ /**
846
+ * 长文本控件的行数(string + format=textarea/json/code 时使用;缺省按控件自身默认)。
847
+ * 值越大字段越高 —— 「长文本」不该靠各节点在 UI 里手写。
848
+ */
849
+ rows?: number | undefined;
850
+ /**
851
+ * 数值下界(含)。控件按它设 `min`,校验按它报错 —— **读同一份声明**(控件只是手感,
852
+ * 判据在 `validateFlow` 一处)。
853
+ *
854
+ * 【为什么目前只有数值的 min/max/step】此前数值没有任何声明位:`maxTokens` 填成 0
855
+ * (而 0 会被当成「显式上限 0」下传)、迭代次数填 0、并发填小数(引擎静默向下取整)——
856
+ * 都只能靠运行期自己撞墙。字符串长度/正则那几项**没有真实声明者**(内置节点里找不到
857
+ * 一个诚实的用例:URL、命令、路径都支持 `{{…}}` 模板,用正则卡死反而会误伤合法取值),
858
+ * 故不先造 —— 等出现真实声明者再加,与本轮清理 `inputRules` 是同一条纪律。
859
+ */
860
+ min?: number | undefined;
861
+ /** 数值上界(含)。 */
862
+ max?: number | undefined;
863
+ /** 数值步长(控件步进;`step: 1` 表达「必须是整数」)。 */
864
+ step?: number | undefined;
865
+ /** 数组字段的最小条目数(低于它时删除按钮禁用;校验侧同时兜底)。 */
866
+ minItems?: number | undefined;
867
+ /** 数组字段的最大条目数(达到它时「新增」按钮禁用)。 */
868
+ maxItems?: number | undefined;
869
+ /**
870
+ * 运行态选项的**种类 id**:候选项依赖运行态(模型路由、脚本目录、已保存工作流…)时声明它,
871
+ * 由客户端在渲染/交互时按当前图上下文解析(**不落进用户文档**)。
872
+ *
873
+ * 【为什么是种类 id 而不是取值本身】「选项从哪来」是可序列化的事实,「怎么取」只有浏览器/
874
+ * 后端知道。同一份定义被宿主校验与画布表单共用,故这里只留下种类 id(如 `workflows`)。
875
+ */
876
+ asyncOptions?: string | undefined;
877
+ /**
878
+ * 该属性渲染在画布节点**内部**时,下拉浮层是否跟随画布平移/缩放。
879
+ * 它是呈现约定(画布内 vs 浮层里),故与其它呈现字段同处共享 schema。
880
+ *
881
+ * 【现状:已无消费者,保留字段不保留能力】全面改用 antd `Select` 后,浮层由
882
+ * `@rc-component/trigger` 定位 —— 它按视口坐标算绝对定位并自行补偿祖先 `transform`,
883
+ * 浮层恒定可读大小、贴住触发器,但**画布滚轮缩放时不跟随**(与 `DateTimeField` 的
884
+ * 日期面板同一取舍)。原先那套自写的跟随实现(`useCanvasFollow.ts`)已随本批删除。
885
+ *
886
+ * 故此字段现在**写与不写都一样**(`node-defs/script.ts` 仍写着 `true`,语义是
887
+ * 「这个字段希望跟随」的历史声明)。保留它是为了避免改共享 schema 这个承重结构;
888
+ * 它已不是「开关」,想要真正跟随得另想办法(见 `DateTimeField.tsx` 的取舍说明)。
889
+ */
890
+ followCanvas?: boolean | undefined;
891
+ }
892
+ /** 连接端口定义。 */
893
+ interface NodePortSchema {
894
+ /** 端口唯一 ID(同一节点内不可重复)。 */
895
+ id: string;
896
+ /** 显示名。 */
897
+ label?: string | undefined;
898
+ /** 最大连接数(缺省不限)。 */
899
+ maxConnections?: number | undefined;
900
+ /**
901
+ * 端口的**语义类型**(画布连线校验读它;缺省 = 无约束)。
902
+ *
903
+ * - `'value'`:普通值端口(绝大多数输入/输出口);
904
+ * - `'branch'`:分支出口(条件节点的 true/false)—— 目标是普通输入口,可喂给任意下游。
905
+ *
906
+ * 【为什么两端都缺省时放行】校验只拦「两端都声明了且冲突」的情形:
907
+ * 绝大多数节点没有类型概念,若把「未声明」当成不匹配,会把所有既有连线判死。
908
+ *
909
+ * 【为什么不再有 `'body'`】它只为迭代节点的循环体出口存在;迭代节点已被
910
+ * 「执行模式 `each` + 条目路由」取代(见 `item-flow.spec.md` 范围 H)。
911
+ */
912
+ dataType?: NodePortDataType | undefined;
913
+ }
914
+ /**
915
+ * 端口语义类型(见 {@link NodePortSchema.dataType})。
916
+ *
917
+ * 刻意与 {@link PropertyType}(字段值类型)分开:那是**数据**的类型,这是**连线语义**的类型;
918
+ * 两者混用会让「字符串口」与「分支口」被判成不兼容。
919
+ */
920
+ type NodePortDataType = 'value' | 'branch';
921
+ /**
922
+ * 节点元信息。
923
+ *
924
+ * 【为什么不提供「按类型配色」】这里曾经有一个 `color?: string`,但零生产者(没有任何定义填它)
925
+ * 且零消费者(画布从不读)。消费方画布的设计语言是「暖灰近乎单色 + 全局一个饱和色」,且
926
+ * **运行态是画布上唯一的饱和元素** —— 再给十几种节点各配一色会和「运行态最响」正面冲突。
927
+ * 节点的类型识别由 `icon` 承担;要加「类型色」之前请先改那条语言。
928
+ */
929
+ interface NodeMetaSchema {
930
+ /** 标签。 */
931
+ label: string;
932
+ /** 描述。 */
933
+ description?: string | undefined;
934
+ /** 图标(统一字符串:emoji / 内置图标名)。 */
935
+ icon: string;
936
+ /** 分类。 */
937
+ category: string;
938
+ }
939
+ /**
940
+ * 节点声明的一个**输出字段**(下游写 `{{节点名.<name>}}` 时引用的名字)。
941
+ *
942
+ * 【为什么要声明】没有声明时,任何 `{{节点名.<任意字段>}}` 都会被静默解释为空值
943
+ * (引用路径缺失 → null),断引用只能靠用户肉眼发现。声明之后,字段级
944
+ * 引用校验、画布补全与**变量选择器**都有据可依(名单由定义文件给出,不是运行时猜的)。
945
+ *
946
+ * 【item 流下的要求变了】改造前「产出是一个字符串的节点」声明空字段域
947
+ * (`NO_OUTPUT_FIELDS`),因为那时值就是标量本身。现在 `Item.json` 必须是对象,
948
+ * 标量产出统一包进一个声明字段(`prompt` / `llm` / `agent` → `text`,`command` → `stdout`,
949
+ * `workflow` → `result`,`output` → `value`)—— 于是**每个节点都必须声明它的真实字段**,
950
+ * 声明与运行期形状的一致性是硬要求。
951
+ */
952
+ interface NodeOutputVar {
953
+ /** 字段名(`{{节点名.<name>}}` 的第二段)。 */
954
+ name: string;
955
+ /**
956
+ * 字段类型(供字段级引用校验与展示)。
957
+ *
958
+ * 缺省 = **类型不可知**(表单节点里由用户自定、只有值没有字段 schema 的键)。
959
+ * 校验据此放行继续下钻(宁可漏报,不可把合法引用判死)。
960
+ */
961
+ type?: PropertyType | undefined;
962
+ /** 说明(i18n key 或原文;供画布引用提示展示)。 */
963
+ description?: string | undefined;
964
+ /**
965
+ * 子字段(`object` 时;缺省 = 形状未知,允许继续下钻)。
966
+ *
967
+ * `array` 特例:字段取值不支持下标(读到数组即止),故 `array` 类型**不允许下钻** ——
968
+ * 校验与求值在这里必须一致,否则会放行一个运行期恒为 null 的引用。
969
+ */
970
+ children?: NodeOutputVar[] | undefined;
971
+ }
972
+ /**
973
+ * 定义对「本节点产出值」的声明 —— 四种形态,决定字段级引用校验能把话说多硬。
974
+ *
975
+ * - `readonly NodeOutputVar[]`:**枚举全部可寻址字段**(空数组 = 产出是整值,没有任何可寻址字段)。
976
+ * 引用未列出的字段 → 设计期报错。
977
+ * - `'formFields'`:字段由节点 data **动态派生**(表单节点的字段是用户在设计器里定的)。
978
+ * - `'passthrough'`:产出 = 原样透传上游值(审批是数据通道)。字段域沿**唯一入边**继续解析;
979
+ * 多于一条入边时按 `'unknown'` 处理(静态不猜,因为哪条边活跃取决于运行期分支)。
980
+ * - `'unknown'`:形状**不可静态知晓**(脚本 `data`、子工作流的 Output、模板解析结果)。
981
+ * 字段级校验整体跳过,但仍做节点级校验(存在 / 非自引用 / 上游可达)。
982
+ *
983
+ * 内置定义必须显式选一种(守卫测试拒绝遗漏):省略等于「没人想过这件事」,而省略会让
984
+ * 字段级校验静默变弱 —— 正是 W5 要消灭的那类静默。
985
+ */
986
+ type NodeOutputDeclaration = readonly NodeOutputVar[] | 'formFields' | 'passthrough' | 'unknown';
987
+ /**
988
+ * 解析之后的**产出字段域**:{@link NodeOutputDeclaration} 去掉 `'formFields'`
989
+ * (它已被展开成具体字段清单)。
990
+ *
991
+ * 校验与画布补全读这个形态 —— 它们不该再关心「字段是写死的还是派生的」。
992
+ */
993
+ type NodeOutputFieldDomain = readonly NodeOutputVar[] | 'passthrough' | 'unknown';
994
+ /**
995
+ * 节点的一条**运行前必填项**:该字段取到空白就报诊断(运行前 fail-fast)。
996
+ *
997
+ * 取代原先按节点类型写死的 `if / else if` 链 —— 那段链每加一种节点就要改一次校验文件。
998
+ */
999
+ /**
1000
+ * 字段条件的运算符白名单:**真源在 `script-contract`**(下层),这里复用。
1001
+ *
1002
+ * 【为什么不各写一份】`workflow` 依赖 `script-contract`(产物规则的真源在下层),共享词汇
1003
+ * 只能由下层定义。各写一份字面量联合虽然结构上相等,但两处漂移时只有运行期的
1004
+ * `typert-gen` 结构比对会发现 —— 复用同一个常量就没有这个缝。
1005
+ */
1006
+ declare const FIELD_CONDITION_OPERATORS: readonly ["eq", "not", "gte", "lte", "gt", "lt", "startsWith", "endsWith", "includes", "regex", "exists"];
1007
+ /** 字段条件运算符。 */
1008
+ type FieldConditionOperator = ScriptFieldConditionOperator;
1009
+ /**
1010
+ * 一个字段条件:`key` 取到的值是否满足 `op` / `value`(或命中 `values` 里的任一个)。
1011
+ *
1012
+ * 【为什么取代 `{ key, equals }`】原先只能「单键相等」:真实需求里「显示 A 字段」的条件常常是
1013
+ * 「B 选了这几个值之一」「C 不小于 10」「D 已填」—— 全都表达不了,只能退化成把字段常显。
1014
+ * 现在 `values` 表达「候选值之一」(OR),运算符表达比较方式,而多条条件写成一个数组即「同时满足」
1015
+ * (AND)—— 与 n8n `displayOptions` 的「多键 AND + 每键候选 OR」同构,但只保留白名单运算符。
1016
+ */
1017
+ interface FieldCondition {
1018
+ /** 判据键(支持点分路径)。 */
1019
+ key: string;
1020
+ /** 运算符(缺省 `eq`)。 */
1021
+ op?: FieldConditionOperator | undefined;
1022
+ /** 比较值(`op = exists` 时忽略)。 */
1023
+ value?: FlowDataValue | undefined;
1024
+ /** 候选值(OR):给了它就命中任一即可,此时忽略 `op` / `value`。 */
1025
+ values?: readonly FlowDataValue[] | undefined;
1026
+ }
1027
+ /**
1028
+ * 一组字段条件:**全部满足**才成立(AND)。单个条件可以只写一个对象(求值前归一成数组)。
1029
+ */
1030
+ type FieldConditionSet = FieldCondition | readonly FieldCondition[];
1031
+ interface NodeRequiredKey {
1032
+ /** 字段 key(节点 data 里的一级键)。 */
1033
+ key: string;
1034
+ /** 诊断文案(中文原文,与既有校验文案保持一致)。 */
1035
+ message: string;
1036
+ /**
1037
+ * 稳定错误码(机器可路由)。缺省由字段名派生(`blank-<kebab 字段名>`)——
1038
+ * 已经发布过的码要显式写死,否则改字段名会连带改掉对外码。
1039
+ */
1040
+ code?: string | undefined;
1041
+ /**
1042
+ * 例外条件:命中了就不再要求该字段(条件词汇与 `visibleWhen` 同一套)。
1043
+ *
1044
+ * 例:某字段在「跳过开关」打开时不要求填写。
1045
+ */
1046
+ unless?: FieldCondition | undefined;
1047
+ }
1048
+ /**
1049
+ * 宿主能力词汇:节点**依赖宿主提供什么**才能跑。
1050
+ *
1051
+ * 【为什么需要它】此前这些依赖只在执行到该节点时才被发现(`runAgentNode` 在
1052
+ * `hostDeps.agent === undefined` 时抛错、`runCommandNode` 在缺 `shell` 时抛错)——
1053
+ * 用户能在画布上摆好、配好、保存通过校验,**直到点运行才知道这个节点在当前入口跑不了**。
1054
+ * `runtime` 表达的是「怎么调度」,不是「依赖什么」;后者原先在定义里没有位置。
1055
+ *
1056
+ * 取值刻意保持极小:只列**部署/入口事实**(服务有没有组合、本次运行有没有父 agent),
1057
+ * 不列「模型支不支持图片」这类上游目录事实(那是 dsh-llm 的真相,插件不建第二套)。
1058
+ */
1059
+ type HostCapability =
1060
+ /** LLM 服务(模型调用节点)。 */
1061
+ 'llm' |
1062
+ /** 子代理服务 + 本次运行有父 agent 上下文(AI 智能体节点)。 */
1063
+ 'subagents' |
1064
+ /** shell 服务(执行命令节点)。 */
1065
+ 'shell' |
1066
+ /** 附件存储(把文件投递给模型)。 */
1067
+ 'attachments';
1068
+ /** 节点对宿主能力的一条需求。 */
1069
+ interface NodeRequirement {
1070
+ capability: HostCapability;
1071
+ /**
1072
+ * 为什么需要它(给人看的一句话,出现在缺能力的诊断里)。
1073
+ *
1074
+ * 【为什么必填】诊断要说清「缺哪个能力、为什么这个节点需要它」,否则用户只知道
1075
+ * 「跑不了」却不知道该改什么。
1076
+ */
1077
+ reason: string;
1078
+ }
1079
+ /**
1080
+ * 节点定义(可序列化形式):代码定义与 UI 定义共同收敛的结构,也是**单一注册表**的条目形状。
1081
+ *
1082
+ * 【为什么它必须是可序列化的】同一份定义要同时被宿主(运行前校验、执行分派)与客户端
1083
+ * (画布表单、属性面板、节点库)读取。任何一方私藏一份副本都会漂移 —— 所以「一处定义」
1084
+ * 只能落在这里,客户端只补它独有的东西(视图组件、异步选项解析器)。
1085
+ *
1086
+ * 【加一个节点要动什么】写一个 `node-defs/<type>.ts`,在 `node-defs/index.ts` 的数组里加一行。
1087
+ * 需要新执行逻辑时,在宿主的执行器注册表里加一条(键 = 本条 `runner`);需要自定义卡片时,
1088
+ * 在客户端的视图表里加一条。**没有 switch,没有第二处类型清单。**
1089
+ */
1090
+ interface NodeDefinitionSchema {
1091
+ /** 注册表 key(也是文档里 `data.nodeType` 的值)。 */
1092
+ type: string;
1093
+ /**
1094
+ * 定义版本(**必填**)。
1095
+ *
1096
+ * 与文档里的 `data.nodeVersion` 比对:文档声明了一个**取不到**的版本时,按最新定义解读
1097
+ * 并给出诊断(`unknown-node-version`)—— 绝不静默按错形状解释。用户显式点「同步节点」
1098
+ * 才把文档升级到当前版本(加载不改写存档)。
1099
+ *
1100
+ * 【为什么必填】缺省值会让「这份定义属于哪一版」变成推断;版本解析(表 key、比较、
1101
+ * 同步)三处都要各自兜一次底。内置定义实测都显式写了 1,必填不增加任何书写成本。
1102
+ */
1103
+ nodeVersion: number;
1104
+ meta: NodeMetaSchema;
1105
+ properties: NodePropertySchema[];
1106
+ ports: {
1107
+ inputs: NodePortSchema[];
1108
+ outputs: NodePortSchema[];
1109
+ };
1110
+ /**
1111
+ * 执行槽位:**引擎按它决定怎么调度**这个节点(缺省 `value`)。
1112
+ *
1113
+ * 【为什么在定义里而不是在引擎里按类型判】原先引擎写死了「哪些类型是脚本节点、哪个是审批、
1114
+ * 哪个是布局框」—— 每加一种节点都要动引擎,而引擎本该只认「调度语义」。现在定义声明自己的
1115
+ * 槽位,引擎读定义分派,`shared/workflow-engine` 里再没有具体节点类型的痕迹。
1116
+ */
1117
+ runtime?: NodeRuntime | undefined;
1118
+ /**
1119
+ * 该类型节点的**默认执行模式**(缺省 `'all'`):`all` = 拿到整批一次算完;
1120
+ * `each` = 引擎对输入口 `in` 的每一条跑一遍(恰好 1 进 1 出)。
1121
+ *
1122
+ * 【为什么是「默认」而不是「模式」】模式是**节点级选择**(落在 `data.execMode`,可由属性面板改),
1123
+ * 定义给的是这个类型的自然默认:模型调用与脚本天然是整批一次,条件分流天然是逐条。
1124
+ * 与 n8n 的 Code 节点同构(下拉在节点上,`Code.node.ts:217-220`),也与本仓已有的
1125
+ * 「按数据派生端口」先例同处(`nodeOutputPorts(def, data)`)。
1126
+ */
1127
+ execMode?: ExecMode | undefined;
1128
+ /**
1129
+ * 该节点的产出**就是本次运行的返回值**(缺省 false;多个时取最后一个成功执行的)。
1130
+ *
1131
+ * 【为什么在定义里】「哪个节点的产出一路带回去」原先由引擎按类型名判断 —— 加一种「产出型」
1132
+ * 节点就要改引擎。声明之后引擎只读这个标记,`shared/workflow-engine` 里不再有值节点类型名。
1133
+ */
1134
+ runOutput?: boolean | undefined;
1135
+ /**
1136
+ * 产出值声明(字段域;语义见 {@link NodeOutputDeclaration})。
1137
+ *
1138
+ * 【为什么这里不再有 `branchByValue`】改造前条件节点靠引擎按一个布尔值选出口,于是需要
1139
+ * 「哪个值对应哪个句柄」的声明。现在条件节点在 `each` 模式下**自己把条目路由**到
1140
+ * `true` / `false` 输出口(见 `condition.ts` 的定义注释),分支选择不再是引擎的词汇。
1141
+ */
1142
+ outputVars?: NodeOutputDeclaration | undefined;
1143
+ /**
1144
+ * 本节点的产出里**哪些字段可作业务键**(台账跳过式重试的键候选;缺省 = 没有可用键)。
1145
+ *
1146
+ * 【为什么键由产出方声明而不是运行期猜】业务键是**业务事实**:「哪一列是这条数据的身份」
1147
+ * 只有产出它的节点知道。运行期猜(例如「取叫 `id` 的字段」)会在没有 `id` 的批次上静默
1148
+ * 退化成别的字段,而按错误的键跳过是**静默少做事** —— 比不重试更糟。
1149
+ *
1150
+ * 【为什么分两层:候选在这里、选用在节点上】候选是**节点类型的性质**(这种节点产出的形状
1151
+ * 里哪些字段能当键),选用是**这一份文档的选择**(落在 `data.keyField`)。换脚本 / 换数据
1152
+ * 只需在画布上选一下,不必改代码 —— 这正是用户要的「重试设置」形态。
1153
+ *
1154
+ * 【与 {@link NodeOutputDeclaration} 的分工】`outputVars` 描述产出的**全部**字段(引用校验用),
1155
+ * 本字段只挑出**可当键**的那些。两者不可互相推导:数组字段通常不能当键,而可作为键的
1156
+ * 字段也不一定需要被下游引用。
1157
+ */
1158
+ keyFieldCandidates?: readonly string[] | undefined;
1159
+ /**
1160
+ * 本节点的一次执行**开启一个新批次**(缺省 false)。
1161
+ *
1162
+ * 【为什么需要这个声明】「这批数据的账挂在哪个批次上」必须有个来源。否则引擎要靠节点类型名
1163
+ * 判断(那是它一贯避免的事:`shared/workflow-engine` 里不该出现具体节点类型的痕迹),
1164
+ * 或者靠「哪一步产出多于一条」猜(`merge` 也会产出两条,但那不是一批新的业务数据)。
1165
+ *
1166
+ * 【为什么由产出方声明】批次的边界是「一个节点的输出口上的一次产出」,只有它自己知道
1167
+ * 这次产出是不是一批有业务身份的数据。
1168
+ */
1169
+ startsBatch?: boolean | undefined;
1170
+ /** 运行前空白必填项(取代按类型写死的校验分支)。 */
1171
+ requiredKeys?: NodeRequiredKey[] | undefined;
1172
+ /**
1173
+ * 宿主能力需求:缺了它,这张图在**当前环境 / 入口**不可运行(运行前报错,不等到执行到该节点)。
1174
+ *
1175
+ * 【为什么是「环境事实」而不是「配置错误」】用户填错了字段有 `requiredKeys` 兜(诊断文案教他改);
1176
+ * 而「部署没组合 shell 服务」不是用户能改的,必须说清是环境缺什么。
1177
+ * 【为什么没有 `when`(条件豁免)】真需要条件的只有「带了文件才需要附件存储」这一种,
1178
+ * 而它依赖字段条件求值(本轮未引入)。当下把 `attachments` 留作**运行期**判据(带了文件而缺服务时
1179
+ * 明确报错),不写进 `requires` —— 否则纯文本调用会被误判成不可运行。
1180
+ */
1181
+ requires?: NodeRequirement[] | undefined;
1182
+ /**
1183
+ * 本类型节点**用不上**的节点级设置(按 `property.key` 声明;缺省 = 全都适用)。
1184
+ *
1185
+ * 【为什么需要它】节点级设置是统一追加的(见 `node-defs/index.ts` 的 `withNodeSettings`),
1186
+ * 而其中一些对个别节点没有意义:引擎写死了**审批恒定不重试**
1187
+ * (`workflow-engine/src/engine.ts` 的 `NO_RETRY`)—— 给审批节点显示「失败重试次数」,
1188
+ * 用户填了不生效,是界面在撒谎。这类判断**只有该节点的定义知道**。
1189
+ *
1190
+ * 【为什么不做成「声明我适用哪些」】那样 14 个定义要各列一遍,而其中 13 个的答案是「全都适用」
1191
+ * —— 默认值与多数情形相反,每次改设置清单都要碰一大片文件。声明**例外**只需一处。
1192
+ *
1193
+ * 【与「有没有数据入口」的分工】没有 `in` 口的节点由端口自动摘掉逐条参数组
1194
+ * (判据从定义自己的端口算,见 `settingsFor`),不必在这里重复声明 ——
1195
+ * 本字段只用于端口判据盖不到的情形。
1196
+ */
1197
+ excludesSettings?: readonly string[] | undefined;
1198
+ }
1199
+ /**
1200
+ * 节点的**执行模式**:引擎按它对一个节点的输入批做什么。
1201
+ *
1202
+ * - `all`(缺省):拿到**整批**,执行器一次算完(表单 / HTTP / 脚本 / 调用工作流 / 模型调用 / Merge);
1203
+ * - `each`:引擎对输入口 `in` 的**每一条**跑一遍执行器,且**恰好 1 进 1 出**
1204
+ * (产出多于 1 条即报错,不静默丢弃)—— 图片识别、逐条翻译、条件分流、批处理脚本。
1205
+ *
1206
+ * 参照 n8n 的 `runOnceForAllItems` / `runOnceForEachItem`(`nodes/Code/Code.node.ts:217-220`):
1207
+ * 「批量」在那边也不是节点类型,而是执行模式。
1208
+ */
1209
+ type ExecMode = 'all' | 'each';
1210
+ /**
1211
+ * 节点的**执行槽位**:引擎按它分派调度方式。
1212
+ *
1213
+ * - `value`(缺省):值节点 —— 走宿主的值节点执行器,不限并发;
1214
+ * - `script`:脚本节点 —— 走脚本执行器,受 `maxConcurrency` 限流;
1215
+ * - `approval`:人工审批屏障 —— 引擎内置(串行等待 + `run-need-approval` 事件);
1216
+ * - `layout`:画布组织手段(分组框)—— **不执行**,也不进运行步骤。
1217
+ *
1218
+ * 【为什么不再有 `iteration`】「批量」已由 {@link ExecMode} 的 `each` 表达,
1219
+ * 不再需要一个「节点类型 + 内联子运行」的容器(见 `item-flow.spec.md` 范围 H)。
1220
+ */
1221
+ type NodeRuntime = 'value' | 'script' | 'approval' | 'layout';
1222
+ /** 运行开始事件。 */
1223
+ interface RunStartEvent {
1224
+ runId: string;
1225
+ nodeCount: number;
1226
+ }
1227
+ /** 节点生命周期事件的公共字段。 */
1228
+ interface RunNodeEvent {
1229
+ runId: string;
1230
+ nodeId: string;
1231
+ nodeType: string;
1232
+ label: string;
1233
+ }
1234
+ /** 节点结算事件(成功带批的规模,失败带 error)。 */
1235
+ interface RunNodeResultEvent extends RunNodeEvent {
1236
+ ok: boolean;
1237
+ /**
1238
+ * 本步骤各输入口 / 输出口的条目数。
1239
+ *
1240
+ * 【为什么不是产出值】一批可能上万条,事件载荷走宿主事件总线与运行面板,塞整批会把它压死;
1241
+ * 样本从运行快照(`RunStep.items`)读,事件只报规模。
1242
+ */
1243
+ itemCounts?: {
1244
+ in: Record<string, number>;
1245
+ out: Record<string, number>;
1246
+ } | undefined;
1247
+ error?: string | undefined;
1248
+ }
1249
+ /** 运行暂停、等待人工审批(可恢复点)。 */
1250
+ interface RunNeedApprovalEvent {
1251
+ runId: string;
1252
+ nodeId: string;
1253
+ label: string;
1254
+ }
1255
+ /** 运行终止事件。 */
1256
+ /**
1257
+ * 运行**终态**集合:`run-end` 的 `stopReason` 只能取自这里。
1258
+ *
1259
+ * 【为什么单独成一个常量而不是把字面量写进接口】`RunState['status']` 是 6 值联合
1260
+ * (多出 `running` / `waiting` / `crashed`),而一次运行**结束时**只可能是这三者之一:
1261
+ * 引擎的三个出口分别置 `completed` / `failed` / `cancelled`;`waiting` 期间
1262
+ * `runInner` 根本不返回(审批等待是 `await`),`crashed` 由宿主扫尾赋值、不经 `run()`。
1263
+ * 把这三值提成常量,是为了让**发射点**能显式取它 —— 先前那里直接写 `stopReason: state.status`,
1264
+ * 类型上把 6 值塞进 3 值(`emit` 通道是 `unknown`,编译器不拦),类型与事实脱节。
1265
+ */
1266
+ declare const RUN_TERMINAL_STATUSES: readonly ["completed", "failed", "cancelled"];
1267
+ /** 运行终态({@link RUN_TERMINAL_STATUSES} 的成员)。 */
1268
+ type RunTerminalStatus = (typeof RUN_TERMINAL_STATUSES)[number];
1269
+ /** 运行结束事件(终态)。 */
1270
+ interface RunEndEvent {
1271
+ runId: string;
1272
+ stopReason: RunTerminalStatus;
1273
+ result?: FlowDataValue | undefined;
1274
+ }
1275
+ //#endregion
1276
+ //#region src/types.d.ts
1277
+ /**
1278
+ * @Remote 错误码登记:错误码是**对外契约**的一部分,而 typert 的 codec 要求它们出现在
1279
+ * `RemoteErrorDetailsMap` 里(否则 `new RemoteError('autoFlow/…')` 通不过类型检查)。
1280
+ */
1281
+ declare module '@deepseek-ai/dsh-typert-protocol' {
1282
+ interface RemoteErrorDetailsMap {
1283
+ /** 运行工作流但未组合 dsh-ag 插件。 */
1284
+ 'autoFlow/ag-missing': {};
1285
+ /** 运行不存在或已结束。 */
1286
+ 'autoFlow/run-not-found': {};
1287
+ /** 运行前校验失败:工作流存在环 / 条件分支缺句柄 / 必填参数缺失 / 变量引用非法等。 */
1288
+ 'autoFlow/invalid-flow': {
1289
+ readonly diagnostics: readonly FlowDiagnostic[];
1290
+ };
1291
+ /**
1292
+ * 恢复运行被拒:没有磁盘现场 / 不是可恢复的暂停态(已崩溃或已收尾)/ 没有待答复的暂停。
1293
+ *
1294
+ * 与 `run-not-found` 分开是有用的:前者是「它不该被恢复」(该去看历史里的结局),
1295
+ * 后者是「根本不知道这个 runId」(纯粹传错了)。
1296
+ */
1297
+ 'autoFlow/run-not-resumable': {
1298
+ readonly reason: string;
1299
+ };
1300
+ /** 暂停已**全局超时**(部署级时限已过):该运行永远不会恢复。 */
1301
+ 'autoFlow/pause-expired': {
1302
+ readonly pauseId: string;
1303
+ };
1304
+ /** 该暂停已被答复过:暂停实体单次可用,不能重复消费。 */
1305
+ 'autoFlow/pause-consumed': {
1306
+ readonly pauseId: string;
1307
+ };
1308
+ /**
1309
+ * 重试被拒:这次运行里**没有批次**(数据没经过「展开列表」节点),或对应工作流已被删除。
1310
+ *
1311
+ * 与 `run-not-resumable` 分开:那一个是「暂停现场的问题」(去历史里看结局就行),
1312
+ * 这一个是「这次运行压根没有按业务键补做的前提」。
1313
+ */
1314
+ 'autoFlow/run-not-retryable': {
1315
+ readonly runId?: string;
1316
+ readonly workflowId?: string;
1317
+ };
1318
+ /**
1319
+ * 重试被拒:本批数据**没有业务键**(展开列表节点没选「业务键」字段)。
1320
+ *
1321
+ * 【为什么必须单独一个码、且明确报出】没有键时唯一正确的事是**如实说「不支持跳过式重试」**;
1322
+ * 静默退化成按下标跳过是静默少做事(剪掉了没做过的、重做了做过的),比不重试更糟。
1323
+ */
1324
+ 'autoFlow/batch-not-retryable': {
1325
+ readonly runId: string;
1326
+ readonly batchId: string;
1327
+ readonly nodeId: string;
1328
+ };
1329
+ }
1330
+ }
1331
+ declare module '@deepseek-ai/cordis' {
1332
+ interface Events {
1333
+ 'auto-flow/run-start'(data: RunStartEvent): void;
1334
+ 'auto-flow/node-start'(data: RunNodeEvent): void;
1335
+ 'auto-flow/node-result'(data: RunNodeResultEvent): void;
1336
+ 'auto-flow/run-need-approval'(data: RunNeedApprovalEvent): void;
1337
+ 'auto-flow/run-end'(data: RunEndEvent): void;
1338
+ }
1339
+ }
1340
+ /**
1341
+ * 一次「重试未成功项」的**回执**:新运行 id + 这次重试的计划摘要。
1342
+ *
1343
+ * 【为什么是宿主与客户端共用的词汇】它是 `@Remote('retryRun')` 的返回形状:
1344
+ * 宿主生产它、客户端展示它,两处必须读同一份声明(否则界面按旧形状解读会静默显示错数字)。
1345
+ * 放在本文件的理由与其它 `@Remote` 契约一样 —— 这里是「本插件自己的对外契约」的归属地。
1346
+ *
1347
+ * 【为什么把计划也回给客户端】用户点了按钮要立刻知道「补做几条 / 跳不跳已成功的」——
1348
+ * 等运行结束再从 trace 里推断是事后诸葛。
1349
+ */
1350
+ interface RetryPlanSummary {
1351
+ /** 本次补做所属的批次。 */
1352
+ batchId: string;
1353
+ /** 本批的业务键字段。 */
1354
+ keyField: string;
1355
+ /** 生效的跳过策略。 */
1356
+ resumeMode: ResumeMode;
1357
+ /** 台账里未成功的条目数(本次要补的条数)。 */
1358
+ pendingCount: number;
1359
+ /** 台账里已知的条目总数(含已成功)。 */
1360
+ knownCount: number;
1361
+ }
1362
+ /**
1363
+ * `@Remote('unitUrl')` 的返回形状(应用单元入口结果)。
1364
+ *
1365
+ * 【为什么形状里没有单元名】入口有几个(会话面板里一颗按钮一个),但"取某个单元的入口"这件事的
1366
+ * 结果形状是同一个:`unitUrl(unit)` 的入参指明问的是谁,返回值只说它此刻能不能服务。
1367
+ *
1368
+ * 【为什么在这里再写一遍而不是直接用 `app-unit` 的 `AppUnitEntry`】远处那条形状是**运行时**的产物
1369
+ * (`mountAppUnits` 的返回值),而这里是**线上契约**:`@Remote` 签名里出现的类型必须由本包自己的类型
1370
+ * 入口导出,否则 typert 生成器写不出那条 import(它按类型的归属模块决定 import,遇到 `app-unit` 这种
1371
+ * 不是依赖的裸包名时会**丢掉**那条 import —— 生成的 `.d.ts` 于是引用一个没导入的名字)。
1372
+ *
1373
+ * 【两处不会漂】装配点把 `app-unit` 的结果**逐个字段投影**到这个形状(见 `host/service.ts` 的
1374
+ * `unitUrl`):那边改了字段名或类型,这里当场编译失败,而不是静默发出一个旧契约。
1375
+ */
1376
+ interface UnitEntryResult {
1377
+ /** 单元入口地址(带进程令牌的跨源 URL);给不出时为 `null`。 */
1378
+ url: string | null;
1379
+ /**
1380
+ * `url` 为 `null` 时的原因(给面板显示,含处置命令:收产物 / 开反代);给得出地址时不出现。
1381
+ *
1382
+ * 【为什么可空而不是必有】成功那一档不需要原因;让契约里恒有一个空字符串只会让"到底有没有原因"
1383
+ * 变成读的人自己判断。
1384
+ */
1385
+ reason?: string;
1386
+ }
1387
+ //#endregion
1388
+ export { RunTerminalStatus as $, NodeOutputFieldDomain as A, PropertyPlacement as B, LedgerEntry as C, NodeLogEntry as D, NodeDefinitionSchema as E, NodeRequiredKey as F, RunEndEvent as G, RESUME_MODES as H, NodeRequirement as I, RunNodeResultEvent as J, RunNeedApprovalEvent as K, NodeRuntime as L, NodePortDataType as M, NodePortSchema as N, NodeMetaSchema as O, NodePropertySchema as P, RunSummary as Q, PortData as R, Item as S, NodeAttempt as T, RUN_TERMINAL_STATUSES as U, PropertyType as V, ResumeMode as W, RunState as X, RunStartEvent as Y, RunStep as Z, FlowNode as _, ScriptResult as _t, DEFAULT_RESUME_MODE as a, StringFormat as at, FlowValidationResult as b, FIELD_CONDITION_OPERATORS as c, WorkflowFile as ct, FieldConditionSet as d, WorkflowListRouteModelsResult as dt, ScriptDiagnosticInfo as et, FlowDataValue as f, WorkflowListRunsRequest as ft, FlowGraph as g, ScriptPayload as gt, FlowEdgeType as h, ScriptArtifactSpec as ht, Batch as i, SelectOption as it, NodeOutputVar as j, NodeOutputDeclaration as k, FieldCondition as l, WorkflowListLlmRoutesResult as lt, FlowEdge as m, WorkflowRouteModel as mt, UnitEntryResult as n, ScriptParamField as nt, DatetimeFormat as o, WORKFLOW_FORMAT_VERSION as ot, FlowDiagnostic as p, WorkflowLlmRoute as pt, RunNodeEvent as q, ApprovalOption as r, ScriptSourceInfo as rt, ExecMode as s, WorkflowDefinition as st, RetryPlanSummary as t, ScriptInfo as tt, FieldConditionOperator as u, WorkflowListRouteModelsRequest as ut, FlowNodeData as v, ScriptRunOptions as vt, LedgerStatus as w, HostCapability as x, FlowSummary as y, PortProjection as z };
1389
+ //# sourceMappingURL=types-B5z-T_7S.d.ts.map