@codesys/mcp-toolkit 1.2.4 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,49 +1,88 @@
1
- import sys, os, threading, json, time, tempfile
1
+ import sys, os, json, time, tempfile, traceback
2
2
  import scriptengine as script_engine
3
3
 
4
4
  # Define markers for script results
5
5
  SCRIPT_SUCCESS_MARKER = "SCRIPT_SUCCESS"
6
6
  SCRIPT_ERROR_MARKER = "SCRIPT_ERROR"
7
7
 
8
- # Session state file and pipes
8
+ # Session state and command files
9
9
  SESSION_DIR = os.path.join(tempfile.gettempdir(), "codesys_session")
10
10
  SESSION_STATE_FILE = os.path.join(SESSION_DIR, "session_state.json")
11
- COMMAND_PIPE = os.path.join(SESSION_DIR, "command_pipe")
12
- RESULT_PIPE = os.path.join(SESSION_DIR, "result_pipe")
11
+ COMMAND_FILE = os.path.join(SESSION_DIR, "command.json")
12
+ RESULT_FILE = os.path.join(SESSION_DIR, "result.json")
13
+ SESSION_LOCK_FILE = os.path.join(SESSION_DIR, "session.lock")
14
+ POLL_INTERVAL = 0.1 # seconds (faster polling for better responsiveness)
15
+ MAX_INACTIVITY_TIME = 600 # seconds (10 minutes of inactivity before auto-shutdown)
13
16
 
14
17
  def setup_session_dir():
15
- """Create the session directory and pipes if they don't exist"""
18
+ """Create the session directory and initial files"""
16
19
  try:
20
+ # Clean up any existing session first
21
+ if os.path.exists(SESSION_DIR):
22
+ try:
23
+ # Try to read existing session state
24
+ if os.path.exists(SESSION_STATE_FILE):
25
+ try:
26
+ with open(SESSION_STATE_FILE, 'r') as f:
27
+ old_state = json.load(f)
28
+ print(f"Cleaning up previous session (ID: {old_state.get('session_id', 'unknown')})")
29
+ except:
30
+ pass
31
+
32
+ # Remove all files in the directory
33
+ for filename in os.listdir(SESSION_DIR):
34
+ filepath = os.path.join(SESSION_DIR, filename)
35
+ try:
36
+ if os.path.isfile(filepath):
37
+ os.unlink(filepath)
38
+ except Exception as e:
39
+ print(f"Error removing file {filepath}: {e}")
40
+ except Exception as e:
41
+ print(f"Error cleaning up previous session: {e}")
42
+
17
43
  # Create session directory
18
44
  if not os.path.exists(SESSION_DIR):
19
45
  os.makedirs(SESSION_DIR)
20
46
 
21
- # Create named pipes if they don't exist
22
- if not os.path.exists(COMMAND_PIPE):
23
- os.mkfifo(COMMAND_PIPE)
24
-
25
- if not os.path.exists(RESULT_PIPE):
26
- os.mkfifo(RESULT_PIPE)
27
-
28
47
  # Initialize session state
48
+ session_id = str(time.time()) + "_" + str(os.getpid())
29
49
  session_state = {
30
50
  "active": True,
51
+ "session_id": session_id,
31
52
  "start_time": time.time(),
53
+ "last_activity": time.time(),
54
+ "status": "running",
32
55
  "project_open": False,
33
56
  "project_name": "",
34
- "project_path": ""
57
+ "project_path": "",
58
+ "version": "1.0"
35
59
  }
36
60
 
61
+ # Write session state
37
62
  with open(SESSION_STATE_FILE, 'w') as f:
38
- json.dump(session_state, f)
63
+ json.dump(session_state, f, indent=2)
39
64
 
65
+ # Create empty command file
66
+ with open(COMMAND_FILE, 'w') as f:
67
+ json.dump({"status": "ready", "command": None, "timestamp": time.time()}, f, indent=2)
68
+
69
+ # Create empty result file
70
+ with open(RESULT_FILE, 'w') as f:
71
+ json.dump({"status": "none", "result": None, "timestamp": time.time()}, f, indent=2)
72
+
73
+ # Create lock file
74
+ with open(SESSION_LOCK_FILE, 'w') as f:
75
+ f.write(str(time.time()))
76
+
77
+ print(f"Session directory setup at: {SESSION_DIR}")
78
+ print(f"Session ID: {session_id}")
40
79
  return True
