agentworkshop 0.7.37 → 0.7.39

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 (91) hide show
  1. package/.AgentWorkShop/prompts/aml-mission-default.md +20 -20
  2. package/.AgentWorkShop/prompts/aml-playbook.md +49 -49
  3. package/.AgentWorkShop/prompts/host-tools.json +1238 -1238
  4. package/.AgentWorkShop/prompts/peer-reply-cross-channel.md +10 -10
  5. package/.output/.AgentWorkShop/prompts/aml-mission-default.md +20 -20
  6. package/.output/.AgentWorkShop/prompts/aml-playbook.md +49 -49
  7. package/.output/.AgentWorkShop/prompts/host-tools.json +1238 -1238
  8. package/.output/.AgentWorkShop/prompts/peer-reply-cross-channel.md +10 -10
  9. package/.output/nitro.json +1 -1
  10. package/.output/public/_nuxt/{BG8sCcwx.js → CUDwzgLV.js} +4 -4
  11. package/.output/public/_nuxt/CmsOtOAA2.js +1 -0
  12. package/.output/public/_nuxt/builds/latest.json +1 -1
  13. package/.output/public/_nuxt/builds/meta/4457d63f-0866-4cea-b320-04a321f24795.json +1 -0
  14. package/.output/public/_nuxt/default.BXI_fZUi.css +1 -0
  15. package/.output/server/chunks/_/nitro.mjs +703 -704
  16. package/.output/server/chunks/_/nitro.mjs.map +1 -1
  17. package/.output/server/chunks/build/{default-hseyOqBT.mjs → default-CZmZFpqH.mjs} +27 -42
  18. package/.output/server/chunks/build/{default-hseyOqBT.mjs.map → default-CZmZFpqH.mjs.map} +1 -1
  19. package/.output/server/chunks/routes/renderer.mjs +1 -1
  20. package/.output/server/chunks/virtual/entry.mjs +3 -1
  21. package/.output/server/chunks/virtual/precomputed.mjs +1 -1
  22. package/.output/server/package.json +1 -1
  23. package/README-zh.md +6 -6
  24. package/README.md +6 -6
  25. package/app/components/AppHeader.vue +0 -17
  26. package/app/components/AppSidebar.vue +1 -0
  27. package/docs/cli.en.md +2 -2
  28. package/docs/cli.md +2 -2
  29. package/docs/full-test-plan.md +5 -1
  30. package/docs/plugins.en.md +2 -1
  31. package/docs/plugins.md +2 -1
  32. package/docs/sdk.en.md +1 -1
  33. package/docs/sdk.md +1 -1
  34. package/i18n/locales/en.ts +1 -0
  35. package/i18n/locales/zh-CN.ts +1 -0
  36. package/package.json +1 -1
  37. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.continue.json +1 -0
  38. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.json +1 -0
  39. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/054bb4bc4e4217a2ab0b0c6b663747214a9af464f5568ea60e784704e340bb0f.source +153 -0
  40. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/0554197c3d2bac203a1f5c48a5d52495d1336db1bd7aa4f4c19875810363e684.source +153 -0
  41. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/05c203d12a954fffba942e1d626e3f5fec05b18a5aef2bac003b7884a4b77406.source +431 -0
  42. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/145a6e6ba811af35aed31e0cc3211f53706a14980fa069ee9e3c1591139f96f0.source +180 -0
  43. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/1ae0fb281f97899c025e368ca73f41349b01fb5f85b19e08d7ebccff49095ed5.source +112 -0
  44. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/20ee9967aa0541c516db7575bdd4a1a745939bc7016bd10d7635903113edb8ec.source +227 -0
  45. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/233751843aeba9d4c5263f275b8102de00443c06f286918cbd23e0b6729e01aa.source +131 -0
  46. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/263c2e05b425e5c322e60c6475279f963bab3a873eb521b21df7fec935a46597.source +160 -0
  47. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/2ac5eaac28eaaee2178c4b32014df8e732f9dd432a285a872f5b7f31c1395fa4.source +157 -0
  48. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/33a0db0d2d95d2c11d0720090458e5fba1425376f5ceb5b422d1d3471be3226b.source +348 -0
  49. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/359e474ae83db35e4615d3728de6bf694b64d4dce9b536730a3b6518f9c0494a.source +296 -0
  50. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/37a4c3c31c4b70f6b719c53d1e9c4fcba603f31adfdd11bb6ef9f0d094923c56.source +285 -0
  51. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/461feca1c14a695eb8b0f17a6f95fe97eeceabc0510faaea4ba6d25bde147d8d.source +217 -0
  52. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/4cfadbda8591f29d5e1e230dc000e701b756e850441b3900fb596a4d2f6e98c2.source +301 -0
  53. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/50ca2810cf0b248c96948994c0c404531bbad6e553166b6816b3b03bfa0f75d2.source +151 -0
  54. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/52d9e8cb591a9ae86e238c3620a610cd386717bee222c0f37b25b2ac7e523dc4.source +135 -0
  55. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/6c87daafa4c006560a954636a97333f6123153c9d34d96203f7ee3a14e556357.source +156 -0
  56. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/77bc47854b577900621cacf1697f81fa2bc005af129d37614a97a3441c08f99f.source +72 -0
  57. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/7bbb789d3a527817118cf8399ed1861d6a962ade10878fd07223435d333a7089.source +469 -0
  58. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/86b7c1daafc8b60ba9982dde5bf1bcc74265709483c176e71f23ec6fc267451f.source +259 -0
  59. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/8df66d62bd41984d0970e8236882080c5085ac937d07a29779463d6a03b37c76.source +97 -0
  60. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/9962f2d1b3a85137bd40cc835725600b39f89ee02a27c06b2e0402e1eafde115.source +277 -0
  61. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/a1090ea066b792cb201739f4994e37bf7c12f52e805239a1eab87f18a537f266.source +171 -0
  62. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/a5241091650134d0919897e086a606a3846c8b2b54f8b4c84a8696c145ac3e9e.source +425 -0
  63. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/af19fa4c6e34d5540028be4b17600161283af3a2a2dfc61a3fdc0834436a0c14.source +99 -0
  64. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/b3713d78e2b2f93905ff6be8d58a8d0a2f42dea322eb67c987fda22ff6a084d5.source +325 -0
  65. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/b3849943c3aff102d1af815631fc843a2a52c17646a4b04696ece323e483bbec.source +332 -0
  66. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/bac735a3803a6650b7f1eb8f587e6dc5c68ea055000c3c99a11789438b78e837.source +43 -0
  67. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/d32c24d61eb8b33c73e5a8fc7e4dc7ef437aed238ee4371c2b00617e6104090a.source +154 -0
  68. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/d5170dc9a9879fda04fdf91d8a110d4efb6787ee38dda596d4da899883d36a72.source +531 -0
  69. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/dc59bcd7bb397be63ba2f2c3fec23f3782dcb17d2d75a9981eeced761f33eb32.source +439 -0
  70. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/eb5ff8cf66b92aec7bf6726781d8df74faa2b030c3aaac17d6b00c1fc04c9b32.source +323 -0
  71. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/f10615a6fae18436e70d1201b2d44fdae3174a3aa7bb4db97dea1b3ed291d721.source +433 -0
  72. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/f36d3e271668bc1260c2823542d22dbf1d0f7041ff16b1ac175bce2256e582ca.source +280 -0
  73. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/f94449380af09f31ac352152007146a75cc80bae8d6411ffe57b2c56f933b0ff.source +117 -0
  74. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/fe4dfd81178fd6b648335f6405d9ede71319aebde796008e50db21f8adf44c99.source +160 -0
  75. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/ffb4e3aa548cfdadabaa44ab599f9b6a3ca6288176a2cc7a4b2fc2a12b5fb848.source +101 -0
  76. package/scripts/_audit/detached-start.mjs +103 -0
  77. package/scripts/_audit/guard-upstream-liveness.mjs +104 -0
  78. package/scripts/_dbg-perms-e2e.mjs +10 -3
  79. package/scripts/_dbg-protocol-matrix.mjs +453 -441
  80. package/scripts/_dbg-render-regression.mjs +151 -130
  81. package/scripts/_dbg-town-agents-probe.mjs +60 -0
  82. package/scripts/_dbg-visual-acceptance.mjs +63 -0
  83. package/scripts/e2e-agent-team.mjs +5 -3
  84. package/scripts/test-plugin-hardening.mjs +32 -0
  85. package/scripts/three-system-e2e.mjs +6 -6
  86. package/server/plugins/dev-stability-guard.ts +40 -63
  87. package/server/plugins-builtin/rag-bridge/index.mjs +9 -2
  88. package/server/utils/stability-severity.ts +66 -0
  89. package/.output/public/_nuxt/DVsjtsvZ2.js +0 -1
  90. package/.output/public/_nuxt/builds/meta/a66fdef7-dcfd-453b-b1f6-543c7dfd98d5.json +0 -1
  91. package/.output/public/_nuxt/default.DE_vT238.css +0 -1
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentworkshop-prod",
3
- "version": "0.7.37",
3
+ "version": "0.7.39",
4
4
  "type": "module",
