@code-yeongyu/senpi 2026.8.29 → 2026.8.30-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 (311) hide show
  1. package/CHANGELOG.md +122 -0
  2. package/dist/core/agent-session.d.ts +45 -1
  3. package/dist/core/agent-session.d.ts.map +1 -1
  4. package/dist/core/agent-session.js +329 -29
  5. package/dist/core/agent-session.js.map +1 -1
  6. package/dist/core/bash-executor.d.ts.map +1 -1
  7. package/dist/core/bash-executor.js +155 -37
  8. package/dist/core/bash-executor.js.map +1 -1
  9. package/dist/core/compaction/compaction-execution.d.ts +14 -0
  10. package/dist/core/compaction/compaction-execution.d.ts.map +1 -0
  11. package/dist/core/compaction/compaction-execution.js +121 -0
  12. package/dist/core/compaction/compaction-execution.js.map +1 -0
  13. package/dist/core/compaction/compaction-settings.d.ts +17 -0
  14. package/dist/core/compaction/compaction-settings.d.ts.map +1 -0
  15. package/dist/core/compaction/compaction-settings.js +17 -0
  16. package/dist/core/compaction/compaction-settings.js.map +1 -0
  17. package/dist/core/compaction/compaction.d.ts +13 -26
  18. package/dist/core/compaction/compaction.d.ts.map +1 -1
  19. package/dist/core/compaction/compaction.js +7 -136
  20. package/dist/core/compaction/compaction.js.map +1 -1
  21. package/dist/core/compaction/ideal-compaction-settings.d.ts +9 -0
  22. package/dist/core/compaction/ideal-compaction-settings.d.ts.map +1 -0
  23. package/dist/core/compaction/ideal-compaction-settings.js +7 -0
  24. package/dist/core/compaction/ideal-compaction-settings.js.map +1 -0
  25. package/dist/core/compaction-settings-access.d.ts +19 -0
  26. package/dist/core/compaction-settings-access.d.ts.map +1 -0
  27. package/dist/core/compaction-settings-access.js +10 -0
  28. package/dist/core/compaction-settings-access.js.map +1 -0
  29. package/dist/core/compaction-settings-resolver.d.ts +22 -0
  30. package/dist/core/compaction-settings-resolver.d.ts.map +1 -0
  31. package/dist/core/compaction-settings-resolver.js +46 -0
  32. package/dist/core/compaction-settings-resolver.js.map +1 -0
  33. package/dist/core/credential-accounts.d.ts.map +1 -1
  34. package/dist/core/credential-accounts.js +23 -9
  35. package/dist/core/credential-accounts.js.map +1 -1
  36. package/dist/core/credential-pool/env-slots.d.ts.map +1 -1
  37. package/dist/core/credential-pool/env-slots.js +2 -0
  38. package/dist/core/credential-pool/env-slots.js.map +1 -1
  39. package/dist/core/extensions/builtin/compaction/context-pipeline.d.ts +17 -0
  40. package/dist/core/extensions/builtin/compaction/context-pipeline.d.ts.map +1 -0
  41. package/dist/core/extensions/builtin/compaction/context-pipeline.js +28 -0
  42. package/dist/core/extensions/builtin/compaction/context-pipeline.js.map +1 -0
  43. package/dist/core/extensions/builtin/compaction/deterministic-fallback.d.ts +8 -2
  44. package/dist/core/extensions/builtin/compaction/deterministic-fallback.d.ts.map +1 -1
  45. package/dist/core/extensions/builtin/compaction/deterministic-fallback.js +163 -48
  46. package/dist/core/extensions/builtin/compaction/deterministic-fallback.js.map +1 -1
  47. package/dist/core/extensions/builtin/compaction/emergency-prune.d.ts +17 -0
  48. package/dist/core/extensions/builtin/compaction/emergency-prune.d.ts.map +1 -0
  49. package/dist/core/extensions/builtin/compaction/emergency-prune.js +64 -0
  50. package/dist/core/extensions/builtin/compaction/emergency-prune.js.map +1 -0
  51. package/dist/core/extensions/builtin/compaction/extension-wiring.d.ts +27 -0
  52. package/dist/core/extensions/builtin/compaction/extension-wiring.d.ts.map +1 -0
  53. package/dist/core/extensions/builtin/compaction/extension-wiring.js +84 -0
  54. package/dist/core/extensions/builtin/compaction/extension-wiring.js.map +1 -0
  55. package/dist/core/extensions/builtin/compaction/idle.d.ts +1 -0
  56. package/dist/core/extensions/builtin/compaction/idle.d.ts.map +1 -1
  57. package/dist/core/extensions/builtin/compaction/idle.js +13 -2
  58. package/dist/core/extensions/builtin/compaction/idle.js.map +1 -1
  59. package/dist/core/extensions/builtin/compaction/index.d.ts +1 -1
  60. package/dist/core/extensions/builtin/compaction/index.d.ts.map +1 -1
  61. package/dist/core/extensions/builtin/compaction/index.js +121 -147
  62. package/dist/core/extensions/builtin/compaction/index.js.map +1 -1
  63. package/dist/core/extensions/builtin/compaction/log.d.ts +1 -1
  64. package/dist/core/extensions/builtin/compaction/log.d.ts.map +1 -1
  65. package/dist/core/extensions/builtin/compaction/log.js +2 -0
  66. package/dist/core/extensions/builtin/compaction/log.js.map +1 -1
  67. package/dist/core/extensions/builtin/compaction/model-selection.d.ts +14 -0
  68. package/dist/core/extensions/builtin/compaction/model-selection.d.ts.map +1 -0
  69. package/dist/core/extensions/builtin/compaction/model-selection.js +37 -0
  70. package/dist/core/extensions/builtin/compaction/model-selection.js.map +1 -0
  71. package/dist/core/extensions/builtin/compaction/openai-remote-dependencies.d.ts +9 -0
  72. package/dist/core/extensions/builtin/compaction/openai-remote-dependencies.d.ts.map +1 -0
  73. package/dist/core/extensions/builtin/compaction/openai-remote-dependencies.js +2 -0
  74. package/dist/core/extensions/builtin/compaction/openai-remote-dependencies.js.map +1 -0
  75. package/dist/core/extensions/builtin/compaction/openai-remote.d.ts +3 -6
  76. package/dist/core/extensions/builtin/compaction/openai-remote.d.ts.map +1 -1
  77. package/dist/core/extensions/builtin/compaction/openai-remote.js.map +1 -1
  78. package/dist/core/extensions/builtin/compaction/orchestration.d.ts +56 -0
  79. package/dist/core/extensions/builtin/compaction/orchestration.d.ts.map +1 -0
  80. package/dist/core/extensions/builtin/compaction/orchestration.js +136 -0
  81. package/dist/core/extensions/builtin/compaction/orchestration.js.map +1 -0
  82. package/dist/core/extensions/builtin/compaction/policy.d.ts +5 -2
  83. package/dist/core/extensions/builtin/compaction/policy.d.ts.map +1 -1
  84. package/dist/core/extensions/builtin/compaction/policy.js +43 -26
  85. package/dist/core/extensions/builtin/compaction/policy.js.map +1 -1
  86. package/dist/core/extensions/builtin/compaction/retained-message-safety.d.ts.map +1 -1
  87. package/dist/core/extensions/builtin/compaction/retained-message-safety.js +31 -8
  88. package/dist/core/extensions/builtin/compaction/retained-message-safety.js.map +1 -1
  89. package/dist/core/extensions/builtin/compaction/speculation-lead.d.ts +8 -0
  90. package/dist/core/extensions/builtin/compaction/speculation-lead.d.ts.map +1 -0
  91. package/dist/core/extensions/builtin/compaction/speculation-lead.js +17 -0
  92. package/dist/core/extensions/builtin/compaction/speculation-lead.js.map +1 -0
  93. package/dist/core/extensions/builtin/compaction/speculative-job.d.ts +22 -0
  94. package/dist/core/extensions/builtin/compaction/speculative-job.d.ts.map +1 -0
  95. package/dist/core/extensions/builtin/compaction/speculative-job.js +16 -0
  96. package/dist/core/extensions/builtin/compaction/speculative-job.js.map +1 -0
  97. package/dist/core/extensions/builtin/compaction/speculative-summary.d.ts +22 -0
  98. package/dist/core/extensions/builtin/compaction/speculative-summary.d.ts.map +1 -0
  99. package/dist/core/extensions/builtin/compaction/speculative-summary.js +131 -0
  100. package/dist/core/extensions/builtin/compaction/speculative-summary.js.map +1 -0
  101. package/dist/core/extensions/builtin/compaction/speculative.d.ts +8 -13
  102. package/dist/core/extensions/builtin/compaction/speculative.d.ts.map +1 -1
  103. package/dist/core/extensions/builtin/compaction/speculative.js +7 -196
  104. package/dist/core/extensions/builtin/compaction/speculative.js.map +1 -1
  105. package/dist/core/extensions/builtin/compaction/token-budget-reminder.d.ts +18 -0
  106. package/dist/core/extensions/builtin/compaction/token-budget-reminder.d.ts.map +1 -0
  107. package/dist/core/extensions/builtin/compaction/token-budget-reminder.js +18 -0
  108. package/dist/core/extensions/builtin/compaction/token-budget-reminder.js.map +1 -0
  109. package/dist/core/extensions/builtin/compaction/tool-admission.d.ts +36 -0
  110. package/dist/core/extensions/builtin/compaction/tool-admission.d.ts.map +1 -0
  111. package/dist/core/extensions/builtin/compaction/tool-admission.js +92 -0
  112. package/dist/core/extensions/builtin/compaction/tool-admission.js.map +1 -0
  113. package/dist/core/extensions/builtin/cursor-cli-oauth/stream.d.ts.map +1 -1
  114. package/dist/core/extensions/builtin/cursor-cli-oauth/stream.js +8 -28
  115. package/dist/core/extensions/builtin/cursor-cli-oauth/stream.js.map +1 -1
  116. package/dist/core/extensions/builtin/permission-system/external-dir.d.ts.map +1 -1
  117. package/dist/core/extensions/builtin/permission-system/external-dir.js +15 -5
  118. package/dist/core/extensions/builtin/permission-system/external-dir.js.map +1 -1
  119. package/dist/core/extensions/builtin/permission-system/parsers.d.ts.map +1 -1
  120. package/dist/core/extensions/builtin/permission-system/parsers.js +16 -1
  121. package/dist/core/extensions/builtin/permission-system/parsers.js.map +1 -1
  122. package/dist/core/extensions/builtin/terminal/manager.d.ts +5 -0
  123. package/dist/core/extensions/builtin/terminal/manager.d.ts.map +1 -1
  124. package/dist/core/extensions/builtin/terminal/manager.js +31 -3
  125. package/dist/core/extensions/builtin/terminal/manager.js.map +1 -1
  126. package/dist/core/extensions/builtin/terminal/monitor-permission.d.ts +8 -0
  127. package/dist/core/extensions/builtin/terminal/monitor-permission.d.ts.map +1 -0
  128. package/dist/core/extensions/builtin/terminal/monitor-permission.js +16 -0
  129. package/dist/core/extensions/builtin/terminal/monitor-permission.js.map +1 -0
  130. package/dist/core/extensions/builtin/terminal/monitor-registry.d.ts +13 -0
  131. package/dist/core/extensions/builtin/terminal/monitor-registry.d.ts.map +1 -1
  132. package/dist/core/extensions/builtin/terminal/monitor-registry.js +403 -3
  133. package/dist/core/extensions/builtin/terminal/monitor-registry.js.map +1 -1
  134. package/dist/core/extensions/builtin/terminal/session-bundle.d.ts.map +1 -1
  135. package/dist/core/extensions/builtin/terminal/session-bundle.js +1 -0
  136. package/dist/core/extensions/builtin/terminal/session-bundle.js.map +1 -1
  137. package/dist/core/extensions/builtin/terminal/tools/kill-bash.d.ts.map +1 -1
  138. package/dist/core/extensions/builtin/terminal/tools/kill-bash.js +5 -2
  139. package/dist/core/extensions/builtin/terminal/tools/kill-bash.js.map +1 -1
  140. package/dist/core/extensions/builtin/terminal/tools/monitor.d.ts +4 -0
  141. package/dist/core/extensions/builtin/terminal/tools/monitor.d.ts.map +1 -1
  142. package/dist/core/extensions/builtin/terminal/tools/monitor.js +40 -2
  143. package/dist/core/extensions/builtin/terminal/tools/monitor.js.map +1 -1
  144. package/dist/core/model-resolver.js +2 -2
  145. package/dist/core/model-resolver.js.map +1 -1
  146. package/dist/core/session-manager.d.ts.map +1 -1
  147. package/dist/core/session-manager.js +10 -1
  148. package/dist/core/session-manager.js.map +1 -1
  149. package/dist/core/settings-manager.d.ts +15 -85
  150. package/dist/core/settings-manager.d.ts.map +1 -1
  151. package/dist/core/settings-manager.js +15 -17
  152. package/dist/core/settings-manager.js.map +1 -1
  153. package/dist/core/settings-public-types.d.ts +11 -0
  154. package/dist/core/settings-public-types.d.ts.map +1 -0
  155. package/dist/core/settings-public-types.js +2 -0
  156. package/dist/core/settings-public-types.js.map +1 -0
  157. package/dist/core/settings-shapes.d.ts +36 -0
  158. package/dist/core/settings-shapes.d.ts.map +1 -0
  159. package/dist/core/settings-shapes.js +2 -0
  160. package/dist/core/settings-shapes.js.map +1 -0
  161. package/dist/core/shared-host-policy.d.ts +6 -0
  162. package/dist/core/shared-host-policy.d.ts.map +1 -0
  163. package/dist/core/shared-host-policy.js +7 -0
  164. package/dist/core/shared-host-policy.js.map +1 -0
  165. package/dist/core/terminal-settings.d.ts +22 -0
  166. package/dist/core/terminal-settings.d.ts.map +1 -0
  167. package/dist/core/terminal-settings.js +2 -0
  168. package/dist/core/terminal-settings.js.map +1 -0
  169. package/dist/core/tools/bash.d.ts.map +1 -1
  170. package/dist/core/tools/bash.js +97 -15
  171. package/dist/core/tools/bash.js.map +1 -1
  172. package/dist/core/tools/index.d.ts +1 -0
  173. package/dist/core/tools/index.d.ts.map +1 -1
  174. package/dist/core/tools/index.js +8 -0
  175. package/dist/core/tools/index.js.map +1 -1
  176. package/dist/core/tools/output-accumulator.d.ts +1 -1
  177. package/dist/core/tools/output-accumulator.d.ts.map +1 -1
  178. package/dist/core/tools/output-accumulator.js +76 -34
  179. package/dist/core/tools/output-accumulator.js.map +1 -1
  180. package/dist/main.d.ts.map +1 -1
  181. package/dist/main.js +17 -5
  182. package/dist/main.js.map +1 -1
  183. package/dist/modes/interactive/components/footer.d.ts +7 -0
  184. package/dist/modes/interactive/components/footer.d.ts.map +1 -1
  185. package/dist/modes/interactive/components/footer.js +34 -8
  186. package/dist/modes/interactive/components/footer.js.map +1 -1
  187. package/dist/modes/interactive/grok/chrome.d.ts +2 -0
  188. package/dist/modes/interactive/grok/chrome.d.ts.map +1 -1
  189. package/dist/modes/interactive/grok/chrome.js.map +1 -1
  190. package/dist/modes/interactive/interactive-host-runtime.d.ts +121 -1
  191. package/dist/modes/interactive/interactive-host-runtime.d.ts.map +1 -1
  192. package/dist/modes/interactive/interactive-host-runtime.js +336 -65
  193. package/dist/modes/interactive/interactive-host-runtime.js.map +1 -1
  194. package/dist/modes/interactive/interactive-mode.d.ts +7 -0
  195. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  196. package/dist/modes/interactive/interactive-mode.js +114 -2
  197. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  198. package/dist/modes/rpc/connection-handler.d.ts +8 -1
  199. package/dist/modes/rpc/connection-handler.d.ts.map +1 -1
  200. package/dist/modes/rpc/connection-handler.js +145 -49
  201. package/dist/modes/rpc/connection-handler.js.map +1 -1
  202. package/dist/modes/rpc/host-ensure.d.ts +19 -0
  203. package/dist/modes/rpc/host-ensure.d.ts.map +1 -1
  204. package/dist/modes/rpc/host-ensure.js +79 -15
  205. package/dist/modes/rpc/host-ensure.js.map +1 -1
  206. package/dist/modes/rpc/host-lifecycle.d.ts +29 -0
  207. package/dist/modes/rpc/host-lifecycle.d.ts.map +1 -1
  208. package/dist/modes/rpc/host-lifecycle.js +96 -25
  209. package/dist/modes/rpc/host-lifecycle.js.map +1 -1
  210. package/dist/modes/rpc/host-watchdog.d.ts +2 -0
  211. package/dist/modes/rpc/host-watchdog.d.ts.map +1 -1
  212. package/dist/modes/rpc/host-watchdog.js +23 -12
  213. package/dist/modes/rpc/host-watchdog.js.map +1 -1
  214. package/dist/modes/rpc/multi-session-host.js +6 -4
  215. package/dist/modes/rpc/multi-session-host.js.map +1 -1
  216. package/dist/modes/rpc/rpc-client.d.ts +10 -4
  217. package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
  218. package/dist/modes/rpc/rpc-client.js +47 -10
  219. package/dist/modes/rpc/rpc-client.js.map +1 -1
  220. package/dist/modes/rpc/rpc-input-validation.d.ts +1 -0
  221. package/dist/modes/rpc/rpc-input-validation.d.ts.map +1 -1
  222. package/dist/modes/rpc/rpc-input-validation.js +66 -0
  223. package/dist/modes/rpc/rpc-input-validation.js.map +1 -1
  224. package/dist/modes/rpc/rpc-types.d.ts +86 -1
  225. package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
  226. package/dist/modes/rpc/rpc-types.js.map +1 -1
  227. package/dist/modes/rpc/session-binding.d.ts +1 -0
  228. package/dist/modes/rpc/session-binding.d.ts.map +1 -1
  229. package/dist/modes/rpc/session-binding.js +21 -1
  230. package/dist/modes/rpc/session-binding.js.map +1 -1
  231. package/dist/modes/rpc/session-command-router.d.ts.map +1 -1
  232. package/dist/modes/rpc/session-command-router.js +1 -0
  233. package/dist/modes/rpc/session-command-router.js.map +1 -1
  234. package/dist/modes/rpc/session-event-writer.d.ts +2 -0
  235. package/dist/modes/rpc/session-event-writer.d.ts.map +1 -1
  236. package/dist/modes/rpc/session-event-writer.js +54 -7
  237. package/dist/modes/rpc/session-event-writer.js.map +1 -1
  238. package/dist/modes/rpc/session-extension-ui-requests.d.ts +1 -0
  239. package/dist/modes/rpc/session-extension-ui-requests.d.ts.map +1 -1
  240. package/dist/modes/rpc/session-extension-ui-requests.js +5 -0
  241. package/dist/modes/rpc/session-extension-ui-requests.js.map +1 -1
  242. package/dist/modes/rpc/session-registry.d.ts +10 -2
  243. package/dist/modes/rpc/session-registry.d.ts.map +1 -1
  244. package/dist/modes/rpc/session-registry.js +58 -4
  245. package/dist/modes/rpc/session-registry.js.map +1 -1
  246. package/dist/modes/rpc/socket-event-fanout.d.ts +22 -0
  247. package/dist/modes/rpc/socket-event-fanout.d.ts.map +1 -0
  248. package/dist/modes/rpc/socket-event-fanout.js +84 -0
  249. package/dist/modes/rpc/socket-event-fanout.js.map +1 -0
  250. package/docs/rpc.md +18 -2
  251. package/docs/settings.md +34 -0
  252. package/docs/windows.md +2 -0
  253. package/node_modules/@code-yeongyu/senpi-codemode/CHANGELOG.md +50 -0
  254. package/node_modules/@code-yeongyu/senpi-codemode/package.json +4 -4
  255. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/worker-indirect-eval.js +139 -90
  256. package/node_modules/@code-yeongyu/senpi-codemode/src/prompt/eval-prompt.ts +4 -4
  257. package/node_modules/@earendil-works/pi-agent-core/dist/harness/env/nodejs.d.ts.map +1 -1
  258. package/node_modules/@earendil-works/pi-agent-core/dist/harness/env/nodejs.js +40 -20
  259. package/node_modules/@earendil-works/pi-agent-core/dist/harness/env/nodejs.js.map +1 -1
  260. package/node_modules/@earendil-works/pi-agent-core/dist/harness/types.d.ts +3 -3
  261. package/node_modules/@earendil-works/pi-agent-core/dist/harness/types.d.ts.map +1 -1
  262. package/node_modules/@earendil-works/pi-agent-core/dist/harness/types.js.map +1 -1
  263. package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/shell-output.d.ts +1 -1
  264. package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/shell-output.d.ts.map +1 -1
  265. package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/shell-output.js +28 -9
  266. package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/shell-output.js.map +1 -1
  267. package/node_modules/@earendil-works/pi-agent-core/package.json +3 -3
  268. package/node_modules/@earendil-works/pi-ai/dist/api/anthropic-messages.d.ts.map +1 -1
  269. package/node_modules/@earendil-works/pi-ai/dist/api/anthropic-messages.js +67 -38
  270. package/node_modules/@earendil-works/pi-ai/dist/api/anthropic-messages.js.map +1 -1
  271. package/node_modules/@earendil-works/pi-ai/dist/api/cursor-agent/measure.d.ts +20 -0
  272. package/node_modules/@earendil-works/pi-ai/dist/api/cursor-agent/measure.d.ts.map +1 -0
  273. package/node_modules/@earendil-works/pi-ai/dist/api/cursor-agent/measure.js +443 -0
  274. package/node_modules/@earendil-works/pi-ai/dist/api/cursor-agent/measure.js.map +1 -0
  275. package/node_modules/@earendil-works/pi-ai/dist/api/cursor-agent.d.ts +2 -0
  276. package/node_modules/@earendil-works/pi-ai/dist/api/cursor-agent.d.ts.map +1 -1
  277. package/node_modules/@earendil-works/pi-ai/dist/api/cursor-agent.js +2 -0
  278. package/node_modules/@earendil-works/pi-ai/dist/api/cursor-agent.js.map +1 -1
  279. package/node_modules/@earendil-works/pi-ai/dist/api/openai-completions.js +1 -1
  280. package/node_modules/@earendil-works/pi-ai/dist/api/openai-completions.js.map +1 -1
  281. package/node_modules/@earendil-works/pi-ai/dist/index.d.ts +1 -0
  282. package/node_modules/@earendil-works/pi-ai/dist/index.d.ts.map +1 -1
  283. package/node_modules/@earendil-works/pi-ai/dist/index.js +1 -0
  284. package/node_modules/@earendil-works/pi-ai/dist/index.js.map +1 -1
  285. package/node_modules/@earendil-works/pi-ai/dist/providers/data/.manifest.json +1 -1
  286. package/node_modules/@earendil-works/pi-ai/dist/providers/data/cloudflare-ai-gateway.json +1 -1
  287. package/node_modules/@earendil-works/pi-ai/dist/providers/data/cloudflare-workers-ai.json +1 -1
  288. package/node_modules/@earendil-works/pi-ai/dist/providers/data/fireworks.json +1 -1
  289. package/node_modules/@earendil-works/pi-ai/dist/providers/data/opencode-go.json +1 -1
  290. package/node_modules/@earendil-works/pi-ai/dist/providers/data/opencode.json +1 -1
  291. package/node_modules/@earendil-works/pi-ai/dist/providers/data/openrouter.json +1 -1
  292. package/node_modules/@earendil-works/pi-ai/dist/providers/data/together.json +1 -1
  293. package/node_modules/@earendil-works/pi-ai/dist/providers/data/zai-coding-cn.json +1 -1
  294. package/node_modules/@earendil-works/pi-ai/dist/providers/data/zai.json +1 -1
  295. package/node_modules/@earendil-works/pi-ai/package.json +2 -2
  296. package/node_modules/@earendil-works/pi-pty/package.json +1 -1
  297. package/node_modules/@earendil-works/pi-telemetry/package.json +1 -1
  298. package/node_modules/@earendil-works/pi-tui/dist/components/editor.d.ts +7 -0
  299. package/node_modules/@earendil-works/pi-tui/dist/components/editor.d.ts.map +1 -1
  300. package/node_modules/@earendil-works/pi-tui/dist/components/editor.js +5 -0
  301. package/node_modules/@earendil-works/pi-tui/dist/components/editor.js.map +1 -1
  302. package/node_modules/@earendil-works/pi-tui/dist/mux.d.ts +1 -1
  303. package/node_modules/@earendil-works/pi-tui/dist/mux.d.ts.map +1 -1
  304. package/node_modules/@earendil-works/pi-tui/dist/mux.js +2 -2
  305. package/node_modules/@earendil-works/pi-tui/dist/mux.js.map +1 -1
  306. package/node_modules/@earendil-works/pi-tui/dist/terminal.d.ts +1 -0
  307. package/node_modules/@earendil-works/pi-tui/dist/terminal.d.ts.map +1 -1
  308. package/node_modules/@earendil-works/pi-tui/dist/terminal.js +19 -0
  309. package/node_modules/@earendil-works/pi-tui/dist/terminal.js.map +1 -1
  310. package/node_modules/@earendil-works/pi-tui/package.json +1 -1
  311. package/package.json +7 -7
