@code-yeongyu/senpi 2026.9.15 → 2026.9.16-2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (333) hide show
  1. package/CHANGELOG.md +73 -0
  2. package/README.md +1 -1
  3. package/dist/core/agent-session.d.ts +28 -0
  4. package/dist/core/agent-session.d.ts.map +1 -1
  5. package/dist/core/agent-session.js +111 -13
  6. package/dist/core/agent-session.js.map +1 -1
  7. package/dist/core/compaction/compaction.d.ts.map +1 -1
  8. package/dist/core/compaction/compaction.js +4 -2
  9. package/dist/core/compaction/compaction.js.map +1 -1
  10. package/dist/core/compaction/stream-watchdog.d.ts +56 -2
  11. package/dist/core/compaction/stream-watchdog.d.ts.map +1 -1
  12. package/dist/core/compaction/stream-watchdog.js +93 -15
  13. package/dist/core/compaction/stream-watchdog.js.map +1 -1
  14. package/dist/core/extensions/builtin/claude-sdk-oauth/session-binding.d.ts +6 -0
  15. package/dist/core/extensions/builtin/claude-sdk-oauth/session-binding.d.ts.map +1 -1
  16. package/dist/core/extensions/builtin/claude-sdk-oauth/session-binding.js +17 -0
  17. package/dist/core/extensions/builtin/claude-sdk-oauth/session-binding.js.map +1 -1
  18. package/dist/core/extensions/builtin/claude-sdk-oauth/session-continuity.d.ts +4 -1
  19. package/dist/core/extensions/builtin/claude-sdk-oauth/session-continuity.d.ts.map +1 -1
  20. package/dist/core/extensions/builtin/claude-sdk-oauth/session-continuity.js +31 -0
  21. package/dist/core/extensions/builtin/claude-sdk-oauth/session-continuity.js.map +1 -1
  22. package/dist/core/extensions/builtin/claude-sdk-oauth/session-reattach.d.ts +2 -0
  23. package/dist/core/extensions/builtin/claude-sdk-oauth/session-reattach.d.ts.map +1 -1
  24. package/dist/core/extensions/builtin/claude-sdk-oauth/session-reattach.js +16 -0
  25. package/dist/core/extensions/builtin/claude-sdk-oauth/session-reattach.js.map +1 -1
  26. package/dist/core/extensions/builtin/claude-sdk-oauth/session-registry-wiring.d.ts.map +1 -1
  27. package/dist/core/extensions/builtin/claude-sdk-oauth/session-registry-wiring.js +19 -8
  28. package/dist/core/extensions/builtin/claude-sdk-oauth/session-registry-wiring.js.map +1 -1
  29. package/dist/core/extensions/builtin/claude-sdk-oauth/session-stream.d.ts.map +1 -1
  30. package/dist/core/extensions/builtin/claude-sdk-oauth/session-stream.js +2 -1
  31. package/dist/core/extensions/builtin/claude-sdk-oauth/session-stream.js.map +1 -1
  32. package/dist/core/extensions/builtin/compaction/deterministic-fallback.d.ts +13 -1
  33. package/dist/core/extensions/builtin/compaction/deterministic-fallback.d.ts.map +1 -1
  34. package/dist/core/extensions/builtin/compaction/deterministic-fallback.js +66 -2
  35. package/dist/core/extensions/builtin/compaction/deterministic-fallback.js.map +1 -1
  36. package/dist/core/extensions/builtin/compaction/index.d.ts.map +1 -1
  37. package/dist/core/extensions/builtin/compaction/index.js +16 -10
  38. package/dist/core/extensions/builtin/compaction/index.js.map +1 -1
  39. package/dist/core/extensions/builtin/compaction/speculative-summary.d.ts.map +1 -1
  40. package/dist/core/extensions/builtin/compaction/speculative-summary.js +4 -2
  41. package/dist/core/extensions/builtin/compaction/speculative-summary.js.map +1 -1
  42. package/dist/core/extensions/builtin/compaction/speculative.d.ts +8 -1
  43. package/dist/core/extensions/builtin/compaction/speculative.d.ts.map +1 -1
  44. package/dist/core/extensions/builtin/compaction/speculative.js +31 -7
  45. package/dist/core/extensions/builtin/compaction/speculative.js.map +1 -1
  46. package/dist/core/extensions/builtin/compaction/transient-failure.d.ts.map +1 -1
  47. package/dist/core/extensions/builtin/compaction/transient-failure.js +6 -3
  48. package/dist/core/extensions/builtin/compaction/transient-failure.js.map +1 -1
  49. package/dist/core/extensions/builtin/config-reload/index.d.ts.map +1 -1
  50. package/dist/core/extensions/builtin/config-reload/index.js +25 -8
  51. package/dist/core/extensions/builtin/config-reload/index.js.map +1 -1
  52. package/dist/core/extensions/builtin/config-reload/watch-engine.d.ts +4 -5
  53. package/dist/core/extensions/builtin/config-reload/watch-engine.d.ts.map +1 -1
  54. package/dist/core/extensions/builtin/config-reload/watch-engine.js +14 -18
  55. package/dist/core/extensions/builtin/config-reload/watch-engine.js.map +1 -1
  56. package/dist/core/extensions/builtin/config-reload/watch-event-source.d.ts.map +1 -1
  57. package/dist/core/extensions/builtin/config-reload/watch-event-source.js +32 -9
  58. package/dist/core/extensions/builtin/config-reload/watch-event-source.js.map +1 -1
  59. package/dist/core/extensions/builtin/cursor-cli-oauth/catalog-refresh.d.ts +23 -0
  60. package/dist/core/extensions/builtin/cursor-cli-oauth/catalog-refresh.d.ts.map +1 -0
  61. package/dist/core/extensions/builtin/cursor-cli-oauth/catalog-refresh.js +48 -0
  62. package/dist/core/extensions/builtin/cursor-cli-oauth/catalog-refresh.js.map +1 -0
  63. package/dist/core/extensions/builtin/cursor-cli-oauth/environment.d.ts +13 -0
  64. package/dist/core/extensions/builtin/cursor-cli-oauth/environment.d.ts.map +1 -0
  65. package/dist/core/extensions/builtin/cursor-cli-oauth/environment.js +24 -0
  66. package/dist/core/extensions/builtin/cursor-cli-oauth/environment.js.map +1 -0
  67. package/dist/core/extensions/builtin/cursor-cli-oauth/executable.d.ts +1 -0
  68. package/dist/core/extensions/builtin/cursor-cli-oauth/executable.d.ts.map +1 -1
  69. package/dist/core/extensions/builtin/cursor-cli-oauth/executable.js +7 -1
  70. package/dist/core/extensions/builtin/cursor-cli-oauth/executable.js.map +1 -1
  71. package/dist/core/extensions/builtin/cursor-cli-oauth/index.d.ts +2 -0
  72. package/dist/core/extensions/builtin/cursor-cli-oauth/index.d.ts.map +1 -1
  73. package/dist/core/extensions/builtin/cursor-cli-oauth/index.js +11 -9
  74. package/dist/core/extensions/builtin/cursor-cli-oauth/index.js.map +1 -1
  75. package/dist/core/extensions/builtin/cursor-cli-oauth/models-probe.d.ts +18 -0
  76. package/dist/core/extensions/builtin/cursor-cli-oauth/models-probe.d.ts.map +1 -0
  77. package/dist/core/extensions/builtin/cursor-cli-oauth/models-probe.js +61 -0
  78. package/dist/core/extensions/builtin/cursor-cli-oauth/models-probe.js.map +1 -0
  79. package/dist/core/extensions/builtin/cursor-cli-oauth/models.d.ts +4 -1
  80. package/dist/core/extensions/builtin/cursor-cli-oauth/models.d.ts.map +1 -1
  81. package/dist/core/extensions/builtin/cursor-cli-oauth/models.js +1 -44
  82. package/dist/core/extensions/builtin/cursor-cli-oauth/models.js.map +1 -1
  83. package/dist/core/extensions/builtin/cursor-cli-oauth/oauth-login.d.ts +22 -0
  84. package/dist/core/extensions/builtin/cursor-cli-oauth/oauth-login.d.ts.map +1 -1
  85. package/dist/core/extensions/builtin/cursor-cli-oauth/oauth-login.js +1 -1
  86. package/dist/core/extensions/builtin/cursor-cli-oauth/oauth-login.js.map +1 -1
  87. package/dist/core/extensions/builtin/cursor-cli-oauth/transport.d.ts.map +1 -1
  88. package/dist/core/extensions/builtin/cursor-cli-oauth/transport.js +2 -13
  89. package/dist/core/extensions/builtin/cursor-cli-oauth/transport.js.map +1 -1
  90. package/dist/core/extensions/builtin/terminal/monitor-file-digest.d.ts +3 -0
  91. package/dist/core/extensions/builtin/terminal/monitor-file-digest.d.ts.map +1 -0
  92. package/dist/core/extensions/builtin/terminal/monitor-file-digest.js +25 -0
  93. package/dist/core/extensions/builtin/terminal/monitor-file-digest.js.map +1 -0
  94. package/dist/core/extensions/builtin/terminal/monitor-file-watch.d.ts +9 -0
  95. package/dist/core/extensions/builtin/terminal/monitor-file-watch.d.ts.map +1 -0
  96. package/dist/core/extensions/builtin/terminal/monitor-file-watch.js +33 -0
  97. package/dist/core/extensions/builtin/terminal/monitor-file-watch.js.map +1 -0
  98. package/dist/core/extensions/builtin/terminal/monitor-line-buffer.d.ts +6 -0
  99. package/dist/core/extensions/builtin/terminal/monitor-line-buffer.d.ts.map +1 -0
  100. package/dist/core/extensions/builtin/terminal/monitor-line-buffer.js +22 -0
  101. package/dist/core/extensions/builtin/terminal/monitor-line-buffer.js.map +1 -0
  102. package/dist/core/extensions/builtin/terminal/monitor-registry.d.ts.map +1 -1
  103. package/dist/core/extensions/builtin/terminal/monitor-registry.js +14 -35
  104. package/dist/core/extensions/builtin/terminal/monitor-registry.js.map +1 -1
  105. package/dist/core/extensions/kernel-tools-context.d.ts +13 -0
  106. package/dist/core/extensions/kernel-tools-context.d.ts.map +1 -0
  107. package/dist/core/extensions/kernel-tools-context.js +3 -0
  108. package/dist/core/extensions/kernel-tools-context.js.map +1 -0
  109. package/dist/core/extensions/runner.d.ts +19 -0
  110. package/dist/core/extensions/runner.d.ts.map +1 -1
  111. package/dist/core/extensions/runner.js +103 -1
  112. package/dist/core/extensions/runner.js.map +1 -1
  113. package/dist/core/extensions/types.d.ts +21 -0
  114. package/dist/core/extensions/types.d.ts.map +1 -1
  115. package/dist/core/extensions/types.js.map +1 -1
  116. package/dist/core/keybindings.d.ts +5 -0
  117. package/dist/core/keybindings.d.ts.map +1 -1
  118. package/dist/core/keybindings.js +1 -0
  119. package/dist/core/keybindings.js.map +1 -1
  120. package/dist/core/resource-loader.d.ts.map +1 -1
  121. package/dist/core/resource-loader.js +9 -17
  122. package/dist/core/resource-loader.js.map +1 -1
  123. package/dist/core/retry-fallback/settings.d.ts +3 -0
  124. package/dist/core/retry-fallback/settings.d.ts.map +1 -1
  125. package/dist/core/retry-fallback/settings.js.map +1 -1
  126. package/dist/core/sdk.d.ts.map +1 -1
  127. package/dist/core/sdk.js +1 -0
  128. package/dist/core/sdk.js.map +1 -1
  129. package/dist/core/session-manager.d.ts +30 -1
  130. package/dist/core/session-manager.d.ts.map +1 -1
  131. package/dist/core/session-manager.js +92 -5
  132. package/dist/core/session-manager.js.map +1 -1
  133. package/dist/core/session-resident-store.d.ts +33 -7
  134. package/dist/core/session-resident-store.d.ts.map +1 -1
  135. package/dist/core/session-resident-store.js +163 -14
  136. package/dist/core/session-resident-store.js.map +1 -1
  137. package/dist/core/session-write-reservation.d.ts +6 -0
  138. package/dist/core/session-write-reservation.d.ts.map +1 -1
  139. package/dist/core/session-write-reservation.js +19 -0
  140. package/dist/core/session-write-reservation.js.map +1 -1
  141. package/dist/core/settings-manager.d.ts +35 -1
  142. package/dist/core/settings-manager.d.ts.map +1 -1
  143. package/dist/core/settings-manager.js +62 -0
  144. package/dist/core/settings-manager.js.map +1 -1
  145. package/dist/core/slash-commands.d.ts.map +1 -1
  146. package/dist/core/slash-commands.js +2 -1
  147. package/dist/core/slash-commands.js.map +1 -1
  148. package/dist/core/tools/index.d.ts +1 -1
  149. package/dist/core/tools/index.d.ts.map +1 -1
  150. package/dist/core/tools/index.js +4 -3
  151. package/dist/core/tools/index.js.map +1 -1
  152. package/dist/core/tools/read.d.ts +3 -1
  153. package/dist/core/tools/read.d.ts.map +1 -1
  154. package/dist/core/tools/read.js +16 -2
  155. package/dist/core/tools/read.js.map +1 -1
  156. package/dist/index.d.ts +1 -0
  157. package/dist/index.d.ts.map +1 -1
  158. package/dist/index.js +1 -0
  159. package/dist/index.js.map +1 -1
  160. package/dist/modes/interactive/components/assistant-render-descriptors.d.ts.map +1 -1
  161. package/dist/modes/interactive/components/assistant-render-descriptors.js +12 -5
  162. package/dist/modes/interactive/components/assistant-render-descriptors.js.map +1 -1
  163. package/dist/modes/interactive/components/extension-input.d.ts +2 -0
  164. package/dist/modes/interactive/components/extension-input.d.ts.map +1 -1
  165. package/dist/modes/interactive/components/extension-input.js +5 -0
  166. package/dist/modes/interactive/components/extension-input.js.map +1 -1
  167. package/dist/modes/interactive/interactive-mode.d.ts +19 -2
  168. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  169. package/dist/modes/interactive/interactive-mode.js +69 -17
  170. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  171. package/dist/modes/interactive/tips/catalog/session-tips.js +1 -1
  172. package/dist/modes/interactive/tips/catalog/session-tips.js.map +1 -1
  173. package/dist/modes/interactive/working-status.d.ts +3 -2
  174. package/dist/modes/interactive/working-status.d.ts.map +1 -1
  175. package/dist/modes/interactive/working-status.js +17 -4
  176. package/dist/modes/interactive/working-status.js.map +1 -1
  177. package/dist/modes/print-mode.d.ts +1 -1
  178. package/dist/modes/print-mode.d.ts.map +1 -1
  179. package/dist/modes/print-mode.js +5 -1
  180. package/dist/modes/print-mode.js.map +1 -1
  181. package/dist/modes/rpc/host-watchdog.d.ts.map +1 -1
  182. package/dist/modes/rpc/host-watchdog.js +5 -24
  183. package/dist/modes/rpc/host-watchdog.js.map +1 -1
  184. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  185. package/dist/modes/rpc/rpc-mode.js +3 -8
  186. package/dist/modes/rpc/rpc-mode.js.map +1 -1
  187. package/dist/modes/rpc/session-command-router.d.ts.map +1 -1
  188. package/dist/modes/rpc/session-command-router.js +3 -0
  189. package/dist/modes/rpc/session-command-router.js.map +1 -1
  190. package/dist/modes/rpc/session-worker-client.d.ts +3 -0
  191. package/dist/modes/rpc/session-worker-client.d.ts.map +1 -1
  192. package/dist/modes/rpc/session-worker-client.js +17 -6
  193. package/dist/modes/rpc/session-worker-client.js.map +1 -1
  194. package/dist/modes/rpc/shutdown.d.ts +3 -0
  195. package/dist/modes/rpc/shutdown.d.ts.map +1 -0
  196. package/dist/modes/rpc/shutdown.js +17 -0
  197. package/dist/modes/rpc/shutdown.js.map +1 -0
  198. package/docs/extensions.md +5 -2
  199. package/docs/keybindings.md +1 -0
  200. package/docs/session-format.md +1 -1
  201. package/docs/sessions.md +3 -3
  202. package/docs/settings.md +31 -1
  203. package/docs/usage.md +1 -1
  204. package/node_modules/@code-yeongyu/senpi-codemode/CHANGELOG.md +50 -0
  205. package/node_modules/@code-yeongyu/senpi-codemode/README.md +24 -0
  206. package/node_modules/@code-yeongyu/senpi-codemode/package.json +4 -4
  207. package/node_modules/@code-yeongyu/senpi-codemode/src/bridge/http-server.ts +7 -1
  208. package/node_modules/@code-yeongyu/senpi-codemode/src/bridge/kernel-tools-protocol.ts +85 -0
  209. package/node_modules/@code-yeongyu/senpi-codemode/src/bridge/protocol.ts +6 -0
  210. package/node_modules/@code-yeongyu/senpi-codemode/src/bridges/agent-bridge.ts +30 -11
  211. package/node_modules/@code-yeongyu/senpi-codemode/src/extension/session-manager.ts +11 -0
  212. package/node_modules/@code-yeongyu/senpi-codemode/src/index.ts +10 -0
  213. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/jl/prelude.jl +46 -0
  214. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/jl/runner.jl +1 -1
  215. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/context-manager.ts +58 -10
  216. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/kernel-contract.ts +12 -0
  217. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/kernel-tools-context.d.ts +13 -0
  218. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/kernel-tools-context.js +7 -0
  219. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/kernel-tools-define.d.ts +10 -0
  220. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/kernel-tools-define.js +12 -0
  221. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/kernel-tools-errors.js +22 -0
  222. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/kernel-tools-errors.ts +26 -0
  223. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/kernel-tools-host.ts +134 -0
  224. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/kernel-tools-naming.js +28 -0
  225. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/kernel-tools-parse.d.ts +5 -0
  226. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/kernel-tools-parse.js +255 -0
  227. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/kernel-tools-pump.js +105 -0
  228. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/kernel-tools-registry.d.ts +27 -0
  229. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/kernel-tools-registry.js +127 -0
  230. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/kernel-tools-schema.js +69 -0
  231. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/kernel-tools-types.ts +42 -0
  232. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/prelude.ts +2 -0
  233. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/worker-core.js +26 -3
  234. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/worker-runtime.js +20 -8
  235. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/worker-slot.ts +6 -1
  236. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/worker-startup.ts +9 -7
  237. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/workpool.js +37 -0
  238. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/kernel-tools-unavailable.ts +5 -0
  239. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/py/kernel.ts +13 -0
  240. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/py/prelude.py +57 -3
  241. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/rb/prelude.rb +13 -2
  242. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/rb/workpool.rb +38 -0
  243. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/shared/subprocess-kernel.ts +13 -0
  244. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/shared/subprocess-queue.ts +9 -1
  245. package/node_modules/@code-yeongyu/senpi-codemode/src/prompt/eval-prompt-template.ts +4 -2
  246. package/node_modules/@code-yeongyu/senpi-codemode/src/tool/cell-handler.ts +31 -3
  247. package/node_modules/@code-yeongyu/senpi-codemode/src/tool/detached-cell-manager.ts +36 -71
  248. package/node_modules/@code-yeongyu/senpi-codemode/src/tool/image-resize.ts +52 -0
  249. package/node_modules/@code-yeongyu/senpi-codemode/src/tool/image.ts +37 -72
  250. package/node_modules/@code-yeongyu/senpi-codemode/src/tool/managed-cell.ts +78 -0
  251. package/node_modules/@code-yeongyu/senpi-codemode/src/tool/render.ts +30 -5
  252. package/node_modules/@code-yeongyu/senpi-codemode/src/tool/run-eval-cell.ts +52 -30
  253. package/node_modules/@code-yeongyu/senpi-codemode/src/tool/terminal-snapshot-store.ts +35 -0
  254. package/node_modules/@code-yeongyu/senpi-codemode/src/tool/tool-result-marshal.ts +23 -0
  255. package/node_modules/@code-yeongyu/senpi-codemode/src/tool/types.ts +2 -0
  256. package/node_modules/@earendil-works/pi-agent-core/dist/agent-loop.d.ts.map +1 -1
  257. package/node_modules/@earendil-works/pi-agent-core/dist/agent-loop.js +30 -3
  258. package/node_modules/@earendil-works/pi-agent-core/dist/agent-loop.js.map +1 -1
  259. package/node_modules/@earendil-works/pi-agent-core/dist/agent.d.ts +5 -0
  260. package/node_modules/@earendil-works/pi-agent-core/dist/agent.d.ts.map +1 -1
  261. package/node_modules/@earendil-works/pi-agent-core/dist/agent.js +2 -0
  262. package/node_modules/@earendil-works/pi-agent-core/dist/agent.js.map +1 -1
  263. package/node_modules/@earendil-works/pi-agent-core/dist/empty-assistant-recovery.d.ts.map +1 -1
  264. package/node_modules/@earendil-works/pi-agent-core/dist/empty-assistant-recovery.js +51 -12
  265. package/node_modules/@earendil-works/pi-agent-core/dist/empty-assistant-recovery.js.map +1 -1
  266. package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/read.d.ts +3 -0
  267. package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/read.d.ts.map +1 -1
  268. package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/read.js +18 -3
  269. package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/read.js.map +1 -1
  270. package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/read-folders/brace-scanner.d.ts +4 -0
  271. package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/read-folders/brace-scanner.d.ts.map +1 -0
  272. package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/read-folders/brace-scanner.js +268 -0
  273. package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/read-folders/brace-scanner.js.map +1 -0
  274. package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/read-folders/header-protection.d.ts +18 -0
  275. package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/read-folders/header-protection.d.ts.map +1 -0
  276. package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/read-folders/header-protection.js +69 -0
  277. package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/read-folders/header-protection.js.map +1 -0
  278. package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/read-folders/index.d.ts +27 -0
  279. package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/read-folders/index.d.ts.map +1 -0
  280. package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/read-folders/index.js +118 -0
  281. package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/read-folders/index.js.map +1 -0
  282. package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/read-folders/lexical-context.d.ts +21 -0
  283. package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/read-folders/lexical-context.d.ts.map +1 -0
  284. package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/read-folders/lexical-context.js +24 -0
  285. package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/read-folders/lexical-context.js.map +1 -0
  286. package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/read-folders/lexical-spans.d.ts +12 -0
  287. package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/read-folders/lexical-spans.d.ts.map +1 -0
  288. package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/read-folders/lexical-spans.js +92 -0
  289. package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/read-folders/lexical-spans.js.map +1 -0
  290. package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/read-folders/types.d.ts +47 -0
  291. package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/read-folders/types.d.ts.map +1 -0
  292. package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/read-folders/types.js +8 -0
  293. package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/read-folders/types.js.map +1 -0
  294. package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/segmented-read-view.d.ts +52 -0
  295. package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/segmented-read-view.d.ts.map +1 -0
  296. package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/segmented-read-view.js +155 -0
  297. package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/segmented-read-view.js.map +1 -0
  298. package/node_modules/@earendil-works/pi-agent-core/dist/index.d.ts +4 -0
  299. package/node_modules/@earendil-works/pi-agent-core/dist/index.d.ts.map +1 -1
  300. package/node_modules/@earendil-works/pi-agent-core/dist/index.js +3 -0
  301. package/node_modules/@earendil-works/pi-agent-core/dist/index.js.map +1 -1
  302. package/node_modules/@earendil-works/pi-agent-core/dist/stream-throughput-watchdog.d.ts +101 -0
  303. package/node_modules/@earendil-works/pi-agent-core/dist/stream-throughput-watchdog.d.ts.map +1 -0
  304. package/node_modules/@earendil-works/pi-agent-core/dist/stream-throughput-watchdog.js +178 -0
  305. package/node_modules/@earendil-works/pi-agent-core/dist/stream-throughput-watchdog.js.map +1 -0
  306. package/node_modules/@earendil-works/pi-agent-core/dist/types.d.ts +9 -0
  307. package/node_modules/@earendil-works/pi-agent-core/dist/types.d.ts.map +1 -1
  308. package/node_modules/@earendil-works/pi-agent-core/dist/types.js.map +1 -1
  309. package/node_modules/@earendil-works/pi-agent-core/package.json +3 -3
  310. package/node_modules/@earendil-works/pi-ai/dist/index.d.ts +1 -0
  311. package/node_modules/@earendil-works/pi-ai/dist/index.d.ts.map +1 -1
  312. package/node_modules/@earendil-works/pi-ai/dist/index.js +1 -0
  313. package/node_modules/@earendil-works/pi-ai/dist/index.js.map +1 -1
  314. package/node_modules/@earendil-works/pi-ai/dist/providers/data/.manifest.json +1 -1
  315. package/node_modules/@earendil-works/pi-ai/dist/providers/data/alibaba-token-plan.json +1 -1
  316. package/node_modules/@earendil-works/pi-ai/dist/providers/data/amazon-bedrock.json +1 -1
  317. package/node_modules/@earendil-works/pi-ai/dist/providers/data/fireworks.json +1 -1
  318. package/node_modules/@earendil-works/pi-ai/dist/providers/data/openrouter.json +1 -1
  319. package/node_modules/@earendil-works/pi-ai/dist/providers/data/qwen-token-plan.json +1 -1
  320. package/node_modules/@earendil-works/pi-ai/dist/providers/data/vercel-ai-gateway.json +1 -1
  321. package/node_modules/@earendil-works/pi-ai/dist/utils/empty-response-errors.d.ts +15 -0
  322. package/node_modules/@earendil-works/pi-ai/dist/utils/empty-response-errors.d.ts.map +1 -0
  323. package/node_modules/@earendil-works/pi-ai/dist/utils/empty-response-errors.js +15 -0
  324. package/node_modules/@earendil-works/pi-ai/dist/utils/empty-response-errors.js.map +1 -0
  325. package/node_modules/@earendil-works/pi-ai/dist/utils/retry.d.ts +24 -0
  326. package/node_modules/@earendil-works/pi-ai/dist/utils/retry.d.ts.map +1 -1
  327. package/node_modules/@earendil-works/pi-ai/dist/utils/retry.js +92 -0
  328. package/node_modules/@earendil-works/pi-ai/dist/utils/retry.js.map +1 -1
  329. package/node_modules/@earendil-works/pi-ai/package.json +2 -2
  330. package/node_modules/@earendil-works/pi-pty/package.json +1 -1
  331. package/node_modules/@earendil-works/pi-telemetry/package.json +1 -1
  332. package/node_modules/@earendil-works/pi-tui/package.json +1 -1
  333. package/package.json +7 -7
