@code-yeongyu/senpi 2026.9.15 → 2026.9.16-3

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 (339) hide show
  1. package/CHANGELOG.md +89 -0
  2. package/README.md +1 -1
  3. package/dist/cli/help-extension-flags.d.ts +22 -0
  4. package/dist/cli/help-extension-flags.d.ts.map +1 -0
  5. package/dist/cli/help-extension-flags.js +30 -0
  6. package/dist/cli/help-extension-flags.js.map +1 -0
  7. package/dist/cli/help-fast-path.d.ts +19 -0
  8. package/dist/cli/help-fast-path.d.ts.map +1 -0
  9. package/dist/cli/help-fast-path.js +64 -0
  10. package/dist/cli/help-fast-path.js.map +1 -0
  11. package/dist/cli/help-flags-cache.d.ts +23 -0
  12. package/dist/cli/help-flags-cache.d.ts.map +1 -0
  13. package/dist/cli/help-flags-cache.js +118 -0
  14. package/dist/cli/help-flags-cache.js.map +1 -0
  15. package/dist/cli/startup-loading-indicator.d.ts.map +1 -1
  16. package/dist/cli/startup-loading-indicator.js +10 -4
  17. package/dist/cli/startup-loading-indicator.js.map +1 -1
  18. package/dist/cli.js +9 -0
  19. package/dist/cli.js.map +1 -1
  20. package/dist/core/agent-session.d.ts +17 -0
  21. package/dist/core/agent-session.d.ts.map +1 -1
  22. package/dist/core/agent-session.js +62 -8
  23. package/dist/core/agent-session.js.map +1 -1
  24. package/dist/core/compaction/compaction.d.ts.map +1 -1
  25. package/dist/core/compaction/compaction.js +4 -2
  26. package/dist/core/compaction/compaction.js.map +1 -1
  27. package/dist/core/compaction/stream-watchdog.d.ts +56 -2
  28. package/dist/core/compaction/stream-watchdog.d.ts.map +1 -1
  29. package/dist/core/compaction/stream-watchdog.js +93 -15
  30. package/dist/core/compaction/stream-watchdog.js.map +1 -1
  31. package/dist/core/extensions/builtin/claude-sdk-oauth/session-binding.d.ts +6 -0
  32. package/dist/core/extensions/builtin/claude-sdk-oauth/session-binding.d.ts.map +1 -1
  33. package/dist/core/extensions/builtin/claude-sdk-oauth/session-binding.js +17 -0
  34. package/dist/core/extensions/builtin/claude-sdk-oauth/session-binding.js.map +1 -1
  35. package/dist/core/extensions/builtin/claude-sdk-oauth/session-continuity.d.ts +4 -1
  36. package/dist/core/extensions/builtin/claude-sdk-oauth/session-continuity.d.ts.map +1 -1
  37. package/dist/core/extensions/builtin/claude-sdk-oauth/session-continuity.js +31 -0
  38. package/dist/core/extensions/builtin/claude-sdk-oauth/session-continuity.js.map +1 -1
  39. package/dist/core/extensions/builtin/claude-sdk-oauth/session-reattach.d.ts +2 -0
  40. package/dist/core/extensions/builtin/claude-sdk-oauth/session-reattach.d.ts.map +1 -1
  41. package/dist/core/extensions/builtin/claude-sdk-oauth/session-reattach.js +16 -0
  42. package/dist/core/extensions/builtin/claude-sdk-oauth/session-reattach.js.map +1 -1
  43. package/dist/core/extensions/builtin/claude-sdk-oauth/session-registry-wiring.d.ts.map +1 -1
  44. package/dist/core/extensions/builtin/claude-sdk-oauth/session-registry-wiring.js +19 -8
  45. package/dist/core/extensions/builtin/claude-sdk-oauth/session-registry-wiring.js.map +1 -1
  46. package/dist/core/extensions/builtin/claude-sdk-oauth/session-stream.d.ts.map +1 -1
  47. package/dist/core/extensions/builtin/claude-sdk-oauth/session-stream.js +2 -1
  48. package/dist/core/extensions/builtin/claude-sdk-oauth/session-stream.js.map +1 -1
  49. package/dist/core/extensions/builtin/compaction/deterministic-fallback.d.ts +13 -1
  50. package/dist/core/extensions/builtin/compaction/deterministic-fallback.d.ts.map +1 -1
  51. package/dist/core/extensions/builtin/compaction/deterministic-fallback.js +66 -2
  52. package/dist/core/extensions/builtin/compaction/deterministic-fallback.js.map +1 -1
  53. package/dist/core/extensions/builtin/compaction/index.d.ts.map +1 -1
  54. package/dist/core/extensions/builtin/compaction/index.js +16 -10
  55. package/dist/core/extensions/builtin/compaction/index.js.map +1 -1
  56. package/dist/core/extensions/builtin/compaction/speculative-summary.d.ts.map +1 -1
  57. package/dist/core/extensions/builtin/compaction/speculative-summary.js +4 -2
  58. package/dist/core/extensions/builtin/compaction/speculative-summary.js.map +1 -1
  59. package/dist/core/extensions/builtin/compaction/speculative.d.ts +8 -1
  60. package/dist/core/extensions/builtin/compaction/speculative.d.ts.map +1 -1
  61. package/dist/core/extensions/builtin/compaction/speculative.js +31 -7
  62. package/dist/core/extensions/builtin/compaction/speculative.js.map +1 -1
  63. package/dist/core/extensions/builtin/compaction/transient-failure.d.ts.map +1 -1
  64. package/dist/core/extensions/builtin/compaction/transient-failure.js +6 -3
  65. package/dist/core/extensions/builtin/compaction/transient-failure.js.map +1 -1
  66. package/dist/core/extensions/builtin/config-reload/index.d.ts.map +1 -1
  67. package/dist/core/extensions/builtin/config-reload/index.js +25 -8
  68. package/dist/core/extensions/builtin/config-reload/index.js.map +1 -1
  69. package/dist/core/extensions/builtin/config-reload/watch-engine.d.ts +4 -5
  70. package/dist/core/extensions/builtin/config-reload/watch-engine.d.ts.map +1 -1
  71. package/dist/core/extensions/builtin/config-reload/watch-engine.js +14 -18
  72. package/dist/core/extensions/builtin/config-reload/watch-engine.js.map +1 -1
  73. package/dist/core/extensions/builtin/config-reload/watch-event-source.d.ts.map +1 -1
  74. package/dist/core/extensions/builtin/config-reload/watch-event-source.js +32 -9
  75. package/dist/core/extensions/builtin/config-reload/watch-event-source.js.map +1 -1
  76. package/dist/core/extensions/builtin/cursor-cli-oauth/catalog-refresh.d.ts +23 -0
  77. package/dist/core/extensions/builtin/cursor-cli-oauth/catalog-refresh.d.ts.map +1 -0
  78. package/dist/core/extensions/builtin/cursor-cli-oauth/catalog-refresh.js +48 -0
  79. package/dist/core/extensions/builtin/cursor-cli-oauth/catalog-refresh.js.map +1 -0
  80. package/dist/core/extensions/builtin/cursor-cli-oauth/environment.d.ts +13 -0
  81. package/dist/core/extensions/builtin/cursor-cli-oauth/environment.d.ts.map +1 -0
  82. package/dist/core/extensions/builtin/cursor-cli-oauth/environment.js +24 -0
  83. package/dist/core/extensions/builtin/cursor-cli-oauth/environment.js.map +1 -0
  84. package/dist/core/extensions/builtin/cursor-cli-oauth/executable.d.ts +1 -0
  85. package/dist/core/extensions/builtin/cursor-cli-oauth/executable.d.ts.map +1 -1
  86. package/dist/core/extensions/builtin/cursor-cli-oauth/executable.js +7 -1
  87. package/dist/core/extensions/builtin/cursor-cli-oauth/executable.js.map +1 -1
  88. package/dist/core/extensions/builtin/cursor-cli-oauth/index.d.ts +2 -0
  89. package/dist/core/extensions/builtin/cursor-cli-oauth/index.d.ts.map +1 -1
  90. package/dist/core/extensions/builtin/cursor-cli-oauth/index.js +11 -9
  91. package/dist/core/extensions/builtin/cursor-cli-oauth/index.js.map +1 -1
  92. package/dist/core/extensions/builtin/cursor-cli-oauth/models-probe.d.ts +18 -0
  93. package/dist/core/extensions/builtin/cursor-cli-oauth/models-probe.d.ts.map +1 -0
  94. package/dist/core/extensions/builtin/cursor-cli-oauth/models-probe.js +61 -0
  95. package/dist/core/extensions/builtin/cursor-cli-oauth/models-probe.js.map +1 -0
  96. package/dist/core/extensions/builtin/cursor-cli-oauth/models.d.ts +4 -1
  97. package/dist/core/extensions/builtin/cursor-cli-oauth/models.d.ts.map +1 -1
  98. package/dist/core/extensions/builtin/cursor-cli-oauth/models.js +1 -44
  99. package/dist/core/extensions/builtin/cursor-cli-oauth/models.js.map +1 -1
  100. package/dist/core/extensions/builtin/cursor-cli-oauth/oauth-login.d.ts +22 -0
  101. package/dist/core/extensions/builtin/cursor-cli-oauth/oauth-login.d.ts.map +1 -1
  102. package/dist/core/extensions/builtin/cursor-cli-oauth/oauth-login.js +1 -1
  103. package/dist/core/extensions/builtin/cursor-cli-oauth/oauth-login.js.map +1 -1
  104. package/dist/core/extensions/builtin/cursor-cli-oauth/transport.d.ts.map +1 -1
  105. package/dist/core/extensions/builtin/cursor-cli-oauth/transport.js +2 -13
  106. package/dist/core/extensions/builtin/cursor-cli-oauth/transport.js.map +1 -1
  107. package/dist/core/extensions/builtin/terminal/monitor-file-digest.d.ts +3 -0
  108. package/dist/core/extensions/builtin/terminal/monitor-file-digest.d.ts.map +1 -0
  109. package/dist/core/extensions/builtin/terminal/monitor-file-digest.js +25 -0
  110. package/dist/core/extensions/builtin/terminal/monitor-file-digest.js.map +1 -0
  111. package/dist/core/extensions/builtin/terminal/monitor-file-watch.d.ts +9 -0
  112. package/dist/core/extensions/builtin/terminal/monitor-file-watch.d.ts.map +1 -0
  113. package/dist/core/extensions/builtin/terminal/monitor-file-watch.js +33 -0
  114. package/dist/core/extensions/builtin/terminal/monitor-file-watch.js.map +1 -0
  115. package/dist/core/extensions/builtin/terminal/monitor-line-buffer.d.ts +6 -0
  116. package/dist/core/extensions/builtin/terminal/monitor-line-buffer.d.ts.map +1 -0
  117. package/dist/core/extensions/builtin/terminal/monitor-line-buffer.js +22 -0
  118. package/dist/core/extensions/builtin/terminal/monitor-line-buffer.js.map +1 -0
  119. package/dist/core/extensions/builtin/terminal/monitor-registry.d.ts.map +1 -1
  120. package/dist/core/extensions/builtin/terminal/monitor-registry.js +14 -35
  121. package/dist/core/extensions/builtin/terminal/monitor-registry.js.map +1 -1
  122. package/dist/core/extensions/builtin/ttsr/detectors/collapse-near-duplicates.d.ts +38 -0
  123. package/dist/core/extensions/builtin/ttsr/detectors/collapse-near-duplicates.d.ts.map +1 -0
  124. package/dist/core/extensions/builtin/ttsr/detectors/collapse-near-duplicates.js +155 -0
  125. package/dist/core/extensions/builtin/ttsr/detectors/collapse-near-duplicates.js.map +1 -0
  126. package/dist/core/extensions/builtin/ttsr/detectors/collapse.d.ts +2 -0
  127. package/dist/core/extensions/builtin/ttsr/detectors/collapse.d.ts.map +1 -1
  128. package/dist/core/extensions/builtin/ttsr/detectors/collapse.js +4 -1
  129. package/dist/core/extensions/builtin/ttsr/detectors/collapse.js.map +1 -1
  130. package/dist/core/extensions/kernel-tools-context.d.ts +13 -0
  131. package/dist/core/extensions/kernel-tools-context.d.ts.map +1 -0
  132. package/dist/core/extensions/kernel-tools-context.js +3 -0
  133. package/dist/core/extensions/kernel-tools-context.js.map +1 -0
  134. package/dist/core/extensions/runner.d.ts +19 -0
  135. package/dist/core/extensions/runner.d.ts.map +1 -1
  136. package/dist/core/extensions/runner.js +103 -1
  137. package/dist/core/extensions/runner.js.map +1 -1
  138. package/dist/core/extensions/types.d.ts +21 -0
  139. package/dist/core/extensions/types.d.ts.map +1 -1
  140. package/dist/core/extensions/types.js.map +1 -1
  141. package/dist/core/keybindings.d.ts +5 -0
  142. package/dist/core/keybindings.d.ts.map +1 -1
  143. package/dist/core/keybindings.js +1 -0
  144. package/dist/core/keybindings.js.map +1 -1
  145. package/dist/core/resource-loader.d.ts.map +1 -1
  146. package/dist/core/resource-loader.js +9 -17
  147. package/dist/core/resource-loader.js.map +1 -1
  148. package/dist/core/session-manager.d.ts +30 -1
  149. package/dist/core/session-manager.d.ts.map +1 -1
  150. package/dist/core/session-manager.js +92 -5
  151. package/dist/core/session-manager.js.map +1 -1
  152. package/dist/core/session-resident-store.d.ts +33 -7
  153. package/dist/core/session-resident-store.d.ts.map +1 -1
  154. package/dist/core/session-resident-store.js +163 -14
  155. package/dist/core/session-resident-store.js.map +1 -1
  156. package/dist/core/session-write-reservation.d.ts +6 -0
  157. package/dist/core/session-write-reservation.d.ts.map +1 -1
  158. package/dist/core/session-write-reservation.js +19 -0
  159. package/dist/core/session-write-reservation.js.map +1 -1
  160. package/dist/core/settings-manager.d.ts +23 -0
  161. package/dist/core/settings-manager.d.ts.map +1 -1
  162. package/dist/core/settings-manager.js +41 -0
  163. package/dist/core/settings-manager.js.map +1 -1
  164. package/dist/core/slash-commands.d.ts.map +1 -1
  165. package/dist/core/slash-commands.js +2 -1
  166. package/dist/core/slash-commands.js.map +1 -1
  167. package/dist/core/tools/index.d.ts +1 -1
  168. package/dist/core/tools/index.d.ts.map +1 -1
  169. package/dist/core/tools/index.js +4 -3
  170. package/dist/core/tools/index.js.map +1 -1
  171. package/dist/core/tools/read.d.ts +3 -1
  172. package/dist/core/tools/read.d.ts.map +1 -1
  173. package/dist/core/tools/read.js +16 -2
  174. package/dist/core/tools/read.js.map +1 -1
  175. package/dist/index.d.ts +1 -0
  176. package/dist/index.d.ts.map +1 -1
  177. package/dist/index.js +1 -0
  178. package/dist/index.js.map +1 -1
  179. package/dist/main.d.ts.map +1 -1
  180. package/dist/main.js +31 -3
  181. package/dist/main.js.map +1 -1
  182. package/dist/modes/interactive/components/assistant-render-descriptors.d.ts.map +1 -1
  183. package/dist/modes/interactive/components/assistant-render-descriptors.js +12 -5
  184. package/dist/modes/interactive/components/assistant-render-descriptors.js.map +1 -1
  185. package/dist/modes/interactive/components/extension-input.d.ts +2 -0
  186. package/dist/modes/interactive/components/extension-input.d.ts.map +1 -1
  187. package/dist/modes/interactive/components/extension-input.js +5 -0
  188. package/dist/modes/interactive/components/extension-input.js.map +1 -1
  189. package/dist/modes/interactive/interactive-mode.d.ts +7 -1
  190. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  191. package/dist/modes/interactive/interactive-mode.js +35 -16
  192. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  193. package/dist/modes/interactive/tips/catalog/session-tips.js +1 -1
  194. package/dist/modes/interactive/tips/catalog/session-tips.js.map +1 -1
  195. package/dist/modes/print-mode.d.ts +1 -1
  196. package/dist/modes/print-mode.d.ts.map +1 -1
  197. package/dist/modes/print-mode.js +5 -1
  198. package/dist/modes/print-mode.js.map +1 -1
  199. package/dist/modes/rpc/host-watchdog.d.ts.map +1 -1
  200. package/dist/modes/rpc/host-watchdog.js +5 -24
  201. package/dist/modes/rpc/host-watchdog.js.map +1 -1
  202. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  203. package/dist/modes/rpc/rpc-mode.js +3 -8
  204. package/dist/modes/rpc/rpc-mode.js.map +1 -1
  205. package/dist/modes/rpc/session-command-router.d.ts.map +1 -1
  206. package/dist/modes/rpc/session-command-router.js +3 -0
  207. package/dist/modes/rpc/session-command-router.js.map +1 -1
  208. package/dist/modes/rpc/session-worker-client.d.ts +3 -0
  209. package/dist/modes/rpc/session-worker-client.d.ts.map +1 -1
  210. package/dist/modes/rpc/session-worker-client.js +17 -6
  211. package/dist/modes/rpc/session-worker-client.js.map +1 -1
  212. package/dist/modes/rpc/shutdown.d.ts +3 -0
  213. package/dist/modes/rpc/shutdown.d.ts.map +1 -0
  214. package/dist/modes/rpc/shutdown.js +17 -0
  215. package/dist/modes/rpc/shutdown.js.map +1 -0
  216. package/dist/senpi +9 -0
  217. package/docs/extensions.md +5 -2
  218. package/docs/keybindings.md +1 -0
  219. package/docs/session-format.md +1 -1
  220. package/docs/sessions.md +3 -3
  221. package/docs/settings.md +12 -0
  222. package/docs/usage.md +1 -1
  223. package/node_modules/@code-yeongyu/senpi-codemode/CHANGELOG.md +62 -0
  224. package/node_modules/@code-yeongyu/senpi-codemode/README.md +24 -0
  225. package/node_modules/@code-yeongyu/senpi-codemode/package.json +4 -4
  226. package/node_modules/@code-yeongyu/senpi-codemode/src/bridge/http-server.ts +7 -1
  227. package/node_modules/@code-yeongyu/senpi-codemode/src/bridge/kernel-tools-protocol.ts +85 -0
  228. package/node_modules/@code-yeongyu/senpi-codemode/src/bridge/protocol.ts +6 -0
  229. package/node_modules/@code-yeongyu/senpi-codemode/src/bridges/agent-bridge.ts +30 -11
  230. package/node_modules/@code-yeongyu/senpi-codemode/src/extension/session-manager.ts +11 -0
  231. package/node_modules/@code-yeongyu/senpi-codemode/src/index.ts +10 -0
  232. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/jl/prelude.jl +46 -0
  233. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/jl/runner.jl +1 -1
  234. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/context-manager.ts +58 -10
  235. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/kernel-contract.ts +12 -0
  236. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/kernel-tools-context.d.ts +13 -0
  237. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/kernel-tools-context.js +7 -0
  238. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/kernel-tools-define.d.ts +10 -0
  239. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/kernel-tools-define.js +12 -0
  240. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/kernel-tools-errors.js +22 -0
  241. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/kernel-tools-errors.ts +26 -0
  242. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/kernel-tools-host.ts +134 -0
  243. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/kernel-tools-naming.js +28 -0
  244. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/kernel-tools-parse.d.ts +5 -0
  245. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/kernel-tools-parse.js +255 -0
  246. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/kernel-tools-pump.js +105 -0
  247. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/kernel-tools-registry.d.ts +27 -0
  248. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/kernel-tools-registry.js +127 -0
  249. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/kernel-tools-schema.js +69 -0
  250. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/kernel-tools-types.ts +42 -0
  251. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/prelude.ts +2 -0
  252. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/worker-core.js +26 -3
  253. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/worker-runtime.js +20 -8
  254. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/worker-slot.ts +6 -1
  255. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/worker-startup.ts +9 -7
  256. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/workpool.js +37 -0
  257. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/kernel-tools-unavailable.ts +5 -0
  258. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/py/kernel.ts +13 -0
  259. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/py/prelude.py +57 -3
  260. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/rb/prelude.rb +13 -2
  261. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/rb/workpool.rb +38 -0
  262. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/shared/subprocess-kernel.ts +13 -0
  263. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/shared/subprocess-queue.ts +9 -1
  264. package/node_modules/@code-yeongyu/senpi-codemode/src/prompt/eval-prompt-template.ts +4 -2
  265. package/node_modules/@code-yeongyu/senpi-codemode/src/tool/cell-handler.ts +31 -3
  266. package/node_modules/@code-yeongyu/senpi-codemode/src/tool/detached-cell-manager.ts +36 -71
  267. package/node_modules/@code-yeongyu/senpi-codemode/src/tool/image-resize.ts +52 -0
  268. package/node_modules/@code-yeongyu/senpi-codemode/src/tool/image.ts +37 -72
  269. package/node_modules/@code-yeongyu/senpi-codemode/src/tool/managed-cell.ts +78 -0
  270. package/node_modules/@code-yeongyu/senpi-codemode/src/tool/render.ts +30 -5
  271. package/node_modules/@code-yeongyu/senpi-codemode/src/tool/run-eval-cell.ts +52 -30
  272. package/node_modules/@code-yeongyu/senpi-codemode/src/tool/terminal-snapshot-store.ts +35 -0
  273. package/node_modules/@code-yeongyu/senpi-codemode/src/tool/tool-result-marshal.ts +23 -0
  274. package/node_modules/@code-yeongyu/senpi-codemode/src/tool/types.ts +2 -0
  275. package/node_modules/@earendil-works/pi-agent-core/dist/empty-assistant-recovery.d.ts.map +1 -1
  276. package/node_modules/@earendil-works/pi-agent-core/dist/empty-assistant-recovery.js +51 -12
  277. package/node_modules/@earendil-works/pi-agent-core/dist/empty-assistant-recovery.js.map +1 -1
  278. package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/read.d.ts +3 -0
  279. package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/read.d.ts.map +1 -1
  280. package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/read.js +18 -3
  281. package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/read.js.map +1 -1
  282. package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/read-folders/brace-scanner.d.ts +4 -0
  283. package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/read-folders/brace-scanner.d.ts.map +1 -0
  284. package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/read-folders/brace-scanner.js +268 -0
  285. package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/read-folders/brace-scanner.js.map +1 -0
  286. package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/read-folders/header-protection.d.ts +18 -0
  287. package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/read-folders/header-protection.d.ts.map +1 -0
  288. package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/read-folders/header-protection.js +69 -0
  289. package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/read-folders/header-protection.js.map +1 -0
  290. package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/read-folders/index.d.ts +27 -0
  291. package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/read-folders/index.d.ts.map +1 -0
  292. package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/read-folders/index.js +118 -0
  293. package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/read-folders/index.js.map +1 -0
  294. package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/read-folders/lexical-context.d.ts +21 -0
  295. package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/read-folders/lexical-context.d.ts.map +1 -0
  296. package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/read-folders/lexical-context.js +24 -0
  297. package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/read-folders/lexical-context.js.map +1 -0
  298. package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/read-folders/lexical-spans.d.ts +12 -0
  299. package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/read-folders/lexical-spans.d.ts.map +1 -0
  300. package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/read-folders/lexical-spans.js +92 -0
  301. package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/read-folders/lexical-spans.js.map +1 -0
  302. package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/read-folders/types.d.ts +47 -0
  303. package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/read-folders/types.d.ts.map +1 -0
  304. package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/read-folders/types.js +8 -0
  305. package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/read-folders/types.js.map +1 -0
  306. package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/segmented-read-view.d.ts +52 -0
  307. package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/segmented-read-view.d.ts.map +1 -0
  308. package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/segmented-read-view.js +155 -0
  309. package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/segmented-read-view.js.map +1 -0
  310. package/node_modules/@earendil-works/pi-agent-core/dist/index.d.ts +2 -0
  311. package/node_modules/@earendil-works/pi-agent-core/dist/index.d.ts.map +1 -1
  312. package/node_modules/@earendil-works/pi-agent-core/dist/index.js +2 -0
  313. package/node_modules/@earendil-works/pi-agent-core/dist/index.js.map +1 -1
  314. package/node_modules/@earendil-works/pi-agent-core/package.json +3 -3
  315. package/node_modules/@earendil-works/pi-ai/dist/index.d.ts +1 -0
  316. package/node_modules/@earendil-works/pi-ai/dist/index.d.ts.map +1 -1
  317. package/node_modules/@earendil-works/pi-ai/dist/index.js +1 -0
  318. package/node_modules/@earendil-works/pi-ai/dist/index.js.map +1 -1
  319. package/node_modules/@earendil-works/pi-ai/dist/providers/data/.manifest.json +1 -1
  320. package/node_modules/@earendil-works/pi-ai/dist/providers/data/alibaba-token-plan.json +1 -1
  321. package/node_modules/@earendil-works/pi-ai/dist/providers/data/amazon-bedrock.json +1 -1
  322. package/node_modules/@earendil-works/pi-ai/dist/providers/data/fireworks.json +1 -1
  323. package/node_modules/@earendil-works/pi-ai/dist/providers/data/openrouter.json +1 -1
  324. package/node_modules/@earendil-works/pi-ai/dist/providers/data/qwen-token-plan.json +1 -1
  325. package/node_modules/@earendil-works/pi-ai/dist/providers/data/venice.json +1 -1
  326. package/node_modules/@earendil-works/pi-ai/dist/providers/data/vercel-ai-gateway.json +1 -1
  327. package/node_modules/@earendil-works/pi-ai/dist/utils/empty-response-errors.d.ts +15 -0
  328. package/node_modules/@earendil-works/pi-ai/dist/utils/empty-response-errors.d.ts.map +1 -0
  329. package/node_modules/@earendil-works/pi-ai/dist/utils/empty-response-errors.js +15 -0
  330. package/node_modules/@earendil-works/pi-ai/dist/utils/empty-response-errors.js.map +1 -0
  331. package/node_modules/@earendil-works/pi-ai/dist/utils/retry.d.ts +23 -0
  332. package/node_modules/@earendil-works/pi-ai/dist/utils/retry.d.ts.map +1 -1
  333. package/node_modules/@earendil-works/pi-ai/dist/utils/retry.js +74 -0
  334. package/node_modules/@earendil-works/pi-ai/dist/utils/retry.js.map +1 -1
  335. package/node_modules/@earendil-works/pi-ai/package.json +2 -2
  336. package/node_modules/@earendil-works/pi-pty/package.json +1 -1
  337. package/node_modules/@earendil-works/pi-telemetry/package.json +1 -1
  338. package/node_modules/@earendil-works/pi-tui/package.json +1 -1
  339. package/package.json +7 -7