@@ -14,10 +14,11 @@
14
14
  */
15
15
  import { randomUUID } from "node:crypto";
16
16
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
17
+ import { rm } from "node:fs/promises";
17
18
  import { basename, dirname } from "node:path";
18
19
  import { isDeepStrictEqual } from "node:util";
19
20
  import { ProviderRetryWatchdogAbortError, prepareAgentToolCall } from "@earendil-works/pi-agent-core";
20
- import { contentText, SERVER_FALLBACK_ABORTED_DIAGNOSTIC } from "@earendil-works/pi-ai";
21
+ import { contentText, measureCursorHistorySerializedBytes, SERVER_FALLBACK_ABORTED_DIAGNOSTIC, } from "@earendil-works/pi-ai";
21
22
  import { cleanupSessionResources, cursorOverflowCompactionSettings, isClassifierRefusal, isContextOverflow, isCursorPayloadResourceExhausted, isCursorQuotaResourceExhausted, isCursorZeroTokenResourceExhausted, isProviderStreamStallError, isProviderTimeoutError, isRecoverableLength, isRetryableAssistantError, modelsAreEqual, resetApiProviders, shouldRetryOverflowWithoutCompact, streamSimple, } from "@earendil-works/pi-ai/compat";
22
23
  import { extract429RetryAfterMs, parseRetryAfterMsMarker } from "@earendil-works/pi-ai/utils/retry-hint";
