@code-yeongyu/senpi 2026.8.28 → 2026.8.29

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 (128) hide show
  1. package/CHANGELOG.md +74 -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 +5 -0
  10. package/dist/core/agent-session.d.ts.map +1 -1
  11. package/dist/core/agent-session.js +1 -0
  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/credential-accounts.d.ts.map +1 -1
  18. package/dist/core/credential-accounts.js +22 -12
  19. package/dist/core/credential-accounts.js.map +1 -1
  20. package/dist/core/credential-pool/classify.d.ts +6 -1
  21. package/dist/core/credential-pool/classify.d.ts.map +1 -1
  22. package/dist/core/credential-pool/classify.js +22 -5
  23. package/dist/core/credential-pool/classify.js.map +1 -1
  24. package/dist/core/credential-pool/failover.d.ts +8 -0
  25. package/dist/core/credential-pool/failover.d.ts.map +1 -1
  26. package/dist/core/credential-pool/failover.js +24 -6
  27. package/dist/core/credential-pool/failover.js.map +1 -1
  28. package/dist/core/credential-pool/rotation-stream.d.ts +13 -2
  29. package/dist/core/credential-pool/rotation-stream.d.ts.map +1 -1
  30. package/dist/core/credential-pool/rotation-stream.js +97 -10
  31. package/dist/core/credential-pool/rotation-stream.js.map +1 -1
  32. package/dist/core/extensions/builtin/goal/index.d.ts.map +1 -1
  33. package/dist/core/extensions/builtin/goal/index.js +7 -0
  34. package/dist/core/extensions/builtin/goal/index.js.map +1 -1
  35. package/dist/core/extensions/builtin/service-tier.d.ts.map +1 -1
  36. package/dist/core/extensions/builtin/service-tier.js +13 -0
  37. package/dist/core/extensions/builtin/service-tier.js.map +1 -1
  38. package/dist/core/extensions/builtin/terminal/extension.d.ts.map +1 -1
  39. package/dist/core/extensions/builtin/terminal/extension.js +4 -2
  40. package/dist/core/extensions/builtin/terminal/extension.js.map +1 -1
  41. package/dist/core/model-config-schema.d.ts +4 -4
  42. package/dist/core/model-runtime.d.ts +2 -0
  43. package/dist/core/model-runtime.d.ts.map +1 -1
  44. package/dist/core/model-runtime.js +74 -37
  45. package/dist/core/model-runtime.js.map +1 -1
  46. package/dist/core/retry-fallback/chains.d.ts +23 -1
  47. package/dist/core/retry-fallback/chains.d.ts.map +1 -1
  48. package/dist/core/retry-fallback/chains.js +59 -3
  49. package/dist/core/retry-fallback/chains.js.map +1 -1
  50. package/dist/core/retry-fallback/controller.d.ts.map +1 -1
  51. package/dist/core/retry-fallback/controller.js +9 -2
  52. package/dist/core/retry-fallback/controller.js.map +1 -1
  53. package/dist/core/retry-fallback/settings.d.ts.map +1 -1
  54. package/dist/core/retry-fallback/settings.js +9 -0
  55. package/dist/core/retry-fallback/settings.js.map +1 -1
  56. package/dist/core/retry-fallback/validate.js +1 -1
  57. package/dist/core/retry-fallback/validate.js.map +1 -1
  58. package/dist/core/sdk.js +1 -1
  59. package/dist/core/sdk.js.map +1 -1
  60. package/dist/core/session-manager.d.ts +9 -0
  61. package/dist/core/session-manager.d.ts.map +1 -1
  62. package/dist/core/session-manager.js +31 -0
  63. package/dist/core/session-manager.js.map +1 -1
  64. package/dist/main.d.ts +7 -0
  65. package/dist/main.d.ts.map +1 -1
  66. package/dist/main.js +10 -1
  67. package/dist/main.js.map +1 -1
  68. package/dist/modes/interactive/components/favorite-models-selector.d.ts.map +1 -1
  69. package/dist/modes/interactive/components/favorite-models-selector.js +2 -2
  70. package/dist/modes/interactive/components/favorite-models-selector.js.map +1 -1
  71. package/dist/modes/interactive/interactive-host-runtime.d.ts.map +1 -1
  72. package/dist/modes/interactive/interactive-host-runtime.js +323 -33
  73. package/dist/modes/interactive/interactive-host-runtime.js.map +1 -1
  74. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  75. package/dist/modes/interactive/interactive-mode.js +276 -237
  76. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  77. package/dist/modes/rpc/connection-handler.d.ts.map +1 -1
  78. package/dist/modes/rpc/connection-handler.js +133 -24
  79. package/dist/modes/rpc/connection-handler.js.map +1 -1
  80. package/dist/modes/rpc/multi-session-host.js +14 -4
  81. package/dist/modes/rpc/multi-session-host.js.map +1 -1
  82. package/dist/modes/rpc/rpc-client.d.ts +82 -13
  83. package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
  84. package/dist/modes/rpc/rpc-client.js +85 -28
  85. package/dist/modes/rpc/rpc-client.js.map +1 -1
  86. package/dist/modes/rpc/rpc-mode.d.ts +3 -1
  87. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  88. package/dist/modes/rpc/rpc-mode.js +3 -1
  89. package/dist/modes/rpc/rpc-mode.js.map +1 -1
  90. package/dist/modes/rpc/rpc-types.d.ts +162 -2
  91. package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
  92. package/dist/modes/rpc/rpc-types.js.map +1 -1
  93. package/dist/modes/rpc/session-command-router.d.ts +22 -0
  94. package/dist/modes/rpc/session-command-router.d.ts.map +1 -1
  95. package/dist/modes/rpc/session-command-router.js +147 -16
  96. package/dist/modes/rpc/session-command-router.js.map +1 -1
  97. package/dist/modes/rpc/session-event-writer.d.ts +2 -0
  98. package/dist/modes/rpc/session-event-writer.d.ts.map +1 -1
  99. package/dist/modes/rpc/session-event-writer.js +4 -0
  100. package/dist/modes/rpc/session-event-writer.js.map +1 -1
  101. package/dist/modes/rpc/session-registry.d.ts +7 -0
  102. package/dist/modes/rpc/session-registry.d.ts.map +1 -1
  103. package/dist/modes/rpc/session-registry.js +9 -0
  104. package/dist/modes/rpc/session-registry.js.map +1 -1
  105. package/docs/rpc.md +37 -0
  106. package/node_modules/@code-yeongyu/senpi-codemode/CHANGELOG.md +24 -0
  107. package/node_modules/@code-yeongyu/senpi-codemode/package.json +4 -4
  108. package/node_modules/@earendil-works/pi-agent-core/dist/agent-loop.d.ts.map +1 -1
  109. package/node_modules/@earendil-works/pi-agent-core/dist/agent-loop.js +6 -3
  110. package/node_modules/@earendil-works/pi-agent-core/dist/agent-loop.js.map +1 -1
  111. package/node_modules/@earendil-works/pi-agent-core/package.json +3 -3
  112. package/node_modules/@earendil-works/pi-ai/dist/providers/data/.manifest.json +1 -1
  113. package/node_modules/@earendil-works/pi-ai/dist/providers/data/amazon-bedrock.json +1 -1
  114. package/node_modules/@earendil-works/pi-ai/dist/providers/data/baseten.json +1 -1
  115. package/node_modules/@earendil-works/pi-ai/dist/providers/data/cloudflare-ai-gateway.json +1 -1
  116. package/node_modules/@earendil-works/pi-ai/dist/providers/data/cloudflare-workers-ai.json +1 -1
  117. package/node_modules/@earendil-works/pi-ai/dist/providers/data/fireworks.json +1 -1
  118. package/node_modules/@earendil-works/pi-ai/dist/providers/data/huggingface.json +1 -1
  119. package/node_modules/@earendil-works/pi-ai/dist/providers/data/nvidia.json +1 -1
  120. package/node_modules/@earendil-works/pi-ai/dist/providers/data/opencode-go.json +1 -1
  121. package/node_modules/@earendil-works/pi-ai/dist/providers/data/opencode.json +1 -1
  122. package/node_modules/@earendil-works/pi-ai/dist/providers/data/openrouter.json +1 -1
  123. package/node_modules/@earendil-works/pi-ai/dist/providers/data/vercel-ai-gateway.json +1 -1
  124. package/node_modules/@earendil-works/pi-ai/package.json +2 -2
  125. package/node_modules/@earendil-works/pi-pty/package.json +1 -1
  126. package/node_modules/@earendil-works/pi-telemetry/package.json +1 -1
  127. package/node_modules/@earendil-works/pi-tui/package.json +1 -1
  128. package/package.json +7 -7
