@code-yeongyu/senpi 2026.8.28 → 2026.8.30

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 (272) hide show
  1. package/CHANGELOG.md +140 -0
  2. package/dist/cli/args.d.ts +2 -0
  3. package/dist/cli/args.d.ts.map +1 -1
  4. package/dist/cli/args.js +4 -0
  5. package/dist/cli/args.js.map +1 -1
  6. package/dist/core/agent-session-services.d.ts.map +1 -1
  7. package/dist/core/agent-session-services.js +2 -0
  8. package/dist/core/agent-session-services.js.map +1 -1
  9. package/dist/core/agent-session.d.ts +50 -1
  10. package/dist/core/agent-session.d.ts.map +1 -1
  11. package/dist/core/agent-session.js +313 -25
  12. package/dist/core/agent-session.js.map +1 -1
  13. package/dist/core/auth-storage.d.ts +1 -0
  14. package/dist/core/auth-storage.d.ts.map +1 -1
  15. package/dist/core/auth-storage.js +3 -0
  16. package/dist/core/auth-storage.js.map +1 -1
  17. package/dist/core/bash-executor.d.ts.map +1 -1
  18. package/dist/core/bash-executor.js +155 -37
  19. package/dist/core/bash-executor.js.map +1 -1
  20. package/dist/core/credential-accounts.d.ts.map +1 -1
  21. package/dist/core/credential-accounts.js +45 -21
  22. package/dist/core/credential-accounts.js.map +1 -1
  23. package/dist/core/credential-pool/classify.d.ts +6 -1
  24. package/dist/core/credential-pool/classify.d.ts.map +1 -1
  25. package/dist/core/credential-pool/classify.js +22 -5
  26. package/dist/core/credential-pool/classify.js.map +1 -1
  27. package/dist/core/credential-pool/env-slots.d.ts.map +1 -1
  28. package/dist/core/credential-pool/env-slots.js +2 -0
  29. package/dist/core/credential-pool/env-slots.js.map +1 -1
  30. package/dist/core/credential-pool/failover.d.ts +8 -0
  31. package/dist/core/credential-pool/failover.d.ts.map +1 -1
  32. package/dist/core/credential-pool/failover.js +24 -6
  33. package/dist/core/credential-pool/failover.js.map +1 -1
  34. package/dist/core/credential-pool/rotation-stream.d.ts +13 -2
  35. package/dist/core/credential-pool/rotation-stream.d.ts.map +1 -1
  36. package/dist/core/credential-pool/rotation-stream.js +97 -10
  37. package/dist/core/credential-pool/rotation-stream.js.map +1 -1
  38. package/dist/core/extensions/builtin/compaction/deterministic-fallback.d.ts +8 -2
  39. package/dist/core/extensions/builtin/compaction/deterministic-fallback.d.ts.map +1 -1
  40. package/dist/core/extensions/builtin/compaction/deterministic-fallback.js +160 -47
  41. package/dist/core/extensions/builtin/compaction/deterministic-fallback.js.map +1 -1
  42. package/dist/core/extensions/builtin/compaction/retained-message-safety.d.ts.map +1 -1
  43. package/dist/core/extensions/builtin/compaction/retained-message-safety.js +31 -8
  44. package/dist/core/extensions/builtin/compaction/retained-message-safety.js.map +1 -1
  45. package/dist/core/extensions/builtin/cursor-cli-oauth/stream.d.ts.map +1 -1
  46. package/dist/core/extensions/builtin/cursor-cli-oauth/stream.js +8 -28
  47. package/dist/core/extensions/builtin/cursor-cli-oauth/stream.js.map +1 -1
  48. package/dist/core/extensions/builtin/goal/index.d.ts.map +1 -1
  49. package/dist/core/extensions/builtin/goal/index.js +7 -0
  50. package/dist/core/extensions/builtin/goal/index.js.map +1 -1
  51. package/dist/core/extensions/builtin/permission-system/external-dir.d.ts.map +1 -1
  52. package/dist/core/extensions/builtin/permission-system/external-dir.js +15 -5
  53. package/dist/core/extensions/builtin/permission-system/external-dir.js.map +1 -1
  54. package/dist/core/extensions/builtin/permission-system/parsers.d.ts.map +1 -1
  55. package/dist/core/extensions/builtin/permission-system/parsers.js +16 -1
  56. package/dist/core/extensions/builtin/permission-system/parsers.js.map +1 -1
  57. package/dist/core/extensions/builtin/service-tier.d.ts.map +1 -1
  58. package/dist/core/extensions/builtin/service-tier.js +13 -0
  59. package/dist/core/extensions/builtin/service-tier.js.map +1 -1
  60. package/dist/core/extensions/builtin/terminal/extension.d.ts.map +1 -1
  61. package/dist/core/extensions/builtin/terminal/extension.js +4 -2
  62. package/dist/core/extensions/builtin/terminal/extension.js.map +1 -1
  63. package/dist/core/extensions/builtin/terminal/manager.d.ts +5 -0
  64. package/dist/core/extensions/builtin/terminal/manager.d.ts.map +1 -1
  65. package/dist/core/extensions/builtin/terminal/manager.js +31 -3
  66. package/dist/core/extensions/builtin/terminal/manager.js.map +1 -1
  67. package/dist/core/extensions/builtin/terminal/monitor-permission.d.ts +8 -0
  68. package/dist/core/extensions/builtin/terminal/monitor-permission.d.ts.map +1 -0
  69. package/dist/core/extensions/builtin/terminal/monitor-permission.js +16 -0
  70. package/dist/core/extensions/builtin/terminal/monitor-permission.js.map +1 -0
  71. package/dist/core/extensions/builtin/terminal/monitor-registry.d.ts +13 -0
  72. package/dist/core/extensions/builtin/terminal/monitor-registry.d.ts.map +1 -1
  73. package/dist/core/extensions/builtin/terminal/monitor-registry.js +403 -3
  74. package/dist/core/extensions/builtin/terminal/monitor-registry.js.map +1 -1
  75. package/dist/core/extensions/builtin/terminal/session-bundle.d.ts.map +1 -1
  76. package/dist/core/extensions/builtin/terminal/session-bundle.js +1 -0
  77. package/dist/core/extensions/builtin/terminal/session-bundle.js.map +1 -1
  78. package/dist/core/extensions/builtin/terminal/tools/kill-bash.d.ts.map +1 -1
  79. package/dist/core/extensions/builtin/terminal/tools/kill-bash.js +5 -2
  80. package/dist/core/extensions/builtin/terminal/tools/kill-bash.js.map +1 -1
  81. package/dist/core/extensions/builtin/terminal/tools/monitor.d.ts +4 -0
  82. package/dist/core/extensions/builtin/terminal/tools/monitor.d.ts.map +1 -1
  83. package/dist/core/extensions/builtin/terminal/tools/monitor.js +40 -2
  84. package/dist/core/extensions/builtin/terminal/tools/monitor.js.map +1 -1
  85. package/dist/core/model-config-schema.d.ts +4 -4
  86. package/dist/core/model-resolver.js +2 -2
  87. package/dist/core/model-resolver.js.map +1 -1
  88. package/dist/core/model-runtime.d.ts +2 -0
  89. package/dist/core/model-runtime.d.ts.map +1 -1
  90. package/dist/core/model-runtime.js +74 -37
  91. package/dist/core/model-runtime.js.map +1 -1
  92. package/dist/core/retry-fallback/chains.d.ts +23 -1
  93. package/dist/core/retry-fallback/chains.d.ts.map +1 -1
  94. package/dist/core/retry-fallback/chains.js +59 -3
  95. package/dist/core/retry-fallback/chains.js.map +1 -1
  96. package/dist/core/retry-fallback/controller.d.ts.map +1 -1
  97. package/dist/core/retry-fallback/controller.js +9 -2
  98. package/dist/core/retry-fallback/controller.js.map +1 -1
  99. package/dist/core/retry-fallback/settings.d.ts.map +1 -1
  100. package/dist/core/retry-fallback/settings.js +9 -0
  101. package/dist/core/retry-fallback/settings.js.map +1 -1
  102. package/dist/core/retry-fallback/validate.js +1 -1
  103. package/dist/core/retry-fallback/validate.js.map +1 -1
  104. package/dist/core/sdk.js +1 -1
  105. package/dist/core/sdk.js.map +1 -1
  106. package/dist/core/session-manager.d.ts +9 -0
  107. package/dist/core/session-manager.d.ts.map +1 -1
  108. package/dist/core/session-manager.js +40 -0
  109. package/dist/core/session-manager.js.map +1 -1
  110. package/dist/core/settings-manager.d.ts +7 -0
  111. package/dist/core/settings-manager.d.ts.map +1 -1
  112. package/dist/core/settings-manager.js +6 -0
  113. package/dist/core/settings-manager.js.map +1 -1
  114. package/dist/core/tools/bash.d.ts.map +1 -1
  115. package/dist/core/tools/bash.js +97 -15
  116. package/dist/core/tools/bash.js.map +1 -1
  117. package/dist/core/tools/index.d.ts +1 -0
  118. package/dist/core/tools/index.d.ts.map +1 -1
  119. package/dist/core/tools/index.js +8 -0
  120. package/dist/core/tools/index.js.map +1 -1
  121. package/dist/core/tools/output-accumulator.d.ts +1 -1
  122. package/dist/core/tools/output-accumulator.d.ts.map +1 -1
  123. package/dist/core/tools/output-accumulator.js +76 -34
  124. package/dist/core/tools/output-accumulator.js.map +1 -1
  125. package/dist/main.d.ts +7 -0
  126. package/dist/main.d.ts.map +1 -1
  127. package/dist/main.js +10 -1
  128. package/dist/main.js.map +1 -1
  129. package/dist/modes/interactive/components/favorite-models-selector.d.ts.map +1 -1
  130. package/dist/modes/interactive/components/favorite-models-selector.js +2 -2
  131. package/dist/modes/interactive/components/favorite-models-selector.js.map +1 -1
  132. package/dist/modes/interactive/components/footer.d.ts +7 -0
  133. package/dist/modes/interactive/components/footer.d.ts.map +1 -1
  134. package/dist/modes/interactive/components/footer.js +34 -8
  135. package/dist/modes/interactive/components/footer.js.map +1 -1
  136. package/dist/modes/interactive/grok/chrome.d.ts +2 -0
  137. package/dist/modes/interactive/grok/chrome.d.ts.map +1 -1
  138. package/dist/modes/interactive/grok/chrome.js.map +1 -1
  139. package/dist/modes/interactive/interactive-host-runtime.d.ts +51 -1
  140. package/dist/modes/interactive/interactive-host-runtime.d.ts.map +1 -1
  141. package/dist/modes/interactive/interactive-host-runtime.js +545 -40
  142. package/dist/modes/interactive/interactive-host-runtime.js.map +1 -1
  143. package/dist/modes/interactive/interactive-mode.d.ts +7 -0
  144. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  145. package/dist/modes/interactive/interactive-mode.js +390 -239
  146. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  147. package/dist/modes/rpc/connection-handler.d.ts +8 -1
  148. package/dist/modes/rpc/connection-handler.d.ts.map +1 -1
  149. package/dist/modes/rpc/connection-handler.js +268 -63
  150. package/dist/modes/rpc/connection-handler.js.map +1 -1
  151. package/dist/modes/rpc/host-ensure.d.ts +2 -0
  152. package/dist/modes/rpc/host-ensure.d.ts.map +1 -1
  153. package/dist/modes/rpc/host-ensure.js +15 -2
  154. package/dist/modes/rpc/host-ensure.js.map +1 -1
  155. package/dist/modes/rpc/host-lifecycle.d.ts.map +1 -1
  156. package/dist/modes/rpc/host-lifecycle.js +31 -12
  157. package/dist/modes/rpc/host-lifecycle.js.map +1 -1
  158. package/dist/modes/rpc/host-watchdog.d.ts +2 -0
  159. package/dist/modes/rpc/host-watchdog.d.ts.map +1 -1
  160. package/dist/modes/rpc/host-watchdog.js +23 -12
  161. package/dist/modes/rpc/host-watchdog.js.map +1 -1
  162. package/dist/modes/rpc/multi-session-host.js +20 -8
  163. package/dist/modes/rpc/multi-session-host.js.map +1 -1
  164. package/dist/modes/rpc/rpc-client.d.ts +91 -16
  165. package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
  166. package/dist/modes/rpc/rpc-client.js +129 -37
  167. package/dist/modes/rpc/rpc-client.js.map +1 -1
  168. package/dist/modes/rpc/rpc-input-validation.d.ts +1 -0
  169. package/dist/modes/rpc/rpc-input-validation.d.ts.map +1 -1
  170. package/dist/modes/rpc/rpc-input-validation.js +66 -0
  171. package/dist/modes/rpc/rpc-input-validation.js.map +1 -1
  172. package/dist/modes/rpc/rpc-mode.d.ts +3 -1
  173. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  174. package/dist/modes/rpc/rpc-mode.js +3 -1
  175. package/dist/modes/rpc/rpc-mode.js.map +1 -1
  176. package/dist/modes/rpc/rpc-types.d.ts +241 -3
  177. package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
  178. package/dist/modes/rpc/rpc-types.js.map +1 -1
  179. package/dist/modes/rpc/session-binding.d.ts +1 -0
  180. package/dist/modes/rpc/session-binding.d.ts.map +1 -1
  181. package/dist/modes/rpc/session-binding.js +21 -1
  182. package/dist/modes/rpc/session-binding.js.map +1 -1
  183. package/dist/modes/rpc/session-command-router.d.ts +22 -0
  184. package/dist/modes/rpc/session-command-router.d.ts.map +1 -1
  185. package/dist/modes/rpc/session-command-router.js +148 -16
  186. package/dist/modes/rpc/session-command-router.js.map +1 -1
  187. package/dist/modes/rpc/session-event-writer.d.ts +4 -0
  188. package/dist/modes/rpc/session-event-writer.d.ts.map +1 -1
  189. package/dist/modes/rpc/session-event-writer.js +58 -7
  190. package/dist/modes/rpc/session-event-writer.js.map +1 -1
  191. package/dist/modes/rpc/session-extension-ui-requests.d.ts +1 -0
  192. package/dist/modes/rpc/session-extension-ui-requests.d.ts.map +1 -1
  193. package/dist/modes/rpc/session-extension-ui-requests.js +5 -0
  194. package/dist/modes/rpc/session-extension-ui-requests.js.map +1 -1
  195. package/dist/modes/rpc/session-registry.d.ts +17 -2
  196. package/dist/modes/rpc/session-registry.d.ts.map +1 -1
  197. package/dist/modes/rpc/session-registry.js +67 -4
  198. package/dist/modes/rpc/session-registry.js.map +1 -1
  199. package/dist/modes/rpc/socket-event-fanout.d.ts +22 -0
  200. package/dist/modes/rpc/socket-event-fanout.d.ts.map +1 -0
  201. package/dist/modes/rpc/socket-event-fanout.js +84 -0
  202. package/dist/modes/rpc/socket-event-fanout.js.map +1 -0
  203. package/docs/rpc.md +40 -1
  204. package/docs/settings.md +34 -0
  205. package/docs/windows.md +2 -0
  206. package/node_modules/@code-yeongyu/senpi-codemode/CHANGELOG.md +62 -0
  207. package/node_modules/@code-yeongyu/senpi-codemode/package.json +4 -4
  208. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/worker-indirect-eval.js +139 -90
  209. package/node_modules/@code-yeongyu/senpi-codemode/src/prompt/eval-prompt.ts +4 -4
  210. package/node_modules/@earendil-works/pi-agent-core/dist/agent-loop.d.ts.map +1 -1
  211. package/node_modules/@earendil-works/pi-agent-core/dist/agent-loop.js +6 -3
  212. package/node_modules/@earendil-works/pi-agent-core/dist/agent-loop.js.map +1 -1
  213. package/node_modules/@earendil-works/pi-agent-core/dist/harness/env/nodejs.d.ts.map +1 -1
  214. package/node_modules/@earendil-works/pi-agent-core/dist/harness/env/nodejs.js +40 -20
  215. package/node_modules/@earendil-works/pi-agent-core/dist/harness/env/nodejs.js.map +1 -1
  216. package/node_modules/@earendil-works/pi-agent-core/dist/harness/types.d.ts +3 -3
  217. package/node_modules/@earendil-works/pi-agent-core/dist/harness/types.d.ts.map +1 -1
  218. package/node_modules/@earendil-works/pi-agent-core/dist/harness/types.js.map +1 -1
  219. package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/shell-output.d.ts +1 -1
  220. package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/shell-output.d.ts.map +1 -1
  221. package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/shell-output.js +28 -9
  222. package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/shell-output.js.map +1 -1
  223. package/node_modules/@earendil-works/pi-agent-core/package.json +3 -3
  224. package/node_modules/@earendil-works/pi-ai/dist/api/anthropic-messages.d.ts.map +1 -1
  225. package/node_modules/@earendil-works/pi-ai/dist/api/anthropic-messages.js +58 -31
  226. package/node_modules/@earendil-works/pi-ai/dist/api/anthropic-messages.js.map +1 -1
  227. package/node_modules/@earendil-works/pi-ai/dist/api/cursor-agent/measure.d.ts +20 -0
  228. package/node_modules/@earendil-works/pi-ai/dist/api/cursor-agent/measure.d.ts.map +1 -0
  229. package/node_modules/@earendil-works/pi-ai/dist/api/cursor-agent/measure.js +443 -0
  230. package/node_modules/@earendil-works/pi-ai/dist/api/cursor-agent/measure.js.map +1 -0
  231. package/node_modules/@earendil-works/pi-ai/dist/api/cursor-agent.d.ts +2 -0
  232. package/node_modules/@earendil-works/pi-ai/dist/api/cursor-agent.d.ts.map +1 -1
  233. package/node_modules/@earendil-works/pi-ai/dist/api/cursor-agent.js +2 -0
  234. package/node_modules/@earendil-works/pi-ai/dist/api/cursor-agent.js.map +1 -1
  235. package/node_modules/@earendil-works/pi-ai/dist/api/openai-completions.js +1 -1
  236. package/node_modules/@earendil-works/pi-ai/dist/api/openai-completions.js.map +1 -1
  237. package/node_modules/@earendil-works/pi-ai/dist/index.d.ts +1 -0
  238. package/node_modules/@earendil-works/pi-ai/dist/index.d.ts.map +1 -1
  239. package/node_modules/@earendil-works/pi-ai/dist/index.js +1 -0
  240. package/node_modules/@earendil-works/pi-ai/dist/index.js.map +1 -1
  241. package/node_modules/@earendil-works/pi-ai/dist/providers/data/.manifest.json +1 -1
  242. package/node_modules/@earendil-works/pi-ai/dist/providers/data/amazon-bedrock.json +1 -1
  243. package/node_modules/@earendil-works/pi-ai/dist/providers/data/baseten.json +1 -1
  244. package/node_modules/@earendil-works/pi-ai/dist/providers/data/cloudflare-ai-gateway.json +1 -1
  245. package/node_modules/@earendil-works/pi-ai/dist/providers/data/cloudflare-workers-ai.json +1 -1
  246. package/node_modules/@earendil-works/pi-ai/dist/providers/data/fireworks.json +1 -1
  247. package/node_modules/@earendil-works/pi-ai/dist/providers/data/huggingface.json +1 -1
  248. package/node_modules/@earendil-works/pi-ai/dist/providers/data/nvidia.json +1 -1
  249. package/node_modules/@earendil-works/pi-ai/dist/providers/data/opencode-go.json +1 -1
  250. package/node_modules/@earendil-works/pi-ai/dist/providers/data/opencode.json +1 -1
  251. package/node_modules/@earendil-works/pi-ai/dist/providers/data/openrouter.json +1 -1
  252. package/node_modules/@earendil-works/pi-ai/dist/providers/data/together.json +1 -1
  253. package/node_modules/@earendil-works/pi-ai/dist/providers/data/vercel-ai-gateway.json +1 -1
  254. package/node_modules/@earendil-works/pi-ai/dist/providers/data/zai-coding-cn.json +1 -1
  255. package/node_modules/@earendil-works/pi-ai/dist/providers/data/zai.json +1 -1
  256. package/node_modules/@earendil-works/pi-ai/package.json +2 -2
  257. package/node_modules/@earendil-works/pi-pty/package.json +1 -1
  258. package/node_modules/@earendil-works/pi-telemetry/package.json +1 -1
  259. package/node_modules/@earendil-works/pi-tui/dist/components/editor.d.ts +7 -0
  260. package/node_modules/@earendil-works/pi-tui/dist/components/editor.d.ts.map +1 -1
  261. package/node_modules/@earendil-works/pi-tui/dist/components/editor.js +5 -0
  262. package/node_modules/@earendil-works/pi-tui/dist/components/editor.js.map +1 -1
  263. package/node_modules/@earendil-works/pi-tui/dist/mux.d.ts +1 -1
  264. package/node_modules/@earendil-works/pi-tui/dist/mux.d.ts.map +1 -1
  265. package/node_modules/@earendil-works/pi-tui/dist/mux.js +2 -2
  266. package/node_modules/@earendil-works/pi-tui/dist/mux.js.map +1 -1
  267. package/node_modules/@earendil-works/pi-tui/dist/terminal.d.ts +1 -0
  268. package/node_modules/@earendil-works/pi-tui/dist/terminal.d.ts.map +1 -1
  269. package/node_modules/@earendil-works/pi-tui/dist/terminal.js +19 -0
  270. package/node_modules/@earendil-works/pi-tui/dist/terminal.js.map +1 -1
  271. package/node_modules/@earendil-works/pi-tui/package.json +1 -1
  272. package/package.json +7 -7
