@gajae-code/coding-agent 0.15.2 → 0.15.4
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.
- package/CHANGELOG.md +43 -1
- package/dist/types/config/autorouting-tier-map.d.ts +0 -9
- package/dist/types/config/file-lock.d.ts +7 -1
- package/dist/types/config/keybindings.d.ts +13 -3
- package/dist/types/config/model-registry.d.ts +5 -0
- package/dist/types/config/model-resolver.d.ts +1 -0
- package/dist/types/config/settings-schema.d.ts +65 -1
- package/dist/types/coordinator/contract.d.ts +1 -1
- package/dist/types/coordinator-mcp/question-state.d.ts +42 -2
- package/dist/types/extensibility/extensions/types.d.ts +2 -0
- package/dist/types/gjc-runtime/deep-interview-state.d.ts +12 -0
- package/dist/types/gjc-runtime/workflow-placeholder.d.ts +6 -0
- package/dist/types/main.d.ts +1 -1
- package/dist/types/modes/action-registry.d.ts +1 -0
- package/dist/types/modes/components/hook-selector.d.ts +1 -0
- package/dist/types/modes/components/runtime-mcp-add-wizard.d.ts +1 -1
- package/dist/types/modes/components/welcome.d.ts +2 -0
- package/dist/types/modes/controllers/input-controller.d.ts +16 -1
- package/dist/types/modes/controllers/runtime-mcp-command-controller.d.ts +42 -0
- package/dist/types/modes/interactive-mode.d.ts +7 -0
- package/dist/types/modes/shared/oauth-url-copy.d.ts +9 -0
- package/dist/types/modes/types.d.ts +3 -0
- package/dist/types/modes/ui-language.d.ts +2 -1
- package/dist/types/sdk/broker/lifecycle-ledger.d.ts +1 -0
- package/dist/types/sdk/broker/session-index.d.ts +18 -0
- package/dist/types/sdk/bus/index.d.ts +4 -1
- package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +4 -1
- package/dist/types/sdk/bus/telegram-daemon.d.ts +2 -0
- package/dist/types/sdk/cli/session-cli.d.ts +1 -1
- package/dist/types/sdk/lifecycle/service.d.ts +32 -5
- package/dist/types/sdk/session.d.ts +5 -0
- package/dist/types/session/agent-session.d.ts +4 -0
- package/dist/types/session/fallback-chain-controller.d.ts +8 -0
- package/dist/types/slash-commands/helpers/aside.d.ts +54 -0
- package/dist/types/tools/ask.d.ts +9 -0
- package/dist/types/utils/clipboard.d.ts +9 -1
- package/package.json +7 -7
- package/scripts/generate-sdk-adapter-parity-manifest.ts +5 -2
- package/scripts/generate-sdk-operation-inventory.ts +3 -0
- package/src/commands/sdk.ts +2 -2
- package/src/config/autorouting-tier-map.ts +0 -9
- package/src/config/file-lock.ts +15 -1
- package/src/config/keybindings.ts +13 -3
- package/src/config/model-registry.ts +132 -21
- package/src/config/model-resolver.ts +2 -1
- package/src/config/models-config-schema.ts +2 -2
- package/src/config/settings-schema.ts +66 -1
- package/src/config/settings.ts +1 -1
- package/src/coordinator/contract.ts +1 -0
- package/src/coordinator-mcp/question-state.ts +235 -4
- package/src/coordinator-mcp/server.ts +402 -2
- package/src/dap/client.ts +19 -8
- package/src/deep-interview/render-middleware.ts +6 -2
- package/src/extensibility/extensions/types.ts +2 -0
- package/src/gjc-runtime/deep-interview-stage.ts +20 -1
- package/src/gjc-runtime/deep-interview-state.ts +3 -1
- package/src/gjc-runtime/ralplan-runtime.ts +6 -1
- package/src/gjc-runtime/session-state-lock.ts +11 -2
- package/src/gjc-runtime/ultragoal-runtime.ts +5 -0
- package/src/gjc-runtime/workflow-placeholder.ts +33 -0
- package/src/internal-urls/docs-index.generated.ts +11 -9
- package/src/lsp/client.ts +39 -4
- package/src/main.ts +42 -11
- package/src/modes/action-registry.ts +7 -0
- package/src/modes/components/hook-selector.ts +2 -1
- package/src/modes/components/iterm-pet-transport.ts +4 -2
- package/src/modes/components/runtime-mcp-add-wizard.ts +38 -1
- package/src/modes/components/settings-selector.ts +1 -0
- package/src/modes/components/tool-status-header.ts +149 -41
- package/src/modes/components/welcome.ts +8 -0
- package/src/modes/controllers/extension-ui-controller.ts +1 -0
- package/src/modes/controllers/goal-mode-controller.ts +7 -2
- package/src/modes/controllers/input-controller.ts +141 -7
- package/src/modes/controllers/plan-mode-controller.ts +12 -4
- package/src/modes/controllers/runtime-mcp-command-controller.ts +164 -20
- package/src/modes/controllers/selector-controller.ts +11 -2
- package/src/modes/interactive-mode.ts +88 -1
- package/src/modes/shared/oauth-url-copy.ts +33 -0
- package/src/modes/types.ts +3 -0
- package/src/modes/ui-language.ts +44 -3
- package/src/runtime-mcp/oauth-flow.ts +19 -8
- package/src/runtime-mcp/transports/stdio.ts +27 -13
- package/src/sdk/acp/adapter.ts +29 -2
- package/src/sdk/broker/broker.ts +26 -6
- package/src/sdk/broker/lifecycle-ledger.ts +28 -1
- package/src/sdk/broker/lifecycle.ts +664 -5
- package/src/sdk/broker/session-index.ts +246 -0
- package/src/sdk/broker/transport.ts +1 -0
- package/src/sdk/bus/index.ts +135 -38
- package/src/sdk/bus/natives-augment.d.ts +20 -1
- package/src/sdk/bus/telegram-daemon-contract.ts +4 -1
- package/src/sdk/bus/telegram-daemon.ts +3 -0
- package/src/sdk/cli/session-cli.ts +34 -7
- package/src/sdk/lifecycle/service.ts +166 -11
- package/src/sdk/mcp/server.ts +2 -1
- package/src/sdk/protocol/operation-inventory.generated.json +40 -0
- package/src/sdk/protocol/operation-registry.ts +2 -1
- package/src/sdk/session.ts +91 -7
- package/src/session/agent-session.ts +473 -66
- package/src/session/fallback-chain-controller.ts +17 -0
- package/src/setup/frictionless-onboarding.ts +4 -4
- package/src/slash-commands/aside-command.test.ts +366 -0
- package/src/slash-commands/builtin-registry.ts +19 -0
- package/src/slash-commands/helpers/aside.ts +299 -0
- package/src/tools/ask-contract.ts +54 -3
- package/src/tools/ask.ts +54 -6
- package/src/utils/clipboard.ts +37 -28
|
@@ -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,
|
|
@@ -900,6 +901,8 @@ export interface AgentSessionConfig {
|
|
|
900
901
|
initialMCPToolSelectionIsExplicit?: boolean;
|
|
901
902
|
/** Whether a discoverable built-in selection was explicitly supplied to the constructor, including an empty selection. */
|
|
902
903
|
initialDiscoveredBuiltinToolSelectionIsExplicit?: boolean;
|
|
904
|
+
/** Whether the caller explicitly disabled every built-in tool for this session. */
|
|
905
|
+
explicitEmptyToolSelection?: boolean;
|
|
903
906
|
|
|
904
907
|
/** Whether constructor-provided MCP selections should be persisted immediately. */
|
|
905
908
|
persistInitialMCPToolSelection?: boolean;
|
|
@@ -2142,7 +2145,8 @@ const AGENT_CONTINUE_BUSY_MAX_RESCHEDULES = 50;
|
|
|
2142
2145
|
* in-loop resample budget, so without this cap a deterministic escaper loops
|
|
2143
2146
|
* forever: the fallback chain never sees a charge to exhaust on. Matching the
|
|
2144
2147
|
* agent loop's own per-loop budget (MAX_ESCAPED_NONASCII_RESAMPLES + 1 wire
|
|
2145
|
-
* attempts)
|
|
2148
|
+
* attempts) gives every eligible model two steered retries before one policy
|
|
2149
|
+
* charge advances the chain.
|
|
2146
2150
|
*/
|
|
2147
2151
|
const MAX_ESCAPED_NONASCII_MANAGED_RETRIES = 2;
|
|
2148
2152
|
|
|
@@ -2427,6 +2431,8 @@ export class AgentSession {
|
|
|
2427
2431
|
// required to re-trigger. A content signature (not object identity) is used so
|
|
2428
2432
|
// it survives the message-array rebuild that compaction/prune perform.
|
|
2429
2433
|
#lastMidRunMaintenanceAnchorSignature: string | undefined = undefined;
|
|
2434
|
+
#adaptiveCompactionTracker = new AdaptiveCompactionTracker();
|
|
2435
|
+
#adaptiveCompactionRecordedMessageKey: string | undefined;
|
|
2430
2436
|
#resourceSampler: () => EmergencyCompactionSample = () => this.#defaultResourceSample();
|
|
2431
2437
|
#retainedMemorySampler: (() => RetainedMemorySample) | undefined;
|
|
2432
2438
|
|
|
@@ -2481,8 +2487,11 @@ export class AgentSession {
|
|
|
2481
2487
|
#retryPromise: Promise<void> | undefined = undefined;
|
|
2482
2488
|
#retryResolve: (() => void) | undefined = undefined;
|
|
2483
2489
|
#defaultFallbackController: FallbackChainController | undefined;
|
|
2490
|
+
#fallbackTransitionGeneration = 0;
|
|
2484
2491
|
/** Managed escaped-non-ASCII retries issued for the current logical run. Bounded so a deterministic escaper cannot loop forever through un-charged fallback retries. */
|
|
2485
2492
|
#escapedNonAsciiManagedRetries = 0;
|
|
2493
|
+
/** Concrete models that already exhausted escaped-argument recovery this turn. */
|
|
2494
|
+
#escapedNonAsciiExhaustedModelKeys = new Set<string>();
|
|
2486
2495
|
#overflowMaintenanceAttempts = 0;
|
|
2487
2496
|
#defaultFallbackExhaustedLastTurn = false;
|
|
2488
2497
|
#fallbackInvocationId = 0;
|
|
@@ -2660,6 +2669,7 @@ export class AgentSession {
|
|
|
2660
2669
|
/** Constructor authority applies only while this AgentSession instance remains alive. */
|
|
2661
2670
|
#constructorMCPToolSelection: string[] | undefined;
|
|
2662
2671
|
#constructorDiscoveredBuiltinToolSelection: string[] | undefined;
|
|
2672
|
+
#explicitEmptyToolSelection = false;
|
|
2663
2673
|
#recoveryHydrationContext: RecoveryHydrationContext | undefined;
|
|
2664
2674
|
#memoryGuardClaimsLease: MemoryGuardClaimsLease | undefined;
|
|
2665
2675
|
|
|
@@ -3827,6 +3837,7 @@ export class AgentSession {
|
|
|
3827
3837
|
this.#networkPrewarmService = config.networkPrewarmService;
|
|
3828
3838
|
this.#onWorkspaceTreeReady = config.onWorkspaceTreeReady;
|
|
3829
3839
|
this.#mcpDiscoveryEnabled = config.mcpDiscoveryEnabled ?? false;
|
|
3840
|
+
this.#explicitEmptyToolSelection = config.explicitEmptyToolSelection === true;
|
|
3830
3841
|
const configuredDiscoveryMode = config.settings.get("tools.discoveryMode");
|
|
3831
3842
|
this.#discoveryMode =
|
|
3832
3843
|
config.discoveryMode ??
|
|
@@ -6373,6 +6384,8 @@ export class AgentSession {
|
|
|
6373
6384
|
releasePredecessor();
|
|
6374
6385
|
if (startsQueuedSuccessor) {
|
|
6375
6386
|
this.#defaultFallbackChain().resetAttemptBudget();
|
|
6387
|
+
this.#escapedNonAsciiManagedRetries = 0;
|
|
6388
|
+
this.#escapedNonAsciiExhaustedModelKeys.clear();
|
|
6376
6389
|
this.#overflowMaintenanceAttempts = 0;
|
|
6377
6390
|
}
|
|
6378
6391
|
},
|
|
@@ -7692,6 +7705,7 @@ export class AgentSession {
|
|
|
7692
7705
|
* such event.
|
|
7693
7706
|
*/
|
|
7694
7707
|
#syncAgentSessionId(sessionId?: string): void {
|
|
7708
|
+
this.#resetAdaptiveCompactionState();
|
|
7695
7709
|
this.#reasoningControlContextGeneration++;
|
|
7696
7710
|
const sid = this.#providerSessionId ?? sessionId ?? this.sessionManager.getSessionId();
|
|
7697
7711
|
this.agent.sessionId = sid;
|
|
@@ -8271,6 +8285,11 @@ export class AgentSession {
|
|
|
8271
8285
|
return this.agent.state.tools.map(t => t.name);
|
|
8272
8286
|
}
|
|
8273
8287
|
|
|
8288
|
+
/** Whether this session explicitly requested zero built-in tools. */
|
|
8289
|
+
isExplicitEmptyToolSelection(): boolean {
|
|
8290
|
+
return this.#explicitEmptyToolSelection;
|
|
8291
|
+
}
|
|
8292
|
+
|
|
8274
8293
|
/** Whether the edit tool is registered in this session. */
|
|
8275
8294
|
get hasEditTool(): boolean {
|
|
8276
8295
|
return this.#toolRegistry.has("edit");
|
|
@@ -8902,9 +8921,11 @@ export class AgentSession {
|
|
|
8902
8921
|
? this.#filterSelectableMCPToolNames(sessionContext.selectedMCPToolNames)
|
|
8903
8922
|
: (constructorMCPToolNames ?? this.#getConfiguredDefaultSelectedMCPToolNames());
|
|
8904
8923
|
const constructorDiscoveredBuiltinToolNames = this.#resolveConstructorDiscoveredBuiltinToolSelection();
|
|
8905
|
-
const restoredDiscoveredBuiltinToolNames =
|
|
8906
|
-
?
|
|
8907
|
-
:
|
|
8924
|
+
const restoredDiscoveredBuiltinToolNames = this.#explicitEmptyToolSelection
|
|
8925
|
+
? []
|
|
8926
|
+
: sessionContext.hasPersistedDiscoveredBuiltinToolSelection
|
|
8927
|
+
? this.#selectRestorableDiscoveredBuiltinToolNames(sessionContext.selectedDiscoveredBuiltinToolNames ?? [])
|
|
8928
|
+
: (constructorDiscoveredBuiltinToolNames ?? []);
|
|
8908
8929
|
this.#selectedDiscoveredToolNames = new Set(restoredDiscoveredBuiltinToolNames);
|
|
8909
8930
|
await this.#applyActiveToolsByName(
|
|
8910
8931
|
[...nextActiveNonMCPToolNames, ...restoredMCPToolNames, ...restoredDiscoveredBuiltinToolNames],
|
|
@@ -9845,6 +9866,7 @@ export class AgentSession {
|
|
|
9845
9866
|
}
|
|
9846
9867
|
|
|
9847
9868
|
async #attachAskToolIfWorkflowActive(): Promise<void> {
|
|
9869
|
+
if (this.#explicitEmptyToolSelection) return;
|
|
9848
9870
|
const sessionId = this.sessionManager.getSessionId();
|
|
9849
9871
|
const inMemoryActiveSkill =
|
|
9850
9872
|
this.#activeSkillState && (!this.#activeSkillState.sessionId || this.#activeSkillState.sessionId === sessionId)
|
|
@@ -9877,6 +9899,7 @@ export class AgentSession {
|
|
|
9877
9899
|
}
|
|
9878
9900
|
|
|
9879
9901
|
#attachAskTool(): void {
|
|
9902
|
+
if (this.#explicitEmptyToolSelection) return;
|
|
9880
9903
|
const askTool = this.#toolRegistry.get("ask");
|
|
9881
9904
|
if (!askTool || this.getActiveToolNames().includes(askTool.name)) return;
|
|
9882
9905
|
this.#setGuardedAgentTools([...this.agent.state.tools, askTool]);
|
|
@@ -9959,6 +9982,7 @@ export class AgentSession {
|
|
|
9959
9982
|
}
|
|
9960
9983
|
|
|
9961
9984
|
async #activatePendingGjcGoalModeRequest(): Promise<boolean> {
|
|
9985
|
+
if (this.#explicitEmptyToolSelection) return false;
|
|
9962
9986
|
if (!this.settings.get("goal.enabled")) return false;
|
|
9963
9987
|
const pendingGoal = await consumePendingGoalModeRequest(
|
|
9964
9988
|
this.sessionManager.getCwd(),
|
|
@@ -13850,9 +13874,13 @@ export class AgentSession {
|
|
|
13850
13874
|
role,
|
|
13851
13875
|
this.#formatRoleModelValue(role, model, options?.selector, options?.thinkingLevel),
|
|
13852
13876
|
);
|
|
13853
|
-
|
|
13877
|
+
// Only an explicit user selection starts a new fallback epoch. Internal
|
|
13878
|
+
// fallback switches must preserve the exhausted-model set while advancing.
|
|
13879
|
+
if (role === "default" && (options?.cause ?? "user-selection") === "user-selection") {
|
|
13880
|
+
this.#fallbackTransitionGeneration++;
|
|
13854
13881
|
this.#defaultFallbackController = undefined;
|
|
13855
13882
|
this.#defaultFallbackExhaustedLastTurn = false;
|
|
13883
|
+
this.#escapedNonAsciiExhaustedModelKeys.clear();
|
|
13856
13884
|
}
|
|
13857
13885
|
this.settings.getStorage()?.recordModelUsage(`${model.provider}/${model.id}`);
|
|
13858
13886
|
|
|
@@ -13906,7 +13934,12 @@ export class AgentSession {
|
|
|
13906
13934
|
? this.#modelRegistry.getModelProfile?.(this.#activeModelProfile)
|
|
13907
13935
|
: undefined,
|
|
13908
13936
|
});
|
|
13909
|
-
if (
|
|
13937
|
+
if (
|
|
13938
|
+
!this.#explicitEmptyToolSelection &&
|
|
13939
|
+
eagerTasks &&
|
|
13940
|
+
this.#toolRegistry.has("task") &&
|
|
13941
|
+
!this.getActiveToolNames().includes("task")
|
|
13942
|
+
) {
|
|
13910
13943
|
await this.activateDiscoveredTools(["task"]);
|
|
13911
13944
|
}
|
|
13912
13945
|
await this.refreshBaseSystemPrompt();
|
|
@@ -15828,6 +15861,7 @@ export class AgentSession {
|
|
|
15828
15861
|
fromExtension,
|
|
15829
15862
|
preserveData,
|
|
15830
15863
|
);
|
|
15864
|
+
this.#recordAdaptiveCompactionReset(tokensBefore);
|
|
15831
15865
|
await this.#applyCompactionPostAppend(compactionEntryId, firstKeptEntryId, fromExtension);
|
|
15832
15866
|
|
|
15833
15867
|
const compactionResult: CompactionResult = {
|
|
@@ -16351,7 +16385,12 @@ export class AgentSession {
|
|
|
16351
16385
|
// which breaks the provider prompt-cache prefix mid-epoch. Only prune at a
|
|
16352
16386
|
// sanctioned maintenance boundary, i.e. when the un-pruned context already
|
|
16353
16387
|
// crosses the compaction threshold. Pruning may then avert full compaction.
|
|
16354
|
-
|
|
16388
|
+
const adaptiveCompactionSettings = this.#recordAdaptiveCompactionCall(
|
|
16389
|
+
contextTokens,
|
|
16390
|
+
compactionSettings,
|
|
16391
|
+
assistantMessage,
|
|
16392
|
+
);
|
|
16393
|
+
if (!shouldCompact(contextTokens, contextWindow, adaptiveCompactionSettings, autoCompactionOutputReserveTokens))
|
|
16355
16394
|
return true;
|
|
16356
16395
|
const pruneEstimate = estimateToolOutputPruneSavings(this.sessionManager.getBranch(), DEFAULT_PRUNE_CONFIG, {
|
|
16357
16396
|
relaxedMinimum: 0,
|
|
@@ -16362,7 +16401,7 @@ export class AgentSession {
|
|
|
16362
16401
|
!shouldCompact(
|
|
16363
16402
|
Math.max(0, contextTokens - pruneEstimate.tokensSaved),
|
|
16364
16403
|
contextWindow,
|
|
16365
|
-
|
|
16404
|
+
adaptiveCompactionSettings,
|
|
16366
16405
|
autoCompactionOutputReserveTokens,
|
|
16367
16406
|
)
|
|
16368
16407
|
) {
|
|
@@ -16370,7 +16409,8 @@ export class AgentSession {
|
|
|
16370
16409
|
if (ownershipSignal?.aborted) return false;
|
|
16371
16410
|
if (pruneResult) contextTokens = Math.max(0, contextTokens - pruneResult.tokensSaved);
|
|
16372
16411
|
}
|
|
16373
|
-
|
|
16412
|
+
const prunedCompactionSettings = this.#compactionSettingsWithAdaptiveState(compactionSettings);
|
|
16413
|
+
if (shouldCompact(contextTokens, contextWindow, prunedCompactionSettings, autoCompactionOutputReserveTokens)) {
|
|
16374
16414
|
// Try promotion first — if a larger model is available, switch instead of compacting
|
|
16375
16415
|
const promoted = await this.#tryContextPromotion(assistantMessage, ownershipSignal);
|
|
16376
16416
|
if (ownershipSignal?.aborted) return false;
|
|
@@ -16424,7 +16464,7 @@ export class AgentSession {
|
|
|
16424
16464
|
}
|
|
16425
16465
|
if (isAborted()) return result("aborted");
|
|
16426
16466
|
|
|
16427
|
-
//
|
|
16467
|
+
// context-full maintenance. "off" defers entirely; "handoff"
|
|
16428
16468
|
// keeps its existing agent_end / pre-prompt boundaries (a mid-tool-loop
|
|
16429
16469
|
// session swap would be far more disruptive than the overflow it avoids).
|
|
16430
16470
|
const compactionSettings = this.settings.getGroup("compaction");
|
|
@@ -16440,7 +16480,12 @@ export class AgentSession {
|
|
|
16440
16480
|
const autoCompactionOutputReserveTokens = 0;
|
|
16441
16481
|
const anchor = this.#findMidRunUsageAnchor(context.messages);
|
|
16442
16482
|
let contextTokens = this.#estimateMidRunContextTokens(context.messages);
|
|
16443
|
-
|
|
16483
|
+
const adaptiveCompactionSettings = anchor
|
|
16484
|
+
? this.#recordAdaptiveCompactionCall(contextTokens, compactionSettings, anchor.message)
|
|
16485
|
+
: this.#compactionSettingsWithAdaptiveState(compactionSettings);
|
|
16486
|
+
if (
|
|
16487
|
+
!shouldCompact(contextTokens, contextWindow, adaptiveCompactionSettings, autoCompactionOutputReserveTokens)
|
|
16488
|
+
) {
|
|
16444
16489
|
return result("not-needed");
|
|
16445
16490
|
}
|
|
16446
16491
|
// Anti-loop (#1662): a given provider response anchors at most one
|
|
@@ -16474,7 +16519,7 @@ export class AgentSession {
|
|
|
16474
16519
|
!shouldCompact(
|
|
16475
16520
|
Math.max(0, contextTokens - pruneEstimate.tokensSaved),
|
|
16476
16521
|
contextWindow,
|
|
16477
|
-
|
|
16522
|
+
adaptiveCompactionSettings,
|
|
16478
16523
|
autoCompactionOutputReserveTokens,
|
|
16479
16524
|
)
|
|
16480
16525
|
) {
|
|
@@ -16483,7 +16528,9 @@ export class AgentSession {
|
|
|
16483
16528
|
if (pruneResult?.failure === "artifact_persistence") return result("failed");
|
|
16484
16529
|
if (pruneResult) contextTokens = Math.max(0, contextTokens - pruneResult.tokensSaved);
|
|
16485
16530
|
}
|
|
16486
|
-
if (
|
|
16531
|
+
if (
|
|
16532
|
+
!shouldCompact(contextTokens, contextWindow, adaptiveCompactionSettings, autoCompactionOutputReserveTokens)
|
|
16533
|
+
) {
|
|
16487
16534
|
return pruneResult?.committed ? result("pruned", true) : result("not-needed");
|
|
16488
16535
|
}
|
|
16489
16536
|
|
|
@@ -16646,7 +16693,8 @@ export class AgentSession {
|
|
|
16646
16693
|
// Model maxTokens is a capability ceiling, not a per-turn reservation.
|
|
16647
16694
|
// Auto maintenance should track actual context fullness.
|
|
16648
16695
|
const autoCompactionOutputReserveTokens = 0;
|
|
16649
|
-
|
|
16696
|
+
const adaptiveCompactionSettings = this.#compactionSettingsWithAdaptiveState(compactionSettings);
|
|
16697
|
+
if (!shouldCompact(contextTokens, contextWindow, adaptiveCompactionSettings, autoCompactionOutputReserveTokens)) {
|
|
16650
16698
|
// Below the compaction threshold: optionally run evidence-gated maintenance
|
|
16651
16699
|
// pruning (opt-in, high savings + cache-epoch payback required).
|
|
16652
16700
|
await this.#maybeRunBelowThresholdMaintenancePrune();
|
|
@@ -16662,14 +16710,15 @@ export class AgentSession {
|
|
|
16662
16710
|
!shouldCompact(
|
|
16663
16711
|
Math.max(0, contextTokens - pruneEstimate.tokensSaved),
|
|
16664
16712
|
contextWindow,
|
|
16665
|
-
|
|
16713
|
+
adaptiveCompactionSettings,
|
|
16666
16714
|
autoCompactionOutputReserveTokens,
|
|
16667
16715
|
)
|
|
16668
16716
|
) {
|
|
16669
16717
|
const pruneResult = await this.#pruneToolOutputs(undefined, true);
|
|
16670
16718
|
if (pruneResult) contextTokens = Math.max(0, contextTokens - pruneResult.tokensSaved);
|
|
16671
16719
|
}
|
|
16672
|
-
|
|
16720
|
+
const prunedCompactionSettings = this.#compactionSettingsWithAdaptiveState(compactionSettings);
|
|
16721
|
+
if (shouldCompact(contextTokens, contextWindow, prunedCompactionSettings, autoCompactionOutputReserveTokens)) {
|
|
16673
16722
|
await this.#runAutoCompaction("threshold", false, false, {
|
|
16674
16723
|
continueAfterMaintenance: false,
|
|
16675
16724
|
deferHandoffMaintenance: false,
|
|
@@ -16677,6 +16726,45 @@ export class AgentSession {
|
|
|
16677
16726
|
}
|
|
16678
16727
|
}
|
|
16679
16728
|
|
|
16729
|
+
#recordAdaptiveCompactionCall<T extends { adaptive?: { enabled: boolean; turnWindow: number } }>(
|
|
16730
|
+
contextTokens: number,
|
|
16731
|
+
settings: T,
|
|
16732
|
+
assistantMessage: AssistantMessage,
|
|
16733
|
+
): T {
|
|
16734
|
+
if (!settings.adaptive?.enabled) return settings;
|
|
16735
|
+
this.#adaptiveCompactionTracker.setWindowMs(settings.adaptive.turnWindow * 60_000);
|
|
16736
|
+
const messageKey = this.#adaptiveCompactionMessageKey(assistantMessage);
|
|
16737
|
+
if (this.#adaptiveCompactionRecordedMessageKey === messageKey) {
|
|
16738
|
+
return this.#compactionSettingsWithAdaptiveState(settings);
|
|
16739
|
+
}
|
|
16740
|
+
this.#adaptiveCompactionRecordedMessageKey = messageKey;
|
|
16741
|
+
this.#adaptiveCompactionTracker.recordCall(contextTokens);
|
|
16742
|
+
return this.#compactionSettingsWithAdaptiveState(settings);
|
|
16743
|
+
}
|
|
16744
|
+
|
|
16745
|
+
#adaptiveCompactionMessageKey(message: AssistantMessage): string {
|
|
16746
|
+
return `${message.provider}\u0000${message.model}\u0000${message.timestamp}\u0000${message.usage?.totalTokens ?? ""}`;
|
|
16747
|
+
}
|
|
16748
|
+
|
|
16749
|
+
#compactionSettingsWithAdaptiveState<T extends { adaptive?: { enabled: boolean } }>(settings: T): T {
|
|
16750
|
+
if (!settings.adaptive?.enabled) return settings;
|
|
16751
|
+
return {
|
|
16752
|
+
...settings,
|
|
16753
|
+
adaptiveState: this.#adaptiveCompactionTracker.decisionState(),
|
|
16754
|
+
};
|
|
16755
|
+
}
|
|
16756
|
+
|
|
16757
|
+
#resetAdaptiveCompactionState(): void {
|
|
16758
|
+
this.#adaptiveCompactionTracker.reset();
|
|
16759
|
+
this.#adaptiveCompactionRecordedMessageKey = undefined;
|
|
16760
|
+
}
|
|
16761
|
+
|
|
16762
|
+
#recordAdaptiveCompactionReset(contextTokens: number): void {
|
|
16763
|
+
this.#adaptiveCompactionTracker.reset();
|
|
16764
|
+
this.#adaptiveCompactionTracker.recordCompact(contextTokens);
|
|
16765
|
+
this.#adaptiveCompactionRecordedMessageKey = undefined;
|
|
16766
|
+
}
|
|
16767
|
+
|
|
16680
16768
|
#assistantEndedWithSuccessfulYield(assistantMessage: AssistantMessage): boolean {
|
|
16681
16769
|
const toolCallId = this.#lastSuccessfulYieldToolCallId;
|
|
16682
16770
|
if (!toolCallId) return false;
|
|
@@ -18127,6 +18215,7 @@ export class AgentSession {
|
|
|
18127
18215
|
fromExtension,
|
|
18128
18216
|
preserveData,
|
|
18129
18217
|
);
|
|
18218
|
+
this.#recordAdaptiveCompactionReset(tokensBefore);
|
|
18130
18219
|
await this.#applyCompactionPostAppend(compactionEntryId, firstKeptEntryId, fromExtension);
|
|
18131
18220
|
if (autoCompactionSignal.aborted) return await emitAborted();
|
|
18132
18221
|
|
|
@@ -18586,6 +18675,7 @@ export class AgentSession {
|
|
|
18586
18675
|
},
|
|
18587
18676
|
onManagedAttemptAccepted: () => {
|
|
18588
18677
|
controller.resetAttemptBudget();
|
|
18678
|
+
this.#escapedNonAsciiManagedRetries = 0;
|
|
18589
18679
|
this.#overflowMaintenanceAttempts = 0;
|
|
18590
18680
|
},
|
|
18591
18681
|
onManagedAttemptOutcome: outcome => this.#handleManagedAttemptOutcome(outcome),
|
|
@@ -18596,6 +18686,7 @@ export class AgentSession {
|
|
|
18596
18686
|
// A fresh user turn gets a fresh escaped-non-ASCII retry budget: the
|
|
18597
18687
|
// defect is per-turn wire luck, not a sticky model property.
|
|
18598
18688
|
this.#escapedNonAsciiManagedRetries = 0;
|
|
18689
|
+
this.#escapedNonAsciiExhaustedModelKeys.clear();
|
|
18599
18690
|
const controller = this.#defaultFallbackChain();
|
|
18600
18691
|
if (this.#defaultFallbackExhaustedLastTurn) {
|
|
18601
18692
|
this.#defaultFallbackExhaustedLastTurn = false;
|
|
@@ -18690,6 +18781,11 @@ export class AgentSession {
|
|
|
18690
18781
|
}
|
|
18691
18782
|
|
|
18692
18783
|
async #handleManagedAttemptOutcome(outcome: ManagedAttemptOutcome): Promise<ManagedAttemptDecision> {
|
|
18784
|
+
const activePromptHandle = this.activePromptHandle;
|
|
18785
|
+
const cancellationSignal = activePromptHandle
|
|
18786
|
+
? this.#runCancellationDomains.lookup(activePromptHandle)?.signal
|
|
18787
|
+
: undefined;
|
|
18788
|
+
if (cancellationSignal?.aborted) return { type: "terminal", terminal: { stopReason: "cancelled" } };
|
|
18693
18789
|
if (outcome.type === "run_terminal") {
|
|
18694
18790
|
this.#defaultFallbackChain().resetAttemptBudget();
|
|
18695
18791
|
return { type: "terminal", terminal: { stopReason: outcome.reason } };
|
|
@@ -18699,34 +18795,148 @@ export class AgentSession {
|
|
|
18699
18795
|
// evidence: never charge the attempt, advance the chain, or suppress the
|
|
18700
18796
|
// selector. The loop already removed the defective turn from history and
|
|
18701
18797
|
// bounded its own resample budget, so this decision just re-issues the
|
|
18702
|
-
// same request on the same model.
|
|
18703
|
-
// steering instruction
|
|
18704
|
-
//
|
|
18705
|
-
// its spelling; the instruction never lands in durable history.
|
|
18798
|
+
// same request on the same model. Every bounded re-issue carries the
|
|
18799
|
+
// transient steering instruction, so a deterministic escaper has a reason
|
|
18800
|
+
// to change its spelling; the instruction never lands in durable history.
|
|
18706
18801
|
//
|
|
18707
18802
|
// The retries are un-charged by design, so a deterministic escaper
|
|
18708
18803
|
// would otherwise loop forever: each continuation is a fresh loop with
|
|
18709
|
-
// a fresh in-loop resample budget
|
|
18710
|
-
// charge
|
|
18711
|
-
//
|
|
18712
|
-
// fail-closed answer
|
|
18713
|
-
// rejection. New user turns reset the budget in #resetDefaultFallbackForNewTurn.
|
|
18804
|
+
// a fresh in-loop resample budget. After that bounded recovery budget is
|
|
18805
|
+
// spent, charge the active model once and advance to the next eligible
|
|
18806
|
+
// fallback entry. Only the final eligible model returns the terminal
|
|
18807
|
+
// fail-closed answer.
|
|
18714
18808
|
this.#escapedNonAsciiManagedRetries += 1;
|
|
18715
18809
|
if (this.#escapedNonAsciiManagedRetries > MAX_ESCAPED_NONASCII_MANAGED_RETRIES) {
|
|
18810
|
+
const errorMessage =
|
|
18811
|
+
`Managed fallback retried the escaped non-ASCII tool-call turn ${MAX_ESCAPED_NONASCII_MANAGED_RETRIES} times without a literal-UTF-8 re-issue; ` +
|
|
18812
|
+
`giving up on this model so the next eligible fallback can re-issue with literal UTF-8.`;
|
|
18813
|
+
const controller = this.#defaultFallbackChain();
|
|
18814
|
+
const fallbackFrom = controller.currentSelector();
|
|
18815
|
+
const previousModel = this.model;
|
|
18816
|
+
const previousThinkingLevel = this.thinkingLevel;
|
|
18817
|
+
if (this.model) {
|
|
18818
|
+
this.#escapedNonAsciiExhaustedModelKeys.add(`${this.model.provider}\u0000${this.model.id}`);
|
|
18819
|
+
}
|
|
18820
|
+
controller.discardStartedAttempt();
|
|
18821
|
+
const controllerStateBeforeFailure = controller.snapshotRuntimeState();
|
|
18822
|
+
const advanced = controller.recordEscapedArgumentsFailure(errorMessage, false);
|
|
18823
|
+
this.#escapedNonAsciiManagedRetries = 0;
|
|
18824
|
+
const abortEpoch = this.#abortAdmissionEpoch;
|
|
18825
|
+
if (advanced) {
|
|
18826
|
+
const transitionGeneration = ++this.#fallbackTransitionGeneration;
|
|
18827
|
+
return {
|
|
18828
|
+
type: "retry",
|
|
18829
|
+
continuation: async ownership => {
|
|
18830
|
+
const restoreOwnedTransition = async () => {
|
|
18831
|
+
if (this.#fallbackTransitionGeneration !== transitionGeneration) return;
|
|
18832
|
+
this.#fallbackTransitionGeneration++;
|
|
18833
|
+
if (!this.agent.state.isStreaming)
|
|
18834
|
+
await this.#restoreDefaultFallbackTransition(
|
|
18835
|
+
controller,
|
|
18836
|
+
controllerStateBeforeFailure,
|
|
18837
|
+
previousModel,
|
|
18838
|
+
previousThinkingLevel,
|
|
18839
|
+
);
|
|
18840
|
+
};
|
|
18841
|
+
const continuationCancelled =
|
|
18842
|
+
!ownership.isCurrent() ||
|
|
18843
|
+
ownership.lease.signal.aborted ||
|
|
18844
|
+
cancellationSignal?.aborted ||
|
|
18845
|
+
this.#abortAdmissionEpoch !== abortEpoch ||
|
|
18846
|
+
this.#fallbackTransitionGeneration !== transitionGeneration;
|
|
18847
|
+
if (continuationCancelled) {
|
|
18848
|
+
await restoreOwnedTransition();
|
|
18849
|
+
return;
|
|
18850
|
+
}
|
|
18851
|
+
if (!controller.advance()) {
|
|
18852
|
+
this.#fallbackTransitionGeneration++;
|
|
18853
|
+
this.#defaultFallbackExhaustedLastTurn = true;
|
|
18854
|
+
this.agent.requestRunTerminal(ownership.handle.logicalRunId, {
|
|
18855
|
+
stopReason: "exhausted",
|
|
18856
|
+
messages: [
|
|
18857
|
+
this.#managedFallbackExhaustionMessage(
|
|
18858
|
+
outcome.message,
|
|
18859
|
+
`Managed fallback exhausted every eligible model after escaped non-ASCII tool-call retries; the run fails closed. ${errorMessage}`,
|
|
18860
|
+
),
|
|
18861
|
+
],
|
|
18862
|
+
});
|
|
18863
|
+
return;
|
|
18864
|
+
}
|
|
18865
|
+
const switched = await this.#advanceDefaultFallback(
|
|
18866
|
+
controller,
|
|
18867
|
+
"escaped_non_ascii",
|
|
18868
|
+
1,
|
|
18869
|
+
cancellationSignal,
|
|
18870
|
+
abortEpoch,
|
|
18871
|
+
controllerStateBeforeFailure,
|
|
18872
|
+
previousModel,
|
|
18873
|
+
previousThinkingLevel,
|
|
18874
|
+
{ emitSwitchEvent: false },
|
|
18875
|
+
transitionGeneration,
|
|
18876
|
+
);
|
|
18877
|
+
if (!switched) {
|
|
18878
|
+
if (
|
|
18879
|
+
this.#abortAdmissionEpoch !== abortEpoch ||
|
|
18880
|
+
cancellationSignal?.aborted ||
|
|
18881
|
+
this.#fallbackTransitionGeneration !== transitionGeneration
|
|
18882
|
+
) {
|
|
18883
|
+
await restoreOwnedTransition();
|
|
18884
|
+
return;
|
|
18885
|
+
}
|
|
18886
|
+
this.#fallbackTransitionGeneration++;
|
|
18887
|
+
this.#defaultFallbackExhaustedLastTurn = true;
|
|
18888
|
+
this.agent.requestRunTerminal(ownership.handle.logicalRunId, {
|
|
18889
|
+
stopReason: "exhausted",
|
|
18890
|
+
messages: [
|
|
18891
|
+
this.#managedFallbackExhaustionMessage(
|
|
18892
|
+
outcome.message,
|
|
18893
|
+
`Managed fallback exhausted every eligible model after escaped non-ASCII tool-call retries; the run fails closed. ${errorMessage}`,
|
|
18894
|
+
),
|
|
18895
|
+
],
|
|
18896
|
+
});
|
|
18897
|
+
return;
|
|
18898
|
+
}
|
|
18899
|
+
const continuation = this.agent.continue({
|
|
18900
|
+
...this.#managedFallbackPromptOptions(),
|
|
18901
|
+
transientRecoveryMessage: this.#escapedNonAsciiRecoveryMessage(),
|
|
18902
|
+
});
|
|
18903
|
+
if (
|
|
18904
|
+
cancellationSignal?.aborted ||
|
|
18905
|
+
this.#abortAdmissionEpoch !== abortEpoch ||
|
|
18906
|
+
this.#fallbackTransitionGeneration !== transitionGeneration
|
|
18907
|
+
) {
|
|
18908
|
+
await restoreOwnedTransition();
|
|
18909
|
+
return;
|
|
18910
|
+
}
|
|
18911
|
+
this.#emitFallbackSwitchEvent(
|
|
18912
|
+
controller,
|
|
18913
|
+
fallbackFrom,
|
|
18914
|
+
controller.currentSelector(),
|
|
18915
|
+
"escaped_non_ascii",
|
|
18916
|
+
1,
|
|
18917
|
+
);
|
|
18918
|
+
await continuation;
|
|
18919
|
+
this.#fallbackTransitionGeneration++;
|
|
18920
|
+
},
|
|
18921
|
+
};
|
|
18922
|
+
}
|
|
18923
|
+
controller.advance();
|
|
18924
|
+
if (this.#abortAdmissionEpoch !== abortEpoch || cancellationSignal?.aborted)
|
|
18925
|
+
return { type: "terminal", terminal: { stopReason: "cancelled" } };
|
|
18926
|
+
this.#defaultFallbackExhaustedLastTurn = true;
|
|
18716
18927
|
return this.#managedFallbackExhaustionDecision(
|
|
18717
18928
|
outcome.message,
|
|
18718
|
-
`Managed fallback
|
|
18929
|
+
`Managed fallback exhausted every eligible model after escaped non-ASCII tool-call retries; the run fails closed. ${errorMessage}`,
|
|
18719
18930
|
);
|
|
18720
18931
|
}
|
|
18721
18932
|
this.#defaultFallbackChain().discardStartedAttempt();
|
|
18722
|
-
const steering = outcome.steeringPending === true;
|
|
18723
18933
|
return {
|
|
18724
18934
|
type: "retry",
|
|
18725
18935
|
continuation: async ownership => {
|
|
18726
18936
|
if (!ownership.isCurrent() || ownership.lease.signal.aborted) return;
|
|
18727
18937
|
await this.agent.continue({
|
|
18728
18938
|
...this.#managedFallbackPromptOptions(),
|
|
18729
|
-
|
|
18939
|
+
transientRecoveryMessage: this.#escapedNonAsciiRecoveryMessage(),
|
|
18730
18940
|
});
|
|
18731
18941
|
},
|
|
18732
18942
|
};
|
|
@@ -18798,6 +19008,7 @@ export class AgentSession {
|
|
|
18798
19008
|
return {
|
|
18799
19009
|
...discarded,
|
|
18800
19010
|
content: [{ type: "text", text: "" }],
|
|
19011
|
+
providerPayload: undefined,
|
|
18801
19012
|
stopReason: "error",
|
|
18802
19013
|
errorMessage,
|
|
18803
19014
|
timestamp: Date.now(),
|
|
@@ -18842,45 +19053,162 @@ export class AgentSession {
|
|
|
18842
19053
|
return undefined;
|
|
18843
19054
|
}
|
|
18844
19055
|
|
|
19056
|
+
async #restoreDefaultFallbackTransition(
|
|
19057
|
+
controller: FallbackChainController,
|
|
19058
|
+
rollbackState: FallbackChainRuntimeState | undefined,
|
|
19059
|
+
previousModel: Model | undefined,
|
|
19060
|
+
previousThinkingLevel: ThinkingLevel | undefined,
|
|
19061
|
+
): Promise<void> {
|
|
19062
|
+
if (rollbackState) controller.restoreRuntimeState(rollbackState);
|
|
19063
|
+
if (previousModel && this.model !== previousModel) {
|
|
19064
|
+
const previousEditMode = this.#resolveActiveEditMode();
|
|
19065
|
+
this.#setModelAuthoritatively(previousModel, "fallback-switch");
|
|
19066
|
+
this.#thinkingLevel = previousThinkingLevel;
|
|
19067
|
+
this.agent.setThinkingLevel(toReasoningEffort(previousThinkingLevel));
|
|
19068
|
+
await this.#syncEditToolModeAfterModelChange(previousEditMode);
|
|
19069
|
+
}
|
|
19070
|
+
if (previousModel && this.model === previousModel && this.thinkingLevel !== previousThinkingLevel) {
|
|
19071
|
+
this.#thinkingLevel = previousThinkingLevel;
|
|
19072
|
+
this.agent.setThinkingLevel(toReasoningEffort(previousThinkingLevel));
|
|
19073
|
+
}
|
|
19074
|
+
}
|
|
19075
|
+
|
|
18845
19076
|
async #advanceDefaultFallback(
|
|
18846
19077
|
controller: FallbackChainController,
|
|
18847
19078
|
reason: string,
|
|
18848
19079
|
attemptsUsed: number,
|
|
19080
|
+
signal?: AbortSignal,
|
|
19081
|
+
abortEpoch?: number,
|
|
19082
|
+
rollbackState?: FallbackChainRuntimeState,
|
|
19083
|
+
previousModel?: Model,
|
|
19084
|
+
previousThinkingLevel?: ThinkingLevel,
|
|
19085
|
+
options?: { emitSwitchEvent?: boolean },
|
|
19086
|
+
transitionGeneration?: number,
|
|
18849
19087
|
): Promise<boolean> {
|
|
19088
|
+
const promptAbortSignal = this.#promptPreflightAbortController.signal;
|
|
19089
|
+
const cancellationSignal = signal ? AbortSignal.any([signal, promptAbortSignal]) : promptAbortSignal;
|
|
19090
|
+
const transitionStillOwned = () =>
|
|
19091
|
+
transitionGeneration === undefined || this.#fallbackTransitionGeneration === transitionGeneration;
|
|
19092
|
+
const restoreFallbackTransition = () =>
|
|
19093
|
+
transitionStillOwned()
|
|
19094
|
+
? this.#restoreDefaultFallbackTransition(controller, rollbackState, previousModel, previousThinkingLevel)
|
|
19095
|
+
: Promise.resolve();
|
|
19096
|
+
const awaitWithCancellation = async <T>(pending: Promise<T>) => {
|
|
19097
|
+
const cancellation = Promise.withResolvers<"aborted">();
|
|
19098
|
+
const onAbort = () => cancellation.resolve("aborted");
|
|
19099
|
+
cancellationSignal.addEventListener("abort", onAbort, { once: true });
|
|
19100
|
+
try {
|
|
19101
|
+
return await Promise.race([
|
|
19102
|
+
pending.then(value => ({ kind: "resolved" as const, value })),
|
|
19103
|
+
cancellation.promise.then(() => ({ kind: "aborted" as const })),
|
|
19104
|
+
]);
|
|
19105
|
+
} finally {
|
|
19106
|
+
cancellationSignal.removeEventListener("abort", onAbort);
|
|
19107
|
+
}
|
|
19108
|
+
};
|
|
19109
|
+
const rollbackCancelled = async (): Promise<boolean> => {
|
|
19110
|
+
await restoreFallbackTransition();
|
|
19111
|
+
return false;
|
|
19112
|
+
};
|
|
19113
|
+
if (
|
|
19114
|
+
cancellationSignal.aborted ||
|
|
19115
|
+
(abortEpoch !== undefined && this.#abortAdmissionEpoch !== abortEpoch) ||
|
|
19116
|
+
!transitionStillOwned()
|
|
19117
|
+
)
|
|
19118
|
+
return await rollbackCancelled();
|
|
18850
19119
|
while (!controller.isExhausted()) {
|
|
18851
19120
|
const selector = controller.currentSelector();
|
|
18852
19121
|
if (!selector) return false;
|
|
18853
19122
|
const profileAliasIntent = this.#persistedModelProfileAliasIntent("default");
|
|
18854
|
-
const
|
|
18855
|
-
? await
|
|
18856
|
-
|
|
18857
|
-
this.#modelRegistry
|
|
18858
|
-
|
|
18859
|
-
|
|
18860
|
-
|
|
18861
|
-
|
|
18862
|
-
|
|
18863
|
-
|
|
19123
|
+
const profileResolution = profileAliasIntent
|
|
19124
|
+
? await (async () => {
|
|
19125
|
+
const canonicalSessionId = this.agent.providerSessionId ?? this.sessionId;
|
|
19126
|
+
const previousCanonicalVariant = this.#modelRegistry.getSessionCanonicalVariant(canonicalSessionId);
|
|
19127
|
+
const restoreCanonicalVariant = () => {
|
|
19128
|
+
if (previousCanonicalVariant !== undefined) {
|
|
19129
|
+
this.#modelRegistry.restoreSessionCanonicalVariant(
|
|
19130
|
+
canonicalSessionId,
|
|
19131
|
+
previousCanonicalVariant,
|
|
19132
|
+
);
|
|
19133
|
+
} else {
|
|
19134
|
+
this.#modelRegistry.clearCanonicalVariant(canonicalSessionId);
|
|
19135
|
+
}
|
|
19136
|
+
};
|
|
19137
|
+
try {
|
|
19138
|
+
const pendingResolution = resolveModelChainWithAuth(
|
|
19139
|
+
[selector],
|
|
19140
|
+
this.#modelRegistry,
|
|
19141
|
+
this.settings,
|
|
19142
|
+
this.credentialSessionId,
|
|
19143
|
+
{
|
|
19144
|
+
managedFallback: true,
|
|
19145
|
+
...profileAliasIntent,
|
|
19146
|
+
canonicalSessionId,
|
|
19147
|
+
credentialSessionId: this.credentialSessionId,
|
|
19148
|
+
signal: cancellationSignal,
|
|
19149
|
+
},
|
|
19150
|
+
);
|
|
19151
|
+
return await awaitWithCancellation(
|
|
19152
|
+
pendingResolution.finally(() => {
|
|
19153
|
+
if (cancellationSignal.aborted) restoreCanonicalVariant();
|
|
19154
|
+
}),
|
|
19155
|
+
);
|
|
19156
|
+
} catch (error) {
|
|
19157
|
+
if (cancellationSignal.aborted) restoreCanonicalVariant();
|
|
19158
|
+
await restoreFallbackTransition();
|
|
19159
|
+
throw error;
|
|
19160
|
+
}
|
|
19161
|
+
})()
|
|
19162
|
+
: undefined;
|
|
19163
|
+
if (profileResolution?.kind === "aborted") return await rollbackCancelled();
|
|
19164
|
+
const resolved =
|
|
19165
|
+
profileResolution?.kind === "resolved"
|
|
19166
|
+
? profileResolution.value
|
|
19167
|
+
: resolveModelRoleValue(selector, this.#modelRegistry.getAvailable(), {
|
|
19168
|
+
settings: this.settings,
|
|
19169
|
+
matchPreferences: { usageOrder: this.settings.getStorage()?.getModelUsageOrder() },
|
|
19170
|
+
modelRegistry: this.#modelRegistry,
|
|
19171
|
+
sessionId: this.agent.providerSessionId ?? this.sessionId,
|
|
18864
19172
|
credentialSessionId: this.credentialSessionId,
|
|
18865
|
-
}
|
|
18866
|
-
|
|
18867
|
-
|
|
18868
|
-
|
|
18869
|
-
|
|
18870
|
-
|
|
18871
|
-
|
|
18872
|
-
credentialSessionId: this.credentialSessionId,
|
|
18873
|
-
});
|
|
19173
|
+
});
|
|
19174
|
+
if (
|
|
19175
|
+
cancellationSignal.aborted ||
|
|
19176
|
+
(abortEpoch !== undefined && this.#abortAdmissionEpoch !== abortEpoch) ||
|
|
19177
|
+
!transitionStillOwned()
|
|
19178
|
+
)
|
|
19179
|
+
return await rollbackCancelled();
|
|
18874
19180
|
if (!resolved.model) {
|
|
18875
19181
|
controller.onResolutionSkip("unknown_model");
|
|
18876
19182
|
continue;
|
|
18877
19183
|
}
|
|
19184
|
+
const resolvedModel = resolved.model;
|
|
19185
|
+
if (this.#escapedNonAsciiExhaustedModelKeys.has(`${resolved.model.provider}\u0000${resolved.model.id}`)) {
|
|
19186
|
+
controller.onResolutionSkip("escaped_non_ascii_model_exhausted");
|
|
19187
|
+
continue;
|
|
19188
|
+
}
|
|
18878
19189
|
const managedCursorUnavailable = managedCursorFallbackUnavailableReason(resolved.model, selector);
|
|
18879
19190
|
if (managedCursorUnavailable) {
|
|
18880
19191
|
controller.onResolutionSkip(managedCursorUnavailable);
|
|
18881
19192
|
continue;
|
|
18882
19193
|
}
|
|
18883
|
-
const
|
|
19194
|
+
const keyResult = await (async () => {
|
|
19195
|
+
try {
|
|
19196
|
+
return await awaitWithCancellation(
|
|
19197
|
+
this.#modelRegistry.getApiKey(resolvedModel, this.credentialSessionId),
|
|
19198
|
+
);
|
|
19199
|
+
} catch (error) {
|
|
19200
|
+
await rollbackCancelled();
|
|
19201
|
+
throw error;
|
|
19202
|
+
}
|
|
19203
|
+
})();
|
|
19204
|
+
if (keyResult.kind === "aborted") return await rollbackCancelled();
|
|
19205
|
+
const key = keyResult.value;
|
|
19206
|
+
if (
|
|
19207
|
+
cancellationSignal.aborted ||
|
|
19208
|
+
(abortEpoch !== undefined && this.#abortAdmissionEpoch !== abortEpoch) ||
|
|
19209
|
+
!transitionStillOwned()
|
|
19210
|
+
)
|
|
19211
|
+
return await rollbackCancelled();
|
|
18884
19212
|
if (!isAuthenticated(key) && key !== kNoAuth) {
|
|
18885
19213
|
controller.onResolutionSkip("unauthenticated");
|
|
18886
19214
|
continue;
|
|
@@ -18889,33 +19217,64 @@ export class AgentSession {
|
|
|
18889
19217
|
controller.tried.at(-1)?.selector ?? controller.chain.entries[controller.activeIndex - 1] ?? selector;
|
|
18890
19218
|
const to = selector;
|
|
18891
19219
|
const previousEditMode = this.#resolveActiveEditMode();
|
|
19220
|
+
this.#escapedNonAsciiManagedRetries = 0;
|
|
18892
19221
|
this.#setModelAuthoritatively(resolved.model, "fallback-switch");
|
|
18893
|
-
this
|
|
19222
|
+
this.#thinkingLevel = resolveThinkingLevelForModel(
|
|
19223
|
+
this.model,
|
|
18894
19224
|
this.#thinkingLevelForResolvedFallback(resolved.explicitThinkingLevel, resolved.thinkingLevel),
|
|
18895
19225
|
);
|
|
19226
|
+
this.agent.setThinkingLevel(toReasoningEffort(this.#thinkingLevel));
|
|
18896
19227
|
await this.#syncEditToolModeAfterModelChange(previousEditMode);
|
|
18897
|
-
if (
|
|
18898
|
-
|
|
18899
|
-
|
|
18900
|
-
|
|
18901
|
-
|
|
18902
|
-
|
|
18903
|
-
reason,
|
|
18904
|
-
role:
|
|
18905
|
-
controller.chain.origin === "subagent"
|
|
18906
|
-
? (controller.chain.identity ?? controller.chain.role)
|
|
18907
|
-
: controller.chain.role,
|
|
18908
|
-
scope: controller.chain.origin === "subagent" ? "subagent-call" : "session",
|
|
18909
|
-
activeIndex: controller.activeIndex,
|
|
18910
|
-
chainLength: controller.chain.entries.length,
|
|
18911
|
-
attemptsUsed,
|
|
18912
|
-
});
|
|
19228
|
+
if (
|
|
19229
|
+
cancellationSignal.aborted ||
|
|
19230
|
+
(abortEpoch !== undefined && this.#abortAdmissionEpoch !== abortEpoch) ||
|
|
19231
|
+
!transitionStillOwned()
|
|
19232
|
+
) {
|
|
19233
|
+
return await rollbackCancelled();
|
|
18913
19234
|
}
|
|
19235
|
+
if (options?.emitSwitchEvent ?? true)
|
|
19236
|
+
this.#emitFallbackSwitchEvent(controller, from, to, reason, attemptsUsed);
|
|
19237
|
+
if (
|
|
19238
|
+
cancellationSignal.aborted ||
|
|
19239
|
+
(abortEpoch !== undefined && this.#abortAdmissionEpoch !== abortEpoch) ||
|
|
19240
|
+
!transitionStillOwned()
|
|
19241
|
+
)
|
|
19242
|
+
return await rollbackCancelled();
|
|
18914
19243
|
return true;
|
|
18915
19244
|
}
|
|
18916
19245
|
return false;
|
|
18917
19246
|
}
|
|
18918
19247
|
|
|
19248
|
+
#emitFallbackSwitchEvent(
|
|
19249
|
+
controller: FallbackChainController,
|
|
19250
|
+
from: string | undefined,
|
|
19251
|
+
to: string | undefined,
|
|
19252
|
+
reason: string,
|
|
19253
|
+
attemptsUsed: number,
|
|
19254
|
+
): void {
|
|
19255
|
+
if (!from || !to || from === to) return;
|
|
19256
|
+
const event: Extract<AgentSessionEvent, { type: "model_fallback_switched" }> = {
|
|
19257
|
+
type: "model_fallback_switched",
|
|
19258
|
+
eventId: crypto.randomUUID(),
|
|
19259
|
+
from,
|
|
19260
|
+
to,
|
|
19261
|
+
reason,
|
|
19262
|
+
role:
|
|
19263
|
+
controller.chain.origin === "subagent"
|
|
19264
|
+
? (controller.chain.identity ?? controller.chain.role)
|
|
19265
|
+
: controller.chain.role,
|
|
19266
|
+
scope: controller.chain.origin === "subagent" ? "subagent-call" : "session",
|
|
19267
|
+
activeIndex: controller.activeIndex,
|
|
19268
|
+
chainLength: controller.chain.entries.length,
|
|
19269
|
+
attemptsUsed,
|
|
19270
|
+
};
|
|
19271
|
+
if (this.#eventListeners.length === 0) {
|
|
19272
|
+
this.#pendingFallbackSwitches.push(event);
|
|
19273
|
+
} else {
|
|
19274
|
+
this.#emit(event);
|
|
19275
|
+
}
|
|
19276
|
+
}
|
|
19277
|
+
|
|
18919
19278
|
#emitResolutionFallbackSwitch(controller: FallbackChainController): void {
|
|
18920
19279
|
if (controller.activeIndex <= 0) return;
|
|
18921
19280
|
const to = controller.currentSelector();
|
|
@@ -19292,6 +19651,12 @@ export class AgentSession {
|
|
|
19292
19651
|
}
|
|
19293
19652
|
|
|
19294
19653
|
const retry = async (ownership?: ManagedAttemptContinuationOwnership): Promise<void> => {
|
|
19654
|
+
const activePromptHandle = this.activePromptHandle;
|
|
19655
|
+
const cancellationSignal =
|
|
19656
|
+
ownership?.domain.signal ??
|
|
19657
|
+
(activePromptHandle ? this.#runCancellationDomains.lookup(activePromptHandle)?.signal : undefined);
|
|
19658
|
+
if (ownership && (!ownership.isCurrent() || cancellationSignal?.aborted)) return;
|
|
19659
|
+
const abortEpoch = this.#abortAdmissionEpoch;
|
|
19295
19660
|
let quotaPoolExhausted = false;
|
|
19296
19661
|
if (managedFallback && !credentialRotated && !providerRetryCeilingReached) {
|
|
19297
19662
|
const mark = await this.#markFailedCredential(trigger);
|
|
@@ -19300,13 +19665,55 @@ export class AgentSession {
|
|
|
19300
19665
|
}
|
|
19301
19666
|
let advanced = outcome !== "advance";
|
|
19302
19667
|
let resolutionError: unknown;
|
|
19668
|
+
let controllerStateBeforeAdvance: FallbackChainRuntimeState | undefined;
|
|
19669
|
+
let previousModel: Model | undefined;
|
|
19670
|
+
let previousThinkingLevel: ThinkingLevel | undefined;
|
|
19303
19671
|
if (outcome === "advance") {
|
|
19672
|
+
controllerStateBeforeAdvance = controller.snapshotRuntimeState();
|
|
19673
|
+
const failedEntryIndex = controller.activeIndex - 1;
|
|
19674
|
+
if (failedEntryIndex >= 0 && failedEntryIndex < controller.chain.entries.length) {
|
|
19675
|
+
controllerStateBeforeAdvance.activeIndex = failedEntryIndex;
|
|
19676
|
+
controllerStateBeforeAdvance.attemptsUsed = 0;
|
|
19677
|
+
controllerStateBeforeAdvance.totalAttemptsUsed = Math.max(
|
|
19678
|
+
0,
|
|
19679
|
+
controllerStateBeforeAdvance.totalAttemptsUsed - 1,
|
|
19680
|
+
);
|
|
19681
|
+
controllerStateBeforeAdvance.attemptStarted = false;
|
|
19682
|
+
controllerStateBeforeAdvance.exhaustedForTurn = false;
|
|
19683
|
+
controllerStateBeforeAdvance.tried = controllerStateBeforeAdvance.tried.slice(0, -1);
|
|
19684
|
+
}
|
|
19685
|
+
previousModel = this.model;
|
|
19686
|
+
previousThinkingLevel = this.thinkingLevel;
|
|
19304
19687
|
try {
|
|
19305
|
-
advanced = await this.#advanceDefaultFallback(
|
|
19688
|
+
advanced = await this.#advanceDefaultFallback(
|
|
19689
|
+
controller,
|
|
19690
|
+
trigger.class,
|
|
19691
|
+
attemptsUsed,
|
|
19692
|
+
cancellationSignal,
|
|
19693
|
+
abortEpoch,
|
|
19694
|
+
controllerStateBeforeAdvance,
|
|
19695
|
+
previousModel,
|
|
19696
|
+
previousThinkingLevel,
|
|
19697
|
+
);
|
|
19306
19698
|
} catch (error) {
|
|
19307
19699
|
resolutionError = error;
|
|
19308
19700
|
}
|
|
19309
19701
|
}
|
|
19702
|
+
if (ownership && (!ownership.isCurrent() || cancellationSignal?.aborted)) {
|
|
19703
|
+
if (this.#abortAdmissionEpoch === abortEpoch) {
|
|
19704
|
+
await this.#restoreDefaultFallbackTransition(
|
|
19705
|
+
controller,
|
|
19706
|
+
controllerStateBeforeAdvance,
|
|
19707
|
+
previousModel,
|
|
19708
|
+
previousThinkingLevel,
|
|
19709
|
+
);
|
|
19710
|
+
}
|
|
19711
|
+
return;
|
|
19712
|
+
}
|
|
19713
|
+
if (this.#abortAdmissionEpoch !== abortEpoch) {
|
|
19714
|
+
controller.resetForNewTurn();
|
|
19715
|
+
return;
|
|
19716
|
+
}
|
|
19310
19717
|
if (!advanced) {
|
|
19311
19718
|
let errorMessage = resolutionError
|
|
19312
19719
|
? `${this.#fallbackExhaustionError(controller)}; resolution failed: ${resolutionError instanceof Error ? resolutionError.message : String(resolutionError)}`
|