@codesys/mcp-toolkit 1.5.7 → 2.0.0-alpha.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.
Files changed (109) hide show
  1. package/CHANGELOG.md +153 -183
  2. package/LICENSE +21 -21
  3. package/README.md +224 -107
  4. package/dist/actions.d.ts +28 -0
  5. package/dist/actions.js +98 -0
  6. package/dist/actions.js.map +1 -0
  7. package/dist/bridge/bridge.d.ts +105 -0
  8. package/dist/bridge/bridge.js +400 -0
  9. package/dist/bridge/bridge.js.map +1 -0
  10. package/dist/cli.d.ts +2 -0
  11. package/dist/cli.js +196 -0
  12. package/dist/cli.js.map +1 -0
  13. package/dist/codesys/cache.d.ts +48 -0
  14. package/dist/codesys/cache.js +82 -0
  15. package/dist/codesys/cache.js.map +1 -0
  16. package/dist/codesys/changes.d.ts +57 -0
  17. package/dist/codesys/changes.js +133 -0
  18. package/dist/codesys/changes.js.map +1 -0
  19. package/dist/codesys/decl.d.ts +45 -0
  20. package/dist/codesys/decl.js +251 -0
  21. package/dist/codesys/decl.js.map +1 -0
  22. package/dist/codesys/devices.d.ts +34 -0
  23. package/dist/codesys/devices.js +82 -0
  24. package/dist/codesys/devices.js.map +1 -0
  25. package/dist/codesys/diff.d.ts +13 -0
  26. package/dist/codesys/diff.js +61 -0
  27. package/dist/codesys/diff.js.map +1 -0
  28. package/dist/codesys/install.d.ts +40 -0
  29. package/dist/codesys/install.js +144 -0
  30. package/dist/codesys/install.js.map +1 -0
  31. package/dist/codesys/library-browse.d.ts +19 -0
  32. package/dist/codesys/library-browse.js +57 -0
  33. package/dist/codesys/library-browse.js.map +1 -0
  34. package/dist/codesys/outline.d.ts +32 -0
  35. package/dist/codesys/outline.js +219 -0
  36. package/dist/codesys/outline.js.map +1 -0
  37. package/dist/codesys/simulation.d.ts +34 -0
  38. package/dist/codesys/simulation.js +57 -0
  39. package/dist/codesys/simulation.js.map +1 -0
  40. package/dist/codesys/st.d.ts +36 -0
  41. package/dist/codesys/st.js +69 -0
  42. package/dist/codesys/st.js.map +1 -0
  43. package/dist/daemon.d.ts +46 -0
  44. package/dist/daemon.js +173 -0
  45. package/dist/daemon.js.map +1 -0
  46. package/dist/driver.d.ts +25 -0
  47. package/dist/driver.js +33 -0
  48. package/dist/driver.js.map +1 -0
  49. package/dist/panel/sim-panel.html +357 -0
  50. package/dist/panel/viewer.html +126 -0
  51. package/dist/panel-types.d.ts +20 -0
  52. package/dist/panel-types.js +3 -0
  53. package/dist/panel-types.js.map +1 -0
  54. package/dist/panel.d.ts +27 -0
  55. package/dist/panel.js +208 -0
  56. package/dist/panel.js.map +1 -0
  57. package/dist/server.d.ts +5 -0
  58. package/dist/server.js +33 -941
  59. package/dist/server.js.map +1 -0
  60. package/dist/tools.d.ts +103 -0
  61. package/dist/tools.js +793 -0
  62. package/dist/tools.js.map +1 -0
  63. package/dist/viewer.d.ts +14 -0
  64. package/dist/viewer.js +125 -0
  65. package/dist/viewer.js.map +1 -0
  66. package/package.json +37 -21
  67. package/watcher/mcp_compat.py +147 -0
  68. package/watcher/mcp_ops.py +1178 -0
  69. package/watcher/mcp_sim.py +246 -0
  70. package/watcher/watcher.py +255 -0
  71. package/.claude/settings.local.json +0 -29
  72. package/CLAUDE.md +0 -26
  73. package/CONTRIBUTING.md +0 -72
  74. package/README.md.enhanced +0 -155
  75. package/codesys-mcp-toolkit-1.2.0.tgz +0 -0
  76. package/codesys-mcp-toolkit-1.2.1.tgz +0 -0
  77. package/codesys-mcp-toolkit-1.5.7.tgz +0 -0
  78. package/dist/bin.js +0 -75
  79. package/dist/check_session_paths.js +0 -149
  80. package/dist/codesys_interop.js +0 -435
  81. package/dist/direct_session_command.js +0 -293
  82. package/dist/scriptLoader.js +0 -165
  83. package/dist/scripts/check_status.py +0 -21
  84. package/dist/scripts/compile_project.py +0 -65
  85. package/dist/scripts/create_method.py +0 -80
  86. package/dist/scripts/create_pou.py +0 -102
  87. package/dist/scripts/create_project.py +0 -116
  88. package/dist/scripts/create_property.py +0 -78
  89. package/dist/scripts/ensure_project_open.py +0 -154
  90. package/dist/scripts/find_object_by_path.py +0 -111
  91. package/dist/scripts/get_pou_code.py +0 -91
  92. package/dist/scripts/get_project_structure.py +0 -72
  93. package/dist/scripts/open_project.py +0 -25
  94. package/dist/scripts/save_project.py +0 -29
  95. package/dist/scripts/session_status.py +0 -281
  96. package/dist/scripts/set_pou_code.py +0 -109
  97. package/dist/scripts/start_session.py +0 -809
  98. package/dist/scripts/stop_session.py +0 -324
  99. package/dist/sessionManager.js +0 -798
  100. package/docs/configuration.md +0 -141
  101. package/docs/index.md +0 -26
  102. package/docs/installation.md +0 -84
  103. package/docs/session_management.md +0 -110
  104. package/examples/README.md +0 -78
  105. package/examples/sample_config.json +0 -32
  106. package/replacements_session_status.txt +0 -16
  107. package/scriptDoc.txt +0 -8679
  108. package/scripts/build.js +0 -25
  109. package/scripts/fix_fstrings.py +0 -76