@@ -0,0 +1,78 @@
1
+ import type { AgentToolResult } from "@code-yeongyu/senpi";
2
+ import { type CellDeadlineExpiry, CellDeadlines } from "./cell-deadlines.ts";
3
+ import type { EvalDetachedCellSnapshot, EvalDetachedCellState } from "./detached-cell-contract.ts";
4
+ import { detachedNotificationSpillPath } from "./detached-cell-notification.ts";
5
+ import type { EvalKernel, EvalToolDetails, EvalToolInput } from "./types.ts";
6
+
7
+ export type LiveResultProvider = () => AgentToolResult<EvalToolDetails>;
8
+
9
+ export type ManagedCell = {
10
+ readonly cellId: string;
11
+ readonly input: EvalToolInput;
12
+ readonly spillPath: string | undefined;
13
+ readonly startedAtMs: number;
14
+ readonly terminal: PromiseWithResolvers<EvalDetachedCellSnapshot>;
15
+ state: EvalDetachedCellState;
16
+ canDetach: boolean;
17
+ wasDetached: boolean;
18
+ kernel: EvalKernel | undefined;
19
+ stateRetained: boolean | undefined;
20
+ interruptNote: string | undefined;
21
+ /** Holds the completion notification until the interrupt has reported whether kernel state survived. */
22
+ interruptOutcome: PromiseWithResolvers<void> | undefined;
23
+ liveResult: LiveResultProvider | undefined;
24
+ terminalResult: AgentToolResult<EvalToolDetails> | undefined;
25
+ notificationQueued: boolean;
26
+ readonly deadlines: CellDeadlines;
27
+ readonly hardLimitSeconds: number;
28
+ readonly runBudgetSeconds: number;
29
+ hardLimited: boolean;
30
+ runBudgetExhausted: boolean;
31
+ /** 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. */
32
+ onKill: ((error: Error) => void) | undefined;
33
+ };
34
+
35
+ export interface ManagedCellInit {
36
+ readonly cellId: string;
37
+ readonly input: EvalToolInput;
38
+ readonly artifactsDir: string | undefined;
39
+ readonly now: () => number;
40
+ readonly defaultHardLimitSeconds: number;
41
+ readonly defaultRunBudgetSeconds: number;
42
+ readonly onKill: ((error: Error) => void) | undefined;
43
+ readonly onExpire: (cellId: string, expiry: CellDeadlineExpiry) => void;
44
+ }
45
+
46
+ export function createManagedCell(init: ManagedCellInit): ManagedCell {
47
+ // An explicit longer per-call timeout raises the deadline, mirroring bash keeping explicit timeouts.
48
+ const hardLimitSeconds = Math.max(init.defaultHardLimitSeconds, init.input.timeout ?? 0);
49
+ const runBudgetSeconds = init.input.timeout ?? init.defaultRunBudgetSeconds;
50
+ return {
51
+ cellId: init.cellId,
52
+ input: init.input,
53
+ spillPath: detachedNotificationSpillPath(init.artifactsDir, init.cellId),
54
+ startedAtMs: init.now(),
55
+ state: "running",
56
+ canDetach: false,
57
+ wasDetached: false,
58
+ kernel: undefined,
59
+ stateRetained: undefined,
60
+ interruptNote: undefined,
61
+ interruptOutcome: undefined,
62
+ liveResult: undefined,
63
+ terminalResult: undefined,
64
+ notificationQueued: false,
65
+ deadlines: new CellDeadlines({
66
+ cellId: init.cellId,
67
+ hardLimitSeconds,
68
+ runBudgetSeconds,
69
+ onExpire: (expiry) => init.onExpire(init.cellId, expiry),
70
+ }),
71
+ hardLimitSeconds,
72
+ runBudgetSeconds,
73
+ hardLimited: false,
74
+ runBudgetExhausted: false,
75
+ onKill: init.onKill,
76
+ terminal: Promise.withResolvers<EvalDetachedCellSnapshot>(),
77
+ };
78
+ }
@@ -71,10 +71,15 @@ const TOOL_CALL_COLLAPSED_VISUAL_LINES = 4;
71
71
  const TOOL_CALL_COLLAPSED_ERROR_CODE_POINTS = 512;
