@codesys/mcp-toolkit 1.5.8 → 2.0.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 +153 -189
- package/LICENSE +21 -21
- package/README.md +269 -152
- package/dist/actions.d.ts +28 -0
- package/dist/actions.js +98 -0
- package/dist/actions.js.map +1 -0
- package/dist/bridge/bridge.d.ts +105 -0
- package/dist/bridge/bridge.js +400 -0
- package/dist/bridge/bridge.js.map +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +196 -0
- package/dist/cli.js.map +1 -0
- package/dist/codesys/cache.d.ts +48 -0
- package/dist/codesys/cache.js +82 -0
- package/dist/codesys/cache.js.map +1 -0
- package/dist/codesys/changes.d.ts +57 -0
- package/dist/codesys/changes.js +133 -0
- package/dist/codesys/changes.js.map +1 -0
- package/dist/codesys/decl.d.ts +45 -0
- package/dist/codesys/decl.js +251 -0
- package/dist/codesys/decl.js.map +1 -0
- package/dist/codesys/devices.d.ts +34 -0
- package/dist/codesys/devices.js +82 -0
- package/dist/codesys/devices.js.map +1 -0
- package/dist/codesys/diff.d.ts +13 -0
- package/dist/codesys/diff.js +61 -0
- package/dist/codesys/diff.js.map +1 -0
- package/dist/codesys/install.d.ts +40 -0
- package/dist/codesys/install.js +144 -0
- package/dist/codesys/install.js.map +1 -0
- package/dist/codesys/library-browse.d.ts +19 -0
- package/dist/codesys/library-browse.js +57 -0
- package/dist/codesys/library-browse.js.map +1 -0
- package/dist/codesys/outline.d.ts +32 -0
- package/dist/codesys/outline.js +219 -0
- package/dist/codesys/outline.js.map +1 -0
- package/dist/codesys/simulation.d.ts +34 -0
- package/dist/codesys/simulation.js +57 -0
- package/dist/codesys/simulation.js.map +1 -0
- package/dist/codesys/st.d.ts +36 -0
- package/dist/codesys/st.js +69 -0
- package/dist/codesys/st.js.map +1 -0
- package/dist/daemon.d.ts +46 -0
- package/dist/daemon.js +173 -0
- package/dist/daemon.js.map +1 -0
- package/dist/driver.d.ts +25 -0
- package/dist/driver.js +33 -0
- package/dist/driver.js.map +1 -0
- package/dist/panel/sim-panel.html +357 -0
- package/dist/panel/viewer.html +126 -0
- package/dist/panel-types.d.ts +20 -0
- package/dist/panel-types.js +3 -0
- package/dist/panel-types.js.map +1 -0
- package/dist/panel.d.ts +27 -0
- package/dist/panel.js +208 -0
- package/dist/panel.js.map +1 -0
- package/dist/server.d.ts +5 -0
- package/dist/server.js +33 -974
- package/dist/server.js.map +1 -0
- package/dist/tools.d.ts +103 -0
- package/dist/tools.js +793 -0
- package/dist/tools.js.map +1 -0
- package/dist/viewer.d.ts +14 -0
- package/dist/viewer.js +125 -0
- package/dist/viewer.js.map +1 -0
- package/package.json +66 -51
- package/watcher/mcp_compat.py +147 -0
- package/watcher/mcp_ops.py +1178 -0
- package/watcher/mcp_sim.py +246 -0
- package/watcher/watcher.py +255 -0
- package/.claude/settings.local.json +0 -29
- package/CLAUDE.md +0 -26
- package/CONTRIBUTING.md +0 -72
- package/README.md.enhanced +0 -155
- package/codesys-mcp-toolkit-1.2.0.tgz +0 -0
- package/codesys-mcp-toolkit-1.2.1.tgz +0 -0
- package/codesys-mcp-toolkit-1.5.7.tgz +0 -0
- package/codesys-mcp-toolkit-1.5.8.tgz +0 -0
- package/dist/bin.js +0 -75
- package/dist/check_session_paths.js +0 -149
- package/dist/codesys_interop.js +0 -435
- package/dist/direct_session_command.js +0 -293
- package/dist/scriptLoader.js +0 -165
- package/dist/scripts/check_status.py +0 -21
- package/dist/scripts/compile_project.py +0 -65
- package/dist/scripts/create_method.py +0 -80
- package/dist/scripts/create_pou.py +0 -102
- package/dist/scripts/create_project.py +0 -116
- package/dist/scripts/create_property.py +0 -78
- package/dist/scripts/ensure_project_open.py +0 -154
- package/dist/scripts/find_object_by_path.py +0 -111
- package/dist/scripts/get_pou_code.py +0 -91
- package/dist/scripts/get_project_structure.py +0 -72
- package/dist/scripts/open_project.py +0 -25
- package/dist/scripts/save_project.py +0 -29
- package/dist/scripts/session_status.py +0 -281
- package/dist/scripts/set_pou_code.py +0 -109
- package/dist/scripts/start_session.py +0 -809
- package/dist/scripts/stop_session.py +0 -324
- package/dist/sessionManager.js +0 -798
- package/docs/configuration.md +0 -141
- package/docs/index.md +0 -26
- package/docs/installation.md +0 -84
- package/docs/session_management.md +0 -110
- package/examples/README.md +0 -78
- package/examples/sample_config.json +0 -32
- package/replacements_session_status.txt +0 -16
- package/scriptDoc.txt +0 -8679
- package/scripts/build.js +0 -25
- package/scripts/fix_fstrings.py +0 -76
package/dist/sessionManager.js
DELETED
|
@@ -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;
|