@@ -1 +1 @@
1
- {"version":3,"file":"interactive-host-runtime.d.ts","sourceRoot":"","sources":["../../../src/modes/interactive/interactive-host-runtime.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAC;AACnE,OAAO,KAAK,EAAE,YAAY,EAAgD,MAAM,6BAA6B,CAAC;AAC9G,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,qCAAqC,CAAC;AAE/E,OAAO,EAAE,KAAK,WAAW,EAAc,MAAM,uBAAuB,CAAC;AACrE,OAAO,EAAa,KAAK,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAEtE,eAAO,MAAM,iCAAiC,qEAAqE,CAAC;AAEpH,MAAM,WAAW,sBAAsB;IACtC,QAAQ,CAAC,IAAI,EAAE,2BAA2B,GAAG,gCAAgC,CAAC;IAC9E,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;CACxB;AAED;;;;;;GAMG;AACH,MAAM,MAAM,kBAAkB,GAAG,IAAI,CACpC,YAAY,EACZ,oBAAoB,GAAG,4BAA4B,GAAG,iBAAiB,GAAG,2BAA2B,CACrG,GAAG;IACH,kBAAkB,IAAI,aAAa,GAAG,SAAS,GAAG,OAAO,CAAC,aAAa,GAAG,SAAS,CAAC,CAAC;IACrF,0BAA0B,IAAI,aAAa,EAAE,GAAG,OAAO,CAAC,aAAa,EAAE,CAAC,CAAC;IACzE,eAAe,IACZ,UAAU,CAAC,YAAY,CAAC,iBAAiB,CAAC,CAAC,GAC3C,OAAO,CAAC,UAAU,CAAC,YAAY,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAAC;IACxD,yBAAyB,IACtB,UAAU,CAAC,YAAY,CAAC,2BAA2B,CAAC,CAAC,GACrD,OAAO,CAAC,UAAU,CAAC,YAAY,CAAC,2BAA2B,CAAC,CAAC,CAAC,CAAC;CAClE,CAAC;AAEF,MAAM,WAAW,6BAA6B;IAC7C,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC,OAAO,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,KAAK,OAAO,CAAC,WAAW,GAAG,SAAS,CAAC,CAAC;IAC3G,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,sBAAsB,KAAK,IAAI,CAAC;CACtD;AAED;;;;;GAKG;AACH,wBAAsB,4BAA4B,CACjD,YAAY,EAAE,mBAAmB,EACjC,OAAO,EAAE,6BAA6B,GACpC,OAAO,CAAC,mBAAmB,CAAC,CA0B9B;AAoOD,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,cAAc,GAAG,KAAK,IAAI,OAAO,CAAC,cAAc,EAAE;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,CAEhH"}
1
+ {"version":3,"file":"interactive-host-runtime.d.ts","sourceRoot":"","sources":["../../../src/modes/interactive/interactive-host-runtime.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAC;AACnE,OAAO,KAAK,EAAE,YAAY,EAAgD,MAAM,6BAA6B,CAAC;AAC9G,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,qCAAqC,CAAC;AAO/E,OAAO,EAAE,KAAK,WAAW,EAAc,MAAM,uBAAuB,CAAC;AACrE,OAAO,EAAa,KAAK,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAEtE,eAAO,MAAM,iCAAiC,qEAAqE,CAAC;AAEpH,MAAM,WAAW,sBAAsB;IACtC,QAAQ,CAAC,IAAI,EAAE,2BAA2B,GAAG,gCAAgC,CAAC;IAC9E,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;CACxB;AAED;;;;;;GAMG;AACH,MAAM,MAAM,kBAAkB,GAAG,IAAI,CACpC,YAAY,EACZ,oBAAoB,GAAG,4BAA4B,GAAG,iBAAiB,GAAG,2BAA2B,CACrG,GAAG;IACH,kBAAkB,IAAI,aAAa,GAAG,SAAS,GAAG,OAAO,CAAC,aAAa,GAAG,SAAS,CAAC,CAAC;IACrF,0BAA0B,IAAI,aAAa,EAAE,GAAG,OAAO,CAAC,aAAa,EAAE,CAAC,CAAC;IACzE,eAAe,IACZ,UAAU,CAAC,YAAY,CAAC,iBAAiB,CAAC,CAAC,GAC3C,OAAO,CAAC,UAAU,CAAC,YAAY,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAAC;IACxD,yBAAyB,IACtB,UAAU,CAAC,YAAY,CAAC,2BAA2B,CAAC,CAAC,GACrD,OAAO,CAAC,UAAU,CAAC,YAAY,CAAC,2BAA2B,CAAC,CAAC,CAAC,CAAC;CAClE,CAAC;AAEF,MAAM,WAAW,6BAA6B;IAC7C,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC,OAAO,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,KAAK,OAAO,CAAC,WAAW,GAAG,SAAS,CAAC,CAAC;IAC3G,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,sBAAsB,KAAK,IAAI,CAAC;CACtD;AAED;;;;;GAKG;AACH,wBAAsB,4BAA4B,CACjD,YAAY,EAAE,mBAAmB,EACjC,OAAO,EAAE,6BAA6B,GACpC,OAAO,CAAC,mBAAmB,CAAC,CAgC9B;AAqiBD,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,cAAc,GAAG,KAAK,IAAI,OAAO,CAAC,cAAc,EAAE;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,CAEhH"}
@@ -1,3 +1,6 @@
1
+ import { executeBashWithOperations } from "../../core/bash-executor.js";
2
+ import { SessionManager } from "../../core/session-manager.js";
3
+ import { SettingsManager } from "../../core/settings-manager.js";
1
4
  import { ensureHost } from "../rpc/host-ensure.js";
2
5
  import { RpcClient } from "../rpc/rpc-client.js";
3
6
  export const INTERACTIVE_HOST_FALLBACK_WARNING = "Warning: shared interactive host unavailable; continuing locally";
@@ -23,7 +26,7 @@ export async function createInteractiveHostRuntime(localRuntime, options) {
23
26
  modelId: localRuntime.session.model?.id,
24
27
  thinkingLevel: localRuntime.session.thinkingLevel,
25
28
  });
26
- const remoteSession = createRemoteSessionProxy(localRuntime.session, client, opened.state, options.onWarning);
29
+ const remoteSession = createRemoteSessionProxy(localRuntime.session, localRuntime.services.agentDir, client, opened.state, options.onWarning);
27
30
  return new RemoteInteractiveRuntime(localRuntime, remoteSession, client);
28
31
  }
