@earendil-works/pi-coding-agent 0.80.2 → 0.80.5

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 (211) hide show
  1. package/CHANGELOG.md +115 -1
  2. package/README.md +3 -2
  3. package/dist/cli/args.d.ts.map +1 -1
  4. package/dist/cli/args.js +2 -2
  5. package/dist/cli/args.js.map +1 -1
  6. package/dist/cli/config-selector.d.ts +4 -2
  7. package/dist/cli/config-selector.d.ts.map +1 -1
  8. package/dist/cli/config-selector.js +1 -1
  9. package/dist/cli/config-selector.js.map +1 -1
  10. package/dist/cli/file-processor.d.ts.map +1 -1
  11. package/dist/cli/file-processor.js +13 -25
  12. package/dist/cli/file-processor.js.map +1 -1
  13. package/dist/core/agent-session.d.ts +22 -5
  14. package/dist/core/agent-session.d.ts.map +1 -1
  15. package/dist/core/agent-session.js +133 -50
  16. package/dist/core/agent-session.js.map +1 -1
  17. package/dist/core/auth-storage.d.ts.map +1 -1
  18. package/dist/core/auth-storage.js +13 -5
  19. package/dist/core/auth-storage.js.map +1 -1
  20. package/dist/core/cache-stats.d.ts +49 -0
  21. package/dist/core/cache-stats.d.ts.map +1 -0
  22. package/dist/core/cache-stats.js +101 -0
  23. package/dist/core/cache-stats.js.map +1 -0
  24. package/dist/core/compaction/compaction.d.ts +1 -2
  25. package/dist/core/compaction/compaction.d.ts.map +1 -1
  26. package/dist/core/compaction/compaction.js +56 -81
  27. package/dist/core/compaction/compaction.js.map +1 -1
  28. package/dist/core/extensions/index.d.ts +1 -1
  29. package/dist/core/extensions/index.d.ts.map +1 -1
  30. package/dist/core/extensions/index.js.map +1 -1
  31. package/dist/core/extensions/loader.d.ts.map +1 -1
  32. package/dist/core/extensions/loader.js +10 -0
  33. package/dist/core/extensions/loader.js.map +1 -1
  34. package/dist/core/extensions/runner.d.ts +5 -3
  35. package/dist/core/extensions/runner.d.ts.map +1 -1
  36. package/dist/core/extensions/runner.js +38 -0
  37. package/dist/core/extensions/runner.js.map +1 -1
  38. package/dist/core/extensions/types.d.ts +39 -5
  39. package/dist/core/extensions/types.d.ts.map +1 -1
  40. package/dist/core/extensions/types.js.map +1 -1
  41. package/dist/core/http-dispatcher.d.ts.map +1 -1
  42. package/dist/core/http-dispatcher.js +28 -1
  43. package/dist/core/http-dispatcher.js.map +1 -1
  44. package/dist/core/index.d.ts +1 -1
  45. package/dist/core/index.d.ts.map +1 -1
  46. package/dist/core/index.js.map +1 -1
  47. package/dist/core/keybindings.d.ts.map +1 -1
  48. package/dist/core/keybindings.js +3 -6
  49. package/dist/core/keybindings.js.map +1 -1
  50. package/dist/core/model-registry.d.ts +3 -0
  51. package/dist/core/model-registry.d.ts.map +1 -1
  52. package/dist/core/model-registry.js +16 -2
  53. package/dist/core/model-registry.js.map +1 -1
  54. package/dist/core/model-resolver.d.ts +10 -0
  55. package/dist/core/model-resolver.d.ts.map +1 -1
  56. package/dist/core/model-resolver.js +15 -18
  57. package/dist/core/model-resolver.js.map +1 -1
  58. package/dist/core/package-manager.d.ts +4 -1
  59. package/dist/core/package-manager.d.ts.map +1 -1
  60. package/dist/core/package-manager.js +68 -26
  61. package/dist/core/package-manager.js.map +1 -1
  62. package/dist/core/provider-attribution.d.ts.map +1 -1
  63. package/dist/core/provider-attribution.js +0 -10
  64. package/dist/core/provider-attribution.js.map +1 -1
  65. package/dist/core/resource-loader.d.ts +2 -2
  66. package/dist/core/resource-loader.d.ts.map +1 -1
  67. package/dist/core/resource-loader.js +8 -7
  68. package/dist/core/resource-loader.js.map +1 -1
  69. package/dist/core/sdk.d.ts +1 -1
  70. package/dist/core/sdk.d.ts.map +1 -1
  71. package/dist/core/sdk.js +8 -1
  72. package/dist/core/sdk.js.map +1 -1
  73. package/dist/core/session-manager.d.ts +21 -2
  74. package/dist/core/session-manager.d.ts.map +1 -1
  75. package/dist/core/session-manager.js +104 -72
  76. package/dist/core/session-manager.js.map +1 -1
  77. package/dist/core/settings-manager.d.ts +10 -0
  78. package/dist/core/settings-manager.d.ts.map +1 -1
  79. package/dist/core/settings-manager.js +27 -0
  80. package/dist/core/settings-manager.js.map +1 -1
  81. package/dist/core/slash-commands.d.ts +1 -0
  82. package/dist/core/slash-commands.d.ts.map +1 -1
  83. package/dist/core/slash-commands.js +3 -3
  84. package/dist/core/slash-commands.js.map +1 -1
  85. package/dist/core/timings.d.ts +4 -2
  86. package/dist/core/timings.d.ts.map +1 -1
  87. package/dist/core/timings.js +24 -14
  88. package/dist/core/timings.js.map +1 -1
  89. package/dist/core/tools/bash.d.ts.map +1 -1
  90. package/dist/core/tools/bash.js +20 -5
  91. package/dist/core/tools/bash.js.map +1 -1
  92. package/dist/core/tools/edit.d.ts.map +1 -1
  93. package/dist/core/tools/edit.js +2 -2
  94. package/dist/core/tools/edit.js.map +1 -1
  95. package/dist/core/tools/read.d.ts.map +1 -1
  96. package/dist/core/tools/read.js +12 -25
  97. package/dist/core/tools/read.js.map +1 -1
  98. package/dist/index.d.ts +3 -2
  99. package/dist/index.d.ts.map +1 -1
  100. package/dist/index.js +2 -1
  101. package/dist/index.js.map +1 -1
  102. package/dist/main.d.ts +2 -2
  103. package/dist/main.d.ts.map +1 -1
  104. package/dist/main.js +17 -4
  105. package/dist/main.js.map +1 -1
  106. package/dist/modes/interactive/components/assistant-message.d.ts +3 -1
  107. package/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
  108. package/dist/modes/interactive/components/assistant-message.js +23 -15
  109. package/dist/modes/interactive/components/assistant-message.js.map +1 -1
  110. package/dist/modes/interactive/components/config-selector.d.ts +34 -3
  111. package/dist/modes/interactive/components/config-selector.d.ts.map +1 -1
  112. package/dist/modes/interactive/components/config-selector.js +280 -25
  113. package/dist/modes/interactive/components/config-selector.js.map +1 -1
  114. package/dist/modes/interactive/components/custom-entry.d.ts +19 -0
  115. package/dist/modes/interactive/components/custom-entry.d.ts.map +1 -0
  116. package/dist/modes/interactive/components/custom-entry.js +52 -0
  117. package/dist/modes/interactive/components/custom-entry.js.map +1 -0
  118. package/dist/modes/interactive/components/extension-editor.d.ts +3 -1
  119. package/dist/modes/interactive/components/extension-editor.d.ts.map +1 -1
  120. package/dist/modes/interactive/components/extension-editor.js +12 -3
  121. package/dist/modes/interactive/components/extension-editor.js.map +1 -1
  122. package/dist/modes/interactive/components/footer.d.ts +4 -0
  123. package/dist/modes/interactive/components/footer.d.ts.map +1 -1
  124. package/dist/modes/interactive/components/footer.js +1 -1
  125. package/dist/modes/interactive/components/footer.js.map +1 -1
  126. package/dist/modes/interactive/components/oauth-selector.d.ts +3 -1
  127. package/dist/modes/interactive/components/oauth-selector.d.ts.map +1 -1
  128. package/dist/modes/interactive/components/oauth-selector.js +17 -6
  129. package/dist/modes/interactive/components/oauth-selector.js.map +1 -1
  130. package/dist/modes/interactive/components/settings-selector.d.ts +4 -0
  131. package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  132. package/dist/modes/interactive/components/settings-selector.js +23 -1
  133. package/dist/modes/interactive/components/settings-selector.js.map +1 -1
  134. package/dist/modes/interactive/components/status-indicator.d.ts +28 -0
  135. package/dist/modes/interactive/components/status-indicator.d.ts.map +1 -0
  136. package/dist/modes/interactive/components/status-indicator.js +60 -0
  137. package/dist/modes/interactive/components/status-indicator.js.map +1 -0
  138. package/dist/modes/interactive/components/user-message.d.ts +6 -2
  139. package/dist/modes/interactive/components/user-message.d.ts.map +1 -1
  140. package/dist/modes/interactive/components/user-message.js +19 -6
  141. package/dist/modes/interactive/components/user-message.js.map +1 -1
  142. package/dist/modes/interactive/interactive-mode.d.ts +21 -10
  143. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  144. package/dist/modes/interactive/interactive-mode.js +417 -196
  145. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  146. package/dist/modes/print-mode.d.ts.map +1 -1
  147. package/dist/modes/print-mode.js +1 -1
  148. package/dist/modes/print-mode.js.map +1 -1
  149. package/dist/modes/rpc/rpc-client.d.ts +21 -6
  150. package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
  151. package/dist/modes/rpc/rpc-client.js +17 -3
  152. package/dist/modes/rpc/rpc-client.js.map +1 -1
  153. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  154. package/dist/modes/rpc/rpc-mode.js +20 -1
  155. package/dist/modes/rpc/rpc-mode.js.map +1 -1
  156. package/dist/modes/rpc/rpc-types.d.ts +26 -0
  157. package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
  158. package/dist/modes/rpc/rpc-types.js.map +1 -1
  159. package/dist/package-manager-cli.d.ts +2 -2
  160. package/dist/package-manager-cli.d.ts.map +1 -1
  161. package/dist/package-manager-cli.js +71 -17
  162. package/dist/package-manager-cli.js.map +1 -1
  163. package/dist/rpc-entry.d.ts +3 -0
  164. package/dist/rpc-entry.d.ts.map +1 -0
  165. package/dist/rpc-entry.js +10 -0
  166. package/dist/rpc-entry.js.map +1 -0
  167. package/dist/utils/clipboard-image.d.ts.map +1 -1
  168. package/dist/utils/clipboard-image.js +1 -1
  169. package/dist/utils/clipboard-image.js.map +1 -1
  170. package/dist/utils/image-convert.d.ts +1 -0
  171. package/dist/utils/image-convert.d.ts.map +1 -1
  172. package/dist/utils/image-convert.js +21 -15
  173. package/dist/utils/image-convert.js.map +1 -1
  174. package/dist/utils/image-process.d.ts +18 -0
  175. package/dist/utils/image-process.d.ts.map +1 -0
  176. package/dist/utils/image-process.js +83 -0
  177. package/dist/utils/image-process.js.map +1 -0
  178. package/dist/utils/mime.d.ts.map +1 -1
  179. package/dist/utils/mime.js +41 -0
  180. package/dist/utils/mime.js.map +1 -1
  181. package/docs/extensions.md +88 -17
  182. package/docs/keybindings.md +1 -1
  183. package/docs/models.md +4 -4
  184. package/docs/packages.md +2 -2
  185. package/docs/providers.md +2 -1
  186. package/docs/rpc.md +71 -3
  187. package/docs/sdk.md +50 -0
  188. package/docs/session-format.md +18 -8
  189. package/docs/settings.md +11 -0
  190. package/docs/usage.md +3 -3
  191. package/examples/extensions/README.md +1 -0
  192. package/examples/extensions/custom-compaction.ts +1 -0
  193. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  194. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  195. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  196. package/examples/extensions/entry-renderer.ts +41 -0
  197. package/examples/extensions/gondolin/package-lock.json +2 -2
  198. package/examples/extensions/gondolin/package.json +1 -1
  199. package/examples/extensions/handoff.ts +1 -1
  200. package/examples/extensions/qna.ts +1 -1
  201. package/examples/extensions/question.ts +1 -0
  202. package/examples/extensions/reload-runtime.ts +2 -2
  203. package/examples/extensions/sandbox/package-lock.json +2 -2
  204. package/examples/extensions/sandbox/package.json +1 -1
  205. package/examples/extensions/summarize.ts +1 -0
  206. package/examples/extensions/with-deps/package-lock.json +2 -2
  207. package/examples/extensions/with-deps/package.json +1 -1
  208. package/examples/sdk/12-full-control.ts +1 -1
  209. package/examples/sdk/README.md +1 -1
  210. package/npm-shrinkwrap.json +12 -12
  211. package/package.json +8 -5
