@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,8 +1,13 @@
1
- import type { ToolDefinition } from "@code-yeongyu/senpi";
2
- import { DEFAULT_FOREGROUND_WINDOW_SECONDS } from "../config/settings.ts";
1
+ import type { AgentToolResult, ToolDefinition } from "@code-yeongyu/senpi";
2
+ import type { TUnsafe } from "typebox";
3
+ import {
4
+ DEFAULT_FOREGROUND_WINDOW_SECONDS,
5
+ defaultCodemodeSettings,
6
+ resolveMaxDetachedCells,
7
+ } from "../config/settings.ts";
3
8
  import { buildEvalPrompt } from "../prompt/eval-prompt.ts";
4
9
  import { EvalDetachedCellManager } from "./detached-cell-manager.ts";
5
- import { detachedKernelBusyError, executeEvalControl } from "./detached-eval-result.ts";
10
+ import { executeEvalControl } from "./detached-eval-result.ts";
6
11
  import { clampEvalSummary, isEvalControlRequest, parseEvalRequest } from "./eval-request.ts";
7
12
  import type { CreateEvalToolOptions } from "./eval-tool-options.ts";
8
13
  import { runEvalCell } from "./run-eval-cell.ts";
@@ -10,6 +15,9 @@ import {
10
15
  createEvalInputSchema,
11
16
  defaultEvalDeadlineSeconds,
12
17
  type EvalInputSchema,
18
+ type EvalListDetails,
19
+ type EvalListInput,
20
+ type EvalResultDetails,
13
21
  type EvalToolDetails,
14
22
  type EvalToolRequest,
15
23
  enabledLanguageList,
@@ -19,8 +27,16 @@ export type { EvalTimeoutFactory } from "./cell-execution.ts";
19
27
  export type { CreateEvalToolOptions } from "./eval-tool-options.ts";
20
28
  export type { EnabledEvalLanguages, EvalKernel, EvalKernelManager } from "./types.ts";
21
29
 
22
- export function createEvalTool(options: CreateEvalToolOptions): ToolDefinition<EvalInputSchema, EvalToolDetails> {
30
+ type EvalExecuteArgs<Request extends EvalToolRequest> = Parameters<
31
+ ToolDefinition<TUnsafe<Request>, EvalToolDetails>["execute"]
32
+ >;
33
+
34
+ export function createEvalTool(options: CreateEvalToolOptions) {
23
35
  const foregroundWindowSeconds = options.foregroundWindowSeconds ?? DEFAULT_FOREGROUND_WINDOW_SECONDS;
36
+ const maxDetachedCells =
37
+ options.cellManager?.maxDetachedCells ??
38
+ options.maxDetachedCells ??
39
+ resolveMaxDetachedCells(options.settings ?? defaultCodemodeSettings);
24
40
  const deadlines = {
25
41
  runBudgetSeconds: options.runBudgetSeconds ?? defaultEvalDeadlineSeconds.runBudgetSeconds,
26
42
  detachAfterSeconds: Math.min(options.cellTimeoutSeconds, foregroundWindowSeconds),
@@ -31,6 +47,7 @@ export function createEvalTool(options: CreateEvalToolOptions): ToolDefinition<E
31
47
  const prompt = buildEvalPrompt(options.enabledLanguages, {
32
48
  spawns: options.spawns ?? false,
33
49
  monitor: options.monitor,
50
+ maxDetachedCells,
34
51
  runBudgetSeconds: deadlines.runBudgetSeconds,
35
52
  ...(options.spawnDefaultAgent === undefined ? {} : { spawnDefaultAgent: options.spawnDefaultAgent }),
36
53
  ...(options.modelId === undefined ? {} : { modelId: options.modelId }),
@@ -42,10 +59,43 @@ export function createEvalTool(options: CreateEvalToolOptions): ToolDefinition<E
42
59
  const cellManager =
43
60
  options.cellManager ??
44
61
  new EvalDetachedCellManager({
62
+ maxDetachedCells,
45
63
  ...(options.artifactsDir === undefined ? {} : { artifactsDir: options.artifactsDir }),
46
64
  ...(options.hardLimitSeconds === undefined ? {} : { hardLimitSeconds: options.hardLimitSeconds }),
47
65
  ...(options.runBudgetSeconds === undefined ? {} : { runBudgetSeconds: options.runBudgetSeconds }),
48
66
  });
67
+ // Keep run/peek/stop results typed as execution details; list has no single language or output cell.
68
+ function execute(
69
+ ...args: EvalExecuteArgs<Exclude<EvalToolRequest, EvalListInput>>
70
+ ): Promise<AgentToolResult<EvalToolDetails>>;
71
+ function execute(...args: EvalExecuteArgs<EvalListInput>): Promise<AgentToolResult<EvalListDetails>>;
72
+ function execute(...args: EvalExecuteArgs<EvalToolRequest>): Promise<AgentToolResult<EvalResultDetails>>;
73
+ async function execute(
74
+ ...[toolCallId, params, signal, onUpdate, ctx]: EvalExecuteArgs<EvalToolRequest>
75
+ ): Promise<AgentToolResult<EvalResultDetails>> {
76
+ const request = parseEvalRequest(params);
77
+ if (isEvalControlRequest(request)) return await executeEvalControl(cellManager, request);
78
+ if (options.proxyExecutor) return await options.proxyExecutor(request, signal);
79
+ if (!languages.includes(request.language))
80
+ throw new RangeError(
81
+ `Unsupported eval language "${request.language}". Enabled languages: ${languages.join(", ")}`,
82
+ );
83
+ options.executionTracker?.assertEvalExecutionAllowed();
84
+ const lifecycleController = new AbortController();
85
+ const combinedSignal = signal
86
+ ? AbortSignal.any([signal, lifecycleController.signal])
87
+ : lifecycleController.signal;
88
+ const execution = runEvalCell(options, cellManager, {
89
+ cellId: toolCallId,
90
+ input: request,
91
+ signal: combinedSignal,
92
+ onUpdate,
93
+ ctx,
94
+ });
95
+ return options.executionTracker
96
+ ? await options.executionTracker.trackEvalExecution(execution, lifecycleController)
97
+ : await execution;
98
+ }
49
99
  return {
50
100
  name: "eval",
51
101
  label: "Eval",
@@ -57,7 +107,8 @@ export function createEvalTool(options: CreateEvalToolOptions): ToolDefinition<E
57
107
  prepareArguments: (args) => {
58
108
  if (typeof args !== "object" || args === null) return args as EvalToolRequest;
59
109
  const record = args as Record<string, unknown>;
60
- if (record.action === "peek" || record.action === "stop") return args as EvalToolRequest;
110
+ if (record.action === "peek" || record.action === "stop" || record.action === "list")
111
+ return args as EvalToolRequest;
61
112
  const summary = clampEvalSummary(record.summary);
62
113
  if (summary === undefined) delete record.summary;
63
114
  else record.summary = summary;
@@ -65,36 +116,6 @@ export function createEvalTool(options: CreateEvalToolOptions): ToolDefinition<E
65
116
  },
66
117
  ...(options.renderers?.renderCall === undefined ? {} : { renderCall: options.renderers.renderCall }),
67
118
  ...(options.renderers?.renderResult === undefined ? {} : { renderResult: options.renderers.renderResult }),
68
- async execute(toolCallId, params, signal, onUpdate, ctx) {
69
- const request = parseEvalRequest(params);
70
- if (isEvalControlRequest(request)) return await executeEvalControl(cellManager, request);
71
- if (options.proxyExecutor) return await options.proxyExecutor(request, signal);
72
- if (!languages.includes(request.language))
73
- throw new RangeError(
74
- `Unsupported eval language "${request.language}". Enabled languages: ${languages.join(", ")}`,
75
- );
76
- const busy = cellManager.busyFor(request.language);
77
- if (busy !== undefined) {
78
- const idleLanguages = languages.filter(
79
- (language) => language !== request.language && cellManager.busyFor(language) === undefined,
80
- );
81
- throw detachedKernelBusyError(busy, idleLanguages);
82
- }
83
- options.executionTracker?.assertEvalExecutionAllowed();
84
- const lifecycleController = new AbortController();
85
- const combinedSignal = signal
86
- ? AbortSignal.any([signal, lifecycleController.signal])
87
- : lifecycleController.signal;
88
- const execution = runEvalCell(options, cellManager, {
89
- cellId: toolCallId,
90
- input: request,
91
- signal: combinedSignal,
92
- onUpdate,
93
- ctx,
94
- });
95
- return options.executionTracker
96
- ? await options.executionTracker.trackEvalExecution(execution, lifecycleController)
97
- : await execution;
98
- },
99
- };
119
+ execute,
120
+ } satisfies ToolDefinition<EvalInputSchema, EvalResultDetails>;
100
121
  }
@@ -12,7 +12,9 @@ export type ManagedCell = {
12
12
  readonly spillPath: string | undefined;
13
13
  readonly startedAtMs: number;
14
14
  readonly terminal: PromiseWithResolvers<EvalDetachedCellSnapshot>;
15
- state: EvalDetachedCellState;
15
+ state: Exclude<EvalDetachedCellState, "detached">;
16
+ runStartedAtMs: number | undefined;
17
+ detached: boolean;
16
18
  canDetach: boolean;
17
19
  wasDetached: boolean;
18
20
  kernel: EvalKernel | undefined;
@@ -47,12 +49,22 @@ export function createManagedCell(init: ManagedCellInit): ManagedCell {
47
49
  // An explicit longer per-call timeout raises the deadline, mirroring bash keeping explicit timeouts.
48
50
  const hardLimitSeconds = Math.max(init.defaultHardLimitSeconds, init.input.timeout ?? 0);
49
51
  const runBudgetSeconds = init.input.timeout ?? init.defaultRunBudgetSeconds;
52
+ const deadlines = new CellDeadlines({
53
+ cellId: init.cellId,
54
+ hardLimitSeconds,
55
+ runBudgetSeconds,
56
+ onExpire: (expiry) => init.onExpire(init.cellId, expiry),
57
+ });
58
+ // Queue time never consumes execution budget; the submission-time hard limit remains armed.
59
+ deadlines.pause();
50
60
  return {
51
61
  cellId: init.cellId,
52
62
  input: init.input,
53
63
  spillPath: detachedNotificationSpillPath(init.artifactsDir, init.cellId),
54
64
  startedAtMs: init.now(),
55
- state: "running",
65
+ state: "queued",
66
+ runStartedAtMs: undefined,
67
+ detached: false,
56
68
  canDetach: false,
57
69
  wasDetached: false,
58
70
  kernel: undefined,
@@ -62,12 +74,7 @@ export function createManagedCell(init: ManagedCellInit): ManagedCell {
62
74
  liveResult: undefined,
63
75
  terminalResult: undefined,
64
76
  notificationQueued: false,
65
- deadlines: new CellDeadlines({
66
- cellId: init.cellId,
67
- hardLimitSeconds,
68
- runBudgetSeconds,
69
- onExpire: (expiry) => init.onExpire(init.cellId, expiry),
70
- }),
77
+ deadlines,
71
78
  hardLimitSeconds,
72
79
  runBudgetSeconds,
73
80
  hardLimited: false,
@@ -25,13 +25,14 @@ import type {
25
25
  EvalCellResult,
26
26
  EvalInputSchema,
27
27
  EvalLanguage,
28
+ EvalResultDetails,
28
29
  EvalStatusEvent,
29
30
  EvalToolDetails,
30
31
  EvalToolInput,
31
32
  EvalToolRequest,
32
33
  } from "./types.ts";
33
34
 
34
- type EvalToolDefinition = ToolDefinition<EvalInputSchema, EvalToolDetails>;
35
+ type EvalToolDefinition = ToolDefinition<EvalInputSchema, EvalResultDetails>;
35
36
  type RenderContext = Parameters<NonNullable<EvalToolDefinition["renderCall"]>>[2];
36
37
  type ResultRenderContext = Parameters<NonNullable<EvalToolDefinition["renderResult"]>>[3];
37
38
  type CollapsibleKind = "code" | "output";
@@ -301,6 +302,8 @@ function cellPresentation(status: CellStatus, spinnerFrame: number | undefined):
301
302
  switch (status) {
302
303
  case "pending":
303
304
  return { label: "pending", icon: "○", color: "muted" };
305
+ case "queued":
306
+ return { label: "queued", icon: "○", color: "muted" };
304
307
  case "running":
305
308
  return { label: "running", icon: spinner(spinnerFrame), color: "warning" };
306
309
  case "detached":
@@ -337,6 +340,8 @@ function cellHeader(cell: EvalCellResult, environment: RenderEnvironment, badges
337
340
  const presentation = cellPresentation(cell.status, environment.spinnerFrame);
338
341
  const runtimeBadge = cell.runtime === undefined ? "" : ` (${formatRuntimeBadge(cell.language, cell.runtime)})`;
339
342
  let header = `eval ${cell.language}${runtimeBadge} ${presentation.label} ${presentation.icon}`;
343
+ if (cell.queuedBehind !== undefined && cell.queuedBehind.length > 0)
344
+ header += ` · queued behind ${cell.queuedBehind.map(sanitizeTerminalLabel).join(", ")}`;
340
345
  const throughputBadge = badges.throughput === undefined ? undefined : formatThroughputBadge(badges.throughput);
341
346
  if (throughputBadge !== undefined) header += ` · ${throughputBadge}`;
342
347
  const elapsedMs = badges.throughput?.wallDurationMs ?? cellElapsedMs(cell, environment);
@@ -706,7 +711,7 @@ function renderJsonOutputs(values: readonly unknown[], environment: RenderEnviro
706
711
 
707
712
  function renderDetailedLines(
708
713
  details: EvalToolDetails,
709
- result: AgentToolResult<EvalToolDetails>,
714
+ result: AgentToolResult<EvalResultDetails>,
710
715
  context: DetailedRenderContext,
711
716
  ): string[] {
712
717
  const lines: string[] = [];
@@ -765,7 +770,7 @@ function renderDetailedLines(
765
770
  return lines;
766
771
  }
767
772
 
768
- function textOutput(result: AgentToolResult<EvalToolDetails>, showImageFallback: boolean): string {
773
+ function textOutput(result: AgentToolResult<EvalResultDetails>, showImageFallback: boolean): string {
769
774
  const lines: string[] = [];
770
775
  for (const part of result.content) {
771
776
  if (part.type === "text") lines.push(part.text);
@@ -777,7 +782,7 @@ function textOutput(result: AgentToolResult<EvalToolDetails>, showImageFallback:
777
782
  }
778
783
 
779
784
  function isEvalRunInput(args: EvalToolRequest): args is EvalToolInput {
780
- return args.action !== "peek" && args.action !== "stop";
785
+ return args.action === undefined || args.action === "run";
781
786
  }
782
787
 
783
788
  function toolCallRows(details: EvalToolDetails | undefined): ToolCallRow[] {
@@ -891,7 +896,8 @@ export function renderEvalCall(
891
896
  return component;
892
897
  }
893
898
  if (!isEvalRunInput(args)) {
894
- component.setBlocks([{ kind: "text", text: style(theme, "toolTitle", `eval ${args.action} ${args.cell_id}`) }]);
899
+ const title = args.action === "list" ? "eval list" : `eval ${args.action} ${args.cell_id}`;
900
+ component.setBlocks([{ kind: "text", text: style(theme, "toolTitle", title) }]);
895
901
  return component;
896
902
  }
897
903
  if (theme === undefined && context.spinnerFrame === undefined) {
@@ -942,13 +948,21 @@ export function renderEvalCall(
942
948
  }
943
949
 
944
950
  export function renderEvalResult(
945
- result: AgentToolResult<EvalToolDetails>,
951
+ result: AgentToolResult<EvalResultDetails>,
946
952
  options: ToolRenderResultOptions,
947
953
  theme: Theme | undefined,
948
954
  context: ResultRenderContext,
949
955
  ): EvalRenderComponent {
950
956
  const component = componentFor(context);
951
957
  const details = result.details;
958
+ if (details && "action" in details) {
959
+ component.syncLiveTicker(false, context.invalidate);
960
+ component.setBlocks([
961
+ { kind: "text", text: style(theme, "toolTitle", "eval list") },
962
+ { kind: "text", text: style(theme, "toolOutput", textOutput(result, false)) },
963
+ ]);
964
+ return component;
965
+ }
952
966
  const expanded = options.expanded || context.expanded;
953
967
  const imageProtocol = context.imageProtocol ?? null;
954
968
  component.syncLiveTicker(hasLiveCell(details), context.invalidate);
@@ -6,6 +6,7 @@ import {
6
6
  type ExtensionKernelTools,
7
7
  kernelToolsStorage,
8
8
  } from "@code-yeongyu/senpi";
9
+ import type { KernelToHostMessage } from "../bridge/protocol.ts";
9
10
  import { DEFAULT_FOREGROUND_WINDOW_SECONDS, defaultCodemodeSettings } from "../config/settings.ts";
10
11
  import {
11
12
  KERNEL_TOOLS_CAPABILITIES,
@@ -16,8 +17,9 @@ import { TIMEOUT_PAUSE_OP, TIMEOUT_RESUME_OP } from "../timeouts/bridge-timeout.
16
17
  import { abortError, CellExecution, defaultTimeoutFactory } from "./cell-execution.ts";
17
18
  import { CellHandler, type CellState } from "./cell-handler.ts";
18
19
  import type { EvalDetachedCellManager } from "./detached-cell-manager.ts";
19
- import { resultAfterDetach } from "./detached-eval-result.ts";
20
+ import { EvalBackgroundCapacityError, resultAfterDetach, resultForDetachedState } from "./detached-eval-result.ts";
20
21
  import { buildEvalExecutionEventPayload, type EvalExecutionSettleOutcome } from "./eval-execution-event.ts";
22
+ import { EvalKernelResetRefusedError } from "./eval-kernel-reset-refused-error.ts";
21
23
  import { evalTimeoutBehavior } from "./eval-request.ts";
22
24
  import type { CreateEvalToolOptions, EvalCellInvocation } from "./eval-tool-options.ts";
23
25
  import { describeTimeoutState } from "./interrupt-note.ts";
@@ -54,7 +56,7 @@ export async function runEvalCell(
54
56
  phase: undefined,
55
57
  error: undefined,
56
58
  durationMs: 0,
57
- status: "pending",
59
+ status: "queued",
58
60
  };
59
61
  let detached = false;
60
62
  let execution: CellExecution;
@@ -65,6 +67,24 @@ export async function runEvalCell(
65
67
  execution.detach();
66
68
  return true;
67
69
  };
70
+ const cancelAtCapacity = (idleError: Error): void => {
71
+ const liveCells = cellManager.liveCells(undefined, { except: invocation.cellId });
72
+ if (
73
+ !cell.canDetach &&
74
+ liveCells.filter((live) => live.state === "detached").length < cellManager.maxDetachedCells
75
+ ) {
76
+ execution.cancel(idleError);
77
+ return;
78
+ }
79
+ execution.cancel(
80
+ new EvalBackgroundCapacityError(
81
+ cellManager.maxDetachedCells,
82
+ invocation.cellId,
83
+ Date.now() - state.startedAt,
84
+ liveCells.map((live) => live.cellId),
85
+ ),
86
+ );
87
+ };
68
88
  execution = new CellExecution({
69
89
  callerSignal: invocation.signal,
70
90
  cellId: invocation.cellId,
@@ -74,7 +94,13 @@ export async function runEvalCell(
74
94
  timeoutMs: detachAfterMs,
75
95
  maxPauseGraceMs: foregroundWindowMs,
76
96
  onTimeout: (error: Error) => {
77
- if (!detach()) execution.cancel(error);
97
+ if (detach()) return;
98
+ const remainingMs = foregroundWindowMs - (Date.now() - state.startedAt);
99
+ if (remainingMs <= 0) cancelAtCapacity(error);
100
+ else
101
+ execution.rearmIdle(remainingMs, () => {
102
+ if (!detach()) cancelAtCapacity(error);
103
+ });
78
104
  },
79
105
  },
80
106
  }
@@ -116,6 +142,7 @@ export async function runEvalCell(
116
142
  state,
117
143
  outcome,
118
144
  completedAt: Date.now(),
145
+ queuedMs: Math.max(0, (cell.runStartedAtMs ?? Date.now()) - cell.startedAtMs),
119
146
  detached,
120
147
  }),
121
148
  );
@@ -137,7 +164,12 @@ export async function runEvalCell(
137
164
  finalized.then((result) => ({ kind: "result" as const, result })),
138
165
  execution.detached.then(() => ({ kind: "detached" as const })),
139
166
  ]);
140
- if (outcome.kind === "detached") return resultAfterDetach(cellManager.peek(invocation.cellId), invocation.input);
167
+ if (outcome.kind === "detached")
168
+ return resultAfterDetach(
169
+ cellManager.peek(invocation.cellId),
170
+ invocation.input,
171
+ cellManager.liveCells(undefined, { except: invocation.cellId }).length,
172
+ );
141
173
  return outcome.result;
142
174
  } finally {
143
175
  steeringSignal?.removeEventListener("abort", onSteering);
@@ -157,30 +189,30 @@ async function executeCell(
157
189
  ): Promise<AgentToolResult<EvalToolDetails>> {
158
190
  let handler: CellHandler | undefined;
159
191
  try {
160
- const kernel = await execution.wait(
161
- options.kernelManager.getKernel(invocation.input.language, (message) => {
162
- if (!state.active || handler === undefined) return;
163
- if (message.type === "status") {
164
- if (message.event.op === TIMEOUT_PAUSE_OP) {
165
- execution.pause();
166
- cellManager.pause(cell);
167
- return;
168
- }
169
- if (message.event.op === TIMEOUT_RESUME_OP) {
170
- execution.resume();
171
- cellManager.resume(cell);
172
- return;
173
- }
192
+ const onMessage = (message: KernelToHostMessage): void => {
193
+ if (!state.active || handler === undefined) return;
194
+ if (message.type === "status") {
195
+ if (message.event.op === TIMEOUT_PAUSE_OP) {
196
+ execution.pause();
197
+ cellManager.pause(cell);
198
+ return;
174
199
  }
175
- const pending = handler.handle(message);
176
- void pending.catch((error: unknown) => execution.cancel(error));
177
- }),
178
- );
200
+ if (message.event.op === TIMEOUT_RESUME_OP) {
201
+ execution.resume();
202
+ cellManager.resume(cell);
203
+ return;
204
+ }
205
+ }
206
+ const pending = handler.handle(message);
207
+ void pending.catch((error: unknown) => execution.cancel(error));
208
+ };
209
+ const kernel = await execution.wait(options.kernelManager.getKernel(invocation.input.language, onMessage));
179
210
  // Computed before the handler so the cell's capability can be entered per host tool call from the
180
211
  // worker's message loop, which runs outside the `kernelToolsStorage.run` context below (#1754).
181
212
  const kernelTools = jsKernelTools(kernel, invocation.input.language);
182
213
  const runBound = async (): Promise<AgentToolResult<EvalToolDetails>> => {
183
- execution.setKernel(kernel);
214
+ const queue = kernel.queueSnapshot();
215
+ state.queuedBehind = [...(queue.activeCellId === null ? [] : [queue.activeCellId]), ...queue.queuedCellIds];
184
216
  const activeHandler = new CellHandler(kernel, state, {
185
217
  executeTool: options.executeTool,
186
218
  ...(options.listTools === undefined ? {} : { listTools: options.listTools }),
@@ -194,25 +226,57 @@ async function executeCell(
194
226
  ...(kernelTools === undefined ? {} : { kernelTools }),
195
227
  });
196
228
  handler = activeHandler;
197
- cellManager.markRunning(
229
+ cellManager.bindKernel(
198
230
  cell,
199
231
  kernel,
200
232
  () => activeHandler.liveResult(),
201
233
  (error) => execution.cancel(error),
202
234
  );
235
+ if (invocation.input.reset) {
236
+ const liveCells = cellManager.liveCells(invocation.input.language, { except: invocation.cellId });
237
+ if (liveCells.length > 0)
238
+ throw new EvalKernelResetRefusedError(
239
+ invocation.input.language,
240
+ liveCells.map((live) => live.cellId),
241
+ );
242
+ }
203
243
  // Includes a steer already queued at execute start or received while acquiring the kernel.
204
244
  onReady();
205
245
  if ("setContext" in options.kernelManager && typeof options.kernelManager.setContext === "function") {
206
246
  options.kernelManager.setContext(bridgeContext);
207
247
  }
208
248
  if (invocation.input.reset) await execution.wait(kernel.reset());
209
- const result = await execution.wait(kernel.run({ cellId: invocation.cellId, code: invocation.input.code }));
249
+ execution.setKernel(kernel);
250
+ const result = await execution.wait(
251
+ kernel.run({
252
+ cellId: invocation.cellId,
253
+ code: invocation.input.code,
254
+ onMessage,
255
+ onStarted: () => {
256
+ cellManager.markRunning(cell);
257
+ state.runStartedAt = cell.runStartedAtMs;
258
+ state.status = "running";
259
+ state.queuedBehind = undefined;
260
+ state.onUpdate?.(activeHandler.liveResult());
261
+ },
262
+ }),
263
+ );
210
264
  if (result.ok && state.pendingBridgeCalls.length > 0)
211
265
  await execution.wait(Promise.all(state.pendingBridgeCalls));
212
266
  return await handler.finalize(result);
213
267
  };
214
268
  return kernelTools ? await kernelToolsStorage.run(kernelTools, runBound) : await runBound();
215
269
  } catch (error) {
270
+ if (error instanceof EvalBackgroundCapacityError) {
271
+ const final = handler
272
+ ? await handler.finalizeCancellation(error)
273
+ : {
274
+ content: [{ type: "text" as const, text: error.message }],
275
+ details: cellManager.peek(invocation.cellId).result.details,
276
+ };
277
+ const result = resultForDetachedState(final, "cancelled", state.durationMs);
278
+ return { ...result, details: { ...result.details, isError: true, code: error.code } };
279
+ }
216
280
  if (handler && error instanceof Error && error.name === "CodemodeSessionDisposedError")
217
281
  return await handler.finalizeCancellation(error);
218
282
  if (error instanceof Error && error.name === "TimeoutError") throw await describeTimeoutState(error, execution);
@@ -29,6 +29,10 @@ export class TerminalSnapshotStore {
29
29
  this.#snapshots.delete(cellId);
30
30
  }
31
31
 
32
+ list(): readonly EvalDetachedCellSnapshot[] {
33
+ return [...this.#snapshots.values()];
34
+ }
35
+
32
36
  clear(): void {
33
37
  this.#snapshots.clear();
34
38
  }
@@ -54,18 +54,25 @@ export interface EvalToolInput {
54
54
  readonly reset?: boolean;
55
55
  }
56
56
 
57
- export interface EvalControlInput {
58
- readonly action: "peek" | "stop";
59
- readonly cell_id: string;
57
+ export interface EvalListInput {
58
+ readonly action: "list";
60
59
  }
61
60
 
61
+ export type EvalControlInput =
62
+ | EvalListInput
63
+ | {
64
+ readonly action: "peek" | "stop";
65
+ readonly cell_id: string;
66
+ };
67
+
62
68
  export type EvalToolRequest = EvalToolInput | EvalControlInput;
63
69
 
64
70
  function evalInputProperties<Language extends TSchema>(languageSchema: Language, deadlines: EvalDeadlineSeconds) {
65
71
  return {
66
72
  action: Type.Optional(
67
- Type.Union([Type.Literal("run"), Type.Literal("peek"), Type.Literal("stop")], {
68
- description: "Defaults to run. peek and stop require cell_id.",
73
+ Type.Union([Type.Literal("run"), Type.Literal("peek"), Type.Literal("stop"), Type.Literal("list")], {
74
+ description:
75
+ "Defaults to run. peek and stop require cell_id. list: live and recently settled cells across languages.",
69
76
  }),
70
77
  ),
71
78
  language: Type.Optional(languageSchema),
@@ -83,8 +90,12 @@ function evalInputProperties<Language extends TSchema>(languageSchema: Language,
83
90
  description: onTimeoutFieldDescription(deadlines),
84
91
  }),
85
92
  ),
86
- reset: Type.Optional(Type.Boolean({ description: "Reset this language kernel before running." })),
87
- cell_id: Type.Optional(Type.String({ description: "Detached eval cell id for peek or stop." })),
93
+ reset: Type.Optional(
94
+ Type.Boolean({
95
+ description: "Reset this language kernel before running; refused while that language has live cells.",
96
+ }),
97
+ ),
98
+ cell_id: Type.Optional(Type.String({ minLength: 1, description: "Eval cell id for peek or stop." })),
88
99
  };
89
100
  }
90
101
 
@@ -108,7 +119,14 @@ export function createEvalInputSchema(
108
119
  languages.length === 1
109
120
  ? Type.Union([Type.Literal(languages[0])])
110
121
  : Type.Union(languages.map((item) => Type.Literal(item)));
111
- return Type.Unsafe<EvalToolRequest>(Type.Object(evalInputProperties(languageSchema, deadlines))) as EvalInputSchema;
122
+ return Type.Unsafe<EvalToolRequest>(
123
+ Type.Object(evalInputProperties(languageSchema, deadlines), {
124
+ anyOf: [
125
+ { properties: { action: { enum: ["run", "list"] } } },
126
+ { properties: { action: { enum: ["peek", "stop"] } }, required: ["action", "cell_id"] },
127
+ ],
128
+ }),
129
+ ) as EvalInputSchema;
112
130
  }
113
131
  export type EvalKernelResult = Extract<KernelToHostMessage, { type: "result" }>;
114
132
  export type EvalToolCallMessage = Extract<KernelToHostMessage, { type: "tool-call" }>;
@@ -117,6 +135,8 @@ export interface EvalKernelRunInput {
117
135
  readonly cellId: string;
118
136
  readonly code: string;
119
137
  readonly timeoutMs?: number;
138
+ readonly onStarted?: () => void;
139
+ readonly onMessage?: (message: KernelToHostMessage) => void;
120
140
  }
121
141
 
122
142
  export interface KernelInterruptHandle {
@@ -128,7 +148,9 @@ export interface KernelInterruptHandle {
128
148
 
129
149
  export interface EvalKernel {
130
150
  run(input: EvalKernelRunInput): Promise<EvalKernelResult>;
131
- interrupt(reason?: string): Promise<KernelInterruptHandle>;
151
+ cancelQueued(cellId: string, reason: string): boolean;
152
+ interrupt(reason?: string, cellId?: string): Promise<KernelInterruptHandle>;
153
+ queueSnapshot(): { activeCellId: string | null; queuedCellIds: readonly string[] };
132
154
  deliverToolReply(message: Extract<HostToKernelMessage, { type: "tool-reply" }>): void;
133
155
  reset(): Promise<void>;
134
156
  close(): Promise<void>;
@@ -182,7 +204,8 @@ export type EvalCellResult = {
182
204
  readonly language: EvalLanguage;
183
205
  readonly output: string;
184
206
  readonly runtime?: EvalRuntimeInfo;
185
- readonly status: "pending" | "running" | "detached" | "complete" | "error" | "cancelled";
207
+ readonly status: "pending" | "queued" | "running" | "detached" | "complete" | "error" | "cancelled";
208
+ readonly queuedBehind?: readonly string[];
186
209
  readonly exitCode?: number;
187
210
  readonly durationMs?: number;
188
211
  /** Epoch ms when the cell started; lets renderers tick elapsed time between update events. */
@@ -191,6 +214,22 @@ export type EvalCellResult = {
191
214
  readonly hasMarkdown?: boolean;
192
215
  };
193
216
 
217
+ export interface EvalListedCell {
218
+ readonly cellId: string;
219
+ readonly language: EvalLanguage;
220
+ readonly state: "queued" | "running" | "detached" | "completed" | "failed" | "cancelled";
221
+ readonly startedAtMs: number;
222
+ readonly queuedBehind?: readonly string[];
223
+ readonly summary?: string;
224
+ }
225
+
226
+ export interface EvalListDetails {
227
+ readonly action: "list";
228
+ readonly cells: readonly EvalListedCell[];
229
+ }
230
+
231
+ export type EvalResultDetails = EvalToolDetails | EvalListDetails;
232
+
194
233
  export interface EvalToolDetails {
195
234
  readonly language: EvalLanguage;
196
235
  readonly languages?: readonly EvalLanguage[];
@@ -204,6 +243,8 @@ export interface EvalToolDetails {
204
243
  readonly toolCalls: readonly EvalToolCallSummary[];
205
244
  readonly truncated: boolean;
206
245
  readonly isError?: boolean;
246
+ /** Machine-readable reason for a tool-boundary cancellation. */
247
+ readonly code?: string;
207
248
  readonly phase?: string;
208
249
  readonly cells?: readonly EvalCellResult[];
209
250
  readonly statusEvents?: readonly EvalStatusEvent[];
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@earendil-works/pi-agent-core",
3
- "version": "2026.9.21-2",
3
+ "version": "2026.9.22",
4
4
  "description": "General-purpose agent with transport abstraction, state management, and attachment support",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -57,8 +57,8 @@
57
57
  },
58
58
  "dependencies": {
59
59
  "@earendil-works/chord": "0.85.1",
60
- "@earendil-works/pi-ai": "npm:@code-yeongyu/senpi-ai@2026.9.21-2",
61
- "@earendil-works/pi-telemetry": "npm:@code-yeongyu/senpi-telemetry@2026.9.21-2",
60
+ "@earendil-works/pi-ai": "npm:@code-yeongyu/senpi-ai@2026.9.22",
61
+ "@earendil-works/pi-telemetry": "npm:@code-yeongyu/senpi-telemetry@2026.9.22",
62
62
  "diff": "9.0.0",
63
63
  "ignore": "7.0.9",
64
64
  "typebox": "1.3.34",
@@ -458,7 +458,7 @@ Built-in providers resolve these env vars (Node.js; in browsers pass `apiKey` ex
458
458
  | Moonshot AI / Moonshot AI (China) | `MOONSHOT_API_KEY` |
459
459
  | Hugging Face | `HF_TOKEN` |
460
460
  | OpenCode Zen / OpenCode Go | `OPENCODE_API_KEY` |
461
- | Kimi For Coding | `KIMI_API_KEY` |
461
+ | Kimi For Coding | `KIMI_API_KEY` (+ `KIMI_CODE_REGION`) |
462
462
  | Qwen Token Plan (existing catalog) | `QWEN_TOKEN_PLAN_API_KEY` |
463
463
  | Qwen Token Plan (Individual) | `QWEN_TOKEN_PLAN_API_KEY` |
464
464
  | Qwen Token Plan (China) | `QWEN_TOKEN_PLAN_CN_API_KEY` |