@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.
- package/.claude/settings.local.json +3 -1
- package/CHANGELOG.md +14 -0
- package/dist/scripts/session_status.py +162 -141
- package/dist/scripts/start_session.py +222 -63
- package/dist/scripts/stop_session.py +128 -72
- package/dist/sessionManager.js +54 -49
- package/docs/session_management.md +75 -54
- package/package.json +1 -1
|
@@ -1,49 +1,88 @@
|
|
|
1
|
-
import sys, os,
|
|
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
|
|
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
|
-
|
|
12
|
-
|
|
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
|
|
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
|
-
|
|
71
|
-
|
|
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
|
|
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,
|
|
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
|
|
144
|
-
|
|
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
|
|
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
|
|
180
|
-
print(f"Result
|
|
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
|
-
#
|
|
185
|
-
with open(
|
|
186
|
-
|
|
187
|
-
|
|
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
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
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
|
-
|
|
214
|
-
|
|
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
|
|
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,
|
|
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
|
|
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
|
-
|
|
11
|
-
|
|
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
|
|
14
|
-
"""
|
|
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
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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
|
-
"
|
|
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
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
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
|
-
|
|
86
|
-
|
|
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
|
|
90
|
-
|
|
91
|
-
|
|
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
|
-
|
|
147
|
+
if stop_session():
|
|
148
|
+
sys.exit(0)
|
|
149
|
+
else:
|
|
150
|
+
sys.exit(1)
|