@gajae-code/coding-agent 0.15.3 → 0.15.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 (55) hide show
  1. package/CHANGELOG.md +26 -1
  2. package/dist/types/cli/update-cli.d.ts +25 -5
  3. package/dist/types/config/autorouting-tier-map.d.ts +3 -0
  4. package/dist/types/config/settings-schema.d.ts +52 -0
  5. package/dist/types/extensibility/extensions/types.d.ts +5 -0
  6. package/dist/types/extensibility/shared-events.d.ts +2 -0
  7. package/dist/types/gjc-runtime/workflow-placeholder.d.ts +6 -0
  8. package/dist/types/modes/components/hook-selector.d.ts +1 -0
  9. package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +8 -1
  10. package/dist/types/sdk/cli/session-cli.d.ts +42 -1
  11. package/dist/types/sdk/host/host.d.ts +2 -0
  12. package/dist/types/sdk/host/session-runtime.d.ts +4 -0
  13. package/dist/types/session/agent-session.d.ts +4 -2
  14. package/dist/types/session/internal/managed-session-storage.d.ts +4 -3
  15. package/dist/types/tools/ask.d.ts +1 -0
  16. package/dist/types/tools/path-utils.d.ts +5 -6
  17. package/dist/types/tools/tmux-self-injection-guard.d.ts +15 -0
  18. package/package.json +7 -7
  19. package/src/cli/update-cli.ts +102 -1
  20. package/src/commands/sdk.ts +5 -0
  21. package/src/config/autorouting-tier-map.ts +1 -0
  22. package/src/config/model-profiles.ts +5 -5
  23. package/src/config/model-registry.ts +10 -4
  24. package/src/config/settings-schema.ts +53 -0
  25. package/src/config/settings.ts +1 -1
  26. package/src/coordinator-mcp/policy.ts +1 -1
  27. package/src/deep-interview/render-middleware.ts +6 -2
  28. package/src/extensibility/extensions/types.ts +5 -0
  29. package/src/extensibility/shared-events.ts +2 -0
  30. package/src/gjc-runtime/launch-worktree.ts +44 -7
  31. package/src/gjc-runtime/ralplan-runtime.ts +20 -2
  32. package/src/gjc-runtime/tmux-sessions.ts +145 -48
  33. package/src/gjc-runtime/ultragoal-runtime.ts +5 -0
  34. package/src/gjc-runtime/workflow-placeholder.ts +33 -0
  35. package/src/internal-urls/docs-index.generated.ts +10 -9
  36. package/src/modes/components/hook-selector.ts +2 -1
  37. package/src/modes/controllers/extension-ui-controller.ts +1 -0
  38. package/src/modes/prompt-action-autocomplete.ts +26 -47
  39. package/src/sdk/bus/chat-daemon-control.ts +2 -2
  40. package/src/sdk/bus/kind-aware-reconciliation.ts +10 -2
  41. package/src/sdk/bus/prompt-reconciliation.ts +5 -2
  42. package/src/sdk/bus/telegram-daemon-contract.ts +8 -1
  43. package/src/sdk/cli/session-cli.ts +149 -4
  44. package/src/sdk/host/host.ts +7 -0
  45. package/src/sdk/host/session-runtime.test.ts +821 -22
  46. package/src/sdk/host/session-runtime.ts +669 -67
  47. package/src/sdk/router/session-router.ts +69 -15
  48. package/src/session/agent-session.ts +302 -25
  49. package/src/session/internal/managed-session-storage.ts +64 -26
  50. package/src/session/session-manager.ts +26 -8
  51. package/src/tools/ask-contract.ts +54 -3
  52. package/src/tools/ask.ts +46 -6
  53. package/src/tools/bash.ts +14 -0
  54. package/src/tools/path-utils.ts +77 -29
  55. package/src/tools/tmux-self-injection-guard.ts +359 -0
@@ -20,6 +20,7 @@ import * as path from "node:path";
20
20
  import { scheduler } from "node:timers/promises";
21
21
  import * as util from "node:util";
22
22
  import {
23
+ AdaptiveCompactionTracker,
23
24
  type AfterToolCallContext,
24
25
  type AfterToolCallResult,
25
26
  type Agent,
@@ -123,6 +124,7 @@ import {
123
124
  EMPTY_RESPONSE_PROVIDER_CODE,
124
125
  type FallbackAttemptToken,
125
126
  type FallbackTriggerClass,
127
+ SERVER_OVERLOADED_PROVIDER_CODE,
126
128
  STREAM_FIRST_EVENT_TIMEOUT_PROVIDER_CODE,
127
129
  } from "@gajae-code/ai/utils/fallback-transport";
128
130
  import { AttemptRecordStore } from "./attempt-record-store";
@@ -1046,6 +1048,8 @@ export interface PromptOptions {
1046
1048
  onSkillPrepared?: (meta: { name: string; path: string; lineCount?: number; cleanedArgs?: string }) => void;
1047
1049
  /** Optional invocation-scoped cancellation fence used before an accepted skill starts execution. */
1048
1050
  preflightSignal?: AbortSignal;
1051
+ /** Internal SDK lifecycle owner token. */
1052
+ sdkRunToken?: string;
1049
1053
  }
1050
1054
 
1051
1055
  function promptPreflightCancelledError(): Error {
@@ -1296,11 +1300,74 @@ function isBareDefaultMessageOnlyFirstEventTimeout(message: AssistantMessage): b
1296
1300
  BARE_DEFAULT_WATCHDOG_ERROR.test(message.errorMessage ?? ""))
1297
1301
  );
1298
1302
  }
