agentic-python-coder 3.4.0__py3-none-any.whl → 3.4.1__py3-none-any.whl
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.
- agentic_python_coder/kernel.py +33 -6
- agentic_python_coder/mcp_server.py +32 -9
- {agentic_python_coder-3.4.0.dist-info → agentic_python_coder-3.4.1.dist-info}/METADATA +1 -1
- {agentic_python_coder-3.4.0.dist-info → agentic_python_coder-3.4.1.dist-info}/RECORD +7 -7
- {agentic_python_coder-3.4.0.dist-info → agentic_python_coder-3.4.1.dist-info}/WHEEL +0 -0
- {agentic_python_coder-3.4.0.dist-info → agentic_python_coder-3.4.1.dist-info}/entry_points.txt +0 -0
- {agentic_python_coder-3.4.0.dist-info → agentic_python_coder-3.4.1.dist-info}/licenses/LICENSE +0 -0
agentic_python_coder/kernel.py
CHANGED
|
@@ -220,13 +220,13 @@ except ImportError:
|
|
|
220
220
|
self._execute_raw(startup_code)
|
|
221
221
|
|
|
222
222
|
def _cleanup_on_error(self):
|
|
223
|
-
"""Clean up resources if initialization fails.
|
|
223
|
+
"""Clean up resources if initialization fails.
|
|
224
|
+
|
|
225
|
+
Routes through shutdown() so a partially-started kernel tree gets
|
|
226
|
+
the same defensive process-group kill as a healthy one.
|
|
227
|
+
"""
|
|
224
228
|
try:
|
|
225
|
-
|
|
226
|
-
self.kc.stop_channels()
|
|
227
|
-
if hasattr(self, "km") and self.km:
|
|
228
|
-
if self.km.is_alive():
|
|
229
|
-
self.km.shutdown_kernel(now=True)
|
|
229
|
+
self.shutdown()
|
|
230
230
|
except Exception:
|
|
231
231
|
pass # Best effort cleanup
|
|
232
232
|
|
|
@@ -627,6 +627,33 @@ def shutdown_all_kernels():
|
|
|
627
627
|
pass # Best effort
|
|
628
628
|
|
|
629
629
|
|
|
630
|
+
def kill_all_kernel_process_groups():
|
|
631
|
+
"""Hard-kill (SIGKILL) every registered kernel's process group.
|
|
632
|
+
|
|
633
|
+
Deliberately lock-free: this is the termination path, where a busy
|
|
634
|
+
kernel's _exec_lock may be held indefinitely by an in-flight execution
|
|
635
|
+
and the graceful shutdown_all_kernels() would block behind it. Snapshot
|
|
636
|
+
the registry and signal each group directly. Never signals our own
|
|
637
|
+
process group. Kernels mid-creation (registry slot still None) cannot
|
|
638
|
+
be reached here — that sub-second window is a known limitation.
|
|
639
|
+
"""
|
|
640
|
+
try:
|
|
641
|
+
own_pgid = os.getpgid(0)
|
|
642
|
+
except OSError:
|
|
643
|
+
own_pgid = None
|
|
644
|
+
|
|
645
|
+
for state in list(_kernels.values()):
|
|
646
|
+
if state is None:
|
|
647
|
+
continue
|
|
648
|
+
try:
|
|
649
|
+
km = state.kernel.km
|
|
650
|
+
pgid = getattr(getattr(km, "provisioner", None), "pgid", None)
|
|
651
|
+
if pgid and pgid > 0 and pgid != own_pgid:
|
|
652
|
+
os.killpg(pgid, signal.SIGKILL)
|
|
653
|
+
except (ProcessLookupError, PermissionError, OSError, AttributeError):
|
|
654
|
+
pass # Best effort
|
|
655
|
+
|
|
656
|
+
|
|
630
657
|
# =============================================================================
|
|
631
658
|
# Backward Compatibility API
|
|
632
659
|
# =============================================================================
|
|
@@ -21,6 +21,7 @@ from agentic_python_coder.kernel import (
|
|
|
21
21
|
execute_in_kernel,
|
|
22
22
|
interrupt_kernel_by_id,
|
|
23
23
|
kernel_exists,
|
|
24
|
+
kill_all_kernel_process_groups,
|
|
24
25
|
list_kernels,
|
|
25
26
|
restart_kernel,
|
|
26
27
|
shutdown_all_kernels,
|
|
@@ -36,6 +37,7 @@ server = Server("ipython_mcp")
|
|
|
36
37
|
MAX_OUTPUT = 100 * 1024 # 100KB truncation limit
|
|
37
38
|
MAX_TIMEOUT = 300 # Maximum allowed timeout in seconds
|
|
38
39
|
DEFAULT_TIMEOUT = 30 # Default timeout in seconds
|
|
40
|
+
STATUS_PROBE_TIMEOUT = 10 # Timeout for python_status introspection probes
|
|
39
41
|
KERNEL_TIMEOUT_BUFFER = 5 # Extra seconds for kernel deadline vs asyncio timeout
|
|
40
42
|
|
|
41
43
|
# Async lock for session operations (default kernel management)
|
|
@@ -346,7 +348,9 @@ versions
|
|
|
346
348
|
% packages
|
|
347
349
|
)
|
|
348
350
|
try:
|
|
349
|
-
result = await execute_with_timeout(
|
|
351
|
+
result = await execute_with_timeout(
|
|
352
|
+
version_code, DEFAULT_TIMEOUT, kernel_id
|
|
353
|
+
)
|
|
350
354
|
if result.get("result"):
|
|
351
355
|
versions = ast.literal_eval(result["result"])
|
|
352
356
|
if isinstance(versions, dict):
|
|
@@ -430,8 +434,11 @@ versions
|
|
|
430
434
|
except SyntaxError as e:
|
|
431
435
|
payload = {
|
|
432
436
|
"ok": False,
|
|
433
|
-
"error": f"Syntax error at line {e.lineno}: {e.msg}",
|
|
437
|
+
"error": f"Syntax error at line {e.lineno or '?'}: {e.msg}",
|
|
434
438
|
}
|
|
439
|
+
except ValueError as e:
|
|
440
|
+
# compile() raises ValueError e.g. for null bytes in source
|
|
441
|
+
payload = {"ok": False, "error": f"Invalid source: {e}"}
|
|
435
442
|
return [TextContent(type="text", text=json.dumps(payload))]
|
|
436
443
|
|
|
437
444
|
elif name == "python_status":
|
|
@@ -455,7 +462,7 @@ versions
|
|
|
455
462
|
# Get Python version
|
|
456
463
|
try:
|
|
457
464
|
result = await execute_with_timeout(
|
|
458
|
-
"import sys; sys.version",
|
|
465
|
+
"import sys; sys.version", STATUS_PROBE_TIMEOUT, kernel_id
|
|
459
466
|
)
|
|
460
467
|
if result.get("result"):
|
|
461
468
|
status["python_version"] = result["result"].strip("'\"")
|
|
@@ -469,7 +476,9 @@ import importlib.metadata
|
|
|
469
476
|
[f"{d.metadata['Name']} {d.version}" for d in importlib.metadata.distributions()
|
|
470
477
|
if d.metadata['Name'] not in ('pip', 'setuptools', 'wheel', 'ipykernel', 'jupyter-client')][:20]
|
|
471
478
|
"""
|
|
472
|
-
result = await execute_with_timeout(
|
|
479
|
+
result = await execute_with_timeout(
|
|
480
|
+
pkg_code, STATUS_PROBE_TIMEOUT, kernel_id
|
|
481
|
+
)
|
|
473
482
|
if result.get("result"):
|
|
474
483
|
status["packages"] = ast.literal_eval(result["result"])
|
|
475
484
|
except Exception:
|
|
@@ -481,7 +490,9 @@ import importlib.metadata
|
|
|
481
490
|
[name for name in dir() if not name.startswith('_')
|
|
482
491
|
and name not in ('In', 'Out', 'get_ipython', 'exit', 'quit', 'open')]
|
|
483
492
|
"""
|
|
484
|
-
result = await execute_with_timeout(
|
|
493
|
+
result = await execute_with_timeout(
|
|
494
|
+
var_code, STATUS_PROBE_TIMEOUT, kernel_id
|
|
495
|
+
)
|
|
485
496
|
if result.get("result"):
|
|
486
497
|
status["variables"] = ast.literal_eval(result["result"])
|
|
487
498
|
except Exception:
|
|
@@ -567,16 +578,25 @@ async def run_server():
|
|
|
567
578
|
# Hard-exit fallback if kernel cleanup hangs after a termination signal
|
|
568
579
|
_TERMINATION_CLEANUP_TIMEOUT = 10.0
|
|
569
580
|
|
|
581
|
+
# Set on the first termination signal; later signals are ignored instead of
|
|
582
|
+
# stacking additional timers and cleanup threads
|
|
583
|
+
_terminating = threading.Event()
|
|
584
|
+
|
|
570
585
|
|
|
571
586
|
def _handle_termination(signum, frame):
|
|
572
587
|
"""Shut down all kernels on SIGTERM/SIGINT, then exit.
|
|
573
588
|
|
|
574
|
-
|
|
575
|
-
|
|
589
|
+
Kernel process groups are hard-killed first (lock-free, cannot block on
|
|
590
|
+
a busy kernel's exec lock); the graceful registry cleanup then runs in a
|
|
591
|
+
thread, with a timer that hard-exits if it hangs.
|
|
576
592
|
"""
|
|
593
|
+
if _terminating.is_set():
|
|
594
|
+
return
|
|
595
|
+
_terminating.set()
|
|
577
596
|
|
|
578
597
|
def _cleanup_and_exit():
|
|
579
598
|
try:
|
|
599
|
+
kill_all_kernel_process_groups()
|
|
580
600
|
shutdown_all_kernels()
|
|
581
601
|
finally:
|
|
582
602
|
os._exit(128 + signum)
|
|
@@ -594,8 +614,11 @@ def main():
|
|
|
594
614
|
try:
|
|
595
615
|
asyncio.run(run_server())
|
|
596
616
|
finally:
|
|
597
|
-
# stdin EOF / transport close / normal exit:
|
|
598
|
-
#
|
|
617
|
+
# stdin EOF / transport close / normal exit: the client is gone, so
|
|
618
|
+
# hard-kill kernel groups first (immune to a busy kernel's exec
|
|
619
|
+
# lock), then clean up the registry (atexit also runs the graceful
|
|
620
|
+
# path, but only on clean interpreter shutdown)
|
|
621
|
+
kill_all_kernel_process_groups()
|
|
599
622
|
shutdown_all_kernels()
|
|
600
623
|
|
|
601
624
|
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
agentic_python_coder/__init__.py,sha256=e6o-k__u5xNIiQAkZbHtbZC2-NwpeOdi4MdQtAqJBjE,2112
|
|
2
2
|
agentic_python_coder/agent.py,sha256=gzDFf4n7J1JdWawYPzWQFVwcHZ2rsRHUzVZVcGYjQWo,14451
|
|
3
3
|
agentic_python_coder/cli.py,sha256=HS9nlqsiWZAvg0Hc8ExoVT8eEcy2t3Bux6UGukERWqQ,14663
|
|
4
|
-
agentic_python_coder/kernel.py,sha256=
|
|
4
|
+
agentic_python_coder/kernel.py,sha256=Q-yLb8Il54kZv41qev2uzASFlr6bGjQ7ztBy7yD48GM,23273
|
|
5
5
|
agentic_python_coder/llm.py,sha256=zjsk4-Kdx92hFSxywPGIkLv8zflWnE2cMEcpeXu6ttw,6998
|
|
6
|
-
agentic_python_coder/mcp_server.py,sha256=
|
|
6
|
+
agentic_python_coder/mcp_server.py,sha256=Osdl8UnjkupXbPLiYCugiapGDJsryZfVai0o-jepuqw,22047
|
|
7
7
|
agentic_python_coder/project_md.py,sha256=4xkQHlWb4cDPEkvegw1YwcfaTJ3aFbrl0CYcC_xxABY,3249
|
|
8
8
|
agentic_python_coder/runner.py,sha256=Kbw1ceYJ4MVfUKgSIkiuBhMb8ZtbZuJwbBeTUsvPRts,13045
|
|
9
9
|
agentic_python_coder/tools.py,sha256=tjhQbsF3n3bFIegnEOtE8czjzbtMSXy6B9gYeb-YAJ4,12088
|
|
@@ -41,8 +41,8 @@ agentic_python_coder/models/sonnet46.json,sha256=Ops1FpBoyx9PBGRhIXG8u-YGBgIui9D
|
|
|
41
41
|
agentic_python_coder/models/sonnet5.json,sha256=kf6DIkQlcGVZ40IzRv3GXFtNncZF_gKqQeGzZAxE_KM,87
|
|
42
42
|
agentic_python_coder/prompts/system.md,sha256=hREDVAhJXwCBAgJ3AE9JD6j-JRHREUPlkRSvThOvM3A,3807
|
|
43
43
|
agentic_python_coder/prompts/system_todo.md,sha256=TuNeeUNS9KqyF62Da6t2mlUCYhKsgzwGe5ic_vFPDqw,5054
|
|
44
|
-
agentic_python_coder-3.4.
|
|
45
|
-
agentic_python_coder-3.4.
|
|
46
|
-
agentic_python_coder-3.4.
|
|
47
|
-
agentic_python_coder-3.4.
|
|
48
|
-
agentic_python_coder-3.4.
|
|
44
|
+
agentic_python_coder-3.4.1.dist-info/METADATA,sha256=C9UTWTVtnf7-0Bgiu44LMEd16zA00T3UxkS92MVV_Ac,12872
|
|
45
|
+
agentic_python_coder-3.4.1.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
|
|
46
|
+
agentic_python_coder-3.4.1.dist-info/entry_points.txt,sha256=XIW3nZzL4kJ-Igohilj_mTvBcCyHBouDq2jmGzDILrk,107
|
|
47
|
+
agentic_python_coder-3.4.1.dist-info/licenses/LICENSE,sha256=R9-M_V-SPdN71IphOVzM7kCNpUYs_hwj7rtroTwDhs0,11362
|
|
48
|
+
agentic_python_coder-3.4.1.dist-info/RECORD,,
|
|
File without changes
|
{agentic_python_coder-3.4.0.dist-info → agentic_python_coder-3.4.1.dist-info}/entry_points.txt
RENAMED
|
File without changes
|
{agentic_python_coder-3.4.0.dist-info → agentic_python_coder-3.4.1.dist-info}/licenses/LICENSE
RENAMED
|
File without changes
|