@code-yeongyu/senpi-codemode 2026.9.19 → 2026.9.21-2

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 CHANGED
@@ -12,6 +12,58 @@
12
12
 
13
13
  ### Removed
14
14
 
15
+ ## [2026.9.21-2] - 2026-09-21
16
+
17
+ ### Breaking Changes
18
+
19
+ ### Added
20
+
21
+ ### Changed
22
+
23
+ ### Fixed
24
+
25
+ ### Removed
26
+
27
+ ## [2026.9.21] - 2026-09-21
28
+
29
+ ### Breaking Changes
30
+
31
+ ### Added
32
+
33
+ ### Changed
34
+
35
+ - Updated the bundled dependencies: typebox 1.3.27 -> 1.3.34. ([#1895](https://github.com/code-yeongyu/senpi/issues/1895))
36
+
37
+ ### Fixed
38
+
39
+ - Python eval kernel: a kernel whose host process died mid-cell now exits instead of being orphaned forever. ([#1659](https://github.com/code-yeongyu/senpi/issues/1659))
40
+
41
+ ### Removed
42
+
43
+ ## [2026.9.20] - 2026-09-20
44
+
45
+ ### Breaking Changes
46
+
47
+ ### Added
48
+
49
+ ### Changed
50
+
51
+ ### Fixed
52
+
53
+ ### Removed
54
+
55
+ ## [2026.9.19-2] - 2026-09-19
56
+
57
+ ### Breaking Changes
58
+
59
+ ### Added
60
+
61
+ ### Changed
62
+
63
+ ### Fixed
64
+
65
+ ### Removed
66
+
15
67
  ## [2026.9.19] - 2026-09-19
16
68
 
17
69
  ### Breaking Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@code-yeongyu/senpi-codemode",
3
- "version": "2026.9.19",
3
+ "version": "2026.9.21-2",
4
4
  "description": "Source-only senpi extension package for codemode evaluation tools",
5
5
  "type": "module",
6
6
  "main": "./src/index.ts",
@@ -30,14 +30,14 @@
30
30
  },
31
31
  "dependencies": {
32
32
  "@babel/parser": "8.0.4",
33
- "@earendil-works/pi-ai": "npm:@code-yeongyu/senpi-ai@2026.9.19",
34
- "typebox": "1.3.27"
33
+ "@earendil-works/pi-ai": "npm:@code-yeongyu/senpi-ai@2026.9.21-2",
34
+ "typebox": "1.3.34"
35
35
  },
36
36
  "peerDependencies": {
37
- "@code-yeongyu/senpi": "2026.9.19"
37
+ "@code-yeongyu/senpi": "2026.9.21-2"
38
38
  },
39
39
  "devDependencies": {
40
- "@code-yeongyu/senpi": "2026.9.19"
40
+ "@code-yeongyu/senpi": "2026.9.21-2"
41
41
  },
42
42
  "keywords": [
43
43
  "senpi",
@@ -27,7 +27,6 @@ from threading import Lock, Thread
27
27
  from typing import Any, Callable, Union
28
28
  from urllib.parse import unquote
29
29
 
30
- SESSION_ID = ""
31
30
  CONNECTION: dict[str, Any] = {}
32
31
  USER_NS: dict[str, Any] = {"__name__": "__main__", "__doc__": None, "__builtins__": __builtins__}
33
32
  LOOP = asyncio.new_event_loop()
@@ -1056,10 +1055,9 @@ def elapsed(start: float) -> int:
1056
1055
 
1057
1056
 
1058
1057
  def handle(message: dict[str, Any]) -> bool:
1059
- global SESSION_ID, CONNECTION
1058
+ global CONNECTION
1060
1059
  message_type = message.get("type")
1061
1060
  if message_type == "init":
1062
- SESSION_ID = str(message.get("sessionId", ""))
1063
1061
  connection = message.get("connection")
1064
1062
  if not isinstance(connection, dict):
1065
1063
  emit({"type": "init-failed", "error": {"message": "missing bridge connection"}})
@@ -1097,10 +1095,33 @@ def _watch_parent(initial_ppid: int) -> None:
1097
1095
  return
1098
1096
 
1099
1097
 
1098
+ def _watch_named_parent(parent_pid: int) -> None:
1099
+ # The ppid watch above can only observe a change from the ppid captured at boot. A
1100
+ # host that died before the interpreter reached that capture is already replaced in
1101
+ # getppid() by the posthumous value, so no transition ever fires. The host passes its
1102
+ # own pid at spawn (SENPI_PY_KERNEL_PARENT_PID) precisely so this loss is detectable:
1103
+ # poll the named pid instead of the ppid and take the whole group down once it is gone.
1104
+ while True:
1105
+ time.sleep(0.5)
1106
+ try:
1107
+ os.kill(parent_pid, 0)
1108
+ except ProcessLookupError:
1109
+ _terminate_process_group()
1110
+ return
1111
+
1112
+
1100
1113
  def _start_parent_watch() -> None:
1101
1114
  if os.name != "posix":
1102
1115
  return
1103
1116
  Thread(target=_watch_parent, args=(os.getppid(),), name="senpi-parent-watch", daemon=True).start()
1117
+ named_parent = os.environ.get("SENPI_PY_KERNEL_PARENT_PID")
1118
+ if named_parent and named_parent.isdigit():
1119
+ Thread(
1120
+ target=_watch_named_parent,
1121
+ args=(int(named_parent),),
1122
+ name="senpi-named-parent-watch",
1123
+ daemon=True,
1124
+ ).start()
1104
1125
 
1105
1126
 
1106
1127
  def main() -> None:
@@ -30,9 +30,13 @@ export class PythonKernelRetirementError extends Error {
30
30
  }
31
31
 
32
32
  export function defaultSpawn(options: KernelSpawnOptions): KernelChild {
33
+ // Detached children survive their host, so the prelude needs the host pid to
34
+ // watch for a parent that is already gone by the time the interpreter boots.
35
+ const env =
36
+ process.platform === "win32" ? options.env : { ...options.env, SENPI_PY_KERNEL_PARENT_PID: String(process.pid) };
33
37
  return spawn(options.command, [...options.args], {
34
38
  cwd: options.cwd,
35
- env: options.env,
39
+ env,
36
40
  stdio: "pipe",
37
41
  detached: process.platform !== "win32",
38
42
  windowsHide: true,