@code-yeongyu/senpi 2026.9.21-2 → 2026.9.22

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 (239) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/dist/bundle/chunks/{anthropic-messages-C3ZY2ANT.js → anthropic-messages-2P7APOOQ.js} +1 -1
  3. package/dist/bundle/chunks/{app-server-command-HSX5RFAP.js → app-server-command-DLBXJ5K5.js} +1 -1
  4. package/dist/bundle/chunks/{azure-openai-responses-7TAGS2VU.js → azure-openai-responses-3AH7MJWF.js} +1 -1
  5. package/dist/bundle/chunks/{chunk-3SLCHXZO.js → chunk-4AXZDO3E.js} +2 -2
  6. package/dist/bundle/chunks/{chunk-Z3KQB2R3.js → chunk-7ICH7UFW.js} +12 -12
  7. package/dist/bundle/chunks/{chunk-KNM6SRPE.js → chunk-AAVNXTGQ.js} +1 -1
  8. package/dist/bundle/chunks/{chunk-IDTLJYJ5.js → chunk-AY37SZJZ.js} +4 -4
  9. package/dist/bundle/chunks/{chunk-XHMOFY4T.js → chunk-BUPX33Q4.js} +3 -3
  10. package/dist/bundle/chunks/{chunk-GLKVDRNP.js → chunk-CWCY7LXK.js} +1 -1
  11. package/dist/bundle/chunks/{chunk-POMPEGWQ.js → chunk-F3DXL5MX.js} +1 -1
  12. package/dist/bundle/chunks/{chunk-GVI2HP7N.js → chunk-I2HIIJPI.js} +1 -1
  13. package/dist/bundle/chunks/{chunk-T6XA2IZR.js → chunk-JR4X6ECO.js} +1 -1
  14. package/dist/bundle/chunks/{chunk-M2XAY6I7.js → chunk-M2HSO7MH.js} +1 -1
  15. package/dist/bundle/chunks/{chunk-MIVOQT3Q.js → chunk-N5AQJAQ2.js} +1 -1
  16. package/dist/bundle/chunks/{chunk-UYVPLEOL.js → chunk-NK2RNX25.js} +1 -1
  17. package/dist/bundle/chunks/chunk-NMB4C6ZZ.js +2 -0
  18. package/dist/bundle/chunks/{chunk-B7W62BM7.js → chunk-P4OHEJ7P.js} +1 -1
  19. package/dist/bundle/chunks/{chunk-I425CMTO.js → chunk-VBNHBSBD.js} +1 -1
  20. package/dist/bundle/chunks/{chunk-B2IM56LB.js → chunk-VYIJQQ5I.js} +1 -1
  21. package/dist/bundle/chunks/chunk-XRHUMMIW.js +8 -0
  22. package/dist/bundle/chunks/{chunk-47GF6XYZ.js → chunk-YT2ZSZOY.js} +1 -1
  23. package/dist/bundle/chunks/{chunk-OSDJ2SYO.js → chunk-ZCBX2K34.js} +1 -1
  24. package/dist/bundle/chunks/{chunk-D5CL7PPD.js → chunk-ZMVXBX33.js} +2 -2
  25. package/dist/bundle/chunks/{cli-main-GQTEEZ2V.js → cli-main-D2VPNIQ3.js} +1 -1
  26. package/dist/bundle/chunks/{google-generative-ai-JWIH75VF.js → google-generative-ai-Y2IYMUVC.js} +1 -1
  27. package/dist/bundle/chunks/{google-vertex-IRGAY5XR.js → google-vertex-TKBBMPHV.js} +1 -1
  28. package/dist/bundle/chunks/{host-command-KOEKIHIZ.js → host-command-IFIIOX5S.js} +1 -1
  29. package/dist/bundle/chunks/host-lifecycle-3OJZA4QQ.js +3 -0
  30. package/dist/bundle/chunks/host-lifecycle.js +3 -3
  31. package/dist/bundle/chunks/{interactive-host-runtime-2KTFEW7O.js → interactive-host-runtime-V7PBDIOJ.js} +1 -1
  32. package/dist/bundle/chunks/{interactive-mode-I3ILWXQB.js → interactive-mode-RFMMGQCT.js} +1 -1
  33. package/dist/bundle/chunks/kimi-coding.js +2 -2
  34. package/dist/bundle/chunks/{mistral-conversations-VOBYATVJ.js → mistral-conversations-ZYDVFRYF.js} +1 -1
  35. package/dist/bundle/chunks/{multi-session-host-S5FWCIWE.js → multi-session-host-45YMCQIH.js} +8 -7
  36. package/dist/bundle/chunks/openai-codex-responses-MEIUNROM.js +10 -0
  37. package/dist/bundle/chunks/{openai-completions-YK6QTYOH.js → openai-completions-4V76L5ZJ.js} +1 -1
  38. package/dist/bundle/chunks/openai-responses-YR7WRBG3.js +2 -0
  39. package/dist/bundle/chunks/{package-manager-cli-HOL6CAI6.js → package-manager-cli-7DQKRSI5.js} +1 -1
  40. package/dist/bundle/chunks/rotation-stream-YPZP3VJS.js +2 -0
  41. package/dist/bundle/chunks/{rpc-mode-L3DSGTVM.js → rpc-mode-4GWZCJKI.js} +1 -1
  42. package/dist/bundle/chunks/{session-picker-L3FYEQBL.js → session-picker-ZKRYWZGC.js} +1 -1
  43. package/dist/bundle/chunks/session-worker.js +38 -37
  44. package/dist/bundle/cli.js +1 -1
  45. package/dist/bundle/index.js +1 -1
  46. package/dist/bundle/rpc-entry.js +1 -1
  47. package/dist/core/agent-session.d.ts +6 -4
  48. package/dist/core/agent-session.d.ts.map +1 -1
  49. package/dist/core/agent-session.js +9 -7
  50. package/dist/core/agent-session.js.map +1 -1
  51. package/dist/core/credential-pool/classify.d.ts.map +1 -1
  52. package/dist/core/credential-pool/classify.js +12 -0
  53. package/dist/core/credential-pool/classify.js.map +1 -1
  54. package/dist/core/credential-pool/failover.d.ts +37 -5
  55. package/dist/core/credential-pool/failover.d.ts.map +1 -1
  56. package/dist/core/credential-pool/failover.js +60 -24
  57. package/dist/core/credential-pool/failover.js.map +1 -1
  58. package/dist/core/credential-pool/rotation-events.d.ts +5 -0
  59. package/dist/core/credential-pool/rotation-events.d.ts.map +1 -0
  60. package/dist/core/credential-pool/rotation-events.js +25 -0
  61. package/dist/core/credential-pool/rotation-events.js.map +1 -0
  62. package/dist/core/credential-pool/rotation-stream.d.ts +4 -3
  63. package/dist/core/credential-pool/rotation-stream.d.ts.map +1 -1
  64. package/dist/core/credential-pool/rotation-stream.js +8 -11
  65. package/dist/core/credential-pool/rotation-stream.js.map +1 -1
  66. package/dist/core/extensions/builtin/ask-user/schema.d.ts +2 -25
  67. package/dist/core/extensions/builtin/ask-user/schema.d.ts.map +1 -1
  68. package/dist/core/extensions/builtin/ask-user/schema.js.map +1 -1
  69. package/dist/core/extensions/builtin/compaction/deterministic-fallback.d.ts +2 -7
  70. package/dist/core/extensions/builtin/compaction/deterministic-fallback.d.ts.map +1 -1
  71. package/dist/core/extensions/builtin/compaction/deterministic-fallback.js +2 -8
  72. package/dist/core/extensions/builtin/compaction/deterministic-fallback.js.map +1 -1
  73. package/dist/core/extensions/builtin/mcp/expose/naming.d.ts +0 -1
  74. package/dist/core/extensions/builtin/mcp/expose/naming.d.ts.map +1 -1
  75. package/dist/core/extensions/builtin/mcp/expose/naming.js +0 -3
  76. package/dist/core/extensions/builtin/mcp/expose/naming.js.map +1 -1
  77. package/dist/core/extensions/builtin/mcp/expose/register.d.ts +1 -2
  78. package/dist/core/extensions/builtin/mcp/expose/register.d.ts.map +1 -1
  79. package/dist/core/extensions/builtin/mcp/expose/register.js +0 -21
  80. package/dist/core/extensions/builtin/mcp/expose/register.js.map +1 -1
  81. package/dist/core/extensions/builtin/mcp/expose/schema-compat.d.ts +2 -2
  82. package/dist/core/extensions/builtin/mcp/expose/schema-compat.d.ts.map +1 -1
  83. package/dist/core/extensions/builtin/mcp/expose/schema-compat.js +2 -2
  84. package/dist/core/extensions/builtin/mcp/expose/schema-compat.js.map +1 -1
  85. package/dist/core/http-dispatcher.d.ts +29 -0
  86. package/dist/core/http-dispatcher.d.ts.map +1 -1
  87. package/dist/core/http-dispatcher.js +30 -3
  88. package/dist/core/http-dispatcher.js.map +1 -1
  89. package/dist/modes/interactive/components/assistant-render-descriptors.d.ts.map +1 -1
  90. package/dist/modes/interactive/components/assistant-render-descriptors.js +8 -7
  91. package/dist/modes/interactive/components/assistant-render-descriptors.js.map +1 -1
  92. package/dist/modes/print-mode.d.ts.map +1 -1
  93. package/dist/modes/print-mode.js +7 -5
  94. package/dist/modes/print-mode.js.map +1 -1
  95. package/dist/modes/rpc/handoff-activity.d.ts +4 -0
  96. package/dist/modes/rpc/handoff-activity.d.ts.map +1 -0
  97. package/dist/modes/rpc/handoff-activity.js +6 -0
  98. package/dist/modes/rpc/handoff-activity.js.map +1 -0
  99. package/dist/modes/rpc/host-ensure.d.ts.map +1 -1
  100. package/dist/modes/rpc/host-ensure.js +60 -17
  101. package/dist/modes/rpc/host-ensure.js.map +1 -1
  102. package/dist/modes/rpc/host-lifecycle.d.ts +3 -0
  103. package/dist/modes/rpc/host-lifecycle.d.ts.map +1 -1
  104. package/dist/modes/rpc/host-lifecycle.js +25 -5
  105. package/dist/modes/rpc/host-lifecycle.js.map +1 -1
  106. package/dist/modes/rpc/multi-session-host.d.ts +1 -0
  107. package/dist/modes/rpc/multi-session-host.d.ts.map +1 -1
  108. package/dist/modes/rpc/multi-session-host.js +40 -7
  109. package/dist/modes/rpc/multi-session-host.js.map +1 -1
  110. package/dist/modes/rpc/rpc-types.d.ts +13 -0
  111. package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
  112. package/dist/modes/rpc/rpc-types.js.map +1 -1
  113. package/dist/modes/rpc/session-command-router.d.ts +10 -4
  114. package/dist/modes/rpc/session-command-router.d.ts.map +1 -1
  115. package/dist/modes/rpc/session-command-router.js +90 -18
  116. package/dist/modes/rpc/session-command-router.js.map +1 -1
  117. package/dist/modes/rpc/session-event-writer.d.ts +5 -3
  118. package/dist/modes/rpc/session-event-writer.d.ts.map +1 -1
  119. package/dist/modes/rpc/session-event-writer.js +21 -2
  120. package/dist/modes/rpc/session-event-writer.js.map +1 -1
  121. package/dist/modes/rpc/session-worker-client.d.ts +1 -0
  122. package/dist/modes/rpc/session-worker-client.d.ts.map +1 -1
  123. package/dist/modes/rpc/session-worker-client.js +3 -0
  124. package/dist/modes/rpc/session-worker-client.js.map +1 -1
  125. package/dist/modes/rpc/session-worker-protocol.d.ts +3 -1
  126. package/dist/modes/rpc/session-worker-protocol.d.ts.map +1 -1
  127. package/dist/modes/rpc/session-worker-protocol.js.map +1 -1
  128. package/dist/modes/rpc/session-worker.js +7 -1
  129. package/dist/modes/rpc/session-worker.js.map +1 -1
  130. package/docs/providers.md +9 -1
  131. package/docs/rpc.md +58 -8
  132. package/node_modules/@code-yeongyu/senpi-codemode/CHANGELOG.md +15 -0
  133. package/node_modules/@code-yeongyu/senpi-codemode/README.md +51 -13
  134. package/node_modules/@code-yeongyu/senpi-codemode/package.json +4 -4
  135. package/node_modules/@code-yeongyu/senpi-codemode/src/bridges/agent-bridge.ts +53 -9
  136. package/node_modules/@code-yeongyu/senpi-codemode/src/bridges/reserved-dispatch.ts +1 -0
  137. package/node_modules/@code-yeongyu/senpi-codemode/src/config/settings.ts +18 -0
  138. package/node_modules/@code-yeongyu/senpi-codemode/src/extension/eval-status.ts +9 -4
  139. package/node_modules/@code-yeongyu/senpi-codemode/src/index.ts +3 -0
  140. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/AGENTS.md +2 -2
  141. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/jl/prelude.jl +10 -0
  142. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/context-manager.ts +18 -6
  143. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/kernel-contract.ts +2 -6
  144. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/prelude.ts +1 -1
  145. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/run-queue.ts +17 -7
  146. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/worker-runtime.js +6 -1
  147. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/py/kernel-contract.ts +2 -5
  148. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/py/kernel.ts +37 -5
  149. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/py/prelude.py +11 -2
  150. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/rb/prelude.rb +6 -0
  151. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/shared/subprocess-contract.ts +2 -5
  152. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/shared/subprocess-kernel.ts +14 -4
  153. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/shared/subprocess-queue.ts +18 -5
  154. package/node_modules/@code-yeongyu/senpi-codemode/src/prompt/eval-prompt-template.ts +3 -2
  155. package/node_modules/@code-yeongyu/senpi-codemode/src/prompt/eval-prompt.ts +4 -1
  156. package/node_modules/@code-yeongyu/senpi-codemode/src/timeouts/idle-timeout.ts +9 -2
  157. package/node_modules/@code-yeongyu/senpi-codemode/src/tool/AGENTS.md +2 -0
  158. package/node_modules/@code-yeongyu/senpi-codemode/src/tool/cell-execution.ts +19 -2
  159. package/node_modules/@code-yeongyu/senpi-codemode/src/tool/cell-runtime.ts +11 -3
  160. package/node_modules/@code-yeongyu/senpi-codemode/src/tool/detached-cell-contract.ts +6 -1
  161. package/node_modules/@code-yeongyu/senpi-codemode/src/tool/detached-cell-manager.ts +72 -20
  162. package/node_modules/@code-yeongyu/senpi-codemode/src/tool/detached-cell-snapshot.ts +28 -4
  163. package/node_modules/@code-yeongyu/senpi-codemode/src/tool/detached-cell-state.ts +5 -4
  164. package/node_modules/@code-yeongyu/senpi-codemode/src/tool/detached-cell-status.ts +5 -2
  165. package/node_modules/@code-yeongyu/senpi-codemode/src/tool/detached-eval-result.ts +76 -25
  166. package/node_modules/@code-yeongyu/senpi-codemode/src/tool/eval-execution-event.ts +4 -0
  167. package/node_modules/@code-yeongyu/senpi-codemode/src/tool/eval-kernel-reset-refused-error.ts +12 -0
  168. package/node_modules/@code-yeongyu/senpi-codemode/src/tool/eval-request.ts +2 -1
  169. package/node_modules/@code-yeongyu/senpi-codemode/src/tool/eval-tool-options.ts +3 -1
  170. package/node_modules/@code-yeongyu/senpi-codemode/src/tool/eval-tool.ts +58 -37
  171. package/node_modules/@code-yeongyu/senpi-codemode/src/tool/managed-cell.ts +15 -8
  172. package/node_modules/@code-yeongyu/senpi-codemode/src/tool/render.ts +20 -6
  173. package/node_modules/@code-yeongyu/senpi-codemode/src/tool/run-eval-cell.ts +89 -25
  174. package/node_modules/@code-yeongyu/senpi-codemode/src/tool/terminal-snapshot-store.ts +4 -0
  175. package/node_modules/@code-yeongyu/senpi-codemode/src/tool/types.ts +51 -10
  176. package/node_modules/@earendil-works/pi-agent-core/package.json +3 -3
  177. package/node_modules/@earendil-works/pi-ai/README.md +1 -1
  178. package/node_modules/@earendil-works/pi-ai/dist/api/openai-codex-responses.js +14 -56
  179. package/node_modules/@earendil-works/pi-ai/dist/api/openai-codex-responses.js.map +1 -1
  180. package/node_modules/@earendil-works/pi-ai/dist/api/openai-responses.d.ts.map +1 -1
  181. package/node_modules/@earendil-works/pi-ai/dist/api/openai-responses.js +18 -27
  182. package/node_modules/@earendil-works/pi-ai/dist/api/openai-responses.js.map +1 -1
  183. package/node_modules/@earendil-works/pi-ai/dist/api/websocket-transport-failure.d.ts +40 -0
  184. package/node_modules/@earendil-works/pi-ai/dist/api/websocket-transport-failure.d.ts.map +1 -0
  185. package/node_modules/@earendil-works/pi-ai/dist/api/websocket-transport-failure.js +102 -0
  186. package/node_modules/@earendil-works/pi-ai/dist/api/websocket-transport-failure.js.map +1 -0
  187. package/node_modules/@earendil-works/pi-ai/dist/auth/oauth/kimi-coding.d.ts +4 -3
  188. package/node_modules/@earendil-works/pi-ai/dist/auth/oauth/kimi-coding.d.ts.map +1 -1
  189. package/node_modules/@earendil-works/pi-ai/dist/auth/oauth/kimi-coding.js +28 -14
  190. package/node_modules/@earendil-works/pi-ai/dist/auth/oauth/kimi-coding.js.map +1 -1
  191. package/node_modules/@earendil-works/pi-ai/dist/auth/oauth/kimi-region.d.ts +44 -0
  192. package/node_modules/@earendil-works/pi-ai/dist/auth/oauth/kimi-region.d.ts.map +1 -0
  193. package/node_modules/@earendil-works/pi-ai/dist/auth/oauth/kimi-region.js +120 -0
  194. package/node_modules/@earendil-works/pi-ai/dist/auth/oauth/kimi-region.js.map +1 -0
  195. package/node_modules/@earendil-works/pi-ai/dist/auth/pool/failover.d.ts +2 -2
  196. package/node_modules/@earendil-works/pi-ai/dist/auth/pool/failover.d.ts.map +1 -1
  197. package/node_modules/@earendil-works/pi-ai/dist/auth/pool/failover.js +2 -2
  198. package/node_modules/@earendil-works/pi-ai/dist/auth/pool/failover.js.map +1 -1
  199. package/node_modules/@earendil-works/pi-ai/dist/auth/pool/slots.d.ts +3 -0
  200. package/node_modules/@earendil-works/pi-ai/dist/auth/pool/slots.d.ts.map +1 -1
  201. package/node_modules/@earendil-works/pi-ai/dist/auth/pool/slots.js +21 -15
  202. package/node_modules/@earendil-works/pi-ai/dist/auth/pool/slots.js.map +1 -1
  203. package/node_modules/@earendil-works/pi-ai/dist/index.d.ts +1 -0
  204. package/node_modules/@earendil-works/pi-ai/dist/index.d.ts.map +1 -1
  205. package/node_modules/@earendil-works/pi-ai/dist/index.js +1 -0
  206. package/node_modules/@earendil-works/pi-ai/dist/index.js.map +1 -1
  207. package/node_modules/@earendil-works/pi-ai/dist/providers/data/.manifest.json +1 -1
  208. package/node_modules/@earendil-works/pi-ai/dist/providers/data/cloudflare-ai-gateway.json +1 -1
  209. package/node_modules/@earendil-works/pi-ai/dist/providers/data/cloudflare-workers-ai.json +1 -1
  210. package/node_modules/@earendil-works/pi-ai/dist/providers/data/huggingface.json +1 -1
  211. package/node_modules/@earendil-works/pi-ai/dist/providers/data/opencode-go.json +1 -1
  212. package/node_modules/@earendil-works/pi-ai/dist/providers/data/openrouter.json +1 -1
  213. package/node_modules/@earendil-works/pi-ai/dist/providers/data/vercel-ai-gateway.json +1 -1
  214. package/node_modules/@earendil-works/pi-ai/dist/providers/kimi-coding-auth.d.ts +9 -0
  215. package/node_modules/@earendil-works/pi-ai/dist/providers/kimi-coding-auth.d.ts.map +1 -0
  216. package/node_modules/@earendil-works/pi-ai/dist/providers/kimi-coding-auth.js +43 -0
  217. package/node_modules/@earendil-works/pi-ai/dist/providers/kimi-coding-auth.js.map +1 -0
  218. package/node_modules/@earendil-works/pi-ai/dist/providers/kimi-coding.d.ts.map +1 -1
  219. package/node_modules/@earendil-works/pi-ai/dist/providers/kimi-coding.js +3 -2
  220. package/node_modules/@earendil-works/pi-ai/dist/providers/kimi-coding.js.map +1 -1
  221. package/node_modules/@earendil-works/pi-ai/dist/utils/provider-failure-description.d.ts +19 -0
  222. package/node_modules/@earendil-works/pi-ai/dist/utils/provider-failure-description.d.ts.map +1 -0
  223. package/node_modules/@earendil-works/pi-ai/dist/utils/provider-failure-description.js +63 -0
  224. package/node_modules/@earendil-works/pi-ai/dist/utils/provider-failure-description.js.map +1 -0
  225. package/node_modules/@earendil-works/pi-ai/dist/utils/retry.d.ts +1 -0
  226. package/node_modules/@earendil-works/pi-ai/dist/utils/retry.d.ts.map +1 -1
  227. package/node_modules/@earendil-works/pi-ai/dist/utils/retry.js +1 -1
  228. package/node_modules/@earendil-works/pi-ai/dist/utils/retry.js.map +1 -1
  229. package/node_modules/@earendil-works/pi-ai/package.json +2 -2
  230. package/node_modules/@earendil-works/pi-pty/package.json +1 -1
  231. package/node_modules/@earendil-works/pi-telemetry/package.json +1 -1
  232. package/node_modules/@earendil-works/pi-tui/package.json +1 -1
  233. package/package.json +7 -7
  234. package/dist/bundle/chunks/chunk-5J5R5AMP.js +0 -8
  235. package/dist/bundle/chunks/chunk-I3F6VZZF.js +0 -2
  236. package/dist/bundle/chunks/host-lifecycle-DGZJTBDJ.js +0 -3
  237. package/dist/bundle/chunks/openai-codex-responses-L2PO2D6F.js +0 -10
  238. package/dist/bundle/chunks/openai-responses-YDUBVF4S.js +0 -2
  239. package/dist/bundle/chunks/rotation-stream-Y4YU6HV4.js +0 -2