@@ -14,7 +14,7 @@
14
14
  */
15
15
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
16
16
  import { basename, dirname } from "node:path";
17
- import { clampThinkingLevel, cleanupSessionResources, getSupportedThinkingLevels, isContextOverflow, modelsAreEqual, resetApiProviders, streamSimple, } from "@earendil-works/pi-ai/compat";
17
+ import { clampThinkingLevel, cleanupSessionResources, getSupportedThinkingLevels, isContextOverflow, isRetryableAssistantError, modelsAreEqual, resetApiProviders, streamSimple, } from "@earendil-works/pi-ai/compat";
18
18
  import { getThemeByName, theme } from "../modes/interactive/theme/theme.js";
19
19
  import { stripFrontmatter } from "../utils/frontmatter.js";
20
20
  import { resolvePath } from "../utils/paths.js";
@@ -72,6 +72,9 @@ export class AgentSession {
72
72
  // Event subscription state
73
73
  _unsubscribeAgent;
74
74
  _eventListeners = [];
75
+ _isAgentRunActive = false;
76
+ _idleWaitPromise;
77
+ _resolveIdleWait;
75
78
  /** Tracks pending steering messages for UI display. Removed when delivered. */
76
79
  _steeringMessages = [];
77
80
  /** Tracks pending follow-up messages for UI display. Removed when delivered. */
@@ -120,6 +123,7 @@ export class AgentSession {
120
123
  // Base system prompt (without extension appends) - used to apply fresh appends each turn
121
124
  _baseSystemPrompt = "";
122
125
  _baseSystemPromptOptions;
126
+ _systemPromptOverride;
123
127
  constructor(config) {
124
128
  this.agent = config.agent;
125
129
  this.sessionManager = config.sessionManager;
@@ -139,6 +143,7 @@ export class AgentSession {
139
143
  // (session persistence, extensions, auto-compaction, retry logic)
140
144
  this._unsubscribeAgent = this.agent.subscribe(this._handleAgentEvent);
141
145
  this._installAgentToolHooks();
146
+ this._installAgentNextTurnRefresh();
142
147
  this._buildRuntime({
143
148
  activeToolNames: this._initialActiveToolNames,
144
149
  includeAllExtensionTools: true,
@@ -227,6 +232,26 @@ export class AgentSession {
227
232
  };
228
233
  };
229
234
  }
235
+ _installAgentNextTurnRefresh() {
236
+ const previousPrepareNextTurnWithContext = this.agent.prepareNextTurnWithContext ??
237
+ (this.agent.prepareNextTurn
238
+ ? async (_turn, signal) => await this.agent.prepareNextTurn?.(signal)
239
+ : undefined);
240
+ this.agent.prepareNextTurnWithContext = async (turn, signal) => {
241
+ const previousSnapshot = await previousPrepareNextTurnWithContext?.(turn, signal);
242
+ const previousContext = previousSnapshot?.context ?? turn.context;
243
+ return {
244
+ ...previousSnapshot,
245
+ context: {
246
+ ...previousContext,
247
+ systemPrompt: this._systemPromptOverride ?? this._baseSystemPrompt,
248
+ tools: this.agent.state.tools.slice(),
249
+ },
250
+ model: this.agent.state.model,
251
+ thinkingLevel: this.agent.state.thinkingLevel,
252
+ };
253
+ };
254
+ }
230
255
  // =========================================================================
231
256
  // Event Subscription
232
257
  // =========================================================================
@@ -243,6 +268,33 @@ export class AgentSession {
243
268
  followUp: [...this._followUpMessages],
244
269
  });
245
270
  }
271
+ _getIdleWaitPromise() {
272
+ if (!this._idleWaitPromise) {
273
+ this._idleWaitPromise = new Promise((resolve) => {
274
+ this._resolveIdleWait = resolve;
275
+ });
276
+ }
277
+ return this._idleWaitPromise;
278
+ }
279
+ _resolveIdleWaitIfIdle() {
280
+ if (this._isAgentRunActive || !this._resolveIdleWait) {
281
+ return;
282
+ }
283
+ const resolve = this._resolveIdleWait;
284
+ this._idleWaitPromise = undefined;
285
+ this._resolveIdleWait = undefined;
286
+ resolve();
287
+ }
288
+ async _emitAgentSettled() {
289
+ this._isAgentRunActive = false;
290
+ try {
291
+ await this._extensionRunner.emit({ type: "agent_settled" });
292
+ this._emit({ type: "agent_settled" });
293
+ }
294
+ finally {
295
+ this._resolveIdleWaitIfIdle();
296
+ }
297
+ }
246
298
  // Track last assistant message for auto-compaction check
247
299
  _lastAssistantMessage = undefined;
248
300
  /** Internal handler for agent events - shared by subscribe and reconnect */
@@ -404,7 +456,16 @@ export class AgentSession {
404
456
  };
405
457
  const replacement = await this._extensionRunner.emitMessageEnd(extensionEvent);
406
458
  if (replacement) {
407
- this._replaceMessageInPlace(event.message, replacement);
459
+ // Untyped extension handlers can return messages with null/missing content;
460
+ // normalize so it never enters agent state or session history.
461
+ const normalized = (replacement.role === "user" ||
462
+ replacement.role === "assistant" ||
463
+ replacement.role === "toolResult" ||
464
+ replacement.role === "custom") &&
465
+ replacement.content == null
466
+ ? { ...replacement, content: [] }
467
+ : replacement;
468
+ this._replaceMessageInPlace(event.message, normalized);
408
469
  }
409
470
  }
410
471
  else if (event.type === "tool_execution_start") {
@@ -507,9 +568,13 @@ export class AgentSession {
507
568
  get thinkingLevel() {
508
569
  return this.agent.state.thinkingLevel;
509
570
  }
510
- /** Whether agent is currently streaming a response */
571
+ /** Whether the session is currently processing an agent run or post-run continuation. */
511
572
  get isStreaming() {
512
- return this.agent.state.isStreaming;
573
+ return this._isAgentRunActive;
574
+ }
575
+ /** Whether the session has no active agent run, retry, auto-compaction, or queued continuation. */
576
+ get isIdle() {
577
+ return !this._isAgentRunActive;
513
578
  }
514
579
  /** Current effective system prompt (includes any per-turn extension modifications) */
515
580
  get systemPrompt() {
@@ -560,7 +625,7 @@ export class AgentSession {
560
625
  this.agent.state.tools = tools;
561
626
  // Rebuild base system prompt with new tool set
562
627
  this._baseSystemPrompt = this._rebuildSystemPrompt(validToolNames);
563
- this.agent.state.systemPrompt = this._baseSystemPrompt;
628
+ this.agent.state.systemPrompt = this._systemPromptOverride ?? this._baseSystemPrompt;
564
629
  }
565
630
  /** Whether compaction or branch summarization is currently running */
566
631
  get isCompacting() {
@@ -661,6 +726,7 @@ export class AgentSession {
661
726
  // Prompting
662
727
  // =========================================================================
663
728
  async _runAgentPrompt(messages) {
729
+ this._isAgentRunActive = true;
664
730
  try {
665
731
  await this.agent.prompt(messages);
666
732
  while (await this._handlePostAgentRun()) {
@@ -668,7 +734,9 @@ export class AgentSession {
668
734
  }
669
735
  }
670
736
  finally {
737
+ this._systemPromptOverride = undefined;
671
738
  this._flushPendingBashMessages();
739
+ await this._emitAgentSettled();
672
740
  }
673
741
  }
674
742
  async _handlePostAgentRun() {
@@ -769,18 +837,11 @@ export class AgentSession {
769
837
  }
770
838
  throw new Error(formatNoApiKeyFoundMessage(this.model.provider));
771
839
  }
772
- // Check if we need to compact before sending (catches aborted responses)
840
+ // Check if we need to compact before sending (catches aborted responses).
841
+ // The user's new prompt is sent below, so do not call agent.continue() here.
773
842
  const lastAssistant = this._findLastAssistantMessage();
774
- if (lastAssistant && (await this._checkCompaction(lastAssistant, false))) {
775
- try {
776
- await this.agent.continue();
777
- while (await this._handlePostAgentRun()) {
778
- await this.agent.continue();
779
- }
780
- }
781
- finally {
782
- this._flushPendingBashMessages();
783
- }
843
+ if (lastAssistant) {
844
+ await this._checkCompaction(lastAssistant, false);
784
845
  }
785
846
  // Build messages array (custom message if any, then user message)
786
847
  messages = [];
@@ -807,7 +868,8 @@ export class AgentSession {
807
868
  messages.push({
808
869
  role: "custom",
809
870
  customType: msg.customType,
810
- content: msg.content,
871
+ // Untyped extensions can pass null/missing content; normalize at ingestion.
872
+ content: msg.content ?? [],
811
873
  display: msg.display,
812
874
  details: msg.details,
813
875
  timestamp: Date.now(),
@@ -815,11 +877,13 @@ export class AgentSession {
815
877
  }
816
878
  }
817
879
  // Apply extension-modified system prompt, or reset to base
818
- if (result?.systemPrompt) {
880
+ if (result?.systemPrompt !== undefined) {
881
+ this._systemPromptOverride = result.systemPrompt;
819
882
  this.agent.state.systemPrompt = result.systemPrompt;
820
883
  }
821
884
  else {
822
885
  // Ensure we're using the base prompt (in case previous turn had modifications)
886
+ this._systemPromptOverride = undefined;
823
887
  this.agent.state.systemPrompt = this._baseSystemPrompt;
824
888
  }
825
889
  }
@@ -984,7 +1048,8 @@ export class AgentSession {
984
1048
  const appMessage = {
985
1049
  role: "custom",
986
1050
  customType: message.customType,
987
- content: message.content,
1051
+ // Untyped extensions can pass null/missing content; normalize at ingestion.
1052
+ content: message.content ?? [],
988
1053
  display: message.display,
989
1054
  details: message.details,
990
1055
  timestamp: Date.now(),
@@ -1082,7 +1147,13 @@ export class AgentSession {
1082
1147
  async abort() {
1083
1148
  this.abortRetry();
1084
1149
  this.agent.abort();
1085
- await this.agent.waitForIdle();
1150
+ await this.waitForIdle();
1151
+ }
1152
+ async waitForIdle() {
1153
+ if (this.isIdle) {
1154
+ return;
1155
+ }
1156
+ await this._getIdleWaitPromise();
1086
1157
  }
1087
1158
  // =========================================================================
1088
1159
  // Model Management
@@ -1767,7 +1838,11 @@ export class AgentSession {
1767
1838
  });
1768
1839
  },
1769
1840
  appendEntry: (customType, data) => {
1770
- this.sessionManager.appendCustomEntry(customType, data);
1841
+ const entryId = this.sessionManager.appendCustomEntry(customType, data);
1842
+ const entry = this.sessionManager.getEntry(entryId);
1843
+ if (entry) {
1844
+ this._emit({ type: "entry_appended", entry });
1845
+ }
1771
1846
  },
1772
1847
  setSessionName: (name) => {
1773
1848
  this.setSessionName(name);
@@ -1793,7 +1868,7 @@ export class AgentSession {
1793
1868
  setThinkingLevel: (level) => this.setThinkingLevel(level),
1794
1869
  }, {
1795
1870
  getModel: () => this.model,
1796
- isIdle: () => !this.isStreaming,
1871
+ isIdle: () => this.isIdle,
1797
1872
  isProjectTrusted: () => this.settingsManager.isProjectTrusted(),
1798
1873
  getSignal: () => this.agent.signal,
1799
1874
  abort: () => {
@@ -1970,25 +2045,15 @@ export class AgentSession {
1970
2045
  // =========================================================================
1971
2046
  // Auto-Retry
1972
2047
  // =========================================================================
1973
- _isNonRetryableProviderLimitError(errorMessage) {
1974
- return /GoUsageLimitError|FreeUsageLimitError|Monthly usage limit reached|available balance|insufficient_quota|out of budget|quota exceeded|billing/i.test(errorMessage);
1975
- }
1976
2048
  /**
1977
2049
  * Check if an error is retryable (overloaded, rate limit, server errors).
1978
2050
  * Context overflow errors are NOT retryable (handled by compaction instead).
1979
2051
  */
1980
2052
  _isRetryableError(message) {
1981
- if (message.stopReason !== "error" || !message.errorMessage)
2053
+ // Context overflow is handled by compaction, not retry.
2054
+ if (isContextOverflow(message, this.model?.contextWindow ?? 0))
1982
2055
  return false;
1983
- // Context overflow is handled by compaction, not retry
1984
- const contextWindow = this.model?.contextWindow ?? 0;
1985
- if (isContextOverflow(message, contextWindow))
1986
- return false;
1987
- const err = message.errorMessage;
1988
- if (this._isNonRetryableProviderLimitError(err))
1989
- return false;
1990
- // Match: overloaded_error, provider returned error, rate limit, 429, 500, 502, 503, 504, service unavailable, network/connection errors (including connection lost), WebSocket transport closes/errors, fetch failed, premature stream endings, HTTP/2 closed before response, terminated, retry delay exceeded
1991
- return /overloaded|provider.?returned.?error|rate.?limit|too many requests|429|500|502|503|504|service.?unavailable|server.?error|internal.?error|network.?error|connection.?error|connection.?refused|connection.?lost|websocket.?closed|websocket.?error|other side closed|fetch failed|upstream.?connect|reset before headers|socket hang up|ended without|stream ended before message_stop|http2 request did not get a response|timed? out|timeout|terminated|retry delay/i.test(err);
2056
+ return isRetryableAssistantError(message);
1992
2057
  }
1993
2058
  /**
1994
2059
  * Prepare a retryable error for continuation with exponential backoff.
@@ -2154,7 +2219,9 @@ export class AgentSession {
2154
2219
  */
2155
2220
  setSessionName(name) {
2156
2221
  this.sessionManager.appendSessionInfo(name);
2157
- this._emit({ type: "session_info_changed", name: this.sessionManager.getSessionName() });
2222
+ const event = { type: "session_info_changed", name: this.sessionManager.getSessionName() };
2223
+ this._emit(event);
2224
+ void this._extensionRunner.emit(event);
2158
2225
  }
2159
2226
  // =========================================================================
2160
2227
  // Tree Navigation
@@ -2359,28 +2426,44 @@ export class AgentSession {
2359
2426
  return "";
2360
2427
  }
2361
2428
  /**
2362
- * Get session statistics.
2429
+ * Get session statistics. Aggregates over ALL session entries (including
2430
+ * history that was compacted away), so token/cost totals reflect what was
2431
+ * actually billed across the session.
2363
2432
  */
2364
2433
  getSessionStats() {
2365
- const state = this.state;
2366
- const userMessages = state.messages.filter((m) => m.role === "user").length;
2367
- const assistantMessages = state.messages.filter((m) => m.role === "assistant").length;
2368
- const toolResults = state.messages.filter((m) => m.role === "toolResult").length;
2434
+ let userMessages = 0;
2435
+ let assistantMessages = 0;
2436
+ let toolResults = 0;
2437
+ let totalMessages = 0;
2369
2438
  let toolCalls = 0;
2370
2439
  let totalInput = 0;
2371
2440
  let totalOutput = 0;
2372
2441
  let totalCacheRead = 0;
2373
2442
  let totalCacheWrite = 0;
2374
2443
  let totalCost = 0;
2375
- for (const message of state.messages) {
2376
- if (message.role === "assistant") {
2444
+ for (const entry of this.sessionManager.getEntries()) {
2445
+ if (entry.type !== "message")
2446
+ continue;
2447
+ totalMessages++;
2448
+ const message = entry.message;
2449
+ if (message.role === "user") {
2450
+ userMessages++;
2451
+ }
2452
+ else if (message.role === "toolResult") {
2453
+ toolResults++;
2454
+ }
2455
+ else if (message.role === "assistant") {
2456
+ assistantMessages++;
2377
2457
  const assistantMsg = message;
2378
- toolCalls += assistantMsg.content.filter((c) => c.type === "toolCall").length;
2379
- totalInput += assistantMsg.usage.input;
2380
- totalOutput += assistantMsg.usage.output;
2381
- totalCacheRead += assistantMsg.usage.cacheRead;
2382
- totalCacheWrite += assistantMsg.usage.cacheWrite;
2383
- totalCost += assistantMsg.usage.cost.total;
2458
+ if (Array.isArray(assistantMsg.content)) {
2459
+ toolCalls += assistantMsg.content.filter((c) => c.type === "toolCall").length;
2460
+ }
2461
+ const usage = assistantMsg.usage;
2462
+ totalInput += usage.input;
2463
+ totalOutput += usage.output;
2464
+ totalCacheRead += usage.cacheRead;
2465
+ totalCacheWrite += usage.cacheWrite;
2466
+ totalCost += usage.cost.total;
2384
2467
  }
2385
2468
  }
2386
2469
  return {
@@ -2390,7 +2473,7 @@ export class AgentSession {
2390
2473
  assistantMessages,
2391
2474
  toolCalls,
2392
2475
  toolResults,
2393
- totalMessages: state.messages.length,
2476
+ totalMessages,
2394
2477
  tokens: {
2395
2478
  input: totalInput,
2396
2479
  output: totalOutput,