agentworkshop 0.7.40 → 0.7.41

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 (116) hide show
  1. package/.output/nitro.json +1 -1
  2. package/.output/public/_nuxt/{CzOCSGLe.js → BN16aRdQ.js} +1 -1
  3. package/.output/public/_nuxt/BWVhZntO.js +1 -0
  4. package/.output/public/_nuxt/{Dihnimlk.js → C0ZrS1KL.js} +1 -1
  5. package/.output/public/_nuxt/{Dr9v6C0f.js → C4s_niy-.js} +2 -2
  6. package/.output/public/_nuxt/D6mbFA-a.js +1 -0
  7. package/.output/public/_nuxt/{DcsCcxp4.js → D98NH8kG.js} +1 -1
  8. package/.output/public/_nuxt/{C_iYD-EB.js → DGbd-UiG.js} +1 -1
  9. package/.output/public/_nuxt/{DCn7QZ5N.js → Dap5PmTL.js} +1 -1
  10. package/.output/public/_nuxt/DvLxILQP.js +1 -0
  11. package/.output/public/_nuxt/{DZshyNLa.js → IREZ1eC2.js} +1 -1
  12. package/.output/public/_nuxt/_id_.D9ymtHB5.css +1 -0
  13. package/.output/public/_nuxt/_id_.jf-_8E_c.css +1 -0
  14. package/.output/public/_nuxt/builds/latest.json +1 -1
  15. package/.output/public/_nuxt/builds/meta/78a295bb-2072-48a4-8b46-6d545fdae743.json +1 -0
  16. package/.output/public/_nuxt/ca4TJ3Op.js +1 -0
  17. package/.output/public/_nuxt/daq.DjSgY94l.css +1 -0
  18. package/.output/public/_nuxt/{BP-NyKVG.js → eqq3wWQF.js} +1 -1
  19. package/.output/server/chunks/_/nitro.mjs +886 -673
  20. package/.output/server/chunks/_/nitro.mjs.map +1 -1
  21. package/.output/server/chunks/build/{TownView-BuLAsmpR.mjs → TownView--otsnr_7.mjs} +4 -4
  22. package/.output/server/chunks/build/{TownView-BuLAsmpR.mjs.map → TownView--otsnr_7.mjs.map} +1 -1
  23. package/.output/server/chunks/build/{_id_-BU1wfOID.mjs → _id_-Brv-JSvr.mjs} +141 -140
  24. package/.output/server/chunks/build/_id_-Brv-JSvr.mjs.map +1 -0
  25. package/.output/server/chunks/build/{_id_-BYsEua57.mjs → _id_-D9pl1OZy.mjs} +37 -36
  26. package/.output/server/chunks/build/_id_-D9pl1OZy.mjs.map +1 -0
  27. package/.output/server/chunks/build/{_wsId_-BxlDcJHg.mjs → _wsId_-BASfDf0s.mjs} +4 -4
  28. package/.output/server/chunks/build/{_wsId_-BxlDcJHg.mjs.map → _wsId_-BASfDf0s.mjs.map} +1 -1
  29. package/.output/server/chunks/build/{daq-cgyjKmJu.mjs → daq-DkMvD8sg.mjs} +93 -92
  30. package/.output/server/chunks/build/daq-DkMvD8sg.mjs.map +1 -0
  31. package/.output/server/chunks/build/{dcw-CoTxBlAZ.mjs → dcw-C1jgVJkO.mjs} +3 -3
  32. package/.output/server/chunks/build/{dcw-CoTxBlAZ.mjs.map → dcw-C1jgVJkO.mjs.map} +1 -1
  33. package/.output/server/chunks/build/{logs-BSOeZwwI.mjs → logs--dByNyhw.mjs} +2 -2
  34. package/.output/server/chunks/build/{logs-BSOeZwwI.mjs.map → logs--dByNyhw.mjs.map} +1 -1
  35. package/.output/server/chunks/build/{pages-DFxjn9Xa.mjs → pages-B0bUWnC6.mjs} +3 -3
  36. package/.output/server/chunks/build/{pages-DFxjn9Xa.mjs.map → pages-B0bUWnC6.mjs.map} +1 -1
  37. package/.output/server/chunks/build/{town-qMPql9Se.mjs → town-CoQdblbZ.mjs} +4 -4
  38. package/.output/server/chunks/build/{town-qMPql9Se.mjs.map → town-CoQdblbZ.mjs.map} +1 -1
  39. package/.output/server/chunks/build/{useDaqStream-CIBaiGAl.mjs → useDaqStream-DfCFpb3p.mjs} +9 -3
  40. package/.output/server/chunks/build/useDaqStream-DfCFpb3p.mjs.map +1 -0
  41. package/.output/server/chunks/build/{useDcwStream-CBbiYnWe.mjs → useDcwStream-UPiX-3iO.mjs} +11 -2
  42. package/.output/server/chunks/build/useDcwStream-UPiX-3iO.mjs.map +1 -0
  43. package/.output/server/chunks/routes/api/workshop/channel-templates/_id/instantiate.post.mjs +1 -1
  44. package/.output/server/chunks/routes/api/workshop/channels.post.mjs +3 -3
  45. package/.output/server/chunks/routes/api/workshop/dcw/index5.post.mjs +1 -1
  46. package/.output/server/chunks/routes/api/workshop/dcw/lines/_id/start.post.mjs +1 -1
  47. package/.output/server/chunks/routes/api/workshop/dcw/lines/_id/stop.post.mjs +1 -1
  48. package/.output/server/chunks/routes/api/workshop/dcw/params/_id_.delete.mjs +1 -1
  49. package/.output/server/chunks/routes/api/workshop/dcw/params/_id_.patch.mjs +1 -1
  50. package/.output/server/chunks/routes/api/workshop/dcw/templates/_key_.delete.mjs +1 -1
  51. package/.output/server/chunks/routes/api/workshop/dcw/templates/_key_.patch.mjs +1 -1
  52. package/.output/server/chunks/routes/api/workshop/harnesses/_id/providers.get.mjs +1 -1
  53. package/.output/server/chunks/routes/api/workshop/harnesses.get.mjs +1 -1
  54. package/.output/server/chunks/routes/api/workshop/hitl/pending.get.mjs +1 -1
  55. package/.output/server/chunks/routes/api/workshop/hitl/respond.post.mjs +1 -1
  56. package/.output/server/chunks/routes/api/workshop/index.put.mjs +1 -1
  57. package/.output/server/chunks/routes/api/workshop/index5.get.mjs +3 -1
  58. package/.output/server/chunks/routes/api/workshop/index5.get.mjs.map +1 -1
  59. package/.output/server/chunks/routes/api/workshop/index6.get.mjs +3 -1
  60. package/.output/server/chunks/routes/api/workshop/index6.get.mjs.map +1 -1
  61. package/.output/server/chunks/routes/api/workshop/plugins/_name/disable.post.mjs +1 -1
  62. package/.output/server/chunks/routes/api/workshop/plugins/_name/enable.post.mjs +1 -1
  63. package/.output/server/chunks/routes/api/workshop/scene/layouts/_channelId_.delete.mjs +1 -1
  64. package/.output/server/chunks/routes/api/workshop/scene/layouts/_channelId_.put.mjs +1 -1
  65. package/.output/server/chunks/routes/api/workshop/workspaces/_id/channel-templates/_tplId_.post.mjs +1 -1
  66. package/.output/server/chunks/routes/renderer.mjs +2 -2
  67. package/.output/server/chunks/virtual/entry.mjs +9 -9
  68. package/.output/server/chunks/virtual/precomputed.mjs +1 -1
  69. package/.output/server/package.json +1 -1
  70. package/README-zh.md +5 -5
  71. package/README.md +6 -6
  72. package/app/composables/workshop/useDaqStream.ts +7 -4
  73. package/app/composables/workshop/useDcwStream.ts +8 -2
  74. package/app/pages/daq/[id].vue +9 -5
  75. package/app/pages/daq/index.vue +13 -10
  76. package/app/pages/dcw/[id].vue +8 -4
  77. package/docs/cli.en.md +2 -2
  78. package/docs/cli.md +2 -2
  79. package/docs/plugins.en.md +33 -1
  80. package/docs/plugins.md +29 -1
  81. package/docs/sdk.en.md +1 -1
  82. package/docs/sdk.md +1 -1
  83. package/package.json +3 -2
  84. package/scripts/_loop-biax-campaign.mjs +124 -0
  85. package/scripts/test-serial-bridge.mjs +418 -0
  86. package/scripts/ui/e2e-interactive.mjs +328 -0
  87. package/scripts/ui/seed-industrial-demo.mjs +171 -0
  88. package/server/api/workshop/daq/index.get.ts +3 -1
  89. package/server/api/workshop/dcw/index.get.ts +3 -0
  90. package/server/plugins/aw-plugins.ts +9 -7
  91. package/server/plugins-builtin/serial-bridge/client.mjs +210 -0
  92. package/server/plugins-builtin/serial-bridge/drivers.mjs +409 -0
  93. package/server/plugins-builtin/serial-bridge/i18n.json +62 -0
  94. package/server/plugins-builtin/serial-bridge/index.mjs +247 -0
  95. package/server/plugins-builtin/serial-bridge/lib.mjs +208 -0
  96. package/server/plugins-builtin/serial-bridge/rtu.mjs +168 -0
  97. package/server/services/workshop/daq/daq-controller.ts +7 -2
  98. package/server/services/workshop/daq/drivers.ts +78 -3
  99. package/server/services/workshop/dcw/dcw-controller.ts +5 -1
  100. package/server/services/workshop/dcw/drivers.ts +85 -2
  101. package/server/services/workshop/dcw/plugin-bridge.ts +45 -0
  102. package/server/services/workshop/plugins/host.mjs +30 -0
  103. package/shared/daq-protocol.ts +15 -0
  104. package/.output/public/_nuxt/BOt2SP4z.js +0 -1
  105. package/.output/public/_nuxt/ClILjvWH.js +0 -1
  106. package/.output/public/_nuxt/D8qvkfyY.js +0 -1
  107. package/.output/public/_nuxt/Du9ZJtnj.js +0 -1
  108. package/.output/public/_nuxt/_id_.CM4bIgKY.css +0 -1
  109. package/.output/public/_nuxt/_id_.bNV69Dtf.css +0 -1
  110. package/.output/public/_nuxt/builds/meta/f4707884-7cf4-4a31-80d2-e3d72b73745f.json +0 -1
  111. package/.output/public/_nuxt/daq.B0D3dPAc.css +0 -1
  112. package/.output/server/chunks/build/_id_-BU1wfOID.mjs.map +0 -1
  113. package/.output/server/chunks/build/_id_-BYsEua57.mjs.map +0 -1
  114. package/.output/server/chunks/build/daq-cgyjKmJu.mjs.map +0 -1
  115. package/.output/server/chunks/build/useDaqStream-CIBaiGAl.mjs.map +0 -1
  116. package/.output/server/chunks/build/useDcwStream-CBbiYnWe.mjs.map +0 -1
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentworkshop-prod",
3
- "version": "0.7.40",
3
+ "version": "0.7.41",
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.39</b> · 14 个执行引擎 · 5 种现场协议 · 99 个运行时设置项 · 双语文档(简体中文 / English)</sub>
31
+ <sub><b>v0.7.41</b> · 14 个执行引擎 · 6 种现场协议(5 内置 + 串口插件) · 99 个运行时设置项 · 双语文档(简体中文 / English)</sub>
32
32
 
