@code-yeongyu/senpi 2026.8.29 → 2026.8.30-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 (346) hide show
  1. package/CHANGELOG.md +172 -0
  2. package/dist/core/agent-session.d.ts +47 -1
  3. package/dist/core/agent-session.d.ts.map +1 -1
  4. package/dist/core/agent-session.js +406 -39
  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 +18 -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 +32 -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 +164 -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-retry.d.ts +1 -1
  56. package/dist/core/extensions/builtin/compaction/idle-retry.d.ts.map +1 -1
  57. package/dist/core/extensions/builtin/compaction/idle-retry.js +1 -1
  58. package/dist/core/extensions/builtin/compaction/idle-retry.js.map +1 -1
  59. package/dist/core/extensions/builtin/compaction/idle.d.ts +1 -0
  60. package/dist/core/extensions/builtin/compaction/idle.d.ts.map +1 -1
  61. package/dist/core/extensions/builtin/compaction/idle.js +13 -2
  62. package/dist/core/extensions/builtin/compaction/idle.js.map +1 -1
  63. package/dist/core/extensions/builtin/compaction/index.d.ts +1 -1
  64. package/dist/core/extensions/builtin/compaction/index.d.ts.map +1 -1
  65. package/dist/core/extensions/builtin/compaction/index.js +148 -150
  66. package/dist/core/extensions/builtin/compaction/index.js.map +1 -1
  67. package/dist/core/extensions/builtin/compaction/log.d.ts +1 -1
  68. package/dist/core/extensions/builtin/compaction/log.d.ts.map +1 -1
  69. package/dist/core/extensions/builtin/compaction/log.js +2 -0
  70. package/dist/core/extensions/builtin/compaction/log.js.map +1 -1
  71. package/dist/core/extensions/builtin/compaction/model-selection.d.ts +14 -0
  72. package/dist/core/extensions/builtin/compaction/model-selection.d.ts.map +1 -0
  73. package/dist/core/extensions/builtin/compaction/model-selection.js +37 -0
  74. package/dist/core/extensions/builtin/compaction/model-selection.js.map +1 -0
  75. package/dist/core/extensions/builtin/compaction/model-usability-budget.d.ts +30 -0
  76. package/dist/core/extensions/builtin/compaction/model-usability-budget.d.ts.map +1 -0
  77. package/dist/core/extensions/builtin/compaction/model-usability-budget.js +74 -0
  78. package/dist/core/extensions/builtin/compaction/model-usability-budget.js.map +1 -0
  79. package/dist/core/extensions/builtin/compaction/openai-remote-dependencies.d.ts +9 -0
  80. package/dist/core/extensions/builtin/compaction/openai-remote-dependencies.d.ts.map +1 -0
  81. package/dist/core/extensions/builtin/compaction/openai-remote-dependencies.js +2 -0
  82. package/dist/core/extensions/builtin/compaction/openai-remote-dependencies.js.map +1 -0
  83. package/dist/core/extensions/builtin/compaction/openai-remote.d.ts +3 -6
  84. package/dist/core/extensions/builtin/compaction/openai-remote.d.ts.map +1 -1
  85. package/dist/core/extensions/builtin/compaction/openai-remote.js.map +1 -1
  86. package/dist/core/extensions/builtin/compaction/orchestration.d.ts +46 -0
  87. package/dist/core/extensions/builtin/compaction/orchestration.d.ts.map +1 -0
  88. package/dist/core/extensions/builtin/compaction/orchestration.js +103 -0
  89. package/dist/core/extensions/builtin/compaction/orchestration.js.map +1 -0
  90. package/dist/core/extensions/builtin/compaction/policy.d.ts +12 -2
  91. package/dist/core/extensions/builtin/compaction/policy.d.ts.map +1 -1
  92. package/dist/core/extensions/builtin/compaction/policy.js +52 -26
  93. package/dist/core/extensions/builtin/compaction/policy.js.map +1 -1
  94. package/dist/core/extensions/builtin/compaction/retained-message-safety.d.ts.map +1 -1
  95. package/dist/core/extensions/builtin/compaction/retained-message-safety.js +31 -8
  96. package/dist/core/extensions/builtin/compaction/retained-message-safety.js.map +1 -1
  97. package/dist/core/extensions/builtin/compaction/speculation-lead.d.ts +8 -0
  98. package/dist/core/extensions/builtin/compaction/speculation-lead.d.ts.map +1 -0
  99. package/dist/core/extensions/builtin/compaction/speculation-lead.js +17 -0
  100. package/dist/core/extensions/builtin/compaction/speculation-lead.js.map +1 -0
  101. package/dist/core/extensions/builtin/compaction/speculative-job.d.ts +22 -0
  102. package/dist/core/extensions/builtin/compaction/speculative-job.d.ts.map +1 -0
  103. package/dist/core/extensions/builtin/compaction/speculative-job.js +16 -0
  104. package/dist/core/extensions/builtin/compaction/speculative-job.js.map +1 -0
  105. package/dist/core/extensions/builtin/compaction/speculative-summary.d.ts +22 -0
  106. package/dist/core/extensions/builtin/compaction/speculative-summary.d.ts.map +1 -0
  107. package/dist/core/extensions/builtin/compaction/speculative-summary.js +131 -0
  108. package/dist/core/extensions/builtin/compaction/speculative-summary.js.map +1 -0
  109. package/dist/core/extensions/builtin/compaction/speculative.d.ts +8 -13
  110. package/dist/core/extensions/builtin/compaction/speculative.d.ts.map +1 -1
  111. package/dist/core/extensions/builtin/compaction/speculative.js +7 -196
  112. package/dist/core/extensions/builtin/compaction/speculative.js.map +1 -1
  113. package/dist/core/extensions/builtin/compaction/token-budget-reminder.d.ts +24 -0
  114. package/dist/core/extensions/builtin/compaction/token-budget-reminder.d.ts.map +1 -0
  115. package/dist/core/extensions/builtin/compaction/token-budget-reminder.js +25 -0
  116. package/dist/core/extensions/builtin/compaction/token-budget-reminder.js.map +1 -0
  117. package/dist/core/extensions/builtin/compaction/tool-admission.d.ts +27 -0
  118. package/dist/core/extensions/builtin/compaction/tool-admission.d.ts.map +1 -0
  119. package/dist/core/extensions/builtin/compaction/tool-admission.js +56 -0
  120. package/dist/core/extensions/builtin/compaction/tool-admission.js.map +1 -0
  121. package/dist/core/extensions/builtin/cursor-cli-oauth/stream.d.ts.map +1 -1
  122. package/dist/core/extensions/builtin/cursor-cli-oauth/stream.js +8 -28
  123. package/dist/core/extensions/builtin/cursor-cli-oauth/stream.js.map +1 -1
  124. package/dist/core/extensions/builtin/permission-system/external-dir.d.ts.map +1 -1
  125. package/dist/core/extensions/builtin/permission-system/external-dir.js +15 -5
  126. package/dist/core/extensions/builtin/permission-system/external-dir.js.map +1 -1
  127. package/dist/core/extensions/builtin/permission-system/parsers.d.ts.map +1 -1
  128. package/dist/core/extensions/builtin/permission-system/parsers.js +16 -1
  129. package/dist/core/extensions/builtin/permission-system/parsers.js.map +1 -1
  130. package/dist/core/extensions/builtin/terminal/manager.d.ts +5 -0
  131. package/dist/core/extensions/builtin/terminal/manager.d.ts.map +1 -1
  132. package/dist/core/extensions/builtin/terminal/manager.js +31 -3
  133. package/dist/core/extensions/builtin/terminal/manager.js.map +1 -1
  134. package/dist/core/extensions/builtin/terminal/monitor-permission.d.ts +8 -0
  135. package/dist/core/extensions/builtin/terminal/monitor-permission.d.ts.map +1 -0
  136. package/dist/core/extensions/builtin/terminal/monitor-permission.js +16 -0
  137. package/dist/core/extensions/builtin/terminal/monitor-permission.js.map +1 -0
  138. package/dist/core/extensions/builtin/terminal/monitor-registry.d.ts +13 -0
  139. package/dist/core/extensions/builtin/terminal/monitor-registry.d.ts.map +1 -1
  140. package/dist/core/extensions/builtin/terminal/monitor-registry.js +403 -3
  141. package/dist/core/extensions/builtin/terminal/monitor-registry.js.map +1 -1
  142. package/dist/core/extensions/builtin/terminal/runtime-session.d.ts +11 -0
  143. package/dist/core/extensions/builtin/terminal/runtime-session.d.ts.map +1 -1
  144. package/dist/core/extensions/builtin/terminal/runtime-session.js +18 -2
  145. package/dist/core/extensions/builtin/terminal/runtime-session.js.map +1 -1
  146. package/dist/core/extensions/builtin/terminal/session-bundle.d.ts.map +1 -1
  147. package/dist/core/extensions/builtin/terminal/session-bundle.js +1 -0
  148. package/dist/core/extensions/builtin/terminal/session-bundle.js.map +1 -1
  149. package/dist/core/extensions/builtin/terminal/tools/kill-bash.d.ts.map +1 -1
  150. package/dist/core/extensions/builtin/terminal/tools/kill-bash.js +5 -2
  151. package/dist/core/extensions/builtin/terminal/tools/kill-bash.js.map +1 -1
  152. package/dist/core/extensions/builtin/terminal/tools/monitor.d.ts +4 -0
  153. package/dist/core/extensions/builtin/terminal/tools/monitor.d.ts.map +1 -1
  154. package/dist/core/extensions/builtin/terminal/tools/monitor.js +40 -2
  155. package/dist/core/extensions/builtin/terminal/tools/monitor.js.map +1 -1
  156. package/dist/core/model-resolver.js +2 -2
  157. package/dist/core/model-resolver.js.map +1 -1
  158. package/dist/core/sdk.d.ts.map +1 -1
  159. package/dist/core/sdk.js +5 -0
  160. package/dist/core/sdk.js.map +1 -1
  161. package/dist/core/session-manager.d.ts.map +1 -1
  162. package/dist/core/session-manager.js +10 -1
  163. package/dist/core/session-manager.js.map +1 -1
  164. package/dist/core/settings-manager.d.ts +15 -85
  165. package/dist/core/settings-manager.d.ts.map +1 -1
  166. package/dist/core/settings-manager.js +15 -17
  167. package/dist/core/settings-manager.js.map +1 -1
  168. package/dist/core/settings-public-types.d.ts +11 -0
  169. package/dist/core/settings-public-types.d.ts.map +1 -0
  170. package/dist/core/settings-public-types.js +2 -0
  171. package/dist/core/settings-public-types.js.map +1 -0
  172. package/dist/core/settings-shapes.d.ts +36 -0
  173. package/dist/core/settings-shapes.d.ts.map +1 -0
  174. package/dist/core/settings-shapes.js +2 -0
  175. package/dist/core/settings-shapes.js.map +1 -0
  176. package/dist/core/shared-host-policy.d.ts +6 -0
  177. package/dist/core/shared-host-policy.d.ts.map +1 -0
  178. package/dist/core/shared-host-policy.js +7 -0
  179. package/dist/core/shared-host-policy.js.map +1 -0
  180. package/dist/core/terminal-settings.d.ts +22 -0
  181. package/dist/core/terminal-settings.d.ts.map +1 -0
  182. package/dist/core/terminal-settings.js +2 -0
  183. package/dist/core/terminal-settings.js.map +1 -0
  184. package/dist/core/tools/bash.d.ts.map +1 -1
  185. package/dist/core/tools/bash.js +97 -15
  186. package/dist/core/tools/bash.js.map +1 -1
  187. package/dist/core/tools/index.d.ts +1 -0
  188. package/dist/core/tools/index.d.ts.map +1 -1
  189. package/dist/core/tools/index.js +8 -0
  190. package/dist/core/tools/index.js.map +1 -1
  191. package/dist/core/tools/output-accumulator.d.ts +1 -1
  192. package/dist/core/tools/output-accumulator.d.ts.map +1 -1
  193. package/dist/core/tools/output-accumulator.js +76 -34
  194. package/dist/core/tools/output-accumulator.js.map +1 -1
  195. package/dist/index.d.ts +1 -1
  196. package/dist/index.d.ts.map +1 -1
  197. package/dist/index.js +1 -1
  198. package/dist/index.js.map +1 -1
  199. package/dist/main.d.ts.map +1 -1
  200. package/dist/main.js +17 -5
  201. package/dist/main.js.map +1 -1
  202. package/dist/modes/index.d.ts +1 -1
  203. package/dist/modes/index.d.ts.map +1 -1
  204. package/dist/modes/index.js +1 -1
  205. package/dist/modes/index.js.map +1 -1
  206. package/dist/modes/interactive/components/footer.d.ts +7 -0
  207. package/dist/modes/interactive/components/footer.d.ts.map +1 -1
  208. package/dist/modes/interactive/components/footer.js +34 -8
  209. package/dist/modes/interactive/components/footer.js.map +1 -1
  210. package/dist/modes/interactive/grok/chrome.d.ts +2 -0
  211. package/dist/modes/interactive/grok/chrome.d.ts.map +1 -1
  212. package/dist/modes/interactive/grok/chrome.js.map +1 -1
  213. package/dist/modes/interactive/interactive-host-runtime.d.ts +132 -1
  214. package/dist/modes/interactive/interactive-host-runtime.d.ts.map +1 -1
  215. package/dist/modes/interactive/interactive-host-runtime.js +633 -117
  216. package/dist/modes/interactive/interactive-host-runtime.js.map +1 -1
  217. package/dist/modes/interactive/interactive-mode.d.ts +7 -0
  218. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  219. package/dist/modes/interactive/interactive-mode.js +114 -2
  220. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  221. package/dist/modes/rpc/connection-handler.d.ts +8 -1
  222. package/dist/modes/rpc/connection-handler.d.ts.map +1 -1
  223. package/dist/modes/rpc/connection-handler.js +158 -49
  224. package/dist/modes/rpc/connection-handler.js.map +1 -1
  225. package/dist/modes/rpc/host-ensure.d.ts +19 -0
  226. package/dist/modes/rpc/host-ensure.d.ts.map +1 -1
  227. package/dist/modes/rpc/host-ensure.js +79 -15
  228. package/dist/modes/rpc/host-ensure.js.map +1 -1
  229. package/dist/modes/rpc/host-lifecycle.d.ts +29 -0
  230. package/dist/modes/rpc/host-lifecycle.d.ts.map +1 -1
  231. package/dist/modes/rpc/host-lifecycle.js +96 -25
  232. package/dist/modes/rpc/host-lifecycle.js.map +1 -1
  233. package/dist/modes/rpc/host-watchdog.d.ts +2 -0
  234. package/dist/modes/rpc/host-watchdog.d.ts.map +1 -1
  235. package/dist/modes/rpc/host-watchdog.js +23 -12
  236. package/dist/modes/rpc/host-watchdog.js.map +1 -1
  237. package/dist/modes/rpc/multi-session-host.js +6 -4
  238. package/dist/modes/rpc/multi-session-host.js.map +1 -1
  239. package/dist/modes/rpc/rpc-client.d.ts +20 -4
  240. package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
  241. package/dist/modes/rpc/rpc-client.js +76 -17
  242. package/dist/modes/rpc/rpc-client.js.map +1 -1
  243. package/dist/modes/rpc/rpc-input-validation.d.ts +1 -0
  244. package/dist/modes/rpc/rpc-input-validation.d.ts.map +1 -1
  245. package/dist/modes/rpc/rpc-input-validation.js +66 -0
  246. package/dist/modes/rpc/rpc-input-validation.js.map +1 -1
  247. package/dist/modes/rpc/rpc-types.d.ts +86 -1
  248. package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
  249. package/dist/modes/rpc/rpc-types.js.map +1 -1
  250. package/dist/modes/rpc/session-binding.d.ts +1 -0
  251. package/dist/modes/rpc/session-binding.d.ts.map +1 -1
  252. package/dist/modes/rpc/session-binding.js +21 -1
  253. package/dist/modes/rpc/session-binding.js.map +1 -1
  254. package/dist/modes/rpc/session-command-router.d.ts.map +1 -1
  255. package/dist/modes/rpc/session-command-router.js +1 -0
  256. package/dist/modes/rpc/session-command-router.js.map +1 -1
  257. package/dist/modes/rpc/session-event-writer.d.ts +2 -0
  258. package/dist/modes/rpc/session-event-writer.d.ts.map +1 -1
  259. package/dist/modes/rpc/session-event-writer.js +54 -7
  260. package/dist/modes/rpc/session-event-writer.js.map +1 -1
  261. package/dist/modes/rpc/session-extension-ui-requests.d.ts +1 -0
  262. package/dist/modes/rpc/session-extension-ui-requests.d.ts.map +1 -1
  263. package/dist/modes/rpc/session-extension-ui-requests.js +5 -0
  264. package/dist/modes/rpc/session-extension-ui-requests.js.map +1 -1
  265. package/dist/modes/rpc/session-registry.d.ts +10 -2
  266. package/dist/modes/rpc/session-registry.d.ts.map +1 -1
  267. package/dist/modes/rpc/session-registry.js +58 -4
  268. package/dist/modes/rpc/session-registry.js.map +1 -1
  269. package/dist/modes/rpc/socket-event-fanout.d.ts +22 -0
  270. package/dist/modes/rpc/socket-event-fanout.d.ts.map +1 -0
  271. package/dist/modes/rpc/socket-event-fanout.js +84 -0
  272. package/dist/modes/rpc/socket-event-fanout.js.map +1 -0
  273. package/docs/rpc.md +22 -2
  274. package/docs/settings.md +34 -0
  275. package/docs/windows.md +2 -0
  276. package/node_modules/@code-yeongyu/senpi-codemode/CHANGELOG.md +62 -0
  277. package/node_modules/@code-yeongyu/senpi-codemode/package.json +4 -4
  278. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/worker-indirect-eval.js +139 -90
  279. package/node_modules/@code-yeongyu/senpi-codemode/src/prompt/eval-prompt.ts +4 -4
  280. package/node_modules/@earendil-works/pi-agent-core/dist/harness/env/nodejs.d.ts.map +1 -1
  281. package/node_modules/@earendil-works/pi-agent-core/dist/harness/env/nodejs.js +40 -20
  282. package/node_modules/@earendil-works/pi-agent-core/dist/harness/env/nodejs.js.map +1 -1
  283. package/node_modules/@earendil-works/pi-agent-core/dist/harness/types.d.ts +3 -3
  284. package/node_modules/@earendil-works/pi-agent-core/dist/harness/types.d.ts.map +1 -1
  285. package/node_modules/@earendil-works/pi-agent-core/dist/harness/types.js.map +1 -1
  286. package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/shell-output.d.ts +1 -1
  287. package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/shell-output.d.ts.map +1 -1
  288. package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/shell-output.js +28 -9
  289. package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/shell-output.js.map +1 -1
  290. package/node_modules/@earendil-works/pi-agent-core/package.json +3 -3
  291. package/node_modules/@earendil-works/pi-ai/dist/api/anthropic-messages.d.ts.map +1 -1
  292. package/node_modules/@earendil-works/pi-ai/dist/api/anthropic-messages.js +67 -38
  293. package/node_modules/@earendil-works/pi-ai/dist/api/anthropic-messages.js.map +1 -1
  294. package/node_modules/@earendil-works/pi-ai/dist/api/cursor-agent/measure.d.ts +20 -0
  295. package/node_modules/@earendil-works/pi-ai/dist/api/cursor-agent/measure.d.ts.map +1 -0
  296. package/node_modules/@earendil-works/pi-ai/dist/api/cursor-agent/measure.js +443 -0
  297. package/node_modules/@earendil-works/pi-ai/dist/api/cursor-agent/measure.js.map +1 -0
  298. package/node_modules/@earendil-works/pi-ai/dist/api/cursor-agent.d.ts +2 -0
  299. package/node_modules/@earendil-works/pi-ai/dist/api/cursor-agent.d.ts.map +1 -1
  300. package/node_modules/@earendil-works/pi-ai/dist/api/cursor-agent.js +2 -0
  301. package/node_modules/@earendil-works/pi-ai/dist/api/cursor-agent.js.map +1 -1
  302. package/node_modules/@earendil-works/pi-ai/dist/api/openai-completions.js +1 -1
  303. package/node_modules/@earendil-works/pi-ai/dist/api/openai-completions.js.map +1 -1
  304. package/node_modules/@earendil-works/pi-ai/dist/index.d.ts +1 -0
  305. package/node_modules/@earendil-works/pi-ai/dist/index.d.ts.map +1 -1
  306. package/node_modules/@earendil-works/pi-ai/dist/index.js +1 -0
  307. package/node_modules/@earendil-works/pi-ai/dist/index.js.map +1 -1
  308. package/node_modules/@earendil-works/pi-ai/dist/providers/data/.manifest.json +1 -1
  309. package/node_modules/@earendil-works/pi-ai/dist/providers/data/cloudflare-ai-gateway.json +1 -1
  310. package/node_modules/@earendil-works/pi-ai/dist/providers/data/cloudflare-workers-ai.json +1 -1
  311. package/node_modules/@earendil-works/pi-ai/dist/providers/data/fireworks.json +1 -1
  312. package/node_modules/@earendil-works/pi-ai/dist/providers/data/opencode-go.json +1 -1
  313. package/node_modules/@earendil-works/pi-ai/dist/providers/data/opencode.json +1 -1
  314. package/node_modules/@earendil-works/pi-ai/dist/providers/data/openrouter.json +1 -1
  315. package/node_modules/@earendil-works/pi-ai/dist/providers/data/together.json +1 -1
  316. package/node_modules/@earendil-works/pi-ai/dist/providers/data/zai-coding-cn.json +1 -1
  317. package/node_modules/@earendil-works/pi-ai/dist/providers/data/zai.json +1 -1
  318. package/node_modules/@earendil-works/pi-ai/package.json +2 -2
  319. package/node_modules/@earendil-works/pi-pty/dist/screen-operations.d.ts +42 -0
  320. package/node_modules/@earendil-works/pi-pty/dist/screen-operations.d.ts.map +1 -0
  321. package/node_modules/@earendil-works/pi-pty/dist/screen-operations.js +34 -0
  322. package/node_modules/@earendil-works/pi-pty/dist/screen-operations.js.map +1 -0
  323. package/node_modules/@earendil-works/pi-pty/dist/screen-text.d.ts +8 -0
  324. package/node_modules/@earendil-works/pi-pty/dist/screen-text.d.ts.map +1 -0
  325. package/node_modules/@earendil-works/pi-pty/dist/screen-text.js +51 -0
  326. package/node_modules/@earendil-works/pi-pty/dist/screen-text.js.map +1 -0
  327. package/node_modules/@earendil-works/pi-pty/dist/screen.d.ts +45 -0
  328. package/node_modules/@earendil-works/pi-pty/dist/screen.d.ts.map +1 -1
  329. package/node_modules/@earendil-works/pi-pty/dist/screen.js +178 -55
  330. package/node_modules/@earendil-works/pi-pty/dist/screen.js.map +1 -1
  331. package/node_modules/@earendil-works/pi-pty/package.json +1 -1
  332. package/node_modules/@earendil-works/pi-telemetry/package.json +1 -1
  333. package/node_modules/@earendil-works/pi-tui/dist/components/editor.d.ts +7 -0
  334. package/node_modules/@earendil-works/pi-tui/dist/components/editor.d.ts.map +1 -1
  335. package/node_modules/@earendil-works/pi-tui/dist/components/editor.js +5 -0
  336. package/node_modules/@earendil-works/pi-tui/dist/components/editor.js.map +1 -1
  337. package/node_modules/@earendil-works/pi-tui/dist/mux.d.ts +1 -1
  338. package/node_modules/@earendil-works/pi-tui/dist/mux.d.ts.map +1 -1
  339. package/node_modules/@earendil-works/pi-tui/dist/mux.js +2 -2
  340. package/node_modules/@earendil-works/pi-tui/dist/mux.js.map +1 -1
  341. package/node_modules/@earendil-works/pi-tui/dist/terminal.d.ts +1 -0
  342. package/node_modules/@earendil-works/pi-tui/dist/terminal.d.ts.map +1 -1
  343. package/node_modules/@earendil-works/pi-tui/dist/terminal.js +19 -0
  344. package/node_modules/@earendil-works/pi-tui/dist/terminal.js.map +1 -1
  345. package/node_modules/@earendil-works/pi-tui/package.json +1 -1
  346. package/package.json +7 -7
