@xtalpi/agentic-lab-skills 0.0.9 → 0.0.10
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.
- package/package.json +1 -1
- package/skills/lab-flow-designer/SKILL.md +30 -11
- package/skills/lab-flow-designer/embedded-template/SKILL.md +16 -1
- package/skills/lab-flow-designer/embedded-template/pools//345/205/245/345/217/243/346/261/240.md +9 -0
- package/skills/lab-flow-designer/embedded-template/pools//345/207/272/345/217/243/346/261/240.md +9 -0
- package/skills/lab-flow-designer/embedded-template/valves//347/244/272/344/276/213/346/225/260/346/215/256/344/270/216/346/240/241/351/252/214/351/227/250/346/216/247.md +15 -0
- package/skills/lab-flow-designer/references/agentic-lab-processer.md +46 -2
- package/skills/lab-flow-designer/references/skill-package-layout.md +65 -1
- package/skills/lab-flow-designer/references//344/270/232/345/212/241/346/265/201/347/250/213/346/226/207/346/241/243/346/240/207/345/207/206.md +12 -4
- package/skills/lab-flow-designer/templates//344/270/232/345/212/241/346/265/201/347/250/213/346/226/207/346/241/243/346/250/241/346/235/277.md +24 -1
- package/skills/lab-flow-designer/templates//344/270/232/345/212/241/346/265/201/347/250/213/346/226/207/346/241/243/347/244/272/344/276/213.md +10 -0
- package/skills/lab-flow-designer/testing/test-processer.mjs +330 -165
package/package.json
CHANGED
|
@@ -4,7 +4,7 @@ description: >-
|
|
|
4
4
|
根据流程说明 Markdown 初次生成流程注册包时,须先按 references/业务流程文档标准.md 做合规预检,不通过则不得写入产出包并须输出优化建议;对已存在的流程注册包做增量修改时跳过流程文档预检。
|
|
5
5
|
也可根据用户描述生成符合规范的业务流程文档模板,供用户完善后用于流程注册包生成。
|
|
6
6
|
产出与本 skill 内 embedded-template、references/skill-package-layout.md 版式同构;根 SKILL.md 须含概述、核心概念、流程图(Mermaid 池为矩形、门控为六边形)、连接关系、节点清单、门控执行规范、使用方式。
|
|
7
|
-
Use when scaffolding flow skills, valve scripts from gate YAML and compound KB rules, Processer start/complete from pipeline docs, or generating flow document templates.
|
|
7
|
+
Use when scaffolding flow skills, valve scripts from gate YAML and compound KB rules, Processer start/complete/run from pipeline docs, or generating flow document templates.
|
|
8
8
|
license: Proprietary
|
|
9
9
|
metadata:
|
|
10
10
|
embedded-template-dir: embedded-template
|
|
@@ -38,7 +38,7 @@ metadata:
|
|
|
38
38
|
| **极简目录范例(优先打开)** | 本 skill 内 [`embedded-template/`](embedded-template/):2 池 + 1 门控;[`embedded-template/valves/示例数据与校验门控.md`](embedded-template/valves/示例数据与校验门控.md) 与 [`embedded-template/scripts/示例数据与校验门控.js`](embedded-template/scripts/示例数据与校验门控.js) 含「数据查询 / 映射 / 规则」与 SDK 对齐的完整参考 |
|
|
39
39
|
| **版式条文** | [references/skill-package-layout.md](references/skill-package-layout.md):目录约定、根 `SKILL.md` 各块格式、`pools`/`valves` 文字范例摘录 |
|
|
40
40
|
| 门控脚本 API | [references/agentic-lab-sdk.md](references/agentic-lab-sdk.md)(生成 `scripts/*.js` 前必读) |
|
|
41
|
-
| 门控脚本规范 | [references/agentic-lab-processer.md](references/agentic-lab-processer.md):`Processer` 类的 `start`/`complete`
|
|
41
|
+
| 门控脚本规范 | [references/agentic-lab-processer.md](references/agentic-lab-processer.md):`Processer` 类的 `start`/`complete`(人工触发)与 `run`(自动触发)输入输出类型定义与代码风格参考(参数与返回值均为 **snake_case**) |
|
|
42
42
|
| **业务流程文档模板** | [templates/业务流程文档模板.md](templates/业务流程文档模板.md):空白模板,各章节带占位提示;用于「生成流程文档」模式 |
|
|
43
43
|
| **业务流程文档示例** | [templates/业务流程文档示例.md](templates/业务流程文档示例.md):基于 Fragment 分装流程的填写示例,供参考 |
|
|
44
44
|
|
|
@@ -121,6 +121,8 @@ metadata:
|
|
|
121
121
|
- 规则摘要表(条件要点 + 业务动作),落入 `complete` 函数体
|
|
122
122
|
- 提交参数结构(如 SDK 调用方法的参数定义与键级说明)
|
|
123
123
|
- 出口池路由条件与字段映射
|
|
124
|
+
- **触发方式**:每个门控的 `### 触发方式`(`自动触发` 或 `人工触发`)。自动触发门控无人工处理阶段,脚本实现 `run`(见 [references/agentic-lab-processer.md](references/agentic-lab-processer.md));人工触发门控保持 `start` + `complete`。
|
|
125
|
+
- **模型引用**(若有):数据池或门控的 `### 模型引用`(模型名称与版本),写入对应 `pools/*.md` 或 `valves/*.md` 的 `## 模型引用` 章节;无模型引用时省略。
|
|
124
126
|
- **门控脚本配置项**:提取全局配置表中的各项,落入脚本为**模块级常量**:
|
|
125
127
|
- `PageUrl`(含 `{bookid}` 占位符)→ 脚本常量 `PAGE_URL`
|
|
126
128
|
- `StationBaseURL`(若有)→ 脚本常量 `STATION_BASE_URL`
|
|
@@ -160,7 +162,7 @@ metadata:
|
|
|
160
162
|
固定两个三级标题,正文可结合本流程改写,语义须与模板一致:
|
|
161
163
|
|
|
162
164
|
- **`### 数据池(Pool)`**:说明 Pool 存记录、每池有独立 Schema、随阶段变化。
|
|
163
|
-
- **`### 门控(Valve)`**:说明每个 Valve 对应脚本、`Processer` 的 `start` / `complete`
|
|
165
|
+
- **`### 门控(Valve)`**:说明每个 Valve 对应脚本、`Processer` 的 `start` / `complete`(人工触发)或 `run`(自动触发)职责分工。
|
|
164
166
|
另起简短列表说明执行引擎通过 `this.context` 暴露的能力,**仅写** [references/agentic-lab-sdk.md](references/agentic-lab-sdk.md)「可用方法一览」中已列出的方法名(按流程实际使用选取),**不要**抄写模板里已过期的成员名。
|
|
165
167
|
|
|
166
168
|
#### 3.4 `## 流程图`(本节内须含「连接关系」列表,与模板一致)
|
|
@@ -214,7 +216,7 @@ metadata:
|
|
|
214
216
|
|
|
215
217
|
#### 3.6 `## 门控执行规范`
|
|
216
218
|
|
|
217
|
-
`Processer` 代码块**原文照抄** [references/skill-package-layout.md](references/skill-package-layout.md) §2.5
|
|
219
|
+
`Processer` 代码块**原文照抄** [references/skill-package-layout.md](references/skill-package-layout.md) §2.5:人工触发门控使用 `constructor`、`async start`、`async complete`;自动触发门控使用 `constructor`、`async run`;不含导出语句。不在此节展开 `context` 各方法签名(见 [references/agentic-lab-sdk.md](references/agentic-lab-sdk.md))。
|
|
218
220
|
|
|
219
221
|
#### 3.7 `## 使用方式`
|
|
220
222
|
|
|
@@ -231,13 +233,16 @@ metadata:
|
|
|
231
233
|
- **字段标题**:与流程说明或业务系统一致的**展示名**(可为英文短语如 `Compound ID`,或简短中文标题),供人读表与 UI 列头;**不要**把原「字段」列的英文混进「字段」列。
|
|
232
234
|
- **字段描述**、**字段类型**、**属性**、**默认值**:规则同前(属性仍仅写枚举/格式归纳,无则留空)。
|
|
233
235
|
- **属性列**:仅当流程说明对该字段给出了**可选值/枚举**(含「枚举:…」「如 A、B」、用顿号/逗号分隔的取值列表等)时,将归纳后的约束写入本列,**建议**以 `枚举:` 开头(例:`枚举:固体颗粒过大,流动性差,强吸水性,易结块,易粘黏,强静电吸附,液体`)。若说明仅在「说明」列内嵌枚举长句,可将枚举部分抽到「属性」,「字段描述」保留简短业务含义。若说明中约定了**日期或时间的表达格式**(如 ISO8601、`YYYYMMDD`、批次号中的日期段),以 `格式:` 开头写入本列。无枚举、无格式约定则**属性列留空**(不写 `-` 占位语)。业务含义仍以「字段描述」为主,避免在描述与属性中完全重复粘贴同一段长文。
|
|
236
|
+
- **模型引用**(若流程说明为该池指定了 3D 模型):在 `## Schema` 之后添加 `## 模型引用`,含模型名称与版本(格式见 [references/skill-package-layout.md](references/skill-package-layout.md) §3)。无模型引用时省略。
|
|
234
237
|
|
|
235
238
|
### 5. 写入每个 `valves/<基名>.md`
|
|
236
239
|
|
|
237
|
-
章节与排版对齐抽取文档 §4,**至少**包含:`## 概述`(编号步骤)、`## 关联脚本`、`## 执行流程`、`## 输入/输出`。`输入/输出` 须与门控 YAML 的 `primary`/`secondary` 与各输出池**显示名称**一致,**勿**照搬范例中的池名。
|
|
240
|
+
章节与排版对齐抽取文档 §4,**至少**包含:`## 概述`(编号步骤)、`## 关联脚本`、`## 触发方式`、`## 执行流程`、`## 输入/输出`。`输入/输出` 须与门控 YAML 的 `primary`/`secondary` 与各输出池**显示名称**一致,**勿**照搬范例中的池名。
|
|
238
241
|
|
|
239
242
|
若流程说明对某门控给出了下列块,须在对应 `valves/<基名>.md` 中**原样结构化呈现**(标题可用 `##` / `###`,便于脚本作者对照):
|
|
240
243
|
|
|
244
|
+
- **触发方式**:`## 触发方式`,取值 `自动触发` 或 `人工触发`,置于 `## 关联脚本` 之后。
|
|
245
|
+
- **模型引用**(若流程说明指定了模型):`## 模型引用`,含模型名称与版本。无模型引用时省略。
|
|
241
246
|
- **门控 YAML**(`valve_id`、`name`、`order`、`input`、`output`);若文档使用 `Stash:` 等非标准键表示目标池,**保留原文**,并在 valve 文内加一句说明:实现时按 [agentic-lab-sdk.md](references/agentic-lab-sdk.md) 获取出口池,以返回的 `pool.name` 与**池显示名称**匹配。
|
|
242
247
|
- **前置处理**(对应 `start`):提取配置项(如 `bookid`)、规则摘要表,说明 `start` 函数需执行的逻辑。
|
|
243
248
|
- **人工处理**:界面形态与数据绑定描述(仅供参考,不由门控脚本实现)。
|
|
@@ -250,8 +255,9 @@ metadata:
|
|
|
250
255
|
|
|
251
256
|
### 6. 写入每个 `scripts/<基名>.js`
|
|
252
257
|
|
|
253
|
-
1. **先读** [references/agentic-lab-sdk.md](references/agentic-lab-sdk.md) 与 [references/agentic-lab-processer.md](references/agentic-lab-processer.md);`this.context` **仅**使用 sdk 文件已列出的成员;`start`/`complete`
|
|
254
|
-
2. **结构**对齐 [embedded-template/scripts/示例数据与校验门控.js](embedded-template/scripts/示例数据与校验门控.js):`Processer` 类、`constructor(context)`、`async start` / `async complete
|
|
258
|
+
1. **先读** [references/agentic-lab-sdk.md](references/agentic-lab-sdk.md) 与 [references/agentic-lab-processer.md](references/agentic-lab-processer.md);`this.context` **仅**使用 sdk 文件已列出的成员;`start`/`complete`(或 `run`)的输入输出类型须对齐 processer 规范。
|
|
259
|
+
2. **结构**对齐 [embedded-template/scripts/示例数据与校验门控.js](embedded-template/scripts/示例数据与校验门控.js):`Processer` 类、`constructor(context)`、`async start` / `async complete`(人工触发)或 `async run`(自动触发);脚本以类定义结束,禁止添加任何导出语句。示例中与本门控文档无关的业务分支**省略**。
|
|
260
|
+
2.5. **触发方式分支**:读取对应 `valves/<基名>.md` 中的 `## 触发方式`。若为 **人工触发** → 脚本结构同现有(`constructor` + `start` + `complete`);若为 **自动触发** → 脚本使用 `constructor` + `run`(类型定义见 [references/agentic-lab-processer.md](references/agentic-lab-processer.md)),不含 `start` 或 `complete`。
|
|
255
261
|
3. **注释**与流程说明规则编号对应;业务分支处可用 `TODO`,**不得**编造文档未定义的池名或 API。
|
|
256
262
|
4. **实现逻辑**须遵循下文「门控脚本编写指引」(含 **`limit`/`offset` 默认全量** 约定)。
|
|
257
263
|
5. **按需生成**:详见下文「编写指引 §0」。
|
|
@@ -307,6 +313,14 @@ metadata:
|
|
|
307
313
|
|
|
308
314
|
- **`complete(params)`**(对应**后置处理**):解构 **`valve_id`** 与 **`tickets`**(snake_case 入参)→ **优先使用 `params.tickets` 作为业务数据源**(引擎已传入完整 ticket 数据,无需重新查询;仅当流程说明后置处理中**明确要求**获取额外数据或最新状态时才按需查询)→ 按流程说明后置处理中的规则执行业务逻辑(准备提交参数、按需调用 SDK 方法、判定成败等)→ 按 [agentic-lab-sdk.md](references/agentic-lab-sdk.md) 获取出口池 → 将入口池 tickets 更新为 `finished` → 按出口池条件映射 `new_tickets`(含 `flow_id`、`pool_id`、`order_id`、`detail`、`status: 'created'`、`parent_ticket_id`)→ 返回 `{ new_tickets }`(由引擎自动创建,**不**在脚本内直接追加 tickets)。
|
|
309
315
|
|
|
316
|
+
### 3.1 `run` 分工(自动触发门控专用)
|
|
317
|
+
|
|
318
|
+
自动触发门控无人工处理阶段,`run` 合并了 `start` 与 `complete` 的职责。
|
|
319
|
+
|
|
320
|
+
- **`run(params)`**:解构 **`valve_id`、`pool_ids`**(snake_case)→ 按 [agentic-lab-sdk.md](references/agentic-lab-sdk.md) 查询入口池数据 → 按需调用 SDK 方法 → 按数据处理规则更新 `ticket.detail` → 按 SDK 更新 tickets → 按 [agentic-lab-sdk.md](references/agentic-lab-sdk.md) 获取出口池 → 将入口池 tickets 更新为 `finished` → 按出口池条件映射 `new_tickets` → 返回 `{ new_tickets }`。
|
|
321
|
+
|
|
322
|
+
**与 `start` + `complete` 的区别**:`run` **不返回** `orbit_link` 或 `ticket_ids`(无人工阶段无需页面链接或 ticket 列表交互)。类型定义见 [references/agentic-lab-processer.md](references/agentic-lab-processer.md)。
|
|
323
|
+
|
|
310
324
|
### 4. 运行环境约束(沙箱可用全局对象)
|
|
311
325
|
|
|
312
326
|
门控脚本在**沙箱**中执行,**仅**以下全局对象/函数可用:
|
|
@@ -430,6 +444,8 @@ console.info(`[Processer] v${__ARTIFACT_VERSION__} (skill: ${__ARTIFACT_SKILL__}
|
|
|
430
444
|
- 一级参数/返回值 snake_case;嵌套 JSON 保持原格式(见「编写指引 §3」)
|
|
431
445
|
- `orbit_link` = `PageUrl` + `bookid`(见「编写指引 §3 orbit_link 生成步骤」)
|
|
432
446
|
- 门控三阶段:前置处理 → `start`、后置处理 → `complete`、人工处理 → UI 技能(见「解析流程说明 §1」)
|
|
447
|
+
- **触发方式**:自动触发门控脚本须实现 `run`(非 `start` + `complete`);人工触发保持 `start` + `complete`(见「编写指引 §3.1」与 [agentic-lab-processer.md](references/agentic-lab-processer.md))
|
|
448
|
+
- **模型引用**:流程说明中关联 3D 模型的池和门控,须在对应 `pools/*.md` 或 `valves/*.md` 写入 `## 模型引用`(模型名称 + 版本);无模型引用时省略
|
|
433
449
|
- `complete` 优先使用 `params.tickets`,非必要不重查(见「编写指引 §3」)
|
|
434
450
|
- 脚本以 `Processer` 类结束,禁止 `return`/`module.exports`/`export`(见「生成流程 §6」)
|
|
435
451
|
- 按需生成,禁止冗余:代码和 Schema 仅覆盖流程文档明确写出的内容(见「编写指引 §0」)
|
|
@@ -483,10 +499,11 @@ node <本skill目录>/testing/test-processer.mjs \
|
|
|
483
499
|
| 轮次 | 检查内容 | 失败级别 |
|
|
484
500
|
|------|---------|---------|
|
|
485
501
|
| **语法检查** | JavaScript 语法正确性 | 阻断 |
|
|
486
|
-
| **结构检查** | `Processer` 类存在,含 `constructor
|
|
487
|
-
| **正常轮 `start`** |
|
|
488
|
-
| **正常轮 `complete`** |
|
|
489
|
-
|
|
|
502
|
+
| **结构检查** | `Processer` 类存在,含 `constructor` + (`start`、`complete`)(人工触发)或 `run`(自动触发) | 阻断 |
|
|
503
|
+
| **正常轮 `start`** | 人工触发:正常类型 mock 数据运行,检查返回 `{ orbit_link: string, ticket_ids: number[] }` | 阻断 |
|
|
504
|
+
| **正常轮 `complete`** | 人工触发:正常类型 mock 数据运行,检查返回 `{ new_tickets: [...] }` 结构正确 | 阻断 |
|
|
505
|
+
| **正常轮 `run`** | 自动触发:正常类型 mock 数据运行,检查返回 `{ new_tickets: [...] }` 结构正确 | 阻断 |
|
|
506
|
+
| **对抗轮 `start` + `complete` / `run`** | 第 3 条 ticket 的 text 字段填为 number/null,验证类型安全编码 | 警告(不阻断) |
|
|
490
507
|
| **合规审计** | SDK 方法白名单、列表查询 `limit`/`offset`、返回值 snake_case | 未知 SDK 方法→阻断;其余→警告 |
|
|
491
508
|
|
|
492
509
|
### 结果判定与自动修复
|
|
@@ -522,6 +539,8 @@ node <本skill目录>/testing/test-processer.mjs \
|
|
|
522
539
|
| `ticket.list called without limit` | `ticket.list called without explicit limit` | 添加 `limit: DEFAULT_QUERY_LIMIT, offset: 0` |
|
|
523
540
|
| `camelCase key in return value` | `"ticketIds" is camelCase — must be snake_case` | 改为 `ticket_ids` 等 snake_case 键名 |
|
|
524
541
|
| `Processer missing start() method` | 结构检查未通过 | 确保 `Processer` 类包含 `async start(params)` 方法 |
|
|
542
|
+
| `Processer missing run() method` | 自动触发门控结构检查未通过 | 确保 `Processer` 类包含 `async run(params)` 方法,移除 `start`/`complete` |
|
|
543
|
+
| `run() new_tickets 缺必填字段` | `new_tickets[0].pool_id must be number` | 检查 run 中 new_tickets 映射逻辑,补全 `flow_id`/`pool_id`/`order_id`/`detail`/`status` |
|
|
525
544
|
|
|
526
545
|
## 脚本预览(试运行)
|
|
527
546
|
|
|
@@ -59,7 +59,7 @@ flowchart LR
|
|
|
59
59
|
|
|
60
60
|
## 门控执行规范
|
|
61
61
|
|
|
62
|
-
每个 Valve 的 JavaScript 脚本导出 `Processer` 类,必须实现 `start` / `complete
|
|
62
|
+
每个 Valve 的 JavaScript 脚本导出 `Processer` 类,必须实现 `start` / `complete`(人工触发)或 `run`(自动触发)。由流程说明生成真实流程注册包时,脚本**仅实现文档明确要求**的逻辑与字段处理,须遵循主 `SKILL.md`「门控脚本编写指引」;下方代码块仅示意类骨架。
|
|
63
63
|
|
|
64
64
|
```javascript
|
|
65
65
|
class Processer {
|
|
@@ -81,6 +81,21 @@ class Processer {
|
|
|
81
81
|
}
|
|
82
82
|
```
|
|
83
83
|
|
|
84
|
+
**自动触发门控**使用 `run` 替代 `start` + `complete`(详见 [references/agentic-lab-processer.md](../references/agentic-lab-processer.md)):
|
|
85
|
+
|
|
86
|
+
```javascript
|
|
87
|
+
class Processer {
|
|
88
|
+
constructor(context) {
|
|
89
|
+
this.context = context;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async run(params) {
|
|
93
|
+
// params: { valve_id, pool_ids }
|
|
94
|
+
// 查询入口池 → 执行业务逻辑 → 获取出口池 → 映射 new_tickets
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
84
99
|
## 使用方式
|
|
85
100
|
|
|
86
101
|
1. 查看各 Pool 的 [pools/](pools/) 目录了解数据表结构
|
package/skills/lab-flow-designer/embedded-template/pools//345/205/245/345/217/243/346/261/240.md
CHANGED
|
@@ -10,3 +10,12 @@
|
|
|
10
10
|
|------|----------|----------|----------|------|--------|
|
|
11
11
|
| id | ID | 业务主键 | string | | - |
|
|
12
12
|
| detail | Detail | 业务明细 JSON | json | | - |
|
|
13
|
+
|
|
14
|
+
## 模型引用
|
|
15
|
+
|
|
16
|
+
> 本池无关联 3D 模型,本节仅作版式示意。实际生成时,无模型引用的池省略本节。
|
|
17
|
+
|
|
18
|
+
| 属性 | 值 |
|
|
19
|
+
|------|-----|
|
|
20
|
+
| 名称 | example-pool-model |
|
|
21
|
+
| 版本 | 1.0.0 |
|
package/skills/lab-flow-designer/embedded-template/pools//345/207/272/345/217/243/346/261/240.md
CHANGED
|
@@ -10,3 +10,12 @@
|
|
|
10
10
|
|------|----------|----------|----------|------|--------|
|
|
11
11
|
| id | ID | 业务主键 | string | | - |
|
|
12
12
|
| detail | Detail | 业务明细 JSON | json | | - |
|
|
13
|
+
|
|
14
|
+
## 模型引用
|
|
15
|
+
|
|
16
|
+
> 本池无关联 3D 模型,本节仅作版式示意。实际生成时,无模型引用的池省略本节。
|
|
17
|
+
|
|
18
|
+
| 属性 | 值 |
|
|
19
|
+
|------|-----|
|
|
20
|
+
| 名称 | example-pool-model |
|
|
21
|
+
| 版本 | 1.0.0 |
|
|
@@ -10,6 +10,21 @@
|
|
|
10
10
|
|
|
11
11
|
该脚本导出 `Processer` 类,实现 `start` 和 `complete` 方法。
|
|
12
12
|
|
|
13
|
+
## 触发方式
|
|
14
|
+
|
|
15
|
+
人工触发
|
|
16
|
+
|
|
17
|
+
> 人工触发门控执行顺序:前置处理(脚本 `start`)→ 人工处理(页面)→ 后置处理(脚本 `complete`)。自动触发门控使用 `run` 方法替代 `start` + `complete`,详见 [references/agentic-lab-processer.md](../../references/agentic-lab-processer.md)。
|
|
18
|
+
|
|
19
|
+
## 模型引用
|
|
20
|
+
|
|
21
|
+
> 本门控无关联 3D 模型,本节仅作版式示意。实际生成时,无模型引用的门控省略本节。
|
|
22
|
+
|
|
23
|
+
| 属性 | 值 |
|
|
24
|
+
|------|-----|
|
|
25
|
+
| 名称 | example-valve-model |
|
|
26
|
+
| 版本 | 1.0.0 |
|
|
27
|
+
|
|
13
28
|
## 门控 YAML(与流程说明对齐)
|
|
14
29
|
|
|
15
30
|
流程说明中可能使用 `Stash:` 等键表示目标池;生成脚本时按 [agentic-lab-sdk.md](../../references/agentic-lab-sdk.md) 获取出口池,以返回的 **`pool.name`**(或文档中的池显示名称)匹配目标池(`valve_id` 来自 `Processer` 入参)。
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
> Agentic Lab 门控脚本
|
|
2
2
|
>
|
|
3
|
-
> `this.context` 上可用的 SDK 方法详见 [agentic-lab-sdk.md](agentic-lab-sdk.md)
|
|
3
|
+
> `this.context` 上可用的 SDK 方法详见 [agentic-lab-sdk.md](agentic-lab-sdk.md)。本文件定义 `Processer` 类的 `start`/`complete`(人工触发门控)与 `run`(自动触发门控)输入输出类型与代码风格。
|
|
4
4
|
|
|
5
5
|
### 类型定义
|
|
6
6
|
|
|
@@ -39,8 +39,27 @@ interface CompleteExecutionResult {
|
|
|
39
39
|
async complete(params: CompleteExecutionParams): Promise<CompleteExecutionResult>
|
|
40
40
|
```
|
|
41
41
|
|
|
42
|
+
#### run(自动触发门控专用)
|
|
42
43
|
|
|
43
|
-
|
|
44
|
+
自动触发门控不经过人工处理阶段,使用 `run` 替代 `start` + `complete`。`run` 合并了前置与后置处理:接收 `valve_id` 与 `pool_ids`(同 `start`),直接返回 `new_tickets`(同 `complete`),无需 `orbit_link` 或 `ticket_ids`。
|
|
45
|
+
|
|
46
|
+
```typescript
|
|
47
|
+
interface RunExecutionParams {
|
|
48
|
+
valve_id: number;
|
|
49
|
+
pool_ids: number[];
|
|
50
|
+
[propName: string]: any;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
interface RunExecutionResult {
|
|
54
|
+
new_tickets?: Record<string, any>[]; // 待新增的tickets,由引擎创建到数据表
|
|
55
|
+
[propName: string]: any;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async run(params: RunExecutionParams): Promise<RunExecutionResult>
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
### 示例(人工触发门控)
|
|
44
63
|
|
|
45
64
|
```javascript
|
|
46
65
|
class Processer {
|
|
@@ -76,3 +95,28 @@ class Processer {
|
|
|
76
95
|
```
|
|
77
96
|
|
|
78
97
|
完整可运行代码参考见 [`embedded-template/scripts/示例数据与校验门控.js`](../embedded-template/scripts/示例数据与校验门控.js)。
|
|
98
|
+
|
|
99
|
+
### 示例(自动触发门控)
|
|
100
|
+
|
|
101
|
+
```javascript
|
|
102
|
+
class Processer {
|
|
103
|
+
// context: 执行引擎提供的SDK
|
|
104
|
+
constructor(context) {
|
|
105
|
+
this.context = context;
|
|
106
|
+
console.info(`[Processer] v${__ARTIFACT_VERSION__} (skill: ${__ARTIFACT_SKILL__})`);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// params: { valve_id, pool_ids }
|
|
110
|
+
async run(params) {
|
|
111
|
+
// 按 agentic-lab-sdk.md 查询入口池数据
|
|
112
|
+
// 按需调用 SDK 方法查询化合物/工站/流程等外部数据
|
|
113
|
+
// 执行业务逻辑(无人工处理阶段)
|
|
114
|
+
// 按 agentic-lab-sdk.md 获取出口池
|
|
115
|
+
// 将入口池 tickets 更新为 finished 状态
|
|
116
|
+
// 按出口池 Schema 映射 new_tickets
|
|
117
|
+
return {
|
|
118
|
+
new_tickets: [/* 按出口池映射的新 tickets */]
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
```
|
|
@@ -86,7 +86,7 @@ metadata:
|
|
|
86
86
|
|
|
87
87
|
### 2.5 `## 门控执行规范` 代码块(`Processer` 类骨架,须保留;不含脚本尾部导出)
|
|
88
88
|
|
|
89
|
-
门控执行顺序:**前置处理(脚本 `start`)→ 人工处理(页面)→ 后置处理(脚本 `complete
|
|
89
|
+
门控执行顺序:**前置处理(脚本 `start`)→ 人工处理(页面)→ 后置处理(脚本 `complete`)**(人工触发门控)或 **`run`**(自动触发门控)。参数与返回值**一律 snake_case**(对齐 [agentic-lab-processer.md](agentic-lab-processer.md))。
|
|
90
90
|
|
|
91
91
|
```javascript
|
|
92
92
|
class Processer {
|
|
@@ -117,6 +117,26 @@ class Processer {
|
|
|
117
117
|
}
|
|
118
118
|
```
|
|
119
119
|
|
|
120
|
+
**自动触发门控**的 `Processer` 骨架使用 `run` 替代 `start` + `complete`(详见 [agentic-lab-processer.md](agentic-lab-processer.md)):
|
|
121
|
+
|
|
122
|
+
```javascript
|
|
123
|
+
class Processer {
|
|
124
|
+
constructor(context) {
|
|
125
|
+
this.context = context;
|
|
126
|
+
console.info(`[Processer] v${__ARTIFACT_VERSION__} (skill: ${__ARTIFACT_SKILL__})`);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// --- 自动触发 → run ---
|
|
130
|
+
// params: { valve_id, pool_ids }
|
|
131
|
+
// returns: { new_tickets }
|
|
132
|
+
async run(params) {
|
|
133
|
+
const { valve_id, pool_ids } = params;
|
|
134
|
+
// 查询入口池数据 → 执行业务逻辑 → 获取出口池 → 入口池 tickets 更新为 finished → 映射 new_tickets
|
|
135
|
+
return { new_tickets };
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
```
|
|
139
|
+
|
|
120
140
|
### 2.6 `## 使用方式`(三条,语义对齐)
|
|
121
141
|
|
|
122
142
|
```markdown
|
|
@@ -152,6 +172,21 @@ class Processer {
|
|
|
152
172
|
|
|
153
173
|
**属性列规则**:流程说明里若写清枚举/可选值,写入 `枚举:…`;若写清日期或时间串格式,写入 `格式:…`;否则该格留空。
|
|
154
174
|
|
|
175
|
+
### 模型引用(可选)
|
|
176
|
+
|
|
177
|
+
若流程说明为该数据池指定了 3D 模型文件,在 `## Schema` 之后添加:
|
|
178
|
+
|
|
179
|
+
```markdown
|
|
180
|
+
## 模型引用
|
|
181
|
+
|
|
182
|
+
| 属性 | 值 |
|
|
183
|
+
|------|-----|
|
|
184
|
+
| 名称 | (模型文件名称) |
|
|
185
|
+
| 版本 | (模型版本) |
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
无模型引用时**省略本节**(不写空占位)。
|
|
189
|
+
|
|
155
190
|
---
|
|
156
191
|
|
|
157
192
|
## 4. `valves/<基名>.md` 格式(节选范例)
|
|
@@ -182,6 +217,35 @@ class Processer {
|
|
|
182
217
|
- **输出池**: 出口池
|
|
183
218
|
```
|
|
184
219
|
|
|
220
|
+
### 触发方式(推荐,缺省为人工触发)
|
|
221
|
+
|
|
222
|
+
每个门控文档建议在 `## 关联脚本` 之后包含 `## 触发方式`:
|
|
223
|
+
|
|
224
|
+
```markdown
|
|
225
|
+
## 触发方式
|
|
226
|
+
|
|
227
|
+
人工触发
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
取值为 `自动触发` 或 `人工触发`。未指定时默认按**人工触发**处理:
|
|
231
|
+
- **人工触发**(默认):门控执行顺序为 前置处理(脚本 `start`)→ 人工处理(页面)→ 后置处理(脚本 `complete`),脚本实现 `start` + `complete`。
|
|
232
|
+
- **自动触发**:门控无人工处理阶段,脚本实现 `run` 方法(见 [agentic-lab-processer.md](agentic-lab-processer.md))。
|
|
233
|
+
|
|
234
|
+
### 模型引用(可选)
|
|
235
|
+
|
|
236
|
+
若流程说明为该门控指定了 3D 模型文件,添加:
|
|
237
|
+
|
|
238
|
+
```markdown
|
|
239
|
+
## 模型引用
|
|
240
|
+
|
|
241
|
+
| 属性 | 值 |
|
|
242
|
+
|------|-----|
|
|
243
|
+
| 名称 | (模型文件名称) |
|
|
244
|
+
| 版本 | (模型版本) |
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
无模型引用时**省略本节**(不写空占位)。
|
|
248
|
+
|
|
185
249
|
---
|
|
186
250
|
|
|
187
251
|
## 5. `scripts/<基名>.js` 格式
|
|
@@ -64,6 +64,8 @@
|
|
|
64
64
|
**检查点**:每个在拓扑与门控 YAML 中出现的池,表中有一行;**显示名称**与 YAML `input`/`output` 中的池名一致。
|
|
65
65
|
**检查点**:**逻辑 ID** 在同一文档内唯一(如 `stash_input`),且仅使用字母、数字、下划线(便于下游映射)。
|
|
66
66
|
|
|
67
|
+
**模型引用(可选)**:若数据池关联 3D 模型,须在该池的描述中包含 `模型引用` 标题子节,写明模型名称与版本。无关联模型时省略。
|
|
68
|
+
|
|
67
69
|
---
|
|
68
70
|
|
|
69
71
|
### 2.4 `## 4. 池间流转(拓扑)`(必备)
|
|
@@ -97,13 +99,17 @@
|
|
|
97
99
|
|
|
98
100
|
对每个门控,建议按下列顺序组织(可合并标题,但**信息不得缺**):
|
|
99
101
|
|
|
102
|
+
> **标题层级**:下列各项标题文字(如「触发方式」「模型引用」等)的 Markdown 层级(`##` / `###` / `####`)取决于门控在文档中的嵌套深度,以标题文字匹配为准,不要求固定 `#` 数量。
|
|
103
|
+
|
|
100
104
|
1. **门控标题**:`### 5.x <门控显示名称>`(编号与 `order` 建议一致或递增)。
|
|
101
105
|
2. **YAML 代码块**:`yaml`,须含 `name`、`order`、`input`、`output`;**`valve_id` 可选**(若写则全文档唯一)。
|
|
102
106
|
- 目标池键名可使用 `Stash:` 等非标准键,但须在总述或脚注说明与实现侧 **pool 显示名称** 的对应关系。
|
|
103
|
-
3.
|
|
104
|
-
4.
|
|
105
|
-
5.
|
|
106
|
-
6.
|
|
107
|
+
3. **触发方式**(推荐,缺省为人工触发):`触发方式` 标题,取值 `自动触发` 或 `人工触发`。未指定时默认按**人工触发**处理。自动触发门控无人工处理阶段,脚本实现 `run`;人工触发门控脚本实现 `start` + `complete`。
|
|
108
|
+
4. **化合物 / 外部数据查询方式**(若本门控需要):说明查哪类库、用哪些入口字段作键、汇总规则等。
|
|
109
|
+
5. **字段映射表**(若存在库表/API 列与门控加工字段对应):两列表头清晰(如「门控加工数据 | 库字段」)。
|
|
110
|
+
6. **数据处理规则表**(强烈推荐):列至少包含 **`序号` | `规则摘要` | `条件要点` | `业务动作或结果`**;序号建议 **章内连续小数编号**(如 `1.1`、`1.2`,与门控章节号前缀一致),便于脚本注释逐条对应。
|
|
111
|
+
7. **操作员步骤**(若为人工门控):编号列表,步骤可执行、可验收。
|
|
112
|
+
8. **模型引用**(若本门控关联 3D 模型):`模型引用` 标题,写明模型名称与版本。无关联模型时省略。
|
|
107
113
|
|
|
108
114
|
**Process 批量提交(可选)**:若门控需触发 **Rhea / 设备侧批量提交任务**,须在流程文档中写明调用时机、**`items` 每条**中的 **`process`(流程模板名,即 process_name 语义)**、**`params` / `table_objects` 等如何从业务数据拼装**,以及 **`baseURL`(服务根地址)的语义来源**(配置、环境变量、订单字段等,由集成解析为字符串 URL)。**path、HTTP 方法** 由 SDK 固定,流程文档**不要求**写 path / method;生成脚本时映射为 SDK 中对应方法(见 [`references/agentic-lab-sdk.md`](agentic-lab-sdk.md);Wire 细节见 [`references/rhea-api/execute_process_batch.md`](rhea-api/execute_process_batch.md) 供预检对照 `items[]` 形状)。
|
|
109
115
|
|
|
@@ -182,6 +188,8 @@
|
|
|
182
188
|
- [ ] **§3.1**:门控规则与 `output` 条件可判定,无未定义模糊用语;分支互斥或优先级已说明。
|
|
183
189
|
- [ ] **§3.2**:所提依赖、**SDK/API**、字段均在 **agentic-lab-sdk**(及已列 rhea-api)与 §2 中有依据;**无不支持或未文档化的 API**。
|
|
184
190
|
- [ ] **§3.3**:凡 **Process / 任务提交 / SDK 调用**,**`items`** 与 **`baseURL` 语义**可对照 **agentic-lab-sdk + rhea-api** 校验;其它查询的分页与 **filter** 意图可落地;**不**要求流程写 path/HTTP 方法。
|
|
191
|
+
- [ ] **触发方式**(推荐):建议每个门控含 `触发方式` 标题(`自动触发` 或 `人工触发`);未指定时默认为人工触发。自动触发门控无操作员步骤与人工处理章节。
|
|
192
|
+
- [ ] **模型引用**:关联 3D 模型的数据池或门控含 `模型引用` 标题(模型名称、模型版本);无关联模型时可省略。
|
|
185
193
|
|
|
186
194
|
---
|
|
187
195
|
|
|
@@ -60,6 +60,14 @@
|
|
|
60
60
|
|
|
61
61
|
> **提示**:显示名称须与门控 YAML 中的 `input`/`output` 池名逐字一致(含标点、中间点「·」等)。逻辑 ID 仅使用小写字母、数字、下划线。
|
|
62
62
|
|
|
63
|
+
### 3.1 数据池模型引用(按需)
|
|
64
|
+
|
|
65
|
+
> 若数据池关联 3D 模型,在此列出模型信息。无关联模型时省略本节。
|
|
66
|
+
|
|
67
|
+
| 池显示名称 | 模型名称 | 模型版本 |
|
|
68
|
+
| --------- | ------- | ------- |
|
|
69
|
+
| (例:待处理池) | (例:reactor_v2) | (例:1.0.0) |
|
|
70
|
+
|
|
63
71
|
***
|
|
64
72
|
|
|
65
73
|
## 4. 池间流转(拓扑)
|
|
@@ -111,6 +119,10 @@ output:
|
|
|
111
119
|
condition: (路由条件)
|
|
112
120
|
```
|
|
113
121
|
|
|
122
|
+
#### 触发方式
|
|
123
|
+
|
|
124
|
+
(自动触发 / 人工触发)
|
|
125
|
+
|
|
114
126
|
**化合物数据查询方式**(若本门控需要查询外部数据)
|
|
115
127
|
|
|
116
128
|
(说明查哪类库、用哪些入口字段作键、返回什么、汇总规则等)
|
|
@@ -133,6 +145,13 @@ output:
|
|
|
133
145
|
1. (步骤描述,可执行、可验收)
|
|
134
146
|
2. (...)
|
|
135
147
|
|
|
148
|
+
**模型引用**(若本门控关联 3D 模型,无则省略)
|
|
149
|
+
|
|
150
|
+
| 属性 | 值 |
|
|
151
|
+
|------|-----|
|
|
152
|
+
| 名称 | (模型名称) |
|
|
153
|
+
| 版本 | (模型版本) |
|
|
154
|
+
|
|
136
155
|
***
|
|
137
156
|
|
|
138
157
|
### 5.2 (门控 2 显示名称)
|
|
@@ -149,13 +168,17 @@ output:
|
|
|
149
168
|
condition: (路由条件)
|
|
150
169
|
```
|
|
151
170
|
|
|
171
|
+
#### 触发方式
|
|
172
|
+
|
|
173
|
+
(自动触发 / 人工触发)
|
|
174
|
+
|
|
152
175
|
**数据处理规则**
|
|
153
176
|
|
|
154
177
|
| 序号 | 规则摘要 | 条件要点 | 业务动作或结果 |
|
|
155
178
|
| --- | ------- | ------- | ----------- |
|
|
156
179
|
| 2.1 | (简述) | (条件) | (动作或路由结果) |
|
|
157
180
|
|
|
158
|
-
> **提示**:按需复制门控子节,每个门控至少包含 YAML
|
|
181
|
+
> **提示**:按需复制门控子节,每个门控至少包含 YAML 代码块和触发方式。需查询外部数据的加「查询方式」和「字段映射表」,含计算/判定的加「数据处理规则」,人工触发门控加「操作员步骤」,关联 3D 模型的加「模型引用」。
|
|
159
182
|
|
|
160
183
|
***
|
|
161
184
|
|
|
@@ -76,6 +76,8 @@
|
|
|
76
76
|
| 缺料化合物池 | `stash_missing` | 异常池 | 无可用源瓶或库存不足 |
|
|
77
77
|
| 已分装化合物池 | `stash_done` | 终点池 | 交货确认完成,流程结束 |
|
|
78
78
|
|
|
79
|
+
> **提示**:本流程数据池未关联 3D 模型,故省略模型引用。
|
|
80
|
+
|
|
79
81
|
***
|
|
80
82
|
|
|
81
83
|
## 4. 池间流转(拓扑)
|
|
@@ -133,6 +135,10 @@ output:
|
|
|
133
135
|
condition: 是否缺料 = 缺料
|
|
134
136
|
```
|
|
135
137
|
|
|
138
|
+
#### 触发方式
|
|
139
|
+
|
|
140
|
+
人工触发
|
|
141
|
+
|
|
136
142
|
**化合物数据查询方式**
|
|
137
143
|
|
|
138
144
|
查询化合物库存的可用量:使用输入池 Compound ID 作为索引,通过 XFC 上海系统接口查询化合物库存(`compound.getStockFromXfcSh`),查找 status = 可用的记录,返回可用量字段的值并汇总加和。
|
|
@@ -174,6 +180,10 @@ output:
|
|
|
174
180
|
condition: 任一档位偏差超出容差或其它执行失败情形
|
|
175
181
|
```
|
|
176
182
|
|
|
183
|
+
#### 触发方式
|
|
184
|
+
|
|
185
|
+
人工触发
|
|
186
|
+
|
|
177
187
|
**数据处理规则**
|
|
178
188
|
|
|
179
189
|
| 序号 | 规则摘要 | 条件要点 | 业务动作或结果 |
|
|
@@ -356,6 +356,34 @@ function validateCompleteResult(result) {
|
|
|
356
356
|
return errors;
|
|
357
357
|
}
|
|
358
358
|
|
|
359
|
+
function validateRunResult(result) {
|
|
360
|
+
const errors = [];
|
|
361
|
+
if (typeof result !== 'object' || result === null) {
|
|
362
|
+
errors.push('run() must return an object, got ' + (result === null ? 'null' : typeof result));
|
|
363
|
+
return errors;
|
|
364
|
+
}
|
|
365
|
+
if (result.new_tickets !== undefined) {
|
|
366
|
+
if (!Array.isArray(result.new_tickets)) {
|
|
367
|
+
errors.push(`run().new_tickets must be array, got ${typeof result.new_tickets}`);
|
|
368
|
+
} else {
|
|
369
|
+
for (let i = 0; i < Math.min(result.new_tickets.length, 3); i++) {
|
|
370
|
+
const t = result.new_tickets[i];
|
|
371
|
+
if (typeof t.flow_id !== 'number') errors.push(`new_tickets[${i}].flow_id must be number, got ${typeof t.flow_id}`);
|
|
372
|
+
if (typeof t.pool_id !== 'number') errors.push(`new_tickets[${i}].pool_id must be number, got ${typeof t.pool_id}`);
|
|
373
|
+
if (typeof t.order_id !== 'number') errors.push(`new_tickets[${i}].order_id must be number, got ${typeof t.order_id}`);
|
|
374
|
+
if (typeof t.detail !== 'object' || t.detail === null) errors.push(`new_tickets[${i}].detail must be object`);
|
|
375
|
+
if (typeof t.status !== 'string') errors.push(`new_tickets[${i}].status must be string, got ${typeof t.status}`);
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
for (const key of Object.keys(result)) {
|
|
380
|
+
if (CAMEL_CASE_PATTERN.test(key)) {
|
|
381
|
+
errors.push(`run() return key "${key}" is camelCase — must be snake_case`);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
return errors;
|
|
385
|
+
}
|
|
386
|
+
|
|
359
387
|
// ─── Script loader via vm ──────────────────────────────────────────
|
|
360
388
|
|
|
361
389
|
function loadProcesserClass(scriptContent, filename) {
|
|
@@ -621,8 +649,10 @@ function formatTicketsTable(tickets, maxDetailKeys) {
|
|
|
621
649
|
|
|
622
650
|
function formatPreviewReport(trace) {
|
|
623
651
|
const { scriptPath, valveName, valveConfig, pools, dataSource,
|
|
624
|
-
inputTickets,
|
|
625
|
-
|
|
652
|
+
inputTickets, isAutoTrigger,
|
|
653
|
+
startResult, startError, startTraceLog, startDetailDiffs,
|
|
654
|
+
completeTickets, completeResult, completeError, completeTraceLog, completeDetailDiffs,
|
|
655
|
+
runResult, runError, runTraceLog, runDetailDiffs } = trace;
|
|
626
656
|
|
|
627
657
|
const lines = [];
|
|
628
658
|
const push = (...args) => lines.push(...args);
|
|
@@ -638,108 +668,176 @@ function formatPreviewReport(trace) {
|
|
|
638
668
|
push(`- 入口池: ${inputPoolNames}`);
|
|
639
669
|
push(`- 出口池: ${outputPoolNames}`);
|
|
640
670
|
push(`- 数据来源: ${dataSource}`);
|
|
641
|
-
push(`- tickets 数: ${inputTickets.length}
|
|
671
|
+
push(`- tickets 数: ${inputTickets.length}`);
|
|
672
|
+
push(`- 触发方式: ${isAutoTrigger ? '自动触发 (run)' : '人工触发 (start/complete)'}`, '');
|
|
673
|
+
|
|
674
|
+
if (isAutoTrigger) {
|
|
675
|
+
// run() trace
|
|
676
|
+
push('## run() 执行追踪', '');
|
|
677
|
+
push(`### 输入数据 (${inputTickets.length} 条 tickets)`, '');
|
|
678
|
+
push(formatTicketsTable(inputTickets));
|
|
679
|
+
|
|
680
|
+
push('### SDK 调用链', '');
|
|
681
|
+
for (const entry of (runTraceLog || [])) {
|
|
682
|
+
const paramStr = JSON.stringify(entry.params);
|
|
683
|
+
push(`${entry.seq + 1}. \`${entry.method}(${paramStr === '{}' ? '' : paramStr})\` → ${entry.returnSummary}`);
|
|
684
|
+
}
|
|
685
|
+
push('');
|
|
642
686
|
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
687
|
+
if (runDetailDiffs && runDetailDiffs.length > 0) {
|
|
688
|
+
push('### ticket.detail 变更 (run 阶段)', '');
|
|
689
|
+
push('| ticket | 字段 | 变更前 | 变更后 |');
|
|
690
|
+
push('|--------|------|--------|--------|');
|
|
691
|
+
for (const d of runDetailDiffs) {
|
|
692
|
+
for (const c of d.changes) {
|
|
693
|
+
push(`| #${d.ticketId} | ${c.key} | ${c.before} | ${c.after} |`);
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
push('');
|
|
697
|
+
}
|
|
647
698
|
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
push(
|
|
699
|
+
if (runError) {
|
|
700
|
+
push(`### run() 错误`, '');
|
|
701
|
+
push(`\`\`\`\n${runError}\n\`\`\``, '');
|
|
702
|
+
} else if (runResult) {
|
|
703
|
+
push('### 数据路由', '');
|
|
704
|
+
if (runResult.new_tickets && runResult.new_tickets.length > 0) {
|
|
705
|
+
const routeMap = new Map();
|
|
706
|
+
for (const nt of runResult.new_tickets) {
|
|
707
|
+
const poolId = nt.pool_id;
|
|
708
|
+
if (!routeMap.has(poolId)) routeMap.set(poolId, []);
|
|
709
|
+
routeMap.get(poolId).push(nt);
|
|
710
|
+
}
|
|
711
|
+
push('| 目标池 | pool_id | 数量 | status |');
|
|
712
|
+
push('|--------|---------|------|--------|');
|
|
713
|
+
for (const [poolId, nts] of routeMap) {
|
|
714
|
+
const poolName = valveConfig.output_pools.find((_, i) => 100 + i === poolId)?.name || `pool_${poolId}`;
|
|
715
|
+
push(`| ${poolName} | ${poolId} | ${nts.length} | ${nts[0]?.status || '-'} |`);
|
|
716
|
+
}
|
|
717
|
+
push('');
|
|
718
|
+
} else {
|
|
719
|
+
push('(无 new_tickets 返回)', '');
|
|
662
720
|
}
|
|
663
721
|
}
|
|
664
|
-
push('');
|
|
665
|
-
}
|
|
666
722
|
|
|
667
|
-
|
|
668
|
-
push(
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
push(`- ticket_ids: [${(startResult.ticket_ids || []).join(', ')}]`);
|
|
674
|
-
push('');
|
|
675
|
-
}
|
|
723
|
+
// Summary
|
|
724
|
+
push('## 执行摘要', '');
|
|
725
|
+
const usedMethods = new Set((runTraceLog || []).map(c => c.method));
|
|
726
|
+
const allMethods = ['ticket.list', 'ticket.update', 'ticket.append', 'pool.getNext',
|
|
727
|
+
'compound.getStockFromXfcSh', 'process.list', 'process.execute', 'station.list'];
|
|
728
|
+
const unused = allMethods.filter(m => !usedMethods.has(m));
|
|
676
729
|
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
730
|
+
const runStatus = runError
|
|
731
|
+
? `✗ 失败: ${runError.split('\n')[0]}`
|
|
732
|
+
: `✓ 成功 (${(runTraceLog || []).length} SDK 调用, ${(runResult?.new_tickets || []).length} new_tickets)`;
|
|
680
733
|
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
push(
|
|
690
|
-
push(
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
}
|
|
734
|
+
push(`- run: ${runStatus}`);
|
|
735
|
+
if (unused.length > 0) {
|
|
736
|
+
push(`- 未调用的 SDK 方法: ${unused.join(', ')}`);
|
|
737
|
+
}
|
|
738
|
+
push('');
|
|
739
|
+
} else {
|
|
740
|
+
// start() trace
|
|
741
|
+
push('## start() 执行追踪', '');
|
|
742
|
+
push(`### 输入数据 (${inputTickets.length} 条 tickets)`, '');
|
|
743
|
+
push(formatTicketsTable(inputTickets));
|
|
744
|
+
|
|
745
|
+
push('### SDK 调用链', '');
|
|
746
|
+
for (const entry of startTraceLog) {
|
|
747
|
+
const paramStr = JSON.stringify(entry.params);
|
|
748
|
+
push(`${entry.seq + 1}. \`${entry.method}(${paramStr === '{}' ? '' : paramStr})\` → ${entry.returnSummary}`);
|
|
696
749
|
}
|
|
697
750
|
push('');
|
|
698
|
-
}
|
|
699
751
|
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
const poolId = nt.pool_id;
|
|
709
|
-
if (!routeMap.has(poolId)) routeMap.set(poolId, []);
|
|
710
|
-
routeMap.get(poolId).push(nt);
|
|
752
|
+
if (startDetailDiffs.length > 0) {
|
|
753
|
+
push('### ticket.detail 变更 (start 阶段)', '');
|
|
754
|
+
push('| ticket | 字段 | 变更前 | 变更后 |');
|
|
755
|
+
push('|--------|------|--------|--------|');
|
|
756
|
+
for (const d of startDetailDiffs) {
|
|
757
|
+
for (const c of d.changes) {
|
|
758
|
+
push(`| #${d.ticketId} | ${c.key} | ${c.before} | ${c.after} |`);
|
|
759
|
+
}
|
|
711
760
|
}
|
|
712
|
-
push('
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
761
|
+
push('');
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
if (startError) {
|
|
765
|
+
push(`### start() 错误`, '');
|
|
766
|
+
push(`\`\`\`\n${startError}\n\`\`\``, '');
|
|
767
|
+
} else if (startResult) {
|
|
768
|
+
push('### 返回值', '');
|
|
769
|
+
push(`- orbit_link: \`${startResult.orbit_link}\``);
|
|
770
|
+
push(`- ticket_ids: [${(startResult.ticket_ids || []).join(', ')}]`);
|
|
771
|
+
push('');
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
// complete() trace
|
|
775
|
+
push('## complete() 执行追踪', '');
|
|
776
|
+
push(`### 输入 tickets (${(completeTickets || []).length} 条)`, '');
|
|
777
|
+
|
|
778
|
+
push('### SDK 调用链', '');
|
|
779
|
+
for (const entry of completeTraceLog) {
|
|
780
|
+
const paramStr = JSON.stringify(entry.params);
|
|
781
|
+
push(`${entry.seq + 1}. \`${entry.method}(${paramStr === '{}' ? '' : paramStr})\` → ${entry.returnSummary}`);
|
|
782
|
+
}
|
|
783
|
+
push('');
|
|
784
|
+
|
|
785
|
+
if (completeDetailDiffs.length > 0) {
|
|
786
|
+
push('### ticket.detail 变更 (complete 阶段)', '');
|
|
787
|
+
push('| ticket | 字段 | 变更前 | 变更后 |');
|
|
788
|
+
push('|--------|------|--------|--------|');
|
|
789
|
+
for (const d of completeDetailDiffs) {
|
|
790
|
+
for (const c of d.changes) {
|
|
791
|
+
push(`| #${d.ticketId} | ${c.key} | ${c.before} | ${c.after} |`);
|
|
792
|
+
}
|
|
717
793
|
}
|
|
718
794
|
push('');
|
|
719
|
-
} else {
|
|
720
|
-
push('(无 new_tickets 返回)', '');
|
|
721
795
|
}
|
|
722
|
-
}
|
|
723
796
|
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
797
|
+
if (completeError) {
|
|
798
|
+
push(`### complete() 错误`, '');
|
|
799
|
+
push(`\`\`\`\n${completeError}\n\`\`\``, '');
|
|
800
|
+
} else if (completeResult) {
|
|
801
|
+
push('### 数据路由', '');
|
|
802
|
+
if (completeResult.new_tickets && completeResult.new_tickets.length > 0) {
|
|
803
|
+
const routeMap = new Map();
|
|
804
|
+
for (const nt of completeResult.new_tickets) {
|
|
805
|
+
const poolId = nt.pool_id;
|
|
806
|
+
if (!routeMap.has(poolId)) routeMap.set(poolId, []);
|
|
807
|
+
routeMap.get(poolId).push(nt);
|
|
808
|
+
}
|
|
809
|
+
push('| 目标池 | pool_id | 数量 | status |');
|
|
810
|
+
push('|--------|---------|------|--------|');
|
|
811
|
+
for (const [poolId, nts] of routeMap) {
|
|
812
|
+
const poolName = valveConfig.output_pools.find((_, i) => 100 + i === poolId)?.name || `pool_${poolId}`;
|
|
813
|
+
push(`| ${poolName} | ${poolId} | ${nts.length} | ${nts[0]?.status || '-'} |`);
|
|
814
|
+
}
|
|
815
|
+
push('');
|
|
816
|
+
} else {
|
|
817
|
+
push('(无 new_tickets 返回)', '');
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
// Summary
|
|
822
|
+
push('## 执行摘要', '');
|
|
823
|
+
const allCalls = [...startTraceLog, ...completeTraceLog];
|
|
824
|
+
const usedMethods = new Set(allCalls.map(c => c.method));
|
|
825
|
+
const allMethods = ['ticket.list', 'ticket.update', 'ticket.append', 'pool.getNext',
|
|
826
|
+
'compound.getStockFromXfcSh', 'process.list', 'process.execute', 'station.list'];
|
|
827
|
+
const unused = allMethods.filter(m => !usedMethods.has(m));
|
|
828
|
+
|
|
829
|
+
const startStatus = startError ? `✗ 失败: ${startError.split('\n')[0]}` : `✓ 成功 (${startTraceLog.length} SDK 调用, ${inputTickets.length} tickets)`;
|
|
830
|
+
const completeStatus = completeError
|
|
831
|
+
? `✗ 失败: ${completeError.split('\n')[0]}`
|
|
832
|
+
: `✓ 成功 (${completeTraceLog.length} SDK 调用, ${(completeResult?.new_tickets || []).length} new_tickets)`;
|
|
833
|
+
|
|
834
|
+
push(`- start: ${startStatus}`);
|
|
835
|
+
push(`- complete: ${completeStatus}`);
|
|
836
|
+
if (unused.length > 0) {
|
|
837
|
+
push(`- 未调用的 SDK 方法: ${unused.join(', ')}`);
|
|
838
|
+
}
|
|
839
|
+
push('');
|
|
741
840
|
}
|
|
742
|
-
push('');
|
|
743
841
|
|
|
744
842
|
return lines.join('\n');
|
|
745
843
|
}
|
|
@@ -787,59 +885,85 @@ async function runPreview(args) {
|
|
|
787
885
|
}
|
|
788
886
|
|
|
789
887
|
const inputPoolIds = valveConfig.input_pools.map((_, i) => i + 1);
|
|
888
|
+
const proto = ProcesserClass.prototype;
|
|
889
|
+
const isAutoTrigger = typeof proto.run === 'function' && typeof proto.start !== 'function';
|
|
890
|
+
|
|
790
891
|
const trace = {
|
|
791
|
-
scriptPath, valveName, valveConfig, pools, dataSource, inputTickets,
|
|
892
|
+
scriptPath, valveName, valveConfig, pools, dataSource, inputTickets, isAutoTrigger,
|
|
792
893
|
startResult: null, startError: null, startTraceLog: [], startDetailDiffs: [],
|
|
793
894
|
completeTickets: null, completeResult: null, completeError: null, completeTraceLog: [], completeDetailDiffs: [],
|
|
895
|
+
runResult: null, runError: null, runTraceLog: [], runDetailDiffs: [],
|
|
794
896
|
};
|
|
795
897
|
|
|
796
|
-
|
|
797
|
-
|
|
898
|
+
if (isAutoTrigger) {
|
|
899
|
+
// Auto-trigger: run()
|
|
798
900
|
const tc = createTracingContext(pools, valveConfig, inputTickets);
|
|
799
901
|
let processer;
|
|
800
902
|
try {
|
|
801
903
|
processer = new ProcesserClass(tc.context);
|
|
802
904
|
} catch (e) {
|
|
803
|
-
trace.
|
|
905
|
+
trace.runError = `constructor threw: ${e.message}\n${e.stack}`;
|
|
804
906
|
console.log(formatPreviewReport(trace));
|
|
805
907
|
process.exit(1);
|
|
806
908
|
}
|
|
807
909
|
|
|
808
910
|
try {
|
|
809
|
-
trace.
|
|
911
|
+
trace.runResult = await processer.run({ valve_id: 1, pool_ids: inputPoolIds });
|
|
810
912
|
} catch (e) {
|
|
811
|
-
trace.
|
|
913
|
+
trace.runError = `${e.constructor.name}: ${e.message}\n${e.stack}`;
|
|
812
914
|
}
|
|
813
|
-
trace.
|
|
814
|
-
trace.
|
|
815
|
-
}
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
915
|
+
trace.runTraceLog = tc.traceLog;
|
|
916
|
+
trace.runDetailDiffs = tc.detailDiffs;
|
|
917
|
+
} else {
|
|
918
|
+
// Manual-trigger: start() + complete()
|
|
919
|
+
// Run start()
|
|
920
|
+
{
|
|
921
|
+
const tc = createTracingContext(pools, valveConfig, inputTickets);
|
|
922
|
+
let processer;
|
|
923
|
+
try {
|
|
924
|
+
processer = new ProcesserClass(tc.context);
|
|
925
|
+
} catch (e) {
|
|
926
|
+
trace.startError = `constructor threw: ${e.message}\n${e.stack}`;
|
|
927
|
+
console.log(formatPreviewReport(trace));
|
|
928
|
+
process.exit(1);
|
|
929
|
+
}
|
|
821
930
|
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
trace.
|
|
828
|
-
|
|
829
|
-
process.exit(1);
|
|
931
|
+
try {
|
|
932
|
+
trace.startResult = await processer.start({ valve_id: 1, pool_ids: inputPoolIds });
|
|
933
|
+
} catch (e) {
|
|
934
|
+
trace.startError = `${e.constructor.name}: ${e.message}\n${e.stack}`;
|
|
935
|
+
}
|
|
936
|
+
trace.startTraceLog = tc.traceLog;
|
|
937
|
+
trace.startDetailDiffs = tc.detailDiffs;
|
|
830
938
|
}
|
|
831
939
|
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
trace.
|
|
940
|
+
// Run complete()
|
|
941
|
+
{
|
|
942
|
+
const completeTickets = inputTickets.map(t => ({ ...t, status: 'created' }));
|
|
943
|
+
trace.completeTickets = completeTickets;
|
|
944
|
+
|
|
945
|
+
const tc = createTracingContext(pools, valveConfig, completeTickets);
|
|
946
|
+
let processer;
|
|
947
|
+
try {
|
|
948
|
+
processer = new ProcesserClass(tc.context);
|
|
949
|
+
} catch (e) {
|
|
950
|
+
trace.completeError = `constructor threw: ${e.message}\n${e.stack}`;
|
|
951
|
+
console.log(formatPreviewReport(trace));
|
|
952
|
+
process.exit(1);
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
try {
|
|
956
|
+
trace.completeResult = await processer.complete({ valve_id: 1, tickets: completeTickets });
|
|
957
|
+
} catch (e) {
|
|
958
|
+
trace.completeError = `${e.constructor.name}: ${e.message}\n${e.stack}`;
|
|
959
|
+
}
|
|
960
|
+
trace.completeTraceLog = tc.traceLog;
|
|
961
|
+
trace.completeDetailDiffs = tc.detailDiffs;
|
|
836
962
|
}
|
|
837
|
-
trace.completeTraceLog = tc.traceLog;
|
|
838
|
-
trace.completeDetailDiffs = tc.detailDiffs;
|
|
839
963
|
}
|
|
840
964
|
|
|
841
965
|
console.log(formatPreviewReport(trace));
|
|
842
|
-
const hasError = trace.startError || trace.completeError;
|
|
966
|
+
const hasError = isAutoTrigger ? trace.runError : (trace.startError || trace.completeError);
|
|
843
967
|
process.exit(hasError ? 1 : 0);
|
|
844
968
|
}
|
|
845
969
|
|
|
@@ -856,7 +980,7 @@ async function runTests(args) {
|
|
|
856
980
|
script: scriptPath,
|
|
857
981
|
phases: {
|
|
858
982
|
syntax: { pass: false },
|
|
859
|
-
structure: { pass: false, hasConstructor: false, hasStart: false, hasComplete: false },
|
|
983
|
+
structure: { pass: false, hasConstructor: false, hasStart: false, hasComplete: false, hasRun: false },
|
|
860
984
|
normalRun: {
|
|
861
985
|
start: { pass: false, returnValid: false, errors: [] },
|
|
862
986
|
complete: { pass: false, returnValid: false, errors: [] },
|
|
@@ -914,18 +1038,23 @@ async function runTests(args) {
|
|
|
914
1038
|
result.phases.structure.hasConstructor = typeof ProcesserClass === 'function';
|
|
915
1039
|
result.phases.structure.hasStart = typeof proto.start === 'function';
|
|
916
1040
|
result.phases.structure.hasComplete = typeof proto.complete === 'function';
|
|
1041
|
+
result.phases.structure.hasRun = typeof proto.run === 'function';
|
|
917
1042
|
result.phases.structure.pass =
|
|
918
1043
|
result.phases.structure.hasConstructor &&
|
|
919
|
-
|
|
920
|
-
|
|
1044
|
+
(
|
|
1045
|
+
(result.phases.structure.hasStart && result.phases.structure.hasComplete) ||
|
|
1046
|
+
result.phases.structure.hasRun
|
|
1047
|
+
);
|
|
921
1048
|
|
|
922
1049
|
if (!result.phases.structure.pass) {
|
|
923
|
-
if (!result.phases.structure.hasStart) result.errors.push('Processer missing start() method');
|
|
924
|
-
if (!result.phases.structure.hasComplete) result.errors.push('Processer missing complete() method');
|
|
1050
|
+
if (!result.phases.structure.hasRun && !result.phases.structure.hasStart) result.errors.push('Processer missing start() method (manual-trigger) or run() method (auto-trigger)');
|
|
1051
|
+
if (!result.phases.structure.hasRun && !result.phases.structure.hasComplete) result.errors.push('Processer missing complete() method (manual-trigger) or run() method (auto-trigger)');
|
|
925
1052
|
result.status = 'fail';
|
|
926
1053
|
return result;
|
|
927
1054
|
}
|
|
928
1055
|
|
|
1056
|
+
const isAutoTrigger = result.phases.structure.hasRun && !result.phases.structure.hasStart;
|
|
1057
|
+
|
|
929
1058
|
// Load pool schemas and valve config
|
|
930
1059
|
const pools = loadAllPools(poolsDir);
|
|
931
1060
|
const valveConfig = loadValveConfig(valvesDir, valveName);
|
|
@@ -938,41 +1067,62 @@ async function runTests(args) {
|
|
|
938
1067
|
try {
|
|
939
1068
|
processer = new ProcesserClass(mock.context);
|
|
940
1069
|
} catch (e) {
|
|
941
|
-
|
|
1070
|
+
const phaseKey = isAutoTrigger ? 'run' : 'start';
|
|
1071
|
+
if (!result.phases.normalRun[phaseKey]) result.phases.normalRun[phaseKey] = { pass: false, returnValid: false, errors: [] };
|
|
1072
|
+
result.phases.normalRun[phaseKey].errors.push(`constructor threw: ${e.message}`);
|
|
942
1073
|
result.errors.push(`Normal run constructor error: ${e.message}`);
|
|
943
1074
|
result.status = 'fail';
|
|
944
1075
|
return result;
|
|
945
1076
|
}
|
|
946
1077
|
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
result.
|
|
1078
|
+
if (isAutoTrigger) {
|
|
1079
|
+
// Auto-trigger: test run()
|
|
1080
|
+
if (!result.phases.normalRun.run) result.phases.normalRun.run = { pass: false, returnValid: false, errors: [] };
|
|
1081
|
+
try {
|
|
1082
|
+
const runResult = await processer.run({ valve_id: 1, pool_ids: inputPoolIds });
|
|
1083
|
+
const runErrors = validateRunResult(runResult);
|
|
1084
|
+
result.phases.normalRun.run.returnValid = runErrors.length === 0;
|
|
1085
|
+
result.phases.normalRun.run.errors = runErrors;
|
|
1086
|
+
result.phases.normalRun.run.pass = runErrors.length === 0;
|
|
1087
|
+
if (runErrors.length > 0) {
|
|
1088
|
+
result.errors.push(...runErrors.map(e => `[normal run] ${e}`));
|
|
1089
|
+
}
|
|
1090
|
+
} catch (e) {
|
|
1091
|
+
result.phases.normalRun.run.errors.push(e.message);
|
|
1092
|
+
result.errors.push(`[normal run] Runtime error: ${e.message}`);
|
|
1093
|
+
}
|
|
1094
|
+
} else {
|
|
1095
|
+
// Manual-trigger: test start() + complete()
|
|
1096
|
+
// start
|
|
1097
|
+
try {
|
|
1098
|
+
const startResult = await processer.start({ valve_id: 1, pool_ids: inputPoolIds });
|
|
1099
|
+
const startErrors = validateStartResult(startResult);
|
|
1100
|
+
result.phases.normalRun.start.returnValid = startErrors.length === 0;
|
|
1101
|
+
result.phases.normalRun.start.errors = startErrors;
|
|
1102
|
+
result.phases.normalRun.start.pass = startErrors.length === 0;
|
|
1103
|
+
if (startErrors.length > 0) {
|
|
1104
|
+
result.errors.push(...startErrors.map(e => `[normal start] ${e}`));
|
|
1105
|
+
}
|
|
1106
|
+
} catch (e) {
|
|
1107
|
+
result.phases.normalRun.start.errors.push(e.message);
|
|
1108
|
+
result.errors.push(`[normal start] Runtime error: ${e.message}`);
|
|
956
1109
|
}
|
|
957
|
-
} catch (e) {
|
|
958
|
-
result.phases.normalRun.start.errors.push(e.message);
|
|
959
|
-
result.errors.push(`[normal start] Runtime error: ${e.message}`);
|
|
960
|
-
}
|
|
961
1110
|
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
1111
|
+
// complete — use mock tickets from the start phase (with updated details if any)
|
|
1112
|
+
try {
|
|
1113
|
+
const completeTickets = mock.mockTickets.map(t => ({ ...t, status: 'created' }));
|
|
1114
|
+
const completeResult = await processer.complete({ valve_id: 1, tickets: completeTickets });
|
|
1115
|
+
const completeErrors = validateCompleteResult(completeResult);
|
|
1116
|
+
result.phases.normalRun.complete.returnValid = completeErrors.length === 0;
|
|
1117
|
+
result.phases.normalRun.complete.errors = completeErrors;
|
|
1118
|
+
result.phases.normalRun.complete.pass = completeErrors.length === 0;
|
|
1119
|
+
if (completeErrors.length > 0) {
|
|
1120
|
+
result.errors.push(...completeErrors.map(e => `[normal complete] ${e}`));
|
|
1121
|
+
}
|
|
1122
|
+
} catch (e) {
|
|
1123
|
+
result.phases.normalRun.complete.errors.push(e.message);
|
|
1124
|
+
result.errors.push(`[normal complete] Runtime error: ${e.message}`);
|
|
972
1125
|
}
|
|
973
|
-
} catch (e) {
|
|
974
|
-
result.phases.normalRun.complete.errors.push(e.message);
|
|
975
|
-
result.errors.push(`[normal complete] Runtime error: ${e.message}`);
|
|
976
1126
|
}
|
|
977
1127
|
|
|
978
1128
|
result.sdkCalls = [...new Set(mock.callLog.map(c => c.method))];
|
|
@@ -998,7 +1148,9 @@ async function runTests(args) {
|
|
|
998
1148
|
}
|
|
999
1149
|
}
|
|
1000
1150
|
|
|
1001
|
-
const normalPass =
|
|
1151
|
+
const normalPass = isAutoTrigger
|
|
1152
|
+
? result.phases.normalRun.run?.pass
|
|
1153
|
+
: result.phases.normalRun.start.pass && result.phases.normalRun.complete.pass;
|
|
1002
1154
|
if (!normalPass) {
|
|
1003
1155
|
result.status = 'fail';
|
|
1004
1156
|
}
|
|
@@ -1010,26 +1162,39 @@ async function runTests(args) {
|
|
|
1010
1162
|
try {
|
|
1011
1163
|
processer = new ProcesserClass(mock.context);
|
|
1012
1164
|
} catch (e) {
|
|
1013
|
-
|
|
1165
|
+
const phaseKey = isAutoTrigger ? 'run' : 'start';
|
|
1166
|
+
if (!result.phases.adversarialRun[phaseKey]) result.phases.adversarialRun[phaseKey] = { pass: false, errors: [] };
|
|
1167
|
+
result.phases.adversarialRun[phaseKey].errors.push(`constructor threw: ${e.message}`);
|
|
1014
1168
|
result.warnings.push(`[adversarial] constructor error: ${e.message}`);
|
|
1015
1169
|
}
|
|
1016
1170
|
|
|
1017
1171
|
if (processer) {
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1172
|
+
if (isAutoTrigger) {
|
|
1173
|
+
if (!result.phases.adversarialRun.run) result.phases.adversarialRun.run = { pass: false, errors: [] };
|
|
1174
|
+
try {
|
|
1175
|
+
await processer.run({ valve_id: 1, pool_ids: inputPoolIds });
|
|
1176
|
+
result.phases.adversarialRun.run.pass = true;
|
|
1177
|
+
} catch (e) {
|
|
1178
|
+
result.phases.adversarialRun.run.errors.push(e.message);
|
|
1179
|
+
result.warnings.push(`[adversarial run] ${e.constructor.name}: ${e.message}`);
|
|
1180
|
+
}
|
|
1181
|
+
} else {
|
|
1182
|
+
try {
|
|
1183
|
+
await processer.start({ valve_id: 1, pool_ids: inputPoolIds });
|
|
1184
|
+
result.phases.adversarialRun.start.pass = true;
|
|
1185
|
+
} catch (e) {
|
|
1186
|
+
result.phases.adversarialRun.start.errors.push(e.message);
|
|
1187
|
+
result.warnings.push(`[adversarial start] ${e.constructor.name}: ${e.message}`);
|
|
1188
|
+
}
|
|
1025
1189
|
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1190
|
+
try {
|
|
1191
|
+
const completeTickets = mock.mockTickets.map(t => ({ ...t, status: 'created' }));
|
|
1192
|
+
await processer.complete({ valve_id: 1, tickets: completeTickets });
|
|
1193
|
+
result.phases.adversarialRun.complete.pass = true;
|
|
1194
|
+
} catch (e) {
|
|
1195
|
+
result.phases.adversarialRun.complete.errors.push(e.message);
|
|
1196
|
+
result.warnings.push(`[adversarial complete] ${e.constructor.name}: ${e.message}`);
|
|
1197
|
+
}
|
|
1033
1198
|
}
|
|
1034
1199
|
|
|
1035
1200
|
result.phases.compliance.warnings.push(
|