33
33
  <br />
34
34
 
@@ -146,7 +146,7 @@ AgentWorkShop 起家于**多智能体软件工作坊**——Channel 内的编码
146
146
 
147
147
  | 能力 | 为何重要 |
148
148
  |---|---|
149
- | **五协议现场总线** | Modbus TCP、Modbus RTU-over-TCP(串口网关)、OPC UA、MQTT、HTTP/REST——数采**与**写控双驱动带连接池、分类错误文案与逐驱动连接测试;`mock` 覆盖演示/CI;插件可注册新协议。 |
149
+ | **六协议现场总线** | Modbus TCP、Modbus RTU-over-TCP(串口网关)、OPC UA、MQTT、HTTP/REST,外加**内置串口插件**(RS-232/485 直连:Modbus RTU + ASCII 行)——数采**与**写控双驱动带连接池、分类错误文案与逐驱动连接测试;`mock` 覆盖演示/CI;协议即插件:`ctx.daq.registerDriver` / `ctx.dcw.registerWriteDriver` 注入后,前端协议下拉与参数表单即插即现(带 ⌁ 徽标)。 |
150
150
  | **Agent 团队 × 工业作用域** | 把 Agent 绑定到数采/数控节点。Agent 看到的是语义卡(物理含义、单位、安全量程、配方窗口)——而不是裸寄存器。 |
151
151
  | **人工审批的写控** | 数控下发经过「**安全量程 ∩ 活动配方窗口**」联锁 → 可选 **HITL 审批** → PLC 写入 → **回读校验** → 带签名的写历史。 |
152
152
  | **数控读写通道** | 每个控制节点都能沿它写入时所用的同一套标定**读回 PLC 当前值**:周期读 + 按需读 + Agent 读取,SET 与 ACT 并排呈现——读是被动观测,永不被写联锁阻断。 |
@@ -162,7 +162,7 @@ AgentWorkShop 起家于**多智能体软件工作坊**——Channel 内的编码
162
162
  | **产线级权限** | 工业数据按**产线**三态门控(无权/仅查看/可操控),强制点在数据面——普通用户未授权前看不到任何产线数据。 |
163
163
  | **全操作审计日志** | 用户 / Agent / 系统 的每个动作都落进同一份可检索日志;操作者归属「Channel名/成员名」,与用户和系统天然区分。经 WS 实时推送。 |
164
164
  | **团队级插件开关** | 每个团队(Channel)持有**独立插件开关组**(`channel_plugins`):被关闭插件的工具不注入该团队 Agent。插件本体经 `aw plugin` 与 `/plugins` 页热管理。 |