@@ -1,9 +1,12 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { rm } from "node:fs/promises";
1
3
  import { executeBashWithOperations } from "../../core/bash-executor.js";
2
4
  import { SessionManager } from "../../core/session-manager.js";
3
5
  import { SettingsManager } from "../../core/settings-manager.js";
4
6
  import { ensureHost } from "../rpc/host-ensure.js";
5
- import { RpcClient } from "../rpc/rpc-client.js";
7
+ import { isTransportGoneError, RpcClient } from "../rpc/rpc-client.js";
6
8
  export const INTERACTIVE_HOST_FALLBACK_WARNING = "Warning: shared interactive host unavailable; continuing locally";
9
+ export const INTERACTIVE_HOST_RECONNECTING_WARNING = "Warning: shared interactive host connection lost; reconnecting";
7
10
  /**
8
11
  * Replace only the transport-facing session operations. The object returned is
9
12
  * deliberately still an AgentSessionRuntime: InteractiveMode and extensions
@@ -15,7 +18,82 @@ export async function createInteractiveHostRuntime(localRuntime, options) {
15
18
  if (!sessionPath)
16
19
  return localRuntime;
17
20
  const startHost = options.ensureHost ?? ((hostOptions) => ensureHost(hostOptions));
18
- const client = new RpcClient({ socketPath: options.socket });
21
+ let remoteSession;
22
+ let remoteRuntime;
23
+ let reconnecting;
24
+ let disconnectQueued = false;
25
+ let disposed = false;
26
+ let fallbackWarned = false;
27
+ let reconnectWarningShown = false;
28
+ const warnReconnect = (cause) => {
29
+ if (reconnectWarningShown)
30
+ return;
31
+ reconnectWarningShown = true;
32
+ options.onWarning?.({
33
+ type: "interactive_host_action_failed",
34
+ message: INTERACTIVE_HOST_RECONNECTING_WARNING,
35
+ cause,
36
+ });
37
+ };
38
+ const warnFallback = (cause) => {
39
+ if (fallbackWarned)
40
+ return;
41
+ fallbackWarned = true;
42
+ options.onWarning?.({ type: "interactive_host_fallback", message: INTERACTIVE_HOST_FALLBACK_WARNING, cause });
43
+ };
44
+ let scheduleReconnect = () => { };
45
+ const client = new RpcClient({
46
+ socketPath: options.socket,
47
+ onDisconnect: () => {
48
+ disconnectQueued = true;
49
+ scheduleReconnect();
50
+ },
51
+ });
52
+ scheduleReconnect = () => {
53
+ if (!remoteSession || reconnecting || disposed || remoteRuntime?.isFallback)
54
+ return;
55
+ remoteRuntime?.enterReconnecting();
56
+ reconnecting = (async () => {
57
+ let cause;
58
+ for (let attempt = 0; attempt < 3; attempt++) {
59
+ if (disposed)
60
+ return;
61
+ disconnectQueued = false;
62
+ try {
63
+ await startHost({ socket: options.socket, agentDir: options.agentDir });
64
+ if (disposed)
65
+ return;
66
+ await client.start();
67
+ if (disposed)
68
+ return;
69
+ await client.openSession({ sessionPath, cwd: localRuntime.cwd });
70
+ if (disposed)
71
+ return;
72
+ await remoteSession.refresh();
73
+ if (disposed)
74
+ return;
75
+ fallbackWarned = false;
76
+ reconnectWarningShown = false;
77
+ remoteRuntime?.enterConnected();
78
+ return;
79
+ }
80
+ catch (error) {
81
+ cause = error;
82
+ await client.stop().catch(() => { });
83
+ if (disposed)
84
+ return;
85
+ }
86
+ }
87
+ if (!disposed) {
88
+ await remoteRuntime?.enterFallback();
89
+ warnFallback(cause);
90
+ }
91
+ })().finally(() => {
92
+ reconnecting = undefined;
93
+ if (disconnectQueued && !disposed && !remoteRuntime?.isFallback)
94
+ scheduleReconnect();
95
+ });
96
+ };
19
97
  try {
20
98
  await startHost({ socket: options.socket, agentDir: options.agentDir });
21
99
  await client.start();
@@ -26,38 +104,108 @@ export async function createInteractiveHostRuntime(localRuntime, options) {
26
104
  modelId: localRuntime.session.model?.id,
27
105
  thinkingLevel: localRuntime.session.thinkingLevel,
28
106
  });
29
- const remoteSession = createRemoteSessionProxy(localRuntime.session, localRuntime.services.agentDir, client, opened.state, options.onWarning);
30
- return new RemoteInteractiveRuntime(localRuntime, remoteSession, client);
107
+ remoteSession = createRemoteSessionProxy(localRuntime.session, localRuntime.services.agentDir, client, opened.state, options.onWarning, (cause) => {
108
+ if (remoteRuntime?.isReconnecting)
109
+ warnReconnect(cause);
110
+ else
111
+ warnFallback(cause);
112
+ });
113
+ if (opened.state.isBashRunning && !opened.attached) {
114
+ // Only a newly opened session can have an execution orphaned by a
115
+ // previous connection. An attach is an observer of the same live runtime;
116
+ // aborting it would cancel work owned by the surviving attachment.
117
+ await client.abortBash().catch(() => { });
118
+ }
119
+ if (opened.attached)
120
+ await remoteSession.refresh();
121
+ remoteRuntime = new RemoteInteractiveRuntime(localRuntime, remoteSession, client, {
122
+ onTransportGone: (cause) => {
123
+ if (remoteRuntime?.isReconnecting)
124
+ warnReconnect(cause);
125
+ else
126
+ warnFallback(cause);
127
+ },
128
+ get reconnecting() {
129
+ return reconnecting;
130
+ },
131
+ dispose: () => {
132
+ disposed = true;
133
+ },
134
+ });
135
+ return remoteRuntime;
31
136
  }
32
137
  catch (cause) {
33
138
  await client.stop().catch(() => { });
34
- options.onWarning?.({
35
- type: "interactive_host_fallback",
36
- message: `${INTERACTIVE_HOST_FALLBACK_WARNING}: ${cause instanceof Error ? cause.message : String(cause)}`,
37
- cause,
38
- });
139
+ warnFallback(cause);
39
140
  return localRuntime;
40
141
  }
41
142
  }
42
- class RemoteInteractiveRuntime {
143
+ export class RemoteInteractiveRuntime {
43
144
  #local;
44
145
  #remoteSession;
45
146
  #client;
147
+ #lifecycle;
148
+ #onTransportGone;
46
149
  #rebindSession;
47
150
  #beforeSessionInvalidate;
48
- constructor(local, remoteSession, client) {
151
+ #state = "connected";
152
+ #fallbackHandoff;
153
+ get isReconnecting() {
154
+ return this.#state === "reconnecting";
155
+ }
156
+ get isFallback() {
157
+ return this.#state === "fallback";
158
+ }
159
+ constructor(local, remoteSession, client, lifecycle) {
49
160
  this.#local = local;
50
161
  this.#remoteSession = remoteSession;
51
162
  this.#client = client;
163
+ this.#lifecycle = lifecycle;
164
+ this.#onTransportGone = lifecycle?.onTransportGone ?? (() => { });
165
+ }
166
+ async #call(call) {
167
+ try {
168
+ return await call();
169
+ }
170
+ catch (error) {
171
+ if (isTransportGoneError(error)) {
172
+ this.#onTransportGone(error);
173
+ return { cancelled: true };
174
+ }
175
+ throw error;
176
+ }
177
+ }
178
+ enterConnected() {
179
+ if (this.#state !== "disposed" && this.#state !== "fallback")
180
+ this.#state = "connected";
181
+ }
182
+ enterReconnecting() {
183
+ if (this.#state !== "disposed" && this.#state !== "fallback")
184
+ this.#state = "reconnecting";
185
+ }
186
+ async enterFallback() {
187
+ if (this.#state === "disposed" || this.#state === "fallback")
188
+ return;
189
+ this.#state = "fallback";
190
+ this.#beforeSessionInvalidate?.();
191
+ const rebind = this.#rebindSession?.();
192
+ if (rebind) {
193
+ this.#fallbackHandoff = rebind.catch((error) => {
194
+ this.#onTransportGone(error);
195
+ });
196
+ await this.#fallbackHandoff;
197
+ }
52
198
  }
53
199
  get session() {
54
- return this.#remoteSession.session;
200
+ return this.#state === "fallback" || this.#state === "disposed"
201
+ ? this.#local.session
202
+ : this.#remoteSession.session;
55
203
  }
56
204
  get services() {
57
205
  return this.#local.services;
58
206
  }
59
207
  get cwd() {
60
- return this.#remoteSession.session.sessionManager.getCwd();
208
+ return this.session.sessionManager.getCwd();
61
209
  }
62
210
  get diagnostics() {
63
211
  return this.#local.diagnostics;
@@ -70,90 +218,238 @@ class RemoteInteractiveRuntime {
70
218
  }
71
219
  setBeforeSessionInvalidate(callback) {
72
220
  this.#beforeSessionInvalidate = callback;
221
+ this.#local.setBeforeSessionInvalidate(callback);
73
222
  }
74
223
  setRebindSession(callback) {
75
224
  this.#rebindSession = callback;
225
+ this.#local.setRebindSession(callback);
226
+ if (callback && this.#state === "fallback" && !this.#fallbackHandoff) {
227
+ this.#fallbackHandoff = callback().catch((error) => {
228
+ this.#onTransportGone(error);
229
+ });
230
+ }
231
+ }
232
+ setHostUiHandler(callback) {
233
+ this.#remoteSession.setHostUiHandler(callback);
76
234
  }
77
235
  async dispose() {
78
- await this.#client.closeSession();
79
- await this.#client.stop();
80
- await this.#local.dispose();
236
+ if (this.#state === "disposed")
237
+ return;
238
+ this.#state = "disposed";
239
+ this.#lifecycle?.dispose();
240
+ await this.#lifecycle?.reconnecting?.catch(() => { });
241
+ await this.#fallbackHandoff?.catch(() => { });
242
+ const errors = [];
243
+ for (const cleanup of [
244
+ () => this.#remoteSession.abortLocalBash(),
245
+ () => this.#client.closeSession(),
246
+ () => this.#client.stop(),
247
+ () => this.#local.dispose(),
248
+ ]) {
249
+ try {
250
+ await cleanup();
251
+ }
252
+ catch (error) {
253
+ errors.push(error);
254
+ }
255
+ }
256
+ if (errors.length > 0)
257
+ throw errors[0];
81
258
  }
82
259
  async newSession(options) {
83
- const result = await this.#client.newSession(options?.parentSession);
84
- if (!result.cancelled) {
85
- this.#beforeSessionInvalidate?.();
86
- this.#remoteSession.abortLocalBash();
87
- await this.#remoteSession.refresh();
88
- if (options?.setup) {
89
- const capture = SessionManager.inMemory(this.#remoteSession.session.sessionManager.getCwd());
90
- await options.setup(capture);
91
- for (const entry of capture.getEntries())
92
- await this.#client.appendSessionEntry(entry);
260
+ if (this.#state === "fallback")
261
+ return this.#local.newSession(options);
262
+ // Suppresses for the ENTIRE command, not just the refresh tail: a
263
+ // multi-session host emits session_replaced while completing it, so the echo
264
+ // can arrive before the refresh/rebind sequence below starts and race it -
265
+ // newSession transports setup entries between two refreshes.
266
+ return this.#remoteSession
267
+ .aroundLocalReplacement(async () => {
268
+ const result = await this.#call(() => this.#client.newSession(options?.parentSession));
269
+ if (!result.cancelled) {
270
+ this.#beforeSessionInvalidate?.();
271
+ this.#remoteSession.abortLocalBash();
93
272
  await this.#remoteSession.refresh();
273
+ if (options?.setup) {
274
+ const capture = SessionManager.inMemory(this.#remoteSession.session.sessionManager.getCwd());
275
+ await options.setup(capture);
276
+ for (const entry of capture.getEntries())
277
+ await this.#client.appendSessionEntry(entry);
278
+ await this.#remoteSession.refresh();
279
+ }
280
+ await this.#rebindSession?.();
281
+ if (options?.withSession)
282
+ await options.withSession(this.#remoteSession.createReplacedSessionContext());
94
283
  }
95
- await this.#rebindSession?.();
96
- if (options?.withSession)
97
- await options.withSession(this.#remoteSession.createReplacedSessionContext());
98
- }
99
- return result;
284
+ return result;
285
+ })
286
+ .catch((error) => {
287
+ if (isTransportGoneError(error))
288
+ return { cancelled: true };
289
+ throw error;
290
+ });
100
291
  }
101
292
  async switchSession(sessionPath, options) {
102
- const result = await this.#client.switchSession(sessionPath, options);
103
- if (!result.cancelled) {
104
- this.#beforeSessionInvalidate?.();
105
- this.#remoteSession.abortLocalBash();
106
- await this.#remoteSession.refresh();
107
- await this.#rebindSession?.();
108
- // The shared host already resolved trust; this callback is retained for
109
- // compatibility, but its result cannot override host-authoritative state.
110
- options?.projectTrustContextFactory?.(this.#remoteSession.session.sessionManager.getCwd());
111
- if (options?.withSession)
112
- await options.withSession(this.#remoteSession.createReplacedSessionContext());
113
- }
114
- return result;
293
+ if (this.#state === "fallback")
294
+ return this.#local.switchSession(sessionPath, options);
295
+ // See newSession: the suppression must cover the command itself, since the
296
+ // host emits session_replaced while completing it.
297
+ return this.#remoteSession
298
+ .aroundLocalReplacement(async () => {
299
+ const result = await this.#call(() => this.#client.switchSession(sessionPath, options?.cwdOverride === undefined ? undefined : { cwdOverride: options.cwdOverride }));
300
+ if (!result.cancelled) {
301
+ this.#beforeSessionInvalidate?.();
302
+ this.#remoteSession.abortLocalBash();
303
+ await this.#remoteSession.refresh();
304
+ await this.#rebindSession?.();
305
+ // The shared host already resolved trust; this callback is retained for
306
+ // compatibility, but its result cannot override host-authoritative state.
307
+ options?.projectTrustContextFactory?.(this.#remoteSession.session.sessionManager.getCwd());
308
+ if (options?.withSession)
309
+ await options.withSession(this.#remoteSession.createReplacedSessionContext());
310
+ }
311
+ return result;
312
+ })
313
+ .catch((error) => {
314
+ if (isTransportGoneError(error))
315
+ return { cancelled: true };
316
+ throw error;
317
+ });
115
318
  }
116
319
  async fork(entryId, options) {
117
- const result = await this.#client.fork(entryId, options);
118
- if (!result.cancelled) {
119
- this.#beforeSessionInvalidate?.();
120
- this.#remoteSession.abortLocalBash();
121
- await this.#refreshAndRebind();
122
- if (options?.withSession)
123
- await options.withSession(this.#remoteSession.createReplacedSessionContext());
124
- }
125
- return { cancelled: result.cancelled, selectedText: result.text };
320
+ if (this.#state === "fallback")
321
+ return this.#local.fork(entryId, options);
322
+ // See newSession: the suppression must cover the command itself.
323
+ return this.#remoteSession
324
+ .aroundLocalReplacement(async () => {
325
+ const result = await this.#call(() => this.#client.fork(entryId, options));
326
+ if (!result.cancelled) {
327
+ this.#beforeSessionInvalidate?.();
328
+ this.#remoteSession.abortLocalBash();
329
+ await this.#refreshAndRebind();
330
+ if (options?.withSession)
331
+ await options.withSession(this.#remoteSession.createReplacedSessionContext());
332
+ }
333
+ return { cancelled: result.cancelled, selectedText: result.text };
334
+ })
335
+ .catch((error) => {
336
+ if (isTransportGoneError(error))
337
+ return { cancelled: true };
338
+ throw error;
339
+ });
126
340
  }
127
341
  async importFromJsonl(inputPath, cwdOverride) {
128
- const result = await this.#client.importJsonl(inputPath, cwdOverride);
129
- if (!result.cancelled) {
130
- this.#beforeSessionInvalidate?.();
131
- this.#remoteSession.abortLocalBash();
132
- await this.#refreshAndRebind();
133
- }
134
- return result;
342
+ if (this.#state === "fallback")
343
+ return this.#local.importFromJsonl(inputPath, cwdOverride);
344
+ // See newSession: the suppression must cover the command itself.
345
+ return this.#remoteSession
346
+ .aroundLocalReplacement(async () => {
347
+ const result = await this.#call(() => this.#client.importJsonl(inputPath, cwdOverride));
348
+ if (!result.cancelled) {
349
+ this.#beforeSessionInvalidate?.();
350
+ this.#remoteSession.abortLocalBash();
351
+ await this.#refreshAndRebind();
352
+ }
353
+ return result;
354
+ })
355
+ .catch((error) => {
356
+ if (isTransportGoneError(error))
357
+ return { cancelled: true };
358
+ throw error;
359
+ });
135
360
  }
136
361
  async #refreshAndRebind() {
137
362
  await this.#remoteSession.refresh();
138
363
  await this.#rebindSession?.();
139
364
  }
140
365
  }
141
- function createRemoteSessionProxy(local, agentDir, client, initialState, onWarning) {
366
+ /** Exported for direct coverage of the wire-event handling below. */
367
+ export function createRemoteSessionProxy(local, agentDir, client, initialState, onWarning, onTransportGone) {
142
368
  // Fire-and-forget setters keep the sync AgentSession signature, but their RPC
143
369
  // failures must not vanish: the matching *_changed wire event confirms success,
144
370
  // and a rejection here is the only signal of failure.
145
371
  const reportActionFailure = (action) => (error) => {
146
- onWarning?.({
147
- type: "interactive_host_action_failed",
148
- message: `Warning: shared interactive host ${action} failed: ${error instanceof Error ? error.message : String(error)}`,
149
- cause: error,
150
- });
372
+ const transportGone = isTransportGoneError(error);
373
+ if (transportGone)
374
+ onTransportGone?.(error);
375
+ else
376
+ onWarning?.({
377
+ type: "interactive_host_action_failed",
378
+ message: `Warning: shared interactive host ${action} failed: ${error instanceof Error ? error.message : String(error)}`,
379
+ cause: error,
380
+ });
381
+ };
382
+ const transportCall = async (action, call, cancelledValue) => {
383
+ try {
384
+ return await call();
385
+ }
386
+ catch (error) {
387
+ if (!isTransportGoneError(error))
388
+ throw error;
389
+ reportActionFailure(action)(error);
390
+ return cancelledValue;
391
+ }
151
392
  };
152
393
  let state = { ...initialState };
153
- let bashChunk;
394
+ const waitForBashCallbacks = async (promises, allowAbandonment) => {
395
+ if (promises.size === 0)
396
+ return true;
397
+ const settled = Promise.allSettled([...promises]).then(() => true);
398
+ let timeout;
399
+ const bounded = new Promise((resolve) => {
400
+ timeout = setTimeout(resolve, allowAbandonment ? 100 : 5_000, false);
401
+ timeout.unref?.();
402
+ });
403
+ try {
404
+ return await Promise.race([settled, bounded]);
405
+ }
406
+ finally {
407
+ if (timeout)
408
+ clearTimeout(timeout);
409
+ }
410
+ };
411
+ const executionNamespace = randomUUID();
412
+ const cleanupRemoteBashOutput = async (path) => {
413
+ let cleanupTimeout;
414
+ try {
415
+ await Promise.race([
416
+ client.cleanupBashOutput(path),
417
+ new Promise((_, reject) => {
418
+ cleanupTimeout = setTimeout(() => reject(new Error("RPC cleanup timed out")), 1_000);
419
+ }),
420
+ ]);
421
+ return;
422
+ }
423
+ catch (transportError) {
424
+ // Spill paths are shared host filesystem paths. If the RPC transport is
425
+ // already unavailable, remove locally as a bounded best-effort fallback;
426
+ // a later reconnect is not required to prevent this callback failure from
427
+ // permanently leaking the host-owned artifact.
428
+ try {
429
+ await rm(path, { force: true });
430
+ return;
431
+ }
432
+ catch (localError) {
433
+ onWarning?.({
434
+ type: "interactive_host_action_failed",
435
+ message: `Warning: failed to clean up shared bash output spill: ${localError instanceof Error ? localError.message : String(localError)}`,
436
+ cause: new AggregateError([transportError, localError]),
437
+ });
438
+ }
439
+ }
440
+ finally {
441
+ if (cleanupTimeout)
442
+ clearTimeout(cleanupTimeout);
443
+ }
444
+ };
445
+ let nextBashExecutionId = 0;
446
+ const bashExecutions = new Map();
447
+ let hostUiHandler;
448
+ const pendingUiRequests = [];
154
449
  let localBashAbortController;
155
450
  let localBashRunning = false;
156
451
  let hostBashRunning = initialState.isBashRunning;
452
+ let nextQueuedInputOrder = Math.max(0, ...initialState.ordered.map((item) => item.enqueueOrder));
157
453
  let sessionManager = local.sessionManager;
158
454
  let settingsManager = SettingsManager.create(initialState.cwd, agentDir, {
159
455
  projectTrusted: initialState.projectTrusted,
@@ -164,8 +460,10 @@ function createRemoteSessionProxy(local, agentDir, client, initialState, onWarni
164
460
  const remoteSessionManager = new Proxy({}, {
165
461
  get(_target, property, _receiver) {
166
462
  if (property === "appendLabelChange") {
167
- return (entryId, label) => void client.setLabel(entryId, label);
463
+ return (entryId, label) => void client.setLabel(entryId, label).catch(reportActionFailure("appendLabelChange"));
168
464
  }
465
+ if (property === "getCwd")
466
+ return () => state.cwd;
169
467
  if (property === "getSessionName")
170
468
  return () => state.sessionName;
171
469
  if (property === "getUsageTotals")
@@ -176,8 +474,38 @@ function createRemoteSessionProxy(local, agentDir, client, initialState, onWarni
176
474
  });
177
475
  let streamingAssistant;
178
476
  let mirroredCurrentAssistantUsage = false;
477
+ /** Non-zero while this runtime is driving its own replacement sequence. */
478
+ let localReplacementDepth = 0;
179
479
  const listeners = new Set();
180
480
  client.onEvent((wireEvent) => {
481
+ if (wireEvent.type === "extension_ui_request") {
482
+ const request = wireEvent;
483
+ if (hostUiHandler)
484
+ void Promise.resolve(hostUiHandler(request))
485
+ .then((response) => response && client.sendExtensionUIResponse(response))
486
+ .catch(reportActionFailure("host UI response"));
487
+ else
488
+ pendingUiRequests.push(request);
489
+ return;
490
+ }
491
+ if (wireEvent.type === "session_replaced") {
492
+ // The host swapped the live session behind this connection - another
493
+ // attached client issued the replacement, or an extension drove one that no
494
+ // client issued. Nothing else re-reads the binding, so without this the
495
+ // proxy keeps serving the previous session's manager, settings and message
496
+ // mirror while the host has already moved on. The command response carries
497
+ // only `{ cancelled }`, so this event is the only signal available here.
498
+ //
499
+ // A multi-session host broadcasts the event to every connection including
500
+ // the one that issued the replacement, and this runtime's own replacement
501
+ // methods already run an ordered refresh/rebind - newSession additionally
502
+ // transports setup entries between two refreshes. Refreshing again from
503
+ // here would race that sequence, so self-driven replacements are skipped.
504
+ if (localReplacementDepth === 0) {
505
+ void refresh().catch(reportActionFailure("session refresh after replacement"));
506
+ }
507
+ return;
508
+ }
181
509
  if (wireEvent.type === "agent_settled")
182
510
  state = { ...state, isStreaming: false, retryAttempt: 0 };
183
511
  if (wireEvent.type === "bash_start") {
@@ -188,8 +516,30 @@ function createRemoteSessionProxy(local, agentDir, client, initialState, onWarni
188
516
  hostBashRunning = false;
189
517
  updateBashState();
190
518
  }
191
- if (wireEvent.type === "bash_execution_update")
192
- bashChunk?.(wireEvent.delta);
519
+ if (wireEvent.type === "bash_execution_update" && wireEvent.id) {
520
+ const execution = bashExecutions.get(wireEvent.id);
521
+ if (execution?.chunk && !execution.hasError) {
522
+ try {
523
+ const callbackPromise = Promise.resolve(execution.chunk(wireEvent.delta)).catch((error) => {
524
+ if (!execution.hasError) {
525
+ execution.error = error;
526
+ execution.hasError = true;
527
+ void client.abortBash().catch(() => { });
528
+ }
529
+ throw error;
530
+ });
531
+ execution.promises.add(callbackPromise);
532
+ void callbackPromise.catch(() => { }).finally(() => execution.promises.delete(callbackPromise));
533
+ }
534
+ catch (error) {
535
+ if (!execution.hasError) {
536
+ execution.error = error;
537
+ execution.hasError = true;
538
+ void client.abortBash().catch(() => { });
539
+ }
540
+ }
541
+ }
542
+ }
193
543
  if (wireEvent.type === "agent_start")
194
544
  state = { ...state, isStreaming: true };
195
545
  if (wireEvent.type === "compaction_start")
@@ -201,6 +551,7 @@ function createRemoteSessionProxy(local, agentDir, client, initialState, onWarni
201
551
  if (wireEvent.type === "auto_retry_end")
202
552
  state = { ...state, retryAttempt: 0 };
203
553
  if (wireEvent.type === "queue_update") {
554
+ nextQueuedInputOrder = Math.max(nextQueuedInputOrder, ...wireEvent.ordered.map((item) => item.enqueueOrder));
204
555
  state = {
205
556
  ...state,
206
557
  steering: [...wireEvent.steering],
@@ -217,6 +568,23 @@ function createRemoteSessionProxy(local, agentDir, client, initialState, onWarni
217
568
  if (wireEvent.type === "service_tier_changed") {
218
569
  state = { ...state, serviceTier: wireEvent.tier, fastMode: wireEvent.fastMode };
219
570
  }
571
+ if (wireEvent.type === "session_settings_changed") {
572
+ state = {
573
+ ...state,
574
+ steeringMode: wireEvent.steeringMode,
575
+ followUpMode: wireEvent.followUpMode,
576
+ autoCompactionEnabled: wireEvent.autoCompactionEnabled,
577
+ };
578
+ }
579
+ if (wireEvent.type === "entry_appended") {
580
+ try {
581
+ sessionManager.appendEntry(wireEvent.entry);
582
+ local.agent.state.messages = sessionManager.buildSessionContext().messages;
583
+ }
584
+ catch {
585
+ // Non-fatal if the local snapshot cannot accept a concurrent entry.
586
+ }
587
+ }
220
588
  if (wireEvent.type === "session_info_changed")
221
589
  state = { ...state, sessionName: wireEvent.name };
222
590
  if (wireEvent.type === "message_start") {
@@ -233,8 +601,13 @@ function createRemoteSessionProxy(local, agentDir, client, initialState, onWarni
233
601
  if (usage && !mirroredCurrentAssistantUsage) {
234
602
  mirroredCurrentAssistantUsage = true;
235
603
  const latestPromptTokens = usage.input + usage.cacheRead + usage.cacheWrite;
604
+ const contextWindow = state.model?.contextWindow ?? 0;
605
+ const contextTokens = usage.input + usage.cacheRead + usage.cacheWrite;
236
606
  state = {
237
607
  ...state,
608
+ contextUsage: contextWindow > 0
609
+ ? { tokens: contextTokens, contextWindow, percent: (contextTokens / contextWindow) * 100 }
610
+ : undefined,
238
611
  usageTotals: {
239
612
  ...state.usageTotals,
240
613
  input: state.usageTotals.input + usage.input,
@@ -254,7 +627,9 @@ function createRemoteSessionProxy(local, agentDir, client, initialState, onWarni
254
627
  }
255
628
  if (wireEvent.type === "compaction_end" && wireEvent.accepted && !wireEvent.aborted) {
256
629
  try {
257
- sessionManager.reloadFromDisk?.();
630
+ local.sessionManager.reloadFromDisk?.();
631
+ if (sessionManager !== local.sessionManager)
632
+ sessionManager.reloadFromDisk?.();
258
633
  local.agent.state.messages = sessionManager.buildSessionContext().messages;
259
634
  }
260
635
  catch {
@@ -265,43 +640,109 @@ function createRemoteSessionProxy(local, agentDir, client, initialState, onWarni
265
640
  for (const listener of listeners)
266
641
  listener(event);
267
642
  });
268
- const refresh = async () => {
269
- const nextState = await client.getState();
643
+ const performRefresh = async () => {
644
+ // Captured before the first await: entries that arrive via `entry_appended`
645
+ // while this refresh is in flight are newer than the snapshot it reconciles
646
+ // against, and must survive the rebuild below.
647
+ const idsAtRefreshStart = new Set(sessionManager.getEntries().map((entry) => entry.id));
648
+ const nextState = await transportCall("refresh", () => client.getState());
649
+ if (!nextState)
650
+ return;
270
651
  state = { ...stateFromRpc(nextState) };
652
+ nextQueuedInputOrder = Math.max(0, ...nextState.ordered.map((item) => item.enqueueOrder));
271
653
  let messages;
272
654
  settingsManager = SettingsManager.create(nextState.cwd, agentDir, {
273
655
  projectTrusted: nextState.projectTrusted,
274
656
  });
275
657
  if (nextState.sessionFile) {
276
- // SessionManager.open retains the explicit path even when the host has
277
- // deferred creating the file for a setup-only session.
278
- sessionManager = SessionManager.open(nextState.sessionFile, undefined, nextState.cwd);
279
- if (nextState.entries?.length && !sessionManager.getEntries().length) {
280
- for (const entry of nextState.entries)
281
- sessionManager.appendEntry(entry);
658
+ // Keep the caller-owned manager identity when refreshing the same session.
659
+ // Navigation and host-side mutations must update every existing mirror, not
660
+ // leave the interactive runtime holding the pre-refresh snapshot.
661
+ if (sessionManager.getSessionFile() === nextState.sessionFile) {
662
+ sessionManager.reloadFromDisk?.();
663
+ }
664
+ else {
665
+ // SessionManager.open retains the explicit path even when the host has
666
+ // deferred creating the file for a setup-only session.
667
+ sessionManager = SessionManager.open(nextState.sessionFile, undefined, nextState.cwd);
668
+ }
669
+ // The host ships its complete entry list while the session file is still
670
+ // deferred (no assistant message yet, so nothing has been written), which
671
+ // makes that list the only authoritative view of the session. Reconcile the
672
+ // mirror to it exactly: `entry_appended` notifications that cross a
673
+ // concurrent refresh land on whichever manager is current, so the mirror
674
+ // can hold an arbitrary partial set rather than a prefix, and a snapshot
675
+ // taken mid-bind can simply be short. Backfilling only an empty mirror
676
+ // wedged it at whatever partial set it happened to hold.
677
+ const authoritative = nextState.entries;
678
+ if (authoritative?.length) {
679
+ const mirror = sessionManager.getEntries();
680
+ const matches = mirror.length === authoritative.length &&
681
+ mirror.every((entry, index) => entry.id === authoritative[index]?.id);
682
+ if (!matches) {
683
+ const rebuilt = SessionManager.open(nextState.sessionFile, undefined, nextState.cwd);
684
+ for (const entry of authoritative)
685
+ rebuilt.appendEntry(entry);
686
+ // A notification that crossed this refresh refers to an entry the
687
+ // snapshot predates, so it is newer than the snapshot; dropping it
688
+ // would strand the entry until an unrelated refresh happened to
689
+ // run. Keep it, in arrival order, after the authoritative list.
690
+ const authoritativeIds = new Set(authoritative.map((entry) => entry.id));
691
+ for (const entry of mirror) {
692
+ if (!idsAtRefreshStart.has(entry.id) && !authoritativeIds.has(entry.id)) {
693
+ rebuilt.appendEntry(entry);
694
+ }
695
+ }
696
+ sessionManager = rebuilt;
697
+ }
282
698
  }
283
699
  messages = sessionManager.buildSessionContext().messages;
284
700
  }
285
701
  else {
286
- messages = await client.getMessages();
702
+ messages = await transportCall("refresh", () => client.getMessages(), []);
287
703
  }
288
704
  local.agent.state.messages.splice(0, local.agent.state.messages.length, ...structuredClone(messages));
289
705
  streamingAssistant = undefined;
290
706
  };
707
+ // Refreshes must not interleave: each one reassigns sessionManager,
708
+ // settingsManager and the mirrored message list, so two in flight can commit a
709
+ // mixed snapshot of two different sessions. A replacement-driven refresh races
710
+ // exactly that way against a caller-driven one, so run them in order.
711
+ let refreshChain = Promise.resolve();
712
+ const refresh = () => {
713
+ const next = refreshChain.then(performRefresh, performRefresh);
714
+ refreshChain = next.then(() => { }, () => { });
715
+ return next;
716
+ };
291
717
  const session = new Proxy(local, {
292
718
  get(target, property, receiver) {
293
719
  if (property === "prompt")
294
- return (message, options) => client.prompt(message, {
295
- ...(options?.images ? { images: options.images } : {}),
296
- ...(options?.streamingBehavior ? { streamingBehavior: options.streamingBehavior } : {}),
297
- ...(options?.thinkingLevel ? { thinkingLevel: options.thinkingLevel } : {}),
298
- ...(options?.promptDisposition ? { promptDisposition: options.promptDisposition } : {}),
299
- ...(options?.preflightResult ? { preflightResult: options.preflightResult } : {}),
300
- });
720
+ return async (message, options) => {
721
+ try {
722
+ await client.prompt(message, {
723
+ ...(options?.images ? { images: options.images } : {}),
724
+ ...(options?.streamingBehavior ? { streamingBehavior: options.streamingBehavior } : {}),
725
+ ...(options?.thinkingLevel ? { thinkingLevel: options.thinkingLevel } : {}),
726
+ ...(options?.sessionTitlePrompt !== undefined
727
+ ? { sessionTitlePrompt: options.sessionTitlePrompt }
728
+ : {}),
729
+ ...(options?.expandPromptTemplates !== undefined
730
+ ? { expandPromptTemplates: options.expandPromptTemplates }
731
+ : {}),
732
+ ...(options?.promptDisposition ? { promptDisposition: options.promptDisposition } : {}),
733
+ ...(options?.preflightResult ? { preflightResult: options.preflightResult } : {}),
734
+ });
735
+ }
736
+ catch (error) {
737
+ if (!isTransportGoneError(error))
738
+ throw error;
739
+ reportActionFailure("prompt")(error);
740
+ }
741
+ };
301
742
  if (property === "abort")
302
743
  return () => client.abort();
303
744
  if (property === "abortCompaction")
304
- return () => void client.abortCompaction();
745
+ return () => void client.abortCompaction().catch(reportActionFailure("abortCompaction"));
305
746
  if (property === "steer")
306
747
  return (message, images, recovery) => {
307
748
  const enqueueOrder = recovery?.enqueueOrder ?? Math.max(0, ...state.ordered.map((item) => item.enqueueOrder)) + 1;
@@ -311,7 +752,7 @@ function createRemoteSessionProxy(local, agentDir, client, initialState, onWarni
311
752
  ordered: [...state.ordered, { text: message, mode: "steer", enqueueOrder }],
312
753
  pendingMessageCount: state.pendingMessageCount + 1,
313
754
  };
314
- return client.steer(message, images, { ...recovery, enqueueOrder });
755
+ return transportCall("steer", () => client.steer(message, images, { ...recovery, enqueueOrder }));
315
756
  };
316
757
  if (property === "followUp")
317
758
  return (message, images, recovery) => {
@@ -322,31 +763,53 @@ function createRemoteSessionProxy(local, agentDir, client, initialState, onWarni
322
763
  ordered: [...state.ordered, { text: message, mode: "followUp", enqueueOrder }],
323
764
  pendingMessageCount: state.pendingMessageCount + 1,
324
765
  };
325
- return client.followUp(message, images, { ...recovery, enqueueOrder });
766
+ return transportCall("followUp", () => client.followUp(message, images, { ...recovery, enqueueOrder }));
326
767
  };
327
768
  if (property === "waitForIdle")
328
769
  return () => client.waitForIdle();
329
770
  if (property === "getLastAssistantText")
330
771
  return () => target.getLastAssistantText();
331
- if (property === "setModel")
772
+ if (property === "setModel" || property === "setSessionModel")
332
773
  return async (model) => {
333
- const next = await client.setModel(model.provider, model.id);
334
- return { systemPromptName: next.systemPromptName, model: next };
774
+ const next = await transportCall("setModel", () => client.setModel(model.provider, model.id), undefined);
775
+ return next ? { systemPromptName: next.systemPromptName, model: next } : undefined;
776
+ };
777
+ if (property === "setSessionThinkingLevel")
778
+ return (level) => void client
779
+ .setThinkingLevel(level, { scope: "turn" })
780
+ .catch(reportActionFailure("setSessionThinkingLevel"));
781
+ if (property === "setSessionFastMode")
782
+ return (enabled) => void client.setFastMode(enabled).catch(reportActionFailure("setSessionFastMode"));
783
+ if (property === "abortRetry")
784
+ return () => void client.abortRetry().catch(reportActionFailure("abortRetry"));
785
+ if (property === "setAutoRetryEnabled")
786
+ return (enabled) => void client.setAutoRetry(enabled).catch(reportActionFailure("setAutoRetryEnabled"));
787
+ if (property === "reserveQueuedInputOrder")
788
+ return () => ++nextQueuedInputOrder;
789
+ if (property === "setFavoriteModels")
790
+ return (models) => {
791
+ state = { ...state, favoriteModels: [...models] };
792
+ void client.setFavoriteModels(models).catch(reportActionFailure("setFavoriteModels"));
793
+ };
794
+ if (property === "setScopedModels")
795
+ return (models) => {
796
+ state = { ...state, scopedModels: [...models] };
797
+ void client.setScopedModels(models).catch(reportActionFailure("setScopedModels"));
335
798
  };
336
799
  if (property === "cycleModel")
337
- return () => client.cycleModel();
800
+ return (direction) => transportCall("cycleModel", () => client.cycleModel(direction), undefined);
338
801
  if (property === "setThinkingLevel")
339
802
  return (level) => void client.setThinkingLevel(level).catch(reportActionFailure("setThinkingLevel"));
340
803
  if (property === "cycleThinkingLevel")
341
- return () => client.cycleThinkingLevel().then((result) => result?.level);
804
+ return () => transportCall("cycleThinkingLevel", () => client.cycleThinkingLevel(), undefined).then((result) => result?.level);
342
805
  if (property === "getAvailableThinkingLevels")
343
- return () => client.getAvailableThinkingLevels();
806
+ return () => transportCall("getAvailableThinkingLevels", () => client.getAvailableThinkingLevels(), []);
344
807
  if (property === "setSteeringMode")
345
808
  return (mode) => void client.setSteeringMode(mode).catch(reportActionFailure("setSteeringMode"));
346
809
  if (property === "setFollowUpMode")
347
810
  return (mode) => void client.setFollowUpMode(mode).catch(reportActionFailure("setFollowUpMode"));
348
811
  if (property === "compact")
349
- return (instructions) => client.compact(instructions);
812
+ return (instructions) => transportCall("compact", () => client.compact(instructions));
350
813
  if (property === "setAutoCompactionEnabled")
351
814
  return (enabled) => void client.setAutoCompaction(enabled).catch(reportActionFailure("setAutoCompaction"));
352
815
  if (property === "executeBash")
@@ -362,7 +825,7 @@ function createRemoteSessionProxy(local, agentDir, client, initialState, onWarni
362
825
  try {
363
826
  const result = await executeBashWithOperations(resolvedCommand, state.cwd, options.operations, { onChunk, signal: abortController.signal });
364
827
  if (state.sessionId === sessionAtStart) {
365
- await client.recordBashResult(command, result, options.excludeFromContext);
828
+ await transportCall("recordBashResult", () => client.recordBashResult(command, result, options.excludeFromContext));
366
829
  }
367
830
  return result;
368
831
  }
@@ -372,15 +835,35 @@ function createRemoteSessionProxy(local, agentDir, client, initialState, onWarni
372
835
  updateBashState();
373
836
  }
374
837
  }
375
- bashChunk = onChunk;
838
+ const executionId = `bash-${executionNamespace}-${++nextBashExecutionId}`;
839
+ const execution = {
840
+ chunk: onChunk,
841
+ promises: new Set(),
842
+ error: undefined,
843
+ hasError: false,
844
+ };
845
+ bashExecutions.set(executionId, execution);
376
846
  try {
377
- return await client.bash(command, {
847
+ const result = await transportCall("bash", () => client.bash(command, {
378
848
  excludeFromContext: options?.excludeFromContext,
849
+ executionId,
379
850
  operations: options?.operations,
380
- });
851
+ }), { output: "", exitCode: undefined, cancelled: true, truncated: false });
852
+ const callbacksSettled = await waitForBashCallbacks(execution.promises, false);
853
+ if (!callbacksSettled) {
854
+ if (result.fullOutputPath)
855
+ await cleanupRemoteBashOutput(result.fullOutputPath);
856
+ throw new Error("Bash output callback did not settle within 5000ms");
857
+ }
858
+ if (execution.hasError) {
859
+ if (result.fullOutputPath)
860
+ await cleanupRemoteBashOutput(result.fullOutputPath);
861
+ throw execution.error;
862
+ }
863
+ return result;
381
864
  }
382
865
  finally {
383
- bashChunk = undefined;
866
+ bashExecutions.delete(executionId);
384
867
  }
385
868
  };
386
869
  if (property === "abortBash")
@@ -390,21 +873,25 @@ function createRemoteSessionProxy(local, agentDir, client, initialState, onWarni
390
873
  else
391
874
  void client.abortBash().catch(reportActionFailure("abortBash"));
392
875
  };
876
+ if (property === "getContextUsage")
877
+ return () => state.contextUsage;
393
878
  if (property === "getSessionStats")
394
- return () => client.getSessionStats();
879
+ return () => transportCall("getSessionStats", () => client.getSessionStats(), local.getSessionStats());
395
880
  if (property === "exportToHtml")
396
- return (outputPath) => client.exportHtml(outputPath).then((result) => result.path);
881
+ return (outputPath, options) => transportCall("exportToHtml", () => client.exportHtml(outputPath, options?.themeName)).then((result) => result?.path);
397
882
  if (property === "setSessionName")
398
883
  return (name) => client.setSessionName(name).catch(reportActionFailure("setSessionName"));
399
884
  if (property === "navigateTree")
400
885
  return async (targetId, options) => {
401
- const result = await client.navigateTree(targetId, options);
886
+ const result = await transportCall("navigateTree", () => client.navigateTree(targetId, options), {
887
+ cancelled: true,
888
+ });
402
889
  if (!result.cancelled)
403
890
  await refresh();
404
891
  return result;
405
892
  };
406
893
  if (property === "getUserMessagesForForking")
407
- return () => client.getForkMessages();
894
+ return () => transportCall("getUserMessagesForForking", () => client.getForkMessages(), []);
408
895
  if (property === "subscribe")
409
896
  return (listener) => {
410
897
  listeners.add(listener);
@@ -416,10 +903,10 @@ function createRemoteSessionProxy(local, agentDir, client, initialState, onWarni
416
903
  };
417
904
  if (property === "isStreaming")
418
905
  return state.isStreaming;
906
+ if (property === "isIdle")
907
+ return !state.isStreaming;
419
908
  if (property === "isCompacting")
420
909
  return state.isCompacting;
421
- if (property === "isFastModeActive")
422
- return () => state.fastMode;
423
910
  if (property === "pendingMessageCount")
424
911
  return state.pendingMessageCount;
425
912
  if (property === "getSteeringMessages")
@@ -450,11 +937,11 @@ function createRemoteSessionProxy(local, agentDir, client, initialState, onWarni
450
937
  if (property === "isBashRunning")
451
938
  return state.isBashRunning;
452
939
  if (property === "reload")
453
- return (_options) => client.reload();
940
+ return (_options) => transportCall("reload", () => client.reload(), { cancelled: true });
454
941
  if (property === "checkReloadVeto")
455
- return () => client.checkReloadVeto();
942
+ return () => transportCall("checkReloadVeto", () => client.checkReloadVeto(), { cancelled: true });
456
943
  if (property === "exportToJsonl")
457
- return (outputPath) => client.exportJsonl(outputPath).then((result) => result.path);
944
+ return (outputPath) => transportCall("exportToJsonl", () => client.exportJsonl(outputPath)).then((result) => result?.path);
458
945
  // The footer and other renderers read session.state.*; surface the
459
946
  // host-authoritative fields there too, not only via the direct getters.
460
947
  if (property === "state") {
@@ -475,12 +962,24 @@ function createRemoteSessionProxy(local, agentDir, client, initialState, onWarni
475
962
  return state.sessionName;
476
963
  if (property === "serviceTier")
477
964
  return state.serviceTier;
965
+ if (property === "steeringMode")
966
+ return state.steeringMode;
967
+ if (property === "followUpMode")
968
+ return state.followUpMode;
969
+ if (property === "autoCompactionEnabled")
970
+ return state.autoCompactionEnabled;
971
+ if (property === "isFastModeActive")
972
+ return () => state.fastMode;
478
973
  if (property === "sessionManager")
479
974
  return remoteSessionManager;
480
975
  if (property === "settingsManager")
481
976
  return settingsManager;
482
977
  if (property === "messages")
483
978
  return target.messages;
979
+ if (property === "favoriteModels")
980
+ return state.favoriteModels;
981
+ if (property === "scopedModels")
982
+ return state.scopedModels;
484
983
  if (property === "model")
485
984
  return state.model ?? target.model;
486
985
  if (property === "thinkingLevel")
@@ -490,32 +989,49 @@ function createRemoteSessionProxy(local, agentDir, client, initialState, onWarni
490
989
  });
491
990
  return {
492
991
  session,
992
+ setHostUiHandler: (callback) => {
993
+ hostUiHandler = callback;
994
+ if (callback)
995
+ for (const request of pendingUiRequests.splice(0))
996
+ void Promise.resolve(callback(request))
997
+ .then((response) => response && client.sendExtensionUIResponse(response))
998
+ .catch(reportActionFailure("host UI response"));
999
+ },
493
1000
  refresh,
1001
+ aroundLocalReplacement: async (body) => {
1002
+ localReplacementDepth++;
1003
+ try {
1004
+ return await body();
1005
+ }
1006
+ finally {
1007
+ localReplacementDepth--;
1008
+ }
1009
+ },
494
1010
  abortLocalBash: () => localBashAbortController?.abort(),
495
1011
  createReplacedSessionContext: () => {
496
1012
  const context = local.createReplacedSessionContext();
497
1013
  Object.defineProperty(context, "cwd", { value: state.cwd });
498
1014
  Object.defineProperty(context, "sessionManager", { value: remoteSessionManager });
499
- context.sendMessage = (message, options) => client.sendCustomMessage(message, {
1015
+ context.sendMessage = (message, options) => transportCall("sendMessage", () => client.sendCustomMessage(message, {
500
1016
  triggerTurn: options?.triggerTurn,
501
1017
  deliverAs: options?.deliverAs,
502
- });
1018
+ }));
503
1019
  context.sendUserMessage = (content, options) => {
504
1020
  if (typeof content === "string")
505
- return client.prompt(content, {
1021
+ return transportCall("sendUserMessage", () => client.prompt(content, {
506
1022
  streamingBehavior: options?.deliverAs,
507
1023
  expandPromptTemplates: options?.expandPromptTemplates,
508
- });
1024
+ }));
509
1025
  const text = content
510
1026
  .filter((part) => part.type === "text")
511
1027
  .map((part) => part.text)
512
1028
  .join("\n");
513
1029
  const images = content.filter((part) => part.type === "image");
514
- return client.prompt(text, {
1030
+ return transportCall("sendUserMessage", () => client.prompt(text, {
515
1031
  images,
516
1032
  streamingBehavior: options?.deliverAs,
517
1033
  expandPromptTemplates: options?.expandPromptTemplates,
518
- });
1034
+ }));
519
1035
  };
520
1036
  return context;
521
1037
  },