@oh-my-pi/pi-coding-agent 17.2.8 → 17.2.10

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 (246) hide show
  1. package/CHANGELOG.md +94 -1
  2. package/dist/{CHANGELOG-0b0w17se.md → CHANGELOG-rhwzpexa.md} +94 -1
  3. package/dist/cli.js +13216 -15614
  4. package/dist/types/async/job-manager.d.ts +12 -0
  5. package/dist/types/cli/args.d.ts +1 -0
  6. package/dist/types/config/model-resolver.d.ts +13 -0
  7. package/dist/types/discovery/agents.d.ts +11 -0
  8. package/dist/types/extensibility/custom-commands/types.d.ts +5 -5
  9. package/dist/types/extensibility/custom-tools/types.d.ts +1 -1
  10. package/dist/types/extensibility/extensions/loader.d.ts +2 -2
  11. package/dist/types/extensibility/extensions/types.d.ts +34 -8
  12. package/dist/types/extensibility/hooks/types.d.ts +5 -5
  13. package/dist/types/extensibility/legacy-pi-coding-agent-shim.d.ts +10 -0
  14. package/dist/types/hindsight/state.d.ts +0 -1
  15. package/dist/types/index.d.ts +2 -2
  16. package/dist/types/lsp/utils.d.ts +8 -0
  17. package/dist/types/main.d.ts +1 -1
  18. package/dist/types/modes/acp/acp-agent.d.ts +1 -1
  19. package/dist/types/modes/acp/acp-client-bridge.d.ts +1 -1
  20. package/dist/types/modes/acp/acp-event-mapper.d.ts +1 -1
  21. package/dist/types/modes/acp/acp-mode.d.ts +1 -1
  22. package/dist/types/modes/components/agent-hub-projection.d.ts +38 -0
  23. package/dist/types/modes/components/agent-hub-renderer.d.ts +41 -0
  24. package/dist/types/modes/components/agent-hub.d.ts +12 -5
  25. package/dist/types/modes/components/model-browser.d.ts +9 -1
  26. package/dist/types/modes/interactive-mode.d.ts +8 -1
  27. package/dist/types/modes/session-observer-registry.d.ts +2 -0
  28. package/dist/types/registry/agent-lifecycle.d.ts +1 -1
  29. package/dist/types/registry/agent-registry.d.ts +47 -0
  30. package/dist/types/registry/persisted-agents.d.ts +3 -1
  31. package/dist/types/session/agent-session-types.d.ts +7 -0
  32. package/dist/types/session/agent-session.d.ts +8 -4
  33. package/dist/types/session/session-entries.d.ts +10 -0
  34. package/dist/types/session/session-loader.d.ts +11 -1
  35. package/dist/types/session/session-manager.d.ts +9 -1
  36. package/dist/types/session/session-tools.d.ts +10 -1
  37. package/dist/types/session/turn-recovery.d.ts +6 -2
  38. package/dist/types/slash-commands/acp-builtins.d.ts +1 -1
  39. package/dist/types/slash-commands/available-commands.d.ts +1 -1
  40. package/dist/types/task/executor.d.ts +10 -0
  41. package/dist/types/task/index.d.ts +2 -3
  42. package/dist/types/task/read-only-policy.d.ts +3 -0
  43. package/dist/types/task/structured-subagent.d.ts +2 -0
  44. package/dist/types/task/types.d.ts +4 -0
  45. package/dist/types/tools/bash.d.ts +3 -3
  46. package/dist/types/tools/browser/launch.d.ts +3 -0
  47. package/dist/types/tools/browser/tab-worker.d.ts +2 -2
  48. package/dist/types/tools/path-utils.d.ts +8 -0
  49. package/dist/types/tools/read.d.ts +3 -5
  50. package/dist/types/tools/run-scope.d.ts +27 -1
  51. package/dist/types/tools/shell-tokenize.d.ts +21 -3
  52. package/dist/types/tools/terminal-output.d.ts +1 -1
  53. package/dist/types/utils/changelog.d.ts +3 -2
  54. package/dist/types/utils/late-cleanup.d.ts +2 -0
  55. package/dist/types/utils/turndown.d.ts +1 -1
  56. package/dist/types/vibe/runtime.d.ts +4 -3
  57. package/dist/types/web/scrapers/readthedocs.d.ts +0 -3
  58. package/dist/types/web/search/providers/browser-headers.d.ts +2 -3
  59. package/examples/custom-tools/README.md +1 -1
  60. package/examples/extensions/README.md +1 -1
  61. package/examples/extensions/api-demo.ts +5 -6
  62. package/examples/extensions/chalk-logger.ts +1 -1
  63. package/examples/extensions/hello.ts +2 -2
  64. package/examples/extensions/reload-runtime.ts +1 -1
  65. package/examples/extensions/tools.ts +2 -2
  66. package/examples/extensions/with-deps/index.ts +1 -1
  67. package/examples/sdk/06-extensions.ts +1 -1
  68. package/package.json +15 -31
  69. package/scripts/bundle-dist.ts +1 -11
  70. package/scripts/omp +11 -2
  71. package/src/advisor/runtime.ts +54 -0
  72. package/src/async/job-manager.ts +26 -0
  73. package/src/cli/agents-cli.ts +1 -1
  74. package/src/cli/args.ts +24 -2
  75. package/src/cli/auth-broker-cli.ts +1 -1
  76. package/src/cli/auth-gateway-cli.ts +1 -1
  77. package/src/cli/bench-cli.ts +1 -1
  78. package/src/cli/claude-trace-cli.ts +1 -1
  79. package/src/cli/config-cli.ts +1 -1
  80. package/src/cli/dry-balance-cli.ts +1 -1
  81. package/src/cli/file-processor.ts +1 -1
  82. package/src/cli/flag-tables.ts +4 -0
  83. package/src/cli/grep-cli.ts +1 -1
  84. package/src/cli/grievances-cli.ts +1 -1
  85. package/src/cli/help-extra.ts +1 -1
  86. package/src/cli/models-cli.ts +1 -1
  87. package/src/cli/plugin-cli.ts +1 -1
  88. package/src/cli/read-cli.ts +1 -1
  89. package/src/cli/setup-cli.ts +1 -1
  90. package/src/cli/shell-cli.ts +1 -1
  91. package/src/cli/ssh-cli.ts +1 -1
  92. package/src/cli/stats-cli.ts +4 -4
  93. package/src/cli/tiny-models-cli.ts +1 -1
  94. package/src/cli/ttsr-cli.ts +1 -1
  95. package/src/cli/update-cli.ts +3 -57
  96. package/src/cli/usage-cli.ts +1 -1
  97. package/src/cli/web-search-cli.ts +1 -1
  98. package/src/cli/worktree-cli.ts +1 -1
  99. package/src/commands/say.ts +1 -1
  100. package/src/commands/token.ts +1 -1
  101. package/src/commit/agentic/agent.ts +1 -1
  102. package/src/commit/cli.ts +1 -1
  103. package/src/config/config-file.ts +2 -2
  104. package/src/config/keybindings.ts +3 -3
  105. package/src/config/model-discovery.ts +36 -2
  106. package/src/config/model-registry.ts +4 -1
  107. package/src/config/model-resolver.ts +54 -7
  108. package/src/discovery/agents.ts +73 -3
  109. package/src/discovery/builtin-rules/ts-no-inline-cast-access.md +5 -4
  110. package/src/discovery/builtin-rules/ts-no-tiny-functions.md +1 -1
  111. package/src/discovery/claude.ts +8 -5
  112. package/src/discovery/cursor.ts +8 -5
  113. package/src/discovery/gemini.ts +11 -8
  114. package/src/discovery/vscode.ts +1 -0
  115. package/src/discovery/windsurf.ts +4 -2
  116. package/src/edit/hashline/filesystem.ts +7 -7
  117. package/src/eval/agent-bridge.ts +1 -3
  118. package/src/eval/jl/prelude.jl +4 -4
  119. package/src/eval/js/shared/prelude.txt +2 -2
  120. package/src/eval/py/prelude.py +0 -3
  121. package/src/eval/py/runner.py +38 -1
  122. package/src/eval/rb/prelude.rb +1 -2
  123. package/src/extensibility/custom-commands/loader.ts +4 -4
  124. package/src/extensibility/custom-commands/types.ts +5 -5
  125. package/src/extensibility/custom-tools/loader.ts +4 -4
  126. package/src/extensibility/custom-tools/types.ts +1 -1
  127. package/src/extensibility/extensions/loader.ts +7 -6
  128. package/src/extensibility/extensions/types.ts +39 -8
  129. package/src/extensibility/hooks/loader.ts +4 -5
  130. package/src/extensibility/hooks/types.ts +5 -5
  131. package/src/extensibility/legacy-pi-coding-agent-shim.ts +23 -0
  132. package/src/extensibility/plugins/legacy-pi-compat.ts +53 -0
  133. package/src/hindsight/state.ts +2 -22
  134. package/src/index.ts +2 -2
  135. package/src/internal-urls/memory-protocol.ts +33 -0
  136. package/src/launch/terminal-output.ts +1 -1
  137. package/src/lsp/client.ts +2 -2
  138. package/src/lsp/utils.ts +29 -0
  139. package/src/main.ts +85 -21
  140. package/src/markit/converters/docx.ts +1 -1
  141. package/src/markit/converters/epub.ts +1 -1
  142. package/src/markit/converters/pptx.ts +1 -1
  143. package/src/markit/converters/xlsx.ts +1 -1
  144. package/src/modes/acp/acp-agent.ts +7 -6
  145. package/src/modes/acp/acp-client-bridge.ts +1 -1
  146. package/src/modes/acp/acp-event-mapper.ts +1 -1
  147. package/src/modes/acp/acp-mode.ts +1 -1
  148. package/src/modes/components/agent-hub-projection.ts +248 -0
  149. package/src/modes/components/agent-hub-renderer.ts +194 -0
  150. package/src/modes/components/agent-hub.ts +670 -192
  151. package/src/modes/components/agent-transcript-viewer.ts +1 -3
  152. package/src/modes/components/assistant-message.ts +1 -1
  153. package/src/modes/components/bordered-loader.ts +1 -1
  154. package/src/modes/components/custom-editor.ts +2 -2
  155. package/src/modes/components/extensions/inspector-panel.ts +11 -6
  156. package/src/modes/components/footer.ts +1 -3
  157. package/src/modes/components/hook-input.ts +1 -1
  158. package/src/modes/components/hook-selector.ts +2 -2
  159. package/src/modes/components/model-browser.ts +11 -3
  160. package/src/modes/components/model-hub.ts +4 -1
  161. package/src/modes/components/session-selector.ts +2 -2
  162. package/src/modes/components/status-line/segments.ts +4 -3
  163. package/src/modes/controllers/event-controller.ts +188 -7
  164. package/src/modes/controllers/extension-ui-controller.ts +4 -3
  165. package/src/modes/controllers/mcp-command-controller.ts +81 -20
  166. package/src/modes/controllers/selector-controller.ts +40 -49
  167. package/src/modes/controllers/streaming-reveal.ts +1 -1
  168. package/src/modes/interactive-mode.ts +13 -2
  169. package/src/modes/print-mode.ts +29 -12
  170. package/src/modes/rpc/rpc-mode.ts +2 -2
  171. package/src/modes/runtime-init.ts +1 -1
  172. package/src/modes/session-observer-registry.ts +5 -0
  173. package/src/modes/theme/theme.ts +2 -2
  174. package/src/prompts/tools/computer.md +1 -1
  175. package/src/registry/agent-lifecycle.ts +35 -10
  176. package/src/registry/agent-registry.ts +65 -2
  177. package/src/registry/persisted-agents.ts +341 -16
  178. package/src/sdk.ts +11 -5
  179. package/src/session/agent-session-types.ts +8 -0
  180. package/src/session/agent-session.ts +177 -188
  181. package/src/session/session-entries.ts +10 -0
  182. package/src/session/session-listing.ts +1 -1
  183. package/src/session/session-loader.ts +74 -8
  184. package/src/session/session-manager.ts +22 -2
  185. package/src/session/session-paths.ts +105 -110
  186. package/src/session/session-tools.ts +28 -1
  187. package/src/session/settings-stream-fn.ts +7 -3
  188. package/src/session/turn-recovery.ts +236 -18
  189. package/src/session/unexpected-stop-classifier.ts +12 -3
  190. package/src/slash-commands/acp-builtins.ts +1 -1
  191. package/src/slash-commands/available-commands.ts +1 -1
  192. package/src/slash-commands/helpers/stats-dashboard.ts +2 -1
  193. package/src/task/agents.ts +1 -1
  194. package/src/task/executor.ts +173 -47
  195. package/src/task/index.ts +9 -28
  196. package/src/task/isolation-runner.ts +43 -12
  197. package/src/task/persisted-revive.ts +12 -5
  198. package/src/task/read-only-policy.ts +27 -0
  199. package/src/task/structured-subagent.ts +31 -6
  200. package/src/task/types.ts +4 -0
  201. package/src/tiny/worker.ts +1 -1
  202. package/src/tools/auto-generated-guard.ts +1 -1
  203. package/src/tools/bash-interactive.ts +4 -4
  204. package/src/tools/bash-interceptor.ts +8 -4
  205. package/src/tools/bash.ts +6 -6
  206. package/src/tools/browser/cmux/cmux-tab.ts +66 -18
  207. package/src/tools/browser/launch.ts +76 -19
  208. package/src/tools/browser/readable.ts +9 -9
  209. package/src/tools/browser/tab-supervisor.ts +12 -3
  210. package/src/tools/browser/tab-worker-entry.ts +1 -1
  211. package/src/tools/browser/tab-worker.ts +101 -12
  212. package/src/tools/fetch.ts +1 -1
  213. package/src/tools/glob.ts +4 -2
  214. package/src/tools/path-utils.ts +22 -5
  215. package/src/tools/read.ts +22 -5
  216. package/src/tools/run-scope.ts +290 -4
  217. package/src/tools/shell-tokenize.ts +38 -9
  218. package/src/tools/terminal-output.ts +1 -1
  219. package/src/tools/todo.ts +1 -1
  220. package/src/utils/changelog.ts +5 -4
  221. package/src/utils/clipboard.ts +10 -2
  222. package/src/utils/external-editor.ts +4 -2
  223. package/src/utils/jj.ts +1 -1
  224. package/src/utils/late-cleanup.ts +17 -0
  225. package/src/utils/turndown.ts +1 -2
  226. package/src/vibe/runtime.ts +202 -58
  227. package/src/web/scrapers/arxiv.ts +1 -1
  228. package/src/web/scrapers/go-pkg.ts +1 -1
  229. package/src/web/scrapers/hackage.ts +1 -12
  230. package/src/web/scrapers/iacr.ts +1 -1
  231. package/src/web/scrapers/readthedocs.ts +2 -1
  232. package/src/web/scrapers/twitter.ts +2 -2
  233. package/src/web/scrapers/types.ts +1 -1
  234. package/src/web/scrapers/wikipedia.ts +1 -1
  235. package/src/web/search/providers/browser-headers.ts +12 -39
  236. package/src/web/search/providers/codex.ts +3 -26
  237. package/src/web/search/providers/duckduckgo.ts +1 -1
  238. package/src/web/search/providers/ecosia.ts +2 -2
  239. package/src/web/search/providers/exa.ts +1 -1
  240. package/src/web/search/providers/firecrawl.ts +1 -1
  241. package/src/web/search/providers/google.ts +1 -1
  242. package/src/web/search/providers/mojeek.ts +2 -2
  243. package/src/web/search/providers/perplexity.ts +1 -1
  244. package/src/web/search/providers/public.ts +1 -1
  245. package/src/web/search/providers/startpage.ts +2 -2
  246. package/src/markit/converters/mammoth.d.ts +0 -24