29
32
  catch (cause) {
@@ -38,23 +41,23 @@ export async function createInteractiveHostRuntime(localRuntime, options) {
38
41
  }
39
42
  class RemoteInteractiveRuntime {
40
43
  #local;
41
- #session;
44
+ #remoteSession;
42
45
  #client;
43
46
  #rebindSession;
44
47
  #beforeSessionInvalidate;
45
- constructor(local, session, client) {
48
+ constructor(local, remoteSession, client) {
46
49
  this.#local = local;
47
- this.#session = session;
50
+ this.#remoteSession = remoteSession;
48
51
  this.#client = client;
49
52
  }
50
53
  get session() {
51
- return this.#session;
54
+ return this.#remoteSession.session;
52
55
  }
53
56
  get services() {
54
57
  return this.#local.services;
55
58
  }
56
59
  get cwd() {
57
- return this.#local.cwd;
60
+ return this.#remoteSession.session.sessionManager.getCwd();
58
61
  }
59
62
  get diagnostics() {
60
63
  return this.#local.diagnostics;
@@ -76,32 +79,66 @@ class RemoteInteractiveRuntime {
76
79
  await this.#client.stop();
77
80
  await this.#local.dispose();
78
81
  }
79
- async newSession() {
80
- this.#beforeSessionInvalidate?.();
81
- const result = await this.#client.newSession();
82
- if (!result.cancelled)
82
+ 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);
93
+ await this.#remoteSession.refresh();
94
+ }
83
95
  await this.#rebindSession?.();
96
+ if (options?.withSession)
97
+ await options.withSession(this.#remoteSession.createReplacedSessionContext());
98
+ }
84
99
  return result;
85
100
  }
86
- async switchSession(sessionPath) {
87
- this.#beforeSessionInvalidate?.();
88
- const result = await this.#client.switchSession(sessionPath);
89
- if (!result.cancelled)
101
+ 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();
90
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
+ }
91
114
  return result;
92
115
  }
93
- async fork(entryId) {
94
- this.#beforeSessionInvalidate?.();
95
- const result = await this.#client.fork(entryId);
96
- if (!result.cancelled)
97
- await this.#rebindSession?.();
116
+ 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
+ }
98
125
  return { cancelled: result.cancelled, selectedText: result.text };
99
126
  }
