@aryacode/dsh-engineering 0.0.0-stage → 0.4.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 +128 -0
- package/LICENSE +21 -0
- package/README.md +230 -2
- package/cordis.patch.yml +123 -0
- package/lib/client.js +3448 -0
- package/lib/index.d.ts +2 -0
- package/lib/index.js +9 -0
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +94 -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,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Preflight and handoff for a manual FactoryTalk View Studio import.
|
|
3
|
+
*
|
|
4
|
+
* This entry never runs a vendor utility. Preflight reports what a Studio
|
|
5
|
+
* import would touch and which prerequisites are missing; handoff returns the
|
|
6
|
+
* ordered manual steps. Automation through `SEAddComponent.exe` is deliberately
|
|
7
|
+
* absent until it can be validated in a licensed environment.
|
|
8
|
+
*
|
|
9
|
+
* @module dsh-engineering-ftview/studio/preflight
|
|
10
|
+
*/
|
|
11
|
+
/** File names the Add Component utility refuses (vendor restriction). */
|
|
12
|
+
const UNSAFE_NAME = /[%.":?*<>#{}\[\]\\/;=&+\-\u2019]/u;
|
|
13
|
+
/**
|
|
14
|
+
* Compare two exports and check whether the changed displays can be imported.
|
|
15
|
+
*
|
|
16
|
+
* @param before - The earlier export.
|
|
17
|
+
* @param after - The edited export.
|
|
18
|
+
* @param discovery - Studio tooling that was found.
|
|
19
|
+
* @returns Changed files, blockers, warnings and readiness.
|
|
20
|
+
*/
|
|
21
|
+
export function studioPreflight(before, after, discovery) {
|
|
22
|
+
const beforeHashes = new Map(before.files.map(file => [file.file, file.sha256]));
|
|
23
|
+
const changed = after.files.filter(file => beforeHashes.get(file.file) !== file.sha256).map(file => file.file);
|
|
24
|
+
const blockers = [];
|
|
25
|
+
const warnings = [];
|
|
26
|
+
if (changed.length === 0)
|
|
27
|
+
blockers.push('No file differs between the two exports, so there is nothing to import.');
|
|
28
|
+
for (const display of after.displays) {
|
|
29
|
+
if (!changed.includes(display.file))
|
|
30
|
+
continue;
|
|
31
|
+
const stem = display.file.replace(/\\/gu, '/').split('/').pop()?.replace(/\.xml$/iu, '') ?? display.file;
|
|
32
|
+
if (UNSAFE_NAME.test(stem))
|
|
33
|
+
blockers.push(`Display file ${JSON.stringify(display.file)} has a name Studio's import refuses; rename it in Studio first.`);
|
|
34
|
+
if (display.parseError !== undefined)
|
|
35
|
+
blockers.push(`Display ${JSON.stringify(display.name)} does not parse: ${display.parseError}`);
|
|
36
|
+
}
|
|
37
|
+
if (discovery.utilities.length === 0)
|
|
38
|
+
warnings.push('No SEAddComponent utility was discovered: only the manual Studio import is available.');
|
|
39
|
+
if (discovery.schemas.length === 0)
|
|
40
|
+
warnings.push('No ME XSD was discovered: schema validation is unavailable.');
|
|
41
|
+
return { changed, blockers, warnings, ready: blockers.length === 0, utility_available: discovery.utilities.length > 0 };
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Produce the ordered manual import plan.
|
|
45
|
+
*
|
|
46
|
+
* @param before - The earlier export.
|
|
47
|
+
* @param after - The edited export.
|
|
48
|
+
* @param discovery - Studio tooling that was found.
|
|
49
|
+
* @returns The plan.
|
|
50
|
+
*/
|
|
51
|
+
export function handoffPlan(before, after, discovery) {
|
|
52
|
+
const changed = studioPreflight(before, after, discovery).changed;
|
|
53
|
+
return {
|
|
54
|
+
application: after.name,
|
|
55
|
+
changed,
|
|
56
|
+
utility_available: discovery.utilities.length > 0,
|
|
57
|
+
steps: [
|
|
58
|
+
{ step: 1, action: `Open the ME application for ${after.name} in FactoryTalk View Studio.` },
|
|
59
|
+
{ step: 2, action: 'Import only the changed display components listed here, using the Graphics editor or Add Component.', files: changed },
|
|
60
|
+
{ step: 3, action: 'Save the application and export a fresh full export folder.' },
|
|
61
|
+
{ step: 4, action: 'Reopen both exports with ftview_open and run ftview_compare to confirm only the intended files changed.' },
|
|
62
|
+
],
|
|
63
|
+
};
|
|
64
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The FTView Studio entry: discovery, preflight and the manual handoff plan.
|
|
3
|
+
*
|
|
4
|
+
* @module dsh-engineering-ftview/studio/tools
|
|
5
|
+
*/
|
|
6
|
+
import { type ToolDefinition } from '../core.ts';
|
|
7
|
+
import type { ViewSession } from '../session.ts';
|
|
8
|
+
/**
|
|
9
|
+
* The Studio tools.
|
|
10
|
+
*
|
|
11
|
+
* @param session - View handles from the read entry.
|
|
12
|
+
* @param roots - Install roots to search.
|
|
13
|
+
* @returns Tool definitions in registration order.
|
|
14
|
+
*/
|
|
15
|
+
export declare function studioTools(session: ViewSession, roots: readonly string[]): readonly ToolDefinition[];
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The FTView Studio entry: discovery, preflight and the manual handoff plan.
|
|
3
|
+
*
|
|
4
|
+
* @module dsh-engineering-ftview/studio/tools
|
|
5
|
+
*/
|
|
6
|
+
import { object, optionalString, requiredString, string, success, tool } from "../core.js";
|
|
7
|
+
import { discoverStudio } from "./discover.js";
|
|
8
|
+
import { handoffPlan, studioPreflight } from "./preflight.js";
|
|
9
|
+
/** Nothing here proves what Studio will do. */
|
|
10
|
+
const COVERAGE = {
|
|
11
|
+
complete: false,
|
|
12
|
+
unverified: [
|
|
13
|
+
'FactoryTalk View Studio import behavior',
|
|
14
|
+
'vendor XSD validation',
|
|
15
|
+
'runtime behavior on the terminal',
|
|
16
|
+
'automation through SEAddComponent (not executed)',
|
|
17
|
+
],
|
|
18
|
+
};
|
|
19
|
+
/** A compact view of the discovery for tool output. */
|
|
20
|
+
function discoveryData(discovery) {
|
|
21
|
+
return {
|
|
22
|
+
searched: discovery.searched,
|
|
23
|
+
products: discovery.products,
|
|
24
|
+
schemas: discovery.schemas,
|
|
25
|
+
utilities: discovery.utilities,
|
|
26
|
+
help: discovery.help,
|
|
27
|
+
truncated: discovery.truncated,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* The Studio tools.
|
|
32
|
+
*
|
|
33
|
+
* @param session - View handles from the read entry.
|
|
34
|
+
* @param roots - Install roots to search.
|
|
35
|
+
* @returns Tool definitions in registration order.
|
|
36
|
+
*/
|
|
37
|
+
export function studioTools(session, roots) {
|
|
38
|
+
return [
|
|
39
|
+
tool({
|
|
40
|
+
name: 'ftview_studio_status',
|
|
41
|
+
description: 'Discover installed FactoryTalk View ME tooling: product folders, ME XSD schemas, help files and the Add Component utility. Read-only; does not start Studio or prove licensing.',
|
|
42
|
+
parameters: object({}),
|
|
43
|
+
async run(_args, exec) {
|
|
44
|
+
const discovery = await discoverStudio(session.fs, roots, exec.signal);
|
|
45
|
+
const products = discovery.products.length;
|
|
46
|
+
const utilities = discovery.utilities.length;
|
|
47
|
+
return success(`Searched ${discovery.searched.length} root(s): ${products} product folder(s), ${utilities} Add Component utility, ${discovery.schemas.length} schema(s).`, discoveryData(discovery), utilities === 0 ? [] : [{ tool: 'ftview_studio_preflight', why: 'Check a change set before importing it.' }], COVERAGE);
|
|
48
|
+
},
|
|
49
|
+
}),
|
|
50
|
+
tool({
|
|
51
|
+
name: 'ftview_studio_preflight',
|
|
52
|
+
description: 'Check the files that differ between two opened exports for a manual Studio import: parse state, name restrictions, discovered utility and schemas. Writes nothing and starts no Studio.',
|
|
53
|
+
parameters: object({
|
|
54
|
+
before_id: string('View id of the export before the edit.'),
|
|
55
|
+
after_id: string('View id of the edited export.'),
|
|
56
|
+
}, ['before_id', 'after_id']),
|
|
57
|
+
async run(args, exec) {
|
|
58
|
+
const before = session.require(requiredString(args, 'before_id'), exec).app;
|
|
59
|
+
const after = session.require(requiredString(args, 'after_id'), exec).app;
|
|
60
|
+
const discovery = await discoverStudio(session.fs, roots, exec.signal);
|
|
61
|
+
const check = studioPreflight(before, after, discovery);
|
|
62
|
+
return success(`${check.changed.length} file(s) differ; ${check.blockers.length} blocker(s).`, { ...check, discovery: discoveryData(discovery) }, check.ready ? [{ tool: 'ftview_studio_handoff', args: { before_id: requiredString(args, 'before_id'), after_id: requiredString(args, 'after_id') }, why: 'Get the ordered manual import steps.' }] : [], COVERAGE);
|
|
63
|
+
},
|
|
64
|
+
}),
|
|
65
|
+
tool({
|
|
66
|
+
name: 'ftview_studio_handoff',
|
|
67
|
+
description: 'Return the ordered manual FactoryTalk View Studio import plan for the changed displays between two opened exports. No vendor utility runs and no file is written.',
|
|
68
|
+
parameters: object({
|
|
69
|
+
before_id: string('View id of the export before the edit.'),
|
|
70
|
+
after_id: string('View id of the edited export.'),
|
|
71
|
+
application: string('ME application name the human opens in Studio. Example: "Line".'),
|
|
72
|
+
}, ['before_id', 'after_id', 'application']),
|
|
73
|
+
async run(args, exec) {
|
|
74
|
+
const before = session.require(requiredString(args, 'before_id'), exec).app;
|
|
75
|
+
const after = session.require(requiredString(args, 'after_id'), exec).app;
|
|
76
|
+
const discovery = await discoverStudio(session.fs, roots, exec.signal);
|
|
77
|
+
const plan = handoffPlan(before, after, discovery);
|
|
78
|
+
const application = optionalString(args, 'application') ?? plan.application;
|
|
79
|
+
return success(`${plan.changed.length} changed file(s) to import into ${application} by hand.`, { ...plan, application }, [], COVERAGE);
|
|
80
|
+
},
|
|
81
|
+
}),
|
|
82
|
+
];
|
|
83
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The FTView Studio entry: discovery, preflight and manual handoff.
|
|
3
|
+
*
|
|
4
|
+
* Deliberately does not execute vendor utilities. The old plugin's
|
|
5
|
+
* `ftview_studio_apply` ran `SEAddComponent.exe` behind a capability probe that
|
|
6
|
+
* only a licensed Studio install can produce; that path is out of scope until
|
|
7
|
+
* it can be validated, so this entry stops at a verifiable handoff.
|
|
8
|
+
*
|
|
9
|
+
* @module dsh-engineering-ftview/studio
|
|
10
|
+
*/
|
|
11
|
+
import type { PluginContext } from './core.ts';
|
|
12
|
+
/** Plugin name in a composition. */
|
|
13
|
+
export declare const name = "ftview-studio";
|
|
14
|
+
/** The read entry's view service must already be mounted. */
|
|
15
|
+
export declare const inject: readonly ["tools", "fs", "ftviewViews"];
|
|
16
|
+
/** Configuration for the Studio entry. */
|
|
17
|
+
export interface Config {
|
|
18
|
+
/** Install roots to search. Defaults to the two Rockwell Programs folders. */
|
|
19
|
+
searchRoots?: string[];
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Register the Studio tools.
|
|
23
|
+
*
|
|
24
|
+
* @param ctx - Plugin context carrying the read entry's view service.
|
|
25
|
+
* @param config - Deployment limits.
|
|
26
|
+
*/
|
|
27
|
+
export declare function apply(ctx: PluginContext, config?: Config): void;
|
|
28
|
+
export { discoverStudio, DEFAULT_SEARCH_ROOTS } from './studio/discover.ts';
|
|
29
|
+
export { handoffPlan, studioPreflight } from './studio/preflight.ts';
|
|
30
|
+
export { studioTools } from './studio/tools.ts';
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The FTView Studio entry: discovery, preflight and manual handoff.
|
|
3
|
+
*
|
|
4
|
+
* Deliberately does not execute vendor utilities. The old plugin's
|
|
5
|
+
* `ftview_studio_apply` ran `SEAddComponent.exe` behind a capability probe that
|
|
6
|
+
* only a licensed Studio install can produce; that path is out of scope until
|
|
7
|
+
* it can be validated, so this entry stops at a verifiable handoff.
|
|
8
|
+
*
|
|
9
|
+
* @module dsh-engineering-ftview/studio
|
|
10
|
+
*/
|
|
11
|
+
import { DEFAULT_SEARCH_ROOTS } from "./studio/discover.js";
|
|
12
|
+
import { studioTools } from "./studio/tools.js";
|
|
13
|
+
/** Plugin name in a composition. */
|
|
14
|
+
export const name = 'ftview-studio';
|
|
15
|
+
/** The read entry's view service must already be mounted. */
|
|
16
|
+
export const inject = ['tools', 'fs', 'ftviewViews'];
|
|
17
|
+
/**
|
|
18
|
+
* Register the Studio tools.
|
|
19
|
+
*
|
|
20
|
+
* @param ctx - Plugin context carrying the read entry's view service.
|
|
21
|
+
* @param config - Deployment limits.
|
|
22
|
+
*/
|
|
23
|
+
export function apply(ctx, config = {}) {
|
|
24
|
+
const session = ctx.get?.('ftviewViews');
|
|
25
|
+
if (session === undefined) {
|
|
26
|
+
throw new Error('ftview-studio: the ftviewViews service is unavailable; mount the FTView read entry before this one');
|
|
27
|
+
}
|
|
28
|
+
const roots = config.searchRoots ?? [...DEFAULT_SEARCH_ROOTS];
|
|
29
|
+
if (!Array.isArray(roots) || roots.some(root => typeof root !== 'string' || root === '')) {
|
|
30
|
+
throw new Error('ftview-studio: searchRoots must be an array of non-empty absolute paths');
|
|
31
|
+
}
|
|
32
|
+
for (const definition of studioTools(session, roots))
|
|
33
|
+
ctx.tools.register(definition);
|
|
34
|
+
}
|
|
35
|
+
export { discoverStudio, DEFAULT_SEARCH_ROOTS } from "./studio/discover.js";
|
|
36
|
+
export { handoffPlan, studioPreflight } from "./studio/preflight.js";
|
|
37
|
+
export { studioTools } from "./studio/tools.js";
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The FTView read surface: six intents over one opened export folder.
|
|
3
|
+
*
|
|
4
|
+
* @module dsh-engineering-ftview/tools
|
|
5
|
+
*/
|
|
6
|
+
import { type ToolDefinition } from './core.ts';
|
|
7
|
+
import type { ViewSession } from './session.ts';
|
|
8
|
+
/**
|
|
9
|
+
* The read tools, bound to one chat's view store.
|
|
10
|
+
*
|
|
11
|
+
* @param session - View handles for the calling chat.
|
|
12
|
+
* @returns Tool definitions in registration order.
|
|
13
|
+
*/
|
|
14
|
+
export declare function ftviewTools(session: ViewSession): readonly ToolDefinition[];
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The FTView read surface: six intents over one opened export folder.
|
|
3
|
+
*
|
|
4
|
+
* @module dsh-engineering-ftview/tools
|
|
5
|
+
*/
|
|
6
|
+
import { EngineeringError, choice, integer, object, optionalInteger, optionalString, requiredString, string, success, tool } from "./core.js";
|
|
7
|
+
import { findingsOf } from "./checks.js";
|
|
8
|
+
/** What this surface cannot prove. */
|
|
9
|
+
const COVERAGE = {
|
|
10
|
+
complete: false,
|
|
11
|
+
unverified: [
|
|
12
|
+
'vendor XSD schema validation',
|
|
13
|
+
'PLC binding and runtime behavior',
|
|
14
|
+
'visual rendering',
|
|
15
|
+
'displays or tags absent from the supplied export',
|
|
16
|
+
],
|
|
17
|
+
};
|
|
18
|
+
/** The view handle parameter, described once. */
|
|
19
|
+
const VIEW_ID = string('View handle returned by ftview_open. Omit when exactly one export is open in this chat.');
|
|
20
|
+
/** Page parameters shared by the list-shaped tools. */
|
|
21
|
+
const PAGE = {
|
|
22
|
+
offset: integer('Number of rows to skip. Default 0.', { minimum: 0, default: 0 }),
|
|
23
|
+
limit: integer('Most rows to return. Default 50, maximum 200.', { minimum: 1, maximum: 200, default: 50 }),
|
|
24
|
+
};
|
|
25
|
+
/** Slice one page. */
|
|
26
|
+
function page(rows, offset, limit) {
|
|
27
|
+
return { rows: rows.slice(offset, offset + limit), offset, limit, total: rows.length, truncated: offset + limit < rows.length };
|
|
28
|
+
}
|
|
29
|
+
/** Display rows shared by list and compare. */
|
|
30
|
+
function displayRows(displays) {
|
|
31
|
+
return displays.map(display => ({ name: display.name, file: display.file, objects: display.objects.length }));
|
|
32
|
+
}
|
|
33
|
+
/** Tag rows shared by list and compare. */
|
|
34
|
+
function tagRows(tags) {
|
|
35
|
+
return tags.map(tag => ({ name: tag.name, type: tag.type, description: tag.description }));
|
|
36
|
+
}
|
|
37
|
+
/** The closest names for a not-found failure. */
|
|
38
|
+
function nearest(names, wanted) {
|
|
39
|
+
const lower = wanted.toLowerCase();
|
|
40
|
+
return names.filter(name => name.toLowerCase().includes(lower)).slice(0, 5);
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* The read tools, bound to one chat's view store.
|
|
44
|
+
*
|
|
45
|
+
* @param session - View handles for the calling chat.
|
|
46
|
+
* @returns Tool definitions in registration order.
|
|
47
|
+
*/
|
|
48
|
+
export function ftviewTools(session) {
|
|
49
|
+
return [
|
|
50
|
+
tool({
|
|
51
|
+
name: 'ftview_open',
|
|
52
|
+
description: 'Open one FactoryTalk View ME export folder and return its application summary: displays, tag rows, structural diagnostics and coverage. Read-only; does not decode MER, contact a terminal, or change files.',
|
|
53
|
+
parameters: object({
|
|
54
|
+
root: string('Export folder containing the display XML files and the HMI tag CSV. Example: "C:/Projects/Line/ME Export".'),
|
|
55
|
+
}, ['root']),
|
|
56
|
+
async run(args, exec) {
|
|
57
|
+
const view = await session.open(requiredString(args, 'root'), exec);
|
|
58
|
+
const { app } = view;
|
|
59
|
+
return success(`${app.name}: ${app.displays.length} displays, ${app.tags.length} tags, ${app.diagnostics.length + app.tagErrors.length} diagnostic(s).`, {
|
|
60
|
+
view_id: view.id,
|
|
61
|
+
root: app.root,
|
|
62
|
+
name: app.name,
|
|
63
|
+
displays: app.displays.length,
|
|
64
|
+
tags: app.tags.length,
|
|
65
|
+
tag_files: app.tagFiles,
|
|
66
|
+
diagnostics: app.diagnostics,
|
|
67
|
+
tag_errors: app.tagErrors,
|
|
68
|
+
files: app.files.length,
|
|
69
|
+
}, [
|
|
70
|
+
{ tool: 'ftview_list', args: { view_id: view.id, kind: 'display' }, why: 'List the displays you can read.' },
|
|
71
|
+
{ tool: 'ftview_check', args: { view_id: view.id }, why: 'See the structural findings before reporting.' },
|
|
72
|
+
], COVERAGE);
|
|
73
|
+
},
|
|
74
|
+
}),
|
|
75
|
+
tool({
|
|
76
|
+
name: 'ftview_list',
|
|
77
|
+
description: 'List the displays or the HMI tags in one opened export, with a name filter and paging.',
|
|
78
|
+
parameters: object({
|
|
79
|
+
view_id: VIEW_ID,
|
|
80
|
+
kind: choice('What to list: the display files or the HMI tag rows.', ['display', 'tag']),
|
|
81
|
+
filter: string('Optional case-insensitive substring for the display or tag name.'),
|
|
82
|
+
...PAGE,
|
|
83
|
+
}, ['kind']),
|
|
84
|
+
async run(args, exec) {
|
|
85
|
+
const { app } = session.require(optionalString(args, 'view_id'), exec);
|
|
86
|
+
const kind = requiredString(args, 'kind');
|
|
87
|
+
const filter = optionalString(args, 'filter')?.toLowerCase();
|
|
88
|
+
const rows = kind === 'tag'
|
|
89
|
+
? tagRows(filter === undefined ? app.tags : app.tags.filter(tag => tag.name.toLowerCase().includes(filter) || tag.description.toLowerCase().includes(filter)))
|
|
90
|
+
: displayRows(filter === undefined ? app.displays : app.displays.filter(display => display.name.toLowerCase().includes(filter) || display.file.toLowerCase().includes(filter)));
|
|
91
|
+
const sliced = page(rows, optionalInteger(args, 'offset') ?? 0, optionalInteger(args, 'limit') ?? 50);
|
|
92
|
+
return success(`${sliced.total} ${kind === 'tag' ? 'tags' : 'displays'}${sliced.truncated ? `; showing ${sliced.offset + 1}-${sliced.offset + sliced.rows.length}` : ''}.`, { kind, ...sliced }, [{ tool: 'ftview_get', args: { view_id: optionalString(args, 'view_id'), kind, name: 'NAME' }, why: 'Read one row in full.' }], COVERAGE);
|
|
93
|
+
},
|
|
94
|
+
}),
|
|
95
|
+
tool({
|
|
96
|
+
name: 'ftview_get',
|
|
97
|
+
description: 'Read one display or one HMI tag in full: a display exposes its named objects; a tag exposes its CSV row.',
|
|
98
|
+
parameters: object({
|
|
99
|
+
view_id: VIEW_ID,
|
|
100
|
+
kind: choice('What to read: one display file or one HMI tag row.', ['display', 'tag']),
|
|
101
|
+
name: string('Exact display name or tag name. Example: "Main" or "StartPB".'),
|
|
102
|
+
}, ['kind', 'name']),
|
|
103
|
+
async run(args, exec) {
|
|
104
|
+
const { app } = session.require(optionalString(args, 'view_id'), exec);
|
|
105
|
+
const kind = requiredString(args, 'kind');
|
|
106
|
+
const name = requiredString(args, 'name');
|
|
107
|
+
if (kind === 'tag') {
|
|
108
|
+
const tag = app.tags.find(candidate => candidate.name === name);
|
|
109
|
+
if (tag === undefined) {
|
|
110
|
+
const candidates = nearest(app.tags.map(candidate => candidate.name), name);
|
|
111
|
+
throw new EngineeringError('COMPONENT_NOT_FOUND', `No tag is named ${JSON.stringify(name)} in this export.`, candidates.length > 0 ? `Pass one of: ${candidates.join(', ')}.` : 'Call ftview_list with kind "tag" to see the exported tag names.', candidates);
|
|
112
|
+
}
|
|
113
|
+
return success(`Tag ${tag.name}.`, tag, [], COVERAGE);
|
|
114
|
+
}
|
|
115
|
+
const display = app.displays.find(candidate => candidate.name === name);
|
|
116
|
+
if (display === undefined) {
|
|
117
|
+
const candidates = nearest(app.displays.map(candidate => candidate.name), name);
|
|
118
|
+
throw new EngineeringError('COMPONENT_NOT_FOUND', `No display is named ${JSON.stringify(name)} in this export.`, candidates.length > 0 ? `Pass one of: ${candidates.join(', ')}.` : 'Call ftview_list with kind "display" to see the exported display names.', candidates);
|
|
119
|
+
}
|
|
120
|
+
return success(`Display ${display.name}: ${display.objects.length} objects.`, { name: display.name, file: display.file, objects: display.objects.slice(0, 200), object_count: display.objects.length }, [], COVERAGE);
|
|
121
|
+
},
|
|
122
|
+
}),
|
|
123
|
+
tool({
|
|
124
|
+
name: 'ftview_find',
|
|
125
|
+
description: 'Search one opened export by name or text across displays, display objects and HMI tags.',
|
|
126
|
+
parameters: object({
|
|
127
|
+
view_id: VIEW_ID,
|
|
128
|
+
query: string('Text to search for, case-insensitive. Example: "StartPB".'),
|
|
129
|
+
limit: integer('Most matches to return. Default 50, maximum 200.', { minimum: 1, maximum: 200, default: 50 }),
|
|
130
|
+
}, ['query']),
|
|
131
|
+
async run(args, exec) {
|
|
132
|
+
const { app } = session.require(optionalString(args, 'view_id'), exec);
|
|
133
|
+
const query = requiredString(args, 'query').toLowerCase();
|
|
134
|
+
const matches = [];
|
|
135
|
+
for (const display of app.displays) {
|
|
136
|
+
if (display.name.toLowerCase().includes(query) || display.file.toLowerCase().includes(query)) {
|
|
137
|
+
matches.push({ kind: 'display', where: display.file, text: display.name });
|
|
138
|
+
}
|
|
139
|
+
for (const object of display.objects) {
|
|
140
|
+
const named = object.name ?? '';
|
|
141
|
+
const values = Object.values(object.attributes);
|
|
142
|
+
const hit = named.toLowerCase().includes(query) || values.some(value => value.toLowerCase().includes(query));
|
|
143
|
+
if (hit)
|
|
144
|
+
matches.push({ kind: 'object', where: display.file, text: named === '' ? object.type : `${object.type} ${named}` });
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
for (const tag of app.tags) {
|
|
148
|
+
if (tag.name.toLowerCase().includes(query) || tag.description.toLowerCase().includes(query)) {
|
|
149
|
+
matches.push({ kind: 'tag', where: 'tag CSV', text: `${tag.name}${tag.description === '' ? '' : `  ${tag.description}`}` });
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
const limit = optionalInteger(args, 'limit') ?? 50;
|
|
153
|
+
return success(`${matches.length} match(es) for ${JSON.stringify(requiredString(args, 'query'))}${matches.length > limit ? `; showing ${limit}` : ''}.`, { query: requiredString(args, 'query'), total: matches.length, matches: matches.slice(0, limit) }, [], COVERAGE);
|
|
154
|
+
},
|
|
155
|
+
}),
|
|
156
|
+
tool({
|
|
157
|
+
name: 'ftview_check',
|
|
158
|
+
description: 'Run structural checks over one opened export: tag CSV integrity, duplicate names within displays and the tag file, and files that did not parse. Vendor XSD validation and PLC binding are not performed.',
|
|
159
|
+
parameters: object({ view_id: VIEW_ID }, []),
|
|
160
|
+
async run(args, exec) {
|
|
161
|
+
const { app } = session.require(optionalString(args, 'view_id'), exec);
|
|
162
|
+
const findings = findingsOf(app);
|
|
163
|
+
const byRule = {};
|
|
164
|
+
for (const finding of findings)
|
|
165
|
+
byRule[finding.rule] = (byRule[finding.rule] ?? 0) + 1;
|
|
166
|
+
return success(`${findings.length} finding(s) in ${app.name}.`, { total: findings.length, by_rule: byRule, findings, coverage: { schema: 'not run', plc_binding: 'not run' } }, [], COVERAGE);
|
|
167
|
+
},
|
|
168
|
+
}),
|
|
169
|
+
tool({
|
|
170
|
+
name: 'ftview_compare',
|
|
171
|
+
description: 'Compare two opened exports by display name, tag name and file content hash.',
|
|
172
|
+
parameters: object({
|
|
173
|
+
before_id: string('View id of the earlier export.'),
|
|
174
|
+
after_id: string('View id of the later export.'),
|
|
175
|
+
...PAGE,
|
|
176
|
+
}, ['before_id', 'after_id']),
|
|
177
|
+
async run(args, exec) {
|
|
178
|
+
const before = session.require(requiredString(args, 'before_id'), exec).app;
|
|
179
|
+
const after = session.require(requiredString(args, 'after_id'), exec).app;
|
|
180
|
+
const names = (app) => ({
|
|
181
|
+
displays: new Map(app.displays.map(display => [display.name, display.file])),
|
|
182
|
+
tags: new Map(app.tags.map(tag => [tag.name, tag.type])),
|
|
183
|
+
});
|
|
184
|
+
const oldNames = names(before);
|
|
185
|
+
const newNames = names(after);
|
|
186
|
+
const diff = (left, right) => ({
|
|
187
|
+
added: [...right.keys()].filter(name => !left.has(name)),
|
|
188
|
+
removed: [...left.keys()].filter(name => !right.has(name)),
|
|
189
|
+
});
|
|
190
|
+
const hashOf = new Map(before.files.map(file => [file.file, file.sha256]));
|
|
191
|
+
const changedFiles = after.files.filter(file => hashOf.get(file.file) !== file.sha256).map(file => file.file);
|
|
192
|
+
const limit = optionalInteger(args, 'limit') ?? 50;
|
|
193
|
+
return success(`${diff(oldNames.displays, newNames.displays).added.length + diff(oldNames.displays, newNames.displays).removed.length} display change(s), ${diff(oldNames.tags, newNames.tags).added.length + diff(oldNames.tags, newNames.tags).removed.length} tag change(s), ${changedFiles.length} changed file(s).`, {
|
|
194
|
+
displays: diff(oldNames.displays, newNames.displays),
|
|
195
|
+
tags: diff(oldNames.tags, newNames.tags),
|
|
196
|
+
files_changed: changedFiles.slice(0, limit),
|
|
197
|
+
truncated: changedFiles.length > limit,
|
|
198
|
+
}, [], COVERAGE);
|
|
199
|
+
},
|
|
200
|
+
}),
|
|
201
|
+
];
|
|
202
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# dsh-engineering-l5x
|
|
2
|
+
|
|
3
|
+
Studio 5000 L5X tools for the `dsh-engineering` bundle. Four loader entries share one package:
|
|
4
|
+
|
|
5
|
+
| Entry | Tools | Needs |
|
|
6
|
+
|---|---|---|
|
|
7
|
+
| `dsh-engineering-l5x` | 11 offline read and reference tools | Node |
|
|
8
|
+
| `dsh-engineering-l5x/editing` | 9 `draft_*` tools | the read entry |
|
|
9
|
+
| `dsh-engineering-l5x/studio` | 6 Logix Designer SDK tools and 7 supervised controller tools | Studio 5000, Logix Designer SDK, FactoryTalk Linx |
|
|
10
|
+
| `dsh-engineering-l5x/live` | `poll_live_tags`, `start_plc_trend` | a reachable controller |
|
|
11
|
+
|
|
12
|
+
The `rockwell-l5x` guidance ships as the separate `dsh-engineering-l5x/skill` entry, and the PLC
|
|
13
|
+
trend card ships as the client face (`dsh.client`).
|
|
14
|
+
|
|
15
|
+
## Read surface
|
|
16
|
+
|
|
17
|
+
`open_project` opens one export and returns a `project_id`. `project_overview`, `list_components` and
|
|
18
|
+
`get_component` walk the structure; `search_logic`, `find_references`, `trace_dependencies`,
|
|
19
|
+
`resolve_tag` and `usage_map` answer where a symbol is used; `run_checks` and `compare_projects`
|
|
20
|
+
produce review evidence. Every result carries `summary`, `data`, `coverage` and `next`.
|
|
21
|
+
|
|
22
|
+
Reading accepts L5X revision 19 or later and never needs Studio. A changed export is refused with
|
|
23
|
+
`STALE_EXPORT`; reopen it before continuing.
|
|
24
|
+
|
|
25
|
+
## Editing
|
|
26
|
+
|
|
27
|
+
`draft_rung`, `draft_st`, `draft_routine`, `draft_tag`, `draft_tag_description`, `draft_udt_member` and
|
|
28
|
+
`draft_udt_description` append one operation per call; `draft_validate` prepares the draft and
|
|
29
|
+
`draft_apply` writes one new L5X beside the source. Nothing overwrites, imports, connects or changes
|
|
30
|
+
a controller.
|
|
31
|
+
|
|
32
|
+
## Studio and controller
|
|
33
|
+
|
|
34
|
+
`studio_sdk_status`, `studio_capabilities`, `export_acd_to_l5x`, `verify_offline_l5x`,
|
|
35
|
+
`build_acd_from_draft` and `studio_review_evidence` cover offline SDK work. The controller tools
|
|
36
|
+
(`capture_controller_snapshot`, `go_online`, `read_controller_mode`, `read_live_tag`,
|
|
37
|
+
`change_controller_mode`, `download_project`, `controller_approval_status`) are read-only unless the
|
|
38
|
+
operation is an upload, mode change or download; those require per-call human approval and a
|
|
39
|
+
`verification_id` from a passed build.
|
|
40
|
+
|
|
41
|
+
## Live
|
|
42
|
+
|
|
43
|
+
`poll_live_tags` and `start_plc_trend` read declared scalar tags over EtherNet/IP without Studio,
|
|
44
|
+
Linx, an ACD or the SDK. They are read-only and report that live logic matching the export is
|
|
45
|
+
unverified.
|
|
46
|
+
|
|
47
|
+
## Configuration
|
|
48
|
+
|
|
49
|
+
| Entry | Option | Default |
|
|
50
|
+
|---|---|---|
|
|
51
|
+
| read | `maxFileBytes` | 64 MiB |
|
|
52
|
+
| editing | `maxFileBytes`, `maxDraftOperations` | 64 MiB, 200 |
|
|
53
|
+
| studio | `maxFileBytes` | 64 MiB |
|
|
54
|
+
| live | `enabled`, `minIntervalMs`, `defaultIntervalMs`, `maxTags`, `maxSamples` | true, 50 ms, 200 ms, 16, 1200 |
|
|
55
|
+
|
|
56
|
+
## Develop
|
|
57
|
+
|
|
58
|
+
```sh
|
|
59
|
+
pnpm install
|
|
60
|
+
pnpm run build
|
|
61
|
+
pnpm test
|
|
62
|
+
```
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: rockwell-l5x
|
|
3
|
+
description: Read and review Studio 5000 L5X exports: open a project, list and read components, find references, trace dependencies, resolve addresses, map bit usage, run checks, and compare exports.
|
|
4
|
+
whenToUse: Use when the user asks about a Studio 5000 L5X export, Logix tags, routines, UDTs, AOIs, I/O modules, references, dependency tracing, checks, or comparison between exports.
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Rockwell L5X engineering
|
|
8
|
+
|
|
9
|
+
## Boundary
|
|
10
|
+
|
|
11
|
+
This surface reads and drafts. A draft writes only a new L5X file: it never overwrites the source, imports into Studio, connects to a controller, changes mode, or writes runtime data.
|
|
12
|
+
|
|
13
|
+
## Start
|
|
14
|
+
|
|
15
|
+
1. Call `open_project` with the export path. Keep the returned `project_id`.
|
|
16
|
+
2. Later calls may omit `project_id` when exactly one project is open; pass it when several are.
|
|
17
|
+
3. A read refuses a changed export with `STALE_EXPORT`; call `open_project` again before continuing.
|
|
18
|
+
|
|
19
|
+
## Find the thing
|
|
20
|
+
|
|
21
|
+
- `list_components` enumerates one kind: program, task, tag, udt, routine, aoi, module, alarm. Filter by name, page with offset and limit.
|
|
22
|
+
- `get_component` reads one in full by exact name. Pass `scope` for a program-scoped tag or routine.
|
|
23
|
+
- `search_logic` finds literal text in rungs, ST lines and diagram labels.
|
|
24
|
+
- `find_references` answers where a symbol is read or written. Use `detail: "evidence"` for rung or line rows with access type and context.
|
|
25
|
+
- `trace_dependencies` follows upstream or downstream operands. It is static evidence, not runtime causality.
|
|
26
|
+
- `resolve_tag` resolves an alias chain, member, integer bit or array subscript.
|
|
27
|
+
- `usage_map` reports which array elements and bits a tag uses and which remain free.
|
|
28
|
+
|
|
29
|
+
## Scope rules
|
|
30
|
+
|
|
31
|
+
- Program-scoped tags and routines need `scope` set to the program name.
|
|
32
|
+
- AOI members are written `AOI_Name::Member`, for example `AOI_EncoderScale::Out_FPM`. `find_references` accepts that spelling; `trace_dependencies` splits it and resolves the member against the AOI.
|
|
33
|
+
- An unresolved symbol is refused with `UNRESOLVED_SYMBOL` and candidate scope names. Do not guess past it; fix the scope or the spelling.
|
|
34
|
+
|
|
35
|
+
## Checks and comparison
|
|
36
|
+
|
|
37
|
+
- `run_checks` returns findings with a rule and severity. Filter with `severity` or `rule`, page with offset and limit. Findings are candidates for engineering review, never proof of a defect or of safety.
|
|
38
|
+
- `compare_projects` diffs two open exports by component. Export timestamps and XML formatting are ignored by design.
|
|
39
|
+
|
|
40
|
+
## Live reads
|
|
41
|
+
|
|
42
|
+
The `l5x-live` entry reads declared scalar tags straight from a controller over EtherNet/IP, without Studio 5000, FactoryTalk Linx or the SDK. It may be disabled; if `poll_live_tags` is unavailable, say live reads are not enabled rather than trying another tool.
|
|
43
|
+
|
|
44
|
+
- `poll_live_tags` takes an explicit IPv4 address, a CPU slot and 1 to 16 declared scalar tags (`BOOL`, `INT`, `DINT`, `REAL`), and returns timestamped sweeps, quality and the controller identity.
|
|
45
|
+
- `start_plc_trend` samples the same way as a session-owned job the chart can follow; stop it through the job controls.
|
|
46
|
+
- These are read-only. Never write controller data, force tags, toggle bits or edit online.
|
|
47
|
+
- Report `live_logic_matches_export: "unverified"`: the read proves a value at a moment, not that the controller runs the exported logic, and sequential reads can miss transitions.
|
|
48
|
+
|
|
49
|
+
## Reporting
|
|
50
|
+
|
|
51
|
+
- Every result carries `summary`, `data`, `coverage` and `next`. Read `coverage` before claiming the answer is complete.
|
|
52
|
+
- State what the export cannot prove: FBD and SFC execution semantics, dynamic indices, runtime behavior, external HMI and driver consumers, and whether online logic matches the export.
|
|
53
|
+
- Quote the program, routine and rung or ST line for each finding.
|
|
54
|
+
- Do not call a tag unused, or an output permanently zero, from the absence of references alone. External consumers are invisible here.
|
|
55
|
+
|
|
56
|
+
## Revisions
|
|
57
|
+
|
|
58
|
+
- Reading accepts any export revision 19 or later. Revisions outside the observed matrix open and report `verification: "untested_revision"`.
|
|
59
|
+
- Reading never needs Studio 5000. The studio entry adds offline import and build, and its controller
|
|
60
|
+
tools handle upload, mode change and download under per-call approval.
|
|
61
|
+
|
|
62
|
+
## Studio
|
|
63
|
+
|
|
64
|
+
The `l5x-studio` entry adds offline SDK work; it may be disabled. Call `studio_sdk_status` before using it.
|
|
65
|
+
|
|
66
|
+
- `export_acd_to_l5x` writes one new full L5X from an ACD. No controller contact.
|
|
67
|
+
- `verify_offline_l5x` imports a full L5X into a new offline ACD and builds it. Build needs project revision 37 or later; `probe: true` attempts a lower revision and reports the SDK error. A Studio-exported L5X builds; an L5X produced by `export_acd_to_l5x` may be refused by the SDK as "no changes".
|
|
68
|
+
- `build_acd_from_draft` imports one changed RLL or ST routine into a new ACD copy, builds it and exports it. The baseline must be an L5X exported from that ACD in this chat, and neither file may change afterwards.
|
|
69
|
+
- `studio_review_evidence` checks program, routine and rung or line names and verbatim quotes before you report findings.
|
|
70
|
+
- `controller_approval_status` reports whether this session can ask a human. `capture_controller_snapshot`, `change_controller_mode` and `download_project` each require per-call human approval of the exact controller, route and target, and proceed only on `allowed-once`; `download_project` accepts only a `verification_id` from a passed build in this chat.
|
|
71
|
+
- `go_online`, `read_controller_mode` and `read_live_tag` are read-only and need no approval. Never write controller data, force tags, toggle bits or edit online.
|
|
72
|
+
|
|
73
|
+
## Editing
|
|
74
|
+
|
|
75
|
+
Editing uses the `draft_*` family. It may be disabled in this profile; if a draft tool is unavailable, say editing is not enabled here rather than trying another tool.
|
|
76
|
+
|
|
77
|
+
- One operation per call: `draft_rung`, `draft_st`, `draft_routine`, `draft_tag`, `draft_tag_description`, `draft_udt_member`, `draft_udt_description`. Each returns the draft id and the operation count.
|
|
78
|
+
- `draft_validate` prepares the accumulated draft against the project and returns its hash, diff summary and check findings. Read the findings before applying.
|
|
79
|
+
- `draft_apply` writes the draft to ONE new `.L5X` path. It never overwrites an existing file.
|
|
80
|
+
- After applying, reopen the written file with `open_project` to review it.
|
|
81
|
+
- Drafts belong to this chat. Another chat starts with none, and an applied draft is cleared.
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Human approval for the controller actions that change a machine.
|
|
3
|
+
*
|
|
4
|
+
* Reading a controller needs no approval. Uploading a snapshot, changing mode
|
|
5
|
+
* and downloading a project each ask the session's approval service for this
|
|
6
|
+
* exact operation, and proceed only on `allowed-once`. A session whose policy
|
|
7
|
+
* is not `ask` cannot request approval at all, and the tool refuses before it
|
|
8
|
+
* touches the controller.
|
|
9
|
+
*
|
|
10
|
+
* @module dsh-engineering-l5x/approvals
|
|
11
|
+
*/
|
|
12
|
+
import type { ExecContext, PluginContext } from './core/tool.ts';
|
|
13
|
+
/** The approval service the composition may provide. */
|
|
14
|
+
export interface ApprovalService {
|
|
15
|
+
request?(request: {
|
|
16
|
+
readonly agent: unknown;
|
|
17
|
+
readonly toolName: string;
|
|
18
|
+
readonly callId: unknown;
|
|
19
|
+
readonly reason: string;
|
|
20
|
+
readonly signal?: AbortSignal;
|
|
21
|
+
}): Promise<string>;
|
|
22
|
+
overrideOf?(session: unknown): 'ask' | 'never' | undefined;
|
|
23
|
+
readonly config?: {
|
|
24
|
+
readonly policy?: 'ask' | 'never';
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Report whether this session can request per-operation approval.
|
|
29
|
+
*
|
|
30
|
+
* @param ctx - Plugin context that may provide the approval service.
|
|
31
|
+
* @param exec - Calling context carrying the agent.
|
|
32
|
+
* @returns The status fields the tool shows the reader.
|
|
33
|
+
*/
|
|
34
|
+
export declare function approvalStatus(ctx: PluginContext, exec?: ExecContext): Record<string, unknown>;
|
|
35
|
+
/**
|
|
36
|
+
* Refuse when approval cannot be requested at all.
|
|
37
|
+
*
|
|
38
|
+
* @param ctx - Plugin context.
|
|
39
|
+
* @param exec - Calling context.
|
|
40
|
+
*/
|
|
41
|
+
export declare function ensureApprovalReady(ctx: PluginContext, exec?: ExecContext): void;
|
|
42
|
+
/**
|
|
43
|
+
* Ask for approval of one exact operation and refuse unless it is granted once.
|
|
44
|
+
*
|
|
45
|
+
* @param ctx - Plugin context.
|
|
46
|
+
* @param exec - Calling context.
|
|
47
|
+
* @param toolName - Tool asking, recorded with the request.
|
|
48
|
+
* @param reason - Exact operation the human is approving.
|
|
49
|
+
*/
|
|
50
|
+
export declare function requireHumanApproval(ctx: PluginContext, exec: ExecContext | undefined, toolName: string, reason: string): Promise<void>;
|