@@ -58,7 +58,6 @@ import type {
58
58
  ImageContent,
59
59
  Message,
60
60
  Model,
61
- ModelUsageHealth,
62
61
  OAuthAccountIdentity,
63
62
  ProviderSessionState,
64
63
  ResetCreditAccountStatus,
@@ -83,6 +82,7 @@ import { modelsAreEqual } from "@oh-my-pi/pi-catalog/models";
83
82
  import { MacOSPowerAssertion } from "@oh-my-pi/pi-natives";
84
83
  import {
85
84
  $env,
85
+ APP_NAME,
86
86
  escapeXmlText,
87
87
  formatDuration,
88
88
  getAgentDbPath,
@@ -101,7 +101,7 @@ import { type AdvisorConfig, type AdvisorRuntimeStatus, loadAdvisorTranscriptCos
101
101
  import { type AsyncJob, AsyncJobManager } from "../async";
102
102
  import { shouldEnableAppendOnlyContext } from "../config/append-only-context-mode";
103
103
  import type { ModelRegistry } from "../config/model-registry";
104
- import { type ResolvedModelRoleValue, resolveModelOverride } from "../config/model-resolver";
104
+ import type { ResolvedModelRoleValue } from "../config/model-resolver";
105
105
  import { expandPromptTemplate, type PromptTemplate } from "../config/prompt-templates";
106
106
  import { buildServiceTierByFamily } from "../config/service-tier";
107
107
  import type { Settings, SkillsSettings } from "../config/settings";
@@ -127,6 +127,7 @@ import type {
127
127
  ToolExecutionEndEvent,
128
128
  ToolExecutionStartEvent,
129
129
  ToolExecutionUpdateEvent,
130
+ ToolInfo,
130
131
  TreePreparation,
131
132
  TurnEndEvent,
132
133
  TurnStartEvent,
@@ -230,7 +231,7 @@ import type {
230
231
  SessionHandoffOptions,
231
232
  SessionOAuthAccountList,
232
233
  SessionStats,
233
- UsageFallbackConfirmation,
234
+ UsageFallbackConfirmer,
234
235
  } from "./agent-session-types";
235
236
  import {
236
237
  ASYNC_INLINE_RESULT_MAX_CHARS,
@@ -312,7 +313,6 @@ import {
312
313
  queueChipText,
313
314
  toRestoredQueuedMessage,
314
315
  } from "./queued-messages";
315
- import { formatRetryFallbackSelector, type RetryFallbackSelector } from "./retry-fallback-chains";
316
316
  import { type AdvisorStats, SessionAdvisors, type SessionAdvisorsHost } from "./session-advisors";
317
317
  import type { BuildSessionContextOptions, SessionContext } from "./session-context";
318
318
  import { getRestorableSessionModels } from "./session-context";
@@ -447,6 +447,7 @@ export class AgentSession {
447
447
  // Event subscription state
448
448
  #unsubscribeAgent?: () => void;
449
449
  #cancelExitRecorder?: () => void;
450
+ #cancelFatalRecoveryHint?: () => void;
450
451
  #exitRecorded = false;
451
452
  #unsubscribeAppendOnly?: () => void;
452
453
  #unsubscribeModelRoles?: () => void;
@@ -485,6 +486,7 @@ export class AgentSession {
485
486
 
486
487
  // Retry state
487
488
  readonly #recovery: TurnRecovery;
489
+ #textOutputCommitted = true;
488
490
  #planModeReminderCount = 0;
489
491
  #planModeReminderAwaitingProgress = false;
490
492
  readonly #todo: TodoTracker;
@@ -558,9 +560,13 @@ export class AgentSession {
558
560
 
559
561
  // Model registry for API key resolution
560
562
  #modelRegistry: ModelRegistry;
561
- #usageFallbackConfirmer: ((confirmation: UsageFallbackConfirmation) => Promise<boolean>) | undefined;
562
- #usageReserveApprovedSelector: string | undefined;
563
+ #usageFallbackConfirmer: UsageFallbackConfirmer | undefined;
563
564
  #usagePreflightAbortControllers = new Set<AbortController>();
565
+ #queuedMessageDrainBlocked = false;
566
+ #usagePreflightReadyForNextModelCall = false;
567
+ #usagePreflightReadyModel: Model | undefined;
568
+ #detachUsageBeforeQueueDequeue: (() => void) | undefined;
569
+ #detachUsageBeforeModelCall: (() => void) | undefined;
564
570
 
565
571
  #transformContext: (messages: AgentMessage[], signal?: AbortSignal) => AgentMessage[] | Promise<AgentMessage[]>;
566
572
  #onPayload: SimpleStreamOptions["onPayload"] | undefined;
@@ -768,8 +774,10 @@ export class AgentSession {
768
774
  !this.#canAutoContinueForFollowUp()
769
775
  ? [...this.agent.peekFollowUpQueue()]
770
776
  : [];
777
+ const parkedQueueDrainBlocked = parkedFollowUps.length > 0 && this.#queuedMessageDrainBlocked;
771
778
  if (parkedFollowUps.length > 0) {
772
779
  this.agent.replaceQueues([...this.agent.peekSteeringQueue()], []);
780
+ if (parkedQueueDrainBlocked) this.#queuedMessageDrainBlocked = false;
773
781
  }
774
782
  let finishObservation: ((error?: unknown) => void | Promise<void>) | undefined;
775
783
  try {
@@ -805,6 +813,7 @@ export class AgentSession {
805
813
  [...this.agent.peekSteeringQueue()],
806
814
  [...parkedFollowUps, ...this.agent.peekFollowUpQueue()],
807
815
  );
816
+ this.#queuedMessageDrainBlocked ||= parkedQueueDrainBlocked;
808
817
  }
809
818
  this.#endInFlight(async () => {
810
819
  try {
@@ -830,6 +839,7 @@ export class AgentSession {
830
839
  steering.filter(m => !isAdvisorCard(m)),
831
840
  followUp.filter(m => !isAdvisorCard(m)),
832
841
  );
842
+ this.#reconcileQueuedMessageDrain();
833
843
  return cards;
834
844
  }
835
845
 
@@ -1025,6 +1035,7 @@ export class AgentSession {
1025
1035
  modelRegistry: this.#modelRegistry,
1026
1036
  configWarnings: this.configWarnings,
1027
1037
  model: () => this.model,
1038
+ textOutputCommitted: () => this.#textOutputCommitted,
1028
1039
  thinkingLevel: () => this.thinkingLevel,
1029
1040
  configuredThinkingLevel: () => this.configuredThinkingLevel(),
1030
1041
  setThinkingLevel: level => this.setThinkingLevel(level),
@@ -1050,6 +1061,31 @@ export class AgentSession {
1050
1061
  withBashBranchTransition: operation => this.#bash.withBranchTransition(operation),
1051
1062
  };
1052
1063
  this.#recovery = new TurnRecovery(recoveryHost, { initialRetryFallback: config.initialRetryFallback });
1064
+ this.#detachUsageBeforeQueueDequeue = this.agent.addBeforeQueuedMessageDequeueHook(async signal => {
1065
+ if (
1066
+ !this.settings.get("retry.usageAwareFallback") ||
1067
+ (this.#usagePreflightReadyForNextModelCall && this.#usagePreflightReadyModel === this.model)
1068
+ ) {
1069
+ return;
1070
+ }
1071
+ if (!(await this.#runQueuedUsageAwarePreflight(signal))) {
1072
+ signal?.throwIfAborted();
1073
+ throw new DOMException("Usage preflight cancelled", "AbortError");
1074
+ }
1075
+ });
1076
+ this.#detachUsageBeforeModelCall = this.agent.addBeforeModelCallHook(async signal => {
1077
+ if (!this.settings.get("retry.usageAwareFallback")) return;
1078
+ if (this.#usagePreflightReadyForNextModelCall) {
1079
+ const checkedModel = this.#usagePreflightReadyModel;
1080
+ this.#usagePreflightReadyForNextModelCall = false;
1081
+ this.#usagePreflightReadyModel = undefined;
1082
+ if (checkedModel === this.model) return;
1083
+ }
1084
+ if (!(await this.#runUsageAwarePreflight(signal))) {
1085
+ signal?.throwIfAborted();
1086
+ throw new DOMException("Usage preflight cancelled", "AbortError");
1087
+ }
1088
+ });
1053
1089
  const statsHost: SessionStatsTrackerHost = {
1054
1090
  session: this,
1055
1091
  agent: this.agent,
@@ -1343,6 +1379,14 @@ export class AgentSession {
1343
1379
  this.#cancelExitRecorder = postmortem.register(`agent-session:${this.sessionManager.getSessionId()}`, reason => {
1344
1380
  this.#recordSessionExit(reason);
1345
1381
  });
1382
+ this.#cancelFatalRecoveryHint = postmortem.registerFatalRecoveryHint(() => {
1383
+ const sessionId = this.sessionManager.getSessionId();
1384
+ if (!sessionId || !this.sessionManager.getSessionFile()) return undefined;
1385
+ return {
1386
+ label: this.#agentId ?? (this.#agentKind === "main" ? "Main" : "Agent"),
1387
+ command: `${APP_NAME} --resume ${sessionId}`,
1388
+ };
1389
+ });
1346
1390
 
1347
1391
  const advisorsHost: SessionAdvisorsHost = {
1348
1392
  agent: this.agent,
@@ -2658,14 +2702,21 @@ export class AgentSession {
2658
2702
  // outside the session transcript (issue #6177).
2659
2703
  logProviderTurnError(msg);
2660
2704
 
2661
- // Invalidate GitHub Copilot credentials on auth failure so stale tokens
2662
- // aren't reused on the next request
2663
- if (
2664
- msg.stopReason === "error" &&
2665
- msg.provider === "github-copilot" &&
2666
- AIError.is(AIError.classifyMessage(msg), AIError.Flag.AuthFailed)
2667
- ) {
2668
- await this.#modelRegistry.authStorage.remove("github-copilot");
2705
+ // Invalidate GitHub Copilot credentials on a hard auth failure (401, or an
2706
+ // expired/revoked token) so stale tokens aren't reused on the next request.
2707
+ // Account usage caps and concurrency caps leave the credential valid: the
2708
+ // former rotates until its reset window, while the latter is retried after
2709
+ // a short backoff without touching the credential pool.
2710
+ if (msg.stopReason === "error" && msg.provider === "github-copilot") {
2711
+ const errorId = AIError.classifyMessage(msg);
2712
+ const isConcurrencyCap = AIError.parseRateLimitReason(msg.errorMessage ?? "") === "CONCURRENT_LIMIT";
2713
+ if (
2714
+ AIError.is(errorId, AIError.Flag.AuthFailed) &&
2715
+ !AIError.is(errorId, AIError.Flag.UsageLimit) &&
2716
+ !isConcurrencyCap
2717
+ ) {
2718
+ await this.#modelRegistry.authStorage.remove("github-copilot");
2719
+ }
2669
2720
  }
2670
2721
 
2671
2722
  if (this.#maintenance.skipPostTurnMaintenanceAssistantTimestamp === msg.timestamp) {
@@ -2967,19 +3018,17 @@ export class AgentSession {
2967
3018
  this.#beginInFlight();
2968
3019
  try {
2969
3020
  await this.#recovery.maybeRestoreRetryFallbackPrimary();
2970
- if (
2971
- this.settings.get("retry.modelFallback") &&
2972
- this.settings.get("retry.usageAwareFallback") &&
2973
- !(await this.#runUsageAwarePreflight())
2974
- ) {
2975
- this.#skipAgentContinue("session-unavailable", options);
2976
- return;
2977
- }
2978
3021
  if (signal.aborted || this.#isDisposed) {
2979
3022
  this.#skipAgentContinue("post-restore-unavailable", options);
2980
3023
  return;
2981
3024
  }
2982
- await this.agent.continue();
3025
+ if (this.settings.get("retry.usageAwareFallback")) {
3026
+ if (!(await this.#runQueuedUsageAwarePreflight(signal))) {
3027
+ this.#skipAgentContinue("session-unavailable", options);
3028
+ return;
3029
+ }
3030
+ }
3031
+ await this.agent.continue(signal);
2983
3032
  } catch (error) {
2984
3033
  logger.warn("agent.continue failed after scheduling", {
2985
3034
  error: error instanceof Error ? error.message : String(error),
@@ -2987,6 +3036,7 @@ export class AgentSession {
2987
3036
  });
2988
3037
  options?.onError?.(error);
2989
3038
  } finally {
3039
+ this.#usagePreflightReadyForNextModelCall = false;
2990
3040
  this.#endInFlight();
2991
3041
  }
2992
3042
  },
@@ -3605,6 +3655,12 @@ export class AgentSession {
3605
3655
  */
3606
3656
  beginDispose(): void {
3607
3657
  this.#isDisposed = true;
3658
+ this.#queuedMessageDrainBlocked = false;
3659
+ this.#usagePreflightReadyForNextModelCall = false;
3660
+ this.#detachUsageBeforeQueueDequeue?.();
3661
+ this.#detachUsageBeforeQueueDequeue = undefined;
3662
+ this.#detachUsageBeforeModelCall?.();
3663
+ this.#detachUsageBeforeModelCall = undefined;
3608
3664
  this.#memory.cancelLocalMemoryStartup();
3609
3665
  this.#titleGenerationAbortController.abort();
3610
3666
  this.#abortAutolearnCapture();
@@ -3713,6 +3769,8 @@ export class AgentSession {
3713
3769
  this.#recordSessionExit(options.reason ?? "dispose");
3714
3770
  this.#cancelExitRecorder?.();
3715
3771
  this.#cancelExitRecorder = undefined;
3772
+ this.#cancelFatalRecoveryHint?.();
3773
+ this.#cancelFatalRecoveryHint = undefined;
3716
3774
  try {
3717
3775
  await emitSessionShutdownEvent(this.#extensionRunner);
3718
3776
  } catch (error) {
@@ -3926,174 +3984,69 @@ export class AgentSession {
3926
3984
  }
3927
3985
 
3928
3986
  /** Install the interactive decision surface for reserve-triggered model changes. */
3929
- setUsageFallbackConfirmer(
3930
- confirmer: ((confirmation: UsageFallbackConfirmation) => Promise<boolean>) | undefined,
3931
- ): void {
3987
+ setUsageFallbackConfirmer(confirmer: UsageFallbackConfirmer | undefined): void {
3932
3988
  this.#usageFallbackConfirmer = confirmer;
3933
3989
  }
3934
3990
 
3935
- async #runUsageAwarePreflight(): Promise<boolean> {
3936
- const generation = this.#promptGeneration;
3937
- const controller = new AbortController();
3938
- this.#usagePreflightAbortControllers.add(controller);
3939
- try {
3940
- await this.#maybeApplyUsageAwareFallback(controller.signal);
3941
- return !controller.signal.aborted && this.#promptGeneration === generation;
3942
- } catch (error) {
3943
- if (controller.signal.aborted || this.#promptGeneration !== generation) return false;
3944
- throw error;
3945
- } finally {
3946
- this.#usagePreflightAbortControllers.delete(controller);
3947
- }
3991
+ #allowQueuedMessageDrainRetry(): void {
3992
+ this.#queuedMessageDrainBlocked = false;
3948
3993
  }
3949
3994
 
3950
- async #confirmUsageFallback(confirmation: UsageFallbackConfirmation, signal: AbortSignal): Promise<boolean> {
3951
- const confirmer = this.#usageFallbackConfirmer;
3952
- if (!confirmer || signal.aborted) return false;
3953
- const aborted = Promise.withResolvers<boolean>();
3954
- const onAbort = () => aborted.resolve(false);
3955
- signal.addEventListener("abort", onAbort, { once: true });
3956
- try {
3957
- return await Promise.race([confirmer(confirmation), aborted.promise]);
3958
- } finally {
3959
- signal.removeEventListener("abort", onAbort);
3995
+ #reconcileQueuedMessageDrain(): void {
3996
+ if (!this.agent.hasQueuedMessages()) {
3997
+ this.#queuedMessageDrainBlocked = false;
3960
3998
  }
3961
3999
  }
3962
4000
 
3963
- async #maybeApplyUsageAwareFallback(signal: AbortSignal): Promise<void> {
3964
- if (!this.settings.get("retry.modelFallback") || !this.settings.get("retry.usageAwareFallback")) return;
3965
- const currentModel = this.model;
3966
- if (!currentModel) return;
3967
- const currentSelector = formatRetryFallbackSelector(currentModel, this.thinkingLevel);
3968
- let health: ModelUsageHealth;
4001
+ async #runQueuedUsageAwarePreflight(signal?: AbortSignal): Promise<boolean> {
3969
4002
  try {
3970
- health = await this.#modelRegistry.authStorage.getModelUsageHealth(currentModel.provider, {
3971
- modelId: currentModel.id,
3972
- sessionId: this.sessionId,
3973
- baseUrl: currentModel.baseUrl,
3974
- reserveFraction: this.settings.get("retry.usageReservePct") / 100,
3975
- signal,
3976
- });
4003
+ const allowed = await this.#runUsageAwarePreflight(signal);
4004
+ this.#usagePreflightReadyForNextModelCall = allowed;
4005
+ this.#usagePreflightReadyModel = allowed ? this.model : undefined;
4006
+ this.#queuedMessageDrainBlocked = !allowed && this.agent.hasQueuedMessages();
4007
+ return allowed;
3977
4008
  } catch (error) {
3978
- logger.debug("Usage-aware runtime preflight failed open", {
3979
- provider: currentModel.provider,
3980
- model: currentModel.id,
3981
- error: String(error),
3982
- });
3983
- return;
4009
+ this.#queuedMessageDrainBlocked = this.agent.hasQueuedMessages();
4010
+ throw error;
3984
4011
  }
3985
- if (signal.aborted) return;
4012
+ }
3986
4013
 
3987
- if (health.state === "healthy") {
3988
- this.#usageReserveApprovedSelector = undefined;
3989
- const selected = health.accounts.find(account => account.selected);
3990
- if (selected && selected.state !== "healthy" && health.accounts.some(account => account.state === "healthy")) {
3991
- this.#modelRegistry.authStorage.releaseSessionCredentialForReselection(
3992
- currentModel.provider,
3993
- this.sessionId,
3994
- );
3995
- }
3996
- return;
3997
- }
3998
- if (health.state === "unknown") {
3999
- this.#usageReserveApprovedSelector = undefined;
4000
- return;
4001
- }
4002
- const reservePolicy = this.settings.get("retry.usageReservePolicy");
4003
- if (reservePolicy === "fail-closed") {
4004
- const condition = health.state === "reserve" ? "reserve reached" : "usage depleted";
4005
- throw new Error(`${condition} for ${currentSelector}; reserve policy is fail-closed.`);
4006
- }
4007
-
4008
- const role = this.#recovery.resolveRetryFallbackRole(currentSelector, currentModel);
4009
- if (!role) return;
4010
- let fallback: { selector: RetryFallbackSelector; apiKey: string } | undefined;
4011
- for (const candidate of this.#recovery.findRetryFallbackCandidates(role, currentSelector, currentModel)) {
4012
- if (this.#recovery.isRetryFallbackSelectorSuppressed(candidate)) continue;
4013
- const resolved = resolveModelOverride([candidate.raw], this.#modelRegistry, this.settings);
4014
- const candidateModel = resolved.model ?? this.#modelRegistry.find(candidate.provider, candidate.id);
4015
- if (!candidateModel) continue;
4016
- if (!this.#modelRegistry.hasConfiguredAuth(candidateModel)) continue;
4017
- try {
4018
- const candidateHealth = await this.#modelRegistry.authStorage.getModelUsageHealth(candidateModel.provider, {
4019
- modelId: candidateModel.id,
4020
- sessionId: this.sessionId,
4021
- baseUrl: candidateModel.baseUrl,
4022
- reserveFraction: this.settings.get("retry.usageReservePct") / 100,
4023
- signal,
4024
- });
4025
- if (signal.aborted) return;
4026
- if (candidateHealth.state === "depleted" || candidateHealth.state === "reserve") continue;
4027
- if (candidateHealth.state === "healthy") {
4028
- const selected = candidateHealth.accounts.find(account => account.selected);
4029
- if (
4030
- selected &&
4031
- selected.state !== "healthy" &&
4032
- candidateHealth.accounts.some(account => account.state === "healthy")
4033
- ) {
4034
- this.#modelRegistry.authStorage.releaseSessionCredentialForReselection(
4035
- candidateModel.provider,
4036
- this.sessionId,
4037
- );
4038
- }
4039
- }
4040
- } catch {
4041
- if (signal.aborted) return;
4042
- // Unknown usage fails open for an otherwise valid fallback.
4043
- }
4044
- if (signal.aborted) return;
4045
- let apiKey: string | undefined;
4046
- try {
4047
- apiKey = await this.#modelRegistry.getApiKey(candidateModel, this.sessionId, { signal });
4048
- } catch {
4049
- if (signal.aborted) return;
4050
- continue;
4051
- }
4052
- if (signal.aborted) return;
4053
- if (!apiKey) continue;
4054
- fallback = { selector: candidate, apiKey };
4055
- break;
4056
- }
4057
- if (!fallback) return;
4058
-
4059
- if (health.state === "reserve") {
4060
- if (reservePolicy === "confirm" && this.#usageFallbackConfirmer) {
4061
- if (this.#usageReserveApprovedSelector === currentSelector) return;
4062
- const selected = health.accounts.find(account => account.selected);
4063
- const remainingFraction =
4064
- selected?.remainingFraction ??
4065
- health.accounts.reduce<number | undefined>(
4066
- (minimum, account) =>
4067
- account.remainingFraction === undefined
4068
- ? minimum
4069
- : minimum === undefined
4070
- ? account.remainingFraction
4071
- : Math.min(minimum, account.remainingFraction),
4072
- undefined,
4014
+ async #runUsageAwarePreflightForNextModelCall(signal?: AbortSignal): Promise<boolean> {
4015
+ const allowed = await this.#runUsageAwarePreflight(signal);
4016
+ this.#usagePreflightReadyForNextModelCall = allowed;
4017
+ this.#usagePreflightReadyModel = allowed ? this.model : undefined;
4018
+ return allowed;
4019
+ }
4020
+
4021
+ async #runUsageAwarePreflight(signal?: AbortSignal): Promise<boolean> {
4022
+ if (signal?.aborted) return false;
4023
+ const generation = this.#promptGeneration;
4024
+
4025
+ const controller = new AbortController();
4026
+ const onAbort = () => controller.abort(signal?.reason);
4027
+ signal?.addEventListener("abort", onAbort, { once: true });
4028
+ this.#usagePreflightAbortControllers.add(controller);
4029
+ try {
4030
+ while (true) {
4031
+ const model = this.model;
4032
+ try {
4033
+ const fallbackCommitted = await this.#recovery.maybeApplyUsageAwareFallback(
4034
+ controller.signal,
4035
+ this.#usageFallbackConfirmer,
4073
4036
  );
4074
- const shouldFallback = await this.#confirmUsageFallback(
4075
- {
4076
- from: currentSelector,
4077
- to: fallback.selector.raw,
4078
- remainingPercent: remainingFraction === undefined ? undefined : Math.max(0, remainingFraction * 100),
4079
- },
4080
- signal,
4081
- );
4082
- if (signal.aborted) return;
4083
- if (!shouldFallback) {
4084
- this.#usageReserveApprovedSelector = currentSelector;
4085
- return;
4037
+ if (fallbackCommitted) return true;
4038
+ if (controller.signal.aborted || this.#promptGeneration !== generation) return false;
4039
+ if (this.model === model || modelsAreEqual(this.model, model)) return true;
4040
+ } catch (error) {
4041
+ if (controller.signal.aborted || this.#promptGeneration !== generation) return false;
4042
+ if (this.model !== model && !modelsAreEqual(this.model, model)) continue;
4043
+ throw error;
4086
4044
  }
4087
4045
  }
4046
+ } finally {
4047
+ signal?.removeEventListener("abort", onAbort);
4048
+ this.#usagePreflightAbortControllers.delete(controller);
4088
4049
  }
4089
-
4090
- if (signal.aborted) return;
4091
- this.#usageReserveApprovedSelector = undefined;
4092
- await this.#recovery.applyRetryFallbackCandidate(role, fallback.selector, currentSelector, {
4093
- pinFallback: true,
4094
- apiKey: fallback.apiKey,
4095
- signal,
4096
- });
4097
4050
  }
4098
4051
 
4099
4052
  /** Effective thinking level applied to the agent (the resolved level when `auto`). */
@@ -4184,6 +4137,11 @@ export class AgentSession {
4184
4137
  return this.agent.state.systemPrompt;
4185
4138
  }
4186
4139
 
4140
+ /** Marks streamed text as committed or buffered for turn-recovery replay decisions. */
4141
+ setTextOutputCommitted(committed: boolean): void {
4142
+ this.#textOutputCommitted = committed;
4143
+ }
4144
+
4187
4145
  /** Current retry attempt (0 if not retrying) */
4188
4146
  get retryAttempt(): number {
4189
4147
  return this.#recovery.attempt;
@@ -4224,6 +4182,11 @@ export class AgentSession {
4224
4182
  return this.#tools.getAllToolNames();
4225
4183
  }
4226
4184
 
4185
+ /** Full metadata for every registered tool, including source provenance (backs `getAllTools()`). */
4186
+ getAllToolInfos(): ToolInfo[] {
4187
+ return this.#tools.getAllToolInfos();
4188
+ }
4189
+
4227
4190
  /** Installs and activates the ephemeral vibe tool set. */
4228
4191
  activateVibeTools(baseToolNames: string[]): Promise<void> {
4229
4192
  return this.#tools.activateVibeTools(baseToolNames);
@@ -5026,7 +4989,7 @@ export class AgentSession {
5026
4989
  if (this.isStreaming) {
5027
4990
  const streamingBehavior = options?.streamingBehavior;
5028
4991
  if (!streamingBehavior) throw new AgentBusyError();
5029
- if (!(await this.#runUsageAwarePreflight())) return false;
4992
+
5030
4993
  // Steer/follow-up the keyword notices BEFORE the queued user message so the
5031
4994
  // model reads the steering notice ahead of the prompt it modifies.
5032
4995
  for (const notice of keywordNotices) {
@@ -5121,7 +5084,7 @@ export class AgentSession {
5121
5084
  if (options?.queueOnly) {
5122
5085
  const streamingBehavior = options?.streamingBehavior;
5123
5086
  if (!streamingBehavior) throw new AgentBusyError();
5124
- if (!(await this.#runUsageAwarePreflight())) return;
5087
+
5125
5088
  for (const notice of keywordNotices) {
5126
5089
  await this.#queueCustomMessage(notice, streamingBehavior);
5127
5090
  }
@@ -5131,7 +5094,7 @@ export class AgentSession {
5131
5094
  if (this.isStreaming) {
5132
5095
  const streamingBehavior = options?.streamingBehavior;
5133
5096
  if (!streamingBehavior) throw new AgentBusyError();
5134
- if (!(await this.#runUsageAwarePreflight())) return;
5097
+
5135
5098
  for (const notice of keywordNotices) {
5136
5099
  await this.#queueCustomMessage(notice, streamingBehavior);
5137
5100
  }
@@ -5168,7 +5131,7 @@ export class AgentSession {
5168
5131
  const generation = this.#promptGeneration;
5169
5132
  try {
5170
5133
  await this.#recovery.maybeRestoreRetryFallbackPrimary();
5171
- if (!(await this.#runUsageAwarePreflight())) return;
5134
+ if (!(await this.#runUsageAwarePreflightForNextModelCall())) return;
5172
5135
  // Flush any pending bash messages before the new prompt
5173
5136
  await this.#bash.flushPending();
5174
5137
  this.#eval.flushPending();
@@ -5379,6 +5342,7 @@ export class AgentSession {
5379
5342
  await this.#waitForPostPromptRecovery(generation);
5380
5343
  }
5381
5344
  } finally {
5345
+ this.#usagePreflightReadyForNextModelCall = false;
5382
5346
  this.#endInFlight();
5383
5347
  }
5384
5348
  }
@@ -5544,7 +5508,6 @@ export class AgentSession {
5544
5508
  }
5545
5509
 
5546
5510
  const expandedText = expandPromptTemplate(text, [...this.#promptTemplates]);
5547
- if (!(await this.#runUsageAwarePreflight())) return;
5548
5511
  await this.#queueUserMessage(expandedText, images, "steer");
5549
5512
  }
5550
5513
 
@@ -5562,7 +5525,6 @@ export class AgentSession {
5562
5525
 
5563
5526
  const expandedText =
5564
5527
  options?.expandPromptTemplates === false ? text : expandPromptTemplate(text, [...this.#promptTemplates]);
5565
- if (!(await this.#runUsageAwarePreflight())) return;
5566
5528
  if (!options?.synthetic) {
5567
5529
  await this.#queueUserMessage(expandedText, images, "followUp");
5568
5530
  return;
@@ -5579,6 +5541,7 @@ export class AgentSession {
5579
5541
  const imageDescriptionNotice = normalizedImages?.length
5580
5542
  ? await this.#buildImageDescriptionNotice(normalizedImages)
5581
5543
  : undefined;
5544
+ this.#allowQueuedMessageDrainRetry();
5582
5545
  if (imageDescriptionNotice) this.agent.followUp(imageDescriptionNotice);
5583
5546
  this.agent.followUp({
5584
5547
  role: "developer",
@@ -5608,6 +5571,7 @@ export class AgentSession {
5608
5571
  const imageDescriptionNotice = normalizedImages?.length
5609
5572
  ? await this.#buildImageDescriptionNotice(normalizedImages)
5610
5573
  : undefined;
5574
+ this.#allowQueuedMessageDrainRetry();
5611
5575
  if (mode === "followUp") {
5612
5576
  if (imageDescriptionNotice) this.agent.followUp(imageDescriptionNotice);
5613
5577
  this.agent.followUp({
@@ -5634,7 +5598,12 @@ export class AgentSession {
5634
5598
  }
5635
5599
 
5636
5600
  #scheduleQueuedMessageDrain(): void {
5637
- if (this.#queuedMessageDrainScheduled || !this.#canAutoContinueForFollowUp() || !this.agent.hasQueuedMessages()) {
5601
+ if (
5602
+ this.#queuedMessageDrainScheduled ||
5603
+ this.#queuedMessageDrainBlocked ||
5604
+ !this.#canAutoContinueForFollowUp() ||
5605
+ !this.agent.hasQueuedMessages()
5606
+ ) {
5638
5607
  return;
5639
5608
  }
5640
5609
  this.#queuedMessageDrainScheduled = true;
@@ -5648,6 +5617,7 @@ export class AgentSession {
5648
5617
  },
5649
5618
  onError: () => {
5650
5619
  this.#queuedMessageDrainScheduled = false;
5620
+ this.#queuedMessageDrainBlocked = this.agent.hasQueuedMessages();
5651
5621
  },
5652
5622
  });
5653
5623
  }
@@ -5784,7 +5754,7 @@ export class AgentSession {
5784
5754
  ): Promise<void> {
5785
5755
  this.#beginInFlight();
5786
5756
  try {
5787
- if (!(await this.#runUsageAwarePreflight())) return;
5757
+ if (!(await this.#runUsageAwarePreflightForNextModelCall())) return;
5788
5758
  const acceptTerminalEmptyStop = options?.acceptTerminalEmptyStop === true;
5789
5759
  if (acceptTerminalEmptyStop) {
5790
5760
  this.#resetPromptMaintenanceState();
@@ -5793,6 +5763,7 @@ export class AgentSession {
5793
5763
  await this.agent.prompt(message);
5794
5764
  await this.#waitForPostPromptRecovery();
5795
5765
  } finally {
5766
+ this.#usagePreflightReadyForNextModelCall = false;
5796
5767
  this.#recovery.setAcceptTerminalEmptyStop(false);
5797
5768
  this.#endInFlight();
5798
5769
  }
@@ -5824,6 +5795,7 @@ export class AgentSession {
5824
5795
  timestamp: Date.now(),
5825
5796
  };
5826
5797
  const normalizedAppMessage = await this.#normalizeAgentMessageImages(appMessage);
5798
+ this.#allowQueuedMessageDrainRetry();
5827
5799
  if (deliverAs === "followUp") {
5828
5800
  this.agent.followUp(normalizedAppMessage);
5829
5801
  } else {
@@ -5880,7 +5852,7 @@ export class AgentSession {
5880
5852
  this.#queueHiddenNextTurnMessage(normalizedAppMessage, options?.triggerTurn ?? false);
5881
5853
  return false;
5882
5854
  }
5883
- if (!(await this.#runUsageAwarePreflight())) return false;
5855
+ this.#allowQueuedMessageDrainRetry();
5884
5856
 
5885
5857
  if (options?.deliverAs === "followUp") {
5886
5858
  this.agent.followUp(normalizedAppMessage);
@@ -5963,8 +5935,6 @@ export class AgentSession {
5963
5935
  if (images.length === 0) images = undefined;
5964
5936
  }
5965
5937
 
5966
- if (options?.deliverAs && !(await this.#runUsageAwarePreflight())) return;
5967
-
5968
5938
  if (options?.deliverAs === "followUp") {
5969
5939
  await this.#queueUserMessage(text, images, "followUp");
5970
5940
  return;
@@ -6004,6 +5974,7 @@ export class AgentSession {
6004
5974
  ? isAdvisorCard
6005
5975
  : m => !isUserQueuedMessage(m) && !isHiddenUserCompanion(m);
6006
5976
  this.agent.replaceQueues(steeringAll.filter(keep), followUpAll.filter(keep));
5977
+ this.#reconcileQueuedMessageDrain();
6007
5978
  return { steering, followUp };
6008
5979
  }
6009
5980
 
@@ -6053,12 +6024,14 @@ export class AgentSession {
6053
6024
  if (fromSteer >= 0) {
6054
6025
  const removed = steering[fromSteer];
6055
6026
  this.agent.replaceQueues(removeWithCompanions(steering, fromSteer), followUp.slice());
6027
+ this.#reconcileQueuedMessageDrain();
6056
6028
  return toRestoredQueuedMessage(removed);
6057
6029
  }
6058
6030
  const fromFollowUp = lastUserIndex(followUp);
6059
6031
  if (fromFollowUp >= 0) {
6060
6032
  const removed = followUp[fromFollowUp];
6061
6033
  this.agent.replaceQueues(steering.slice(), removeWithCompanions(followUp, fromFollowUp));
6034
+ this.#reconcileQueuedMessageDrain();
6062
6035
  return toRestoredQueuedMessage(removed);
6063
6036
  }
6064
6037
  return undefined;
@@ -6348,6 +6321,8 @@ export class AgentSession {
6348
6321
  await this.#memory.resetContextForNewTranscript();
6349
6322
  this.#pendingNextTurnMessages = [];
6350
6323
  this.#scheduledHiddenNextTurnGeneration = undefined;
6324
+ this.#queuedMessageDrainBlocked = false;
6325
+ this.#usagePreflightReadyForNextModelCall = false;
6351
6326
 
6352
6327
  this.sessionManager.appendThinkingLevelChange(this.thinkingLevel, this.configuredThinkingLevel());
6353
6328
  this.sessionManager.appendServiceTierChange(this.#models.serviceTierEntry());
@@ -6712,9 +6687,10 @@ export class AgentSession {
6712
6687
  }
6713
6688
 
6714
6689
  #extractRewindReport(messages: AgentMessage[]): string | undefined {
6715
- if (!this.#checkpointState) return undefined;
6690
+ const checkpointState = this.#checkpointState;
6691
+ if (!checkpointState) return undefined;
6716
6692
  if (this.#pendingRewindReport) return this.#pendingRewindReport;
6717
- for (let i = messages.length - 1; i >= 0; i--) {
6693
+ for (let i = messages.length - 1; i >= checkpointState.checkpointMessageCount; i--) {
6718
6694
  const message = messages[i];
6719
6695
  if (message?.role !== "toolResult" || message.isError) continue;
6720
6696
  const semanticResult = semanticToolResult(message.toolName, message);
@@ -7372,6 +7348,9 @@ export class AgentSession {
7372
7348
  const previousFollowUpMessages = [...this.agent.peekFollowUpQueue()];
7373
7349
  const previousPendingNextTurnMessages = [...this.#pendingNextTurnMessages];
7374
7350
  const previousScheduledHiddenNextTurnGeneration = this.#scheduledHiddenNextTurnGeneration;
7351
+ const previousQueuedMessageDrainBlocked = this.#queuedMessageDrainBlocked;
7352
+ const previousUsagePreflightReadyForNextModelCall = this.#usagePreflightReadyForNextModelCall;
7353
+ const previousUsagePreflightReadyModel = this.#usagePreflightReadyModel;
7375
7354
  const previousModel = this.model;
7376
7355
  const previousThinkingLevel = this.thinkingLevel;
7377
7356
  const previousAutoThinking = this.isAutoThinking;
@@ -7396,6 +7375,9 @@ export class AgentSession {
7396
7375
  this.agent.clearAllQueues();
7397
7376
  this.#pendingNextTurnMessages = [];
7398
7377
  this.#scheduledHiddenNextTurnGeneration = undefined;
7378
+ this.#queuedMessageDrainBlocked = false;
7379
+ this.#usagePreflightReadyForNextModelCall = false;
7380
+ this.#usagePreflightReadyModel = undefined;
7399
7381
 
7400
7382
  try {
7401
7383
  if (switchingToDifferentSession) {
@@ -7564,6 +7546,9 @@ export class AgentSession {
7564
7546
  this.agent.replaceQueues(previousSteeringMessages, previousFollowUpMessages);
7565
7547
  this.#pendingNextTurnMessages = previousPendingNextTurnMessages;
7566
7548
  this.#scheduledHiddenNextTurnGeneration = previousScheduledHiddenNextTurnGeneration;
7549
+ this.#queuedMessageDrainBlocked = previousQueuedMessageDrainBlocked;
7550
+ this.#usagePreflightReadyForNextModelCall = previousUsagePreflightReadyForNextModelCall;
7551
+ this.#usagePreflightReadyModel = previousUsagePreflightReadyModel;
7567
7552
  this.#inheritedProviderPromptCacheKey = previousInheritedProviderPromptCacheKey;
7568
7553
  this.#checkpointState = previousCheckpointState;
7569
7554
  this.#pendingRewindReport = previousPendingRewindReport;
@@ -7652,6 +7637,8 @@ export class AgentSession {
7652
7637
  // Clear pending messages (bound to old session state)
7653
7638
  this.#pendingNextTurnMessages = [];
7654
7639
  this.#scheduledHiddenNextTurnGeneration = undefined;
7640
+ this.#queuedMessageDrainBlocked = false;
7641
+ this.#usagePreflightReadyForNextModelCall = false;
7655
7642
 
7656
7643
  await this.#bash.flushPending();
7657
7644
  // Flush pending writes before branching
@@ -7779,6 +7766,8 @@ export class AgentSession {
7779
7766
  this.#pendingNextTurnMessages = [];
7780
7767
  this.#scheduledHiddenNextTurnGeneration = undefined;
7781
7768
  this.agent.replaceQueues([], []);
7769
+ this.#queuedMessageDrainBlocked = false;
7770
+ this.#usagePreflightReadyForNextModelCall = false;
7782
7771
  await this.#bash.flushPending();
7783
7772
  await this.sessionManager.flush();
7784
7773
  const bashTransition = this.#bash.beginSessionTransition();