@code-yeongyu/senpi 2026.9.5 → 2026.9.7-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.
Files changed (307) hide show
  1. package/CHANGELOG.md +104 -0
  2. package/dist/core/agent-session.d.ts +60 -5
  3. package/dist/core/agent-session.d.ts.map +1 -1
  4. package/dist/core/agent-session.js +262 -78
  5. package/dist/core/agent-session.js.map +1 -1
  6. package/dist/core/compaction/compaction.d.ts +6 -2
  7. package/dist/core/compaction/compaction.d.ts.map +1 -1
  8. package/dist/core/compaction/compaction.js +6 -1
  9. package/dist/core/compaction/compaction.js.map +1 -1
  10. package/dist/core/compaction/stuck-overflow.d.ts +11 -0
  11. package/dist/core/compaction/stuck-overflow.d.ts.map +1 -0
  12. package/dist/core/compaction/stuck-overflow.js +17 -0
  13. package/dist/core/compaction/stuck-overflow.js.map +1 -0
  14. package/dist/core/extensions/builtin/cache-keepalive/index.d.ts.map +1 -1
  15. package/dist/core/extensions/builtin/cache-keepalive/index.js +3 -21
  16. package/dist/core/extensions/builtin/cache-keepalive/index.js.map +1 -1
  17. package/dist/core/extensions/builtin/claude-sdk-oauth/prompt-bridge.d.ts.map +1 -1
  18. package/dist/core/extensions/builtin/claude-sdk-oauth/prompt-bridge.js +2 -0
  19. package/dist/core/extensions/builtin/claude-sdk-oauth/prompt-bridge.js.map +1 -1
  20. package/dist/core/extensions/builtin/claude-sdk-oauth/session-binding.d.ts.map +1 -1
  21. package/dist/core/extensions/builtin/claude-sdk-oauth/session-binding.js +36 -19
  22. package/dist/core/extensions/builtin/claude-sdk-oauth/session-binding.js.map +1 -1
  23. package/dist/core/extensions/builtin/claude-sdk-oauth/session-commit-boundary.d.ts.map +1 -1
  24. package/dist/core/extensions/builtin/claude-sdk-oauth/session-commit-boundary.js +21 -10
  25. package/dist/core/extensions/builtin/claude-sdk-oauth/session-commit-boundary.js.map +1 -1
  26. package/dist/core/extensions/builtin/claude-sdk-oauth/session-continuity.d.ts +4 -17
  27. package/dist/core/extensions/builtin/claude-sdk-oauth/session-continuity.d.ts.map +1 -1
  28. package/dist/core/extensions/builtin/claude-sdk-oauth/session-continuity.js +23 -33
  29. package/dist/core/extensions/builtin/claude-sdk-oauth/session-continuity.js.map +1 -1
  30. package/dist/core/extensions/builtin/claude-sdk-oauth/session-registry-pump.d.ts.map +1 -1
  31. package/dist/core/extensions/builtin/claude-sdk-oauth/session-registry-pump.js +15 -2
  32. package/dist/core/extensions/builtin/claude-sdk-oauth/session-registry-pump.js.map +1 -1
  33. package/dist/core/extensions/builtin/claude-sdk-oauth/session-stream.d.ts.map +1 -1
  34. package/dist/core/extensions/builtin/claude-sdk-oauth/session-stream.js +1 -0
  35. package/dist/core/extensions/builtin/claude-sdk-oauth/session-stream.js.map +1 -1
  36. package/dist/core/extensions/builtin/claude-sdk-oauth/session-sync.d.ts.map +1 -1
  37. package/dist/core/extensions/builtin/claude-sdk-oauth/session-sync.js +5 -3
  38. package/dist/core/extensions/builtin/claude-sdk-oauth/session-sync.js.map +1 -1
  39. package/dist/core/extensions/builtin/claude-sdk-oauth/session-turn-attempt.d.ts.map +1 -1
  40. package/dist/core/extensions/builtin/claude-sdk-oauth/session-turn-attempt.js +8 -3
  41. package/dist/core/extensions/builtin/claude-sdk-oauth/session-turn-attempt.js.map +1 -1
  42. package/dist/core/extensions/builtin/claude-sdk-oauth/session-turn-claim.d.ts +1 -1
  43. package/dist/core/extensions/builtin/claude-sdk-oauth/session-turn-claim.d.ts.map +1 -1
  44. package/dist/core/extensions/builtin/claude-sdk-oauth/session-turn-claim.js +2 -3
  45. package/dist/core/extensions/builtin/claude-sdk-oauth/session-turn-claim.js.map +1 -1
  46. package/dist/core/extensions/builtin/claude-sdk-oauth/tools.d.ts +6 -5
  47. package/dist/core/extensions/builtin/claude-sdk-oauth/tools.d.ts.map +1 -1
  48. package/dist/core/extensions/builtin/claude-sdk-oauth/tools.js +6 -5
  49. package/dist/core/extensions/builtin/claude-sdk-oauth/tools.js.map +1 -1
  50. package/dist/core/extensions/builtin/compaction/circuit-breaker.d.ts +8 -0
  51. package/dist/core/extensions/builtin/compaction/circuit-breaker.d.ts.map +1 -1
  52. package/dist/core/extensions/builtin/compaction/circuit-breaker.js +8 -0
  53. package/dist/core/extensions/builtin/compaction/circuit-breaker.js.map +1 -1
  54. package/dist/core/extensions/builtin/compaction/deterministic-fallback.d.ts +4 -0
  55. package/dist/core/extensions/builtin/compaction/deterministic-fallback.d.ts.map +1 -1
  56. package/dist/core/extensions/builtin/compaction/deterministic-fallback.js +40 -20
  57. package/dist/core/extensions/builtin/compaction/deterministic-fallback.js.map +1 -1
  58. package/dist/core/extensions/builtin/compaction/fallback-failed-turn-normalization.d.ts +18 -0
  59. package/dist/core/extensions/builtin/compaction/fallback-failed-turn-normalization.d.ts.map +1 -0
  60. package/dist/core/extensions/builtin/compaction/fallback-failed-turn-normalization.js +20 -0
  61. package/dist/core/extensions/builtin/compaction/fallback-failed-turn-normalization.js.map +1 -0
  62. package/dist/core/extensions/builtin/compaction/index.js +2 -2
  63. package/dist/core/extensions/builtin/compaction/index.js.map +1 -1
  64. package/dist/core/extensions/builtin/compaction/lane-policy.d.ts +12 -0
  65. package/dist/core/extensions/builtin/compaction/lane-policy.d.ts.map +1 -1
  66. package/dist/core/extensions/builtin/compaction/lane-policy.js +47 -18
  67. package/dist/core/extensions/builtin/compaction/lane-policy.js.map +1 -1
  68. package/dist/core/extensions/builtin/compaction/openai-remote-convert.d.ts.map +1 -1
  69. package/dist/core/extensions/builtin/compaction/openai-remote-convert.js +2 -0
  70. package/dist/core/extensions/builtin/compaction/openai-remote-convert.js.map +1 -1
  71. package/dist/core/extensions/builtin/compaction/retained-message-safety.d.ts.map +1 -1
  72. package/dist/core/extensions/builtin/compaction/retained-message-safety.js +14 -1
  73. package/dist/core/extensions/builtin/compaction/retained-message-safety.js.map +1 -1
  74. package/dist/core/extensions/builtin/goal/cache-warm-renderer.js +6 -4
  75. package/dist/core/extensions/builtin/goal/cache-warm-renderer.js.map +1 -1
  76. package/dist/core/extensions/builtin/goal/cache-warm.d.ts +19 -1
  77. package/dist/core/extensions/builtin/goal/cache-warm.d.ts.map +1 -1
  78. package/dist/core/extensions/builtin/goal/cache-warm.js +23 -10
  79. package/dist/core/extensions/builtin/goal/cache-warm.js.map +1 -1
  80. package/dist/core/extensions/builtin/goal/continuation-recovery.d.ts +1 -0
  81. package/dist/core/extensions/builtin/goal/continuation-recovery.d.ts.map +1 -1
  82. package/dist/core/extensions/builtin/goal/continuation-recovery.js +2 -0
  83. package/dist/core/extensions/builtin/goal/continuation-recovery.js.map +1 -1
  84. package/dist/core/extensions/builtin/goal/continuation.d.ts +3 -1
  85. package/dist/core/extensions/builtin/goal/continuation.d.ts.map +1 -1
  86. package/dist/core/extensions/builtin/goal/continuation.js +4 -0
  87. package/dist/core/extensions/builtin/goal/continuation.js.map +1 -1
  88. package/dist/core/extensions/builtin/goal/lifecycle-helpers.d.ts +3 -0
  89. package/dist/core/extensions/builtin/goal/lifecycle-helpers.d.ts.map +1 -1
  90. package/dist/core/extensions/builtin/goal/lifecycle-helpers.js +14 -3
  91. package/dist/core/extensions/builtin/goal/lifecycle-helpers.js.map +1 -1
  92. package/dist/core/extensions/builtin/goal/monitor-continuation.d.ts +1 -1
  93. package/dist/core/extensions/builtin/goal/monitor-continuation.d.ts.map +1 -1
  94. package/dist/core/extensions/builtin/goal/monitor-continuation.js +7 -3
  95. package/dist/core/extensions/builtin/goal/monitor-continuation.js.map +1 -1
  96. package/dist/core/extensions/builtin/hooks/plugin-manifest.js +2 -2
  97. package/dist/core/extensions/builtin/hooks/plugin-manifest.js.map +1 -1
  98. package/dist/core/extensions/builtin/hooks/safety.js +2 -2
  99. package/dist/core/extensions/builtin/hooks/safety.js.map +1 -1
  100. package/dist/core/extensions/builtin/permission-system/external-dir.d.ts.map +1 -1
  101. package/dist/core/extensions/builtin/permission-system/external-dir.js +5 -22
  102. package/dist/core/extensions/builtin/permission-system/external-dir.js.map +1 -1
  103. package/dist/core/extensions/builtin/permission-system/parsers.d.ts.map +1 -1
  104. package/dist/core/extensions/builtin/permission-system/parsers.js +5 -7
  105. package/dist/core/extensions/builtin/permission-system/parsers.js.map +1 -1
  106. package/dist/core/extensions/builtin/prompt-preset/gpt-6-astra.d.ts +2 -2
  107. package/dist/core/extensions/builtin/prompt-preset/gpt-6-astra.d.ts.map +1 -1
  108. package/dist/core/extensions/builtin/prompt-preset/gpt-6-astra.js +8 -2
  109. package/dist/core/extensions/builtin/prompt-preset/gpt-6-astra.js.map +1 -1
  110. package/dist/core/extensions/builtin/terminal/monitor-registry.d.ts.map +1 -1
  111. package/dist/core/extensions/builtin/terminal/monitor-registry.js +14 -7
  112. package/dist/core/extensions/builtin/terminal/monitor-registry.js.map +1 -1
  113. package/dist/core/messages.d.ts +7 -0
  114. package/dist/core/messages.d.ts.map +1 -1
  115. package/dist/core/messages.js +2 -0
  116. package/dist/core/messages.js.map +1 -1
  117. package/dist/core/package-identity.d.ts +12 -0
  118. package/dist/core/package-identity.d.ts.map +1 -0
  119. package/dist/core/package-identity.js +57 -0
  120. package/dist/core/package-identity.js.map +1 -0
  121. package/dist/core/resource-loader.d.ts +0 -2
  122. package/dist/core/resource-loader.d.ts.map +1 -1
  123. package/dist/core/resource-loader.js +8 -60
  124. package/dist/core/resource-loader.js.map +1 -1
  125. package/dist/core/retry-fallback/controller.d.ts.map +1 -1
  126. package/dist/core/retry-fallback/controller.js +5 -4
  127. package/dist/core/retry-fallback/controller.js.map +1 -1
  128. package/dist/core/sdk.d.ts.map +1 -1
  129. package/dist/core/sdk.js +5 -0
  130. package/dist/core/sdk.js.map +1 -1
  131. package/dist/core/session-manager.d.ts +12 -1
  132. package/dist/core/session-manager.d.ts.map +1 -1
  133. package/dist/core/session-manager.js +29 -3
  134. package/dist/core/session-manager.js.map +1 -1
  135. package/dist/core/settings-manager.d.ts +6 -2
  136. package/dist/core/settings-manager.d.ts.map +1 -1
  137. package/dist/core/settings-manager.js +4 -1
  138. package/dist/core/settings-manager.js.map +1 -1
  139. package/dist/core/tools/bounded-realpath.d.ts +28 -0
  140. package/dist/core/tools/bounded-realpath.d.ts.map +1 -0
  141. package/dist/core/tools/bounded-realpath.js +53 -0
  142. package/dist/core/tools/bounded-realpath.js.map +1 -0
  143. package/dist/core/tools/file-mutation-queue.d.ts.map +1 -1
  144. package/dist/core/tools/file-mutation-queue.js +15 -13
  145. package/dist/core/tools/file-mutation-queue.js.map +1 -1
  146. package/dist/core/tools/filesystem-policy.d.ts +7 -0
  147. package/dist/core/tools/filesystem-policy.d.ts.map +1 -1
  148. package/dist/core/tools/filesystem-policy.js +25 -15
  149. package/dist/core/tools/filesystem-policy.js.map +1 -1
  150. package/dist/core/trust-manager.d.ts.map +1 -1
  151. package/dist/core/trust-manager.js +13 -2
  152. package/dist/core/trust-manager.js.map +1 -1
  153. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  154. package/dist/modes/interactive/interactive-mode.js +5 -1
  155. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  156. package/dist/modes/interactive/tips/catalog/memory-tips.d.ts.map +1 -1
  157. package/dist/modes/interactive/tips/catalog/memory-tips.js +6 -0
  158. package/dist/modes/interactive/tips/catalog/memory-tips.js.map +1 -1
  159. package/dist/modes/interactive/tips/catalog/subagent-tips.d.ts.map +1 -1
  160. package/dist/modes/interactive/tips/catalog/subagent-tips.js +6 -0
  161. package/dist/modes/interactive/tips/catalog/subagent-tips.js.map +1 -1
  162. package/dist/modes/rpc/host-lifecycle.d.ts.map +1 -1
  163. package/dist/modes/rpc/host-lifecycle.js +31 -5
  164. package/dist/modes/rpc/host-lifecycle.js.map +1 -1
  165. package/dist/modes/rpc/host-watchdog.d.ts +11 -0
  166. package/dist/modes/rpc/host-watchdog.d.ts.map +1 -1
  167. package/dist/modes/rpc/host-watchdog.js +16 -2
  168. package/dist/modes/rpc/host-watchdog.js.map +1 -1
  169. package/dist/modes/rpc/multi-session-host.d.ts +3 -7
  170. package/dist/modes/rpc/multi-session-host.d.ts.map +1 -1
  171. package/dist/modes/rpc/multi-session-host.js +57 -29
  172. package/dist/modes/rpc/multi-session-host.js.map +1 -1
  173. package/dist/modes/rpc/rpc-types.d.ts +1 -2
  174. package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
  175. package/dist/modes/rpc/rpc-types.js +0 -1
  176. package/dist/modes/rpc/rpc-types.js.map +1 -1
  177. package/dist/modes/rpc/session-registry.d.ts +1 -4
  178. package/dist/modes/rpc/session-registry.d.ts.map +1 -1
  179. package/dist/modes/rpc/session-registry.js +0 -15
  180. package/dist/modes/rpc/session-registry.js.map +1 -1
  181. package/dist/modes/rpc/socket-ownership.d.ts +46 -0
  182. package/dist/modes/rpc/socket-ownership.d.ts.map +1 -0
  183. package/dist/modes/rpc/socket-ownership.js +178 -0
  184. package/dist/modes/rpc/socket-ownership.js.map +1 -0
  185. package/dist/utils/fs-watch.d.ts +17 -0
  186. package/dist/utils/fs-watch.d.ts.map +1 -1
  187. package/dist/utils/fs-watch.js +38 -10
  188. package/dist/utils/fs-watch.js.map +1 -1
  189. package/dist/utils/paths.d.ts +33 -0
  190. package/dist/utils/paths.d.ts.map +1 -1
  191. package/dist/utils/paths.js +109 -3
  192. package/dist/utils/paths.js.map +1 -1
  193. package/docs/compaction-guide.md +2 -2
  194. package/docs/compaction.md +2 -2
  195. package/docs/development.md +3 -2
  196. package/docs/providers.md +1 -1
  197. package/docs/rpc.md +6 -7
  198. package/node_modules/@code-yeongyu/senpi-codemode/CHANGELOG.md +74 -0
  199. package/node_modules/@code-yeongyu/senpi-codemode/README.md +34 -5
  200. package/node_modules/@code-yeongyu/senpi-codemode/package.json +4 -4
  201. package/node_modules/@code-yeongyu/senpi-codemode/src/bridge/protocol.ts +1 -0
  202. package/node_modules/@code-yeongyu/senpi-codemode/src/bridge/reserved.ts +2 -0
  203. package/node_modules/@code-yeongyu/senpi-codemode/src/extension/runtime-factory.ts +3 -0
  204. package/node_modules/@code-yeongyu/senpi-codemode/src/extension/session-manager.ts +7 -0
  205. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/AGENTS.md +7 -0
  206. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/jl/kernel.ts +6 -2
  207. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/context-manager.ts +73 -129
  208. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/inline-worker.ts +2 -2
  209. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/interrupt-bounds.ts +66 -0
  210. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/kernel-contract.ts +3 -0
  211. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/run-queue.ts +18 -3
  212. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/worker-core.js +59 -0
  213. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/worker-indirect-eval.js +10 -6
  214. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/worker-runtime.js +16 -0
  215. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/worker-shell-capture.d.ts +25 -0
  216. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/worker-shell-capture.js +70 -8
  217. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/worker-slot.ts +106 -0
  218. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/worker-startup.ts +70 -0
  219. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/py/kernel-contract.ts +3 -0
  220. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/py/transport.ts +11 -3
  221. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/rb/kernel.ts +6 -2
  222. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/session-env.ts +56 -0
  223. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/shared/runtime-asset.ts +40 -14
  224. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/shared/subprocess-contract.ts +3 -0
  225. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/shared/subprocess-kernel.ts +9 -1
  226. package/node_modules/@code-yeongyu/senpi-codemode/src/output/output-meta.ts +26 -2
  227. package/node_modules/@code-yeongyu/senpi-codemode/src/prompt/eval-prompt.ts +10 -5
  228. package/node_modules/@code-yeongyu/senpi-codemode/src/tool/cell-execution.ts +9 -11
  229. package/node_modules/@code-yeongyu/senpi-codemode/src/tool/detached-cell-contract.ts +45 -0
  230. package/node_modules/@code-yeongyu/senpi-codemode/src/tool/detached-cell-manager.ts +43 -71
  231. package/node_modules/@code-yeongyu/senpi-codemode/src/tool/detached-cell-notification.ts +4 -5
  232. package/node_modules/@code-yeongyu/senpi-codemode/src/tool/detached-cell-snapshot.ts +2 -0
  233. package/node_modules/@code-yeongyu/senpi-codemode/src/tool/detached-cell-status.ts +30 -0
  234. package/node_modules/@code-yeongyu/senpi-codemode/src/tool/detached-eval-result.ts +1 -0
  235. package/node_modules/@code-yeongyu/senpi-codemode/src/tool/detached-notification-queue.ts +2 -2
  236. package/node_modules/@code-yeongyu/senpi-codemode/src/tool/image.ts +12 -3
  237. package/node_modules/@code-yeongyu/senpi-codemode/src/tool/interrupt-note.ts +29 -15
  238. package/node_modules/@code-yeongyu/senpi-codemode/src/tool/types.ts +2 -0
  239. package/node_modules/@earendil-works/pi-agent-core/dist/agent.d.ts.map +1 -1
  240. package/node_modules/@earendil-works/pi-agent-core/dist/agent.js +3 -1
  241. package/node_modules/@earendil-works/pi-agent-core/dist/agent.js.map +1 -1
  242. package/node_modules/@earendil-works/pi-agent-core/dist/harness/agent-harness.d.ts.map +1 -1
  243. package/node_modules/@earendil-works/pi-agent-core/dist/harness/agent-harness.js +17 -0
  244. package/node_modules/@earendil-works/pi-agent-core/dist/harness/agent-harness.js.map +1 -1
  245. package/node_modules/@earendil-works/pi-agent-core/dist/harness/compaction/branch-summarization.d.ts.map +1 -1
  246. package/node_modules/@earendil-works/pi-agent-core/dist/harness/compaction/branch-summarization.js +1 -0
  247. package/node_modules/@earendil-works/pi-agent-core/dist/harness/compaction/branch-summarization.js.map +1 -1
  248. package/node_modules/@earendil-works/pi-agent-core/dist/harness/compaction/compaction.d.ts.map +1 -1
  249. package/node_modules/@earendil-works/pi-agent-core/dist/harness/compaction/compaction.js +1 -0
  250. package/node_modules/@earendil-works/pi-agent-core/dist/harness/compaction/compaction.js.map +1 -1
  251. package/node_modules/@earendil-works/pi-agent-core/dist/harness/reducer.d.ts.map +1 -1
  252. package/node_modules/@earendil-works/pi-agent-core/dist/harness/reducer.js +6 -0
  253. package/node_modules/@earendil-works/pi-agent-core/dist/harness/reducer.js.map +1 -1
  254. package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/context.d.ts +2 -1
  255. package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/context.d.ts.map +1 -1
  256. package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/context.js +5 -1
  257. package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/context.js.map +1 -1
  258. package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/jsonl/codec.d.ts.map +1 -1
  259. package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/jsonl/codec.js +1 -0
  260. package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/jsonl/codec.js.map +1 -1
  261. package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/types.d.ts +7 -1
  262. package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/types.d.ts.map +1 -1
  263. package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/types.js.map +1 -1
  264. package/node_modules/@earendil-works/pi-agent-core/dist/types.d.ts +2 -0
  265. package/node_modules/@earendil-works/pi-agent-core/dist/types.d.ts.map +1 -1
  266. package/node_modules/@earendil-works/pi-agent-core/dist/types.js.map +1 -1
  267. package/node_modules/@earendil-works/pi-agent-core/package.json +3 -3
  268. package/node_modules/@earendil-works/pi-ai/dist/api/mistral-conversations.js +2 -0
  269. package/node_modules/@earendil-works/pi-ai/dist/api/mistral-conversations.js.map +1 -1
  270. package/node_modules/@earendil-works/pi-ai/dist/api/openai-responses-shared.d.ts.map +1 -1
  271. package/node_modules/@earendil-works/pi-ai/dist/api/openai-responses-shared.js +15 -0
  272. package/node_modules/@earendil-works/pi-ai/dist/api/openai-responses-shared.js.map +1 -1
  273. package/node_modules/@earendil-works/pi-ai/dist/image-models.generated.js +4 -4
  274. package/node_modules/@earendil-works/pi-ai/dist/image-models.generated.js.map +1 -1
  275. package/node_modules/@earendil-works/pi-ai/dist/providers/data/.manifest.json +1 -1
  276. package/node_modules/@earendil-works/pi-ai/dist/providers/data/amazon-bedrock.json +1 -1
  277. package/node_modules/@earendil-works/pi-ai/dist/providers/data/azure-openai-responses.json +1 -1
  278. package/node_modules/@earendil-works/pi-ai/dist/providers/data/cerebras.json +1 -1
  279. package/node_modules/@earendil-works/pi-ai/dist/providers/data/cloudflare-ai-gateway.json +1 -1
  280. package/node_modules/@earendil-works/pi-ai/dist/providers/data/github-copilot.json +1 -1
  281. package/node_modules/@earendil-works/pi-ai/dist/providers/data/moonshotai-cn.json +1 -1
  282. package/node_modules/@earendil-works/pi-ai/dist/providers/data/moonshotai.json +1 -1
  283. package/node_modules/@earendil-works/pi-ai/dist/providers/data/openai-codex.json +1 -1
  284. package/node_modules/@earendil-works/pi-ai/dist/providers/data/openai.json +1 -1
  285. package/node_modules/@earendil-works/pi-ai/dist/providers/data/opencode-go.json +1 -1
  286. package/node_modules/@earendil-works/pi-ai/dist/providers/data/opencode.json +1 -1
  287. package/node_modules/@earendil-works/pi-ai/dist/providers/data/opengateway.json +1 -1
  288. package/node_modules/@earendil-works/pi-ai/dist/providers/data/openrouter.json +1 -1
  289. package/node_modules/@earendil-works/pi-ai/dist/providers/data/vercel-ai-gateway.json +1 -1
  290. package/node_modules/@earendil-works/pi-ai/dist/providers/faux.d.ts.map +1 -1
  291. package/node_modules/@earendil-works/pi-ai/dist/providers/faux.js +2 -0
  292. package/node_modules/@earendil-works/pi-ai/dist/providers/faux.js.map +1 -1
  293. package/node_modules/@earendil-works/pi-ai/dist/types.d.ts +8 -1
  294. package/node_modules/@earendil-works/pi-ai/dist/types.d.ts.map +1 -1
  295. package/node_modules/@earendil-works/pi-ai/dist/types.js.map +1 -1
  296. package/node_modules/@earendil-works/pi-ai/dist/utils/estimate.d.ts.map +1 -1
  297. package/node_modules/@earendil-works/pi-ai/dist/utils/estimate.js +2 -0
  298. package/node_modules/@earendil-works/pi-ai/dist/utils/estimate.js.map +1 -1
  299. package/node_modules/@earendil-works/pi-ai/dist/utils/overflow.d.ts +1 -1
  300. package/node_modules/@earendil-works/pi-ai/dist/utils/overflow.d.ts.map +1 -1
  301. package/node_modules/@earendil-works/pi-ai/dist/utils/overflow.js +14 -3
  302. package/node_modules/@earendil-works/pi-ai/dist/utils/overflow.js.map +1 -1
  303. package/node_modules/@earendil-works/pi-ai/package.json +2 -2
  304. package/node_modules/@earendil-works/pi-pty/package.json +1 -1
  305. package/node_modules/@earendil-works/pi-telemetry/package.json +1 -1
  306. package/node_modules/@earendil-works/pi-tui/package.json +1 -1
  307. package/package.json +7 -7
