@gajae-code/coding-agent 0.12.21 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +67 -2
- package/dist/types/cli/args.d.ts +5 -2
- package/dist/types/cli/root-flags.d.ts +246 -0
- package/dist/types/cli.d.ts +11 -19
- package/dist/types/commands/daemon.d.ts +0 -30
- package/dist/types/commands/launch.d.ts +11 -19
- package/dist/types/commands/quick-lane.d.ts +32 -0
- package/dist/types/commands/sdk.d.ts +21 -15
- package/dist/types/config/file-lock-gc.d.ts +2 -0
- package/dist/types/config/model-equivalence.d.ts +20 -0
- package/dist/types/config/model-profile-activation.d.ts +62 -17
- package/dist/types/config/model-profiles.d.ts +10 -0
- package/dist/types/config/model-registry.d.ts +68 -1
- package/dist/types/config/model-resolver.d.ts +15 -4
- package/dist/types/config/models-config-schema.d.ts +7 -2
- package/dist/types/config/provider-selection-policy.d.ts +62 -0
- package/dist/types/config/settings-schema.d.ts +36 -78
- package/dist/types/config/settings.d.ts +2 -0
- package/dist/types/edit/renderer.d.ts +5 -2
- package/dist/types/edit/streaming.d.ts +13 -0
- package/dist/types/extensibility/custom-tools/types.d.ts +2 -0
- package/dist/types/extensibility/extensions/compact-handler.d.ts +2 -1
- package/dist/types/extensibility/extensions/types.d.ts +3 -0
- package/dist/types/gjc-runtime/gc-runtime.d.ts +19 -5
- package/dist/types/internal-urls/local-protocol.d.ts +1 -0
- package/dist/types/modes/components/model-selector.d.ts +0 -2
- package/dist/types/modes/components/provider-order-context.d.ts +45 -0
- package/dist/types/modes/components/provider-order-editor.d.ts +24 -0
- package/dist/types/modes/components/settings-defs.d.ts +5 -1
- package/dist/types/modes/components/settings-selector.d.ts +2 -0
- package/dist/types/quick-lane/classify.d.ts +40 -0
- package/dist/types/runtime/stt-settings-setup.d.ts +18 -0
- package/dist/types/sdk/broker/broker.d.ts +56 -2
- package/dist/types/sdk/broker/operation-receipt-ledger.d.ts +25 -0
- package/dist/types/sdk/broker/session-index.d.ts +83 -2
- package/dist/types/sdk/broker/state-version.d.ts +1 -1
- package/dist/types/sdk/bus/notification-service.d.ts +6 -0
- package/dist/types/sdk/bus/prompt-reconciliation.d.ts +8 -1
- package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +4 -1
- package/dist/types/sdk/bus/telegram-daemon.d.ts +26 -0
- package/dist/types/sdk/cli/rows.d.ts +98 -0
- package/dist/types/sdk/cli/session-cli.d.ts +35 -3
- package/dist/types/sdk/elevation/allowlist.d.ts +20 -0
- package/dist/types/sdk/elevation/audit.d.ts +93 -0
- package/dist/types/sdk/elevation/capability.d.ts +3 -0
- package/dist/types/sdk/elevation/digest.d.ts +72 -0
- package/dist/types/sdk/elevation/dispatch-receipt.d.ts +85 -0
- package/dist/types/sdk/elevation/grant-ledger.d.ts +163 -0
- package/dist/types/sdk/elevation/owner.d.ts +48 -0
- package/dist/types/sdk/guides/cache.d.ts +98 -0
- package/dist/types/sdk/guides/catalog.d.ts +156 -0
- package/dist/types/sdk/guides/cli.d.ts +39 -0
- package/dist/types/sdk/guides/manifest.d.ts +66 -0
- package/dist/types/sdk/guides/verify.d.ts +66 -0
- package/dist/types/sdk/host/control/dispatch.d.ts +8 -0
- package/dist/types/sdk/host/control/index.d.ts +1 -1
- package/dist/types/sdk/host/control/operations.d.ts +3 -2
- package/dist/types/sdk/host/query/cursor.d.ts +10 -0
- package/dist/types/sdk/host/query/handlers.d.ts +21 -0
- package/dist/types/sdk/host/session-runtime.d.ts +9 -0
- package/dist/types/sdk/mcp/server.d.ts +2 -2
- package/dist/types/sdk/prompt-status.d.ts +11 -0
- package/dist/types/sdk/session.d.ts +4 -0
- package/dist/types/sdk/transport/index.d.ts +1 -0
- package/dist/types/sdk/transport/relay.d.ts +1 -0
- package/dist/types/sdk/transport/serve-cli.d.ts +20 -1
- package/dist/types/session/agent-session.d.ts +32 -18
- package/dist/types/session/blob-store.d.ts +13 -7
- package/dist/types/session/fallback-chain-controller.d.ts +15 -0
- package/dist/types/session/internal/managed-session-storage.d.ts +25 -1
- package/dist/types/session/internal/session-memory-sidecar.d.ts +5 -5
- package/dist/types/session/session-manager.d.ts +139 -10
- package/dist/types/session/session-storage.d.ts +48 -0
- package/dist/types/setup/provider-onboarding.d.ts +7 -1
- package/dist/types/slash-commands/builtin-registry.d.ts +14 -0
- package/dist/types/stt/setup.d.ts +2 -0
- package/dist/types/task/executor.d.ts +10 -2
- package/dist/types/tools/browser/runtime-diagnostics.d.ts +65 -0
- package/dist/types/tools/browser/tab-protocol.d.ts +4 -0
- package/dist/types/tools/browser/tab-supervisor.d.ts +2 -0
- package/dist/types/tools/browser/tab-worker.d.ts +2 -0
- package/dist/types/tools/browser.d.ts +2 -0
- package/dist/types/tools/image-gen.d.ts +14 -18
- package/dist/types/tools/index.d.ts +2 -0
- package/dist/types/tools/resource-gc.d.ts +2 -0
- package/dist/types/utils/pasted-image-path.d.ts +6 -0
- package/package.json +9 -9
- package/scripts/generate-sdk-operation-inventory.ts +8 -0
- package/scripts/run-test-manifest.ts +61 -21
- package/scripts/verify-gjc-sdk-canonicalization.ts +9 -4
- package/src/cli/args.ts +62 -9
- package/src/cli/completion-cli.ts +1 -0
- package/src/cli/fast-help.ts +1 -0
- package/src/cli/root-flags.ts +100 -0
- package/src/cli/setup-cli.ts +6 -2
- package/src/cli.ts +42 -65
- package/src/commands/acp.ts +1 -1
- package/src/commands/daemon.ts +2 -31
- package/src/commands/gc.ts +1 -1
- package/src/commands/launch.ts +8 -129
- package/src/commands/quick-lane.ts +51 -0
- package/src/commands/sdk.ts +200 -26
- package/src/commands/setup.ts +4 -2
- package/src/config/file-lock-gc.ts +4 -1
- package/src/config/model-equivalence.ts +76 -7
- package/src/config/model-profile-activation.ts +1115 -265
- package/src/config/model-profiles.ts +134 -2
- package/src/config/model-registry.ts +352 -50
- package/src/config/model-resolver.ts +289 -38
- package/src/config/models-config-schema.ts +11 -4
- package/src/config/provider-selection-policy.ts +146 -0
- package/src/config/settings-schema.ts +50 -77
- package/src/config/settings.ts +40 -0
- package/src/coordinator-mcp/server.ts +32 -2
- package/src/edit/renderer.ts +175 -223
- package/src/edit/streaming.ts +108 -1
- package/src/export/html/index.ts +57 -7
- package/src/extensibility/custom-tools/types.ts +2 -0
- package/src/extensibility/extensions/compact-handler.ts +3 -2
- package/src/extensibility/extensions/runner.ts +6 -0
- package/src/extensibility/extensions/types.ts +3 -0
- package/src/gjc-runtime/gc-runtime.ts +379 -20
- package/src/gjc-runtime/launch-worktree.ts +5 -1
- package/src/gjc-runtime/team-runtime.ts +59 -2
- package/src/gjc-runtime/tmux-owner-isolation.ts +19 -0
- package/src/gjc-runtime/ultragoal-receipt-freshness.ts +16 -1
- package/src/internal-urls/docs-index.generated.ts +17 -13
- package/src/internal-urls/local-protocol.ts +1 -0
- package/src/main.ts +40 -3
- package/src/memories/index.ts +4 -2
- package/src/modes/acp/acp-agent.ts +235 -91
- package/src/modes/components/model-selector.ts +206 -42
- package/src/modes/components/provider-order-context.ts +197 -0
- package/src/modes/components/provider-order-editor.ts +441 -0
- package/src/modes/components/settings-defs.ts +21 -1
- package/src/modes/components/settings-selector.ts +45 -2
- package/src/modes/components/tool-execution.ts +47 -15
- package/src/modes/controllers/command-controller.ts +6 -3
- package/src/modes/controllers/extension-ui-controller.ts +2 -0
- package/src/modes/controllers/input-controller.ts +73 -2
- package/src/modes/controllers/selector-controller.ts +260 -153
- package/src/modes/prompt-suggestion-controller.ts +1 -1
- package/src/modes/runtime-init.ts +1 -0
- package/src/modes/utils/ui-helpers.ts +18 -0
- package/src/prompts/tools/browser.md +1 -0
- package/src/quick-lane/classify.ts +257 -0
- package/src/rlm/index.ts +1 -1
- package/src/runtime/stt-settings-setup.ts +57 -0
- package/src/sdk/acp/adapter.ts +2 -1
- package/src/sdk/broker/broker.ts +780 -12
- package/src/sdk/broker/lifecycle.ts +70 -29
- package/src/sdk/broker/operation-receipt-ledger.ts +144 -0
- package/src/sdk/broker/session-index.ts +463 -48
- package/src/sdk/broker/state-version.ts +13 -9
- package/src/sdk/broker/transport.ts +14 -1
- package/src/sdk/bus/index.ts +34 -13
- package/src/sdk/bus/kind-aware-reconciliation.ts +21 -2
- package/src/sdk/bus/notification-service.ts +47 -1
- package/src/sdk/bus/prompt-reconciliation.ts +22 -3
- package/src/sdk/bus/telegram-daemon-contract.ts +4 -1
- package/src/sdk/bus/telegram-daemon.ts +54 -3
- package/src/sdk/cli/rows.ts +247 -0
- package/src/sdk/cli/session-cli.ts +930 -79
- package/src/sdk/elevation/allowlist.ts +54 -0
- package/src/sdk/elevation/audit.ts +208 -0
- package/src/sdk/elevation/capability.ts +41 -0
- package/src/sdk/elevation/digest.ts +129 -0
- package/src/sdk/elevation/dispatch-receipt.ts +184 -0
- package/src/sdk/elevation/grant-ledger.ts +1142 -0
- package/src/sdk/elevation/owner.ts +109 -0
- package/src/sdk/guides/cache.ts +363 -0
- package/src/sdk/guides/catalog.ts +554 -0
- package/src/sdk/guides/cli.ts +235 -0
- package/src/sdk/guides/manifest.ts +152 -0
- package/src/sdk/guides/verify.ts +178 -0
- package/src/sdk/host/control/dispatch.ts +49 -1
- package/src/sdk/host/control/index.ts +1 -0
- package/src/sdk/host/control/operations.ts +8 -1
- package/src/sdk/host/query/cursor.ts +47 -3
- package/src/sdk/host/query/handlers.ts +155 -2
- package/src/sdk/host/sdk-surface-parity.test.ts +1 -0
- package/src/sdk/host/session-runtime.test.ts +391 -1
- package/src/sdk/host/session-runtime.ts +308 -73
- package/src/sdk/host/surface-policy.ts +3 -0
- package/src/sdk/host/websocket-transport.ts +7 -1
- package/src/sdk/mcp/server.ts +88 -15
- package/src/sdk/prompt-status.ts +8 -0
- package/src/sdk/protocol/operation-inventory.generated.json +62 -0
- package/src/sdk/protocol/operation-registry.ts +3 -2
- package/src/sdk/session.ts +87 -39
- package/src/sdk/transport/index.ts +1 -0
- package/src/sdk/transport/relay.ts +3 -0
- package/src/sdk/transport/serve-cli.ts +111 -24
- package/src/secrets/obfuscator.ts +92 -12
- package/src/session/agent-session.ts +322 -93
- package/src/session/blob-store.ts +60 -8
- package/src/session/fallback-chain-controller.ts +35 -0
- package/src/session/internal/managed-session-scope.ts +25 -3
- package/src/session/internal/managed-session-storage.ts +574 -73
- package/src/session/internal/session-memory-sidecar.ts +19 -29
- package/src/session/session-manager.ts +2166 -743
- package/src/session/session-storage.ts +715 -48
- package/src/setup/provider-onboarding.ts +19 -3
- package/src/setup/provider-presets.json +3 -1
- package/src/slash-commands/builtin-registry.ts +85 -3
- package/src/stt/downloader.ts +2 -3
- package/src/stt/setup.ts +29 -4
- package/src/task/executor.ts +22 -18
- package/src/task/index.ts +24 -1
- package/src/tools/archive-reader.ts +11 -2
- package/src/tools/browser/runtime-diagnostics.ts +249 -0
- package/src/tools/browser/tab-protocol.ts +4 -0
- package/src/tools/browser/tab-supervisor.ts +5 -0
- package/src/tools/browser/tab-worker.ts +36 -1
- package/src/tools/browser.ts +7 -0
- package/src/tools/image-gen.ts +110 -166
- package/src/tools/index.ts +2 -0
- package/src/tools/read.ts +1 -1
- package/src/tools/resource-gc.ts +6 -1
- package/src/tools/tool-catalog.generated.ts +5 -1
- package/src/utils/pasted-image-path.ts +93 -0
- package/src/web/search/index.ts +3 -3
|
@@ -116,6 +116,7 @@ import {
|
|
|
116
116
|
type AuthDisposition,
|
|
117
117
|
beginAttempt,
|
|
118
118
|
classifyFallbackTrigger,
|
|
119
|
+
EMPTY_RESPONSE_PROVIDER_CODE,
|
|
119
120
|
type FallbackAttemptToken,
|
|
120
121
|
type FallbackTriggerClass,
|
|
121
122
|
STREAM_FIRST_EVENT_TIMEOUT_PROVIDER_CODE,
|
|
@@ -193,9 +194,11 @@ import type { CasReceipt } from "../config/atomic-yaml-patch";
|
|
|
193
194
|
import { activateModelProfile, materializeActiveModelProfileAssignment } from "../config/model-profile-activation";
|
|
194
195
|
import {
|
|
195
196
|
ModelProfileRegistryError,
|
|
197
|
+
resolveModelProfileName,
|
|
196
198
|
UnknownModelProfileError,
|
|
197
199
|
validateModelProfileName,
|
|
198
200
|
} from "../config/model-profile-contract";
|
|
201
|
+
import { resolveProfileBindings } from "../config/model-profiles";
|
|
199
202
|
import {
|
|
200
203
|
GJC_MODEL_ASSIGNMENT_TARGETS,
|
|
201
204
|
isAuthenticated,
|
|
@@ -389,6 +392,7 @@ import {
|
|
|
389
392
|
compactionRetryDelay,
|
|
390
393
|
effectiveFallbackDelay,
|
|
391
394
|
FallbackChainController,
|
|
395
|
+
type FallbackChainRuntimeState,
|
|
392
396
|
} from "./fallback-chain-controller";
|
|
393
397
|
|
|
394
398
|
export { DefaultModelSelectionRecoveryError } from "./default-model-selection";
|
|
@@ -716,6 +720,8 @@ export interface AgentSessionConfig {
|
|
|
716
720
|
* so that credential sticky selection is consistent with the session's streaming calls.
|
|
717
721
|
*/
|
|
718
722
|
providerSessionId?: string;
|
|
723
|
+
/** Optional auth-selection identity, distinct from logical/canonical and provider-cache identity. */
|
|
724
|
+
credentialSessionId?: string;
|
|
719
725
|
/** Optional provider-facing cache identity, distinct from logical session identity. */
|
|
720
726
|
providerCacheSessionId?: string;
|
|
721
727
|
}
|
|
@@ -933,6 +939,7 @@ type RetryErrorClassification =
|
|
|
933
939
|
| "terminal"
|
|
934
940
|
| "usage_limit"
|
|
935
941
|
| "first_event_timeout"
|
|
942
|
+
| "empty_response"
|
|
936
943
|
| "transient"
|
|
937
944
|
| "local_unavailable"
|
|
938
945
|
| "unknown";
|
|
@@ -1785,6 +1792,12 @@ type SessionAdmissionLease = {
|
|
|
1785
1792
|
release(): void;
|
|
1786
1793
|
};
|
|
1787
1794
|
|
|
1795
|
+
export interface DefaultFallbackRuntimeState {
|
|
1796
|
+
chain: ConfiguredFallbackChain;
|
|
1797
|
+
controller: FallbackChainRuntimeState;
|
|
1798
|
+
exhaustedLastTurn: boolean;
|
|
1799
|
+
}
|
|
1800
|
+
|
|
1788
1801
|
/**
|
|
1789
1802
|
* Fire-and-forget continuations (auto-compaction retries, queued follow-ups) race a
|
|
1790
1803
|
* still-busy agent whose current turn can legitimately hold the run for minutes.
|
|
@@ -2069,6 +2082,7 @@ export class AgentSession {
|
|
|
2069
2082
|
#ircRosterEpoch = 0;
|
|
2070
2083
|
#ircRosterClaim: IrcRosterClaim | null = null;
|
|
2071
2084
|
#providerSessionId: string | undefined;
|
|
2085
|
+
#credentialSessionId: string | undefined;
|
|
2072
2086
|
#providerCacheSessionId: string | undefined;
|
|
2073
2087
|
#isDisposed = false;
|
|
2074
2088
|
#disposePromise: Promise<void> | undefined;
|
|
@@ -2949,6 +2963,7 @@ export class AgentSession {
|
|
|
2949
2963
|
});
|
|
2950
2964
|
this.#agentRegistry = config.agentRegistry;
|
|
2951
2965
|
this.#providerSessionId = config.providerSessionId;
|
|
2966
|
+
this.#credentialSessionId = config.credentialSessionId;
|
|
2952
2967
|
this.#providerCacheSessionId = config.providerCacheSessionId;
|
|
2953
2968
|
// Per-tool TTSR reminders are folded into the matched tool's result via this hook.
|
|
2954
2969
|
this.agent.afterToolCall = ctx => this.#ttsrAfterToolCall(ctx);
|
|
@@ -4869,6 +4884,11 @@ export class AgentSession {
|
|
|
4869
4884
|
onError?: (error: unknown) => void;
|
|
4870
4885
|
resourceRunId?: string;
|
|
4871
4886
|
maintenanceContinuation?: boolean;
|
|
4887
|
+
// Queue-consuming continuations (follow-up/steer drained after execution
|
|
4888
|
+
// tails settle) must run continueQueuedMessages() so they deliver the
|
|
4889
|
+
// queued turn without replaying the non-assistant tail. Every other
|
|
4890
|
+
// scheduled continuation keeps continue() semantics.
|
|
4891
|
+
continueQueuedOnly?: boolean;
|
|
4872
4892
|
}): Promise<void> {
|
|
4873
4893
|
const predecessorAgentEndHold = options?.suppressPredecessorAgentEnd
|
|
4874
4894
|
? this.#reserveDeferredAgentEndForContinuation()
|
|
@@ -4955,10 +4975,15 @@ export class AgentSession {
|
|
|
4955
4975
|
predecessorAccepted = true;
|
|
4956
4976
|
this.#releaseDeferredAgentEndLease(predecessorAgentEnd);
|
|
4957
4977
|
};
|
|
4978
|
+
const hasQueuedMessages = this.agent.hasQueuedMessages();
|
|
4958
4979
|
const startsQueuedSuccessor =
|
|
4959
|
-
|
|
4980
|
+
hasQueuedMessages &&
|
|
4981
|
+
(options?.continueQueuedOnly === true || this.agent.state.messages.at(-1)?.role === "assistant");
|
|
4982
|
+
const continueQueued = options?.continueQueuedOnly
|
|
4983
|
+
? this.agent.continueQueuedMessages.bind(this.agent)
|
|
4984
|
+
: this.agent.continue.bind(this.agent);
|
|
4960
4985
|
try {
|
|
4961
|
-
await
|
|
4986
|
+
await continueQueued({
|
|
4962
4987
|
...this.#managedFallbackPromptOptions(),
|
|
4963
4988
|
maintenanceContinuation: options?.maintenanceContinuation,
|
|
4964
4989
|
// Reset only after continue() has claimed the queued turn. Skipped or stale
|
|
@@ -5696,6 +5721,7 @@ export class AgentSession {
|
|
|
5696
5721
|
getManagedLegacyLocalMigrationSource: () =>
|
|
5697
5722
|
prepared?.managedLegacyLocalMigrationSource ?? this.sessionManager.getManagedLegacyLocalMigrationSource(),
|
|
5698
5723
|
getSessionId: () => prepared?.sessionId ?? this.sessionManager.getSessionId(),
|
|
5724
|
+
getCredentialSessionId: () => this.credentialSessionId,
|
|
5699
5725
|
};
|
|
5700
5726
|
}
|
|
5701
5727
|
|
|
@@ -6205,7 +6231,7 @@ export class AgentSession {
|
|
|
6205
6231
|
this.agent.sessionId = sid;
|
|
6206
6232
|
this.agent.providerSessionId = this.#providerCacheSessionId ?? sid;
|
|
6207
6233
|
this.agent.setMetadataResolver((provider: string) =>
|
|
6208
|
-
buildSessionMetadata(sid, provider, this.#modelRegistry.authStorage),
|
|
6234
|
+
buildSessionMetadata(sid, provider, this.#modelRegistry.authStorage, this.credentialSessionId),
|
|
6209
6235
|
);
|
|
6210
6236
|
}
|
|
6211
6237
|
|
|
@@ -7384,6 +7410,7 @@ export class AgentSession {
|
|
|
7384
7410
|
const getCustomToolContext = (): CustomToolContext => ({
|
|
7385
7411
|
sessionManager: createReadonlySessionManager(this.sessionManager),
|
|
7386
7412
|
modelRegistry: this.#modelRegistry,
|
|
7413
|
+
credentialSessionId: this.credentialSessionId,
|
|
7387
7414
|
model: this.model,
|
|
7388
7415
|
isIdle: () => !this.isStreaming,
|
|
7389
7416
|
hasQueuedMessages: () => this.queuedMessageCount > 0,
|
|
@@ -7427,6 +7454,7 @@ export class AgentSession {
|
|
|
7427
7454
|
return {
|
|
7428
7455
|
sessionManager: createReadonlySessionManager(this.sessionManager),
|
|
7429
7456
|
modelRegistry: this.#modelRegistry,
|
|
7457
|
+
credentialSessionId: this.credentialSessionId,
|
|
7430
7458
|
model: this.model,
|
|
7431
7459
|
isIdle: () => !this.isStreaming,
|
|
7432
7460
|
hasQueuedMessages: () => this.queuedMessageCount > 0,
|
|
@@ -7804,6 +7832,11 @@ export class AgentSession {
|
|
|
7804
7832
|
return this.#providerSessionId ?? this.sessionManager.getSessionId();
|
|
7805
7833
|
}
|
|
7806
7834
|
|
|
7835
|
+
/** Credential selection identity; defaults to the provider-facing session identity. */
|
|
7836
|
+
get credentialSessionId(): string {
|
|
7837
|
+
return this.#credentialSessionId ?? this.sessionId;
|
|
7838
|
+
}
|
|
7839
|
+
|
|
7807
7840
|
/** Current session display name, if set */
|
|
7808
7841
|
get sessionName(): string | undefined {
|
|
7809
7842
|
return this.sessionManager.getSessionName();
|
|
@@ -8894,7 +8927,7 @@ export class AgentSession {
|
|
|
8894
8927
|
const apiKey = await this.#awaitPromptPreflight(
|
|
8895
8928
|
generation,
|
|
8896
8929
|
preflightSignal,
|
|
8897
|
-
this.#modelRegistry.getApiKey(this.model, this.
|
|
8930
|
+
this.#modelRegistry.getApiKey(this.model, this.credentialSessionId, { signal: preflightSignal }),
|
|
8898
8931
|
);
|
|
8899
8932
|
if (!apiKey) {
|
|
8900
8933
|
throw new Error(formatNoCredentialOnboardingError(this.model.provider));
|
|
@@ -9224,6 +9257,7 @@ export class AgentSession {
|
|
|
9224
9257
|
cwd: this.sessionManager.getCwd(),
|
|
9225
9258
|
sessionManager: createReadonlySessionManager(this.sessionManager),
|
|
9226
9259
|
modelRegistry: this.#modelRegistry,
|
|
9260
|
+
credentialSessionId: this.credentialSessionId,
|
|
9227
9261
|
model: this.model ?? undefined,
|
|
9228
9262
|
getActivePromptHandle: () => this.activePromptHandle,
|
|
9229
9263
|
isIdle: () => !this.isStreaming,
|
|
@@ -9443,6 +9477,7 @@ export class AgentSession {
|
|
|
9443
9477
|
if (!this.#cancelAndSubmitInProgress && this.#canAutoContinueForSteer()) {
|
|
9444
9478
|
this.#scheduleAgentContinue({
|
|
9445
9479
|
shouldContinue: () => this.#canAutoContinueForSteer() && this.agent.hasQueuedSteering(),
|
|
9480
|
+
continueQueuedOnly: true,
|
|
9446
9481
|
});
|
|
9447
9482
|
}
|
|
9448
9483
|
}
|
|
@@ -9475,11 +9510,7 @@ export class AgentSession {
|
|
|
9475
9510
|
// agent.continue() only dequeues follow-ups from an assistant-ended state;
|
|
9476
9511
|
// resuming from user/toolResult state runs an extra model call on the
|
|
9477
9512
|
// stale prompt before draining the queue.
|
|
9478
|
-
|
|
9479
|
-
this.#scheduleAgentContinue({
|
|
9480
|
-
shouldContinue: () => this.#canAutoContinueForFollowUp() && this.agent.hasQueuedMessages(),
|
|
9481
|
-
});
|
|
9482
|
-
}
|
|
9513
|
+
this.#scheduleQueuedFollowUpContinuation();
|
|
9483
9514
|
}
|
|
9484
9515
|
|
|
9485
9516
|
/**
|
|
@@ -9487,10 +9518,21 @@ export class AgentSession {
|
|
|
9487
9518
|
*/
|
|
9488
9519
|
#canAutoContinueForFollowUp(): boolean {
|
|
9489
9520
|
if (this.isStreaming) return false;
|
|
9521
|
+
if (this.isCompacting) return false;
|
|
9522
|
+
if (this.isBashRunning) return false;
|
|
9523
|
+
if (this.isEvalRunning) return false;
|
|
9490
9524
|
if (this.isRetrying) return false;
|
|
9491
9525
|
const messages = this.agent.state.messages;
|
|
9492
9526
|
const last = messages[messages.length - 1];
|
|
9493
|
-
return last?.role === "assistant";
|
|
9527
|
+
return last?.role === "assistant" || last?.role === "bashExecution" || last?.role === "pythonExecution";
|
|
9528
|
+
}
|
|
9529
|
+
#scheduleQueuedFollowUpContinuation(): void {
|
|
9530
|
+
if (!this.#cancelAndSubmitInProgress && this.#canAutoContinueForFollowUp() && this.agent.hasQueuedMessages()) {
|
|
9531
|
+
this.#scheduleAgentContinue({
|
|
9532
|
+
shouldContinue: () => this.#canAutoContinueForFollowUp() && this.agent.hasQueuedMessages(),
|
|
9533
|
+
continueQueuedOnly: true,
|
|
9534
|
+
});
|
|
9535
|
+
}
|
|
9494
9536
|
}
|
|
9495
9537
|
|
|
9496
9538
|
/**
|
|
@@ -10284,6 +10326,7 @@ export class AgentSession {
|
|
|
10284
10326
|
delayMs: 1,
|
|
10285
10327
|
generation: this.#promptGeneration,
|
|
10286
10328
|
shouldContinue: () => this.agent.hasQueuedSteering(),
|
|
10329
|
+
continueQueuedOnly: true,
|
|
10287
10330
|
});
|
|
10288
10331
|
}
|
|
10289
10332
|
return outcome;
|
|
@@ -10717,6 +10760,18 @@ export class AgentSession {
|
|
|
10717
10760
|
}
|
|
10718
10761
|
}
|
|
10719
10762
|
this.#clearConstructorToolSelectionAuthority();
|
|
10763
|
+
const configuredDefaultProfile = this.settings.get("modelProfile.default");
|
|
10764
|
+
const configuredDefaultProfileIdentity = configuredDefaultProfile
|
|
10765
|
+
? resolveModelProfileName(
|
|
10766
|
+
configuredDefaultProfile,
|
|
10767
|
+
this.#modelRegistry.getModelProfiles?.() ?? new Map<string, unknown>(),
|
|
10768
|
+
)
|
|
10769
|
+
: undefined;
|
|
10770
|
+
if (this.#activeModelProfile && this.#activeModelProfile !== configuredDefaultProfileIdentity) {
|
|
10771
|
+
this.settings.clearOverride("modelRoles");
|
|
10772
|
+
this.settings.clearOverride("task.agentModelOverrides");
|
|
10773
|
+
this.#activeModelProfile = undefined;
|
|
10774
|
+
}
|
|
10720
10775
|
const inheritedThinkingLevel = resolveThinkingLevelForModel(this.model, this.#getInheritedThinkingLevel());
|
|
10721
10776
|
this.#thinkingLevelMutationRevision++;
|
|
10722
10777
|
this.#thinkingLevelLiveMutationRevision++;
|
|
@@ -10836,15 +10891,13 @@ export class AgentSession {
|
|
|
10836
10891
|
await this.sessionManager.flush();
|
|
10837
10892
|
|
|
10838
10893
|
const boundedColdForkEligible =
|
|
10839
|
-
this.sessionManager.getSessionMemoryStats().coldRetirementActive &&
|
|
10840
|
-
!this.sessionManager.isManagedDestination() &&
|
|
10841
|
-
previousSessionFile !== undefined;
|
|
10894
|
+
this.sessionManager.getSessionMemoryStats().coldRetirementActive && previousSessionFile !== undefined;
|
|
10842
10895
|
if (boundedColdForkEligible) {
|
|
10843
10896
|
const previousManager = this.sessionManager;
|
|
10844
10897
|
const forkedManager = await SessionManager.forkFrom(
|
|
10845
10898
|
previousSessionFile,
|
|
10846
10899
|
previousManager.getCwd(),
|
|
10847
|
-
|
|
10900
|
+
previousManager.getDestinationForFork(),
|
|
10848
10901
|
undefined,
|
|
10849
10902
|
"copy-retain",
|
|
10850
10903
|
this.settings.get("sessionMemory.mode"),
|
|
@@ -10852,8 +10905,8 @@ export class AgentSession {
|
|
|
10852
10905
|
try {
|
|
10853
10906
|
await initializeLocalRoot({
|
|
10854
10907
|
getArtifactsDir: () => forkedManager.getArtifactsDir(),
|
|
10855
|
-
isManagedDestination: () =>
|
|
10856
|
-
getManagedLegacyLocalMigrationSource: () =>
|
|
10908
|
+
isManagedDestination: () => forkedManager.isManagedDestination(),
|
|
10909
|
+
getManagedLegacyLocalMigrationSource: () => forkedManager.getManagedLegacyLocalMigrationSource(),
|
|
10857
10910
|
getSessionId: () => forkedManager.getSessionId(),
|
|
10858
10911
|
});
|
|
10859
10912
|
await this.#settleOwnAsyncJobsBeforeArtifactRetirement();
|
|
@@ -10919,15 +10972,22 @@ export class AgentSession {
|
|
|
10919
10972
|
async setModel(
|
|
10920
10973
|
model: Model,
|
|
10921
10974
|
role: string = "default",
|
|
10922
|
-
options?: {
|
|
10975
|
+
options?: {
|
|
10976
|
+
selector?: string;
|
|
10977
|
+
thinkingLevel?: ThinkingLevel;
|
|
10978
|
+
cause?: ModelChangeCause;
|
|
10979
|
+
onMutationStarted?: () => void;
|
|
10980
|
+
},
|
|
10923
10981
|
): Promise<void> {
|
|
10924
10982
|
const previousEditMode = this.#resolveActiveEditMode();
|
|
10925
|
-
const apiKey = await this.#modelRegistry.getApiKey(model, this.
|
|
10983
|
+
const apiKey = await this.#modelRegistry.getApiKey(model, this.credentialSessionId);
|
|
10926
10984
|
if (!apiKey) {
|
|
10927
10985
|
throw new Error(`No API key for ${model.provider}/${model.id}`);
|
|
10928
10986
|
}
|
|
10929
10987
|
|
|
10988
|
+
options?.onMutationStarted?.();
|
|
10930
10989
|
this.#setModelAuthoritatively(model, options?.cause ?? "user-selection");
|
|
10990
|
+
this.#seedSessionCanonicalVariant(model);
|
|
10931
10991
|
this.sessionManager.appendModelChange(`${model.provider}/${model.id}`, role);
|
|
10932
10992
|
this.settings.setModelRole(
|
|
10933
10993
|
role,
|
|
@@ -10974,6 +11034,19 @@ export class AgentSession {
|
|
|
10974
11034
|
return this.#activeModelProfile;
|
|
10975
11035
|
}
|
|
10976
11036
|
|
|
11037
|
+
/** Resolver intent only for assignments owned by the active profile. */
|
|
11038
|
+
#persistedModelProfileAliasIntent(role: string): { aliasIntent: "preset-equivalent" } | undefined {
|
|
11039
|
+
if (!this.#activeModelProfile) return undefined;
|
|
11040
|
+
const profile = this.#modelRegistry.getModelProfile?.(this.#activeModelProfile);
|
|
11041
|
+
if (!profile) return undefined;
|
|
11042
|
+
const bindings = resolveProfileBindings(profile);
|
|
11043
|
+
const owned =
|
|
11044
|
+
role === "default"
|
|
11045
|
+
? bindings.defaultSelector !== undefined
|
|
11046
|
+
: Object.hasOwn(bindings.modelRoles, role) || Object.hasOwn(bindings.agentModelOverrides, role);
|
|
11047
|
+
return owned ? { aliasIntent: "preset-equivalent" } : undefined;
|
|
11048
|
+
}
|
|
11049
|
+
|
|
10977
11050
|
/**
|
|
10978
11051
|
* Drop the in-session profile marker and the runtime settings overrides a
|
|
10979
11052
|
* session-only profile activation installed. Session transitions
|
|
@@ -11000,9 +11073,6 @@ export class AgentSession {
|
|
|
11000
11073
|
const modelRoles = { ...this.settings.get("modelRoles") };
|
|
11001
11074
|
const agentOverrides = { ...this.settings.get("task.agentModelOverrides") };
|
|
11002
11075
|
for (const [role, baseline] of this.#activeProfileInstalledRoles) {
|
|
11003
|
-
// The `default` role is rewritten durably by concrete picks and
|
|
11004
|
-
// transitions; never shadow the newer durable value with a stale
|
|
11005
|
-
// pre-profile override.
|
|
11006
11076
|
if (role === "default" || baseline === undefined) delete modelRoles[role];
|
|
11007
11077
|
else modelRoles[role] = baseline;
|
|
11008
11078
|
}
|
|
@@ -11015,14 +11085,9 @@ export class AgentSession {
|
|
|
11015
11085
|
this.#activeProfileInstalledRoles.clear();
|
|
11016
11086
|
this.#activeProfileInstalledAgentOverrides.clear();
|
|
11017
11087
|
}
|
|
11018
|
-
// Configured modelBindings share these override slots with profile
|
|
11019
|
-
// activations and must survive the drop even when the profile contributed
|
|
11020
|
-
// no role keys; a profile-free transition leaves them untouched.
|
|
11021
11088
|
if (hadInstalledKeys || this.getActiveModelProfile() !== undefined) {
|
|
11022
11089
|
this.#modelRegistry.reapplyConfiguredModelBindings(this.settings);
|
|
11023
11090
|
}
|
|
11024
|
-
// A dropped profile may have installed a fallback chain; clear it so
|
|
11025
|
-
// retry/resume does not reconstruct the stale profile chain.
|
|
11026
11091
|
const defaultChain = getSessionContextForInternalRead(this.sessionManager).configuredModelChains.default;
|
|
11027
11092
|
if (defaultChain && defaultChain.identity !== undefined) {
|
|
11028
11093
|
this.setConfiguredModelChain("default", [], "user-selection");
|
|
@@ -11031,22 +11096,13 @@ export class AgentSession {
|
|
|
11031
11096
|
this.#preProfileModel = undefined;
|
|
11032
11097
|
}
|
|
11033
11098
|
|
|
11034
|
-
/**
|
|
11035
|
-
* Record which runtime override keys a profile activation installed together
|
|
11036
|
-
* with their pre-profile effective baseline (durable/project value or a
|
|
11037
|
-
* configured `modelBindings` value). First activation for a key wins, so a
|
|
11038
|
-
* profile switch never captures the previous profile's roles as the
|
|
11039
|
-
* baseline. The session-scoped reset restores exactly these keys.
|
|
11040
|
-
*/
|
|
11099
|
+
/** Record runtime override keys installed by a profile activation. */
|
|
11041
11100
|
noteProfileInstalledOverrides(
|
|
11042
11101
|
modelRoles: readonly string[],
|
|
11043
11102
|
agentModelOverrides: readonly string[],
|
|
11044
11103
|
preProfileModel: Model | undefined,
|
|
11045
11104
|
): void {
|
|
11046
11105
|
const bindings = this.#modelRegistry.getConfiguredModelBindings?.();
|
|
11047
|
-
// Captured by the caller before activation replaced the runtime model; reading
|
|
11048
|
-
// `this.model` here would record the profile's own model. First activation wins,
|
|
11049
|
-
// so a chain of session-only profiles still restores the original selection.
|
|
11050
11106
|
if (this.#preProfileModel === undefined) this.#preProfileModel = preProfileModel;
|
|
11051
11107
|
for (const role of modelRoles) {
|
|
11052
11108
|
if (this.#activeProfileInstalledRoles.has(role)) continue;
|
|
@@ -11066,7 +11122,7 @@ export class AgentSession {
|
|
|
11066
11122
|
}
|
|
11067
11123
|
}
|
|
11068
11124
|
|
|
11069
|
-
/** Drop the recorded profile-installed override keys
|
|
11125
|
+
/** Drop the recorded profile-installed override keys after materialization. */
|
|
11070
11126
|
clearProfileInstalledOverrides(): void {
|
|
11071
11127
|
this.#activeProfileInstalledRoles.clear();
|
|
11072
11128
|
this.#activeProfileInstalledAgentOverrides.clear();
|
|
@@ -11229,6 +11285,25 @@ export class AgentSession {
|
|
|
11229
11285
|
return getSessionContextForInternalRead(this.sessionManager).configuredModelChains[role]?.entries;
|
|
11230
11286
|
}
|
|
11231
11287
|
|
|
11288
|
+
/** Return the persisted configured chain with its durable ownership metadata. */
|
|
11289
|
+
getConfiguredModelChainState(role: string):
|
|
11290
|
+
| {
|
|
11291
|
+
entries: readonly string[];
|
|
11292
|
+
origin: string;
|
|
11293
|
+
identity?: string;
|
|
11294
|
+
explicitHead: boolean;
|
|
11295
|
+
}
|
|
11296
|
+
| undefined {
|
|
11297
|
+
const chain = getSessionContextForInternalRead(this.sessionManager).configuredModelChains[role];
|
|
11298
|
+
if (!chain) return undefined;
|
|
11299
|
+
return {
|
|
11300
|
+
entries: [...chain.entries],
|
|
11301
|
+
origin: chain.origin,
|
|
11302
|
+
identity: chain.identity,
|
|
11303
|
+
explicitHead: chain.explicitHead,
|
|
11304
|
+
};
|
|
11305
|
+
}
|
|
11306
|
+
|
|
11232
11307
|
/** Persist the configured fallback selectors for a model role. */
|
|
11233
11308
|
setConfiguredModelChain(
|
|
11234
11309
|
role: string,
|
|
@@ -11245,6 +11320,10 @@ export class AgentSession {
|
|
|
11245
11320
|
explicitHead,
|
|
11246
11321
|
cleared: entries.length === 0,
|
|
11247
11322
|
});
|
|
11323
|
+
if (role === "default") {
|
|
11324
|
+
this.#defaultFallbackController = undefined;
|
|
11325
|
+
this.#defaultFallbackExhaustedLastTurn = false;
|
|
11326
|
+
}
|
|
11248
11327
|
}
|
|
11249
11328
|
|
|
11250
11329
|
/**
|
|
@@ -11270,6 +11349,25 @@ export class AgentSession {
|
|
|
11270
11349
|
this.#emitResolutionFallbackSwitch(controller);
|
|
11271
11350
|
}
|
|
11272
11351
|
|
|
11352
|
+
getDefaultFallbackRuntimeState(): DefaultFallbackRuntimeState {
|
|
11353
|
+
const controller = this.#defaultFallbackChain(false);
|
|
11354
|
+
return {
|
|
11355
|
+
chain: { ...controller.chain, entries: [...controller.chain.entries] },
|
|
11356
|
+
controller: controller.snapshotRuntimeState(),
|
|
11357
|
+
exhaustedLastTurn: this.#defaultFallbackExhaustedLastTurn,
|
|
11358
|
+
};
|
|
11359
|
+
}
|
|
11360
|
+
|
|
11361
|
+
restoreDefaultFallbackRuntimeState(state: DefaultFallbackRuntimeState): void {
|
|
11362
|
+
const controller = new FallbackChainController(
|
|
11363
|
+
{ ...state.chain, entries: [...state.chain.entries] },
|
|
11364
|
+
this.settings.get("fallback.maxAttempts"),
|
|
11365
|
+
);
|
|
11366
|
+
controller.restoreRuntimeState(state.controller);
|
|
11367
|
+
this.#defaultFallbackController = controller;
|
|
11368
|
+
this.#defaultFallbackExhaustedLastTurn = state.exhaustedLastTurn;
|
|
11369
|
+
}
|
|
11370
|
+
|
|
11273
11371
|
/**
|
|
11274
11372
|
* The model selector ("provider/id") that resume restores as the session
|
|
11275
11373
|
* default — the latest session-log `model_change` with role="default".
|
|
@@ -11292,13 +11390,16 @@ export class AgentSession {
|
|
|
11292
11390
|
}
|
|
11293
11391
|
|
|
11294
11392
|
/**
|
|
11295
|
-
*
|
|
11296
|
-
*
|
|
11297
|
-
*
|
|
11298
|
-
*
|
|
11299
|
-
* profile main model the failed activation already recorded as the default.
|
|
11393
|
+
* Record or clear the session resume default ("provider/id") without touching
|
|
11394
|
+
* the live runtime model. An undefined selector appends an explicit clear
|
|
11395
|
+
* marker so rollback preserves the absence of a prior default during replay.
|
|
11396
|
+
* Never writes global settings.
|
|
11300
11397
|
*/
|
|
11301
|
-
recordResumeDefaultModel(selector: string): void {
|
|
11398
|
+
recordResumeDefaultModel(selector: string | undefined): void {
|
|
11399
|
+
if (selector === undefined) {
|
|
11400
|
+
this.sessionManager.clearModelRole("default");
|
|
11401
|
+
return;
|
|
11402
|
+
}
|
|
11302
11403
|
this.sessionManager.appendModelChange(selector, "default");
|
|
11303
11404
|
}
|
|
11304
11405
|
|
|
@@ -11309,10 +11410,9 @@ export class AgentSession {
|
|
|
11309
11410
|
* The change is recorded in the session log as `role: "temporary"` by
|
|
11310
11411
|
* default, which means it is NOT restored as the session default on resume —
|
|
11311
11412
|
* transient retry/fallback/context-promotion/plan switches must not clobber
|
|
11312
|
-
* the user's explicit pick (issue #849).
|
|
11313
|
-
*
|
|
11314
|
-
*
|
|
11315
|
-
* global settings (new sessions keep the global default).
|
|
11413
|
+
* the user's explicit pick (issue #849). Callers that intentionally own the
|
|
11414
|
+
* session resume default may opt into `persistAsSessionDefault: true` without
|
|
11415
|
+
* changing global settings.
|
|
11316
11416
|
* @throws Error if no API key available for the model
|
|
11317
11417
|
*/
|
|
11318
11418
|
async setModelTemporary(
|
|
@@ -11332,7 +11432,7 @@ export class AgentSession {
|
|
|
11332
11432
|
if (suppliedScope && this.#temporaryProviderSessionScopes.at(-1)?.token !== suppliedScope) return;
|
|
11333
11433
|
const previousEditMode = this.#resolveActiveEditMode();
|
|
11334
11434
|
const expectedSessionId = this.sessionId;
|
|
11335
|
-
const apiKey = await this.#modelRegistry.getApiKey(model,
|
|
11435
|
+
const apiKey = await this.#modelRegistry.getApiKey(model, this.credentialSessionId);
|
|
11336
11436
|
if (options?.signal?.aborted) return;
|
|
11337
11437
|
if (this.sessionId !== expectedSessionId) {
|
|
11338
11438
|
throw new Error("Session changed while selecting model");
|
|
@@ -11371,6 +11471,9 @@ export class AgentSession {
|
|
|
11371
11471
|
options?.persistAsSessionDefault ? "default" : "temporary",
|
|
11372
11472
|
);
|
|
11373
11473
|
this.settings.getStorage()?.recordModelUsage(`${model.provider}/${model.id}`);
|
|
11474
|
+
if (options?.persistAsSessionDefault) {
|
|
11475
|
+
this.#seedSessionCanonicalVariant(model);
|
|
11476
|
+
}
|
|
11374
11477
|
|
|
11375
11478
|
// Apply explicit thinking level if given; otherwise prefer the model's
|
|
11376
11479
|
// configured defaultLevel; otherwise re-clamp the current level.
|
|
@@ -11384,6 +11487,22 @@ export class AgentSession {
|
|
|
11384
11487
|
return scope;
|
|
11385
11488
|
}
|
|
11386
11489
|
|
|
11490
|
+
/** Restore the exact live-model state captured before a failed selector transaction. */
|
|
11491
|
+
async restoreModelSelectionForRollback(
|
|
11492
|
+
model: Model | undefined,
|
|
11493
|
+
thinkingLevel: ThinkingLevel | undefined,
|
|
11494
|
+
): Promise<void> {
|
|
11495
|
+
if (model) {
|
|
11496
|
+
await this.setModelTemporary(model, thinkingLevel, { cause: "rollback", reason: "other" });
|
|
11497
|
+
return;
|
|
11498
|
+
}
|
|
11499
|
+
const previousEditMode = this.#resolveActiveEditMode();
|
|
11500
|
+
this.#clearActiveRetryFallback();
|
|
11501
|
+
this.#setModelWithProviderSessionReset(undefined);
|
|
11502
|
+
this.setThinkingLevel(thinkingLevel);
|
|
11503
|
+
await this.#syncEditToolModeAfterModelChange(previousEditMode);
|
|
11504
|
+
}
|
|
11505
|
+
|
|
11387
11506
|
async #restoreDefaultModelSelectionCommit(
|
|
11388
11507
|
commit: CasReceipt,
|
|
11389
11508
|
): Promise<{ readonly stage: DefaultModelSelectionRollbackStage; readonly message: string } | undefined> {
|
|
@@ -11446,6 +11565,7 @@ export class AgentSession {
|
|
|
11446
11565
|
#publishDefaultModelSelection(model: Model, thinkingLevel: ThinkingLevel, systemPrompt: string[] | undefined): void {
|
|
11447
11566
|
this.#clearActiveRetryFallback();
|
|
11448
11567
|
this.#setModelWithProviderSessionReset(model);
|
|
11568
|
+
this.#seedSessionCanonicalVariant(model);
|
|
11449
11569
|
const thinkingLevelChanged = this.#thinkingLevel !== thinkingLevel;
|
|
11450
11570
|
this.#thinkingLevelMutationRevision++;
|
|
11451
11571
|
this.#thinkingLevelLiveMutationRevision++;
|
|
@@ -11514,7 +11634,7 @@ export class AgentSession {
|
|
|
11514
11634
|
if (thinkingLevel === ThinkingLevel.Inherit) {
|
|
11515
11635
|
throw new Error("Default model selection cannot inherit a thinking level");
|
|
11516
11636
|
}
|
|
11517
|
-
const apiKey = await this.#modelRegistry.getApiKey(model, this.
|
|
11637
|
+
const apiKey = await this.#modelRegistry.getApiKey(model, this.credentialSessionId);
|
|
11518
11638
|
if (!apiKey) {
|
|
11519
11639
|
throw new Error(`No API key for ${model.provider}/${model.id}`);
|
|
11520
11640
|
}
|
|
@@ -11612,10 +11732,13 @@ export class AgentSession {
|
|
|
11612
11732
|
|
|
11613
11733
|
/** Number of configured role-model candidates that can be cycled. */
|
|
11614
11734
|
getRoleModelCycleCandidateCount(roleOrder: readonly string[] = this.settings.get("cycleOrder")): number {
|
|
11615
|
-
return this.#getRoleModelCycleCandidates(roleOrder).length;
|
|
11735
|
+
return this.#getRoleModelCycleCandidates(roleOrder, undefined).length;
|
|
11616
11736
|
}
|
|
11617
11737
|
|
|
11618
|
-
#getRoleModelCycleCandidates(
|
|
11738
|
+
#getRoleModelCycleCandidates(
|
|
11739
|
+
roleOrder: readonly string[],
|
|
11740
|
+
canonicalSessionId: string | undefined,
|
|
11741
|
+
): RoleModelCycleCandidate[] {
|
|
11619
11742
|
const availableModels = this.#modelRegistry.getAvailable();
|
|
11620
11743
|
const currentModel = this.model;
|
|
11621
11744
|
if (availableModels.length === 0 || !currentModel) return [];
|
|
@@ -11633,7 +11756,9 @@ export class AgentSession {
|
|
|
11633
11756
|
settings: this.settings,
|
|
11634
11757
|
matchPreferences,
|
|
11635
11758
|
modelRegistry: this.#modelRegistry,
|
|
11636
|
-
sessionId:
|
|
11759
|
+
...(canonicalSessionId ? { sessionId: canonicalSessionId } : {}),
|
|
11760
|
+
...(this.#persistedModelProfileAliasIntent(role) ?? {}),
|
|
11761
|
+
credentialSessionId: this.credentialSessionId,
|
|
11637
11762
|
});
|
|
11638
11763
|
if (!resolved.model) continue;
|
|
11639
11764
|
|
|
@@ -11658,7 +11783,7 @@ export class AgentSession {
|
|
|
11658
11783
|
roleOrder: readonly string[],
|
|
11659
11784
|
options?: { temporary?: boolean },
|
|
11660
11785
|
): Promise<RoleModelCycleResult | undefined> {
|
|
11661
|
-
const roleModels = this.#getRoleModelCycleCandidates(roleOrder);
|
|
11786
|
+
const roleModels = this.#getRoleModelCycleCandidates(roleOrder, this.sessionId);
|
|
11662
11787
|
if (roleModels.length <= 1) return undefined;
|
|
11663
11788
|
|
|
11664
11789
|
const currentModel = this.model!;
|
|
@@ -11701,7 +11826,7 @@ export class AgentSession {
|
|
|
11701
11826
|
if (apiKeysByProvider.has(provider)) {
|
|
11702
11827
|
apiKey = apiKeysByProvider.get(provider);
|
|
11703
11828
|
} else {
|
|
11704
|
-
apiKey = await this.#modelRegistry.getApiKeyForProvider(provider, this.
|
|
11829
|
+
apiKey = await this.#modelRegistry.getApiKeyForProvider(provider, this.credentialSessionId);
|
|
11705
11830
|
apiKeysByProvider.set(provider, apiKey);
|
|
11706
11831
|
}
|
|
11707
11832
|
|
|
@@ -11746,7 +11871,7 @@ export class AgentSession {
|
|
|
11746
11871
|
const nextIndex = direction === "forward" ? (currentIndex + 1) % len : (currentIndex - 1 + len) % len;
|
|
11747
11872
|
const nextModel = availableModels[nextIndex];
|
|
11748
11873
|
|
|
11749
|
-
const apiKey = await this.#modelRegistry.getApiKey(nextModel, this.
|
|
11874
|
+
const apiKey = await this.#modelRegistry.getApiKey(nextModel, this.credentialSessionId);
|
|
11750
11875
|
if (!apiKey) {
|
|
11751
11876
|
throw new Error(`No API key for ${nextModel.provider}/${nextModel.id}`);
|
|
11752
11877
|
}
|
|
@@ -12936,7 +13061,7 @@ export class AgentSession {
|
|
|
12936
13061
|
if (!model) {
|
|
12937
13062
|
throw new Error("No model selected for handoff");
|
|
12938
13063
|
}
|
|
12939
|
-
const apiKey = await this.#modelRegistry.getApiKey(model, this.
|
|
13064
|
+
const apiKey = await this.#modelRegistry.getApiKey(model, this.credentialSessionId);
|
|
12940
13065
|
if (!apiKey) {
|
|
12941
13066
|
throw new Error(`No API key for ${model.provider}`);
|
|
12942
13067
|
}
|
|
@@ -12987,7 +13112,7 @@ export class AgentSession {
|
|
|
12987
13112
|
// and the generated handoff document is preserved for copy/retry.
|
|
12988
13113
|
const previousSessionFile = this.sessionFile;
|
|
12989
13114
|
await this.sessionManager.flush();
|
|
12990
|
-
const rollbackSessionState = this.sessionManager.
|
|
13115
|
+
const rollbackSessionState = await this.sessionManager.captureRollbackState();
|
|
12991
13116
|
const rollbackAgentMessages = [...this.agent.state.messages];
|
|
12992
13117
|
const rollbackSteeringMessages = [...this.#steeringMessages];
|
|
12993
13118
|
const rollbackFollowUpMessages = [...this.#followUpMessages];
|
|
@@ -13099,7 +13224,7 @@ export class AgentSession {
|
|
|
13099
13224
|
// failure is non-destructive. Predecessor gate emitter, provider
|
|
13100
13225
|
// sessions, async jobs, IRC/plan bookkeeping, and injection signatures
|
|
13101
13226
|
// were never mutated before commit, so they survive intact.
|
|
13102
|
-
this.sessionManager.
|
|
13227
|
+
await this.sessionManager.restoreRollbackState(rollbackSessionState);
|
|
13103
13228
|
this.#syncAgentSessionId(rollbackSessionState.sessionId);
|
|
13104
13229
|
this.#rekeyHindsightMemoryForCurrentSessionId();
|
|
13105
13230
|
this.agent.replaceMessages(rollbackAgentMessages, {
|
|
@@ -13531,7 +13656,10 @@ export class AgentSession {
|
|
|
13531
13656
|
imageBytes,
|
|
13532
13657
|
sessionResidentImageBytes: this.sessionManager.getResidentImageBytes(),
|
|
13533
13658
|
materializedResidentBytes:
|
|
13534
|
-
this.#streamingEditFileCache.totalBytes +
|
|
13659
|
+
this.#streamingEditFileCache.totalBytes +
|
|
13660
|
+
sessionMemory.allocatedCacheBytes +
|
|
13661
|
+
sessionMemory.hotResidentBytes +
|
|
13662
|
+
sessionMemory.metadataResidentBytes,
|
|
13535
13663
|
tuiChatChildren: retainedMemory.tuiChatChildren ?? 0,
|
|
13536
13664
|
tuiCachedRenderBytes: retainedMemory.tuiCachedRenderBytes ?? 0,
|
|
13537
13665
|
};
|
|
@@ -14035,7 +14163,7 @@ export class AgentSession {
|
|
|
14035
14163
|
if (!candidate) return undefined;
|
|
14036
14164
|
if (modelsAreEqual(candidate, currentModel)) return undefined;
|
|
14037
14165
|
if (candidate.contextWindow <= contextWindow) return undefined;
|
|
14038
|
-
const apiKey = await this.#modelRegistry.getApiKey(candidate, this.
|
|
14166
|
+
const apiKey = await this.#modelRegistry.getApiKey(candidate, this.credentialSessionId);
|
|
14039
14167
|
if (!apiKey || signal?.aborted) return undefined;
|
|
14040
14168
|
return candidate;
|
|
14041
14169
|
}
|
|
@@ -14075,6 +14203,22 @@ export class AgentSession {
|
|
|
14075
14203
|
this.#syncAppendOnlyContext(model);
|
|
14076
14204
|
}
|
|
14077
14205
|
|
|
14206
|
+
/**
|
|
14207
|
+
* Seed (or clear) the session's sticky canonical variant to reflect an
|
|
14208
|
+
* explicit user/control model selection. When the selected concrete model has
|
|
14209
|
+
* a canonical identity it becomes the session's preferred variant; otherwise
|
|
14210
|
+
* any stale sticky variant is cleared so a previous provider selection cannot
|
|
14211
|
+
* silently resurrect. Never invoked from settings-edit paths — only explicit
|
|
14212
|
+
* model selections remap the session's canonical stickiness.
|
|
14213
|
+
*/
|
|
14214
|
+
#seedSessionCanonicalVariant(model: Model): void {
|
|
14215
|
+
if (this.#modelRegistry.getCanonicalId?.(model)) {
|
|
14216
|
+
this.#modelRegistry.seedCanonicalVariant?.(this.sessionId, model);
|
|
14217
|
+
} else {
|
|
14218
|
+
this.#modelRegistry.clearCanonicalVariant?.(this.sessionId);
|
|
14219
|
+
}
|
|
14220
|
+
}
|
|
14221
|
+
|
|
14078
14222
|
#closeCodexProviderSessionsForHistoryRewrite(): void {
|
|
14079
14223
|
const currentModel = this.model;
|
|
14080
14224
|
if (currentModel?.api !== "openai-codex-responses") return;
|
|
@@ -14409,7 +14553,8 @@ export class AgentSession {
|
|
|
14409
14553
|
settings: this.settings,
|
|
14410
14554
|
matchPreferences: { usageOrder: this.settings.getStorage()?.getModelUsageOrder() },
|
|
14411
14555
|
modelRegistry: this.#modelRegistry,
|
|
14412
|
-
|
|
14556
|
+
credentialSessionId: this.credentialSessionId,
|
|
14557
|
+
...(this.#persistedModelProfileAliasIntent(role) ?? {}),
|
|
14413
14558
|
});
|
|
14414
14559
|
}
|
|
14415
14560
|
|
|
@@ -14505,7 +14650,7 @@ export class AgentSession {
|
|
|
14505
14650
|
const telemetry = resolveTelemetry(this.agent.telemetry, this.sessionId);
|
|
14506
14651
|
|
|
14507
14652
|
for (const candidate of candidates) {
|
|
14508
|
-
const apiKey = await this.#modelRegistry.getApiKey(candidate, this.
|
|
14653
|
+
const apiKey = await this.#modelRegistry.getApiKey(candidate, this.credentialSessionId);
|
|
14509
14654
|
if (!apiKey) continue;
|
|
14510
14655
|
|
|
14511
14656
|
try {
|
|
@@ -14515,7 +14660,10 @@ export class AgentSession {
|
|
|
14515
14660
|
metadata: this.agent.metadataForProvider(candidate.provider),
|
|
14516
14661
|
convertToLlm,
|
|
14517
14662
|
telemetry,
|
|
14518
|
-
authCredentialType: this.#modelRegistry.getSessionCredentialType(
|
|
14663
|
+
authCredentialType: this.#modelRegistry.getSessionCredentialType(
|
|
14664
|
+
candidate.provider,
|
|
14665
|
+
this.credentialSessionId,
|
|
14666
|
+
),
|
|
14519
14667
|
});
|
|
14520
14668
|
} catch (error) {
|
|
14521
14669
|
if (!this.#isCompactionAuthFailure(error)) {
|
|
@@ -14785,6 +14933,7 @@ export class AgentSession {
|
|
|
14785
14933
|
generation,
|
|
14786
14934
|
shouldContinue: () => this.agent.hasQueuedMessages(),
|
|
14787
14935
|
rescheduleOnBusy: true,
|
|
14936
|
+
continueQueuedOnly: true,
|
|
14788
14937
|
onSkip: skipReason => this.#logCompactionContinuationSkipped("queued_continue", skipReason),
|
|
14789
14938
|
onError: error => this.#logCompactionContinuationError("queued_continue", error),
|
|
14790
14939
|
resourceRunId: options?.resourceRunId,
|
|
@@ -14806,6 +14955,7 @@ export class AgentSession {
|
|
|
14806
14955
|
|
|
14807
14956
|
shouldContinue: () => this.agent.hasQueuedMessages(),
|
|
14808
14957
|
rescheduleOnBusy: true,
|
|
14958
|
+
continueQueuedOnly: true,
|
|
14809
14959
|
onSkip: skipReason => this.#logCompactionContinuationSkipped("queued_continue", skipReason),
|
|
14810
14960
|
onError: error => this.#logCompactionContinuationError("queued_continue", error),
|
|
14811
14961
|
resourceRunId: options?.resourceRunId,
|
|
@@ -14895,7 +15045,7 @@ export class AgentSession {
|
|
|
14895
15045
|
let lastError: unknown;
|
|
14896
15046
|
|
|
14897
15047
|
for (const candidate of candidates) {
|
|
14898
|
-
const apiKey = await this.#modelRegistry.getApiKey(candidate, this.
|
|
15048
|
+
const apiKey = await this.#modelRegistry.getApiKey(candidate, this.credentialSessionId);
|
|
14899
15049
|
if (!apiKey) continue;
|
|
14900
15050
|
|
|
14901
15051
|
let attempt = 0;
|
|
@@ -14914,7 +15064,7 @@ export class AgentSession {
|
|
|
14914
15064
|
telemetry,
|
|
14915
15065
|
authCredentialType: this.#modelRegistry.getSessionCredentialType(
|
|
14916
15066
|
candidate.provider,
|
|
14917
|
-
this.
|
|
15067
|
+
this.credentialSessionId,
|
|
14918
15068
|
),
|
|
14919
15069
|
});
|
|
14920
15070
|
break;
|
|
@@ -15065,6 +15215,7 @@ export class AgentSession {
|
|
|
15065
15215
|
suppressPredecessorAgentEnd: true,
|
|
15066
15216
|
shouldContinue: () => this.agent.hasQueuedMessages(),
|
|
15067
15217
|
rescheduleOnBusy: true,
|
|
15218
|
+
continueQueuedOnly: true,
|
|
15068
15219
|
onSkip: reason => this.#logCompactionContinuationSkipped("queued_continue", reason),
|
|
15069
15220
|
onError: error => this.#logCompactionContinuationError("queued_continue", error),
|
|
15070
15221
|
resourceRunId: options?.resourceRunId,
|
|
@@ -15185,6 +15336,10 @@ export class AgentSession {
|
|
|
15185
15336
|
return message.transportFailure?.providerCode?.toLowerCase() === STREAM_FIRST_EVENT_TIMEOUT_PROVIDER_CODE;
|
|
15186
15337
|
}
|
|
15187
15338
|
|
|
15339
|
+
#isTypedEmptyResponse(message: AssistantMessage): boolean {
|
|
15340
|
+
return message.transportFailure?.providerCode?.toLowerCase() === EMPTY_RESPONSE_PROVIDER_CODE;
|
|
15341
|
+
}
|
|
15342
|
+
|
|
15188
15343
|
#isTerminalProviderFirstEventTimeout(message: AssistantMessage): boolean {
|
|
15189
15344
|
return this.#isKimiCodeFirstEventTimeout(message) || this.#isAlibabaTokenPlanFirstEventTimeout(message);
|
|
15190
15345
|
}
|
|
@@ -15209,7 +15364,8 @@ export class AgentSession {
|
|
|
15209
15364
|
classification === "usage_limit" ||
|
|
15210
15365
|
classification === "transient" ||
|
|
15211
15366
|
classification === "unknown" ||
|
|
15212
|
-
classification === "first_event_timeout"
|
|
15367
|
+
classification === "first_event_timeout" ||
|
|
15368
|
+
classification === "empty_response"
|
|
15213
15369
|
);
|
|
15214
15370
|
}
|
|
15215
15371
|
const trigger = classifyFallbackTrigger(transportFailure ?? { status: message.errorStatus });
|
|
@@ -15320,6 +15476,7 @@ export class AgentSession {
|
|
|
15320
15476
|
if (message.stopReason !== "error") return "none";
|
|
15321
15477
|
if (message.errorKind === "provider_safety_stop") return "terminal";
|
|
15322
15478
|
if (this.#isTypedFirstEventTimeout(message)) return "first_event_timeout";
|
|
15479
|
+
if (this.#isTypedEmptyResponse(message)) return "empty_response";
|
|
15323
15480
|
if (!message.errorMessage) return "none";
|
|
15324
15481
|
const err = message.errorMessage;
|
|
15325
15482
|
// Provider safety refusals (e.g. Anthropic stop_reason "refusal" /
|
|
@@ -15470,8 +15627,12 @@ export class AgentSession {
|
|
|
15470
15627
|
controller.chain.entries.slice(resolutionStart),
|
|
15471
15628
|
this.#modelRegistry,
|
|
15472
15629
|
this.settings,
|
|
15473
|
-
this.
|
|
15474
|
-
{
|
|
15630
|
+
this.credentialSessionId,
|
|
15631
|
+
{
|
|
15632
|
+
managedFallback: true,
|
|
15633
|
+
canonicalSessionId: this.sessionId,
|
|
15634
|
+
...(this.#persistedModelProfileAliasIntent("default") ?? {}),
|
|
15635
|
+
},
|
|
15475
15636
|
);
|
|
15476
15637
|
const activeIndex = resolutionStart + resolution.activeIndex;
|
|
15477
15638
|
if (activeIndex > resolutionStart) {
|
|
@@ -15489,7 +15650,7 @@ export class AgentSession {
|
|
|
15489
15650
|
* metadata. Consumers seed only resolution state; role/origin/identity stay
|
|
15490
15651
|
* intrinsic to controller construction and are never inferred at runtime.
|
|
15491
15652
|
*/
|
|
15492
|
-
#defaultFallbackChain(): FallbackChainController {
|
|
15653
|
+
#defaultFallbackChain(materializeLegacyChain = true): FallbackChainController {
|
|
15493
15654
|
const configuredChain = getSessionContextForInternalRead(this.sessionManager).configuredModelChains.default;
|
|
15494
15655
|
|
|
15495
15656
|
const settingsEntries = normalizeModelSelectorValue(
|
|
@@ -15499,7 +15660,7 @@ export class AgentSession {
|
|
|
15499
15660
|
configuredChain?.origin === "legacy_session" &&
|
|
15500
15661
|
configuredChain.entries.length === 1 &&
|
|
15501
15662
|
settingsEntries.length > 1;
|
|
15502
|
-
if (materializeSettingsChain) {
|
|
15663
|
+
if (materializeSettingsChain && materializeLegacyChain) {
|
|
15503
15664
|
this.setConfiguredModelChain("default", settingsEntries, "modelRoles");
|
|
15504
15665
|
}
|
|
15505
15666
|
const chain: ConfiguredFallbackChain = materializeSettingsChain
|
|
@@ -15640,12 +15801,27 @@ export class AgentSession {
|
|
|
15640
15801
|
while (!controller.isExhausted()) {
|
|
15641
15802
|
const selector = controller.currentSelector();
|
|
15642
15803
|
if (!selector) return false;
|
|
15643
|
-
const
|
|
15644
|
-
|
|
15645
|
-
|
|
15646
|
-
|
|
15647
|
-
|
|
15648
|
-
|
|
15804
|
+
const profileAliasIntent = this.#persistedModelProfileAliasIntent("default");
|
|
15805
|
+
const resolved = profileAliasIntent
|
|
15806
|
+
? await resolveModelChainWithAuth(
|
|
15807
|
+
[selector],
|
|
15808
|
+
this.#modelRegistry,
|
|
15809
|
+
this.settings,
|
|
15810
|
+
this.credentialSessionId,
|
|
15811
|
+
{
|
|
15812
|
+
managedFallback: true,
|
|
15813
|
+
...profileAliasIntent,
|
|
15814
|
+
canonicalSessionId: this.agent.providerSessionId ?? this.sessionId,
|
|
15815
|
+
credentialSessionId: this.credentialSessionId,
|
|
15816
|
+
},
|
|
15817
|
+
)
|
|
15818
|
+
: resolveModelRoleValue(selector, this.#modelRegistry.getAvailable(), {
|
|
15819
|
+
settings: this.settings,
|
|
15820
|
+
matchPreferences: { usageOrder: this.settings.getStorage()?.getModelUsageOrder() },
|
|
15821
|
+
modelRegistry: this.#modelRegistry,
|
|
15822
|
+
sessionId: this.agent.providerSessionId ?? this.sessionId,
|
|
15823
|
+
credentialSessionId: this.credentialSessionId,
|
|
15824
|
+
});
|
|
15649
15825
|
if (!resolved.model) {
|
|
15650
15826
|
controller.onResolutionSkip("unknown_model");
|
|
15651
15827
|
continue;
|
|
@@ -15655,7 +15831,7 @@ export class AgentSession {
|
|
|
15655
15831
|
controller.onResolutionSkip(managedCursorUnavailable);
|
|
15656
15832
|
continue;
|
|
15657
15833
|
}
|
|
15658
|
-
const key = await this.#modelRegistry.getApiKey(resolved.model, this.
|
|
15834
|
+
const key = await this.#modelRegistry.getApiKey(resolved.model, this.credentialSessionId);
|
|
15659
15835
|
if (!isAuthenticated(key) && key !== kNoAuth) {
|
|
15660
15836
|
controller.onResolutionSkip("unauthenticated");
|
|
15661
15837
|
continue;
|
|
@@ -15756,24 +15932,24 @@ export class AgentSession {
|
|
|
15756
15932
|
// (1) Pin guard, before any mutation and for every branch.
|
|
15757
15933
|
if (authStorage.hasRuntimeApiKey(provider) || authStorage.hasRuntimeCredentialSelector(provider)) return false;
|
|
15758
15934
|
|
|
15759
|
-
const
|
|
15760
|
-
const activeApiKey = await this.#modelRegistry.getApiKey(this.model,
|
|
15935
|
+
const credentialSessionId = this.credentialSessionId;
|
|
15936
|
+
const activeApiKey = await this.#modelRegistry.getApiKey(this.model, credentialSessionId);
|
|
15761
15937
|
|
|
15762
15938
|
let mutated: boolean;
|
|
15763
15939
|
if (trigger.class === "auth") {
|
|
15764
15940
|
if (!isAuthenticated(activeApiKey)) return false;
|
|
15765
15941
|
mutated = await authStorage.invalidateCredentialMatching(provider, activeApiKey, {
|
|
15766
|
-
sessionId:
|
|
15942
|
+
sessionId: credentialSessionId,
|
|
15767
15943
|
});
|
|
15768
15944
|
} else {
|
|
15769
|
-
mutated = await authStorage.markUsageLimitReached(provider,
|
|
15945
|
+
mutated = await authStorage.markUsageLimitReached(provider, credentialSessionId, {
|
|
15770
15946
|
retryAfterMs: trigger.retryAfterMs,
|
|
15771
15947
|
});
|
|
15772
15948
|
}
|
|
15773
15949
|
if (!mutated) return false;
|
|
15774
15950
|
|
|
15775
15951
|
// (3) Distinct-row proof.
|
|
15776
|
-
return (await this.#modelRegistry.getApiKey(this.model,
|
|
15952
|
+
return (await this.#modelRegistry.getApiKey(this.model, credentialSessionId)) !== activeApiKey;
|
|
15777
15953
|
}
|
|
15778
15954
|
|
|
15779
15955
|
/** Handle retryable errors with exponential backoff. */
|
|
@@ -15797,6 +15973,7 @@ export class AgentSession {
|
|
|
15797
15973
|
if (!managedFallback && !retrySettings.enabled) return false;
|
|
15798
15974
|
const classification = this.#classifyErrorForRetry(message);
|
|
15799
15975
|
const firstEventTimeout = classification === "first_event_timeout";
|
|
15976
|
+
const emptyResponse = classification === "empty_response";
|
|
15800
15977
|
// Content-free message-only watchdog prose (wrapped canonical or bare
|
|
15801
15978
|
// per-provider variants) is admitted like the typed path: it is
|
|
15802
15979
|
// replay-safe, so a bare-default retry may re-issue the request even
|
|
@@ -15805,6 +15982,14 @@ export class AgentSession {
|
|
|
15805
15982
|
if (!managedFallback && firstEventTimeout && this.#retryAttempt === 0) {
|
|
15806
15983
|
this.#firstEventTimeoutRetryStartedAt = Date.now();
|
|
15807
15984
|
}
|
|
15985
|
+
if (emptyResponse && (assistantMessageHasVisibleOrToolContent(message) || !scope || !scopeWasClean)) {
|
|
15986
|
+
return managedOutcome
|
|
15987
|
+
? {
|
|
15988
|
+
type: "terminal",
|
|
15989
|
+
terminal: { stopReason: "error", messages: [message] },
|
|
15990
|
+
}
|
|
15991
|
+
: false;
|
|
15992
|
+
}
|
|
15808
15993
|
const requiresScopedFirstEventTimeout = managedFallback || !legacyRetryConfigured;
|
|
15809
15994
|
if (
|
|
15810
15995
|
(firstEventTimeout || messageOnlyWatchdogTimeout) &&
|
|
@@ -15854,7 +16039,8 @@ export class AgentSession {
|
|
|
15854
16039
|
// Bare defaults retain their narrow watchdog and Codex admissions. A
|
|
15855
16040
|
// first-event timeout adds the typed, content-free, current-clean-scope
|
|
15856
16041
|
// requirement above; other transient watchdogs preserve legacy behavior.
|
|
15857
|
-
|
|
16042
|
+
const canReplayEmptyResponse = emptyResponse && (this.#retryAttempt === 0 || this.#hasCleanRetryReplaySafety);
|
|
16043
|
+
if (!managedFallback && !legacyRetryConfigured && !canReplayRotatedCredential && !canReplayEmptyResponse) {
|
|
15858
16044
|
const bareDefaultCodexOverload = isBareDefaultCodexOverload(message);
|
|
15859
16045
|
const canReplayCodexOverload = bareDefaultCodexOverload;
|
|
15860
16046
|
if (
|
|
@@ -16372,6 +16558,7 @@ export class AgentSession {
|
|
|
16372
16558
|
return result;
|
|
16373
16559
|
} finally {
|
|
16374
16560
|
this.#bashAbortControllers.delete(abortController);
|
|
16561
|
+
this.#scheduleQueuedFollowUpContinuation();
|
|
16375
16562
|
}
|
|
16376
16563
|
}
|
|
16377
16564
|
|
|
@@ -16514,10 +16701,12 @@ export class AgentSession {
|
|
|
16514
16701
|
() => {
|
|
16515
16702
|
this.#evalAbortControllers.delete(abortController);
|
|
16516
16703
|
this.#activeEvalExecutions.delete(execution);
|
|
16704
|
+
this.#scheduleQueuedFollowUpContinuation();
|
|
16517
16705
|
},
|
|
16518
16706
|
() => {
|
|
16519
16707
|
this.#evalAbortControllers.delete(abortController);
|
|
16520
16708
|
this.#activeEvalExecutions.delete(execution);
|
|
16709
|
+
this.#scheduleQueuedFollowUpContinuation();
|
|
16521
16710
|
},
|
|
16522
16711
|
);
|
|
16523
16712
|
return execution;
|
|
@@ -16554,6 +16743,13 @@ export class AgentSession {
|
|
|
16554
16743
|
} else {
|
|
16555
16744
|
this.agent.appendMessage(pythonMessage);
|
|
16556
16745
|
this.sessionManager.appendMessage(pythonMessage);
|
|
16746
|
+
if (!this.#cancelAndSubmitInProgress && this.agent.hasQueuedMessages()) {
|
|
16747
|
+
this.#scheduleAgentContinue({
|
|
16748
|
+
shouldContinue: () => this.#canAutoContinueForFollowUp() && this.agent.hasQueuedMessages(),
|
|
16749
|
+
rescheduleOnBusy: true,
|
|
16750
|
+
continueQueuedOnly: true,
|
|
16751
|
+
});
|
|
16752
|
+
}
|
|
16557
16753
|
options?.onPersisted?.();
|
|
16558
16754
|
}
|
|
16559
16755
|
}
|
|
@@ -16985,7 +17181,7 @@ export class AgentSession {
|
|
|
16985
17181
|
thinkingLevel: this.thinkingLevel ?? ThinkingLevel.Off,
|
|
16986
17182
|
hideThinkingSummary: this.agent.hideThinkingSummary ?? false,
|
|
16987
17183
|
serviceTier: this.serviceTier,
|
|
16988
|
-
credentialSessionId:
|
|
17184
|
+
credentialSessionId: this.credentialSessionId,
|
|
16989
17185
|
providerAffinitySessionId,
|
|
16990
17186
|
sideSessionId: `${providerAffinitySessionId}:btw:${crypto.randomUUID()}`,
|
|
16991
17187
|
};
|
|
@@ -17048,7 +17244,10 @@ export class AgentSession {
|
|
|
17048
17244
|
try {
|
|
17049
17245
|
const model = this.model;
|
|
17050
17246
|
if (!model) throw new Error("No active model on session");
|
|
17051
|
-
const apiKey = await awaitEphemeralAbort(
|
|
17247
|
+
const apiKey = await awaitEphemeralAbort(
|
|
17248
|
+
this.#modelRegistry.getApiKey(model, this.credentialSessionId),
|
|
17249
|
+
args.signal,
|
|
17250
|
+
);
|
|
17052
17251
|
if (!apiKey) throw new Error(`No API key for ${model.provider}/${model.id}`);
|
|
17053
17252
|
sideAttempt = this.agent.mintSideAttemptScope();
|
|
17054
17253
|
const sideScope = sideAttempt.scope;
|
|
@@ -17079,7 +17278,7 @@ export class AgentSession {
|
|
|
17079
17278
|
ephemeralSessionId,
|
|
17080
17279
|
model.provider,
|
|
17081
17280
|
this.#modelRegistry.authStorage,
|
|
17082
|
-
this.
|
|
17281
|
+
this.credentialSessionId,
|
|
17083
17282
|
),
|
|
17084
17283
|
reasoning: toReasoningEffort(this.thinkingLevel),
|
|
17085
17284
|
hideThinkingSummary: this.agent.hideThinkingSummary,
|
|
@@ -17405,7 +17604,7 @@ export class AgentSession {
|
|
|
17405
17604
|
this.#disconnectFromAgent();
|
|
17406
17605
|
// Flush pending writes before switching so restore snapshots reflect committed state.
|
|
17407
17606
|
await this.sessionManager.flush();
|
|
17408
|
-
const previousSessionState = this.sessionManager.
|
|
17607
|
+
const previousSessionState = await this.sessionManager.captureRollbackState();
|
|
17409
17608
|
const previousSessionContext = this.buildDisplaySessionContext();
|
|
17410
17609
|
// switchSession replaces these arrays wholesale during load/rollback, so retaining
|
|
17411
17610
|
// the existing message objects is sufficient and avoids structured-clone failures for
|
|
@@ -17417,6 +17616,7 @@ export class AgentSession {
|
|
|
17417
17616
|
const previousScheduledHiddenNextTurnGeneration = this.#scheduledHiddenNextTurnGeneration;
|
|
17418
17617
|
const previousModel = this.model;
|
|
17419
17618
|
const previousThinkingLevel = this.#thinkingLevel;
|
|
17619
|
+
const previousActiveModelProfile = this.#activeModelProfile;
|
|
17420
17620
|
const previousServiceTier = this.agent.serviceTier;
|
|
17421
17621
|
const previousSelectedMCPToolNames = new Set(this.#selectedMCPToolNames);
|
|
17422
17622
|
const previousTools = [...this.agent.state.tools];
|
|
@@ -17479,20 +17679,48 @@ export class AgentSession {
|
|
|
17479
17679
|
}
|
|
17480
17680
|
|
|
17481
17681
|
const resumeModelBehavior = this.settings.get("session.resumeModelBehavior");
|
|
17482
|
-
const configuredDefaultChain = sessionContext.configuredModelChains.default
|
|
17682
|
+
const configuredDefaultChain = sessionContext.configuredModelChains.default;
|
|
17483
17683
|
const settingsDefaultEntries = normalizeModelSelectorValue(this.settings.getModelRole("default"));
|
|
17484
17684
|
const defaultEntries =
|
|
17485
17685
|
resumeModelBehavior === "useCurrentDefault"
|
|
17486
17686
|
? settingsDefaultEntries
|
|
17487
|
-
: (configuredDefaultChain ??
|
|
17687
|
+
: (configuredDefaultChain?.entries ??
|
|
17688
|
+
(sessionContext.models.default ? [sessionContext.models.default] : []));
|
|
17689
|
+
const profileDefinitions = this.#modelRegistry.getModelProfiles?.() ?? new Map<string, unknown>();
|
|
17690
|
+
const configuredProfileName = this.settings.get("modelProfile.default");
|
|
17691
|
+
const configuredProfileIdentity = configuredProfileName
|
|
17692
|
+
? resolveModelProfileName(configuredProfileName, profileDefinitions)
|
|
17693
|
+
: undefined;
|
|
17694
|
+
const persistedProfileIdentity = configuredDefaultChain?.identity
|
|
17695
|
+
? resolveModelProfileName(configuredDefaultChain.identity, profileDefinitions)
|
|
17696
|
+
: undefined;
|
|
17697
|
+
const liveProfileIdentity = previousActiveModelProfile
|
|
17698
|
+
? resolveModelProfileName(previousActiveModelProfile, profileDefinitions)
|
|
17699
|
+
: undefined;
|
|
17700
|
+
this.#activeModelProfile =
|
|
17701
|
+
resumeModelBehavior === "useCurrentDefault"
|
|
17702
|
+
? liveProfileIdentity && profileDefinitions.has(liveProfileIdentity)
|
|
17703
|
+
? liveProfileIdentity
|
|
17704
|
+
: configuredProfileIdentity && profileDefinitions.has(configuredProfileIdentity)
|
|
17705
|
+
? configuredProfileIdentity
|
|
17706
|
+
: undefined
|
|
17707
|
+
: configuredDefaultChain?.origin === "profile-activation" &&
|
|
17708
|
+
persistedProfileIdentity &&
|
|
17709
|
+
profileDefinitions.has(persistedProfileIdentity)
|
|
17710
|
+
? persistedProfileIdentity
|
|
17711
|
+
: undefined;
|
|
17488
17712
|
this.#defaultFallbackController = undefined;
|
|
17489
17713
|
if (defaultEntries.length > 0) {
|
|
17490
17714
|
const resolution = await resolveModelChainWithAuth(
|
|
17491
17715
|
defaultEntries,
|
|
17492
17716
|
this.#modelRegistry,
|
|
17493
17717
|
this.settings,
|
|
17494
|
-
this.
|
|
17495
|
-
{
|
|
17718
|
+
this.credentialSessionId,
|
|
17719
|
+
{
|
|
17720
|
+
managedFallback: true,
|
|
17721
|
+
canonicalSessionId: this.sessionId,
|
|
17722
|
+
...(this.#persistedModelProfileAliasIntent("default") ?? {}),
|
|
17723
|
+
},
|
|
17496
17724
|
);
|
|
17497
17725
|
const controller = this.#defaultFallbackChain();
|
|
17498
17726
|
this.seedDefaultFallbackResolution(resolution.activeIndex, resolution.skips);
|
|
@@ -17607,9 +17835,10 @@ export class AgentSession {
|
|
|
17607
17835
|
return true;
|
|
17608
17836
|
} catch (error) {
|
|
17609
17837
|
if (transitionCleanupCommitted) throw error;
|
|
17610
|
-
this.sessionManager.
|
|
17838
|
+
await this.sessionManager.restoreRollbackState(previousSessionState);
|
|
17611
17839
|
this.#defaultFallbackController = undefined;
|
|
17612
17840
|
this.#syncAgentSessionId(previousSessionState.sessionId);
|
|
17841
|
+
this.#activeModelProfile = previousActiveModelProfile;
|
|
17613
17842
|
this.#restoreWorkflowGateEmitter(suspendedWorkflowGateEmitter);
|
|
17614
17843
|
this.#rekeyHindsightMemoryForCurrentSessionId();
|
|
17615
17844
|
let restoreMcpError: unknown;
|
|
@@ -17864,7 +18093,7 @@ export class AgentSession {
|
|
|
17864
18093
|
let summaryDetails: unknown;
|
|
17865
18094
|
if (options.summarize && entriesToSummarize.length > 0 && !hookSummary) {
|
|
17866
18095
|
const model = this.model!;
|
|
17867
|
-
const apiKey = await this.#modelRegistry.getApiKey(model, this.
|
|
18096
|
+
const apiKey = await this.#modelRegistry.getApiKey(model, this.credentialSessionId);
|
|
17868
18097
|
if (!apiKey) {
|
|
17869
18098
|
throw new Error(`No API key for ${model.provider}`);
|
|
17870
18099
|
}
|