41
80
  except Exception as e:
42
81
  print(f"Failed to set up session directory: {e}")
43
82
  print(f"{SCRIPT_ERROR_MARKER}: Session setup failed - {str(e)}")
44
83
  return False
45
84
 
46
- def update_session_state():
85
+ def update_session_state(status="running"):
47
86
  """Update the session state file with current CODESYS state"""
48
87
  try:
49
88
  # Get current state
@@ -67,28 +106,41 @@ def update_session_state():
67
106
  project_name = "Unnamed (Path Error)"
68
107
 
69
108
  # Read current state file
70
- with open(SESSION_STATE_FILE, 'r') as f:
71
- session_state = json.load(f)
109
+ try:
110
+ with open(SESSION_STATE_FILE, 'r') as f:
111
+ session_state = json.load(f)
112
+ except (FileNotFoundError, json.JSONDecodeError):
113
+ # State file doesn't exist or is invalid, create a new one
114
+ session_id = str(time.time()) + "_" + str(os.getpid())
115
+ session_state = {
116
+ "active": True,
117
+ "session_id": session_id,
118
+ "start_time": time.time(),
119
+ "status": status,
120
+ "version": "1.0"
121
+ }
72
122
 
73
123
  # Update state
74
124
  session_state["project_open"] = project_open
75
125
  session_state["project_name"] = project_name
76
126
  session_state["project_path"] = project_path
77
127
  session_state["last_update"] = time.time()
128
+ session_state["status"] = status
78
129
 
79
130
  # Write updated state
80
131
  with open(SESSION_STATE_FILE, 'w') as f:
81
- json.dump(session_state, f)
132
+ json.dump(session_state, f, indent=2)
82
133
 
83
134
  return True
84
135
  except Exception as e:
85
136
  print(f"Failed to update session state: {e}")
137
+ traceback.print_exc()
86
138
  return False
87
139
 
88
140
  def execute_command(command_data):
89
- """Execute a command from the command pipe"""
141
+ """Execute a command"""
90
142
  try:
91
- cmd_type = command_data.get("type", "")
143
+ cmd_type = command_data.get("type", "execute_script")
92
144
  script = command_data.get("script", "")
93
145
  cmd_id = command_data.get("id", "unknown")
94
146
 
@@ -102,6 +154,10 @@ def execute_command(command_data):
102
154
  if cmd_type == "execute_script" and script:
103
155
  # Execute the script in the current CODESYS context
104
156
  try:
157
+ # Create a temporary globals dictionary with the current globals
158
+ # This allows us to isolate the script execution somewhat
159
+ local_globals = globals().copy()
160
+
105
161
  # Capture output by redirecting stdout/stderr
106
162
  import io
107
163
  from contextlib import redirect_stdout, redirect_stderr
@@ -111,7 +167,7 @@ def execute_command(command_data):
111
167
 
112
168
  with redirect_stdout(stdout_buffer), redirect_stderr(stderr_buffer):
113
169
  # Execute the script
114
- exec(script, globals())
170
+ exec(script, local_globals)
115
171
 
116
172
  # Get the captured output
117
173
  stdout_output = stdout_buffer.getvalue()
@@ -131,8 +187,9 @@ def execute_command(command_data):
131
187
  result["error"] = stderr_output
132
188
 
133
189
  except Exception as e:
190
+ traceback.print_exc()
134
191
  result["success"] = False
135
- result["error"] = f"Error executing script: {str(e)}"
192
+ result["error"] = f"Error executing script: {str(e)}\n{traceback.format_exc()}"
136
193
  result["output"] = f"{SCRIPT_ERROR_MARKER}: {str(e)}"
137
194
 
138
195
  elif cmd_type == "stop":
@@ -140,19 +197,17 @@ def execute_command(command_data):
140
197
  result["success"] = True
141
198
  result["output"] = f"{SCRIPT_SUCCESS_MARKER}: Session stopping"
142
199
 
143
- # Update session state to mark as inactive
144
- with open(SESSION_STATE_FILE, 'r') as f:
145
- session_state = json.load(f)
146
-
147
- session_state["active"] = False
148
- session_state["end_time"] = time.time()
149
-
150
- with open(SESSION_STATE_FILE, 'w') as f:
151
- json.dump(session_state, f)
200
+ # Update session state to mark as stopping
201
+ update_session_state("stopping")
152
202
 
