@selesai/code 0.3.9 → 0.4.0

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 (222) hide show
  1. package/dist/cli/args.d.ts.map +1 -1
  2. package/dist/cli/args.js +4 -4
  3. package/dist/cli/args.js.map +1 -1
  4. package/dist/cli/config-selector.d.ts +4 -2
  5. package/dist/cli/config-selector.d.ts.map +1 -1
  6. package/dist/cli/config-selector.js +1 -1
  7. package/dist/cli/config-selector.js.map +1 -1
  8. package/dist/cli/file-processor.d.ts.map +1 -1
  9. package/dist/cli/file-processor.js +13 -25
  10. package/dist/cli/file-processor.js.map +1 -1
  11. package/dist/core/agent-session.d.ts +22 -5
  12. package/dist/core/agent-session.d.ts.map +1 -1
  13. package/dist/core/agent-session.js +130 -49
  14. package/dist/core/agent-session.js.map +1 -1
  15. package/dist/core/auth-storage.d.ts.map +1 -1
  16. package/dist/core/auth-storage.js +13 -5
  17. package/dist/core/auth-storage.js.map +1 -1
  18. package/dist/core/cache-stats.d.ts +49 -0
  19. package/dist/core/cache-stats.d.ts.map +1 -0
  20. package/dist/core/cache-stats.js +101 -0
  21. package/dist/core/cache-stats.js.map +1 -0
  22. package/dist/core/compaction/compaction.d.ts +1 -2
  23. package/dist/core/compaction/compaction.d.ts.map +1 -1
  24. package/dist/core/compaction/compaction.js +56 -81
  25. package/dist/core/compaction/compaction.js.map +1 -1
  26. package/dist/core/extensions/index.d.ts +1 -1
  27. package/dist/core/extensions/index.d.ts.map +1 -1
  28. package/dist/core/extensions/index.js.map +1 -1
  29. package/dist/core/extensions/loader.d.ts.map +1 -1
  30. package/dist/core/extensions/loader.js +10 -0
  31. package/dist/core/extensions/loader.js.map +1 -1
  32. package/dist/core/extensions/runner.d.ts +5 -3
  33. package/dist/core/extensions/runner.d.ts.map +1 -1
  34. package/dist/core/extensions/runner.js +38 -0
  35. package/dist/core/extensions/runner.js.map +1 -1
  36. package/dist/core/extensions/types.d.ts +41 -12
  37. package/dist/core/extensions/types.d.ts.map +1 -1
  38. package/dist/core/extensions/types.js.map +1 -1
  39. package/dist/core/http-dispatcher.d.ts.map +1 -1
  40. package/dist/core/http-dispatcher.js +28 -1
  41. package/dist/core/http-dispatcher.js.map +1 -1
  42. package/dist/core/index.d.ts +1 -1
  43. package/dist/core/index.d.ts.map +1 -1
  44. package/dist/core/index.js.map +1 -1
  45. package/dist/core/keybindings.d.ts.map +1 -1
  46. package/dist/core/keybindings.js +3 -6
  47. package/dist/core/keybindings.js.map +1 -1
  48. package/dist/core/model-registry.d.ts +4 -6
  49. package/dist/core/model-registry.d.ts.map +1 -1
  50. package/dist/core/model-registry.js +34 -8
  51. package/dist/core/model-registry.js.map +1 -1
  52. package/dist/core/model-resolver.d.ts +10 -0
  53. package/dist/core/model-resolver.d.ts.map +1 -1
  54. package/dist/core/model-resolver.js +15 -18
  55. package/dist/core/model-resolver.js.map +1 -1
  56. package/dist/core/package-manager.d.ts +4 -1
  57. package/dist/core/package-manager.d.ts.map +1 -1
  58. package/dist/core/package-manager.js +68 -26
  59. package/dist/core/package-manager.js.map +1 -1
  60. package/dist/core/provider-attribution.d.ts.map +1 -1
  61. package/dist/core/provider-attribution.js +0 -10
  62. package/dist/core/provider-attribution.js.map +1 -1
  63. package/dist/core/resource-loader.d.ts +2 -2
  64. package/dist/core/resource-loader.d.ts.map +1 -1
  65. package/dist/core/resource-loader.js +8 -7
  66. package/dist/core/resource-loader.js.map +1 -1
  67. package/dist/core/sdk.d.ts +1 -1
  68. package/dist/core/sdk.d.ts.map +1 -1
  69. package/dist/core/sdk.js +8 -1
  70. package/dist/core/sdk.js.map +1 -1
  71. package/dist/core/session-manager.d.ts +21 -2
  72. package/dist/core/session-manager.d.ts.map +1 -1
  73. package/dist/core/session-manager.js +104 -72
  74. package/dist/core/session-manager.js.map +1 -1
  75. package/dist/core/settings-manager.d.ts +14 -3
  76. package/dist/core/settings-manager.d.ts.map +1 -1
  77. package/dist/core/settings-manager.js +29 -1
  78. package/dist/core/settings-manager.js.map +1 -1
  79. package/dist/core/slash-commands.d.ts +1 -0
  80. package/dist/core/slash-commands.d.ts.map +1 -1
  81. package/dist/core/slash-commands.js +3 -3
  82. package/dist/core/slash-commands.js.map +1 -1
  83. package/dist/core/timings.d.ts +4 -2
  84. package/dist/core/timings.d.ts.map +1 -1
  85. package/dist/core/timings.js +24 -14
  86. package/dist/core/timings.js.map +1 -1
  87. package/dist/core/tools/bash.d.ts.map +1 -1
  88. package/dist/core/tools/bash.js +20 -5
  89. package/dist/core/tools/bash.js.map +1 -1
  90. package/dist/core/tools/edit.d.ts.map +1 -1
  91. package/dist/core/tools/edit.js +2 -2
  92. package/dist/core/tools/edit.js.map +1 -1
  93. package/dist/core/tools/read.d.ts.map +1 -1
  94. package/dist/core/tools/read.js +12 -25
  95. package/dist/core/tools/read.js.map +1 -1
  96. package/dist/defaults/models.json +2 -3
  97. package/dist/extensions/caveman/index.js +16 -1
  98. package/dist/extensions/caveman/test/extension.test.js +7 -4
  99. package/dist/extensions/caveman/test/helpers.test.js +12 -1
  100. package/dist/extensions/context-compaction-reminder.test.ts +82 -0
  101. package/dist/extensions/context-compaction-reminder.ts +28 -0
  102. package/dist/extensions/handoff-new.test.ts +2 -13
  103. package/dist/extensions/handoff-new.ts +16 -25
  104. package/dist/extensions/package.json +1 -0
  105. package/dist/extensions/pi-subagents/agents/architect.md +25 -173
  106. package/dist/extensions/pi-subagents/agents/builder.md +16 -105
  107. package/dist/extensions/pi-subagents/agents/commentator.md +19 -116
  108. package/dist/extensions/pi-subagents/agents/explorer.md +13 -33
  109. package/dist/extensions/pi-subagents/agents/recapper.md +19 -11
  110. package/dist/extensions/pi-subagents/agents/researcher.md +10 -33
  111. package/dist/extensions/question/constants.ts +0 -7
  112. package/dist/extensions/question/index.ts +11 -108
  113. package/dist/extensions/question/schemas.ts +1 -26
  114. package/dist/extensions/question/shortcuts.ts +5 -14
  115. package/dist/extensions/question/tests/question-list.test.ts +8 -0
  116. package/dist/extensions/question/tests/ui-protocol.test.ts +1 -7
  117. package/dist/extensions/question/tui-adapter.ts +3 -20
  118. package/dist/extensions/question/types.ts +0 -6
  119. package/dist/extensions/question/ui-protocol.ts +2 -17
  120. package/dist/extensions/workflow/adapter.ts +384 -124
  121. package/dist/extensions/workflow/extension.ts +2 -1
  122. package/dist/extensions/workflow/modes/prototype.ts +6 -6
  123. package/dist/extensions/workflow/modes/quick.ts +6 -6
  124. package/dist/extensions/workflow/modes/task.ts +79 -0
  125. package/dist/extensions/workflow/run-state.ts +124 -0
  126. package/dist/extensions/workflow/state-machine.ts +4 -8
  127. package/dist/index.d.ts +3 -2
  128. package/dist/index.d.ts.map +1 -1
  129. package/dist/index.js +2 -1
  130. package/dist/index.js.map +1 -1
  131. package/dist/main.d.ts +2 -2
  132. package/dist/main.d.ts.map +1 -1
  133. package/dist/main.js +17 -4
  134. package/dist/main.js.map +1 -1
  135. package/dist/modes/interactive/components/assistant-message.d.ts +3 -1
  136. package/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
  137. package/dist/modes/interactive/components/assistant-message.js +23 -15
  138. package/dist/modes/interactive/components/assistant-message.js.map +1 -1
  139. package/dist/modes/interactive/components/config-selector.d.ts +34 -3
  140. package/dist/modes/interactive/components/config-selector.d.ts.map +1 -1
  141. package/dist/modes/interactive/components/config-selector.js +280 -25
  142. package/dist/modes/interactive/components/config-selector.js.map +1 -1
  143. package/dist/modes/interactive/components/custom-entry.d.ts +19 -0
  144. package/dist/modes/interactive/components/custom-entry.d.ts.map +1 -0
  145. package/dist/modes/interactive/components/custom-entry.js +52 -0
  146. package/dist/modes/interactive/components/custom-entry.js.map +1 -0
  147. package/dist/modes/interactive/components/extension-editor.d.ts +3 -1
  148. package/dist/modes/interactive/components/extension-editor.d.ts.map +1 -1
  149. package/dist/modes/interactive/components/extension-editor.js +12 -3
  150. package/dist/modes/interactive/components/extension-editor.js.map +1 -1
  151. package/dist/modes/interactive/components/footer.d.ts +4 -0
  152. package/dist/modes/interactive/components/footer.d.ts.map +1 -1
  153. package/dist/modes/interactive/components/footer.js +1 -1
  154. package/dist/modes/interactive/components/footer.js.map +1 -1
  155. package/dist/modes/interactive/components/oauth-selector.d.ts +3 -1
  156. package/dist/modes/interactive/components/oauth-selector.d.ts.map +1 -1
  157. package/dist/modes/interactive/components/oauth-selector.js +17 -6
  158. package/dist/modes/interactive/components/oauth-selector.js.map +1 -1
  159. package/dist/modes/interactive/components/settings-selector.d.ts +4 -0
  160. package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  161. package/dist/modes/interactive/components/settings-selector.js +25 -2
  162. package/dist/modes/interactive/components/settings-selector.js.map +1 -1
  163. package/dist/modes/interactive/components/status-indicator.d.ts +28 -0
  164. package/dist/modes/interactive/components/status-indicator.d.ts.map +1 -0
  165. package/dist/modes/interactive/components/status-indicator.js +60 -0
  166. package/dist/modes/interactive/components/status-indicator.js.map +1 -0
  167. package/dist/modes/interactive/components/thinking-selector.d.ts.map +1 -1
  168. package/dist/modes/interactive/components/thinking-selector.js +2 -1
  169. package/dist/modes/interactive/components/thinking-selector.js.map +1 -1
  170. package/dist/modes/interactive/components/user-message.d.ts +6 -2
  171. package/dist/modes/interactive/components/user-message.d.ts.map +1 -1
  172. package/dist/modes/interactive/components/user-message.js +19 -6
  173. package/dist/modes/interactive/components/user-message.js.map +1 -1
  174. package/dist/modes/interactive/interactive-mode.d.ts +21 -10
  175. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  176. package/dist/modes/interactive/interactive-mode.js +417 -196
  177. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  178. package/dist/modes/interactive/theme/dark.json +1 -0
  179. package/dist/modes/interactive/theme/light.json +1 -0
  180. package/dist/modes/interactive/theme/theme-schema.json +6 -2
  181. package/dist/modes/interactive/theme/theme.d.ts +3 -2
  182. package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
  183. package/dist/modes/interactive/theme/theme.js +10 -3
  184. package/dist/modes/interactive/theme/theme.js.map +1 -1
  185. package/dist/modes/print-mode.d.ts.map +1 -1
  186. package/dist/modes/print-mode.js +1 -1
  187. package/dist/modes/print-mode.js.map +1 -1
  188. package/dist/modes/rpc/rpc-client.d.ts +21 -6
  189. package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
  190. package/dist/modes/rpc/rpc-client.js +17 -3
  191. package/dist/modes/rpc/rpc-client.js.map +1 -1
  192. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  193. package/dist/modes/rpc/rpc-mode.js +20 -1
  194. package/dist/modes/rpc/rpc-mode.js.map +1 -1
  195. package/dist/modes/rpc/rpc-types.d.ts +26 -0
  196. package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
  197. package/dist/modes/rpc/rpc-types.js.map +1 -1
  198. package/dist/package-manager-cli.d.ts +2 -2
  199. package/dist/package-manager-cli.d.ts.map +1 -1
  200. package/dist/package-manager-cli.js +71 -17
  201. package/dist/package-manager-cli.js.map +1 -1
  202. package/dist/rpc-entry.d.ts +3 -0
  203. package/dist/rpc-entry.d.ts.map +1 -0
  204. package/dist/rpc-entry.js +10 -0
  205. package/dist/rpc-entry.js.map +1 -0
  206. package/dist/skills/workflow-creation/SKILL.md +72 -0
  207. package/dist/utils/clipboard-image.d.ts.map +1 -1
  208. package/dist/utils/clipboard-image.js +1 -1
  209. package/dist/utils/clipboard-image.js.map +1 -1
  210. package/dist/utils/image-convert.d.ts +1 -0
  211. package/dist/utils/image-convert.d.ts.map +1 -1
  212. package/dist/utils/image-convert.js +21 -15
  213. package/dist/utils/image-convert.js.map +1 -1
  214. package/dist/utils/image-process.d.ts +18 -0
  215. package/dist/utils/image-process.d.ts.map +1 -0
  216. package/dist/utils/image-process.js +83 -0
  217. package/dist/utils/image-process.js.map +1 -0
  218. package/dist/utils/mime.d.ts.map +1 -1
  219. package/dist/utils/mime.js +41 -0
  220. package/dist/utils/mime.js.map +1 -1
  221. package/docs/workflows.md +40 -6
  222. 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";