72
72
  const TOOL_ERROR_OMISSION_MARKER = "[tool error omitted]";
73
73
  const LIVE_ELAPSED_TICK_MS = 1_000;
74
+ // A live row repaints on every tick, so this many ticks without a render means the row is gone.
75
+ const LIVE_TICKER_MAX_IDLE_TICKS = 60;
74
76
 
75
77
  class PlainTextComponent implements EvalRenderComponent {
76
78
  #blocks: readonly RenderBlock[] = [];
77
79
  #ticker: ReturnType<typeof setInterval> | undefined;
80
+ #live = false;
81
+ #invalidate: (() => void) | undefined;
82
+ #idleTicks = 0;
78
83
 
79
84
  setBlocks(blocks: readonly RenderBlock[]): void {
80
85
  this.#blocks = blocks;
@@ -84,16 +89,19 @@ class PlainTextComponent implements EvalRenderComponent {
84
89
  * The host only animates tool rows for streaming args, `task`, and results carrying
85
90
  * `details.progress`; an eval row matches none of them, so nothing repaints it between
86
91
  * update events. While a cell is non-terminal this drives the repaint itself so the
87
- * header's elapsed time advances, and it emits no tool updates or RPC traffic.
92
+ * header's elapsed time advances, and it emits no tool updates or RPC traffic. Detached
93
+ * and terminal cards never arm it, and a ticker whose row stopped rendering (transcript
94
+ * rebuild, session switch) stops itself after LIVE_TICKER_MAX_IDLE_TICKS and rearms on
95
+ * the next render, so dropped rows cannot accumulate intervals.
88
96
  */
89
97
  syncLiveTicker(isLive: boolean, invalidate: () => void): void {
98
+ this.#live = isLive;
99
+ this.#invalidate = invalidate;
90
100
  if (!isLive) {
91
101
  this.stopLiveTicker();
92
102
  return;
93
103
  }
94
- if (this.#ticker !== undefined) return;
95
- this.#ticker = setInterval(invalidate, LIVE_ELAPSED_TICK_MS);
96
- this.#ticker.unref?.();
104
+ this.#armTicker();
97
105
  }
98
106
 
99
107
  stopLiveTicker(): void {
@@ -102,7 +110,24 @@ class PlainTextComponent implements EvalRenderComponent {
102
110
  this.#ticker = undefined;
103
111
  }
104
112
 
113
+ #armTicker(): void {
114
+ if (this.#ticker !== undefined || this.#invalidate === undefined) return;
115
+ this.#ticker = setInterval(() => this.#tick(), LIVE_ELAPSED_TICK_MS);
116
+ this.#ticker.unref?.();
117
+ }
118
+
119
+ #tick(): void {
120
+ this.#idleTicks += 1;
121
+ if (this.#idleTicks >= LIVE_TICKER_MAX_IDLE_TICKS) {
122
+ this.stopLiveTicker();
123
+ return;
124
+ }
125
+ this.#invalidate?.();
126
+ }
127
+
105
128
  render(width: number): string[] {
129
+ this.#idleTicks = 0;
130
+ if (this.#live) this.#armTicker();
106
131
  const lines: string[] = [];
107
132
  for (const block of this.#blocks) {
108
133
  switch (block.kind) {
@@ -269,7 +294,7 @@ function spinner(frame: number | undefined): string {
269
294
  }
270
295
 
271
296
  function isLiveCellStatus(status: CellStatus): boolean {
272
- return status === "pending" || status === "running" || status === "detached";
297
+ return status === "pending" || status === "running";
273
298
  }
274
299
 
275
300
  function cellPresentation(status: CellStatus, spinnerFrame: number | undefined): StatusPresentation {
@@ -1,7 +1,8 @@
1
1
  import { randomUUID } from "node:crypto";
2
2
  import { join } from "node:path";
3
- import type { AgentToolResult, ExtensionContext } from "@code-yeongyu/senpi";
3
+ import { type AgentToolResult, type ExtensionContext, kernelToolsStorage } from "@code-yeongyu/senpi";
4
4
  import { DEFAULT_FOREGROUND_WINDOW_SECONDS, defaultCodemodeSettings } from "../config/settings.ts";
5
+ import type { KernelToolsCapability } from "../kernels/js/kernel-tools-types.ts";
5
6
  import { TIMEOUT_PAUSE_OP, TIMEOUT_RESUME_OP } from "../timeouts/bridge-timeout.ts";
6
7
  import { abortError, CellExecution, defaultTimeoutFactory } from "./cell-execution.ts";
7
8
  import { CellHandler, type CellState } from "./cell-handler.ts";
@@ -11,7 +12,7 @@ import { buildEvalExecutionEventPayload, type EvalExecutionSettleOutcome } from
11
12
  import { evalTimeoutBehavior } from "./eval-request.ts";
12
13
  import type { CreateEvalToolOptions, EvalCellInvocation } from "./eval-tool-options.ts";
13
14
  import { describeTimeoutState } from "./interrupt-note.ts";
14
- import type { EvalToolDetails } from "./types.ts";
15
+ import type { EvalKernel, EvalToolDetails } from "./types.ts";
15
16
 
16
17
  export async function runEvalCell(
17
18
  options: CreateEvalToolOptions,
@@ -166,34 +167,42 @@ async function executeCell(
166
167
  void pending.catch((error: unknown) => execution.cancel(error));
167
168
  }),
168
169
  );
169
- execution.setKernel(kernel);
170
- const activeHandler = new CellHandler(kernel, state, {
171
- executeTool: options.executeTool,
172
- ...(options.listTools === undefined ? {} : { listTools: options.listTools }),
173
- settings: options.settings ?? defaultCodemodeSettings,
174
- ...(options.complete === undefined ? {} : { complete: options.complete }),
175
- ctx: bridgeContext,
176
- ...(options.artifactsDir === undefined
177
- ? {}
178
- : { artifactPath: join(options.artifactsDir, `eval-${randomUUID()}.log`) }),
179
- ...(options.imageResizer === undefined ? {} : { imageResizer: options.imageResizer }),
180
- });
181
- handler = activeHandler;
182
- cellManager.markRunning(
183
- cell,
184
- kernel,
185
- () => activeHandler.liveResult(),
186
- (error) => execution.cancel(error),
187
- );
188
- // Includes a steer already queued at execute start or received while acquiring the kernel.
189
- onReady();
190
- if ("setContext" in options.kernelManager && typeof options.kernelManager.setContext === "function") {
191
- options.kernelManager.setContext(bridgeContext);
192
- }
193
- if (invocation.input.reset) await execution.wait(kernel.reset());
194
- const result = await execution.wait(kernel.run({ cellId: invocation.cellId, code: invocation.input.code }));
195
- if (result.ok && state.pendingBridgeCalls.length > 0) await execution.wait(Promise.all(state.pendingBridgeCalls));
196
- return await handler.finalize(result);
170
+ // Computed before the handler so the cell's capability can be entered per host tool call from the
171
+ // worker's message loop, which runs outside the `kernelToolsStorage.run` context below (#1754).
172
+ const kernelTools = jsKernelTools(kernel, invocation.input.language);
173
+ const runBound = async (): Promise<AgentToolResult<EvalToolDetails>> => {
174
+ execution.setKernel(kernel);
175
+ const activeHandler = new CellHandler(kernel, state, {
176
+ executeTool: options.executeTool,
177
+ ...(options.listTools === undefined ? {} : { listTools: options.listTools }),
178
+ settings: options.settings ?? defaultCodemodeSettings,
179
+ ...(options.complete === undefined ? {} : { complete: options.complete }),
180
+ ctx: bridgeContext,
181
+ ...(options.artifactsDir === undefined
182
+ ? {}
183
+ : { artifactPath: join(options.artifactsDir, `eval-${randomUUID()}.log`) }),
184
+ ...(options.imageResizer === undefined ? {} : { imageResizer: options.imageResizer }),
185
+ ...(kernelTools === undefined ? {} : { kernelTools }),
186
+ });
187
+ handler = activeHandler;
188
+ cellManager.markRunning(
189
+ cell,
190
+ kernel,
191
+ () => activeHandler.liveResult(),
192
+ (error) => execution.cancel(error),
193
+ );
194
+ // Includes a steer already queued at execute start or received while acquiring the kernel.
195
+ onReady();
196
+ if ("setContext" in options.kernelManager && typeof options.kernelManager.setContext === "function") {
197
+ options.kernelManager.setContext(bridgeContext);
198
+ }
199
+ if (invocation.input.reset) await execution.wait(kernel.reset());
200
+ const result = await execution.wait(kernel.run({ cellId: invocation.cellId, code: invocation.input.code }));
201
+ if (result.ok && state.pendingBridgeCalls.length > 0)
202
+ await execution.wait(Promise.all(state.pendingBridgeCalls));
203
+ return await handler.finalize(result);
204
+ };
205
+ return kernelTools ? await kernelToolsStorage.run(kernelTools, runBound) : await runBound();
197
206
  } catch (error) {
198
207
  if (handler && error instanceof Error && error.name === "CodemodeSessionDisposedError")
199
208
  return await handler.finalizeCancellation(error);
@@ -206,3 +215,16 @@ async function executeCell(
206
215
  if (handler) await handler.flushOutput();
207
216
  }
208
217
  }
218
+
219
+ function jsKernelTools(kernel: EvalKernel, language: string): KernelToolsCapability | undefined {
220
+ if (language !== "js") return undefined;
221
+ if (!("describeKernelTools" in kernel) || typeof kernel.describeKernelTools !== "function") return undefined;
222
+ const js = kernel as EvalKernel & {
223
+ describeKernelTools: KernelToolsCapability["describe"];
224
+ invokeKernelTool: KernelToolsCapability["invoke"];
225
+ };
226
+ return {
227
+ describe: (names) => js.describeKernelTools(names),
228
+ invoke: (request, signal) => js.invokeKernelTool(request, signal),
229
+ };
230
+ }
@@ -0,0 +1,35 @@
1
+ import type { EvalDetachedCellSnapshot } from "./detached-cell-contract.ts";
2
+
3
+ export const TERMINAL_SNAPSHOT_CAP = 32;
4
+
5
+ /** Bounded LRU of settled-cell snapshots; without it every settled cell pins its result and closures for the session lifetime (#1695). */
6
+ export class TerminalSnapshotStore {
7
+ readonly #snapshots = new Map<string, EvalDetachedCellSnapshot>();
8
+ readonly #cap: number;
9
+
10
+ constructor(cap = TERMINAL_SNAPSHOT_CAP) {
11
+ this.#cap = cap;
12
+ }
13
+
14
+ remember(snapshot: EvalDetachedCellSnapshot): void {
15
+ this.#snapshots.delete(snapshot.cellId);
16
+ this.#snapshots.set(snapshot.cellId, snapshot);
17
+ while (this.#snapshots.size > this.#cap) {
18
+ const oldest = this.#snapshots.keys().next();
19
+ if (oldest.done === true) break;
20
+ this.#snapshots.delete(oldest.value);
21
+ }
22
+ }
23
+
24
+ get(cellId: string): EvalDetachedCellSnapshot | undefined {
25
+ return this.#snapshots.get(cellId);
26
+ }
27
+
28
+ delete(cellId: string): void {
29
+ this.#snapshots.delete(cellId);
30
+ }
31
+
32
+ clear(): void {
33
+ this.#snapshots.clear();
34
+ }
35
+ }
@@ -0,0 +1,23 @@
1
+ import type { AgentToolResult } from "@code-yeongyu/senpi";
2
+
3
+ export function marshalToolResult(result: AgentToolResult<unknown>) {
4
+ const texts = result.content.filter((part) => part.type === "text").map((part) => part.text);
5
+ const images = result.content
6
+ .filter((part) => part.type === "image")
7
+ .map((part) => ({ mimeType: part.mimeType, dataBase64: part.data }));
8
+ const details =
9
+ typeof result.details === "object" &&
10
+ result.details !== null &&
11
+ !Array.isArray(result.details) &&
12
+ Object.keys(result.details).length === 0
13
+ ? undefined
14
+ : result.details;
15
+ const hasError = toolResultIsError(result);
16
+ const text = texts.join("\n");
17
+ return images.length === 0 && details === undefined && !hasError ? { text } : { text, details, images, hasError };
18
+ }
19
+
20
+ export function toolResultIsError(result: AgentToolResult<unknown>): boolean {
21
+ const details = result.details;
22
+ return typeof details === "object" && details !== null && "isError" in details && details.isError === true;
23
+ }
@@ -132,6 +132,8 @@ export interface EvalKernel {
132
132
  deliverToolReply(message: Extract<HostToKernelMessage, { type: "tool-reply" }>): void;
133
133
  reset(): Promise<void>;
134
134
  close(): Promise<void>;
135
+ /** Names this kernel has registered; JS collides with other languages in the same session. */
136
+ listKernelToolNames?(): readonly string[];
135
137
  }
136
138
 
137
139
  export interface EvalKernelManager {
@@ -1 +1 @@
1
- {"version":3,"file":"empty-assistant-recovery.d.ts","sourceRoot":"","sources":["../src/empty-assistant-recovery.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,KAAK,GAAG,EAQR,KAAK,KAAK,EAEV,MAAM,uBAAuB,CAAC;AAC/B,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAoH3C,wBAAgB,0BAA0B,CAAC,IAAI,SAAS,GAAG,EAAE,KAAK,EAAE,KAAK,CAAC,IAAI,CAAC,EAAE,cAAc,EAAE,QAAQ,GAAG,QAAQ,CAMnH"}
1
+ {"version":3,"file":"empty-assistant-recovery.d.ts","sourceRoot":"","sources":["../src/empty-assistant-recovery.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,KAAK,GAAG,EAaR,KAAK,KAAK,EAEV,MAAM,uBAAuB,CAAC;AAC/B,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AA4K3C,wBAAgB,0BAA0B,CAAC,IAAI,SAAS,GAAG,EAAE,KAAK,EAAE,KAAK,CAAC,IAAI,CAAC,EAAE,cAAc,EAAE,QAAQ,GAAG,QAAQ,CAOnH"}
@@ -1,31 +1,60 @@
1
- import { createAssistantMessageEventStream, getToolCallFormat, hasVisibleAssistantContent, hasVisibleText, shouldRecoverTextToolCalls, } from "@earendil-works/pi-ai";
2
- const EMPTY_RESPONSE_ERROR = "Model returned an empty response twice";
3
- const EMPTY_TOOL_USE_ERROR = "Model returned tool_use without a tool call twice";
1
+ import { createAssistantMessageEventStream, EMPTY_RESPONSE_ERROR, EMPTY_TOOL_USE_ERROR, FORWARDED_EMPTY_RESPONSE_ERROR, FORWARDED_EMPTY_TOOL_USE_ERROR, getToolCallFormat, hasKimiTextToolCallRecovery, hasVisibleAssistantContent, hasVisibleText, shouldRecoverTextToolCalls, } from "@earendil-works/pi-ai";
4
2
  function isEmptyStop(message) {
5
3
  return message.stopReason === "stop" && !hasVisibleAssistantContent(message);
6
4
  }
7
5
  function isEmptyToolUse(message) {
8
6
  return message.stopReason === "toolUse" && !message.content.some((block) => block.type === "toolCall");
9
7
  }
10
- function eventStartsVisibleContent(event) {
11
- return event.type === "toolcall_start" || (event.type === "text_delta" && hasVisibleText(event.delta));
8
+ /** Zero-width or whitespace-only deltas never commit, so format-only noise stays buffered. */
9
+ function isMeaningfulContentEvent(event, policy) {
10
+ switch (event.type) {
11
+ case "toolcall_start":
12
+ return true;
13
+ case "text_delta":
14
+ return hasVisibleText(event.delta);
15
+ case "text_end":
16
+ return hasVisibleText(event.content);
17
+ case "thinking_delta":
18
+ return policy.thinkingCommits && hasVisibleText(event.delta);
19
+ case "thinking_end":
20
+ return policy.thinkingCommits && hasVisibleText(event.content);
21
+ default:
22
+ return false;
23
+ }
12
24
  }
13
- function appendRetryDiagnostic(message, type = "empty_assistant_response_recovery") {
25
+ function appendRetryDiagnostic(message, type = "empty_assistant_response_recovery", details = { retries: 1 }) {
14
26
  return {
15
27
  ...message,
16
- diagnostics: [...(message.diagnostics ?? []), { type, timestamp: Date.now(), details: { retries: 1 } }],
28
+ diagnostics: [...(message.diagnostics ?? []), { type, timestamp: Date.now(), details }],
17
29
  };
18
30
  }
31
+ function recoveryDiagnosticType(toolUse) {
32
+ return toolUse ? "empty_tool_use_response_recovery" : "empty_assistant_response_recovery";
33
+ }
19
34
  function createEmptyResponseFailure(message, toolUse = false) {
20
35
  const errorMessage = toolUse ? EMPTY_TOOL_USE_ERROR : EMPTY_RESPONSE_ERROR;
21
36
  return {
22
- ...appendRetryDiagnostic(message, toolUse ? "empty_tool_use_response_recovery" : undefined),
37
+ ...appendRetryDiagnostic(message, recoveryDiagnosticType(toolUse)),
23
38
  content: [{ type: "text", text: errorMessage }],
24
39
  stopReason: "error",
25
40
  errorMessage,
26
41
  };
27
42
  }
28
- function createRetryingStream(firstStream, createStream) {
43
+ /**
44
+ * An attempt whose reasoning already streamed cannot be replayed here: a second `start` would
45
+ * duplicate the partial message, and stitching this attempt's thinking onto a retry's content
46
+ * would break provider replay of signed thinking blocks. The turn ends as a retryable error
47
+ * (see the classifier in pi-ai's retry.ts) and keeps the content the user already saw, so the
48
+ * session's turn retry re-requests it with the failed attempt dropped from the provider context.
49
+ */
50
+ function createForwardedEmptyFailure(message, toolUse) {
51
+ return {
52
+ ...appendRetryDiagnostic(message, recoveryDiagnosticType(toolUse), { retries: 0, forwarded: true }),
53
+ stopReason: "error",
54
+ errorMessage: toolUse ? FORWARDED_EMPTY_TOOL_USE_ERROR : FORWARDED_EMPTY_RESPONSE_ERROR,
55
+ };
56
+ }
57
+ function createRetryingStream(firstStream, createStream, policy) {
29
58
  const outerStream = createAssistantMessageEventStream();
30
59
  void (async () => {
31
60
  try {
@@ -39,6 +68,15 @@ function createRetryingStream(firstStream, createStream) {
39
68
  if (event.type === "done") {
40
69
  const emptyToolUse = isEmptyToolUse(event.message);
41
70
  if (isEmptyStop(event.message) || emptyToolUse) {
71
+ if (forwarding) {
72
+ outerStream.push({
73
+ type: "error",
74
+ reason: "error",
75
+ error: createForwardedEmptyFailure(event.message, emptyToolUse),
76
+ });
77
+ outerStream.end();
78
+ return;
79
+ }
42
80
  if (!retrying) {
43
81
  retry = true;
44
82
  break;
@@ -51,7 +89,7 @@ function createRetryingStream(firstStream, createStream) {
51
89
  const terminal = retrying
52
90
  ? {
53
91
  ...event,
54
- message: appendRetryDiagnostic(event.message, isEmptyToolUse(event.message) ? "empty_tool_use_response_recovery" : undefined),
92
+ message: appendRetryDiagnostic(event.message, recoveryDiagnosticType(isEmptyToolUse(event.message))),
55
93
  }
56
94
  : event;
57
95
  if (!forwarding) {
@@ -76,7 +114,7 @@ function createRetryingStream(firstStream, createStream) {
76
114
  continue;
77
115
  }
78
116
  buffered.push(event);
79
- if (eventStartsVisibleContent(event)) {
117
+ if (isMeaningfulContentEvent(event, policy)) {
80
118
  for (const pending of buffered)
81
119
  outerStream.push(pending);
82
120
  forwarding = true;
@@ -103,9 +141,10 @@ function createRetryingStream(firstStream, createStream) {
103
141
  export function withEmptyAssistantRecovery(model, streamFunction) {
104
142
  if (!shouldRecoverTextToolCalls(model) && getToolCallFormat(model) === undefined)
105
143
  return streamFunction;
144
+ const policy = { thinkingCommits: !hasKimiTextToolCallRecovery(model) };
106
145
  return async (requestedModel, context, options) => {
107
146
  const createStream = () => streamFunction(requestedModel, context, options);
108
- return createRetryingStream(await createStream(), createStream);
147
+ return createRetryingStream(await createStream(), createStream, policy);
109
148
  };
110
149
  }
111
150
  //# sourceMappingURL=empty-assistant-recovery.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"empty-assistant-recovery.js","sourceRoot":"","sources":["../src/empty-assistant-recovery.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n\ttype Api,\n\ttype AssistantMessage,\n\ttype AssistantMessageEvent,\n\ttype AssistantMessageEventStream,\n\tcreateAssistantMessageEventStream,\n\tgetToolCallFormat,\n\thasVisibleAssistantContent,\n\thasVisibleText,\n\ttype Model,\n\tshouldRecoverTextToolCalls,\n} from \"@earendil-works/pi-ai\";\nimport type { StreamFn } from \"./types.ts\";\n\ntype StreamFactory = () => AssistantMessageEventStream | Promise<AssistantMessageEventStream>;\n\nconst EMPTY_RESPONSE_ERROR = \"Model returned an empty response twice\";\nconst EMPTY_TOOL_USE_ERROR = \"Model returned tool_use without a tool call twice\";\n\nfunction isEmptyStop(message: AssistantMessage): boolean {\n\treturn message.stopReason === \"stop\" && !hasVisibleAssistantContent(message);\n}\n\nfunction isEmptyToolUse(message: AssistantMessage): boolean {\n\treturn message.stopReason === \"toolUse\" && !message.content.some((block) => block.type === \"toolCall\");\n}\n\nfunction eventStartsVisibleContent(event: AssistantMessageEvent): boolean {\n\treturn event.type === \"toolcall_start\" || (event.type === \"text_delta\" && hasVisibleText(event.delta));\n}\n\nfunction appendRetryDiagnostic(\n\tmessage: AssistantMessage,\n\ttype = \"empty_assistant_response_recovery\",\n): AssistantMessage {\n\treturn {\n\t\t...message,\n\t\tdiagnostics: [...(message.diagnostics ?? []), { type, timestamp: Date.now(), details: { retries: 1 } }],\n\t};\n}\n\nfunction createEmptyResponseFailure(message: AssistantMessage, toolUse = false): AssistantMessage {\n\tconst errorMessage = toolUse ? EMPTY_TOOL_USE_ERROR : EMPTY_RESPONSE_ERROR;\n\treturn {\n\t\t...appendRetryDiagnostic(message, toolUse ? \"empty_tool_use_response_recovery\" : undefined),\n\t\tcontent: [{ type: \"text\", text: errorMessage }],\n\t\tstopReason: \"error\",\n\t\terrorMessage,\n\t};\n}\n\nfunction createRetryingStream(firstStream: AssistantMessageEventStream, createStream: StreamFactory) {\n\tconst outerStream = createAssistantMessageEventStream();\n\n\tvoid (async (): Promise<void> => {\n\t\ttry {\n\t\t\tlet stream = firstStream;\n\t\t\tlet retrying = false;\n\t\t\tfor (;;) {\n\t\t\t\tconst buffered: AssistantMessageEvent[] = [];\n\t\t\t\tlet forwarding = false;\n\t\t\t\tlet retry = false;\n\t\t\t\tfor await (const event of stream) {\n\t\t\t\t\tif (event.type === \"done\") {\n\t\t\t\t\t\tconst emptyToolUse = isEmptyToolUse(event.message);\n\t\t\t\t\t\tif (isEmptyStop(event.message) || emptyToolUse) {\n\t\t\t\t\t\t\tif (!retrying) {\n\t\t\t\t\t\t\t\tretry = true;\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tconst error = createEmptyResponseFailure(event.message, emptyToolUse);\n\t\t\t\t\t\t\touterStream.push({ type: \"error\", reason: \"error\", error });\n\t\t\t\t\t\t\touterStream.end();\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst terminal = retrying\n\t\t\t\t\t\t\t? {\n\t\t\t\t\t\t\t\t\t...event,\n\t\t\t\t\t\t\t\t\tmessage: appendRetryDiagnostic(\n\t\t\t\t\t\t\t\t\t\tevent.message,\n\t\t\t\t\t\t\t\t\t\tisEmptyToolUse(event.message) ? \"empty_tool_use_response_recovery\" : undefined,\n\t\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t: event;\n\t\t\t\t\t\tif (!forwarding) {\n\t\t\t\t\t\t\tfor (const pending of buffered) outerStream.push(pending);\n\t\t\t\t\t\t}\n\t\t\t\t\t\touterStream.push(terminal);\n\t\t\t\t\t\touterStream.end();\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tif (event.type === \"error\") {\n\t\t\t\t\t\tif (!forwarding) {\n\t\t\t\t\t\t\tfor (const pending of buffered) outerStream.push(pending);\n\t\t\t\t\t\t}\n\t\t\t\t\t\touterStream.push(event);\n\t\t\t\t\t\touterStream.end();\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tif (forwarding) {\n\t\t\t\t\t\touterStream.push(event);\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t\tbuffered.push(event);\n\t\t\t\t\tif (eventStartsVisibleContent(event)) {\n\t\t\t\t\t\tfor (const pending of buffered) outerStream.push(pending);\n\t\t\t\t\t\tforwarding = true;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (!retry) {\n\t\t\t\t\touterStream.end(retrying ? appendRetryDiagnostic(await stream.result()) : await stream.result());\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tretrying = true;\n\t\t\t\tstream = await createStream();\n\t\t\t}\n\t\t} catch (error) {\n\t\t\touterStream.fail(error);\n\t\t}\n\t})();\n\n\treturn outerStream;\n}\n\n// Wrapping replaces the provider stream with a buffering proxy, which does not carry the\n// underlying stream's liveness surface (trackLocalWork/hasPendingLocalWork) that the loop's\n// idle watchdog reads. Only wrap models that actually need stream-level recovery; the\n// empty-tool_use contradiction is normalized for every model by the agent loop instead.\nexport function withEmptyAssistantRecovery<TApi extends Api>(model: Model<TApi>, streamFunction: StreamFn): StreamFn {\n\tif (!shouldRecoverTextToolCalls(model) && getToolCallFormat(model) === undefined) return streamFunction;\n\treturn async (requestedModel, context, options) => {\n\t\tconst createStream = (): ReturnType<StreamFn> => streamFunction(requestedModel, context, options);\n\t\treturn createRetryingStream(await createStream(), createStream);\n\t};\n}\n"]}
1
+ {"version":3,"file":"empty-assistant-recovery.js","sourceRoot":"","sources":["../src/empty-assistant-recovery.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n\ttype Api,\n\ttype AssistantMessage,\n\ttype AssistantMessageEvent,\n\ttype AssistantMessageEventStream,\n\tcreateAssistantMessageEventStream,\n\tEMPTY_RESPONSE_ERROR,\n\tEMPTY_TOOL_USE_ERROR,\n\tFORWARDED_EMPTY_RESPONSE_ERROR,\n\tFORWARDED_EMPTY_TOOL_USE_ERROR,\n\tgetToolCallFormat,\n\thasKimiTextToolCallRecovery,\n\thasVisibleAssistantContent,\n\thasVisibleText,\n\ttype Model,\n\tshouldRecoverTextToolCalls,\n} from \"@earendil-works/pi-ai\";\nimport type { StreamFn } from \"./types.ts\";\n\ntype StreamFactory = () => AssistantMessageEventStream | Promise<AssistantMessageEventStream>;\n\nfunction isEmptyStop(message: AssistantMessage): boolean {\n\treturn message.stopReason === \"stop\" && !hasVisibleAssistantContent(message);\n}\n\nfunction isEmptyToolUse(message: AssistantMessage): boolean {\n\treturn message.stopReason === \"toolUse\" && !message.content.some((block) => block.type === \"toolCall\");\n}\n\ninterface CommitPolicy {\n\t/**\n\t * Whether reasoning commits the attempt. True for models whose thinking channel is native\n\t * (Claude, antml/other text formats): a reasoning model shows its work for seconds before the\n\t * first text or tool call, and holding that back blanks the transcript for the whole phase.\n\t * False for the Kimi XTML lane: its thinking channel is the documented misrouting vector for\n\t * text tool calls and `recoverKimiXtmlThinking` only rewrites the finished message, so a\n\t * leaked protocol fragment forwarded live could never be retracted (#759).\n\t */\n\treadonly thinkingCommits: boolean;\n}\n\n/** Zero-width or whitespace-only deltas never commit, so format-only noise stays buffered. */\nfunction isMeaningfulContentEvent(event: AssistantMessageEvent, policy: CommitPolicy): boolean {\n\tswitch (event.type) {\n\t\tcase \"toolcall_start\":\n\t\t\treturn true;\n\t\tcase \"text_delta\":\n\t\t\treturn hasVisibleText(event.delta);\n\t\tcase \"text_end\":\n\t\t\treturn hasVisibleText(event.content);\n\t\tcase \"thinking_delta\":\n\t\t\treturn policy.thinkingCommits && hasVisibleText(event.delta);\n\t\tcase \"thinking_end\":\n\t\t\treturn policy.thinkingCommits && hasVisibleText(event.content);\n\t\tdefault:\n\t\t\treturn false;\n\t}\n}\n\nfunction appendRetryDiagnostic(\n\tmessage: AssistantMessage,\n\ttype = \"empty_assistant_response_recovery\",\n\tdetails: Record<string, unknown> = { retries: 1 },\n): AssistantMessage {\n\treturn {\n\t\t...message,\n\t\tdiagnostics: [...(message.diagnostics ?? []), { type, timestamp: Date.now(), details }],\n\t};\n}\n\nfunction recoveryDiagnosticType(toolUse: boolean): string {\n\treturn toolUse ? \"empty_tool_use_response_recovery\" : \"empty_assistant_response_recovery\";\n}\n\nfunction createEmptyResponseFailure(message: AssistantMessage, toolUse = false): AssistantMessage {\n\tconst errorMessage = toolUse ? EMPTY_TOOL_USE_ERROR : EMPTY_RESPONSE_ERROR;\n\treturn {\n\t\t...appendRetryDiagnostic(message, recoveryDiagnosticType(toolUse)),\n\t\tcontent: [{ type: \"text\", text: errorMessage }],\n\t\tstopReason: \"error\",\n\t\terrorMessage,\n\t};\n}\n\n/**\n * An attempt whose reasoning already streamed cannot be replayed here: a second `start` would\n * duplicate the partial message, and stitching this attempt's thinking onto a retry's content\n * would break provider replay of signed thinking blocks. The turn ends as a retryable error\n * (see the classifier in pi-ai's retry.ts) and keeps the content the user already saw, so the\n * session's turn retry re-requests it with the failed attempt dropped from the provider context.\n */\nfunction createForwardedEmptyFailure(message: AssistantMessage, toolUse: boolean): AssistantMessage {\n\treturn {\n\t\t...appendRetryDiagnostic(message, recoveryDiagnosticType(toolUse), { retries: 0, forwarded: true }),\n\t\tstopReason: \"error\",\n\t\terrorMessage: toolUse ? FORWARDED_EMPTY_TOOL_USE_ERROR : FORWARDED_EMPTY_RESPONSE_ERROR,\n\t};\n}\n\nfunction createRetryingStream(\n\tfirstStream: AssistantMessageEventStream,\n\tcreateStream: StreamFactory,\n\tpolicy: CommitPolicy,\n) {\n\tconst outerStream = createAssistantMessageEventStream();\n\n\tvoid (async (): Promise<void> => {\n\t\ttry {\n\t\t\tlet stream = firstStream;\n\t\t\tlet retrying = false;\n\t\t\tfor (;;) {\n\t\t\t\tconst buffered: AssistantMessageEvent[] = [];\n\t\t\t\tlet forwarding = false;\n\t\t\t\tlet retry = false;\n\t\t\t\tfor await (const event of stream) {\n\t\t\t\t\tif (event.type === \"done\") {\n\t\t\t\t\t\tconst emptyToolUse = isEmptyToolUse(event.message);\n\t\t\t\t\t\tif (isEmptyStop(event.message) || emptyToolUse) {\n\t\t\t\t\t\t\tif (forwarding) {\n\t\t\t\t\t\t\t\touterStream.push({\n\t\t\t\t\t\t\t\t\ttype: \"error\",\n\t\t\t\t\t\t\t\t\treason: \"error\",\n\t\t\t\t\t\t\t\t\terror: createForwardedEmptyFailure(event.message, emptyToolUse),\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\touterStream.end();\n\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (!retrying) {\n\t\t\t\t\t\t\t\tretry = true;\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tconst error = createEmptyResponseFailure(event.message, emptyToolUse);\n\t\t\t\t\t\t\touterStream.push({ type: \"error\", reason: \"error\", error });\n\t\t\t\t\t\t\touterStream.end();\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst terminal = retrying\n\t\t\t\t\t\t\t? {\n\t\t\t\t\t\t\t\t\t...event,\n\t\t\t\t\t\t\t\t\tmessage: appendRetryDiagnostic(\n\t\t\t\t\t\t\t\t\t\tevent.message,\n\t\t\t\t\t\t\t\t\t\trecoveryDiagnosticType(isEmptyToolUse(event.message)),\n\t\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t: event;\n\t\t\t\t\t\tif (!forwarding) {\n\t\t\t\t\t\t\tfor (const pending of buffered) outerStream.push(pending);\n\t\t\t\t\t\t}\n\t\t\t\t\t\touterStream.push(terminal);\n\t\t\t\t\t\touterStream.end();\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tif (event.type === \"error\") {\n\t\t\t\t\t\tif (!forwarding) {\n\t\t\t\t\t\t\tfor (const pending of buffered) outerStream.push(pending);\n\t\t\t\t\t\t}\n\t\t\t\t\t\touterStream.push(event);\n\t\t\t\t\t\touterStream.end();\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tif (forwarding) {\n\t\t\t\t\t\touterStream.push(event);\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t\tbuffered.push(event);\n\t\t\t\t\tif (isMeaningfulContentEvent(event, policy)) {\n\t\t\t\t\t\tfor (const pending of buffered) outerStream.push(pending);\n\t\t\t\t\t\tforwarding = true;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (!retry) {\n\t\t\t\t\touterStream.end(retrying ? appendRetryDiagnostic(await stream.result()) : await stream.result());\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tretrying = true;\n\t\t\t\tstream = await createStream();\n\t\t\t}\n\t\t} catch (error) {\n\t\t\touterStream.fail(error);\n\t\t}\n\t})();\n\n\treturn outerStream;\n}\n\n// Wrapping replaces the provider stream with a buffering proxy, which does not carry the\n// underlying stream's liveness surface (trackLocalWork/hasPendingLocalWork) that the loop's\n// idle watchdog reads. Only wrap models that actually need stream-level recovery; the\n// empty-tool_use contradiction is normalized for every model by the agent loop instead.\nexport function withEmptyAssistantRecovery<TApi extends Api>(model: Model<TApi>, streamFunction: StreamFn): StreamFn {\n\tif (!shouldRecoverTextToolCalls(model) && getToolCallFormat(model) === undefined) return streamFunction;\n\tconst policy: CommitPolicy = { thinkingCommits: !hasKimiTextToolCallRecovery(model) };\n\treturn async (requestedModel, context, options) => {\n\t\tconst createStream = (): ReturnType<StreamFn> => streamFunction(requestedModel, context, options);\n\t\treturn createRetryingStream(await createStream(), createStream, policy);\n\t};\n}\n"]}
@@ -1,6 +1,7 @@
1
1
  import { type Static, Type } from "typebox";
2
2
  import type { Context } from "../context.ts";
3
3
  import type { AgentHarnessTool } from "../types.ts";
4
+ import { type ReadFolder } from "../utils/read-folders/index.ts";
4
5
  import { type TruncationResult } from "../utils/truncate.ts";
5
6
  import type { ExecutionToolContext } from "./tool-context.ts";
6
7
  declare const readSchema: Type.TObject<{
@@ -29,6 +30,8 @@ export interface ReadToolOptions {
29
30
  autoResizeImages?: boolean;
30
31
  /** Optional image conversion/resizing implementation. */
31
32
  imageProcessor?: ReadImageProcessor;
33
+ /** Structural folder. Default options select the measured folder; omit here for verbatim reads. */
34
+ folder?: ReadFolder;
32
35
  }
33
36
  export declare function createReadTool<TContext extends ExecutionToolContext = ExecutionToolContext>(options?: ReadToolOptions): AgentHarnessTool<TContext, typeof readSchema, ReadToolDetails | undefined>;
34
37
  export {};
@@ -1 +1 @@
1
- {"version":3,"file":"read.d.ts","sourceRoot":"","sources":["../../../src/harness/tools/read.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,MAAM,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AAC5C,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAC7C,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAEpD,OAAO,EAIN,KAAK,gBAAgB,EAErB,MAAM,sBAAsB,CAAC;AAG9B,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AAE9D,QAAA,MAAM,UAAU;;;;EAId,CAAC;AAEH,MAAM,MAAM,aAAa,GAAG,MAAM,CAAC,OAAO,UAAU,CAAC,CAAC;AAEtD,MAAM,WAAW,eAAe;IAC/B,UAAU,CAAC,EAAE,gBAAgB,CAAC;CAC9B;AAED,MAAM,MAAM,wBAAwB,GACjC;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,EAAE,CAAA;CAAE,GAC7D;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AAElC,MAAM,MAAM,kBAAkB,GAAG,CAChC,KAAK,EAAE,UAAU,EACjB,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE;IAAE,gBAAgB,EAAE,OAAO,CAAA;CAAE,EACtC,OAAO,EAAE,OAAO,KACZ,OAAO,CAAC,wBAAwB,CAAC,CAAC;AAEvC,MAAM,WAAW,eAAe;IAC/B,+EAA+E;IAC/E,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,yDAAyD;IACzD,cAAc,CAAC,EAAE,kBAAkB,CAAC;CACpC;AAED,wBAAgB,cAAc,CAAC,QAAQ,SAAS,oBAAoB,GAAG,oBAAoB,EAC1F,OAAO,CAAC,EAAE,eAAe,GACvB,gBAAgB,CAAC,QAAQ,EAAE,OAAO,UAAU,EAAE,eAAe,GAAG,SAAS,CAAC,CAoG5E"}
1
+ {"version":3,"file":"read.d.ts","sourceRoot":"","sources":["../../../src/harness/tools/read.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,MAAM,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AAC5C,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAC7C,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAEpD,OAAO,EAAE,KAAK,UAAU,EAAsB,MAAM,gCAAgC,CAAC;AAErF,OAAO,EAIN,KAAK,gBAAgB,EAErB,MAAM,sBAAsB,CAAC;AAG9B,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AAE9D,QAAA,MAAM,UAAU;;;;EAId,CAAC;AAEH,MAAM,MAAM,aAAa,GAAG,MAAM,CAAC,OAAO,UAAU,CAAC,CAAC;AAEtD,MAAM,WAAW,eAAe;IAC/B,UAAU,CAAC,EAAE,gBAAgB,CAAC;CAC9B;AAED,MAAM,MAAM,wBAAwB,GACjC;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,EAAE,CAAA;CAAE,GAC7D;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AAElC,MAAM,MAAM,kBAAkB,GAAG,CAChC,KAAK,EAAE,UAAU,EACjB,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE;IAAE,gBAAgB,EAAE,OAAO,CAAA;CAAE,EACtC,OAAO,EAAE,OAAO,KACZ,OAAO,CAAC,wBAAwB,CAAC,CAAC;AAEvC,MAAM,WAAW,eAAe;IAC/B,+EAA+E;IAC/E,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,yDAAyD;IACzD,cAAc,CAAC,EAAE,kBAAkB,CAAC;IACpC,mGAAmG;IACnG,MAAM,CAAC,EAAE,UAAU,CAAC;CACpB;AAED,wBAAgB,cAAc,CAAC,QAAQ,SAAS,oBAAoB,GAAG,oBAAoB,EAC1F,OAAO,GAAE,eAAgD,GACvD,gBAAgB,CAAC,QAAQ,EAAE,OAAO,UAAU,EAAE,eAAe,GAAG,SAAS,CAAC,CA+G5E"}
@@ -1,5 +1,7 @@
1
1
  import { Type } from "typebox";
2
- import { getOrThrow } from "../types.js";
2
+ import { FileError, getOrThrow } from "../types.js";
3
+ import { selectedReadFolder } from "../utils/read-folders/index.js";
4
+ import { createDefaultReadSummary } from "../utils/segmented-read-view.js";
3
5
  import { DEFAULT_MAX_BYTES, DEFAULT_MAX_LINES, formatSize, truncateHead, } from "../utils/truncate.js";
4
6
  import { detectSupportedImageMimeType, encodeBase64 } from "./image.js";
5
7
  import { resolveReadToolPath } from "./path-utils.js";
@@ -8,7 +10,7 @@ const readSchema = Type.Object({
8
10
  offset: Type.Optional(Type.Number({ description: "Line number to start reading from (1-indexed)" })),
9
11
  limit: Type.Optional(Type.Number({ description: "Maximum number of lines to read" })),
10
12
  });
11
- export function createReadTool(options) {
13
+ export function createReadTool(options = { folder: selectedReadFolder }) {
12
14
  return {
13
15
  name: "read",
14
16
  label: "read",
@@ -17,6 +19,8 @@ export function createReadTool(options) {
17
19
  async execute(_toolCallId, { path, offset, limit }, _onUpdate, { env }, _invocation, context) {
18
20
  const absolutePath = await resolveReadToolPath(env, path, context);
19
21
  const bytes = getOrThrow(await env.readBinaryFile(absolutePath, context));
22
+ if (context.abortSignal?.aborted)
23
+ throw new FileError("aborted", "Operation aborted", absolutePath);
20
24
  const mimeType = detectSupportedImageMimeType(bytes);
21
25
  if (mimeType) {
22
26
  if (options?.imageProcessor) {
@@ -74,9 +78,20 @@ export function createReadTool(options) {
74
78
  selectedContent = allLines.slice(startLine).join("\n");
75
79
  }
76
80
  const truncation = truncateHead(selectedContent);
81
+ const summary = createDefaultReadSummary({
82
+ path: absolutePath,
83
+ text: textContent,
84
+ offset,
85
+ limit,
86
+ folder: options.folder,
87
+ truncated: truncation.truncated,
88
+ });
77
89
  let outputText;
78
90
  let details;
79
- if (truncation.firstLineExceedsLimit) {
91
+ if (summary) {
92
+ outputText = summary.text;
93
+ }
94
+ else if (truncation.firstLineExceedsLimit) {
80
95
  const firstLineSize = formatSize(new TextEncoder().encode(allLines[startLine]).byteLength);
81
96
  outputText = `[Line ${startLineDisplay} is ${firstLineSize}, exceeds ${formatSize(DEFAULT_MAX_BYTES)} limit. Use bash: sed -n '${startLineDisplay}p' ${path} | head -c ${DEFAULT_MAX_BYTES}]`;
82
97
  details = { truncation };