@codesys/mcp-toolkit 1.2.1 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -58,9 +58,20 @@ const os = __importStar(require("os"));
58
58
  const fs = __importStar(require("fs"));
59
59
  const events_1 = require("events");
60
60
  const uuid_1 = require("uuid");
61
+ const scriptLoader_1 = require("./scriptLoader");
61
62
  // Default timeout values
62
63
  const DEFAULT_SCRIPT_TIMEOUT = 60000; // 60 seconds
63
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';
64
75
  // Event names
65
76
  exports.SESSION_EVENTS = {
66
77
  STARTED: 'session:started',
@@ -109,6 +120,13 @@ class CodesysSessionManager extends events_1.EventEmitter {
109
120
  this.codesysExePath = codesysExePath;
110
121
  this.codesysProfileName = codesysProfileName;
111
122
  this.idleTimeoutMs = idleTimeoutMs;
123
+ this.sessionId = (0, uuid_1.v4)();
124
+ // Set up session directory and files
125
+ this.sessionDir = path.join(SESSION_DIR, this.sessionId);
126
+ this.commandFilePath = path.join(this.sessionDir, COMMAND_FILE);
127
+ this.resultFilePath = path.join(this.sessionDir, RESULT_FILE);
128
+ this.stateFilePath = path.join(this.sessionDir, STATE_FILE);
129
+ this.lockFilePath = path.join(this.sessionDir, SESSION_LOCK_FILE);
112
130
  // Log status changes through event emitter
113
131
  this.on(exports.SESSION_EVENTS.STARTED, () => {
114
132
  console.error(`SESSION_MANAGER: Session started successfully`);
@@ -134,6 +152,70 @@ class CodesysSessionManager extends events_1.EventEmitter {
134
152
  getState() {
135
153
  return this.sessionState;
136
154
  }
155
+ /**
156
+ * Get the session directory
157
+ */
158
+ getSessionDir() {
159
+ return this.sessionDir;
160
+ }
161
+ /**
162
+ * Get the session ID
163
+ */
164
+ getSessionId() {
165
+ return this.sessionId;
166
+ }
167
+ /**
168
+ * Check if the session is active
169
+ * @returns Promise that resolves to true if the session is active
170
+ */
171
+ isSessionActive() {
172
+ return __awaiter(this, void 0, void 0, function* () {
173
+ try {
174
+ // If our internal state is not running, return false immediately
175
+ if (this.sessionState !== exports.SessionState.RUNNING && this.sessionState !== exports.SessionState.BUSY) {
176
+ return false;
177
+ }
178
+ // Check if session directory exists
179
+ if (!fs.existsSync(this.sessionDir)) {
180
+ return false;
181
+ }
182
+ // Check if state file exists
183
+ if (!fs.existsSync(this.stateFilePath)) {
184
+ return false;
185
+ }
186
+ // Request status from the running session
187
+ const statusResult = yield this.executeScript('print("SCRIPT_SUCCESS: Session is active")', 5000);
188
+ return statusResult.success;
189
+ }
190
+ catch (error) {
191
+ console.error(`SESSION_MANAGER: Error checking session status: ${error}`);
192
+ return false;
193
+ }
194
+ });
195
+ }
196
+ /**
197
+ * Get detailed session information
198
+ * @returns Promise that resolves to session information
199
+ */
200
+ getSessionInfo() {
201
+ return __awaiter(this, void 0, void 0, function* () {
202
+ try {
203
+ // Check if session is active
204
+ const isActive = yield this.isSessionActive();
205
+ if (!isActive) {
206
+ return null;
207
+ }
208
+ // Read the state file
209
+ const stateContent = yield (0, promises_1.readFile)(this.stateFilePath, 'utf8');
210
+ const stateData = JSON.parse(stateContent);
211
+ return stateData;
212
+ }
213
+ catch (error) {
214
+ console.error(`SESSION_MANAGER: Error getting session info: ${error}`);
215
+ return null;
216
+ }
217
+ });
218
+ }
137
219
  /**
138
220
  * Start a CODESYS session
139
221
  * @returns Promise that resolves when the session is started
@@ -145,55 +227,122 @@ class CodesysSessionManager extends events_1.EventEmitter {
145
227
  return Promise.resolve(); // Session already running
146
228
  }
147
229
  this.sessionState = exports.SessionState.STARTING;
148
- return new Promise((resolve, reject) => {
230
+ try {
231
+ console.error(`SESSION_MANAGER: Starting CODESYS session with profile: ${this.codesysProfileName}`);
232
+ // Create session directory if it doesn't exist
233
+ if (!fs.existsSync(SESSION_DIR)) {
234
+ fs.mkdirSync(SESSION_DIR, { recursive: true });
235
+ }
236
+ // Create our specific session directory
237
+ if (!fs.existsSync(this.sessionDir)) {
238
+ fs.mkdirSync(this.sessionDir, { recursive: true });
239
+ }
240
+ // Create initial state file
241
+ const initialState = {
242
+ sessionId: this.sessionId,
243
+ status: exports.SessionState.STARTING,
244
+ startTime: new Date().toISOString()
245
+ };
246
+ yield (0, promises_1.writeFile)(this.stateFilePath, JSON.stringify(initialState, null, 2), 'utf8');
247
+ // Load the start_session script template
248
+ const scriptTemplate = yield (0, scriptLoader_1.loadScriptTemplate)('start_session');
249
+ // Interpolate parameters
250
+ const scriptContent = (0, scriptLoader_1.interpolateScript)(scriptTemplate, {
251
+ SESSION_DIR: this.sessionDir,
252
+ COMMAND_FILE: COMMAND_FILE,
253
+ RESULT_FILE: RESULT_FILE,
254
+ STATE_FILE: STATE_FILE,
255
+ SESSION_ID: this.sessionId,
256
+ });
257
+ // Create a temp file for the script
258
+ const tempDir = os.tmpdir();
259
+ const tempFileName = `codesys_start_session_${Date.now()}_${Math.random().toString(36).substring(2, 9)}.py`;
260
+ const tempFilePath = path.join(tempDir, tempFileName);
261
+ // Write the script to the temp file
262
+ yield (0, promises_1.writeFile)(tempFilePath, scriptContent, 'latin1');
263
+ // Prepare CODESYS process
264
+ const codesysDir = path.dirname(this.codesysExePath);
265
+ const quotedExePath = `"${this.codesysExePath}"`;
266
+ const profileArg = `--profile="${this.codesysProfileName}"`;
267
+ // Start CODESYS with the session script - in "visible" mode so we can see it's running
268
+ const fullCommandString = `${quotedExePath} ${profileArg} --runscript="${tempFilePath}"`;
269
+ // Create a modified environment with CODESYS bin dir in PATH
270
+ const spawnEnv = Object.assign({}, process.env);
271
+ const pathSeparator = os.platform() === 'win32' ? ';' : ':';
272
+ const originalPath = spawnEnv.PATH || '';
273
+ spawnEnv.PATH = `${codesysDir}${pathSeparator}${originalPath}`;
274
+ // Spawn the process
275
+ this.codesysProcess = (0, child_process_1.spawn)(fullCommandString, [], {
276
+ windowsHide: false, // Make visible to user
277
+ cwd: codesysDir,
278
+ env: spawnEnv,
279
+ shell: true
280
+ });
281
+ // Handle process events
282
+ this.codesysProcess.on('error', (error) => {
283
+ console.error(`SESSION_MANAGER: Error starting CODESYS: ${error.message}`);
284
+ this.emit(exports.SESSION_EVENTS.FAILED, error);
285
+ this.sessionState = exports.SessionState.ERROR;
286
+ });
287
+ this.codesysProcess.on('exit', (code) => {
288
+ console.error(`SESSION_MANAGER: CODESYS process exited with code ${code}`);
289
+ // Only change state if we're not already stopping
290
+ if (this.sessionState !== exports.SessionState.STOPPING && this.sessionState !== exports.SessionState.STOPPED) {
291
+ this.sessionState = exports.SessionState.ERROR;
292
+ this.emit(exports.SESSION_EVENTS.FAILED, new Error(`CODESYS process exited unexpectedly with code ${code}`));
293
+ }
294
+ });
295
+ // Wait for session to initialize by checking for the existence of pipes
296
+ const success = yield this.waitForSessionInitialization();
297
+ if (success) {
298
+ this.sessionState = exports.SessionState.RUNNING;
299
+ this.emit(exports.SESSION_EVENTS.STARTED);
300
+ this.resetIdleTimeout();
301
+ }
302
+ else {
303
+ throw new Error('Session failed to initialize within timeout');
304
+ }
305
+ }
306
+ catch (error) {
307
+ console.error(`SESSION_MANAGER: Error during session start: ${error.message}`);
308
+ this.sessionState = exports.SessionState.ERROR;
309
+ this.emit(exports.SESSION_EVENTS.FAILED, error);
310
+ throw error;
311
+ }
312
+ });
313
+ }
314
+ /**
315
+ * Wait for session initialization by checking for the existence of the pipes
316
+ * @param timeoutMs Timeout in milliseconds
317
+ * @returns Promise that resolves to true if session initialized successfully
318
+ */
319
+ waitForSessionInitialization() {
320
+ return __awaiter(this, arguments, void 0, function* (timeoutMs = 30000) {
321
+ const startTime = Date.now();
322
+ while (Date.now() - startTime < timeoutMs) {
149
323
  try {
150
- console.error(`SESSION_MANAGER: Starting CODESYS session with profile: ${this.codesysProfileName}`);
151
- // Create a keep-alive script that keeps CODESYS running
152
- const keepAliveScript = this.createKeepAliveScript();
153
- // Prepare CODESYS process
154
- const codesysDir = path.dirname(this.codesysExePath);
155
- const quotedExePath = `"${this.codesysExePath}"`;
156
- const profileArg = `--profile="${this.codesysProfileName}"`;
157
- // Start CODESYS with the keep-alive script - in "visible" mode so we can see it's running
158
- const fullCommandString = `${quotedExePath} ${profileArg} --runscript="${keepAliveScript}"`;
159
- // Create a modified environment with CODESYS bin dir in PATH
160
- const spawnEnv = Object.assign({}, process.env);
161
- const pathSeparator = os.platform() === 'win32' ? ';' : ':';
162
- const originalPath = spawnEnv.PATH || '';
163
- spawnEnv.PATH = `${codesysDir}${pathSeparator}${originalPath}`;
164
- // Spawn the process
165
- this.codesysProcess = (0, child_process_1.spawn)(fullCommandString, [], {
166
- windowsHide: false, // Make visible to user
167
- cwd: codesysDir,
168
- env: spawnEnv,
169
- shell: true
170
- });
171
- // Handle process events
172
- this.codesysProcess.on('error', (error) => {
173
- console.error(`SESSION_MANAGER: Error starting CODESYS: ${error.message}`);
174
- this.emit(exports.SESSION_EVENTS.FAILED, error);
175
- reject(error);
176
- });
177
- // Give it a small delay to start properly
178
- setTimeout(() => {
179
- if (this.codesysProcess && !this.codesysProcess.killed) {
180
- this.emit(exports.SESSION_EVENTS.STARTED);
181
- this.resetIdleTimeout();
182
- resolve();
324
+ // Check if files exist
325
+ const commandFileExists = fs.existsSync(this.commandFilePath);
326
+ const resultFileExists = fs.existsSync(this.resultFilePath);
327
+ if (commandFileExists && resultFileExists) {
328
+ // Read state file to check if session is ready
329
+ const stateContent = yield (0, promises_1.readFile)(this.stateFilePath, 'utf8');
330
+ const stateData = JSON.parse(stateContent);
331
+ if (stateData.status === exports.SessionState.RUNNING) {
332
+ console.error(`SESSION_MANAGER: Session initialized successfully`);
333
+ return true;
183
334
  }
184
- else {
185
- const error = new Error('CODESYS process failed to start');
186
- this.emit(exports.SESSION_EVENTS.FAILED, error);
187
- reject(error);
188
- }
189
- }, 2000); // 2 second delay to let CODESYS initialize
335
+ }
336
+ // Wait a bit before checking again
337
+ yield new Promise(resolve => setTimeout(resolve, DEFAULT_SESSION_POLL_INTERVAL));
190
338
  }
191
339
  catch (error) {
192
- console.error(`SESSION_MANAGER: Error during session start: ${error.message}`);
193
- this.emit(exports.SESSION_EVENTS.FAILED, error);
194
- reject(error);
340
+ console.error(`SESSION_MANAGER: Error checking session initialization: ${error}`);
341
+ // Continue waiting
195
342
  }
196
- });
343
+ }
344
+ console.error(`SESSION_MANAGER: Session initialization timed out after ${timeoutMs}ms`);
345
+ return false;
197
346
  });
198
347
  }
199
348
  /**
@@ -217,29 +366,123 @@ class CodesysSessionManager extends events_1.EventEmitter {
217
366
  pending.reject(new Error('Session was stopped while script was pending'));
218
367
  this.pendingScripts.delete(id);
219
368
  }
220
- return new Promise((resolve) => {
369
+ try {
370
+ // Check if we have a running session
371
+ if (fs.existsSync(this.sessionDir) && fs.existsSync(this.commandFilePath)) {
372
+ // Load the stop_session script template
373
+ const scriptTemplate = yield (0, scriptLoader_1.loadScriptTemplate)('stop_session');
374
+ // Interpolate parameters
375
+ const scriptContent = (0, scriptLoader_1.interpolateScript)(scriptTemplate, {
376
+ SESSION_DIR: this.sessionDir,
377
+ COMMAND_FILE: COMMAND_FILE,
378
+ RESULT_FILE: RESULT_FILE,
379
+ STATE_FILE: STATE_FILE,
380
+ SESSION_ID: this.sessionId,
381
+ });
382
+ try {
383
+ // Execute the stop script
384
+ const tempDir = os.tmpdir();
385
+ const tempFileName = `codesys_stop_session_${Date.now()}_${Math.random().toString(36).substring(2, 9)}.py`;
386
+ const tempFilePath = path.join(tempDir, tempFileName);
387
+ // Write the script to the temp file
388
+ yield (0, promises_1.writeFile)(tempFilePath, scriptContent, 'latin1');
389
+ // Prepare CODESYS process
390
+ const codesysDir = path.dirname(this.codesysExePath);
391
+ const quotedExePath = `"${this.codesysExePath}"`;
392
+ const profileArg = `--profile="${this.codesysProfileName}"`;
393
+ // Execute the stop script
394
+ const fullCommandString = `${quotedExePath} ${profileArg} --noUI --runscript="${tempFilePath}"`;
395
+ // Create a modified environment with CODESYS bin dir in PATH
396
+ const spawnEnv = Object.assign({}, process.env);
397
+ const pathSeparator = os.platform() === 'win32' ? ';' : ':';
398
+ const originalPath = spawnEnv.PATH || '';
399
+ spawnEnv.PATH = `${codesysDir}${pathSeparator}${originalPath}`;
400
+ // Execute the script
401
+ console.error('SESSION_MANAGER: Executing stop session script');
402
+ const stopProcess = (0, child_process_1.spawn)(fullCommandString, [], {
403
+ windowsHide: true,
404
+ cwd: codesysDir,
405
+ env: spawnEnv,
406
+ shell: true
407
+ });
408
+ yield new Promise((resolve) => {
409
+ stopProcess.on('exit', () => {
410
+ resolve();
411
+ });
412
+ // Set a timeout in case the stop script hangs
413
+ setTimeout(() => {
414
+ resolve();
415
+ }, 5000);
416
+ });
417
+ // Clean up the temp file
418
+ try {
419
+ yield (0, promises_1.unlink)(tempFilePath);
420
+ }
421
+ catch (error) {
422
+ console.error(`SESSION_MANAGER: Error deleting temp file ${tempFilePath}: ${error}`);
423
+ }
424
+ }
425
+ catch (error) {
426
+ console.error(`SESSION_MANAGER: Error executing stop script: ${error}`);
427
+ }
428
+ }
429
+ // If we have a process, kill it
221
430
  if (this.codesysProcess && !this.codesysProcess.killed) {
222
431
  console.error('SESSION_MANAGER: Stopping CODESYS process');
223
432
  // First try SIGTERM
224
433
  this.codesysProcess.kill('SIGTERM');
225
434
  // Set a timeout to force kill if needed
226
- setTimeout(() => {
227
- if (this.codesysProcess && !this.codesysProcess.killed) {
228
- console.error('SESSION_MANAGER: SIGTERM failed, using SIGKILL');
229
- this.codesysProcess.kill('SIGKILL');
230
- }
231
- this.codesysProcess = null;
232
- this.emit(exports.SESSION_EVENTS.STOPPED);
233
- resolve();
234
- }, 2000); // Give it 2 seconds to terminate gracefully
435
+ yield new Promise((resolve) => {
436
+ setTimeout(() => {
437
+ if (this.codesysProcess && !this.codesysProcess.killed) {
438
+ console.error('SESSION_MANAGER: SIGTERM failed, using SIGKILL');
439
+ this.codesysProcess.kill('SIGKILL');
440
+ }
441
+ resolve();
442
+ }, 2000); // Give it 2 seconds to terminate gracefully
443
+ });
444
+ }
445
+ // Clean up session directory
446
+ try {
447
+ if (fs.existsSync(this.sessionDir)) {
448
+ console.error(`SESSION_MANAGER: Cleaning up session directory: ${this.sessionDir}`);
449
+ // Use recursive: true once we're on Node.js 14+
450
+ this.recursiveRemoveDir(this.sessionDir);
451
+ }
452
+ }
453
+ catch (error) {
454
+ console.error(`SESSION_MANAGER: Error cleaning up session directory: ${error}`);
455
+ }
456
+ this.codesysProcess = null;
457
+ this.sessionState = exports.SessionState.STOPPED;
458
+ this.emit(exports.SESSION_EVENTS.STOPPED);
459
+ }
460
+ catch (error) {
461
+ console.error(`SESSION_MANAGER: Error during session stop: ${error}`);
462
+ this.sessionState = exports.SessionState.ERROR;
463
+ this.emit(exports.SESSION_EVENTS.FAILED, error);
464
+ }
465
+ });
466
+ }
467
+ /**
468
+ * Recursively remove a directory
469
+ * @param dirPath Directory path to remove
470
+ */
471
+ recursiveRemoveDir(dirPath) {
472
+ if (fs.existsSync(dirPath)) {
473
+ fs.readdirSync(dirPath).forEach((file) => {
474
+ const curPath = path.join(dirPath, file);
475
+ if (fs.lstatSync(curPath).isDirectory()) {
476
+ // Recursive call
477
+ this.recursiveRemoveDir(curPath);
235
478
  }
236
479
  else {
237
- this.codesysProcess = null;
238
- this.emit(exports.SESSION_EVENTS.STOPPED);
239
- resolve();
480
+ // Delete file
481
+ fs.unlinkSync(curPath);
240
482
  }
241
483
  });
242
- });
484
+ fs.rmdirSync(dirPath);
485
+ }
243
486
  }
244
487
  /**
245
488
  * Execute a script in the persistent CODESYS session
@@ -283,31 +526,35 @@ class CodesysSessionManager extends events_1.EventEmitter {
283
526
  this.resetIdleTimeout();
284
527
  // Mark the session as busy
285
528
  this.sessionState = exports.SessionState.BUSY;
529
+ // Check if the command file exists
530
+ if (!fs.existsSync(this.commandFilePath)) {
531
+ this.sessionState = exports.SessionState.ERROR;
532
+ throw new Error(`Command file not found at ${this.commandFilePath}`);
533
+ }
534
+ // Check if the result file exists
535
+ if (!fs.existsSync(this.resultFilePath)) {
536
+ this.sessionState = exports.SessionState.ERROR;
537
+ throw new Error(`Result file not found at ${this.resultFilePath}`);
538
+ }
286
539
  // Return a promise that will be resolved when script execution completes
287
540
  return new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () {
288
541
  try {
289
- // Create a temp file for the script
290
- const tempDir = os.tmpdir();
291
- const tempFileName = `codesys_script_${Date.now()}_${Math.random().toString(36).substring(2, 9)}.py`;
292
- const tempFilePath = path.join(tempDir, tempFileName);
293
- // Write the script to the temp file
294
- yield (0, promises_1.writeFile)(tempFilePath, scriptContent, 'latin1');
295
- console.error(`SESSION_MANAGER: Script ${scriptId} created at ${tempFilePath}`);
542
+ console.error(`SESSION_MANAGER: Executing script ${scriptId}`);
296
543
  // Set up a timeout for this script execution
297
544
  const scriptTimeout = setTimeout(() => {
298
545
  if (this.pendingScripts.has(scriptId)) {
299
546
  const pending = this.pendingScripts.get(scriptId);
300
547
  this.pendingScripts.delete(scriptId);
301
548
  pending.reject(new Error(`Script execution timed out after ${timeoutMs}ms`));
549
+ // Check if we have more pending scripts
550
+ if (this.pendingScripts.size === 0) {
551
+ this.sessionState = exports.SessionState.RUNNING;
552
+ }
302
553
  }
303
554
  }, timeoutMs);
304
555
  // Add to pending scripts
305
556
  this.pendingScripts.set(scriptId, {
306
557
  resolve: (result) => {
307
- // Clean up the temp file
308
- (0, promises_1.unlink)(tempFilePath).catch(err => {
309
- console.error(`SESSION_MANAGER: Error deleting temp file ${tempFilePath}: ${err.message}`);
310
- });
311
558
  resolve(result);
312
559
  // Check if we have more pending scripts
313
560
  if (this.pendingScripts.size === 0) {
@@ -315,10 +562,6 @@ class CodesysSessionManager extends events_1.EventEmitter {
315
562
  }
316
563
  },
317
564
  reject: (error) => {
318
- // Clean up the temp file
319
- (0, promises_1.unlink)(tempFilePath).catch(err => {
320
- console.error(`SESSION_MANAGER: Error deleting temp file ${tempFilePath}: ${err.message}`);
321
- });
322
565
  reject(error);
323
566
  // Check if we have more pending scripts
324
567
  if (this.pendingScripts.size === 0) {
@@ -327,33 +570,103 @@ class CodesysSessionManager extends events_1.EventEmitter {
327
570
  },
328
571
  timeout: scriptTimeout
329
572
  });
330
- // TODO: Implement the actual execution of the script in the running CODESYS instance
331
- // For now, we'll just simulate it with a timeout
332
- setTimeout(() => {
333
- if (this.pendingScripts.has(scriptId)) {
334
- const pending = this.pendingScripts.get(scriptId);
335
- clearTimeout(pending.timeout);
336
- this.pendingScripts.delete(scriptId);
337
- // Return a mocked result
338
- const result = {
339
- id: scriptId,
340
- success: true,
341
- output: "SCRIPT_SUCCESS: This is a mock implementation of script execution!"
342
- };
343
- pending.resolve(result);
344
- }
345
- }, 1000); // 1 second delay for mock result
573
+ // Create the command to send
574
+ const command = {
575
+ id: scriptId,
576
+ type: "execute_script",
577
+ script: scriptContent,
578
+ timestamp: Date.now()
579
+ };
580
+ // Prepare the command data
581
+ const commandData = {
582
+ status: "pending",
583
+ command: command,
584
+ timestamp: Date.now()
585
+ };
586
+ // Send the command by writing to the command file
587
+ fs.writeFileSync(this.commandFilePath, JSON.stringify(commandData));
588
+ console.error(`SESSION_MANAGER: Command written to file`);
589
+ // Start reading the result file
590
+ this.readResultFile(scriptId);
346
591
  }
347
592
  catch (error) {
348
- reject(error);
349
- // Check if we have more pending scripts
593
+ console.error(`SESSION_MANAGER: Error executing script: ${error.message}`);
594
+ // Remove from pending scripts
595
+ if (this.pendingScripts.has(scriptId)) {
596
+ const pending = this.pendingScripts.get(scriptId);
597
+ clearTimeout(pending.timeout);
598
+ this.pendingScripts.delete(scriptId);
599
+ }
600
+ // Update session state if no more pending scripts
350
601
  if (this.pendingScripts.size === 0) {
351
602
  this.sessionState = exports.SessionState.RUNNING;
352
603
  }
604
+ reject(error);
353
605
  }
354
606
  }));
355
607
  });
356
608
  }
609
+ /**
610
+ * Read the result file for a specific script execution
611
+ * @param scriptId The script ID to read results for
612
+ */
613
+ readResultFile(scriptId) {
614
+ console.error(`SESSION_MANAGER: Starting to read result file for script ${scriptId}`);
615
+ // We'll use a polling approach to read from the file
616
+ const pollInterval = setInterval(() => __awaiter(this, void 0, void 0, function* () {
617
+ try {
618
+ if (!this.pendingScripts.has(scriptId)) {
619
+ console.error(`SESSION_MANAGER: Script ${scriptId} is no longer pending, stopping poll`);
620
+ clearInterval(pollInterval);
621
+ return;
622
+ }
623
+ // Check if the result file exists
624
+ if (!fs.existsSync(this.resultFilePath)) {
625
+ console.error(`SESSION_MANAGER: Result file not found at ${this.resultFilePath}`);
626
+ return;
627
+ }
628
+ // Read the result file
629
+ const resultContent = yield (0, promises_1.readFile)(this.resultFilePath, 'utf8');
630
+ try {
631
+ // Parse the result data
632
+ const resultData = JSON.parse(resultContent);
633
+ // Check if the result is complete and for our script
634
+ if (resultData.status === 'complete' && resultData.result) {
635
+ const result = resultData.result;
636
+ if (result.id === scriptId) {
637
+ console.error(`SESSION_MANAGER: Found result for script ${scriptId}`);
638
+ // Remove from pending scripts
639
+ if (this.pendingScripts.has(scriptId)) {
640
+ const pending = this.pendingScripts.get(scriptId);
641
+ clearTimeout(pending.timeout);
642
+ this.pendingScripts.delete(scriptId);
643
+ // Resolve with the result
644
+ const scriptResult = {
645
+ id: scriptId,
646
+ success: result.success,
647
+ output: result.output
648
+ };
649
+ pending.resolve(scriptResult);
650
+ }
651
+ // Stop polling
652
+ clearInterval(pollInterval);
653
+ return;
654
+ }
655
+ }
656
+ }
657
+ catch (parseError) {
658
+ console.error(`SESSION_MANAGER: Error parsing result data: ${parseError}`);
659
+ }
660
+ }
661
+ catch (error) {
662
+ console.error(`SESSION_MANAGER: Error reading result file: ${error}`);
663
+ }
664
+ }), 100); // Poll every 100ms
665
+ // Set a timeout to stop polling after the script timeout
666
+ setTimeout(() => {
667
+ clearInterval(pollInterval);
668
+ }, 60000); // 1 minute max polling time
669
+ }
357
670
  /**
358
671
  * Reset the idle timeout
359
672
  */
@@ -371,31 +684,5 @@ class CodesysSessionManager extends events_1.EventEmitter {
371
684
  }, this.idleTimeoutMs);
372
685
  }