1303
+ /**
1304
+ * True when the attempt's structured evidence is exactly the provider's typed
1305
+ * capacity-overload code and nothing else: no status, no retained retry header,
1306
+ * no second typed code, no error kind, and none of the transport's other
1307
+ * observations (request size, first-event timing, endpoint class, provider retry
1308
+ * ceiling). The code arrives statuslessly (a Codex error event, an HTTP 200
1309
+ * terminal Responses envelope), so these facts ARE the overload rather than
1310
+ * evidence that the attempt did observable work; any additional fact means the
1311
+ * attempt is no longer provably that bare capacity rejection. The code is
1312
+ * compared case-sensitively, matching the parser and the transport gate.
1313
+ */
1314
+ function isExactTypedOverloadFacts(message: AssistantMessage): boolean {
1315
+ if (message.errorKind !== undefined || message.errorStatus !== undefined) return false;
1316
+ const facts = message.transportFailure;
1317
+ if (!facts) return false;
1318
+ return (
1319
+ facts.providerCode === SERVER_OVERLOADED_PROVIDER_CODE &&
1320
+ facts.status === undefined &&
1321
+ facts.anthropicErrorType === undefined &&
1322
+ (facts.openaiErrorCode === undefined || facts.openaiErrorCode === SERVER_OVERLOADED_PROVIDER_CODE) &&
1323
+ (facts.headers === undefined || Object.keys(facts.headers).length === 0) &&
1324
+ facts.requestBytes === undefined &&
1325
+ facts.firstEventElapsedMs === undefined &&
1326
+ facts.firstEventTimeoutMs === undefined &&
1327
+ facts.endpointClass === undefined &&
1328
+ facts.retryMaxAttempts === undefined
1329
+ );
1330
+ }
1331
+ /**
1332
+ * True when transport facts are exactly the provider's statusless typed
1333
+ * capacity-overload code (the provider code verbatim and, when present, the
1334
+ * OpenAI code verbatim, with no status). The agent loop must not treat the new
1335
+ * facts as managed transaction authority, and managed session fallback must
1336
+ * not gain retry/advance authority from them, so this predicate keeps both
1337
+ * guards reading only typed facts, never error prose.
1338
+ */
1339
+ function isStatuslessTypedOverloadFacts(facts: TransportFailureFacts | undefined): boolean {
1340
+ if (!facts) return false;
1341
+ return (
1342
+ facts.status === undefined &&
1343
+ facts.providerCode === SERVER_OVERLOADED_PROVIDER_CODE &&
1344
+ (facts.openaiErrorCode === undefined || facts.openaiErrorCode === SERVER_OVERLOADED_PROVIDER_CODE)
1345
+ );
1346
+ }
1347
+
1299
1348
  function isBareDefaultCodexOverload(message: AssistantMessage): boolean {
1300
1349
  return (
1301
1350
  message.api === "openai-codex-responses" &&
1302
1351
  BARE_DEFAULT_CODEX_OVERLOAD_ERROR.test(message.errorMessage ?? "") &&
1303
- !hasBareDefaultRetryDisqualifyingFacts(message) &&
1352
+ (isExactTypedOverloadFacts(message) || !hasBareDefaultRetryDisqualifyingFacts(message)) &&
1353
+ !assistantMessageHasVisibleOrToolContent(message)
1354
+ );
1355
+ }
1356
+
1357
+ /**
1358
+ * True when a generic OpenAI Responses turn failed with exactly OpenAI's typed
1359
+ * capacity-overload code. Unlike the Codex and Anthropic admissions this reads
1360
+ * no prose at all: the terminal Responses envelope carries the code in typed
1361
+ * transport facts, so the code plus a content-free attempt is the whole
1362
+ * admission. Both `openaiErrorCode` and the derived `providerCode` must be the
1363
+ * exact code, so a different transport's overload facts cannot enter through
1364
+ * this path.
1365
+ */
1366
+ function isBareDefaultOpenAIResponsesOverload(message: AssistantMessage): boolean {
1367
+ return (
1368
+ message.api === "openai-responses" &&
1369
+ message.transportFailure?.openaiErrorCode === SERVER_OVERLOADED_PROVIDER_CODE &&
1370
+ isExactTypedOverloadFacts(message) &&
1304
1371
  !assistantMessageHasVisibleOrToolContent(message)
1305
1372
  );
1306
1373
  }
@@ -2364,6 +2431,8 @@ export class AgentSession {
2364
2431
  this.#followUpPromotionHooks.delete(message);
2365
2432
  followUpHook({ startsOwnRun: true, removed: true });
2366
2433
  }
2434
+ this.#sdkRunTokensByQueuedMessage.delete(message);
2435
+ this.#deepInterviewGenuineUserMessageEpochs.delete(message);
2367
2436
  }
2368
2437
  }
2369
2438
  #fireQueuedPromotionHooks(messages: readonly AgentMessage[], promotion?: { startsOwnRun?: boolean }): void {
@@ -2383,6 +2452,29 @@ export class AgentSession {
2383
2452
  }
2384
2453
  }
2385
2454
  }
2455
+ #queuedMessagesForSessionTransition(): AgentMessage[] {
2456
+ return [
2457
+ ...new Set([
2458
+ ...this.agent.snapshotSteering(),
2459
+ ...this.agent.snapshotFollowUp(),
2460
+ ...this.#deferredSdkFollowUps,
2461
+ ]),
2462
+ ];
2463
+ }
2464
+ /** Drop queued SDK work when the session identity is replaced. The old
2465
+ * promotion hooks belong to the predecessor runtime; retaining the message
2466
+ * would let it execute later under the successor without an owner. */
2467
+ #terminalizeQueuedSdkWorkForSessionTransition(messages: readonly AgentMessage[]): void {
2468
+ if (messages.length === 0) return;
2469
+ this.#fireQueuedRemovalHooks(messages);
2470
+ for (const message of messages) this.#sdkRunTokensByQueuedMessage.delete(message);
2471
+ this.#deferredSdkFollowUps = this.#deferredSdkFollowUps.filter(message => !messages.includes(message));
2472
+ }
2473
+ #resetActiveSdkRunOwnership(): void {
2474
+ this.#activeSdkRunToken = undefined;
2475
+ this.#activeAttemptScope = undefined;
2476
+ this.#activeLogicalRunId = undefined;
2477
+ }
2386
2478
  /** Messages queued to be included with the next user prompt as context ("asides"). */