23
24
  import { retryBackoffDelayMs } from "@earendil-works/pi-ai/utils/retry-profile/backoff";
@@ -40,6 +41,7 @@ import { buildDynamicSystemPrompt } from "./dynamic-prompt/index.js";
40
41
  import { areExperimentalFeaturesEnabled } from "./experimental.js";
41
42
  import { exportSessionToHtml } from "./export-html/index.js";
42
43
  import { createToolHtmlRenderer } from "./export-html/tool-renderer.js";
44
+ import { resolveReserveTokens } from "./extensions/builtin/compaction/policy.js";
43
45
  import { CODEX_RESPONSES_API } from "./extensions/builtin/service-tier.js";
44
46
  import { deriveExtensionRegistrationId } from "./extensions/builtin/tool-search/engine/marker.js";
45
47
  import { getToolSearchService } from "./extensions/builtin/tool-search/service.js";
@@ -47,7 +49,7 @@ import { ExecuteToolError, ExtensionRunner, wrapRegisteredTools, } from "./exten
47
49
  import { emitSessionShutdownEvent } from "./extensions/runner.js";
48
50
  import { normalizeToolExposure, RUNTIME_EXTENSION_PATH } from "./extensions/types.js";
49
51
  import { shouldWarnHighReasoning } from "./high-reasoning-warning.js";