@@ -1,798 +0,0 @@
1
- "use strict";
2
- /**
3
- * sessionManager.ts
4
- * Manages a persistent CODESYS session for improved performance
5
- *
6
- * This module implements a session management system that allows
7
- * keeping a CODESYS instance running for multiple operations, rather
8
- * than starting and stopping CODESYS for each command.
9
- */
10
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
11
- if (k2 === undefined) k2 = k;
12
- var desc = Object.getOwnPropertyDescriptor(m, k);
13
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
14
- desc = { enumerable: true, get: function() { return m[k]; } };
15
- }
16
- Object.defineProperty(o, k2, desc);
17
- }) : (function(o, m, k, k2) {
18
- if (k2 === undefined) k2 = k;
19
- o[k2] = m[k];
20
- }));
21
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
22
- Object.defineProperty(o, "default", { enumerable: true, value: v });
23
- }) : function(o, v) {
24
- o["default"] = v;
25
- });
26
- var __importStar = (this && this.__importStar) || (function () {
27
- var ownKeys = function(o) {
28
- ownKeys = Object.getOwnPropertyNames || function (o) {
29
- var ar = [];
30
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
31
- return ar;
32
- };
33
- return ownKeys(o);
34
- };
35
- return function (mod) {
36
- if (mod && mod.__esModule) return mod;
37
- var result = {};
38
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
39
- __setModuleDefault(result, mod);
40
- return result;
41
- };
42
- })();
43
- var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
44
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
45
- return new (P || (P = Promise))(function (resolve, reject) {
46
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
47
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
48
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
49
- step((generator = generator.apply(thisArg, _arguments || [])).next());
50
- });
51
- };
52
- Object.defineProperty(exports, "__esModule", { value: true });
53
- exports.CodesysSessionManager = exports.SessionState = exports.SESSION_EVENTS = void 0;
54
- const child_process_1 = require("child_process");
55
- const promises_1 = require("fs/promises");
56
- const path = __importStar(require("path"));
57
- const os = __importStar(require("os"));
58
- const fs = __importStar(require("fs"));
59
- const events_1 = require("events");
60
- const uuid_1 = require("uuid");
61
- const scriptLoader_1 = require("./scriptLoader");
62
- // Default timeout values
63
- const DEFAULT_SCRIPT_TIMEOUT = 60000; // 60 seconds
64
- const DEFAULT_SESSION_IDLE_TIMEOUT = 300000; // 5 minutes
65
- const DEFAULT_SESSION_POLL_INTERVAL = 1000; // 1 second
66
- // Session directory and file names
67
- const SESSION_DIR = path.join(os.tmpdir(), 'codesys_session');
68
- const COMMAND_FILE = 'command.json';
69
- const RESULT_FILE = 'result.json';
70
- const STATE_FILE = 'session_state.json';
71
- const SESSION_LOCK_FILE = 'session.lock';
72
- // Define expected success/error markers from the Python scripts
73
- const SCRIPT_SUCCESS_MARKER = 'SCRIPT_SUCCESS';
74
- const SCRIPT_ERROR_MARKER = 'SCRIPT_ERROR';
75
- // Event names
76
- exports.SESSION_EVENTS = {
77
- STARTED: 'session:started',
78
- FAILED: 'session:failed',
79
- STOPPED: 'session:stopped',
80
- TIMEOUT: 'session:timeout',
81
- SCRIPT_COMPLETE: 'script:complete',
82
- SCRIPT_ERROR: 'script:error'
83
- };
84
- /**
85
- * Session state constants
86
- */
87
- exports.SessionState = {
88
- IDLE: 'idle',
89
- STARTING: 'starting',
90
- RUNNING: 'running',
91
- BUSY: 'busy',
92
- STOPPING: 'stopping',
93
- STOPPED: 'stopped',
94
- ERROR: 'error'
95
- };
96
- /**
97
- * Class to manage a persistent CODESYS session
98
- */
99
- class CodesysSessionManager extends events_1.EventEmitter {
100
- /**
101
- * Creates a new CODESYS session manager
102
- *
103
- * @param codesysExePath Path to CODESYS.exe
104
- * @param codesysProfileName CODESYS profile name
105
- * @param idleTimeoutMs Optional idle timeout in milliseconds
106
- */
107
- constructor(codesysExePath, codesysProfileName, idleTimeoutMs = DEFAULT_SESSION_IDLE_TIMEOUT) {
108
- super();
109
- this.codesysProcess = null;
110
- this.sessionState = exports.SessionState.STOPPED;
111
- this.idleTimeout = null;
112
- this.pendingScripts = new Map();
113
- if (!codesysExePath)
114
- throw new Error('CODESYS executable path was not provided.');
115
- if (!codesysProfileName)
116
- throw new Error('CODESYS profile name was not provided.');
117
- if (!fs.existsSync(codesysExePath)) {
118
- throw new Error(`CODESYS executable not found at provided path: ${codesysExePath}`);
119
- }
120
- this.codesysExePath = codesysExePath;
121
- this.codesysProfileName = codesysProfileName;
122
- this.idleTimeoutMs = idleTimeoutMs;
123
- this.sessionId = (0, uuid_1.v4)();
124
- // Set up session directory and files
125
- // IMPORTANT: Don't use a subdirectory - Python scripts expect files directly in SESSION_DIR
126
- this.sessionDir = SESSION_DIR;
127
- this.commandFilePath = path.join(this.sessionDir, COMMAND_FILE);
128
- this.resultFilePath = path.join(this.sessionDir, RESULT_FILE);
129
- this.stateFilePath = path.join(this.sessionDir, STATE_FILE);
130
- this.lockFilePath = path.join(this.sessionDir, SESSION_LOCK_FILE);
131
- // Log status changes through event emitter
132
- this.on(exports.SESSION_EVENTS.STARTED, () => {
133
- console.error(`SESSION_MANAGER: Session started successfully`);
134
- this.sessionState = exports.SessionState.RUNNING;
135
- this.resetIdleTimeout();
136
- });
137
- this.on(exports.SESSION_EVENTS.FAILED, (error) => {
138
- console.error(`SESSION_MANAGER: Session failed to start: ${error.message}`);
139
- this.sessionState = exports.SessionState.ERROR;
140
- });
141
- this.on(exports.SESSION_EVENTS.STOPPED, () => {
142
- console.error(`SESSION_MANAGER: Session stopped`);
143
- this.sessionState = exports.SessionState.STOPPED;
144
- });
145
- this.on(exports.SESSION_EVENTS.TIMEOUT, () => {
146
- console.error(`SESSION_MANAGER: Session timed out due to inactivity`);
147
- this.stopSession();
148
- });
149
- }
150
- /**
151
- * Get current session state
152
- */
153
- getState() {
154
- return this.sessionState;
155
- }
156
- /**
157
- * Get the session directory
158
- */
159
- getSessionDir() {
160
- return this.sessionDir;
161
- }
162
- /**
163
- * Get the session ID
164
- */
165
- getSessionId() {
166
- return this.sessionId;
167
- }
168
- /**
169
- * Check if the session is active
170
- * @returns Promise that resolves to true if the session is active
171
- */
172
- isSessionActive() {
173
- return __awaiter(this, void 0, void 0, function* () {
174
- try {
175
- // If our internal state is not running, return false immediately
176
- if (this.sessionState !== exports.SessionState.RUNNING && this.sessionState !== exports.SessionState.BUSY) {
177
- return false;
178
- }
179
- // Check if session directory exists
180
- if (!fs.existsSync(this.sessionDir)) {
181
- return false;
182
- }
183
- // Check if state file exists
184
- if (!fs.existsSync(this.stateFilePath)) {
185
- return false;
186
- }
187
- // Request status from the running session
188
- const statusResult = yield this.executeScript('print("SCRIPT_SUCCESS: Session is active")', 5000);
189
- return statusResult.success;
190
- }
191
- catch (error) {
192
- console.error(`SESSION_MANAGER: Error checking session status: ${error}`);
193
- return false;
194
- }
195
- });
196
- }
197
- /**
198
- * Get detailed session information
199
- * @returns Promise that resolves to session information
200
- */
201
- getSessionInfo() {
202
- return __awaiter(this, void 0, void 0, function* () {
203
- try {
204
- // Check if session is active
205
- const isActive = yield this.isSessionActive();
206
- if (!isActive) {
207
- return null;
208
- }
209
- // Read the state file
210
- const stateContent = yield (0, promises_1.readFile)(this.stateFilePath, 'utf8');
211
- const stateData = JSON.parse(stateContent);
212
- return stateData;
213
- }
214
- catch (error) {
215
- console.error(`SESSION_MANAGER: Error getting session info: ${error}`);
216
- return null;
217
- }
218
- });
219
- }
220
- /**
221
- * Start a CODESYS session
222
- * @returns Promise that resolves when the session is started
223
- */
224
- startSession() {
225
- return __awaiter(this, void 0, void 0, function* () {
226
- if (this.sessionState !== exports.SessionState.STOPPED && this.sessionState !== exports.SessionState.ERROR) {
227
- console.error(`SESSION_MANAGER: Cannot start session - already in state: ${this.sessionState}`);
228
- return Promise.resolve(); // Session already running
229
- }
230
- this.sessionState = exports.SessionState.STARTING;
231
- try {
232
- console.error(`SESSION_MANAGER: Starting CODESYS session with profile: ${this.codesysProfileName}`);
233
- // Create session directory if it doesn't exist
234
- if (!fs.existsSync(SESSION_DIR)) {
235
- fs.mkdirSync(SESSION_DIR, { recursive: true });
236
- }
237
- // Create our specific session directory
238
- if (!fs.existsSync(this.sessionDir)) {
239
- fs.mkdirSync(this.sessionDir, { recursive: true });
240
- }
241
- // Create initial state file
242
- const initialState = {
243
- sessionId: this.sessionId,
244
- status: exports.SessionState.STARTING,
245
- startTime: new Date().toISOString()
246
- };
247
- yield (0, promises_1.writeFile)(this.stateFilePath, JSON.stringify(initialState, null, 2), 'utf8');
248
- // Load the start_session script template
249
- const scriptTemplate = yield (0, scriptLoader_1.loadScriptTemplate)('start_session');
250
- // Interpolate parameters
251
- const scriptContent = (0, scriptLoader_1.interpolateScript)(scriptTemplate, {
252
- SESSION_DIR: this.sessionDir,
253
- COMMAND_FILE: COMMAND_FILE,
254
- RESULT_FILE: RESULT_FILE,
255
- STATE_FILE: STATE_FILE,
256
- SESSION_ID: this.sessionId,
257
- });
258
- // Create a temp file for the script
259
- const tempDir = os.tmpdir();
260
- const tempFileName = `codesys_start_session_${Date.now()}_${Math.random().toString(36).substring(2, 9)}.py`;
261
- const tempFilePath = path.join(tempDir, tempFileName);
262
- // Write the script to the temp file
263
- yield (0, promises_1.writeFile)(tempFilePath, scriptContent, 'latin1');
264
- // Prepare CODESYS process
265
- const codesysDir = path.dirname(this.codesysExePath);
266
- const quotedExePath = `"${this.codesysExePath}"`;
267
- const profileArg = `--profile="${this.codesysProfileName}"`;
268
- // Start CODESYS with the session script - in "visible" mode so we can see it's running
269
- const fullCommandString = `${quotedExePath} ${profileArg} --runscript="${tempFilePath}"`;
270
- // Create a modified environment with CODESYS bin dir in PATH
271
- const spawnEnv = Object.assign({}, process.env);
272
- const pathSeparator = os.platform() === 'win32' ? ';' : ':';
273
- const originalPath = spawnEnv.PATH || '';
274
- spawnEnv.PATH = `${codesysDir}${pathSeparator}${originalPath}`;
275
- // Spawn the process
276
- this.codesysProcess = (0, child_process_1.spawn)(fullCommandString, [], {
277
- windowsHide: false, // Make visible to user
278
- cwd: codesysDir,
279
- env: spawnEnv,
280
- shell: true
281
- });
282
- // Handle process events
283
- this.codesysProcess.on('error', (error) => {
284
- console.error(`SESSION_MANAGER: Error starting CODESYS: ${error.message}`);
285
- this.emit(exports.SESSION_EVENTS.FAILED, error);
286
- this.sessionState = exports.SessionState.ERROR;
287
- });
288
- this.codesysProcess.on('exit', (code) => {
289
- console.error(`SESSION_MANAGER: CODESYS process exited with code ${code}`);
290
- // Only change state if we're not already stopping
291
- if (this.sessionState !== exports.SessionState.STOPPING && this.sessionState !== exports.SessionState.STOPPED) {
292
- this.sessionState = exports.SessionState.ERROR;
293
- this.emit(exports.SESSION_EVENTS.FAILED, new Error(`CODESYS process exited unexpectedly with code ${code}`));
294
- }
295
- });
296
- // Wait for session to initialize by checking for the existence of pipes
297
- const success = yield this.waitForSessionInitialization();
298
- if (success) {
299
- this.sessionState = exports.SessionState.RUNNING;
300
- this.emit(exports.SESSION_EVENTS.STARTED);
301
- this.resetIdleTimeout();
302
- }
303
- else {
304
- throw new Error('Session failed to initialize within timeout');
305
- }
306
- }
307
- catch (error) {
308
- console.error(`SESSION_MANAGER: Error during session start: ${error.message}`);
309
- this.sessionState = exports.SessionState.ERROR;
310
- this.emit(exports.SESSION_EVENTS.FAILED, error);
311
- throw error;
312
- }
313
- });
314
- }
315
- /**
316
- * Wait for session initialization by checking for the existence of the pipes
317
- * @param timeoutMs Timeout in milliseconds
318
- * @returns Promise that resolves to true if session initialized successfully
319
- */
320
- waitForSessionInitialization() {
321
- return __awaiter(this, arguments, void 0, function* (timeoutMs = 30000) {
322
- const startTime = Date.now();
323
- while (Date.now() - startTime < timeoutMs) {
324
- try {
325
- // Check if files exist
326
- const commandFileExists = fs.existsSync(this.commandFilePath);
327
- const resultFileExists = fs.existsSync(this.resultFilePath);
328
- if (commandFileExists && resultFileExists) {
329
- // Read state file to check if session is ready
330
- const stateContent = yield (0, promises_1.readFile)(this.stateFilePath, 'utf8');
331
- const stateData = JSON.parse(stateContent);
332
- if (stateData.status === exports.SessionState.RUNNING) {
333
- console.error(`SESSION_MANAGER: Session initialized successfully`);
334
- return true;
335
- }
336
- }
337
- // Wait a bit before checking again
338
- yield new Promise(resolve => setTimeout(resolve, DEFAULT_SESSION_POLL_INTERVAL));
339
- }
340
- catch (error) {
341
- console.error(`SESSION_MANAGER: Error checking session initialization: ${error}`);
342
- // Continue waiting
343
- }
344
- }
345
- console.error(`SESSION_MANAGER: Session initialization timed out after ${timeoutMs}ms`);
346
- return false;
347
- });
348
- }
349
- /**
350
- * Stop the CODESYS session
351
- */
352
- stopSession() {
353
- return __awaiter(this, void 0, void 0, function* () {
354
- if (this.sessionState === exports.SessionState.STOPPED) {
355
- console.error('SESSION_MANAGER: Session already stopped');
356
- return Promise.resolve();
357
- }
358
- this.sessionState = exports.SessionState.STOPPING;
359
- // Clear idle timeout if it exists
360
- if (this.idleTimeout) {
361
- clearTimeout(this.idleTimeout);
362
- this.idleTimeout = null;
363
- }
364
- // Reject any pending scripts
365
- for (const [id, pending] of this.pendingScripts.entries()) {
366
- clearTimeout(pending.timeout);
367
- pending.reject(new Error('Session was stopped while script was pending'));
368
- this.pendingScripts.delete(id);
369
- }
370
- try {
371
- // Check if we have a running session
372
- if (fs.existsSync(this.sessionDir) && fs.existsSync(this.commandFilePath)) {
373
- // Load the stop_session script template
374
- const scriptTemplate = yield (0, scriptLoader_1.loadScriptTemplate)('stop_session');
375
- // Interpolate parameters
376
- const scriptContent = (0, scriptLoader_1.interpolateScript)(scriptTemplate, {
377
- SESSION_DIR: this.sessionDir,
378
- COMMAND_FILE: COMMAND_FILE,
379
- RESULT_FILE: RESULT_FILE,
380
- STATE_FILE: STATE_FILE,
381
- SESSION_ID: this.sessionId,
382
- });
383
- try {
384
- // Execute the stop script
385
- const tempDir = os.tmpdir();
386
- const tempFileName = `codesys_stop_session_${Date.now()}_${Math.random().toString(36).substring(2, 9)}.py`;
387
- const tempFilePath = path.join(tempDir, tempFileName);
388
- // Write the script to the temp file
389
- yield (0, promises_1.writeFile)(tempFilePath, scriptContent, 'latin1');
390
- // Prepare CODESYS process
391
- const codesysDir = path.dirname(this.codesysExePath);
392
- const quotedExePath = `"${this.codesysExePath}"`;
393
- const profileArg = `--profile="${this.codesysProfileName}"`;
394
- // Execute the stop script
395
- const fullCommandString = `${quotedExePath} ${profileArg} --noUI --runscript="${tempFilePath}"`;
396
- // Create a modified environment with CODESYS bin dir in PATH
397
- const spawnEnv = Object.assign({}, process.env);
398
- const pathSeparator = os.platform() === 'win32' ? ';' : ':';
399
- const originalPath = spawnEnv.PATH || '';
400
- spawnEnv.PATH = `${codesysDir}${pathSeparator}${originalPath}`;
401
- // Execute the script
402
- console.error('SESSION_MANAGER: Executing stop session script');
403
- const stopProcess = (0, child_process_1.spawn)(fullCommandString, [], {
404
- windowsHide: true,
405
- cwd: codesysDir,
406
- env: spawnEnv,
407
- shell: true
408
- });
409
- yield new Promise((resolve) => {
410
- stopProcess.on('exit', () => {
411
- resolve();
412
- });
413
- // Set a timeout in case the stop script hangs
414
- setTimeout(() => {
415
- resolve();
416
- }, 5000);
417
- });
418
- // Clean up the temp file
419
- try {
420
- yield (0, promises_1.unlink)(tempFilePath);
421
- }
422
- catch (error) {
423
- console.error(`SESSION_MANAGER: Error deleting temp file ${tempFilePath}: ${error}`);
424
- }
425
- }
426
- catch (error) {
427
- console.error(`SESSION_MANAGER: Error executing stop script: ${error}`);
428
- }
429
- }
430
- // If we have a process, kill it
431
- if (this.codesysProcess && !this.codesysProcess.killed) {
432
- console.error('SESSION_MANAGER: Stopping CODESYS process');
433
- // First try SIGTERM
434
- this.codesysProcess.kill('SIGTERM');
435
- // Set a timeout to force kill if needed
436
- yield new Promise((resolve) => {
437
- setTimeout(() => {
438
- if (this.codesysProcess && !this.codesysProcess.killed) {
439
- console.error('SESSION_MANAGER: SIGTERM failed, using SIGKILL');
440
- this.codesysProcess.kill('SIGKILL');
441
- }
442
- resolve();
443
- }, 2000); // Give it 2 seconds to terminate gracefully
444
- });
445
- }
446
- // Clean up session files (not entire directory)
447
- try {
448
- if (fs.existsSync(this.sessionDir)) {
449
- console.error(`SESSION_MANAGER: Cleaning up session files in: ${this.sessionDir}`);
450
- // Remove only the session-related files, not the entire directory
451
- const filesToRemove = [
452
- this.commandFilePath,
453
- this.resultFilePath,
454
- this.stateFilePath,
455
- this.lockFilePath
456
- ];
457
- for (const filePath of filesToRemove) {
458
- if (fs.existsSync(filePath)) {
459
- fs.unlinkSync(filePath);
460
- console.error(`SESSION_MANAGER: Removed session file: ${filePath}`);
461
- }
462
- }
463
- }
464
- }
465
- catch (error) {
466
- console.error(`SESSION_MANAGER: Error cleaning up session files: ${error}`);
467
- }
468
- this.codesysProcess = null;
469
- this.sessionState = exports.SessionState.STOPPED;
470
- this.emit(exports.SESSION_EVENTS.STOPPED);
471
- }
472
- catch (error) {
473
- console.error(`SESSION_MANAGER: Error during session stop: ${error}`);
474
- this.sessionState = exports.SessionState.ERROR;
475
- this.emit(exports.SESSION_EVENTS.FAILED, error);
476
- }
477
- });
478
- }
479
- /**
480
- * Recursively remove a directory
481
- * @param dirPath Directory path to remove
482
- */
483
- recursiveRemoveDir(dirPath) {
484
- if (fs.existsSync(dirPath)) {
485
- fs.readdirSync(dirPath).forEach((file) => {
486
- const curPath = path.join(dirPath, file);
487
- if (fs.lstatSync(curPath).isDirectory()) {
488
- // Recursive call
489
- this.recursiveRemoveDir(curPath);
490
- }
491
- else {
492
- // Delete file
493
- fs.unlinkSync(curPath);
494
- }
495
- });
496
- fs.rmdirSync(dirPath);
497
- }
498
- }
499
- /**
500
- * Execute a script in the persistent CODESYS session
501
- *
502
- * @param scriptContent The Python script to execute
503
- * @param timeoutMs Optional timeout for the script execution
504
- * @returns Promise with script execution result
505
- */
506
- executeScript(scriptContent_1) {
507
- return __awaiter(this, arguments, void 0, function* (scriptContent, timeoutMs = DEFAULT_SCRIPT_TIMEOUT) {
508
- // Generate a unique ID for this script execution
509
- const scriptId = (0, uuid_1.v4)();
510
- // Make sure we have an active session
511
- const isActiveSession = this.sessionState === exports.SessionState.RUNNING ||
512
- this.sessionState === exports.SessionState.BUSY;
513
- if (!isActiveSession) {
514
- if (this.sessionState === exports.SessionState.STARTING) {
515
- // Wait for session to start
516
- console.error('SESSION_MANAGER: Waiting for session to complete starting');
517
- yield new Promise((resolve) => {
518
- const checkInterval = setInterval(() => {
519
- if (this.sessionState === exports.SessionState.RUNNING ||
520
- this.sessionState === exports.SessionState.ERROR ||
521
- this.sessionState === exports.SessionState.STOPPED) {
522
- clearInterval(checkInterval);
523
- resolve();
524
- }
525
- }, 200);
526
- });
527
- const isNowActive = this.sessionState === exports.SessionState.RUNNING ||
528
- this.sessionState === exports.SessionState.BUSY;
529
- if (!isNowActive) {
530
- throw new Error(`Session is in ${this.sessionState} state, not running. Cannot execute script.`);
531
- }
532
- }
533
- else {
534
- throw new Error(`Session is in ${this.sessionState} state, not running. Cannot execute script.`);
535
- }
536
- }
537
- // Reset idle timeout since we're about to do work
538
- this.resetIdleTimeout();
539
- // Check if the command file exists
540
- if (!fs.existsSync(this.commandFilePath)) {
541
- this.sessionState = exports.SessionState.ERROR;
542
- throw new Error(`Command file not found at ${this.commandFilePath}`);
543
- }
544
- // Check if the result file exists
545
- if (!fs.existsSync(this.resultFilePath)) {
546
- this.sessionState = exports.SessionState.ERROR;
547
- throw new Error(`Result file not found at ${this.resultFilePath}`);
548
- }
549
- // IMPORTANT: Ensure only one script is executed at a time by checking if other scripts are pending
550
- if (this.pendingScripts.size > 0) {
551
- console.error(`SESSION_MANAGER: Waiting for ${this.pendingScripts.size} pending scripts to complete before executing new script`);
552
- // Wait for all pending scripts to complete
553
- yield new Promise((resolve) => {
554
- const checkInterval = setInterval(() => {
555
- if (this.pendingScripts.size === 0) {
556
- clearInterval(checkInterval);
557
- resolve();
558
- }
559
- }, 100);
560
- });
561
- console.error('SESSION_MANAGER: All pending scripts completed, proceeding with new script execution');
562
- }
563
- // Mark the session as busy
564
- this.sessionState = exports.SessionState.BUSY;
565
- // Read current command file to ensure we're not overwriting an in-progress command
566
- try {
567
- const currentCommandData = JSON.parse(fs.readFileSync(this.commandFilePath, 'utf8'));
568
- if (currentCommandData.status === 'pending' || currentCommandData.status === 'processing') {
569
- console.error(`SESSION_MANAGER: Found pending command in file, waiting for it to complete`);
570
- // Wait for the command to complete (status to be 'ready')
571
- yield new Promise((resolve) => {
572
- const checkInterval = setInterval(() => {
573
- try {
574
- const checkData = JSON.parse(fs.readFileSync(this.commandFilePath, 'utf8'));
575
- if (checkData.status === 'ready') {
576
- clearInterval(checkInterval);
577
- resolve();
578
- }
579
- }
580
- catch (err) {
581
- // If we can't read the file, just continue
582
- console.error(`SESSION_MANAGER: Error reading command file during wait: ${err}`);
583
- }
584
- }, 100);
585
- });
586
- console.error('SESSION_MANAGER: Previous command completed, proceeding with new command');
587
- }
588
- }
589
- catch (err) {
590
- console.error(`SESSION_MANAGER: Error reading current command file: ${err}`);
591
- // If we can't read the file, we'll try to proceed anyway
592
- }
593
- // Return a promise that will be resolved when script execution completes
594
- return new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () {
595
- try {
596
- console.error(`SESSION_MANAGER: Executing script ${scriptId}`);
597
- // Set up a timeout for this script execution
598
- const scriptTimeout = setTimeout(() => {
599
- if (this.pendingScripts.has(scriptId)) {
600
- const pending = this.pendingScripts.get(scriptId);
601
- this.pendingScripts.delete(scriptId);
602
- pending.reject(new Error(`Script execution timed out after ${timeoutMs}ms`));
603
- // Check if we have more pending scripts
604
- if (this.pendingScripts.size === 0) {
605
- this.sessionState = exports.SessionState.RUNNING;
606
- }
607
- }
608
- }, timeoutMs);
609
- // Add to pending scripts
610
- this.pendingScripts.set(scriptId, {
611
- resolve: (result) => {
612
- resolve(result);
613
- // Check if we have more pending scripts
614
- if (this.pendingScripts.size === 0) {
615
- this.sessionState = exports.SessionState.RUNNING;
616
- }
617
- },
618
- reject: (error) => {
619
- reject(error);
620
- // Check if we have more pending scripts
621
- if (this.pendingScripts.size === 0) {
622
- this.sessionState = exports.SessionState.RUNNING;
623
- }
624
- },
625
- timeout: scriptTimeout
626
- });
627
- // Create the command to send
628
- const command = {
629
- id: scriptId,
630
- type: "execute_script",
631
- script: scriptContent,
632
- timestamp: Date.now()
633
- };
634
- // Prepare the command data
635
- const commandData = {
636
- status: "pending",
637
- command: command,
638
- timestamp: Date.now()
639
- };
640
- // Send the command by writing to the command file
641
- console.error(`SESSION_MANAGER: Writing command to file ${this.commandFilePath}`);
642
- console.error(`SESSION_MANAGER: Command data: ${JSON.stringify(commandData)}`);
643
- try {
644
- // Write command to file
645
- fs.writeFileSync(this.commandFilePath, JSON.stringify(commandData, null, 2));
646
- console.error(`SESSION_MANAGER: Command written to file`);
647
- // Verify the file was written correctly
648
- try {
649
- const verifyContent = fs.readFileSync(this.commandFilePath, 'utf8');
650
- console.error(`SESSION_MANAGER: Verification - Command file now contains: ${verifyContent.substring(0, 100)}...`);
651
- }
652
- catch (verifyErr) {
653
- console.error(`SESSION_MANAGER: Verification failed - could not read command file: ${verifyErr}`);
654
- }
655
- }
656
- catch (writeErr) {
657
- console.error(`SESSION_MANAGER: ERROR writing command file: ${writeErr}`);
658
- throw writeErr; // Re-throw to propagate the error
659
- }
660
- // Start reading the result file
661
- this.readResultFile(scriptId);
662
- }
663
- catch (error) {
664
- console.error(`SESSION_MANAGER: Error executing script: ${error.message}`);
665
- // Remove from pending scripts
666
- if (this.pendingScripts.has(scriptId)) {
667
- const pending = this.pendingScripts.get(scriptId);
668
- clearTimeout(pending.timeout);
669
- this.pendingScripts.delete(scriptId);
670
- }
671
- // Update session state if no more pending scripts
672
- if (this.pendingScripts.size === 0) {
673
- this.sessionState = exports.SessionState.RUNNING;
674
- }
675
- reject(error);
676
- }
677
- }));
678
- });
679
- }
680
- /**
681
- * Read the result file for a specific script execution
682
- * @param scriptId The script ID to read results for
683
- */
684
- readResultFile(scriptId) {
685
- console.error(`SESSION_MANAGER: Starting to read result file for script ${scriptId}`);
686
- // Count polling attempts for logging purposes
687
- let pollCount = 0;
688
- let lastLogTime = Date.now();
689
- // We'll use a polling approach to read from the file
690
- const pollInterval = setInterval(() => __awaiter(this, void 0, void 0, function* () {
691
- try {
692
- pollCount++;
693
- const now = Date.now();
694
- // Log progress every 5 seconds to show we're still working
695
- if (now - lastLogTime > 5000) {
696
- console.error(`SESSION_MANAGER: Still polling for result of script ${scriptId}, attempt ${pollCount}`);
697
- lastLogTime = now;
698
- }
699
- if (!this.pendingScripts.has(scriptId)) {
700
- console.error(`SESSION_MANAGER: Script ${scriptId} is no longer pending, stopping poll`);
701
- clearInterval(pollInterval);
702
- return;
703
- }
704
- // Check if the result file exists
705
- if (!fs.existsSync(this.resultFilePath)) {
706
- if (pollCount % 50 === 0) { // Log less frequently
707
- console.error(`SESSION_MANAGER: Result file not found at ${this.resultFilePath} (attempt ${pollCount})`);
708
- }
709
- return;
710
- }
711
- // Read the result file
712
- const resultContent = yield (0, promises_1.readFile)(this.resultFilePath, 'utf8');
713
- try {
714
- // Parse the result data
715
- const resultData = JSON.parse(resultContent);
716
- // Check if the result is complete and for our script
717
- if (resultData.status === 'complete' && resultData.result) {
718
- const result = resultData.result;
719
- if (result.id === scriptId) {
720
- console.error(`SESSION_MANAGER: Found result for script ${scriptId} after ${pollCount} polls`);
721
- // Remove from pending scripts
722
- if (this.pendingScripts.has(scriptId)) {
723
- const pending = this.pendingScripts.get(scriptId);
724
- clearTimeout(pending.timeout);
725
- this.pendingScripts.delete(scriptId);
726
- // Resolve with the result
727
- const scriptResult = {
728
- id: scriptId,
729
- success: result.success,
730
- output: result.output
731
- };
732
- pending.resolve(scriptResult);
733
- }
734
- // Stop polling
735
- clearInterval(pollInterval);
736
- return;
737
- }
738
- }
739
- }
740
- catch (parseError) {
741
- console.error(`SESSION_MANAGER: Error parsing result data: ${parseError}`);
742
- }
743
- }
744
- catch (error) {
745
- console.error(`SESSION_MANAGER: Error reading result file: ${error}`);
746
- }
747
- }), 100); // Poll every 100ms
748
- // Set a timeout to stop polling and forcibly resolve/reject the script after the script timeout
749
- setTimeout(() => {
750
- console.error(`SESSION_MANAGER: Max polling time reached for script ${scriptId}, cleaning up`);
751
- clearInterval(pollInterval);
752
- // If the script is still pending, force resolve/reject with timeout error
753
- if (this.pendingScripts.has(scriptId)) {
754
- const pending = this.pendingScripts.get(scriptId);
755
- this.pendingScripts.delete(scriptId);
756
- // Create a timeout result
757
- const timeoutResult = {
758
- id: scriptId,
759
- success: false,
760
- output: `SCRIPT_ERROR: Script execution timed out after reaching maximum polling time. This typically happens when:\n` +
761
- `1. The script is stuck in an infinite loop\n` +
762
- `2. The script has triggered a deadlock in CODESYS\n` +
763
- `3. A CODESYS dialog or error popup is blocking execution\n` +
764
- `4. The project creation or opening is taking too long\n\n` +
765
- `If this is a project creation script, the project file might still have been created successfully.\n` +
766
- `Check the CODESYS UI to see if a project was created and manually close any dialog boxes.`
767
- };
768
- // Resolve with the timeout result rather than rejecting
769
- // This allows the session to continue operating instead of going into error state
770
- console.error(`SESSION_MANAGER: Resolving script ${scriptId} with timeout result`);
771
- pending.resolve(timeoutResult);
772
- }
773
- // Check if we need to update session state
774
- if (this.pendingScripts.size === 0) {
775
- console.error(`SESSION_MANAGER: No more pending scripts, setting state to RUNNING`);
776
- this.sessionState = exports.SessionState.RUNNING;
777
- }
778
- }, 60000); // 1 minute max polling time
779
- }
780
- /**
781
- * Reset the idle timeout
782
- */
783
- resetIdleTimeout() {
784
- // Clear existing timeout if any
785
- if (this.idleTimeout) {
786
- clearTimeout(this.idleTimeout);
787
- this.idleTimeout = null;
788
- }
789
- // Set new timeout
790
- if (this.idleTimeoutMs > 0) {
791
- this.idleTimeout = setTimeout(() => {
792
- console.error(`SESSION_MANAGER: Idle timeout (${this.idleTimeoutMs}ms) reached`);
793
- this.emit(exports.SESSION_EVENTS.TIMEOUT);
794
- }, this.idleTimeoutMs);
795
- }
796
- }
797
- }
798
- exports.CodesysSessionManager = CodesysSessionManager;