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
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* harness 启动面策略 —— 谁能指定"拉起什么可执行文件/在哪个目录/带什么环境"。
|
|
3
|
+
*
|
|
4
|
+
* 背景(2026-10-28 审计):Channel/Agent 创建接受任意 `config`,manager 原样落库,
|
|
5
|
+
* 各 harness impl 直接拿 `config.command/args/cwd` 去 spawn;`assertHarnessUsable`
|
|
6
|
+
* 只判断"文件存在"。任何登录账号因此可以把 lead 的 command 设成 cmd.exe/sh,
|
|
7
|
+
* 在首个调度回合获得服务账号的命令执行能力。
|
|
8
|
+
*
|
|
9
|
+
* 本模块把判定收敛为纯函数(便于单测):
|
|
10
|
+
* - 非 admin 不得覆盖 command/args/cwd/env(一律 403,文案给出下一步);
|
|
11
|
+
* - admin 覆盖 command 时,basename 必须与引擎自身命令一致(omp/omp.exe…),
|
|
12
|
+
* 拒绝 cmd/sh/node 等通用解释器;
|
|
13
|
+
* - cwd 必须落在 process.cwd() 或 configRoot 之下(admin 可用
|
|
14
|
+
* AW_ALLOW_ARBITRARY_HARNESS_CWD=1 显式豁免,仅限受控部署)。
|
|
15
|
+
*
|
|
16
|
+
* 真正的执行层防御仍在 adapters/line-spawn(不走 shell、参数逐个校验、
|
|
17
|
+
* 敏感 env 剥离),本层负责"授权谁可以改这些参数"。
|
|
18
|
+
*/
|
|
19
|
+
import { basename, resolve } from 'node:path'
|
|
20
|
+
import { AppError } from '../../../utils/errors'
|
|
21
|
+
|
|
22
|
+
/** 引擎自身允许的命令 basename(不含扩展名比较;.cmd/.bat shim 也算同族) */
|
|
23
|
+
const ALLOWED_COMMANDS: Record<string, string[]> = {
|
|
24
|
+
omp: ['omp'],
|
|
25
|
+
claude: ['claude'],
|
|
26
|
+
codex: ['codex'],
|
|
27
|
+
opencode: ['opencode'],
|
|
28
|
+
dsh: ['dsh'],
|
|
29
|
+
gemini: ['gemini'],
|
|
30
|
+
copilot: ['copilot'],
|
|
31
|
+
cursor: ['cursor-agent', 'cursor'],
|
|
32
|
+
crush: ['crush'],
|
|
33
|
+
goose: ['goose'],
|
|
34
|
+
qwen: ['qwen'],
|
|
35
|
+
pi: ['pi'],
|
|
36
|
+
hermes: ['hermes'],
|
|
37
|
+
mock: [],
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** 允许被 config 覆盖的启动键(出现即需要 admin) */
|
|
41
|
+
export const SPAWN_CONFIG_KEYS = ['command', 'args', 'cwd', 'env'] as const
|
|
42
|
+
|
|
43
|
+
export interface SpawnActor {
|
|
44
|
+
id?: string
|
|
45
|
+
role: string
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** 覆盖了哪些启动键(空数组 = 使用平台默认,任何角色都允许) */
|
|
49
|
+
export function spawnOverridesOf(config: Record<string, unknown> | undefined | null): string[] {
|
|
50
|
+
if (!config || typeof config !== 'object') return []
|
|
51
|
+
return SPAWN_CONFIG_KEYS.filter((k) => {
|
|
52
|
+
const v = config[k]
|
|
53
|
+
if (v === undefined || v === null) return false
|
|
54
|
+
if (typeof v === 'string') return v.trim() !== ''
|
|
55
|
+
if (Array.isArray(v)) return v.length > 0
|
|
56
|
+
return true
|
|
57
|
+
})
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function baseNameOf(command: string): string {
|
|
61
|
+
const base = basename(command.trim()).toLowerCase()
|
|
62
|
+
return base.replace(/\.(exe|cmd|bat|com|sh)$/i, '')
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function cwdAllowed(cwd: string): boolean {
|
|
66
|
+
if (process.env.AW_ALLOW_ARBITRARY_HARNESS_CWD === '1') return true
|
|
67
|
+
const target = resolve(cwd)
|
|
68
|
+
const roots = [resolve(process.cwd())]
|
|
69
|
+
const configRoot = process.env.AW_HOME
|
|
70
|
+
if (configRoot) roots.push(resolve(configRoot))
|
|
71
|
+
return roots.some(root => target === root || target.startsWith(root + '\\') || target.startsWith(root + '/'))
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* 校验一次 harness 启动配置。通过返回 void;拒绝抛 403(message 可直接给用户)。
|
|
76
|
+
* @param harness 引擎 id(用于取命令白名单)
|
|
77
|
+
* @param config 即将落库的 agent/channel config
|
|
78
|
+
* @param actor 调用者(角色决定是否允许覆盖)
|
|
79
|
+
*/
|
|
80
|
+
export function assertSpawnConfigAllowed(
|
|
81
|
+
harness: string,
|
|
82
|
+
config: Record<string, unknown> | undefined | null,
|
|
83
|
+
actor: SpawnActor,
|
|
84
|
+
): void {
|
|
85
|
+
const overrides = spawnOverridesOf(config)
|
|
86
|
+
if (overrides.length === 0) return
|
|
87
|
+
if (actor.role !== 'admin') {
|
|
88
|
+
throw new AppError(
|
|
89
|
+
403,
|
|
90
|
+
'HARNESS_SPAWN_FORBIDDEN',
|
|
91
|
+
`自定义 harness 启动参数(${overrides.join('/')})仅管理员可配置。请使用平台提供的引擎默认安装路径,或联系管理员在受控部署中设置。`,
|
|
92
|
+
)
|
|
93
|
+
}
|
|
94
|
+
const command = typeof config?.command === 'string' ? config.command.trim() : ''
|
|
95
|
+
if (command) {
|
|
96
|
+
const base = baseNameOf(command)
|
|
97
|
+
const allowed = ALLOWED_COMMANDS[harness] ?? []
|
|
98
|
+
if (!allowed.includes(base)) {
|
|
99
|
+
throw new AppError(
|
|
100
|
+
403,
|
|
101
|
+
'HARNESS_COMMAND_DENIED',
|
|
102
|
+
`harness「${harness}」不允许以「${base}」启动:命令必须与该引擎自身一致(允许 ${allowed.join('/') || '(无)'})。通用解释器(cmd/sh/node/powershell)一律拒绝。`,
|
|
103
|
+
)
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
const cwd = typeof config?.cwd === 'string' ? config.cwd.trim() : ''
|
|
107
|
+
if (cwd && !cwdAllowed(cwd)) {
|
|
108
|
+
throw new AppError(
|
|
109
|
+
403,
|
|
110
|
+
'HARNESS_CWD_DENIED',
|
|
111
|
+
`harness 工作目录「${cwd}」不在允许范围内(需位于平台工作根或配置根之下)。受控部署可显式设置 AW_ALLOW_ARBITRARY_HARNESS_CWD=1。`,
|
|
112
|
+
)
|
|
113
|
+
}
|
|
114
|
+
}
|
|
@@ -38,8 +38,30 @@ import { handleCreateTeamAgent, handleGetQueueOverview, handleListTeamAgents, ha
|
|
|
38
38
|
* 分发一次 host tool 调用(全引擎唯一入口):
|
|
39
39
|
* 插件工具优先 → workspace 面 → 工业工具族 → 插件兜底 → 未知工具。
|
|
40
40
|
*/
|
|
41
|
+
/**
|
|
42
|
+
* 工具活性注册表(2026-10-28 审查 C-P1-3)。
|
|
43
|
+
*
|
|
44
|
+
* 调度停滞看门狗以「最近一次工具调用」为健康信号,但旧实现只在 manager.invokeHostTool
|
|
45
|
+
* (REST/MCP 回程)打点 —— 默认引擎 omp 的原生 host_tool_call 与 pi 扩展完全不经那里,
|
|
46
|
+
* 信号恒空:biz 忙碌 5min 误 notify、回合间隙被误判 stall 而 COMPLETED/CANCELED。
|
|
47
|
+
* 打点必须放在**全引擎唯一分发入口**;这里只存 epoch ms,零业务耦合。
|
|
48
|
+
*/
|
|
49
|
+
const toolActivity = new Map<string, number>()
|
|
50
|
+
export function noteToolActivity(agentId: string): void {
|
|
51
|
+
if (agentId) toolActivity.set(agentId, Date.now())
|
|
52
|
+
}
|
|
53
|
+
export function toolActivityAt(agentId: string): number | null {
|
|
54
|
+
return toolActivity.get(agentId) ?? null
|
|
55
|
+
}
|
|
56
|
+
/** 仅供测试:清空活性注册表 */
|
|
57
|
+
export function resetToolActivity(): void {
|
|
58
|
+
toolActivity.clear()
|
|
59
|
+
}
|
|
60
|
+
|
|
41
61
|
/** 统一出口:所有工具结果经截断收口(单点修改,全族生效) */
|
|
42
62
|
export async function dispatchHostTool(ctx: HostToolBridgeContext, req: HostToolCall): Promise<HostToolResult> {
|
|
63
|
+
// 活性打点先于一切校验:被拒的工具调用同样证明 agent 在推进(不是死锁)
|
|
64
|
+
noteToolActivity(ctx.identity.agentId)
|
|
43
65
|
return truncateToolResult(await dispatchHostToolInner(ctx, req), req.toolName)
|
|
44
66
|
}
|
|
45
67
|
|
|
@@ -13,4 +13,4 @@ export { HOST_TOOLS, LEAD_ONLY_TOOL_NAMES, hostToolsForRole } from './catalog'
|
|
|
13
13
|
export { hostToolsForAgent, agentToolResources } from './per-agent'
|
|
14
14
|
export type { HostToolBridgeContext, HostToolCall, HostToolResult, HostToolSessionState } from './types'
|
|
15
15
|
export { createSessionState, partsToText } from './session'
|
|
16
|
-
export { dispatchHostTool } from './dispatch'
|
|
16
|
+
export { dispatchHostTool, noteToolActivity, toolActivityAt, resetToolActivity } from './dispatch'
|
|
@@ -27,6 +27,8 @@ export interface AgentToolResourceView {
|
|
|
27
27
|
daqNodes: Array<{ id: string, name: string, unit: string }>
|
|
28
28
|
/** 频道绑线带来的只读数采节点(v28;执行面 daqTargetsOf 同样并入) */
|
|
29
29
|
daqLineNodes: Array<{ id: string, name: string, unit: string }>
|
|
30
|
+
/** Agent 显式绑定的数控节点(kind=dcw)= 点位写授权面(2026-10-28 双轨授权) */
|
|
31
|
+
writeNodes: Array<{ id: string, name: string, unit: string }>
|
|
30
32
|
recipes: Array<{ id: string, name: string, version: number, params: Array<{ nodeId: string, name: string, unit: string }> }>
|
|
31
33
|
mesNodes: Array<{ id: string, name: string, unit: string }>
|
|
32
34
|
}
|
|
@@ -46,7 +48,7 @@ function channelLineOf(agentId: string): string {
|
|
|
46
48
|
|
|
47
49
|
/** 读取 Agent 的绑定面(只读;异常降级为空,绝不影响工具注入) */
|
|
48
50
|
export function agentToolResources(agentId: string): AgentToolResourceView {
|
|
49
|
-
const out: AgentToolResourceView = { daqNodes: [], daqLineNodes: [], recipes: [], mesNodes: [] }
|
|
51
|
+
const out: AgentToolResourceView = { daqNodes: [], daqLineNodes: [], writeNodes: [], recipes: [], mesNodes: [] }
|
|
50
52
|
if (!agentId) return out
|
|
51
53
|
try {
|
|
52
54
|
const bindings = getAgentNodeBindingRepo().byAgent(agentId)
|
|
@@ -63,6 +65,11 @@ export function agentToolResources(agentId: string): AgentToolResourceView {
|
|
|
63
65
|
const n = daq.byId(b.nodeId)
|
|
64
66
|
if (n) out.daqNodes.push({ id: n.id, name: n.name, unit: n.unit ?? '' })
|
|
65
67
|
}
|
|
68
|
+
else if (b.kind === 'dcw') {
|
|
69
|
+
// 点位写授权面:让模型不必先探测就知道自己能对哪些节点 trial/apply
|
|
70
|
+
const n = dcw.byId(b.nodeId)
|
|
71
|
+
if (n) out.writeNodes.push({ id: n.id, name: n.name, unit: n.unit ?? '' })
|
|
72
|
+
}
|
|
66
73
|
else if (b.kind === 'recipe') {
|
|
67
74
|
const r = dcw.listRecipes().find(x => x.id === b.nodeId)
|
|
68
75
|
if (!r) continue
|
|
@@ -109,15 +116,22 @@ export function hostToolsForAgent(agentId: string, role: 'lead' | 'worker', chan
|
|
|
109
116
|
const res = agentToolResources(agentId)
|
|
110
117
|
// WP6:排序保证同一绑定面确定性输出(折叠/不折叠都稳定)
|
|
111
118
|
const sortedDaq = sortedById(res.daqNodes)
|
|
119
|
+
const sortedWrite = sortedById(res.writeNodes)
|
|
112
120
|
const sortedRecipes = sortedById(res.recipes)
|
|
113
121
|
const sortedMes = sortedById(res.mesNodes)
|
|
114
122
|
const lineOnlyDaq = sortedById(res.daqLineNodes.filter(n => !sortedDaq.some(x => x.id === n.id)))
|
|
115
|
-
if (sortedDaq.length === 0 && lineOnlyDaq.length === 0 && sortedRecipes.length === 0 && !bindings.length) return filtered
|
|
123
|
+
if (sortedDaq.length === 0 && lineOnlyDaq.length === 0 && sortedWrite.length === 0 && sortedRecipes.length === 0 && !bindings.length) return filtered
|
|
116
124
|
const daqParts = [
|
|
117
125
|
sortedDaq.length > 0 ? foldList(tr(locale, `本 Agent 直接绑定的数采节点(仅这些可查):`, `DAQ nodes bound directly to this agent (queryable):`), sortedDaq.map(x => `${x.name}(${x.id}${x.unit ? `,${x.unit}` : ''})`)) : '',
|
|
118
126
|
lineOnlyDaq.length > 0 ? foldList(tr(locale, `频道绑线只读数采节点(可查):`, `Read-only DAQ nodes from the channel line binding:`), lineOnlyDaq.map(x => `${x.name}(${x.id}${x.unit ? `,${x.unit}` : ''})`)) : '',
|
|
119
127
|
].filter(Boolean)
|
|
120
128
|
const daqLine = daqParts.length > 0 ? `\n\n${daqParts.join('\n')}` : ''
|
|
129
|
+
const writeLine = sortedWrite.length > 0
|
|
130
|
+
? foldList(
|
|
131
|
+
tr(locale, `\n\n本 Agent 的数控节点写授权面(点位写授权;可在这些节点上 trial/apply,换配方仍有效):`, `\n\nDCW node write-authorization face of this agent (point-level; trial/apply allowed on these nodes, survives recipe switches):`),
|
|
132
|
+
sortedWrite.map(x => `${x.name}(${x.id}${x.unit ? `,${x.unit}` : ''})`),
|
|
133
|
+
)
|
|
134
|
+
: ''
|
|
121
135
|
const recipeLine = sortedRecipes.length > 0
|
|
122
136
|
? foldList(
|
|
123
137
|
tr(locale, `\n\n本 Agent 已绑定的配方(仅这些可写/下发):`, `\n\nRecipes bound to this agent (writable/dispatchable):`),
|
|
@@ -130,7 +144,7 @@ export function hostToolsForAgent(agentId: string, role: 'lead' | 'worker', chan
|
|
|
130
144
|
return filtered.map((t) => {
|
|
131
145
|
let suffix = ''
|
|
132
146
|
if (DAQ_TOOLS.has(t.name)) suffix = daqLine
|
|
133
|
-
else if (RECIPE_TOOLS.has(t.name)) suffix = recipeLine
|
|
147
|
+
else if (RECIPE_TOOLS.has(t.name)) suffix = `${recipeLine}${writeLine}`
|
|
134
148
|
else if (MES_TOOLS.has(t.name)) suffix = mesLine
|
|
135
149
|
else if (OPTIMIZER_TOOL_NAMES.has(t.name)) suffix = optimizerLine
|
|
136
150
|
if (!suffix) return t
|
|
@@ -42,7 +42,7 @@ export async function handleDispatchTask(args: Record<string, unknown>, ws: Agen
|
|
|
42
42
|
const grantNodeIds = (Array.isArray(rawGrant) ? rawGrant : String(rawGrant).split(',')).map(x => String(x ?? '').trim()).filter(Boolean)
|
|
43
43
|
if (!grantNodeIds.length) return { text: 'grant_node_ids 为空:要么不传,要么给出至少一个节点 id。', isError: true }
|
|
44
44
|
const grantMode = args.grant_mode === 'manual' || args.grant_mode === 'auto' ? (args.grant_mode as 'auto' | 'manual') : undefined
|
|
45
|
-
const grant = await grantNodesForDispatch(identity, assigneeId, grantNodeIds, grantMode, ws)
|
|
45
|
+
const grant = await grantNodesForDispatch(identity, assigneeId, grantNodeIds, grantMode, ws, args.grant_confirm)
|
|
46
46
|
if (!grant.ok) return { text: `未派发:${grant.text}\n请先用 team_grant_nodes 确认你持有这些节点,或修正 node id 后重试。`, isError: true }
|
|
47
47
|
grantNote = `\n${grant.text}`
|
|
48
48
|
}
|
|
@@ -42,6 +42,8 @@ export async function handleGrantNodes(identity: HostToolBridgeContext['identity
|
|
|
42
42
|
nodeIds,
|
|
43
43
|
members: await membersOf(ws),
|
|
44
44
|
mode,
|
|
45
|
+
// B-P1-3:mode=auto 摘除逐次人工审批,须显式风险确认(与 REST 同语义)
|
|
46
|
+
confirm: args.confirm,
|
|
45
47
|
})
|
|
46
48
|
const bindings = applyDelegation(plan, identity.agentId)
|
|
47
49
|
return {
|
|
@@ -84,7 +86,7 @@ export interface DispatchGrantResult {
|
|
|
84
86
|
* 派发时随任务授予节点(dispatch_task 的 grant_node_ids;lead 专属)。
|
|
85
87
|
* fail-closed:任一节点不在 lead 绑定面 → 整单失败,任务不创建。
|
|
86
88
|
*/
|
|
87
|
-
export async function grantNodesForDispatch(identity: HostToolBridgeContext['identity'], assigneeId: string, nodeIds: string[], mode: 'auto' | 'manual' | undefined, ws: MembersView): Promise<DispatchGrantResult> {
|
|
89
|
+
export async function grantNodesForDispatch(identity: HostToolBridgeContext['identity'], assigneeId: string, nodeIds: string[], mode: 'auto' | 'manual' | undefined, ws: MembersView, confirm?: unknown): Promise<DispatchGrantResult> {
|
|
88
90
|
try {
|
|
89
91
|
const plan = planDelegation({
|
|
90
92
|
leaderAgentId: identity.agentId,
|
|
@@ -93,6 +95,8 @@ export async function grantNodesForDispatch(identity: HostToolBridgeContext['ide
|
|
|
93
95
|
nodeIds,
|
|
94
96
|
members: await membersOf(ws),
|
|
95
97
|
mode,
|
|
98
|
+
// B-P1-3:随任务授予同样不得绕过 auto 的显式确认(dispatch_task.grant_confirm)
|
|
99
|
+
confirm,
|
|
96
100
|
})
|
|
97
101
|
const bindings = applyDelegation(plan, identity.agentId)
|
|
98
102
|
return { ok: true, text: `随任务授予 ${bindings.length} 条节点授权 → ${assigneeId}${summaryText(bindings)}` }
|
|
@@ -263,8 +263,12 @@ async function dcwControlGovernedLegacy(agentId: string, args: { node_id?: strin
|
|
|
263
263
|
text: `下发受理:${node.name}(${tpl?.ch ?? node.templateKey})设定 ${value}${node.unit} → PLC 原始值 ${outcome.raw ?? '-'};${ackTxt}。${winTxt}。${outcome.message}\n[调控闭环] ${loopTxt}${manualFeedback ? `\n[人工反馈] ${manualFeedback}` : ''}`,
|
|
264
264
|
}
|
|
265
265
|
}
|
|
266
|
-
//
|
|
267
|
-
|
|
266
|
+
// 失败文案带自我纠正线索:区分「确定未写」与「可能已写但未证实」
|
|
267
|
+
// S15:回读不一致/无确认通道时,PLC 可能已经被改,不能再宣称"值未被改动"。
|
|
268
|
+
const uncertain = outcome.raw != null || outcome.ack === 'unverified'
|
|
269
|
+
const keptHint = uncertain
|
|
270
|
+
? `写入未获证实,PLC 值可能已变更(原始值 ${outcome.raw ?? '-'}),请先读取核对后再决定是否重试`
|
|
271
|
+
: `当前设定值保持 ${node.value ?? '原值'}${node.unit} 未被改动`
|
|
268
272
|
const retryHint = /忙|busy/i.test(outcome.message) ? '链路忙属瞬时状态,可稍后重试' : '可缩小步进幅度后重试'
|
|
269
273
|
return { text: `下发失败:${outcome.message}(节点 ${node.name},物理量 ${tpl?.ch ?? node.templateKey},安全量程 ${node.min}~${node.max}${node.unit};${keptHint};${retryHint})`, isError: true }
|
|
270
274
|
}
|
|
@@ -750,8 +750,8 @@ export async function toolRecipeUpdate(agentId: string, args: {
|
|
|
750
750
|
if (!target.ok) return { text: target.text, isError: true }
|
|
751
751
|
const recipe = target.recipe
|
|
752
752
|
const recipeId = recipe.id
|
|
753
|
-
// 权限模型 v2
|
|
754
|
-
const gate = assertRecipeOperation(agentId, recipe, 'update')
|
|
753
|
+
// 权限模型 v2 + 2026-10-28 双轨:版本操作只认 recipe 绑定(节点绑定不授予版本权)
|
|
754
|
+
const gate = assertRecipeOperation(agentId, recipe, 'update', list.map(p => String(p.node_id).trim()))
|
|
755
755
|
if (!gate.ok) return { text: gate.text, isError: true }
|
|
756
756
|
for (const p of list) {
|
|
757
757
|
const nodeId = String(p.node_id).trim()
|
|
@@ -761,6 +761,13 @@ export async function toolRecipeUpdate(agentId: string, args: {
|
|
|
761
761
|
if (!node.enabled) return { text: `节点「${node.name}」已停用,参数保存被拒。请先让用户恢复节点控制,或保存到其他节点。`, isError: true }
|
|
762
762
|
}
|
|
763
763
|
// HITL:manual 绑定(或全局下发审批开关)→ 保存前挂人工审批,理由随审批卡展示
|
|
764
|
+
// B-P1-4(2026-10-28 审查):审批卡展示的是"此刻的"配方定义;审批窗口内若有人并发
|
|
765
|
+
// 编辑同一配方,旧实现会把批准结果静默并进**已变的**定义(本次只改列出的参数,其余
|
|
766
|
+
// 取最新值),人工批准的语义被偷换。进入审批前记录定义指纹,批准后逐字比对。
|
|
767
|
+
const approvedDefinitionSnapshot = JSON.stringify({
|
|
768
|
+
version: recipe.version ?? 1,
|
|
769
|
+
params: recipe.params.map(p => ({ nodeId: p.nodeId, value: p.value, min: p.min ?? null, max: p.max ?? null })),
|
|
770
|
+
})
|
|
764
771
|
let hitlNote = ''
|
|
765
772
|
if (gate.hitlRequired) {
|
|
766
773
|
const gateDecision = await hitlDispatchGate(
|
|
@@ -788,8 +795,24 @@ export async function toolRecipeUpdate(agentId: string, args: {
|
|
|
788
795
|
}
|
|
789
796
|
}
|
|
790
797
|
if (gateDecision.comment) hitlNote = `\n人工附言:${gateDecision.comment}`
|
|
791
|
-
const stale = await revalidateApprovedAction(agentId, recipe, 'update', 'recipe_update', { recipe_id: recipeId }, gate.activeContext)
|
|
798
|
+
const stale = await revalidateApprovedAction(agentId, recipe, 'update', 'recipe_update', { recipe_id: recipeId, params: list.map(p => ({ node_id: p.node_id })) }, gate.activeContext)
|
|
792
799
|
if (stale) return stale
|
|
800
|
+
// B-P1-4:批准后复核配方定义 —— 审批窗口内的并发编辑不得被静默并入已批版本。
|
|
801
|
+
const latestRecipe = getDcwRecipeRepo().byId(recipeId)
|
|
802
|
+
const latestSnapshot = latestRecipe
|
|
803
|
+
? JSON.stringify({
|
|
804
|
+
version: latestRecipe.version ?? 1,
|
|
805
|
+
params: latestRecipe.params.map(p => ({ nodeId: p.nodeId, value: p.value, min: p.min ?? null, max: p.max ?? null })),
|
|
806
|
+
})
|
|
807
|
+
: ''
|
|
808
|
+
if (!latestRecipe || latestSnapshot !== approvedDefinitionSnapshot) {
|
|
809
|
+
return {
|
|
810
|
+
text: `人工已批准,但审批期间配方「${recipe.name}」的定义已被其他操作修改(v${recipe.version ?? 1} → v${latestRecipe?.version ?? '已删除'});`
|
|
811
|
+
+ `为避免把本次批准静默并入已变更的定义,本次未执行、配方未改动。`
|
|
812
|
+
+ `请调用 recipe_catalog/recipe_versions 重新读取最新参数与理由后,基于最新定义重新提交 recipe_update。`,
|
|
813
|
+
isError: true,
|
|
814
|
+
}
|
|
815
|
+
}
|
|
793
816
|
}
|
|
794
817
|
// 旧值快照(repo.update 原地改写 recipe.params,事后再取会把 before 读成新值)
|
|
795
818
|
const beforeOf = new Map(recipe.params.map(p => [p.nodeId, p.value]))
|
|
@@ -992,11 +1015,12 @@ async function hitlDispatchGate(agentId: string, recipeId: string, detail: strin
|
|
|
992
1015
|
*/
|
|
993
1016
|
function approvedContextError(
|
|
994
1017
|
agentId: string,
|
|
995
|
-
recipe: { id: string, name: string, lineId?: string, access?: { requireAuth?: boolean, authorizedAgentIds?: string[] } },
|
|
1018
|
+
recipe: { id: string, name: string, lineId?: string, params?: Array<{ nodeId: string }>, access?: { requireAuth?: boolean, authorizedAgentIds?: string[] } },
|
|
996
1019
|
op: 'update' | 'dispatch' | 'trial' | 'rollback',
|
|
997
1020
|
expectedContext: ActiveRecipeContext,
|
|
1021
|
+
targetNodeIds?: string[],
|
|
998
1022
|
): { text: string, isError: true } | null {
|
|
999
|
-
const currentGate = assertRecipeOperation(agentId, recipe, op)
|
|
1023
|
+
const currentGate = assertRecipeOperation(agentId, recipe, op, targetNodeIds)
|
|
1000
1024
|
if (!currentGate.ok) {
|
|
1001
1025
|
return { text: `人工已批准,但审批期间授权已失效,本次未执行:${currentGate.text}`, isError: true }
|
|
1002
1026
|
}
|
|
@@ -1013,15 +1037,26 @@ function approvedContextError(
|
|
|
1013
1037
|
return null
|
|
1014
1038
|
}
|
|
1015
1039
|
|
|
1040
|
+
/** 从审批时的工具参数还原「本次覆盖的节点集」(trial/update 只写声明节点;其余=整批)。 */
|
|
1041
|
+
function targetNodesFromArgs(op: 'update' | 'dispatch' | 'trial' | 'rollback', args: Record<string, unknown>): string[] | undefined {
|
|
1042
|
+
if (op !== 'trial' && op !== 'update') return undefined
|
|
1043
|
+
const params = Array.isArray(args.params) ? args.params : []
|
|
1044
|
+
const ids = params
|
|
1045
|
+
.map(p => String((p as { node_id?: unknown } | null)?.node_id ?? '').trim())
|
|
1046
|
+
.filter(Boolean)
|
|
1047
|
+
return ids.length > 0 ? ids : undefined
|
|
1048
|
+
}
|
|
1049
|
+
|
|
1016
1050
|
export async function revalidateApprovedAction(
|
|
1017
1051
|
agentId: string,
|
|
1018
|
-
recipe: { id: string, name: string, lineId?: string, access?: { requireAuth?: boolean, authorizedAgentIds?: string[] } },
|
|
1052
|
+
recipe: { id: string, name: string, lineId?: string, params?: Array<{ nodeId: string }>, access?: { requireAuth?: boolean, authorizedAgentIds?: string[] } },
|
|
1019
1053
|
op: 'update' | 'dispatch' | 'trial' | 'rollback',
|
|
1020
1054
|
toolName: string,
|
|
1021
1055
|
args: Record<string, unknown>,
|
|
1022
1056
|
expectedContext: ActiveRecipeContext,
|
|
1023
1057
|
): Promise<{ text: string, isError: true } | null> {
|
|
1024
|
-
const
|
|
1058
|
+
const targetNodeIds = targetNodesFromArgs(op, args)
|
|
1059
|
+
const initialContextError = approvedContextError(agentId, recipe, op, expectedContext, targetNodeIds)
|
|
1025
1060
|
if (initialContextError) return initialContextError
|
|
1026
1061
|
try {
|
|
1027
1062
|
// manager 未装配(单测/嵌入)→ 只做绑定与运行门复核;装配后叠加产线 grant 复核
|
|
@@ -1041,7 +1076,7 @@ export async function revalidateApprovedAction(
|
|
|
1041
1076
|
// Grant lookup is asynchronous. Re-read the active tuple after that await so
|
|
1042
1077
|
// queued line/run changes cannot pass between the initial comparison and the
|
|
1043
1078
|
// caller's dispatch continuation.
|
|
1044
|
-
const changedAfterGrantCheck = approvedContextError(agentId, recipe, op, expectedContext)
|
|
1079
|
+
const changedAfterGrantCheck = approvedContextError(agentId, recipe, op, expectedContext, targetNodeIds)
|
|
1045
1080
|
if (changedAfterGrantCheck) return changedAfterGrantCheck
|
|
1046
1081
|
return null
|
|
1047
1082
|
}
|
|
@@ -1082,8 +1117,8 @@ export async function toolRecipeTrial(agentId: string, args: {
|
|
|
1082
1117
|
if (!target.ok) return { text: target.text, isError: true }
|
|
1083
1118
|
const recipe = target.recipe
|
|
1084
1119
|
const recipeId = recipe.id
|
|
1085
|
-
// 权限模型 v2
|
|
1086
|
-
const gate = assertRecipeOperation(agentId, recipe, 'trial')
|
|
1120
|
+
// 权限模型 v2 + 2026-10-28 双轨:recipe 绑定 ∨ 本次候选覆盖集 ⊆ dcw 节点绑定面
|
|
1121
|
+
const gate = assertRecipeOperation(agentId, recipe, 'trial', list.map(p => p.nodeId))
|
|
1087
1122
|
if (!gate.ok) return { text: gate.text, isError: true }
|
|
1088
1123
|
for (const p of list) {
|
|
1089
1124
|
const node = getDcwController().byId(p.nodeId)
|
|
@@ -1129,11 +1164,15 @@ export async function toolRecipeTrial(agentId: string, args: {
|
|
|
1129
1164
|
}
|
|
1130
1165
|
// 人工附言随批准回执回流(Agent 与审计都能看到放行时人的原话)
|
|
1131
1166
|
if (gateDecision.comment) hitlNote = `\n人工附言:${gateDecision.comment}`
|
|
1132
|
-
const stale = await revalidateApprovedAction(agentId, recipe, 'trial', 'recipe_trial', { recipe_id: recipeId }, gate.activeContext)
|
|
1167
|
+
const stale = await revalidateApprovedAction(agentId, recipe, 'trial', 'recipe_trial', { recipe_id: recipeId, params: list.map(p => ({ node_id: p.nodeId })) }, gate.activeContext)
|
|
1133
1168
|
if (stale) return stale
|
|
1134
|
-
const staleAtDispatch = approvedContextError(agentId, recipe, 'trial', gate.activeContext)
|
|
1169
|
+
const staleAtDispatch = approvedContextError(agentId, recipe, 'trial', gate.activeContext, list.map(p => p.nodeId))
|
|
1135
1170
|
if (staleAtDispatch) return staleAtDispatch
|
|
1136
1171
|
}
|
|
1172
|
+
// 2026-10-28 执行点复核(双轨授权,D13 同款):审批/节拍等待期间绑定可能被撤销,
|
|
1173
|
+
// 下发前最后一次同步重算覆盖集 —— 收回即失活,不把授权变更带进写入。
|
|
1174
|
+
const execGate = assertRecipeOperation(agentId, recipe, 'trial', list.map(p => p.nodeId))
|
|
1175
|
+
if (!execGate.ok) return { text: `下发前授权复核未通过,本次未执行:${execGate.text}`, isError: true }
|
|
1137
1176
|
const run = await getDcwController().applyRecipe(recipeId, { overrides: list, trial: true, agentDispatch: true, agentApprovalId: gateApprovalId, emergency })
|
|
1138
1177
|
// ACK 鉴定三段口径(2026-10-08):ok 只是命令受理,未证实/失败必须如实回给 Agent
|
|
1139
1178
|
const acks = run.ackSummary ?? { verified: run.results.filter(r => r.ok).length, unverified: 0, failed: run.results.filter(r => !r.ok).length, total: run.results.length }
|
|
@@ -1244,6 +1283,9 @@ export async function toolRecipeApply(agentId: string, args: {
|
|
|
1244
1283
|
const staleAtDispatch = approvedContextError(agentId, recipe, 'dispatch', gate.activeContext)
|
|
1245
1284
|
if (staleAtDispatch) return staleAtDispatch
|
|
1246
1285
|
}
|
|
1286
|
+
// 2026-10-28 执行点复核(双轨授权):recipe 绑定或节点覆盖集在下发前再算一次
|
|
1287
|
+
const execGate = assertRecipeOperation(agentId, recipe, 'dispatch')
|
|
1288
|
+
if (!execGate.ok) return { text: `下发前授权复核未通过,本次未执行:${execGate.text}`, isError: true }
|
|
1247
1289
|
// 完整覆盖集让控制器复制每个已批准值,逐参数异步写期间也不再共享可变配方参数。
|
|
1248
1290
|
const run = await getDcwController().applyRecipe(recipeId, { overrides: dispatch.params, agentDispatch: true, agentApprovalId: gateApprovalId, emergency })
|
|
1249
1291
|
// ACK 鉴定三段口径(2026-10-08):ok 只是命令受理,未证实/失败必须如实回给 Agent
|
|
@@ -1,15 +1,24 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Recipe 操作统一鉴权门(权限模型 v2)。
|
|
2
|
+
* Recipe 操作统一鉴权门(权限模型 v2 + 2026-10-28 节点级写授权双轨)。
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* ①
|
|
4
|
+
* 对产线的全部写动作仍收敛在配方管道上;授权面有两条轨(取并集):
|
|
5
|
+
* ① 绑定门:
|
|
6
|
+
* - recipe 绑定(kind='recipe',nodeId 字段=配方 id)= **配方操作权**
|
|
7
|
+
* (update/apply/trial/rollback 全部放行,维持权限模型 v2 语义);
|
|
8
|
+
* - dcw 节点绑定(kind='dcw')= **点位写授权**(2026-10-28 恢复,非直写入口):
|
|
9
|
+
* 对「当前活动配方」的 trial/apply,只要本次覆盖的节点 ⊆ 自己的 dcw 绑定面
|
|
10
|
+
* 即放行 —— 授权跟设备节点走(稳定),管控跟活动配方走(动态),换配方不停摆。
|
|
11
|
+
* 版本操作(update/rollback)不授予:配方级操作权仍须 recipe 绑定。
|
|
12
|
+
* - 目标节点集缺省 = 配方全部参数(apply / 无 overrides 的 trial);
|
|
13
|
+
* trial 传本次 overrides 的节点集(只写声明节点,部分覆盖即拒)。
|
|
6
14
|
* ② 认证门:recipe.access.requireAuth=true 时,只有 authorizedAgentIds 清单内的
|
|
7
15
|
* Agent 可操作(fail-closed:开认证但清单为空 = 无人可操作);
|
|
8
16
|
* ③ 运行门:配方必须**正在执行**(该产线存在本配方的活动批次)—— 未运行的配方
|
|
9
17
|
* 一律不可操作(参数写入/下发/试验/回退同样受限);
|
|
10
18
|
* ④ HITL 判定:绑定 mode='manual' → 每次动作挂起等人工批准(必带 Agent 理由);
|
|
11
19
|
* mode='auto'(显式确认切换)→ 免批直执行。全局开关 security.recipeDispatchApproval
|
|
12
|
-
* 保持兼容:开启时即便 auto
|
|
20
|
+
* 保持兼容:开启时即便 auto 绑定也挂审批。节点级轨的 manual 语义跟随覆盖该批
|
|
21
|
+
* 节点的 dcw 绑定(任一 manual 即挂审批)。
|
|
13
22
|
* 线级总闸(2026-10-08):line.controlMode='manual'(缺省,fail-safe)时 auto 绑定
|
|
14
23
|
* 也强制人工审批 —— 手动模式下人类必须过目每一次写动作。
|
|
15
24
|
*/
|
|
@@ -50,22 +59,59 @@ export function activeRunOfRecipe(recipeId: string): ActiveRecipeContext | null
|
|
|
50
59
|
}
|
|
51
60
|
}
|
|
52
61
|
|
|
53
|
-
/**
|
|
62
|
+
/** 节点展示名(拒绝文案用;节点已删除时退回 id) */
|
|
63
|
+
function nodeLabel(nodeId: string): string {
|
|
64
|
+
const n = getDcwController().byId(nodeId)
|
|
65
|
+
return n ? `${n.name}(${nodeId})` : nodeId
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function listNodes(ids: string[]): string {
|
|
69
|
+
return ids.length > 0 ? ids.map(nodeLabel).join('、') : '(无)'
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** 统一鉴权门:绑定(配方级 ∪ 节点级)→ 二级认证 → 运行门;返回绑定与 HITL 判定(不做挂起,挂起由调用方执行)
|
|
73
|
+
* targetNodeIds:本次动作实际覆盖的节点集;缺省 = 配方全部参数(整批 apply)。 */
|
|
54
74
|
export function assertRecipeOperation(
|
|
55
75
|
agentId: string,
|
|
56
|
-
recipe: { id: string, name: string, lineId?: string, access?: { requireAuth?: boolean, authorizedAgentIds?: string[] } },
|
|
76
|
+
recipe: { id: string, name: string, lineId?: string, params?: Array<{ nodeId: string }>, access?: { requireAuth?: boolean, authorizedAgentIds?: string[] } },
|
|
57
77
|
op: RecipeOp,
|
|
78
|
+
targetNodeIds?: string[],
|
|
58
79
|
): RecipeGateResult {
|
|
59
80
|
const opLabel = op === 'update' ? '参数写入' : op === 'dispatch' ? '下发' : op === 'trial' ? '试验下发' : '回退'
|
|
60
|
-
// ① 绑定门(
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
81
|
+
// ① 绑定门(双轨):recipe 绑定 = 配方操作权;dcw 绑定 = 点位写授权(仅 trial/apply)
|
|
82
|
+
const bindings = getAgentNodeBindingRepo().byAgent(agentId)
|
|
83
|
+
const recipeBinding = bindings.find(b => b.kind === 'recipe' && b.nodeId === recipe.id)
|
|
84
|
+
const dcwBindings = bindings.filter(b => b.kind === 'dcw')
|
|
85
|
+
const dcwBound = new Set(dcwBindings.map(b => b.nodeId))
|
|
86
|
+
const recipeNodes = (recipe.params ?? []).map(p => p.nodeId).filter(Boolean)
|
|
87
|
+
const targets = (targetNodeIds && targetNodeIds.length > 0 ? targetNodeIds : recipeNodes)
|
|
88
|
+
.map(id => String(id ?? '').trim())
|
|
89
|
+
.filter(Boolean)
|
|
90
|
+
const versionOp = op === 'update' || op === 'rollback'
|
|
91
|
+
let coveringBindings: AgentNodeBinding[] = []
|
|
92
|
+
if (!recipeBinding) {
|
|
93
|
+
if (versionOp) {
|
|
94
|
+
return {
|
|
95
|
+
ok: false,
|
|
96
|
+
text: `无权${opLabel}配方「${recipe.name}」(${recipe.id}):节点绑定(dcw)只授予参数下发/试验权(点位授权),配方版本操作(参数保存/回退)需要绑定该配方(kind='recipe',配方级操作权)。请让用户或 lead 为你绑定该配方(recipe 绑定首绑默认 manual,每次动作需人工批准)。`,
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
if (targets.length === 0) {
|
|
100
|
+
return {
|
|
101
|
+
ok: false,
|
|
102
|
+
text: `无权${opLabel}配方「${recipe.name}」:配方没有参数节点,无法确认授权覆盖集。请先为配方配置参数节点,或让用户/lead 绑定该配方(kind='recipe')。`,
|
|
103
|
+
}
|
|
66
104
|
}
|
|
105
|
+
const missing = targets.filter(id => !dcwBound.has(id))
|
|
106
|
+
if (missing.length > 0) {
|
|
107
|
+
return {
|
|
108
|
+
ok: false,
|
|
109
|
+
text: `无权${opLabel}配方「${recipe.name}」:本次覆盖的节点中有 ${missing.length} 个不在你的数控节点绑定面内 —— ${listNodes(missing)}。你已绑定的可写节点:${listNodes([...dcwBound])}。请让用户或 lead 为你补绑这些节点(节点绑定 = 点位写授权),或改用已授权节点重试;若要对整批配方操作,也可改为绑定配方(kind='recipe')。`,
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
coveringBindings = dcwBindings.filter(b => targets.includes(b.nodeId))
|
|
67
113
|
}
|
|
68
|
-
// ② 二级认证(用户在配方编辑里开启并勾选授权 Agent)
|
|
114
|
+
// ② 二级认证(用户在配方编辑里开启并勾选授权 Agent;双轨共用)
|
|
69
115
|
if (recipe.access?.requireAuth && !(recipe.access.authorizedAgentIds ?? []).includes(agentId)) {
|
|
70
116
|
return {
|
|
71
117
|
ok: false,
|
|
@@ -82,7 +128,7 @@ export function assertRecipeOperation(
|
|
|
82
128
|
}
|
|
83
129
|
// ④ HITL 判定:manual 绑定 → 逐动作人工批准;auto → 免批。
|
|
84
130
|
// 线级总闸(2026-10-08 生产化):line.controlMode='manual'(缺省)时,即便绑定为
|
|
85
|
-
// auto 也强制人工审批 ——
|
|
131
|
+
// auto 也强制人工审批 —— 手动模式下人类必须过目每一次写动作。
|
|
86
132
|
// 频道层钳位(2026-10-28 三模式):Agent 所属频道 executionMode='manual' 时同理强制;
|
|
87
133
|
// 'auto'(中性缺省)/'observe'(分发层已拒写,不可达)不改变本判定。
|
|
88
134
|
const lineManual = recipe.lineId ? getDcwLineRepo().controlModeOf(recipe.lineId) === 'manual' : false
|
|
@@ -90,7 +136,14 @@ export function assertRecipeOperation(
|
|
|
90
136
|
// ⑤ 全局开关兼容位:security.recipeDispatchApproval 开启时即便 auto 也挂审批(保留语义)
|
|
91
137
|
const globalGate = false // 由调用方按 settingOf('security.recipeDispatchApproval') 叠加(本模块不依赖设置服务)
|
|
92
138
|
void globalGate
|
|
93
|
-
|
|
139
|
+
// 节点级轨的 manual 语义:覆盖本次批次的 dcw 绑定任一 manual 即挂审批(fail-safe)
|
|
140
|
+
const nodeManual = coveringBindings.some(b => b.mode === 'manual')
|
|
141
|
+
const binding = recipeBinding ?? coveringBindings[0]
|
|
142
|
+
if (!binding) {
|
|
143
|
+
// 理论上不可达(targets 非空且全部已覆盖);fail-closed 防实现漂移
|
|
144
|
+
return { ok: false, text: `无权${opLabel}配方「${recipe.name}」:未找到有效授权绑定。` }
|
|
145
|
+
}
|
|
146
|
+
return { ok: true, binding, hitlRequired: binding.mode === 'manual' || nodeManual || lineManual || channelManual, activeContext: run }
|
|
94
147
|
}
|
|
95
148
|
|
|
96
149
|
/** 新建配方的授权门(尚无 recipe 可绑定):要求 Agent 在该线至少有一个写向绑定
|
|
@@ -34,9 +34,9 @@ export const LEGACY_VERIFY_SPECS: Record<string, ToolVerifySpec> = {
|
|
|
34
34
|
line_status: { refs: [{ param: 'line_id', kind: 'line', mode: 'read' }] },
|
|
35
35
|
line_start: { refs: [{ param: 'recipe_id', kind: 'recipe', mode: 'write' }] },
|
|
36
36
|
recipe_versions: { refs: [{ param: 'recipe_id', kind: 'recipe', mode: 'read' }] },
|
|
37
|
-
recipe_update: { atLeastOne: [{ params: ['recipe_id', 'line_id'], hint: 'recipe_id 或 line_id 至少提供一个:只给 line_id 时按该线当前活动批次解析配方。' }], refs: [{ param: 'recipe_id', kind: 'recipe', mode: 'write' }, { param: 'line_id', kind: 'line', mode: 'read' }], deepNodeRefs: ['params[].node_id'] },
|
|
37
|
+
recipe_update: { atLeastOne: [{ params: ['recipe_id', 'line_id'], hint: 'recipe_id 或 line_id 至少提供一个:只给 line_id 时按该线当前活动批次解析配方。' }], refs: [{ param: 'recipe_id', kind: 'recipe', mode: 'write' }, { param: 'line_id', kind: 'line', mode: 'read' }], deepNodeRefs: ['params[].node_id'], writeNodeRefs: ['params[].node_id'] },
|
|
38
38
|
recipe_rollback: { refs: [{ param: 'recipe_id', kind: 'recipe', mode: 'write' }] },
|
|
39
|
-
recipe_trial: { atLeastOne: [{ params: ['recipe_id', 'line_id'], hint: 'recipe_id 或 line_id 至少提供一个:只给 line_id 时按该线当前活动批次解析配方。' }], refs: [{ param: 'recipe_id', kind: 'recipe', mode: 'write' }, { param: 'line_id', kind: 'line', mode: 'read' }], deepNodeRefs: ['params[].node_id'] },
|
|
39
|
+
recipe_trial: { atLeastOne: [{ params: ['recipe_id', 'line_id'], hint: 'recipe_id 或 line_id 至少提供一个:只给 line_id 时按该线当前活动批次解析配方。' }], refs: [{ param: 'recipe_id', kind: 'recipe', mode: 'write' }, { param: 'line_id', kind: 'line', mode: 'read' }], deepNodeRefs: ['params[].node_id'], writeNodeRefs: ['params[].node_id'] },
|
|
40
40
|
recipe_apply: { atLeastOne: [{ params: ['recipe_id', 'line_id'], hint: 'recipe_id 或 line_id 至少提供一个:只给 line_id 时按该线当前活动批次解析配方。' }], refs: [{ param: 'recipe_id', kind: 'recipe', mode: 'write' }, { param: 'line_id', kind: 'line', mode: 'read' }] },
|
|
41
41
|
recipe_propose: { refs: [{ param: 'recipe_id', kind: 'recipe', mode: 'read' }], deepNodeRefs: ['packages[].params[].node_id'] },
|
|
42
42
|
// 分析与经验面
|
|
@@ -42,6 +42,9 @@ export interface ToolRefFaces {
|
|
|
42
42
|
agentId: string
|
|
43
43
|
/** 绑定面节点:dcw+daq 绑定节点 ∪ 绑定配方参数节点 */
|
|
44
44
|
boundNodes: Set<string>
|
|
45
|
+
/** 写授权面 W(a):dcw 绑定节点 ∪ 绑定配方参数节点(节点级写授权,与 recipe-gate 同口径;
|
|
46
|
+
* daq 只读绑定与频道绑线只读扩权不进写面) */
|
|
47
|
+
writeFace: Set<string>
|
|
45
48
|
/** 仅数采绑定(dcw 工具只认 dcw 绑定,读取面只认 daq 绑定——分面保留) */
|
|
46
49
|
daqBoundNodes: Set<string>
|
|
47
50
|
/** 绑定的配方(kind='recipe' 的绑定,nodeId 字段存 recipeId) */
|
|
@@ -60,6 +63,7 @@ export interface ToolRefFaces {
|
|
|
60
63
|
export function resolveRefFaces(agentId: string): ToolRefFaces {
|
|
61
64
|
const bindings = getAgentNodeBindingRepo().byAgent(agentId)
|
|
62
65
|
const boundNodes = new Set<string>()
|
|
66
|
+
const writeFace = new Set<string>()
|
|
63
67
|
const daqBoundNodes = new Set<string>()
|
|
64
68
|
const boundRecipes = new Set<string>()
|
|
65
69
|
const recipeLines = new Set<string>()
|
|
@@ -72,11 +76,15 @@ export function resolveRefFaces(agentId: string): ToolRefFaces {
|
|
|
72
76
|
recipeLines.add(r.lineId)
|
|
73
77
|
readableLines.add(r.lineId)
|
|
74
78
|
}
|
|
75
|
-
for (const p of (r?.params ?? []))
|
|
79
|
+
for (const p of (r?.params ?? [])) {
|
|
80
|
+
boundNodes.add(p.nodeId)
|
|
81
|
+
writeFace.add(p.nodeId)
|
|
82
|
+
}
|
|
76
83
|
continue
|
|
77
84
|
}
|
|
78
85
|
boundNodes.add(b.nodeId)
|
|
79
86
|
if (b.kind === 'daq') daqBoundNodes.add(b.nodeId)
|
|
87
|
+
else writeFace.add(b.nodeId)
|
|
80
88
|
const n = b.kind === 'dcw' ? getDcwController().byId(b.nodeId) : getDaqNodeRepo().byId(b.nodeId)
|
|
81
89
|
if (n?.lineId) readableLines.add(n.lineId)
|
|
82
90
|
}
|
|
@@ -86,7 +94,7 @@ export function resolveRefFaces(agentId: string): ToolRefFaces {
|
|
|
86
94
|
if (channelLineId) {
|
|
87
95
|
for (const n of getDaqNodeRepo().all()) if (n.lineId === channelLineId) channelLineNodes.add(n.id)
|
|
88
96
|
}
|
|
89
|
-
return { agentId, boundNodes, daqBoundNodes, boundRecipes, recipeLines, readableLines, channelLineId, channelLineNodes }
|
|
97
|
+
return { agentId, boundNodes, writeFace, daqBoundNodes, boundRecipes, recipeLines, readableLines, channelLineId, channelLineNodes }
|
|
90
98
|
}
|
|
91
99
|
|
|
92
100
|
// ===== 归属判定 =====
|
|
@@ -143,6 +151,17 @@ function denyNode(faces: ToolRefFaces, toolName: string, args: Record<string, un
|
|
|
143
151
|
], faces, hint, { param, value: id, lineId: row?.lineId })
|
|
144
152
|
}
|
|
145
153
|
|
|
154
|
+
/** 写面拒绝(2026-10-28):参数要写到的节点必须落在 W(a)=dcw 绑定 ∪ 绑定配方参数节点。
|
|
155
|
+
* 比 denyNode 更明确:读得到 ≠ 写得到(频道绑线只读扩权/他配方节点都不构成写授权)。 */
|
|
156
|
+
function denyWriteNode(faces: ToolRefFaces, toolName: string, args: Record<string, unknown>, param: string, id: string, exists: boolean): HostToolResult {
|
|
157
|
+
const row = exists ? nodeRow(id) : null
|
|
158
|
+
const where = row ? `(节点「${row.name}」${row.lineId ? `,归属产线 ${row.lineId}` : ''})` : '(节点不存在或已删除)'
|
|
159
|
+
const hint = `该节点不在你的写授权面(节点绑定=点位写授权;配方绑定=配方操作权)。你的可写节点:${listOf(faces.writeFace)};你已绑定的配方参数节点也在此列。如需写入,请让用户或 lead 为你绑定该数控节点(dcw),或改用上述已授权节点;若要对整批配方操作,可请求绑定配方(kind='recipe')。`
|
|
160
|
+
return deny(toolName, args, [
|
|
161
|
+
`工具调用被拒(写授权校验,${toolName}):参数节点「${id}」不在你的写授权面内${where}。`,
|
|
162
|
+
], faces, hint, { param, value: id, lineId: row?.lineId })
|
|
163
|
+
}
|
|
164
|
+
|
|
146
165
|
function denyLine(faces: ToolRefFaces, toolName: string, args: Record<string, unknown>, param: string, id: string): HostToolResult {
|
|
147
166
|
const exists = getDcwController().listLines().some(l => l.id === id)
|
|
148
167
|
const hint = `你的可读产线:${listOf(faces.readableLines)}${faces.channelLineId ? `(其中「${faces.channelLineId}」来自频道绑线,只读)` : ''}。如需访问该产线,请让用户授予对应产线权限(权限管理),或把当前频道绑定到该产线;也可以改用上述可读产线重试。`
|
|
@@ -370,6 +389,16 @@ export function verifyToolReferences(agentId: string, toolName: string, args: Re
|
|
|
370
389
|
if (!exists || !nodeAllowed(faces, id)) return denyNode(faces, toolName, args, key, id, exists)
|
|
371
390
|
}
|
|
372
391
|
}
|
|
392
|
+
// 写入型参数节点:必须落在写授权面 W(a)(只读面/频道绑线/他配方节点不构成写授权)。
|
|
393
|
+
// 先于 deepNodeRefs 检查:nodeAllowed 允许"绑定配方所在产线的任意节点",对写入过宽,
|
|
394
|
+
// 写面判定才是本工具真正的授权边界(拒绝文案也更能行动)。
|
|
395
|
+
for (const path of spec.writeNodeRefs ?? []) {
|
|
396
|
+
const leaf = path.split('.').pop() ?? path
|
|
397
|
+
for (const id of collectDeep(args, path)) {
|
|
398
|
+
const exists = nodeRow(id) !== null
|
|
399
|
+
if (!exists || !faces.writeFace.has(id)) return denyWriteNode(faces, toolName, args, leaf, id, exists)
|
|
400
|
+
}
|
|
401
|
+
}
|
|
373
402
|
for (const path of spec.deepNodeRefs ?? []) {
|
|
374
403
|
// 深路径的 param 名用于文案:取最后一段
|
|
375
404
|
const leaf = path.split('.').pop() ?? path
|