100
- async importFromJsonl() {
101
- throw new Error("Session import is not available while connected to the shared host");
127
+ 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;
135
+ }
136
+ async #refreshAndRebind() {
137
+ await this.#remoteSession.refresh();
138
+ await this.#rebindSession?.();
102
139
  }
103
140
  }
104
- function createRemoteSessionProxy(local, client, initialState, onWarning) {
141
+ function createRemoteSessionProxy(local, agentDir, client, initialState, onWarning) {
105
142
  // Fire-and-forget setters keep the sync AgentSession signature, but their RPC
106
143
  // failures must not vanish: the matching *_changed wire event confirms success,
107
144
  // and a rejection here is the only signal of failure.
@@ -112,36 +149,145 @@ function createRemoteSessionProxy(local, client, initialState, onWarning) {
112
149
  cause: error,
113
150
  });
114
151
  };
115
- let state = initialState;
152
+ let state = { ...initialState };
153
+ let bashChunk;
154
+ let localBashAbortController;
155
+ let localBashRunning = false;
156
+ let hostBashRunning = initialState.isBashRunning;
157
+ let sessionManager = local.sessionManager;
158
+ let settingsManager = SettingsManager.create(initialState.cwd, agentDir, {
159
+ projectTrusted: initialState.projectTrusted,
160
+ });
161
+ const updateBashState = () => {
162
+ state = { ...state, isBashRunning: localBashRunning || hostBashRunning };
163
+ };
164
+ const remoteSessionManager = new Proxy({}, {
165
+ get(_target, property, _receiver) {
166
+ if (property === "appendLabelChange") {
167
+ return (entryId, label) => void client.setLabel(entryId, label);
168
+ }
169
+ if (property === "getSessionName")
170
+ return () => state.sessionName;
171
+ if (property === "getUsageTotals")
172
+ return () => state.usageTotals;
173
+ const value = Reflect.get(sessionManager, property, sessionManager);
174
+ return typeof value === "function" ? value.bind(sessionManager) : value;
175
+ },
176
+ });
116
177
  let streamingAssistant;