153
203
  # Return the result and signal to stop the loop
154
204
  return result, True
155
205
 
206
+ elif cmd_type == "ping":
207
+ # Simple ping command to check if session is alive
208
+ result["success"] = True
209
+ result["output"] = f"{SCRIPT_SUCCESS_MARKER}: Session is active"
210
+
156
211
  else:
157
212
  result["success"] = False
158
213
  result["error"] = f"Unknown command type: {cmd_type}"
@@ -164,61 +219,161 @@ def execute_command(command_data):
164
219
  return result, False
165
220
 
166
221
  except Exception as e:
222
+ traceback.print_exc()
167
223
  return {
168
224
  "id": command_data.get("id", "unknown"),
169
225
  "success": False,
170
226
  "output": f"{SCRIPT_ERROR_MARKER}: Command execution failed",
171
- "error": str(e)
227
+ "error": f"{str(e)}\n{traceback.format_exc()}"
172
228
  }, False
173
229
 
230
+ def check_command():
231
+ """Check if there is a new command to execute"""
232
+ try:
233
+ # If command file doesn't exist, return immediately
234
+ if not os.path.exists(COMMAND_FILE):
235
+ return False
236
+
237
+ # Read the command file
238
+ try:
239
+ with open(COMMAND_FILE, 'r') as f:
240
+ command_data = json.load(f)
241
+ except json.JSONDecodeError:
242
+ # If command file is corrupted, reset it
243
+ command_data = {"status": "ready", "command": None, "timestamp": time.time()}
244
+ with open(COMMAND_FILE, 'w') as f:
245
+ json.dump(command_data, f, indent=2)
246
+ return False
247
+
248
+ # Check if there's a new command
249
+ if command_data.get("status") == "pending" and command_data.get("command"):
250
+ # Extract the command
251
+ command = command_data.get("command")
252
+
253
+ # Mark as processing
254
+ command_data["status"] = "processing"
255
+ with open(COMMAND_FILE, 'w') as f:
256
+ json.dump(command_data, f, indent=2)
257
+
258
+ # Update session state to busy
259
+ update_session_state("busy")
260
+
261
+ # Execute the command
262
+ result, stop_requested = execute_command(command)
263
+
264
+ # Write the result
265
+ result_data = {
266
+ "status": "complete",
267
+ "result": result,
268
+ "timestamp": time.time()
269
+ }
270
+
271
+ with open(RESULT_FILE, 'w') as f:
272
+ json.dump(result_data, f, indent=2)
273
+
274
+ # Reset command file
275
+ command_data = {
276
+ "status": "ready",
277
+ "command": None,
278
+ "timestamp": time.time()
279
+ }
280
+
281
+ with open(COMMAND_FILE, 'w') as f:
282
+ json.dump(command_data, f, indent=2)
283
+
284
+ # Update session state to running again
285
+ if not stop_requested:
286
+ update_session_state("running")
287
+
288
+ # Update last activity time
289
+ update_last_activity()
290
+
291
+ return stop_requested
292
+
293
+ return False
294
+ except Exception as e:
295
+ print(f"Error checking command: {e}")
296
+ traceback.print_exc()
297
+ return False
298
+
299
+ def update_last_activity():
300
+ """Update the last activity timestamp in the session state"""
301
+ try:
302
+ if os.path.exists(SESSION_STATE_FILE):
303
+ with open(SESSION_STATE_FILE, 'r') as f:
304
+ state = json.load(f)
305
+
306
+ state["last_activity"] = time.time()
307
+
308
+ with open(SESSION_STATE_FILE, 'w') as f:
309
+ json.dump(state, f, indent=2)
310
+ except Exception as e:
311
+ print(f"Error updating last activity: {e}")
312
+
313
+ def check_inactivity_timeout():
314
+ """Check if session has been inactive for too long"""
315
+ try:
316
+ if os.path.exists(SESSION_STATE_FILE):
317
+ with open(SESSION_STATE_FILE, 'r') as f:
318
+ state = json.load(f)
319
+
320
+ last_activity = state.get("last_activity", state.get("start_time", time.time()))
321
+ current_time = time.time()
322
+
323
+ if (current_time - last_activity) > MAX_INACTIVITY_TIME:
324
+ print(f"Session inactive for {current_time - last_activity:.1f} seconds, shutting down")
325
+ return True
326
+
327
+ return False
328
+ except Exception as e:
329
+ print(f"Error checking inactivity timeout: {e}")
330
+ return False
331
+
174
332
  def command_loop():