2387
2479
  #pendingNextTurnMessages: Array<{ message: CustomMessage; origin: "turn" | "external" }> = [];
2388
2480
  #scheduledHiddenNextTurnGeneration: number | undefined = undefined;
@@ -2430,6 +2522,8 @@ export class AgentSession {
2430
2522
  // required to re-trigger. A content signature (not object identity) is used so
2431
2523
  // it survives the message-array rebuild that compaction/prune perform.
2432
2524
  #lastMidRunMaintenanceAnchorSignature: string | undefined = undefined;
2525
+ #adaptiveCompactionTracker = new AdaptiveCompactionTracker();
2526
+ #adaptiveCompactionRecordedMessageKey: string | undefined;
2433
2527
  #resourceSampler: () => EmergencyCompactionSample = () => this.#defaultResourceSample();
2434
2528
  #retainedMemorySampler: (() => RetainedMemorySample) | undefined;
2435
2529
 
@@ -2565,6 +2659,8 @@ export class AgentSession {
2565
2659
  /** SDK follow-up ownership by queued message and the attempt that dequeues it. */
2566
2660
  #sdkRunTokensByQueuedMessage = new WeakMap<AgentMessage, string>();
2567
2661
  #sdkRunTokensByAttemptScope = new WeakMap<AttemptScope, string>();
2662
+ #activeSdkRunToken: string | undefined;
2663
+ #activeAttemptScope: AttemptScope | undefined;
2568
2664
  #attemptAuthority!: AttemptScopeAuthority;
2569
2665
  #attemptRecordStore!: AttemptRecordStore;
2570
2666
  #activeLogicalRunId: AttemptRunHandle["logicalRunId"] | undefined;
@@ -2573,6 +2669,7 @@ export class AgentSession {
2573
2669
  // previously recorded run id rather than throwing inside the callback.
2574
2670
  if (!handle) return;
2575
2671
  this.#activeLogicalRunId = handle.logicalRunId;
2672
+ this.#activeAttemptScope = handle.scope;
2576
2673
  }
2577
2674
 
2578
2675
  #turnIndex = 0;
@@ -3972,7 +4069,20 @@ export class AgentSession {
3972
4069
  // when the batch is drained by a continuation the message did not
3973
4070
  // schedule (a skipped continuation must never discard the correlation
3974
4071
  // of work that is still consumed; review thread P2).
4072
+ const consumedSdkRunToken =
4073
+ promotion.startsOwnRun === true
4074
+ ? [...messages, ...dropped]
4075
+ .map(message => this.#sdkRunTokensByQueuedMessage.get(message))
4076
+ .find((token): token is string => token !== undefined)
4077
+ : undefined;
3975
4078
  this.#fireQueuedPromotionHooks(messages, promotion);
4079
+ if (consumedSdkRunToken !== undefined) this.#activeSdkRunToken = consumedSdkRunToken;
4080
+ // Dropped or in-run follow-ups have no later own-run acceptance callback;
4081
+ // delete those tokens now. Own-run messages stay mapped until
4082
+ // #scheduleAgentContinue.onRunAccepted binds the new attempt scope.
4083
+ for (const message of dropped) this.#sdkRunTokensByQueuedMessage.delete(message);
4084
+ if (promotion.startsOwnRun !== true)
4085
+ for (const message of messages) this.#sdkRunTokensByQueuedMessage.delete(message);
3976
4086
  };
3977
4087
  // Steering consumed mid-run never starts its own run: fire the stored
3978
4088
  // promotion hook at the REAL dequeue boundary so the SDK attaches the
@@ -6365,18 +6475,23 @@ export class AgentSession {
6365
6475
  // agent_start (review P1); their queued messages are in-run
6366
6476
  // consumptions, not own-run promotions.
6367
6477
  const startsOwn = options?.maintenanceContinuation !== true;
6478
+ const consumedSdkRunToken = startsOwn
6479
+ ? acceptance.consumedQueuedMessages
6480
+ .map(message => this.#sdkRunTokensByQueuedMessage.get(message))
6481
+ .find((token): token is string => token !== undefined)
6482
+ : undefined;
6368
6483
  this.#fireQueuedPromotionHooks(acceptance.consumedQueuedMessages, {
6369
6484
  startsOwnRun: startsOwn,
6370
6485
  });
6371
- for (const message of acceptance.consumedQueuedMessages) {
6372
- const sdkRunToken = this.#sdkRunTokensByQueuedMessage.get(message);
6373
- if (sdkRunToken) {
6374
- this.#sdkRunTokensByAttemptScope.set(handle.scope, sdkRunToken);
6375
- break;
6376
- }
6377
- }
6486
+ if (consumedSdkRunToken !== undefined)
6487
+ this.#sdkRunTokensByAttemptScope.set(handle.scope, consumedSdkRunToken);
6488
+ if (startsOwn) this.#activeSdkRunToken = consumedSdkRunToken;
6378
6489
  this.#acceptRunHandle(handle);
6379
6490
  options?.onRunAccepted?.(handle);
6491
+ // Keep the queued token available through the acceptance callback;
6492
+ // SDK follow-up owners bind it to the new attempt scope there.
6493
+ for (const message of acceptance.consumedQueuedMessages)
6494
+ this.#sdkRunTokensByQueuedMessage.delete(message);
6380
6495
  settleLease();
6381
6496
  releasePredecessor();
6382
6497
  if (startsQueuedSuccessor) {
@@ -7438,12 +7553,30 @@ export class AgentSession {
7438
7553
  await this.#flushWorkerIntegrationForAgentEnd();
7439
7554
  }
7440
7555
  const deliveryScope = scope ?? (event as AgentSessionEvent & { scope?: AttemptScopeRef }).scope;
7556
+ const eventToken = (event as AgentSessionEvent & { sdkRunToken?: unknown }).sdkRunToken;
7557
+ const sdkRunToken =
7558
+ typeof eventToken === "string"
7559
+ ? eventToken
7560
+ : deliveryScope
7561
+ ? this.#sdkRunTokensByAttemptScope.get(deliveryScope as AttemptScope)
7562
+ : this.#activeSdkRunToken;
7441
7563
  const isTerminalAgentEnd =
7442
7564
  event.type === "agent_end" && !(event.stopReason === "maintenance" && event.maintenanceOutcome !== "aborted");
7443
7565
  const finishAttempt = () => {
7444
7566
  if (!isTerminalAgentEnd) return;
7445
- if (deliveryScope) this.#attemptRecordStore.retire(deliveryScope as AttemptScope);
7446
- this.#activeLogicalRunId = undefined;
7567
+ const isActiveAttempt =
7568
+ deliveryScope === undefined ||
7569
+ this.#activeAttemptScope === undefined ||
7570
+ deliveryScope === this.#activeAttemptScope;
7571
+ if (deliveryScope) {
7572
+ this.#attemptRecordStore.retire(deliveryScope as AttemptScope);
7573
+ this.#sdkRunTokensByAttemptScope.delete(deliveryScope as AttemptScope);
7574
+ }
7575
+ if (isActiveAttempt && sdkRunToken === this.#activeSdkRunToken) this.#activeSdkRunToken = undefined;
7576
+ if (isActiveAttempt) {
7577
+ this.#activeAttemptScope = undefined;
7578
+ this.#activeLogicalRunId = undefined;
7579
+ }
7447
7580
  };
7448
7581
  if (!this.#extensionRunner) {
7449
7582
  finishAttempt();
@@ -7456,9 +7589,7 @@ export class AgentSession {
7456
7589
  await this.#extensionRunner.emit(
7457
7590
  {
7458
7591
  type: "agent_start",
7459
- ...(deliveryScope
7460
- ? { sdkRunToken: this.#sdkRunTokensByAttemptScope.get(deliveryScope as AttemptScope) }
7461
- : {}),
7592
+ ...(sdkRunToken ? { sdkRunToken } : {}),
7462
7593
  },
7463
7594
  undefined,
7464
7595
  deliveryScope,
@@ -7469,6 +7600,7 @@ export class AgentSession {
7469
7600
  type: "agent_failed",
7470
7601
  error: sanitizePromptFailure(event.error),
7471
7602
  scope: event.scope,
7603
+ ...(sdkRunToken ? { sdkRunToken } : {}),
7472
7604
  },
7473
7605
  undefined,
7474
7606
  deliveryScope,
@@ -7480,6 +7612,7 @@ export class AgentSession {
7480
7612
  messages: event.messages,
7481
7613
  stopReason: event.stopReason,
7482
7614
  maintenanceOutcome: event.maintenanceOutcome,
7615
+ ...(sdkRunToken ? { sdkRunToken } : {}),
7483
7616
  },
7484
7617
  undefined,
7485
7618
  deliveryScope,
@@ -7702,6 +7835,7 @@ export class AgentSession {
7702
7835
  * such event.
7703
7836
  */
7704
7837
  #syncAgentSessionId(sessionId?: string): void {
7838
+ this.#resetAdaptiveCompactionState();
7705
7839
  this.#reasoningControlContextGeneration++;
7706
7840
  const sid = this.#providerSessionId ?? sessionId ?? this.sessionManager.getSessionId();
7707
7841
  this.agent.sessionId = sid;
@@ -9567,7 +9701,7 @@ export class AgentSession {
9567
9701
  args = "",
9568
9702
  options?: Pick<
9569
9703
  PromptOptions,
9570
- "onPreflightAccepted" | "onPreflightAcceptCommit" | "onSkillPrepared" | "preflightSignal"
9704
+ "onPreflightAccepted" | "onPreflightAcceptCommit" | "onSkillPrepared" | "preflightSignal" | "sdkRunToken"
9571
9705
  >,
9572
9706
  ): Promise<{ name: string; path: string; args?: string; lineCount?: number }> {
9573
9707
  if (options?.preflightSignal?.aborted) throw promptPreflightCancelledError();
@@ -10326,6 +10460,7 @@ export class AgentSession {
10326
10460
  ? { onPreflightAcceptCommit: options.onPreflightAcceptCommit }
10327
10461
  : {}),
10328
10462
  ...(options.preflightSignal ? { preflightSignal: options.preflightSignal } : {}),
10463
+ ...(options.sdkRunToken ? { sdkRunToken: options.sdkRunToken } : {}),
10329
10464
  }
10330
10465
  : undefined,
10331
10466
  );
@@ -10579,6 +10714,7 @@ export class AgentSession {
10579
10714
  | "onPreflightAccepted"
10580
10715
  | "onPreflightAcceptCommit"
10581
10716
  | "preflightSignal"
10717
+ | "sdkRunToken"
10582
10718
  >,
10583
10719
  ): Promise<void> {
10584
10720
  if (options?.preflightSignal?.aborted) throw promptPreflightCancelledError();
@@ -10680,6 +10816,7 @@ export class AgentSession {
10680
10816
  | "onPreflightAccepted"
10681
10817
  | "onPreflightAcceptCommit"
10682
10818
  | "preflightSignal"
10819
+ | "sdkRunToken"
10683
10820
  > & {
10684
10821
  prependMessages?: AgentMessage[];
10685
10822
  skipPostPromptRecoveryWait?: boolean;
@@ -11013,8 +11150,13 @@ export class AgentSession {
11013
11150
  const agentPromptOptions = {
11014
11151
  ...(options?.toolChoice ? { toolChoice: options.toolChoice } : undefined),
11015
11152
  ...this.#managedFallbackPromptOptions(),
11153
+ ...(options?.sdkRunToken ? { sdkRunToken: options.sdkRunToken } : {}),
11016
11154
  onRunAccepted: (handle: AttemptRunHandle) => {
11017
11155
  this.#acceptRunHandle(handle);
11156
+ if (options?.sdkRunToken) {
11157
+ this.#sdkRunTokensByAttemptScope.set(handle.scope, options.sdkRunToken);
11158
+ this.#activeSdkRunToken = options.sdkRunToken;
11159
+ }
11018
11160
  options?.onRunAccepted?.(handle);
11019
11161
  options?.admissionLease?.release();
11020
11162
  // R3.3: the accepted-run wrapper is the exact acceptance boundary —
@@ -12106,6 +12248,7 @@ export class AgentSession {
12106
12248
  await this.prompt(text, {
12107
12249
  expandPromptTemplates: false,
12108
12250
  images,
12251
+ sdkRunToken: options?.sdkRunToken,
12109
12252
  onPreflightAccepted: () => {
12110
12253
  options?.onPreflightAccepted?.();
12111
12254
  fireQueuedPromotion();
@@ -13457,6 +13600,8 @@ export class AgentSession {
13457
13600
  this.#cancelOwnAsyncJobs();
13458
13601
  this.#closeAllProviderSessions("new session");
13459
13602
  this.#rebindProviderSessionState(new Map());
13603
+ this.#terminalizeQueuedSdkWorkForSessionTransition(this.#queuedMessagesForSessionTransition());
13604
+ this.#resetActiveSdkRunOwnership();
13460
13605
  this.agent.reset();
13461
13606
  if (!options?.drop) await this.sessionManager.flush();
13462
13607
  const noLeasePreviousSessionIdentity = this.sessionManager.getSessionId();
@@ -13554,6 +13699,8 @@ export class AgentSession {
13554
13699
  this.#disconnectFromAgent();
13555
13700
  this.#closeAllProviderSessions("new session");
13556
13701
  this.#rebindProviderSessionState(new Map());
13702
+ this.#terminalizeQueuedSdkWorkForSessionTransition(this.#queuedMessagesForSessionTransition());
13703
+ this.#resetActiveSdkRunOwnership();
13557
13704
  this.agent.reset();
13558
13705
  this.setTodoPhases([]);
13559
13706
  this.#syncAgentSessionId();
@@ -13676,6 +13823,8 @@ export class AgentSession {
13676
13823
  this.yieldQueue.clear();
13677
13824
  this.#pendingBackgroundExchanges = [];
13678
13825
  this.#closeAllProviderSessions("context clear");
13826
+ this.#terminalizeQueuedSdkWorkForSessionTransition(this.#queuedMessagesForSessionTransition());
13827
+ this.#resetActiveSdkRunOwnership();
13679
13828
  this.agent.reset();
13680
13829
  await this.sessionManager.flush();
13681
13830
  this.sessionManager.appendContextClearEntry({ sessionId });
@@ -15857,6 +16006,7 @@ export class AgentSession {
15857
16006
  fromExtension,
15858
16007
  preserveData,
15859
16008
  );
16009
+ this.#recordAdaptiveCompactionReset(tokensBefore);
15860
16010
  await this.#applyCompactionPostAppend(compactionEntryId, firstKeptEntryId, fromExtension);
15861
16011
 
15862
16012
  const compactionResult: CompactionResult = {
@@ -16099,6 +16249,7 @@ export class AgentSession {
16099
16249
  const rollbackPendingNextTurnMessages = [...this.#pendingNextTurnMessages];
16100
16250
  const rollbackScheduledHiddenNextTurnGeneration = this.#scheduledHiddenNextTurnGeneration;
16101
16251
  const rollbackTodoReminderCount = this.#todoReminderCount;
16252
+ const rollbackDeferredSdkFollowUps = [...this.#deferredSdkFollowUps];
16102
16253
  // Snapshot the agent's executable queues so a rollback restores queued
16103
16254
  // user work that agent.reset() would otherwise clear.
16104
16255
  const rollbackAgentSteeringQueue = this.agent.snapshotSteering();
@@ -16139,6 +16290,12 @@ export class AgentSession {
16139
16290
  this.#rekeyJobManagerForSessionIdentity(rollbackSessionState.sessionId, rollbackSessionState.sessionFile);
16140
16291
  committed = true;
16141
16292
  await this.#runToolSessionTransitionCleanups();
16293
+ this.#terminalizeQueuedSdkWorkForSessionTransition([
16294
+ ...rollbackAgentSteeringQueue,
16295
+ ...rollbackAgentFollowUpQueue,
16296
+ ...rollbackDeferredSdkFollowUps,
16297
+ ]);
16298
+ this.#resetActiveSdkRunOwnership();
16142
16299
  this.agent.reset();
16143
16300
  this.#syncAgentSessionId();
16144
16301
  this.#rekeyHindsightMemoryForCurrentSessionId();
@@ -16222,6 +16379,7 @@ export class AgentSession {
16222
16379
  this.#pendingNextTurnMessages = rollbackPendingNextTurnMessages;
16223
16380
  this.#scheduledHiddenNextTurnGeneration = rollbackScheduledHiddenNextTurnGeneration;
16224
16381
  this.#todoReminderCount = rollbackTodoReminderCount;
16382
+ this.#deferredSdkFollowUps = rollbackDeferredSdkFollowUps;
16225
16383
  this.#syncTodoPhasesFromBranch();
16226
16384
  // Exact-discard only the staged successor; predecessor state was never adopted.
16227
16385
  const rollbackError = prepared
@@ -16380,7 +16538,12 @@ export class AgentSession {
16380
16538
  // which breaks the provider prompt-cache prefix mid-epoch. Only prune at a
16381
16539
  // sanctioned maintenance boundary, i.e. when the un-pruned context already
16382
16540
  // crosses the compaction threshold. Pruning may then avert full compaction.
16383
- if (!shouldCompact(contextTokens, contextWindow, compactionSettings, autoCompactionOutputReserveTokens))
16541
+ const adaptiveCompactionSettings = this.#recordAdaptiveCompactionCall(
16542
+ contextTokens,
16543
+ compactionSettings,
16544
+ assistantMessage,
16545
+ );
16546
+ if (!shouldCompact(contextTokens, contextWindow, adaptiveCompactionSettings, autoCompactionOutputReserveTokens))
16384
16547
  return true;
16385
16548
  const pruneEstimate = estimateToolOutputPruneSavings(this.sessionManager.getBranch(), DEFAULT_PRUNE_CONFIG, {
16386
16549
  relaxedMinimum: 0,
@@ -16391,7 +16554,7 @@ export class AgentSession {
16391
16554
  !shouldCompact(
16392
16555
  Math.max(0, contextTokens - pruneEstimate.tokensSaved),
16393
16556
  contextWindow,
16394
- compactionSettings,
16557
+ adaptiveCompactionSettings,
16395
16558
  autoCompactionOutputReserveTokens,
16396
16559
  )
16397
16560
  ) {
@@ -16399,7 +16562,8 @@ export class AgentSession {
16399
16562
  if (ownershipSignal?.aborted) return false;
16400
16563
  if (pruneResult) contextTokens = Math.max(0, contextTokens - pruneResult.tokensSaved);
16401
16564
  }
16402
- if (shouldCompact(contextTokens, contextWindow, compactionSettings, autoCompactionOutputReserveTokens)) {
16565
+ const prunedCompactionSettings = this.#compactionSettingsWithAdaptiveState(compactionSettings);
16566
+ if (shouldCompact(contextTokens, contextWindow, prunedCompactionSettings, autoCompactionOutputReserveTokens)) {
16403
16567
  // Try promotion first — if a larger model is available, switch instead of compacting
16404
16568
  const promoted = await this.#tryContextPromotion(assistantMessage, ownershipSignal);
16405
16569
  if (ownershipSignal?.aborted) return false;
@@ -16453,7 +16617,7 @@ export class AgentSession {
16453
16617
  }
16454
16618
  if (isAborted()) return result("aborted");
16455
16619
 
16456
- // In-place context-full maintenance only. "off" defers entirely; "handoff"
16620
+ // context-full maintenance. "off" defers entirely; "handoff"
16457
16621
  // keeps its existing agent_end / pre-prompt boundaries (a mid-tool-loop
16458
16622
  // session swap would be far more disruptive than the overflow it avoids).
16459
16623
  const compactionSettings = this.settings.getGroup("compaction");
@@ -16469,7 +16633,12 @@ export class AgentSession {
16469
16633
  const autoCompactionOutputReserveTokens = 0;
16470
16634
  const anchor = this.#findMidRunUsageAnchor(context.messages);
16471
16635
  let contextTokens = this.#estimateMidRunContextTokens(context.messages);
16472
- if (!shouldCompact(contextTokens, contextWindow, compactionSettings, autoCompactionOutputReserveTokens)) {
16636
+ const adaptiveCompactionSettings = anchor
16637
+ ? this.#recordAdaptiveCompactionCall(contextTokens, compactionSettings, anchor.message)
16638
+ : this.#compactionSettingsWithAdaptiveState(compactionSettings);
16639
+ if (
16640
+ !shouldCompact(contextTokens, contextWindow, adaptiveCompactionSettings, autoCompactionOutputReserveTokens)
16641
+ ) {
16473
16642
  return result("not-needed");
16474
16643
  }
16475
16644
  // Anti-loop (#1662): a given provider response anchors at most one
@@ -16503,7 +16672,7 @@ export class AgentSession {
16503
16672
  !shouldCompact(
16504
16673
  Math.max(0, contextTokens - pruneEstimate.tokensSaved),
16505
16674
  contextWindow,
16506
- compactionSettings,
16675
+ adaptiveCompactionSettings,
16507
16676
  autoCompactionOutputReserveTokens,
16508
16677
  )
16509
16678
  ) {
@@ -16512,7 +16681,9 @@ export class AgentSession {
16512
16681
  if (pruneResult?.failure === "artifact_persistence") return result("failed");
16513
16682
  if (pruneResult) contextTokens = Math.max(0, contextTokens - pruneResult.tokensSaved);
16514
16683
  }
16515
- if (!shouldCompact(contextTokens, contextWindow, compactionSettings, autoCompactionOutputReserveTokens)) {
16684
+ if (
16685
+ !shouldCompact(contextTokens, contextWindow, adaptiveCompactionSettings, autoCompactionOutputReserveTokens)
16686
+ ) {
16516
16687
  return pruneResult?.committed ? result("pruned", true) : result("not-needed");
16517
16688
  }
16518
16689
 
@@ -16675,7 +16846,8 @@ export class AgentSession {
16675
16846
  // Model maxTokens is a capability ceiling, not a per-turn reservation.
16676
16847
  // Auto maintenance should track actual context fullness.
16677
16848
  const autoCompactionOutputReserveTokens = 0;
16678
- if (!shouldCompact(contextTokens, contextWindow, compactionSettings, autoCompactionOutputReserveTokens)) {
16849
+ const adaptiveCompactionSettings = this.#compactionSettingsWithAdaptiveState(compactionSettings);
16850
+ if (!shouldCompact(contextTokens, contextWindow, adaptiveCompactionSettings, autoCompactionOutputReserveTokens)) {
16679
16851
  // Below the compaction threshold: optionally run evidence-gated maintenance
16680
16852
  // pruning (opt-in, high savings + cache-epoch payback required).
16681
16853
  await this.#maybeRunBelowThresholdMaintenancePrune();
@@ -16691,14 +16863,15 @@ export class AgentSession {
16691
16863
  !shouldCompact(
16692
16864
  Math.max(0, contextTokens - pruneEstimate.tokensSaved),
16693
16865
  contextWindow,
16694
- compactionSettings,
16866
+ adaptiveCompactionSettings,
16695
16867
  autoCompactionOutputReserveTokens,
16696
16868
  )
16697
16869
  ) {
16698
16870
  const pruneResult = await this.#pruneToolOutputs(undefined, true);
16699
16871
  if (pruneResult) contextTokens = Math.max(0, contextTokens - pruneResult.tokensSaved);
16700
16872
  }
16701
- if (shouldCompact(contextTokens, contextWindow, compactionSettings, autoCompactionOutputReserveTokens)) {
16873
+ const prunedCompactionSettings = this.#compactionSettingsWithAdaptiveState(compactionSettings);
16874
+ if (shouldCompact(contextTokens, contextWindow, prunedCompactionSettings, autoCompactionOutputReserveTokens)) {
16702
16875
  await this.#runAutoCompaction("threshold", false, false, {
16703
16876
  continueAfterMaintenance: false,
16704
16877
  deferHandoffMaintenance: false,
@@ -16706,6 +16879,45 @@ export class AgentSession {
16706
16879
  }
16707
16880
  }
16708
16881
 
16882
+ #recordAdaptiveCompactionCall<T extends { adaptive?: { enabled: boolean; turnWindow: number } }>(
16883
+ contextTokens: number,
16884
+ settings: T,
16885
+ assistantMessage: AssistantMessage,
16886
+ ): T {
16887
+ if (!settings.adaptive?.enabled) return settings;
16888
+ this.#adaptiveCompactionTracker.setWindowMs(settings.adaptive.turnWindow * 60_000);
16889
+ const messageKey = this.#adaptiveCompactionMessageKey(assistantMessage);
16890
+ if (this.#adaptiveCompactionRecordedMessageKey === messageKey) {
16891
+ return this.#compactionSettingsWithAdaptiveState(settings);
16892
+ }
16893
+ this.#adaptiveCompactionRecordedMessageKey = messageKey;
16894
+ this.#adaptiveCompactionTracker.recordCall(contextTokens);
16895
+ return this.#compactionSettingsWithAdaptiveState(settings);
16896
+ }
16897
+
16898
+ #adaptiveCompactionMessageKey(message: AssistantMessage): string {
16899
+ return `${message.provider}\u0000${message.model}\u0000${message.timestamp}\u0000${message.usage?.totalTokens ?? ""}`;
16900
+ }
16901
+
16902
+ #compactionSettingsWithAdaptiveState<T extends { adaptive?: { enabled: boolean } }>(settings: T): T {
16903
+ if (!settings.adaptive?.enabled) return settings;
16904
+ return {
16905
+ ...settings,
16906
+ adaptiveState: this.#adaptiveCompactionTracker.decisionState(),
16907
+ };
16908
+ }
16909
+
16910
+ #resetAdaptiveCompactionState(): void {
16911
+ this.#adaptiveCompactionTracker.reset();
16912
+ this.#adaptiveCompactionRecordedMessageKey = undefined;
16913
+ }
16914
+
16915
+ #recordAdaptiveCompactionReset(contextTokens: number): void {
16916
+ this.#adaptiveCompactionTracker.reset();
16917
+ this.#adaptiveCompactionTracker.recordCompact(contextTokens);
16918
+ this.#adaptiveCompactionRecordedMessageKey = undefined;
16919
+ }
16920
+
16709
16921
  #assistantEndedWithSuccessfulYield(assistantMessage: AssistantMessage): boolean {
16710
16922
  const toolCallId = this.#lastSuccessfulYieldToolCallId;
16711
16923
  if (!toolCallId) return false;
@@ -18156,6 +18368,7 @@ export class AgentSession {
18156
18368
  fromExtension,
18157
18369
  preserveData,
18158
18370
  );
18371
+ this.#recordAdaptiveCompactionReset(tokensBefore);
18159
18372
  await this.#applyCompactionPostAppend(compactionEntryId, firstKeptEntryId, fromExtension);
18160
18373
  if (autoCompactionSignal.aborted) return await emitAborted();
18161
18374
 
@@ -18935,6 +19148,18 @@ export class AgentSession {
18935
19148
  },
18936
19149
  };
18937
19150
  }
19151
+ // Issue #5018 preserves managed behavior for the typed statusless
19152
+ // Responses overload: before the code survived transport, this failure
19153
+ // reached the session as an ordinary committed error, so the chain never
19154
+ // discarded or advanced on it. Route it to the exhaustion decision
19155
+ // directly, before the retryable path can classify its new facts.
19156
+ if (isStatuslessTypedOverloadFacts(outcome.failure.transportFailure)) {
19157
+ this.#defaultFallbackChain().resetAttemptBudget();
19158
+ return this.#managedFallbackExhaustionDecision(
19159
+ outcome.failure.message,
19160
+ outcome.failure.message.errorMessage || "Model fallback attempt failed",
19161
+ );
19162
+ }
18938
19163
  return this.#handleRetryableError(
18939
19164
  outcome.failure.message,
18940
19165
  true,
@@ -19393,7 +19618,10 @@ export class AgentSession {
19393
19618
  }
19394
19619
  const firstEventTimeout = classification === "first_event_timeout";
19395
19620
  const emptyResponse = classification === "empty_response";
19396
- const canReplayProviderOverload = isBareDefaultCodexOverload(message) || isBareDefaultAnthropicOverload(message);
19621
+ const canReplayProviderOverload =
19622
+ isBareDefaultCodexOverload(message) ||
19623
+ isBareDefaultAnthropicOverload(message) ||
19624
+ isBareDefaultOpenAIResponsesOverload(message);
19397
19625
  const reportedRetryMaxAttempts = transportFailure?.retryMaxAttempts;
19398
19626
  if (reportedRetryMaxAttempts !== undefined) {
19399
19627
  this.#providerRetryMaxAttempts = Math.min(
@@ -19436,6 +19664,17 @@ export class AgentSession {
19436
19664
  const trigger:
19437
19665
  | { class: FallbackTriggerClass; retryAfterMs?: number; authDisposition?: AuthDisposition }
19438
19666
  | undefined = this.#fallbackTriggerFor(message, !managedFallback, transportFailure);
19667
+ // OpenAI's typed statusless capacity-overload code (issue #5018) must not
19668
+ // gain managed-chain retry/advance authority from its new facts. Before
19669
+ // the code survived transport, this failure reached the session as an
19670
+ // ordinary committed error and surfaced immediately, so mirror that
19671
+ // behavior with the existing exhaustion decision.
19672
+ if (managedFallback && isStatuslessTypedOverloadFacts(transportFailure)) {
19673
+ return this.#managedFallbackExhaustionDecision(
19674
+ message,
19675
+ message.errorMessage || "Model fallback attempt failed",
19676
+ );
19677
+ }
19439
19678
  if (!trigger) {
19440
19679
  return managedOutcome
19441
19680
  ? this.#managedFallbackExhaustionDecision(message, message.errorMessage || "Model fallback attempt failed")
@@ -19874,6 +20113,7 @@ export class AgentSession {
19874
20113
  preSubmit: PreSubmitBuilder,
19875
20114
  options?: {
19876
20115
  toolChoice?: ToolChoice;
20116
+ sdkRunToken?: string;
19877
20117
  fallbackManaged?: boolean;
19878
20118
  onRunAccepted?: (handle: AttemptRunHandle) => void;
19879
20119
  },
@@ -21173,6 +21413,10 @@ export class AgentSession {
21173
21413
  const previousSystemPrompt = this.agent.state.systemPrompt;
21174
21414
  const previousAgentSteeringQueue = this.agent.snapshotSteering();
21175
21415
  const previousAgentFollowUpQueue = this.agent.snapshotFollowUp();
21416
+ const previousDeferredSdkFollowUps = [...this.#deferredSdkFollowUps];
21417
+ const previousActiveSdkRunToken = this.#activeSdkRunToken;
21418
+ const previousActiveAttemptScope = this.#activeAttemptScope;
21419
+ const previousActiveLogicalRunId = this.#activeLogicalRunId;
21176
21420
 
21177
21421
  this.#steeringMessages = [];
21178
21422
  this.#followUpMessages = [];
@@ -21220,6 +21464,7 @@ export class AgentSession {
21220
21464
  // The target session is loaded and MCP selections are restored: discard
21221
21465
  // pre-switch delivery queues before completing the restored agent state.
21222
21466
  this.agent.clearAllQueues();
21467
+ this.#resetActiveSdkRunOwnership();
21223
21468
 
21224
21469
  if (historyRewriteReason) {
21225
21470
  this.agent.replaceMessages(sessionContext.messages, {
@@ -21404,6 +21649,12 @@ export class AgentSession {
21404
21649
  ...(options?.transition ? { transition: options.transition } : {}),
21405
21650
  });
21406
21651
  }
21652
+ this.#terminalizeQueuedSdkWorkForSessionTransition([
21653
+ ...previousAgentSteeringQueue,
21654
+ ...previousAgentFollowUpQueue,
21655
+ ...previousDeferredSdkFollowUps,
21656
+ ]);
21657
+ this.#deferredSdkFollowUps = [];
21407
21658
  return true;
21408
21659
  } catch (error) {
21409
21660
  if (transitionCleanupCommitted) throw error;
@@ -21469,6 +21720,10 @@ export class AgentSession {
21469
21720
  this.#followUpMessages = previousFollowUpMessages;
21470
21721
  this.#pendingNextTurnMessages = previousPendingNextTurnMessages;
21471
21722
  this.#scheduledHiddenNextTurnGeneration = previousScheduledHiddenNextTurnGeneration;
21723
+ this.#deferredSdkFollowUps = previousDeferredSdkFollowUps;
21724
+ this.#activeSdkRunToken = previousActiveSdkRunToken;
21725
+ this.#activeAttemptScope = previousActiveAttemptScope;
21726
+ this.#activeLogicalRunId = previousActiveLogicalRunId;
21472
21727
  this.agent.clearAllQueues();
21473
21728
  this.agent.restoreSteering(previousAgentSteeringQueue);
21474
21729
  this.agent.restoreFollowUp(previousAgentFollowUpQueue);
@@ -21535,6 +21790,9 @@ export class AgentSession {
21535
21790
  }
21536
21791
 
21537
21792
  const selectedText = this.#extractUserMessageText(selectedEntry.message.content);
21793
+ const previousAgentSteeringQueue = this.agent.snapshotSteering();
21794
+ const previousAgentFollowUpQueue = this.agent.snapshotFollowUp();
21795
+ const previousDeferredSdkFollowUps = [...this.#deferredSdkFollowUps];
21538
21796
 
21539
21797
  let skipConversationRestore = false;
21540
21798
 
@@ -21570,6 +21828,16 @@ export class AgentSession {
21570
21828
  }
21571
21829
  this.#pendingNextTurnMessages = [];
21572
21830
  this.#scheduledHiddenNextTurnGeneration = undefined;
21831
+ this.#terminalizeQueuedSdkWorkForSessionTransition([
21832
+ ...previousAgentSteeringQueue,
21833
+ ...previousAgentFollowUpQueue,
21834
+ ...previousDeferredSdkFollowUps,
21835
+ ]);
21836
+ this.#deferredSdkFollowUps = [];
21837
+ this.#resetActiveSdkRunOwnership();
21838
+ this.agent.clearAllQueues();
21839
+ this.#steeringMessages = [];
21840
+ this.#followUpMessages = [];
21573
21841
 
21574
21842
  this.#syncTodoPhasesFromBranch();
21575
21843
  this.#syncAgentSessionId();
@@ -21775,6 +22043,15 @@ export class AgentSession {
21775
22043
  this.sessionManager.branch(newLeafId);
21776
22044
  }
21777
22045
 
22046
+ // The history rewrite is now committed. Drop predecessor-owned queued SDK
22047
+ // work at this boundary; cancelled or failed preparation above preserves it.
22048
+ const queuedSdkWork = this.#queuedMessagesForSessionTransition();
22049
+ this.#terminalizeQueuedSdkWorkForSessionTransition(queuedSdkWork);
22050
+ this.#deferredSdkFollowUps = [];
22051
+ this.agent.clearAllQueues();
22052
+ this.#steeringMessages = [];
22053
+ this.#followUpMessages = [];
22054
+
21778
22055
  // Update agent state through the canonical filtered display context so legacy
21779
22056
  // request-scoped entries cannot re-enter live history after tree navigation.
21780
22057
  const displayContext = this.buildDisplaySessionContext();