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
package/scripts/i18n-merge.mjs
CHANGED
|
@@ -1,67 +1,189 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* 合并器:把 i18n/dicts/<ns>.json(zh)+ i18n/dicts/_en.json(zh→en)
|
|
3
3
|
* 注入 i18n/locales/zh-CN.ts 与 en.ts。
|
|
4
|
-
* -
|
|
5
|
-
*
|
|
4
|
+
* - dict 中的点号键('src.user'、'runtime.daq.x')展开为嵌套对象输出,
|
|
5
|
+
* 与 vue-i18n 的路径解析语义一致(resolveValue 按 '.' 分段逐层下钻)
|
|
6
|
+
* - ns 已存在 → 解析块内叶子路径,补插缺失键(整块重建,保持既有条目)
|
|
7
|
+
* - vue-i18n 特殊字符({}@$|)转义为字面量插值;{pN} 占位符保持原样
|
|
6
8
|
* - en 缺失 → 回退 zh(报告计数)
|
|
9
|
+
* - 键风格:合法标识符用裸键,其余用单引号(项目 eslint singlequote 风格)
|
|
7
10
|
*/
|
|
8
11
|
import fs from 'node:fs'
|
|
9
12
|
import path from 'node:path'
|
|
10
13
|
|
|
11
14
|
const esc = v => v
|
|
12
|
-
|
|
13
|
-
.replace(
|
|
14
|
-
.replace(/([@$|])/g, '{\'$1\'}')
|
|
15
|
+
.replace(/\{(?!p\d+\})/g, `{'{'}`)
|
|
16
|
+
.replace(/([@$|])/g, `{'$1'}`)
|
|
15
17
|
const enMap = JSON.parse(fs.readFileSync('i18n/dicts/_en.json', 'utf-8'))
|
|
16
18
|
let miss = 0
|
|
19
|
+
const norm = s => s.replace(/\s+/g, ' ').trim()
|
|
17
20
|
|
|
18
|
-
function
|
|
19
|
-
|
|
21
|
+
function valueFor(zh, isEn) {
|
|
22
|
+
if (!isEn) return zh
|
|
23
|
+
const hit = enMap[norm(zh)] ?? enMap[zh]
|
|
24
|
+
if (hit == null) {
|
|
25
|
+
miss++
|
|
26
|
+
return zh
|
|
27
|
+
}
|
|
28
|
+
return hit
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function quoteKey(k) {
|
|
32
|
+
return /^[A-Za-z0-9_$]+$/.test(k) ? k : `'${k.replace(/\\/g, '\\\\').replace(/'/g, '\\\'')}'`
|
|
33
|
+
}
|
|
34
|
+
function quoteVal(v) {
|
|
35
|
+
return `'${esc(v).replace(/\\/g, '\\\\').replace(/'/g, '\\\'').replace(/\r?\n/g, '\\n')}'`
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// dict → 树:点号键展开为嵌套分组
|
|
39
|
+
function toTree(dict, isEn) {
|
|
40
|
+
const root = { entries: [], groups: new Map() }
|
|
20
41
|
for (const [k, zh] of Object.entries(dict)) {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
42
|
+
const v = valueFor(zh, isEn)
|
|
43
|
+
const segs = k.split('.')
|
|
44
|
+
let node = root
|
|
45
|
+
for (let i = 0; i < segs.length - 1; i++) {
|
|
46
|
+
let g = node.groups.get(segs[i])
|
|
47
|
+
if (!g) {
|
|
48
|
+
g = { entries: [], groups: new Map() }
|
|
49
|
+
node.groups.set(segs[i], g)
|
|
28
50
|
}
|
|
29
|
-
|
|
51
|
+
node = g
|
|
52
|
+
}
|
|
53
|
+
// 同名键不重复插入;值在建树时统一引用化,emit 阶段只写原始字面量
|
|
54
|
+
if (!node.entries.some(e => e[0] === segs[segs.length - 1])) {
|
|
55
|
+
node.entries.push([segs[segs.length - 1], quoteVal(v)])
|
|
30
56
|
}
|
|
31
|
-
const keyLit = /^[A-Za-z0-9_$]+$/.test(k) ? k : JSON.stringify(k)
|
|
32
|
-
out.push(` ${keyLit}: '${esc(v).replace(/\\/g, '\\\\').replace(/'/g, '\\\'').replace(/\r?\n/g, '\\n')}',`)
|
|
33
57
|
}
|
|
58
|
+
return root
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function emitTree(tree, indent) {
|
|
62
|
+
const pad = ' '.repeat(indent)
|
|
63
|
+
const lines = []
|
|
64
|
+
for (const [k, v] of tree.entries) lines.push(`${pad}${quoteKey(k)}: ${v},`)
|
|
65
|
+
for (const [k, g] of tree.groups) {
|
|
66
|
+
lines.push(`${pad}${quoteKey(k)}: {`)
|
|
67
|
+
lines.push(...emitTree(g, indent + 2))
|
|
68
|
+
lines.push(`${pad}},`)
|
|
69
|
+
}
|
|
70
|
+
return lines
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// 解析既有 ns 块 → 有序树(值为已引用的原始字面量,保持原样)
|
|
74
|
+
// 返回 { tree, hadDottedEntry }:块内还存在扁平点号键时标记需重建
|
|
75
|
+
function parseBlockTree(lines) {
|
|
76
|
+
const mk = () => ({ entries: [], groups: new Map() })
|
|
77
|
+
const root = mk()
|
|
78
|
+
const stack = [{ indent: 0, node: root }]
|
|
79
|
+
let hadDottedEntry = false
|
|
80
|
+
for (const raw of lines) {
|
|
81
|
+
const indent = (raw.match(/^ */) || [''])[0].length - 4
|
|
82
|
+
const ln = raw.trim()
|
|
83
|
+
const closeM = ln.match(/^\},?$/)
|
|
84
|
+
if (closeM) {
|
|
85
|
+
while (stack.length > 1 && indent <= stack[stack.length - 1].indent) stack.pop()
|
|
86
|
+
continue
|
|
87
|
+
}
|
|
88
|
+
const openM = ln.match(/^(?:"([^"]+)"|'([^']+)'|([\w$]+)): \{$/)
|
|
89
|
+
const entryM = ln.match(/^(?:"([^"]+)"|'([^']+)'|([\w$]+)): (.*)$/)
|
|
90
|
+
if (openM) {
|
|
91
|
+
const name = openM[1] ?? openM[2] ?? openM[3]
|
|
92
|
+
let g = stack[stack.length - 1].node.groups.get(name)
|
|
93
|
+
if (!g) {
|
|
94
|
+
g = mk()
|
|
95
|
+
stack[stack.length - 1].node.groups.set(name, g)
|
|
96
|
+
}
|
|
97
|
+
stack.push({ indent, node: g })
|
|
98
|
+
}
|
|
99
|
+
else if (entryM) {
|
|
100
|
+
const name = entryM[1] ?? entryM[2] ?? entryM[3]
|
|
101
|
+
// 去掉行尾逗号,保留已引用的字面量原样
|
|
102
|
+
const val = entryM[4].replace(/,\s*$/, '')
|
|
103
|
+
const node = stack[stack.length - 1].node
|
|
104
|
+
if (name.includes('.')) {
|
|
105
|
+
// 旧版扁平点号键:展开为嵌套,记为重建信号
|
|
106
|
+
hadDottedEntry = true
|
|
107
|
+
const segs = name.split('.')
|
|
108
|
+
let cur = node
|
|
109
|
+
for (let i = 0; i < segs.length - 1; i++) {
|
|
110
|
+
let g = cur.groups.get(segs[i])
|
|
111
|
+
if (!g) {
|
|
112
|
+
g = mk()
|
|
113
|
+
cur.groups.set(segs[i], g)
|
|
114
|
+
}
|
|
115
|
+
cur = g
|
|
116
|
+
}
|
|
117
|
+
if (!cur.entries.some(e => e[0] === segs[segs.length - 1])) cur.entries.push([segs[segs.length - 1], val])
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
if (!node.entries.some(e => e[0] === name)) node.entries.push([name, val])
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return { tree: root, hadDottedEntry }
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// 树的全部叶子路径
|
|
128
|
+
function treeLeaves(tree, prefix = '') {
|
|
129
|
+
const out = new Set()
|
|
130
|
+
for (const [k] of tree.entries) out.add(prefix ? `${prefix}.${k}` : k)
|
|
131
|
+
for (const [k, g] of tree.groups) for (const p of treeLeaves(g, prefix ? `${prefix}.${k}` : k)) out.add(p)
|
|
34
132
|
return out
|
|
35
133
|
}
|
|
36
134
|
|
|
135
|
+
// 合并 dict 树缺失叶子到既有树(两侧值均为已引用化字面量)
|
|
136
|
+
function mergeTree(existing, dictTree) {
|
|
137
|
+
let added = 0
|
|
138
|
+
for (const [k, v] of dictTree.entries) {
|
|
139
|
+
if (!existing.entries.some(e => e[0] === k)) {
|
|
140
|
+
existing.entries.push([k, v])
|
|
141
|
+
added++
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
for (const [k, g] of dictTree.groups) {
|
|
145
|
+
let sub = existing.groups.get(k)
|
|
146
|
+
if (!sub) {
|
|
147
|
+
sub = { entries: [], groups: new Map() }
|
|
148
|
+
existing.groups.set(k, sub)
|
|
149
|
+
}
|
|
150
|
+
added += mergeTree(sub, g)
|
|
151
|
+
}
|
|
152
|
+
return added
|
|
153
|
+
}
|
|
154
|
+
|
|
37
155
|
for (const [localeFile, isEn] of [['i18n/locales/zh-CN.ts', false], ['i18n/locales/en.ts', true]]) {
|
|
38
156
|
let src = fs.readFileSync(localeFile, 'utf-8')
|
|
39
157
|
let insertedTotal = 0
|
|
40
158
|
for (const f of fs.readdirSync('i18n/dicts').filter(x => x.endsWith('.json') && !x.startsWith('_'))) {
|
|
41
159
|
const ns = f.replace('.json', '')
|
|
42
160
|
const dict = JSON.parse(fs.readFileSync(path.join('i18n/dicts', f), 'utf-8'))
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
const nsRe = new RegExp(`^ ${ns}: \\{`, 'm')
|
|
161
|
+
if (Object.keys(dict).length === 0) continue
|
|
162
|
+
const dictTree = toTree(dict, isEn)
|
|
163
|
+
const nsRe = new RegExp(`^ (?:${JSON.stringify(ns)}|'${ns}'|${ns}): \\{`, 'm')
|
|
46
164
|
const m = src.match(nsRe)
|
|
47
165
|
if (m) {
|
|
48
|
-
// 已有 ns
|
|
166
|
+
// 已有 ns:解析块 → 检查叶子 → 有缺失或存在旧扁平点号键时整块重建
|
|
49
167
|
const start = m.index + m[0].length
|
|
50
168
|
const end = src.indexOf('\n },', start)
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
169
|
+
const blockLines = src.slice(start, end).split('\n').filter(l => l.trim() !== '')
|
|
170
|
+
const { tree: existingTree, hadDottedEntry } = parseBlockTree(blockLines)
|
|
171
|
+
const existingLeaves = treeLeaves(existingTree)
|
|
172
|
+
const wantLeaves = treeLeaves(dictTree)
|
|
173
|
+
const missing = [...wantLeaves].filter(l => !existingLeaves.has(l)).length
|
|
174
|
+
if (missing > 0 || hadDottedEntry) {
|
|
175
|
+
const added = mergeTree(existingTree, dictTree, isEn)
|
|
176
|
+
const rebuilt = [''].concat(emitTree(existingTree, 4))
|
|
177
|
+
src = src.slice(0, start) + rebuilt.join('\n') + src.slice(end)
|
|
178
|
+
insertedTotal += added
|
|
57
179
|
}
|
|
58
180
|
}
|
|
59
181
|
else {
|
|
60
|
-
// 新 ns:插到文件末尾闭合前
|
|
182
|
+
// 新 ns:插到文件末尾闭合前(ns 名非合法标识符时加引号,如 team-plugins)
|
|
61
183
|
const tail = src.lastIndexOf('}')
|
|
62
|
-
const block = ` ${ns}: {\n${
|
|
184
|
+
const block = ` ${quoteKey(ns)}: {\n${emitTree(dictTree, 4).join('\n')}\n },\n`
|
|
63
185
|
src = src.slice(0, tail) + block + src.slice(tail)
|
|
64
|
-
insertedTotal +=
|
|
186
|
+
insertedTotal += treeLeaves(dictTree).size
|
|
65
187
|
}
|
|
66
188
|
}
|
|
67
189
|
fs.writeFileSync(localeFile, src)
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 生产环境增强闭环 e2e — 数采/数控 × 自动诊断 × 知识沉淀(AgentWorkShop 3001 生产实例)
|
|
3
|
+
*
|
|
4
|
+
* 场景(对应「闭环控制增强」验收):
|
|
5
|
+
* A 生产就绪 3001 生产实例 + 两桥接插件启用 + 外部服务(KB/诊断/RAG)健康
|
|
6
|
+
* B 数采+绑定 产线节点可见、daq 绑定、daq_query/快照导出取到实时数据
|
|
7
|
+
* C 事件自动诊断 kv 配置越限规则 → daq:sample 命中 → 插件自动发起深度诊断(source=auto)
|
|
8
|
+
* → 关闭开关(避免持续触发);在跑诊断由轮询器继续跟踪
|
|
9
|
+
* D 知识辅助决策 kb_search 历史经验 → dcw_control 优化设定(HITL 批准)→
|
|
10
|
+
* dcw_judge 落 keep 判定 → kb_store 优化方案入库 → kb_search 复核
|
|
11
|
+
* E 沉淀验证 rag-bridge /experience 与 two-stage 检索确认知识可查
|
|
12
|
+
*
|
|
13
|
+
* 运行:AW_BASE=http://localhost:3001 node scripts/production-closed-loop-e2e.mjs
|
|
14
|
+
* 前置:scripts/three-system-e2e.mjs 已跑过(演示账号/团队/绑定/授权已就绪)。
|
|
15
|
+
*/
|
|
16
|
+
import { DatabaseSync } from 'node:sqlite'
|
|
17
|
+
import { readFileSync, writeFileSync, renameSync } from 'node:fs'
|
|
18
|
+
import { fileURLToPath } from 'node:url'
|
|
19
|
+
import { dirname, join, resolve } from 'node:path'
|
|
20
|
+
|
|
21
|
+
const __dirname = dirname(fileURLToPath(import.meta.url))
|
|
22
|
+
const ROOT = resolve(__dirname, '..')
|
|
23
|
+
const BASE = process.env.AW_BASE ?? 'http://localhost:3001'
|
|
24
|
+
const KB = process.env.KB_BASE ?? 'http://127.0.0.1:8770'
|
|
25
|
+
const DIAG = process.env.DIAG_BASE ?? 'http://127.0.0.1:3210'
|
|
26
|
+
const LINE = process.env.E2E_LINE ?? 'ln-af002514'
|
|
27
|
+
const DCW_NODE = process.env.E2E_DCW_NODE ?? 'dw-e92bb0e7'
|
|
28
|
+
|
|
29
|
+
let pass = 0
|
|
30
|
+
let fail = 0
|
|
31
|
+
const failures = []
|
|
32
|
+
function ok(cond, label, detail = '') {
|
|
33
|
+
if (cond) {
|
|
34
|
+
pass++
|
|
35
|
+
console.log(` ✔ ${label}${detail ? ` —— ${detail}` : ''}`)
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
fail++
|
|
39
|
+
failures.push(label)
|
|
40
|
+
console.error(` ✘ ${label}${detail ? ` —— ${detail}` : ''}`)
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
const sleep = ms => new Promise(r => setTimeout(r, ms))
|
|
44
|
+
|
|
45
|
+
async function raw(method, url, { body, token, agent, timeoutMs = 20000 } = {}) {
|
|
46
|
+
const headers = {}
|
|
47
|
+
if (body !== undefined) headers['content-type'] = 'application/json'
|
|
48
|
+
if (token) headers.authorization = `Bearer ${token}`
|
|
49
|
+
if (agent) headers['x-aw-agent-token'] = agent.token
|
|
50
|
+
const res = await fetch(url, { method, headers, body: body !== undefined ? JSON.stringify(body) : undefined, signal: AbortSignal.timeout(timeoutMs) })
|
|
51
|
+
return { status: res.status, json: await res.json().catch(() => null) }
|
|
52
|
+
}
|
|
53
|
+
const api = (method, path, opts = {}) => raw(method, `${BASE}${path}`, opts)
|
|
54
|
+
const resultText = r => String(r?.json?.data?.result?.text ?? '')
|
|
55
|
+
const invoke = (agent, tool, args, timeoutMs = 60000) => api('POST', '/api/workshop/agent-tools/invoke', { agent, timeoutMs, body: { agentId: agent.id, tool, args } })
|
|
56
|
+
|
|
57
|
+
// ── 0. 演示账号/团队(复用 three-system-e2e 的持久化状态)─────────────────
|
|
58
|
+
const tokenFile = join(ROOT, '.AgentWorkShop', 'data', '.three-system-e2e-token.json')
|
|
59
|
+
const saved = JSON.parse(readFileSync(tokenFile, 'utf8'))
|
|
60
|
+
const userToken = process.env.AW_E2E_TOKEN ?? saved.token
|
|
61
|
+
const userId = saved.uid ?? ''
|
|
62
|
+
ok(Boolean(userToken), '复用演示账号 token')
|
|
63
|
+
// 夹具:确保产线 operate 授权(users.sqlite 直插,测试专用)
|
|
64
|
+
{
|
|
65
|
+
const db = new DatabaseSync(join(ROOT, '.AgentWorkShop', 'data', 'users.sqlite'))
|
|
66
|
+
db.exec('PRAGMA busy_timeout=4000')
|
|
67
|
+
const row = userId ? { id: userId } : db.prepare('SELECT id FROM users ORDER BY created_at DESC LIMIT 1').get()
|
|
68
|
+
if (row?.id) {
|
|
69
|
+
db.prepare(`INSERT OR REPLACE INTO user_line_grants (user_id, line_id, mode, granted_by, granted_at) VALUES (?, ?, 'operate', 'production-e2e', ?)`).run(row.id, LINE, new Date().toISOString())
|
|
70
|
+
console.log(` · 产线授权夹具就绪(user=${row.id})`)
|
|
71
|
+
}
|
|
72
|
+
db.close()
|
|
73
|
+
}
|
|
74
|
+
const chsRaw = await api('GET', '/api/workshop/channels', { token: userToken }).catch(() => null)
|
|
75
|
+
const chs = Array.isArray(chsRaw?.json?.data) ? chsRaw.json.data : []
|
|
76
|
+
const chA = chs.find(c => c?.name?.startsWith('产线数据分析组'))
|
|
77
|
+
const chB = chs.find(c => c?.name?.startsWith('闭环控制组'))
|
|
78
|
+
const membersOf = async id => (await api('GET', `/api/workshop/channels/${id}/agents`, { token: userToken })).json?.data ?? []
|
|
79
|
+
const mA = await membersOf(chA.id)
|
|
80
|
+
const mB = await membersOf(chB.id)
|
|
81
|
+
const execA = mA.find(m => m?.name === '数据分析执行器')
|
|
82
|
+
const execB = mB.find(m => m?.name === '控制执行器')
|
|
83
|
+
ok(Boolean(execA?.token && execB?.token), '两个 omp 执行器就绪', `${execA?.name} / ${execB?.name}`)
|
|
84
|
+
|
|
85
|
+
// ══ Stage A:生产就绪 ══════════════════════════════════════════════════════
|
|
86
|
+
console.log('\n── Stage A 生产就绪(3001)──')
|
|
87
|
+
{
|
|
88
|
+
const gate = await raw('GET', `${BASE}/api/workshop/plugins`)
|
|
89
|
+
ok(gate.status === 401, '生产实例存活(无 token 401 信封)', `status=${gate.status}`)
|
|
90
|
+
const plugs = await api('GET', '/api/workshop/plugins', { token: userToken })
|
|
91
|
+
const list = plugs.json?.plugins ?? plugs.json?.data?.plugins ?? []
|
|
92
|
+
const on = n => list.find(p => p?.name === n)?.enabled === true
|
|
93
|
+
ok(on('rag-bridge') && on('diag-bridge'), '两桥接插件在生产实例启用', `count=${list.length}`)
|
|
94
|
+
const ragH = await api('GET', '/api/plugins/rag-bridge/health', { token: userToken })
|
|
95
|
+
ok(ragH.status === 200 && ragH.json?.outbound === true, 'rag-bridge 健康', `kb=${ragH.json?.kb?.id?.slice(0, 8)}`)
|
|
96
|
+
const diagH = await api('GET', '/api/plugins/diag-bridge/health', { token: userToken })
|
|
97
|
+
ok(diagH.status === 200 && diagH.json?.remote?.status === 'ok', 'diag-bridge 健康', `activeRuns=${diagH.json?.remote?.activeRuns}`)
|
|
98
|
+
const kbH = await raw('GET', `${KB}/api/v1/health`)
|
|
99
|
+
const dgH = await raw('GET', `${DIAG}/api/health`)
|
|
100
|
+
ok(kbH.json?.status === 'healthy' && dgH.json?.status === 'ok', 'KB 8770 / 诊断 3210 外部服务健康')
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// ══ Stage B:数采 + 绑定 ════════════════════════════════════════════════════
|
|
104
|
+
console.log('\n── Stage B 数采与绑定 ──')
|
|
105
|
+
let freshNode = ''
|
|
106
|
+
{
|
|
107
|
+
const daq = await api('GET', '/api/workshop/daq', { token: userToken })
|
|
108
|
+
const nodes = (daq.json?.data?.nodes ?? daq.json?.nodes ?? []).filter(n => n?.lineId === LINE)
|
|
109
|
+
ok(nodes.length > 0, `产线 ${LINE} 数采节点可见`, `count=${nodes.length}`)
|
|
110
|
+
// 选最近有样本的节点(逐节点探 60min 窗)
|
|
111
|
+
let bestAt = 0
|
|
112
|
+
for (const n of nodes.slice(0, 12)) {
|
|
113
|
+
const s = await api('GET', `/api/workshop/daq/${n.id}/samples?last_minutes=60&limit=1`, { token: userToken }).catch(() => null)
|
|
114
|
+
const at = s?.json?.data?.points?.[0]?.at ?? 0
|
|
115
|
+
if (at > bestAt) {
|
|
116
|
+
bestAt = at
|
|
117
|
+
freshNode = n.id
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
ok(Boolean(freshNode), '定位最近在采样的节点', `${freshNode} (lastAt=${bestAt ? new Date(bestAt).toISOString() : 'n/a'})`)
|
|
121
|
+
|
|
122
|
+
const bind = await api('POST', '/api/workshop/agent-tools/bindings', {
|
|
123
|
+
token: userToken,
|
|
124
|
+
body: { agentId: execA.id, nodeId: freshNode, kind: 'daq', mode: 'auto' },
|
|
125
|
+
})
|
|
126
|
+
ok(bind.status === 200, '分析执行器绑定数采节点(daq)', bind.status === 200 ? 'ok' : JSON.stringify(bind.json).slice(0, 120))
|
|
127
|
+
|
|
128
|
+
const snap = await api('POST', '/api/plugins/diag-bridge/snapshot', {
|
|
129
|
+
token: userToken,
|
|
130
|
+
body: { line: LINE, from_ms: Date.now() - 24 * 60 * 60_000, to_ms: Date.now() },
|
|
131
|
+
})
|
|
132
|
+
ok(snap.json?.success === true && snap.json?.rows > 0, '生产实例导出 DAQ 宽表快照', `rows=${snap.json?.rows} nodes=${snap.json?.nodes}`)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// ══ Stage C:事件自动诊断(daq:sample → 自动 diag_run)══════════════════════
|
|
136
|
+
console.log('\n── Stage C 事件驱动自动诊断 ──')
|
|
137
|
+
let autoRunId = ''
|
|
138
|
+
{
|
|
139
|
+
const kvPath = join(ROOT, '.AgentWorkShop', 'data', 'plugins', 'diag-bridge', 'kv.json')
|
|
140
|
+
const statePath = join(process.env.USERPROFILE ?? process.env.HOME ?? '', '.AgentWorkShop', 'plugins-state.json')
|
|
141
|
+
const touchReload = () => {
|
|
142
|
+
const st = JSON.parse(readFileSync(statePath, 'utf8'))
|
|
143
|
+
st.updatedAt = new Date().toISOString()
|
|
144
|
+
writeFileSync(`${statePath}.tmp`, JSON.stringify(st, null, 2))
|
|
145
|
+
renameSync(`${statePath}.tmp`, statePath)
|
|
146
|
+
}
|
|
147
|
+
const kv = JSON.parse(readFileSync(kvPath, 'utf8'))
|
|
148
|
+
kv.auto_diag_enabled = 'true'
|
|
149
|
+
kv.auto_rules = JSON.stringify({ [freshNode]: { op: 'gt', value: 0 } }) // 阈值 0 → 下一拍样本必命中(确定性触发)
|
|
150
|
+
writeFileSync(kvPath, JSON.stringify(kv, null, 2))
|
|
151
|
+
touchReload()
|
|
152
|
+
console.log(' · 已启用 auto_diag(规则:节点 ' + freshNode + ' > 0)并触发热重载')
|
|
153
|
+
|
|
154
|
+
for (let i = 0; i < 24 && !autoRunId; i++) {
|
|
155
|
+
await sleep(5000)
|
|
156
|
+
const runs = await api('GET', '/api/plugins/diag-bridge/runs', { token: userToken })
|
|
157
|
+
const hit = (runs.json?.runs ?? []).find(x => x.source === 'auto' && x.status === 'running' && !x.runId?.startsWith('f2ce66b4'))
|
|
158
|
+
if (hit) autoRunId = hit.runId
|
|
159
|
+
}
|
|
160
|
+
ok(Boolean(autoRunId), 'daq:sample 越限命中 → 插件自动发起深度诊断(source=auto)', `runId=${autoRunId}`)
|
|
161
|
+
|
|
162
|
+
// 关闭开关(避免持续触发);在跑诊断由轮询器重水化继续跟踪
|
|
163
|
+
const kv2 = JSON.parse(readFileSync(kvPath, 'utf8'))
|
|
164
|
+
kv2.auto_diag_enabled = 'false'
|
|
165
|
+
writeFileSync(kvPath, JSON.stringify(kv2, null, 2))
|
|
166
|
+
touchReload()
|
|
167
|
+
console.log(' · auto_diag 已关闭(冷却+去重仍在,防重复触发)')
|
|
168
|
+
const runs2 = await api('GET', '/api/plugins/diag-bridge/runs', { token: userToken })
|
|
169
|
+
const still = (runs2.json?.runs ?? []).find(x => x.runId === autoRunId)
|
|
170
|
+
ok(still?.status === 'running' || still?.status === 'completed', '热重载后轮询器重水化,自动诊断仍被跟踪', still?.status)
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// ══ Stage D:知识辅助决策 + 优化闭环 + 方案入库 ═════════════════════════════
|
|
174
|
+
console.log('\n── Stage D 知识辅助决策与优化闭环 ──')
|
|
175
|
+
{
|
|
176
|
+
const hist = await invoke(execA, 'kb_search', { query: '压力设定器 优化 经验 判定 keep' })
|
|
177
|
+
ok(resultText(hist).startsWith('检索到'), '知识辅助:检索到历史经验/文档', resultText(hist).split('\n')[0].slice(0, 80))
|
|
178
|
+
|
|
179
|
+
const dcwView = await api('GET', '/api/workshop/dcw', { token: userToken })
|
|
180
|
+
const node = (dcwView.json?.data?.nodes ?? []).find(n => n?.id === DCW_NODE)
|
|
181
|
+
ok(Boolean(node), '数控节点可见', `value=${node?.value} range=${node?.min}~${node?.max}`)
|
|
182
|
+
const span = Number(node?.max) - Number(node?.min)
|
|
183
|
+
const step = span > 0 ? Math.min(span * 0.03, 1) : 0.05
|
|
184
|
+
let target = Number(node?.value) + step
|
|
185
|
+
if (target > Number(node?.max) - span * 0.05) target = Number(node?.value) - step
|
|
186
|
+
|
|
187
|
+
// 发起写控(manual → HITL 挂起),脚本扮演人类在待办出现后批准
|
|
188
|
+
const pendingInvoke = invoke(execB, 'dcw_control', {
|
|
189
|
+
node_id: DCW_NODE,
|
|
190
|
+
value: Number(target.toFixed(4)),
|
|
191
|
+
hypothesis: `依据知识库历史经验(${resultText(hist).split('\n')[1]?.slice(0, 60) ?? '历史经验'})与数采趋势,优化设定至 ${target.toFixed(4)}`,
|
|
192
|
+
}, 300000)
|
|
193
|
+
let approved = false
|
|
194
|
+
let pendRaw = '[]'
|
|
195
|
+
for (let i = 0; i < 30 && !approved; i++) {
|
|
196
|
+
await sleep(2000)
|
|
197
|
+
const pend = await api('GET', '/api/workshop/hitl/pending', { token: userToken })
|
|
198
|
+
const pd = pend?.json?.data
|
|
199
|
+
const arr = Array.isArray(pd) ? pd : (pd?.items ?? pd?.pending ?? [])
|
|
200
|
+
pendRaw = JSON.stringify(arr)
|
|
201
|
+
const item = arr.find(x => x?.kind === 'dcw-approval' && x?.agentId === execB.id)
|
|
202
|
+
if (item?.id) {
|
|
203
|
+
const resp = await api('POST', '/api/workshop/hitl/respond', {
|
|
204
|
+
token: userToken,
|
|
205
|
+
body: { kind: 'dcw-approval', id: item.id, confirmed: true, comment: 'production-e2e 批准' },
|
|
206
|
+
})
|
|
207
|
+
approved = resp.status === 200
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
const w = await pendingInvoke
|
|
211
|
+
const wText = resultText(w)
|
|
212
|
+
ok(approved, 'HITL 待办出现并经 respond 批准', approved ? 'ok' : `pending=${pendRaw.slice(0, 160)}`)
|
|
213
|
+
ok(wText.includes('下发成功'), '优化设定经 HITL 批准并写回成功', wText.split('\n')[0].slice(0, 110))
|
|
214
|
+
const recordId = (wText.match(/优化记录 (\S+) 已开窗/) ?? [])[1] ?? ''
|
|
215
|
+
|
|
216
|
+
if (recordId) {
|
|
217
|
+
const j = await invoke(execB, 'dcw_judge', {
|
|
218
|
+
record_id: recordId,
|
|
219
|
+
verdict: 'keep',
|
|
220
|
+
reason: `写入后 ${freshNode} 数采回读与设定一致、无越配方告警,判定 keep(production-e2e)`,
|
|
221
|
+
})
|
|
222
|
+
ok(resultText(j).includes('keep') || !resultText(j).includes('失败'), 'dcw_judge 落 keep 判定(经验固化)', resultText(j).split('\n')[0].slice(0, 100))
|
|
223
|
+
|
|
224
|
+
const store = await invoke(execA, 'kb_store', {
|
|
225
|
+
title: `优化方案:压力设定器 ${node?.value}→${target.toFixed(4)}(判定 keep)`,
|
|
226
|
+
category: 'optimization',
|
|
227
|
+
problem: '压力设定偏离经验最优区,需要在不触发越限的前提下上调设定',
|
|
228
|
+
solution: `按配方窗口内 ${step.toFixed(4)} 步进上调;写入后观察 ${freshNode} 回读稳定,dcw_judge=keep(记录 ${recordId}),可标 lastGood`,
|
|
229
|
+
key_lessons: ['小步进+回读确认再判定', 'keep 判定须引用数采证据'],
|
|
230
|
+
tags: [LINE, '闭环优化', `record:${recordId}`, 'production-e2e'],
|
|
231
|
+
})
|
|
232
|
+
ok(resultText(store).includes('已沉淀'), '优化方案写入知识库(经验沉淀)', resultText(store).slice(0, 90))
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
const s2 = await invoke(execA, 'kb_search', { query: `压力设定器 优化方案 keep ${LINE}` })
|
|
236
|
+
ok(resultText(s2).startsWith('检索到'), 'kb_search 复核:优化方案可检索', resultText(s2).split('\n')[1]?.slice(0, 100) ?? resultText(s2).slice(0, 100))
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// ══ Stage E:沉淀验证 ══════════════════════════════════════════════════════
|
|
240
|
+
console.log('\n── Stage E 知识沉淀验证 ──')
|
|
241
|
+
{
|
|
242
|
+
const exp = await api('GET', '/api/plugins/rag-bridge/experience?limit=50', { token: userToken })
|
|
243
|
+
const count = exp.json?.count ?? 0
|
|
244
|
+
const hasOpt = JSON.stringify(exp.json?.experiences ?? []).includes('optimization')
|
|
245
|
+
ok(count > 0, '经验库可读', `count=${count}`)
|
|
246
|
+
ok(hasOpt, '经验库含 optimization 类目(优化方案在册)')
|
|
247
|
+
|
|
248
|
+
const s3 = await api('GET', `/api/plugins/rag-bridge/search?q=${encodeURIComponent('闭环优化 设定 判定')}&top_k=3`, { token: userToken })
|
|
249
|
+
ok(s3.json?.success === true && (s3.json?.results?.length ?? 0) > 0, 'two-stage 检索命中闭环优化知识', `results=${s3.json?.results?.length}`)
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
console.log(`\n══ 生产增强闭环结果:${pass} 通过 / ${fail} 失败 ══`)
|
|
253
|
+
if (failures.length) for (const f of failures) console.error(` - ${f}`)
|
|
254
|
+
process.exit(fail > 0 ? 1 : 0)
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 团队级插件开关 + 内置插件 + 配置化 API — 轻量 HTTP 冒烟(AgentWorkShop 3001)。
|
|
3
|
+
* 覆盖:内置识别 / 建队带 plugins / 团队插件 GET+PUT / 工具清单团队过滤 /
|
|
4
|
+
* dispatch 拒绝 / 系统配置 plugins 组读写与热更新 / 插件健康检测。
|
|
5
|
+
* 无真实诊断、无 LLM 会话消耗。
|
|
6
|
+
*/
|
|
7
|
+
import { readFileSync } from 'node:fs'
|
|
8
|
+
import { DatabaseSync } from 'node:sqlite'
|
|
9
|
+
|
|
10
|
+
const BASE = process.env.AW_BASE ?? 'http://localhost:3001'
|
|
11
|
+
const saved = JSON.parse(readFileSync(new URL('../.AgentWorkShop/data/.three-system-e2e-token.json', import.meta.url), 'utf8'))
|
|
12
|
+
const T = process.env.AW_E2E_TOKEN ?? saved.token
|
|
13
|
+
|
|
14
|
+
// admin 夹具:settings API 需要 admin/editor;临时把 e2e 夹具用户全升为 editor,结束还原
|
|
15
|
+
const db = new DatabaseSync(new URL('../.AgentWorkShop/data/users.sqlite', import.meta.url))
|
|
16
|
+
db.exec('PRAGMA busy_timeout=4000')
|
|
17
|
+
const fixtureIds = (db.prepare('SELECT id FROM users WHERE name = \'three-system-demo\' OR name LIKE \'e2e3sys-%\'').all() ?? [])
|
|
18
|
+
.map(r => r.id)
|
|
19
|
+
const upgraded = []
|
|
20
|
+
for (const id of fixtureIds) {
|
|
21
|
+
const role = db.prepare('SELECT role FROM users WHERE id = ?').get(id)?.role
|
|
22
|
+
if (role && role !== 'admin') {
|
|
23
|
+
upgraded.push({ id, role })
|
|
24
|
+
db.prepare('UPDATE users SET role = ? WHERE id = ?').run('admin', id)
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
let pass = 0
|
|
29
|
+
let fail = 0
|
|
30
|
+
const ok = (cond, label, detail = '') => {
|
|
31
|
+
if (cond) {
|
|
32
|
+
pass++
|
|
33
|
+
console.log(` ✔ ${label}${detail ? ` —— ${detail}` : ''}`)
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
fail++
|
|
37
|
+
console.error(` ✘ ${label}${detail ? ` —— ${detail}` : ''}`)
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
const sleep = ms => new Promise(r => setTimeout(r, ms))
|
|
41
|
+
async function api(method, path, { body, token, timeoutMs = 20000 } = {}) {
|
|
42
|
+
const headers = {}
|
|
43
|
+
if (body !== undefined) headers['content-type'] = 'application/json'
|
|
44
|
+
if (token) headers.authorization = `Bearer ${token}`
|
|
45
|
+
const res = await fetch(`${BASE}${path}`, { method, headers, body: body !== undefined ? JSON.stringify(body) : undefined, signal: AbortSignal.timeout(timeoutMs) })
|
|
46
|
+
return { status: res.status, json: await res.json().catch(() => null) }
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
console.log('\n══ 团队插件开关 + 内置插件 + 配置化 API 冒烟 ══')
|
|
50
|
+
// 1) 内置识别
|
|
51
|
+
const plugs = await api('GET', '/api/workshop/plugins', { token: T })
|
|
52
|
+
const list = plugs.json?.plugins ?? plugs.json?.data?.plugins ?? []
|
|
53
|
+
const bridges = list.filter(p => ['rag-bridge', 'diag-bridge'].includes(p?.name))
|
|
54
|
+
ok(list.length > 0, '插件清单可读', `count=${list.length}`)
|
|
55
|
+
ok(bridges.length === 2 && bridges.every(p => p.builtin && p.enabled && p.scope === 'builtin'), '两桥接插件以内置作用域识别且启用', bridges.map(p => `${p.name}(${p.scope})`).join(','))
|
|
56
|
+
|
|
57
|
+
// 2) 健康检测
|
|
58
|
+
const ragH = await api('GET', '/api/plugins/rag-bridge/health', { token: T })
|
|
59
|
+
ok(ragH.status === 200 && ragH.json?.backend?.ok === true && ragH.json?.web?.ok === true, 'rag-bridge 健康检测(backend+web)', `kb=${ragH.json?.kb?.name}`)
|
|
60
|
+
const dgH = await api('GET', '/api/plugins/diag-bridge/health', { token: T })
|
|
61
|
+
ok(dgH.status === 200 && dgH.json?.remote?.status === 'ok', 'diag-bridge 健康检测(3210 可达)', `activeRuns=${dgH.json?.remote?.activeRuns}`)
|
|
62
|
+
|
|
63
|
+
// 3) 系统配置 plugins 组(读 + 写 + 热更新)
|
|
64
|
+
const cfg = await api('GET', '/api/system/settings', { token: T })
|
|
65
|
+
const eff = cfg.json?.data?.effective ?? cfg.json?.effective ?? {}
|
|
66
|
+
ok(eff['plugins.kb.base_url'] === 'http://127.0.0.1:8770' && eff['plugins.diag.base_url'] === 'http://127.0.0.1:3210', '系统配置 plugins 组可读(schema 驱动)', `kb=${eff['plugins.kb.base_url']} diag=${eff['plugins.diag.base_url']}`)
|
|
67
|
+
const patch = await api('PATCH', '/api/system/settings', { token: T, body: { override: { 'plugins.kb.web_url': 'http://127.0.0.1:6789' } } })
|
|
68
|
+
const pRes = patch.json?.data ?? patch.json ?? {}
|
|
69
|
+
ok(pRes.ok === true, 'plugins 配置项可写(热生效)', `changed=${(pRes.changed ?? []).join(',') || '-'}`)
|
|
70
|
+
|
|
71
|
+
// 4) 建队带 plugins(默认全启用)与关闭插件团队
|
|
72
|
+
const nameA = `冒烟-全启用-${Date.now().toString(36)}`
|
|
73
|
+
const cA = await api('POST', '/api/workshop/channels', {
|
|
74
|
+
token: T,
|
|
75
|
+
body: { name: nameA, leadAgent: { name: '冒烟组长', harness: 'mock' } },
|
|
76
|
+
})
|
|
77
|
+
const chA = cA.json?.data?.channelId
|
|
78
|
+
ok(Boolean(chA), '建队(未传 plugins → 默认全启用)')
|
|
79
|
+
const nameB = `冒烟-关闭KB-${Date.now().toString(36)}`
|
|
80
|
+
const cB = await api('POST', '/api/workshop/channels', {
|
|
81
|
+
token: T,
|
|
82
|
+
body: {
|
|
83
|
+
name: nameB,
|
|
84
|
+
leadAgent: { name: '冒烟组长B', harness: 'mock' },
|
|
85
|
+
plugins: [{ name: 'rag-bridge', enabled: false }, { name: 'diag-bridge', enabled: true }],
|
|
86
|
+
},
|
|
87
|
+
})
|
|
88
|
+
const chB = cB.json?.data?.channelId
|
|
89
|
+
ok(Boolean(chB), '建队带 plugins 勾选(rag-bridge 关闭)')
|
|
90
|
+
|
|
91
|
+
// 5) 团队插件视图
|
|
92
|
+
const viewA = await api('GET', `/api/workshop/channels/${chA}/plugins`, { token: T })
|
|
93
|
+
ok(viewA.json?.data?.source === 'default' && viewA.json?.data?.plugins?.every(p => p.enabled), 'A 团队视图:source=default 全启用', `count=${viewA.json?.data?.plugins?.length}`)
|
|
94
|
+
const viewB = await api('GET', `/api/workshop/channels/${chB}/plugins`, { token: T })
|
|
95
|
+
const kbView = (viewB.json?.data?.plugins ?? []).find(p => p.name === 'rag-bridge')
|
|
96
|
+
const dgView = (viewB.json?.data?.plugins ?? []).find(p => p.name === 'diag-bridge')
|
|
97
|
+
ok(viewB.json?.data?.source === 'explicit' && kbView?.enabled === false && dgView?.enabled === true, 'B 团队视图:显式配置(rag 关/diag 开)', `source=${viewB.json?.data?.source}`)
|
|
98
|
+
|
|
99
|
+
// 工具直调用 omp 执行器(mock lead 无 dispatch 面);建队后补执行器成员
|
|
100
|
+
const mkExec = async (ch, name) => {
|
|
101
|
+
const members = (await api('GET', `/api/workshop/channels/${ch}/agents`, { token: T })).json?.data ?? []
|
|
102
|
+
if (!members.some(m => m?.name === name)) {
|
|
103
|
+
await api('POST', `/api/workshop/channels/${ch}/agents`, { token: T, body: { name, harness: 'omp', role: 'worker' } })
|
|
104
|
+
}
|
|
105
|
+
return ((await api('GET', `/api/workshop/channels/${ch}/agents`, { token: T })).json?.data ?? []).find(m => m?.name === name)
|
|
106
|
+
}
|
|
107
|
+
const execA = await mkExec(chA, '冒烟执行器A')
|
|
108
|
+
const execB = await mkExec(chB, '冒烟执行器B')
|
|
109
|
+
|
|
110
|
+
// 6) 工具清单团队过滤(A 团队执行器含 kb_*;B 团队执行器无 kb_*)
|
|
111
|
+
const toolsA = (await api('GET', `/api/workshop/agent-tools/list?agentId=${execA.id}`, { token: T })).json?.data?.tools ?? []
|
|
112
|
+
const toolsB = (await api('GET', `/api/workshop/agent-tools/list?agentId=${execB.id}`, { token: T })).json?.data?.tools ?? []
|
|
113
|
+
ok(toolsA.some(t => t.name === 'kb_search') && toolsA.some(t => t.name === 'diag_run'), 'A 团队工具清单含 kb_*/diag_*(插件工具注入)', `total=${toolsA.length}`)
|
|
114
|
+
ok(!toolsB.some(t => t.name === 'kb_search') && !toolsB.some(t => t.name === 'kb_store'), 'B 团队工具清单无 kb_*(关闭插件不注入,防上下文污染)', `total=${toolsB.length}`)
|
|
115
|
+
ok(toolsB.some(t => t.name === 'diag_run'), 'B 团队 diag_* 仍在(diag-bridge 开启)')
|
|
116
|
+
|
|
117
|
+
// 7) dispatch 拒绝:B 团队调 kb_search → isError
|
|
118
|
+
const invB = await api('POST', '/api/workshop/agent-tools/invoke', {
|
|
119
|
+
token: T,
|
|
120
|
+
body: { agentId: execB.id, tool: 'kb_search', args: { query: '冒烟' } },
|
|
121
|
+
})
|
|
122
|
+
const rB = invB.json?.data?.result
|
|
123
|
+
ok(rB?.isError === true && String(rB?.text).includes('未启用插件'), 'B 团队调用被拒插件工具 → isError + 引导开启', String(rB?.text).slice(0, 70))
|
|
124
|
+
// A 团队正常
|
|
125
|
+
const invA = await api('POST', '/api/workshop/agent-tools/invoke', {
|
|
126
|
+
token: T, timeoutMs: 60000,
|
|
127
|
+
body: { agentId: execA.id, tool: 'kb_search', args: { query: '冒烟验证' } },
|
|
128
|
+
})
|
|
129
|
+
const rA = invA.json?.data?.result
|
|
130
|
+
ok(rA && rA.isError !== true, 'A 团队调用插件工具正常(工具桥链路通)', String(rA?.text ?? '').slice(0, 60))
|
|
131
|
+
|
|
132
|
+
// 8) 团队设置切换:PUT 开回 rag-bridge → 工具回来
|
|
133
|
+
const put = await api('PUT', `/api/workshop/channels/${chB}/plugins`, {
|
|
134
|
+
token: T,
|
|
135
|
+
body: { plugins: [{ name: 'rag-bridge', enabled: true }, { name: 'diag-bridge', enabled: true }] },
|
|
136
|
+
})
|
|
137
|
+
ok(put.status === 200 && put.json?.data?.saved === 2, '团队设置 PUT 切换开关', `saved=${put.json?.data?.saved}`)
|
|
138
|
+
await sleep(1000)
|
|
139
|
+
const toolsB2 = (await api('GET', `/api/workshop/agent-tools/list?agentId=${execB.id}`, { token: T })).json?.data?.tools ?? []
|
|
140
|
+
ok(toolsB2.some(t => t.name === 'kb_search'), '开回后 kb_search 回到 B 团队工具清单(热生效)')
|
|
141
|
+
|
|
142
|
+
// 9) 清理:还原角色、删冒烟频道(删除级联 channel_plugins)
|
|
143
|
+
for (const u of upgraded) db.prepare('UPDATE users SET role = ? WHERE id = ?').run(u.role, u.id)
|
|
144
|
+
db.close()
|
|
145
|
+
for (const ch of [chA, chB]) {
|
|
146
|
+
await api('DELETE', `/api/workshop/channels/${ch}`, { token: T }).catch(() => null)
|
|
147
|
+
}
|
|
148
|
+
console.log(`\n══ 冒烟结果:${pass} 通过 / ${fail} 失败 ══`)
|
|
149
|
+
process.exit(fail > 0 ? 1 : 0)
|