@@ -140,13 +140,22 @@ def __senpi_bridge_request(path, payload)
140
140
  return body["value"] if body.is_a?(Hash) && body["ok"] == true
141
141
 
142
142
  error = body.is_a?(Hash) ? body["error"] : body
143
- raise(error.is_a?(Hash) ? error["message"].to_s : error.to_s)
143
+ raise SenpiBridgeError.new(error.is_a?(Hash) ? error["message"].to_s : error.to_s, error.is_a?(Hash) ? error["code"] : nil)
144
144
  end
145
145
 
146
146
  def __senpi_call_tool(name, args)
147
147
  __senpi_bridge_request("/call", { "callId" => "rb-#{Process.pid}-#{rand(1_000_000)}", "toolName" => name, "args" => args })
148
148
  end
149
149
 
150
+ class SenpiBridgeError < StandardError
151
+ attr_reader :code
152
+
153
+ def initialize(message, code = nil)
154
+ super(message)
155
+ @code = code
156
+ end
157
+ end
158
+
150
159
  class SenpiToolCallable
151
160
  def initialize(name)
152
161
  @name = name
@@ -177,6 +186,8 @@ def tool
177
186
  $__senpi_tool_proxy ||= SenpiToolProxy.new
178
187
  end
179
188
 
189
+ require_relative "workpool"
190
+
180
191
  def completion(prompt, model: "default", system: nil, schema: nil, **kwargs)