@@ -1,5 +1,9 @@
1
1
  import type { AgentToolResult } from "@code-yeongyu/senpi";
2
- import { DEFAULT_HARD_LIMIT_SECONDS, DEFAULT_RUN_BUDGET_SECONDS } from "../config/settings.ts";
2
+ import {
3
+ DEFAULT_HARD_LIMIT_SECONDS,
4
+ DEFAULT_MAX_DETACHED_CELLS,
5
+ DEFAULT_RUN_BUDGET_SECONDS,
6
+ } from "../config/settings.ts";
3
7
  import type { WakeSourceState } from "../extension/wake-source-state.ts";
4
8
  import type { CellDeadlineExpiry } from "./cell-deadlines.ts";
5
9
  import type {
@@ -33,12 +37,13 @@ export class EvalDetachedCellManager {
33
37
  readonly #onStatusChange: ((entries: readonly EvalDetachedCellStatusEntry[]) => void) | undefined;
34
38
  readonly #onWakeSourceState: ((state: WakeSourceState) => void) | undefined;
35
39
  readonly #cells = new Map<string, ManagedCell>();
36
- readonly #detachedByLanguage = new Map<EvalLanguage, ManagedCell>();
40
+ readonly #detached = new Map<string, ManagedCell>();
37
41
  readonly #terminalSnapshots = new TerminalSnapshotStore();
38
42
  readonly #notificationQueue: DetachedNotificationQueue;
39
43
  readonly #now: () => number;
40
44
  readonly #hardLimitSeconds: number;
41
45
  readonly #runBudgetSeconds: number;
46
+ readonly #maxDetachedCells: number;
42
47
 
43
48
  constructor(options: EvalDetachedCellManagerOptions = {}) {
44
49
  this.#artifactsDir = options.artifactsDir;
@@ -48,6 +53,11 @@ export class EvalDetachedCellManager {
48
53
  this.#now = options.now ?? Date.now;
49
54
  this.#hardLimitSeconds = options.hardLimitSeconds ?? DEFAULT_HARD_LIMIT_SECONDS;
50
55
  this.#runBudgetSeconds = options.runBudgetSeconds ?? DEFAULT_RUN_BUDGET_SECONDS;
56
+ this.#maxDetachedCells = options.maxDetachedCells ?? DEFAULT_MAX_DETACHED_CELLS;
57
+ }
58
+
59
+ get maxDetachedCells(): number {
60
+ return this.#maxDetachedCells;
51
61
  }
52
62
 
53
63
  create(cellId: string, input: EvalToolInput, onKill?: (error: Error) => void): ManagedCell {
@@ -74,19 +84,27 @@ export class EvalDetachedCellManager {
74
84
  return cell;
75
85
  }
76
86
 
77
- markRunning(
87
+ bindKernel(
78
88
  cell: ManagedCell,
79
89
  kernel: EvalKernel,
80
90
  liveResult: LiveResultProvider,
81
91
  onKill?: (error: Error) => void,
82
92
  ): void {
83
- if (cell.state !== "running") return;
93
+ if (!detachedCellIsActive(cell.state)) return;
84
94
  cell.onKill = onKill ?? cell.onKill;
85
95
  cell.kernel = kernel;
86
96
  cell.liveResult = liveResult;
87
97
  cell.canDetach = true;
88
98
  }
89
99
 
100
+ markRunning(cell: ManagedCell): void {
101
+ if (!allowsDetachedCellTransition(cell.state, "running")) return;
102
+ cell.state = "running";
103
+ cell.runStartedAtMs = this.#now();
104
+ cell.deadlines.resume();
105
+ if (cell.detached) this.#emitStatus();
106
+ }
107
+
90
108
  /** A host bridge call is in flight for this cell; its run budget stops charging until {@link resume}. */
91
109
  pause(cell: ManagedCell): void {
92
110
  cell.deadlines.pause();
@@ -99,19 +117,26 @@ export class EvalDetachedCellManager {
99
117
  detach(cell: ManagedCell): boolean {
100
118
  if (
101
119
  !cell.canDetach ||
102
- !allowsDetachedCellTransition(cell.state, "detached") ||
103
- this.#detachedByLanguage.has(cell.input.language)
120
+ !detachedCellIsActive(cell.state) ||
121
+ cell.detached ||
122
+ !(this.#detached.size < this.#maxDetachedCells)
104
123
  )
105
124
  return false;
106
- cell.state = "detached";
125
+ cell.detached = true;
107
126
  cell.wasDetached = true;
108
- this.#detachedByLanguage.set(cell.input.language, cell);
127
+ this.#detached.set(cell.cellId, cell);
109
128
  this.#emitStatus();
110
129
  return true;
111
130
  }
112
131
 
113
132
  complete(cell: ManagedCell, result: AgentToolResult<EvalToolDetails>): boolean {
114
- return this.#settle(cell, result.details.isError === true ? "failed" : "completed", result);
133
+ const state =
134
+ result.details.cells?.[0]?.status === "cancelled"
135
+ ? "cancelled"
136
+ : result.details.isError === true
137
+ ? "failed"
138
+ : "completed";
139
+ return this.#settle(cell, state, result);
115
140
  }
116
141
 
117
142
  fail(cell: ManagedCell, error: Error): boolean {
@@ -121,7 +146,8 @@ export class EvalDetachedCellManager {
121
146
  async stop(cellId: string, reason = "Stopped detached eval cell"): Promise<EvalDetachedCellSnapshot> {
122
147
  const live = this.#cells.get(cellId);
123
148
  if (live === undefined) return this.#terminal(cellId);
124
- if (live.state === "detached") await this.#cancel(live, reason);
149
+ if (live.state === "queued") this.#cancelQueued(live, reason);
150
+ else if (live.detached) await this.#cancel(live, reason);
125
151
  return this.#snapshot(live);
126
152
  }
127
153
 
@@ -130,9 +156,20 @@ export class EvalDetachedCellManager {
130
156
  return live === undefined ? this.#terminal(cellId) : this.#snapshot(live);
131
157
  }
132
158
 
133
- busyFor(language: EvalLanguage): EvalDetachedCellSnapshot | undefined {
134
- const cell = this.#detachedByLanguage.get(language);
135
- return cell === undefined ? undefined : this.#snapshot(cell);
159
+ liveCells(language?: EvalLanguage, opts?: { except?: string }): readonly EvalDetachedCellSnapshot[] {
160
+ return [...this.#cells.values()]
161
+ .filter(
162
+ (cell) =>
163
+ detachedCellIsActive(cell.state) &&
164
+ (language === undefined || cell.input.language === language) &&
165
+ cell.cellId !== opts?.except,
166
+ )
167
+ .sort((a, b) => a.startedAtMs - b.startedAtMs)
168
+ .map((cell) => this.#snapshot(cell));
169
+ }
170
+
171
+ list(): { live: readonly EvalDetachedCellSnapshot[]; recent: readonly EvalDetachedCellSnapshot[] } {
172
+ return { live: this.liveCells(), recent: this.#terminalSnapshots.list() };
136
173
  }
137
174
 
138
175
  async waitForTerminal(cellId: string): Promise<EvalDetachedCellSnapshot> {
@@ -141,7 +178,11 @@ export class EvalDetachedCellManager {
141
178
  }
142
179
 
143
180
  async dispose(): Promise<void> {
144
- const detached = [...this.#detachedByLanguage.values()];
181
+ const detached = [...this.#detached.values()];
182
+ // Dequeue first: interrupting an active run can synchronously start its next waiter.
183
+ for (const cell of detached) {
184
+ if (cell.state === "queued") this.#cancelQueued(cell, "Session ended; detached eval cell cancelled");
185
+ }
145
186
  await Promise.allSettled(
146
187
  detached.map(async (cell) => await this.stop(cell.cellId, "Session ended; detached eval cell cancelled")),
147
188
  );
@@ -157,7 +198,7 @@ export class EvalDetachedCellManager {
157
198
 
158
199
  /** Re-publish the current snapshot; consumers reset their per-source counts at session_start. */
159
200
  publishWakeSourceState(): void {
160
- this.#emitWakeSourceState([...this.#detachedByLanguage.values()]);
201
+ this.#emitWakeSourceState([...this.#detached.values()]);
161
202
  }
162
203
 
163
204
  #settle(
@@ -174,8 +215,7 @@ export class EvalDetachedCellManager {
174
215
  this.#cells.delete(cell.cellId);
175
216
  this.#refreshTerminalSnapshot(cell);
176
217
  if (cell.wasDetached) {
177
- if (this.#detachedByLanguage.get(cell.input.language) === cell)
178
- this.#detachedByLanguage.delete(cell.input.language);
218
+ this.#detached.delete(cell.cellId);
179
219
  this.#emitStatus();
180
220
  if (!cell.notificationQueued) {
181
221
  cell.notificationQueued = true;
@@ -197,9 +237,13 @@ export class EvalDetachedCellManager {
197
237
  */
198
238
  async #expireDeadline(cell: ManagedCell, expiry: CellDeadlineExpiry): Promise<void> {
199
239
  if (!detachedCellIsActive(cell.state)) return;
200
- const foreground = cell.state === "running" && cell.onKill !== undefined;
240
+ const foreground = !cell.detached && cell.onKill !== undefined;
201
241
  cell.hardLimited = expiry.kind === "hard-limit";
202
242
  cell.runBudgetExhausted = expiry.kind === "run-budget";
243
+ if (cell.state === "queued") {
244
+ this.#cancelQueued(cell, expiry.error.message, expiry.error);
245
+ return;
246
+ }
203
247
  if (foreground) {
204
248
  if (this.#settle(cell, "cancelled", currentDetachedResult(cell))) cell.onKill?.(expiry.error);
205
249
  return;
@@ -207,12 +251,20 @@ export class EvalDetachedCellManager {
207
251
  await this.#cancel(cell, expiry.error.message);
208
252
  }
209
253
 
254
+ #cancelQueued(cell: ManagedCell, reason: string, error = new Error(reason)): void {
255
+ const dequeued = cell.kernel?.cancelQueued(cell.cellId, reason) ?? false;
256
+ cell.stateRetained = true;
257
+ this.#settle(cell, "cancelled", currentDetachedResult(cell));
258
+ // Acquisition/reset has no queue promise yet; its execution has no interrupt target either.
259
+ if (!dequeued) cell.onKill?.(error);
260
+ }
261
+
210
262
  async #cancel(cell: ManagedCell, reason: string): Promise<void> {
211
263
  const outcome = Promise.withResolvers<void>();
212
264
  cell.interruptOutcome = outcome;
213
265
  try {
214
266
  if (!this.#settle(cell, "cancelled", currentDetachedResult(cell)) || cell.kernel === undefined) return;
215
- const handle = await cell.kernel.interrupt(reason);
267
+ const handle = await cell.kernel.interrupt(reason, cell.cellId);
216
268
  cell.interruptNote = handle.note;
217
269
  cell.stateRetained = await handle.stateRetained;
218
270
  } finally {
@@ -226,7 +278,7 @@ export class EvalDetachedCellManager {
226
278
  }
227
279
 
228
280
  #emitStatus(): void {
229
- const liveCells = [...this.#detachedByLanguage.values()];
281
+ const liveCells = [...this.#detached.values()];
230
282
  this.#onStatusChange?.(detachedStatusEntries(liveCells));
231
283
  this.#emitWakeSourceState(liveCells);
232
284
  }
@@ -1,12 +1,15 @@
1
1
  import type { AgentToolResult } from "@code-yeongyu/senpi";
2
2
  import type { EvalDetachedCellSnapshot, EvalDetachedCellState } from "./detached-cell-manager.ts";
3
3
  import { resultForDetachedState } from "./detached-eval-result.ts";
4
+ import { EvalKernelResetRefusedError } from "./eval-kernel-reset-refused-error.ts";
4
5
  import type { EvalKernel, EvalToolDetails, EvalToolInput } from "./types.ts";
5
6
 
6
7
  export interface DetachedCellResultSource {
7
8
  readonly cellId: string;
8
9
  readonly input: EvalToolInput;
9
10
  readonly startedAtMs: number;
11
+ readonly runStartedAtMs?: number | undefined;
12
+ readonly detached?: boolean;
10
13
  state: EvalDetachedCellState;
11
14
  kernel: EvalKernel | undefined;
12
15
  stateRetained: boolean | undefined;
@@ -20,12 +23,16 @@ export interface DetachedCellResultSource {
20
23
  }
21
24
 
22
25
  export function snapshotDetachedCell(cell: DetachedCellResultSource, nowMs: number): EvalDetachedCellSnapshot {
23
- const durationMs = Math.max(0, nowMs - cell.startedAtMs);
24
- const result = resultForDetachedState(currentDetachedResult(cell), cell.state, durationMs);
26
+ const durationMs = cell.runStartedAtMs === undefined ? 0 : Math.max(0, nowMs - cell.runStartedAtMs);
27
+ const state = cell.detached && (cell.state === "queued" || cell.state === "running") ? "detached" : cell.state;
28
+ const queuedBehind = queuedBehindCell(cell);
29
+ const result = resultForDetachedState(currentDetachedResult(cell), state, durationMs, queuedBehind);
25
30
  return {
26
31
  cellId: cell.cellId,
27
32
  language: cell.input.language,
28
- state: cell.state,
33
+ startedAtMs: cell.startedAtMs,
34
+ state,
35
+ ...(queuedBehind === undefined ? {} : { queuedBehind }),
29
36
  outputTail: detachedOutputTail(result),
30
37
  result,
31
38
  stateRetained: cell.stateRetained,
@@ -39,6 +46,19 @@ export function snapshotDetachedCell(cell: DetachedCellResultSource, nowMs: numb
39
46
  };
40
47
  }
41
48
 
49
+ export function queuedBehindCell(
50
+ cell: Pick<DetachedCellResultSource, "state" | "kernel" | "cellId">,
51
+ ): readonly string[] | undefined {
52
+ if (cell.state !== "queued") return undefined;
53
+ const queue = cell.kernel?.queueSnapshot();
54
+ if (queue === undefined) return [];
55
+ const index = queue.queuedCellIds.indexOf(cell.cellId);
56
+ return [
57
+ ...(queue.activeCellId === null || queue.activeCellId === cell.cellId ? [] : [queue.activeCellId]),
58
+ ...queue.queuedCellIds.slice(0, index < 0 ? undefined : index),
59
+ ];
60
+ }
61
+
42
62
  export function currentDetachedResult(cell: DetachedCellResultSource): AgentToolResult<EvalToolDetails> {
43
63
  return cell.terminalResult ?? cell.liveResult?.() ?? fallbackResult(cell.input);
44
64
  }
@@ -54,7 +74,11 @@ export function detachedErrorResult(cell: DetachedCellResultSource, error: Error
54
74
  },
55
75
  ...current.content.filter((part) => part.type === "image"),
56
76
  ],
57
- details: { ...current.details, isError: true },
77
+ details: {
78
+ ...current.details,
79
+ isError: true,
80
+ ...(error instanceof EvalKernelResetRefusedError ? { code: error.code } : {}),
81
+ },
58
82
  };
59
83
  }
60
84
 
@@ -5,20 +5,21 @@ interface DetachedCellIdentity {
5
5
  readonly cellId: string;
6
6
  readonly input: EvalToolInput;
7
7
  readonly state: EvalDetachedCellState;
8
+ readonly detached?: boolean;
8
9
  }
9
10
 
10
11
  export function activeDetachedCellReuseError(cell: DetachedCellIdentity): Error {
11
12
  return new Error(
12
- `Eval cell ${cell.cellId} from a previous call is still ${cell.state} in the ${cell.input.language} kernel. Use eval({ action: "peek", cell_id: "${cell.cellId}" }) to read it or eval({ action: "stop", cell_id: "${cell.cellId}" }) to end it before its id can be reused.`,
13
+ `Eval cell ${cell.cellId} from a previous call is still ${cell.detached ? "detached" : cell.state} in the ${cell.input.language} kernel. Use eval({ action: "peek", cell_id: "${cell.cellId}" }) to read it or eval({ action: "stop", cell_id: "${cell.cellId}" }) to end it before its id can be reused.`,
13
14
  );
14
15
  }
15
16
 
16
17
  export function allowsDetachedCellTransition(from: EvalDetachedCellState, to: EvalDetachedCellState): boolean {
17
- if (from === "running") return to === "detached" || to === "completed" || to === "failed" || to === "cancelled";
18
- if (from === "detached") return to === "completed" || to === "failed" || to === "cancelled";
18
+ if (from === "queued") return to === "running" || to === "failed" || to === "cancelled";
19
+ if (from === "running") return to === "completed" || to === "failed" || to === "cancelled";
19
20
  return false;
20
21
  }
21
22
 
22
23
  export function detachedCellIsActive(state: EvalDetachedCellState): boolean {
23
- return state === "running" || state === "detached";
24
+ return state === "queued" || state === "running" || state === "detached";
24
25
  }
@@ -1,5 +1,7 @@
1
1
  import { SENPI_CODEMODE_WAKE_SOURCE, type WakeSourceState } from "../extension/wake-source-state.ts";
2
2
  import type { EvalDetachedCellStatusEntry } from "./detached-cell-manager.ts";
3
+ import { queuedBehindCell } from "./detached-cell-snapshot.ts";
4
+ import type { ManagedCell } from "./managed-cell.ts";
3
5
 
4
6
  export interface LiveDetachedCell {
5
7
  readonly cellId: string;
@@ -7,11 +9,12 @@ export interface LiveDetachedCell {
7
9
  readonly input: { readonly language: EvalDetachedCellStatusEntry["language"]; readonly summary?: string };
8
10
  }
9
11
 
10
- export function detachedStatusEntries(liveCells: readonly LiveDetachedCell[]): EvalDetachedCellStatusEntry[] {
12
+ export function detachedStatusEntries(liveCells: readonly ManagedCell[]): EvalDetachedCellStatusEntry[] {
11
13
  return liveCells.map((cell) => ({
12
14
  cellId: cell.cellId,
13
15
  language: cell.input.language,
14
- startedAtMs: cell.startedAtMs,
16
+ startedAtMs: cell.runStartedAtMs ?? cell.startedAtMs,
17
+ ...(cell.state === "queued" ? { queuedBehind: queuedBehindCell(cell) } : {}),
15
18
  ...(cell.input.summary === undefined ? {} : { summary: cell.input.summary }),
16
19
  }));
17
20
  }
@@ -5,27 +5,87 @@ import type {
5
5
  EvalDetachedCellState,
6
6
  } from "./detached-cell-manager.ts";
7
7
  import { interruptionStateNote } from "./interrupt-note.ts";
8
- import type { EvalCellResult, EvalControlInput, EvalLanguage, EvalToolDetails, EvalToolInput } from "./types.ts";
8
+ import type {
9
+ EvalCellResult,
10
+ EvalControlInput,
11
+ EvalListDetails,
12
+ EvalListedCell,
13
+ EvalResultDetails,
14
+ EvalToolDetails,
15
+ EvalToolInput,
16
+ } from "./types.ts";
17
+
18
+ export class EvalBackgroundCapacityError extends Error {
19
+ readonly code = "eval_background_capacity_reached";
20
+ readonly name = "EvalBackgroundCapacityError";
21
+
22
+ constructor(cap: number, cellId: string, foregroundMs: number, liveCellIds: readonly string[]) {
23
+ super(
24
+ `Background capacity (${cap}) reached: cell ${cellId} ran ${Math.floor(foregroundMs / 1_000)}s in the foreground and was cancelled when the foreground window elapsed. Live cells: ${liveCellIds.join(", ")}. Stop one with eval({ action: "stop", cell_id }) or wait for a notification, then re-run this step.`,
25
+ );
26
+ }
27
+ }
9
28
 
10
29
  export async function executeEvalControl(
11
30
  cellManager: EvalDetachedCellManager,
12
31
  request: EvalControlInput,
13
- ): Promise<AgentToolResult<EvalToolDetails>> {
32
+ ): Promise<AgentToolResult<EvalResultDetails>> {
33
+ if (request.action === "list") return createEvalListResult(cellManager);
14
34
  const snapshot =
15
35
  request.action === "stop" ? await cellManager.stop(request.cell_id) : cellManager.peek(request.cell_id);
16
36
  return createDetachedControlResult(snapshot);
17
37
  }
18
38
 
39
+ function createEvalListResult(cellManager: EvalDetachedCellManager): AgentToolResult<EvalListDetails> {
40
+ const { live, recent } = cellManager.list();
41
+ const snapshots = [...live, ...recent];
42
+ const cells: EvalListedCell[] = snapshots.map((snapshot) => {
43
+ const summary = snapshot.result.details.summary;
44
+ return {
45
+ cellId: snapshot.cellId,
46
+ language: snapshot.language,
47
+ state: snapshot.state,
48
+ startedAtMs: snapshot.startedAtMs,
49
+ ...(snapshot.queuedBehind === undefined || snapshot.queuedBehind.length === 0
50
+ ? {}
51
+ : { queuedBehind: [...snapshot.queuedBehind] }),
52
+ ...(summary ? { summary } : {}),
53
+ };
54
+ });
55
+ const text = snapshots
56
+ .map((snapshot, index) => {
57
+ const cell = cells[index];
58
+ const preview = (cell.summary || snapshot.result.details.cells?.[0]?.code?.slice(0, 60) || "").replace(
59
+ /\s+/gu,
60
+ " ",
61
+ );
62
+ const elapsed = Math.floor(snapshot.result.details.durationMs / 1000);
63
+ const queued = cell.queuedBehind === undefined ? "" : ` queued behind ${cell.queuedBehind.join(", ")}`;
64
+ return `${cell.cellId} ${cell.language} ${cell.state} ${elapsed}s${queued} - ${preview}`;
65
+ })
66
+ .join("\n");
67
+ return {
68
+ content: [{ type: "text", text: text || "No eval cells are live; recent: none" }],
69
+ details: { action: "list", cells },
70
+ };
71
+ }
72
+
19
73
  export function resultAfterDetach(
20
74
  snapshot: EvalDetachedCellSnapshot,
21
75
  input: EvalToolInput,
76
+ otherLiveCells: number,
22
77
  ): AgentToolResult<EvalToolDetails> {
23
78
  if (snapshot.state !== "detached" && snapshot.state !== "running") return createDetachedControlResult(snapshot);
79
+ const predecessors = snapshot.queuedBehind?.join(", ");
80
+ const text =
81
+ predecessors === undefined
82
+ ? `Eval cell ${snapshot.cellId} detached and is running in the ${input.language} kernel (${otherLiveCells} other live cells). Completion arrives as a notification; do not re-run it. eval({ action: "peek" | "stop", cell_id }) or eval({ action: "list" }).`
83
+ : `Eval cell ${snapshot.cellId} is queued behind ${predecessors} in the ${input.language} kernel and detached; it runs after ${predecessors} and completes as one notification. peek/stop/list with eval({ action, cell_id })`;
24
84
  return {
25
85
  content: [
26
86
  {
27
87
  type: "text",
28
- text: `Eval cell ${snapshot.cellId} detached and is still running in the ${input.language} kernel. Completion will arrive as a notification. Use eval({ action: "peek", cell_id: "${snapshot.cellId}" }) or eval({ action: "stop", cell_id: "${snapshot.cellId}" }).`,
88
+ text,
29
89
  },
30
90
  ],
31
91
  details: snapshot.result.details,
@@ -55,21 +115,23 @@ export function resultForDetachedState(
55
115
  result: AgentToolResult<EvalToolDetails>,
56
116
  state: EvalDetachedCellState,
57
117
  durationMs: number,
118
+ queuedBehind?: readonly string[],
58
119
  ): AgentToolResult<EvalToolDetails> {
59
120
  const details = result.details;
60
121
  const cells = details.cells ?? [];
61
122
  const nextCells =
62
123
  cells.length === 0
63
124
  ? []
64
- : cells.map((cell, index) =>
65
- index === 0
66
- ? {
67
- ...cell,
68
- durationMs: terminalDuration(cell, state, durationMs),
69
- status: cellStatus(state),
70
- }
71
- : { ...cell },
72
- );
125
+ : cells.map((cell, index) => {
126
+ if (index !== 0) return { ...cell };
127
+ const { queuedBehind: _previousQueue, ...current } = cell;
128
+ return {
129
+ ...current,
130
+ durationMs: terminalDuration(cell, state, durationMs),
131
+ status: state === "detached" && queuedBehind !== undefined ? "queued" : cellStatus(state),
132
+ ...(queuedBehind === undefined ? {} : { queuedBehind }),
133
+ };
134
+ });
73
135
  return {
74
136
  content: result.content.map((part) => ({ ...part })),
75
137
  details: {
@@ -102,19 +164,6 @@ export function resultForDetachedState(
102
164
  };
103
165
  }
104
166
 
105
- export function detachedKernelBusyError(
106
- snapshot: EvalDetachedCellSnapshot,
107
- idleLanguages: readonly EvalLanguage[] = [],
108
- ): Error {
109
- const tail = snapshot.outputTail.length === 0 ? "(no output yet)" : snapshot.outputTail;
110
- const peek = `eval({ action: "peek", cell_id: "${snapshot.cellId}" })`;
111
- const idleHint =
112
- idleLanguages.length === 0 ? "" : ` or continue this step in an idle kernel: ${idleLanguages.join(", ")}`;
113
- return new Error(
114
- `The ${snapshot.language} eval kernel is busy running detached cell ${snapshot.cellId} - peek with ${peek}${idleHint}. Do not re-run the busy cell. Current output tail:\n${tail}`,
115
- );
116
- }
117
-
118
167
  function textContent(result: AgentToolResult<EvalToolDetails>): string {
119
168
  return result.content
120
169
  .filter((part) => part.type === "text")
@@ -133,6 +182,8 @@ function terminalDuration(
133
182
 
134
183
  function cellStatus(state: EvalDetachedCellState): EvalCellResult["status"] {
135
184
  switch (state) {
185
+ case "queued":
186
+ return "queued";
136
187
  case "running":
137
188
  return "running";
138
189
  case "detached":
@@ -30,6 +30,7 @@ interface EvalExecutionBasePayload {
30
30
  readonly completedAt: number;
31
31
  readonly durationMs: number;
32
32
  readonly kernelDurationMs?: number;
33
+ readonly queued_ms: number;
33
34
  readonly detached: boolean;
34
35
  readonly toolCallCount: number;
35
36
  readonly pendingToolCallCount: number;
@@ -66,6 +67,7 @@ export interface BuildEvalExecutionEventPayloadOptions {
66
67
  readonly state: CellState;
67
68
  readonly outcome: EvalExecutionSettleOutcome;
68
69
  readonly completedAt: number;
70
+ readonly queuedMs?: number;
69
71
  readonly detached: boolean;
70
72
  }
71
73
 
@@ -87,6 +89,7 @@ export function buildEvalExecutionEventPayload(
87
89
  startedAt: state.startedAt,
88
90
  completedAt,
89
91
  durationMs: Math.max(0, completedAt - state.startedAt),
92
+ queued_ms: options.queuedMs ?? 0,
90
93
  ...(result === undefined ? {} : { kernelDurationMs: result.details.durationMs }),
91
94
  detached,
92
95
  toolCallCount: state.toolCallMetrics.length,
@@ -110,6 +113,7 @@ export function toEvalExecutionRpcPayload(payload: EvalExecutionEventPayload): E
110
113
  startedAt: payload.startedAt,
111
114
  completedAt: payload.completedAt,
112
115
  durationMs: payload.durationMs,
116
+ queued_ms: payload.queued_ms,
113
117
  ...(payload.kernelDurationMs === undefined ? {} : { kernelDurationMs: payload.kernelDurationMs }),
114
118
  detached: payload.detached,
115
119
  toolCallCount: payload.toolCallCount,
@@ -0,0 +1,12 @@
1
+ import type { EvalLanguage } from "./types.ts";
2
+
3
+ export class EvalKernelResetRefusedError extends Error {
4
+ readonly name = "EvalKernelResetRefusedError";
5
+ readonly code = "eval_kernel_busy_reset_refused";
6
+
7
+ constructor(language: EvalLanguage, liveCellIds: readonly string[]) {
8
+ super(
9
+ `eval_kernel_busy_reset_refused: Cannot reset the ${language} kernel: live cells ${liveCellIds.join(", ")}. Stop them with eval({ action: "stop", cell_id }) or wait for their notifications, then reset.`,
10
+ );
11
+ }
12
+ }
@@ -18,6 +18,7 @@ export function clampEvalSummary(value: unknown): string | undefined {
18
18
 
19
19
  export function parseEvalRequest(params: unknown): EvalToolRequest {
20
20
  if (!isRecord(params)) throw new TypeError("eval parameters must be an object");
21
+ if (params.action === "list") return { action: "list" };
21
22
  if (params.action === "peek" || params.action === "stop") {
22
23
  if (typeof params.cell_id !== "string" || params.cell_id.length === 0)
23
24
  throw new TypeError(`eval action "${params.action}" requires cell_id`);
@@ -46,7 +47,7 @@ export function parseEvalRequest(params: unknown): EvalToolRequest {
46
47
  }
47
48
 
48
49
  export function isEvalControlRequest(request: EvalToolRequest): request is EvalControlInput {
49
- return request.action === "peek" || request.action === "stop";
50
+ return request.action === "peek" || request.action === "stop" || request.action === "list";
50
51
  }
51
52
 
52
53
  export function evalTimeoutBehavior(input: EvalToolInput, ctx: ExtensionContext): "detach" | "error" {
@@ -11,6 +11,7 @@ import type {
11
11
  EnabledEvalLanguages,
12
12
  EvalInputSchema,
13
13
  EvalKernelManager,
14
+ EvalResultDetails,
14
15
  EvalRuntimes,
15
16
  EvalToolDetails,
16
17
  EvalToolInput,
@@ -34,6 +35,7 @@ export interface CreateEvalToolOptions {
34
35
  * replaces it. Rendered into the tool schema and description; also seeds a self-created manager.
35
36
  */
36
37
  readonly runBudgetSeconds?: number;
38
+ readonly maxDetachedCells?: number;
37
39
  readonly executeTool: ExecuteTool;
38
40
  readonly listTools?: () => readonly EvalSchemaToolInfo[];
39
41
  readonly complete?: (request: CompletionRequest, ctx: ExtensionContext) => Promise<CompletionResult>;
@@ -45,7 +47,7 @@ export interface CreateEvalToolOptions {
45
47
  readonly onCellSettled?: (payload: EvalExecutionEventPayload) => void;
46
48
  readonly timeoutFactory?: EvalTimeoutFactory;
47
49
  readonly proxyExecutor?: (params: EvalToolInput, signal?: AbortSignal) => Promise<AgentToolResult<EvalToolDetails>>;
48
- readonly renderers?: Pick<ToolDefinition<EvalInputSchema, EvalToolDetails>, "renderCall" | "renderResult">;
50
+ readonly renderers?: Pick<ToolDefinition<EvalInputSchema, EvalResultDetails>, "renderCall" | "renderResult">;
49
51
  readonly spawns?: boolean;
50
52
  /** Whether the session registry exposes the monitor tool through eval. */
51
53
  readonly monitor?: boolean;