agentic-python-coder 3.3.0__py3-none-any.whl → 3.4.0__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.
@@ -10,6 +10,7 @@ import logging
10
10
  import os
11
11
  import re
12
12
  import shutil
13
+ import signal
13
14
  import threading
14
15
  import time
15
16
  import uuid
@@ -311,7 +312,21 @@ except ImportError:
311
312
  return self._execute_raw(code, deadline_timeout)
312
313
 
313
314
  def shutdown(self):
314
- """Shutdown the kernel and clean up."""
315
+ """Shutdown the kernel and clean up.
316
+
317
+ Kernels run in their own process group (jupyter_client launches with
318
+ start_new_session=True). After the regular shutdown, defensively kill
319
+ the whole group: uv-wrapped kernels are a tree (uv parent + python
320
+ child) and a surviving member would be orphaned.
321
+ """
322
+ # Capture the kernel's process group before shutdown clears it
323
+ pgid = None
324
+ try:
325
+ if hasattr(self, "km") and self.km:
326
+ pgid = getattr(getattr(self.km, "provisioner", None), "pgid", None)
327
+ except Exception:
328
+ pass
329
+
315
330
  try:
316
331
  if hasattr(self, "kc") and self.kc:
317
332
  try:
@@ -330,6 +345,15 @@ except ImportError:
330
345
  # Ignore errors during shutdown
331
346
  pass
332
347
 
348
+ # Defensive: ensure no process-tree member survived. Never signal our
349
+ # own group (pgid could be bogus if start_new_session did not apply).
350
+ if pgid and pgid > 0:
351
+ try:
352
+ if pgid != os.getpgid(0):
353
+ os.killpg(pgid, signal.SIGKILL)
354
+ except (ProcessLookupError, PermissionError, OSError):
355
+ pass
356
+
333
357
 
334
358
  # =============================================================================
335
359
  # Core Registry Functions
@@ -7,6 +7,9 @@ import ast
7
7
  import asyncio
8
8
  import json
9
9
  import logging
10
+ import os
11
+ import signal
12
+ import threading
10
13
 
11
14
  from mcp.server import Server
12
15
  from mcp.server.stdio import stdio_server
@@ -20,6 +23,7 @@ from agentic_python_coder.kernel import (
20
23
  kernel_exists,
21
24
  list_kernels,
22
25
  restart_kernel,
26
+ shutdown_all_kernels,
23
27
  )
24
28
 
25
29
  # Configure logging
@@ -240,6 +244,30 @@ No side effects - safe to call anytime.""",
240
244
  },
241
245
  },
242
246
  ),
247
+ Tool(
248
+ name="submit_code",
249
+ description="""Submit your final, verified, self-contained Python program.
250
+
251
+ Call this ONCE at the end of a solve, only after you have executed the program
252
+ via python_exec and your verification passed. The submission must be a
253
+ standalone program: all imports included, no reliance on session state.
254
+
255
+ The code is syntax-checked with compile():
256
+ - {"ok": true} if it parses
257
+ - {"ok": false, "error": "<detail>"} on a syntax error — fix and resubmit
258
+
259
+ The code is NOT executed and NOT written to disk; the client persists it.""",
260
+ inputSchema={
261
+ "type": "object",
262
+ "properties": {
263
+ "code": {
264
+ "type": "string",
265
+ "description": "Complete standalone Python program (final answer).",
266
+ },
267
+ },
268
+ "required": ["code"],
269
+ },
270
+ ),
243
271
  Tool(
244
272
  name="python_interrupt",
245
273
  description="""Interrupt running code in the session.
@@ -391,6 +419,21 @@ versions
391
419
  result = await execute_with_timeout(code, timeout, kernel_id)
392
420
  return [TextContent(type="text", text=json.dumps(result))]
393
421
 
422
+ elif name == "submit_code":
423
+ code = arguments.get("code", "")
424
+ if not code.strip():
425
+ payload = {"ok": False, "error": "Empty submission"}
426
+ else:
427
+ try:
428
+ compile(code, "<submission>", "exec")
429
+ payload = {"ok": True}
430
+ except SyntaxError as e:
431
+ payload = {
432
+ "ok": False,
433
+ "error": f"Syntax error at line {e.lineno}: {e.msg}",
434
+ }
435
+ return [TextContent(type="text", text=json.dumps(payload))]
436
+
394
437
  elif name == "python_status":