181
192
  options = { "model" => model }.merge(kwargs.transform_keys(&:to_s))
182
193
  options["system"] = system unless system.nil?
@@ -213,7 +224,7 @@ def agent(prompt, agent: "task", model: nil, label: nil, schema: nil, isolated:
213
224
  text_value = record.fetch("text", response)
214
225
  result = schema.nil? ? text_value : record.key?("data") ? record["data"] : JSON.parse(text_value.to_s)
215
226
  return result unless handle
216
- { "text" => text_value, "output" => text_value, "handle" => record["handle"] || (record["id"] && "agent://#{record["id"]}"), "id" => record["id"], "agent" => record.fetch("agent", agent) }.tap do |node|
227
+ { "text" => text_value, "output" => text_value, "handle" => record["handle"] || (record["id"] && "agent://#{record["id"]}"), "id" => record["id"], "run_epoch" => record["run_epoch"], "agent" => record.fetch("agent", agent) }.tap do |node|
217
228
  node["data"] = result unless schema.nil?
218
229
  end
219
230
  end
@@ -0,0 +1,38 @@
1
+ # Only an opaque host identity is retained; all work remains engine-owned.
2
+ def __senpi_workpool_call(args)
3
+ tool.workpool(args)
4
+ rescue SenpiBridgeError => error
5
+ raise SenpiBridgeError.new("No active host workpool tool", "workpool_unavailable") if ["unknown_tool", "inactive_tool"].include?(error.code)
6
+ raise
7
+ end
8
+
9
+ class SenpiWorkpool
10
+ attr_reader :pool_id
11
+
12
+ def initialize(pool_id)
13
+ @pool_id = pool_id
14
+ freeze
15
+ end
16
+
17
+ def push(items)
18
+ __senpi_workpool_call({ "op" => "push", "pool_id" => @pool_id, "items" => items })
19
+ end
20
+
21
+ [:close, :inspect, :cancel].each do |op|
22
+ define_method(op) { __senpi_workpool_call({ "op" => op.to_s, "pool_id" => @pool_id }) }
23
+ end
24
+ end
25
+
26
+ def workpool(agent, name, mode: nil)
27
+ args = { "op" => "create", "agent" => agent, "name" => name }
28
+ args["mode"] = mode unless mode.nil?
29
+ result = __senpi_workpool_call(args)
30
+ details = result["details"]
31
+ if details.is_a?(Hash)
32
+ error = details["error"]
33
+ raise SenpiBridgeError.new(error["message"], error["code"]) if error.is_a?(Hash)
34
+ pool_id = details["pool_id"]
35
+ return SenpiWorkpool.new(pool_id) if !result["hasError"] && pool_id.is_a?(String) && /\Awp_[0-9a-f]{32}\z/.match?(pool_id)
36
+ end
37
+ raise SenpiBridgeError.new("Host did not return a workpool identity", "workpool_unavailable")
38
+ end
@@ -1,6 +1,7 @@
1
1
  import type { HostToKernelMessage, KernelToHostMessage } from "../../bridge/protocol.ts";
2
2
  import { decodeBridgeFrame, encodeBridgeFrame, isKernelToHostMessage } from "../../bridge/protocol.ts";
3
3
  import type { KernelInterruptHandle } from "../../tool/types.ts";
4
+ import { rejectKernelToolsUnavailable } from "../kernel-tools-unavailable.ts";
4
5
  import { applySessionEnvironment } from "../session-env.ts";
5
6
  import type { KernelResult, KernelRunInput, SubprocessKernelOptions, ToolCallMessage } from "./subprocess-contract.ts";
6
7
  import { type SubprocessLike, SubprocessProcess, type SubprocessSpawn, spawnSubprocess } from "./subprocess-process.ts";
@@ -69,6 +70,18 @@ export class SubprocessKernel {
69
70
  return { stateRetained: Promise.resolve(false) };
70
71
  }
71
72
 
73
+ listKernelToolNames(): readonly string[] {
74
+ return [];
75
+ }
76
+
77
+ describeKernelTools(_names: readonly string[]): Promise<never> {
78
+ return rejectKernelToolsUnavailable();
79
+ }
80
+
81
+ invokeKernelTool(_request: unknown, _signal?: AbortSignal): Promise<never> {
82
+ return rejectKernelToolsUnavailable();
83
+ }
84
+
72
85
  nextToolCall(): Promise<ToolCallMessage> {
73
86
  return this.runs.nextToolCall();
74
87
  }
@@ -2,6 +2,9 @@ import type { KernelToHostMessage } from "../../bridge/protocol.ts";
2
2
  import type { KernelResult, KernelRunInput, ToolCallMessage } from "./subprocess-contract.ts";
3
3
  import { createPendingRun, failureResult, type PendingRun, settlePendingRun } from "./subprocess-run.ts";
4
4
 
5
+ // Same bound as the JS kernel's pull-API fallback queue (context-manager.ts); clears happen at run teardown.
6
+ const MAX_PENDING_TOOL_CALLS = 256;
7
+
5
8
  export class SubprocessRunQueue {
6
9
  readonly #queue: PendingRun[] = [];
7
10
  readonly #pendingCalls: ToolCallMessage[] = [];
@@ -59,7 +62,10 @@ export class SubprocessRunQueue {
59
62
  pushToolCall(message: ToolCallMessage): void {
60
63
  const waiter = this.#callWaiters.shift();
61
64
  if (waiter) waiter(message);
62
- else this.#pendingCalls.push(message);
65
+ else {
66
+ this.#pendingCalls.push(message);
67
+ if (this.#pendingCalls.length > MAX_PENDING_TOOL_CALLS) this.#pendingCalls.shift();
68
+ }
63
69
  }
64
70
 
65
71
  handleMessage(
@@ -90,6 +96,8 @@ export class SubprocessRunQueue {
90
96
  case "ready":
91
97
  case "init-failed":
92
98
  case "closed":
99
+ case "kernel-tool-describe-reply":
100
+ case "kernel-tool-invoke-reply":
93
101
  onMessage?.(message);
94
102
  return false;
95
103
  default: {
@@ -57,8 +57,10 @@ tool_schema(name?) → dict
57
57
  completion(prompt, model?="default", system?=None, schema?=None) → str | dict
58
58
  Oneshot, stateless. \`model\`: \`"smol"\` fast | \`"default"\` session | \`"slow"\` most capable. \`schema\` (JSON-Schema) → parsed structured output.
59
59
  {{#if spawns}}agent(prompt, agent?="{{spawnDefaultAgent}}", model?=None, label?=None, schema?=None, handle?=False) → str | dict
60
- Run a subagent → final output. \`agent\` picks a discovered agent. \`schema\` as in completion(). \`handle\` → workflow node { text, output, handle: \`agent://<id>\`, id, agent } (parsed under \`data\` with \`schema\`).
61
- {{/if}}parallel(thunks) → list
60
+ Run a subagent → final output. \`agent\` picks a discovered agent. \`schema\` as in completion(). \`handle\` → workflow node { text, output, handle: \`agent://<id>\`, id, run_epoch, agent } (parsed under \`data\` with \`schema\`).
61
+ {{/if}}workpool(agent, name, mode?) → { pool_id, push(items), close(), inspect(), cancel() }
62
+ Thin host-tool adapter; agent is a plain-data spec. The host owns scheduling and keyed yields; explicitly close for automatic aggregate delivery. Requires the host workpool tool; omitted mode uses its default.
63
+ parallel(thunks) → list
62
64
  Thunks through a bounded pool (as wide as a \`task\` batch), input order kept; a throwing thunk propagates.
63
65
  pipeline(items, ...stages) → list
64
66
  Map items through one-arg stages with a barrier between stages; each stage receives the previous stage's result.
@@ -1,4 +1,9 @@
1
- import { type AgentToolResult, type ExtensionContext, sanitizeTerminalLabel } from "@code-yeongyu/senpi";
1
+ import {
2
+ type AgentToolResult,
3
+ type ExtensionContext,
4
+ kernelToolsStorage,
5
+ sanitizeTerminalLabel,
6
+ } from "@code-yeongyu/senpi";
2
7
  import type { KernelToHostMessage } from "../bridge/protocol.ts";
3
8
  import { RESERVED_SCHEMA_TOOL } from "../bridge/reserved.ts";
4
9
  import type { AgentExecuteTool } from "../bridges/agent-bridge.ts";
@@ -8,6 +13,7 @@ import { appendSchemaHint } from "../bridges/schema-hint.ts";
8
13
  import type { CompletionRequest, CompletionResult } from "../completion/handler.ts";
9
14
  import { handleCompletionToolCall } from "../completion/tool-bridge.ts";
10
15
  import type { ResolvedCodemodeSettings } from "../config/settings.ts";
16
+ import type { KernelToolsCapability } from "../kernels/js/kernel-tools-types.ts";
11
17
  import {
12
18
  boundToolCallArgs,
13
19
  capCodePoints,
@@ -39,6 +45,8 @@ export interface CellBridgeRuntime {
39
45
  readonly ctx: ExtensionContext;
40
46
  readonly artifactPath?: string;
41
47
  readonly imageResizer?: EvalImageResizer;
48
+ /** This cell's live kernel-tool capability; only a JS kernel has one (#1754). */
49
+ readonly kernelTools?: KernelToolsCapability;
42
50
  }
43
51
 
44
52
  export class CellHandler {
@@ -81,7 +89,7 @@ export class CellHandler {
81
89
  this.#resultBuilder.display(message);
82
90
  return;
83
91
  case "tool-call": {
84
- const pending = this.#handleToolCall(message);
92
+ const pending = this.#dispatchToolCall(message);
85
93
  this.#state.pendingBridgeCalls.push(pending);
86
94
  await pending;
87
95
  return;
@@ -90,6 +98,8 @@ export class CellHandler {
90
98
  case "init-failed":
91
99
  case "result":
92
100
  case "closed":
101
+ case "kernel-tool-describe-reply":
102
+ case "kernel-tool-invoke-reply":
93
103
  return;
94
104
  default:
95
105
  throw new TypeError(`Unhandled kernel message: ${String(message)}`);
@@ -112,6 +122,19 @@ export class CellHandler {
112
122
  return this.#resultBuilder.liveResult();
113
123
  }
114
124
 
125
+ /**
126
+ * The worker's message loop fires outside the async context `run-eval-cell.ts` enters around the
127
+ * awaited run chain, so the capability has to be entered here — around the whole dispatch, including
128
+ * the reserved agent()/output() bridges where a host task tool resolves the parent's kernel tools —
129
+ * for exactly the duration of each host tool call this cell makes (#1754). Without a capability
130
+ * (py/rb/jl) the store stays empty and `ctx.kernelTools` remains undefined.
131
+ */
132
+ async #dispatchToolCall(message: Extract<KernelToHostMessage, { type: "tool-call" }>): Promise<void> {
133
+ const kernelTools = this.#runtime.kernelTools;
134
+ if (kernelTools === undefined) return await this.#handleToolCall(message);
135
+ return await kernelToolsStorage.run(kernelTools, async () => await this.#handleToolCall(message));
136
+ }
137
+
115
138
  async #handleToolCall(message: Extract<KernelToHostMessage, { type: "tool-call" }>): Promise<void> {
116
139
  const startedAt = Date.now();
117
140
  const metric = createToolCallMetric(message.toolName, startedAt);
@@ -223,7 +246,12 @@ export class CellHandler {
223
246
  type: "tool-reply",
224
247
  callId: message.callId,
225
248
  ok: false,
226
- error: { message: text },
249
+ error: {
250
+ message: text,
251
+ ...(error instanceof Error && "code" in error && typeof error.code === "string"
252
+ ? { code: error.code }
253
+ : {}),
254
+ },
227
255
  });
228
256
  }
229
257
  this.#resultBuilder.emitUpdate(false);
@@ -1,14 +1,12 @@
1
1
  import type { AgentToolResult } from "@code-yeongyu/senpi";
2
2
  import { DEFAULT_HARD_LIMIT_SECONDS, DEFAULT_RUN_BUDGET_SECONDS } from "../config/settings.ts";
3
3
  import type { WakeSourceState } from "../extension/wake-source-state.ts";
4
- import { type CellDeadlineExpiry, CellDeadlines } from "./cell-deadlines.ts";
4
+ import type { CellDeadlineExpiry } from "./cell-deadlines.ts";
5
5
  import type {
6
6
  EvalDetachedCellManagerOptions,
7
7
  EvalDetachedCellSnapshot,
8
- EvalDetachedCellState,
9
8
  EvalDetachedCellStatusEntry,
10
9
  } from "./detached-cell-contract.ts";
11
- import { detachedNotificationSpillPath } from "./detached-cell-notification.ts";
12
10
  import { currentDetachedResult, detachedErrorResult, snapshotDetachedCell } from "./detached-cell-snapshot.ts";
13
11
  import {
14
12
  activeDetachedCellReuseError,
@@ -17,6 +15,8 @@ import {
17
15
  } from "./detached-cell-state.ts";
18
16
  import { detachedStatusEntries, detachedWakeSourceState } from "./detached-cell-status.ts";
19
17
  import { DetachedNotificationQueue } from "./detached-notification-queue.ts";
18
+ import { createManagedCell, type LiveResultProvider, type ManagedCell } from "./managed-cell.ts";
19
+ import { TerminalSnapshotStore } from "./terminal-snapshot-store.ts";
20
20
  import type { EvalKernel, EvalLanguage, EvalToolDetails, EvalToolInput } from "./types.ts";
21
21
 
22
22
  export type {
@@ -28,40 +28,13 @@ export type {
28
28
  EvalDetachedCellStatusEntry,
29
29
  } from "./detached-cell-contract.ts";
30
30
 
31
- type LiveResultProvider = () => AgentToolResult<EvalToolDetails>;
32
-
33
- type ManagedCell = {
34
- readonly cellId: string;
35
- readonly input: EvalToolInput;
36
- readonly spillPath: string | undefined;
37
- readonly startedAtMs: number;
38
- readonly terminal: PromiseWithResolvers<EvalDetachedCellSnapshot>;
39
- state: EvalDetachedCellState;
40
- canDetach: boolean;
41
- wasDetached: boolean;
42
- kernel: EvalKernel | undefined;
43
- stateRetained: boolean | undefined;
44
- interruptNote: string | undefined;
45
- /** Holds the completion notification until the interrupt has reported whether kernel state survived. */
46
- interruptOutcome: PromiseWithResolvers<void> | undefined;
47
- liveResult: LiveResultProvider | undefined;
48
- terminalResult: AgentToolResult<EvalToolDetails> | undefined;
49
- notificationQueued: boolean;
50
- readonly deadlines: CellDeadlines;
51
- readonly hardLimitSeconds: number;
52
- readonly runBudgetSeconds: number;
53
- hardLimited: boolean;
54
- runBudgetExhausted: boolean;
55
- /** Foreground killer: the still-awaited CellExecution owns interrupting and rejecting its own call; bound from creation so a deadline firing during kernel boot still ends it. */
56
- onKill: ((error: Error) => void) | undefined;
57
- };
58
-
59
31
  export class EvalDetachedCellManager {
60
32
  readonly #artifactsDir: string | undefined;
61
33
  readonly #onStatusChange: ((entries: readonly EvalDetachedCellStatusEntry[]) => void) | undefined;
62
34
  readonly #onWakeSourceState: ((state: WakeSourceState) => void) | undefined;
63
35
  readonly #cells = new Map<string, ManagedCell>();
64
36
  readonly #detachedByLanguage = new Map<EvalLanguage, ManagedCell>();
37
+ readonly #terminalSnapshots = new TerminalSnapshotStore();
65
38
  readonly #notificationQueue: DetachedNotificationQueue;
66
39
  readonly #now: () => number;
67
40
  readonly #hardLimitSeconds: number;
@@ -83,40 +56,20 @@ export class EvalDetachedCellManager {
83
56
  if (detachedCellIsActive(existing.state)) throw activeDetachedCellReuseError(existing);
84
57
  this.#cells.delete(cellId);
85
58
  }
86
- // An explicit longer per-call timeout raises the deadline, mirroring bash keeping explicit timeouts.
87
- const hardLimitSeconds = Math.max(this.#hardLimitSeconds, input.timeout ?? 0);
88
- const runBudgetSeconds = input.timeout ?? this.#runBudgetSeconds;
89
- const cell: ManagedCell = {
59
+ this.#terminalSnapshots.delete(cellId);
60
+ const cell = createManagedCell({
90
61
  cellId,
91
62
  input,
92
- spillPath: detachedNotificationSpillPath(this.#artifactsDir, cellId),
93
- startedAtMs: this.#now(),
94
- state: "running",
95
- canDetach: false,
96
- wasDetached: false,
97
- kernel: undefined,
98
- stateRetained: undefined,
99
- interruptNote: undefined,
100
- interruptOutcome: undefined,
101
- liveResult: undefined,
102
- terminalResult: undefined,
103
- notificationQueued: false,
104
- deadlines: new CellDeadlines({
105
- cellId,
106
- hardLimitSeconds,
107
- runBudgetSeconds,
108
- onExpire: (expiry) => {
109
- const managed = this.#cells.get(cellId);
110
- if (managed !== undefined) void this.#expireDeadline(managed, expiry);
111
- },
112
- }),
113
- hardLimitSeconds,
114
- runBudgetSeconds,
115
- hardLimited: false,
116
- runBudgetExhausted: false,
63
+ artifactsDir: this.#artifactsDir,
64
+ now: this.#now,
65
+ defaultHardLimitSeconds: this.#hardLimitSeconds,
66
+ defaultRunBudgetSeconds: this.#runBudgetSeconds,
117
67
  onKill,
118
- terminal: Promise.withResolvers<EvalDetachedCellSnapshot>(),
119
- };
68
+ onExpire: (expiredId, expiry) => {
69
+ const managed = this.#cells.get(expiredId);
70
+ if (managed !== undefined) void this.#expireDeadline(managed, expiry);
71
+ },
72
+ });
120
73
  this.#cells.set(cellId, cell);
121
74
  return cell;
122
75
  }
@@ -166,13 +119,15 @@ export class EvalDetachedCellManager {
166
119
  }
167
120
 
168
121
  async stop(cellId: string, reason = "Stopped detached eval cell"): Promise<EvalDetachedCellSnapshot> {
169
- const cell = this.#get(cellId);
170
- if (cell.state === "detached") await this.#cancel(cell, reason);
171
- return this.#snapshot(cell);
122
+ const live = this.#cells.get(cellId);
123
+ if (live === undefined) return this.#terminal(cellId);
124
+ if (live.state === "detached") await this.#cancel(live, reason);
125
+ return this.#snapshot(live);
172
126
  }
173
127
 
174
128
  peek(cellId: string): EvalDetachedCellSnapshot {
175
- return this.#snapshot(this.#get(cellId));
129
+ const live = this.#cells.get(cellId);
130
+ return live === undefined ? this.#terminal(cellId) : this.#snapshot(live);
176
131
  }
177
132
 
178
133
  busyFor(language: EvalLanguage): EvalDetachedCellSnapshot | undefined {
@@ -181,7 +136,8 @@ export class EvalDetachedCellManager {
181
136
  }
182
137
 
183
138
  async waitForTerminal(cellId: string): Promise<EvalDetachedCellSnapshot> {
184
- return await this.#get(cellId).terminal.promise;
139
+ const live = this.#cells.get(cellId);
140
+ return live === undefined ? this.#terminal(cellId) : await live.terminal.promise;
185
141
  }
186
142
 
187
143
  async dispose(): Promise<void> {
@@ -191,6 +147,8 @@ export class EvalDetachedCellManager {
191
147
  );
192
148
  if (detached.length === 0) this.#emitWakeSourceState([]);
193
149
  await this.#notificationQueue.flush();
150
+ this.#cells.clear();
151
+ this.#terminalSnapshots.clear();
194
152
  }
195
153
 
196
154
  async flushNotifications(): Promise<void> {
@@ -213,6 +171,8 @@ export class EvalDetachedCellManager {
213
171
  cell.terminalResult = result;
214
172
  cell.liveResult = undefined;
215
173
  cell.terminal.resolve(this.#snapshot(cell));
174
+ this.#cells.delete(cell.cellId);
175
+ this.#refreshTerminalSnapshot(cell);
216
176
  if (cell.wasDetached) {
217
177
  if (this.#detachedByLanguage.get(cell.input.language) === cell)
218
178
  this.#detachedByLanguage.delete(cell.input.language);
@@ -257,9 +217,14 @@ export class EvalDetachedCellManager {
257
217
  cell.stateRetained = await handle.stateRetained;
258
218
  } finally {
259
219
  outcome.resolve();
220
+ this.#refreshTerminalSnapshot(cell);
260
221
  }
261
222
  }
262
223
 
224
+ #refreshTerminalSnapshot(cell: ManagedCell): void {
225
+ if (!this.#cells.has(cell.cellId)) this.#terminalSnapshots.remember(this.#snapshot(cell));
226
+ }
227
+
263
228
  #emitStatus(): void {
264
229
  const liveCells = [...this.#detachedByLanguage.values()];
265
230
  this.#onStatusChange?.(detachedStatusEntries(liveCells));
@@ -274,9 +239,9 @@ export class EvalDetachedCellManager {
274
239
  return snapshotDetachedCell(cell, this.#now());
275
240
  }
276
241
 
277
- #get(cellId: string): ManagedCell {
278
- const cell = this.#cells.get(cellId);
279
- if (cell === undefined) throw new Error(`Unknown detached eval cell "${cellId}"`);
280
- return cell;
242
+ #terminal(cellId: string): EvalDetachedCellSnapshot {
243
+ const snapshot = this.#terminalSnapshots.get(cellId);
244
+ if (snapshot === undefined) throw new Error(`Unknown detached eval cell "${cellId}"`);
245
+ return snapshot;
281
246
  }
282
247
  }
@@ -0,0 +1,52 @@
1
+ import type { ExtensionContext } from "@code-yeongyu/senpi";
2
+ import { convertToPng, formatDimensionNote, resizeImage } from "@code-yeongyu/senpi";
3
+
4
+ export interface EvalImageContent {
5
+ readonly type: "image";
6
+ readonly data: string;
7
+ readonly mimeType: string;
8
+ }
9
+
10
+ export interface EvalImageResizeResult {
11
+ readonly image: EvalImageContent;
12
+ readonly dimensionNote?: string;
13
+ }
14
+
15
+ export type EvalImageResizer = (
16
+ image: EvalImageContent,
17
+ model: ExtensionContext["model"],
18
+ ) => Promise<EvalImageResizeResult>;
19
+
20
+ type WebpModel = { readonly provider: string; readonly api: string } | undefined;
21
+
22
+ export function webpExclusionForModel(model: WebpModel): true | undefined {
23
+ if (model === undefined) return undefined;
24
+ return model.provider === "ollama" ||
25
+ model.provider === "ollama-cloud" ||
26
+ model.provider === "llama.cpp" ||
27
+ model.provider === "lm-studio" ||
28
+ model.provider === "local-server" ||
29
+ model.api === "ollama-chat"
30
+ ? true
31
+ : undefined;
32
+ }
33
+
34
+ export const resizeEvalImage: EvalImageResizer = async (image, model) => {
35
+ const excludeWebP = webpExclusionForModel(model);
36
+ const forceWebpConversion = excludeWebP === true && image.mimeType === "image/webp";
37
+ const resized = await resizeImage(
38
+ Buffer.from(image.data, "base64"),
39
+ image.mimeType,
40
+ forceWebpConversion ? { maxBytes: Buffer.byteLength(image.data, "utf8") } : undefined,
41
+ );
42
+ let output: EvalImageContent =
43
+ resized === null ? image : { type: "image", data: resized.data, mimeType: resized.mimeType };
44
+ if (excludeWebP === true && output.mimeType === "image/webp") {
45
+ const converted = await convertToPng(output.data, output.mimeType);
46
+ if (converted === null)
47
+ throw new TypeError(`Unable to convert ${output.mimeType} display output for the active model`);
48
+ output = { type: "image", data: converted.data, mimeType: converted.mimeType };
49
+ }
50
+ const dimensionNote = resized === null ? undefined : formatDimensionNote(resized);
51
+ return { image: output, ...(dimensionNote === undefined ? {} : { dimensionNote }) };
52
+ };
@@ -1,6 +1,5 @@
1
1
  import { writeFile } from "node:fs/promises";
2
- import type { AgentToolResult, ExtensionContext } from "@code-yeongyu/senpi";
3
- import { convertToPng, formatDimensionNote, resizeImage } from "@code-yeongyu/senpi";
2
+ import type { ExtensionContext } from "@code-yeongyu/senpi";
4
3
  import type { KernelToHostMessage } from "../bridge/protocol.ts";
5
4
  import type { TruncationMeta } from "../output/output-meta.ts";
6
5
  import {
@@ -12,24 +11,24 @@ import {
12
11
  TailBuffer,
13
12
  truncateTail,
14
13
  } from "../output/streaming-output.ts";
14
+ import { type EvalImageContent, type EvalImageResizer, resizeEvalImage } from "./image-resize.ts";
15
15
 
16
- const MAX_DISPLAY_TEXT_BYTES = 8_000;
17
-
18
- export interface EvalImageContent {
19
- readonly type: "image";
20
- readonly data: string;
21
- readonly mimeType: string;
22
- }
16
+ export {
17
+ type EvalImageContent,
18
+ type EvalImageResizeResult,
19
+ type EvalImageResizer,
20
+ resizeEvalImage,
21
+ webpExclusionForModel,
22
+ } from "./image-resize.ts";
23
+ export { marshalToolResult, toolResultIsError } from "./tool-result-marshal.ts";
23
24
 
24
- export interface EvalImageResizeResult {
25
- readonly image: EvalImageContent;
26
- readonly dimensionNote?: string;
27
- }
25
+ const MAX_DISPLAY_TEXT_BYTES = 8_000;
28
26
 
29
- export type EvalImageResizer = (
30
- image: EvalImageContent,
31
- model: ExtensionContext["model"],
32
- ) => Promise<EvalImageResizeResult>;
27
+ // Per-cell display caps: display payloads are retained for the whole result lifetime, so an unbounded
28
+ // burst pins base64 originals plus resized copies on the session heap (#1695).
29
+ const MAX_DISPLAY_IMAGES_PER_CELL = 8;
30
+ const MAX_DISPLAY_IMAGE_BYTES_PER_CELL = 24 * 1024 * 1024;
31
+ const MAX_JSON_OUTPUTS_PER_CELL = 64;
33
32
 
34
33
  export interface EvalOutputOptions {
35
34
  readonly artifactPath?: string;
@@ -51,7 +50,6 @@ export interface EvalOutputResult {
51
50
  }
52
51
 
53
52
  type DisplayMessage = Extract<KernelToHostMessage, { type: "display" }>;
54
- type WebpModel = { readonly provider: string; readonly api: string } | undefined;
55
53
 
56
54
  class DisplayPayloadError extends Error {
57
55
  readonly name = "DisplayPayloadError";
@@ -69,6 +67,9 @@ export class EvalOutputCollector {
69
67
  readonly #displayImages: EvalImageContent[] = [];
70
68
  readonly #images: EvalImageContent[] = [];
71
69
  readonly #jsonOutputs: unknown[] = [];
70
+ #displayImageBytes = 0;
71
+ #displayImagesElided = 0;
72
+ #jsonOutputsElided = 0;
72
73
  #hasMarkdown = false;
73
74
  #imagesProcessed = false;
74
75
 
@@ -92,11 +93,23 @@ export class EvalOutputCollector {
92
93
 
93
94
  display(message: DisplayMessage): void {
94
95
  if (message.mimeType.startsWith("image/")) {
96
+ if (
97
+ this.#displayImages.length >= MAX_DISPLAY_IMAGES_PER_CELL ||
98
+ this.#displayImageBytes + message.dataBase64.length > MAX_DISPLAY_IMAGE_BYTES_PER_CELL
99
+ ) {
100
+ this.#displayImagesElided++;
101
+ return;
102
+ }
95
103
  this.#displayImages.push({ type: "image", mimeType: message.mimeType, data: message.dataBase64 });
104
+ this.#displayImageBytes += message.dataBase64.length;
96
105
  return;
97
106
  }
98
107
  const text = Buffer.from(message.dataBase64, "base64").toString("utf8");
99
108
  if (message.mimeType === "application/json") {
109
+ if (this.#jsonOutputs.length >= MAX_JSON_OUTPUTS_PER_CELL) {
110
+ this.#jsonOutputsElided++;
111
+ return;
112
+ }
100
113
  let value: unknown;
101
114
  try {
102
115
  value = JSON.parse(text);
@@ -118,6 +131,12 @@ export class EvalOutputCollector {
118
131
 
119
132
  async finish(): Promise<EvalOutputResult> {
120
133
  await this.#processImages();
134
+ const elided = this.#displayImagesElided + this.#jsonOutputsElided;
135
+ if (elided > 0) {
136
+ this.#sink.push(
137
+ `[${this.#displayImagesElided} display image(s) and ${this.#jsonOutputsElided} JSON output(s) elided beyond per-cell caps]\n`,
138
+ );
139
+ }
121
140
  const summary = await this.#finalSummary();
122
141
  const meta = truncationMetaFromSummary(summary, this.#options.maxColumns);
123
142
  const notice = summary.artifactId === undefined ? undefined : artifactNotice(summary.artifactId);
@@ -171,38 +190,6 @@ export class EvalOutputCollector {
171
190
  }
172
191
  }
173
192
 
174
- export function webpExclusionForModel(model: WebpModel): true | undefined {
175
- if (model === undefined) return undefined;
176
- return model.provider === "ollama" ||
177
- model.provider === "ollama-cloud" ||
178
- model.provider === "llama.cpp" ||
179
- model.provider === "lm-studio" ||
180
- model.provider === "local-server" ||
181
- model.api === "ollama-chat"
182
- ? true
183
- : undefined;
184
- }
185
-
186
- export const resizeEvalImage: EvalImageResizer = async (image, model) => {
187
- const excludeWebP = webpExclusionForModel(model);
188
- const forceWebpConversion = excludeWebP === true && image.mimeType === "image/webp";
189
- const resized = await resizeImage(
190
- Buffer.from(image.data, "base64"),
191
- image.mimeType,
192
- forceWebpConversion ? { maxBytes: Buffer.byteLength(image.data, "utf8") } : undefined,
193
- );
194
- let output: EvalImageContent =
195
- resized === null ? image : { type: "image", data: resized.data, mimeType: resized.mimeType };
196
- if (excludeWebP === true && output.mimeType === "image/webp") {
197
- const converted = await convertToPng(output.data, output.mimeType);
198
- if (converted === null)
199
- throw new TypeError(`Unable to convert ${output.mimeType} display output for the active model`);
200
- output = { type: "image", data: converted.data, mimeType: converted.mimeType };
201
- }
202
- const dimensionNote = resized === null ? undefined : formatDimensionNote(resized);
203
- return { image: output, ...(dimensionNote === undefined ? {} : { dimensionNote }) };
204
- };
205
-
206
193
  function formatDisplayJson(value: unknown): string {
207
194
  let text: string;
208
195
  try {
@@ -259,25 +246,3 @@ function truncationMetaFromSummary(summary: OutputSummary, maxColumns: number):
259
246
  ...artifact,
260
247
  };
261
248
  }
262
-
263
- export function marshalToolResult(result: AgentToolResult<unknown>) {
264
- const texts = result.content.filter((part) => part.type === "text").map((part) => part.text);
265
- const images = result.content
266
- .filter((part) => part.type === "image")
267
- .map((part) => ({ mimeType: part.mimeType, dataBase64: part.data }));
268
- const details =
269
- typeof result.details === "object" &&
270
- result.details !== null &&
271
- !Array.isArray(result.details) &&
272
- Object.keys(result.details).length === 0
273
- ? undefined
274
- : result.details;
275
- const hasError = toolResultIsError(result);
276
- const text = texts.join("\n");
277
- return images.length === 0 && details === undefined && !hasError ? { text } : { text, details, images, hasError };
278
- }
279
-
280
- export function toolResultIsError(result: AgentToolResult<unknown>): boolean {
281
- const details = result.details;
282
- return typeof details === "object" && details !== null && "isError" in details && details.isError === true;
283
- }