agentworkshop 0.8.5 → 0.8.6
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/.AgentWorkShop/prompts/host-tools.en.json +3 -3
- package/.AgentWorkShop/prompts/host-tools.json +14 -4
- package/.output/.AgentWorkShop/prompts/host-tools.en.json +3 -3
- package/.output/.AgentWorkShop/prompts/host-tools.json +14 -4
- package/.output/nitro.json +1 -1
- package/.output/public/_nuxt/{U_n8pryv2.js → 7op-BMk82.js} +1 -1
- package/.output/public/_nuxt/{zWZ26UWi.js → B1SIgyrl.js} +1 -1
- package/.output/public/_nuxt/{C4tXwpSt.js → BA2lmfW4.js} +1 -1
- package/.output/public/_nuxt/{EJkmXP432.js → BDXQqSkl2.js} +1 -1
- package/.output/public/_nuxt/{CY_7u1ST2.js → BI9jj13f2.js} +1 -1
- package/.output/public/_nuxt/{CGf9neEK2.js → BiXqyYNw2.js} +1 -1
- package/.output/public/_nuxt/{D9GoOkyP2.js → BlOm8JIs2.js} +1 -1
- package/.output/public/_nuxt/{CQ31GiJU2.js → BoXKUOiN2.js} +1 -1
- package/.output/public/_nuxt/{DZzbCFY2.js → BuLoPmcf.js} +1 -1
- package/.output/public/_nuxt/{D9ppMCbw.js → BwOK42JQ.js} +1 -1
- package/.output/public/_nuxt/{CZJ5cw6r2.js → C1RMfPoB2.js} +1 -1
- package/.output/public/_nuxt/{CGviZq4E.js → COfo_Bg3.js} +1 -1
- package/.output/public/_nuxt/CTGJHZ8X2.js +1 -0
- package/.output/public/_nuxt/{SgsmmrsZ.js → CY2NkFvI.js} +1 -1
- package/.output/public/_nuxt/{B4GOTVfN.js → C_4mzQ5X.js} +1 -1
- package/.output/public/_nuxt/{DGmFHb7m.js → CdvMP3lv.js} +1 -1
- package/.output/public/_nuxt/{iqxZGXsY.js → CooauJ7D.js} +1 -1
- package/.output/public/_nuxt/{C3vxePED.js → D1eVi1zr.js} +1 -1
- package/.output/public/_nuxt/{DnuJ66Jy.js → D6PUmBb0.js} +1 -1
- package/.output/public/_nuxt/{BBhroEeN.js → DC8sMA36.js} +1 -1
- package/.output/public/_nuxt/{DOUuGOQR2.js → DDHh85c42.js} +1 -1
- package/.output/public/_nuxt/{Cy_HDgSl.js → DDXOfmo2.js} +2 -2
- package/.output/public/_nuxt/{Cr2xJ_CY.js → DMFwLxfu.js} +1 -1
- package/.output/public/_nuxt/{BZg_MIK42.js → DoprKJ5a2.js} +1 -1
- package/.output/public/_nuxt/{BzBBEE-o2.js → Dr1FVeQG2.js} +1 -1
- package/.output/public/_nuxt/{W0sNnJQh.js → DtnsTW7e.js} +1 -1
- package/.output/public/_nuxt/{gUYi1hLJ2.js → DxDqd6T42.js} +1 -1
- package/.output/public/_nuxt/{B0UUODeh.js → DzU3zEKX.js} +1 -1
- package/.output/public/_nuxt/{BkO5WdX5.js → EgO70wrE.js} +1 -1
- package/.output/public/_nuxt/{7OZQiw002.js → HJKu4eEV2.js} +1 -1
- package/.output/public/_nuxt/{ChF6qLpY2.js → K5Xen0wZ2.js} +1 -1
- package/.output/public/_nuxt/{CNrRZmSx2.js → Rb4o4o-82.js} +1 -1
- package/.output/public/_nuxt/{BqBTlfiz.js → Tf6SJSDj.js} +1 -1
- package/.output/public/_nuxt/{DJaGVY4v2.js → ZzOyoXex2.js} +1 -1
- package/.output/public/_nuxt/{PbzVdCTA2.js → azOGy_tU2.js} +1 -1
- package/.output/public/_nuxt/{D1thFC3z.js → be-4wyTo.js} +1 -1
- package/.output/public/_nuxt/builds/latest.json +1 -1
- package/.output/public/_nuxt/builds/meta/593b65bd-cf1d-4b7f-a993-57e939c38f36.json +1 -0
- package/.output/public/_nuxt/{8b9CKi6F.js → dcjPjLPI.js} +1 -1
- package/.output/public/_nuxt/{ae2-5ZPt.js → h-Fn7pRD.js} +1 -1
- package/.output/public/_nuxt/{-gy9k_TE2.js → hyLdZsOd2.js} +1 -1
- package/.output/public/_nuxt/{BHxG41IQ2.js → o8sYG6xx2.js} +1 -1
- package/.output/server/chunks/_/access.mjs +1 -1
- package/.output/server/chunks/_/agent-dto.mjs +26 -0
- package/.output/server/chunks/_/agent-dto.mjs.map +1 -0
- package/.output/server/chunks/_/data-export.mjs +6 -6
- package/.output/server/chunks/_/data-query.mjs +3 -3
- package/.output/server/chunks/_/eda-run.repo.mjs +2 -2
- package/.output/server/chunks/_/features.mjs +5 -5
- package/.output/server/chunks/_/golden.mjs +3 -3
- package/.output/server/chunks/_/harness-spawn-policy.mjs +77 -0
- package/.output/server/chunks/_/harness-spawn-policy.mjs.map +1 -0
- package/.output/server/chunks/_/index.mjs +1 -1
- package/.output/server/chunks/_/line-scope.mjs +32 -0
- package/.output/server/chunks/_/line-scope.mjs.map +1 -0
- package/.output/server/chunks/_/mes-console.mjs +1 -1
- package/.output/server/chunks/_/nitro.mjs +2429 -1726
- package/.output/server/chunks/_/nitro.mjs.map +1 -1
- package/.output/server/chunks/_/ocap.mjs +1 -1
- package/.output/server/chunks/_/rest-auth.mjs +1 -1
- package/.output/server/chunks/_/spc.mjs +1 -1
- package/.output/server/chunks/build/TownView-BWMDbosY.mjs +1 -1
- package/.output/server/chunks/build/daq-CdxcfY_G.mjs +1 -1
- package/.output/server/chunks/build/dcw-BlDQtS4q.mjs +1 -1
- package/.output/server/chunks/build/types-D6J7slEa.mjs +1 -1
- package/.output/server/chunks/build/useDaqAddNode-D_Rkju0f.mjs +1 -1
- package/.output/server/chunks/build/useDaqDetailNode-DeOsDxK5.mjs +1 -1
- package/.output/server/chunks/build/useDaqDetailParams-Cm9a9r5G.mjs +1 -1
- package/.output/server/chunks/build/useDaqFilters-CctmO03p.mjs +1 -1
- package/.output/server/chunks/build/useDaqStream-B9ZBVQrI.mjs +1 -1
- package/.output/server/chunks/build/useDaqTemplates-CK37sMfi.mjs +1 -1
- package/.output/server/chunks/build/useDcwAddNode-DACVcEHs.mjs +1 -1
- package/.output/server/chunks/build/useDcwStream-Dir5ufAU.mjs +1 -1
- package/.output/server/chunks/routes/api/mcp/workshop.mjs +1 -1
- package/.output/server/chunks/routes/api/system/backup/run.post.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/agent-tools/index.post.mjs +17 -9
- package/.output/server/chunks/routes/api/workshop/agent-tools/index.post.mjs.map +1 -1
- package/.output/server/chunks/routes/api/workshop/agents/_id_.patch.mjs +3 -0
- package/.output/server/chunks/routes/api/workshop/agents/_id_.patch.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/channel-templates/_id_.patch.mjs +6 -0
- package/.output/server/chunks/routes/api/workshop/channel-templates/_id_.patch.mjs.map +1 -1
- package/.output/server/chunks/routes/api/workshop/channels/_id/agents/_agentId_.patch.mjs +5 -0
- package/.output/server/chunks/routes/api/workshop/channels/_id/agents/_agentId_.patch.mjs.map +1 -1
- package/.output/server/chunks/routes/api/workshop/channels/_id/index.get.mjs +5 -2
- package/.output/server/chunks/routes/api/workshop/channels/_id/index.get.mjs.map +1 -1
- package/.output/server/chunks/routes/api/workshop/channels/_id/index.post.mjs +7 -1
- package/.output/server/chunks/routes/api/workshop/channels/_id/index.post.mjs.map +1 -1
- package/.output/server/chunks/routes/api/workshop/channels/index.get.mjs +3 -1
- package/.output/server/chunks/routes/api/workshop/channels/index.get.mjs.map +1 -1
- package/.output/server/chunks/routes/api/workshop/channels.post.mjs +8 -6
- package/.output/server/chunks/routes/api/workshop/channels.post.mjs.map +1 -1
- package/.output/server/chunks/routes/api/workshop/daq/_id/ingest.post.mjs +29 -49
- package/.output/server/chunks/routes/api/workshop/daq/_id/ingest.post.mjs.map +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_.patch.mjs +6 -1
- package/.output/server/chunks/routes/api/workshop/daq/_id_.patch.mjs.map +1 -1
- package/.output/server/chunks/routes/api/workshop/daq/alarms.get.mjs +4 -2
- package/.output/server/chunks/routes/api/workshop/daq/alarms.get.mjs.map +1 -1
- package/.output/server/chunks/routes/api/workshop/daq/index2.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/daq/infra/reconnect.post.mjs +14 -5
- package/.output/server/chunks/routes/api/workshop/daq/infra/reconnect.post.mjs.map +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 +7 -4
- package/.output/server/chunks/routes/api/workshop/daq/test-driver.post.mjs.map +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 +6 -2
- package/.output/server/chunks/routes/api/workshop/dcw/_id/param-ledger.get.mjs.map +1 -1
- package/.output/server/chunks/routes/api/workshop/dcw/_id/read.post.mjs +6 -2
- package/.output/server/chunks/routes/api/workshop/dcw/_id/read.post.mjs.map +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 +6 -1
- package/.output/server/chunks/routes/api/workshop/dcw/_id_.patch.mjs.map +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 +7 -4
- package/.output/server/chunks/routes/api/workshop/dcw/index.get.mjs.map +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 +6 -2
- package/.output/server/chunks/routes/api/workshop/dcw/index3.get.mjs.map +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 +7 -4
- package/.output/server/chunks/routes/api/workshop/dcw/index4.get.mjs.map +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/index5.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/dcw/index6.get.mjs +6 -2
- package/.output/server/chunks/routes/api/workshop/dcw/index6.get.mjs.map +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/mes-artifacts/file.get.mjs +6 -2
- package/.output/server/chunks/routes/api/workshop/dcw/mes-artifacts/file.get.mjs.map +1 -1
- package/.output/server/chunks/routes/api/workshop/dcw/mes-hook-test.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/dcw/mes-test-read.post.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 +6 -2
- package/.output/server/chunks/routes/api/workshop/dcw/optimizations/_id/series.get.mjs.map +1 -1
- package/.output/server/chunks/routes/api/workshop/dcw/optimizations/_id_.get.mjs +4 -2
- package/.output/server/chunks/routes/api/workshop/dcw/optimizations/_id_.get.mjs.map +1 -1
- package/.output/server/chunks/routes/api/workshop/dcw/params/_id/read.post.mjs +5 -2
- package/.output/server/chunks/routes/api/workshop/dcw/params/_id/read.post.mjs.map +1 -1
- package/.output/server/chunks/routes/api/workshop/dcw/params/_id/write.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/dcw/params/_id_.delete.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/dcw/params/_id_.patch.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/rollback-good.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/dcw/recipes/_id/versions.get.mjs +7 -3
- package/.output/server/chunks/routes/api/workshop/dcw/recipes/_id/versions.get.mjs.map +1 -1
- package/.output/server/chunks/routes/api/workshop/dcw/recipes/_id_.delete.mjs +8 -1
- package/.output/server/chunks/routes/api/workshop/dcw/recipes/_id_.delete.mjs.map +1 -1
- package/.output/server/chunks/routes/api/workshop/dcw/runs/_id/close.post.mjs +6 -2
- package/.output/server/chunks/routes/api/workshop/dcw/runs/_id/close.post.mjs.map +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 +7 -4
- package/.output/server/chunks/routes/api/workshop/dcw/test-driver.post.mjs.map +1 -1
- package/.output/server/chunks/routes/api/workshop/eda/commonality.get.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/eda/vm/predict.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/exp/confirmations/_id/decide.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/exp/index.get.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/fs/dirs.get.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.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/index.post.mjs +2 -0
- package/.output/server/chunks/routes/api/workshop/index.post.mjs.map +1 -1
- package/.output/server/chunks/routes/api/workshop/index.put.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/index3.post.mjs +8 -3
- package/.output/server/chunks/routes/api/workshop/index3.post.mjs.map +1 -1
- package/.output/server/chunks/routes/api/workshop/index5.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/index6.get.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/index7.get.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/index7.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/mes/datasets.get.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/ontology/drafts/_draftId/autofix.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/ontology/drafts/_draftId/compile.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/ontology/drafts/_draftId/evidence.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/ontology/drafts/_draftId/publish.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/ontology/drafts/_draftId/rules/_ruleId/approval-decision.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/ontology/drafts/_draftId/rules/_ruleId/approval.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/ontology/drafts/_draftId/rules/_ruleId/transition.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/ontology/drafts/_draftId/subgraph.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/ontology/drafts/_draftId/validate.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/ontology/drafts/_draftId_.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/ontology/export/_ontologyId_.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/ontology/index.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/ontology/scene.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/ontology/traces/_ontologyId_.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/ontology/versions/_ontologyId/compare.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/ontology/versions/_ontologyId_.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/optimization/capabilities.get.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/optimization/index.get.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/optimization/index.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/optimization/index2.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/optimization/profiles/_id/publish.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/optimization/recommendations/_id/crosscheck.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/optimization/recommendations/_id/dispatch.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/optimization/solve.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/process-model/datasets.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/process-model/eligibility.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/process-model/index.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/process-model/specs/_id/publish.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/process-model/train.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/tools/sync.post.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 +1 -1
- package/.output/server/chunks/virtual/precomputed.mjs +1 -1
- package/.output/server/package.json +1 -1
- package/README-zh.md +4 -2
- package/README.md +9 -2
- package/config.yml +1 -1
- package/docker-compose.yml +4 -2
- package/docs/cli.en.md +2 -2
- package/docs/cli.md +2 -2
- package/docs/plugins.en.md +1 -1
- package/docs/plugins.md +1 -1
- package/docs/sdk.en.md +1 -1
- package/docs/sdk.md +1 -1
- package/package.json +1 -1
- package/scripts/_audit/_live-mqtt-e2e-20261028.mjs +219 -0
- package/scripts/_audit/prompt-injection-probe.mts +76 -0
- package/scripts/api-live-e2e.mjs +9 -3
- package/scripts/e2e-multiexpert-remediation-real.mjs +453 -0
- package/scripts/e2e-node-binding-3mode-real.mjs +697 -0
- package/scripts/e2e-node-binding-authz.mjs +332 -0
- package/scripts/security-acceptance.mjs +218 -0
- package/server/api/workshop/agent-tools/bindings/index.post.ts +33 -17
- package/server/api/workshop/agents/[id].patch.ts +3 -0
- package/server/api/workshop/agents/index.post.ts +3 -0
- package/server/api/workshop/channel-templates/[id].patch.ts +5 -0
- package/server/api/workshop/channel-templates/index.post.ts +6 -0
- package/server/api/workshop/channels/[id]/agents/[agentId].patch.ts +5 -0
- package/server/api/workshop/channels/[id]/agents/index.get.ts +8 -2
- package/server/api/workshop/channels/[id]/agents/index.post.ts +7 -0
- package/server/api/workshop/channels/[id]/index.get.ts +10 -4
- package/server/api/workshop/channels.post.ts +3 -0
- package/server/api/workshop/daq/[id]/ingest.post.ts +26 -22
- package/server/api/workshop/daq/[id].patch.ts +8 -0
- package/server/api/workshop/daq/alarms.get.ts +18 -16
- package/server/api/workshop/daq/infra/reconnect.post.ts +21 -5
- package/server/api/workshop/daq/test-driver.post.ts +10 -3
- package/server/api/workshop/dcw/[id]/param-ledger.get.ts +7 -1
- package/server/api/workshop/dcw/[id]/read.post.ts +6 -1
- package/server/api/workshop/dcw/[id].patch.ts +49 -40
- package/server/api/workshop/dcw/journal/index.get.ts +7 -3
- package/server/api/workshop/dcw/mes-artifacts/file.get.ts +7 -1
- package/server/api/workshop/dcw/mes-artifacts/index.get.ts +7 -1
- package/server/api/workshop/dcw/optimizations/[id]/series.get.ts +6 -1
- package/server/api/workshop/dcw/optimizations/[id].get.ts +3 -1
- package/server/api/workshop/dcw/optimizations/index.get.ts +6 -3
- package/server/api/workshop/dcw/params/[id]/read.post.ts +22 -17
- package/server/api/workshop/dcw/recipes/[id]/versions.get.ts +20 -14
- package/server/api/workshop/dcw/recipes/[id].delete.ts +33 -25
- package/server/api/workshop/dcw/recipes/index.get.ts +17 -12
- package/server/api/workshop/dcw/runs/[id]/close.post.ts +21 -15
- package/server/api/workshop/dcw/test-driver.post.ts +21 -16
- package/server/api/workshop/ws/hub.ts +2 -1
- package/server/plugins/01-instance-guard.ts +82 -0
- package/server/plugins/daq.ts +40 -8
- package/server/plugins/workshop.ts +12 -0
- package/server/services/workshop/agents/adapters/line-spawn.ts +24 -1
- package/server/services/workshop/agents/agent-dto.ts +54 -0
- package/server/services/workshop/agents/harness-spawn-policy.ts +114 -0
- package/server/services/workshop/agents/host-tool-bridge/dispatch.ts +22 -0
- package/server/services/workshop/agents/host-tool-bridge/index.ts +1 -1
- package/server/services/workshop/agents/host-tool-bridge/per-agent.ts +17 -3
- package/server/services/workshop/agents/host-tool-bridge/tools/tasks.ts +1 -1
- package/server/services/workshop/agents/host-tool-bridge/tools/team-delegation.ts +5 -1
- package/server/services/workshop/agents/industrial/dcw-tools.ts +6 -2
- package/server/services/workshop/agents/industrial/ops-tools.ts +54 -12
- package/server/services/workshop/agents/industrial/recipe-gate.ts +68 -15
- package/server/services/workshop/agents/industrial/tool-verify-legacy-specs.ts +2 -2
- package/server/services/workshop/agents/industrial/tool-verify.ts +31 -2
- package/server/services/workshop/agents/node-bindings.repo.ts +4 -3
- package/server/services/workshop/agents/node-delegation.ts +49 -4
- package/server/services/workshop/agents/tool-approvals.ts +23 -0
- package/server/services/workshop/agents/tool-reference-spec.ts +3 -0
- package/server/services/workshop/daq/bus/mqtt.adapter.ts +12 -0
- package/server/services/workshop/daq/daq-controller/crud.ts +5 -3
- package/server/services/workshop/daq/daq-controller/ingest.ts +23 -0
- package/server/services/workshop/daq/daq-controller/push.ts +24 -3
- package/server/services/workshop/daq/daq-controller/runtime-control.ts +6 -0
- package/server/services/workshop/daq/daq-controller/sweep-alarms.ts +3 -1
- package/server/services/workshop/daq/daq-controller/tsdb-twin.ts +21 -1
- package/server/services/workshop/daq/daq-node.repo.ts +10 -7
- package/server/services/workshop/daq/daq-node.ts +2 -0
- package/server/services/workshop/daq/daq-runtime.ts +10 -2
- package/server/services/workshop/daq/drivers/modbus-tcp.ts +8 -5
- package/server/services/workshop/daq/drivers/registry.ts +5 -4
- package/server/services/workshop/daq/infra.ts +13 -0
- package/server/services/workshop/daq/ingest-policy.ts +24 -1
- package/server/services/workshop/daq/storage/timescale.adapter.ts +42 -3
- package/server/services/workshop/db/database/migrations.ts +83 -68
- package/server/services/workshop/dcw/dcw-controller/crud.ts +27 -2
- package/server/services/workshop/dcw/dcw-controller/lines.ts +14 -3
- package/server/services/workshop/dcw/dcw-controller/recipes.ts +63 -5
- package/server/services/workshop/dcw/dcw-recipe.repo.ts +10 -1
- package/server/services/workshop/dcw/dcw-runtime.ts +37 -14
- package/server/services/workshop/dcw/drivers/shared.ts +62 -6
- package/server/services/workshop/dcw/recipe-op-anchor.ts +28 -0
- package/server/services/workshop/dcw/recipe-rollback-manager/rollback.ts +204 -190
- package/server/services/workshop/dcw/recipe-rollback-manager/sweep.ts +97 -77
- package/server/services/workshop/line-scope.ts +80 -0
- package/server/services/workshop/optimization/dispatch.service.ts +18 -2
- package/server/services/workshop/permissions.ts +2 -0
- package/server/services/workshop/runtime/agent-runtime/message.ts +17 -0
- package/server/services/workshop/runtime/agent-runtime/state.ts +9 -3
- package/server/services/workshop/runtime/agent-runtime/types.ts +2 -1
- package/server/services/workshop/runtime/channel-runtime.ts +3 -1
- package/server/services/workshop/runtime/mailbox.ts +6 -3
- package/server/services/workshop/runtime/manager/a2a.ts +4 -2
- package/server/services/workshop/runtime/manager/admin-channel.ts +1 -1
- package/server/services/workshop/runtime/manager/chat.ts +3 -1
- package/server/services/workshop/runtime/manager/host-tools.ts +1 -1
- package/server/services/workshop/runtime/manager/runtime-wiring.ts +31 -19
- package/server/services/workshop/runtime/manager/state.ts +43 -1
- package/server/services/workshop/runtime/permission-scope.ts +21 -0
- package/server/services/workshop/runtime/scheduler-loop/tick.ts +16 -4
- package/server/services/workshop/scene-events.ts +19 -0
- package/shared/daq-protocol.ts +24 -5
- package/shared/dcw-protocol/catalog.ts +4 -4
- package/.output/public/_nuxt/CCI-fwcS2.js +0 -1
- package/.output/public/_nuxt/builds/meta/5cd11284-05bf-4fcd-9a69-56a01c9f56dd.json +0 -1
|
@@ -2,9 +2,10 @@
|
|
|
2
2
|
* AgentNodeBindingRepo —— Agent ↔ 工业对象绑定持久化(agent-node-bindings.json)。
|
|
3
3
|
*
|
|
4
4
|
* 一个绑定 = 某 Channel 内的 Agent 对某个工业对象的授权:
|
|
5
|
-
* - kind: 'daq' = 可查询数采; 'recipe' =
|
|
6
|
-
* 'dcw' = (
|
|
7
|
-
*
|
|
5
|
+
* - kind: 'daq' = 可查询数采; 'recipe' = 配方操作权(参数写入+下发+版本;nodeId 字段存 recipeId);
|
|
6
|
+
* 'dcw' = 点位写授权(2026-10-28 恢复,非直写入口):对活动配方的 trial/apply,
|
|
7
|
+
* 覆盖集 ⊆ 节点绑定面即放行;直写工具(param_control/dcw_control)仍恒拒,
|
|
8
|
+
* 写路径 100% 收敛在配方管道(权限模型 v2)。
|
|
8
9
|
* - mode: 'auto' = 工具调用自动执行; 'manual' = 每次执行需用户批准(可附备注)
|
|
9
10
|
*/
|
|
10
11
|
|
|
@@ -4,8 +4,10 @@
|
|
|
4
4
|
* 语义:lead 把**自己已绑定**的 DAQ/DCW 节点授权给同 Channel 的 worker:
|
|
5
5
|
* - 节点必须是 leader 本人已有绑定的节点(权限不可超越授予者,hard rule);
|
|
6
6
|
* - 目标必须是同 Channel 的启用 worker(不能授给 lead 自己/其他频道成员);
|
|
7
|
-
* - 绑定逐 (nodeId, kind) 复制 leader 的 kind/tuning;mode
|
|
8
|
-
*
|
|
7
|
+
* - 绑定逐 (nodeId, kind) 复制 leader 的 kind/tuning;mode 缺省 **manual**
|
|
8
|
+
* (不继承 leader 的 auto);显式 mode=auto 等于摘掉该 worker 的逐次人工审批闸门,
|
|
9
|
+
* 必须同时携带 confirm:true,并落审计(2026-10-28 审查 B-P1-3:此前委派可无
|
|
10
|
+
* confirm/无审计把 manual 抬成 auto,与 REST 面刻意的不变式矛盾);
|
|
9
11
|
* - 委派产生的绑定带 grantedByAgentId/grantedAt 溯源;撤销时 leader 只能撤
|
|
10
12
|
* 「自己授予的」或「自己也持有的」绑定。
|
|
11
13
|
* 校验全部 fail-closed:任何节点不满足 → 整单拒绝并列出明细,不做部分授予。
|
|
@@ -13,6 +15,7 @@
|
|
|
13
15
|
import { AppError, ErrorCodes } from '../../../utils/errors'
|
|
14
16
|
import { getAgentNodeBindingRepo, type AgentNodeBinding, type AgentNodeBindingMode, type AgentNodeBindingKind } from './node-bindings.repo'
|
|
15
17
|
import { getToolApprovals } from './tool-approvals'
|
|
18
|
+
import { recordOps } from '../ops/ops'
|
|
16
19
|
|
|
17
20
|
export interface DelegationMember {
|
|
18
21
|
id: string
|
|
@@ -27,6 +30,8 @@ export interface DelegationGrantInput {
|
|
|
27
30
|
nodeIds: string[]
|
|
28
31
|
members: DelegationMember[]
|
|
29
32
|
mode?: AgentNodeBindingMode
|
|
33
|
+
/** mode=auto 的风险确认(严格 === true;与 REST bindings PATCH 同语义) */
|
|
34
|
+
confirm?: unknown
|
|
30
35
|
}
|
|
31
36
|
|
|
32
37
|
export interface DelegationPlanItem {
|
|
@@ -73,6 +78,8 @@ export function planDelegation(input: DelegationGrantInput): DelegationPlan {
|
|
|
73
78
|
const missing: string[] = []
|
|
74
79
|
const grants: DelegationPlanItem[] = []
|
|
75
80
|
const seen = new Set<string>()
|
|
81
|
+
const repoForExisting = getAgentNodeBindingRepo()
|
|
82
|
+
const autoWithoutConfirm: string[] = []
|
|
76
83
|
for (const nodeId of nodeIds) {
|
|
77
84
|
if (seen.has(nodeId)) continue
|
|
78
85
|
seen.add(nodeId)
|
|
@@ -82,10 +89,20 @@ export function planDelegation(input: DelegationGrantInput): DelegationPlan {
|
|
|
82
89
|
continue
|
|
83
90
|
}
|
|
84
91
|
for (const h of held) {
|
|
92
|
+
// mode 缺省 = manual(安全侧)。旧实现 `input.mode ?? h.mode` 会让 lead 的 auto
|
|
93
|
+
// 静默下放给 worker,且没有 confirm 与审计 —— 委派面成了摘闸门的旁路。
|
|
94
|
+
const targetMode: AgentNodeBindingMode = input.mode ?? 'manual'
|
|
95
|
+
const existing = repoForExisting.find(targetAgentId, nodeId, h.kind)
|
|
96
|
+
if (targetMode === 'auto' && existing?.mode !== 'auto') {
|
|
97
|
+
if (input.confirm !== true) {
|
|
98
|
+
autoWithoutConfirm.push(nodeId)
|
|
99
|
+
continue
|
|
100
|
+
}
|
|
101
|
+
}
|
|
85
102
|
grants.push({
|
|
86
103
|
nodeId,
|
|
87
104
|
kind: h.kind,
|
|
88
|
-
mode:
|
|
105
|
+
mode: targetMode,
|
|
89
106
|
tuning: h.tuning ? { ...h.tuning } : undefined,
|
|
90
107
|
})
|
|
91
108
|
}
|
|
@@ -93,16 +110,44 @@ export function planDelegation(input: DelegationGrantInput): DelegationPlan {
|
|
|
93
110
|
if (missing.length) {
|
|
94
111
|
throw new AppError(403, 'DELEGATION_NOT_OWNED', `以下节点不在 lead 的绑定面内,拒绝授予(权限不可超越授予者):${missing.join('、')}`)
|
|
95
112
|
}
|
|
113
|
+
if (autoWithoutConfirm.length) {
|
|
114
|
+
throw new AppError(400, 'DELEGATION_MODE_CONFIRM_REQUIRED',
|
|
115
|
+
`授予 auto 模式将摘除 worker 对这些节点的逐次人工审批闸门,需显式风险确认:`
|
|
116
|
+
+ `请在参数中携带 confirm: true(布尔)。涉及节点:${autoWithoutConfirm.join('、')}。`
|
|
117
|
+
+ `未做任何授权变更;如需保留人工审批,请改用 mode=manual(缺省值)。`)
|
|
118
|
+
}
|
|
96
119
|
return { targetAgentId, grants }
|
|
97
120
|
}
|
|
98
121
|
|
|
99
122
|
/** 执行授予:按计划逐条落库(已存在的绑定更新 mode/溯源,不重复建行) */
|
|
100
123
|
export function applyDelegation(plan: DelegationPlan, leaderAgentId: string): AgentNodeBinding[] {
|
|
101
124
|
const repo = getAgentNodeBindingRepo()
|
|
102
|
-
|
|
125
|
+
const out = plan.grants.map((g) => {
|
|
103
126
|
const binding = repo.bind(plan.targetAgentId, g.nodeId, g.kind, g.mode, g.tuning, { grantedByAgentId: leaderAgentId })
|
|
104
127
|
return binding
|
|
105
128
|
})
|
|
129
|
+
// B-P1-3:auto 授予必须留痕(与 REST bindings PATCH 的 binding.mode 审计同口径)
|
|
130
|
+
const autoGrants = out.filter(b => b.mode === 'auto')
|
|
131
|
+
if (autoGrants.length > 0) {
|
|
132
|
+
recordOps({
|
|
133
|
+
actor: leaderAgentId,
|
|
134
|
+
actorName: `lead(${leaderAgentId})`,
|
|
135
|
+
actorKind: 'agent',
|
|
136
|
+
action: 'binding.mode',
|
|
137
|
+
kind: 'system',
|
|
138
|
+
summary: `委派授予 auto 控制模式:${autoGrants.map(b => `${b.kind}:${b.nodeId}`).join('、')}`,
|
|
139
|
+
targetKind: 'agent_node_binding',
|
|
140
|
+
targetId: autoGrants[0]!.id,
|
|
141
|
+
detail: {
|
|
142
|
+
targetAgentId: plan.targetAgentId,
|
|
143
|
+
grantedByAgentId: leaderAgentId,
|
|
144
|
+
grants: autoGrants.map(b => ({ bindingId: b.id, nodeId: b.nodeId, kind: b.kind, mode: b.mode })),
|
|
145
|
+
confirm: true,
|
|
146
|
+
source: 'team_grant_nodes',
|
|
147
|
+
},
|
|
148
|
+
})
|
|
149
|
+
}
|
|
150
|
+
return out
|
|
106
151
|
}
|
|
107
152
|
|
|
108
153
|
export interface DelegationRevokeInput {
|
|
@@ -14,6 +14,7 @@ import { sendHitlNote } from '../runtime/platform-notice'
|
|
|
14
14
|
import { notifyLineOperators } from '../runtime/line-notify'
|
|
15
15
|
import { getDcwController } from '../dcw/dcw-controller'
|
|
16
16
|
import { recordRecipeOpAnchor } from '../dcw/recipe-op-anchor'
|
|
17
|
+
import { assertRecipeOperation } from './industrial/recipe-gate'
|
|
17
18
|
|
|
18
19
|
export interface ToolApproval {
|
|
19
20
|
id: string
|
|
@@ -312,6 +313,28 @@ class ToolApprovalService {
|
|
|
312
313
|
}
|
|
313
314
|
catch { /* 配方已删除 → 无参数节点可清理 */ }
|
|
314
315
|
}
|
|
316
|
+
else if (binding.kind === 'dcw') {
|
|
317
|
+
// 2026-10-28 节点级写授权:收回点位授权后,在飞的配方审批单按当前覆盖集重新判定 ——
|
|
318
|
+
// 覆盖失效即按拒收敛(收回即失活)。仍被其余节点绑定/配方绑定完整覆盖的审批保留,
|
|
319
|
+
// 不做过度撤销。
|
|
320
|
+
for (const entry of [...this.pending.values()]) {
|
|
321
|
+
if (entry.approval.agentId !== agentId) continue
|
|
322
|
+
const nodeRef = entry.approval.nodeId
|
|
323
|
+
const recipeId = nodeRef.startsWith('recipe:')
|
|
324
|
+
? nodeRef.slice('recipe:'.length)
|
|
325
|
+
: nodeRef.startsWith('recipe-propose:') ? nodeRef.slice('recipe-propose:'.length) : ''
|
|
326
|
+
if (!recipeId) continue
|
|
327
|
+
const recipe = getDcwController().listRecipes().find(r => r.id === recipeId)
|
|
328
|
+
if (!recipe) continue
|
|
329
|
+
const payload = entry.approval.payload as { op?: string, params?: Array<{ nodeId?: string }> } | undefined
|
|
330
|
+
const op = payload?.op === 'update' || payload?.op === 'rollback' || payload?.op === 'trial'
|
|
331
|
+
? payload.op
|
|
332
|
+
: 'dispatch'
|
|
333
|
+
const target = (payload?.params ?? []).map(p => String(p.nodeId ?? '')).filter(Boolean)
|
|
334
|
+
const gate = assertRecipeOperation(agentId, recipe, op, target.length > 0 ? target : undefined)
|
|
335
|
+
if (!gate.ok) n += this.cancelPendingFor(agentId, nodeRef)
|
|
336
|
+
}
|
|
337
|
+
}
|
|
315
338
|
return n
|
|
316
339
|
}
|
|
317
340
|
|
|
@@ -42,6 +42,9 @@ export interface ToolVerifySpec {
|
|
|
42
42
|
nodeLists?: string[]
|
|
43
43
|
/** 深路径节点引用(如 'params[].node_id' / 'packages[].params[].node_id') */
|
|
44
44
|
deepNodeRefs?: string[]
|
|
45
|
+
/** 深路径节点引用,要求落在**写授权面** W(a)=dcw 绑定节点 ∪ 绑定配方参数节点
|
|
46
|
+
* (2026-10-28 节点级写授权:配方 trial/update 的 params[].node_id) */
|
|
47
|
+
writeNodeRefs?: string[]
|
|
45
48
|
/** 链式引用(dataset_id/job_id/model_id/plan_id/scene_id/binding_id/run_id/recommendation_id) */
|
|
46
49
|
chained?: ToolChainRule[]
|
|
47
50
|
/** 只读工具:节点只要归属可读产线即可引用(与频道绑线只读扩权同口径;写工具不得开启) */
|
|
@@ -35,6 +35,8 @@ export class MqttQueueAdapter implements DaqQueuePort {
|
|
|
35
35
|
private consumers = new Set<DaqConsumer>()
|
|
36
36
|
published = 0
|
|
37
37
|
received = 0
|
|
38
|
+
/** topic 与 payload.nodeId 不一致而丢弃的帧数(S13 身份校验) */
|
|
39
|
+
identityMismatch = 0
|
|
38
40
|
/** 断连窗口未发布计数(仪表可见的队列层丢弃) */
|
|
39
41
|
lostCount = 0
|
|
40
42
|
/** 断连离线缓冲(重连后按原序补发;带上限防内存无限涨) */
|
|
@@ -82,6 +84,16 @@ export class MqttQueueAdapter implements DaqQueuePort {
|
|
|
82
84
|
this.client.on('message', (topic, payload) => {
|
|
83
85
|
try {
|
|
84
86
|
const env = JSON.parse(payload.toString()) as DaqSampleEnvelope
|
|
87
|
+
// 2026-10-28(S13):topic 的 nodeId 与 payload.nodeId 必须一致 ——
|
|
88
|
+
// 否则任何能发布到 broker 的进程都能冒充任意节点注入样本。
|
|
89
|
+
const topicNode = String(topic ?? '').split('/')[2] ?? ''
|
|
90
|
+
if (topicNode && env.nodeId && topicNode !== env.nodeId) {
|
|
91
|
+
this.identityMismatch++
|
|
92
|
+
if (this.identityMismatch <= 5 || this.identityMismatch % 100 === 0) {
|
|
93
|
+
log.warn(`[daq-mqtt] 丢弃身份不一致帧:topic=${topic} payload.nodeId=${env.nodeId}(累计 ${this.identityMismatch})`)
|
|
94
|
+
}
|
|
95
|
+
return
|
|
96
|
+
}
|
|
85
97
|
this.received++
|
|
86
98
|
for (const fn of this.consumers) fn(env)
|
|
87
99
|
}
|
|
@@ -6,7 +6,7 @@ import { DaqControllerRuntimeControl } from './runtime-control'
|
|
|
6
6
|
import type { DaqCreateInput, DaqPatchInput } from './types'
|
|
7
7
|
import { AppError, ErrorCodes } from '../../../../utils/errors'
|
|
8
8
|
import { DaqNode } from '../daq-node'
|
|
9
|
-
import { daqKeyFromRef, normalizeDataTransform } from '../../../../../shared/daq-protocol'
|
|
9
|
+
import { daqKeyFromRef, normalizeDataTransform, stripMaskedSecrets } from '../../../../../shared/daq-protocol'
|
|
10
10
|
import { findDaqTemplate } from '../daq-templates'
|
|
11
11
|
import { getDaqHostPorts } from '../host-ports'
|
|
12
12
|
import { requireDriverKind } from '../drivers'
|
|
@@ -31,7 +31,7 @@ export abstract class DaqControllerCrud extends DaqControllerRuntimeControl {
|
|
|
31
31
|
templateRef: input.templateRef,
|
|
32
32
|
name: input.name ?? (tpl ? `${tpl.name} ${String(seq).padStart(2, '0')}` : (input.driver && input.driver !== 'mock' ? `${input.driver.toUpperCase()} 通道` : undefined)),
|
|
33
33
|
driver: input.driver ? requireDriverKind(input.driver) : undefined,
|
|
34
|
-
driverConfig: input.driverConfig ?? {},
|
|
34
|
+
driverConfig: stripMaskedSecrets(input.driverConfig ?? {}),
|
|
35
35
|
transform: normalizeDataTransform(input.transform),
|
|
36
36
|
enabled: input.enabled,
|
|
37
37
|
intervalMs: input.intervalMs == null ? null : Math.max(this.minIntervalMs, Math.min(60_000, Math.round(input.intervalMs))),
|
|
@@ -61,7 +61,9 @@ export abstract class DaqControllerCrud extends DaqControllerRuntimeControl {
|
|
|
61
61
|
if (!node) throw Object.assign(new Error(`数采节点不存在: ${id}`), { status: 404 })
|
|
62
62
|
if (patch.name !== undefined) node.name = patch.name
|
|
63
63
|
if (patch.driver !== undefined) node.driver = requireDriverKind(patch.driver)
|
|
64
|
-
|
|
64
|
+
// P0-2(2026-10-28 审查):GET 投影的 `******` 可能是整包 PATCH 回来的回显,
|
|
65
|
+
// 入口单点剔除,绝不让掩码覆盖真实凭据(新明文照常写入)。
|
|
66
|
+
if (patch.driverConfig !== undefined) node.driverConfig = { ...node.driverConfig, ...stripMaskedSecrets(patch.driverConfig) }
|
|
65
67
|
if (patch.transform !== undefined) {
|
|
66
68
|
if (patch.transform.kind === 'linear' && (!Number.isFinite(Number(patch.transform.scale)) || Number(patch.transform.scale) === 0)) {
|
|
67
69
|
throw new AppError(400, ErrorCodes.VALIDATION_ERROR, '标定系数 scale 必须为非零数字(物理值 = scale × PLC值 + offset)')
|
|
@@ -12,6 +12,7 @@ import { findDaqTemplate } from '../daq-templates'
|
|
|
12
12
|
import { getDaqHostPorts } from '../host-ports'
|
|
13
13
|
import { getDaqQueue } from '../bus'
|
|
14
14
|
import { tsdbRowOf } from './tsdb-row'
|
|
15
|
+
import type { DaqFrameSample } from '../drivers'
|
|
15
16
|
|
|
16
17
|
export abstract class DaqControllerIngest extends DaqControllerPush {
|
|
17
18
|
/** 生产面:样本帧入队 */
|
|
@@ -20,6 +21,28 @@ export abstract class DaqControllerIngest extends DaqControllerPush {
|
|
|
20
21
|
this.producedCount++
|
|
21
22
|
}
|
|
22
23
|
|
|
24
|
+
/**
|
|
25
|
+
* 外部推送帧的统一后处理入口(2026-10-28 审查 A-P1-3)。
|
|
26
|
+
*
|
|
27
|
+
* 轮询采样 / 被动订阅 / 外部 ingest 三条生产路径必须共用同一 sink 管线:
|
|
28
|
+
* 模板 processor(thumbnail/deriveMetric/qualityGate)→ 图像落对象存储 → 服务端
|
|
29
|
+
* 计算 sha256/size。外部调用方只提供原始 blob 或点列,绝不信任其自报指标与指纹;
|
|
30
|
+
* 否则"完全相同链路"的承诺不成立(缩略图缺失、质量门被绕过、派生指标为空)。
|
|
31
|
+
*/
|
|
32
|
+
async processExternalFrame(
|
|
33
|
+
node: DaqNode,
|
|
34
|
+
raw:
|
|
35
|
+
| { kind: 'vector', points: number[], metrics?: Record<string, number> }
|
|
36
|
+
| { kind: 'image', blob: Buffer, mime: string, width?: number, height?: number, metrics?: Record<string, number> },
|
|
37
|
+
atMs: number,
|
|
38
|
+
): Promise<{ frame: NonNullable<DaqSampleEnvelope['frame']> } | null> {
|
|
39
|
+
// 外部推送未必携带像素尺寸;缺省记 0(未知),不阻断落库与缩略图生成
|
|
40
|
+
const frame: DaqFrameSample['frame'] = raw.kind === 'image'
|
|
41
|
+
? { ...raw, width: raw.width ?? 0, height: raw.height ?? 0 }
|
|
42
|
+
: raw
|
|
43
|
+
return this.processFrameSample(node, frame, atMs)
|
|
44
|
+
}
|
|
45
|
+
|
|
23
46
|
/** 消费面:样本入网关管线(状态派生已在 runtime 完成)。
|
|
24
47
|
* allowPublish = 通过该节点 WS 下发节拍门控;入库/遥测回写不受门控(每帧必达)。 */
|
|
25
48
|
/**
|
|
@@ -23,6 +23,25 @@ import { log } from './helpers'
|
|
|
23
23
|
/** 订阅失败后的重试退避(避免 sweep 每 250ms 打一次 broker) */
|
|
24
24
|
const PUSH_RETRY_BACKOFF_MS = 5_000
|
|
25
25
|
|
|
26
|
+
/** 停线缓存值的最大补种年龄(超过视为无关,等设备新报文) */
|
|
27
|
+
export const RETAINED_SEED_MAX_AGE_MS = 600_000
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* 停线补种计划(纯函数,便于单测;A-P1-2)。
|
|
31
|
+
*
|
|
32
|
+
* retained-only 设备在停线期间只发过一条状态,开跑后需把缓存值补种一次。补种的
|
|
33
|
+
* **时间戳必须是补种时刻**(now):用缓存到达时刻会被运行时乱序游标判 late 丢弃
|
|
34
|
+
* (上一批样本已推进游标),而 runSeeded 又已置真 → 整批零数据。
|
|
35
|
+
*/
|
|
36
|
+
export function retainedSeedPlan(
|
|
37
|
+
bind: { runSeeded: boolean, lastValue: number | null, lastAt: number },
|
|
38
|
+
now: number,
|
|
39
|
+
): { value: number, atMs: number } | null {
|
|
40
|
+
if (bind.runSeeded || bind.lastValue == null) return null
|
|
41
|
+
if (now - bind.lastAt > RETAINED_SEED_MAX_AGE_MS) return null
|
|
42
|
+
return { value: bind.lastValue, atMs: now }
|
|
43
|
+
}
|
|
44
|
+
|
|
26
45
|
interface PushBinding {
|
|
27
46
|
stop: () => void | Promise<void>
|
|
28
47
|
/** 订阅参数指纹(节点 topic/host/jsonPath 变更 → 重建订阅) */
|
|
@@ -308,13 +327,15 @@ export abstract class DaqControllerPush extends DaqControllerState {
|
|
|
308
327
|
continue
|
|
309
328
|
}
|
|
310
329
|
// 新批次开跑:把停线期间缓存的最近值补种一次(retained-only 设备整批只有这一条状态;
|
|
311
|
-
//
|
|
330
|
+
// 超过 10 分钟的旧缓存不补,等新报文)
|
|
312
331
|
if (bind.runId !== run.runId) {
|
|
313
332
|
bind.runId = run.runId
|
|
314
333
|
bind.runSeeded = false
|
|
315
334
|
}
|
|
316
|
-
if (!bind.runSeeded && bind.lastValue != null && (now - bind.lastAt) <=
|
|
317
|
-
|
|
335
|
+
if (!bind.runSeeded && bind.lastValue != null && (now - bind.lastAt) <= RETAINED_SEED_MAX_AGE_MS) {
|
|
336
|
+
// 2026-10-28 审查 A-P1-2:补种时间戳 = 补种时刻(见 retainedSeedPlan 注释)。
|
|
337
|
+
const seed = retainedSeedPlan(bind, now)
|
|
338
|
+
if (seed) this.publishPushValue(node, seed.value, seed.atMs)
|
|
318
339
|
bind.runSeeded = true
|
|
319
340
|
if (node.lastError) node.lastError = null
|
|
320
341
|
}
|
|
@@ -10,6 +10,7 @@ import { getObjectStore } from '../objectstore'
|
|
|
10
10
|
import { getTsdb } from '../storage'
|
|
11
11
|
import { listPluginDrivers } from '../drivers'
|
|
12
12
|
import { startAlarmEscalator } from '../alarm-notify'
|
|
13
|
+
import { AppError } from '../../../../utils/errors'
|
|
13
14
|
|
|
14
15
|
export abstract class DaqControllerRuntimeControl extends DaqControllerFrames {
|
|
15
16
|
/** 后端能力自描述(meta 用) */
|
|
@@ -20,6 +21,11 @@ export abstract class DaqControllerRuntimeControl extends DaqControllerFrames {
|
|
|
20
21
|
// ---------- 控制器全局 ----------
|
|
21
22
|
|
|
22
23
|
startAll(): AepDaqControllerState {
|
|
24
|
+
// S10:后端明确降级时禁止开采(手动 resume 也不能绕过启动屏障)
|
|
25
|
+
const infra = (globalThis as unknown as { __daqInfraStatus?: () => { degraded: boolean, warning?: string } }).__daqInfraStatus?.()
|
|
26
|
+
if (infra?.degraded) {
|
|
27
|
+
throw new AppError(409, 'DAQ_INFRA_DEGRADED', `数采基础设施降级,禁止启动采集:${infra.warning ?? '后端不可用'}。请先修复后端或调用 /api/workshop/daq/infra/reconnect。`)
|
|
28
|
+
}
|
|
23
29
|
this.running = true
|
|
24
30
|
this.ensureLoop()
|
|
25
31
|
startAlarmEscalator() // S5:升级通知扫描器(懒启动,进程内单例)
|
|
@@ -118,6 +118,8 @@ export abstract class DaqControllerSweepAlarms extends DaqControllerIngest {
|
|
|
118
118
|
const payload = {
|
|
119
119
|
id, nodeId: node.id, nodeName: node.name, metric,
|
|
120
120
|
value, rule, threshold, escalation: 0, createdAt,
|
|
121
|
+
// S14:告警帧必须带产线归属,否则 scene-events 会当作"线无关"广播给所有 peer
|
|
122
|
+
lineId: node.lineId || null,
|
|
121
123
|
}
|
|
122
124
|
this.broadcast?.('daq.alarm', payload)
|
|
123
125
|
notifyAlarm(payload)
|
|
@@ -143,7 +145,7 @@ export abstract class DaqControllerSweepAlarms extends DaqControllerIngest {
|
|
|
143
145
|
|
|
144
146
|
/** alarm 恢复沿:广播恢复(报警保持 open 待人工 ack) */
|
|
145
147
|
protected handleAlarmRecover(node: DaqNode, value: number): void {
|
|
146
|
-
this.broadcast?.('daq.alarm.changed', { nodeId: node.id, nodeName: node.name, recovered: true, value, at: new Date().toISOString() })
|
|
148
|
+
this.broadcast?.('daq.alarm.changed', { nodeId: node.id, nodeName: node.name, recovered: true, value, at: new Date().toISOString(), lineId: node.lineId || null })
|
|
147
149
|
}
|
|
148
150
|
|
|
149
151
|
/** 报警确认(HITL 闭环的人为一步;幂等:已确认返回 false) */
|
|
@@ -11,6 +11,7 @@ import { TSDB_WRITE_RETRIES, log } from './helpers'
|
|
|
11
11
|
import { findDaqTemplate } from '../daq-templates'
|
|
12
12
|
import { getDaqHostPorts } from '../host-ports'
|
|
13
13
|
import { getTsdb } from '../storage'
|
|
14
|
+
import { getDcwNodeRepo } from '../../dcw/dcw-node.repo'
|
|
14
15
|
|
|
15
16
|
export abstract class DaqControllerTsdbTwin extends DaqControllerSweepAlarms {
|
|
16
17
|
/** 网关消费泵:队列帧按节点分发回其运行时(乱序防御/状态派生/下发门控在 runtime)。
|
|
@@ -131,11 +132,30 @@ export abstract class DaqControllerTsdbTwin extends DaqControllerSweepAlarms {
|
|
|
131
132
|
/** 遥测 WS 推送(1s/设备节流):孪生状态/遥测事件化直推,前端全量轮询降级为断线兜底 */
|
|
132
133
|
protected twinPushAt = new Map<string, number>()
|
|
133
134
|
|
|
135
|
+
/** 孪生绑定节点所属产线(唯一时返回;多线/未绑定 → null,保持全局语义) */
|
|
136
|
+
private twinLineId(twinId: string): string | null {
|
|
137
|
+
try {
|
|
138
|
+
const lines = new Set<string>()
|
|
139
|
+
for (const n of this.repo.all()) {
|
|
140
|
+
if (n.lineId && (n.deviceIds ?? []).includes(twinId)) lines.add(n.lineId)
|
|
141
|
+
}
|
|
142
|
+
for (const n of getDcwNodeRepo().all()) {
|
|
143
|
+
if (n.lineId && (n.deviceIds ?? []).includes(twinId)) lines.add(n.lineId)
|
|
144
|
+
}
|
|
145
|
+
return lines.size === 1 ? [...lines][0]! : null
|
|
146
|
+
}
|
|
147
|
+
catch { return null }
|
|
148
|
+
}
|
|
149
|
+
|
|
134
150
|
protected pushTwinTelemetry(twinId: string): void {
|
|
135
151
|
const now = Date.now()
|
|
136
152
|
if (now - (this.twinPushAt.get(twinId) ?? 0) < 1000) return
|
|
137
153
|
this.twinPushAt.set(twinId, now)
|
|
138
154
|
const payload = getDaqHostPorts()?.telemetry.scenePayload(twinId)
|
|
139
|
-
if (payload)
|
|
155
|
+
if (payload) {
|
|
156
|
+
// S14:补产线归属,让 scene-events 按 peer 可见集过滤;唯一产线才补,多线保持全局
|
|
157
|
+
const lineId = this.twinLineId(twinId)
|
|
158
|
+
this.broadcast?.('device.updated', lineId ? { ...(payload as object), lineId } : payload)
|
|
159
|
+
}
|
|
140
160
|
}
|
|
141
161
|
}
|
|
@@ -23,17 +23,20 @@ function load(): { nodes: DaqNode[], migrated: number } {
|
|
|
23
23
|
let migrated = 0
|
|
24
24
|
const nodes = (parsed as Record<string, unknown>[]).map((r) => {
|
|
25
25
|
const node = DaqNode.fromRow(r)
|
|
26
|
-
// P0-5 启动迁移:旧命名归一(modbus→modbus-tcp 等)
|
|
27
|
-
//
|
|
26
|
+
// P0-5 启动迁移:旧命名归一(modbus→modbus-tcp 等)。
|
|
27
|
+
// S11(2026-10-28):未知 kind 保留原名并显式标记不可用,**绝不降级 mock** ——
|
|
28
|
+
// 真实节点被悄悄换成假数据会污染训练/质量链;采样由 resolveDaqDriver 显式失败。
|
|
28
29
|
if (r.driver != null) {
|
|
29
30
|
const m = migrateDriverKind(String(r.driver))
|
|
30
|
-
if (m.
|
|
31
|
+
if (m.unknown) {
|
|
32
|
+
node.driver = m.kind
|
|
33
|
+
migrated++
|
|
34
|
+
node.lastError = `驱动「${String(r.driver)}」未注册 —— 已拒绝采样,请安装对应驱动插件或重新配置节点驱动`
|
|
35
|
+
log.warn(`[daq] 节点 ${node.id}(${node.name}) 驱动「${String(r.driver)}」未注册,已拒绝采样(不降级 mock)`)
|
|
36
|
+
}
|
|
37
|
+
else if (m.kind !== node.driver) {
|
|
31
38
|
node.driver = m.kind
|
|
32
39
|
migrated++
|
|
33
|
-
if (m.downgraded) {
|
|
34
|
-
node.lastError = `驱动「${String(r.driver)}」未注册,已降级 mock —— 请重新配置节点驱动后重采`
|
|
35
|
-
log.warn(`[daq] 节点 ${node.id}(${node.name}) 驱动「${String(r.driver)}」未注册,已降级 mock`)
|
|
36
|
-
}
|
|
37
40
|
}
|
|
38
41
|
}
|
|
39
42
|
return node
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
|
|
12
12
|
import { applyTransform } from '../../../../shared/daq-protocol'
|
|
13
13
|
import { classifyCommError, type DaqFrameSample } from './drivers'
|
|
14
|
+
import { ingestFutureSkewMs } from './ingest-policy'
|
|
14
15
|
import type { DaqNode } from './daq-node'
|
|
15
16
|
import type { DaqSampleEnvelope } from './bus/queue-port'
|
|
16
17
|
|
|
@@ -45,7 +46,7 @@ export interface DaqRuntimeHost {
|
|
|
45
46
|
}
|
|
46
47
|
|
|
47
48
|
/** onSample 结果(网关据此计指标) */
|
|
48
|
-
export type ConsumeVerdict = 'late' | 'ok' | 'unknown-node'
|
|
49
|
+
export type ConsumeVerdict = 'late' | 'future' | 'ok' | 'unknown-node'
|
|
49
50
|
|
|
50
51
|
export class DaqNodeRuntime {
|
|
51
52
|
/** 采样时点(epoch ms;-Inf = 立即到期) */
|
|
@@ -157,6 +158,9 @@ export class DaqNodeRuntime {
|
|
|
157
158
|
onSample(env: DaqSampleEnvelope): ConsumeVerdict {
|
|
158
159
|
const node = this.node
|
|
159
160
|
const tsMs = Date.parse(env.at)
|
|
161
|
+
// 2026-10-28(S12):未来时间戳必须拒绝且**不推进游标** —— 否则一条未来样本会把
|
|
162
|
+
// lastIngestAt 推到未来,之后所有实时样本被判 late 静默丢弃。
|
|
163
|
+
if (tsMs && tsMs > Date.now() + ingestFutureSkewMs()) return 'future'
|
|
160
164
|
// 乱序防御(broker 多投递者场景):迟到帧直接丢弃
|
|
161
165
|
if (tsMs && tsMs <= this.lastIngestAt) return 'late'
|
|
162
166
|
this.lastIngestAt = tsMs
|
|
@@ -165,7 +169,11 @@ export class DaqNodeRuntime {
|
|
|
165
169
|
// 指标阈值告警在网关 ingestFrame 按模板 metrics 规则边沿判定,这里只做
|
|
166
170
|
// 节点活跃度/展示值更新 + WS 下发节拍门控。
|
|
167
171
|
if (env.frame) {
|
|
168
|
-
|
|
172
|
+
// 2026-10-28 审查 A-P1-1:帧只负责"活性"(offline→ok)。指标阈值告警是跨帧持续态,
|
|
173
|
+
// 旧代码每帧无条件置 ok,会把 ingestFrame 刚判出的 alarm 立即抹掉,告警面板
|
|
174
|
+
// (metricStates)与节点态直接矛盾。warn/alarm 的进入与恢复由网关
|
|
175
|
+
// evaluateMetricAlarms 的边沿语义独占,此处不得改写。
|
|
176
|
+
if (node.state === 'offline') node.state = 'ok'
|
|
169
177
|
node.touchReading(env.value, env.at)
|
|
170
178
|
const pubMs = Math.max(0, Math.min(PUBLISH_INTERVAL_MAX, node.publishIntervalMs ?? this.host.defaults().publishIntervalMs))
|
|
171
179
|
const now = Date.now()
|
|
@@ -162,14 +162,17 @@ export function decodeRegisters(data: number[], dataType: string, byteOrder: str
|
|
|
162
162
|
const raw = data[0] ?? 0
|
|
163
163
|
return dataType === 'int16' ? (raw << 16) >> 16 : raw
|
|
164
164
|
}
|
|
165
|
-
// 字节序约定(
|
|
166
|
-
// big
|
|
167
|
-
//
|
|
168
|
-
//
|
|
165
|
+
// 字节序约定(值字节 A B C D = MSB..LSB;寄存器字 w0/w1 各按"高字节在前"读):
|
|
166
|
+
// big = ABCD → w0=[A B], w1=[C D] —— Modbus 标准大端,最常用
|
|
167
|
+
// wordSwap = CDAB → w0=[C D], w1=[A B](仅交换字序)
|
|
168
|
+
// little = DCBA → w0=[D C], w1=[B A](整 32 位小端:每字字节反转 + 字序反转)
|
|
169
|
+
// 2026-10-28 审查 P0-1:旧 little 分支实际解出 BADC(与注释 DCBA、写侧 encodeWords
|
|
170
|
+
// 均不一致),真实 DCBA/CDAB 设备会静默读成 1e-41 量级错值。三序现与
|
|
171
|
+
// dcw/drivers/shared.ts:encodeWords 严格互逆,并与 UI 目录标签同义。
|
|
169
172
|
const hi = (x: number) => (x >> 8) & 0xFF
|
|
170
173
|
const lo = (x: number) => x & 0xFF
|
|
171
174
|
let b: number[]
|
|
172
|
-
if (byteOrder === 'little') b = [
|
|
175
|
+
if (byteOrder === 'little') b = [lo(data[1]!), hi(data[1]!), lo(data[0]!), hi(data[0]!)]
|
|
173
176
|
else if (byteOrder === 'wordSwap') b = [hi(data[1]!), lo(data[1]!), hi(data[0]!), lo(data[0]!)]
|
|
174
177
|
else b = [hi(data[0]!), lo(data[0]!), hi(data[1]!), lo(data[1]!)]
|
|
175
178
|
const buf = Buffer.from(b)
|
|
@@ -85,14 +85,15 @@ export function requireDriverKind(kind: string): DaqDriverKind {
|
|
|
85
85
|
}
|
|
86
86
|
|
|
87
87
|
/**
|
|
88
|
-
* 启动迁移:磁盘遗留 kind
|
|
89
|
-
* (
|
|
88
|
+
* 启动迁移:磁盘遗留 kind 别名归一。
|
|
89
|
+
* 2026-10-28(S11):未知 kind **不再降级为 mock**(真实节点被悄悄换成假数据是工业高危默认);
|
|
90
|
+
* 保留原 kind,由 resolveDaqDriver 的 unavailableDaqDriver 显式拒绝采样,等插件注册/人工重配。
|
|
90
91
|
*/
|
|
91
|
-
export function migrateDriverKind(kind: string): { kind: DaqDriverKind, downgraded: boolean } {
|
|
92
|
+
export function migrateDriverKind(kind: string): { kind: DaqDriverKind, downgraded: boolean, unknown?: boolean } {
|
|
92
93
|
if (kind === 'modbus') return { kind: 'modbus-tcp', downgraded: false }
|
|
93
94
|
if (kind === 'rtu' || kind === 'modbus-rtu-tcp') return { kind: 'modbus-rtu', downgraded: false }
|
|
94
95
|
if (kind in REGISTRY || pluginRegistry().has(kind)) return { kind: kind as DaqDriverKind, downgraded: false }
|
|
95
|
-
return { kind:
|
|
96
|
+
return { kind: kind as DaqDriverKind, downgraded: false, unknown: true }
|
|
96
97
|
}
|
|
97
98
|
|
|
98
99
|
/**
|
|
@@ -109,6 +109,19 @@ export function daqInfraStatus(): DaqInfraStatus {
|
|
|
109
109
|
return { ...state().status }
|
|
110
110
|
}
|
|
111
111
|
|
|
112
|
+
/**
|
|
113
|
+
* 后端装配失败(S10):端口探测通过但 TSDB/队列/对象存储 init 失败时,
|
|
114
|
+
* 必须把状态显式置为 degraded —— 否则 30s 自动重连永不触发,采样会写进占位后端。
|
|
115
|
+
*/
|
|
116
|
+
export function markDaqInfraFailure(err: unknown): void {
|
|
117
|
+
const s = state().status
|
|
118
|
+
s.degraded = true
|
|
119
|
+
s.lastCheckAt = new Date().toISOString()
|
|
120
|
+
s.lastError = err instanceof Error ? err.message : String(err)
|
|
121
|
+
s.warning = `数采后端初始化失败,已降级并停采:${s.lastError}`
|
|
122
|
+
log.error('[daq-infra]', s.warning)
|
|
123
|
+
}
|
|
124
|
+
|
|
112
125
|
/** 运行配置快照(启动插件装配时写入;重连端点复用) */
|
|
113
126
|
export function daqInfraConfig(): DaqInfraConfig {
|
|
114
127
|
return state().cfg
|
|
@@ -29,11 +29,34 @@ export function isWideBackfillWindow(env: Record<string, string | undefined> = p
|
|
|
29
29
|
return ingestBackfillWindowMs(env) > WIDE_WINDOW_THRESHOLD_MS
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
/** 时间点是否在允许窗内 */
|
|
32
|
+
/** 时间点是否在允许窗内(旧口径:双向 ±window;新代码请用 ingestTimeVerdict) */
|
|
33
33
|
export function withinBackfillWindow(atMs: number, now: number, windowMs: number): boolean {
|
|
34
34
|
return Number.isFinite(atMs) && Math.abs(atMs - now) <= windowMs
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
+
/** 未来时间戳容忍度(ms):缺省 60s;env AW_DAQ_INGEST_FUTURE_SKEW_MS 可调,钳 0..1h */
|
|
38
|
+
export function ingestFutureSkewMs(env: Record<string, string | undefined> = process.env): number {
|
|
39
|
+
const raw = Number(env.AW_DAQ_INGEST_FUTURE_SKEW_MS)
|
|
40
|
+
if (!Number.isFinite(raw) || raw < 0) return 60_000
|
|
41
|
+
return Math.min(raw, 3600_000)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export type IngestTimeVerdict = 'ok' | 'future' | 'out-of-window'
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* 时间戳方向判定(2026-10-28 S12):
|
|
48
|
+
* - 未来超过 skew → future(必须拒绝;否则单条未来样本会把该节点 lastIngestAt 推到未来,
|
|
49
|
+
* 之后所有实时样本被判 late 丢弃,最长持续到墙钟追上);
|
|
50
|
+
* - 过去超过回填窗 → out-of-window;
|
|
51
|
+
* - 其余 ok。
|
|
52
|
+
*/
|
|
53
|
+
export function ingestTimeVerdict(atMs: number, now: number, windowMs: number, skewMs: number): IngestTimeVerdict {
|
|
54
|
+
if (!Number.isFinite(atMs)) return 'out-of-window'
|
|
55
|
+
if (atMs > now + skewMs) return 'future'
|
|
56
|
+
if (now - atMs > windowMs) return 'out-of-window'
|
|
57
|
+
return 'ok'
|
|
58
|
+
}
|
|
59
|
+
|
|
37
60
|
/** 推送鉴权模式判定(纯函数便于单测):token 已配置 → 校验头;未配置 → 会话+产线权限 */
|
|
38
61
|
export function ingestAuthMode(input: { configuredToken?: string, providedToken?: string }): 'token' | 'token-mismatch' | 'session' {
|
|
39
62
|
if (input.configuredToken) {
|
|
@@ -229,17 +229,56 @@ export class TimescaleAdapter implements TsdbPort {
|
|
|
229
229
|
return out
|
|
230
230
|
}
|
|
231
231
|
const { rows } = await this.pool.query(
|
|
232
|
-
`SELECT node_id, ts, value FROM daq_samples WHERE ${whereSql} ORDER BY ts ASC LIMIT $${params.length + 1}`,
|
|
232
|
+
`SELECT node_id, ts, value, state, ingested_at FROM daq_samples WHERE ${whereSql} ORDER BY ts ASC LIMIT $${params.length + 1}`,
|
|
233
233
|
[...params, limit],
|
|
234
234
|
)
|
|
235
|
-
for (const r of rows as Array<{ node_id: string, ts: string, value: number }>) {
|
|
235
|
+
for (const r of rows as Array<{ node_id: string, ts: string, value: number, state: string | null, ingested_at: string | number | null }>) {
|
|
236
236
|
const list = out.get(String(r.node_id)) ?? []
|
|
237
|
-
|
|
237
|
+
// D-P1-1(2026-10-28 审查):state/ingested_at 与 sqlite 同口径返回;
|
|
238
|
+
// 否则生产后端上的坏点清洗(cleanPoints)与入库水位判定是静默空操作。
|
|
239
|
+
list.push({
|
|
240
|
+
at: tsToMs(r.ts),
|
|
241
|
+
value: Number(r.value),
|
|
242
|
+
state: String(r.state ?? 'ok'),
|
|
243
|
+
...(r.ingested_at == null ? {} : { ingestedAt: Number(r.ingested_at) }),
|
|
244
|
+
})
|
|
238
245
|
out.set(String(r.node_id), list)
|
|
239
246
|
}
|
|
240
247
|
return out
|
|
241
248
|
}
|
|
242
249
|
|
|
250
|
+
/**
|
|
251
|
+
* 逐节点 raw 分页(D-P1-1,2026-10-28 审查:此前后端缺 queryTaggedPage,
|
|
252
|
+
* 训练/导出在 Timescale 上退化为 query() 截断)。
|
|
253
|
+
* 语义与 sqlite.adapter 完全一致:稳定 ASC、游标严格大于 afterTs、每页限额、
|
|
254
|
+
* done=false 时 nextCursor = 本页最后 at。
|
|
255
|
+
*/
|
|
256
|
+
async queryTaggedPage(nodeId: string, q: import('./tsdb-port').TsdbPageQuery = {}): Promise<import('./tsdb-port').TsdbPage> {
|
|
257
|
+
if (!this.pool) return { nodeId, points: [], nextCursor: null, done: true }
|
|
258
|
+
const limit = Math.max(1, Math.min(q.limit ?? 5000, 500_000))
|
|
259
|
+
const from = Math.max(q.fromMs ?? 0, (q.afterTs ?? -1) + 1)
|
|
260
|
+
const to = q.toMs ?? Date.now()
|
|
261
|
+
const withState = q.withState !== false
|
|
262
|
+
const args = [nodeId, new Date(from).toISOString(), new Date(to).toISOString(), limit]
|
|
263
|
+
const rows = withState
|
|
264
|
+
? (await this.pool.query(
|
|
265
|
+
'SELECT ts, value, state, ingested_at FROM daq_samples WHERE node_id = $1 AND ts >= $2 AND ts <= $3 ORDER BY ts ASC LIMIT $4',
|
|
266
|
+
args,
|
|
267
|
+
)).rows
|
|
268
|
+
: (await this.pool.query(
|
|
269
|
+
'SELECT ts, value FROM daq_samples WHERE node_id = $1 AND ts >= $2 AND ts <= $3 ORDER BY ts ASC LIMIT $4',
|
|
270
|
+
args,
|
|
271
|
+
)).rows
|
|
272
|
+
const points = (rows as Array<{ ts: string, value: number, state?: string | null, ingested_at?: string | number | null }>).map(r => ({
|
|
273
|
+
at: tsToMs(r.ts),
|
|
274
|
+
value: Number(r.value),
|
|
275
|
+
...(withState ? { state: String(r.state ?? 'ok') } : {}),
|
|
276
|
+
...(r.ingested_at == null ? {} : { ingestedAt: Number(r.ingested_at) }),
|
|
277
|
+
}))
|
|
278
|
+
const done = points.length < limit
|
|
279
|
+
return { nodeId, points, nextCursor: done ? null : points[points.length - 1]?.at ?? null, done }
|
|
280
|
+
}
|
|
281
|
+
|
|
243
282
|
async latest(): Promise<Map<string, DaqSampleRow>> {
|
|
244
283
|
const out = new Map<string, DaqSampleRow>()
|
|
245
284
|
if (!this.pool) return out
|