373
686
  }
374
- /**
375
- * Create a keep-alive script that keeps CODESYS running
376
- * This is a placeholder for now - we'll need to implement
377
- * a proper mechanism to keep CODESYS running while allowing
378
- * additional scripts to be executed
379
- */
380
- createKeepAliveScript() {
381
- // This is a placeholder - we need a real implementation
382
- // that keeps CODESYS running and allows for executing other scripts
383
- const tempScript = `
384
- import time
385
-
386
- # This is a placeholder keep-alive script
387
- # In a real implementation, we would:
388
- # 1. Set up a communication channel (named pipe, socket, etc.)
389
- # 2. Listen for script execution requests
390
- # 3. Execute them and return results
391
-
392
- print("Keep-alive script running...")
393
-
394
- # Just keep running for now
395
- while True:
396
- time.sleep(1)
397
- `;
398
- return tempScript;
399
- }
400
687
  }
401
688
  exports.CodesysSessionManager = CodesysSessionManager;
@@ -1,4 +1,9 @@
1
- # CODESYS MCP Session Management
1
+ # CODESYS MCP Session Management (Not Yet Implemented)
2
+
3
+ > **IMPORTANT:** Session management is currently disabled in version 1.2.4.
4
+ > The `start_session` and `stop_session` commands exist but are non-functional.
5
+ > All operations will run in standard mode (starting and stopping CODESYS for each operation).
6
+ > This feature will be fully implemented in a future release.
2
7
 
3
8
  ## Overview
4
9
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codesys/mcp-toolkit",
3
- "version": "1.2.1",
3
+ "version": "1.3.0",
4
4
  "description": "Model Context Protocol (MCP) server for CODESYS automation platform",
5
5
  "main": "dist/server.js",
6
6
  "bin": {