@aryacode/dsh-engineering 0.0.0-stage → 0.3.0
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/CHANGELOG.md +115 -0
- package/LICENSE +21 -0
- package/README.md +228 -2
- package/cordis.patch.yml +122 -0
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +77 -3
- package/vendor/ftview/README.md +55 -0
- package/vendor/ftview/SKILL.md +74 -0
- package/vendor/ftview/lib/checks.d.ts +27 -0
- package/vendor/ftview/lib/checks.js +39 -0
- package/vendor/ftview/lib/core.d.ts +208 -0
- package/vendor/ftview/lib/core.js +160 -0
- package/vendor/ftview/lib/edit/drafts.d.ts +49 -0
- package/vendor/ftview/lib/edit/drafts.js +84 -0
- package/vendor/ftview/lib/edit/operations.d.ts +62 -0
- package/vendor/ftview/lib/edit/operations.js +126 -0
- package/vendor/ftview/lib/edit/prepare.d.ts +38 -0
- package/vendor/ftview/lib/edit/prepare.js +133 -0
- package/vendor/ftview/lib/edit/tools.d.ts +25 -0
- package/vendor/ftview/lib/edit/tools.js +198 -0
- package/vendor/ftview/lib/edit.d.ts +30 -0
- package/vendor/ftview/lib/edit.js +38 -0
- package/vendor/ftview/lib/index.d.ts +31 -0
- package/vendor/ftview/lib/index.js +36 -0
- package/vendor/ftview/lib/model.d.ts +80 -0
- package/vendor/ftview/lib/model.js +267 -0
- package/vendor/ftview/lib/session.d.ts +48 -0
- package/vendor/ftview/lib/session.js +98 -0
- package/vendor/ftview/lib/skill.d.ts +29 -0
- package/vendor/ftview/lib/skill.js +43 -0
- package/vendor/ftview/lib/studio/discover.d.ts +36 -0
- package/vendor/ftview/lib/studio/discover.js +87 -0
- package/vendor/ftview/lib/studio/preflight.d.ts +51 -0
- package/vendor/ftview/lib/studio/preflight.js +64 -0
- package/vendor/ftview/lib/studio/tools.d.ts +15 -0
- package/vendor/ftview/lib/studio/tools.js +83 -0
- package/vendor/ftview/lib/studio.d.ts +30 -0
- package/vendor/ftview/lib/studio.js +37 -0
- package/vendor/ftview/lib/tools.d.ts +14 -0
- package/vendor/ftview/lib/tools.js +202 -0
- package/vendor/l5x/README.md +62 -0
- package/vendor/l5x/SKILL.md +81 -0
- package/vendor/l5x/lib/approvals.d.ts +50 -0
- package/vendor/l5x/lib/approvals.js +93 -0
- package/vendor/l5x/lib/client.js +412 -0
- package/vendor/l5x/lib/client.js.map +1 -0
- package/vendor/l5x/lib/core/errors.d.ts +38 -0
- package/vendor/l5x/lib/core/errors.js +48 -0
- package/vendor/l5x/lib/core/params.d.ts +49 -0
- package/vendor/l5x/lib/core/params.js +39 -0
- package/vendor/l5x/lib/core/result.d.ts +46 -0
- package/vendor/l5x/lib/core/result.js +21 -0
- package/vendor/l5x/lib/core/tool.d.ts +102 -0
- package/vendor/l5x/lib/core/tool.js +19 -0
- package/vendor/l5x/lib/drafts.d.ts +66 -0
- package/vendor/l5x/lib/drafts.js +98 -0
- package/vendor/l5x/lib/editing.d.ts +30 -0
- package/vendor/l5x/lib/editing.js +42 -0
- package/vendor/l5x/lib/engine/acd-edit.d.ts +13 -0
- package/vendor/l5x/lib/engine/acd-edit.js +115 -0
- package/vendor/l5x/lib/engine/analysis.d.ts +22 -0
- package/vendor/l5x/lib/engine/analysis.js +258 -0
- package/vendor/l5x/lib/engine/capabilities.d.ts +9 -0
- package/vendor/l5x/lib/engine/capabilities.js +40 -0
- package/vendor/l5x/lib/engine/change-set.d.ts +122 -0
- package/vendor/l5x/lib/engine/change-set.js +446 -0
- package/vendor/l5x/lib/engine/configuration.d.ts +21 -0
- package/vendor/l5x/lib/engine/configuration.js +42 -0
- package/vendor/l5x/lib/engine/details.d.ts +47 -0
- package/vendor/l5x/lib/engine/details.js +173 -0
- package/vendor/l5x/lib/engine/direct-polling.d.ts +91 -0
- package/vendor/l5x/lib/engine/direct-polling.js +193 -0
- package/vendor/l5x/lib/engine/download-review.d.ts +15 -0
- package/vendor/l5x/lib/engine/download-review.js +63 -0
- package/vendor/l5x/lib/engine/logic-syntax.d.ts +60 -0
- package/vendor/l5x/lib/engine/logic-syntax.js +237 -0
- package/vendor/l5x/lib/engine/observations.d.ts +41 -0
- package/vendor/l5x/lib/engine/observations.js +67 -0
- package/vendor/l5x/lib/engine/project-analysis.d.ts +131 -0
- package/vendor/l5x/lib/engine/project-analysis.js +314 -0
- package/vendor/l5x/lib/engine/review-evidence.d.ts +8 -0
- package/vendor/l5x/lib/engine/review-evidence.js +49 -0
- package/vendor/l5x/lib/engine/review.d.ts +18 -0
- package/vendor/l5x/lib/engine/review.js +123 -0
- package/vendor/l5x/lib/engine/studio-sdk.d.ts +101 -0
- package/vendor/l5x/lib/engine/studio-sdk.js +571 -0
- package/vendor/l5x/lib/engine/tag-resolution.d.ts +20 -0
- package/vendor/l5x/lib/engine/tag-resolution.js +169 -0
- package/vendor/l5x/lib/engine/usage.d.ts +16 -0
- package/vendor/l5x/lib/engine/usage.js +92 -0
- package/vendor/l5x/lib/engine/xml-regions.d.ts +23 -0
- package/vendor/l5x/lib/engine/xml-regions.js +77 -0
- package/vendor/l5x/lib/fs.d.ts +17 -0
- package/vendor/l5x/lib/fs.js +36 -0
- package/vendor/l5x/lib/index.d.ts +36 -0
- package/vendor/l5x/lib/index.js +42 -0
- package/vendor/l5x/lib/live.d.ts +48 -0
- package/vendor/l5x/lib/live.js +48 -0
- package/vendor/l5x/lib/model/aois.d.ts +39 -0
- package/vendor/l5x/lib/model/aois.js +112 -0
- package/vendor/l5x/lib/model/errors.d.ts +15 -0
- package/vendor/l5x/lib/model/errors.js +18 -0
- package/vendor/l5x/lib/model/index.d.ts +20 -0
- package/vendor/l5x/lib/model/index.js +19 -0
- package/vendor/l5x/lib/model/module.d.ts +37 -0
- package/vendor/l5x/lib/model/module.js +54 -0
- package/vendor/l5x/lib/model/modules.d.ts +24 -0
- package/vendor/l5x/lib/model/modules.js +55 -0
- package/vendor/l5x/lib/model/parse.d.ts +32 -0
- package/vendor/l5x/lib/model/parse.js +116 -0
- package/vendor/l5x/lib/model/project.d.ts +15 -0
- package/vendor/l5x/lib/model/project.js +31 -0
- package/vendor/l5x/lib/model/routines.d.ts +71 -0
- package/vendor/l5x/lib/model/routines.js +183 -0
- package/vendor/l5x/lib/model/tags.d.ts +51 -0
- package/vendor/l5x/lib/model/tags.js +119 -0
- package/vendor/l5x/lib/model/types.d.ts +304 -0
- package/vendor/l5x/lib/model/types.js +12 -0
- package/vendor/l5x/lib/model/udts.d.ts +34 -0
- package/vendor/l5x/lib/model/udts.js +81 -0
- package/vendor/l5x/lib/model/xml.d.ts +139 -0
- package/vendor/l5x/lib/model/xml.js +291 -0
- package/vendor/l5x/lib/model/xref.d.ts +28 -0
- package/vendor/l5x/lib/model/xref.js +154 -0
- package/vendor/l5x/lib/session.d.ts +119 -0
- package/vendor/l5x/lib/session.js +236 -0
- package/vendor/l5x/lib/skill.d.ts +30 -0
- package/vendor/l5x/lib/skill.js +44 -0
- package/vendor/l5x/lib/studio-state.d.ts +77 -0
- package/vendor/l5x/lib/studio-state.js +79 -0
- package/vendor/l5x/lib/studio.d.ts +29 -0
- package/vendor/l5x/lib/studio.js +40 -0
- package/vendor/l5x/lib/tools/checks.d.ts +19 -0
- package/vendor/l5x/lib/tools/checks.js +140 -0
- package/vendor/l5x/lib/tools/common.d.ts +41 -0
- package/vendor/l5x/lib/tools/common.js +84 -0
- package/vendor/l5x/lib/tools/controller.d.ts +21 -0
- package/vendor/l5x/lib/tools/controller.js +170 -0
- package/vendor/l5x/lib/tools/drafts.d.ts +23 -0
- package/vendor/l5x/lib/tools/drafts.js +270 -0
- package/vendor/l5x/lib/tools/live.d.ts +21 -0
- package/vendor/l5x/lib/tools/live.js +175 -0
- package/vendor/l5x/lib/tools/read.d.ts +19 -0
- package/vendor/l5x/lib/tools/read.js +245 -0
- package/vendor/l5x/lib/tools/references.d.ts +20 -0
- package/vendor/l5x/lib/tools/references.js +269 -0
- package/vendor/l5x/lib/tools/studio.d.ts +21 -0
- package/vendor/l5x/lib/tools/studio.js +193 -0
- package/vendor/l5x/poll-worker.mjs +77 -0
- package/vendor/l5x/studio-bridge/NuGet.Config +1 -0
- package/vendor/l5x/studio-bridge/Program.cs +304 -0
- package/vendor/l5x/studio-bridge/StudioBridge.csproj +11 -0
- package/vendor/mwi/README.md +60 -0
- package/vendor/mwi/SKILL.md +87 -0
- package/vendor/mwi/bridge/README.md +8 -0
- package/vendor/mwi/bridge/mw_bridge.ps1 +2763 -0
- package/vendor/mwi/bridge/sdk-interop-importer.cs +30 -0
- package/vendor/mwi/bridge/sdk-variable-reader.cs +22 -0
- package/vendor/mwi/bridge/start_bridge.cmd +21 -0
- package/vendor/mwi/bridge/watch_dialogs.cmd +13 -0
- package/vendor/mwi/bridge/watch_dialogs.ps1 +153 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__init__.py +14 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/__init__.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/block_interfaces.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/cfb.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/datatypes.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/declarations.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/errors.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/graphical_listing.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/grid.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/knowledge.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/mwt_bind.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/navigation.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/program_checks.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/project.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/staging.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/structure.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/task_bindings.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/tasks.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/tree.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/validation.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/variable_edit.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/variables.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/workflow.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/block_interfaces.py +275 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/build.py +339 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/capture.py +448 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/cfb.py +765 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/com.py +268 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/datatypes.py +204 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/declarations.py +399 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/descriptions.py +30 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/eip.py +292 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/errors.py +36 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/graphical_listing.py +208 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/grid.py +227 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/ide.py +317 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/iec.py +317 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/installed_help.py +151 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/knowledge.py +218 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/libraries.py +250 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/manuals.py +520 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/mwt_bind.py +133 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/navigation.py +44 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/pou_writer.py +1547 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/program_checks.py +360 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/project.py +704 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/reference_data/catalog.json +672 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/reference_data/patterns.json +86 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/restore.py +208 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/server.py +1913 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/snapshot.py +138 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/st_comments.py +25 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/staging.py +179 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/stlint.py +676 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/structure.py +98 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/task_bindings.py +68 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/tasks.py +162 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/transaction.py +157 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/tree.py +563 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/tree_writer.py +790 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/uia.py +465 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/validation.py +41 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/variable_edit.py +183 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/variables.py +472 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/workflow.py +130 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/writer.py +666 -0
- package/vendor/mwi/code/mw_code.py +1258 -0
- package/vendor/mwi/legacy/edit-session.js +50 -0
- package/vendor/mwi/legacy/fb-insertion.js +74 -0
- package/vendor/mwi/legacy/native-code.js +83 -0
- package/vendor/mwi/legacy/native-groups.js +75 -0
- package/vendor/mwi/legacy/native-structure.js +143 -0
- package/vendor/mwi/legacy/native-variables.js +77 -0
- package/vendor/mwi/legacy/pou-conversion.js +47 -0
- package/vendor/mwi/legacy/pou-package.js +66 -0
- package/vendor/mwi/lib/core/bridge.d.ts +71 -0
- package/vendor/mwi/lib/core/bridge.js +53 -0
- package/vendor/mwi/lib/core/code-engine.d.ts +47 -0
- package/vendor/mwi/lib/core/code-engine.js +109 -0
- package/vendor/mwi/lib/core/errors.d.ts +37 -0
- package/vendor/mwi/lib/core/errors.js +47 -0
- package/vendor/mwi/lib/core/params.d.ts +52 -0
- package/vendor/mwi/lib/core/params.js +46 -0
- package/vendor/mwi/lib/core/ps-bridge.d.ts +62 -0
- package/vendor/mwi/lib/core/ps-bridge.js +127 -0
- package/vendor/mwi/lib/core/result.d.ts +45 -0
- package/vendor/mwi/lib/core/result.js +20 -0
- package/vendor/mwi/lib/core/tool.d.ts +80 -0
- package/vendor/mwi/lib/core/tool.js +55 -0
- package/vendor/mwi/lib/edit/compare.d.ts +33 -0
- package/vendor/mwi/lib/edit/compare.js +70 -0
- package/vendor/mwi/lib/edit.d.ts +20 -0
- package/vendor/mwi/lib/edit.js +32 -0
- package/vendor/mwi/lib/index.d.ts +21 -0
- package/vendor/mwi/lib/index.js +33 -0
- package/vendor/mwi/lib/read/service.d.ts +117 -0
- package/vendor/mwi/lib/read/service.js +229 -0
- package/vendor/mwi/lib/read.d.ts +21 -0
- package/vendor/mwi/lib/read.js +33 -0
- package/vendor/mwi/lib/session/project.d.ts +34 -0
- package/vendor/mwi/lib/session/project.js +54 -0
- package/vendor/mwi/lib/session/service.d.ts +167 -0
- package/vendor/mwi/lib/session/service.js +566 -0
- package/vendor/mwi/lib/session/stage.d.ts +84 -0
- package/vendor/mwi/lib/session/stage.js +187 -0
- package/vendor/mwi/lib/session/workspace.d.ts +52 -0
- package/vendor/mwi/lib/session/workspace.js +90 -0
- package/vendor/mwi/lib/skill.d.ts +36 -0
- package/vendor/mwi/lib/skill.js +56 -0
- package/vendor/mwi/lib/tools/edit.d.ts +20 -0
- package/vendor/mwi/lib/tools/edit.js +286 -0
- package/vendor/mwi/lib/tools/read.d.ts +19 -0
- package/vendor/mwi/lib/tools/read.js +161 -0
- package/vendor/mwi/lib/tools/session.d.ts +20 -0
- package/vendor/mwi/lib/tools/session.js +166 -0
- package/vendor/mwi/lib/tools/verify.d.ts +19 -0
- package/vendor/mwi/lib/tools/verify.js +85 -0
- package/vendor/mwi/lib/verify.d.ts +17 -0
- package/vendor/mwi/lib/verify.js +29 -0
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Provenance for ACD exports and verified builds, scoped to one chat.
|
|
3
|
+
*
|
|
4
|
+
* An ACD export is only usable as a baseline for a later build when this chat
|
|
5
|
+
* exported that exact L5X from that exact ACD: both hashes must still match.
|
|
6
|
+
* The record is what makes that check possible without trusting a path.
|
|
7
|
+
*
|
|
8
|
+
* @module dsh-engineering-l5x/studio-state
|
|
9
|
+
*/
|
|
10
|
+
/** Exports and verified builds, scoped to the chat that ran them. */
|
|
11
|
+
export class StudioState {
|
|
12
|
+
chats = new WeakMap();
|
|
13
|
+
detached = new Map();
|
|
14
|
+
buildChats = new WeakMap();
|
|
15
|
+
detachedBuilds = new Map();
|
|
16
|
+
/**
|
|
17
|
+
* Record one export.
|
|
18
|
+
*
|
|
19
|
+
* @param exec - Calling context; its chat owns the record.
|
|
20
|
+
* @param record - Export binding to remember.
|
|
21
|
+
*/
|
|
22
|
+
record(exec, record) {
|
|
23
|
+
this.chatsOf(exec).set(record.l5xPath.toLowerCase(), record);
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* The export that produced one L5X, if this chat produced it.
|
|
27
|
+
*
|
|
28
|
+
* @param exec - Calling context.
|
|
29
|
+
* @param l5xPath - Resolved L5X path.
|
|
30
|
+
* @returns The export record, or undefined.
|
|
31
|
+
*/
|
|
32
|
+
forL5x(exec, l5xPath) {
|
|
33
|
+
return this.chatsOf(exec).get(l5xPath.toLowerCase());
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Remember one build that passed and may later be downloaded.
|
|
37
|
+
*
|
|
38
|
+
* @param exec - Calling context.
|
|
39
|
+
* @param id - Verification id returned to the caller.
|
|
40
|
+
* @param build - Build binding and its review.
|
|
41
|
+
*/
|
|
42
|
+
addVerified(exec, id, build) {
|
|
43
|
+
this.buildsOf(exec).set(id, build);
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* The build behind one verification id, if this chat produced it.
|
|
47
|
+
*
|
|
48
|
+
* @param exec - Calling context.
|
|
49
|
+
* @param id - Verification id.
|
|
50
|
+
* @returns The verified build, or undefined.
|
|
51
|
+
*/
|
|
52
|
+
verified(exec, id) {
|
|
53
|
+
return this.buildsOf(exec).get(id);
|
|
54
|
+
}
|
|
55
|
+
/** The record map for one calling chat. */
|
|
56
|
+
chatsOf(exec) {
|
|
57
|
+
const chat = exec?.chat;
|
|
58
|
+
if (chat === undefined)
|
|
59
|
+
return this.detached;
|
|
60
|
+
const existing = this.chats.get(chat);
|
|
61
|
+
if (existing !== undefined)
|
|
62
|
+
return existing;
|
|
63
|
+
const created = new Map();
|
|
64
|
+
this.chats.set(chat, created);
|
|
65
|
+
return created;
|
|
66
|
+
}
|
|
67
|
+
/** The verified-build map for one calling chat. */
|
|
68
|
+
buildsOf(exec) {
|
|
69
|
+
const chat = exec?.chat;
|
|
70
|
+
if (chat === undefined)
|
|
71
|
+
return this.detachedBuilds;
|
|
72
|
+
const existing = this.buildChats.get(chat);
|
|
73
|
+
if (existing !== undefined)
|
|
74
|
+
return existing;
|
|
75
|
+
const created = new Map();
|
|
76
|
+
this.buildChats.set(chat, created);
|
|
77
|
+
return created;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The L5X studio entry: offline SDK work over the read entry's project handles.
|
|
3
|
+
*
|
|
4
|
+
* Separate so the SDK's ACD export, build and verification schemas are paid for
|
|
5
|
+
* only where Studio 5000 is installed and the user opted in. No tool in this
|
|
6
|
+
* entry contacts a controller.
|
|
7
|
+
*
|
|
8
|
+
* @module dsh-engineering-l5x/studio
|
|
9
|
+
*/
|
|
10
|
+
import type { PluginContext } from './core/tool.ts';
|
|
11
|
+
/** Plugin name in a composition. */
|
|
12
|
+
export declare const name = "l5x-studio";
|
|
13
|
+
/** The read entry's project service must already be mounted. */
|
|
14
|
+
export declare const inject: readonly ["tools", "l5xProjects"];
|
|
15
|
+
/** Configuration for the studio entry. */
|
|
16
|
+
export interface Config {
|
|
17
|
+
/** Largest export, in bytes, one verification may read. Defaults to 64 MiB. */
|
|
18
|
+
maxFileBytes?: number;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Register the studio tools.
|
|
22
|
+
*
|
|
23
|
+
* @param ctx - Plugin context carrying the read entry's project service.
|
|
24
|
+
* @param config - Deployment limits.
|
|
25
|
+
*/
|
|
26
|
+
export declare function apply(ctx: PluginContext, config?: Config): void;
|
|
27
|
+
export { StudioState } from './studio-state.ts';
|
|
28
|
+
export { studioTools } from './tools/studio.ts';
|
|
29
|
+
export { controllerTools } from './tools/controller.ts';
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The L5X studio entry: offline SDK work over the read entry's project handles.
|
|
3
|
+
*
|
|
4
|
+
* Separate so the SDK's ACD export, build and verification schemas are paid for
|
|
5
|
+
* only where Studio 5000 is installed and the user opted in. No tool in this
|
|
6
|
+
* entry contacts a controller.
|
|
7
|
+
*
|
|
8
|
+
* @module dsh-engineering-l5x/studio
|
|
9
|
+
*/
|
|
10
|
+
import { StudioState } from "./studio-state.js";
|
|
11
|
+
import { studioTools } from "./tools/studio.js";
|
|
12
|
+
import { controllerTools } from "./tools/controller.js";
|
|
13
|
+
import { PROJECTS_SERVICE } from "./index.js";
|
|
14
|
+
/** Plugin name in a composition. */
|
|
15
|
+
export const name = 'l5x-studio';
|
|
16
|
+
/** The read entry's project service must already be mounted. */
|
|
17
|
+
export const inject = ['tools', PROJECTS_SERVICE];
|
|
18
|
+
/**
|
|
19
|
+
* Register the studio tools.
|
|
20
|
+
*
|
|
21
|
+
* @param ctx - Plugin context carrying the read entry's project service.
|
|
22
|
+
* @param config - Deployment limits.
|
|
23
|
+
*/
|
|
24
|
+
export function apply(ctx, config = {}) {
|
|
25
|
+
const session = ctx.get?.(PROJECTS_SERVICE);
|
|
26
|
+
if (session === undefined) {
|
|
27
|
+
throw new Error('l5x-studio: the l5xProjects service is unavailable; mount the L5X read entry before this one');
|
|
28
|
+
}
|
|
29
|
+
const maxFileBytes = config.maxFileBytes ?? 64 * 1024 * 1024;
|
|
30
|
+
if (!Number.isInteger(maxFileBytes) || maxFileBytes <= 0) {
|
|
31
|
+
throw new Error('l5x-studio: maxFileBytes must be a positive integer');
|
|
32
|
+
}
|
|
33
|
+
const state = new StudioState();
|
|
34
|
+
for (const definition of [...studioTools(session, state), ...controllerTools(session, state, ctx)]) {
|
|
35
|
+
ctx.tools.register(definition);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
export { StudioState } from "./studio-state.js";
|
|
39
|
+
export { studioTools } from "./tools/studio.js";
|
|
40
|
+
export { controllerTools } from "./tools/controller.js";
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Checks and comparison: what is wrong with this export, and what changed
|
|
3
|
+
* between two exports.
|
|
4
|
+
*
|
|
5
|
+
* Both tools report evidence and limits rather than verdicts. A check finding is
|
|
6
|
+
* a candidate for engineering review, and a comparison covers parsed engineering
|
|
7
|
+
* content only — never timestamps or XML formatting.
|
|
8
|
+
*
|
|
9
|
+
* @module dsh-engineering-l5x/tools/checks
|
|
10
|
+
*/
|
|
11
|
+
import type { ToolDefinition } from '../core/tool.ts';
|
|
12
|
+
import type { ProjectSession } from '../session.ts';
|
|
13
|
+
/**
|
|
14
|
+
* The checks and comparison tools, bound to one chat's project store.
|
|
15
|
+
*
|
|
16
|
+
* @param session - Project handles for the calling chat.
|
|
17
|
+
* @returns Tool definitions in registration order.
|
|
18
|
+
*/
|
|
19
|
+
export declare function checkTools(session: ProjectSession): readonly ToolDefinition[];
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Checks and comparison: what is wrong with this export, and what changed
|
|
3
|
+
* between two exports.
|
|
4
|
+
*
|
|
5
|
+
* Both tools report evidence and limits rather than verdicts. A check finding is
|
|
6
|
+
* a candidate for engineering review, and a comparison covers parsed engineering
|
|
7
|
+
* content only — never timestamps or XML formatting.
|
|
8
|
+
*
|
|
9
|
+
* @module dsh-engineering-l5x/tools/checks
|
|
10
|
+
*/
|
|
11
|
+
import { runChecks, compareProjects } from "../engine/review.js";
|
|
12
|
+
import { success } from "../core/result.js";
|
|
13
|
+
import { choice, integer, object, string } from "../core/params.js";
|
|
14
|
+
import { COVERAGE, PROJECT_ID, integerArg, optionalString, requiredString, tool } from "./common.js";
|
|
15
|
+
/** Findings are candidates for review, not proof of safety. */
|
|
16
|
+
const CHECK_COVERAGE = {
|
|
17
|
+
complete: false,
|
|
18
|
+
unverified: [
|
|
19
|
+
'findings are heuristics and require engineering review',
|
|
20
|
+
'external HMI, produced/consumed and driver consumers are not visible',
|
|
21
|
+
'absence of findings does not prove controller safety',
|
|
22
|
+
],
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* The checks and comparison tools, bound to one chat's project store.
|
|
26
|
+
*
|
|
27
|
+
* @param session - Project handles for the calling chat.
|
|
28
|
+
* @returns Tool definitions in registration order.
|
|
29
|
+
*/
|
|
30
|
+
export function checkTools(session) {
|
|
31
|
+
return [
|
|
32
|
+
tool({
|
|
33
|
+
name: 'run_checks',
|
|
34
|
+
description: 'Run evidence-backed engineering checks on the open project: unused tags, unresolved references, unscheduled programs and undocumented logic. Pass baseline_id to add I/O change checks against an earlier export.',
|
|
35
|
+
parameters: object({
|
|
36
|
+
baseline_id: string('Optional second open project id to compare against, enabling I/O module change checks.'),
|
|
37
|
+
rule: string('Only return findings with this exact rule name. Example: "UNUSED_TAG".'),
|
|
38
|
+
severity: choice('Only return findings with this severity.', ['info', 'warning', 'critical']),
|
|
39
|
+
offset: integer('Zero-based index of the first finding to return.', { minimum: 0, default: 0 }),
|
|
40
|
+
limit: integer('Maximum findings to return.', { minimum: 1, maximum: 100, default: 20 }),
|
|
41
|
+
project_id: PROJECT_ID,
|
|
42
|
+
}, []),
|
|
43
|
+
async run(args, exec) {
|
|
44
|
+
const project = await session.require(optionalString(args, 'project_id'), exec);
|
|
45
|
+
const baselineId = optionalString(args, 'baseline_id');
|
|
46
|
+
const baseline = baselineId === undefined ? undefined : await session.require(baselineId, exec);
|
|
47
|
+
const result = runChecks(project, baseline);
|
|
48
|
+
const rule = optionalString(args, 'rule');
|
|
49
|
+
const severity = optionalString(args, 'severity');
|
|
50
|
+
const filtered = result.findings.filter(finding => (rule === undefined || finding.rule === rule) && (severity === undefined || finding.severity === severity));
|
|
51
|
+
const offset = integerArg(args, 'offset', 0);
|
|
52
|
+
const limit = Math.min(Math.max(integerArg(args, 'limit', 20), 1), 100);
|
|
53
|
+
const page = filtered.slice(offset, offset + limit);
|
|
54
|
+
const bySeverity = countBy(filtered, finding => finding.severity);
|
|
55
|
+
const byRule = countBy(filtered, finding => finding.rule);
|
|
56
|
+
const symbol = firstSymbol(page);
|
|
57
|
+
return success(`${filtered.length} ${filtered.length === 1 ? 'finding' : 'findings'}${rule === undefined ? '' : ` for rule ${rule}`} in ${project.document.controller.name}.`, {
|
|
58
|
+
total: result.count,
|
|
59
|
+
filtered_total: filtered.length,
|
|
60
|
+
offset,
|
|
61
|
+
limit,
|
|
62
|
+
truncated: offset + limit < filtered.length,
|
|
63
|
+
by_severity: bySeverity,
|
|
64
|
+
by_rule: byRule,
|
|
65
|
+
findings: page,
|
|
66
|
+
note: result.note,
|
|
67
|
+
}, [
|
|
68
|
+
...(symbol === undefined
|
|
69
|
+
? []
|
|
70
|
+
: [{ tool: 'find_references', args: { symbol }, why: 'Inspect the evidence behind the first finding.' }]),
|
|
71
|
+
{ tool: 'list_components', args: { kind: 'tag' }, why: 'Review declared tags before changing anything.' },
|
|
72
|
+
], CHECK_COVERAGE);
|
|
73
|
+
},
|
|
74
|
+
}),
|
|
75
|
+
tool({
|
|
76
|
+
name: 'compare_projects',
|
|
77
|
+
description: 'Compare two open exports by engineering component: programs, tasks, tags, UDTs, AOIs, modules, alarms, routines, rungs, ST bodies and diagrams. Export timestamps and XML formatting are ignored.',
|
|
78
|
+
parameters: object({
|
|
79
|
+
before_id: string('Project id of the earlier export. Open both exports first with open_project.'),
|
|
80
|
+
after_id: string('Project id of the later export.'),
|
|
81
|
+
offset: integer('Zero-based index of the first change to return.', { minimum: 0, default: 0 }),
|
|
82
|
+
limit: integer('Maximum changes to return.', { minimum: 1, maximum: 100, default: 20 }),
|
|
83
|
+
}, ['before_id', 'after_id']),
|
|
84
|
+
async run(args, exec) {
|
|
85
|
+
const before = await session.require(requiredString(args, 'before_id'), exec);
|
|
86
|
+
const after = await session.require(requiredString(args, 'after_id'), exec);
|
|
87
|
+
const result = compareProjects(before, after);
|
|
88
|
+
const offset = integerArg(args, 'offset', 0);
|
|
89
|
+
const limit = Math.min(Math.max(integerArg(args, 'limit', 20), 1), 100);
|
|
90
|
+
const page = result.changes.slice(offset, offset + limit);
|
|
91
|
+
const byComponent = countBy(result.changes, change => change.component);
|
|
92
|
+
const byKind = countBy(result.changes, change => change.kind);
|
|
93
|
+
return success(`${result.count} ${result.count === 1 ? 'change' : 'changes'} between ${before.requestedPath} and ${after.requestedPath}.`, {
|
|
94
|
+
before: result.before,
|
|
95
|
+
after: result.after,
|
|
96
|
+
count: result.count,
|
|
97
|
+
offset,
|
|
98
|
+
limit,
|
|
99
|
+
truncated: offset + limit < result.count,
|
|
100
|
+
by_component: byComponent,
|
|
101
|
+
by_kind: byKind,
|
|
102
|
+
changes: page,
|
|
103
|
+
}, [
|
|
104
|
+
{ tool: 'run_checks', args: { baseline_id: before.id }, why: 'Check I/O module changes against the earlier export.' },
|
|
105
|
+
{ tool: 'get_component', args: { kind: 'program', name: before.document.programs[0]?.name ?? '' }, why: 'Open a changed program in the later export.' },
|
|
106
|
+
], {
|
|
107
|
+
complete: false,
|
|
108
|
+
unverified: [
|
|
109
|
+
'binary or encoded content equivalence',
|
|
110
|
+
'timestamps and XML formatting are ignored by design',
|
|
111
|
+
...COVERAGE.unverified,
|
|
112
|
+
],
|
|
113
|
+
});
|
|
114
|
+
},
|
|
115
|
+
}),
|
|
116
|
+
];
|
|
117
|
+
}
|
|
118
|
+
/** Count values by a key function. */
|
|
119
|
+
function countBy(values, key) {
|
|
120
|
+
const counts = {};
|
|
121
|
+
for (const value of values) {
|
|
122
|
+
const name = key(value);
|
|
123
|
+
counts[name] = (counts[name] ?? 0) + 1;
|
|
124
|
+
}
|
|
125
|
+
return counts;
|
|
126
|
+
}
|
|
127
|
+
/** The first symbol named in a finding's evidence, when there is one. */
|
|
128
|
+
function firstSymbol(findings) {
|
|
129
|
+
for (const finding of findings) {
|
|
130
|
+
const evidence = finding.evidence;
|
|
131
|
+
if (evidence === null || typeof evidence !== 'object')
|
|
132
|
+
continue;
|
|
133
|
+
const record = evidence;
|
|
134
|
+
if (typeof record.tag === 'string' && record.tag !== '')
|
|
135
|
+
return record.tag;
|
|
136
|
+
if (typeof record.symbol === 'string' && record.symbol !== '')
|
|
137
|
+
return record.symbol;
|
|
138
|
+
}
|
|
139
|
+
return undefined;
|
|
140
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared tool scaffolding: the envelope, the failure translation, and the
|
|
3
|
+
* argument readers every tool uses.
|
|
4
|
+
*
|
|
5
|
+
* @module dsh-engineering-l5x/tools/common
|
|
6
|
+
*/
|
|
7
|
+
import { type Parameters } from '../core/params.ts';
|
|
8
|
+
import { type ExecContext, type ToolDefinition } from '../core/tool.ts';
|
|
9
|
+
/** What this surface cannot establish, stated once. */
|
|
10
|
+
export declare const COVERAGE: {
|
|
11
|
+
readonly complete: false;
|
|
12
|
+
readonly unverified: readonly ["FBD and SFC execution semantics", "dynamic indices", "runtime behavior", "external HMI, produced/consumed and driver consumers are not visible", "whether online logic matches the export"];
|
|
13
|
+
};
|
|
14
|
+
/** The handle every read can omit when one project is open. */
|
|
15
|
+
export declare const PROJECT_ID: import("../core/params.ts").ParameterSchema;
|
|
16
|
+
/** One tool body, before the shared envelope and error handling are attached. */
|
|
17
|
+
export interface ToolSpec {
|
|
18
|
+
readonly name: string;
|
|
19
|
+
readonly description: string;
|
|
20
|
+
readonly parameters: Parameters;
|
|
21
|
+
run(args: Readonly<Record<string, unknown>>, exec: ExecContext): Promise<unknown>;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Attach the canonical output declaration and the failure envelope.
|
|
25
|
+
*
|
|
26
|
+
* @param spec - Tool name, description, parameters and body.
|
|
27
|
+
* @returns A registerable tool definition.
|
|
28
|
+
*/
|
|
29
|
+
export declare function tool(spec: ToolSpec): ToolDefinition;
|
|
30
|
+
/** Whole-number argument with a default. */
|
|
31
|
+
export declare function integerArg(args: Readonly<Record<string, unknown>>, name: string, fallback: number): number;
|
|
32
|
+
/** A required string argument. */
|
|
33
|
+
export declare function requiredString(args: Readonly<Record<string, unknown>>, name: string): string;
|
|
34
|
+
/** A string argument, or undefined when absent or empty. */
|
|
35
|
+
export declare function optionalString(args: Readonly<Record<string, unknown>>, name: string): string | undefined;
|
|
36
|
+
/** An integer argument, or undefined when absent or not an integer. */
|
|
37
|
+
export declare function optionalInteger(args: Readonly<Record<string, unknown>>, name: string): number | undefined;
|
|
38
|
+
/** A number argument, or undefined when absent or not a finite number. */
|
|
39
|
+
export declare function optionalNumber(args: Readonly<Record<string, unknown>>, name: string): number | undefined;
|
|
40
|
+
/** The parameters object for a tool that takes no arguments but the project handle. */
|
|
41
|
+
export declare function projectOnly(): Parameters;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared tool scaffolding: the envelope, the failure translation, and the
|
|
3
|
+
* argument readers every tool uses.
|
|
4
|
+
*
|
|
5
|
+
* @module dsh-engineering-l5x/tools/common
|
|
6
|
+
*/
|
|
7
|
+
import { L5xError, errorEnvelope } from "../core/errors.js";
|
|
8
|
+
import { object, string } from "../core/params.js";
|
|
9
|
+
import { jsonText } from "../core/tool.js";
|
|
10
|
+
/** What this surface cannot establish, stated once. */
|
|
11
|
+
export const COVERAGE = {
|
|
12
|
+
complete: false,
|
|
13
|
+
unverified: [
|
|
14
|
+
'FBD and SFC execution semantics',
|
|
15
|
+
'dynamic indices',
|
|
16
|
+
'runtime behavior',
|
|
17
|
+
'external HMI, produced/consumed and driver consumers are not visible',
|
|
18
|
+
'whether online logic matches the export',
|
|
19
|
+
],
|
|
20
|
+
};
|
|
21
|
+
/** The handle every read can omit when one project is open. */
|
|
22
|
+
export const PROJECT_ID = string('Handle returned by open_project. Omit when exactly one project is open in this chat.', {});
|
|
23
|
+
/**
|
|
24
|
+
* Attach the canonical output declaration and the failure envelope.
|
|
25
|
+
*
|
|
26
|
+
* @param spec - Tool name, description, parameters and body.
|
|
27
|
+
* @returns A registerable tool definition.
|
|
28
|
+
*/
|
|
29
|
+
export function tool(spec) {
|
|
30
|
+
return {
|
|
31
|
+
name: spec.name,
|
|
32
|
+
description: spec.description,
|
|
33
|
+
parameters: spec.parameters,
|
|
34
|
+
output: { schema: { type: 'object', additionalProperties: true }, render: jsonText },
|
|
35
|
+
async execute(args, exec) {
|
|
36
|
+
const context = {
|
|
37
|
+
...(exec.signal === undefined ? {} : { signal: exec.signal }),
|
|
38
|
+
...(exec.agent?.session?.header?.cwd === undefined ? {} : { cwd: exec.agent.session.header.cwd }),
|
|
39
|
+
...(exec.agent?.session === undefined ? {} : { chat: exec.agent.session }),
|
|
40
|
+
...(exec.callId === undefined ? {} : { callId: exec.callId }),
|
|
41
|
+
...(exec.sessionId === undefined ? {} : { sessionId: exec.sessionId }),
|
|
42
|
+
...(exec.agent === undefined ? {} : { agent: exec.agent }),
|
|
43
|
+
};
|
|
44
|
+
try {
|
|
45
|
+
return await spec.run(args, context);
|
|
46
|
+
}
|
|
47
|
+
catch (error) {
|
|
48
|
+
return errorEnvelope(error);
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
/** Whole-number argument with a default. */
|
|
54
|
+
export function integerArg(args, name, fallback) {
|
|
55
|
+
const value = args[name];
|
|
56
|
+
return typeof value === 'number' && Number.isInteger(value) ? value : fallback;
|
|
57
|
+
}
|
|
58
|
+
/** A required string argument. */
|
|
59
|
+
export function requiredString(args, name) {
|
|
60
|
+
const value = optionalString(args, name);
|
|
61
|
+
if (value === undefined) {
|
|
62
|
+
throw new L5xError('INVALID_ARGUMENT', `${name} is required.`, `Pass ${name} as a non-empty string.`);
|
|
63
|
+
}
|
|
64
|
+
return value;
|
|
65
|
+
}
|
|
66
|
+
/** A string argument, or undefined when absent or empty. */
|
|
67
|
+
export function optionalString(args, name) {
|
|
68
|
+
const value = args[name];
|
|
69
|
+
return typeof value === 'string' && value !== '' ? value : undefined;
|
|
70
|
+
}
|
|
71
|
+
/** An integer argument, or undefined when absent or not an integer. */
|
|
72
|
+
export function optionalInteger(args, name) {
|
|
73
|
+
const value = args[name];
|
|
74
|
+
return typeof value === 'number' && Number.isInteger(value) ? value : undefined;
|
|
75
|
+
}
|
|
76
|
+
/** A number argument, or undefined when absent or not a finite number. */
|
|
77
|
+
export function optionalNumber(args, name) {
|
|
78
|
+
const value = args[name];
|
|
79
|
+
return typeof value === 'number' && Number.isFinite(value) ? value : undefined;
|
|
80
|
+
}
|
|
81
|
+
/** The parameters object for a tool that takes no arguments but the project handle. */
|
|
82
|
+
export function projectOnly() {
|
|
83
|
+
return object({ project_id: PROJECT_ID }, []);
|
|
84
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The supervised controller lifecycle: upload, connect, mode change, download.
|
|
3
|
+
*
|
|
4
|
+
* Only three actions change a machine — capture, mode change and download —
|
|
5
|
+
* and each one asks for per-call human approval of the exact controller, route
|
|
6
|
+
* and target before it sends anything. Reading is unapproved and read-only.
|
|
7
|
+
*
|
|
8
|
+
* @module dsh-engineering-l5x/tools/controller
|
|
9
|
+
*/
|
|
10
|
+
import type { PluginContext, ToolDefinition } from '../core/tool.ts';
|
|
11
|
+
import type { ProjectSession } from '../session.ts';
|
|
12
|
+
import type { StudioState } from '../studio-state.ts';
|
|
13
|
+
/**
|
|
14
|
+
* The controller tools.
|
|
15
|
+
*
|
|
16
|
+
* @param session - Project handles for the calling chat.
|
|
17
|
+
* @param state - Verified builds and exports for the calling chat.
|
|
18
|
+
* @param ctx - Plugin context that may provide the approval service.
|
|
19
|
+
* @returns Tool definitions in registration order.
|
|
20
|
+
*/
|
|
21
|
+
export declare function controllerTools(session: ProjectSession, state: StudioState, ctx: PluginContext): readonly ToolDefinition[];
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The supervised controller lifecycle: upload, connect, mode change, download.
|
|
3
|
+
*
|
|
4
|
+
* Only three actions change a machine — capture, mode change and download —
|
|
5
|
+
* and each one asks for per-call human approval of the exact controller, route
|
|
6
|
+
* and target before it sends anything. Reading is unapproved and read-only.
|
|
7
|
+
*
|
|
8
|
+
* @module dsh-engineering-l5x/tools/controller
|
|
9
|
+
*/
|
|
10
|
+
import { createHash } from 'node:crypto';
|
|
11
|
+
import { readFile } from 'node:fs/promises';
|
|
12
|
+
import { L5xError } from "../core/errors.js";
|
|
13
|
+
import { success } from "../core/result.js";
|
|
14
|
+
import { choice, object, string } from "../core/params.js";
|
|
15
|
+
import { approvalStatus, ensureApprovalReady, requireHumanApproval } from "../approvals.js";
|
|
16
|
+
import { captureControllerSnapshot, changeControllerMode, controllerActionPreflight, controllerUploadPreflight, downloadProject, goOnline, readControllerMode, readControllerModeForAction, readLiveTag, rememberControllerRoute, resolveControllerRoute, } from "../engine/studio-sdk.js";
|
|
17
|
+
import { PROJECT_ID, optionalString, requiredString, tool } from "./common.js";
|
|
18
|
+
/** A controller answer is not proof of the exported or edited logic. */
|
|
19
|
+
const CONTROLLER_COVERAGE = {
|
|
20
|
+
complete: false,
|
|
21
|
+
unverified: [
|
|
22
|
+
'whether the controller is running the exported logic',
|
|
23
|
+
'physical machine behavior',
|
|
24
|
+
'values change between reads',
|
|
25
|
+
],
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* The controller tools.
|
|
29
|
+
*
|
|
30
|
+
* @param session - Project handles for the calling chat.
|
|
31
|
+
* @param state - Verified builds and exports for the calling chat.
|
|
32
|
+
* @param ctx - Plugin context that may provide the approval service.
|
|
33
|
+
* @returns Tool definitions in registration order.
|
|
34
|
+
*/
|
|
35
|
+
export function controllerTools(session, state, ctx) {
|
|
36
|
+
return [
|
|
37
|
+
tool({
|
|
38
|
+
name: 'controller_approval_status',
|
|
39
|
+
description: 'Read this session\'s permission preset and human-approval policy before upload, mode change or download. Full access normally cannot ask for approval. No SDK, file search or controller access.',
|
|
40
|
+
parameters: object({}),
|
|
41
|
+
async run(_args, exec) {
|
|
42
|
+
const status = approvalStatus(ctx, exec);
|
|
43
|
+
return success(status.approved_controller_actions_requestable
|
|
44
|
+
? 'This session can request per-call approval for controller actions.'
|
|
45
|
+
: 'This session cannot request approval for controller actions.', status, [], CONTROLLER_COVERAGE);
|
|
46
|
+
},
|
|
47
|
+
}),
|
|
48
|
+
tool({
|
|
49
|
+
name: 'capture_controller_snapshot',
|
|
50
|
+
description: 'Upload from the supplied controller IP to a new local ACD. Resolve the exact FactoryTalk Linx route from a named ACD or L5X; requires per-call human approval.',
|
|
51
|
+
parameters: object({
|
|
52
|
+
ip_address: string('Controller IPv4 address the human named. Example: "192.168.1.10".'),
|
|
53
|
+
route_project_path: string('ACD or L5X whose saved communications path names the controller route. Example: "C:/Projects/Line/Line.ACD".'),
|
|
54
|
+
output_path: string('New .ACD path for the upload. It must not exist.'),
|
|
55
|
+
}, ['ip_address', 'route_project_path', 'output_path']),
|
|
56
|
+
async run(args, exec) {
|
|
57
|
+
ensureApprovalReady(ctx, exec);
|
|
58
|
+
const ip = requiredString(args, 'ip_address');
|
|
59
|
+
const routePath = await session.resolvePath(requiredString(args, 'route_project_path'), exec);
|
|
60
|
+
const outputPath = await session.resolvePath(requiredString(args, 'output_path'), exec);
|
|
61
|
+
const commPath = await resolveControllerRoute(ip, routePath, exec.signal);
|
|
62
|
+
const output = await controllerUploadPreflight(commPath, outputPath);
|
|
63
|
+
await requireHumanApproval(ctx, exec, 'capture_controller_snapshot', `Upload controller at user-supplied IP ${ip} using verified FactoryTalk Linx route ${commPath} into new local ACD ${output}`);
|
|
64
|
+
const result = await captureControllerSnapshot(commPath, output, exec.signal);
|
|
65
|
+
await rememberControllerRoute(ip, result.output_path, commPath);
|
|
66
|
+
return success(`Uploaded controller at ${ip} to ${result.output_path}.`, { ip_address: ip, ...result }, [], CONTROLLER_COVERAGE);
|
|
67
|
+
},
|
|
68
|
+
}),
|
|
69
|
+
tool({
|
|
70
|
+
name: 'go_online',
|
|
71
|
+
description: 'Connect a matching ACD to a controller for read-only diagnostics. Does not change mode or controller data.',
|
|
72
|
+
parameters: object({
|
|
73
|
+
snapshot_path: string('ACD uploaded from that controller. Example: "C:/Projects/Line/Upload.ACD".'),
|
|
74
|
+
ip_address: string('Controller IPv4 address. Example: "192.168.1.10".'),
|
|
75
|
+
}, ['snapshot_path', 'ip_address']),
|
|
76
|
+
async run(args, exec) {
|
|
77
|
+
const ip = requiredString(args, 'ip_address');
|
|
78
|
+
const snapshot = await session.resolvePath(requiredString(args, 'snapshot_path'), exec);
|
|
79
|
+
return success(`Connected ${snapshot} to ${ip}.`, { ip_address: ip, ...await goOnline(snapshot, await resolveControllerRoute(ip, snapshot, exec.signal), exec.signal) }, [], CONTROLLER_COVERAGE);
|
|
80
|
+
},
|
|
81
|
+
}),
|
|
82
|
+
tool({
|
|
83
|
+
name: 'read_controller_mode',
|
|
84
|
+
description: 'Read controller mode for the explicitly supplied controller IP. Supply the matching snapshot ACD to avoid reimporting an L5X. Read-only.',
|
|
85
|
+
parameters: object({
|
|
86
|
+
project_id: PROJECT_ID,
|
|
87
|
+
ip_address: string('Controller IPv4 address. Example: "192.168.1.10".'),
|
|
88
|
+
snapshot_path: string('Optional matching ACD upload. Omit to use the open project\u2019s path for the route.'),
|
|
89
|
+
}, ['ip_address']),
|
|
90
|
+
async run(args, exec) {
|
|
91
|
+
const project = await session.require(optionalString(args, 'project_id'), exec);
|
|
92
|
+
const ip = requiredString(args, 'ip_address');
|
|
93
|
+
const snapshotArg = optionalString(args, 'snapshot_path');
|
|
94
|
+
const snapshot = snapshotArg === undefined ? undefined : await session.resolvePath(snapshotArg, exec);
|
|
95
|
+
return success(`Read mode from ${ip}.`, { ip_address: ip, ...await readControllerMode(project, await resolveControllerRoute(ip, snapshot ?? project.path, exec.signal), exec.signal, snapshot) }, [], CONTROLLER_COVERAGE);
|
|
96
|
+
},
|
|
97
|
+
}),
|
|
98
|
+
tool({
|
|
99
|
+
name: 'read_live_tag',
|
|
100
|
+
description: 'Read one controller-scoped scalar BOOL, INT, DINT or REAL from a matching uploaded ACD. Supply the declared data type; read-only.',
|
|
101
|
+
parameters: object({
|
|
102
|
+
snapshot_path: string('ACD uploaded from that controller.'),
|
|
103
|
+
ip_address: string('Controller IPv4 address.'),
|
|
104
|
+
tag: string('Controller-scoped tag name. Example: "LineSpeed".'),
|
|
105
|
+
data_type: choice('Declared scalar type.', ['BOOL', 'INT', 'DINT', 'REAL']),
|
|
106
|
+
}, ['snapshot_path', 'ip_address', 'tag', 'data_type']),
|
|
107
|
+
async run(args, exec) {
|
|
108
|
+
const ip = requiredString(args, 'ip_address');
|
|
109
|
+
const snapshot = await session.resolvePath(requiredString(args, 'snapshot_path'), exec);
|
|
110
|
+
const dataType = requiredString(args, 'data_type');
|
|
111
|
+
return success(`Read ${requiredString(args, 'tag')} from ${ip}.`, { ip_address: ip, ...await readLiveTag(snapshot, await resolveControllerRoute(ip, snapshot, exec.signal), requiredString(args, 'tag'), dataType, exec.signal) }, [], CONTROLLER_COVERAGE);
|
|
112
|
+
},
|
|
113
|
+
}),
|
|
114
|
+
tool({
|
|
115
|
+
name: 'change_controller_mode',
|
|
116
|
+
description: 'Change controller mode to Program, Run or Test. Requires per-call human approval for the exact controller and target mode.',
|
|
117
|
+
parameters: object({
|
|
118
|
+
snapshot_path: string('Matching ACD for the controller.'),
|
|
119
|
+
ip_address: string('Controller IPv4 address.'),
|
|
120
|
+
target_mode: choice('Mode the human approved.', ['Program', 'Run', 'Test']),
|
|
121
|
+
}, ['snapshot_path', 'ip_address', 'target_mode']),
|
|
122
|
+
async run(args, exec) {
|
|
123
|
+
ensureApprovalReady(ctx, exec);
|
|
124
|
+
const ip = requiredString(args, 'ip_address');
|
|
125
|
+
const snapshot = await session.resolvePath(requiredString(args, 'snapshot_path'), exec);
|
|
126
|
+
const mode = requiredString(args, 'target_mode');
|
|
127
|
+
if (!['Program', 'Run', 'Test'].includes(mode)) {
|
|
128
|
+
throw new L5xError('INVALID_MODE', 'target_mode must be Program, Run or Test.', 'Pass one of: Program, Run, Test.');
|
|
129
|
+
}
|
|
130
|
+
const commPath = await resolveControllerRoute(ip, snapshot, exec.signal);
|
|
131
|
+
const preflight = await controllerActionPreflight(snapshot, commPath);
|
|
132
|
+
const currentMode = await readControllerModeForAction(preflight, commPath, exec.signal);
|
|
133
|
+
if (currentMode === mode) {
|
|
134
|
+
throw new L5xError('MODE_ALREADY_SET', `The controller is already in ${mode} mode.`, 'Read the mode and report it; no change is needed.');
|
|
135
|
+
}
|
|
136
|
+
await requireHumanApproval(ctx, exec, 'change_controller_mode', `Change controller at user-supplied IP ${ip} via ${commPath} from ${currentMode} to ${mode} mode using ${preflight.path} (SHA-256 ${preflight.sha256})`);
|
|
137
|
+
return success(`Changed controller at ${ip} from ${currentMode} to ${mode} mode.`, { ip_address: ip, ...await changeControllerMode(preflight, commPath, mode, currentMode, exec.signal) }, [], CONTROLLER_COVERAGE);
|
|
138
|
+
},
|
|
139
|
+
}),
|
|
140
|
+
tool({
|
|
141
|
+
name: 'download_project',
|
|
142
|
+
description: 'Download only an ACD built and reviewed by verify_offline_l5x or build_acd_from_draft in this chat, using its verification_id. Requires per-call human approval; never changes mode automatically.',
|
|
143
|
+
parameters: object({
|
|
144
|
+
verification_id: string('Verification id returned by a passed offline build. Example: "b1f2...".'),
|
|
145
|
+
ip_address: string('Controller IPv4 address the human named.'),
|
|
146
|
+
}, ['verification_id', 'ip_address']),
|
|
147
|
+
async run(args, exec) {
|
|
148
|
+
ensureApprovalReady(ctx, exec);
|
|
149
|
+
const id = requiredString(args, 'verification_id');
|
|
150
|
+
const ip = requiredString(args, 'ip_address');
|
|
151
|
+
const verified = state.verified(exec, id);
|
|
152
|
+
if (verified === undefined) {
|
|
153
|
+
throw new L5xError('UNVERIFIED_ACD', 'No successful SDK build and baseline review exists for this verification_id in this chat.', 'Run verify_offline_l5x or build_acd_from_draft until it returns download_ready true, then use that verification_id.');
|
|
154
|
+
}
|
|
155
|
+
const commPath = await resolveControllerRoute(ip, verified.baselinePath, exec.signal);
|
|
156
|
+
const baselineHash = createHash('sha256').update(await readFile(verified.baselinePath)).digest('hex');
|
|
157
|
+
const sourceHash = createHash('sha256').update(await readFile(verified.sourcePath)).digest('hex');
|
|
158
|
+
if (baselineHash !== verified.review.baseline_sha256 || sourceHash !== verified.review.source_sha256) {
|
|
159
|
+
throw new L5xError('STALE_EXPORT', 'The baseline or edited L5X changed after SDK verification.', 'Build again, then download that new verification id.');
|
|
160
|
+
}
|
|
161
|
+
const preflight = await controllerActionPreflight(verified.path, commPath);
|
|
162
|
+
if (preflight.sha256 !== verified.sha256) {
|
|
163
|
+
throw new L5xError('ACD_CHANGED', 'The built ACD changed after SDK verification.', 'Build again, then download that new verification id.');
|
|
164
|
+
}
|
|
165
|
+
await requireHumanApproval(ctx, exec, 'download_project', `Download ACD ${verified.path} (SHA-256 ${verified.sha256}) to controller at user-supplied IP ${ip} via ${commPath}; mode is not changed`);
|
|
166
|
+
return success(`Downloaded the verified build to ${ip}.`, { ip_address: ip, verification_id: id, ...await downloadProject(preflight, commPath, exec.signal) }, [], CONTROLLER_COVERAGE);
|
|
167
|
+
},
|
|
168
|
+
}),
|
|
169
|
+
];
|
|
170
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The draft family: one operation per call, accumulated, validated, applied.
|
|
3
|
+
*
|
|
4
|
+
* Replaces a single tool that took a JSON string of every operation. Each tool
|
|
5
|
+
* here is flat, names its own intent, and returns the growing operation count,
|
|
6
|
+
* so a small model can see the draft take shape without holding a nested
|
|
7
|
+
* document in its head.
|
|
8
|
+
*
|
|
9
|
+
* @module dsh-engineering-l5x/tools/drafts
|
|
10
|
+
*/
|
|
11
|
+
import type { ToolDefinition } from '../core/tool.ts';
|
|
12
|
+
import type { ProjectSession } from '../session.ts';
|
|
13
|
+
import type { DraftStore } from '../drafts.ts';
|
|
14
|
+
/**
|
|
15
|
+
* The draft tools, bound to one chat's project and draft stores.
|
|
16
|
+
*
|
|
17
|
+
* @param session - Project handles for the calling chat.
|
|
18
|
+
* @param drafts - Drafts for the calling chat.
|
|
19
|
+
* @param maxOperations - Inclusive operations per draft.
|
|
20
|
+
* @param maxFileBytes - Byte budget the preparation may read.
|
|
21
|
+
* @returns Tool definitions in registration order.
|
|
22
|
+
*/
|
|
23
|
+
export declare function draftTools(session: ProjectSession, drafts: DraftStore, maxOperations: number, maxFileBytes: number): readonly ToolDefinition[];
|