395
438
  kernel_id = get_kernel_id(arguments)
396
439
  is_active = kernel_exists(kernel_id)
@@ -521,9 +564,39 @@ async def run_server():
521
564
  await server.run(read, write, server.create_initialization_options())
522
565
 
523
566
 
567
+ # Hard-exit fallback if kernel cleanup hangs after a termination signal
568
+ _TERMINATION_CLEANUP_TIMEOUT = 10.0
569
+
570
+
571
+ def _handle_termination(signum, frame):
572
+ """Shut down all kernels on SIGTERM/SIGINT, then exit.
573
+
574
+ The cleanup runs in a thread (not in signal-handler context) so it can
575
+ safely take the kernel-registry locks; a timer hard-exits if it hangs.
576
+ """
577
+
578
+ def _cleanup_and_exit():
579
+ try:
580
+ shutdown_all_kernels()
581
+ finally:
582
+ os._exit(128 + signum)
583
+
584
+ threading.Timer(
585
+ _TERMINATION_CLEANUP_TIMEOUT, lambda: os._exit(128 + signum)
586
+ ).start()
587
+ threading.Thread(target=_cleanup_and_exit, daemon=True).start()
588
+
589
+
524
590
  def main():
525
591
  """Entry point for coder-mcp command."""
526
- asyncio.run(run_server())
592
+ signal.signal(signal.SIGTERM, _handle_termination)
593
+ signal.signal(signal.SIGINT, _handle_termination)
594
+ try:
595
+ asyncio.run(run_server())
596
+ finally:
597
+ # stdin EOF / transport close / normal exit: deterministic cleanup
598
+ # (atexit also runs this, but only on clean interpreter shutdown)
599
+ shutdown_all_kernels()
527
600
 
528
601
 
529
602
  if __name__ == "__main__":
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agentic-python-coder
3
- Version: 3.3.0
3
+ Version: 3.4.0
4
4
  Summary: A lightweight Python coding agent that writes, executes, and iterates on code through natural language instructions
5
5
  Author: Stefan Szeider
6
6
  License: Apache-2.0
@@ -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=PQrKMi0_mKVeG1q2wXTu3-W2ZYKwYNjtPI_3A4m4Gvg,21284
4
+ agentic_python_coder/kernel.py,sha256=JFN3gtZp3S-bGbQf2B-iQAE3AF8Q1TyQg4wrsx4Fdok,22261
5
5
  agentic_python_coder/llm.py,sha256=zjsk4-Kdx92hFSxywPGIkLv8zflWnE2cMEcpeXu6ttw,6998
6
- agentic_python_coder/mcp_server.py,sha256=2Z5O9n5QgZrgn3GdFljjF7Yq5e9tE1zNlAWBPyckrlI,18503
6
+ agentic_python_coder/mcp_server.py,sha256=7Qs5kJ9_W238vIywN5OIMG01vuPzfQ1cy1xFR8C7nGM,21054
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.3.0.dist-info/METADATA,sha256=ec4FIlQitVNa_5bMQhu2NPpJV-5VwDRQy1hzK_4b_G8,12872
45
- agentic_python_coder-3.3.0.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
46
- agentic_python_coder-3.3.0.dist-info/entry_points.txt,sha256=XIW3nZzL4kJ-Igohilj_mTvBcCyHBouDq2jmGzDILrk,107
47
- agentic_python_coder-3.3.0.dist-info/licenses/LICENSE,sha256=R9-M_V-SPdN71IphOVzM7kCNpUYs_hwj7rtroTwDhs0,11362
48
- agentic_python_coder-3.3.0.dist-info/RECORD,,
44
+ agentic_python_coder-3.4.0.dist-info/METADATA,sha256=A0vE8oBRtMdgx3kwnaVwZSzhCgTi-VXBRCxcJotjXY0,12872
45
+ agentic_python_coder-3.4.0.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
46
+ agentic_python_coder-3.4.0.dist-info/entry_points.txt,sha256=XIW3nZzL4kJ-Igohilj_mTvBcCyHBouDq2jmGzDILrk,107
47
+ agentic_python_coder-3.4.0.dist-info/licenses/LICENSE,sha256=R9-M_V-SPdN71IphOVzM7kCNpUYs_hwj7rtroTwDhs0,11362
48
+ agentic_python_coder-3.4.0.dist-info/RECORD,,