50
- import { filterContextExcludedMessages } from "./messages.js";
52
+ import { convertToLlm, filterContextExcludedMessages, } from "./messages.js";
51
53
  import { ModelRegistry } from "./model-registry.js";
52
54
  import { getModelNarrowingPatterns, resolveModelScope } from "./model-resolver.js";
53
55
  import { PROMPT_CACHE_SAFE_WAIT_ENV, resolvePromptCacheSafeWaitSeconds } from "./prompt-cache-budget.js";
@@ -70,9 +72,21 @@ import { getSupportedThinkingLevels, supportsMax, supportsXhigh } from "./thinki
70
72
  import { resetTimings, time } from "./timings.js";
71
73
  import { createLocalBashOperations } from "./tools/bash.js";
72
74
  import { composeFilesystemPolicies } from "./tools/filesystem-policy.js";
73
- import { createAllToolDefinitions } from "./tools/index.js";
75
+ import { createAllToolDefinitions, temporarilyDisabledToolNames } from "./tools/index.js";
74
76
  import { createToolDefinitionFromAgentTool } from "./tools/tool-definition-wrapper.js";
75
77
  import { addUsageToTotals, createUsageTotals } from "./usage-totals.js";
78
+ /** Built-in shell tools routed exclusively through eval when experimental.bashEvalOnly is enabled. */
79
+ const EVAL_ONLY_SHELL_TOOL_NAMES = ["bash", "powershell"];
80
+ /** Workflow tool routed exclusively through eval when experimental.workflowEvalOnly is enabled. */
81
+ const EVAL_ONLY_WORKFLOW_TOOL_NAMES = ["workflow"];
82
+ /** Sample eval-cell call for an eval-only tool, using the argument name that tool actually takes. */
83
+ function evalHelperCall(name) {
84
+ if (EVAL_ONLY_SHELL_TOOL_NAMES.includes(name))
85
+ return `tool.${name}({ command: "..." })`;
86
+ if (EVAL_ONLY_WORKFLOW_TOOL_NAMES.includes(name))
87
+ return `tool.${name}({ action: "..." })`;
88
+ return `tool.${name}({ ... })`;
89
+ }
76
90
  const TURN_RETRY_SUPPRESSION_PREFIX = "senpi:no-turn-retry:";
77
91
  const DEFERRED_RETRY_QUEUE_OWNERS = new WeakSet();
78
92
  /** Format the user-attributed payload for one or more explicit skill invocations. */
