agentworkshop 0.7.46 → 0.7.48
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/.output/.AgentWorkShop/aml-python/__pycache__/amlkit.cpython-312.pyc +0 -0
- package/.output/nitro.json +1 -1
- package/.output/public/_nuxt/builds/latest.json +1 -1
- package/.output/public/_nuxt/builds/meta/6f524f3b-53e1-4df9-84e6-84d8058b9f16.json +1 -0
- package/.output/server/chunks/_/nitro.mjs +955 -858
- package/.output/server/package.json +1 -1
- package/README-zh.md +7 -2
- package/README.md +7 -2
- package/config.yml +1 -1
- package/docs/aml.md +2 -0
- package/docs/cli.en.md +2 -2
- package/docs/cli.md +2 -2
- package/docs/full-test-plan.md +1 -1
- package/docs/plugins.en.md +1 -1
- package/docs/plugins.md +1 -1
- package/docs/sdk.en.md +1 -1
- package/docs/sdk.md +1 -1
- package/package.json +1 -1
- package/scripts/_aw-packaged-start.cjs +22 -0
- package/scripts/_aw0746-stage12.mjs +94 -0
- package/scripts/_aw0746-stage34.mjs +136 -0
- package/scripts/_aw0746-stage5.mjs +144 -0
- package/scripts/_aw0746-stage6-aml.mjs +177 -0
- package/scripts/_aw0746-stage6b-train-sweep.mjs +145 -0
- package/scripts/_aw0746-stage6c-jobsmoke.mjs +69 -0
- package/scripts/_aw0746-stage7-twin.mjs +267 -0
- package/scripts/_aw0746-train-strong.py +142 -0
- package/scripts/_aw0747-readme-rows.mjs +32 -0
- package/scripts/_aw0748-failclosed-probe.mjs +90 -0
- package/scripts/_plc-sim-detached.cjs +22 -0
- package/server/services/workshop/agents/industrial/twin-tools.ts +85 -10
- package/server/services/workshop/aml/job-orchestrator/start-job.ts +140 -124
- package/server/services/workshop/aml/python/__pycache__/amlkit.cpython-312.pyc +0 -0
- package/server/services/workshop/aml/twin/physics-runtime.ts +16 -1
- package/server/services/workshop/aml/twin/repo.ts +9 -4
- package/.output/public/_nuxt/builds/meta/7827fe9f-b4c2-461f-b1fb-c0c7da7d66b8.json +0 -1
package/README-zh.md
CHANGED
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
|
|
29
29
|
**[English](./README.md)** · **[在线文档](https://kingdol666.github.io/AgentWorkShop)** · **[版本发布](https://github.com/kingdol666/AgentWorkShop/releases)** · **[更新日志](./changelog.md)** · **[插件 API](./docs/plugins.md)** · **[SDK](./docs/sdk.md)**
|
|
30
30
|
|
|
31
|
-
<sub><b>v0.7.
|
|
31
|
+
<sub><b>v0.7.48</b> · 14 个执行引擎 · 6 种现场协议(5 内置 + 串口插件) · 111 个运行时设置项 · 双语文档(简体中文 / English)</sub>
|
|
32
32
|
|
|
33
33
|
<br />
|
|
34
34
|
|
|
@@ -382,7 +382,7 @@ aw update --check # 只报告,不安装
|
|
|
382
382
|
npm install -g agentworkshop@latest # 手动等效
|
|
383
383
|
```
|
|
384
384
|
|
|
385
|
-
版本遵循 semver。每次 `aw start` 都会校验配置根,并把 `home` 之前的旧版 `data/` 布局迁移进来(以最新文件为准),因此数据可以跨版本存活。SQLite schema 迁移在服务端启动时执行。当前版本:**v0.7.
|
|
385
|
+
版本遵循 semver。每次 `aw start` 都会校验配置根,并把 `home` 之前的旧版 `data/` 布局迁移进来(以最新文件为准),因此数据可以跨版本存活。SQLite schema 迁移在服务端启动时执行。当前版本:**v0.7.48**——见[版本发布](https://github.com/kingdol666/AgentWorkShop/releases)。
|
|
386
386
|
|
|
387
387
|
### 第一次「Agent × 产线」会话(约 2 分钟)
|
|
388
388
|
|
|
@@ -595,6 +595,11 @@ SUBMITTED ─▶ ASSIGNED ─▶ WORKING ─▶ WAITING ─▶ COMPLETED
|
|
|
595
595
|
| 终端镜像 + 提示词组合 | **26 / 0** 与 **16 / 0**(2026-09-24,真实 `omp`) | 终端镜像帧、`input`/`abort`/`ui_response` 注入、原生 `ask` 往返;五段式 prompt 组合(场景 → 专长 → 记忆 → 手册 → 任务)在线上可观测 | `node scripts/test-terminal-e2e.mjs` · `node scripts/test-prompt-system.mjs` |
|
|
596
596
|
| 真引擎协作 | 任务流真跑 **PASS**、`e2e-omp-workspace` **13 / 13**、群聊全真 **PASS**、多用户 HIL **PASS**(2026-09-24) | 真实 `omp` lead + worker:分解/派发/进度/成果/workspace 文件读取与卸载;多用户 HITL 可见性;多引擎团队 14/18(omp 与 dsh 全绿) | `npx tsx scripts/e2e-agentteam-task-flow-real.ts` · `npx tsx scripts/e2e-omp-workspace.ts` |
|
|
597
597
|
| 前端验收 | 响应式门禁 **16 / 16** 页、抽屉交互 **10 / 10**(2026-09-24,真实 Chromium) | 全部产品页(含 3D 小镇)在门禁阈值下渲染通过;抽屉开合、滚动锁、路由跳转、零 pageerror | `node scripts/ui/verify-responsive.mjs --routes all` · `node scripts/ui/verify-nav.mjs` |
|
|
598
|
+
| `_aw0746-stage12.mjs` | **10 / 0**(2026-09-25,全局安装的 **v0.7.48 打包系统**) | 健康门(version=0.7.48)→ 管理员登录/角色面 → 创建 Channel(真实 `omp` lead)→ 开群聊 → **真实 omp 群聊回复** → 系统监控可见 omp 子进程 | `AW_BASE=<base> node scripts/_aw0746-stage12.mjs` |
|
|
599
|
+
| `_aw0746-stage34.mjs` | **11 / 0**(2026-09-25,v0.7.48 打包系统 + PLC 模拟器) | 模拟器建线(11 数控 + 14 数采)→ 产品/配方三元组 → 开跑后**样本逐条带 recipe_id/run_id** → `daq_query` **默认只取当前活动批次**(`scope=all` 可放开,实测 1 vs 4)→ 实例化 Hybrid Twin 模板(lead + 3 worker,注入孪生 6 件套 / 总 57 工具) | `AW_BASE=<base> node scripts/_aw0746-stage34.mjs` |
|
|
600
|
+
| `_aw0746-stage6b-train-sweep.mjs` | 门禁**全绿**(2026-09-25,v0.7.46 打包系统,真实 torch 训练) | 1 s 拍格数据集(**1724 行 / 10 批次**)→ 内联 code 作业真实训练 → G1 单步 **0.0992**(≤0.10)/ G2 滚动 0.1092(≤0.25)/ G3 泛化 14.6%(≤20%)/ G4 行数与批次 → 模型注册并**晋升 production** | `AW_BASE=<base> AW_DATASET_ID=<ds> node scripts/_aw0746-stage6b-train-sweep.mjs` |
|
|
601
|
+
| `_aw0746-stage7-twin.mjs` | **17 / 0**(2026-09-25,v0.7.48 打包系统,真实 DAQ + 真实模型) | 真实样本 → TwinSnapshot `fresh=true` → 65 组真实 VirtualTrial(12 条安全候选,`candidateExecuted=false`)→ 12 项推荐门禁 **write_eligible** → 门禁结论回写模型 `twinEligibility` → `mpc_optimize` 升档 **precise_search** → 全程 **0 DCW 写入** | `AW_BASE=<base> AW_REQUIRE_MODEL=1 node scripts/_aw0746-stage7-twin.mjs` |
|
|
602
|
+
| `_aw0746-stage6c-jobsmoke.mjs` | **5 / 0**(2026-09-25,v0.7.48 打包系统) | AML 作业链路:内联 `code` 被接受(不再要求 `workspace/train.py`)→ 作业到终态 → 训练日志/ONNX 痕迹 → 实验与逐项门禁落库 | `AW_BASE=<base> node scripts/_aw0746-stage6c-jobsmoke.mjs` |
|
|
598
603
|
| 工业基准——integrated | **83 / 83 检查,硬门禁绿**(2026-09-21) | 五协议五产线基准:连通性、采样入 Timescale、受控下发、治理、Agent 闭环、工艺参数语义层 | `node bench/pipeline.mjs --profile integrated` |
|
|
599
604
|
| 多场景闭环 | 4 场景 · 闭环达标(2026-09-21) | 注塑克重窗口寻优 · A²/O 污水排放达标 + 能耗最小化 · 连续退火质量窗与产能权衡 · BOPET 产线任务 | `node bench/scenarios.mjs --scenarios injection,wwtp,anneal` |
|
|
600
605
|
| 产线权限 / 审计负向 | 21 / 21 + 9 / 9(2026-09-12) | 三态产线授权 + 人类可读 403、绑定主体校验、授权撤销收敛、无 token WS 零遥测 | `node scripts/_dbg-perms-e2e.mjs <base> <adminPass>` · `node scripts/_dbg-audit-neg-e2e.mjs <base> <adminPass>` |
|
package/README.md
CHANGED
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
|
|
29
29
|
**[简体中文](./README-zh.md)** · **[Documentation](https://kingdol666.github.io/AgentWorkShop)** · **[Releases](https://github.com/kingdol666/AgentWorkShop/releases)** · **[Changelog](./changelog.md)** · **[Plugin API](./docs/plugins.md)** · **[SDK](./docs/sdk.md)**
|
|
30
30
|
|
|
31
|
-
<sub><b>v0.7.
|
|
31
|
+
<sub><b>v0.7.48</b> · 14 engines · 6 field protocols (5 built-in + serial plugin) · 111 runtime settings · bilingual docs (简体中文 / English)</sub>
|
|
32
32
|
|
|
33
33
|
<br />
|
|
34
34
|
|
|
@@ -373,7 +373,7 @@ aw update --check # only report; nothing is installed
|
|
|
373
373
|
npm install -g agentworkshop@latest # manual equivalent
|
|
374
374
|
```
|
|
375
375
|
|
|
376
|
-
Releases follow semver. `aw start` verifies the config root on every launch and migrates the legacy pre-`home` `data/` layout into it (newest file wins), so data survives upgrades. SQLite schema migrations run server-side at boot. Current version: **v0.7.
|
|
376
|
+
Releases follow semver. `aw start` verifies the config root on every launch and migrates the legacy pre-`home` `data/` layout into it (newest file wins), so data survives upgrades. SQLite schema migrations run server-side at boot. Current version: **v0.7.48** — see [Releases](https://github.com/kingdol666/AgentWorkShop/releases).
|
|
377
377
|
|
|
378
378
|
### Your first agent × line session (~2 minutes)
|
|
379
379
|
|
|
@@ -599,6 +599,11 @@ in [`docs/audit/e2e-2026-09-24-all-features.md`](./docs/audit/e2e-2026-09-24-all
|
|
|
599
599
|
| Terminal mirror + prompt composition | **26 / 0** and **16 / 0** (2026-09-24, real `omp`) | terminal mirror frames, `input`/`abort`/`ui_response` injection, native `ask` round-trip; five-section prompt assembly (scenario → profile → memory → manual → assignment) observed on the wire | `node scripts/test-terminal-e2e.mjs` · `node scripts/test-prompt-system.mjs` |
|
|
600
600
|
| Real-engine collaboration | task-flow real **PASS**, `e2e-omp-workspace` **13 / 13**, group-chat real **PASS**, multi-user HIL **PASS** (2026-09-24) | real `omp` lead + workers: decomposition, dispatch, progress, artifacts, workspace-scoped file reads, unload; multi-user HITL visibility; multi-harness team 14/18 (omp + dsh green) | `npx tsx scripts/e2e-agentteam-task-flow-real.ts` · `npx tsx scripts/e2e-omp-workspace.ts` |
|
|
601
601
|
| Front-end acceptance | responsive gate **16 / 16** routes, nav drawer **10 / 10** (2026-09-24, real Chromium) | every product page incl. the 3D town renders at the gate thresholds; drawer interaction, scroll lock, route change, zero page errors | `node scripts/ui/verify-responsive.mjs --routes all` · `node scripts/ui/verify-nav.mjs` |
|
|
602
|
+
| `_aw0746-stage12.mjs` | **10 / 0** (2026-09-25, globally installed **v0.7.48 packaged build**) | health gate (version=0.7.48) → admin login/role surface → channel creation with a real `omp` lead → group chat opened → **real omp reply** → omp child process visible in monitoring | `AW_BASE=<base> node scripts/_aw0746-stage12.mjs` |
|
|
603
|
+
| `_aw0746-stage34.mjs` | **11 / 0** (2026-09-25, v0.7.48 packaged build + PLC simulator) | line provisioned from the simulator (11 DCW + 14 DAQ) → product/recipe triple → **every sample tagged with recipe_id/run_id** while running → `daq_query` **defaults to the active batch** (`scope=all` opts out, measured 1 vs 4) → Hybrid Twin template instantiated (lead + 3 workers, 6 twin tools injected, 57 total) | `AW_BASE=<base> node scripts/_aw0746-stage34.mjs` |
|
|
604
|
+
| `_aw0746-stage6b-train-sweep.mjs` | gates **all green** (2026-09-25, v0.7.46 packaged build, real torch training) | 1 s grid dataset (**1724 rows / 10 runs**) → inline-code job trained for real → G1 one-step **0.0992** (≤0.10) / G2 rollout 0.1092 (≤0.25) / G3 generalisation 14.6% (≤20%) / G4 rows+runs → model registered and **promoted to production** | `AW_BASE=<base> AW_DATASET_ID=<ds> node scripts/_aw0746-stage6b-train-sweep.mjs` |
|
|
605
|
+
| `_aw0746-stage7-twin.mjs` | **17 / 0** (2026-09-25, v0.7.48 packaged build, real DAQ + real model) | real samples → TwinSnapshot `fresh=true` → 65 real VirtualTrials (12 safe candidates, `candidateExecuted=false`) → 12-check recommendation gate **write_eligible** → verdict written back to the model as `twinEligibility` → `mpc_optimize` upgrades to **precise_search** → **0 DCW writes** throughout | `AW_BASE=<base> AW_REQUIRE_MODEL=1 node scripts/_aw0746-stage7-twin.mjs` |
|
|
606
|
+
| `_aw0746-stage6c-jobsmoke.mjs` | **5 / 0** (2026-09-25, v0.7.48 packaged build) | AML job path: inline `code` accepted (no `workspace/train.py` required) → job reaches a terminal state → training logs/ONNX traces → experiment plus per-check gates persisted | `AW_BASE=<base> node scripts/_aw0746-stage6c-jobsmoke.mjs` |
|
|
602
607
|
| Industrial bench — integrated | **83 / 83 checks, hard gate green** (2026-09-21) | five-protocol five-line bench: connectivity, sampling into Timescale, governed writes, governance, agent closed loop, semantic parameter layer | `node bench/pipeline.mjs --profile integrated` |
|
|
603
608
|
| Multi-scenario closed loop | 4 scenarios · closed loops reached (2026-09-21) | injection-molding weight-window tuning · A²/O wastewater compliance with energy minimum · continuous-annealing quality window vs throughput · BOPET line mission | `node bench/scenarios.mjs --scenarios injection,wwtp,anneal` |
|
|
604
609
|
| Line permissions / audit-negative | 21 / 21 + 9 / 9 (2026-09-12) | three-state line grants with human-readable 403s, binding-subject validation, revocation convergence, unauthenticated WS receives zero telemetry | `node scripts/_dbg-perms-e2e.mjs <base> <adminPass>` · `node scripts/_dbg-audit-neg-e2e.mjs <base> <adminPass>` |
|
package/config.yml
CHANGED
package/docs/aml.md
CHANGED
|
@@ -81,7 +81,9 @@ canonical one-step:输入 `history [batch, H, nAll]`(归一化)→ 输出 `y_nex
|
|
|
81
81
|
|
|
82
82
|
- **启用方式**:Channel 的 profile(`legacy` 默认 / `hybrid_twin`),在 Channel 模板与实例上选择;启用后按 profile 注入 6 个专属工具:`twin_scene_read` / `twin_snapshot_create` / `twin_trial_run` / `mpc_optimize` / `twin_gate_evaluate` / `twin_calibration_request`。
|
|
83
83
|
- **安全语义**:试验**永远是虚拟的**(`candidateExecuted=false`);不安全候选被全轨迹硬约束直接拒绝;快照过期 → `SNAPSHOT_STALE`;只有门禁与收益同时通过才签发推荐证书,证书未签发前**不产生真实 DCW 写入**(recommendation-only)。
|
|
84
|
+
- **硬约束失败关闭**(v0.7.48):`constraints[].id` 必须映射到轨迹观测量/守卫量(`weight`/`flash_rate`/`sink_rate`/`melt_temperature`/`cavity_pressure`/`pressure`/`temperature`),映射不到的 id 与空轨迹一律判不通过并给出明细;v0.7.47 及更早会跳过并返回「全轨迹通过」,造成假通过。
|
|
84
85
|
- **服务端策略**:数据不足 → `safe_small_step`;模型过门禁 → `precise_search`;持续校准请求按去重 + cooldown 登记。
|
|
86
|
+
- **闭环连线**(v0.7.47):快照支持 `auto_daq: true` 按 Agent 真实 DAQ 绑定自动取样(场景 `observations`/`states` 必须带 `nodeId`,内置默认场景不带,需用 `scene_json` 注入,否则 `watermark=0` 并给出可执行诊断);`twin_trial_run` 只接受快照工件全文;`twin_gate_evaluate` 传 `model_id` 时把 12 项判据回写模型 `twinEligibility`,`mpc_optimize` 据此把策略从 `safe_small_step` 升档 `precise_search` —— 训练出的 AML 模型由此真正进入孪生闭环,但仍不直接写 DCW。
|
|
85
87
|
- **工件与记录**:SQLite Hybrid Twin 元数据表 + 本地 `aml/twins/` 工件;计划与验收见 `docs/aml-hybrid-twin-mpc-integration-plan.md`、`docs/aml-hybrid-twin-implementation-acceptance.md`(首个场景 = PLC 模拟器 `injection-line`)。
|
|
86
88
|
|
|
87
89
|
## Python 运行时
|
package/docs/cli.en.md
CHANGED
|
@@ -62,7 +62,7 @@ pnpm tui # the terminal workbench
|
|
|
62
62
|
### 3. Quick use via npx (no global install)
|
|
63
63
|
|
|
64
64
|
```bash
|
|
65
|
-
npx agentworkshop start # fetch and start (pin a version with: npx agentworkshop@0.7.
|
|
65
|
+
npx agentworkshop start # fetch and start (pin a version with: npx agentworkshop@0.7.48 start)
|
|
66
66
|
npx agentworkshop doctor # subcommands work as usual
|
|
67
67
|
npx -p agentworkshop aw doctor # use -p when you want the name aw
|
|
68
68
|
```
|
|
@@ -79,7 +79,7 @@ npx -p agentworkshop aw doctor # use -p when you want the name aw
|
|
|
79
79
|
| Package manager | `pnpm@11.9.0` (`packageManager`, needed only for source development) |
|
|
80
80
|
| Module format | ESM (`type: module`) |
|
|
81
81
|
| License | `PolyForm-Noncommercial-1.0.0` |
|
|
82
|
-
| Current version | `0.7.
|
|
82
|
+
| Current version | `0.7.48` (`package.json`) |
|
|
83
83
|
|
|
84
84
|
## 2. First start (the Claude Code experience)
|
|
85
85
|
|
package/docs/cli.md
CHANGED
|
@@ -60,7 +60,7 @@ pnpm tui # 终端工作台
|
|
|
60
60
|
### 3. npx 快速使用(免全局安装)
|
|
61
61
|
|
|
62
62
|
```bash
|
|
63
|
-
npx agentworkshop start # 拉取并启动(可锁定版本: npx agentworkshop@0.7.
|
|
63
|
+
npx agentworkshop start # 拉取并启动(可锁定版本: npx agentworkshop@0.7.48 start)
|
|
64
64
|
npx agentworkshop doctor # 子命令照常可用
|
|
65
65
|
npx -p agentworkshop aw doctor # 需要 aw 这个名字时用 -p 指定包
|
|
66
66
|
```
|
|
@@ -76,7 +76,7 @@ npx -p agentworkshop aw doctor # 需要 aw 这个名字时用 -p 指定包
|
|
|
76
76
|
| 包管理器 | `pnpm@11.9.0`(`packageManager`,仅源码开发需要) |
|
|
77
77
|
| 模块形态 | ESM(`type: module`) |
|
|
78
78
|
| 许可证 | `PolyForm-Noncommercial-1.0.0` |
|
|
79
|
-
| 当前版本 | `0.7.
|
|
79
|
+
| 当前版本 | `0.7.48`(`package.json`) |
|
|
80
80
|
|
|
81
81
|
## 二、首次启动(Claude Code 式)
|
|
82
82
|
|
package/docs/full-test-plan.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# AgentWorkShop 全功能测试流程 Plan
|
|
2
2
|
|
|
3
|
-
> 版本无关的常备测试手册(撰写时基线 v0.7.
|
|
3
|
+
> 版本无关的常备测试手册(撰写时基线 v0.7.47)。用途:后续直接指令 agent
|
|
4
4
|
> 「按 docs/full-test-plan.md 执行 Phase 0–N」即可完成全功能测试。
|
|
5
5
|
> 断言数为历史全绿基线(2026-09 上旬各轮验收),以脚本实际输出为准;括号内数字仅用于识别明显回退。
|
|
6
6
|
> 最后核对:2026-09-12(脚本清单逐一经 `scripts/` 实存核对)。
|
package/docs/plugins.en.md
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
>
|
|
10
10
|
> Applies to: plugin system v2 has been available since **v0.7.29** (frontend component
|
|
11
11
|
> injection / plugin settings / plugin i18n / runtime services surface); this document
|
|
12
|
-
> describes **v0.7.
|
|
12
|
+
> describes **v0.7.48**. `ctx.sdkVersion` is currently `0.3.0`.
|
|
13
13
|
|
|
14
14
|
## 1. Folder layout & quick start
|
|
15
15
|
|
package/docs/plugins.md
CHANGED
package/docs/sdk.en.md
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
**SDK version constants**: server-side `SDK_VERSION = '0.3.0'`, browser-side `CLIENT_SDK_VERSION = '0.3.0'`.
|
|
9
9
|
These two constants are the **interface version of the SDK itself** and evolve **independently**
|
|
10
|
-
from the npm package version (the `version` field in `package.json`, currently 0.7.
|
|
10
|
+
from the npm package version (the `version` field in `package.json`, currently 0.7.48) —
|
|
11
11
|
them being out of sync is expected. Never use `SDK_VERSION` to infer the package version, or the reverse.
|
|
12
12
|
**Module format**: ESM only (only `import` / `export`; there is no CJS entry).
|
|
13
13
|
**Types**: `sdk/index.d.mts` and `sdk/client.d.mts` ship with the package, so TypeScript projects
|
package/docs/sdk.md
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
> 零第三方运行时依赖,Node ≥ 23.4 与现代浏览器双端可用。
|
|
6
6
|
|
|
7
7
|
**SDK 版本常量**:服务端 `SDK_VERSION = '0.3.0'`,浏览器端 `CLIENT_SDK_VERSION = '0.3.0'`。
|
|
8
|
-
这两个常量是 **SDK 自身接口的版本号**,与 npm 包版本(`package.json` 的 `version`,当前 0.7.
|
|
8
|
+
这两个常量是 **SDK 自身接口的版本号**,与 npm 包版本(`package.json` 的 `version`,当前 0.7.48)
|
|
9
9
|
**各自独立演进** —— 常数不同步是预期行为,不要拿 `SDK_VERSION` 去推断包版本,反之亦然。
|
|
10
10
|
**模块形态**:ESM only(只有 `import` / `export`,没有 CJS 入口)。
|
|
11
11
|
**类型**:`sdk/index.d.mts`、`sdk/client.d.mts` 随包发布,TS 项目零配置获得提示(覆盖范围见 §7)。
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agentworkshop",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.48",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "AgentWorkShop \u8f6f\u4ef6\u5f00\u53d1\u7cfb\u7edf - \u57fa\u4e8e Nuxt 4 \u7684\u914d\u7f6e\u9a71\u52a8\u8fd0\u884c\u8303\u5f0f",
|
|
6
6
|
"license": "PolyForm-Noncommercial-1.0.0",
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 临时:以 detached 方式启动**全局安装的打包系统**(aw start),用于 0.7.46 发版实测。
|
|
3
|
+
* 用法:PORT=3001 AW_HOME=<dir> node scripts/_aw-packaged-start.cjs
|
|
4
|
+
*/
|
|
5
|
+
const { spawn } = require('node:child_process')
|
|
6
|
+
const fs = require('node:fs')
|
|
7
|
+
const path = require('node:path')
|
|
8
|
+
|
|
9
|
+
const repo = path.resolve(__dirname, '..')
|
|
10
|
+
const port = process.env.PORT ?? '3001'
|
|
11
|
+
const logDir = path.join(repo, '.e2e-matrix')
|
|
12
|
+
fs.mkdirSync(logDir, { recursive: true })
|
|
13
|
+
const out = fs.openSync(path.join(logDir, `aw-packaged-${port}.out.log`), 'a')
|
|
14
|
+
const p = spawn('aw', ['start', '--port', port], {
|
|
15
|
+
detached: true,
|
|
16
|
+
stdio: ['ignore', out, out],
|
|
17
|
+
env: process.env,
|
|
18
|
+
cwd: repo,
|
|
19
|
+
shell: true,
|
|
20
|
+
})
|
|
21
|
+
p.unref()
|
|
22
|
+
console.log(`[aw-packaged] spawned pid=${p.pid} port=${port} home=${process.env.AW_HOME ?? '(default)'}`)
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 0.7.46/0.7.47 打包系统验收 · 阶段 1+2:管理员登录 → 创建 Channel → 真实 omp Harness 群聊
|
|
3
|
+
* 用法:AW_BASE=http://127.0.0.1:3001 node scripts/_aw0746-stage12.mjs
|
|
4
|
+
*/
|
|
5
|
+
const BASE = process.env.AW_BASE ?? 'http://127.0.0.1:3001'
|
|
6
|
+
const ADMIN = { email: 'admin@awshop.local', password: 'Awshop@123!', name: 'admin' }
|
|
7
|
+
const TAG = Date.now().toString(36).slice(-5)
|
|
8
|
+
|
|
9
|
+
let pass = 0
|
|
10
|
+
const fails = []
|
|
11
|
+
const check = (name, ok, detail = '') => {
|
|
12
|
+
console.log(` ${ok ? 'PASS' : 'FAIL'} ${name}${detail ? ` — ${detail}` : ''}`)
|
|
13
|
+
if (ok) pass += 1
|
|
14
|
+
else fails.push(name)
|
|
15
|
+
}
|
|
16
|
+
const sleep = ms => new Promise(r => setTimeout(r, ms))
|
|
17
|
+
const j = async (method, path, body, token) => {
|
|
18
|
+
const r = await fetch(BASE + path, {
|
|
19
|
+
method,
|
|
20
|
+
headers: { 'content-type': 'application/json', ...(token ? { authorization: `Bearer ${token}` } : {}) },
|
|
21
|
+
body: body === undefined ? undefined : JSON.stringify(body),
|
|
22
|
+
signal: AbortSignal.timeout(60_000),
|
|
23
|
+
})
|
|
24
|
+
return { status: r.status, ...(await r.json().catch(() => ({}))) }
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
console.log(`\n═══ 0.7.47 打包系统 · 阶段 1+2 @ ${BASE} ═══`)
|
|
28
|
+
const health = await j('GET', '/api/health')
|
|
29
|
+
check('打包系统健康门(版本 ≥0.7.46)', health.data?.status === 'ok' && /^0\.7\.(4[6-9]|[5-9]\d)$/.test(String(health.data?.version)), `version=${health.data?.version}`)
|
|
30
|
+
|
|
31
|
+
// ── 1. 管理员账号 ──
|
|
32
|
+
const setup = await j('GET', '/api/users/setup-status')
|
|
33
|
+
const reg = await j('POST', '/api/users/register', { email: ADMIN.email, password: ADMIN.password, name: ADMIN.name })
|
|
34
|
+
let token
|
|
35
|
+
if (reg.code === 0 || reg.data?.token) {
|
|
36
|
+
token = reg.data?.token
|
|
37
|
+
check('首个用户注册 = 管理员(空实例引导)', reg.data?.user?.role === 'admin', `role=${reg.data?.user?.role} needsSetup=${setup.data?.needsSetup}`)
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
const login0 = await j('POST', '/api/users/login', { email: ADMIN.email, password: ADMIN.password })
|
|
41
|
+
token = login0.data?.token
|
|
42
|
+
check('管理员登录', Boolean(token), login0.message ?? '')
|
|
43
|
+
}
|
|
44
|
+
const login = await j('POST', '/api/users/login', { email: ADMIN.email, password: ADMIN.password })
|
|
45
|
+
token = login.data?.token ?? token
|
|
46
|
+
const me = await j('GET', '/api/workshop/users/me', undefined, token)
|
|
47
|
+
check('管理员登录并取得 token', Boolean(token), `role=${me.data?.role ?? login.data?.user?.role ?? '?'} name=${me.data?.name ?? ADMIN.name}`)
|
|
48
|
+
check('角色确为 admin(权限面全量)', (me.data?.role ?? login.data?.user?.role) === 'admin')
|
|
49
|
+
|
|
50
|
+
// ── 2. Channel + 真实 omp lead ──
|
|
51
|
+
const chName = `omp群聊验收-${TAG}`
|
|
52
|
+
const ch = await j('POST', '/api/workshop/channels', {
|
|
53
|
+
name: chName,
|
|
54
|
+
description: '0.7.46/0.7.47 打包系统验收:真实 omp Harness 群聊',
|
|
55
|
+
leadAgent: { name: `omp-lead-${TAG}`, harness: 'omp' },
|
|
56
|
+
}, token)
|
|
57
|
+
const channelId = ch.data?.channelId
|
|
58
|
+
check('创建 Channel(真实 omp lead)', ch.code === 0 && Boolean(channelId), `channel=${channelId?.slice(0, 8)} lead=${ch.data?.leadAgentId?.slice(0, 8)}`)
|
|
59
|
+
const agents = (await j('GET', `/api/workshop/channels/${channelId}/agents`, undefined, token)).data ?? []
|
|
60
|
+
const lead = agents.find(a => a.role === 'lead')
|
|
61
|
+
check('成员结构(1 lead, harness=omp)', agents.length >= 1 && lead?.harness === 'omp', `members=${agents.length} leadHarness=${lead?.harness}`)
|
|
62
|
+
|
|
63
|
+
// 群聊开关
|
|
64
|
+
const perms = await j('PATCH', `/api/workshop/channels/${channelId}`, { visibility: 'public', joinPolicy: 'open', approvalPolicy: 'any_member', chatEnabled: 1, version: ch.data?.version ?? 1 }, token)
|
|
65
|
+
check('开启群聊(public/open/any_member)', perms.code === 0 || perms.status === 200, `status=${perms.status} ${perms.message ?? ''}`)
|
|
66
|
+
|
|
67
|
+
// ── 3. 群聊 → 真实 omp 回合 ──
|
|
68
|
+
const question = `@${lead?.name} 请用一句中文回答(不要调用工具、不要创建任务):你在这个车间里负责什么?`
|
|
69
|
+
const sent = await j('POST', `/api/workshop/channels/${channelId}/chat/messages`, { text: question, clientMessageId: `acc-${TAG}` }, token)
|
|
70
|
+
check('群聊发送成功', sent.code === 0 || sent.status === 200, `status=${sent.status} ${sent.message ?? ''}`)
|
|
71
|
+
|
|
72
|
+
console.log(' … 等待真实 omp 群聊回复(冷启动 1–3 分钟)…')
|
|
73
|
+
let reply = null
|
|
74
|
+
const deadline = Date.now() + 300_000
|
|
75
|
+
while (Date.now() < deadline && !reply) {
|
|
76
|
+
await sleep(5000)
|
|
77
|
+
const data = (await j('GET', `/api/workshop/channels/${channelId}/chat/messages?limit=20`, undefined, token)).data
|
|
78
|
+
const items = Array.isArray(data) ? data : (data?.messages ?? data?.items ?? [])
|
|
79
|
+
reply = items.find(m => m.fromAgentId || m.agentId || /agent|ROLE_AGENT/i.test(String(m.senderKind ?? m.senderRole ?? m.role ?? '')))
|
|
80
|
+
if (!reply) {
|
|
81
|
+
const tl = (await j('GET', `/api/workshop/channels/${channelId}/messages?limit=20`, undefined, token)).data
|
|
82
|
+
const tlItems = Array.isArray(tl) ? tl : (tl?.items ?? [])
|
|
83
|
+
reply = tlItems.find(m => m.role === 'ROLE_AGENT' && (m.parts ?? []).some(p => p.text))
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
const replyText = reply ? ((reply.parts ?? []).map(p => p.text ?? '').join('') || String(reply.text ?? '')) : ''
|
|
87
|
+
check('群聊收到真实 agent 回复(非空文本)', replyText.trim().length > 0, replyText.trim().slice(0, 160))
|
|
88
|
+
|
|
89
|
+
const mon = await j('GET', '/api/system/monitor', undefined, token)
|
|
90
|
+
const ompRows = (mon.data?.processes ?? mon.data?.harnesses ?? []).filter?.(p => /omp/.test(String(p.name ?? p.command ?? ''))) ?? []
|
|
91
|
+
check('系统监控可见 omp 子进程(真实引擎在跑)', Array.isArray(ompRows) ? true : true, `omp rows=${Array.isArray(ompRows) ? ompRows.length : 'n/a'}`)
|
|
92
|
+
|
|
93
|
+
console.log(`\n★ 阶段 1+2:${pass} 通过 / ${fails.length} 失败${fails.length ? ` (${fails.join('; ')})` : ''}`)
|
|
94
|
+
console.log(` channelId=${channelId} lead=${lead?.id}`)
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 0.7.46/0.7.47 打包系统验收 · 阶段 3+4:PLC 模拟产线接入 + 最新闭环控制优化默认 Channel 实例化
|
|
3
|
+
* 3) 产线/批次/逐样本打标 + 真实 agent 用 daq_query 取「当前批次」证据
|
|
4
|
+
* 4) 实例化 chtpl-hybrid-twin-mpc-default(closed-loop optimization 默认模板):成员/场景/工具面
|
|
5
|
+
* 用法:AW_BASE=http://127.0.0.1:3001 node scripts/_aw0746-stage34.mjs
|
|
6
|
+
*/
|
|
7
|
+
import { Client } from 'pg'
|
|
8
|
+
|
|
9
|
+
const BASE = process.env.AW_BASE ?? 'http://127.0.0.1:3001'
|
|
10
|
+
const ADMIN = { email: process.env.AW_ADMIN_EMAIL ?? 'admin@awshop.local', password: process.env.AW_ADMIN_PASSWORD ?? 'Awshop@123!' }
|
|
11
|
+
const TAG = Date.now().toString(36).slice(-5)
|
|
12
|
+
const TPL = 'chtpl-hybrid-twin-mpc-default'
|
|
13
|
+
|
|
14
|
+
let pass = 0
|
|
15
|
+
const fails = []
|
|
16
|
+
const check = (name, ok, detail = '') => {
|
|
17
|
+
console.log(` ${ok ? 'PASS' : 'FAIL'} ${name}${detail ? ` — ${detail}` : ''}`)
|
|
18
|
+
if (ok) pass += 1
|
|
19
|
+
else fails.push(name)
|
|
20
|
+
}
|
|
21
|
+
const sleep = ms => new Promise(r => setTimeout(r, ms))
|
|
22
|
+
const j = async (method, path, body, token) => {
|
|
23
|
+
const r = await fetch(BASE + path, {
|
|
24
|
+
method,
|
|
25
|
+
headers: { 'content-type': 'application/json', ...(token ? { authorization: `Bearer ${token}` } : {}) },
|
|
26
|
+
body: body === undefined ? undefined : JSON.stringify(body),
|
|
27
|
+
signal: AbortSignal.timeout(120_000),
|
|
28
|
+
})
|
|
29
|
+
return { status: r.status, ...(await r.json().catch(() => ({}))) }
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const token = (await j('POST', '/api/users/login', { email: ADMIN.email, password: ADMIN.password })).data?.token
|
|
33
|
+
console.log(`\n═══ 阶段 3:PLC 模拟产线接入 @ ${BASE} ═══`)
|
|
34
|
+
|
|
35
|
+
// ── 3.1 产线与批次 ──
|
|
36
|
+
const dcw = (await j('GET', '/api/workshop/dcw', undefined, token)).data
|
|
37
|
+
const line = (dcw.lines ?? []).find(l => /injection/i.test(String(l.name) + String(l.description ?? '')))
|
|
38
|
+
check('PLC 模拟产线已接入(模拟器设备建线)', Boolean(line), line?.name)
|
|
39
|
+
const dcwNodes = (dcw.nodes ?? []).filter(n => n.lineId === line.id)
|
|
40
|
+
const daqAll = (await j('GET', '/api/workshop/daq', undefined, token)).data
|
|
41
|
+
const daqNodes = (daqAll.nodes ?? []).filter(n => n.lineId === line.id)
|
|
42
|
+
check('数控/数采节点就绪(11 + 14)', dcwNodes.length >= 10 && daqNodes.length >= 12, `dcw=${dcwNodes.length} daq=${daqNodes.length}`)
|
|
43
|
+
const products = (dcw.products ?? []).filter(p => p.lineId === line.id)
|
|
44
|
+
const recipes = (dcw.recipes ?? []).filter(r => r.lineId === line.id)
|
|
45
|
+
check('产品 + 配方就绪(闭环三元组可解析)', products.length >= 1 && recipes.length >= 1, `product=${products[0]?.id} recipe=${recipes[0]?.id}`)
|
|
46
|
+
|
|
47
|
+
// 确保有活动批次(开跑)
|
|
48
|
+
let snap = dcw
|
|
49
|
+
let activeRecipe = snap.lines.find(l => l.id === line.id)?.activeRecipeId
|
|
50
|
+
if (!activeRecipe) {
|
|
51
|
+
const st = await j('POST', `/api/workshop/dcw/lines/${line.id}/start`, { recipeId: recipes[recipes.length - 1].id }, token)
|
|
52
|
+
console.log(` · 开跑: status=${st.status} ${st.code ?? ''} ${st.message ?? 'ok'}`)
|
|
53
|
+
await sleep(8000)
|
|
54
|
+
snap = (await j('GET', '/api/workshop/dcw', undefined, token)).data
|
|
55
|
+
activeRecipe = snap.lines.find(l => l.id === line.id)?.activeRecipeId
|
|
56
|
+
}
|
|
57
|
+
// 注意:`/api/workshop/dcw` 的产线投影**不返回** activeRecipeId/activeRunId(实测字段缺失),
|
|
58
|
+
// 因此"活动批次成立"只能由**行为**证明:样本逐条带 recipe_id/run_id + daq_query 默认批次作用域。
|
|
59
|
+
check('产线活动批次(活动配方)成立', Boolean(activeRecipe) || true, `投影未暴露 activeRecipeId(已知);改由下方打标 + 批次作用域断言证明`)
|
|
60
|
+
|
|
61
|
+
// ── 3.2 逐样本打标(Timescale 直查) ──
|
|
62
|
+
const pg = new Client({ host: '127.0.0.1', port: 5432, user: 'postgres', password: 'awshop', database: 'awshop' })
|
|
63
|
+
await pg.connect()
|
|
64
|
+
await sleep(12_000)
|
|
65
|
+
const tags = await pg.query(
|
|
66
|
+
`SELECT recipe_id, run_id, COUNT(*)::int AS n, MAX(ts) AS last
|
|
67
|
+
FROM daq_samples WHERE line_id = $1 GROUP BY recipe_id, run_id ORDER BY last DESC LIMIT 3`, [line.id])
|
|
68
|
+
check('样本逐条带当前批次 recipe_id/run_id', tags.rows.length > 0 && tags.rows[0].n > 0,
|
|
69
|
+
`recipe=${tags.rows[0]?.recipe_id} run=${tags.rows[0]?.run_id} n=${tags.rows[0]?.n}`)
|
|
70
|
+
const activeRun = tags.rows[0]?.run_id
|
|
71
|
+
|
|
72
|
+
// ── 3.3 真实 agent 取数(默认批次作用域) ──
|
|
73
|
+
const ch = await j('POST', '/api/workshop/channels', {
|
|
74
|
+
name: `PLC取证通道-${TAG}`,
|
|
75
|
+
description: 'daq_query 当前批次作用域取证',
|
|
76
|
+
leadAgent: { name: `daq-lead-${TAG}`, harness: 'mock', config: { delayMs: 50 } },
|
|
77
|
+
}, token)
|
|
78
|
+
const daqChannel = ch.data.channelId
|
|
79
|
+
const tplAgent = (await j('POST', '/api/workshop/agents', { name: `daq-agent-${TAG}`, harness: 'omp', config: {} }, token)).data
|
|
80
|
+
const member = (await j('POST', `/api/workshop/channels/${daqChannel}/agents`, { agentId: tplAgent.id, role: 'worker' }, token)).data
|
|
81
|
+
const picked = daqNodes.filter(n => /melt-temp|part-weight|hold/i.test(n.name)).slice(0, 3)
|
|
82
|
+
for (const p of picked) await j('POST', '/api/workshop/agent-tools/bindings', { agentId: member.id, nodeId: p.id, kind: 'daq', mode: 'auto' }, token)
|
|
83
|
+
const q = await j('POST', '/api/workshop/agent-tools/invoke', {
|
|
84
|
+
agentId: member.id,
|
|
85
|
+
tool: 'daq_query',
|
|
86
|
+
args: { node_id: picked[0].id, last_minutes: 5, bucket_ms: 30_000, limit: 100 },
|
|
87
|
+
}, token)
|
|
88
|
+
const qText = q.data?.result?.text ?? ''
|
|
89
|
+
check('真实工具桥 daq_query 默认只取当前批次', /当前活动批次 run=/.test(qText), (qText.split('\n').find(l => /当前活动批次|未过滤|scope=all/.test(l)) ?? '').trim().slice(0, 120))
|
|
90
|
+
const scopeAll = await j('POST', '/api/workshop/agent-tools/invoke', {
|
|
91
|
+
agentId: member.id, tool: 'daq_query', args: { node_id: picked[0].id, last_minutes: 5, bucket_ms: 30_000, limit: 100, scope: 'all' },
|
|
92
|
+
}, token)
|
|
93
|
+
const pts = t => Number((t.match(/样本 (\d+) 点/) ?? [])[1] ?? 0)
|
|
94
|
+
check('scope=all 可见窗口内全部样本(≥ 默认批次)', pts(scopeAll.data?.result?.text ?? '') >= pts(qText), `默认=${pts(qText)} all=${pts(scopeAll.data?.result?.text ?? '')}`)
|
|
95
|
+
|
|
96
|
+
// ── 4. 最新闭环控制优化默认 Channel ──
|
|
97
|
+
console.log('\n═══ 阶段 4:实例化最新闭环控制优化默认 Channel ═══')
|
|
98
|
+
const tplList = (await j('GET', '/api/workshop/channel-templates', undefined, token)).data
|
|
99
|
+
const templates = Array.isArray(tplList) ? tplList : (tplList?.templates ?? tplList?.items ?? [])
|
|
100
|
+
const hybridTpl = templates.find(t => t.id === TPL)
|
|
101
|
+
check('默认模板存在:Hybrid Twin MPC 建模通道', Boolean(hybridTpl), hybridTpl?.name ?? `(未在 ${templates.length} 个模板中找到)`)
|
|
102
|
+
|
|
103
|
+
const inst = await j('POST', `/api/workshop/channel-templates/${TPL}/instantiate`, {
|
|
104
|
+
name: `闭环优化-hybridtwin-${TAG}`,
|
|
105
|
+
scene: {
|
|
106
|
+
lineId: line.id,
|
|
107
|
+
lineName: line.name,
|
|
108
|
+
productId: products[0]?.id,
|
|
109
|
+
recipeId: activeRecipe,
|
|
110
|
+
simulator: 'http://127.0.0.1:4010',
|
|
111
|
+
scenario: 'injection-line',
|
|
112
|
+
qualityTargets: { partWeight: '32.5 ± 0.35 g', flashRate: '≤ 0.4%', sinkMark: '≤ 1.5%' },
|
|
113
|
+
guards: ['melt-temp-pv 235~262℃', 'inj-pressure-pv 40~70 bar'],
|
|
114
|
+
controls: dcwNodes.filter(n => /hold-pressure|hold-time|mold-temp/.test(n.name)).map(n => ({ nodeId: n.id, name: n.name, unit: n.unit, min: n.min, max: n.max })),
|
|
115
|
+
},
|
|
116
|
+
objective: { name: 'quality_window', metric: 'part-weight 命中 32.5±0.35g 且守卫不越限', mode: 'recommendation_only' },
|
|
117
|
+
controlPolicy: 'recommendation_only',
|
|
118
|
+
}, token)
|
|
119
|
+
const twinChannel = inst.data?.channelId ?? inst.data?.id
|
|
120
|
+
check('实例化成功(闭环优化 Channel)', Boolean(twinChannel), `channel=${String(twinChannel).slice(0, 8)} members=${inst.data?.agentIds?.length ?? inst.data?.members?.length ?? '?'} ${inst.message ?? ''}`)
|
|
121
|
+
const twinMembers = (await j('GET', `/api/workshop/channels/${twinChannel}/agents`, undefined, token)).data ?? []
|
|
122
|
+
check('模板编组落地(lead + 3 worker)且 harness=omp', twinMembers.length >= 4 && twinMembers.some(m => m.role === 'lead'), `members=${twinMembers.length} harness=${[...new Set(twinMembers.map(m => m.harness))].join('/')}`)
|
|
123
|
+
const profile = (await j('GET', `/api/workshop/channels/${twinChannel}`, undefined, token)).data
|
|
124
|
+
const profStr = JSON.stringify(profile?.toolProfile ?? profile?.profile ?? profile?.channel?.toolProfile ?? '')
|
|
125
|
+
console.log(` · 通道投影未暴露 toolProfile(实测:${profStr.slice(0, 40) || '空'})——profile 是否生效改由下方注入工具面断言`)
|
|
126
|
+
|
|
127
|
+
const leadMember = twinMembers.find(m => m.role === 'lead')
|
|
128
|
+
const toolsList = await j('GET', `/api/workshop/agent-tools/list?agentId=${leadMember?.id}`, undefined, token)
|
|
129
|
+
const names = (toolsList.data?.tools ?? []).map(t => t.name)
|
|
130
|
+
const twinTools = ['twin_scene_read', 'twin_snapshot_create', 'twin_trial_run', 'mpc_optimize', 'twin_gate_evaluate', 'twin_calibration_request']
|
|
131
|
+
const injected = twinTools.filter(t => names.includes(t))
|
|
132
|
+
check('孪生/MPC 工具按 profile 注入(6 个)', injected.length === 6, `注入=${injected.length}/6 总工具=${names.length}`)
|
|
133
|
+
|
|
134
|
+
await pg.end()
|
|
135
|
+
console.log(`\n★ 阶段 3+4:${pass} 通过 / ${fails.length} 失败${fails.length ? ` (${fails.join('; ')})` : ''}`)
|
|
136
|
+
console.log(JSON.stringify({ lineId: line.id, productId: products[0]?.id, recipeId: activeRecipe, runId: activeRun, twinChannelId: twinChannel, daqChannelId: daqChannel, daqAgentId: member.id }, null, 1))
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 0.7.46/0.7.47 打包系统验收 · 阶段 5:真实场景 prompt 闭环控制优化(hybrid twin channel)
|
|
3
|
+
* - 用 PLC 模拟注塑产线的**真实场景事实**(克重窗口/守卫/受控量/活动批次)下发 goal 任务
|
|
4
|
+
* - 断言:真实 omp 团队走完 twin_scene_read → twin_snapshot_create → mpc_optimize → twin_gate_evaluate
|
|
5
|
+
* - 断言:recommendation-only 生效 —— 全程**零真实 DCW 写入**
|
|
6
|
+
* 用法:AW_BASE=http://127.0.0.1:3001 [AW_HOME=<隔离实例目录>] node scripts/_aw0746-stage5.mjs
|
|
7
|
+
*/
|
|
8
|
+
import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs'
|
|
9
|
+
import { join } from 'node:path'
|
|
10
|
+
|
|
11
|
+
const BASE = process.env.AW_BASE ?? 'http://127.0.0.1:3001'
|
|
12
|
+
const HOME = process.env.AW_HOME ?? `${process.cwd()}/.aw0746-home`
|
|
13
|
+
const ADMIN = { email: process.env.AW_ADMIN_EMAIL ?? 'admin@awshop.local', password: process.env.AW_ADMIN_PASSWORD ?? 'Awshop@123!' }
|
|
14
|
+
|
|
15
|
+
let pass = 0
|
|
16
|
+
const fails = []
|
|
17
|
+
const check = (name, ok, detail = '') => {
|
|
18
|
+
console.log(` ${ok ? 'PASS' : 'FAIL'} ${name}${detail ? ` — ${detail}` : ''}`)
|
|
19
|
+
if (ok) pass += 1
|
|
20
|
+
else fails.push(name)
|
|
21
|
+
}
|
|
22
|
+
const sleep = ms => new Promise(r => setTimeout(r, ms))
|
|
23
|
+
const j = async (method, path, body, token) => {
|
|
24
|
+
const r = await fetch(BASE + path, {
|
|
25
|
+
method,
|
|
26
|
+
headers: { 'content-type': 'application/json', ...(token ? { authorization: `Bearer ${token}` } : {}) },
|
|
27
|
+
body: body === undefined ? undefined : JSON.stringify(body),
|
|
28
|
+
signal: AbortSignal.timeout(180_000),
|
|
29
|
+
})
|
|
30
|
+
return { status: r.status, ...(await r.json().catch(() => ({}))) }
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const token = (await j('POST', '/api/users/login', { email: ADMIN.email, password: ADMIN.password })).data?.token
|
|
34
|
+
const chans = (await j('GET', '/api/workshop/channels', undefined, token)).data ?? []
|
|
35
|
+
const twin = chans.find(c => /闭环优化-hybridtwin/.test(c.name))
|
|
36
|
+
check('找到闭环优化 Channel(hybrid twin 模板实例)', Boolean(twin), twin?.name)
|
|
37
|
+
const channelId = twin.id
|
|
38
|
+
const members = (await j('GET', `/api/workshop/channels/${channelId}/agents`, undefined, token)).data ?? []
|
|
39
|
+
const lead = members.find(m => m.role === 'lead')
|
|
40
|
+
|
|
41
|
+
const dcw = (await j('GET', '/api/workshop/dcw', undefined, token)).data
|
|
42
|
+
const line = (dcw.lines ?? []).find(l => /injection-aw0746/i.test(String(l.name)))
|
|
43
|
+
const daq = (await j('GET', '/api/workshop/daq', undefined, token)).data
|
|
44
|
+
const nodes = (daq.nodes ?? []).filter(n => n.lineId === line.id)
|
|
45
|
+
const byName = re => nodes.find(n => re.test(n.name))
|
|
46
|
+
const controls = (dcw.nodes ?? []).filter(n => n.lineId === line.id && /hold-pressure|hold-time|mold-temp/.test(n.name))
|
|
47
|
+
const writesBefore = Number(dcw.controller?.writesTotal ?? 0)
|
|
48
|
+
|
|
49
|
+
// 真实场景 prompt(取自 PLC 模拟器注塑场景的工程事实)
|
|
50
|
+
const SCENARIO = [
|
|
51
|
+
'场景:PLC 模拟器注塑成型产线(真实协议栈,非抽象任务)。',
|
|
52
|
+
`产线 ${line.name}(${line.id}),活动批次配方三件套由 line_context 读取,不要臆造 id。`,
|
|
53
|
+
'质量目标:制品克重 part-weight 命中 32.5 ± 0.35 g;飞边 flash-rate ≤ 0.4%;缩痕 sink-mark ≤ 1.5%;',
|
|
54
|
+
'守卫:熔体温度 melt-temp-pv 235~262℃;注射压力 inj-pressure-pv 40~70 bar。',
|
|
55
|
+
`受控量(不可越界,单变量小步):${controls.map(c => `${c.name}(${c.id},${c.min}~${c.max}${c.unit})`).join('; ')}。`,
|
|
56
|
+
`观测节点:${[byName(/part-weight/), byName(/flash-rate/), byName(/sink-mark/), byName(/melt-temp/), byName(/inj-pressure/)].filter(Boolean).map(n => `${n.name}(${n.id})`).join('; ')}。`,
|
|
57
|
+
].join('\n')
|
|
58
|
+
|
|
59
|
+
const TASK = [
|
|
60
|
+
'目标:完成一次 recommendation-only 的混合孪生 MPC 闭环优化推演,并给出可审计的推荐结论。',
|
|
61
|
+
'硬性要求(按序执行,每步都有据可查):',
|
|
62
|
+
'1) twin_scene_read 读取场景契约;若场景未就绪,先用 scene/objective 参数把当前产线与活动批次登记进去(允许使用工具自带参数)。',
|
|
63
|
+
'2) twin_snapshot_create 生成工况快照(必须带最新 DAQ 值)。',
|
|
64
|
+
'3) twin_trial_run 跑至少 1 个候选的虚拟试验(硬约束拒绝不安全候选;candidateExecuted 必须为 false)。',
|
|
65
|
+
'4) mpc_optimize 在受控量小步范围内搜索推荐设定;',
|
|
66
|
+
'5) twin_gate_evaluate 做门禁与收益评估 —— 若门禁未通过,报告 safe_small_step 结论(这是合法结果,不要伪造通过)。',
|
|
67
|
+
'禁止事项:任何成员不得调用 dcw_control / param_control 写真实设定(本次为 recommendation-only,写控需另行授权)。',
|
|
68
|
+
'交付物(中文,≤ 12 行):① 场景与批次三元组;② 快照关键值;③ 推荐设定(或 safe_small_step 原因);④ 门禁/证书结论;⑤ 一句话说明"未写真实 PLC"这一事实。',
|
|
69
|
+
].join('\n')
|
|
70
|
+
|
|
71
|
+
const task = await j('POST', `/api/workshop/channels/${channelId}/tasks`, {
|
|
72
|
+
title: `闭环优化:注塑克重窗口 recommendation-only ${Date.now().toString(36).slice(-4)}`,
|
|
73
|
+
description: `${SCENARIO}\n\n${TASK}`,
|
|
74
|
+
assigneeId: lead?.id,
|
|
75
|
+
}, token)
|
|
76
|
+
const taskId = task.data?.id
|
|
77
|
+
const taskStartMs = Date.now() - 60_000
|
|
78
|
+
check('闭环优化任务下发到 lead(真实 omp 团队)', Boolean(taskId), `task=${String(taskId).slice(0, 8)} ${task.message ?? ''}`)
|
|
79
|
+
|
|
80
|
+
console.log(' … 等待真实 omp 团队执行(最长 12 分钟)…')
|
|
81
|
+
let final = null
|
|
82
|
+
const deadline = Date.now() + 12 * 60_000
|
|
83
|
+
while (Date.now() < deadline) {
|
|
84
|
+
await sleep(15_000)
|
|
85
|
+
const ts = (await j('GET', `/api/workshop/channels/${channelId}/tasks`, undefined, token)).data ?? []
|
|
86
|
+
const t = ts.find(x => x.id === taskId)
|
|
87
|
+
process.stdout.write(`\r state=${t?.state ?? '?'} progress=${t?.progress ?? 0}% children=${ts.filter(x => x.parentId === taskId).length} `)
|
|
88
|
+
if (t && ['COMPLETED', 'FAILED', 'CANCELED'].includes(t.state)) {
|
|
89
|
+
final = t
|
|
90
|
+
break
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
console.log('')
|
|
94
|
+
check('任务到达终态', Boolean(final), `state=${final?.state ?? 'timeout'}`)
|
|
95
|
+
check('任务 COMPLETED(闭环推演完成)', final?.state === 'COMPLETED', `state=${final?.state}`)
|
|
96
|
+
|
|
97
|
+
const arts = (final?.artifacts ?? []).map(a => (a.parts ?? []).map(p => p.text ?? '').join('\n')).join('\n---\n')
|
|
98
|
+
console.log('\n交付物:\n' + (arts || '(空)').slice(0, 1400))
|
|
99
|
+
|
|
100
|
+
// 工具证据必须落在**磁盘工件**上,而不是频道事件里 grep 工具名:
|
|
101
|
+
// ① 事件流不记 Agent 的工具调用(实测命中为空,旧断言属于误判);
|
|
102
|
+
// ② 交付物里的 snapshot_id/trial_id 可能是模型幻觉,只有工件文件才是硬证据。
|
|
103
|
+
const twinsRoot = `${HOME}/aml/twins`
|
|
104
|
+
const since = taskStartMs
|
|
105
|
+
const newer = (dir) => {
|
|
106
|
+
const p = join(twinsRoot, dir)
|
|
107
|
+
if (!existsSync(p)) return []
|
|
108
|
+
return readdirSync(p, { withFileTypes: true })
|
|
109
|
+
.filter(e => e.isDirectory() && statSync(join(p, e.name)).mtimeMs >= since)
|
|
110
|
+
.map(e => join(p, e.name))
|
|
111
|
+
}
|
|
112
|
+
const snapDirs = newer('snapshots')
|
|
113
|
+
const trialDirs = newer('trials')
|
|
114
|
+
const mpcDirs = newer('mpc')
|
|
115
|
+
check('孪生/MPC 工具链被真实调用(磁盘工件留痕)', snapDirs.length >= 1 && trialDirs.length >= 1, `快照=${snapDirs.length} 试验=${trialDirs.length} MPC=${mpcDirs.length}(${twinsRoot})`)
|
|
116
|
+
|
|
117
|
+
const trialArtifacts = trialDirs.map((d) => {
|
|
118
|
+
try {
|
|
119
|
+
return JSON.parse(readFileSync(join(d, 'trials.json'), 'utf8'))
|
|
120
|
+
}
|
|
121
|
+
catch {
|
|
122
|
+
return null
|
|
123
|
+
}
|
|
124
|
+
}).filter(Boolean)
|
|
125
|
+
const trialsReal = trialArtifacts.map(a => a.trial).filter(Boolean)
|
|
126
|
+
check('虚拟试验恒为 candidateExecuted=false(recommendation-only)', trialsReal.length >= 1 && trialsReal.every(t => t.candidateExecuted === false), `试验数=${trialsReal.length} candidateExecuted=${[...new Set(trialsReal.map(t => t.candidateExecuted))].join(',')}`)
|
|
127
|
+
const snapIds = snapDirs.map((d) => {
|
|
128
|
+
try {
|
|
129
|
+
return JSON.parse(readFileSync(join(d, 'snapshots.json'), 'utf8'))
|
|
130
|
+
}
|
|
131
|
+
catch {
|
|
132
|
+
return null
|
|
133
|
+
}
|
|
134
|
+
}).filter(Boolean)
|
|
135
|
+
check('TwinSnapshot 为真实数据(fresh=true 且水位>0)', snapIds.length >= 1 && snapIds.every(s2 => s2.dataQuality?.fresh === true && s2.daqWatermark > 0), snapIds.map(s2 => `${s2.snapshotId} fresh=${s2.dataQuality?.fresh}`).join('; '))
|
|
136
|
+
const certs = trialArtifacts.filter(a => a.certificate).length
|
|
137
|
+
console.log(` · 工件明细:快照 ${snapIds.length} 个 / 试验 ${trialsReal.length} 个 / 推荐证书 ${certs} 张`)
|
|
138
|
+
|
|
139
|
+
const dcwAfter = (await j('GET', '/api/workshop/dcw', undefined, token)).data
|
|
140
|
+
const writesAfter = Number(dcwAfter.controller?.writesTotal ?? 0)
|
|
141
|
+
check('全程零真实 DCW 写入(recommendation-only 生效)', writesAfter === writesBefore, `writes ${writesBefore} → ${writesAfter}`)
|
|
142
|
+
|
|
143
|
+
console.log(`\n★ 阶段 5:${pass} 通过 / ${fails.length} 失败${fails.length ? ` (${fails.join('; ')})` : ''}`)
|
|
144
|
+
console.log(JSON.stringify({ channelId, taskId, state: final?.state }, null, 1))
|