165
- | **插件扩展 API** | `plugins/<name>/` 下的一个自包含目录**同时增强两半**:`index.mjs`(服务端:钩子、路由、Agent 工具、数采驱动/处理器/模板、配置分组、KV、定时器)与 `client.mjs`(浏览器:注入具名插槽的面板、i18n、设置 UI)。三种作用域——`builtin`(随包发布)> `project`(检出)> `user`(`~/.AgentWorkShop`)——启停**与代码修改**均有约 1 秒热重载。完整契约见 [`docs/plugins.md`](./docs/plugins.md)。 |
165
+ | **插件扩展 API** | `plugins/<name>/` 下的一个自包含目录**同时增强两半**:`index.mjs`(服务端:钩子、路由、Agent 工具、**数采读驱动/写控写驱动**/帧处理器/节点模板、配置分组、KV、定时器)与 `client.mjs`(浏览器:注入具名插槽的面板、i18n、设置 UI)。三种作用域——`builtin`(随包发布)> `project`(检出)> `user`(`~/.AgentWorkShop`)——启停**与代码修改**均有约 1 秒热重载;停用插件的驱动随热重载立即摘除。内置示例 **serial-bridge**(串口通信:读/写驱动 + 串口探针 API + 前端面板)。完整契约见 [`docs/plugins.md`](./docs/plugins.md)。 |
166
166
  | **AML —— 自动建模实验室** | 数据集构建 → 训练作业 → 排行榜 → 晋级门禁 → 模型引用,全部可在 `/aml` 页驱动,也可由 Agent 通过 10 个 `aml_*` 工具驱动。Python 运行时由 `uv` 引导至 `./aml` 资产根;产物与元数据都留在配置根下。 |
167
167
  | **全量配置驱动运行时** | 全部运行旋钮(记忆预算、上下文压缩、回退护栏、保留策略、备份、日志级别…)在设置描述符注册表声明一次,优先级 **config.yml < runtime-settings < env**——**99 个设置项、16 组**,代码零硬编码默认。 |
168
168
  | **可配置节拍** | 采样与查询的默认值/下限全部是 **live 设置**(`daq.sampling.*`、`daq.query.*`):热重载、create/patch 时钳制,Agent 工具描述实时携带当前值。 |
@@ -365,7 +365,7 @@ aw update --check # 只报告,不安装
365
365
  npm install -g agentworkshop@latest # 手动等效
366
366
  ```
367
367
 
368
- 版本遵循 semver。每次 `aw start` 都会校验配置根,并把 `home` 之前的旧版 `data/` 布局迁移进来(最新文件胜出),因此数据可以跨版本存活。SQLite schema 迁移在服务端启动时执行。当前版本:**v0.7.39**——见[版本发布](https://github.com/kingdol666/AgentWorkShop/releases)。
368
+ 版本遵循 semver。每次 `aw start` 都会校验配置根,并把 `home` 之前的旧版 `data/` 布局迁移进来(最新文件胜出),因此数据可以跨版本存活。SQLite schema 迁移在服务端启动时执行。当前版本:**v0.7.41**——见[版本发布](https://github.com/kingdol666/AgentWorkShop/releases)。
369
369
 
370
370
  ### 第一次「Agent × 产线」会话(约 2 分钟)
371
371
 
@@ -453,7 +453,7 @@ export async function run(argv, ctx) {
453
453
 
454
454
  ### 数据采集(DAQ)
455
455
 
456
- - **五协议驱动**:Modbus TCP、Modbus RTU-over-TCP(串口网关)、OPC UA、MQTT、HTTP/REST——连接池、分类错误文案、逐驱动连接测试;驱动注册表接受插件注册新协议。
456
+ - **六协议驱动**:Modbus TCP、Modbus RTU-over-TCP(串口网关)、OPC UA、MQTT、HTTP/REST,加 **serial-bridge 内置插件**(RS-232/485 直连:Modbus RTU 帧机 + ASCII 行协议,写侧带同址回读校验,附串口枚举/探针 API 与前端面板)——连接池、分类错误文案、逐驱动连接测试;驱动注册表接受插件注册任意新协议(自带 meta 自描述时前端表单零改动)。
457
457
  - **逐节点边缘运行时**:独立采样节拍、下发节拍、节点级在飞互斥——一个慢驱动绝不拖累邻居。采样与查询的默认值/下限由 `daq.sampling.*`、`daq.query.*` live 设置驱动。
458
458
  - **管线**:驱动 → 队列(进程内 / MQTT,断连离线缓冲)→ 消费泵乱序防御 → 三路分发:WS 实时直推(节拍门控)、TSDB 批量落库、设备孪生回写。
459
459
  - **鲁棒性**:TSDB 单 in-flight 写 + 有界重试,缓冲背压带丢弃计数,真实丢失指标随 `daq.controller` 帧暴露。
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.39</b> · 14 agent engines · 5 field protocols · 99 runtime settings · bilingual docs (简体中文 / English)</sub>
31
+ <sub><b>v0.7.41</b> · 14 agent engines · 6 field protocols · 99 runtime settings · bilingual docs (简体中文 / English)</sub>
32
32
 
33
33
  <br />
34
34
 
@@ -75,7 +75,7 @@
75
75
 
76
76
  Supervisory layer, second-level soft real-time<br/>
77
77
  14 harness engines · 4 entry points<br/>
78
- 5 field protocols (read + write)<br/>
78
+ 6 field protocols (5 built-in + serial plugin, read + write)<br/>
79
79
  7-state task machine · FTS5 + vector memory<br/>
80
80
  Hot-reloadable plugins · SDK · CLI · TUI
81
81
 
@@ -146,7 +146,7 @@ Recorded against a running instance: real DAQ history, real write control, real
146
146
 
147
147
  | Capability | Why it matters |
148
148
  |---|---|
149
- | **Five field protocols** | Modbus TCP, Modbus RTU-over-TCP (serial gateway), OPC UA, MQTT and HTTP/REST — acquisition **and** write-control drivers with connection pools, classified error messages and per-driver connection tests. `mock` covers demos/CI; plugins can register new protocols. |
149
+ | **Six field protocols** | Modbus TCP, Modbus RTU-over-TCP (serial gateway), OPC UA, MQTT and HTTP/REST, plus a **built-in serial plugin** (direct RS-232/485: Modbus RTU + ASCII line) — acquisition **and** write-control drivers with connection pools, classified error messages and per-driver connection tests. `mock` covers demos/CI. Protocols are plugins: `ctx.daq.registerDriver` / `ctx.dcw.registerWriteDriver` inject straight into the frontend protocol dropdown and dynamic forms (⌁ badge), zero frontend changes. |
150
150
  | **Agent teams, industrial scope** | Agents bind to DAQ/DCW nodes and see semantic cards — physical meaning, units, safe range, recipe window — never raw registers. |
151
151
  | **Human-approved write control** | DCW writes flow through **safe-range ∩ recipe-window** interlock → optional **HITL approval** → PLC write → **readback verification** → signed write history. |
152
152
  | **Read-write DCW channels** | Every control node also **reads its PLC value back** through the same calibration path it writes with: periodic + on-demand + agent reads surface **SET vs ACT** side by side — passive observation, never blocked by write interlocks. |
@@ -162,7 +162,7 @@ Recorded against a running instance: real DAQ history, real write control, real
162
162
  | **Line-level permissions** | Industrial data is gated **per production line** with three states (none / read-only / operate), enforced in the data plane — regular users see no line data until granted. |
163
163
  | **Full-operation audit log** | Every user / agent / system action lands in one queryable log; operators are attributed to "Channel/Member", distinct from users and the system. Streamed live over WS. |
164
164
  | **Team-scoped plugin switches** | Each team (channel) keeps an **independent plugin switch set** (`channel_plugins`): a disabled plugin's tools are not injected into that team's agents. Plugins themselves are hot-managed via `aw plugin` and the `/plugins` page. |
165
- | **Plugin extension API** | A self-contained directory under `plugins/<name>/` enhances **both halves at once**: `index.mjs` (server: hooks, routes, agent tools, DAQ drivers/processors/templates, config groups, KV, timers) and `client.mjs` (browser: panels injected into named slots, i18n, settings UI). Three scopes — `builtin` (shipped) > `project` (checkout) > `user` (`~/.AgentWorkShop`) — with ~1 s hot reload on enable/disable **and on code edits**. Full contract in [`docs/plugins.md`](./docs/plugins.md). |
165
+ | **Plugin extension API** | A self-contained directory under `plugins/<name>/` enhances **both halves at once**: `index.mjs` (server: hooks, routes, agent tools, **DAQ read drivers / DCW write drivers**, frame processors, node templates, config groups, KV, timers) and `client.mjs` (browser: panels injected into named slots, i18n, settings UI). Three scopes — `builtin` (shipped) > `project` (checkout) > `user` (`~/.AgentWorkShop`) — with ~1 s hot reload on enable/disable **and on code edits**; a disabled plugin's drivers are removed on hot reload immediately. Built-in example: **serial-bridge** (serial communication: read/write drivers, serial probe API, frontend panel). Full contract in [`docs/plugins.md`](./docs/plugins.md). |
166
166
  | **AML — auto-modeling lab** | Dataset build → training job → leaderboard → promotion gates → model reference, all driven from `/aml` or by agents through 10 `aml_*` tools. Python runtime bootstrapped with `uv` into an `./aml` asset root; artifacts and metadata stay under the config root. |
167
167
  | **Fully config-driven runtime** | Every runtime knob (memory budgets, compaction, rollback guardrails, retention, backups, log level…) is declared once in the settings descriptor registry with precedence **config.yml < runtime-settings < env** — **99 settings across 16 groups**, no hardcoded defaults in code. |
168
168
  | **Configurable cadences** | Sampling and query defaults/floors are **live settings** (`daq.sampling.*`, `daq.query.*`): hot-reloaded, clamped on node create/patch, and agent tool descriptions always carry the current values. |
@@ -355,7 +355,7 @@ aw update --check # only report; nothing is installed
355
355
  npm install -g agentworkshop@latest # manual equivalent
356
356
  ```
