agentworkshop 0.7.24 → 0.7.27
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.output/nitro.json +1 -1
- package/.output/public/_nuxt/2jaJHWIJ.js +1 -0
- package/.output/public/_nuxt/B7gZokg82.js +209 -0
- package/.output/public/_nuxt/{Dfe8vXdG.js → B8kt898d.js} +1 -1
- package/.output/public/_nuxt/{BvNJUu3s.js → BFGpEo8S.js} +1 -1
- package/.output/public/_nuxt/{BAfp6tmu.js → BG4zduPf.js} +5 -5
- package/.output/public/_nuxt/{BMb80_tm.js → BGhOrgle.js} +1 -1
- package/.output/public/_nuxt/{Bc-_IsBP.js → BHcpzI2n.js} +1 -1
- package/.output/public/_nuxt/{Djir5rSD.js → BTLEvDxM.js} +1 -1
- package/.output/public/_nuxt/{b50gYVZl.js → BWyR3awA.js} +1 -1
- package/.output/public/_nuxt/Bc89zKq2.js +1 -0
- package/.output/public/_nuxt/{CxY6LfeN.js → BetmGSO4.js} +1 -1
- package/.output/public/_nuxt/{DAR70Lf52.js → BgCrZKv_2.js} +1 -1
- package/.output/public/_nuxt/{DUToq-k_.js → BowTbasd.js} +1 -1
- package/.output/public/_nuxt/{D6HDb-vN2.js → BvtknqSC2.js} +1 -1
- package/.output/public/_nuxt/{CLZWFhZC.js → CTfqWGdy.js} +1 -1
- package/.output/public/_nuxt/Chc5pfAA.js +1 -0
- package/.output/public/_nuxt/CobSUzQv.js +2 -0
- package/.output/public/_nuxt/{kZV5TQvh2.js → CucID8qg2.js} +1 -1
- package/.output/public/_nuxt/{CAGL7TNK.js → DHbTyoAV.js} +1 -1
- package/.output/public/_nuxt/{DSa2O56y.js → DfLUN8kY.js} +1 -1
- package/.output/public/_nuxt/{Coik560n.js → DjU9Dx5Y.js} +1 -1
- package/.output/public/_nuxt/DnziMlcX.js +1 -0
- package/.output/public/_nuxt/{Ct68Z0232.js → DosOJEmZ2.js} +1 -1
- package/.output/public/_nuxt/{CDLv-n51.js → Q_SJDSgx.js} +1 -1
- package/.output/public/_nuxt/{TownView.zu2rU5nS.css → TownView.CtskuMXS.css} +1 -1
- package/.output/public/_nuxt/{CuVGPuP1.js → XY2z78rj.js} +1 -1
- package/.output/public/_nuxt/_id_.DdbYeLqn.css +1 -0
- package/.output/public/_nuxt/{C1CSiSBN.js → bFnZa2yd.js} +1 -1
- package/.output/public/_nuxt/builds/latest.json +1 -1
- package/.output/public/_nuxt/builds/meta/f7a8e33d-5422-45ac-895d-27168f97e3ad.json +1 -0
- package/.output/public/_nuxt/daq.DGV9QS8d.css +1 -0
- package/.output/public/_nuxt/{entry.AZM1Culo.css → entry.BN02X8iR.css} +1 -1
- package/.output/public/_nuxt/{X6IraCTB.js → rkVgzg62.js} +1 -1
- package/.output/public/_nuxt/settings.W6qrYAui.css +1 -0
- package/.output/public/_nuxt/teams.BBXVkyKq.css +1 -0
- package/.output/public/assets/game/character/yangyang-walk.glb +0 -0
- package/.output/server/chunks/_/host-bindings.mjs +1 -1
- package/.output/server/chunks/_/nitro.mjs +1013 -844
- package/.output/server/chunks/_/nitro.mjs.map +1 -1
- package/.output/server/chunks/build/{TownView-DxeQe1Bl.mjs → TownView-ftIylqiD.mjs} +178 -151
- package/.output/server/chunks/build/{TownView-DxeQe1Bl.mjs.map → TownView-ftIylqiD.mjs.map} +1 -1
- package/.output/server/chunks/build/{_id_-DQDqj-cZ.mjs → _id_-3Axz2-gV.mjs} +35 -35
- package/.output/server/chunks/build/{_id_-DQDqj-cZ.mjs.map → _id_-3Axz2-gV.mjs.map} +1 -1
- package/.output/server/chunks/build/{_id_-DY6v6sqB.mjs → _id_-CahfkmEt.mjs} +5 -5
- package/.output/server/chunks/build/{_id_-DY6v6sqB.mjs.map → _id_-CahfkmEt.mjs.map} +1 -1
- package/.output/server/chunks/build/{_wsId_-D38Vx6Ky.mjs → _wsId_-BymlQhzS.mjs} +7 -7
- package/.output/server/chunks/build/{_wsId_-D38Vx6Ky.mjs.map → _wsId_-BymlQhzS.mjs.map} +1 -1
- package/.output/server/chunks/build/{agents-GnmUGZir.mjs → agents-DuMiSWKp.mjs} +4 -4
- package/.output/server/chunks/build/{agents-GnmUGZir.mjs.map → agents-DuMiSWKp.mjs.map} +1 -1
- package/.output/server/chunks/build/{channel-templates-B4z6v8Z5.mjs → channel-templates-BStqJ-of.mjs} +2 -2
- package/.output/server/chunks/build/{channel-templates-B4z6v8Z5.mjs.map → channel-templates-BStqJ-of.mjs.map} +1 -1
- package/.output/server/chunks/build/{checkbox-dzcpU_Cd.mjs → checkbox-C1gABOca.mjs} +4 -4
- package/.output/server/chunks/build/{checkbox-dzcpU_Cd.mjs.map → checkbox-C1gABOca.mjs.map} +1 -1
- package/.output/server/chunks/build/{daq-B-3xCp_2.mjs → daq-ChYCABz1.mjs} +312 -426
- package/.output/server/chunks/build/daq-ChYCABz1.mjs.map +1 -0
- package/.output/server/chunks/build/{dcw-BL2dhuz7.mjs → dcw-CcQNGggh.mjs} +3 -3
- package/.output/server/chunks/build/{dcw-BL2dhuz7.mjs.map → dcw-CcQNGggh.mjs.map} +1 -1
- package/.output/server/chunks/build/{default-CLf5m0NE.mjs → default-95-zOTPt.mjs} +2 -2
- package/.output/server/chunks/build/{default-CLf5m0NE.mjs.map → default-95-zOTPt.mjs.map} +1 -1
- package/.output/server/chunks/build/{logs-Dto_umFY.mjs → logs-D-2UyGtE.mjs} +3 -3
- package/.output/server/chunks/build/{logs-Dto_umFY.mjs.map → logs-D-2UyGtE.mjs.map} +1 -1
- package/.output/server/chunks/build/{monitor-Cp4V-H2n.mjs → monitor-mfG5jBtz.mjs} +3 -3
- package/.output/server/chunks/build/{monitor-Cp4V-H2n.mjs.map → monitor-mfG5jBtz.mjs.map} +1 -1
- package/.output/server/chunks/build/{pages-Dc2H1QFu.mjs → pages-Dfosfchr.mjs} +4 -4
- package/.output/server/chunks/build/{pages-Dc2H1QFu.mjs.map → pages-Dfosfchr.mjs.map} +1 -1
- package/.output/server/chunks/build/{permissions-BTRW2CVd.mjs → permissions-B-hhZuzm.mjs} +3 -3
- package/.output/server/chunks/build/{permissions-BTRW2CVd.mjs.map → permissions-B-hhZuzm.mjs.map} +1 -1
- package/.output/server/chunks/build/{settings-UfGyDgM7.mjs → settings-BajN9TQW.mjs} +249 -20
- package/.output/server/chunks/build/settings-BajN9TQW.mjs.map +1 -0
- package/.output/server/chunks/build/{table-Cswq-l2r.mjs → table-Bf1r2JjE.mjs} +2 -2
- package/.output/server/chunks/build/{table-Cswq-l2r.mjs.map → table-Bf1r2JjE.mjs.map} +1 -1
- package/.output/server/chunks/build/{teams-Cbg27MWW.mjs → teams-t2SkRlJP.mjs} +346 -46
- package/.output/server/chunks/build/teams-t2SkRlJP.mjs.map +1 -0
- package/.output/server/chunks/build/{tokens-Bmm6rDfj.mjs → tokens-DiCojbN8.mjs} +3 -3
- package/.output/server/chunks/build/{tokens-Bmm6rDfj.mjs.map → tokens-DiCojbN8.mjs.map} +1 -1
- package/.output/server/chunks/build/{town-Dsh0jlwZ.mjs → town-C7QM8DYK.mjs} +5 -5
- package/.output/server/chunks/build/{town-Dsh0jlwZ.mjs.map → town-C7QM8DYK.mjs.map} +1 -1
- package/.output/server/chunks/build/{useDaqStream-DkRL3tEW.mjs → useDaqStream-BeA2X8KR.mjs} +26 -11
- package/.output/server/chunks/build/useDaqStream-BeA2X8KR.mjs.map +1 -0
- package/.output/server/chunks/build/{useDcwStream-DyB2ufQI.mjs → useDcwStream-BJW_MitY.mjs} +14 -6
- package/.output/server/chunks/build/useDcwStream-BJW_MitY.mjs.map +1 -0
- package/.output/server/chunks/build/{useDeviceTwins-ZbT86L9O.mjs → useDeviceTwins-Dwz_EY_i.mjs} +2 -2
- package/.output/server/chunks/build/{useDeviceTwins-ZbT86L9O.mjs.map → useDeviceTwins-Dwz_EY_i.mjs.map} +1 -1
- package/.output/server/chunks/build/{useOpsLog-8HCYFb6n.mjs → useOpsLog-Dm8QEb1I.mjs} +2 -2
- package/.output/server/chunks/build/{useOpsLog-8HCYFb6n.mjs.map → useOpsLog-Dm8QEb1I.mjs.map} +1 -1
- package/.output/server/chunks/build/{useWorkshopApi-D2gq06N2.mjs → useWorkshopApi-B-L3MmIX.mjs} +9 -2
- package/.output/server/chunks/build/{useWorkshopApi-D2gq06N2.mjs.map → useWorkshopApi-B-L3MmIX.mjs.map} +1 -1
- package/.output/server/chunks/build/{users-B50HHrfi.mjs → users-B9SAdEtH.mjs} +3 -3
- package/.output/server/chunks/build/{users-B50HHrfi.mjs.map → users-B9SAdEtH.mjs.map} +1 -1
- package/.output/server/chunks/routes/api/plugins/_name/_...path_.mjs +14 -2
- package/.output/server/chunks/routes/api/plugins/_name/_...path_.mjs.map +1 -1
- package/.output/server/chunks/routes/api/workshop/channel-templates/_id/instantiate.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/channels/_id/index.put.mjs +40 -0
- package/.output/server/chunks/routes/api/workshop/channels/_id/index.put.mjs.map +1 -0
- package/.output/server/chunks/routes/api/workshop/channels/_id/index4.get.mjs +21 -9
- package/.output/server/chunks/routes/api/workshop/channels/_id/index4.get.mjs.map +1 -1
- package/.output/server/chunks/routes/api/workshop/channels/_id/index5.get.mjs +33 -0
- package/.output/server/chunks/routes/api/workshop/channels/_id/index5.get.mjs.map +1 -0
- package/.output/server/chunks/routes/api/workshop/channels/_id/terminals.get.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/channels.post.mjs +15 -1
- package/.output/server/chunks/routes/api/workshop/channels.post.mjs.map +1 -1
- package/.output/server/chunks/routes/api/workshop/daq/_id/bind.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/daq/_id/bindings.put.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/daq/_id/frames/content.get.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/daq/_id/frames.get.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/daq/_id/samples.get.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/daq/_id/test.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/daq/_id_.delete.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/daq/_id_.patch.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/daq/alarms/_id/ack.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/daq/controller.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/daq/index.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/daq/infra/reconnect.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/daq/infra.get.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/daq/templates/_key_.delete.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/daq/templates/_key_.patch.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/daq/test-driver.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/dcw/_id/bind.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/dcw/_id/bindings.put.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/dcw/_id/param-ledger.get.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/dcw/_id/read.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/dcw/_id/test.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/dcw/_id/write.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/dcw/_id_.delete.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/dcw/_id_.patch.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/dcw/controller.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/dcw/index.get.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/dcw/index.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/dcw/index2.get.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/dcw/index2.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/dcw/index3.get.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/dcw/index3.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/dcw/index4.get.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/dcw/index4.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/dcw/index5.get.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/dcw/journal/node/_nodeId/rollback.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/dcw/line/query.get.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/dcw/lines/_id/start.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/dcw/lines/_id/stop.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/dcw/lines/_id_.delete.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/dcw/optimizations/_id/judge.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/dcw/optimizations/_id/rollback.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/dcw/optimizations/_id/series.get.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/dcw/optimizations/_id_.get.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/dcw/products/_id_.delete.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/dcw/products/_id_.patch.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/dcw/recipes/_id/apply.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/dcw/recipes/_id/mark-good.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/dcw/recipes/_id/rollback-good.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/dcw/recipes/_id_.delete.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/dcw/runs/_id/close.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/dcw/runs/_id/data.get.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/dcw/runs/_id/rollback.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/dcw/templates/_key_.delete.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/dcw/templates/_key_.patch.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/dcw/test-driver.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/device-twins/_id_.delete.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/device-twins/_id_.patch.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/harnesses/_id/providers.get.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/harnesses.get.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/hitl/pending.get.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/hitl/respond.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/index.put.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/index3.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/index4.get.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/index4.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/index5.get.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/index5.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/index6.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/plugins/_name/disable.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/plugins/_name/enable.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/scene/layouts/_channelId_.delete.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/scene/layouts/_channelId_.put.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/workspaces/_id/channel-templates/_tplId_.post.mjs +1 -1
- package/.output/server/chunks/routes/renderer.mjs +2 -2
- package/.output/server/chunks/virtual/entry.mjs +352 -433
- package/.output/server/chunks/virtual/precomputed.mjs +1 -1
- package/.output/server/package.json +1 -1
- package/app/components/workshop/DaqNodeRow.vue +398 -0
- package/app/components/workshop/town/ModelPreview3D.vue +26 -4
- package/app/components/workshop/town/TownScene3D.ts +60 -12
- package/app/components/workshop/town/TownView.vue +4 -3
- package/app/composables/workshop/useCharacterAssets.ts +1 -0
- package/app/composables/workshop/useDaqStream.ts +24 -5
- package/app/composables/workshop/useDcwStream.ts +13 -4
- package/app/composables/workshop/useWorkshopApi.ts +294 -259
- package/app/pages/daq/[id].vue +27 -3
- package/app/pages/daq/index.vue +8 -355
- package/app/pages/settings.vue +253 -2
- package/app/pages/workshop/teams.vue +212 -2
- package/i18n/dicts/_en.json +361 -1
- package/i18n/dicts/plugins.json +10 -0
- package/i18n/dicts/settings.json +1 -0
- package/i18n/dicts/teamPlugins.json +10 -0
- package/i18n/locales/en.ts +1860 -1900
- package/i18n/locales/zh-CN.ts +1788 -1829
- package/package.json +149 -148
- package/public/assets/game/character/yangyang-walk.glb +0 -0
- package/scripts/_dbg-cpu-profile.mjs +53 -0
- package/scripts/_dbg-render-perf.mjs +142 -0
- package/scripts/_dbg-render-regression.mjs +130 -0
- package/scripts/i18n-merge.mjs +152 -30
- package/scripts/production-closed-loop-e2e.mjs +254 -0
- package/scripts/team-plugins-api-smoke.mjs +149 -0
- package/scripts/three-system-e2e.mjs +448 -0
- package/server/api/plugins/[name]/[...path].ts +46 -31
- package/server/api/workshop/channels/[id]/plugins/index.get.ts +35 -0
- package/server/api/workshop/channels/[id]/plugins/index.put.ts +39 -0
- package/server/api/workshop/channels.post.ts +17 -0
- package/server/data/dcw-lines.json +7 -0
- package/server/data/dcw-products.json +7 -0
- package/server/data/dcw-recipes.json +1 -1
- package/server/data/dcw-rollback.json +523 -11
- package/server/data/dcw-writes.json +72 -0
- package/server/data/dcws.json +368 -368
- package/server/data/scene-layouts.json +66 -0
- package/server/plugins/aw-plugins.ts +35 -27
- package/server/plugins-builtin/diag-bridge/index.mjs +556 -0
- package/server/plugins-builtin/rag-bridge/index.mjs +364 -0
- package/server/services/workshop/agents/host-tool-bridge.ts +659 -642
- package/server/services/workshop/agents/omp-agent.ts +1178 -1178
- package/server/services/workshop/agents/plugin-tools.ts +10 -1
- package/server/services/workshop/db/channel-plugins.repo.ts +65 -0
- package/server/services/workshop/plugins/host.mjs +52 -4
- package/server/services/workshop/runtime/manager.ts +2976 -2968
- package/shared/config/schema.json +4 -0
- package/.output/public/_nuxt/BUcmySDa2.js +0 -209
- package/.output/public/_nuxt/CYzpZzB_.js +0 -1
- package/.output/public/_nuxt/CjXIpPMR.js +0 -1
- package/.output/public/_nuxt/DKHcFE1i.js +0 -1
- package/.output/public/_nuxt/DjtE7AYt.js +0 -1
- package/.output/public/_nuxt/JOwsShod.js +0 -2
- package/.output/public/_nuxt/_id_.C-YXjn8Q.css +0 -1
- package/.output/public/_nuxt/builds/meta/024de5f7-6a10-4f53-82ab-bc65f3ec9e35.json +0 -1
- package/.output/public/_nuxt/daq.3OKj58HH.css +0 -1
- package/.output/public/_nuxt/settings.BqMF2nfj.css +0 -1
- package/.output/public/_nuxt/teams.DN0pAGGu.css +0 -1
- package/.output/server/chunks/build/daq-B-3xCp_2.mjs.map +0 -1
- package/.output/server/chunks/build/settings-UfGyDgM7.mjs.map +0 -1
- package/.output/server/chunks/build/teams-Cbg27MWW.mjs.map +0 -1
- package/.output/server/chunks/build/useDaqStream-DkRL3tEW.mjs.map +0 -1
- package/.output/server/chunks/build/useDcwStream-DyB2ufQI.mjs.map +0 -1
|
@@ -0,0 +1,448 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 三系统集成 e2e — AgentWorkShop × industrial-deep-diagnostic × rag-knowledge
|
|
3
|
+
*
|
|
4
|
+
* 覆盖(对应 .omc/plans/2026-09-07-three-system-integration.md 验收标准):
|
|
5
|
+
* Stage 1 系统与插件健康:四服务存活特征 / 插件装载 / 插件路由鉴权门(auth:'user')
|
|
6
|
+
* Stage 2 工具链与团队演示:DAQ 实时→快照 CSV / kb_index+kb_search 回环 / kb_store /
|
|
7
|
+
* 双团队(数据分析组+闭环控制组)/ 跨通道消息 / DCW(manual)→HITL→写回
|
|
8
|
+
* Stage 3(--full)真实诊断:diag_run(omp 引擎)→ 轮询至完成 → 报告自动入 KB → 检索命中
|
|
9
|
+
* Stage 4 插件启停:plugins-state.json 热重载 → 工具与路由同时消失/恢复
|
|
10
|
+
*
|
|
11
|
+
* 用法:node scripts/three-system-e2e.mjs [--full]
|
|
12
|
+
* 鉴权:沿用 api-live-e2e 惯例(env AW_E2E_TOKEN 复用,否则注册临时用户);
|
|
13
|
+
* 产线权限由脚本直接向 users.sqlite 种一行 user_line_grants(测试夹具,非平台面)。
|
|
14
|
+
*/
|
|
15
|
+
import { DatabaseSync } from 'node:sqlite'
|
|
16
|
+
import { existsSync, readFileSync, writeFileSync, renameSync } from 'node:fs'
|
|
17
|
+
import { fileURLToPath } from 'node:url'
|
|
18
|
+
import { dirname, join, resolve } from 'node:path'
|
|
19
|
+
|
|
20
|
+
const __dirname = dirname(fileURLToPath(import.meta.url))
|
|
21
|
+
const ROOT = resolve(__dirname, '..')
|
|
22
|
+
const FULL = process.argv.includes('--full')
|
|
23
|
+
|
|
24
|
+
const BASE = process.env.AW_BASE ?? 'http://localhost:3021'
|
|
25
|
+
const KB = process.env.KB_BASE ?? 'http://127.0.0.1:8770'
|
|
26
|
+
const KBWEB = process.env.KB_WEB ?? 'http://127.0.0.1:6789'
|
|
27
|
+
const DIAG = process.env.DIAG_BASE ?? 'http://127.0.0.1:3210'
|
|
28
|
+
const LINE = process.env.E2E_LINE ?? 'ln-af002514' // 1号产线(6 个 mock 数采节点)
|
|
29
|
+
const DCW_NODE = process.env.E2E_DCW_NODE ?? 'dw-e92bb0e7' // 压力设定器(mock 驱动)
|
|
30
|
+
const CH_A = '产线数据分析组'
|
|
31
|
+
const CH_B = '闭环控制组'
|
|
32
|
+
|
|
33
|
+
let pass = 0
|
|
34
|
+
let fail = 0
|
|
35
|
+
const failures = []
|
|
36
|
+
function ok(cond, label, detail = '') {
|
|
37
|
+
if (cond) {
|
|
38
|
+
pass++
|
|
39
|
+
console.log(` ✔ ${label}${detail ? ` —— ${detail}` : ''}`)
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
fail++
|
|
43
|
+
failures.push(label)
|
|
44
|
+
console.error(` ✘ ${label}${detail ? ` —— ${detail}` : ''}`)
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
const sleep = ms => new Promise(r => setTimeout(r, ms))
|
|
48
|
+
|
|
49
|
+
async function raw(method, url, { body, token, agent, timeoutMs = 20000 } = {}) {
|
|
50
|
+
const headers = {}
|
|
51
|
+
if (body !== undefined) headers['content-type'] = 'application/json'
|
|
52
|
+
if (token) headers.authorization = `Bearer ${token}`
|
|
53
|
+
if (agent) headers['x-aw-agent-token'] = agent.token
|
|
54
|
+
const res = await fetch(url, {
|
|
55
|
+
method, headers,
|
|
56
|
+
body: body !== undefined ? JSON.stringify(body) : undefined,
|
|
57
|
+
signal: AbortSignal.timeout(timeoutMs),
|
|
58
|
+
})
|
|
59
|
+
const json = await res.json().catch(() => null)
|
|
60
|
+
return { status: res.status, json }
|
|
61
|
+
}
|
|
62
|
+
const api = (method, path, opts = {}) => raw(method, `${BASE}${path}`, opts)
|
|
63
|
+
const envelopeData = r => r?.json?.data
|
|
64
|
+
const resultText = r => String(r?.json?.data?.result?.text ?? '')
|
|
65
|
+
const invoke = (agent, tool, args, timeoutMs = 60000) => api('POST', '/api/workshop/agent-tools/invoke', {
|
|
66
|
+
agent, timeoutMs, body: { agentId: agent.id, tool, args },
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
// ── 0. 用户 token(持久化演示账号 → env → 现场注册)────────────────────────
|
|
70
|
+
console.log('\n══ 三系统集成 e2e ══')
|
|
71
|
+
const TOKEN_FILE = join(ROOT, '.AgentWorkShop', 'data', '.three-system-e2e-token.json')
|
|
72
|
+
let userToken = ''
|
|
73
|
+
let userId = ''
|
|
74
|
+
try {
|
|
75
|
+
const saved = JSON.parse(readFileSync(TOKEN_FILE, 'utf8'))
|
|
76
|
+
userToken = saved.token ?? ''
|
|
77
|
+
userId = saved.uid ?? ''
|
|
78
|
+
}
|
|
79
|
+
catch { /* 首跑 */ }
|
|
80
|
+
if (!userToken && process.env.AW_E2E_TOKEN) userToken = process.env.AW_E2E_TOKEN
|
|
81
|
+
if (!userToken) {
|
|
82
|
+
const name = `e2e3sys-${Date.now().toString(36)}`
|
|
83
|
+
const reg = await api('POST', '/api/workshop/users/register', { body: { name } })
|
|
84
|
+
userToken = reg?.json?.data?.token ?? ''
|
|
85
|
+
userId = reg?.json?.data?.id ?? ''
|
|
86
|
+
try {
|
|
87
|
+
writeFileSync(TOKEN_FILE, JSON.stringify({ token: userToken, uid: userId, name }))
|
|
88
|
+
}
|
|
89
|
+
catch { /* 忽略 */ }
|
|
90
|
+
ok(Boolean(userToken), '注册 e2e 用户拿 token', userToken ? 'ok' : `resp=${JSON.stringify(reg.json).slice(0, 120)}`)
|
|
91
|
+
if (!userToken) process.exit(1)
|
|
92
|
+
}
|
|
93
|
+
else { console.log(' · 复用持久化演示账号 token') }
|
|
94
|
+
|
|
95
|
+
// ── 0b. 夹具种子:e2e 用户对目标产线 operate 授权(users.sqlite 直插,测试专用)──
|
|
96
|
+
try {
|
|
97
|
+
const dbPath = join(ROOT, '.AgentWorkShop', 'data', 'users.sqlite')
|
|
98
|
+
if (!existsSync(dbPath)) throw new Error(`users.sqlite 不存在: ${dbPath}`)
|
|
99
|
+
let uid = userId
|
|
100
|
+
if (!uid) {
|
|
101
|
+
const db = new DatabaseSync(dbPath)
|
|
102
|
+
db.exec('PRAGMA busy_timeout=4000')
|
|
103
|
+
const cand = db.prepare('SELECT id FROM users ORDER BY created_at DESC LIMIT 200').all()
|
|
104
|
+
.find(() => true) // 兜底:下面按 token 哈希反查不可行,直接用最新 e2e 用户
|
|
105
|
+
uid = cand?.id ?? ''
|
|
106
|
+
db.close()
|
|
107
|
+
}
|
|
108
|
+
if (uid) {
|
|
109
|
+
const db2 = new DatabaseSync(dbPath)
|
|
110
|
+
db2.exec('PRAGMA busy_timeout=4000')
|
|
111
|
+
db2.prepare(`INSERT OR REPLACE INTO user_line_grants (user_id, line_id, mode, granted_by, granted_at) VALUES (?, ?, 'operate', 'three-system-e2e', ?)`)
|
|
112
|
+
.run(uid, LINE, new Date().toISOString())
|
|
113
|
+
db2.close()
|
|
114
|
+
console.log(` · 已为用户 ${uid} 种入产线 ${LINE} operate 授权(测试夹具)`)
|
|
115
|
+
}
|
|
116
|
+
else {
|
|
117
|
+
console.log(' ! 未能取得 user id,产线授权未种(相关断言可能失败)')
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
catch (err) {
|
|
121
|
+
console.log(` ! 授权夹具异常(继续): ${err?.message ?? err}`)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// ══ Stage 1:四服务健康 + 插件装载 + 鉴权门 ═══════════════════════════════
|
|
125
|
+
console.log('\n── Stage 1 系统与插件健康 ──')
|
|
126
|
+
{
|
|
127
|
+
const awsGate = await api('GET', '/api/workshop/plugins')
|
|
128
|
+
ok(awsGate.status === 401, 'AWS 3021 活着(无 token 401 信封)', `status=${awsGate.status}`)
|
|
129
|
+
|
|
130
|
+
const kbH = await raw('GET', `${KB}/api/v1/health`)
|
|
131
|
+
ok(kbH.json?.status === 'healthy', 'rag-knowledge 后端 8770 healthy', JSON.stringify(kbH.json))
|
|
132
|
+
const kbWeb = await raw('GET', `${KBWEB}/api/kb/catalog`)
|
|
133
|
+
ok(kbWeb.json?.success === true, 'rag-knowledge web 6789 catalog 可用', `count=${kbWeb.json?.count}`)
|
|
134
|
+
const dgH = await raw('GET', `${DIAG}/api/health`)
|
|
135
|
+
ok(dgH.json?.status === 'ok', '诊断服务 3210 healthy', `activeRuns=${dgH.json?.checks?.activeRuns}`)
|
|
136
|
+
const rgH = await raw('GET', 'http://127.0.0.1:8764/health', { timeoutMs: 5000 }).catch(e => ({ json: { status: String(e) } }))
|
|
137
|
+
ok(rgH.json?.status === 'healthy', '诊断 RAG 引擎 8764 healthy(降级容忍)', JSON.stringify(rgH.json ?? {}))
|
|
138
|
+
|
|
139
|
+
const plugs = await api('GET', '/api/workshop/plugins', { token: userToken })
|
|
140
|
+
// 兼容两种形状:顶层 {plugins:[...]}(插件清单端点未包信封)/ 信封 data.plugins
|
|
141
|
+
const list = plugs.json?.plugins ?? envelopeData(plugs)?.plugins ?? []
|
|
142
|
+
const names = list.map(p => p?.name)
|
|
143
|
+
const enabled = n => list.find(p => p?.name === n)?.enabled === true
|
|
144
|
+
ok(Array.isArray(list) && list.length > 0, '插件清单可读', list.length ? `count=${list.length}` : `resp=${JSON.stringify(plugs.json).slice(0, 160)}`)
|
|
145
|
+
ok(names.includes('rag-bridge'), 'rag-bridge 已装载')
|
|
146
|
+
ok(names.includes('diag-bridge'), 'diag-bridge 已装载')
|
|
147
|
+
ok(enabled('rag-bridge') && enabled('diag-bridge'), '两桥接插件均为启用态')
|
|
148
|
+
|
|
149
|
+
const noAuthRag = await raw('GET', `${BASE}/api/plugins/rag-bridge/health`)
|
|
150
|
+
const noAuthDiag = await raw('GET', `${BASE}/api/plugins/diag-bridge/health`)
|
|
151
|
+
ok(noAuthRag.status === 401 && noAuthDiag.status === 401, '插件路由鉴权门生效(auth:user → 无 token 401)', `rag=${noAuthRag.status} diag=${noAuthDiag.status}`)
|
|
152
|
+
|
|
153
|
+
const ragH = await raw('GET', `${BASE}/api/plugins/rag-bridge/health`, { token: userToken })
|
|
154
|
+
ok(ragH.status === 200 && ragH.json?.outbound === true && ragH.json?.kb?.id, 'rag-bridge 健康(kb.id 就绪)', `kb=${ragH.json?.kb?.id}`)
|
|
155
|
+
const diagH = await raw('GET', `${BASE}/api/plugins/diag-bridge/health`, { token: userToken })
|
|
156
|
+
ok(diagH.status === 200 && diagH.json?.remote?.status === 'ok', 'diag-bridge 健康(3210 可达)', `activeRuns=${diagH.json?.remote?.activeRuns}`)
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// ══ Stage 2:双团队 + 工具链 + 跨通道 + HITL ═══════════════════════════════
|
|
160
|
+
let leadA
|
|
161
|
+
let toolInvoker // 工具直调执行器(omp;mock lead 不实现 dispatch 面)
|
|
162
|
+
let leadB
|
|
163
|
+
let channelA
|
|
164
|
+
let channelB
|
|
165
|
+
{
|
|
166
|
+
console.log('\n── Stage 2 团队与工具链 ──')
|
|
167
|
+
async function ensureChannel(name, leadName, execName) {
|
|
168
|
+
const found = await api('GET', '/api/workshop/channels', { token: userToken })
|
|
169
|
+
const items = envelopeData(found) ?? []
|
|
170
|
+
const hit = (Array.isArray(items) ? items : []).find(c => c?.name === name)
|
|
171
|
+
let channelId = hit?.id ?? ''
|
|
172
|
+
if (!channelId) {
|
|
173
|
+
let created = await api('POST', '/api/workshop/channels', {
|
|
174
|
+
token: userToken,
|
|
175
|
+
body: { name, description: `三系统集成示例团队(e2e 创建)`, leadAgent: { name: leadName, harness: 'mock' } },
|
|
176
|
+
})
|
|
177
|
+
if (!(created.status === 200 && envelopeData(created)?.channelId)) {
|
|
178
|
+
// 同名频道被其他账号占用 → 加随机后缀重建(演示语义不变)
|
|
179
|
+
name = `${name}-${Math.random().toString(36).slice(2, 6)}`
|
|
180
|
+
created = await api('POST', '/api/workshop/channels', {
|
|
181
|
+
token: userToken,
|
|
182
|
+
body: { name, description: `三系统集成示例团队(e2e 创建)`, leadAgent: { name: leadName, harness: 'mock' } },
|
|
183
|
+
})
|
|
184
|
+
}
|
|
185
|
+
channelId = envelopeData(created)?.channelId ?? ''
|
|
186
|
+
}
|
|
187
|
+
const readMembers = async () => (envelopeData(await api('GET', `/api/workshop/channels/${channelId}/agents`, { token: userToken })) ?? [])
|
|
188
|
+
let members = await readMembers()
|
|
189
|
+
// 每队配一个 omp「执行器」成员:REST 直调工具经共享 host-tool-bridge 分发,零 LLM 会话
|
|
190
|
+
if (!members.some(m => m?.name === execName)) {
|
|
191
|
+
await api('POST', `/api/workshop/channels/${channelId}/agents`, {
|
|
192
|
+
token: userToken,
|
|
193
|
+
body: { name: execName, harness: 'omp', role: 'worker' },
|
|
194
|
+
})
|
|
195
|
+
members = await readMembers()
|
|
196
|
+
}
|
|
197
|
+
const lead = members.find(m => m?.role === 'lead')
|
|
198
|
+
const exec = members.find(m => m?.name === execName)
|
|
199
|
+
return { channelId, lead, exec, name }
|
|
200
|
+
}
|
|
201
|
+
const a = await ensureChannel(CH_A, '分析组长', '数据分析执行器')
|
|
202
|
+
const b = await ensureChannel(CH_B, '控制组长', '控制执行器')
|
|
203
|
+
channelA = a.channelId
|
|
204
|
+
channelB = b.channelId
|
|
205
|
+
leadA = a.lead
|
|
206
|
+
leadB = b.lead
|
|
207
|
+
const execA = a.exec
|
|
208
|
+
const execB = b.exec
|
|
209
|
+
const nameB = b.name ?? CH_B
|
|
210
|
+
ok(Boolean(channelA && leadA?.id && leadA?.token), `团队A「${a.name ?? CH_A}」就绪(lead=${leadA?.name})`)
|
|
211
|
+
ok(Boolean(channelB && leadB?.id && leadB?.token), `团队B「${nameB}」就绪(lead=${leadB?.name})`)
|
|
212
|
+
ok(Boolean(execA?.id && execA?.token), '团队A 执行器(omp)就绪', execA?.name)
|
|
213
|
+
ok(Boolean(execB?.id && execB?.token), '团队B 执行器(omp)就绪', execB?.name)
|
|
214
|
+
toolInvoker = execA ?? leadA
|
|
215
|
+
|
|
216
|
+
// 工具清单:5 工具可见 + 无 url/base_url/host 参数(SSRF 面)
|
|
217
|
+
const listR = await api('GET', `/api/workshop/agent-tools/list?agentId=${toolInvoker.id}`, { agent: toolInvoker })
|
|
218
|
+
const tools = listR?.json?.tools ?? listR?.json?.data?.tools ?? []
|
|
219
|
+
const toolNames = tools.map(t => t?.name)
|
|
220
|
+
for (const t of ['diag_run', 'diag_status', 'kb_search', 'kb_store', 'kb_index']) {
|
|
221
|
+
ok(toolNames.includes(t), `agent 工具清单含 ${t}`)
|
|
222
|
+
}
|
|
223
|
+
const bridge = tools.filter(t => ['diag_run', 'diag_status', 'kb_search', 'kb_store', 'kb_index'].includes(t?.name))
|
|
224
|
+
const leak = bridge.some(t => Object.keys(t?.parameters?.properties ?? {}).some(k => /url|host/i.test(k)))
|
|
225
|
+
ok(bridge.length === 5 && !leak, '桥接工具参数面无 url/host 注入口', `tools=${bridge.length}`)
|
|
226
|
+
|
|
227
|
+
// DAQ 实时数据 → 快照导出
|
|
228
|
+
let snap = await api('POST', '/api/plugins/diag-bridge/snapshot', {
|
|
229
|
+
token: userToken,
|
|
230
|
+
body: { line: LINE, from_ms: Date.now() - 15 * 60_000, to_ms: Date.now() },
|
|
231
|
+
})
|
|
232
|
+
if (snap.json?.success === false) {
|
|
233
|
+
snap = await api('POST', '/api/plugins/diag-bridge/snapshot', {
|
|
234
|
+
token: userToken,
|
|
235
|
+
body: { line: LINE, from_ms: Date.now() - 120 * 60_000, to_ms: Date.now() },
|
|
236
|
+
})
|
|
237
|
+
}
|
|
238
|
+
ok(snap.json?.success === true && snap.json?.rows > 0, 'DAQ 实时数采 → 快照 CSV 导出成功', `rows=${snap.json?.rows} nodes=${snap.json?.nodes} path=${snap.json?.csvPath}`)
|
|
239
|
+
if (snap.json?.csvPath) {
|
|
240
|
+
ok(String(snap.json.csvPath).startsWith('data/aw-snapshots/'), '快照落诊断服务 aw-snapshots 目录(相对路径)', snap.json.csvPath)
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// KB 回环:kb_index 入库独特自然语句 → kb_search 命中(嵌入/分词对自然语句才可靠);kb_store 沉淀经验
|
|
244
|
+
const marker = `三系统验证密语${Date.now().toString(36)}:青花瓷泵站三十七号叶片于黄昏完成校准`
|
|
245
|
+
const idx = await invoke(toolInvoker, 'kb_index', {
|
|
246
|
+
title: `三系统集成验证 ${Date.now().toString(36)}`,
|
|
247
|
+
content: `# 三系统集成验证\n\n${marker}。\n\n本文由 three-system-e2e 经 rag-bridge 插件写入,验证 文档写盘 → 向量索引 → kb_search 检索 全链路。\n`,
|
|
248
|
+
tags: ['e2e'],
|
|
249
|
+
})
|
|
250
|
+
ok(resultText(idx).includes('已入库'), 'kb_index 文档入库成功', resultText(idx).slice(0, 80))
|
|
251
|
+
let hit = ''
|
|
252
|
+
for (let i = 0; i < 10; i++) { // 首次检索可能触发 BGE-M3 模型加载,放宽到 10×3s
|
|
253
|
+
await sleep(3000)
|
|
254
|
+
const s = await invoke(toolInvoker, 'kb_search', { query: marker })
|
|
255
|
+
hit = resultText(s)
|
|
256
|
+
if (hit.includes(marker)) break
|
|
257
|
+
}
|
|
258
|
+
ok(hit.includes(marker), 'kb_search 检索命中刚入库文档', hit.split('\n')[1]?.slice(0, 90) ?? hit.slice(0, 90))
|
|
259
|
+
|
|
260
|
+
const store = await invoke(toolInvoker, 'kb_store', {
|
|
261
|
+
title: `e2e 经验:${marker} 快照导出前先确认产线在采样`,
|
|
262
|
+
category: 'troubleshooting',
|
|
263
|
+
problem: 'diag_run 报「时窗内无数采样本」',
|
|
264
|
+
solution: '先查产线 LineRun 是否活动、节点 publishIntervalMs 是否在节拍上,再缩窗重试。',
|
|
265
|
+
key_lessons: ['空窗先查打标窗口', 'bucket 5s 可对齐多节点'],
|
|
266
|
+
tags: ['e2e', 'daq'],
|
|
267
|
+
})
|
|
268
|
+
ok(resultText(store).includes('已沉淀'), 'kb_store 经验沉淀成功', resultText(store).slice(0, 80))
|
|
269
|
+
|
|
270
|
+
// 跨通道:分析组长 → 控制组长(require_reply;用频道 ID 定向,避免跨账号同名频道歧义)
|
|
271
|
+
const xmsg = await invoke(leadA, 'send_cross_channel_message', {
|
|
272
|
+
to_channel_id: channelB,
|
|
273
|
+
message: `分析组已完成 ${LINE} 最近时窗的数据诊断与知识检索;请控制组给出 ${DCW_NODE}(压力设定器)的控制策略建议。`,
|
|
274
|
+
require_reply: true,
|
|
275
|
+
}, 120000)
|
|
276
|
+
ok(!xmsg.json?.result?.isError, '分析组长 → 控制组长 跨通道消息发送成功', resultText(xmsg).slice(0, 80))
|
|
277
|
+
let landedB = ''
|
|
278
|
+
for (let i = 0; i < 8; i++) {
|
|
279
|
+
await sleep(1000)
|
|
280
|
+
const msgs = await api('GET', `/api/workshop/channels/${channelB}/messages`, { token: userToken })
|
|
281
|
+
const items = envelopeData(msgs)?.items ?? envelopeData(msgs) ?? []
|
|
282
|
+
const arr = Array.isArray(items) ? items : (items?.messages ?? [])
|
|
283
|
+
landedB = JSON.stringify(arr)
|
|
284
|
+
if (landedB.includes('控制策略建议')) break
|
|
285
|
+
}
|
|
286
|
+
ok(landedB.includes('控制策略建议'), '跨通道消息落入控制组频道')
|
|
287
|
+
|
|
288
|
+
// DCW(manual)→ HITL → 写回
|
|
289
|
+
const dcwView = await api('GET', '/api/workshop/dcw', { token: userToken })
|
|
290
|
+
const dcwNodes = envelopeData(dcwView)?.nodes ?? []
|
|
291
|
+
const node = dcwNodes.find(n => n?.id === DCW_NODE)
|
|
292
|
+
ok(Boolean(node), `DCW 节点 ${DCW_NODE} 对授权用户可见`, `driver=${node?.driver} value=${node?.value}`)
|
|
293
|
+
const target = Number(node?.value ?? 0)
|
|
294
|
+
const bind = await api('POST', '/api/workshop/agent-tools/bindings', {
|
|
295
|
+
token: userToken,
|
|
296
|
+
body: { agentId: execB.id, nodeId: DCW_NODE, kind: 'dcw', mode: 'manual' },
|
|
297
|
+
})
|
|
298
|
+
ok(bind.status === 200 && Boolean(envelopeData(bind)?.binding?.id), '控制执行器绑定 DCW 节点(manual)', bind.status === 200 ? `mode=${envelopeData(bind)?.binding?.mode}` : JSON.stringify(bind.json).slice(0, 120))
|
|
299
|
+
ok(envelopeData(bind)?.binding?.mode === 'manual', '绑定模式确为 manual(审批路径生效前置)')
|
|
300
|
+
|
|
301
|
+
const pendingInvoke = invoke(execB, 'dcw_control', {
|
|
302
|
+
node_id: DCW_NODE,
|
|
303
|
+
value: target,
|
|
304
|
+
hypothesis: `依据分析组诊断结论与历史经验,维持设定值 ${target}(e2e 闭环演示)`,
|
|
305
|
+
}, 300000)
|
|
306
|
+
let approved = false
|
|
307
|
+
let pendRaw = '[]'
|
|
308
|
+
for (let i = 0; i < 15 && !approved; i++) {
|
|
309
|
+
await sleep(2000)
|
|
310
|
+
const pend = await api('GET', '/api/workshop/hitl/pending', { token: userToken })
|
|
311
|
+
const pd = envelopeData(pend)
|
|
312
|
+
const arr = Array.isArray(pd) ? pd : (pd?.items ?? pd?.pending ?? [])
|
|
313
|
+
pendRaw = JSON.stringify(arr)
|
|
314
|
+
const item = arr.find(x => x?.kind === 'dcw-approval' && x?.agentId === execB.id)
|
|
315
|
+
if (item?.id) {
|
|
316
|
+
const resp = await api('POST', '/api/workshop/hitl/respond', {
|
|
317
|
+
token: userToken,
|
|
318
|
+
body: { kind: 'dcw-approval', id: item.id, confirmed: true, comment: 'three-system-e2e 批准' },
|
|
319
|
+
})
|
|
320
|
+
approved = resp.status === 200
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
const writeRes = await pendingInvoke
|
|
324
|
+
ok(approved, 'HITL 待办出现并经 respond 批准', approved ? 'ok' : `pending=${pendRaw.slice(0, 200)}`)
|
|
325
|
+
ok(resultText(writeRes).includes('下发成功'), 'dcw_control 经审批写回成功', resultText(writeRes).slice(0, 120))
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// ══ Stage 3:真实诊断(kickoff 常跑;--full 才轮询到完成)═══════════════════
|
|
329
|
+
let runId = ''
|
|
330
|
+
{
|
|
331
|
+
console.log('\n── Stage 3 真实深度诊断(omp 引擎)──')
|
|
332
|
+
let r = null
|
|
333
|
+
for (const mins of [30, 180, 1440]) { // 采样间歇(共享实例节拍被并行调节)→ 逐步加宽窗口
|
|
334
|
+
r = await invoke(toolInvoker, 'diag_run', {
|
|
335
|
+
line: LINE,
|
|
336
|
+
from_ms: Date.now() - mins * 60_000,
|
|
337
|
+
to_ms: Date.now(),
|
|
338
|
+
question: `${LINE} 近期数采数据深度根因诊断(three-system-e2e,窗口 ${mins} 分钟)`,
|
|
339
|
+
scene: 'three_system_e2e',
|
|
340
|
+
}, 120000)
|
|
341
|
+
if (resultText(r).includes('runId=')) break
|
|
342
|
+
console.log(` … ${mins} 分钟窗无样本,加宽重试`)
|
|
343
|
+
await sleep(2000)
|
|
344
|
+
}
|
|
345
|
+
const text = resultText(r)
|
|
346
|
+
runId = (text.match(/runId=([a-z0-9-]+)/i) ?? [])[1] ?? ''
|
|
347
|
+
if (!runId) {
|
|
348
|
+
// 产线上已有在跑诊断(如事件自动触发)→ 收养它继续验证,而非报错
|
|
349
|
+
const adopted = (text.match(/run_id=([a-z0-9-]+)/i) ?? [])[1] ?? ''
|
|
350
|
+
if (adopted) {
|
|
351
|
+
runId = adopted
|
|
352
|
+
console.log(` · 产线已有在跑诊断,收养 run_id=${adopted} 继续验证`)
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
ok(Boolean(runId), 'diag_run 异步发起(或收养在跑诊断)拿到 runId', runId || `resp=${JSON.stringify(r.json).slice(0, 260)}`)
|
|
356
|
+
|
|
357
|
+
if (FULL && runId) {
|
|
358
|
+
let done = false
|
|
359
|
+
let lastText = ''
|
|
360
|
+
const maxIter = Math.round(Number(process.env.FULL_WAIT_MIN ?? 45) * 3) // 每 20s 一轮
|
|
361
|
+
for (let i = 0; i < maxIter && !done; i++) {
|
|
362
|
+
await sleep(20_000)
|
|
363
|
+
const s = await invoke(toolInvoker, 'diag_status', { run_id: runId }, 30000)
|
|
364
|
+
lastText = resultText(s)
|
|
365
|
+
if (/状态=completed/.test(lastText)) done = true
|
|
366
|
+
else if (/状态=(failed|stopped)/.test(lastText)) break
|
|
367
|
+
if (i % 6 === 5) console.log(` … 轮询中(${(i + 1) * 20}s):${(lastText.match(/状态=\S+/) ?? [''])[0]}`)
|
|
368
|
+
}
|
|
369
|
+
ok(done, '真实诊断跑至 completed', (lastText.match(/评分=\S+|状态=\S+/g) ?? []).join(' '))
|
|
370
|
+
if (done) {
|
|
371
|
+
let stored = ''
|
|
372
|
+
for (let i = 0; i < 6; i++) {
|
|
373
|
+
await sleep(5000)
|
|
374
|
+
const runs = await raw('GET', `${BASE}/api/plugins/diag-bridge/runs`, { token: userToken })
|
|
375
|
+
const mine = (runs.json?.runs ?? []).find(x => x.runId === runId)
|
|
376
|
+
stored = JSON.stringify(mine ?? {})
|
|
377
|
+
if (mine?.stored === true) break
|
|
378
|
+
}
|
|
379
|
+
ok(stored.includes('"stored":true'), '报告自动入库(diag-bridge → KB)')
|
|
380
|
+
let kbHit = ''
|
|
381
|
+
for (let i = 0; i < 8; i++) {
|
|
382
|
+
await sleep(5000)
|
|
383
|
+
const s = await invoke(leadA, 'kb_search', { query: `${LINE} 深度根因诊断 three_system_e2e 结论` }, 60000)
|
|
384
|
+
kbHit = resultText(s)
|
|
385
|
+
if (!kbHit.startsWith('未检索到') && kbHit.length > 0) break
|
|
386
|
+
}
|
|
387
|
+
ok(!kbHit.startsWith('未检索到') && kbHit.length > 0, 'kb_search 检索到诊断报告/经验', kbHit.split('\n')[0]?.slice(0, 90))
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
else {
|
|
391
|
+
console.log(' ·(未加 --full:不等待诊断完成;完成后的入库由 diag-bridge 轮询器自动完成,可重跑 --full 或用 diag_status 观察)')
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// ══ Stage 4:插件启停(热重载 → 工具与路由同时生灭)═════════════════════════
|
|
396
|
+
{
|
|
397
|
+
console.log('\n── Stage 4 插件启停 ──')
|
|
398
|
+
const candidates = [join(ROOT, '.AgentWorkShop', 'plugins-state.json'), join(process.env.USERPROFILE ?? process.env.HOME ?? '', '.AgentWorkShop', 'plugins-state.json')]
|
|
399
|
+
const statePath = candidates.find(p => existsSync(p))
|
|
400
|
+
ok(Boolean(statePath), '定位 plugins-state.json', statePath ?? '未找到')
|
|
401
|
+
if (statePath && leadA?.id) {
|
|
402
|
+
const readState = () => JSON.parse(readFileSync(statePath, 'utf8'))
|
|
403
|
+
const writeState = (obj) => {
|
|
404
|
+
const tmp = `${statePath}.tmp`
|
|
405
|
+
writeFileSync(tmp, JSON.stringify(obj, null, 2))
|
|
406
|
+
renameSync(tmp, statePath)
|
|
407
|
+
}
|
|
408
|
+
const st0 = readState()
|
|
409
|
+
const disabled0 = Array.isArray(st0.disabled) ? st0.disabled : []
|
|
410
|
+
writeState({ ...st0, disabled: [...new Set([...disabled0, 'diag-bridge'])], updatedAt: new Date().toISOString() })
|
|
411
|
+
// 热重载会停止并重建 agent 运行时,每轮重新取成员(拿最新 token)再断言
|
|
412
|
+
const freshLead = async () => {
|
|
413
|
+
const members = await api('GET', `/api/workshop/channels/${channelA}/agents`, { token: userToken })
|
|
414
|
+
return (envelopeData(members) ?? []).find(m => m?.role === 'lead') ?? leadA
|
|
415
|
+
}
|
|
416
|
+
let gone = false
|
|
417
|
+
for (let i = 0; i < 20 && !gone; i++) {
|
|
418
|
+
await sleep(1500)
|
|
419
|
+
const fl = await freshLead()
|
|
420
|
+
const h = await raw('GET', `${BASE}/api/plugins/diag-bridge/health`, { token: userToken })
|
|
421
|
+
const l = await api('GET', `/api/workshop/agent-tools/list?agentId=${fl.id}`, { agent: fl })
|
|
422
|
+
const names4 = (l?.json?.data?.tools ?? l?.json?.tools ?? []).map(t => t?.name)
|
|
423
|
+
gone = h.status === 404 && !names4.includes('diag_run')
|
|
424
|
+
}
|
|
425
|
+
ok(gone, '停用 diag-bridge → 路由 404 且 diag_run 从工具清单消失(热重载 + 工具注销)')
|
|
426
|
+
|
|
427
|
+
writeState({ ...st0, disabled: disabled0, updatedAt: new Date().toISOString() })
|
|
428
|
+
let back = false
|
|
429
|
+
for (let i = 0; i < 25 && !back; i++) {
|
|
430
|
+
await sleep(1500)
|
|
431
|
+
const fl = await freshLead()
|
|
432
|
+
const h = await raw('GET', `${BASE}/api/plugins/diag-bridge/health`, { token: userToken })
|
|
433
|
+
const l = await api('GET', `/api/workshop/agent-tools/list?agentId=${fl.id}`, { agent: fl })
|
|
434
|
+
const names4 = (l?.json?.data?.tools ?? l?.json?.tools ?? []).map(t => t?.name)
|
|
435
|
+
back = h.status === 200 && names4.includes('diag_run')
|
|
436
|
+
}
|
|
437
|
+
ok(back, '重新启用 diag-bridge → 路由与工具恢复')
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
// ══ 汇总 ══════════════════════════════════════════════════════════════════
|
|
442
|
+
console.log(`\n══ 结果:${pass} 通过 / ${fail} 失败 ══`)
|
|
443
|
+
if (failures.length) {
|
|
444
|
+
console.error('失败项:')
|
|
445
|
+
for (const f of failures) console.error(` - ${f}`)
|
|
446
|
+
}
|
|
447
|
+
if (!FULL && runId) console.log(`\n提示:本次发起的诊断 runId=${runId},完成可加 --full 重跑或调 diag_status 观察。`)
|
|
448
|
+
process.exit(fail > 0 ? 1 : 0)
|
|
@@ -1,31 +1,46 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* /api/plugins/:name/** —— 插件自注册 API 的转发层(exact-match)。
|
|
3
|
-
* 插件经 ctx.route(method, path, handler) 注册;handler(event) 返回值由 nitro 序列化。
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
import {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
1
|
+
/**
|
|
2
|
+
* /api/plugins/:name/** —— 插件自注册 API 的转发层(exact-match)。
|
|
3
|
+
* 插件经 ctx.route(method, path, handler) 注册;handler(event) 返回值由 nitro 序列化。
|
|
4
|
+
* 鉴权:插件在入口声明 auth:'user' | 'admin' | 'agent-or-user' 时由转发层统一校验
|
|
5
|
+
* (缺省 'none' 保持开放,兼容既有插件/浏览器面板)。
|
|
6
|
+
* 错误隔离:插件 handler 抛错 → 结构化日志(带插件归属)+ 干净 500 信封,不裸传堆栈。
|
|
7
|
+
*/
|
|
8
|
+
import { defineEventHandler, createError, readBody } from 'h3'
|
|
9
|
+
import { getPluginHost } from '@/server/services/workshop/plugins/host.mjs'
|
|
10
|
+
import { resolveUser, resolveAgentOrUser, requireAdmin } from '../../workshop/caller'
|
|
11
|
+
|
|
12
|
+
export default defineEventHandler(async (event) => {
|
|
13
|
+
const host = getPluginHost()
|
|
14
|
+
if (!host) throw createError({ statusCode: 503, statusMessage: 'plugin host not ready' })
|
|
15
|
+
const name = String(event.context.params?.name ?? '')
|
|
16
|
+
const path = '/' + (event.context.params?.path ?? '').replace(/^\/+/, '')
|
|
17
|
+
const rec = host.plugins.get(name)
|
|
18
|
+
// 声明式鉴权门:在进入插件 handler 前统一校验(禁用插件的路由表已不含其路由,天然 404)
|
|
19
|
+
const authMode = String(rec?.auth ?? 'none')
|
|
20
|
+
if (authMode !== 'none') {
|
|
21
|
+
try {
|
|
22
|
+
if (authMode === 'admin') await requireAdmin(event)
|
|
23
|
+
else if (authMode === 'agent-or-user') await resolveAgentOrUser(event)
|
|
24
|
+
else await resolveUser(event)
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
throw createError({ statusCode: 401, statusMessage: '需要有效的用户 token(Authorization: Bearer <用户token>)' })
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
const handler = host.routes.resolve(name, event.method, path)
|
|
31
|
+
if (!handler) {
|
|
32
|
+
throw createError({ statusCode: 404, statusMessage: `plugin route not found: ${event.method} /api/plugins/${name}${path}` })
|
|
33
|
+
}
|
|
34
|
+
// 预读 body 挂到 event(插件 handler 无 h3 导入能力,经 event.awBody 消费)
|
|
35
|
+
const awBody = await readBody(event).catch(() => undefined)
|
|
36
|
+
;(event as unknown as Record<string, unknown>).awBody = awBody
|
|
37
|
+
try {
|
|
38
|
+
return await handler(event)
|
|
39
|
+
}
|
|
40
|
+
catch (err) {
|
|
41
|
+
// 问题日志:插件归属 + 路由 + 原始原因(运维按 name 即可定位到具体插件目录)
|
|
42
|
+
const reason = err instanceof Error ? err.message : String(err)
|
|
43
|
+
host.logger?.error?.(`插件路由处理失败 [${name}] ${event.method} /api/plugins/${name}${path}: ${reason}`)
|
|
44
|
+
throw createError({ statusCode: 500, statusMessage: `插件路由处理失败(${name}): ${reason}` })
|
|
45
|
+
}
|
|
46
|
+
})
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GET /api/workshop/channels/:id/plugins —— 该团队的插件开关视图。
|
|
3
|
+
* 返回全部已注册插件的团队级 enabled(显式配置/默认全启用)+ source 标记。
|
|
4
|
+
* 鉴权:channel 归属者(遗留公共 channel 需 admin)。
|
|
5
|
+
*/
|
|
6
|
+
import { resolveUser, requireAdmin, isAdmin } from '../../../../workshop/caller'
|
|
7
|
+
import { defineApiHandler } from '@/server/utils/response'
|
|
8
|
+
import { getWorkshopManager } from '@/server/plugins/workshop'
|
|
9
|
+
import { getChannelPluginsRepo } from '@/server/services/workshop/db/channel-plugins.repo'
|
|
10
|
+
import { getPluginHost, pluginManifest } from '@/server/services/workshop/plugins/host.mjs'
|
|
11
|
+
import { AppError, ErrorCodes } from '@/server/utils/errors'
|
|
12
|
+
|
|
13
|
+
export default defineApiHandler(async (event) => {
|
|
14
|
+
const user = resolveUser(event)
|
|
15
|
+
const id = String(event.context.params?.id ?? '')
|
|
16
|
+
const manager = getWorkshopManager()
|
|
17
|
+
// 归属校验:遗留公共(owner null)仅 admin;否则本人(getChannelForUser 同语义)
|
|
18
|
+
const channel = manager.getChannelForUser(id, user.id)
|
|
19
|
+
if (!channel && channel !== null) throw new AppError(404, ErrorCodes.NOT_FOUND, `channel 不存在: ${id}`)
|
|
20
|
+
if (channel?.ownerUserId === null) requireAdmin(event)
|
|
21
|
+
|
|
22
|
+
const host = getPluginHost()
|
|
23
|
+
const manifest = host ? pluginManifest().filter(p => p.enabled) : []
|
|
24
|
+
const explicit = getChannelPluginsRepo().explicitFor(id)
|
|
25
|
+
return {
|
|
26
|
+
plugins: manifest.map(p => ({
|
|
27
|
+
name: p.name,
|
|
28
|
+
description: p.description,
|
|
29
|
+
builtin: p.builtin,
|
|
30
|
+
enabled: explicit ? explicit.get(p.name) !== false : true,
|
|
31
|
+
})),
|
|
32
|
+
source: explicit ? 'explicit' : 'default',
|
|
33
|
+
isAdmin: isAdmin(user),
|
|
34
|
+
}
|
|
35
|
+
})
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PUT /api/workshop/channels/:id/plugins —— 写入该团队的插件开关(团队级独立控制)。
|
|
3
|
+
* body: { plugins: [{ name, enabled }] }(未知插件名忽略;全量替换语义)
|
|
4
|
+
* 生效:切换即写入 channel_plugins;该团队在跑 Agent 经工具热通知刷新清单(关闭的
|
|
5
|
+
* 插件工具从清单消失,dispatch 同源拒绝),防不需要插件的 channel 上下文被污染。
|
|
6
|
+
* 鉴权:channel 归属者(遗留公共 channel 需 admin)。
|
|
7
|
+
*/
|
|
8
|
+
import { readBody } from 'h3'
|
|
9
|
+
import { resolveUser, requireAdmin } from '../../../../workshop/caller'
|
|
10
|
+
import { defineApiHandler } from '@/server/utils/response'
|
|
11
|
+
import { getWorkshopManager } from '@/server/plugins/workshop'
|
|
12
|
+
import { getChannelPluginsRepo, type ChannelPluginToggle } from '@/server/services/workshop/db/channel-plugins.repo'
|
|
13
|
+
import { getPluginHost, pluginManifest } from '@/server/services/workshop/plugins/host.mjs'
|
|
14
|
+
import { notifyToolChange } from '@/server/services/workshop/agents/plugin-tools'
|
|
15
|
+
import { AppError } from '@/server/utils/errors'
|
|
16
|
+
|
|
17
|
+
export default defineApiHandler(async (event) => {
|
|
18
|
+
const user = resolveUser(event)
|
|
19
|
+
const id = String(event.context.params?.id ?? '')
|
|
20
|
+
const manager = getWorkshopManager()
|
|
21
|
+
const channel = manager.getChannelForUser(id, user.id)
|
|
22
|
+
if (channel?.ownerUserId === null) requireAdmin(event)
|
|
23
|
+
|
|
24
|
+
const body = (await readBody<{ plugins?: Array<{ name?: string, enabled?: boolean }> }>(event)) ?? {}
|
|
25
|
+
const entries = Array.isArray(body?.plugins) ? body.plugins : null
|
|
26
|
+
if (!entries) throw new AppError(400, 'VALIDATION_ERROR', 'body 应为 { plugins: [{ name, enabled }] }')
|
|
27
|
+
|
|
28
|
+
// 只接受已注册且启用的插件名(停用插件无工具可注入,写入无意义)
|
|
29
|
+
const host = getPluginHost()
|
|
30
|
+
const registered = new Set(host ? pluginManifest().filter(p => p.enabled).map(p => p.name) : [])
|
|
31
|
+
const clean: ChannelPluginToggle[] = entries
|
|
32
|
+
.filter((e): e is { name: string, enabled?: boolean } => Boolean(e) && typeof e.name === 'string' && registered.has(e.name as string))
|
|
33
|
+
.map(e => ({ name: e.name as string, enabled: e.enabled !== false }))
|
|
34
|
+
|
|
35
|
+
getChannelPluginsRepo().setMany(id, clean)
|
|
36
|
+
// 热通知:该团队(全部团队,轻量)在跑 agent 重发工具清单,关闭插件的工具即消失
|
|
37
|
+
notifyToolChange()
|
|
38
|
+
return { saved: clean.length, plugins: clean }
|
|
39
|
+
})
|
|
@@ -17,6 +17,13 @@ const createChannelSchema = z.object({
|
|
|
17
17
|
scenarioPrompt: z.string().optional(),
|
|
18
18
|
/** channel 独立工作目录(omp 子进程 cwd);缺省 data/workspaces/<channelId> */
|
|
19
19
|
workspace: z.string().optional(),
|
|
20
|
+
/** 团队级插件开关(创建时即写入;未选中的插件其工具不注入该团队 Agent) */
|
|
21
|
+
plugins: z
|
|
22
|
+
.array(z.object({
|
|
23
|
+
name: z.string().min(1),
|
|
24
|
+
enabled: z.boolean(),
|
|
25
|
+
}))
|
|
26
|
+
.optional(),
|
|
20
27
|
leadAgent: z
|
|
21
28
|
.object({
|
|
22
29
|
name: z.string().min(1, 'leadAgent.name 必填'),
|
|
@@ -38,6 +45,16 @@ export default defineApiHandler(async (event) => {
|
|
|
38
45
|
leadAgent: body.leadAgent,
|
|
39
46
|
ownerUserId: user.id,
|
|
40
47
|
})
|
|
48
|
+
// 团队级插件开关:创建时即写入(未选中的插件其工具不注入该团队 Agent)
|
|
49
|
+
if (Array.isArray(body.plugins) && body.plugins.length) {
|
|
50
|
+
const { getPluginHost, pluginManifest } = await import('@/server/services/workshop/plugins/host.mjs')
|
|
51
|
+
const registered = new Set(getPluginHost() ? pluginManifest().filter(p => p.enabled).map(p => p.name) : [])
|
|
52
|
+
const { getChannelPluginsRepo } = await import('@/server/services/workshop/db/channel-plugins.repo')
|
|
53
|
+
getChannelPluginsRepo().setMany(
|
|
54
|
+
result.channelId,
|
|
55
|
+
body.plugins.filter(p => p && typeof p.name === 'string' && registered.has(p.name)),
|
|
56
|
+
)
|
|
57
|
+
}
|
|
41
58
|
// 全时事件录制:新 channel 即时建立常驻流(server 驱动落库,与订阅者无关)
|
|
42
59
|
const { ensureStream } = await import('./ws')
|
|
43
60
|
ensureStream(manager, result.channelId)
|
|
@@ -1475,5 +1475,12 @@
|
|
|
1475
1475
|
"color": "#3aa0ff",
|
|
1476
1476
|
"description": "",
|
|
1477
1477
|
"createdAt": "2026-09-07T12:04:05.866+08:00"
|
|
1478
|
+
},
|
|
1479
|
+
{
|
|
1480
|
+
"id": "ln-84a78cd4",
|
|
1481
|
+
"name": "daq-cadence 线",
|
|
1482
|
+
"color": "#f4c542",
|
|
1483
|
+
"description": "",
|
|
1484
|
+
"createdAt": "2026-09-09T01:03:37.529+08:00"
|
|
1478
1485
|
}
|
|
1479
1486
|
]
|