178
+ let mirroredCurrentAssistantUsage = false;
117
179
  const listeners = new Set();
118
180
  client.onEvent((wireEvent) => {
119
181
  if (wireEvent.type === "agent_settled")
120
- state = { ...state, isStreaming: false };
182
+ state = { ...state, isStreaming: false, retryAttempt: 0 };
183
+ if (wireEvent.type === "bash_start") {
184
+ hostBashRunning = true;
185
+ updateBashState();
186
+ }
187
+ if (wireEvent.type === "bash_end") {
188
+ hostBashRunning = false;
189
+ updateBashState();
190
+ }
191
+ if (wireEvent.type === "bash_execution_update")
192
+ bashChunk?.(wireEvent.delta);
121
193
  if (wireEvent.type === "agent_start")
122
194
  state = { ...state, isStreaming: true };
195
+ if (wireEvent.type === "compaction_start")
196
+ state = { ...state, isCompacting: true };
197
+ if (wireEvent.type === "compaction_end")
198
+ state = { ...state, isCompacting: false };
199
+ if (wireEvent.type === "auto_retry_start")
200
+ state = { ...state, retryAttempt: wireEvent.attempt };
201
+ if (wireEvent.type === "auto_retry_end")
202
+ state = { ...state, retryAttempt: 0 };
203
+ if (wireEvent.type === "queue_update") {
204
+ state = {
205
+ ...state,
206
+ steering: [...wireEvent.steering],
207
+ followUp: [...wireEvent.followUp],
208
+ ordered: [...wireEvent.ordered],
209
+ pendingMessageCount: wireEvent.steering.length + wireEvent.followUp.length,
210
+ };
211
+ }
123
212
  if (wireEvent.type === "model_changed") {
124
213
  state = { ...state, model: wireEvent.model, thinkingLevel: wireEvent.thinkingLevel };
125
214
  }
126
215
  if (wireEvent.type === "thinking_level_changed")
127
216
  state = { ...state, thinkingLevel: wireEvent.level };
217
+ if (wireEvent.type === "service_tier_changed") {
218
+ state = { ...state, serviceTier: wireEvent.tier, fastMode: wireEvent.fastMode };
219
+ }
220
+ if (wireEvent.type === "session_info_changed")
221
+ state = { ...state, sessionName: wireEvent.name };
128
222
  if (wireEvent.type === "message_start") {
129
- if (wireEvent.message.role === "assistant")
223
+ if (wireEvent.message.role === "assistant") {
130
224
  streamingAssistant = structuredClone(wireEvent.message);
225
+ mirroredCurrentAssistantUsage = false;
226
+ }
131
227
  local.agent.state.messages.push(structuredClone(wireEvent.message));
132
228
  }
133
229
  if (wireEvent.type === "message_end") {
134
- if (wireEvent.message.role === "assistant")
230
+ if (wireEvent.message.role === "assistant") {
135
231
  streamingAssistant = structuredClone(wireEvent.message);
232
+ const usage = wireEvent.message.usage;
233
+ if (usage && !mirroredCurrentAssistantUsage) {
234
+ mirroredCurrentAssistantUsage = true;
235
+ const latestPromptTokens = usage.input + usage.cacheRead + usage.cacheWrite;
236
+ state = {
237
+ ...state,
238
+ usageTotals: {
239
+ ...state.usageTotals,
240
+ input: state.usageTotals.input + usage.input,
241
+ output: state.usageTotals.output + usage.output,
242
+ cacheRead: state.usageTotals.cacheRead + usage.cacheRead,
243
+ cacheWrite: state.usageTotals.cacheWrite + usage.cacheWrite,
244
+ cost: state.usageTotals.cost + (usage.cost?.total ?? 0),
245
+ latestCacheHitRate: latestPromptTokens > 0 ? (usage.cacheRead / latestPromptTokens) * 100 : undefined,
246
+ },
247
+ };
248
+ }
249
+ }
136
250
  const messages = local.agent.state.messages;
137
251
  const previous = messages.at(-1);
138
252
  if (previous?.role === wireEvent.message.role)
139
253
  messages[messages.length - 1] = structuredClone(wireEvent.message);
140
254
  }
255
+ if (wireEvent.type === "compaction_end" && wireEvent.accepted && !wireEvent.aborted) {
256
+ try {
257
+ sessionManager.reloadFromDisk?.();
258
+ local.agent.state.messages = sessionManager.buildSessionContext().messages;
259
+ }
260
+ catch {
261
+ // Non-fatal if session file is transiently locked or unavailable
262
+ }
263
+ }
141
264
  const event = hydrateMessageUpdate(wireEvent, streamingAssistant);
142
265
  for (const listener of listeners)
143
266
  listener(event);
144
267
  });