175
- """Main loop to listen for and execute commands"""
333
+ """Main loop to check for and execute commands"""
176
334
  stop_requested = False
335
+ last_state_update = time.time()
177
336
 
178
337
  print(f"Starting command loop...")
179
- print(f"Command pipe: {COMMAND_PIPE}")
180
- print(f"Result pipe: {RESULT_PIPE}")
338
+ print(f"Command file: {COMMAND_FILE}")
339
+ print(f"Result file: {RESULT_FILE}")
181
340
 
182
341
  while not stop_requested:
183
342
  try:
184
- # Open the command pipe for reading (blocks until data is available)
185
- with open(COMMAND_PIPE, 'r') as cmd_pipe:
186
- # Read the command
187
- command_json = cmd_pipe.read().strip()
343
+ # Update session lock file to indicate we're alive
344
+ with open(SESSION_LOCK_FILE, 'w') as f:
345
+ f.write(str(time.time()))
346
+
347
+ # Check for new commands
348
+ stop_requested = check_command()
349
+
350
+ # Check for inactivity timeout
351
+ if check_inactivity_timeout():
352
+ print("Session timeout due to inactivity")
353
+ update_session_state("timeout")
354
+ stop_requested = True
188
355
 
189
- if command_json:
190
- print(f"Received command: {command_json[:100]}...")
191
- try:
192
- command_data = json.loads(command_json)
193
- result, stop_requested = execute_command(command_data)
194
-
195
- # Write the result to the result pipe
196
- with open(RESULT_PIPE, 'w') as res_pipe:
197
- res_pipe.write(json.dumps(result))
198
-
199
- except json.JSONDecodeError as e:
200
- # Invalid JSON
201
- print(f"Invalid command JSON: {e}")
202
- error_result = {
203
- "id": "unknown",
204
- "success": False,
205
- "output": f"{SCRIPT_ERROR_MARKER}: Invalid command format",
206
- "error": str(e)
207
- }
208
- with open(RESULT_PIPE, 'w') as res_pipe:
209
- res_pipe.write(json.dumps(error_result))
356
+ # Periodically update session state (every 30 seconds)
357
+ current_time = time.time()
358
+ if (current_time - last_state_update) > 30:
359
+ update_session_state()
360
+ last_state_update = current_time
361
+
362
+ # Sleep for a bit
363
+ time.sleep(POLL_INTERVAL)
210
364
 
211
365
  except Exception as e:
212
366
  print(f"Error in command loop: {e}")
213
- # Short delay to prevent CPU spinning in case of repeated errors
214
- time.sleep(0.5)
367
+ traceback.print_exc()
368
+ # Sleep for a bit
369
+ time.sleep(POLL_INTERVAL)
215
370
 
216
371
  print("Command loop stopped.")
217
372
 
218
373
  # Main script
219
374
  def main():
220
375
  try:
221
- # Setup session directory and pipes
376
+ # Setup session directory and files
222
377
  if not setup_session_dir():
223
378
  sys.exit(1)
224
379
 
@@ -229,11 +384,15 @@ def main():
229
384
  print(f"{SCRIPT_SUCCESS_MARKER}: CODESYS session started")
230
385
  command_loop()
231
386
 
387
+ # Update final state
388
+ update_session_state("stopped")
389
+
232
390
  print(f"{SCRIPT_SUCCESS_MARKER}: CODESYS session stopped cleanly")
233
391
  sys.exit(0)
234
392
  except Exception as e:
235
393
  print(f"Error in session: {e}")
236
394
  print(f"{SCRIPT_ERROR_MARKER}: Session failed - {str(e)}")
395
+ traceback.print_exc()
237
396
  sys.exit(1)
238
397
 
239
398
  if __name__ == "__main__":
@@ -1,94 +1,150 @@
1
- import sys, os, json, time, tempfile
1
+ import sys, os, json, time, traceback
2
+ import tempfile
2
3
 
3
4
  # Define markers for script results
4
5
  SCRIPT_SUCCESS_MARKER = "SCRIPT_SUCCESS"
5
6
  SCRIPT_ERROR_MARKER = "SCRIPT_ERROR"
6
7
 