5
5
  "private": true,
6
6
  "dependencies": {
package/README-zh.md CHANGED
@@ -15,7 +15,7 @@
15
15
 
16
16
  **[English →](./README.md)** · **[在线文档 →](https://kingdol666.github.io/AgentWorkShop/)** · **[版本发布 →](https://github.com/kingdol666/AgentWorkShop/releases)** · **[更新日志 →](./changelog.md)**
17
17
 
18
- *当前版本:**v0.7.37** · 14 个执行引擎 · 5 种现场协议 · 99 个运行时设置项 · 双语文档(简体中文 / English)*
18
+ *当前版本:**v0.7.39** · 14 个执行引擎 · 5 种现场协议 · 99 个运行时设置项 · 双语文档(简体中文 / English)*
19
19
 
20
20
  *一个配置驱动的平台:**AI Agent 团队**与**工业数字孪生**共享同一运行时——Agent 查询真实遥测、经人工审批的写控回路下发监督设定值,每个事件实时推送到 3D 孪生。*
21
21
 
@@ -253,7 +253,7 @@ aw update --check # 只报告,不安装
253
253
  npm install -g agentworkshop@latest # 手动等效
254
254
  ```
255
255
 
256
- 版本遵循 semver。每次 `aw start` 都会校验配置根,并把 `home` 之前的旧版 `data/` 布局迁移进来(最新文件胜出),因此数据可以跨版本存活。SQLite schema 迁移在服务端启动时执行。当前版本:**v0.7.37**——见[版本发布](https://github.com/kingdol666/AgentWorkShop/releases)。
256
+ 版本遵循 semver。每次 `aw start` 都会校验配置根,并把 `home` 之前的旧版 `data/` 布局迁移进来(最新文件胜出),因此数据可以跨版本存活。SQLite schema 迁移在服务端启动时执行。当前版本:**v0.7.39**——见[版本发布](https://github.com/kingdol666/AgentWorkShop/releases)。
257
257
 
258
258
  ### 第一次「Agent × 产线」会话(约 2 分钟)
259
259
 
@@ -436,10 +436,10 @@ SUBMITTED ─▶ ASSIGNED ─▶ WORKING ─▶ WAITING ─▶ COMPLETED
436
436
  |---|---|---|---|
437
437
  | `e2e-full-closedloop.mjs` | **124 PASS / 0 FAIL**(2026-09-12,v0.7.36) | 注册 → 登录 → 产线/产品/配方 → 数采采样 → Agent 绑定节点 → `daq_query` → `dcw_control` → HITL 审批 → PLC 写入 → 回读 → 配方回退 → 级联删除 → 数据根隔离 | `node scripts/e2e-full-closedloop.mjs http://127.0.0.1:3111` |
438
438
  | `e2e-aml.ts --real` | 0 失败(2026-09-11) | AML 数据集 → 作业提交 → 状态/日志 → 排行榜 → 晋级门禁,跑在真实 Python 运行时上 | `node node_modules/tsx/dist/cli.mjs --tsconfig .nuxt/tsconfig.server.json scripts/e2e-aml.ts --real` |
439
- | 五协议真实产线 | 37/37 | 逐协议连通、数采入 Timescale、逐协议数控下发 + 回读、Agent 闭环、HITL 经真实 OPC UA 写入、配方 + 参数账本回退 | `node scripts/_dbg-live-line-e2e.mjs` |
440
- | 生产 API 全链路 | 64/64 | 跨重启持久化、模板 CRUD、任务 assign/complete/cancel/loop/pipeline、A2A + mailbox、WS 广播、MCP 端点、级联删除 | `AW_E2E_TOKEN=<token> node scripts/api-live-e2e.mjs` |
441
- | 产线权限 / 审计负向 | 20/20 + 9/9 | 三态产线授权 + 人话 403、授权撤销收敛、无 token WS 零遥测 | `scripts/e2e-auth-matrix.mjs` |
442
- | 渲染回归 | 29/29 | 227 行数采表完整性、WS 驱动行更新、筛选、详情页、3D 小镇 + 模型库、7 页 smoke、零 pageerror | `node scripts/_dbg-render-regression.mjs <base> <email> <pass>` |
439
+ | 五协议真实产线 | 37/37(2026-09-12 干净环境复验) | 逐协议连通、数采入 Timescale、逐协议数控下发 + 回读、Agent 闭环、HITL 经真实 OPC UA 写入、配方 + 参数账本回退 | `node scripts/_dbg-live-line-e2e.mjs` |
440
+ | 生产 API 全链路 | 64/64(2026-09-12 干净环境复验) | 跨重启持久化、模板 CRUD、任务 assign/complete/cancel/loop/pipeline、A2A + mailbox、WS 广播、MCP 端点、级联删除 | `AW_E2E_TOKEN=<token> node scripts/api-live-e2e.mjs` |
441
+ | 产线权限 / 审计负向 | 21/21 + 9/9 | 三态产线授权 + 人话 403、绑定主体校验、授权撤销收敛、无 token WS 零遥测 | `node scripts/_dbg-perms-e2e.mjs <base> <adminPass>` · `node scripts/_dbg-audit-neg-e2e.mjs <base> <adminPass>` |
442
+ | 渲染回归 | 30/30 | 数采表完整性(行数与 API 动态对齐)、WS 驱动行更新、筛选、详情页、3D 小镇 + 模型库、7 页 smoke、零 pageerror | `node scripts/_dbg-render-regression.mjs <base> <email> <pass>` |
443
443
  | 多 Harness 并行 | 21 | omp 闭环 · codex 真实寄存器写入 · dsh 真实数采 · opencode 配方写入+回退——四引擎在一条开跑产线上 | `node scripts/e2e-multiharness-team.mjs` |
444
444
  | 离线单元/属性套件 | 全绿 | AEP 事件索引(`test-events-index`)、LRU、数据根拆分(`test-data-root`)、日志洪泛、回退索引、插件加固、记忆按月查询、CLI 退出码(`test-cli-exit`)、SDK 能力面(`test-sdk-surface`) | `node scripts/test-<name>.mjs` |
445
445
 
package/README.md CHANGED
@@ -15,7 +15,7 @@
15
15
 
16
16
  **[中文文档 →](./README-zh.md)** · **[Online Docs →](https://kingdol666.github.io/AgentWorkShop/)** · **[Releases →](https://github.com/kingdol666/AgentWorkShop/releases)** · **[Changelog →](./changelog.md)**
17
17
 
18
- *Current version: **v0.7.37** · 14 agent engines · 5 field protocols · 99 runtime settings · bilingual docs (简体中文 / English)*
18
+ *Current version: **v0.7.39** · 14 agent engines · 5 field protocols · 99 runtime settings · bilingual docs (简体中文 / English)*
19
19
 
20
20
  *A configuration-driven platform where **AI agent teams** and an **industrial digital twin** share one runtime — agents query real telemetry, issue supervisory setpoints through human-approved write control, and every event streams live to a 3D twin.*
21
21
 
@@ -253,7 +253,7 @@ aw update --check # only report; nothing is installed
253
253
  npm install -g agentworkshop@latest # manual equivalent
254
254
  ```
255
255
 
256
- 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.37** — see [Releases](https://github.com/kingdol666/AgentWorkShop/releases).
256
+ 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.39** — see [Releases](https://github.com/kingdol666/AgentWorkShop/releases).
257
257
 
258
258
  ### Your first agent × line session (~2 minutes)
259
259
 
@@ -444,10 +444,10 @@ the event stream and the HTTP API — not on mocks.
444
444
  |---|---|---|---|
445
445
  | `e2e-full-closedloop.mjs` | **124 PASS / 0 FAIL** (2026-09-12, v0.7.36) | registration → login → line/product/recipe → DAQ sampling → agent bound to nodes → `daq_query` → `dcw_control` → HITL approval → PLC write → readback → recipe rollback → cascade delete → data-root isolation | `node scripts/e2e-full-closedloop.mjs http://127.0.0.1:3111` |
446
446
  | `e2e-aml.ts --real` | 0 failures (2026-09-11) | AML datasets → job submit → status/logs → leaderboard → promotion gates, against the real Python runtime | `node node_modules/tsx/dist/cli.mjs --tsconfig .nuxt/tsconfig.server.json scripts/e2e-aml.ts --real` |
447
- | Five-protocol live line | 37/37 | driver connectivity, sampling into Timescale, DCW dispatch + readback per protocol, agent closed loop, HITL over a real OPC UA write, recipe + param-ledger rollback | `node scripts/_dbg-live-line-e2e.mjs` |
448
- | Production API live | 64/64 | persistence across restart, template CRUD, task assign/complete/cancel/loop/pipeline, A2A + mailbox, WS broadcast, MCP endpoint, cascade delete | `AW_E2E_TOKEN=<token> node scripts/api-live-e2e.mjs` |
449
- | Line permissions / audit-negative | 20/20 + 9/9 | three-state line grants with human-readable 403s, revocation convergence, unauthenticated WS receives zero telemetry | `scripts/e2e-auth-matrix.mjs` |
450
- | Render regression | 29/29 | 227-row DAQ table integrity, WS-driven row updates, filters, detail page, 3D town + model library, 7-page smoke, zero page errors | `node scripts/_dbg-render-regression.mjs <base> <email> <pass>` |
447
+ | Five-protocol live line | 37/37 (re-verified 2026-09-12 on a clean instance) | driver connectivity, sampling into Timescale, DCW dispatch + readback per protocol, agent closed loop, HITL over a real OPC UA write, recipe + param-ledger rollback | `node scripts/_dbg-live-line-e2e.mjs` |
448
+ | Production API live | 64/64 (re-verified 2026-09-12 on a clean instance) | persistence across restart, template CRUD, task assign/complete/cancel/loop/pipeline, A2A + mailbox, WS broadcast, MCP endpoint, cascade delete | `AW_E2E_TOKEN=<token> node scripts/api-live-e2e.mjs` |
449
+ | Line permissions / audit-negative | 21/21 + 9/9 | 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>` |
450
+ | Render regression | 30/30 | DAQ table integrity (row count aligned with the API), WS-driven row updates, filters, detail page, 3D town + model library, 7-page smoke, zero page errors | `node scripts/_dbg-render-regression.mjs <base> <email> <pass>` |
451
451
  | Multi-harness parallel | 21 | omp closed loop · codex real register write · dsh real acquisition · opencode recipe write+rollback — four engines on one running line | `node scripts/e2e-multiharness-team.mjs` |
452
452
  | Offline unit/property suites | all green | AEP event index (`test-events-index`), LRU, data-root split (`test-data-root`), log flooding, rollback index, plugin hardening, memory month query, CLI exit codes (`test-cli-exit`), SDK surface (`test-sdk-surface`) | `node scripts/test-<name>.mjs` |
453
453
 
@@ -204,17 +204,6 @@ const onAvatarMenu: MenuProps['onClick'] = async ({ key }) => {
204
204
 
205
205
  <span class="rail-mark i-tabler-route" />
206
206
 
207
- <!-- AML 建模入口:固定导航点(航迹由访问历史生成,/aml 需常驻入口) -->
208
- <NuxtLink
209
- class="trail-node aml-entry"
210
- :class="{ active: route.path.startsWith('/aml') }"
211
- to="/aml"
212
- :title="t('aml.k1amlx159')"
213
- >
214
- <span class="node-icon i-tabler-flask" />
215
- <span class="node-title">{{ t('aml.k1amlx158') }}</span>
216
- </NuxtLink>
217
-
218
207
  <nav
219
208
  class="trail"
220
209
  :aria-label="t('header.trail')"
@@ -462,12 +451,6 @@ const onAvatarMenu: MenuProps['onClick'] = async ({ key }) => {
462
451
  color: var(--ink-faint);
463
452
  }
464
453
 
465
- /* AML 固定入口:复用航点 chip 语言,紧贴 rail-mark 常驻 */
466
- .aml-entry {
467
- flex: 0 0 auto;
468
- margin-right: 2px;
469
- }
470
-
471
454
  .trail {
472
455
  flex: 0 1 auto;
473
456
  min-width: 0;
@@ -38,6 +38,7 @@ const menuGroups = computed<MenuGroup[]>(() => [
38
38
  { key: '/town', icon: 'i-tabler-map-2', label: t('menu.town'), motion: 'im-pop' },
39
39
  { key: '/daq', icon: 'i-tabler-activity', label: t('menu.daq'), motion: 'im-pop' },
40
40
  { key: '/dcw', icon: 'i-tabler-settings-automation', label: t('menu.dcw'), motion: 'im-pop' },
41
+ { key: '/aml', icon: 'i-tabler-flask', label: t('menu.aml'), motion: 'im-pop' },
41
42
  ],
42
43
  },
43
44
  {
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.37 start)
65
+ npx agentworkshop start # fetch and start (pin a version with: npx agentworkshop@0.7.38 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.37` (`package.json`) |
82
+ | Current version | `0.7.38` (`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.37 start)
63
+ npx agentworkshop start # 拉取并启动(可锁定版本: npx agentworkshop@0.7.38 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.37`(`package.json`) |
79
+ | 当前版本 | `0.7.38`(`package.json`) |
80
80
 
81
81
  ## 二、首次启动(Claude Code 式)
82
82
 
@@ -1,6 +1,6 @@
1
1
  # AgentWorkShop 全功能测试流程 Plan
2
2
 
3
- > 版本无关的常备测试手册(撰写时基线 v0.7.36)。用途:后续直接指令 agent
3
+ > 版本无关的常备测试手册(撰写时基线 v0.7.37)。用途:后续直接指令 agent
4
4
  > 「按 docs/full-test-plan.md 执行 Phase 0–N」即可完成全功能测试。
5
5
  > 断言数为历史全绿基线(2026-09 上旬各轮验收),以脚本实际输出为准;括号内数字仅用于识别明显回退。
6
6
  > 最后核对:2026-09-12(脚本清单逐一经 `scripts/` 实存核对)。
@@ -20,6 +20,8 @@
20
20
  - Bash 写源码/配置会被 Mimosa hook 拒绝 → 用 Write/Edit 工具;git add 源码路径同样被拒。
21
21
  - commitlint:subject 首字符不得大写(中文开头最稳),body 每行 ≤100 字符。本仓库有并行会话共享 git index:commit 必须 pathspec 限定,提交后 `git log` 核对没被卷走。
22
22
  - 共享实例有累积脏数据(数百产线/未结批次):判断「数采没采」前先确认产线批次门控(见 §8 排障表)。
23
+ - **长跑服务别挂在 agent 工具的后台作业里**:这类作业在回合结束/被回收时会连子进程一起终止,而 `aw start` 的 stdout 缓冲区随之丢失 —— 表现就是「日志停在半路、无 fatal、端口释放、退出码 1」,极易误判成服务自己崩了。要做长跑验收请**真分离**:`node scripts/_audit/detached-start.mjs --port 3001`(spawn + detached + stdio 落文件;`--status` 看健康与日志尾,`--stop` 停),之后只靠 `/api/health` 与日志文件判断存活。实测踩过三次「静默退出」,`[stability-guard]` 一行 fatal 都没有。
24
+ - **被强杀会留下陈旧单实例锁**:`aw start` 被外部终止后来不及释放 `.AgentWorkShop/.runtime/aw.lock`,下一次启动会以退出码 2 直接退出(日志只有一行 `__AW_START_EXIT=2`,没有报错正文)。`aw stop` 会清理它(即使锁里的 pid 属于**另一个**模式);`detached-start.mjs` 也内置了这步。
23
25
 
24
26
  **报告产物**:`docs/test-report-<版本>.md`,逐阶段列脚本 → 断言数 → 结果 → 失败详情与处置。
25
27
 
@@ -163,6 +165,8 @@
163
165
  | npm publish/install 被 7890 拦 | `--no-proxy --https-proxy=null --proxy=null`;git push 用 `-c http.proxy=` |
164
166
  | dev 起服务 500 找不到 `D:\shared\*.mjs` | nitro 相对导入打包坑:须 `@/` 别名(勿用裸相对路径) |
165
167
  | tsx 报类型/import 错 | 必须带 `--tsconfig .nuxt/tsconfig.server.json` |
168
+ | 长跑中途服务**静默消失**、端口释放、日志里**没有** fatal 行 | 两种可能,靠"日志有无 `[stability-guard] fatal`"区分:① 被外部强杀(`taskkill`/句柄回收)—— 退出码 1 且 stdout 缓冲区丢失,属进程外因素;② 走到 fatal 分支 —— 会有 `fatal <kind>, exiting:` 加完整错误文本。护栏只有四类异常**不退进程**:`socket`(ECONNRESET/EPIPE)/`db-busy`(SQLITE_BUSY)/`upstream`(ECONNREFUSED/ENOTFOUND/EHOSTUNREACH/ENETUNREACH/连接超时)/`engine`(omp·codex·dsh·opencode API),其余一律 fatal。判据集中在 `server/utils/stability-severity.ts`,可被纯 node 直接断言(`test-plugin-hardening.mjs` 第 3 节,16 条)|
169
+ | 一台设备没开就带走了整个平台(数采/数控/孪生全挂) | 曾实测:Modbus TCP 设备未启动(`connect ECONNREFUSED 127.0.0.1:1502`)触发 `unhandledRejection` 走到 fatal → 生产实例 `exit 1`。`ECONNREFUSED` 等**外部设备/上游不可达**已归入 `upstream`(只记录 + 计数,不退进程)—— 设备离线是运维常态,DAQ 驱动本来就把失败记为节点 `lastError`/`state=offline` 并继续轮询。回归验证:`node scripts/_audit/guard-upstream-liveness.mjs --port 1502`(建一条指向死端口的节点 → 断言服务仍健康)|
166
170
 
167
171
  ---
168
172
 
@@ -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.37**. `ctx.sdkVersion` is currently `0.3.0`.
12
+ > describes **v0.7.38**. `ctx.sdkVersion` is currently `0.3.0`.
13
13
 
14
14
  ## 1. Folder layout & quick start
15
15
 
@@ -500,6 +500,7 @@ export default {
500
500
  | Symptom | Cause / fix |
501
501
  |---|---|
502
502
  | Plugin code edits have no effect | the host has no plugin-directory watcher: touch `<home>/plugins-state.json`, or toggle `aw plugin enable/disable` once, to trigger a reload (the reload imports with `?t=` to defeat the cache); core `server/` and `shared/` changes still need a process restart |
503
+ | Recreated a same-named plugin but routes 404 | a name previously disabled keeps its flag in `<home>/plugins-state.json`; recreating the directory does not reload it — run `aw plugin enable <name>` explicitly after recreating (verified in practice) |
503
504
  | Load failures are invisible | `aw plugin list` does **not** show them. Use `GET /api/workshop/plugins` (returns `{ plugins, failures, initedAt }`, **no `{code,data}` envelope**) or read `[aw-plugins] 装载失败 …` in the startup log |
504
505
  | Plugin route 404 | matching is exact; check the method and the leading `/` of the path, and whether the plugin is enabled (a disabled plugin's routes are dropped from the table) |
505
506
  | Plugin route 401 | the entry declared `auth: 'user' \| 'admin' \| 'agent-or-user'` and the request carried no valid credential; use `auth: 'none'` only for open endpoints |
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.37**。`ctx.sdkVersion` 当前为 `0.3.0`。
9
+ > 运行时服务面);本文档对应当前版本 **v0.7.38**。`ctx.sdkVersion` 当前为 `0.3.0`。
10
10
 
11
11
  ## 一、目录结构与快速开始
12
12
 
@@ -497,6 +497,7 @@ export default {
497
497
  | 现象 | 原因 / 处理 |
498
498
  |---|---|
499
499
  | 改了插件代码没生效 | 宿主没有插件目录 watcher:触碰 `<home>/plugins-state.json` 或 `aw plugin enable/disable` 往返一次触发重载(重载会带 `?t=` 破缓存);核心 `server/`、`shared/` 改动仍需重启进程 |
500
+ | 重建同名插件后路由 404 | 此前 disable 过的插件名在 `<home>/plugins-state.json` 里留有禁用标记,重建目录不会自动恢复装载 —— 重建后必须显式 `aw plugin enable <name>`(实测踩过) |
500
501
  | 装载失败看不到 | `aw plugin list` **不显示**装载失败。用 `GET /api/workshop/plugins`(返回 `{ plugins, failures, initedAt }`,**没有 `{code,data}` 信封**),或看启动日志里的 `[aw-plugins] 装载失败 …` |
501
502
  | 插件路由 404 | exact-match;检查 method 与 path 前导 `/`,以及插件是否处于启用态(停用插件的路由已从路由表移除) |
502
503
  | 插件路由 401 | 入口声明了 `auth: 'user' \| 'admin' \| 'agent-or-user'`,请求未带有效凭据;开放接口才用 `auth: 'none'` |
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.37) —
10
+ from the npm package version (the `version` field in `package.json`, currently 0.7.38) —
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.37)
8
+ 这两个常量是 **SDK 自身接口的版本号**,与 npm 包版本(`package.json` 的 `version`,当前 0.7.38)
9
9
  **各自独立演进** —— 常数不同步是预期行为,不要拿 `SDK_VERSION` 去推断包版本,反之亦然。
10
10
  **模块形态**:ESM only(只有 `import` / `export`,没有 CJS 入口)。
11
11
  **类型**:`sdk/index.d.mts`、`sdk/client.d.mts` 随包发布,TS 项目零配置获得提示(覆盖范围见 §7)。
@@ -25,6 +25,7 @@ export default {
25
25
  tokens: 'API Tokens',
26
26
  daq: 'DAQ Console',
27
27
  dcw: 'Line Ops',
28
+ aml: 'Modeling',
28
29
  logs: 'Logs',
29
30
  users: 'Users',
30
31
  monitor: 'Runtime Monitor',
@@ -25,6 +25,7 @@ export default {
25
25
  tokens: 'API Token',
26
26
  daq: '数采中心',
27
27
  dcw: '产线运营',
28
+ aml: '建模',
28
29
  logs: '日志管理',
29
30
  users: '用户管理',
30
31
  monitor: '运行时监控',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentworkshop",
3
- "version": "0.7.37",
3
+ "version": "0.7.39",
4
4
  "type": "module",
5
5
  "description": "AgentWorkShop 软件开发系统 - 基于 Nuxt 4 的配置驱动运行范式",
6
6
  "license": "PolyForm-Noncommercial-1.0.0",
@@ -0,0 +1 @@
1
+ {"schemaVersion":"mimosa-stop-continuation/v1","generation":"mtxwtxme-61028-1f4519fbc4","used":false,"reportPersisted":false,"claim":null,"updatedAt":"2026-09-12T04:53:46.023Z"}
@@ -0,0 +1 @@
1
+ {"touched":[],"bashMutation":false,"reportedFindings":[],"findingEvents":[],"baseline":{"storageId":"mtxwtxme-61028-1f4519fbc4","createdAt":"2026-09-12T04:53:45.974Z","files":{"e2e-agent-channel-task.ts":{"existed":true,"snapshot":"50ca2810cf0b248c96948994c0c404531bbad6e553166b6816b3b03bfa0f75d2.source"},"e2e-aml.ts":{"existed":true,"snapshot":"86b7c1daafc8b60ba9982dde5bf1bcc74265709483c176e71f23ec6fc267451f.source"},"e2e-channel-isolation.ts":{"existed":true,"snapshot":"b3713d78e2b2f93905ff6be8d58a8d0a2f42dea322eb67c987fda22ff6a084d5.source"},"e2e-lead-team-mgmt.ts":{"existed":true,"snapshot":"461feca1c14a695eb8b0f17a6f95fe97eeceabc0510faaea4ba6d25bde147d8d.source"},"e2e-memory-system.ts":{"existed":true,"snapshot":"b3849943c3aff102d1af815631fc843a2a52c17646a4b04696ece323e483bbec.source"},"e2e-multi-harness.ts":{"existed":true,"snapshot":"d5170dc9a9879fda04fdf91d8a110d4efb6787ee38dda596d4da899883d36a72.source"},"e2e-omp-workspace.ts":{"existed":true,"snapshot":"145a6e6ba811af35aed31e0cc3211f53706a14980fa069ee9e3c1591139f96f0.source"},"e2e-task-queue.ts":{"existed":true,"snapshot":"9962f2d1b3a85137bd40cc835725600b39f89ee02a27c06b2e0402e1eafde115.source"},"smoke-optimization-verify.ts":{"existed":true,"snapshot":"77bc47854b577900621cacf1697f81fa2bc005af129d37614a97a3441c08f99f.source"},"test-a2a-types.ts":{"existed":true,"snapshot":"359e474ae83db35e4615d3728de6bf694b64d4dce9b536730a3b6518f9c0494a.source"},"test-agent-brain.ts":{"existed":true,"snapshot":"52d9e8cb591a9ae86e238c3620a610cd386717bee222c0f37b25b2ac7e523dc4.source"},"test-agent-multi-channel.ts":{"existed":true,"snapshot":"fe4dfd81178fd6b648335f6405d9ede71319aebde796008e50db21f8adf44c99.source"},"test-agent-runtime.ts":{"existed":true,"snapshot":"33a0db0d2d95d2c11d0720090458e5fba1425376f5ceb5b422d1d3471be3226b.source"},"test-channel-routing.ts":{"existed":true,"snapshot":"4cfadbda8591f29d5e1e230dc000e701b756e850441b3900fb596a4d2f6e98c2.source"},"test-dcw-read.ts":{"existed":true,"snapshot":"0554197c3d2bac203a1f5c48a5d52495d1336db1bd7aa4f4c19875810363e684.source"},"test-dual-drive.ts":{"existed":true,"snapshot":"20ee9967aa0541c516db7575bdd4a1a745939bc7016bd10d7635903113edb8ec.source"},"test-full-system.ts":{"existed":true,"snapshot":"dc59bcd7bb397be63ba2f2c3fec23f3782dcb17d2d75a9981eeced761f33eb32.source"},"test-game-session.ts":{"existed":true,"snapshot":"263c2e05b425e5c322e60c6475279f963bab3a873eb521b21df7fec935a46597.source"},"test-hitl-registry.ts":{"existed":true,"snapshot":"1ae0fb281f97899c025e368ca73f41349b01fb5f85b19e08d7ebccff49095ed5.source"},"test-mcp-tools.ts":{"existed":true,"snapshot":"37a4c3c31c4b70f6b719c53d1e9c4fcba603f31adfdd11bb6ef9f0d094923c56.source"},"test-memory-dynamic.ts":{"existed":true,"snapshot":"ffb4e3aa548cfdadabaa44ab599f9b6a3ca6288176a2cc7a4b2fc2a12b5fb848.source"},"test-memory-maintenance.ts":{"existed":true,"snapshot":"f94449380af09f31ac352152007146a75cc80bae8d6411ffe57b2c56f933b0ff.source"},"test-memory-vector.ts":{"existed":true,"snapshot":"d32c24d61eb8b33c73e5a8fc7e4dc7ef437aed238ee4371c2b00617e6104090a.source"},"test-memory.ts":{"existed":true,"snapshot":"05c203d12a954fffba942e1d626e3f5fec05b18a5aef2bac003b7884a4b77406.source"},"test-orchestration.ts":{"existed":true,"snapshot":"a5241091650134d0919897e086a606a3846c8b2b54f8b4c84a8696c145ac3e9e.source"},"test-persistence-lazy.ts":{"existed":true,"snapshot":"eb5ff8cf66b92aec7bf6726781d8df74faa2b030c3aaac17d6b00c1fc04c9b32.source"},"test-protocol.ts":{"existed":true,"snapshot":"6c87daafa4c006560a954636a97333f6123153c9d34d96203f7ee3a14e556357.source"},"test-scheduler-loop.ts":{"existed":true,"snapshot":"7bbb789d3a527817118cf8399ed1861d6a962ade10878fd07223435d333a7089.source"},"test-task-engine.ts":{"existed":true,"snapshot":"f10615a6fae18436e70d1201b2d44fdae3174a3aa7bb4db97dea1b3ed291d721.source"},"test-workshop-db.ts":{"existed":true,"snapshot":"f36d3e271668bc1260c2823542d22dbf1d0f7041ff16b1ac175bce2256e582ca.source"},"_dbg-agent-dcw-rw.ts":{"existed":true,"snapshot":"233751843aeba9d4c5263f275b8102de00443c06f286918cbd23e0b6729e01aa.source"},"_dbg-harness-ui-e2e.py":{"existed":true,"snapshot":"8df66d62bd41984d0970e8236882080c5085ac937d07a29779463d6a03b37c76.source"},"_audit/dcw-controller-e2e.ts":{"existed":true,"snapshot":"af19fa4c6e34d5540028be4b17600161283af3a2a2dfc61a3fdc0834436a0c14.source"},"_audit/dcw-flush-cost.ts":{"existed":true,"snapshot":"bac735a3803a6650b7f1eb8f587e6dc5c68ea055000c3c99a11789438b78e837.source"},"_audit/dcw-rollback-ledger.ts":{"existed":true,"snapshot":"054bb4bc4e4217a2ab0b0c6b663747214a9af464f5568ea60e784704e340bb0f.source"},"_audit/dcw-unrecorded-write.ts":{"existed":true,"snapshot":"a1090ea066b792cb201739f4994e37bf7c12f52e805239a1eab87f18a537f266.source"},"_audit/dcw-write-mutex.ts":{"existed":true,"snapshot":"2ac5eaac28eaaee2178c4b32014df8e732f9dd432a285a872f5b7f31c1395fa4.source"}},"complete":true,"candidateLimit":5000,"discoveredFiles":37,"capturedFiles":37,"truncated":false,"omittedAtLeast":0,"firstOmitted":"","errors":[]},"stateErrors":[],"omittedReportedFindings":0,"omittedFindingEvents":0,"processing":null,"updatedAt":"2026-09-12T04:53:46.024Z"}
@@ -0,0 +1,153 @@
1
+ /**
2
+ * 审计实验 2 —— RecipeRollBackRepo 账本算法/落盘成本(只读审计)。
3
+ *
4
+ * 关键:cwd 切到 scripts/_audit/.tmp,使 repo 的 DATA_DIR 指向临时目录,
5
+ * **绝不触碰 server/data/dcw-rollback.json**(真实数据文件)。
6
+ *
7
+ * 运行:node --experimental-transform-types scripts/_audit/run.mjs scripts/_audit/dcw-rollback-ledger.ts
8
+ */
9
+ import { mkdirSync } from 'node:fs'
10
+
11
+ const TMP = new URL('./.tmp/', import.meta.url)
12
+ mkdirSync(new URL('server/data/', TMP), { recursive: true })
13
+ process.chdir(new URL('.tmp/', new URL('.', import.meta.url)).pathname.replace(/^\//, ''))
14
+
15
+ const { RecipeRollBackRepo } = await import('../../server/services/workshop/dcw/recipe-rollback.repo')
16
+
17
+ const CAP_ANCHORS = 20000
18
+ const CAP_RECORDS = 2000
19
+
20
+ function anchorOf(i: number, nodeId = `n-${i % 50}`) {
21
+ return {
22
+ lineId: 'ln-audit',
23
+ nodeId,
24
+ prevValue: i,
25
+ newValue: i + 1,
26
+ source: 'manual' as const,
27
+ actor: 'user',
28
+ recipeRunId: null,
29
+ }
30
+ }
31
+
32
+ /** 贴近真实形状的 windowAgg(实测 server/data/dcw-rollback.json:平均记录 1267 B) */
33
+ function aggOf(nodeId: string) {
34
+ return {
35
+ at: new Date().toISOString(),
36
+ fromMs: Date.now() - 600_000,
37
+ toMs: Date.now(),
38
+ channels: Array.from({ length: 8 }, (_, k) => ({
39
+ daqNodeId: `dq-${k}`,
40
+ ch: `CH${k}`,
41
+ unit: 'C',
42
+ latest: 123.45,
43
+ avg: 121.11,
44
+ min: 100.5,
45
+ max: 140.25,
46
+ cnt: 120,
47
+ breaches: 0,
48
+ nodeIdFallback: nodeId,
49
+ })),
50
+ }
51
+ }
52
+
53
+ function ms(fn: () => void): number {
54
+ const t0 = performance.now()
55
+ fn()
56
+ return performance.now() - t0
57
+ }
58
+ function report(label: string, totalMs: number, iters: number): void {
59
+ console.log(` ${label}: total=${totalMs.toFixed(1)}ms per-op=${(totalMs / iters).toFixed(3)}ms (iters=${iters})`)
60
+ }
61
+
62
+ console.log('=== A. anchors 到达上限后的每次 appendAnchor 成本 ===')
63
+ {
64
+ const repo = new RecipeRollBackRepo()
65
+ for (let i = 0; i < CAP_ANCHORS; i++) repo.appendAnchor(anchorOf(i))
66
+ console.log(` 填充后 anchors=${repo.stats().anchors}(cap=${CAP_ANCHORS})`)
67
+ const iters = 200
68
+ const t = ms(() => {
69
+ for (let i = 0; i < iters; i++) repo.appendAnchor(anchorOf(CAP_ANCHORS + i))
70
+ }, iters)
71
+ report('appendAnchor(在上限稳态,每次触发 splice+new Set)', t, iters)
72
+ console.log(` 追加后 anchors=${repo.stats().anchors}(仍为 cap,说明每写都触发淘汰路径)`)
73
+
74
+ console.log('\n=== B. 查询方法成本(20000 锚 / 50 节点) ===')
75
+ const nodeId = 'n-0'
76
+ let t1 = ms(() => { for (let i = 0; i < 1000; i++) repo.lastStableAnchor(nodeId) }, 1000)
77
+ report('lastStableAnchor (O(1) 索引)', t1, 1000)
78
+ t1 = ms(() => { for (let i = 0; i < 1000; i++) repo.lastRollbackAnchor(nodeId) }, 1000)
79
+ report('lastRollbackAnchor (节点子序列倒扫)', t1, 1000)
80
+ t1 = ms(() => { for (let i = 0; i < 200; i++) repo.anchorById('anc-nonexistent') }, 200)
81
+ report('anchorById (全量 find,未命中=最坏)', t1, 200)
82
+ t1 = ms(() => { for (let i = 0; i < 200; i++) repo.listAnchors({ lineId: 'ln-nope', limit: 100 }) }, 200)
83
+ report('listAnchors({lineId}) 无命中 → 全量 20000 倒扫', t1, 200)
84
+ t1 = ms(() => { for (let i = 0; i < 200; i++) repo.listAnchors({ nodeId, limit: 30 }) }, 200)
85
+ report('listAnchors({nodeId, limit:30}) 走索引', t1, 200)
86
+ }
87
+
88
+ console.log('\n=== C. records(2000 上限)+ open 索引 ===')
89
+ {
90
+ const repo = new RecipeRollBackRepo()
91
+ for (let i = 0; i < CAP_RECORDS; i++) {
92
+ repo.insertRecord({
93
+ lineId: 'ln-audit',
94
+ nodeId: `n-${i % 50}`,
95
+ nodeName: `node-${i % 50}`,
96
+ recipeId: null,
97
+ hypothesis: 'h',
98
+ params: [{ nodeId: `n-${i % 50}`, templateRef: 'cw-temp', from: i, to: i + 1 }],
99
+ setAt: new Date().toISOString(),
100
+ status: 'open',
101
+ judge: null,
102
+ anchorId: `anc-${i}`,
103
+ policy: 'auto_rollback',
104
+ })
105
+ }
106
+ console.log(` records=${repo.stats().records} open=${repo.stats().open}(每节点仅索引最后一条)`)
107
+ const t = ms(() => { for (let i = 0; i < 200; i++) repo.listOpenRecords() }, 200)
108
+ report('listOpenRecords (O(open))', t, 200)
109
+ const t2 = ms(() => { for (let i = 0; i < 200; i++) repo.chainRollbackCount('n-0') }, 200)
110
+ report('chainRollbackCount (全量 2000 filter)', t2, 200)
111
+ const t3 = ms(() => { for (let i = 0; i < 2000; i++) repo.byId('opt-nonexistent') }, 2000)
112
+ report('byId (全量 find,未命中=最坏)', t3, 2000)
113
+ }
114
+
115
+ console.log('\n=== D. 同节点第二条 open 记录 → 索引可见性 ===')
116
+ {
117
+ const repo = new RecipeRollBackRepo()
118
+ const r1 = repo.insertRecord({ nodeId: 'n-x', status: 'open', policy: 'auto_rollback', setAt: new Date().toISOString(), params: [], lineId: 'l' } as never)
119
+ const r2 = repo.insertRecord({ nodeId: 'n-x', status: 'open', policy: 'auto_rollback', setAt: new Date().toISOString(), params: [], lineId: 'l' } as never)
120
+ const open = repo.listOpenRecords()
121
+ const listed = repo.listRecords({ nodeId: 'n-x', status: 'open', limit: 50 })
122
+ console.log(` insertRecord #1=${r1.id} #2=${r2.id}`)
123
+ console.log(` listOpenRecords() 返回 ${open.length} 条;listRecords(status=open) 返回 ${listed.length} 条`)
124
+ console.log(` → 未被索引的 open 记录是否可达:${open.some(r => r.id === r1.id) ? '可达' : '不可达(永不被 sweep 评估)'}`)
125
+ }
126
+
127
+ console.log('\n=== E. flushNow(全量序列化)成本 ===')
128
+ {
129
+ const repo = new RecipeRollBackRepo()
130
+ for (let i = 0; i < CAP_ANCHORS; i++) repo.appendAnchor(anchorOf(i))
131
+ for (let i = 0; i < CAP_RECORDS; i++) {
132
+ repo.insertRecord({
133
+ lineId: 'ln-audit',
134
+ nodeId: `n-${i % 50}`,
135
+ nodeName: `node-${i % 50}`,
136
+ recipeId: null,
137
+ hypothesis: 'h',
138
+ params: [{ nodeId: `n-${i % 50}`, templateRef: 'cw-temp', from: i, to: i + 1 }],
139
+ setAt: new Date().toISOString(),
140
+ status: 'closed-line-stop',
141
+ judge: null,
142
+ anchorId: `anc-${i}`,
143
+ policy: 'auto_rollback',
144
+ windowAgg: aggOf(`n-${i % 50}`),
145
+ } as never)
146
+ }
147
+ const iters = 20
148
+ const t = ms(() => { for (let i = 0; i < iters; i++) repo.flushNow() }, iters)
149
+ report(`flushNow(anchors=${CAP_ANCHORS} + records=${CAP_RECORDS} 含聚合)`, t, iters)
150
+ const { statSync } = await import('node:fs')
151
+ const size = statSync(process.cwd() + '/server/data/dcw-rollback.json').size
152
+ console.log(` 落盘文件大小=${(size / 1024 / 1024).toFixed(2)} MB(每次 agent/rollback 写后同步全量重写)`)
153
+ }
@@ -0,0 +1,153 @@
1
+ /**
2
+ * DCW 读写集成单元测试:驱动 read 原语(mock/modbus 形态判定)/工程量↔原始值对称映射/
3
+ * 节点读状态记账/运行时周期读调度/手动读取/Agent dcw_read 工具(鉴权+执行,AW_HOME 隔离)。
4
+ * 运行: AW_MODE=home AW_HOME=<tmp> npx tsx --tsconfig .nuxt/tsconfig.server.json scripts/test-dcw-read.ts
5
+ */
6
+ import { mkdtempSync, rmSync } from 'node:fs'
7
+ import { tmpdir } from 'node:os'
8
+ import { join } from 'node:path'
9
+
10
+ // 工具段需要文件型仓库:整体进程重定向到一次性配置根(避免触碰检出内 .AgentWorkShop)
11
+ const awHome = mkdtempSync(join(tmpdir(), 'aw-dcw-read-'))
12
+ process.env.AW_MODE = 'home'
13
+ process.env.AW_HOME = awHome
14
+
15
+ let failures = 0
16
+ function check(name: string, ok: boolean, detail = ''): void {
17
+ console.log(` ${ok ? 'PASS' : 'FAIL'} ${name}${detail ? ` — ${detail}` : ''}`)
18
+ if (!ok) failures += 1
19
+ }
20
+
21
+ // ===== 1. 驱动 read 原语 =====
22
+ console.log('\n--- 驱动 read 原语 ---')
23
+ {
24
+ const { mockDcwDriver, supportsDcwRead } = await import('../server/services/workshop/dcw/drivers')
25
+ const domain = { min: 0, max: 300 }
26
+ const w = await mockDcwDriver.write({ eng: 165, tolerance: 0.1, domain, driverConfig: { key: 't1' } })
27
+ check('mock 写入成功', w.ok)
28
+ const r = await mockDcwDriver.read!({ domain, driverConfig: { key: 't1' } })
29
+ check('mock 读回写值(读写闭环)', r.ok && r.eng === 165 && r.raw === 165, `eng=${r.eng}`)
30
+ const miss = await mockDcwDriver.read!({ domain, driverConfig: { key: 'never-written' } })
31
+ check('mock 未写地址读取明确失败', !miss.ok && miss.eng == null)
32
+ check('读能力判定:mock/modbus-tcp/modbus-rtu/opcua 可读,mqtt/http 不可读',
33
+ supportsDcwRead('mock') && supportsDcwRead('modbus-tcp') && supportsDcwRead('modbus-rtu') && supportsDcwRead('opcua')
34
+ && !supportsDcwRead('mqtt') && !supportsDcwRead('http'))
35
+ }
36
+
37
+ // ===== 2. 工程量↔原始值映射对称 =====
38
+ console.log('\n--- 工程量↔原始值映射 ---')
39
+ {
40
+ const { engToRaw, rawToEng } = await import('../server/services/workshop/dcw/drivers')
41
+ const input = { eng: 50, tolerance: 0, domain: { min: 0, max: 100 }, driverConfig: { rawMin: 0, rawMax: 16383, engMin: 0, engMax: 100 } }
42
+ const raw = engToRaw(50, input)
43
+ check('线性映射:eng 50 → raw 8191.5', Math.abs(raw - 8191.5) < 1e-9, String(raw))
44
+ check('映射对称:rawToEng(engToRaw(x)) = x', Math.abs(rawToEng(raw, input) - 50) < 1e-9)
45
+ const pass = { eng: 7, tolerance: 0, domain: { min: 0, max: 100 }, driverConfig: {} }
46
+ check('无原始量程:直传(float32 语义)', engToRaw(7, pass) === 7 && rawToEng(7, pass) === 7)
47
+ }
48
+
49
+ // ===== 3. 节点读状态记账 =====
50
+ console.log('\n--- 节点读状态 ---')
51
+ {
52
+ const { DcwNode } = await import('../server/services/workshop/dcw/dcw-node')
53
+ const n = new DcwNode({ id: 'dw-t1', templateRef: 'dcw-temp-sp', readIntervalMs: 2000 })
54
+ const at = new Date().toISOString()
55
+ n.applyReadResult(168.456, 168.456, true, '读回', at)
56
+ check('成功读数回填(按 decimals 取整)', n.readValue === 168.5 && n.lastReadAt === at && n.lastReadError == null, `readValue=${n.readValue}`)
57
+ n.applyReadResult(null, null, false, '链路超时', new Date().toISOString())
58
+ check('失败读保留旧值并记原因', n.readValue === 168.5 && n.lastReadError === '链路超时')
59
+ n.applyReadResult(170, 170, true, '读回', new Date().toISOString())
60
+ check('恢复成功后清空错误', n.readValue === 170 && n.lastReadError == null)
61
+ const row = n.toRow()
62
+ const n2 = DcwNode.fromRow(row)
63
+ check('持久化 roundtrip(readIntervalMs/readValue/lastRead*)', n2.readIntervalMs === 2000 && n2.readValue === 170 && n2.lastReadError == null)
64
+ const v = n2.toView()
65
+ check('视图透出读字段', v.readValue === 170 && v.readIntervalMs === 2000 && 'lastReadError' in v)
66
+ }
67
+
68
+ // ===== 4. 运行时周期读调度 =====
69
+ console.log('\n--- 运行时周期读调度 ---')
70
+ {
71
+ const { DcwNode } = await import('../server/services/workshop/dcw/dcw-node')
72
+ const rtMod = await import('../server/services/workshop/dcw/dcw-runtime')
73
+ const { DcwNodeRuntime } = rtMod
74
+ type DcwRuntimeHost = import('../server/services/workshop/dcw/dcw-runtime').DcwRuntimeHost
75
+ type DcwNodeT = import('../server/services/workshop/dcw/dcw-node').DcwNode
76
+ const reads: number[] = []
77
+ const atNow = () => new Date().toISOString()
78
+ // 记账语义与控制器一致:读成功回填节点 readValue
79
+ const makeHost = (defaults: { holdIntervalMs: number, readIntervalMs: number }): DcwRuntimeHost => ({
80
+ running: () => true,
81
+ defaults: () => defaults,
82
+ executeWrite: async () => ({ ok: true, message: '', raw: null, readback: null }),
83
+ executeRead: async (node: DcwNodeT) => {
84
+ reads.push(Date.now())
85
+ node.applyReadResult(1, 1, true, '读回', atNow())
86
+ return { ok: true, value: 1, raw: 1, message: '', at: atNow() }
87
+ },
88
+ })
89
+ const n = new DcwNode({ id: 'dw-t2', templateRef: 'dcw-temp-sp', readIntervalMs: 1000 })
90
+ const rt = new DcwNodeRuntime(n, makeHost({ holdIntervalMs: 0, readIntervalMs: 5000 }))
91
+ const t0 = 1_000_000
92
+ rt.tick(t0)
93
+ rt.tick(t0 + 500)
94
+ rt.tick(t0 + 1100)
95
+ check('周期读按 readIntervalMs 节拍触发(两次窗内各一次)', reads.length === 2, `reads=${reads.length}`)
96
+ check('写心跳不触发(value=null)', reads.length === 2)
97
+ n.readValue = 165
98
+ const manual = await rt.readNow()
99
+ check('手动读取(readNow)执行并记账', manual.ok && n.readValue === 1 && n.lastReadError == null, `readValue=${n.readValue}`)
100
+ const schedAfterManual = reads.length
101
+ const off = new DcwNode({ id: 'dw-t3', templateRef: 'dcw-temp-sp', readIntervalMs: 0 })
102
+ const rtOff = new DcwNodeRuntime(off, makeHost({ holdIntervalMs: 0, readIntervalMs: 5000 }))
103
+ rtOff.tick(t0)
104
+ rtOff.tick(t0 + 2000)
105
+ check('readIntervalMs=0 关闭周期读', reads.length === schedAfterManual)
106
+ // 在飞互斥:挂起中的读阻塞 readNow → 409
107
+ let release: (() => void) | null = null
108
+ const slowHost: DcwRuntimeHost = {
109
+ running: () => true,
110
+ defaults: () => ({ holdIntervalMs: 0, readIntervalMs: 5000 }),
111
+ executeWrite: async () => ({ ok: true, message: '', raw: null, readback: null }),
112
+ executeRead: async () => {
113
+ await new Promise<void>((r) => {
114
+ release = r
115
+ })
116
+ return { ok: true, value: 1, raw: null, message: '', at: atNow() }
117
+ },
118
+ }
119
+ const rtSlow = new DcwNodeRuntime(new DcwNode({ id: 'dw-t4', templateRef: 'dcw-temp-sp', readIntervalMs: null }), slowHost)
120
+ const inFlight = rtSlow.readNow()
121
+ let conflicted = false
122
+ await rtSlow.readNow().catch((e: unknown) => {
123
+ conflicted = (e as { status?: number }).status === 409
124
+ })
125
+ release!()
126
+ const done = await inFlight
127
+ check('读在飞互斥(并发 readNow → 409)', conflicted && done.ok)
128
+ }
129
+
130
+ // ===== 5. Agent dcw_read 工具(鉴权 + 执行;隔离配置根) =====
131
+ console.log('\n--- Agent dcw_read 工具 ---')
132
+ {
133
+ const { toolDcwRead } = await import('../server/services/workshop/agents/industrial-tools')
134
+ const denied = await toolDcwRead('agent-x', { node_id: 'dw-none' })
135
+ check('未绑定 agent 读取被拒', denied.isError === true && /未绑定|无权/.test(denied.text))
136
+ const { getDcwController } = await import('../server/services/workshop/dcw/dcw-controller')
137
+ const { getAgentNodeBindingRepo } = await import('../server/services/workshop/agents/node-bindings.repo')
138
+ const node = getDcwController().create({ templateRef: 'dcw-temp-sp', driver: 'mock', driverConfig: { key: 'agent-t' }, readIntervalMs: 0, name: '读链路测试节点' })
139
+ const repo = getAgentNodeBindingRepo()
140
+ repo.bind('agent-a', node.id, 'dcw', 'auto')
141
+ const before = await toolDcwRead('agent-a', { node_id: node.id })
142
+ check('从未下发的节点:鉴权通过并如实报告读取失败(mock 无记录)', before.isError === true && /读取失败/.test(before.text) && !/无权|未绑定/.test(before.text), before.text.slice(0, 80))
143
+ await getDcwController().write(node.id, 172.5, null, { source: 'manual', actor: 'tester' })
144
+ const after = await toolDcwRead('agent-a', { node_id: node.id })
145
+ check('写入后读取:ACT 与 SET 一致', after.isError !== true && /172\.5/.test(after.text) && /一致/.test(after.text), after.text.replace(/\n/g, ' | ').slice(0, 140))
146
+ const other = await toolDcwRead('agent-b', { node_id: node.id })
147
+ check('其他 agent 越权读取被拒', other.isError === true)
148
+ }
149
+
150
+ // 收尾
151
+ rmSync(awHome, { recursive: true, force: true })
152
+ console.log(failures === 0 ? '\nDCW-READ ALL PASS' : `\nDCW-READ FAILED(${failures})`)
153
+ process.exit(failures === 0 ? 0 : 1)