@@ -73,6 +73,9 @@ export class AgentSession {
73
73
  // Event subscription state
74
74
  _unsubscribeAgent;
75
75
  _eventListeners = [];
76
+ _isAgentRunActive = false;
77
+ _idleWaitPromise;
78
+ _resolveIdleWait;
76
79
  /** Tracks pending steering messages for UI display. Removed when delivered. */
77
80
  _steeringMessages = [];
78
81
  /** Tracks pending follow-up messages for UI display. Removed when delivered. */
@@ -121,6 +124,7 @@ export class AgentSession {
121
124
  // Base system prompt (without extension appends) - used to apply fresh appends each turn
122
125
  _baseSystemPrompt = "";
123
126
  _baseSystemPromptOptions;
127
+ _systemPromptOverride;
124
128
  constructor(config) {
125
129
  this.agent = config.agent;
126
130
  this.sessionManager = config.sessionManager;
@@ -140,6 +144,7 @@ export class AgentSession {
140
144
  // (session persistence, extensions, auto-compaction, retry logic)
141
145
  this._unsubscribeAgent = this.agent.subscribe(this._handleAgentEvent);
142
146
  this._installAgentToolHooks();
147
+ this._installAgentNextTurnRefresh();
143
148
  this._buildRuntime({
144
149
  activeToolNames: this._initialActiveToolNames,
145
150
  includeAllExtensionTools: true,
@@ -228,6 +233,26 @@ export class AgentSession {
228
233
  };
229
234
  };
230
235
  }
236
+ _installAgentNextTurnRefresh() {
237
+ const previousPrepareNextTurnWithContext = this.agent.prepareNextTurnWithContext ??
238
+ (this.agent.prepareNextTurn
239
+ ? async (_turn, signal) => await this.agent.prepareNextTurn?.(signal)
240
+ : undefined);
241
+ this.agent.prepareNextTurnWithContext = async (turn, signal) => {
242
+ const previousSnapshot = await previousPrepareNextTurnWithContext?.(turn, signal);
243
+ const previousContext = previousSnapshot?.context ?? turn.context;
244
+ return {
245
+ ...previousSnapshot,
246
+ context: {
247
+ ...previousContext,
248
+ systemPrompt: this._systemPromptOverride ?? this._baseSystemPrompt,
249
+ tools: this.agent.state.tools.slice(),
250
+ },
251
+ model: this.agent.state.model,
252
+ thinkingLevel: this.agent.state.thinkingLevel,
253
+ };
254
+ };
255
+ }
231
256
  // =========================================================================
232
257
  // Event Subscription
233
258
  // =========================================================================
@@ -244,6 +269,33 @@ export class AgentSession {
244
269
  followUp: [...this._followUpMessages],
245
270
  });
246
271
  }
272
+ _getIdleWaitPromise() {
273
+ if (!this._idleWaitPromise) {
274
+ this._idleWaitPromise = new Promise((resolve) => {
275
+ this._resolveIdleWait = resolve;
276
+ });
277
+ }
278
+ return this._idleWaitPromise;
279
+ }
280
+ _resolveIdleWaitIfIdle() {
281
+ if (this._isAgentRunActive || !this._resolveIdleWait) {
282
+ return;
283
+ }
284
+ const resolve = this._resolveIdleWait;
285
+ this._idleWaitPromise = undefined;
286
+ this._resolveIdleWait = undefined;
287
+ resolve();
288
+ }
289
+ async _emitAgentSettled() {
290
+ this._isAgentRunActive = false;
291
+ try {
292
+ await this._extensionRunner.emit({ type: "agent_settled" });
293
+ this._emit({ type: "agent_settled" });
294
+ }
295
+ finally {
296
+ this._resolveIdleWaitIfIdle();
297
+ }
298
+ }
247
299
  // Track last assistant message for auto-compaction check
248
300
  _lastAssistantMessage = undefined;
249
301
  /** Internal handler for agent events - shared by subscribe and reconnect */
@@ -412,7 +464,16 @@ export class AgentSession {
412
464
  };
413
465
  const replacement = await this._extensionRunner.emitMessageEnd(extensionEvent);
414
466
  if (replacement) {
415
- this._replaceMessageInPlace(event.message, replacement);
467
+ // Untyped extension handlers can return messages with null/missing content;
468
+ // normalize so it never enters agent state or session history.
469
+ const normalized = (replacement.role === "user" ||
470
+ replacement.role === "assistant" ||
471
+ replacement.role === "toolResult" ||
472
+ replacement.role === "custom") &&
473
+ replacement.content == null
474
+ ? { ...replacement, content: [] }
475
+ : replacement;
476
+ this._replaceMessageInPlace(event.message, normalized);
416
477
  }
417
478
  }
418
479
  else if (event.type === "tool_execution_start") {
@@ -515,9 +576,13 @@ export class AgentSession {
515
576
  get thinkingLevel() {
516
577
  return this.agent.state.thinkingLevel;
517
578
  }
518
- /** Whether agent is currently streaming a response */
579
+ /** Whether the session is currently processing an agent run or post-run continuation. */
519
580
  get isStreaming() {
520
- return this.agent.state.isStreaming;
581
+ return this._isAgentRunActive;
582
+ }
583
+ /** Whether the session has no active agent run, retry, auto-compaction, or queued continuation. */
584
+ get isIdle() {
585
+ return !this._isAgentRunActive;
521
586
  }
522
587
  /** Current effective system prompt (includes any per-turn extension modifications) */
523
588
  get systemPrompt() {
@@ -568,7 +633,7 @@ export class AgentSession {
568
633
  this.agent.state.tools = tools;
569
634
  // Rebuild base system prompt with new tool set
570
635
  this._baseSystemPrompt = this._rebuildSystemPrompt(validToolNames);
571
- this.agent.state.systemPrompt = this._baseSystemPrompt;
636
+ this.agent.state.systemPrompt = this._systemPromptOverride ?? this._baseSystemPrompt;
572
637
  }
573
638
  /** Whether compaction or branch summarization is currently running */
574
639
  get isCompacting() {
@@ -671,6 +736,7 @@ export class AgentSession {
671
736
  // Prompting
672
737
  // =========================================================================
673
738
  async _runAgentPrompt(messages) {
739
+ this._isAgentRunActive = true;
674
740
  try {
675
741
  await this.agent.prompt(messages);
676
742
  while (await this._handlePostAgentRun()) {
@@ -678,7 +744,9 @@ export class AgentSession {
678
744
  }
679
745
  }
680
746
  finally {
747
+ this._systemPromptOverride = undefined;
681
748
  this._flushPendingBashMessages();
749
+ await this._emitAgentSettled();
682
750
  }
683
751
  }
684
752
  async _handlePostAgentRun() {
@@ -779,18 +847,11 @@ export class AgentSession {
779
847
  }
780
848
  throw new Error(formatNoApiKeyFoundMessage(this.model.provider));
781
849
  }
782
- // Check if we need to compact before sending (catches aborted responses)
850
+ // Check if we need to compact before sending (catches aborted responses).
851
+ // The user's new prompt is sent below, so do not call agent.continue() here.
783
852
  const lastAssistant = this._findLastAssistantMessage();
784
- if (lastAssistant && (await this._checkCompaction(lastAssistant, false))) {
785
- try {
786
- await this.agent.continue();
787
- while (await this._handlePostAgentRun()) {
788
- await this.agent.continue();
789
- }
790
- }
791
- finally {
792
- this._flushPendingBashMessages();
793
- }
853
+ if (lastAssistant) {
854
+ await this._checkCompaction(lastAssistant, false);
794
855
  }
795
856
  // Build messages array (custom message if any, then user message)
796
857
  messages = [];
@@ -817,7 +878,8 @@ export class AgentSession {
817
878
  messages.push({
818
879
  role: "custom",
819
880
  customType: msg.customType,
820
- content: msg.content,
881
+ // Untyped extensions can pass null/missing content; normalize at ingestion.
882
+ content: msg.content ?? [],
821
883
  display: msg.display,
822
884
  details: msg.details,
823
885
  timestamp: Date.now(),
@@ -825,11 +887,13 @@ export class AgentSession {
825
887
  }
826
888
  }
827
889
  // Apply extension-modified system prompt, or reset to base
828
- if (result?.systemPrompt) {
890
+ if (result?.systemPrompt !== undefined) {
891
+ this._systemPromptOverride = result.systemPrompt;
829
892
  this.agent.state.systemPrompt = result.systemPrompt;
830
893
  }
831
894
  else {
832
895
  // Ensure we're using the base prompt (in case previous turn had modifications)
896
+ this._systemPromptOverride = undefined;
833
897
  this.agent.state.systemPrompt = this._baseSystemPrompt;
834
898
  }
835
899
  }
@@ -994,7 +1058,8 @@ export class AgentSession {
994
1058
  const appMessage = {
995
1059
  role: "custom",
996
1060
  customType: message.customType,
997
- content: message.content,
1061
+ // Untyped extensions can pass null/missing content; normalize at ingestion.
1062
+ content: message.content ?? [],
998
1063
  display: message.display,
999
1064
  details: message.details,
1000
1065
  timestamp: Date.now(),
@@ -1092,7 +1157,13 @@ export class AgentSession {
1092
1157
  async abort() {
1093
1158
  this.abortRetry();
1094
1159
  this.agent.abort();
1095
- await this.agent.waitForIdle();
1160
+ await this.waitForIdle();
1161
+ }
1162
+ async waitForIdle() {
1163
+ if (this.isIdle) {
1164
+ return;
1165
+ }
1166
+ await this._getIdleWaitPromise();
1096
1167
  }
1097
1168
  // =========================================================================
1098
1169
  // Model Management
@@ -1777,7 +1848,11 @@ export class AgentSession {
1777
1848
  });
1778
1849
  },
1779
1850
  appendEntry: (customType, data) => {
1780
- this.sessionManager.appendCustomEntry(customType, data);
1851
+ const entryId = this.sessionManager.appendCustomEntry(customType, data);
1852
+ const entry = this.sessionManager.getEntry(entryId);
1853
+ if (entry) {
1854
+ this._emit({ type: "entry_appended", entry });
1855
+ }
1781
1856
  },
1782
1857
  setSessionName: (name) => {
1783
1858
  this.setSessionName(name);
@@ -1803,7 +1878,7 @@ export class AgentSession {
1803
1878
  setThinkingLevel: (level) => this.setThinkingLevel(level),
1804
1879
  }, {
1805
1880
  getModel: () => this.model,
1806
- isIdle: () => !this.isStreaming,
1881
+ isIdle: () => this.isIdle,
1807
1882
  isProjectTrusted: () => this.settingsManager.isProjectTrusted(),
1808
1883
  getSignal: () => this.agent.signal,
1809
1884
  abort: () => {
@@ -1980,25 +2055,15 @@ export class AgentSession {
1980
2055
  // =========================================================================
1981
2056
  // Auto-Retry
1982
2057
  // =========================================================================
1983
- _isNonRetryableProviderLimitError(errorMessage) {
1984
- return /GoUsageLimitError|FreeUsageLimitError|Monthly usage limit reached|available balance|insufficient_quota|out of budget|quota exceeded|billing/i.test(errorMessage);
1985
- }
1986
2058
  /**
1987
2059
  * Check if an error is retryable (overloaded, rate limit, server errors).
1988
2060
  * Context overflow errors are NOT retryable (handled by compaction instead).
1989
2061
  */
1990
2062
  _isRetryableError(message) {
1991
- if (message.stopReason !== "error" || !message.errorMessage)
2063
+ // Context overflow is handled by compaction, not retry.
2064
+ if (isContextOverflow(message, this.model?.contextWindow ?? 0))
1992
2065
  return false;
1993
- // Context overflow is handled by compaction, not retry
1994
- const contextWindow = this.model?.contextWindow ?? 0;
1995
- if (isContextOverflow(message, contextWindow))
1996
- return false;
1997
- const err = message.errorMessage;
1998
- if (this._isNonRetryableProviderLimitError(err))
1999
- return false;
2000
- // 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
2001
- 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);
2066
+ return isRetryableAssistantError(message);
2002
2067
  }
2003
2068
  /**
2004
2069
  * Prepare a retryable error for continuation with exponential backoff.
@@ -2371,28 +2436,44 @@ export class AgentSession {
2371
2436
  return "";
2372
2437
  }
2373
2438
  /**
2374
- * Get session statistics.
2439
+ * Get session statistics. Aggregates over ALL session entries (including
2440
+ * history that was compacted away), so token/cost totals reflect what was
2441
+ * actually billed across the session.
2375
2442
  */
2376
2443
  getSessionStats() {
2377
- const state = this.state;
2378
- const userMessages = state.messages.filter((m) => m.role === "user").length;
2379
- const assistantMessages = state.messages.filter((m) => m.role === "assistant").length;
2380
- const toolResults = state.messages.filter((m) => m.role === "toolResult").length;
2444
+ let userMessages = 0;
2445
+ let assistantMessages = 0;
2446
+ let toolResults = 0;
2447
+ let totalMessages = 0;
2381
2448
  let toolCalls = 0;
2382
2449
  let totalInput = 0;
2383
2450
  let totalOutput = 0;
2384
2451
  let totalCacheRead = 0;
2385
2452
  let totalCacheWrite = 0;
2386
2453
  let totalCost = 0;
2387
- for (const message of state.messages) {
2388
- if (message.role === "assistant") {
2454
+ for (const entry of this.sessionManager.getEntries()) {
2455
+ if (entry.type !== "message")
2456
+ continue;
2457
+ totalMessages++;
2458
+ const message = entry.message;
2459
+ if (message.role === "user") {
2460
+ userMessages++;
2461
+ }
2462
+ else if (message.role === "toolResult") {
2463
+ toolResults++;
2464
+ }
2465
+ else if (message.role === "assistant") {
2466
+ assistantMessages++;
2389
2467
  const assistantMsg = message;
2390
- toolCalls += assistantMsg.content.filter((c) => c.type === "toolCall").length;
2391
- totalInput += assistantMsg.usage.input;
2392
- totalOutput += assistantMsg.usage.output;
2393
- totalCacheRead += assistantMsg.usage.cacheRead;
2394
- totalCacheWrite += assistantMsg.usage.cacheWrite;
2395
- totalCost += assistantMsg.usage.cost.total;
2468
+ if (Array.isArray(assistantMsg.content)) {
2469
+ toolCalls += assistantMsg.content.filter((c) => c.type === "toolCall").length;
2470
+ }
2471
+ const usage = assistantMsg.usage;
2472
+ totalInput += usage.input;
2473
+ totalOutput += usage.output;
2474
+ totalCacheRead += usage.cacheRead;
2475
+ totalCacheWrite += usage.cacheWrite;
2476
+ totalCost += usage.cost.total;
2396
2477
  }
2397
2478
  }
2398
2479
  return {
@@ -2402,7 +2483,7 @@ export class AgentSession {
2402
2483
  assistantMessages,
2403
2484
  toolCalls,
2404
2485
  toolResults,
2405
- totalMessages: state.messages.length,
2486
+ totalMessages,
2406
2487
  tokens: {
2407
2488
  input: totalInput,
2408
2489
  output: totalOutput,