@@ -1,3 +1,8 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { rm } from "node:fs/promises";
3
+ import { executeBashWithOperations } from "../../core/bash-executor.js";
4
+ import { SessionManager } from "../../core/session-manager.js";
5
+ import { SettingsManager } from "../../core/settings-manager.js";
1
6
  import { ensureHost } from "../rpc/host-ensure.js";
2
7
  import { RpcClient } from "../rpc/rpc-client.js";
3
8
  export const INTERACTIVE_HOST_FALLBACK_WARNING = "Warning: shared interactive host unavailable; continuing locally";
@@ -23,7 +28,15 @@ export async function createInteractiveHostRuntime(localRuntime, options) {
23
28
  modelId: localRuntime.session.model?.id,
24
29
  thinkingLevel: localRuntime.session.thinkingLevel,
25
30
  });
26
- const remoteSession = createRemoteSessionProxy(localRuntime.session, client, opened.state, options.onWarning);
31
+ const remoteSession = createRemoteSessionProxy(localRuntime.session, localRuntime.services.agentDir, client, opened.state, options.onWarning);
32
+ if (opened.state.isBashRunning && !opened.attached) {
33
+ // Only a newly opened session can have an execution orphaned by a
34
+ // previous connection. An attach is an observer of the same live runtime;
35
+ // aborting it would cancel work owned by the surviving attachment.
36
+ await client.abortBash().catch(() => { });
37
+ }
38
+ if (opened.attached)
39
+ await remoteSession.refresh();
27
40
  return new RemoteInteractiveRuntime(localRuntime, remoteSession, client);
