agentworkshop 0.7.46 → 0.7.47

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.
Files changed (35) hide show
  1. package/.output/.AgentWorkShop/aml-python/__pycache__/amlkit.cpython-312.pyc +0 -0
  2. package/.output/nitro.json +1 -1
  3. package/.output/public/_nuxt/builds/latest.json +1 -1
  4. package/.output/public/_nuxt/builds/meta/1b18aaf2-ccbc-45e0-b9c3-107655b226f9.json +1 -0
  5. package/.output/server/chunks/_/nitro.mjs +833 -748
  6. package/.output/server/package.json +1 -1
  7. package/README-zh.md +2 -2
  8. package/README.md +2 -2
  9. package/config.yml +1 -1
  10. package/docs/aml.md +1 -0
  11. package/docs/cli.en.md +2 -2
  12. package/docs/cli.md +2 -2
  13. package/docs/full-test-plan.md +1 -1
  14. package/docs/plugins.en.md +1 -1
  15. package/docs/plugins.md +1 -1
  16. package/docs/sdk.en.md +1 -1
  17. package/docs/sdk.md +1 -1
  18. package/package.json +1 -1
  19. package/scripts/_aw-packaged-start.cjs +22 -0
  20. package/scripts/_aw0746-stage12.mjs +94 -0
  21. package/scripts/_aw0746-stage34.mjs +134 -0
  22. package/scripts/_aw0746-stage5.mjs +106 -0
  23. package/scripts/_aw0746-stage6-aml.mjs +177 -0
  24. package/scripts/_aw0746-stage6b-train-sweep.mjs +142 -0
  25. package/scripts/_aw0746-stage7-twin.mjs +259 -0
  26. package/scripts/_aw0746-train-strong.py +142 -0
  27. package/scripts/_plc-sim-detached.cjs +22 -0
  28. package/scripts/_probe-density.cjs +15 -0
  29. package/scripts/_probe-gate-model.mjs +34 -0
  30. package/scripts/_probe-model-eligibility.cjs +13 -0
  31. package/server/services/workshop/agents/industrial/twin-tools.ts +85 -10
  32. package/server/services/workshop/aml/job-orchestrator/start-job.ts +140 -124
  33. package/server/services/workshop/aml/python/__pycache__/amlkit.cpython-312.pyc +0 -0
  34. package/server/services/workshop/aml/twin/repo.ts +9 -4
  35. package/.output/public/_nuxt/builds/meta/7827fe9f-b4c2-461f-b1fb-c0c7da7d66b8.json +0 -1
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentworkshop-prod",
3
- "version": "0.7.46",
3
+ "version": "0.7.47",
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.46</b> · 14 个执行引擎 · 6 种现场协议(5 内置 + 串口插件) · 111 个运行时设置项 · 双语文档(简体中文 / English)</sub>
31
+ <sub><b>v0.7.47</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.46**——见[版本发布](https://github.com/kingdol666/AgentWorkShop/releases)。
385
+ 版本遵循 semver。每次 `aw start` 都会校验配置根,并把 `home` 之前的旧版 `data/` 布局迁移进来(以最新文件为准),因此数据可以跨版本存活。SQLite schema 迁移在服务端启动时执行。当前版本:**v0.7.47**——见[版本发布](https://github.com/kingdol666/AgentWorkShop/releases)。
386
386
 
387
387
  ### 第一次「Agent × 产线」会话(约 2 分钟)
388
388
 
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.46</b> · 14 engines · 6 field protocols (5 built-in + serial plugin) · 111 runtime settings · bilingual docs (简体中文 / English)</sub>
31
+ <sub><b>v0.7.47</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.46** — 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.47** — see [Releases](https://github.com/kingdol666/AgentWorkShop/releases).
377
377
 
378
378
  ### Your first agent × line session (~2 minutes)
379
379
 
package/config.yml CHANGED
@@ -12,7 +12,7 @@
12
12
  app:
13
13
  name: AgentWorkShop
14
14
  title: AgentWorkShop
15
- version: 0.7.46
15
+ version: 0.7.47
16
16
  description: 基于 Nuxt 4 的配置驱动运行范式
17
17
 
18
18
  # 运行模式(dev/prod 实际由启动命令决定,此处仅作运行时展示)
package/docs/aml.md CHANGED
@@ -82,6 +82,7 @@ canonical one-step:输入 `history [batch, H, nAll]`(归一化)→ 输出 `y_nex
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
84
  - **服务端策略**:数据不足 → `safe_small_step`;模型过门禁 → `precise_search`;持续校准请求按去重 + cooldown 登记。
85
+ - **闭环连线**(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
86
  - **工件与记录**:SQLite Hybrid Twin 元数据表 + 本地 `aml/twins/` 工件;计划与验收见 `docs/aml-hybrid-twin-mpc-integration-plan.md`、`docs/aml-hybrid-twin-implementation-acceptance.md`(首个场景 = PLC 模拟器 `injection-line`)。
86
87
 
87
88
  ## 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.46 start)
65
+ npx agentworkshop start # fetch and start (pin a version with: npx agentworkshop@0.7.47 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.46` (`package.json`) |
82
+ | Current version | `0.7.47` (`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.46 start)
63
+ npx agentworkshop start # 拉取并启动(可锁定版本: npx agentworkshop@0.7.47 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.46`(`package.json`) |
79
+ | 当前版本 | `0.7.47`(`package.json`) |
80
80
 
81
81
  ## 二、首次启动(Claude Code 式)
82
82
 
@@ -1,6 +1,6 @@
1
1
  # AgentWorkShop 全功能测试流程 Plan
2
2
 
3
- > 版本无关的常备测试手册(撰写时基线 v0.7.45)。用途:后续直接指令 agent
3
+ > 版本无关的常备测试手册(撰写时基线 v0.7.46)。用途:后续直接指令 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.46**. `ctx.sdkVersion` is currently `0.3.0`.
12
+ > describes **v0.7.47**. `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.46**。`ctx.sdkVersion` 当前为 `0.3.0`。
9
+ > 运行时服务面);本文档对应当前版本 **v0.7.47**。`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.46) —
10
+ from the npm package version (the `version` field in `package.json`, currently 0.7.47) —
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.46)
8
+ 这两个常量是 **SDK 自身接口的版本号**,与 npm 包版本(`package.json` 的 `version`,当前 0.7.47)
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.46",
3
+ "version": "0.7.47",
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 打包系统验收 · 阶段 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.46 打包系统 · 阶段 1+2 @ ${BASE} ═══`)
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}`)
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 打包系统验收:真实 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,134 @@
1
+ /**
2
+ * 0.7.46 打包系统验收 · 阶段 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: 'admin@awshop.local', 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
+ check('产线活动批次(活动配方)成立', Boolean(activeRecipe), `activeRecipe=${activeRecipe}`)
58
+
59
+ // ── 3.2 逐样本打标(Timescale 直查) ──
60
+ const pg = new Client({ host: '127.0.0.1', port: 5432, user: 'postgres', password: 'awshop', database: 'awshop' })
61
+ await pg.connect()
62
+ await sleep(12_000)
63
+ const tags = await pg.query(
64
+ `SELECT recipe_id, run_id, COUNT(*)::int AS n, MAX(ts) AS last
65
+ FROM daq_samples WHERE line_id = $1 GROUP BY recipe_id, run_id ORDER BY last DESC LIMIT 3`, [line.id])
66
+ check('样本逐条带当前批次 recipe_id/run_id', tags.rows.length > 0 && tags.rows[0].n > 0,
67
+ `recipe=${tags.rows[0]?.recipe_id} run=${tags.rows[0]?.run_id} n=${tags.rows[0]?.n}`)
68
+ const activeRun = tags.rows[0]?.run_id
69
+
70
+ // ── 3.3 真实 agent 取数(默认批次作用域) ──
71
+ const ch = await j('POST', '/api/workshop/channels', {
72
+ name: `PLC取证通道-${TAG}`,
73
+ description: 'daq_query 当前批次作用域取证',
74
+ leadAgent: { name: `daq-lead-${TAG}`, harness: 'mock', config: { delayMs: 50 } },
75
+ }, token)
76
+ const daqChannel = ch.data.channelId
77
+ const tplAgent = (await j('POST', '/api/workshop/agents', { name: `daq-agent-${TAG}`, harness: 'omp', config: {} }, token)).data
78
+ const member = (await j('POST', `/api/workshop/channels/${daqChannel}/agents`, { agentId: tplAgent.id, role: 'worker' }, token)).data
79
+ const picked = daqNodes.filter(n => /melt-temp|part-weight|hold/i.test(n.name)).slice(0, 3)
80
+ for (const p of picked) await j('POST', '/api/workshop/agent-tools/bindings', { agentId: member.id, nodeId: p.id, kind: 'daq', mode: 'auto' }, token)
81
+ const q = await j('POST', '/api/workshop/agent-tools/invoke', {
82
+ agentId: member.id,
83
+ tool: 'daq_query',
84
+ args: { node_id: picked[0].id, last_minutes: 5, bucket_ms: 30_000, limit: 100 },
85
+ }, token)
86
+ const qText = q.data?.result?.text ?? ''
87
+ check('真实工具桥 daq_query 默认只取当前批次', /当前活动批次 run=/.test(qText), (qText.split('\n').find(l => /当前活动批次|未过滤|scope=all/.test(l)) ?? '').trim().slice(0, 120))
88
+ const scopeAll = await j('POST', '/api/workshop/agent-tools/invoke', {
89
+ agentId: member.id, tool: 'daq_query', args: { node_id: picked[0].id, last_minutes: 5, bucket_ms: 30_000, limit: 100, scope: 'all' },
90
+ }, token)
91
+ const pts = t => Number((t.match(/样本 (\d+) 点/) ?? [])[1] ?? 0)
92
+ check('scope=all 可见窗口内全部样本(≥ 默认批次)', pts(scopeAll.data?.result?.text ?? '') >= pts(qText), `默认=${pts(qText)} all=${pts(scopeAll.data?.result?.text ?? '')}`)
93
+
94
+ // ── 4. 最新闭环控制优化默认 Channel ──
95
+ console.log('\n═══ 阶段 4:实例化最新闭环控制优化默认 Channel ═══')
96
+ const tplList = (await j('GET', '/api/workshop/channel-templates', undefined, token)).data
97
+ const templates = Array.isArray(tplList) ? tplList : (tplList?.templates ?? tplList?.items ?? [])
98
+ const hybridTpl = templates.find(t => t.id === TPL)
99
+ check('默认模板存在:Hybrid Twin MPC 建模通道', Boolean(hybridTpl), hybridTpl?.name ?? `(未在 ${templates.length} 个模板中找到)`)
100
+
101
+ const inst = await j('POST', `/api/workshop/channel-templates/${TPL}/instantiate`, {
102
+ name: `闭环优化-hybridtwin-${TAG}`,
103
+ scene: {
104
+ lineId: line.id,
105
+ lineName: line.name,
106
+ productId: products[0]?.id,
107
+ recipeId: activeRecipe,
108
+ simulator: 'http://127.0.0.1:4010',
109
+ scenario: 'injection-line',
110
+ qualityTargets: { partWeight: '32.5 ± 0.35 g', flashRate: '≤ 0.4%', sinkMark: '≤ 1.5%' },
111
+ guards: ['melt-temp-pv 235~262℃', 'inj-pressure-pv 40~70 bar'],
112
+ 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 })),
113
+ },
114
+ objective: { name: 'quality_window', metric: 'part-weight 命中 32.5±0.35g 且守卫不越限', mode: 'recommendation_only' },
115
+ controlPolicy: 'recommendation_only',
116
+ }, token)
117
+ const twinChannel = inst.data?.channelId ?? inst.data?.id
118
+ check('实例化成功(闭环优化 Channel)', Boolean(twinChannel), `channel=${String(twinChannel).slice(0, 8)} members=${inst.data?.agentIds?.length ?? inst.data?.members?.length ?? '?'} ${inst.message ?? ''}`)
119
+ const twinMembers = (await j('GET', `/api/workshop/channels/${twinChannel}/agents`, undefined, token)).data ?? []
120
+ 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
+ const profile = (await j('GET', `/api/workshop/channels/${twinChannel}`, undefined, token)).data
122
+ 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 字段)')
124
+
125
+ const leadMember = twinMembers.find(m => m.role === 'lead')
126
+ const toolsList = await j('GET', `/api/workshop/agent-tools/list?agentId=${leadMember?.id}`, undefined, token)
127
+ const names = (toolsList.data?.tools ?? []).map(t => t.name)
128
+ const twinTools = ['twin_scene_read', 'twin_snapshot_create', 'twin_trial_run', 'mpc_optimize', 'twin_gate_evaluate', 'twin_calibration_request']
129
+ const injected = twinTools.filter(t => names.includes(t))
130
+ check('孪生/MPC 工具按 profile 注入(6 个)', injected.length === 6, `注入=${injected.length}/6 总工具=${names.length}`)
131
+
132
+ await pg.end()
133
+ console.log(`\n★ 阶段 3+4:${pass} 通过 / ${fails.length} 失败${fails.length ? ` (${fails.join('; ')})` : ''}`)
134
+ 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,106 @@
1
+ /**
2
+ * 0.7.46 打包系统验收 · 阶段 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 node scripts/_aw0746-stage5.mjs
7
+ */
8
+ const BASE = process.env.AW_BASE ?? 'http://127.0.0.1:3001'
9
+ const ADMIN = { email: 'admin@awshop.local', password: 'Awshop@123!' }
10
+
11
+ let pass = 0
12
+ const fails = []
13
+ const check = (name, ok, detail = '') => {
14
+ console.log(` ${ok ? 'PASS' : 'FAIL'} ${name}${detail ? ` — ${detail}` : ''}`)
15
+ if (ok) pass += 1
16
+ else fails.push(name)
17
+ }
18
+ const sleep = ms => new Promise(r => setTimeout(r, ms))
19
+ const j = async (method, path, body, token) => {
20
+ const r = await fetch(BASE + path, {
21
+ method,
22
+ headers: { 'content-type': 'application/json', ...(token ? { authorization: `Bearer ${token}` } : {}) },
23
+ body: body === undefined ? undefined : JSON.stringify(body),
24
+ signal: AbortSignal.timeout(180_000),
25
+ })
26
+ return { status: r.status, ...(await r.json().catch(() => ({}))) }
27
+ }
28
+
29
+ const token = (await j('POST', '/api/users/login', { email: ADMIN.email, password: ADMIN.password })).data?.token
30
+ const chans = (await j('GET', '/api/workshop/channels', undefined, token)).data ?? []
31
+ const twin = chans.find(c => /闭环优化-hybridtwin/.test(c.name))
32
+ check('找到闭环优化 Channel(hybrid twin 模板实例)', Boolean(twin), twin?.name)
33
+ const channelId = twin.id
34
+ const members = (await j('GET', `/api/workshop/channels/${channelId}/agents`, undefined, token)).data ?? []
35
+ const lead = members.find(m => m.role === 'lead')
36
+
37
+ const dcw = (await j('GET', '/api/workshop/dcw', undefined, token)).data
38
+ const line = (dcw.lines ?? []).find(l => /injection-aw0746/i.test(String(l.name)))
39
+ const daq = (await j('GET', '/api/workshop/daq', undefined, token)).data
40
+ const nodes = (daq.nodes ?? []).filter(n => n.lineId === line.id)
41
+ const byName = re => nodes.find(n => re.test(n.name))
42
+ const controls = (dcw.nodes ?? []).filter(n => n.lineId === line.id && /hold-pressure|hold-time|mold-temp/.test(n.name))
43
+ const writesBefore = Number(dcw.controller?.writesTotal ?? 0)
44
+
45
+ // 真实场景 prompt(取自 PLC 模拟器注塑场景的工程事实)
46
+ const SCENARIO = [
47
+ '场景:PLC 模拟器注塑成型产线(真实协议栈,非抽象任务)。',
48
+ `产线 ${line.name}(${line.id}),活动批次配方三件套由 line_context 读取,不要臆造 id。`,
49
+ '质量目标:制品克重 part-weight 命中 32.5 ± 0.35 g;飞边 flash-rate ≤ 0.4%;缩痕 sink-mark ≤ 1.5%;',
50
+ '守卫:熔体温度 melt-temp-pv 235~262℃;注射压力 inj-pressure-pv 40~70 bar。',
51
+ `受控量(不可越界,单变量小步):${controls.map(c => `${c.name}(${c.id},${c.min}~${c.max}${c.unit})`).join('; ')}。`,
52
+ `观测节点:${[byName(/part-weight/), byName(/flash-rate/), byName(/sink-mark/), byName(/melt-temp/), byName(/inj-pressure/)].filter(Boolean).map(n => `${n.name}(${n.id})`).join('; ')}。`,
53
+ ].join('\n')
54
+
55
+ const TASK = [
56
+ '目标:完成一次 recommendation-only 的混合孪生 MPC 闭环优化推演,并给出可审计的推荐结论。',
57
+ '硬性要求(按序执行,每步都有据可查):',
58
+ '1) twin_scene_read 读取场景契约;若场景未就绪,先用 scene/objective 参数把当前产线与活动批次登记进去(允许使用工具自带参数)。',
59
+ '2) twin_snapshot_create 生成工况快照(必须带最新 DAQ 值)。',
60
+ '3) twin_trial_run 跑至少 1 个候选的虚拟试验(硬约束拒绝不安全候选;candidateExecuted 必须为 false)。',
61
+ '4) mpc_optimize 在受控量小步范围内搜索推荐设定;',
62
+ '5) twin_gate_evaluate 做门禁与收益评估 —— 若门禁未通过,报告 safe_small_step 结论(这是合法结果,不要伪造通过)。',
63
+ '禁止事项:任何成员不得调用 dcw_control / param_control 写真实设定(本次为 recommendation-only,写控需另行授权)。',
64
+ '交付物(中文,≤ 12 行):① 场景与批次三元组;② 快照关键值;③ 推荐设定(或 safe_small_step 原因);④ 门禁/证书结论;⑤ 一句话说明"未写真实 PLC"这一事实。',
65
+ ].join('\n')
66
+
67
+ const task = await j('POST', `/api/workshop/channels/${channelId}/tasks`, {
68
+ title: `闭环优化:注塑克重窗口 recommendation-only ${Date.now().toString(36).slice(-4)}`,
69
+ description: `${SCENARIO}\n\n${TASK}`,
70
+ assigneeId: lead?.id,
71
+ }, token)
72
+ const taskId = task.data?.id
73
+ check('闭环优化任务下发到 lead(真实 omp 团队)', Boolean(taskId), `task=${String(taskId).slice(0, 8)} ${task.message ?? ''}`)
74
+
75
+ console.log(' … 等待真实 omp 团队执行(最长 12 分钟)…')
76
+ let final = null
77
+ const deadline = Date.now() + 12 * 60_000
78
+ while (Date.now() < deadline) {
79
+ await sleep(15_000)
80
+ const ts = (await j('GET', `/api/workshop/channels/${channelId}/tasks`, undefined, token)).data ?? []
81
+ const t = ts.find(x => x.id === taskId)
82
+ 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 }
84
+ }
85
+ console.log('')
86
+ check('任务到达终态', Boolean(final), `state=${final?.state ?? 'timeout'}`)
87
+ check('任务 COMPLETED(闭环推演完成)', final?.state === 'COMPLETED', `state=${final?.state}`)
88
+
89
+ const arts = (final?.artifacts ?? []).map(a => (a.parts ?? []).map(p => p.text ?? '').join('\n')).join('\n---\n')
90
+ console.log('\n交付物:\n' + (arts || '(空)').slice(0, 1400))
91
+
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
+
101
+ const dcwAfter = (await j('GET', '/api/workshop/dcw', undefined, token)).data
102
+ const writesAfter = Number(dcwAfter.controller?.writesTotal ?? 0)
103
+ check('全程零真实 DCW 写入(recommendation-only 生效)', writesAfter === writesBefore, `writes ${writesBefore} → ${writesAfter}`)
104
+
105
+ console.log(`\n★ 阶段 5:${pass} 通过 / ${fails.length} 失败${fails.length ? ` (${fails.join('; ')})` : ''}`)
106
+ console.log(JSON.stringify({ channelId, taskId, state: final?.state }, null, 1))