@code-yeongyu/senpi 2026.8.29 → 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 (200) hide show
  1. package/CHANGELOG.md +66 -0
  2. package/dist/core/agent-session.d.ts +45 -1
  3. package/dist/core/agent-session.d.ts.map +1 -1
  4. package/dist/core/agent-session.js +312 -25
  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/credential-accounts.d.ts.map +1 -1
  10. package/dist/core/credential-accounts.js +23 -9
  11. package/dist/core/credential-accounts.js.map +1 -1
  12. package/dist/core/credential-pool/env-slots.d.ts.map +1 -1
  13. package/dist/core/credential-pool/env-slots.js +2 -0
  14. package/dist/core/credential-pool/env-slots.js.map +1 -1
  15. package/dist/core/extensions/builtin/compaction/deterministic-fallback.d.ts +8 -2
  16. package/dist/core/extensions/builtin/compaction/deterministic-fallback.d.ts.map +1 -1
  17. package/dist/core/extensions/builtin/compaction/deterministic-fallback.js +160 -47
  18. package/dist/core/extensions/builtin/compaction/deterministic-fallback.js.map +1 -1
  19. package/dist/core/extensions/builtin/compaction/retained-message-safety.d.ts.map +1 -1
  20. package/dist/core/extensions/builtin/compaction/retained-message-safety.js +31 -8
  21. package/dist/core/extensions/builtin/compaction/retained-message-safety.js.map +1 -1
  22. package/dist/core/extensions/builtin/cursor-cli-oauth/stream.d.ts.map +1 -1
  23. package/dist/core/extensions/builtin/cursor-cli-oauth/stream.js +8 -28
  24. package/dist/core/extensions/builtin/cursor-cli-oauth/stream.js.map +1 -1
  25. package/dist/core/extensions/builtin/permission-system/external-dir.d.ts.map +1 -1
  26. package/dist/core/extensions/builtin/permission-system/external-dir.js +15 -5
  27. package/dist/core/extensions/builtin/permission-system/external-dir.js.map +1 -1
  28. package/dist/core/extensions/builtin/permission-system/parsers.d.ts.map +1 -1
  29. package/dist/core/extensions/builtin/permission-system/parsers.js +16 -1
  30. package/dist/core/extensions/builtin/permission-system/parsers.js.map +1 -1
  31. package/dist/core/extensions/builtin/terminal/manager.d.ts +5 -0
  32. package/dist/core/extensions/builtin/terminal/manager.d.ts.map +1 -1
  33. package/dist/core/extensions/builtin/terminal/manager.js +31 -3
  34. package/dist/core/extensions/builtin/terminal/manager.js.map +1 -1
  35. package/dist/core/extensions/builtin/terminal/monitor-permission.d.ts +8 -0
  36. package/dist/core/extensions/builtin/terminal/monitor-permission.d.ts.map +1 -0
  37. package/dist/core/extensions/builtin/terminal/monitor-permission.js +16 -0
  38. package/dist/core/extensions/builtin/terminal/monitor-permission.js.map +1 -0
  39. package/dist/core/extensions/builtin/terminal/monitor-registry.d.ts +13 -0
  40. package/dist/core/extensions/builtin/terminal/monitor-registry.d.ts.map +1 -1
  41. package/dist/core/extensions/builtin/terminal/monitor-registry.js +403 -3
  42. package/dist/core/extensions/builtin/terminal/monitor-registry.js.map +1 -1
  43. package/dist/core/extensions/builtin/terminal/session-bundle.d.ts.map +1 -1
  44. package/dist/core/extensions/builtin/terminal/session-bundle.js +1 -0
  45. package/dist/core/extensions/builtin/terminal/session-bundle.js.map +1 -1
  46. package/dist/core/extensions/builtin/terminal/tools/kill-bash.d.ts.map +1 -1
  47. package/dist/core/extensions/builtin/terminal/tools/kill-bash.js +5 -2
  48. package/dist/core/extensions/builtin/terminal/tools/kill-bash.js.map +1 -1
  49. package/dist/core/extensions/builtin/terminal/tools/monitor.d.ts +4 -0
  50. package/dist/core/extensions/builtin/terminal/tools/monitor.d.ts.map +1 -1
  51. package/dist/core/extensions/builtin/terminal/tools/monitor.js +40 -2
  52. package/dist/core/extensions/builtin/terminal/tools/monitor.js.map +1 -1
  53. package/dist/core/model-resolver.js +2 -2
  54. package/dist/core/model-resolver.js.map +1 -1
  55. package/dist/core/session-manager.d.ts.map +1 -1
  56. package/dist/core/session-manager.js +10 -1
  57. package/dist/core/session-manager.js.map +1 -1
  58. package/dist/core/settings-manager.d.ts +7 -0
  59. package/dist/core/settings-manager.d.ts.map +1 -1
  60. package/dist/core/settings-manager.js +6 -0
  61. package/dist/core/settings-manager.js.map +1 -1
  62. package/dist/core/tools/bash.d.ts.map +1 -1
  63. package/dist/core/tools/bash.js +97 -15
  64. package/dist/core/tools/bash.js.map +1 -1
  65. package/dist/core/tools/index.d.ts +1 -0
  66. package/dist/core/tools/index.d.ts.map +1 -1
  67. package/dist/core/tools/index.js +8 -0
  68. package/dist/core/tools/index.js.map +1 -1
  69. package/dist/core/tools/output-accumulator.d.ts +1 -1
  70. package/dist/core/tools/output-accumulator.d.ts.map +1 -1
  71. package/dist/core/tools/output-accumulator.js +76 -34
  72. package/dist/core/tools/output-accumulator.js.map +1 -1
  73. package/dist/modes/interactive/components/footer.d.ts +7 -0
  74. package/dist/modes/interactive/components/footer.d.ts.map +1 -1
  75. package/dist/modes/interactive/components/footer.js +34 -8
  76. package/dist/modes/interactive/components/footer.js.map +1 -1
  77. package/dist/modes/interactive/grok/chrome.d.ts +2 -0
  78. package/dist/modes/interactive/grok/chrome.d.ts.map +1 -1
  79. package/dist/modes/interactive/grok/chrome.js.map +1 -1
  80. package/dist/modes/interactive/interactive-host-runtime.d.ts +51 -1
  81. package/dist/modes/interactive/interactive-host-runtime.d.ts.map +1 -1
  82. package/dist/modes/interactive/interactive-host-runtime.js +235 -20
  83. package/dist/modes/interactive/interactive-host-runtime.js.map +1 -1
  84. package/dist/modes/interactive/interactive-mode.d.ts +7 -0
  85. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  86. package/dist/modes/interactive/interactive-mode.js +114 -2
  87. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  88. package/dist/modes/rpc/connection-handler.d.ts +8 -1
  89. package/dist/modes/rpc/connection-handler.d.ts.map +1 -1
  90. package/dist/modes/rpc/connection-handler.js +145 -49
  91. package/dist/modes/rpc/connection-handler.js.map +1 -1
  92. package/dist/modes/rpc/host-ensure.d.ts +2 -0
  93. package/dist/modes/rpc/host-ensure.d.ts.map +1 -1
  94. package/dist/modes/rpc/host-ensure.js +15 -2
  95. package/dist/modes/rpc/host-ensure.js.map +1 -1
  96. package/dist/modes/rpc/host-lifecycle.d.ts.map +1 -1
  97. package/dist/modes/rpc/host-lifecycle.js +31 -12
  98. package/dist/modes/rpc/host-lifecycle.js.map +1 -1
  99. package/dist/modes/rpc/host-watchdog.d.ts +2 -0
  100. package/dist/modes/rpc/host-watchdog.d.ts.map +1 -1
  101. package/dist/modes/rpc/host-watchdog.js +23 -12
  102. package/dist/modes/rpc/host-watchdog.js.map +1 -1
  103. package/dist/modes/rpc/multi-session-host.js +6 -4
  104. package/dist/modes/rpc/multi-session-host.js.map +1 -1
  105. package/dist/modes/rpc/rpc-client.d.ts +10 -4
  106. package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
  107. package/dist/modes/rpc/rpc-client.js +45 -10
  108. package/dist/modes/rpc/rpc-client.js.map +1 -1
  109. package/dist/modes/rpc/rpc-input-validation.d.ts +1 -0
  110. package/dist/modes/rpc/rpc-input-validation.d.ts.map +1 -1
  111. package/dist/modes/rpc/rpc-input-validation.js +66 -0
  112. package/dist/modes/rpc/rpc-input-validation.js.map +1 -1
  113. package/dist/modes/rpc/rpc-types.d.ts +79 -1
  114. package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
  115. package/dist/modes/rpc/rpc-types.js.map +1 -1
  116. package/dist/modes/rpc/session-binding.d.ts +1 -0
  117. package/dist/modes/rpc/session-binding.d.ts.map +1 -1
  118. package/dist/modes/rpc/session-binding.js +21 -1
  119. package/dist/modes/rpc/session-binding.js.map +1 -1
  120. package/dist/modes/rpc/session-command-router.d.ts.map +1 -1
  121. package/dist/modes/rpc/session-command-router.js +1 -0
  122. package/dist/modes/rpc/session-command-router.js.map +1 -1
  123. package/dist/modes/rpc/session-event-writer.d.ts +2 -0
  124. package/dist/modes/rpc/session-event-writer.d.ts.map +1 -1
  125. package/dist/modes/rpc/session-event-writer.js +54 -7
  126. package/dist/modes/rpc/session-event-writer.js.map +1 -1
  127. package/dist/modes/rpc/session-extension-ui-requests.d.ts +1 -0
  128. package/dist/modes/rpc/session-extension-ui-requests.d.ts.map +1 -1
  129. package/dist/modes/rpc/session-extension-ui-requests.js +5 -0
  130. package/dist/modes/rpc/session-extension-ui-requests.js.map +1 -1
  131. package/dist/modes/rpc/session-registry.d.ts +10 -2
  132. package/dist/modes/rpc/session-registry.d.ts.map +1 -1
  133. package/dist/modes/rpc/session-registry.js +58 -4
  134. package/dist/modes/rpc/session-registry.js.map +1 -1
  135. package/dist/modes/rpc/socket-event-fanout.d.ts +22 -0
  136. package/dist/modes/rpc/socket-event-fanout.d.ts.map +1 -0
  137. package/dist/modes/rpc/socket-event-fanout.js +84 -0
  138. package/dist/modes/rpc/socket-event-fanout.js.map +1 -0
  139. package/docs/rpc.md +3 -1
  140. package/docs/settings.md +34 -0
  141. package/docs/windows.md +2 -0
  142. package/node_modules/@code-yeongyu/senpi-codemode/CHANGELOG.md +38 -0
  143. package/node_modules/@code-yeongyu/senpi-codemode/package.json +4 -4
  144. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/worker-indirect-eval.js +139 -90
  145. package/node_modules/@code-yeongyu/senpi-codemode/src/prompt/eval-prompt.ts +4 -4
  146. package/node_modules/@earendil-works/pi-agent-core/dist/harness/env/nodejs.d.ts.map +1 -1
  147. package/node_modules/@earendil-works/pi-agent-core/dist/harness/env/nodejs.js +40 -20
  148. package/node_modules/@earendil-works/pi-agent-core/dist/harness/env/nodejs.js.map +1 -1
  149. package/node_modules/@earendil-works/pi-agent-core/dist/harness/types.d.ts +3 -3
  150. package/node_modules/@earendil-works/pi-agent-core/dist/harness/types.d.ts.map +1 -1
  151. package/node_modules/@earendil-works/pi-agent-core/dist/harness/types.js.map +1 -1
  152. package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/shell-output.d.ts +1 -1
  153. package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/shell-output.d.ts.map +1 -1
  154. package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/shell-output.js +28 -9
  155. package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/shell-output.js.map +1 -1
  156. package/node_modules/@earendil-works/pi-agent-core/package.json +3 -3
  157. package/node_modules/@earendil-works/pi-ai/dist/api/anthropic-messages.d.ts.map +1 -1
  158. package/node_modules/@earendil-works/pi-ai/dist/api/anthropic-messages.js +58 -31
  159. package/node_modules/@earendil-works/pi-ai/dist/api/anthropic-messages.js.map +1 -1
  160. package/node_modules/@earendil-works/pi-ai/dist/api/cursor-agent/measure.d.ts +20 -0
  161. package/node_modules/@earendil-works/pi-ai/dist/api/cursor-agent/measure.d.ts.map +1 -0
  162. package/node_modules/@earendil-works/pi-ai/dist/api/cursor-agent/measure.js +443 -0
  163. package/node_modules/@earendil-works/pi-ai/dist/api/cursor-agent/measure.js.map +1 -0
  164. package/node_modules/@earendil-works/pi-ai/dist/api/cursor-agent.d.ts +2 -0
  165. package/node_modules/@earendil-works/pi-ai/dist/api/cursor-agent.d.ts.map +1 -1
  166. package/node_modules/@earendil-works/pi-ai/dist/api/cursor-agent.js +2 -0
  167. package/node_modules/@earendil-works/pi-ai/dist/api/cursor-agent.js.map +1 -1
  168. package/node_modules/@earendil-works/pi-ai/dist/api/openai-completions.js +1 -1
  169. package/node_modules/@earendil-works/pi-ai/dist/api/openai-completions.js.map +1 -1
  170. package/node_modules/@earendil-works/pi-ai/dist/index.d.ts +1 -0
  171. package/node_modules/@earendil-works/pi-ai/dist/index.d.ts.map +1 -1
  172. package/node_modules/@earendil-works/pi-ai/dist/index.js +1 -0
  173. package/node_modules/@earendil-works/pi-ai/dist/index.js.map +1 -1
  174. package/node_modules/@earendil-works/pi-ai/dist/providers/data/.manifest.json +1 -1
  175. package/node_modules/@earendil-works/pi-ai/dist/providers/data/cloudflare-ai-gateway.json +1 -1
  176. package/node_modules/@earendil-works/pi-ai/dist/providers/data/cloudflare-workers-ai.json +1 -1
  177. package/node_modules/@earendil-works/pi-ai/dist/providers/data/fireworks.json +1 -1
  178. package/node_modules/@earendil-works/pi-ai/dist/providers/data/opencode-go.json +1 -1
  179. package/node_modules/@earendil-works/pi-ai/dist/providers/data/opencode.json +1 -1
  180. package/node_modules/@earendil-works/pi-ai/dist/providers/data/openrouter.json +1 -1
  181. package/node_modules/@earendil-works/pi-ai/dist/providers/data/together.json +1 -1
  182. package/node_modules/@earendil-works/pi-ai/dist/providers/data/zai-coding-cn.json +1 -1
  183. package/node_modules/@earendil-works/pi-ai/dist/providers/data/zai.json +1 -1
  184. package/node_modules/@earendil-works/pi-ai/package.json +2 -2
  185. package/node_modules/@earendil-works/pi-pty/package.json +1 -1
  186. package/node_modules/@earendil-works/pi-telemetry/package.json +1 -1
  187. package/node_modules/@earendil-works/pi-tui/dist/components/editor.d.ts +7 -0
  188. package/node_modules/@earendil-works/pi-tui/dist/components/editor.d.ts.map +1 -1
  189. package/node_modules/@earendil-works/pi-tui/dist/components/editor.js +5 -0
  190. package/node_modules/@earendil-works/pi-tui/dist/components/editor.js.map +1 -1
  191. package/node_modules/@earendil-works/pi-tui/dist/mux.d.ts +1 -1
  192. package/node_modules/@earendil-works/pi-tui/dist/mux.d.ts.map +1 -1
  193. package/node_modules/@earendil-works/pi-tui/dist/mux.js +2 -2
  194. package/node_modules/@earendil-works/pi-tui/dist/mux.js.map +1 -1
  195. package/node_modules/@earendil-works/pi-tui/dist/terminal.d.ts +1 -0
  196. package/node_modules/@earendil-works/pi-tui/dist/terminal.d.ts.map +1 -1
  197. package/node_modules/@earendil-works/pi-tui/dist/terminal.js +19 -0
  198. package/node_modules/@earendil-works/pi-tui/dist/terminal.js.map +1 -1
  199. package/node_modules/@earendil-works/pi-tui/package.json +1 -1
  200. package/package.json +7 -7