357
357
 
358
- 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).
358
+ 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.41** — see [Releases](https://github.com/kingdol666/AgentWorkShop/releases).
359
359
 
360
360
  ### Your first agent × line session (~2 minutes)
361
361
 
@@ -443,7 +443,7 @@ export async function run(argv, ctx) {
443
443
 
444
444
  ### Data acquisition (DAQ)
445
445
 
446
- - **Five protocol drivers**: Modbus TCP, Modbus RTU-over-TCP (serial gateway), OPC UA, MQTT, HTTP/REST — connection pools, classified error messages, per-driver connection tests; the driver registry accepts plugin-registered protocols.
446
+ - **Six protocol drivers**: Modbus TCP, Modbus RTU-over-TCP (serial gateway), OPC UA, MQTT, HTTP/REST, plus the **serial-bridge built-in plugin** (direct RS-232/485: Modbus RTU framing + ASCII line protocol, write side with same-address readback verification, serial enumeration/probe API and a frontend panel) — connection pools, classified error messages, per-driver connection tests; the driver registry accepts plugin-registered protocols of any kind (with self-describing metadata, the frontend forms render with zero changes).
447
447
  - **Per-node edge runtimes**: independent sampling cadence, publish cadence, in-flight mutex per node — one slow driver never blocks its neighbors. Sampling and query defaults/floors are driven by the live `daq.sampling.*` / `daq.query.*` settings.
448
448
  - **Pipeline**: driver → queue (in-process or MQTT, offline buffer on disconnect) → consumer with out-of-order defense → three-way fan-out: WS live push (gated), TSDB batch write, device-twin writeback.
449
449
  - **Robustness**: TSDB single-in-flight writes with bounded retries, buffer backpressure with drop counters, real loss metrics exposed on `daq.controller` frames.
@@ -10,7 +10,7 @@ import { reactive } from 'vue'
10
10
  import { useTownBus } from './useTownBus'
11
11
  import { apiFetch } from './apiClient'
12
12
  import type { AepEnvelope } from '#shared/workshop-protocol'
13
- import { DAQ_TEMPLATES, type AepDaqControllerState, type AepDaqFrame, type AepDaqReading, type AepDaqNodeChange, type AepDaqTemplateChange, type DaqNodeView, type DaqTemplateDef, type DaqTemplateInput } from '#shared/daq-protocol'
13
+ import { DAQ_TEMPLATES, DAQ_DRIVERS, type AepDaqControllerState, type AepDaqFrame, type AepDaqReading, type AepDaqNodeChange, type AepDaqTemplateChange, type DaqDriverCatalogEntry, type DaqNodeView, type DaqTemplateDef, type DaqTemplateInput } from '#shared/daq-protocol'
14
14
 
15
15
  /** 历史缓冲长度(1s 默认周期 ≈ 最近 5 分钟趋势) */
16
16
  const HIST_CAP = 60
@@ -57,7 +57,8 @@ interface DaqControllerState {
57
57
  interface DaqBackendMeta {
58
58
  tsdb: string
59
59
  queue: string
60
- drivers: Array<{ kind: string, label: string, status: 'builtin' | 'planned' | 'real' }>
60
+ /** 驱动目录(REST `drivers` 权威收敛:内置 + 协议插件自描述;首帧先用内置目录) */
61
+ drivers: DaqDriverCatalogEntry[]
61
62
  produced: number
62
63
  consumed: number
63
64
  dropped: number
@@ -110,7 +111,7 @@ const createStore = () => {
110
111
  const alarms = reactive<DaqAlarmRow[]>([]) // S5:未确认报警(轮询 + ack 后刷新)
111
112
  const controller = reactive<DaqControllerState>({ running: true, defaultIntervalMs: 1000, defaultPublishIntervalMs: 0, queryDisplayIntervalMs: 5000, nodesTotal: 0, nodesOnline: 0 })
112
113
  const meta = reactive<DaqBackendMeta>({
113
- tsdb: '…', queue: '…', drivers: [], driverAvailable: {},
114
+ tsdb: '…', queue: '…', drivers: DAQ_DRIVERS.map(d => ({ kind: d.kind, label: d.label, status: d.status, configFields: d.configFields })), driverAvailable: {},
114
115
  infra: undefined,
115
116
  produced: 0, consumed: 0, dropped: 0, samplesStored: 0,
116
117
  })
@@ -266,7 +267,7 @@ const createStore = () => {
266
267
 
267
268
  async function load(): Promise<void> {
268
269
  try {
269
- const data = await api<{ controller: DaqControllerState, nodes: DaqNodeView[], meta: DaqBackendMeta, driverAvailable?: Record<string, boolean>, infra?: DaqInfraState, templates?: DaqTemplateDef[] }>('')
270
+ const data = await api<{ controller: DaqControllerState, nodes: DaqNodeView[], meta: DaqBackendMeta, driverAvailable?: Record<string, boolean>, drivers?: DaqDriverCatalogEntry[], infra?: DaqInfraState, templates?: DaqTemplateDef[] }>('')
270
271
  // 快照合并:hist 是客户端读数流资产,轮询重载不得清零;
271
272
  // 已有节点就地 assign(保留对象身份 → Vue 只补丁变化字段,不触发整表数组级失效),hist 自然保留
272
273
  const prev = new Map(nodes.map(n => [n.id, n]))
@@ -291,6 +292,8 @@ const createStore = () => {
291
292
  Object.assign(controller, data.controller)
292
293
  Object.assign(meta, data.meta ?? {})
293
294
  meta.driverAvailable = data.driverAvailable ?? {}
295
+ // 驱动目录 server 权威(含协议插件);server 未下发(旧版)时保留内置目录
296
+ if (data.drivers?.length) meta.drivers = data.drivers
294
297
  meta.infra = data.infra
295
298
  if (data.templates?.length) applyTemplates(data.templates)
296
299
  store.loaded = true
@@ -8,7 +8,8 @@ import { reactive } from 'vue'
8
8
  import { useTownBus } from './useTownBus'
9
9
  import { apiFetch } from './apiClient'
10
10
  import type { AepEnvelope } from '#shared/workshop-protocol'
11
- import { DCW_TEMPLATES, type AepDcwControllerState, type AepDcwNodeChange, type AepDcwRead, type AepDcwWritten, type DcwNodeView, type DcwParamLedger, type DcwTemplateDef, type DcwTemplateInput, type LineInput, type LineQueryOpts, type LineQueryResult, type LineRunState, type LineView, type OptimizationRecord, type OptimizationVerdict, type ProductInput, type ProductView, type RecipeInput, type RecipeParam, type RecipeRunView, type RecipeView, type RecipeRunData } from '#shared/dcw-protocol'
11
+ import { DCW_TEMPLATES, DCW_DRIVERS, type AepDcwControllerState, type AepDcwNodeChange, type AepDcwRead, type AepDcwWritten, type DcwNodeView, type DcwParamLedger, type DcwTemplateDef, type DcwTemplateInput, type LineInput, type LineQueryOpts, type LineQueryResult, type LineRunState, type LineView, type OptimizationRecord, type OptimizationVerdict, type ProductInput, type ProductView, type RecipeInput, type RecipeParam, type RecipeRunView, type RecipeView, type RecipeRunData } from '#shared/dcw-protocol'
12
+ import type { DaqDriverCatalogEntry } from '#shared/daq-protocol'
12
13
 
13
14
  export type { DcwNodeView }
14
15
 
@@ -35,6 +36,8 @@ function createStore() {
35
36
  const lineStates = reactive<Record<string, LineRunState>>({})
36
37
  /** Agent 优化记录(调控闭环;dcw.optimization.changed 实时收敛) */
37
38
  const optimizations = reactive<OptimizationRecord[]>([])
39
+ /** 写驱动目录(REST `drivers` 权威:内置 + 协议插件自描述;首帧先用内置目录) */
40
+ const driverCatalog = reactive<DaqDriverCatalogEntry[]>(DCW_DRIVERS.map(d => ({ kind: d.kind, label: d.label, status: d.status, configFields: d.configFields })))
38
41
 
39
42
  function upsert(node: DcwNodeView): void {
40
43
  const i = nodes.findIndex(x => x.id === node.id)
@@ -112,12 +115,14 @@ function createStore() {
112
115
 
113
116
  async function load(): Promise<void> {
114
117
  try {
115
- const data = await api<{ controller: AepDcwControllerState, nodes: DcwNodeView[], templates?: DcwTemplateDef[], recipes?: RecipeView[], runs?: RecipeRunView[], history?: DcwWriteHistoryEntry[], products?: ProductView[], lines?: LineView[], lineStates?: LineRunState[] }>('')
118
+ const data = await api<{ controller: AepDcwControllerState, nodes: DcwNodeView[], templates?: DcwTemplateDef[], drivers?: DaqDriverCatalogEntry[], recipes?: RecipeView[], runs?: RecipeRunView[], history?: DcwWriteHistoryEntry[], products?: ProductView[], lines?: LineView[], lineStates?: LineRunState[] }>('')
116
119
  nodes.splice(0, nodes.length, ...data.nodes)
117
120
  nodeIndex.clear()
118
121
  for (const n of nodes) nodeIndex.set(n.id, n)
119
122
  Object.assign(controller, data.controller)
120
123
  if (data.templates?.length) templates.splice(0, templates.length, ...data.templates.map(t => ({ ...t })))
124
+ // 写驱动目录 server 权威(含协议插件);server 未下发(旧版)时保留内置目录
125
+ if (data.drivers?.length) driverCatalog.splice(0, driverCatalog.length, ...data.drivers)
121
126
  recipes.splice(0, recipes.length, ...(data.recipes ?? []))
122
127
  runs.splice(0, runs.length, ...(data.runs ?? []))
123
128
  history.splice(0, history.length, ...(data.history ?? []))
@@ -144,6 +149,7 @@ function createStore() {
144
149
  lines,
145
150
  lineStates,
146
151
  optimizations,
152
+ driverCatalog,
147
153
  loaded: false,
148
154
  error: '',
149
155
  ensureWsFeed,
@@ -9,7 +9,7 @@ import { useRoute } from 'vue-router'
9
9
  import { useDcwStream } from '@/app/composables/workshop/useDcwStream'
10
10
  import { useDaqStream, type DaqFrameLive, type DaqTsdbPoint } from '@/app/composables/workshop/useDaqStream'
11
11
  import { useDeviceTwins } from '@/app/composables/workshop/useDeviceTwins'
12
- import { daqKeyFromRef, DAQ_DRIVERS, type DaqDriverKind, type DaqNodeState, type DriverConfigField, type DriverTestResult as DaqDriverTestResult } from '#shared/daq-protocol'
12
+ import { daqKeyFromRef, DAQ_DRIVERS, type DaqDriverCatalogEntry, type DaqDriverKind, type DaqNodeState, type DriverConfigField, type DriverTestResult as DaqDriverTestResult } from '#shared/daq-protocol'
13
13
 
14
14
  const { t } = useI18n()
15
15
 
@@ -129,9 +129,13 @@ watch(node, (n) => {
129
129
  }, { immediate: true })
130
130
 
131
131
  // 驱动连接参数编辑(真实协议:host/register/endpoint...;schema 驱动渲染)
132
+ // 驱动目录:server 权威(内置 + 协议插件自描述;REST 未返回时回落静态目录)
133
+ const driverCatalog = computed<DaqDriverCatalogEntry[]>(() => daq.meta.drivers.length
134
+ ? daq.meta.drivers
135
+ : DAQ_DRIVERS.map(d => ({ ...d })))
132
136
  const driverCfg = ref<Record<string, string | number>>({})
133
137
  const driverFields = computed<DriverConfigField[]>(() =>
134
- DAQ_DRIVERS.find(d => d.kind === form.driver)?.configFields ?? [])
138
+ driverCatalog.value.find(d => d.kind === form.driver)?.configFields ?? [])
135
139
  watch(node, (n) => {
136
140
  if (n) driverCfg.value = { ...(n.driverConfig as Record<string, string | number>) }
137
141
  }, { immediate: true })
@@ -508,12 +512,12 @@ watch(effectiveRefreshMs, () => armHistTimer())
508
512
  class="input"
509
513
  >
510
514
  <option
511
- v-for="d in DAQ_DRIVERS"
515
+ v-for="d in driverCatalog"
512
516
  :key="d.kind"
513
517
  :value="d.kind"
514
518
  :disabled="d.status === 'planned'"
515
519
  >
516
- {{ d.label }}{{ d.status === 'planned' ? $t('daqDetail.kz8zr9v035') : '' }}
520
+ {{ d.label }}{{ d.plugin ? ' ⌁' : '' }}{{ d.status === 'planned' ? $t('daqDetail.kz8zr9v035') : '' }}
517
521
  </option>
518
522
  </select>
519
523
  </label>
@@ -655,7 +659,7 @@ watch(effectiveRefreshMs, () => armHistTimer())
655
659
  <!-- 驱动连接参数(mock 空;真实协议 schema 动态表单 + 测试连接) -->
656
660
  <template v-if="driverFields.length">
657
661
  <h3 class="sec mt">
658
- {{ $t('daqDetail.k1p9gioa033') }} {{ DAQ_DRIVERS.find(d => d.kind === form.driver)?.label }}
662
+ {{ $t('daqDetail.k1p9gioa033') }} {{ driverCatalog.find(d => d.kind === form.driver)?.label }}
659
663
  </h3>
660
664
  <div class="driver-grid">
661
665
  <label
@@ -9,7 +9,7 @@ import { message } from 'ant-design-vue'
9
9
  import { useDaqStream, type DaqNodeLive } from '@/app/composables/workshop/useDaqStream'
10
10
  import { useDcwStream } from '@/app/composables/workshop/useDcwStream'
11
11
  import { useWorkshopWs } from '@/app/composables/workshop/useWorkshopWs'
12
- import { DAQ_TEMPLATES, DAQ_DRIVERS, DAQ_TEMPLATE_ICONS, daqKeyFromRef, type DaqNodeView, type DaqNodeState, type DriverConfigField, type DriverTestResult as DaqDriverTestResult, type DaqTemplateDef, type DaqTemplateIcon } from '#shared/daq-protocol'
12
+ import { DAQ_TEMPLATES, DAQ_DRIVERS, DAQ_TEMPLATE_ICONS, daqKeyFromRef, type DaqDriverCatalogEntry, type DaqNodeView, type DaqNodeState, type DriverConfigField, type DriverTestResult as DaqDriverTestResult, type DaqTemplateDef, type DaqTemplateIcon } from '#shared/daq-protocol'
13
13
  import { useDeviceTwins } from '@/app/composables/workshop/useDeviceTwins'
14
14
  import { useOpsLog } from '@/app/composables/workshop/useOpsLog'
15
15
  import { useVisibleInterval } from '@/app/composables/workshop/useVisibleInterval'
@@ -353,7 +353,10 @@ const addTest = ref<DaqDriverTestResult | null>(null)
353
353
  const addSaving = ref(false)
354
354
  const addError = ref('')
355
355
 
356
- const addDriverMeta = computed(() => DAQ_DRIVERS.find(d => d.kind === addDriver.value))
356
+ // 驱动目录:server 权威(内置 + 协议插件自描述;REST 未返回时回落静态目录)
357
+ const driverCatalog = computed<DaqDriverCatalogEntry[]>(() =>
358
+ daq.meta.drivers.length ? daq.meta.drivers : DAQ_DRIVERS.map(d => ({ ...d })))
359
+ const addDriverMeta = computed(() => driverCatalog.value.find(d => d.kind === addDriver.value))
357
360
  const addFields = computed<DriverConfigField[]>(() => addDriverMeta.value?.configFields ?? [])
358
361
  const driverReady = (kind: string): boolean =>
359
362
  daq.meta.drivers.find(d => d.kind === kind)?.status !== 'planned' && (daq.meta.driverAvailable?.[kind] !== false)
@@ -841,11 +844,11 @@ async function doReconnect(): Promise<void> {
841
844
  class="inp"
842
845
  >
843
846
  <option
844
- v-for="d in DAQ_DRIVERS.filter(x => x.status !== 'planned')"
847
+ v-for="d in driverCatalog.filter(x => x.status !== 'planned')"
845
848
  :key="d.kind"
846
849
  :value="d.kind"
847
850
  >
848
- {{ d.label }}{{ driverReady(d.kind) ? '' : $t('daq.kjbqphp098') }}
851
+ {{ d.label }}{{ d.plugin ? ' ⌁' : '' }}{{ driverReady(d.kind) ? '' : $t('daq.kjbqphp098') }}
849
852
  </option>
850
853
  </select>
851
854
  </label>
@@ -1574,9 +1577,9 @@ async function doReconnect(): Promise<void> {
1574
1577
  class="vs-pad"
1575
1578
  aria-hidden="true"
1576
1579
  >
1577
- <td
1578
- :colspan="11"
1579
- :style="{ height: padTopPx + 'px' }"
1580
+ <td
1581
+ :colspan="11"
1582
+ :style="{ height: padTopPx + 'px' }"
1580
1583
  />
1581
1584
  </tr>
1582
1585
  <WorkshopDaqNodeRow
@@ -1590,9 +1593,9 @@ async function doReconnect(): Promise<void> {
1590
1593
  class="vs-pad"
1591
1594
  aria-hidden="true"
1592
1595
  >
1593
- <td
1594
- :colspan="11"
1595
- :style="{ height: padBottomPx + 'px' }"
1596
+ <td
1597
+ :colspan="11"
1598
+ :style="{ height: padBottomPx + 'px' }"
1596
1599
  />
1597
1600
  </tr>
1598
1601
  <tr v-if="daq.loaded && daq.nodes.length === 0">
@@ -2,7 +2,7 @@
2
2
  import { computed, onUnmounted, reactive, ref } from 'vue'
3
3
  import { useRoute } from 'vue-router'
4
4
  import { DCW_DRIVERS, type DcwParamLedger, type DcwTemplateIcon, type LineQueryResult, type RecipeRunData, type RecipeView } from '#shared/dcw-protocol'
5
- import type { DriverConfigField } from '#shared/daq-protocol'
5
+ import type { DaqDriverCatalogEntry, DriverConfigField } from '#shared/daq-protocol'
6
6
  import { useDcwStream } from '~/composables/workshop/useDcwStream'
7
7
  import { useDaqStream } from '~/composables/workshop/useDaqStream'
8
8
  import { useDeviceTwins } from '~/composables/workshop/useDeviceTwins'
@@ -191,7 +191,11 @@ const addTest = ref<{ ok: boolean, message: string } | null>(null)
191
191
  const addSaving = ref(false)
192
192
  const addError = ref('')
193
193
 
194
- const addDriverMeta = computed(() => DCW_DRIVERS.find(d => d.kind === addDriver.value))
194
+ // 写驱动目录:server 权威(内置 + 协议插件自描述;REST 未返回时回落静态目录)
195
+ const driverCatalog = computed<DaqDriverCatalogEntry[]>(() => dcw.driverCatalog.length
196
+ ? dcw.driverCatalog
197
+ : DCW_DRIVERS.map(d => ({ ...d })))
198
+ const addDriverMeta = computed(() => driverCatalog.value.find(d => d.kind === addDriver.value))
195
199
  const addFields = computed<DriverConfigField[]>(() => addDriverMeta.value?.configFields ?? [])
196
200
 
197
201
  function resetAddCfg(): void {
@@ -1132,11 +1136,11 @@ function fmtPoint(p: { value?: number, avg?: number } | undefined): string {
1132
1136
  class="inp"
1133
1137
  >
1134
1138
  <option
1135
- v-for="d in DCW_DRIVERS.filter(x => x.status !== 'builtin')"
1139
+ v-for="d in driverCatalog.filter(x => x.status !== 'builtin')"
1136
1140
  :key="d.kind"
1137
1141
  :value="d.kind"
1138
1142
  >
1139
- {{ d.label }}
1143
+ {{ d.label }}{{ d.plugin ? ' ⌁' : '' }}
1140
1144
  </option>
1141
1145
  </select>
1142
1146
  </label>
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.38 start)
65
+ npx agentworkshop start # fetch and start (pin a version with: npx agentworkshop@0.7.41 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.38` (`package.json`) |
82
+ | Current version | `0.7.41` (`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.38 start)
63
+ npx agentworkshop start # 拉取并启动(可锁定版本: npx agentworkshop@0.7.41 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.38`(`package.json`) |
79
+ | 当前版本 | `0.7.41`(`package.json`) |
80
80
 
81
81
  ## 二、首次启动(Claude Code 式)
82
82
 
@@ -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.38**. `ctx.sdkVersion` is currently `0.3.0`.
12
+ > describes **v0.7.41**. `ctx.sdkVersion` is currently `0.3.0`.
13
13
 
14
14
  ## 1. Folder layout & quick start
15
15
 
@@ -210,10 +210,41 @@ async setup(ctx) {
210
210
  | Events | `ctx.events.on(type, fn)` / `ctx.events.off(type, fn)` | scene-event sugar: subscribes to `event:<type>` internally (see §7) |
211
211
  | Paths | `ctx.paths.home / configRoot / dataDir`; `ctx.dataDir` | `ctx.dataDir` is this plugin's KV folder, `<configRoot>/data/plugins/<plugin>` |
212
212
  | DAQ | `ctx.daq.registerDriver / registerProcessor / registerTemplate / onFrame / onSample / query / nodes` | driver, sink-processor and node-template registration + time-series queries + node metadata snapshot |
213
+ | Write control | `ctx.dcw.registerWriteDriver(driver)` | write-driver injection (symmetric to DAQ read drivers): contract `{ kind, available(), write(input), test(cfg), read?(input), meta? }`; disabled plugins lose their drivers on hot reload |
213
214
  | Tools | `ctx.omp.registerTool({ name, label?, description, parameters, roles?, handler })` | agent tool injection; `roles` accepts `'lead'` and `'worker'` only (see §10) |
214
215
  | Services | `ctx.services.names()` / `.get(name)` / `.provide(name, getter)` | runtime object surface: `get('daq' \| 'lines' \| 'channels' \| 'plugins')`; `provide` auto-prefixes `<plugin>.` |
215
216
  | Permissions | `ctx.permissions.lineMode / visibleLineIds / listGrants / setGrants` | line-grant queries and management |
216
217
 
218
+ ### Communication-protocol plugins (custom interface connections)
219
+
220
+ `ctx.daq.registerDriver` / `ctx.dcw.registerWriteDriver` are the **protocol plug-in points**: serial
221
+ links, other PLC protocols and private buses all inject here — the main pipelines (sampling loop /
222
+ write gateway / frontend forms) need zero changes.
223
+
224
+ ```js
225
+ ctx.daq.registerDriver({
226
+ kind: 'serial', // custom protocol kind (overriding a builtin logs a warning)
227
+ async available() { return true }, // protocol-stack probe (missing package → UI shows "not installed")
228
+ async sample({ driverConfig }) { return 25.3 }, // reading (scalar or v2 frame envelope)
229
+ async test(driverConfig) { return { ok: true, message: 'connected', sampleValue: 25.3 } },
230
+ meta: { // self-description (frontend "add node" dropdown + dynamic form)
231
+ label: 'Serial (RS-232/485)',
232
+ status: 'real',
233
+ configFields: [{ key: 'path', label: 'Port path', type: 'string', required: true, placeholder: 'COM3' }],
234
+ },
235
+ })
236
+ ```
237
+
238
+ Key points:
239
+ - The driver catalog is served via the `drivers` field of `GET /api/workshop/daq` (read) and
240
+ `GET /api/workshop/dcw` (write); the DAQ/DCW pages render plugin protocols and their forms from it
241
+ (with a ⌁ badge). Drivers registered without `meta` only appear in availability;
242
+ - Hot reload clears all plugin drivers first, then re-loads: **disabled/unloaded protocol drivers
243
+ take effect immediately upon removal** and never linger until restart;
244
+ - Builtin example: `server/plugins-builtin/serial-bridge` (serial Modbus RTU + ASCII line protocol,
245
+ with read/write drivers, `health`/`ports`/`probe` APIs, a frontend panel and the `serial_ports`
246
+ agent tool) — use it as the template for new protocol plugins.
247
+
217
248
  ### Where KV lives
218
249
 
219
250
  `<configRoot>/data/plugins/<plugin>/kv.json` — **not** inside the plugin folder. Writes are authoritative in memory and flushed atomically (`tmp` + `rename`) after a 200 ms debounce; on shutdown and hot reload the pending debounce is cancelled and the state is flushed synchronously. `ctx.kv` has exactly four methods: `get / set / all / bump`.
@@ -524,6 +555,7 @@ export default {
524
555
  - **A newly enabled browser plugin injects within 15 s** (instantly when the WS channel is up).
525
556
  - **`server:close` / `onDispose` depend on graceful shutdown signals**; a forced kill on Windows does not trigger them.
526
557
  - **`aw plugin list` only scans directories** — it reflects neither load failures nor runtime health.
558
+ - **Protocol plugins share the host process with their native stacks**: serial-bridge's RTU framing is a self-contained implementation (single serialport native stack; modbus-serial is deliberately NOT used — its wrapper reproducibly crashed under dev mode). Known boundary: **inside the `aw dev` process, a serial transaction that ends in write → timeout → close can trigger a native V8 crash in `bindings-cpp`** (HandleScope fatal — not catchable in JS, regardless of which port is used); **production mode (`aw start`) is unaffected — the exact same sequence was verified live to return an honest timeout with the server healthy**, and open-only probes are also fine under dev. Connect real hardware during development with this in mind; for hardened deployments, run the serial gateway in its own process (see the plc-node-simulator child-process pattern).
527
559
 
528
560
  ## 13. Pre-release checklist
529
561
 
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.38**。`ctx.sdkVersion` 当前为 `0.3.0`。
9
+ > 运行时服务面);本文档对应当前版本 **v0.7.41**。`ctx.sdkVersion` 当前为 `0.3.0`。
10
10
 
11
11
  ## 一、目录结构与快速开始
12
12
 
@@ -207,10 +207,37 @@ async setup(ctx) {
207
207
  | 事件 | `ctx.events.on(type, fn)` / `ctx.events.off(type, fn)` | scene 实时事件糖:内部订阅 `event:<type>`(见 §7) |
208
208
  | 路径 | `ctx.paths.home / configRoot / dataDir`;`ctx.dataDir` | `ctx.dataDir` 即本插件 KV 目录 `<配置根>/data/plugins/<插件名>` |
209
209
  | 数采 | `ctx.daq.registerDriver / registerProcessor / registerTemplate / onFrame / onSample / query / nodes` | 驱动、下沉处理器、节点模板注册 + 时序查询 + 节点元数据快照 |
210
+ | 写控 | `ctx.dcw.registerWriteDriver(driver)` | 写驱动注入(与数采读驱动对称):契约 `{ kind, available(), write(input), test(cfg), read?(input), meta? }`;热重载停用即失效 |
210
211
  | 工具 | `ctx.omp.registerTool({ name, label?, description, parameters, roles?, handler })` | Agent 工具注入;`roles` 仅 `'lead'` 与 `'worker'`(见 §10) |
211
212
  | 服务 | `ctx.services.names()` / `.get(name)` / `.provide(name, getter)` | 运行时对象面:`get('daq' \| 'lines' \| 'channels' \| 'plugins')`;`provide` 自动加 `<插件名>.` 前缀 |
212
213
  | 权限 | `ctx.permissions.lineMode / visibleLineIds / listGrants / setGrants` | 产线授权查询与管理 |
213
214
 
215
+ ### 通信协议插件(自定义接口连接方式)
216
+
217
+ `ctx.daq.registerDriver` / `ctx.dcw.registerWriteDriver` 是**协议接入点**:串口、其他 PLC 协议、
218
+ 私有总线的连接实现都从这里注入,主链路(采样循环 / 写控网关 / 前端表单)零改动。
219
+
220
+ ```js
221
+ ctx.daq.registerDriver({
222
+ kind: 'serial', // 自定义协议 kind(与内置冲突时插件覆盖并告警)
223
+ async available() { return true }, // 协议栈包探测(缺失时 UI 显示「未安装」)
224
+ async sample({ driverConfig }) { return 25.3 }, // 读数(标量或 v2 帧信封)
225
+ async test(driverConfig) { return { ok: true, message: '连接成功', sampleValue: 25.3 } },
226
+ meta: { // 自描述(前端「添加节点」下拉 + 动态参数表单)
227
+ label: '串口 Serial(RS-232/485)',
228
+ status: 'real',
229
+ configFields: [{ key: 'path', label: '串口路径', type: 'string', required: true, placeholder: 'COM3' }],
230
+ },
231
+ })
232
+ ```
233
+
234
+ 要点:
235
+ - 驱动目录经 `GET /api/workshop/daq`(读)与 `GET /api/workshop/dcw`(写)的 `drivers` 字段下发,
236
+ 前端数采/写控页据此渲染插件协议与参数表单(带 ⌁ 徽标);不带 `meta` 的驱动只出现在可用性里;
237
+ - 热重载先清空全部插件驱动再重装载:**停用/卸载的协议驱动立即失效**,不会残留到重启;
238
+ - 内置示例:`server/plugins-builtin/serial-bridge`(串口 Modbus RTU + ASCII 行协议,含读/写驱动、
239
+ `health`/`ports`/`probe` API、前端面板与 `serial_ports` Agent 工具)——新协议插件建议以它为模板。
240
+
214
241
  ### KV 落在哪里
215
242
 
216
243
  `<configRoot>/data/plugins/<插件名>/kv.json` —— **不在插件目录里**。写入以内存为准,200ms 防抖后 `tmp + rename` 原子落盘;关机与热重载时取消挂起的防抖并同步落盘。`ctx.kv` 只有 `get / set / all / bump` 四个方法。
@@ -521,6 +548,7 @@ export default {
521
548
  - **浏览器新启用插件注入延迟 ≤15s**(WS 可用时即时)。
522
549
  - **`server:close` / `onDispose` 依赖优雅关闭信号**,Windows 强杀进程不触发。
523
550
  - **`aw plugin list` 只做目录扫描**,不反映装载失败与运行态健康。
551
+ - **协议插件的原生栈与宿主同进程**:serial-bridge 的 RTU 帧机为自研实现(单 serialport 原生栈;弃用 modbus-serial —— 其包装层在 dev 模式实测触发 V8 原生崩溃)。已知边界:**`aw dev` 进程内,串口事务「写→超时→关闭」序列可能触发 bindings-cpp 的 V8 原生崩溃**(HandleScope fatal,JS 层不可捕获,与具体串口无关);**生产模式(`aw start`)不受影响 —— 已实测同一序列在生产下诚实返回超时且服务存活**,纯打开(ASCII 探针)在 dev 下同样正常。开发期连接真实硬件请知悉此风险;对稳定性要求极高的部署可把串口网关放进独立进程(参考 plc-node-simulator 的子进程模式)。
524
552
 
525
553
  ## 十三、发布前验证清单
526
554
 
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.38) —
10
+ from the npm package version (the `version` field in `package.json`, currently 0.7.41) —
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.38)
8
+ 这两个常量是 **SDK 自身接口的版本号**,与 npm 包版本(`package.json` 的 `version`,当前 0.7.41)
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.40",
3
+ "version": "0.7.41",
4
4
  "type": "module",
5
5
  "description": "AgentWorkShop 软件开发系统 - 基于 Nuxt 4 的配置驱动运行范式",
6
6
  "license": "PolyForm-Noncommercial-1.0.0",
@@ -145,7 +145,8 @@
145
145
  "zod": "^3.24.1"
146
146
  },
147
147
  "optionalDependencies": {
148
- "onnxruntime-node": "^1.20.1"
148
+ "onnxruntime-node": "^1.20.1",
149
+ "serialport": "^13.0.0"
149
150
  },
150
151
  "devDependencies": {
151
152
  "@commitlint/cli": "^19.6.1",