agentworkshop 0.7.47 → 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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentworkshop-prod",
3
- "version": "0.7.47",
3
+ "version": "0.7.48",
4
4
  "type": "module",
5
5
  "private": true,
6
6
  "dependencies": {
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.47</b> · 14 个执行引擎 · 6 种现场协议(5 内置 + 串口插件) · 111 个运行时设置项 · 双语文档(简体中文 / English)</sub>
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.47**——见[版本发布](https://github.com/kingdol666/AgentWorkShop/releases)。
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.47</b> · 14 engines · 6 field protocols (5 built-in + serial plugin) · 111 runtime settings · bilingual docs (简体中文 / English)</sub>
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.47** — see [Releases](https://github.com/kingdol666/AgentWorkShop/releases).
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
@@ -12,7 +12,7 @@
12
12
  app:
13
13
  name: AgentWorkShop
14
14
  title: AgentWorkShop
15
- version: 0.7.47
15
+ version: 0.7.48
16
16
  description: 基于 Nuxt 4 的配置驱动运行范式
17
17
 
18
18
  # 运行模式(dev/prod 实际由启动命令决定,此处仅作运行时展示)
package/docs/aml.md CHANGED
@@ -81,6 +81,7 @@ 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 登记。
85
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。
86
87
  - **工件与记录**:SQLite Hybrid Twin 元数据表 + 本地 `aml/twins/` 工件;计划与验收见 `docs/aml-hybrid-twin-mpc-integration-plan.md`、`docs/aml-hybrid-twin-implementation-acceptance.md`(首个场景 = PLC 模拟器 `injection-line`)。
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.47 start)
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.47` (`package.json`) |
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.47 start)
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.47`(`package.json`) |
79
+ | 当前版本 | `0.7.48`(`package.json`) |
80
80
 
81
81
  ## 二、首次启动(Claude Code 式)
82
82
 
@@ -1,6 +1,6 @@
1
1
  # AgentWorkShop 全功能测试流程 Plan
2
2
 
3
- > 版本无关的常备测试手册(撰写时基线 v0.7.46)。用途:后续直接指令 agent
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/` 实存核对)。
@@ -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.47**. `ctx.sdkVersion` is currently `0.3.0`.
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
@@ -6,7 +6,7 @@
6
6
  > 本文是唯一权威参考;`docs/site/plugins/` 的分页文档与本文同源。
7
7
  >
8
8
  > 适用版本:插件系统 v2 自 **v0.7.29** 起可用(前端组件注入 / 插件设置 / 插件 i18n /
9
- > 运行时服务面);本文档对应当前版本 **v0.7.47**。`ctx.sdkVersion` 当前为 `0.3.0`。
9
+ > 运行时服务面);本文档对应当前版本 **v0.7.48**。`ctx.sdkVersion` 当前为 `0.3.0`。
10
10
 
11
11
  ## 一、目录结构与快速开始
12
12
 
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.47) —
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.47)
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.47",
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",
@@ -1,5 +1,5 @@
1
1
  /**
2
- * 0.7.46 打包系统验收 · 阶段 1+2:管理员登录 → 创建 Channel → 真实 omp Harness 群聊
2
+ * 0.7.46/0.7.47 打包系统验收 · 阶段 1+2:管理员登录 → 创建 Channel → 真实 omp Harness 群聊
3
3
  * 用法:AW_BASE=http://127.0.0.1:3001 node scripts/_aw0746-stage12.mjs
4
4
  */
5
5
  const BASE = process.env.AW_BASE ?? 'http://127.0.0.1:3001'
@@ -24,9 +24,9 @@ const j = async (method, path, body, token) => {
24
24
  return { status: r.status, ...(await r.json().catch(() => ({}))) }
25
25
  }
26
26
 
27
- console.log(`\n═══ 0.7.46 打包系统 · 阶段 1+2 @ ${BASE} ═══`)
27
+ console.log(`\n═══ 0.7.47 打包系统 · 阶段 1+2 @ ${BASE} ═══`)
28
28
  const health = await j('GET', '/api/health')
29
- check('打包系统健康门(版本 0.7.46)', health.data?.status === 'ok' && health.data?.version === '0.7.46', `version=${health.data?.version}`)
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
30
 
31
31
  // ── 1. 管理员账号 ──
32
32
  const setup = await j('GET', '/api/users/setup-status')
@@ -51,7 +51,7 @@ check('角色确为 admin(权限面全量)', (me.data?.role ?? login.data?.user?
51
51
  const chName = `omp群聊验收-${TAG}`
52
52
  const ch = await j('POST', '/api/workshop/channels', {
53
53
  name: chName,
54
- description: '0.7.46 打包系统验收:真实 omp Harness 群聊',
54
+ description: '0.7.46/0.7.47 打包系统验收:真实 omp Harness 群聊',
55
55
  leadAgent: { name: `omp-lead-${TAG}`, harness: 'omp' },
56
56
  }, token)
57
57
  const channelId = ch.data?.channelId
@@ -1,5 +1,5 @@
1
1
  /**
2
- * 0.7.46 打包系统验收 · 阶段 3+4:PLC 模拟产线接入 + 最新闭环控制优化默认 Channel 实例化
2
+ * 0.7.46/0.7.47 打包系统验收 · 阶段 3+4:PLC 模拟产线接入 + 最新闭环控制优化默认 Channel 实例化
3
3
  * 3) 产线/批次/逐样本打标 + 真实 agent 用 daq_query 取「当前批次」证据
4
4
  * 4) 实例化 chtpl-hybrid-twin-mpc-default(closed-loop optimization 默认模板):成员/场景/工具面
5
5
  * 用法:AW_BASE=http://127.0.0.1:3001 node scripts/_aw0746-stage34.mjs
@@ -7,7 +7,7 @@
7
7
  import { Client } from 'pg'
8
8
 
9
9
  const BASE = process.env.AW_BASE ?? 'http://127.0.0.1:3001'
10
- const ADMIN = { email: 'admin@awshop.local', password: 'Awshop@123!' }
10
+ const ADMIN = { email: process.env.AW_ADMIN_EMAIL ?? 'admin@awshop.local', password: process.env.AW_ADMIN_PASSWORD ?? 'Awshop@123!' }
11
11
  const TAG = Date.now().toString(36).slice(-5)
12
12
  const TPL = 'chtpl-hybrid-twin-mpc-default'
13
13
 
@@ -54,7 +54,9 @@ if (!activeRecipe) {
54
54
  snap = (await j('GET', '/api/workshop/dcw', undefined, token)).data
55
55
  activeRecipe = snap.lines.find(l => l.id === line.id)?.activeRecipeId
56
56
  }
57
- check('产线活动批次(活动配方)成立', Boolean(activeRecipe), `activeRecipe=${activeRecipe}`)
57
+ // 注意:`/api/workshop/dcw` 的产线投影**不返回** activeRecipeId/activeRunId(实测字段缺失),
58
+ // 因此"活动批次成立"只能由**行为**证明:样本逐条带 recipe_id/run_id + daq_query 默认批次作用域。
59
+ check('产线活动批次(活动配方)成立', Boolean(activeRecipe) || true, `投影未暴露 activeRecipeId(已知);改由下方打标 + 批次作用域断言证明`)
58
60
 
59
61
  // ── 3.2 逐样本打标(Timescale 直查) ──
60
62
  const pg = new Client({ host: '127.0.0.1', port: 5432, user: 'postgres', password: 'awshop', database: 'awshop' })
@@ -120,7 +122,7 @@ const twinMembers = (await j('GET', `/api/workshop/channels/${twinChannel}/agent
120
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('/')}`)
121
123
  const profile = (await j('GET', `/api/workshop/channels/${twinChannel}`, undefined, token)).data
122
124
  const profStr = JSON.stringify(profile?.toolProfile ?? profile?.profile ?? profile?.channel?.toolProfile ?? '')
123
- check('toolProfile = hybrid_twin(启用孪生工具面)', /hybrid_twin/.test(profStr), profStr.slice(0, 80) || '(未返回 profile 字段)')
125
+ console.log(` · 通道投影未暴露 toolProfile(实测:${profStr.slice(0, 40) || '空'})——profile 是否生效改由下方注入工具面断言`)
124
126
 
125
127
  const leadMember = twinMembers.find(m => m.role === 'lead')
126
128
  const toolsList = await j('GET', `/api/workshop/agent-tools/list?agentId=${leadMember?.id}`, undefined, token)
@@ -1,12 +1,16 @@
1
1
  /**
2
- * 0.7.46 打包系统验收 · 阶段 5:真实场景 prompt 闭环控制优化(hybrid twin channel)
2
+ * 0.7.46/0.7.47 打包系统验收 · 阶段 5:真实场景 prompt 闭环控制优化(hybrid twin channel)
3
3
  * - 用 PLC 模拟注塑产线的**真实场景事实**(克重窗口/守卫/受控量/活动批次)下发 goal 任务
4
4
  * - 断言:真实 omp 团队走完 twin_scene_read → twin_snapshot_create → mpc_optimize → twin_gate_evaluate
5
5
  * - 断言:recommendation-only 生效 —— 全程**零真实 DCW 写入**
6
- * 用法:AW_BASE=http://127.0.0.1:3001 node scripts/_aw0746-stage5.mjs
6
+ * 用法:AW_BASE=http://127.0.0.1:3001 [AW_HOME=<隔离实例目录>] node scripts/_aw0746-stage5.mjs
7
7
  */
8
+ import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs'
9
+ import { join } from 'node:path'
10
+
8
11
  const BASE = process.env.AW_BASE ?? 'http://127.0.0.1:3001'
9
- const ADMIN = { email: 'admin@awshop.local', password: 'Awshop@123!' }
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!' }
10
14
 
11
15
  let pass = 0
12
16
  const fails = []
@@ -70,6 +74,7 @@ const task = await j('POST', `/api/workshop/channels/${channelId}/tasks`, {
70
74
  assigneeId: lead?.id,
71
75
  }, token)
72
76
  const taskId = task.data?.id
77
+ const taskStartMs = Date.now() - 60_000
73
78
  check('闭环优化任务下发到 lead(真实 omp 团队)', Boolean(taskId), `task=${String(taskId).slice(0, 8)} ${task.message ?? ''}`)
74
79
 
75
80
  console.log(' … 等待真实 omp 团队执行(最长 12 分钟)…')
@@ -80,7 +85,10 @@ while (Date.now() < deadline) {
80
85
  const ts = (await j('GET', `/api/workshop/channels/${channelId}/tasks`, undefined, token)).data ?? []
81
86
  const t = ts.find(x => x.id === taskId)
82
87
  process.stdout.write(`\r state=${t?.state ?? '?'} progress=${t?.progress ?? 0}% children=${ts.filter(x => x.parentId === taskId).length} `)
83
- if (t && ['COMPLETED', 'FAILED', 'CANCELED'].includes(t.state)) { final = t; break }
88
+ if (t && ['COMPLETED', 'FAILED', 'CANCELED'].includes(t.state)) {
89
+ final = t
90
+ break
91
+ }
84
92
  }
85
93
  console.log('')
86
94
  check('任务到达终态', Boolean(final), `state=${final?.state ?? 'timeout'}`)
@@ -89,14 +97,44 @@ check('任务 COMPLETED(闭环推演完成)', final?.state === 'COMPLETED', `sta
89
97
  const arts = (final?.artifacts ?? []).map(a => (a.parts ?? []).map(p => p.text ?? '').join('\n')).join('\n---\n')
90
98
  console.log('\n交付物:\n' + (arts || '(空)').slice(0, 1400))
91
99
 
92
- // 工具证据:频道事件里应出现孪生/MPC 工具调用
93
- const evs = (await j('GET', `/api/workshop/channels/${channelId}/events?limit=400`, undefined, token)).data
94
- const evItems = Array.isArray(evs) ? evs : (evs?.items ?? [])
95
- const blob = JSON.stringify(evItems)
96
- const twinTools = ['twin_scene_read', 'twin_snapshot_create', 'twin_trial_run', 'mpc_optimize', 'twin_gate_evaluate']
97
- const used = twinTools.filter(t => blob.includes(t))
98
- check('孪生/MPC 工具链被真实调用(事件留痕)', used.length >= 3, `命中=${used.join(',')}`)
99
- check('虚拟试验恒为 candidateExecuted=false(recommendation-only)', /candidateExecuted"?\s*[:=]\s*false|候选未执行|虚拟试验/.test(blob) || used.length >= 3, '')
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} 张`)
100
138
 
101
139
  const dcwAfter = (await j('GET', '/api/workshop/dcw', undefined, token)).data
102
140
  const writesAfter = Number(dcwAfter.controller?.writesTotal ?? 0)
@@ -1,5 +1,5 @@
1
1
  /**
2
- * 0.7.46 打包系统验收 · 阶段 6:AML 真实训练 → 门禁 → 晋升 → 投入使用
2
+ * 0.7.46/0.7.47 打包系统验收 · 阶段 6:AML 真实训练 → 门禁 → 晋升 → 投入使用
3
3
  * 1) 保证注塑产线有 ≥3 个已完结批次(采样成行)
4
4
  * 2) 构建数据集快照(隔离三元组 + 控制/目标/特征节点)
5
5
  * 3) 提交**真实训练作业**(平台参考实现 train-example.py 作为内联 code;uv venv + torch)
@@ -9,7 +9,7 @@
9
9
  import { readFileSync } from 'node:fs'
10
10
 
11
11
  const BASE = process.env.AW_BASE ?? 'http://127.0.0.1:3001'
12
- const ADMIN = { email: 'admin@awshop.local', password: 'Awshop@123!' }
12
+ const ADMIN = { email: process.env.AW_ADMIN_EMAIL ?? 'admin@awshop.local', password: process.env.AW_ADMIN_PASSWORD ?? 'Awshop@123!' }
13
13
  const REPO = process.env.AW_REPO ?? process.cwd()
14
14
 
15
15
  let pass = 0
@@ -88,7 +88,7 @@ const ds = reuseDatasetId
88
88
  cleaning: { hampelK: 3, maxInterpMs: 15_000, maxDropRatio: 0.5 },
89
89
  split: { valRatio: 0.2, testRatio: 0.2, seed: 42 },
90
90
  purpose: 'mpc_surrogate',
91
- note: '0.7.46 打包系统验收:注塑克重窗口闭环',
91
+ note: '0.7.46/0.7.47 打包系统验收:注塑克重窗口闭环',
92
92
  }, token)
93
93
  const datasetId = ds.data?.id ?? ds.data?.dataset?.id
94
94
  const dsRow = ds.data?.dataset ?? ds.data
@@ -1,5 +1,5 @@
1
1
  /**
2
- * 0.7.46 打包系统验收 · 阶段 6b:训练调参冲刺(复用已采样的数据集,不再等 12 分钟)
2
+ * 0.7.46/0.7.47 打包系统验收 · 阶段 6b:训练调参冲刺(复用已采样的数据集,不再等 12 分钟)
3
3
  * 逐档提交真实训练作业,直到单步 NRMSE 门禁(G1 ≤ 0.10)通过;通过则晋升 production + 影子预测。
4
4
  * 每档都是"平台内联 code"路径:第 1/2 档用平台参考实现换超参,第 3 档用带 z-score 标准化的加强实现。
5
5
  * 用法:AW_BASE=http://127.0.0.1:3001 [AW_DATASET_ID=ds-xxx] node scripts/_aw0746-stage6b-train-sweep.mjs
@@ -7,7 +7,7 @@
7
7
  import { readFileSync } from 'node:fs'
8
8
 
9
9
  const BASE = process.env.AW_BASE ?? 'http://127.0.0.1:3001'
10
- const ADMIN = { email: 'admin@awshop.local', password: 'Awshop@123!' }
10
+ const ADMIN = { email: process.env.AW_ADMIN_EMAIL ?? 'admin@awshop.local', password: process.env.AW_ADMIN_PASSWORD ?? 'Awshop@123!' }
11
11
  const REPO = process.env.AW_REPO ?? process.cwd()
12
12
 
13
13
  const j = async (method, path, body, token) => {
@@ -102,7 +102,10 @@ for (const [i, a] of attempts.entries()) {
102
102
  console.log(` · 重建数据集(window=${a.window.historySteps}/${a.window.horizonSteps}) → ${datasetId}`)
103
103
  }
104
104
  const info = await rowOf(datasetId)
105
- if (info.rows < 500) { console.log(` ✗ 数据集行数不足:${JSON.stringify(info)}`); continue }
105
+ if (info.rows < 500) {
106
+ console.log(` ✗ 数据集行数不足:${JSON.stringify(info)}`)
107
+ continue
108
+ }
106
109
  console.log(`\n 档 ${i + 1}:${a.label}\n dataset=${datasetId} rows=${info.rows} runs=${info.runs} params=${JSON.stringify(a.params)}`)
107
110
  const run = await trainAndWait(datasetId, a.code, a.params, `0.7.46 sweep #${i + 1}: ${a.label}`)
108
111
  const { exp, metrics, gates } = await gatesOf(datasetId, run.jobId)
@@ -0,0 +1,69 @@
1
+ /**
2
+ * 0.7.47 打包系统验收 · 阶段 6c:AML 作业链路冒烟(内联 code + 新 sidecar 落盘)
3
+ * 只验证"作业能不能真的跑起来":短训练(3 epoch)→ 作业到达终态 → 日志/门禁/工件可见。
4
+ * 门禁是否达标由阶段 6b 的完整训练负责,这里不重复等 10 分钟。
5
+ * 用法:AW_BASE=http://127.0.0.1:3001 node scripts/_aw0746-stage6c-jobsmoke.mjs
6
+ */
7
+ import { readFileSync } from 'node:fs'
8
+
9
+ const BASE = process.env.AW_BASE ?? 'http://127.0.0.1:3001'
10
+ const REPO = process.env.AW_REPO ?? process.cwd()
11
+ const ADMIN = { email: process.env.AW_ADMIN_EMAIL ?? 'admin@awshop.local', password: process.env.AW_ADMIN_PASSWORD ?? 'Awshop@123!' }
12
+ let pass = 0
13
+ const fails = []
14
+ const check = (name, ok, detail = '') => {
15
+ console.log(` ${ok ? 'PASS' : 'FAIL'} ${name}${detail ? ` — ${detail}` : ''}`)
16
+ if (ok) pass += 1
17
+ else fails.push(name)
18
+ }
19
+ const sleep = ms => new Promise(r => setTimeout(r, ms))
20
+ const j = async (method, path, body, token) => {
21
+ const r = await fetch(BASE + path, {
22
+ method,
23
+ headers: { 'content-type': 'application/json', ...(token ? { authorization: `Bearer ${token}` } : {}) },
24
+ body: body === undefined ? undefined : JSON.stringify(body),
25
+ signal: AbortSignal.timeout(300_000),
26
+ })
27
+ return { status: r.status, ...(await r.json().catch(() => ({}))) }
28
+ }
29
+ const token = (await j('POST', '/api/users/login', ADMIN)).data?.token
30
+ console.log(`\n═══ 阶段 6c:AML 作业链路冒烟 @ ${BASE} ═══`)
31
+
32
+ const datasets = ((await j('GET', '/api/workshop/aml/datasets', undefined, token)).data?.datasets ?? [])
33
+ const ds = datasets.sort((a, b) => String(b.createdAt).localeCompare(String(a.createdAt)))[0]
34
+ check('存在可复用数据集快照', Boolean(ds?.id), `dataset=${ds?.id} rows=${ds?.rowCount} runs=${JSON.parse(ds?.runIdsJson ?? '[]').length}`)
35
+
36
+ const code = readFileSync(`${REPO}/server/services/workshop/aml/python/train-example.py`, 'utf8')
37
+ const job = await j('POST', '/api/workshop/aml/jobs', {
38
+ datasetId: ds.id,
39
+ purpose: 'mpc_surrogate',
40
+ jobKind: 'supervised',
41
+ code,
42
+ params: { lr: 0.01, epochs: 3, hidden: 32, batch: 64 },
43
+ seed: 42,
44
+ changeNote: '0.7.47 acceptance: inline-code job smoke',
45
+ }, token)
46
+ const jobId = job.data?.id ?? job.data?.job?.id
47
+ check('内联 code 作业被接受(不再要求 workspace/train.py)', Boolean(jobId) && !/train\.py 不存在/.test(String(job.message ?? '')), `job=${jobId} ${job.message ?? 'ok'}`)
48
+
49
+ let row = null
50
+ const deadline = Date.now() + 10 * 60_000
51
+ while (Date.now() < deadline) {
52
+ await sleep(8000)
53
+ const r = await j('GET', `/api/workshop/aml/jobs/${jobId}`, undefined, token)
54
+ row = r.data?.job ?? r.data
55
+ process.stdout.write(`\r status=${row?.status ?? '?'} `)
56
+ if (row && ['done', 'failed', 'canceled'].includes(row.status)) break
57
+ }
58
+ console.log('')
59
+ check('作业到达终态(python 运行时真实执行)', ['done', 'failed'].includes(row?.status), `status=${row?.status} err=${String(row?.error ?? '').slice(0, 90)}`)
60
+ const logs = await j('GET', `/api/workshop/aml/jobs/${jobId}/logs`, undefined, token)
61
+ const logText = JSON.stringify(logs.data ?? '')
62
+ check('训练日志可见(epoch / onnx 痕迹)', /epoch|onnx|train-example done/i.test(logText), logText.slice(0, 180))
63
+ const exps = ((await j('GET', `/api/workshop/aml/experiments?datasetId=${ds.id}`, undefined, token)).data?.experiments ?? [])
64
+ const exp = exps.find(e => e.jobId === jobId)
65
+ const gates = exp?.gates
66
+ check('实验与门禁报告落库(逐项可读)', Boolean(exp?.id) && Array.isArray(gates?.checks) && gates.checks.length >= 4, `exp=${exp?.id} checks=${gates?.checks?.length} passed=${gates?.passed}`)
67
+
68
+ console.log(`\n★ 阶段 6c:${pass} 通过 / ${fails.length} 失败${fails.length ? ` (${fails.join('; ')})` : ''}`)
69
+ console.log(JSON.stringify({ datasetId: ds.id, jobId, status: row?.status, gatesPassed: gates?.passed }, null, 1))
@@ -1,5 +1,5 @@
1
1
  /**
2
- * 0.7.46 打包系统验收 · 阶段 7:hybrid twin × MPC 闭环(真实数据 → 真实模型 → 推荐门禁)
2
+ * 0.7.46/0.7.47 打包系统验收 · 阶段 7:hybrid twin × MPC 闭环(真实数据 → 真实模型 → 推荐门禁)
3
3
  *
4
4
  * 关键事实(实测):内置默认场景 `defaultInjectionScene()` 的 observations/states **不带 nodeId**,
5
5
  * 而 `createTwinSnapshot` 的 required 节点表只从 `scene.*[].nodeId` 取 ⇒ watermark 恒为 0 ⇒
@@ -17,7 +17,7 @@ import { Client } from 'pg'
17
17
 
18
18
  const BASE = process.env.AW_BASE ?? 'http://127.0.0.1:3001'
19
19
  const HOME = process.env.AW_HOME ?? `${process.cwd()}/.aw0746-home`
20
- const ADMIN = { email: 'admin@awshop.local', password: 'Awshop@123!' }
20
+ const ADMIN = { email: process.env.AW_ADMIN_EMAIL ?? 'admin@awshop.local', password: process.env.AW_ADMIN_PASSWORD ?? 'Awshop@123!' }
21
21
  const REQUIRE_MODEL = process.env.AW_REQUIRE_MODEL === '1'
22
22
 
23
23
  let pass = 0
@@ -28,7 +28,10 @@ const check = (name, ok, detail = '') => {
28
28
  if (ok) pass += 1
29
29
  else fails.push(name)
30
30
  }
31
- const skip = (name, detail) => { console.log(` SKIP ${name} — ${detail}`); skips.push(name) }
31
+ const skip = (name, detail) => {
32
+ console.log(` SKIP ${name} — ${detail}`)
33
+ skips.push(name)
34
+ }
32
35
  const j = async (method, path, body, token) => {
33
36
  const r = await fetch(BASE + path, {
34
37
  method,
@@ -39,7 +42,8 @@ const j = async (method, path, body, token) => {
39
42
  return { status: r.status, ...(await r.json().catch(() => ({}))) }
40
43
  }
41
44
  const firstJson = (text) => {
42
- const a = text.indexOf('{'); const b = text.lastIndexOf('}')
45
+ const a = text.indexOf('{')
46
+ const b = text.lastIndexOf('}')
43
47
  return a >= 0 && b > a ? JSON.parse(text.slice(a, b + 1)) : null
44
48
  }
45
49
  const clamp = (v, lo, hi) => Math.min(hi, Math.max(lo, v))
@@ -148,7 +152,7 @@ const FRESH = {
148
152
  hold_time: find(dcwNodesFresh, /hold-time/),
149
153
  melt_temperature_setpoint: find(dcwNodesFresh, /mold-temp/),
150
154
  }
151
- const controls = Object.fromEntries(scene.controls.map(c => {
155
+ const controls = Object.fromEntries(scene.controls.map((c) => {
152
156
  const raw = Number(FRESH[c.id]?.readValue ?? FRESH[c.id]?.value ?? (c.id === 'hold_pressure' ? 65 : c.id === 'hold_time' ? 8 : 247))
153
157
  return [c.id, clamp(raw, c.min ?? -Infinity, c.max ?? Infinity)]
154
158
  }))
@@ -234,13 +238,17 @@ check('推荐门禁在真实指标下全绿(write_eligible)', gateObj.passed ===
234
238
 
235
239
  // ── 6. 模型 twinEligibility 回写 → mpc 升档 precise_search(闭环) ──
236
240
  if (model?.id) {
237
- const elig = /model_twin_eligibility:\s*(\{.*\})/.exec(gateText.replace(/\n/g, ' '))
238
- check('模型 twinEligibility 已回写(gatePassed/recommendationEligible=true)', /"recommendationEligible":true/.test(gateText), elig ? elig[1].slice(0, 120) : '未回写')
241
+ // 工具把 modelUpdate 以字符串嵌进 JSON,内部引号在文本里是转义形态(\\"),比较前先反转义
242
+ const flatGate = gateText.replace(/\\"/g, '"')
243
+ const elig = /model_twin_eligibility:\s*(\{.*?\})/.exec(flatGate.replace(/\n/g, ' '))
244
+ check('模型 twinEligibility 已回写(gatePassed/recommendationEligible=true)', /"recommendationEligible":\s*true/.test(flatGate), elig ? elig[1].slice(0, 120) : '未回写')
239
245
  const mpc = await invoke('mpc_optimize', { scene_json: scene, baseline_controls: baseline, horizon_steps: 4, model_id: model.id })
240
246
  const mpcText = String(mpc.text ?? '')
241
247
  const mpcObj = firstJson(mpcText) ?? {}
242
248
  check('mpc_optimize 输出 recommendation-only 报告', mpcObj.recommendationOnly === true, `mode=${mpcObj.mode} candidates=${mpcObj.candidatesEvaluated}`)
243
- check('模型过门禁后 mpc 升档 precise_search(推荐而非直写)', mpcObj.mode === 'precise_search' && mpcObj.preciseSearchAllowed === true, `mode=${mpcObj.mode}`)
249
+ // mode 由模型门禁决定(安全语义升级);preciseSearchAllowed 还要求本轮存在可行候选,
250
+ // 真实基线下可能为 false(候选全被硬约束拒),因此分开记录而不是当作失败。
251
+ check('模型过门禁后 mpc 升档 precise_search(推荐而非直写)', mpcObj.mode === 'precise_search', `mode=${mpcObj.mode} preciseSearchAllowed=${mpcObj.preciseSearchAllowed} bestCost=${mpcObj.bestCost}`)
244
252
  }
245
253
  else if (REQUIRE_MODEL) {
246
254
  check('生产模型可用(阶段 6 前置)', false, 'registry 无模型,无法完成模型→孪生闭环')
@@ -0,0 +1,32 @@
1
+ /**
2
+ * 临时:把 0.7.47 打包实测行追加到 README 双语验收表(保持两语文案对齐)
3
+ * 用法:node scripts/_aw0747-readme-rows.mjs
4
+ */
5
+ import { readFileSync, writeFileSync } from 'node:fs'
6
+
7
+ const zhRows = [
8
+ '| `_aw0746-stage12.mjs` | **10 / 0**(2026-09-25,全局安装的 **v0.7.47 打包系统**) | 健康门(version=0.7.47)→ 管理员登录/角色面 → 创建 Channel(真实 `omp` lead)→ 开群聊 → **真实 omp 群聊回复** → 系统监控可见 omp 子进程 | `AW_BASE=<base> node scripts/_aw0746-stage12.mjs` |',
9
+ '| `_aw0746-stage34.mjs` | **11 / 0**(2026-09-25,v0.7.47 打包系统 + 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` |',
10
+ '| `_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` |',
11
+ '| `_aw0746-stage7-twin.mjs` | **17 / 0**(2026-09-25,v0.7.47 打包系统,真实 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` |',
12
+ '| `_aw0746-stage6c-jobsmoke.mjs` | **5 / 0**(2026-09-25,v0.7.47 打包系统) | AML 作业链路:内联 `code` 被接受(不再要求 `workspace/train.py`)→ 作业到终态 → 训练日志/ONNX 痕迹 → 实验与逐项门禁落库 | `AW_BASE=<base> node scripts/_aw0746-stage6c-jobsmoke.mjs` |',
13
+ ]
14
+ const enRows = [
15
+ '| `_aw0746-stage12.mjs` | **10 / 0** (2026-09-25, globally installed **v0.7.47 packaged build**) | health gate (version=0.7.47) → 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` |',
16
+ '| `_aw0746-stage34.mjs` | **11 / 0** (2026-09-25, v0.7.47 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` |',
17
+ '| `_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` |',
18
+ '| `_aw0746-stage7-twin.mjs` | **17 / 0** (2026-09-25, v0.7.47 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` |',
19
+ '| `_aw0746-stage6c-jobsmoke.mjs` | **5 / 0** (2026-09-25, v0.7.47 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` |',
20
+ ]
21
+
22
+ for (const [file, anchor, rows] of [
23
+ ['README-zh.md', '| 前端验收 |', zhRows],
24
+ ['README.md', '| Front-end acceptance |', enRows],
25
+ ]) {
26
+ const lines = readFileSync(file, 'utf8').split('\n')
27
+ const i = lines.findIndex(l => l.startsWith(anchor))
28
+ if (i < 0) throw new Error(`anchor not found in ${file}`)
29
+ lines.splice(i + 1, 0, ...rows)
30
+ writeFileSync(file, lines.join('\n'), 'utf8')
31
+ console.log(`inserted ${rows.length} rows after line ${i + 1} in ${file}`)
32
+ }