@@ -1,3 +1,5 @@
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";
@@ -27,6 +29,14 @@ export async function createInteractiveHostRuntime(localRuntime, options) {
27
29
  thinkingLevel: localRuntime.session.thinkingLevel,
28
30
  });
29
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();
30
40
  return new RemoteInteractiveRuntime(localRuntime, remoteSession, client);
31
41
  }
32
42
  catch (cause) {
@@ -39,7 +49,7 @@ export async function createInteractiveHostRuntime(localRuntime, options) {
39
49
  return localRuntime;
40
50
  }
41
51
  }
42
- class RemoteInteractiveRuntime {
52
+ export class RemoteInteractiveRuntime {
43
53
  #local;
44
54
  #remoteSession;
45
55
  #client;
@@ -74,10 +84,26 @@ class RemoteInteractiveRuntime {
74
84
  setRebindSession(callback) {
75
85
  this.#rebindSession = callback;
76
86
  }
87
+ setHostUiHandler(callback) {
88
+ this.#remoteSession.setHostUiHandler(callback);
89
+ }
77
90
  async dispose() {
78
- await this.#client.closeSession();
79
- await this.#client.stop();
80
- 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];
81
107
  }
82
108
  async newSession(options) {
83
109
  const result = await this.#client.newSession(options?.parentSession);
@@ -99,7 +125,7 @@ class RemoteInteractiveRuntime {
99
125
  return result;
100
126
  }
101
127
  async switchSession(sessionPath, options) {
102
- const result = await this.#client.switchSession(sessionPath, options);
128
+ const result = await this.#client.switchSession(sessionPath, options?.cwdOverride === undefined ? undefined : { cwdOverride: options.cwdOverride });
103
129
  if (!result.cancelled) {
104
130
  this.#beforeSessionInvalidate?.();
105
131
  this.#remoteSession.abortLocalBash();
@@ -150,10 +176,65 @@ function createRemoteSessionProxy(local, agentDir, client, initialState, onWarni
150
176
  });
151
177
  };
152
178
  let state = { ...initialState };
153
- let bashChunk;
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 = [];
154
234
  let localBashAbortController;
155
235
  let localBashRunning = false;
156
236
  let hostBashRunning = initialState.isBashRunning;
237
+ let nextQueuedInputOrder = Math.max(0, ...initialState.ordered.map((item) => item.enqueueOrder));
157
238
  let sessionManager = local.sessionManager;
158
239
  let settingsManager = SettingsManager.create(initialState.cwd, agentDir, {
159
240
  projectTrusted: initialState.projectTrusted,
@@ -166,6 +247,8 @@ function createRemoteSessionProxy(local, agentDir, client, initialState, onWarni
166
247
  if (property === "appendLabelChange") {
167
248
  return (entryId, label) => void client.setLabel(entryId, label);
168
249
  }
250
+ if (property === "getCwd")
251
+ return () => state.cwd;
169
252
  if (property === "getSessionName")
170
253
  return () => state.sessionName;
171
254
  if (property === "getUsageTotals")
@@ -178,6 +261,14 @@ function createRemoteSessionProxy(local, agentDir, client, initialState, onWarni
178
261
  let mirroredCurrentAssistantUsage = false;
179
262
  const listeners = new Set();
180
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
+ }
181
272
  if (wireEvent.type === "agent_settled")
182
273
  state = { ...state, isStreaming: false, retryAttempt: 0 };
183
274
  if (wireEvent.type === "bash_start") {
@@ -188,8 +279,30 @@ function createRemoteSessionProxy(local, agentDir, client, initialState, onWarni
188
279
  hostBashRunning = false;
189
280
  updateBashState();
190
281
  }
191
- if (wireEvent.type === "bash_execution_update")
192
- bashChunk?.(wireEvent.delta);
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
+ }
193
306
  if (wireEvent.type === "agent_start")
194
307
  state = { ...state, isStreaming: true };
195
308
  if (wireEvent.type === "compaction_start")
@@ -201,6 +314,7 @@ function createRemoteSessionProxy(local, agentDir, client, initialState, onWarni
201
314
  if (wireEvent.type === "auto_retry_end")
202
315
  state = { ...state, retryAttempt: 0 };
203
316
  if (wireEvent.type === "queue_update") {
317
+ nextQueuedInputOrder = Math.max(nextQueuedInputOrder, ...wireEvent.ordered.map((item) => item.enqueueOrder));
204
318
  state = {
205
319
  ...state,
206
320
  steering: [...wireEvent.steering],
@@ -217,6 +331,23 @@ function createRemoteSessionProxy(local, agentDir, client, initialState, onWarni
217
331
  if (wireEvent.type === "service_tier_changed") {
218
332
  state = { ...state, serviceTier: wireEvent.tier, fastMode: wireEvent.fastMode };
219
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
+ }
220
351
  if (wireEvent.type === "session_info_changed")
221
352
  state = { ...state, sessionName: wireEvent.name };
222
353
  if (wireEvent.type === "message_start") {
@@ -233,8 +364,13 @@ function createRemoteSessionProxy(local, agentDir, client, initialState, onWarni
233
364
  if (usage && !mirroredCurrentAssistantUsage) {
234
365
  mirroredCurrentAssistantUsage = true;
235
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;
236
369
  state = {
237
370
  ...state,
371
+ contextUsage: contextWindow > 0
372
+ ? { tokens: contextTokens, contextWindow, percent: (contextTokens / contextWindow) * 100 }
373
+ : undefined,
238
374
  usageTotals: {
239
375
  ...state.usageTotals,
240
376
  input: state.usageTotals.input + usage.input,
@@ -254,7 +390,9 @@ function createRemoteSessionProxy(local, agentDir, client, initialState, onWarni
254
390
  }
255
391
  if (wireEvent.type === "compaction_end" && wireEvent.accepted && !wireEvent.aborted) {
256
392
  try {
257
- sessionManager.reloadFromDisk?.();
393
+ local.sessionManager.reloadFromDisk?.();
394
+ if (sessionManager !== local.sessionManager)
395
+ sessionManager.reloadFromDisk?.();
258
396
  local.agent.state.messages = sessionManager.buildSessionContext().messages;
259
397
  }
260
398
  catch {
@@ -268,14 +406,23 @@ function createRemoteSessionProxy(local, agentDir, client, initialState, onWarni
268
406
  const refresh = async () => {
269
407
  const nextState = await client.getState();
270
408
  state = { ...stateFromRpc(nextState) };
409
+ nextQueuedInputOrder = Math.max(0, ...nextState.ordered.map((item) => item.enqueueOrder));
271
410
  let messages;
272
411
  settingsManager = SettingsManager.create(nextState.cwd, agentDir, {
273
412
  projectTrusted: nextState.projectTrusted,
274
413
  });
275
414
  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);
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
+ }
279
426
  if (nextState.entries?.length && !sessionManager.getEntries().length) {
280
427
  for (const entry of nextState.entries)
281
428
  sessionManager.appendEntry(entry);
@@ -295,6 +442,12 @@ function createRemoteSessionProxy(local, agentDir, client, initialState, onWarni
295
442
  ...(options?.images ? { images: options.images } : {}),
296
443
  ...(options?.streamingBehavior ? { streamingBehavior: options.streamingBehavior } : {}),
297
444
  ...(options?.thinkingLevel ? { thinkingLevel: options.thinkingLevel } : {}),
445
+ ...(options?.sessionTitlePrompt !== undefined
446
+ ? { sessionTitlePrompt: options.sessionTitlePrompt }
447
+ : {}),
448
+ ...(options?.expandPromptTemplates !== undefined
449
+ ? { expandPromptTemplates: options.expandPromptTemplates }
450
+ : {}),
298
451
  ...(options?.promptDisposition ? { promptDisposition: options.promptDisposition } : {}),
299
452
  ...(options?.preflightResult ? { preflightResult: options.preflightResult } : {}),
300
453
  });
@@ -328,13 +481,35 @@ function createRemoteSessionProxy(local, agentDir, client, initialState, onWarni
328
481
  return () => client.waitForIdle();
329
482
  if (property === "getLastAssistantText")
330
483
  return () => target.getLastAssistantText();
331
- if (property === "setModel")
484
+ if (property === "setModel" || property === "setSessionModel")
332
485
  return async (model) => {
333
486
  const next = await client.setModel(model.provider, model.id);
334
487
  return { systemPromptName: next.systemPromptName, model: next };
335
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"));
510
+ };
336
511
  if (property === "cycleModel")
337
- return () => client.cycleModel();
512
+ return (direction) => client.cycleModel(direction);
338
513
  if (property === "setThinkingLevel")
339
514
  return (level) => void client.setThinkingLevel(level).catch(reportActionFailure("setThinkingLevel"));
340
515
  if (property === "cycleThinkingLevel")
@@ -372,15 +547,35 @@ function createRemoteSessionProxy(local, agentDir, client, initialState, onWarni
372
547
  updateBashState();
373
548
  }
374
549
  }
375
- bashChunk = onChunk;
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);
376
558
  try {
377
- return await client.bash(command, {
559
+ const result = await client.bash(command, {
378
560
  excludeFromContext: options?.excludeFromContext,
561
+ executionId,
379
562
  operations: options?.operations,
380
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;
381
576
  }
382
577
  finally {
383
- bashChunk = undefined;
578
+ bashExecutions.delete(executionId);
384
579
  }
385
580
  };
386
581
  if (property === "abortBash")
@@ -390,10 +585,12 @@ function createRemoteSessionProxy(local, agentDir, client, initialState, onWarni
390
585
  else
391
586
  void client.abortBash().catch(reportActionFailure("abortBash"));
392
587
  };
588
+ if (property === "getContextUsage")
589
+ return () => state.contextUsage;
393
590
  if (property === "getSessionStats")
394
591
  return () => client.getSessionStats();
395
592
  if (property === "exportToHtml")
396
- return (outputPath) => client.exportHtml(outputPath).then((result) => result.path);
593
+ return (outputPath, options) => client.exportHtml(outputPath, options?.themeName).then((result) => result.path);
397
594
  if (property === "setSessionName")
398
595
  return (name) => client.setSessionName(name).catch(reportActionFailure("setSessionName"));
399
596
  if (property === "navigateTree")
@@ -416,10 +613,10 @@ function createRemoteSessionProxy(local, agentDir, client, initialState, onWarni
416
613
  };
417
614
  if (property === "isStreaming")
418
615
  return state.isStreaming;
616
+ if (property === "isIdle")
617
+ return !state.isStreaming;
419
618
  if (property === "isCompacting")
420
619
  return state.isCompacting;
421
- if (property === "isFastModeActive")
422
- return () => state.fastMode;
423
620
  if (property === "pendingMessageCount")
424
621
  return state.pendingMessageCount;
425
622
  if (property === "getSteeringMessages")
@@ -475,12 +672,24 @@ function createRemoteSessionProxy(local, agentDir, client, initialState, onWarni
475
672
  return state.sessionName;
476
673
  if (property === "serviceTier")
477
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;
478
683
  if (property === "sessionManager")
479
684
  return remoteSessionManager;
480
685
  if (property === "settingsManager")
481
686
  return settingsManager;
482
687
  if (property === "messages")
483
688
  return target.messages;
689
+ if (property === "favoriteModels")
690
+ return state.favoriteModels;
691
+ if (property === "scopedModels")
692
+ return state.scopedModels;
484
693
  if (property === "model")
485
694
  return state.model ?? target.model;
486
695
  if (property === "thinkingLevel")
@@ -490,6 +699,12 @@ function createRemoteSessionProxy(local, agentDir, client, initialState, onWarni
490
699
  });
491
700
  return {
492
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
+ },
493
708
  refresh,
494
709
  abortLocalBash: () => localBashAbortController?.abort(),
495
710
  createReplacedSessionContext: () => {