28
41
  }
29
42
  catch (cause) {
@@ -36,25 +49,25 @@ export async function createInteractiveHostRuntime(localRuntime, options) {
36
49
  return localRuntime;
37
50
  }
38
51
  }
39
- class RemoteInteractiveRuntime {
52
+ export class RemoteInteractiveRuntime {
40
53
  #local;
41
- #session;
54
+ #remoteSession;
42
55
  #client;
43
56
  #rebindSession;
44
57
  #beforeSessionInvalidate;
45
- constructor(local, session, client) {
58
+ constructor(local, remoteSession, client) {
46
59
  this.#local = local;
47
- this.#session = session;
60
+ this.#remoteSession = remoteSession;
48
61
  this.#client = client;
49
62
  }
50
63
  get session() {
51
- return this.#session;
64
+ return this.#remoteSession.session;
52
65
  }
53
66
  get services() {
54
67
  return this.#local.services;
55
68
  }
56
69
  get cwd() {
57
- return this.#local.cwd;
70
+ return this.#remoteSession.session.sessionManager.getCwd();
58
71
  }
59
72
  get diagnostics() {
60
73
  return this.#local.diagnostics;
@@ -71,37 +84,87 @@ class RemoteInteractiveRuntime {
71
84
  setRebindSession(callback) {
72
85
  this.#rebindSession = callback;
73
86
  }
87
+ setHostUiHandler(callback) {
88
+ this.#remoteSession.setHostUiHandler(callback);
89
+ }
74
90
  async dispose() {
75
- await this.#client.closeSession();
76
- await this.#client.stop();
77
- await this.#local.dispose();
91
+ const errors = [];
92
+ for (const cleanup of [
93
+ () => this.#remoteSession.abortLocalBash?.(),
94
+ () => this.#client.closeSession(),
95
+ () => this.#client.stop(),
96
+ () => this.#local.dispose(),
97
+ ]) {
98
+ try {
99
+ await cleanup();
100
+ }
101
+ catch (error) {
102
+ errors.push(error);
103
+ }
104
+ }
105
+ if (errors.length > 0)
106
+ throw errors[0];
78
107
  }
79
- async newSession() {
80
- this.#beforeSessionInvalidate?.();
81
- const result = await this.#client.newSession();
82
- if (!result.cancelled)
108
+ async newSession(options) {
109
+ const result = await this.#client.newSession(options?.parentSession);
110
+ if (!result.cancelled) {
111
+ this.#beforeSessionInvalidate?.();
112
+ this.#remoteSession.abortLocalBash();
113
+ await this.#remoteSession.refresh();
114
+ if (options?.setup) {
115
+ const capture = SessionManager.inMemory(this.#remoteSession.session.sessionManager.getCwd());
116
+ await options.setup(capture);
117
+ for (const entry of capture.getEntries())
118
+ await this.#client.appendSessionEntry(entry);
119
+ await this.#remoteSession.refresh();
120
+ }
83
121
  await this.#rebindSession?.();
122
+ if (options?.withSession)
123
+ await options.withSession(this.#remoteSession.createReplacedSessionContext());
124
+ }
84
125
  return result;
85
126
  }
86
- async switchSession(sessionPath) {
87
- this.#beforeSessionInvalidate?.();
88
- const result = await this.#client.switchSession(sessionPath);
89
- if (!result.cancelled)
127
+ async switchSession(sessionPath, options) {
128
+ const result = await this.#client.switchSession(sessionPath, options?.cwdOverride === undefined ? undefined : { cwdOverride: options.cwdOverride });
129
+ if (!result.cancelled) {
130
+ this.#beforeSessionInvalidate?.();
131
+ this.#remoteSession.abortLocalBash();
132
+ await this.#remoteSession.refresh();
90
133
  await this.#rebindSession?.();
134
+ // The shared host already resolved trust; this callback is retained for
135
+ // compatibility, but its result cannot override host-authoritative state.
136
+ options?.projectTrustContextFactory?.(this.#remoteSession.session.sessionManager.getCwd());
137
+ if (options?.withSession)
138
+ await options.withSession(this.#remoteSession.createReplacedSessionContext());
139
+ }
91
140
  return result;
92
141
  }
93
- async fork(entryId) {
94
- this.#beforeSessionInvalidate?.();
95
- const result = await this.#client.fork(entryId);
96
- if (!result.cancelled)
97
- await this.#rebindSession?.();
142
+ async fork(entryId, options) {
143
+ const result = await this.#client.fork(entryId, options);
144
+ if (!result.cancelled) {
145
+ this.#beforeSessionInvalidate?.();
146
+ this.#remoteSession.abortLocalBash();
147
+ await this.#refreshAndRebind();
148
+ if (options?.withSession)
149
+ await options.withSession(this.#remoteSession.createReplacedSessionContext());
150
+ }
98
151
  return { cancelled: result.cancelled, selectedText: result.text };
99
152
  }
100
- async importFromJsonl() {
101
- throw new Error("Session import is not available while connected to the shared host");
153
+ async importFromJsonl(inputPath, cwdOverride) {
154
+ const result = await this.#client.importJsonl(inputPath, cwdOverride);
155
+ if (!result.cancelled) {
156
+ this.#beforeSessionInvalidate?.();
157
+ this.#remoteSession.abortLocalBash();
158
+ await this.#refreshAndRebind();
159
+ }
160
+ return result;
161
+ }
162
+ async #refreshAndRebind() {
163
+ await this.#remoteSession.refresh();
164
+ await this.#rebindSession?.();
102
165
  }
103
166
  }
104
- function createRemoteSessionProxy(local, client, initialState, onWarning) {
167
+ function createRemoteSessionProxy(local, agentDir, client, initialState, onWarning) {
105
168
  // Fire-and-forget setters keep the sync AgentSession signature, but their RPC
106
169
  // failures must not vanish: the matching *_changed wire event confirms success,
107
170
  // and a rejection here is the only signal of failure.
@@ -112,36 +175,266 @@ function createRemoteSessionProxy(local, client, initialState, onWarning) {
112
175
  cause: error,
113
176
  });
114
177
  };
115
- let state = initialState;
178
+ let state = { ...initialState };
179
+ const waitForBashCallbacks = async (promises, allowAbandonment) => {
180
+ if (promises.size === 0)
181
+ return true;
182
+ const settled = Promise.allSettled([...promises]).then(() => true);
183
+ let timeout;
184
+ const bounded = new Promise((resolve) => {
185
+ timeout = setTimeout(resolve, allowAbandonment ? 100 : 5_000, false);
186
+ timeout.unref?.();
187
+ });
188
+ try {
189
+ return await Promise.race([settled, bounded]);
190
+ }
191
+ finally {
192
+ if (timeout)
193
+ clearTimeout(timeout);
194
+ }
195
+ };
196
+ const executionNamespace = randomUUID();
197
+ const cleanupRemoteBashOutput = async (path) => {
198
+ let cleanupTimeout;
199
+ try {
200
+ await Promise.race([
201
+ client.cleanupBashOutput(path),
202
+ new Promise((_, reject) => {
203
+ cleanupTimeout = setTimeout(() => reject(new Error("RPC cleanup timed out")), 1_000);
204
+ }),
205
+ ]);
206
+ return;
207
+ }
208
+ catch (transportError) {
209
+ // Spill paths are shared host filesystem paths. If the RPC transport is
210
+ // already unavailable, remove locally as a bounded best-effort fallback;
211
+ // a later reconnect is not required to prevent this callback failure from
212
+ // permanently leaking the host-owned artifact.
213
+ try {
214
+ await rm(path, { force: true });
215
+ return;
216
+ }
217
+ catch (localError) {
218
+ onWarning?.({
219
+ type: "interactive_host_action_failed",
220
+ message: `Warning: failed to clean up shared bash output spill: ${localError instanceof Error ? localError.message : String(localError)}`,
221
+ cause: new AggregateError([transportError, localError]),
222
+ });
223
+ }
224
+ }
225
+ finally {
226
+ if (cleanupTimeout)
227
+ clearTimeout(cleanupTimeout);
228
+ }
229
+ };
230
+ let nextBashExecutionId = 0;
231
+ const bashExecutions = new Map();
232
+ let hostUiHandler;
233
+ const pendingUiRequests = [];
234
+ let localBashAbortController;
235
+ let localBashRunning = false;
236
+ let hostBashRunning = initialState.isBashRunning;
237
+ let nextQueuedInputOrder = Math.max(0, ...initialState.ordered.map((item) => item.enqueueOrder));
238
+ let sessionManager = local.sessionManager;
239
+ let settingsManager = SettingsManager.create(initialState.cwd, agentDir, {
240
+ projectTrusted: initialState.projectTrusted,
241
+ });
242
+ const updateBashState = () => {
243
+ state = { ...state, isBashRunning: localBashRunning || hostBashRunning };
244
+ };
245
+ const remoteSessionManager = new Proxy({}, {
246
+ get(_target, property, _receiver) {
247
+ if (property === "appendLabelChange") {
248
+ return (entryId, label) => void client.setLabel(entryId, label);
249
+ }
250
+ if (property === "getCwd")
251
+ return () => state.cwd;
252
+ if (property === "getSessionName")
253
+ return () => state.sessionName;
254
+ if (property === "getUsageTotals")
255
+ return () => state.usageTotals;
256
+ const value = Reflect.get(sessionManager, property, sessionManager);
257
+ return typeof value === "function" ? value.bind(sessionManager) : value;
258
+ },
259
+ });
116
260
  let streamingAssistant;
261
+ let mirroredCurrentAssistantUsage = false;
117
262
  const listeners = new Set();
118
263
  client.onEvent((wireEvent) => {
264
+ if (wireEvent.type === "extension_ui_request") {
265
+ const request = wireEvent;
266
+ if (hostUiHandler)
267
+ void Promise.resolve(hostUiHandler(request)).then((response) => response && client.sendExtensionUIResponse(response));
268
+ else
269
+ pendingUiRequests.push(request);
270
+ return;
271
+ }
119
272
  if (wireEvent.type === "agent_settled")
120
- state = { ...state, isStreaming: false };
273
+ state = { ...state, isStreaming: false, retryAttempt: 0 };
274
+ if (wireEvent.type === "bash_start") {
275
+ hostBashRunning = true;
276
+ updateBashState();
277
+ }
278
+ if (wireEvent.type === "bash_end") {
279
+ hostBashRunning = false;
280
+ updateBashState();
281
+ }
282
+ if (wireEvent.type === "bash_execution_update" && wireEvent.id) {
283
+ const execution = bashExecutions.get(wireEvent.id);
284
+ if (execution?.chunk && !execution.hasError) {
285
+ try {
286
+ const callbackPromise = Promise.resolve(execution.chunk(wireEvent.delta)).catch((error) => {
287
+ if (!execution.hasError) {
288
+ execution.error = error;
289
+ execution.hasError = true;
290
+ void client.abortBash().catch(() => { });
291
+ }
292
+ throw error;
293
+ });
294
+ execution.promises.add(callbackPromise);
295
+ void callbackPromise.catch(() => { }).finally(() => execution.promises.delete(callbackPromise));
296
+ }
297
+ catch (error) {
298
+ if (!execution.hasError) {
299
+ execution.error = error;
300
+ execution.hasError = true;
301
+ void client.abortBash().catch(() => { });
302
+ }
303
+ }
304
+ }
305
+ }
121
306
  if (wireEvent.type === "agent_start")
122
307
  state = { ...state, isStreaming: true };
308
+ if (wireEvent.type === "compaction_start")
309
+ state = { ...state, isCompacting: true };
310
+ if (wireEvent.type === "compaction_end")
311
+ state = { ...state, isCompacting: false };
312
+ if (wireEvent.type === "auto_retry_start")
313
+ state = { ...state, retryAttempt: wireEvent.attempt };
314
+ if (wireEvent.type === "auto_retry_end")
315
+ state = { ...state, retryAttempt: 0 };
316
+ if (wireEvent.type === "queue_update") {
317
+ nextQueuedInputOrder = Math.max(nextQueuedInputOrder, ...wireEvent.ordered.map((item) => item.enqueueOrder));
318
+ state = {
319
+ ...state,
320
+ steering: [...wireEvent.steering],
321
+ followUp: [...wireEvent.followUp],
322
+ ordered: [...wireEvent.ordered],
323
+ pendingMessageCount: wireEvent.steering.length + wireEvent.followUp.length,
324
+ };
325
+ }
123
326
  if (wireEvent.type === "model_changed") {
124
327
  state = { ...state, model: wireEvent.model, thinkingLevel: wireEvent.thinkingLevel };
125
328
  }
126
329
  if (wireEvent.type === "thinking_level_changed")
127
330
  state = { ...state, thinkingLevel: wireEvent.level };
331
+ if (wireEvent.type === "service_tier_changed") {
332
+ state = { ...state, serviceTier: wireEvent.tier, fastMode: wireEvent.fastMode };
333
+ }
334
+ if (wireEvent.type === "session_settings_changed") {
335
+ state = {
336
+ ...state,
337
+ steeringMode: wireEvent.steeringMode,
338
+ followUpMode: wireEvent.followUpMode,
339
+ autoCompactionEnabled: wireEvent.autoCompactionEnabled,
340
+ };
341
+ }
342
+ if (wireEvent.type === "entry_appended") {
343
+ try {
344
+ sessionManager.appendEntry(wireEvent.entry);
345
+ local.agent.state.messages = sessionManager.buildSessionContext().messages;
346
+ }
347
+ catch {
348
+ // Non-fatal if the local snapshot cannot accept a concurrent entry.
349
+ }
350
+ }
351
+ if (wireEvent.type === "session_info_changed")
352
+ state = { ...state, sessionName: wireEvent.name };
128
353
  if (wireEvent.type === "message_start") {
129
- if (wireEvent.message.role === "assistant")
354
+ if (wireEvent.message.role === "assistant") {
130
355
  streamingAssistant = structuredClone(wireEvent.message);
356
+ mirroredCurrentAssistantUsage = false;
357
+ }
131
358
  local.agent.state.messages.push(structuredClone(wireEvent.message));
132
359
  }
133
360
  if (wireEvent.type === "message_end") {
134
- if (wireEvent.message.role === "assistant")
361
+ if (wireEvent.message.role === "assistant") {
135
362
  streamingAssistant = structuredClone(wireEvent.message);
363
+ const usage = wireEvent.message.usage;
364
+ if (usage && !mirroredCurrentAssistantUsage) {
365
+ mirroredCurrentAssistantUsage = true;
366
+ const latestPromptTokens = usage.input + usage.cacheRead + usage.cacheWrite;
367
+ const contextWindow = state.model?.contextWindow ?? 0;
368
+ const contextTokens = usage.input + usage.cacheRead + usage.cacheWrite;
369
+ state = {
370
+ ...state,
371
+ contextUsage: contextWindow > 0
372
+ ? { tokens: contextTokens, contextWindow, percent: (contextTokens / contextWindow) * 100 }
373
+ : undefined,
374
+ usageTotals: {
375
+ ...state.usageTotals,
376
+ input: state.usageTotals.input + usage.input,
377
+ output: state.usageTotals.output + usage.output,
378
+ cacheRead: state.usageTotals.cacheRead + usage.cacheRead,
379
+ cacheWrite: state.usageTotals.cacheWrite + usage.cacheWrite,
380
+ cost: state.usageTotals.cost + (usage.cost?.total ?? 0),
381
+ latestCacheHitRate: latestPromptTokens > 0 ? (usage.cacheRead / latestPromptTokens) * 100 : undefined,
382
+ },
383
+ };
384
+ }
385
+ }
136
386
  const messages = local.agent.state.messages;
137
387
  const previous = messages.at(-1);
138
388
  if (previous?.role === wireEvent.message.role)
139
389
  messages[messages.length - 1] = structuredClone(wireEvent.message);
140
390
  }
391
+ if (wireEvent.type === "compaction_end" && wireEvent.accepted && !wireEvent.aborted) {
392
+ try {
393
+ local.sessionManager.reloadFromDisk?.();
394
+ if (sessionManager !== local.sessionManager)
395
+ sessionManager.reloadFromDisk?.();
396
+ local.agent.state.messages = sessionManager.buildSessionContext().messages;
397
+ }
398
+ catch {
399
+ // Non-fatal if session file is transiently locked or unavailable
400
+ }
401
+ }
141
402
  const event = hydrateMessageUpdate(wireEvent, streamingAssistant);
142
403
  for (const listener of listeners)
143
404
  listener(event);
144
405
  });
406
+ const refresh = async () => {
407
+ const nextState = await client.getState();
408
+ state = { ...stateFromRpc(nextState) };
409
+ nextQueuedInputOrder = Math.max(0, ...nextState.ordered.map((item) => item.enqueueOrder));
410
+ let messages;
411
+ settingsManager = SettingsManager.create(nextState.cwd, agentDir, {
412
+ projectTrusted: nextState.projectTrusted,
413
+ });
414
+ if (nextState.sessionFile) {
415
+ // Keep the caller-owned manager identity when refreshing the same session.
416
+ // Navigation and host-side mutations must update every existing mirror, not
417
+ // leave the interactive runtime holding the pre-refresh snapshot.
418
+ if (sessionManager.getSessionFile() === nextState.sessionFile) {
419
+ sessionManager.reloadFromDisk?.();
420
+ }
421
+ else {
422
+ // SessionManager.open retains the explicit path even when the host has
423
+ // deferred creating the file for a setup-only session.
424
+ sessionManager = SessionManager.open(nextState.sessionFile, undefined, nextState.cwd);
425
+ }
426
+ if (nextState.entries?.length && !sessionManager.getEntries().length) {
427
+ for (const entry of nextState.entries)
428
+ sessionManager.appendEntry(entry);
429
+ }
430
+ messages = sessionManager.buildSessionContext().messages;
431
+ }
432
+ else {
433
+ messages = await client.getMessages();
434
+ }
435
+ local.agent.state.messages.splice(0, local.agent.state.messages.length, ...structuredClone(messages));
436
+ streamingAssistant = undefined;
437
+ };
145
438
  const session = new Proxy(local, {
146
439
  get(target, property, receiver) {
147
440
  if (property === "prompt")
@@ -149,25 +442,74 @@ function createRemoteSessionProxy(local, client, initialState, onWarning) {
149
442
  ...(options?.images ? { images: options.images } : {}),
150
443
  ...(options?.streamingBehavior ? { streamingBehavior: options.streamingBehavior } : {}),
151
444
  ...(options?.thinkingLevel ? { thinkingLevel: options.thinkingLevel } : {}),
445
+ ...(options?.sessionTitlePrompt !== undefined
446
+ ? { sessionTitlePrompt: options.sessionTitlePrompt }
447
+ : {}),
448
+ ...(options?.expandPromptTemplates !== undefined
449
+ ? { expandPromptTemplates: options.expandPromptTemplates }
450
+ : {}),
152
451
  ...(options?.promptDisposition ? { promptDisposition: options.promptDisposition } : {}),
153
452
  ...(options?.preflightResult ? { preflightResult: options.preflightResult } : {}),
154
453
  });
155
454
  if (property === "abort")
156
455
  return () => client.abort();
456
+ if (property === "abortCompaction")
457
+ return () => void client.abortCompaction();
157
458
  if (property === "steer")
158
- return (message, images) => client.steer(message, images);
459
+ return (message, images, recovery) => {
460
+ const enqueueOrder = recovery?.enqueueOrder ?? Math.max(0, ...state.ordered.map((item) => item.enqueueOrder)) + 1;
461
+ state = {
462
+ ...state,
463
+ steering: [...state.steering, message],
464
+ ordered: [...state.ordered, { text: message, mode: "steer", enqueueOrder }],
465
+ pendingMessageCount: state.pendingMessageCount + 1,
466
+ };
467
+ return client.steer(message, images, { ...recovery, enqueueOrder });
468
+ };
159
469
  if (property === "followUp")
160
- return (message, images) => client.followUp(message, images);
470
+ return (message, images, recovery) => {
471
+ const enqueueOrder = recovery?.enqueueOrder ?? Math.max(0, ...state.ordered.map((item) => item.enqueueOrder)) + 1;
472
+ state = {
473
+ ...state,
474
+ followUp: [...state.followUp, message],
475
+ ordered: [...state.ordered, { text: message, mode: "followUp", enqueueOrder }],
476
+ pendingMessageCount: state.pendingMessageCount + 1,
477
+ };
478
+ return client.followUp(message, images, { ...recovery, enqueueOrder });
479
+ };
161
480
  if (property === "waitForIdle")
162
481
  return () => client.waitForIdle();
163
482
  if (property === "getLastAssistantText")
164
483
  return () => target.getLastAssistantText();
165
- if (property === "setModel")
484
+ if (property === "setModel" || property === "setSessionModel")
166
485
  return async (model) => {
167
- await client.setModel(model.provider, model.id);
486
+ const next = await client.setModel(model.provider, model.id);
487
+ return { systemPromptName: next.systemPromptName, model: next };
488
+ };
489
+ if (property === "setSessionThinkingLevel")
490
+ return (level) => void client
491
+ .setThinkingLevel(level, { scope: "turn" })
492
+ .catch(reportActionFailure("setSessionThinkingLevel"));
493
+ if (property === "setSessionFastMode")
494
+ return (enabled) => void client.setFastMode(enabled).catch(reportActionFailure("setSessionFastMode"));
495
+ if (property === "abortRetry")
496
+ return () => void client.abortRetry().catch(reportActionFailure("abortRetry"));
497
+ if (property === "setAutoRetryEnabled")
498
+ return (enabled) => void client.setAutoRetry(enabled).catch(reportActionFailure("setAutoRetryEnabled"));
499
+ if (property === "reserveQueuedInputOrder")
500
+ return () => ++nextQueuedInputOrder;
501
+ if (property === "setFavoriteModels")
502
+ return (models) => {
503
+ state = { ...state, favoriteModels: [...models] };
504
+ void client.setFavoriteModels(models).catch(reportActionFailure("setFavoriteModels"));
505
+ };
506
+ if (property === "setScopedModels")
507
+ return (models) => {
508
+ state = { ...state, scopedModels: [...models] };
509
+ void client.setScopedModels(models).catch(reportActionFailure("setScopedModels"));
168
510
  };
169
511
  if (property === "cycleModel")
170
- return () => client.cycleModel();
512
+ return (direction) => client.cycleModel(direction);
171
513
  if (property === "setThinkingLevel")
172
514
  return (level) => void client.setThinkingLevel(level).catch(reportActionFailure("setThinkingLevel"));
173
515
  if (property === "cycleThinkingLevel")
@@ -183,15 +525,81 @@ function createRemoteSessionProxy(local, client, initialState, onWarning) {
183
525
  if (property === "setAutoCompactionEnabled")
184
526
  return (enabled) => void client.setAutoCompaction(enabled).catch(reportActionFailure("setAutoCompaction"));
185
527
  if (property === "executeBash")
186
- return (command) => client.bash(command);
528
+ return async (command, onChunk, options) => {
529
+ if (options?.operations && typeof options.operations.exec === "function") {
530
+ const abortController = new AbortController();
531
+ localBashAbortController = abortController;
532
+ localBashRunning = true;
533
+ updateBashState();
534
+ const sessionAtStart = state.sessionId;
535
+ const prefix = settingsManager.getShellCommandPrefix();
536
+ const resolvedCommand = prefix ? `${prefix}\n${command}` : command;
537
+ try {
538
+ const result = await executeBashWithOperations(resolvedCommand, state.cwd, options.operations, { onChunk, signal: abortController.signal });
539
+ if (state.sessionId === sessionAtStart) {
540
+ await client.recordBashResult(command, result, options.excludeFromContext);
541
+ }
542
+ return result;
543
+ }
544
+ finally {
545
+ localBashAbortController = undefined;
546
+ localBashRunning = false;
547
+ updateBashState();
548
+ }
549
+ }
550
+ const executionId = `bash-${executionNamespace}-${++nextBashExecutionId}`;
551
+ const execution = {
552
+ chunk: onChunk,
553
+ promises: new Set(),
554
+ error: undefined,
555
+ hasError: false,
556
+ };
557
+ bashExecutions.set(executionId, execution);
558
+ try {
559
+ const result = await client.bash(command, {
560
+ excludeFromContext: options?.excludeFromContext,
561
+ executionId,
562
+ operations: options?.operations,
563
+ });
564
+ const callbacksSettled = await waitForBashCallbacks(execution.promises, false);
565
+ if (!callbacksSettled) {
566
+ if (result.fullOutputPath)
567
+ await cleanupRemoteBashOutput(result.fullOutputPath);
568
+ throw new Error("Bash output callback did not settle within 5000ms");
569
+ }
570
+ if (execution.hasError) {
571
+ if (result.fullOutputPath)
572
+ await cleanupRemoteBashOutput(result.fullOutputPath);
573
+ throw execution.error;
574
+ }
575
+ return result;
576
+ }
577
+ finally {
578
+ bashExecutions.delete(executionId);
579
+ }
580
+ };
187
581
  if (property === "abortBash")
188
- return () => void client.abortBash().catch(reportActionFailure("abortBash"));
582
+ return () => {
583
+ if (localBashAbortController)
584
+ localBashAbortController.abort();
585
+ else
586
+ void client.abortBash().catch(reportActionFailure("abortBash"));
587
+ };
588
+ if (property === "getContextUsage")
589
+ return () => state.contextUsage;
189
590
  if (property === "getSessionStats")
190
591
  return () => client.getSessionStats();
191
592
  if (property === "exportToHtml")
192
- return (outputPath) => client.exportHtml(outputPath).then((result) => result.path);
593
+ return (outputPath, options) => client.exportHtml(outputPath, options?.themeName).then((result) => result.path);
193
594
  if (property === "setSessionName")
194
- return (name) => void client.setSessionName(name).catch(reportActionFailure("setSessionName"));
595
+ return (name) => client.setSessionName(name).catch(reportActionFailure("setSessionName"));
596
+ if (property === "navigateTree")
597
+ return async (targetId, options) => {
598
+ const result = await client.navigateTree(targetId, options);
599
+ if (!result.cancelled)
600
+ await refresh();
601
+ return result;
602
+ };
195
603
  if (property === "getUserMessagesForForking")
196
604
  return () => client.getForkMessages();
197
605
  if (property === "subscribe")
@@ -205,6 +613,45 @@ function createRemoteSessionProxy(local, client, initialState, onWarning) {
205
613
  };
206
614
  if (property === "isStreaming")
207
615
  return state.isStreaming;
616
+ if (property === "isIdle")
617
+ return !state.isStreaming;
618
+ if (property === "isCompacting")
619
+ return state.isCompacting;
620
+ if (property === "pendingMessageCount")
621
+ return state.pendingMessageCount;
622
+ if (property === "getSteeringMessages")
623
+ return () => state.steering;
624
+ if (property === "getFollowUpMessages")
625
+ return () => state.followUp;
626
+ if (property === "clearQueue")
627
+ return (options) => {
628
+ const result = {
629
+ steering: [...state.steering],
630
+ followUp: [...state.followUp],
631
+ ordered: [...state.ordered],
632
+ };
633
+ Object.defineProperty(result, "ordered", { value: result.ordered, enumerable: false });
634
+ void client.clearQueue(options).catch(reportActionFailure("clearQueue"));
635
+ return result;
636
+ };
637
+ if (property === "abortBranchSummary")
638
+ return () => void client.abortBranchSummary().catch(reportActionFailure("abortBranchSummary"));
639
+ if (property === "recordBashResult")
640
+ return (command, result, options) => void client
641
+ .recordBashResult(command, result, options?.excludeFromContext)
642
+ .catch(reportActionFailure("recordBashResult"));
643
+ if (property === "set_label")
644
+ return undefined;
645
+ if (property === "retryAttempt")
646
+ return state.retryAttempt;
647
+ if (property === "isBashRunning")
648
+ return state.isBashRunning;
649
+ if (property === "reload")
650
+ return (_options) => client.reload();
651
+ if (property === "checkReloadVeto")
652
+ return () => client.checkReloadVeto();
653
+ if (property === "exportToJsonl")
654
+ return (outputPath) => client.exportJsonl(outputPath).then((result) => result.path);
208
655
  // The footer and other renderers read session.state.*; surface the
209
656
  // host-authoritative fields there too, not only via the direct getters.
210
657
  if (property === "state") {
@@ -214,14 +661,35 @@ function createRemoteSessionProxy(local, client, initialState, onWarning) {
214
661
  model: state.model ?? localState.model,
215
662
  thinkingLevel: state.thinkingLevel,
216
663
  isStreaming: state.isStreaming,
664
+ isCompacting: state.isCompacting,
217
665
  };
218
666
  }
219
667
  if (property === "sessionFile")
220
668
  return state.sessionFile;
221
669
  if (property === "sessionId")
222
670
  return state.sessionId;
671
+ if (property === "sessionName")
672
+ return state.sessionName;
673
+ if (property === "serviceTier")
674
+ return state.serviceTier;
675
+ if (property === "steeringMode")
676
+ return state.steeringMode;
677
+ if (property === "followUpMode")
678
+ return state.followUpMode;
679
+ if (property === "autoCompactionEnabled")
680
+ return state.autoCompactionEnabled;
681
+ if (property === "isFastModeActive")
682
+ return () => state.fastMode;
683
+ if (property === "sessionManager")
684
+ return remoteSessionManager;
685
+ if (property === "settingsManager")
686
+ return settingsManager;
223
687
  if (property === "messages")
224
688
  return target.messages;
689
+ if (property === "favoriteModels")
690
+ return state.favoriteModels;
691
+ if (property === "scopedModels")
692
+ return state.scopedModels;
225
693
  if (property === "model")
226
694
  return state.model ?? target.model;
227
695
  if (property === "thinkingLevel")
@@ -229,7 +697,44 @@ function createRemoteSessionProxy(local, client, initialState, onWarning) {
229
697
  return Reflect.get(target, property, receiver);
230
698
  },
231
699
  });
232
- return session;
700
+ return {
701
+ session,
702
+ setHostUiHandler: (callback) => {
703
+ hostUiHandler = callback;
704
+ if (callback)
705
+ for (const request of pendingUiRequests.splice(0))
706
+ void Promise.resolve(callback(request)).then((response) => response && client.sendExtensionUIResponse(response));
707
+ },
708
+ refresh,
709
+ abortLocalBash: () => localBashAbortController?.abort(),
710
+ createReplacedSessionContext: () => {
711
+ const context = local.createReplacedSessionContext();
712
+ Object.defineProperty(context, "cwd", { value: state.cwd });
713
+ Object.defineProperty(context, "sessionManager", { value: remoteSessionManager });
714
+ context.sendMessage = (message, options) => client.sendCustomMessage(message, {
715
+ triggerTurn: options?.triggerTurn,
716
+ deliverAs: options?.deliverAs,
717
+ });
718
+ context.sendUserMessage = (content, options) => {
719
+ if (typeof content === "string")
720
+ return client.prompt(content, {
721
+ streamingBehavior: options?.deliverAs,
722
+ expandPromptTemplates: options?.expandPromptTemplates,
723
+ });
724
+ const text = content
725
+ .filter((part) => part.type === "text")
726
+ .map((part) => part.text)
727
+ .join("\n");
728
+ const images = content.filter((part) => part.type === "image");
729
+ return client.prompt(text, {
730
+ images,
731
+ streamingBehavior: options?.deliverAs,
732
+ expandPromptTemplates: options?.expandPromptTemplates,
733
+ });
734
+ };
735
+ return context;
736
+ },
737
+ };
233
738
  }
234
739
  function hydrateMessageUpdate(event, streamingAssistant) {
235
740
  if (event.type !== "message_update" || !streamingAssistant)