268
+ const refresh = async () => {
269
+ const nextState = await client.getState();
270
+ state = { ...stateFromRpc(nextState) };
271
+ let messages;
272
+ settingsManager = SettingsManager.create(nextState.cwd, agentDir, {
273
+ projectTrusted: nextState.projectTrusted,
274
+ });
275
+ 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);
282
+ }
283
+ messages = sessionManager.buildSessionContext().messages;
284
+ }
285
+ else {
286
+ messages = await client.getMessages();
287
+ }
288
+ local.agent.state.messages.splice(0, local.agent.state.messages.length, ...structuredClone(messages));
289
+ streamingAssistant = undefined;
290
+ };
145
291
  const session = new Proxy(local, {
146
292
  get(target, property, receiver) {
147
293
  if (property === "prompt")
@@ -154,17 +300,38 @@ function createRemoteSessionProxy(local, client, initialState, onWarning) {
154
300
  });
155
301
  if (property === "abort")
156
302
  return () => client.abort();
303
+ if (property === "abortCompaction")
304
+ return () => void client.abortCompaction();
157
305
  if (property === "steer")
158
- return (message, images) => client.steer(message, images);
306
+ return (message, images, recovery) => {
307
+ const enqueueOrder = recovery?.enqueueOrder ?? Math.max(0, ...state.ordered.map((item) => item.enqueueOrder)) + 1;
308
+ state = {
309
+ ...state,
310
+ steering: [...state.steering, message],
311
+ ordered: [...state.ordered, { text: message, mode: "steer", enqueueOrder }],
312
+ pendingMessageCount: state.pendingMessageCount + 1,
313
+ };
314
+ return client.steer(message, images, { ...recovery, enqueueOrder });
315
+ };
159
316
  if (property === "followUp")
160
- return (message, images) => client.followUp(message, images);
317
+ return (message, images, recovery) => {
318
+ const enqueueOrder = recovery?.enqueueOrder ?? Math.max(0, ...state.ordered.map((item) => item.enqueueOrder)) + 1;
319
+ state = {
320
+ ...state,
321
+ followUp: [...state.followUp, message],
322
+ ordered: [...state.ordered, { text: message, mode: "followUp", enqueueOrder }],
323
+ pendingMessageCount: state.pendingMessageCount + 1,
324
+ };
325
+ return client.followUp(message, images, { ...recovery, enqueueOrder });
326
+ };
161
327
  if (property === "waitForIdle")
162
328
  return () => client.waitForIdle();
163
329
  if (property === "getLastAssistantText")
164
330
  return () => target.getLastAssistantText();
165
331
  if (property === "setModel")
166
332
  return async (model) => {
167
- await client.setModel(model.provider, model.id);
333
+ const next = await client.setModel(model.provider, model.id);
334
+ return { systemPromptName: next.systemPromptName, model: next };
168
335
  };
169
336
  if (property === "cycleModel")
170
337
  return () => client.cycleModel();
@@ -183,15 +350,59 @@ function createRemoteSessionProxy(local, client, initialState, onWarning) {
183
350
  if (property === "setAutoCompactionEnabled")
184
351
  return (enabled) => void client.setAutoCompaction(enabled).catch(reportActionFailure("setAutoCompaction"));
185
352
  if (property === "executeBash")
186
- return (command) => client.bash(command);
353
+ return async (command, onChunk, options) => {
354
+ if (options?.operations && typeof options.operations.exec === "function") {
355
+ const abortController = new AbortController();
356
+ localBashAbortController = abortController;
357
+ localBashRunning = true;
358
+ updateBashState();
359
+ const sessionAtStart = state.sessionId;
360
+ const prefix = settingsManager.getShellCommandPrefix();
361
+ const resolvedCommand = prefix ? `${prefix}\n${command}` : command;
362
+ try {
363
+ const result = await executeBashWithOperations(resolvedCommand, state.cwd, options.operations, { onChunk, signal: abortController.signal });
364
+ if (state.sessionId === sessionAtStart) {
365
+ await client.recordBashResult(command, result, options.excludeFromContext);
366
+ }
367
+ return result;
368
+ }
369
+ finally {
370
+ localBashAbortController = undefined;
371
+ localBashRunning = false;
372
+ updateBashState();
373
+ }
374
+ }
375
+ bashChunk = onChunk;
376
+ try {
377
+ return await client.bash(command, {
378
+ excludeFromContext: options?.excludeFromContext,
379
+ operations: options?.operations,
380
+ });
381
+ }
382
+ finally {
383
+ bashChunk = undefined;
384
+ }
385
+ };
187
386
  if (property === "abortBash")
188
- return () => void client.abortBash().catch(reportActionFailure("abortBash"));
387
+ return () => {
388
+ if (localBashAbortController)
389
+ localBashAbortController.abort();
390
+ else
391
+ void client.abortBash().catch(reportActionFailure("abortBash"));
392
+ };
189
393
  if (property === "getSessionStats")
190
394
  return () => client.getSessionStats();
191
395
  if (property === "exportToHtml")
192
396
  return (outputPath) => client.exportHtml(outputPath).then((result) => result.path);
193
397
  if (property === "setSessionName")
194
- return (name) => void client.setSessionName(name).catch(reportActionFailure("setSessionName"));
398
+ return (name) => client.setSessionName(name).catch(reportActionFailure("setSessionName"));
399
+ if (property === "navigateTree")
400
+ return async (targetId, options) => {
401
+ const result = await client.navigateTree(targetId, options);
402
+ if (!result.cancelled)
403
+ await refresh();
404
+ return result;
405
+ };
195
406
  if (property === "getUserMessagesForForking")
196
407
  return () => client.getForkMessages();
197
408
  if (property === "subscribe")
@@ -205,6 +416,45 @@ function createRemoteSessionProxy(local, client, initialState, onWarning) {
205
416
  };
206
417
  if (property === "isStreaming")
207
418
  return state.isStreaming;
419
+ if (property === "isCompacting")
420
+ return state.isCompacting;
421
+ if (property === "isFastModeActive")
422
+ return () => state.fastMode;
423
+ if (property === "pendingMessageCount")
424
+ return state.pendingMessageCount;
425
+ if (property === "getSteeringMessages")
426
+ return () => state.steering;
427
+ if (property === "getFollowUpMessages")
428
+ return () => state.followUp;
429
+ if (property === "clearQueue")
430
+ return (options) => {
431
+ const result = {
432
+ steering: [...state.steering],
433
+ followUp: [...state.followUp],
434
+ ordered: [...state.ordered],
435
+ };
436
+ Object.defineProperty(result, "ordered", { value: result.ordered, enumerable: false });
437
+ void client.clearQueue(options).catch(reportActionFailure("clearQueue"));
438
+ return result;
439
+ };
440
+ if (property === "abortBranchSummary")
441
+ return () => void client.abortBranchSummary().catch(reportActionFailure("abortBranchSummary"));
442
+ if (property === "recordBashResult")
443
+ return (command, result, options) => void client
444
+ .recordBashResult(command, result, options?.excludeFromContext)
445
+ .catch(reportActionFailure("recordBashResult"));
446
+ if (property === "set_label")
447
+ return undefined;
448
+ if (property === "retryAttempt")
449
+ return state.retryAttempt;
450
+ if (property === "isBashRunning")
451
+ return state.isBashRunning;
452
+ if (property === "reload")
453
+ return (_options) => client.reload();
454
+ if (property === "checkReloadVeto")
455
+ return () => client.checkReloadVeto();
456
+ if (property === "exportToJsonl")
457
+ return (outputPath) => client.exportJsonl(outputPath).then((result) => result.path);
208
458
  // The footer and other renderers read session.state.*; surface the
209
459
  // host-authoritative fields there too, not only via the direct getters.
210
460
  if (property === "state") {
@@ -214,12 +464,21 @@ function createRemoteSessionProxy(local, client, initialState, onWarning) {
214
464
  model: state.model ?? localState.model,
215
465
  thinkingLevel: state.thinkingLevel,
216
466
  isStreaming: state.isStreaming,
467
+ isCompacting: state.isCompacting,
217
468
  };
218
469
  }
219
470
  if (property === "sessionFile")
220
471
  return state.sessionFile;
221
472
  if (property === "sessionId")
222
473
  return state.sessionId;
474
+ if (property === "sessionName")
475
+ return state.sessionName;
476
+ if (property === "serviceTier")
477
+ return state.serviceTier;
478
+ if (property === "sessionManager")
479
+ return remoteSessionManager;
480
+ if (property === "settingsManager")
481
+ return settingsManager;
223
482
  if (property === "messages")
224
483
  return target.messages;
225
484
  if (property === "model")
@@ -229,7 +488,38 @@ function createRemoteSessionProxy(local, client, initialState, onWarning) {
229
488
  return Reflect.get(target, property, receiver);
230
489
  },
231
490
  });
232
- return session;
491
+ return {
492
+ session,
493
+ refresh,
494
+ abortLocalBash: () => localBashAbortController?.abort(),
495
+ createReplacedSessionContext: () => {
496
+ const context = local.createReplacedSessionContext();
497
+ Object.defineProperty(context, "cwd", { value: state.cwd });
498
+ Object.defineProperty(context, "sessionManager", { value: remoteSessionManager });
499
+ context.sendMessage = (message, options) => client.sendCustomMessage(message, {
500
+ triggerTurn: options?.triggerTurn,
501
+ deliverAs: options?.deliverAs,
502
+ });
503
+ context.sendUserMessage = (content, options) => {
504
+ if (typeof content === "string")
505
+ return client.prompt(content, {
506
+ streamingBehavior: options?.deliverAs,
507
+ expandPromptTemplates: options?.expandPromptTemplates,
508
+ });
509
+ const text = content
510
+ .filter((part) => part.type === "text")
511
+ .map((part) => part.text)
512
+ .join("\n");
513
+ const images = content.filter((part) => part.type === "image");
514
+ return client.prompt(text, {
515
+ images,
516
+ streamingBehavior: options?.deliverAs,
517
+ expandPromptTemplates: options?.expandPromptTemplates,
518
+ });
519
+ };
520
+ return context;
521
+ },
522
+ };
233
523
  }
234
524
  function hydrateMessageUpdate(event, streamingAssistant) {
235
525
  if (event.type !== "message_update" || !streamingAssistant)