7
- # Session state file and pipes
8
+ # Session state and command files
8
9
  SESSION_DIR = os.path.join(tempfile.gettempdir(), "codesys_session")
9
10
  SESSION_STATE_FILE = os.path.join(SESSION_DIR, "session_state.json")
10
- COMMAND_PIPE = os.path.join(SESSION_DIR, "command_pipe")
11
- RESULT_PIPE = os.path.join(SESSION_DIR, "result_pipe")
11
+ COMMAND_FILE = os.path.join(SESSION_DIR, "command.json")
12
+ RESULT_FILE = os.path.join(SESSION_DIR, "result.json")
13
+ SESSION_LOCK_FILE = os.path.join(SESSION_DIR, "session.lock")
12
14
 
13
- def check_session_active():
14
- """Check if a session is active by reading the state file"""
15
+ def stop_session():
16
+ """Stop a running CODESYS session"""
15
17
  try:
18
+ # Check if session directory exists
19
+ if not os.path.exists(SESSION_DIR):
20
+ print(f"{SCRIPT_ERROR_MARKER}: No session directory found at {SESSION_DIR}")
21
+ return False
22
+
23
+ # Check if session state file exists
16
24
  if not os.path.exists(SESSION_STATE_FILE):
17
- print(f"No session state file found at {SESSION_STATE_FILE}")
25
+ print(f"{SCRIPT_ERROR_MARKER}: No session state file found at {SESSION_STATE_FILE}")
18
26
  return False
19
-
20
- with open(SESSION_STATE_FILE, 'r') as f:
21
- session_state = json.load(f)
22
-
23
- return session_state.get("active", False)
24
- except Exception as e:
25
- print(f"Error checking session state: {e}")
26
- return False
27
27
 
28
- def send_stop_command():
29
- """Send the stop command to the running session"""
30
- try:
31
- if not os.path.exists(COMMAND_PIPE) or not os.path.exists(RESULT_PIPE):
32
- print(f"Command or result pipes not found")
28
+ # Read session state
29
+ try:
30
+ with open(SESSION_STATE_FILE, 'r') as f:
31
+ session_state = json.load(f)
32
+ except json.JSONDecodeError:
33
+ print(f"{SCRIPT_ERROR_MARKER}: Invalid session state file")
34
+ return False
35
+
36
+ # Check if session is active
37
+ if not session_state.get('active', False) or session_state.get('status') == 'stopped':
38
+ print(f"{SCRIPT_SUCCESS_MARKER}: Session is already stopped")
39
+ return True
40
+
41
+ # Check if command file exists
42
+ if not os.path.exists(COMMAND_FILE):
43
+ print(f"{SCRIPT_ERROR_MARKER}: Command file not found at {COMMAND_FILE}")
33
44
  return False
34
-
45
+
46
+ # Try to read the lock file to check if session is still alive
47
+ lock_time = 0
48
+ try:
49
+ if os.path.exists(SESSION_LOCK_FILE):
50
+ with open(SESSION_LOCK_FILE, 'r') as f:
51
+ lock_time = float(f.read().strip())
52
+
53
+ current_time = time.time()
54
+ if (current_time - lock_time) > 30:
55
+ print(f"WARNING: Session lock file is stale ({current_time - lock_time:.1f} seconds old). Session may have crashed.")
56
+ except Exception as e:
57
+ print(f"Warning: Error reading lock file: {e}")
58
+
59
+ # Create stop command
35
60
  stop_command = {
36
- "id": f"stop_{time.time()}",
61
+ "id": f"stop_{int(time.time())}",
37
62
  "type": "stop",
38
- "script": ""
63
+ "timestamp": time.time()
64
+ }
65
+
66
+ # Write command to file with pretty-printing
67
+ command_data = {
68
+ "status": "pending",
69
+ "command": stop_command,
70
+ "timestamp": time.time()
39
71
  }
40
-
41
- # Send the stop command
42
- with open(COMMAND_PIPE, 'w') as cmd_pipe:
43
- cmd_pipe.write(json.dumps(stop_command))
44
-
45
- # Wait for the result
46
- with open(RESULT_PIPE, 'r') as res_pipe:
47
- result_json = res_pipe.read().strip()
48
-
49
- if result_json:
72
+
73
+ with open(COMMAND_FILE, 'w') as f:
74
+ json.dump(command_data, f, indent=2)
75
+
76
+ print(f"Stop command sent to session")
77
+
78
+ # Wait for result (with timeout)
79
+ timeout = 15 # seconds (increased timeout for more reliability)
80
+ start_time = time.time()
81
+ result_received = False
82
+
83
+ while not result_received and (time.time() - start_time) < timeout:
84
+ # Check if result file exists
85
+ if not os.path.exists(RESULT_FILE):
86
+ time.sleep(0.5)
87
+ continue
88
+
50
89
  try:
51
- result = json.loads(result_json)
52
- return result.get("success", False)
53
- except json.JSONDecodeError:
54
- print(f"Invalid result JSON: {result_json}")
55
- return False
56
- else:
57
- print("No result received")
58
- return False
59
-
60
- except Exception as e:
61
- print(f"Error sending stop command: {e}")
62
- return False
90
+ with open(RESULT_FILE, 'r') as f:
91
+ result_data = json.load(f)
92
+
93
+ if result_data.get("status") == "complete" and result_data.get("result"):
94
+ result = result_data.get("result")
95
+ if result.get('id') == stop_command['id']:
96
+ result_received = True
97
+ print(f"Session stop result: {result.get('output', 'No output')}")
98
+ break
99
+ except Exception as e:
100
+ print(f"Error reading result: {e}")
101
+ time.sleep(0.5)
63
102
 
64
- def main():
65
- try:
66
- # Check if session is active
67
- if not check_session_active():
68
- print(f"{SCRIPT_SUCCESS_MARKER}: No active session found")
69
- sys.exit(0)
70
-
71
- # Send stop command
72
- print("Sending stop command to CODESYS session...")
73
- if send_stop_command():
74
- print(f"{SCRIPT_SUCCESS_MARKER}: Session stop command sent successfully")
75
-
76
- # Give the session a moment to clean up
77
- time.sleep(1)
78
-
79
- # Verify session state has been updated
80
- if not check_session_active():
81
- print(f"{SCRIPT_SUCCESS_MARKER}: Session stopped successfully")
82
- else:
83
- print(f"{SCRIPT_SUCCESS_MARKER}: Stop command accepted, but session may still be shutting down")
103
+ # Update session state to indicate it's stopped regardless of result
104
+ try:
105
+ with open(SESSION_STATE_FILE, 'r') as f:
106
+ session_state = json.load(f)
107
+
108
+ session_state['active'] = False
109
+ session_state['status'] = "stopped"
110
+ session_state['end_time'] = time.time()
111
+
112
+ with open(SESSION_STATE_FILE, 'w') as f:
113
+ json.dump(session_state, f, indent=2)
114
+ except Exception as e:
115
+ print(f"Warning: Failed to update session state file: {e}")
116
+
117
+ if result_received:
118
+ print(f"{SCRIPT_SUCCESS_MARKER}: Session stopped successfully")
119
+ return True
84
120
  else:
85
- print(f"{SCRIPT_ERROR_MARKER}: Failed to stop session")
86
- sys.exit(1)
87
-
121
+ # If we didn't get a response, the session may be hung
122
+ # Force a cleanup by removing session files
123
+ print(f"Warning: Session did not respond to stop command. Forcing cleanup.")
124
+ try:
125
+ # Try to remove all session files to ensure a clean state for next session
126
+ for filename in os.listdir(SESSION_DIR):
127
+ filepath = os.path.join(SESSION_DIR, filename)
128
+ try:
129
+ if os.path.isfile(filepath):
130
+ os.unlink(filepath)
131
+ except Exception as e:
132
+ print(f"Error removing file {filepath}: {e}")
133
+
134
+ print(f"{SCRIPT_SUCCESS_MARKER}: Session files cleaned up forcefully")
135
+ return True
136
+ except Exception as e:
137
+ print(f"{SCRIPT_ERROR_MARKER}: Failed to force cleanup - {str(e)}")
138
+ return False
139
+
88
140
  except Exception as e:
89
- print(f"Error stopping session: {e}")
90
- print(f"{SCRIPT_ERROR_MARKER}: {str(e)}")
91
- sys.exit(1)
141
+ print(f"{SCRIPT_ERROR_MARKER}: Error stopping session - {str(e)}")
142
+ traceback.print_exc()
143
+ return False
92
144
 
145
+ # Main script
93
146
  if __name__ == "__main__":
94
- main()
147
+ if stop_session():
148
+ sys.exit(0)
149
+ else:
150
+ sys.exit(1)