@@ -310,6 +324,121 @@ function isSameOverflowSource(message, model, upstreamModelId) {
310
324
  const THINKING_LEVELS_WITH_MAX = ["off", "minimal", "low", "medium", "high", "xhigh", "max"];
311
325
  /** Caps explicit skill expansion so one prompt cannot consume unbounded context. */
312
326
  export const MAX_SKILL_EXPANSIONS_PER_PROMPT = 5;
327
+ /** Cursor ingest rejects large verbatim tool payloads. The bound is UTF-8 bytes. */
328
+ export const CURSOR_TOOL_RESULT_MAX_CHARS = 2000;
329
+ export const CURSOR_TOOL_RESULT_MAX_BYTES = 50_000;
330
+ const CURSOR_TRUNCATION_MARKER = "\n...[truncated]";
331
+ export function truncateToolResultBodies(messages, maxChars = CURSOR_TOOL_RESULT_MAX_CHARS, maxBytes = CURSOR_TOOL_RESULT_MAX_BYTES, convert = (candidate) => convertToLlm(candidate)) {
332
+ if (!Array.isArray(messages) || messages.length === 0)
333
+ return { messages, changed: false };
334
+ const segmenter = new Intl.Segmenter(undefined, { granularity: "grapheme" });
335
+ const markerChars = [...segmenter.segment(CURSOR_TRUNCATION_MARKER)].length;
336
+ const result = messages.slice();
337
+ let changed = false;
338
+ // Apply the per-result character cap first, independent of the aggregate wire cap.
339
+ for (let messageIndex = result.length - 1; messageIndex >= 0; messageIndex--) {
340
+ const message = result[messageIndex];
341
+ if (message.role !== "toolResult" || !Array.isArray(message.content))
342
+ continue;
343
+ let content = message.content;
344
+ for (let partIndex = content.length - 1; partIndex >= 0; partIndex--) {
345
+ const part = content[partIndex];
346
+ if (part.type === "image" && typeof part.data === "string")
347
+ continue;
348
+ if (part.type !== "text" || typeof part.text !== "string")
349
+ continue;
350
+ const graphemes = [...segmenter.segment(part.text)].map((item) => item.segment);
351
+ if (graphemes.length <= maxChars)
352
+ continue;
353
+ const kept = graphemes.slice(0, Math.max(0, maxChars - markerChars)).join("");
354
+ const nextText = kept + CURSOR_TRUNCATION_MARKER;
355
+ content = content === message.content ? content.slice() : content;
356
+ content[partIndex] = { ...part, text: nextText };
357
+ result[messageIndex] = { ...message, content };
358
+ changed = true;
359
+ }
360
+ }
361
+ const measure = (candidate) => {
362
+ const converted = convert(candidate);
363
+ const activeUserMessageIndex = converted.at(-1)?.role === "user" ? converted.length - 1 : -1;
364
+ return measureCursorHistorySerializedBytes(converted, activeUserMessageIndex);
365
+ };
366
+ const fits = (candidate = result) => measure(candidate) <= maxBytes;
367
+ if (fits())
368
+ return { messages: changed ? result : messages, changed };
369
+ // Empty the oldest result bodies. Search the monotonic prefix of candidates
370
+ // rather than serializing once for every result (admission must stay bounded).
371
+ const emptyToolResult = (message) => {
372
+ if (message.role !== "toolResult" || !Array.isArray(message.content))
373
+ return message;
374
+ const emptiedContent = message.content.map((part) => part.type === "text" ? { ...part, text: "" } : part.type === "image" ? { ...part, data: "" } : part);
375
+ const content = emptiedContent.filter((part, index) => {
376
+ if (index === 0)
377
+ return true;
378
+ const previous = emptiedContent[index - 1];
379
+ const empty = part.type === "text" ? part.text === "" : part.type === "image" && part.data === "";
380
+ const previousEmpty = previous.type === "text" ? previous.text === "" : previous.type === "image" && previous.data === "";
381
+ return !empty || !previousEmpty;
382
+ });
383
+ return { ...message, content };
384
+ };
385
+ const toolResultIndexes = result.flatMap((message, index) => message.role === "toolResult" && Array.isArray(message.content) ? [index] : []);
386
+ const withEmptyPrefix = (count) => {
387
+ const candidate = result.slice();
388
+ for (let i = 0; i < count; i++)
389
+ candidate[toolResultIndexes[i]] = emptyToolResult(candidate[toolResultIndexes[i]]);
390
+ return candidate;
391
+ };
392
+ let low = 0;
393
+ let high = toolResultIndexes.length;
394
+ while (low < high) {
395
+ const middle = Math.floor((low + high) / 2);
396
+ if (fits(withEmptyPrefix(middle + 1)))
397
+ high = middle;
398
+ else
399
+ low = middle + 1;
400
+ }
401
+ const emptyCount = Math.min(low + 1, toolResultIndexes.length);
402
+ if (emptyCount > 0) {
403
+ result.splice(0, result.length, ...withEmptyPrefix(emptyCount));
404
+ changed = true;
405
+ }
406
+ if (fits())
407
+ return { messages: changed ? result : messages, changed };
408
+ // If metadata alone exceeds the cap, discard the oldest complete turns. This
409
+ // search is also monotonic and avoids quadratic whole-history reserialization.
410
+ const turnRanges = [];
411
+ const isConvertedUser = (message) => convert([message])[0]?.role === "user";
412
+ for (let index = 0; index < result.length; index++) {
413
+ if (!isConvertedUser(result[index]))
414
+ continue;
415
+ const nextUser = result.findIndex((message, nextIndex) => nextIndex > index && isConvertedUser(message));
416
+ turnRanges.push([index, nextUser < 0 ? result.length : nextUser]);
417
+ }
418
+ const withoutTurns = (count) => {
419
+ if (count === 0)
420
+ return result;
421
+ const start = turnRanges[0]?.[0] ?? 0;
422
+ const end = turnRanges[count - 1]?.[1] ?? start;
423
+ return [...result.slice(0, start), ...result.slice(end)];
424
+ };
425
+ low = 0;
426
+ high = turnRanges.length;
427
+ while (low < high) {
428
+ const middle = Math.floor((low + high) / 2);
429
+ if (fits(withoutTurns(middle + 1)))
430
+ high = middle;
431
+ else
432
+ low = middle + 1;
433
+ }
434
+ const turnCount = Math.min(low + 1, turnRanges.length);
435
+ if (turnCount > 0) {
436
+ const next = withoutTurns(turnCount);
437
+ result.splice(0, result.length, ...next);
438
+ changed = true;
439
+ }
440
+ return { messages: changed ? result : messages, changed };
441
+ }
313
442
  // ============================================================================
314
443
  // AgentSession Class
315
444
  // ============================================================================
@@ -395,6 +524,10 @@ export class AgentSession {
395
524
  this._pendingBashMessages = [];
396
525
  this._turnIndex = 0;
397
526
  this._baseToolDefinitions = new Map();
527
+ /** Policy tools withheld from the model, retained so disarming can restore direct access. */
528
+ this._withheldEvalOnlyToolNames = new Set();
529
+ /** Eval-only hint names this session published, so a disarm can withdraw exactly those. */
530
+ this._publishedEvalOnlyHintNames = new Set();
398
531
  this._extensionMode = "print";
399
532
  // Tool registry for extension getTools/setTools
400
533
  this._toolRegistry = new Map();
@@ -516,6 +649,8 @@ export class AgentSession {
516
649
  this._extensionRunnerRef = config.extensionRunnerRef;
517
650
  this._initialActiveToolNames = config.initialActiveToolNames;
518
651
  this._defaultToolNames = config.defaultToolNames ? new Set(config.defaultToolNames) : undefined;
652
+ this._evalOnlyToolNamesOverride = config.evalOnlyToolNames ? new Set(config.evalOnlyToolNames) : undefined;
653
+ this._evalOnlyToolNames = this._resolveEvalOnlyToolNames();
519
654
  this._allowedToolNames = config.allowedToolNames ? new Set(config.allowedToolNames) : undefined;
520
655
  this._excludedToolNames = config.excludedToolNames ? new Set(config.excludedToolNames) : undefined;
521
656
  this._baseToolsOverride = config.baseToolsOverride;
@@ -727,6 +862,14 @@ export class AgentSession {
727
862
  (this.agent.prepareNextTurn
728
863
  ? async (_turn, signal) => await this.agent.prepareNextTurn?.(signal)
729
864
  : undefined);
865
+ const previousTransformContext = this.agent.transformContext;
866
+ this.agent.transformContext = async (messages, signal) => {
867
+ const transformed = previousTransformContext ? await previousTransformContext(messages, signal) : messages;
868
+ if (this.model?.provider === "cursor" || this.model?.provider === "cursor-cli-oauth") {
869
+ return ((await truncateToolResultBodies(transformed, CURSOR_TOOL_RESULT_MAX_CHARS, CURSOR_TOOL_RESULT_MAX_BYTES, (candidate) => this.agent.convertToLlm(candidate))).messages ?? transformed);
870
+ }
871
+ return transformed;
872
+ };
730
873
  this.agent.prepareNextTurnWithContext = async (turn, signal) => {
731
874
  // Enforce compaction only when this prepare precedes an actual provider
732
875
  // admission: a tool continuation or queued steer/follow-up messages. A
@@ -735,7 +878,8 @@ export class AgentSession {
735
878
  const compactBeforeNextAdmission = async () => {
736
879
  const provider = this.model?.provider;
737
880
  // Cursor rebuilds the full conversation each hop. Compacting here
738
- // mutates rootPrompt mid-run and poisons conversationId.
881
+ // mutates rootPrompt mid-run and poisons conversationId (#984).
882
+ // Still truncate verbatim toolResult bodies so the skip cannot send MB-scale payloads (#1043).
739
883
  if (provider === "cursor" || provider === "cursor-cli-oauth") {
740
884
  return false;
741
885
  }
@@ -816,6 +960,19 @@ export class AgentSession {
816
960
  // Event Subscription
817
961
  // =========================================================================
818
962
  /** Emit an event to all listeners */
963
+ _emitEntryAppended(entryId) {
964
+ if (this._extensionMode !== "rpc")
965
+ return;
966
+ const entry = this.sessionManager.getEntry(entryId);
967
+ if (entry)
968
+ this._emit({ type: "entry_appended", entry });
969
+ }
970
+ /** Append a transport-provided entry and publish it on the RPC event stream. */
971
+ appendSessionEntry(entry) {
972
+ this.sessionManager.appendEntry(entry);
973
+ this.agent.state.messages = this.sessionManager.buildSessionContext().messages;
974
+ this._emitEntryAppended(entry.id);
975
+ }
819
976
  _emit(event) {
820
977
  this._logSessionEvent(event);
821
978
  for (const l of this._eventListeners) {
@@ -976,6 +1133,7 @@ export class AgentSession {
976
1133
  _invalidateCompactionForModelSelection() {
977
1134
  this.abortCompaction();
978
1135
  this.abortBranchSummary();
1136
+ this._delegatedCompactionKey = undefined;
979
1137
  this._incrementMessageRevision();
980
1138
  }
981
1139
  _getIdleWaitPromise() {
@@ -1367,7 +1525,7 @@ export class AgentSession {
1367
1525
  event.message.role === "assistant" ||
1368
1526
  event.message.role === "toolResult") {
1369
1527
  // Regular LLM message - persist as SessionMessageEntry
1370
- this.sessionManager.appendMessage(event.message);
1528
+ this._emitEntryAppended(this.sessionManager.appendMessage(event.message));
1371
1529
  this._incrementMessageRevision();
1372
1530
  }
1373
1531
  // Other message types (bashExecution, compactionSummary, branchSummary) are persisted elsewhere
@@ -2001,6 +2159,9 @@ export class AgentSession {
2001
2159
  async executeTool(toolName, params, options) {
2002
2160
  let activeTools = this.getActiveToolNames();
2003
2161
  let tool = this.agent.state.tools.find((candidate) => candidate.name === toolName);
2162
+ if (!tool && this._isEvalOnlyPolicyArmed() && this._evalOnlyToolNames?.has(toolName)) {
2163
+ tool = this._toolRegistry.get(toolName);
2164
+ }
2004
2165
  if (!tool &&
2005
2166
  options?.activateInactiveTool === true &&
2006
2167
  this._toolDefinitions.has(toolName) &&
@@ -2075,6 +2236,45 @@ export class AgentSession {
2075
2236
  return false;
2076
2237
  return this._lazyToolActivators.some((activate) => activate(toolName));
2077
2238
  }
2239
+ _isEvalOnlyPolicyArmed() {
2240
+ return this._evalOnlyToolNames !== undefined && this._toolRegistry.has("eval");
2241
+ }
2242
+ /**
2243
+ * Resolve the eval-only policy from settings so a reload picks up a flag change.
2244
+ * An explicitly configured set stays authoritative for SDK embedders and tests.
2245
+ */
2246
+ _resolveEvalOnlyToolNames() {
2247
+ if (this._evalOnlyToolNamesOverride !== undefined)
2248
+ return this._evalOnlyToolNamesOverride;
2249
+ const names = [
2250
+ ...(this.settingsManager.getExperimentalBashEvalOnly() ? EVAL_ONLY_SHELL_TOOL_NAMES : []),
2251
+ ...(this.settingsManager.getExperimentalWorkflowEvalOnly() ? EVAL_ONLY_WORKFLOW_TOOL_NAMES : []),
2252
+ ];
2253
+ return names.length > 0 ? new Set(names) : undefined;
2254
+ }
2255
+ /**
2256
+ * Publish per-tool eval-only redirect hints so a direct call names its own eval helper.
2257
+ *
2258
+ * Hints published here are tracked so a disarm - or a shrinking armed set, e.g. bash+workflow
2259
+ * down to workflow only - withdraws the stale ones. Only tracked names are ever deleted, so
2260
+ * hints owned by other publishers survive.
2261
+ */
2262
+ _publishEvalOnlyToolHints() {
2263
+ const armed = this._isEvalOnlyPolicyArmed() ? (this._evalOnlyToolNames ?? new Set()) : undefined;
2264
+ for (const name of this._publishedEvalOnlyHintNames) {
2265
+ if (armed?.has(name))
2266
+ continue;
2267
+ delete this.agent.removedToolHints[name];
2268
+ }
2269
+ this._publishedEvalOnlyHintNames.clear();
2270
+ if (!armed)
2271
+ return;
2272
+ for (const name of armed) {
2273
+ this.agent.removedToolHints[name] =
2274
+ `Run ${name} inside an eval cell via ${evalHelperCall(name)}; hooks and permissions still apply.`;
2275
+ this._publishedEvalOnlyHintNames.add(name);
2276
+ }
2277
+ }
2078
2278
  /**
2079
2279
  * Set active tools by name.
2080
2280
  * Only tools in the registry can be enabled. Unknown tool names are ignored.
@@ -2102,7 +2302,23 @@ export class AgentSession {
2102
2302
  setActiveToolsByName(toolNames) {
2103
2303
  const tools = [];
2104
2304
  const validToolNames = [];
2105
- for (const name of toolNames) {
2305
+ const policyArmed = this._isEvalOnlyPolicyArmed();
2306
+ this._publishEvalOnlyToolHints();
2307
+ const policyNames = this._evalOnlyToolNames;
2308
+ let filteredToolNames = toolNames;
2309
+ if (policyArmed && policyNames) {
2310
+ for (const name of toolNames) {
2311
+ if (policyNames.has(name))
2312
+ this._withheldEvalOnlyToolNames.add(name);
2313
+ }
2314
+ filteredToolNames = toolNames.filter((name) => !policyNames.has(name));
2315
+ }
2316
+ else {
2317
+ this._withheldEvalOnlyToolNames.clear();
2318
+ }
2319
+ // Remember the unfiltered request so a later disarm can restore withheld shell tools.
2320
+ this._requestedActiveToolNames = [...new Set([...toolNames, ...this._withheldEvalOnlyToolNames])];
2321
+ for (const name of filteredToolNames) {
2106
2322
  const tool = this._toolRegistry.get(name);
2107
2323
  if (tool) {
2108
2324
  tools.push(tool);
@@ -2116,8 +2332,14 @@ export class AgentSession {
2116
2332
  this._baseSystemPrompt = this._rebuildSystemPrompt(validToolNames);
2117
2333
  this.agent.state.systemPrompt = this._systemPromptOverride ?? this._baseSystemPrompt;
2118
2334
  if (activeToolNamesChanged) {
2119
- this.abortCompaction();
2120
- this._incrementMessageRevision();
2335
+ // A tool change emitted while extensions are still binding belongs to the
2336
+ // binding itself, not to a mid-session change. The session was already
2337
+ // committed to the client, so cancelling or invalidating work it started
2338
+ // against that session would be spurious.
2339
+ if (this._extensionBindingPromptReadiness === undefined) {
2340
+ this.abortCompaction();
2341
+ this._incrementMessageRevision();
2342
+ }
2121
2343
  }
2122
2344
  }
2123
2345
  /** Whether compaction or branch summarization is currently running */
@@ -2259,9 +2481,26 @@ export class AgentSession {
2259
2481
  appendSystemPrompt: loaderAppendSystemPrompt.length > 0 ? loaderAppendSystemPrompt.join("\n\n") : undefined,
2260
2482
  };
2261
2483
  const basePrompt = loaderSystemPrompt ?? buildDynamicSystemPrompt(this._baseSystemPromptOptions);
2262
- return loaderAppendSystemPrompt.length > 0
2263
- ? `${basePrompt}\n\n${loaderAppendSystemPrompt.join("\n\n")}`
2264
- : basePrompt;
2484
+ const prompt = loaderAppendSystemPrompt.length > 0 ? `${basePrompt}\n\n${loaderAppendSystemPrompt.join("\n\n")}` : basePrompt;
2485
+ if (!this._isEvalOnlyPolicyArmed())
2486
+ return prompt;
2487
+ const armed = this._evalOnlyToolNames ?? new Set();
2488
+ const sentences = [];
2489
+ const shellHelpers = EVAL_ONLY_SHELL_TOOL_NAMES.filter((name) => armed.has(name)).map(evalHelperCall);
2490
+ if (shellHelpers.length > 0) {
2491
+ sentences.push(`Shell commands run ONLY inside eval cells via ${shellHelpers.join(" or ")}; hooks and permissions still apply.`);
2492
+ }
2493
+ if (armed.has("workflow")) {
2494
+ sentences.push(`The workflow tool runs ONLY inside eval cells via ${evalHelperCall("workflow")}; hooks and permissions still apply.`);
2495
+ }
2496
+ // SDK embedders can arm names outside the built-in groups; they still need eval guidance.
2497
+ const otherHelpers = [...armed]
2498
+ .filter((name) => !EVAL_ONLY_SHELL_TOOL_NAMES.includes(name) && name !== "workflow")
2499
+ .map(evalHelperCall);
2500
+ if (otherHelpers.length > 0) {
2501
+ sentences.push(`These tools run ONLY inside eval cells via ${otherHelpers.join(" or ")}; hooks and permissions still apply.`);
2502
+ }
2503
+ return sentences.length > 0 ? `${prompt}\n\n${sentences.join("\n\n")}` : prompt;
2265
2504
  }
2266
2505
  /**
2267
2506
  * Send a prompt to the agent.
@@ -3053,7 +3292,7 @@ export class AgentSession {
3053
3292
  }
3054
3293
  _appendCustomMessage(appMessage) {
3055
3294
  this.agent.state.messages.push(appMessage);
3056
- this.sessionManager.appendCustomMessageEntry(appMessage.customType, appMessage.content, appMessage.display, appMessage.details);
3295
+ this._emitEntryAppended(this.sessionManager.appendCustomMessageEntry(appMessage.customType, appMessage.content, appMessage.display, appMessage.details));
3057
3296
  this._incrementMessageRevision();
3058
3297
  this._emit({ type: "message_start", message: appMessage });
3059
3298
  this._emit({ type: "message_end", message: appMessage });
@@ -3336,7 +3575,10 @@ export class AgentSession {
3336
3575
  }
3337
3576
  async _switchActiveModel(model, opts) {
3338
3577
  const previousModel = this.model;
3339
- if (opts.invalidateCompaction && this._modelSelectionChangesContext(previousModel, model)) {
3578
+ if (opts.invalidateCompaction &&
3579
+ (this._modelSelectionChangesContext(previousModel, model) ||
3580
+ previousModel?.provider !== model.provider ||
3581
+ previousModel?.id !== model.id)) {
3340
3582
  this._invalidateCompactionForModelSelection();
3341
3583
  }
3342
3584
  const thinking = this._getThinkingForModelSwitch(model, opts.ephemeralThinkingLevel);
@@ -3408,7 +3650,9 @@ export class AgentSession {
3408
3650
  nextIndex = direction === "forward" ? (currentIndex + 1) % len : (currentIndex - 1 + len) % len;
3409
3651
  }
3410
3652
  const next = favoriteModels[nextIndex];
3411
- const invalidatesCompaction = this._modelSelectionChangesContext(currentModel, next.model);
3653
+ const invalidatesCompaction = this._modelSelectionChangesContext(currentModel, next.model) ||
3654
+ currentModel?.provider !== next.model.provider ||
3655
+ currentModel?.id !== next.model.id;
3412
3656
  if (invalidatesCompaction) {
3413
3657
  this._invalidateCompactionForModelSelection();
3414
3658
  }
@@ -3610,6 +3854,7 @@ export class AgentSession {
3610
3854
  setSteeringMode(mode) {
3611
3855
  this.agent.steeringMode = mode;
3612
3856
  this.settingsManager.setSteeringMode(mode);
3857
+ this._emitSessionSettingsChanged();
3613
3858
  }
3614
3859
  /**
3615
3860
  * Set follow-up message mode.
@@ -3618,6 +3863,7 @@ export class AgentSession {
3618
3863
  setFollowUpMode(mode) {
3619
3864
  this.agent.followUpMode = mode;
3620
3865
  this.settingsManager.setFollowUpMode(mode);
3866
+ this._emitSessionSettingsChanged();
3621
3867
  }
3622
3868
  // =========================================================================
3623
3869
  // Compaction
@@ -4100,7 +4346,7 @@ export class AgentSession {
4100
4346
  if (sourceChanged) {
4101
4347
  return await this._rejectCompaction(request, requestId, operationId, "stale-revision", false);
4102
4348
  }
4103
- if (this._wouldCompactionOverflow(pathEntries, compactionResult, fromExtension, model)) {
4349
+ if (await this._wouldCompactionOverflow(pathEntries, compactionResult, fromExtension, model)) {
4104
4350
  return await this._rejectCompaction(request, requestId, operationId, "would-overflow", false);
4105
4351
  }
4106
4352
  const compactionEntryId = this.sessionManager.appendCompaction(compactionResult.summary, compactionResult.firstKeptEntryId, compactionResult.tokensBefore, compactionResult.details, fromExtension, compactionResult.usage);
@@ -4141,6 +4387,7 @@ export class AgentSession {
4141
4387
  })) {
4142
4388
  throw new CompactionCancelledError();
4143
4389
  }
4390
+ this._delegatedCompactionKey = undefined;
4144
4391
  if (request.owner === "compaction" && this._compactionAbortController === request.controller) {
4145
4392
  this._compactionAbortController = undefined;
4146
4393
  }
@@ -4191,7 +4438,7 @@ export class AgentSession {
4191
4438
  finishCompactionWork();
4192
4439
  }
4193
4440
  }
4194
- _wouldCompactionOverflow(pathEntries, compactionResult, fromExtension, model) {
4441
+ async _wouldCompactionOverflow(pathEntries, compactionResult, fromExtension, model) {
4195
4442
  const currentLeaf = pathEntries[pathEntries.length - 1];
4196
4443
  if (!currentLeaf)
4197
4444
  return false;
@@ -4206,10 +4453,18 @@ export class AgentSession {
4206
4453
  details: compactionResult.details,
4207
4454
  fromHook: fromExtension,
4208
4455
  };
4209
- const simulatedMessages = buildSessionContext([...pathEntries, simulatedCompactionEntry], simulatedCompactionEntry.id).messages;
4456
+ let simulatedMessages = buildSessionContext([...pathEntries, simulatedCompactionEntry], simulatedCompactionEntry.id).messages;
4457
+ // Size the same retained context that will be admitted to Cursor. Persisted JSONL
4458
+ // remains verbatim, but the in-memory request representation is bounded first.
4459
+ if (model.provider === "cursor" || model.provider === "cursor-cli-oauth") {
4460
+ simulatedMessages = truncateToolResultBodies(simulatedMessages).messages ?? simulatedMessages;
4461
+ }
4210
4462
  const contextTokens = estimateMessagesTokens(filterContextExcludedMessages(simulatedMessages));
4211
4463
  const settings = this.settingsManager.getCompactionSettings();
4212
- return contextTokens > model.contextWindow - settings.reserveTokens;
4464
+ const reserveTokens = settings.reserveScalingEnabled === false
4465
+ ? settings.reserveTokens
4466
+ : resolveReserveTokens(model.contextWindow, settings.reserveTokens);
4467
+ return contextTokens > model.contextWindow - reserveTokens;
4213
4468
  }
4214
4469
  /**
4215
4470
  * Replaces agent state with the canonical session context while keeping exact
@@ -4237,6 +4492,9 @@ export class AgentSession {
4237
4492
  // also emitted with accepted:false so the compaction extension's circuit-breaker
4238
4493
  // bookkeeping stops being dead code; other builtin session_compact handlers guard
4239
4494
  // on event.accepted.
4495
+ if (rejectionCause === "external-owner" && this.model) {
4496
+ this._delegatedCompactionKey = { provider: this.model.provider, id: this.model.id };
4497
+ }
4240
4498
  const trimmedExtensionReason = extensionReason?.trim();
4241
4499
  const detailedMessage = trimmedExtensionReason
4242
4500
  ? `Compaction rejected: ${trimmedExtensionReason}`
@@ -4370,6 +4628,9 @@ export class AgentSession {
4370
4628
  if (!model)
4371
4629
  return;
4372
4630
  const settings = this.settingsManager.getCompactionSettings();
4631
+ const reserveTokens = settings.reserveScalingEnabled === false
4632
+ ? settings.reserveTokens
4633
+ : resolveReserveTokens(model.contextWindow, settings.reserveTokens);
4373
4634
  const isOversized = () => {
4374
4635
  const providerMessages = filterContextExcludedMessages([...this.agent.state.messages, ...messages]);
4375
4636
  const estimate = estimateContextTokens(providerMessages);
@@ -4380,7 +4641,7 @@ export class AgentSession {
4380
4641
  const contextTokens = usageMessage?.role === "assistant" && this._isAssistantFromBeforeLatestCompaction(usageMessage)
4381
4642
  ? estimateMessagesTokens(providerMessages)
4382
4643
  : estimate.tokens;
4383
- return contextTokens > model.contextWindow - settings.reserveTokens;
4644
+ return contextTokens > model.contextWindow - reserveTokens;
4384
4645
  };
4385
4646
  if (!settings.enabled || !isOversized())
4386
4647
  return;
@@ -4573,15 +4834,15 @@ export class AgentSession {
4573
4834
  return claimant !== undefined && !claimant.signal.aborted;
4574
4835
  }
4575
4836
  _isCompactionDelegated() {
4576
- const state = this._compactionLifecycle.state;
4577
4837
  const model = this.model;
4578
- return (state.status === "failed" &&
4579
- state.rejectionCause === "external-owner" &&
4580
- state.model !== undefined &&
4838
+ return (this._delegatedCompactionKey !== undefined &&
4581
4839
  model !== undefined &&
4582
- state.model.provider === model.provider);
4840
+ this._delegatedCompactionKey.provider === model.provider &&
4841
+ this._delegatedCompactionKey.id === model.id);
4583
4842
  }
4584
4843
  async _runPrePromptCompaction(lastAssistantMessage, skipAbortedCheck, reason = "pre_prompt", willRetry = false, allowSummaryOnly = false) {
4844
+ if (this._isCompactionDelegated())
4845
+ return false;
4585
4846
  const controller = new AbortController();
4586
4847
  const requestId = randomUUID();
4587
4848
  this._claimCompactionController(controller, "compaction");
@@ -4754,6 +5015,8 @@ export class AgentSession {
4754
5015
  * @returns Whether the post-run loop should call `agent.continue()`
4755
5016
  */
4756
5017
  async _runAutoCompaction(reason, willRetry) {
5018
+ if (this._isCompactionDelegated())
5019
+ return false;
4757
5020
  const finishCompactionWork = this._sessionWorkBarrier.begin();
4758
5021
  const agentMessagesAtStart = this.agent.state.messages.slice();
4759
5022
  const autoCompactionController = new AbortController();
@@ -4887,6 +5150,15 @@ export class AgentSession {
4887
5150
  */
4888
5151
  setAutoCompactionEnabled(enabled) {
4889
5152
  this.settingsManager.setCompactionEnabled(enabled);
5153
+ this._emitSessionSettingsChanged();
5154
+ }
5155
+ _emitSessionSettingsChanged() {
5156
+ this._emit({
5157
+ type: "session_settings_changed",
5158
+ steeringMode: this.steeringMode,
5159
+ followUpMode: this.followUpMode,
5160
+ autoCompactionEnabled: this.autoCompactionEnabled,
5161
+ });
4890
5162
  }
4891
5163
  /** Whether auto-compaction is enabled */
4892
5164
  get autoCompactionEnabled() {
@@ -4984,6 +5256,7 @@ export class AgentSession {
4984
5256
  : undefined;
4985
5257
  }
4986
5258
  _refreshCurrentModelFromRegistry() {
5259
+ this._delegatedCompactionKey = undefined;
4987
5260
  const currentModel = this.model;
4988
5261
  if (!currentModel) {
4989
5262
  return;
@@ -5308,8 +5581,11 @@ export class AgentSession {
5308
5581
  }),
5309
5582
  })),
5310
5583
  ].filter((tool) => isAllowedTool(tool.definition.name));
5584
+ // Withheld tools stay in _baseToolDefinitions (and therefore in _toolRegistry, which
5585
+ // getRegisteredTool serves to the Cursor exec bridge) but are dropped from the model-facing
5586
+ // definitions so they never reach the prompt. See temporarilyDisabledToolNames.
5311
5587
  const definitionRegistry = new Map(Array.from(this._baseToolDefinitions.entries())
5312
- .filter(([name]) => isAllowedTool(name))
5588
+ .filter(([name]) => isAllowedTool(name) && !temporarilyDisabledToolNames.has(name))
5313
5589
  .map(([name, definition]) => [
5314
5590
  name,
5315
5591
  {
@@ -5351,13 +5627,22 @@ export class AgentSession {
5351
5627
  toolRegistry.set(tool.name, tool);
5352
5628
  }
5353
5629
  this._toolRegistry = toolRegistry;
5630
+ this._publishEvalOnlyToolHints();
5354
5631
  const isDirectlyExposed = (name) => {
5355
5632
  const entry = this._toolDefinitions.get(name);
5356
5633
  return entry !== undefined && normalizeToolExposure(entry.definition).exposure === "direct";
5357
5634
  };
5358
- const nextActiveToolNames = (options?.activeToolNames ? [...options.activeToolNames] : [...previousActiveToolNames]).filter((name) => {
5635
+ // A withheld tool is dropped from the DEFAULT selection only. An explicit activeToolNames
5636
+ // request names the tool deliberately, and callers that do so (tests, SDK embedders,
5637
+ // filesystem-policy wiring) still expect it to activate.
5638
+ const hasExplicitActiveToolNames = options?.activeToolNames !== undefined;
5639
+ const nextActiveToolNames = (options?.activeToolNames
5640
+ ? [...options.activeToolNames]
5641
+ : [...(this._requestedActiveToolNames ?? previousActiveToolNames)]).filter((name) => {
5359
5642
  if (!isAllowedTool(name))
5360
5643
  return false;
5644
+ if (!hasExplicitActiveToolNames && temporarilyDisabledToolNames.has(name))
5645
+ return false;
5361
5646
  const previousRegistrationIds = options?.previousActiveToolRegistrationIds;
5362
5647
  if (!previousRegistrationIds)
5363
5648
  return true;
@@ -5388,6 +5673,7 @@ export class AgentSession {
5388
5673
  this.setActiveToolsByName([...new Set(nextActiveToolNames)]);
5389
5674
  }
5390
5675
  _buildRuntime(options) {
5676
+ this._delegatedCompactionKey = undefined;
5391
5677
  const autoResizeImages = this.settingsManager.getImageAutoResize();
5392
5678
  const shellCommandPrefix = this.settingsManager.getShellCommandPrefix();
5393
5679
  const shellPath = this.settingsManager.getShellPath();
@@ -5439,7 +5725,9 @@ export class AgentSession {
5439
5725
  const oldExtensionIdentities = oldExtensionRunner.getExtensionIdentities();
5440
5726
  const previousFlagValues = oldExtensionRunner.getFlagValues();
5441
5727
  const previousActiveToolRegistrationIds = new Map();
5442
- for (const name of this.getActiveToolNames()) {
5728
+ // Cover withheld eval-only tools too: the rebuild drops any seeded name missing from this
5729
+ // map, which would strand shell tools when the policy disarms during this reload.
5730
+ for (const name of this._requestedActiveToolNames ?? this.getActiveToolNames()) {
5443
5731
  const entry = this._toolDefinitions.get(name);
5444
5732
  if (entry)
5445
5733
  previousActiveToolRegistrationIds.set(name, deriveExtensionRegistrationId(entry.sourceInfo, name));
@@ -5450,6 +5738,12 @@ export class AgentSession {
5450
5738
  });
5451
5739
  time("shutdown", "reload");
5452
5740
  await this.settingsManager.reload();
5741
+ this._delegatedCompactionKey = undefined;
5742
+ // Capture the unfiltered request BEFORE the rebuild reassigns it, so a policy that
5743
+ // disarms during this reload can still restore its withheld shell tools.
5744
+ const requestedActiveToolNamesBeforeRebuild = [...(this._requestedActiveToolNames ?? this.getActiveToolNames())];
5745
+ // A flag change must take effect on reload, matching documented settings hot-reload behavior.
5746
+ this._evalOnlyToolNames = this._resolveEvalOnlyToolNames();
5453
5747
  this.syncQueueModesFromSettings();
5454
5748
  resetApiProviders();
5455
5749
  time("settings", "reload");
@@ -5474,7 +5768,7 @@ export class AgentSession {
5474
5768
  time("resources", "reload");
5475
5769
  try {
5476
5770
  this._buildRuntime({
5477
- activeToolNames: this.getActiveToolNames(),
5771
+ activeToolNames: requestedActiveToolNamesBeforeRebuild,
5478
5772
  flagValues: previousFlagValues,
5479
5773
  includeAllExtensionTools: true,
5480
5774
  previousActiveToolRegistrationIds,
@@ -6179,12 +6473,13 @@ export class AgentSession {
6179
6473
  try {
6180
6474
  const result = await executeBashWithOperations(resolvedCommand, this.sessionManager.getCwd(), options?.operations ?? createLocalBashOperations({ shellPath }), {
6181
6475
  onChunk: (delta) => {
6182
- onChunk?.(delta);
6476
+ const callbackResult = onChunk?.(delta);
6183
6477
  this._emit({
6184
6478
  type: "bash_execution_update",
6185
6479
  id: options?.id,
6186
6480
  delta,
6187
6481
  });
6482
+ return callbackResult;
6188
6483
  },
6189
6484
  signal: abortController.signal,
6190
6485
  });
@@ -6232,6 +6527,10 @@ export class AgentSession {
6232
6527
  abortController.abort();
6233
6528
  }
6234
6529
  }
6530
+ /** Remove a host-owned full-output spill after a remote observer failed. */
6531
+ async cleanupBashOutput(path) {
6532
+ await rm(path, { force: true });
6533
+ }
6235
6534
  /** Whether a bash command is currently running */
6236
6535
  get isBashRunning() {
6237
6536
  return this._bashAbortControllers.size > 0;
@@ -6433,6 +6732,7 @@ export class AgentSession {
6433
6732
  }
6434
6733
  // Update agent state (preserving exact messages still awaiting persistence)
6435
6734
  this._restoreAgentMessagesFromSession();
6735
+ this._delegatedCompactionKey = undefined;
6436
6736
  this._incrementMessageRevision();
6437
6737
  // Emit session_tree event
6438
6738
  await this._extensionRunner.emit({