@@ -9,3 +9,5 @@ export const RESERVED_SCHEMA_TOOL = "__schema__" as const;
9
9
  export const TIMEOUT_PAUSE_OP = "timeout-pause" as const;
10
10
  /** Canonical oh-my-pi eval-timeout resume operation. */
11
11
  export const TIMEOUT_RESUME_OP = "timeout-resume" as const;
12
+ /** Status op the JS worker emits the moment it receives `interrupt`; proves its event loop is not blocked. */
13
+ export const INTERRUPT_ACK_OP = "interrupt-ack" as const;
@@ -13,6 +13,7 @@ import {
13
13
  getInterpreterAvailability,
14
14
  type InterpreterAvailability,
15
15
  } from "../interpreters/detect.ts";
16
+ import { sessionEnvironmentFrom } from "../kernels/session-env.ts";
16
17
  import { resolveSessionArtifactsDir } from "../output/streaming-output.ts";
17
18
  import type { EnabledEvalLanguages, EvalLanguage, EvalRuntimes } from "../tool/types.ts";
18
19
  import { jsRuntimeInfo, runtimesFromAvailability } from "./runtime-info.ts";
@@ -66,11 +67,13 @@ export async function createRuntime(
66
67
  const executeTool = createExecuteTool(pi, activeTools);
67
68
  const create = options.createSessionManager ?? createCodemodeSessionManager;
68
69
  const sessionId = sessionIdFrom(event);
70
+ const sessionEnv = sessionEnvironmentFrom(ctx);
69
71
  const configuredPoolWidth = settings.parallelPoolWidth;
70
72
  const parallelPoolWidth = Number.isFinite(configuredPoolWidth) ? Math.max(1, Math.trunc(configuredPoolWidth)) : 1;
71
73
  const manager = await create({
72
74
  sessionId,
73
75
  cwd: ctx.cwd,
76
+ sessionEnv,
74
77
  settings,
75
78
  availability,
76
79
  artifactsDir: artifacts.dir,
@@ -11,6 +11,7 @@ import { JuliaKernel } from "../kernels/jl/kernel.ts";
11
11
  import { JavaScriptKernel } from "../kernels/js/context-manager.ts";
12
12
  import { PythonKernel } from "../kernels/py/kernel.ts";
13
13
  import { RubyKernel } from "../kernels/rb/kernel.ts";
14
+ import type { SessionEnvironment } from "../kernels/session-env.ts";
14
15
  import { marshalToolResult } from "../tool/image.ts";
15
16
  import type { EvalKernel, EvalKernelManager, EvalLanguage, ExecuteTool } from "../tool/types.ts";
16
17
 
@@ -40,6 +41,8 @@ export interface CreateCodemodeSessionManagerOptions {
40
41
  readonly localRoots?: Readonly<Record<string, string>>;
41
42
  /** Session-adjacent directory used for persisted eval artifacts. */
42
43
  readonly artifactsDir?: string;
44
+ /** Per-session PI_* values exposed to every kernel and the children it spawns. */
45
+ readonly sessionEnv?: SessionEnvironment;
43
46
  readonly executeTool: ExecuteTool;
44
47
  readonly listTools?: () => readonly EvalSchemaToolInfo[];
45
48
  readonly complete: (request: CompletionRequest, ctx: ExtensionContext) => Promise<CompletionResult>;
@@ -212,6 +215,7 @@ class DefaultCodemodeSessionManager implements CodemodeSessionManager {
212
215
  cwd: this.#options.cwd,
213
216
  parallelPoolWidth,
214
217
  onMessage,
218
+ ...(this.#options.sessionEnv ? { sessionEnv: this.#options.sessionEnv } : {}),
215
219
  ...(localRoots ? { localRoots: { ...localRoots } } : {}),
216
220
  ...(this.#options.artifactsDir ? { artifactsDir: this.#options.artifactsDir } : {}),
217
221
  });
@@ -230,6 +234,7 @@ class DefaultCodemodeSessionManager implements CodemodeSessionManager {
230
234
  interpreterPath: detected.path,
231
235
  sessionId: this.#options.sessionId,
232
236
  cwd: this.#options.cwd,
237
+ ...(this.#options.sessionEnv ? { sessionEnv: this.#options.sessionEnv } : {}),
233
238
  connection,
234
239
  onMessage,
235
240
  });
@@ -239,6 +244,7 @@ class DefaultCodemodeSessionManager implements CodemodeSessionManager {
239
244
  command: detected.path,
240
245
  sessionId: this.#options.sessionId,
241
246
  cwd: this.#options.cwd,
247
+ ...(this.#options.sessionEnv ? { sessionEnv: this.#options.sessionEnv } : {}),
242
248
  connection,
243
249
  onMessage,
244
250
  });
@@ -247,6 +253,7 @@ class DefaultCodemodeSessionManager implements CodemodeSessionManager {
247
253
  command: detected.path,
248
254
  sessionId: this.#options.sessionId,
249
255
  cwd: this.#options.cwd,
256
+ ...(this.#options.sessionEnv ? { sessionEnv: this.#options.sessionEnv } : {}),
250
257
  connection,
251
258
  onMessage,
252
259
  });
@@ -15,6 +15,7 @@ runner/prelude assets).
15
15
  | Ruby kernel | `rb/kernel.ts` + `rb/prelude.rb`, `rb/runner.rb` |
16
16
  | Julia kernel | `jl/kernel.ts` + `jl/prelude.jl`, `jl/runner.jl` |
17
17
  | Shared subprocess layer | `shared/subprocess-kernel.ts`, `subprocess-{contract,process,queue,run}.ts`, `runtime-asset.ts` |
18
+ | Session environment | `session-env.ts` (PI_* contract shared by all kernels; mirrors the core bash tool) |
18
19
 
19
20
  ## CONVENTIONS
20
21
 
@@ -29,6 +30,12 @@ runner/prelude assets).
29
30
  framed subprocesses through `shared/`.
30
31
  - Subprocess retirement/restart, worker recovery, timeout, and interrupt
31
32
  semantics live here, never in the tool layer.
33
+ - Every kernel exposes the active session's `PI_*` environment (`session-env.ts`):
34
+ inherited values are deleted before the session's values are applied, so any
35
+ child spawned from a cell sees the same session environment a bash-tool child
36
+ sees. The JS worker applies it at init (`worker-core.js`; shell capture pins
37
+ the env view under Bun because `delete process.env.X` does not unsetenv),
38
+ and py/rb/jl spawn with it merged into the interpreter environment.
32
39
 
33
40
  ## ANTI-PATTERNS
34
41
 
@@ -1,12 +1,15 @@
1
1
  import { join } from "node:path";
2
2
  import type { BridgeConnectionConfig, KernelToHostMessage } from "../../bridge/protocol.ts";
3
- import { type CodemodeRuntimeAssetEnvironment, resolveCodemodeRuntimeAsset } from "../shared/runtime-asset.ts";
3
+ import type { SessionEnvironment } from "../session-env.ts";
4
+ import { type CodemodeRuntimeAssetEnvironment, requireCodemodeRuntimeAsset } from "../shared/runtime-asset.ts";
4
5
  import { SubprocessKernel, type SubprocessSpawn } from "../shared/subprocess-kernel.ts";
5
6
 
6
7
  export interface JuliaKernelStartOptions {
7
8
  readonly cwd: string;
8
9
  readonly sessionId: string;
9
10
  readonly connection: BridgeConnectionConfig;
11
+ /** Per-session PI_* values merged into the interpreter environment at spawn. */
12
+ readonly sessionEnv?: SessionEnvironment;
10
13
  readonly command?: string;
11
14
  readonly spawn?: SubprocessSpawn;
12
15
  readonly onMessage?: (message: KernelToHostMessage) => void;
@@ -17,7 +20,7 @@ export interface JuliaRunnerPathOptions extends CodemodeRuntimeAssetEnvironment
17
20
  }
18
21
 
19
22
  export function resolveJuliaRunnerPath(options: JuliaRunnerPathOptions = {}): string {
20
- return resolveCodemodeRuntimeAsset(
23
+ return requireCodemodeRuntimeAsset(
21
24
  options.localPath ?? join(import.meta.dirname, "runner.jl"),
22
25
  join("kernels", "jl", "runner.jl"),
23
26
  options,
@@ -42,6 +45,7 @@ export class JuliaKernel extends SubprocessKernel {
42
45
  ],
43
46
  cwd: options.cwd,
44
47
  sessionId: options.sessionId,
48
+ sessionEnv: options.sessionEnv,
45
49
  connection: options.connection,
46
50
  spawn: options.spawn,
47
51
  onMessage: options.onMessage,
@@ -1,9 +1,7 @@
1
- import { dirname, join } from "node:path";
2
- import { fileURLToPath, pathToFileURL } from "node:url";
3
1
  import type { HostToKernelMessage, KernelToHostMessage } from "../../bridge/protocol.ts";
2
+ import { INTERRUPT_ACK_OP } from "../../bridge/reserved.ts";
4
3
  import type { KernelInterruptHandle } from "../../tool/types.ts";
5
- import { type CodemodeRuntimeAssetEnvironment, resolveCodemodeRuntimeAsset } from "../shared/runtime-asset.ts";
6
- import { createInlineWorker, type WorkerLike } from "./inline-worker.ts";
4
+ import { abandonedWorkerNote, awaitCooperativeSettlement, type WorkerRetirement } from "./interrupt-bounds.ts";
7
5
  import {
8
6
  assertJavaScriptKernelOpen,
9
7
  type JavaScriptKernelMode,
@@ -12,31 +10,20 @@ import {
12
10
  type ResultMessage,
13
11
  type ToolCallMessage,
14
12
  } from "./kernel-contract.ts";
15
- import { type JavaScriptKernelOptions, LocalModuleLoader, localBridgeConnection } from "./local-module-loader.ts";
13
+ import { type JavaScriptKernelOptions, LocalModuleLoader } from "./local-module-loader.ts";
16
14
  import { JavaScriptRunQueue, type PendingJavaScriptRun, stoppedResult } from "./run-queue.ts";
17
- import { bridgeError, spawnNodeWorker, WorkerStartupCancelledError, waitForReady } from "./worker-host.ts";
15
+ import { bridgeError, WorkerStartupCancelledError } from "./worker-host.ts";
16
+ import { WorkerSlot } from "./worker-slot.ts";
18
17
 
19
18
  export { JavaScriptKernelClosedError, type JavaScriptKernelMode, type JavaScriptRunInput } from "./kernel-contract.ts";
20
19
  export type { JavaScriptKernelOptions } from "./local-module-loader.ts";
21
-
22
- export interface JavaScriptWorkerEntryUrlOptions extends CodemodeRuntimeAssetEnvironment {
23
- readonly localPath?: string;
24
- }
25
-
26
- export function resolveJsWorkerEntryUrl(options: JavaScriptWorkerEntryUrlOptions = {}): URL {
27
- const localPath = options.localPath ?? join(dirname(fileURLToPath(import.meta.url)), "worker-entry.js");
28
- return pathToFileURL(resolveCodemodeRuntimeAsset(localPath, join("kernels", "js", "worker-entry.js"), options));
29
- }
20
+ export { type JavaScriptWorkerEntryUrlOptions, resolveJsWorkerEntryUrl } from "./worker-startup.ts";
30
21
 
31
22
  export class JavaScriptKernel {
32
23
  readonly #options: JavaScriptKernelOptions;
33
24
  readonly #moduleLoader: LocalModuleLoader;
34
- #worker: WorkerLike | null = null;
35
- #mode: JavaScriptKernelMode = "worker";
25
+ readonly #slot: WorkerSlot;
36
26
  #lifecycle: LifecycleState = "open";
37
- #ready: Promise<void> | null = null;
38
- #startupAbort: AbortController | null = null;
39
- #generation = 0;
40
27
  #activation: Promise<void> | null = null;
41
28
  #recovery: Promise<void> | null = null;
42
29
  #closePromise: Promise<void> | null = null;
@@ -48,10 +35,15 @@ export class JavaScriptKernel {
48
35
  constructor(options: JavaScriptKernelOptions) {
49
36
  this.#options = options;
50
37
  this.#moduleLoader = new LocalModuleLoader(options);
38
+ this.#slot = new WorkerSlot(options, {
39
+ isOpen: () => this.#lifecycle === "open",
40
+ onMessage: (message) => this.#handleMessage(message),
41
+ onCrash: (error) => this.#handleCrash(error),
42
+ });
51
43
  }
52
44
 
53
45
  get mode(): JavaScriptKernelMode {
54
- return this.#mode;
46
+ return this.#slot.mode;
55
47
  }
56
48
 
57
49
  async run(input: JavaScriptRunInput): Promise<ResultMessage> {
@@ -64,13 +56,16 @@ export class JavaScriptKernel {
64
56
  async interrupt(reason = "interrupted"): Promise<KernelInterruptHandle> {
65
57
  assertJavaScriptKernelOpen(this.#lifecycle, "interrupt");
66
58
  const active = this.#runs.active;
67
- const target = this.#runs.takeInterruptTarget();
68
- if (!target) return { stateRetained: Promise.resolve(true) };
69
- if (target === active) this.#clearTimeout();
70
- this.#runs.settle(target, stoppedResult(target.input.cellId, `JS cell interrupted: ${reason}`));
71
- await this.#restartAfterStop();
72
- // A restart always replaces the worker VM, so no user global survives.
73
- return { stateRetained: Promise.resolve(false) };
59
+ if (!active) {
60
+ const queued = this.#runs.takeInterruptTarget();
61
+ if (!queued) return { stateRetained: Promise.resolve(true) };
62
+ this.#runs.settle(queued, stoppedResult(queued.input.cellId, `JS cell interrupted: ${reason}`));
63
+ await this.#restartAfterStop();
64
+ return { stateRetained: Promise.resolve(false) };
65
+ }
66
+ this.#clearTimeout();
67
+ const stop = await this.#stopActive(active, reason, `JS cell interrupted: ${reason}`);
68
+ return { stateRetained: Promise.resolve(stop.retained), ...(stop.note === undefined ? {} : { note: stop.note }) };
74
69
  }
75
70
 
76
71
  async reset(): Promise<void> {
@@ -82,7 +77,7 @@ export class JavaScriptKernel {
82
77
  }
83
78
 
84
79
  deliverToolReply(message: Extract<HostToKernelMessage, { type: "tool-reply" }>): void {
85
- if (this.#lifecycle === "open") this.#worker?.postMessage(message);
80
+ if (this.#lifecycle === "open") this.#slot.postMessage(message);
86
81
  }
87
82
 
88
83
  async nextToolCall(): Promise<ToolCallMessage> {
@@ -93,7 +88,7 @@ export class JavaScriptKernel {
93
88
 
94
89
  async close(): Promise<void> {
95
90
  if (this.#closePromise) return await this.#closePromise;
96
- this.#worker?.postMessage({ type: "close" });
91
+ this.#slot.postMessage({ type: "close" });
97
92
  this.#lifecycle = "closing";
98
93
  this.#runs.settleAll("JS kernel closed");
99
94
  const recovery = this.#recovery;
@@ -129,92 +124,17 @@ export class JavaScriptKernel {
129
124
 
130
125
  async #ensureReady(): Promise<void> {
131
126
  assertJavaScriptKernelOpen(this.#lifecycle, "run");
132
- if (!this.#ready) {
133
- const generation = ++this.#generation;
134
- const controller = new AbortController();
135
- this.#startupAbort = controller;
136
- const ready = this.#startWorker(generation, controller.signal);
137
- this.#ready = ready;
138
- void ready.then(
139
- () => {
140
- if (this.#ready === ready) this.#startupAbort = null;
141
- },
142
- () => {
143
- if (this.#ready === ready) {
144
- this.#ready = null;
145
- this.#startupAbort = null;
146
- }
147
- },
148
- );
149
- }
150
- return await this.#ready;
151
- }
152
-
153
- async #startWorker(generation: number, signal: AbortSignal): Promise<void> {
154
- let worker = this.#spawnWorker();
155
- this.#publishWorker(worker, generation);
156
- try {
157
- await this.#initializeWorker(worker, signal);
158
- return;
159
- } catch (error) {
160
- if (!this.#isCurrent(worker, generation) || error instanceof WorkerStartupCancelledError) {
161
- await worker.terminate();
162
- throw new WorkerStartupCancelledError();
163
- }
164
- if (worker.mode === "inline") throw error;
165
- this.#worker = null;
166
- await worker.terminate();
167
- }
168
- if (this.#lifecycle !== "open" || generation !== this.#generation) throw new WorkerStartupCancelledError();
169
- worker = createInlineWorker(this.#options.cwd, this.#options.parallelPoolWidth);
170
- this.#publishWorker(worker, generation);
171
- await this.#initializeWorker(worker, signal);
172
- }
173
-
174
- #spawnWorker(): WorkerLike {
175
- try {
176
- const url = this.#options.workerEntryUrl ?? resolveJsWorkerEntryUrl();
177
- return spawnNodeWorker(url, this.#options.cwd, this.#options.parallelPoolWidth);
178
- } catch (error) {
179
- if (!(error instanceof Error)) throw error;
180
- return createInlineWorker(this.#options.cwd, this.#options.parallelPoolWidth);
181
- }
182
- }
183
-
184
- #publishWorker(worker: WorkerLike, generation: number): void {
185
- if (this.#lifecycle !== "open" || generation !== this.#generation) throw new WorkerStartupCancelledError();
186
- this.#worker = worker;
187
- this.#mode = worker.mode;
188
- worker.onMessage((message) => {
189
- if (this.#isCurrent(worker, generation)) this.#handleMessage(message);
190
- });
191
- worker.onError((error) => {
192
- if (this.#isCurrent(worker, generation)) this.#handleCrash(error);
193
- });
194
- }
195
-
196
- async #initializeWorker(worker: WorkerLike, signal: AbortSignal): Promise<void> {
197
- const ready = waitForReady(worker, signal);
198
- worker.postMessage({
199
- type: "init",
200
- sessionId: this.#options.sessionId,
201
- connection: localBridgeConnection(this.#options),
202
- });
203
- await ready;
204
- }
205
-
206
- #isCurrent(worker: WorkerLike, generation: number): boolean {
207
- return this.#lifecycle === "open" && this.#worker === worker && this.#generation === generation;
127
+ await this.#slot.ensureReady();
208
128
  }
209
129
 
210
130
  #startNext(): void {
211
- if (this.#lifecycle !== "open" || this.#runs.active || !this.#worker) return;
131
+ if (this.#lifecycle !== "open" || this.#runs.active || !this.#slot.present) return;
212
132
  const next = this.#runs.startNext(performance.now());
213
133
  if (!next) return;
214
134
  if (next.input.timeoutMs) {
215
135
  this.#timeout = setTimeout(() => void this.#timeoutActive(next), next.input.timeoutMs);
216
136
  }
217
- this.#worker.postMessage({
137
+ this.#slot.postMessage({
218
138
  type: "run",
219
139
  cellId: next.input.cellId,
220
140
  code: this.#moduleLoader.prepareCell(next.input.code),
@@ -223,21 +143,46 @@ export class JavaScriptKernel {
223
143
  }
224
144
 
225
145
  async #timeoutActive(run: PendingJavaScriptRun): Promise<void> {
226
- if (!this.#runs.releaseActive(run)) return;
146
+ if (this.#runs.active !== run || run.settled) return;
227
147
  const durationMs = run.input.timeoutMs ?? 0;
228
- this.#runs.settle(run, {
229
- type: "result",
230
- cellId: run.input.cellId,
231
- ok: false,
232
- error: { message: `JS cell timed out after ${durationMs}ms` },
148
+ await this.#stopActive(
149
+ run,
150
+ `timed out after ${durationMs}ms`,
151
+ `JS cell timed out after ${durationMs}ms`,
233
152
  durationMs,
234
- });
235
- await this.#restartAfterStop();
153
+ );
154
+ }
155
+
156
+ /**
157
+ * Asks the worker to settle the active cell cooperatively (rejecting its bridge calls and killing its
158
+ * children); only a cell that stays unsettled past the grace costs the worker VM. Reports whether the
159
+ * worker state survived and, when a blocked worker had to be abandoned, the note that explains it.
160
+ */
161
+ async #stopActive(
162
+ run: PendingJavaScriptRun,
163
+ reason: string,
164
+ message: string,
165
+ durationMs = 0,
166
+ ): Promise<{ readonly retained: boolean; readonly note?: string }> {
167
+ run.interruptResult = { type: "result", cellId: run.input.cellId, ok: false, error: { message }, durationMs };
168
+ run.interruptAck ??= Promise.withResolvers<void>();
169
+ this.#slot.postMessage({ type: "interrupt", reason });
170
+ if ((await awaitCooperativeSettlement(run)) === "settled") return { retained: run.settledByWorker };
171
+ if (!this.#runs.releaseActive(run)) return { retained: run.settledByWorker };
172
+ const retirement = await this.#terminate();
173
+ this.#runs.settle(run, run.interruptResult ?? stoppedResult(run.input.cellId, message));
174
+ void this.#recover(() => Promise.resolve());
175
+ return retirement === "abandoned" ? { retained: false, note: abandonedWorkerNote() } : { retained: false };
236
176
  }
237
177
 
238
178
  async #restartAfterStop(): Promise<void> {
179
+ await this.#recover(() => this.#terminate());
180
+ }
181
+
182
+ /** One recovery at a time: retire through `retire` (a no-op when the worker is already gone), then bring a fresh worker up. */
183
+ async #recover(retire: () => Promise<unknown>): Promise<void> {
239
184
  if (this.#recovery) return await this.#recovery;
240
- const recovery = this.#performRestartAfterStop();
185
+ const recovery = this.#performRecovery(retire);
241
186
  this.#recovery = recovery;
242
187
  try {
243
188
  await recovery;
@@ -246,9 +191,9 @@ export class JavaScriptKernel {
246
191
  }
247
192
  }
248
193
 
249
- async #performRestartAfterStop(): Promise<void> {
194
+ async #performRecovery(retire: () => Promise<unknown>): Promise<void> {
250
195
  try {
251
- await this.#terminate();
196
+ await retire();
252
197
  if (this.#lifecycle !== "open") return;
253
198
  await this.#ensureReady();
254
199
  if (this.#lifecycle === "open") this.#startNext();
@@ -259,6 +204,10 @@ export class JavaScriptKernel {
259
204
  }
260
205
 
261
206
  #handleMessage(message: KernelToHostMessage): void {
207
+ if (message.type === "status" && message.event.op === INTERRUPT_ACK_OP) {
208
+ this.#runs.active?.interruptAck?.resolve();
209
+ return;
210
+ }
262
211
  this.#options.onMessage?.(message);
263
212
  this.#runs.active?.input.onMessage?.(message);
264
213
  if (message.type === "tool-call") {
@@ -272,13 +221,14 @@ export class JavaScriptKernel {
272
221
  if (!active || active.input.cellId !== message.cellId) return;
273
222
  this.#clearTimeout();
274
223
  this.#runs.releaseActive(active);
275
- this.#runs.settle(active, message);
224
+ active.settledByWorker = true;
225
+ this.#runs.settle(active, active.interruptResult ?? message);
276
226
  this.#startNext();
277
227
  }
278
228
 
279
229
  #handleCrash(error: Error): void {
280
230
  const active = this.#runs.active;
281
- if (!active && this.#startupAbort) return;
231
+ if (!active && this.#slot.startingUp) return;
282
232
  this.#clearTimeout();
283
233
  if (active) {
284
234
  this.#runs.releaseActive(active);
@@ -298,14 +248,8 @@ export class JavaScriptKernel {
298
248
  this.#timeout = null;
299
249
  }
300
250
 
301
- async #terminate(): Promise<void> {
251
+ async #terminate(): Promise<WorkerRetirement> {
302
252
  this.#clearTimeout();
303
- this.#generation += 1;
304
- this.#startupAbort?.abort();
305
- this.#startupAbort = null;
306
- this.#ready = null;
307
- const worker = this.#worker;
308
- this.#worker = null;
309
- if (worker) await worker.terminate();
253
+ return await this.#slot.retire();
310
254
  }
311
255
  }
@@ -1,7 +1,7 @@
1
1
  import { dirname, join } from "node:path";
2
2
  import { fileURLToPath, pathToFileURL } from "node:url";
3
3
  import type { HostToKernelMessage, KernelToHostMessage } from "../../bridge/protocol.ts";
4
- import { type CodemodeRuntimeAssetEnvironment, resolveCodemodeRuntimeAsset } from "../shared/runtime-asset.ts";
4
+ import { type CodemodeRuntimeAssetEnvironment, requireCodemodeRuntimeAsset } from "../shared/runtime-asset.ts";
5
5
  import type { JavaScriptKernelMode } from "./kernel-contract.ts";
6
6
  import { spawnNodeWorker } from "./worker-host.ts";
7
7
 
@@ -12,7 +12,7 @@ export interface JavaScriptInlineWorkerEntryUrlOptions extends CodemodeRuntimeAs
12
12
  export function resolveInlineWorkerEntryUrl(options: JavaScriptInlineWorkerEntryUrlOptions = {}): URL {
13
13
  const localPath = options.localPath ?? join(dirname(fileURLToPath(import.meta.url)), "inline-worker-entry.js");
14
14
  return pathToFileURL(
15
- resolveCodemodeRuntimeAsset(localPath, join("kernels", "js", "inline-worker-entry.js"), options),
15
+ requireCodemodeRuntimeAsset(localPath, join("kernels", "js", "inline-worker-entry.js"), options),
16
16
  );
17
17
  }
18
18
 
@@ -0,0 +1,66 @@
1
+ import type { WorkerLike } from "./inline-worker.ts";
2
+ import type { PendingJavaScriptRun } from "./run-queue.ts";
3
+
4
+ /** How long the worker gets to acknowledge `interrupt`; silence means its event loop is blocked in synchronous code. */
5
+ export const INTERRUPT_ACK_MS = 500;
6
+ /** How long an acknowledged cell gets to settle before the VM is replaced. */
7
+ export const JS_INTERRUPT_GRACE_MS = 2_000;
8
+ /** How long `worker.terminate()` may take before the worker is abandoned as blocked in a synchronous call. */
9
+ export const WORKER_TERMINATE_DEADLINE_MS = 3_000;
10
+
11
+ export type CooperativeSettlement = "settled" | "unresponsive";
12
+ export type WorkerRetirement = "terminated" | "abandoned";
13
+
14
+ export interface CooperativeSettlementBounds {
15
+ readonly ackMs: number;
16
+ readonly graceMs: number;
17
+ }
18
+
19
+ const DEFAULT_SETTLEMENT_BOUNDS: CooperativeSettlementBounds = {
20
+ ackMs: INTERRUPT_ACK_MS,
21
+ graceMs: JS_INTERRUPT_GRACE_MS,
22
+ };
23
+
24
+ export async function awaitCooperativeSettlement(
25
+ run: PendingJavaScriptRun,
26
+ bounds = DEFAULT_SETTLEMENT_BOUNDS,
27
+ ): Promise<CooperativeSettlement> {
28
+ if (run.settled) return "settled";
29
+ const settled = run.settlement.then((): "settled" => "settled");
30
+ const acked = run.interruptAck?.promise.then((): "acked" => "acked") ?? Promise.resolve<"acked">("acked");
31
+ const first = await raceDeadline(Promise.race([settled, acked]), bounds.ackMs, "unresponsive");
32
+ if (first !== "acked") return first;
33
+ return await raceDeadline(settled, bounds.graceMs, "unresponsive");
34
+ }
35
+
36
+ export async function retireWorker(
37
+ worker: WorkerLike,
38
+ deadlineMs = WORKER_TERMINATE_DEADLINE_MS,
39
+ ): Promise<WorkerRetirement> {
40
+ const termination = worker.terminate().then((): WorkerRetirement => "terminated");
41
+ const outcome = await raceDeadline(termination, deadlineMs, "abandoned");
42
+ if (outcome === "abandoned") void termination.then(undefined, ignoreLateTerminationFailure);
43
+ return outcome;
44
+ }
45
+
46
+ export function abandonedWorkerNote(deadlineMs = WORKER_TERMINATE_DEADLINE_MS): string {
47
+ return `JavaScript worker did not stop within ${deadlineMs}ms: a synchronous call (for example Bun.spawnSync or child_process.spawnSync) is blocking it. A fresh worker replaced it; the blocked call keeps running until it returns.\n`;
48
+ }
49
+
50
+ async function raceDeadline<T extends string, Fallback extends string>(
51
+ operation: Promise<T>,
52
+ deadlineMs: number,
53
+ fallback: Fallback,
54
+ ): Promise<T | Fallback> {
55
+ let timer: ReturnType<typeof setTimeout> | undefined;
56
+ const deadline = new Promise<Fallback>((resolve) => {
57
+ timer = setTimeout(() => resolve(fallback), deadlineMs);
58
+ });
59
+ try {
60
+ return await Promise.race([operation, deadline]);
61
+ } finally {
62
+ if (timer !== undefined) clearTimeout(timer);
63
+ }
64
+ }
65
+
66
+ function ignoreLateTerminationFailure(): void {}
@@ -1,4 +1,5 @@
1
1
  import type { KernelToHostMessage } from "../../bridge/protocol.ts";
2
+ import type { SessionEnvironment } from "../session-env.ts";
2
3
 
3
4
  export type ResultMessage = Extract<KernelToHostMessage, { type: "result" }>;
4
5
  export type ToolCallMessage = Extract<KernelToHostMessage, { type: "tool-call" }>;
@@ -11,6 +12,8 @@ export interface JavaScriptKernelOptions {
11
12
  readonly parallelPoolWidth: number;
12
13
  readonly onMessage?: (message: KernelToHostMessage) => void;
13
14
  readonly workerEntryUrl?: URL;
15
+ /** Per-session PI_* values applied to the worker environment before the first cell runs. */
16
+ readonly sessionEnv?: SessionEnvironment;
14
17
  }
15
18
 
16
19
  export interface JavaScriptRunInput {
@@ -7,8 +7,13 @@ export interface PendingJavaScriptRun {
7
7
  readonly input: JavaScriptRunInput;
8
8
  readonly resolve: (message: ResultMessage) => void;
9
9
  readonly reject: (error: Error) => void;
10
+ readonly settlement: Promise<ResultMessage>;
10
11
  startedAtMs: number | null;
11
12
  settled: boolean;
13
+ /** Host-composed result that wins over whatever the worker reports once an interrupt is in flight. */
14
+ interruptResult: ResultMessage | null;
15
+ interruptAck: PromiseWithResolvers<void> | null;
16
+ settledByWorker: boolean;
12
17
  }
13
18
 
14
19
  export class JavaScriptRunQueue {
@@ -24,9 +29,19 @@ export class JavaScriptRunQueue {
24
29
  }
25
30
 
26
31
  enqueue(input: JavaScriptRunInput): Promise<ResultMessage> {
27
- return new Promise((resolve, reject) => {
28
- this.#queue.push({ input, resolve, reject, startedAtMs: null, settled: false });
32
+ const { promise, resolve, reject } = Promise.withResolvers<ResultMessage>();
33
+ this.#queue.push({
34
+ input,
35
+ resolve,
36
+ reject,
37
+ settlement: promise,
38
+ startedAtMs: null,
39
+ settled: false,
40
+ interruptResult: null,
41
+ interruptAck: null,
42
+ settledByWorker: false,
29
43
  });
44
+ return promise;
30
45
  }
31
46
 
32
47
  startNext(startedAtMs: number): PendingJavaScriptRun | null {
@@ -64,7 +79,7 @@ export class JavaScriptRunQueue {
64
79
  settleAll(message: string): void {
65
80
  const active = this.#active;
66
81
  this.#active = null;
67
- if (active) this.settle(active, stoppedResult(active.input.cellId, message));
82
+ if (active) this.settle(active, active.interruptResult ?? stoppedResult(active.input.cellId, message));
68
83
  for (const queued of this.#queue.splice(0)) this.settle(queued, stoppedResult(queued.input.cellId, message));
69
84
  }
70
85