@gajae-code/coding-agent 0.18.10 → 0.18.12
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 +295 -0
- package/dist/types/async/job-manager.d.ts +7 -3
- package/dist/types/async/result-formatting.d.ts +2 -0
- package/dist/types/autoresearch/git.d.ts +1 -1
- package/dist/types/capability/fs.d.ts +2 -2
- package/dist/types/cli/accounts-cli.d.ts +2 -2
- package/dist/types/cli/doctor/install-repairs.d.ts +4 -1
- package/dist/types/cli/install-activation.d.ts +10 -4
- package/dist/types/cli/public-command-errors.d.ts +1 -0
- package/dist/types/cli/update-cli.d.ts +14 -1
- package/dist/types/cli-ordinary.d.ts +2 -2
- package/dist/types/commands/accounts.d.ts +2 -1
- package/dist/types/commands/migrate.d.ts +1 -1
- package/dist/types/commands/sdk.d.ts +5 -0
- package/dist/types/commit/changelog/index.d.ts +1 -0
- package/dist/types/config/autorouting-tier-map.d.ts +30 -0
- package/dist/types/config/config-hot-reload-trust.d.ts +12 -0
- package/dist/types/config/config-hot-reload.d.ts +39 -0
- package/dist/types/config/file-lock.d.ts +8 -1
- package/dist/types/config/model-discovery-manager.d.ts +2 -0
- package/dist/types/config/model-profile-activation.d.ts +62 -6
- package/dist/types/config/model-registry.d.ts +56 -5
- package/dist/types/config/model-resolver.d.ts +2 -0
- package/dist/types/config/models-config-schema.d.ts +10 -0
- package/dist/types/config/settings-schema.d.ts +46 -3
- package/dist/types/config/settings.d.ts +33 -1
- package/dist/types/dap/config.d.ts +8 -4
- package/dist/types/debug/system-info.d.ts +6 -1
- package/dist/types/discovery/mcp-compat.d.ts +3 -1
- package/dist/types/edit/modes/patch.d.ts +5 -0
- package/dist/types/edit/modes/replace.d.ts +3 -0
- package/dist/types/exec/bash-executor.d.ts +12 -0
- package/dist/types/gjc-runtime/managed-owner-admission.d.ts +3 -0
- package/dist/types/gjc-runtime/session-state-lock.d.ts +2 -0
- package/dist/types/gjc-runtime/state-runtime.d.ts +0 -2
- package/dist/types/migrate/adapters/codex.d.ts +2 -1
- package/dist/types/migrate/adapters/index.d.ts +10 -1
- package/dist/types/migrate/executor.d.ts +3 -1
- package/dist/types/migrate/types.d.ts +15 -3
- package/dist/types/modes/components/settings-selector.d.ts +6 -0
- package/dist/types/modes/controllers/selector-controller.d.ts +2 -0
- package/dist/types/modes/theme/theme.d.ts +1 -0
- package/dist/types/runtime-mcp/content-limits.d.ts +4 -0
- package/dist/types/runtime-mcp/manager.d.ts +7 -0
- package/dist/types/runtime-mcp/plugin-mcp-name-filter.d.ts +22 -0
- package/dist/types/runtime-mcp/tool-bridge.d.ts +4 -0
- package/dist/types/sdk/broker/broker-exit.d.ts +2 -2
- package/dist/types/sdk/broker/broker.d.ts +7 -1
- package/dist/types/sdk/broker/daemon-entry.d.ts +11 -1
- package/dist/types/sdk/broker/doctor-restart.d.ts +1 -1
- package/dist/types/sdk/broker/ensure.d.ts +136 -7
- package/dist/types/sdk/broker/hop.d.ts +36 -0
- package/dist/types/sdk/broker/lifecycle-ledger.d.ts +18 -10
- package/dist/types/sdk/broker/lock-host-identity.d.ts +5 -0
- package/dist/types/sdk/broker/session-index.d.ts +4 -1
- package/dist/types/sdk/bus/chat-command-policy.d.ts +2 -1
- package/dist/types/sdk/bus/index.d.ts +23 -0
- package/dist/types/sdk/bus/reconciliation-store.d.ts +10 -0
- package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +3 -1
- package/dist/types/sdk/cli/session-cli.d.ts +0 -1
- package/dist/types/sdk/host/session-runtime.d.ts +32 -0
- package/dist/types/sdk/prompt-deadline-flush.d.ts +42 -4
- package/dist/types/sdk/prompt-deadline-manager.d.ts +2 -0
- package/dist/types/sdk/protocol/adapter-validation.d.ts +10 -4
- package/dist/types/sdk/reconciliation-extensions.d.ts +1 -1
- package/dist/types/sdk/session-list.d.ts +1 -1
- package/dist/types/session/agent-session.d.ts +58 -2
- package/dist/types/session/internal/managed-session-scope.d.ts +2 -2
- package/dist/types/session/internal/managed-session-storage.d.ts +1 -1
- package/dist/types/session/internal/native-publish-outcome.d.ts +1 -1
- package/dist/types/session/messages.d.ts +5 -1
- package/dist/types/session/session-manager.d.ts +13 -3
- package/dist/types/session/startup-auth-config.d.ts +24 -2
- package/dist/types/session/streaming-output.d.ts +11 -0
- package/dist/types/session/terminal-abort.d.ts +3 -1
- package/dist/types/session-import/service.d.ts +1 -1
- package/dist/types/skill-state/workflow-mutation-guard.d.ts +7 -3
- package/dist/types/slash-commands/helpers/provider-auth-status.d.ts +8 -0
- package/dist/types/task/executor.d.ts +19 -2
- package/dist/types/tools/ast-edit-write-path.d.ts +10 -0
- package/dist/types/tools/atomic-file-write.d.ts +9 -7
- package/dist/types/tools/bash-allowed-prefixes.d.ts +11 -0
- package/dist/types/tools/descriptors.d.ts +2 -0
- package/dist/types/tools/fetch.d.ts +1 -1
- package/dist/types/tools/index.d.ts +2 -0
- package/dist/types/tools/python.d.ts +1 -1
- package/dist/types/tools/report-issue.d.ts +41 -0
- package/dist/types/tools/subagent.d.ts +23 -0
- package/dist/types/web/scrapers/types.d.ts +8 -1
- package/dist/types/web/search/providers/insane.d.ts +7 -1
- package/dist/types/web/search/providers/searxng.d.ts +5 -0
- package/package.json +8 -8
- package/scripts/build-binary.ts +2 -1
- package/scripts/capture-subagent-compact-panel-showcase.ts +105 -0
- package/scripts/generate-docs-index.ts +8 -1
- package/scripts/generate-sdk-operation-inventory.ts +22 -4
- package/src/async/job-manager.ts +356 -56
- package/src/async/result-formatting.ts +40 -0
- package/src/autoresearch/git.ts +9 -2
- package/src/autoresearch/prompts/prompt-setup.md +1 -1
- package/src/capability/fs.ts +27 -19
- package/src/capability/index.ts +2 -2
- package/src/cli/accounts-cli.ts +176 -8
- package/src/cli/auth-broker-cli.ts +21 -11
- package/src/cli/doctor/install-repairs.ts +35 -5
- package/src/cli/fast-help.ts +1 -1
- package/src/cli/install-activation.ts +47 -4
- package/src/cli/migrate-cli.ts +7 -5
- package/src/cli/public-command-errors.ts +4 -0
- package/src/cli/root-flags.ts +2 -2
- package/src/cli/stats-cli.ts +9 -5
- package/src/cli/update-cli.ts +224 -7
- package/src/cli-ordinary.ts +7 -2
- package/src/commands/accounts.ts +10 -5
- package/src/commands/migrate.ts +3 -3
- package/src/commands/sdk.ts +199 -42
- package/src/commands/ultragoal.ts +16 -8
- package/src/commit/changelog/index.ts +39 -1
- package/src/commit/message.ts +10 -3
- package/src/config/autorouting-tier-map.ts +30 -0
- package/src/config/config-hot-reload-trust.ts +148 -0
- package/src/config/config-hot-reload.ts +734 -0
- package/src/config/file-lock.ts +216 -56
- package/src/config/model-bindings-applier.ts +85 -47
- package/src/config/model-discovery-manager.ts +10 -0
- package/src/config/model-profile-activation.ts +716 -113
- package/src/config/model-registry.ts +1334 -158
- package/src/config/model-resolver.ts +3 -1
- package/src/config/models-config-schema.ts +6 -0
- package/src/config/settings-schema.ts +60 -3
- package/src/config/settings.ts +458 -24
- package/src/coordinator-mcp/policy.ts +26 -1
- package/src/crash/sanitize.ts +4 -0
- package/src/customization/import.ts +3 -1
- package/src/dap/config.ts +31 -11
- package/src/dap/defaults.json +1 -1
- package/src/debug/crash-diagnostics.ts +2 -1
- package/src/debug/report-bundle.ts +19 -0
- package/src/debug/system-info.ts +8 -3
- package/src/defaults/gjc/extensions/grok-cli-vendor/src/shared/base-url.ts +21 -7
- package/src/discovery/agents-md.ts +1 -1
- package/src/discovery/helpers.ts +1 -2
- package/src/discovery/mcp-compat.ts +120 -17
- package/src/edit/apply-patch/index.ts +12 -1
- package/src/edit/index.ts +13 -17
- package/src/edit/modes/patch.ts +187 -8
- package/src/edit/modes/replace.ts +136 -0
- package/src/eval/js/context-manager.ts +29 -3
- package/src/eval/js/worker-entry.ts +51 -18
- package/src/eval/py/executor.ts +14 -4
- package/src/eval/py/runner.py +144 -5
- package/src/exec/bash-executor.ts +81 -4
- package/src/exec/bash-shell-guardian.ts +4 -1
- package/src/exec/bash-shell-supervisor.ts +8 -4
- package/src/exec/isolated-shell.ts +5 -1
- package/src/export/html/index.ts +46 -6
- package/src/extensibility/custom-tools/wrapper.ts +3 -1
- package/src/extensibility/extensions/loader.ts +8 -1
- package/src/extensibility/plugins/marketplace/fetcher.ts +2 -2
- package/src/gjc-runtime/gc-runtime.ts +33 -6
- package/src/gjc-runtime/launch-tmux.ts +7 -2
- package/src/gjc-runtime/launch-worktree.test.ts +148 -0
- package/src/gjc-runtime/launch-worktree.ts +101 -14
- package/src/gjc-runtime/managed-owner-admission.ts +46 -2
- package/src/gjc-runtime/session-resolution.ts +38 -6
- package/src/gjc-runtime/session-state-lock.ts +59 -14
- package/src/gjc-runtime/state-runtime.ts +23 -19
- package/src/hooks/native-skill-hook.ts +14 -11
- package/src/internal-urls/docs-index.generated.ts +14 -13
- package/src/internal-urls/local-protocol.ts +142 -8
- package/src/lsp/index.ts +29 -0
- package/src/lsp/workspace-path.ts +23 -0
- package/src/migrate/action-planner.ts +57 -0
- package/src/migrate/adapters/claude-code.ts +23 -4
- package/src/migrate/adapters/codex.ts +22 -4
- package/src/migrate/adapters/index.ts +22 -4
- package/src/migrate/adapters/opencode.ts +6 -1
- package/src/migrate/executor.ts +84 -7
- package/src/migrate/report.ts +19 -2
- package/src/migrate/types.ts +14 -4
- package/src/modes/DESIGN.md +51 -0
- package/src/modes/acp/permission-mode.ts +18 -1
- package/src/modes/components/assistant-message.ts +55 -1
- package/src/modes/components/settings-selector.ts +103 -37
- package/src/modes/controllers/command-controller.ts +38 -2
- package/src/modes/controllers/plan-mode-controller.ts +17 -7
- package/src/modes/controllers/selector-controller.ts +62 -6
- package/src/modes/interactive-mode.ts +68 -0
- package/src/modes/theme/theme.ts +15 -0
- package/src/prompts/tools/debug.md +1 -1
- package/src/prompts/tools/read.md +1 -1
- package/src/prompts/tools/report-issue.md +13 -0
- package/src/prompts/tools/task.md +1 -1
- package/src/runtime-credential-selector.ts +2 -2
- package/src/runtime-mcp/content-limits.ts +7 -0
- package/src/runtime-mcp/manager-pool.test.ts +5 -4
- package/src/runtime-mcp/manager.ts +32 -0
- package/src/runtime-mcp/oauth-flow.ts +32 -6
- package/src/runtime-mcp/plugin-mcp-name-filter.ts +62 -0
- package/src/runtime-mcp/render.ts +16 -1
- package/src/runtime-mcp/tool-bridge.ts +126 -17
- package/src/sdk/broker/broker-exit.ts +8 -5
- package/src/sdk/broker/broker.ts +342 -82
- package/src/sdk/broker/daemon-entry.ts +338 -116
- package/src/sdk/broker/doctor-restart.ts +2 -1
- package/src/sdk/broker/ensure.ts +1143 -265
- package/src/sdk/broker/hop.ts +274 -0
- package/src/sdk/broker/lifecycle-ledger.ts +35 -11
- package/src/sdk/broker/lock-host-identity.ts +29 -0
- package/src/sdk/broker/session-index.ts +62 -12
- package/src/sdk/bus/chat-command-policy.ts +16 -3
- package/src/sdk/bus/index.ts +52 -21
- package/src/sdk/bus/reconciliation-store.ts +201 -8
- package/src/sdk/bus/telegram-daemon-contract.ts +3 -1
- package/src/sdk/cli/session-cli.ts +21 -6
- package/src/sdk/host/session-runtime.test.ts +118 -23
- package/src/sdk/host/session-runtime.ts +352 -47
- package/src/sdk/lifecycle/service.ts +1 -0
- package/src/sdk/prompt-deadline-flush.ts +78 -5
- package/src/sdk/prompt-deadline-manager.ts +68 -14
- package/src/sdk/protocol/adapter-validation.ts +53 -11
- package/src/sdk/protocol/operation-inventory.generated.json +101 -2
- package/src/sdk/reconciliation-extensions.ts +1 -0
- package/src/sdk/session-list.ts +1 -0
- package/src/sdk/session.ts +114 -61
- package/src/session/agent-session.ts +1043 -81
- package/src/session/internal/managed-session-scope.ts +126 -39
- package/src/session/internal/managed-session-storage.ts +14 -11
- package/src/session/internal/native-publish-outcome.ts +5 -1
- package/src/session/messages.ts +47 -6
- package/src/session/session-manager.ts +236 -14
- package/src/session/startup-auth-config.ts +116 -8
- package/src/session/streaming-output.ts +262 -4
- package/src/session/terminal-abort.ts +14 -2
- package/src/session-import/service.ts +1 -0
- package/src/skill-state/workflow-mutation-guard.ts +491 -32
- package/src/slash-commands/builtin-registry.ts +15 -3
- package/src/slash-commands/helpers/credential-switch.ts +10 -5
- package/src/slash-commands/helpers/provider-auth-status.ts +456 -0
- package/src/task/executor.ts +103 -12
- package/src/task/index.ts +389 -175
- package/src/tools/ast-edit-write-path.ts +64 -0
- package/src/tools/ast-edit.ts +18 -1
- package/src/tools/atomic-file-write.ts +46 -15
- package/src/tools/auto-generated-guard.ts +3 -0
- package/src/tools/bash-allowed-prefixes.ts +153 -5
- package/src/tools/bash-interactive.ts +14 -2
- package/src/tools/bash.ts +14 -0
- package/src/tools/debug.ts +7 -6
- package/src/tools/descriptors.test.ts +44 -0
- package/src/tools/descriptors.ts +34 -2
- package/src/tools/fetch.ts +3 -2
- package/src/tools/index.ts +2 -0
- package/src/tools/output-meta.ts +23 -2
- package/src/tools/python.ts +1 -1
- package/src/tools/read.ts +17 -35
- package/src/tools/report-issue.ts +184 -0
- package/src/tools/subagent-render.ts +114 -12
- package/src/tools/subagent.ts +108 -1
- package/src/tools/todo-write.ts +41 -3
- package/src/tools/tool-catalog.generated.ts +31 -3
- package/src/tools/write.ts +35 -3
- package/src/utils/commit-message-generator.ts +9 -2
- package/src/utils/file-mentions.ts +29 -1
- package/src/utils/title-generator.ts +7 -2
- package/src/web/scrapers/index.ts +3 -1
- package/src/web/scrapers/types.ts +8 -0
- package/src/web/scrapers/youtube.ts +3 -2
- package/src/web/search/providers/insane.ts +91 -28
- package/src/web/search/providers/searxng.ts +62 -10
|
@@ -221,14 +221,21 @@ import {
|
|
|
221
221
|
type SubagentLifecycle,
|
|
222
222
|
} from "../async";
|
|
223
223
|
import { asyncJobEndpointId as deriveAsyncJobEndpointId } from "../async/endpoint-id";
|
|
224
|
+
import { formatAsyncResultForFollowUp } from "../async/result-formatting";
|
|
224
225
|
import { reset as resetCapabilities } from "../capability";
|
|
225
226
|
import type { Rule } from "../capability/rule";
|
|
226
227
|
import type { CasReceipt } from "../config/atomic-yaml-patch";
|
|
228
|
+
import type { ConfigHotReloadCandidate, ConfigHotReloadPaths } from "../config/config-hot-reload";
|
|
227
229
|
import {
|
|
228
230
|
activateModelProfile,
|
|
229
231
|
applyModelProfileRuntimeBindings,
|
|
232
|
+
finishPreparedModelProfileActivation,
|
|
230
233
|
materializeActiveModelProfileAssignment,
|
|
234
|
+
type PreparedModelProfileActivation,
|
|
235
|
+
prepareModelProfileActivation,
|
|
236
|
+
publishPreparedModelProfileActivation,
|
|
231
237
|
resolveMissingSessionModelRecovery,
|
|
238
|
+
rollbackPreparedModelProfileActivation,
|
|
232
239
|
} from "../config/model-profile-activation";
|
|
233
240
|
import {
|
|
234
241
|
ModelProfileRegistryError,
|
|
@@ -249,6 +256,7 @@ import {
|
|
|
249
256
|
kNoAuth,
|
|
250
257
|
MODEL_ROLE_IDS,
|
|
251
258
|
type ModelRegistry,
|
|
259
|
+
type ModelsConfigReloadCandidate,
|
|
252
260
|
} from "../config/model-registry";
|
|
253
261
|
import {
|
|
254
262
|
extractExplicitThinkingSelector,
|
|
@@ -264,10 +272,14 @@ import {
|
|
|
264
272
|
} from "../config/model-resolver";
|
|
265
273
|
import { type ModelSelectorValue, normalizeModelSelectorValue } from "../config/model-selector-value";
|
|
266
274
|
import { expandPromptTemplate, type PromptTemplate } from "../config/prompt-templates";
|
|
267
|
-
import type {
|
|
275
|
+
import type {
|
|
276
|
+
Settings,
|
|
277
|
+
SettingsConfigReloadPublication,
|
|
278
|
+
SettingsGlobalConfigCandidate,
|
|
279
|
+
SkillsSettings,
|
|
280
|
+
} from "../config/settings";
|
|
268
281
|
import { onAppendOnlyModeChanged } from "../config/settings";
|
|
269
|
-
import type
|
|
270
|
-
import { getDefault } from "../config/settings-schema";
|
|
282
|
+
import { getDefault, SETTINGS_SCHEMA, type SettingPath } from "../config/settings-schema";
|
|
271
283
|
import { resolveEagerTaskDelegation } from "../config/task-delegation";
|
|
272
284
|
import { RawSseDebugBuffer } from "../debug/raw-sse-buffer";
|
|
273
285
|
import { loadCapability } from "../discovery";
|
|
@@ -412,11 +424,15 @@ import type { LazyService } from "../runtime/lazy-service";
|
|
|
412
424
|
import type { NetworkPrewarmRuntime } from "../runtime/network-prewarm-service";
|
|
413
425
|
import type { WorkspaceTreeRuntime } from "../runtime/workspace-tree-service";
|
|
414
426
|
import { type ExactMcpServerControlResult, MCPManager } from "../runtime-mcp/manager";
|
|
427
|
+
import {
|
|
428
|
+
omitPluginMcpNameShadows,
|
|
429
|
+
retainPluginMcpMandatoryNames,
|
|
430
|
+
} from "../runtime-mcp/plugin-mcp-name-filter";
|
|
415
431
|
import { attachExactMcpControls, getExactMcpControls, revokeExactMcpControls } from "../runtime-mcp/redaction";
|
|
416
432
|
import type { NotificationSessionController } from "../sdk/bus/session-control";
|
|
417
433
|
import { buildSyntheticModelId, syntheticNamespaceCollision } from "../sdk/model-profile-model";
|
|
418
434
|
import { sanitizePromptFailure } from "../sdk/prompt-failure";
|
|
419
|
-
import type
|
|
435
|
+
import { obfuscateMessages, type SecretObfuscator } from "../secrets/obfuscator";
|
|
420
436
|
import { formatNoCredentialOnboardingError, NoModelSelectedError } from "../setup/model-onboarding-guidance";
|
|
421
437
|
import {
|
|
422
438
|
isCanonicalGjcWorkflowSkill,
|
|
@@ -566,31 +582,6 @@ import { ToolChoiceQueue } from "./tool-choice-queue";
|
|
|
566
582
|
import { pruneSupersededMaintenanceReminders, pruneSupersededVolatileProjectContext } from "./volatile-context-pruning";
|
|
567
583
|
import { YieldQueue } from "./yield-queue";
|
|
568
584
|
|
|
569
|
-
const ASYNC_INLINE_RESULT_MAX_CHARS = 12_000;
|
|
570
|
-
const ASYNC_PREVIEW_MAX_CHARS = 4_000;
|
|
571
|
-
|
|
572
|
-
async function formatParkedAsyncResult(
|
|
573
|
-
sessionManager: SessionManager,
|
|
574
|
-
result: string,
|
|
575
|
-
allowArtifact = true,
|
|
576
|
-
): Promise<string> {
|
|
577
|
-
if (result.length <= ASYNC_INLINE_RESULT_MAX_CHARS) return result;
|
|
578
|
-
const preview = `${result.slice(0, ASYNC_PREVIEW_MAX_CHARS)}\n\n[Output truncated. Showing first ${ASYNC_PREVIEW_MAX_CHARS.toLocaleString()} characters.]`;
|
|
579
|
-
try {
|
|
580
|
-
if (!allowArtifact) return preview;
|
|
581
|
-
const { path: artifactPath, id: artifactId } = await sessionManager.allocateArtifactPath("async");
|
|
582
|
-
if (artifactPath && artifactId) {
|
|
583
|
-
await Bun.write(artifactPath, result);
|
|
584
|
-
return `${preview}\nFull output: artifact://${artifactId}`;
|
|
585
|
-
}
|
|
586
|
-
} catch (error) {
|
|
587
|
-
logger.warn("Failed to persist parked async follow-up artifact", {
|
|
588
|
-
error: error instanceof Error ? error.message : String(error),
|
|
589
|
-
});
|
|
590
|
-
}
|
|
591
|
-
return preview;
|
|
592
|
-
}
|
|
593
|
-
|
|
594
585
|
/**
|
|
595
586
|
* #4560: structured workflow recovery projection from canonical durable
|
|
596
587
|
* Ralplan/Ultragoal state, consumed by the compaction summary context and
|
|
@@ -2010,14 +2001,30 @@ async function discardPreparedNewSessionAfterFailure(
|
|
|
2010
2001
|
// ============================================================================
|
|
2011
2002
|
|
|
2012
2003
|
/** Tools that require user permission before execution when an ACP client is connected. */
|
|
2013
|
-
const PERMISSION_REQUIRED_TOOLS = new Set([
|
|
2004
|
+
const PERMISSION_REQUIRED_TOOLS = new Set([
|
|
2005
|
+
"bash",
|
|
2006
|
+
"monitor",
|
|
2007
|
+
"eval",
|
|
2008
|
+
"edit",
|
|
2009
|
+
"delete",
|
|
2010
|
+
"move",
|
|
2011
|
+
"github",
|
|
2012
|
+
"computer",
|
|
2013
|
+
"python",
|
|
2014
|
+
"recipe",
|
|
2015
|
+
]);
|
|
2014
2016
|
|
|
2015
2017
|
function isShellExecutionPermissionTool(toolName: string): boolean {
|
|
2016
2018
|
return toolName === "bash" || toolName === "monitor";
|
|
2017
2019
|
}
|
|
2018
2020
|
|
|
2019
2021
|
function isExecutionPermissionTool(toolName: string): boolean {
|
|
2020
|
-
return
|
|
2022
|
+
return (
|
|
2023
|
+
isShellExecutionPermissionTool(toolName) ||
|
|
2024
|
+
toolName === "eval" ||
|
|
2025
|
+
toolName === "python" ||
|
|
2026
|
+
toolName === "recipe"
|
|
2027
|
+
);
|
|
2021
2028
|
}
|
|
2022
2029
|
|
|
2023
2030
|
/** Permission options presented to the client on each gated tool call. */
|
|
@@ -2099,6 +2106,16 @@ function getPermissionIntent(
|
|
|
2099
2106
|
cacheKey: toolName,
|
|
2100
2107
|
};
|
|
2101
2108
|
}
|
|
2109
|
+
if (toolName === "python") {
|
|
2110
|
+
const action = getStringProperty(a, "action");
|
|
2111
|
+
if (action === "clear") return { toolName, title: "python clear", cacheKey: toolName };
|
|
2112
|
+
const code = getStringProperty(a, "code")?.slice(0, 80);
|
|
2113
|
+
return { toolName, title: code && code.length > 0 ? code : "python", cacheKey: toolName };
|
|
2114
|
+
}
|
|
2115
|
+
if (toolName === "recipe") {
|
|
2116
|
+
const op = getStringProperty(a, "op")?.slice(0, 80);
|
|
2117
|
+
return { toolName, title: op && op.length > 0 ? op : "recipe", cacheKey: toolName };
|
|
2118
|
+
}
|
|
2102
2119
|
if (toolName === "delete") {
|
|
2103
2120
|
const p = getStringProperty(a, "path");
|
|
2104
2121
|
return { toolName, title: p ? `Delete ${p}` : toolName, paths: p ? [p] : undefined, cacheKey: toolName };
|
|
@@ -2135,6 +2152,10 @@ function getPermissionIntent(
|
|
|
2135
2152
|
};
|
|
2136
2153
|
}
|
|
2137
2154
|
if (toolName === "github") return githubPermissionIntent(args);
|
|
2155
|
+
if (toolName === "computer") {
|
|
2156
|
+
const action = getStringProperty(a, "action");
|
|
2157
|
+
return { toolName, title: action ? `Computer ${action}` : toolName, cacheKey: toolName };
|
|
2158
|
+
}
|
|
2138
2159
|
return undefined;
|
|
2139
2160
|
}
|
|
2140
2161
|
|
|
@@ -2335,8 +2356,10 @@ export class WorkerIntegrationRequestScheduler {
|
|
|
2335
2356
|
this.#ownedRequests.add(request);
|
|
2336
2357
|
void request.finally(() => this.#ownedRequests.delete(request)).catch(() => {});
|
|
2337
2358
|
let timeout: ReturnType<typeof setTimeout> | undefined;
|
|
2359
|
+
let timedOut = false;
|
|
2338
2360
|
const deadline = new Promise<WorkerIntegrationOutcome>(resolve => {
|
|
2339
2361
|
timeout = setTimeout(() => {
|
|
2362
|
+
timedOut = true;
|
|
2340
2363
|
controller.abort(new Error("Worker integration request timed out"));
|
|
2341
2364
|
resolve({ status: "timed_out" });
|
|
2342
2365
|
}, this.timeoutMs);
|
|
@@ -2349,7 +2372,12 @@ export class WorkerIntegrationRequestScheduler {
|
|
|
2349
2372
|
deadline,
|
|
2350
2373
|
]).finally(() => {
|
|
2351
2374
|
if (timeout) clearTimeout(timeout);
|
|
2352
|
-
|
|
2375
|
+
// Only abort if the timeout actually fired. If the request settled before the
|
|
2376
|
+
// timeout (timedOut=false), don't send the abort signal again: this ensures
|
|
2377
|
+
// SDK/ACP settlement is not delayed by recovery requests that ignore their
|
|
2378
|
+
// abort signal (#6508). The request may still be running, but that work
|
|
2379
|
+
// should not block client-visible completion.
|
|
2380
|
+
if (timedOut) controller.abort();
|
|
2353
2381
|
this.#inFlight = undefined;
|
|
2354
2382
|
if (this.#pending) this.#start();
|
|
2355
2383
|
});
|
|
@@ -2681,6 +2709,68 @@ export interface DefaultFallbackRuntimeState {
|
|
|
2681
2709
|
exhaustedLastTurn: boolean;
|
|
2682
2710
|
}
|
|
2683
2711
|
|
|
2712
|
+
export interface AgentSessionProfileInstalledOverrideState {
|
|
2713
|
+
modelRoles: ReadonlyMap<string, ModelSelectorValue | undefined>;
|
|
2714
|
+
agentModelOverrides: ReadonlyMap<string, ModelSelectorValue | undefined>;
|
|
2715
|
+
installedModelRoles: ReadonlyMap<string, ModelSelectorValue>;
|
|
2716
|
+
installedAgentModelOverrides: ReadonlyMap<string, ModelSelectorValue>;
|
|
2717
|
+
manualModelRoles: ReadonlySet<string>;
|
|
2718
|
+
manualAgentModelOverrides: ReadonlySet<string>;
|
|
2719
|
+
configuredAgentModelOverrides: ReadonlySet<string>;
|
|
2720
|
+
preProfileModel: Model | undefined;
|
|
2721
|
+
}
|
|
2722
|
+
|
|
2723
|
+
export interface PreparedProfileModelSelection {
|
|
2724
|
+
readonly sessionId: string;
|
|
2725
|
+
readonly model: Model;
|
|
2726
|
+
readonly thinkingLevel: ThinkingLevel | undefined;
|
|
2727
|
+
readonly committedThinkingLevel: ThinkingLevel | undefined;
|
|
2728
|
+
readonly previousModel: Model | undefined;
|
|
2729
|
+
readonly previousThinkingLevel: ThinkingLevel | undefined;
|
|
2730
|
+
readonly previousEditMode: EditMode;
|
|
2731
|
+
readonly signal: AbortSignal | undefined;
|
|
2732
|
+
}
|
|
2733
|
+
|
|
2734
|
+
export interface ConfigurationReloadResult {
|
|
2735
|
+
readonly applied: boolean;
|
|
2736
|
+
readonly settingsChanged: boolean;
|
|
2737
|
+
readonly modelsChanged: boolean;
|
|
2738
|
+
readonly changedSettings: readonly SettingPath[];
|
|
2739
|
+
}
|
|
2740
|
+
|
|
2741
|
+
export type ConfigurationReloadErrorCode =
|
|
2742
|
+
| "SESSION_UNAVAILABLE"
|
|
2743
|
+
| "PATH_CHANGED"
|
|
2744
|
+
| "SETTINGS_INVALID"
|
|
2745
|
+
| "MODELS_INVALID"
|
|
2746
|
+
| "MODEL_UNAVAILABLE"
|
|
2747
|
+
| "ACTIVE_PROFILE_INVALID"
|
|
2748
|
+
| "PUBLICATION_FAILED"
|
|
2749
|
+
| "VALIDATION_CLEANUP_FAILED";
|
|
2750
|
+
|
|
2751
|
+
const CONFIGURATION_RELOAD_ERROR_MESSAGES: Record<ConfigurationReloadErrorCode, string> = {
|
|
2752
|
+
SESSION_UNAVAILABLE: "Configuration reload is unavailable for this session.",
|
|
2753
|
+
PATH_CHANGED: "Configuration files changed while reload was pending.",
|
|
2754
|
+
SETTINGS_INVALID: "The updated settings configuration is invalid.",
|
|
2755
|
+
MODELS_INVALID: "The updated model catalog is invalid.",
|
|
2756
|
+
MODEL_UNAVAILABLE: "The selected model is unavailable in the updated configuration.",
|
|
2757
|
+
ACTIVE_PROFILE_INVALID: "The active model preset is invalid for the updated configuration.",
|
|
2758
|
+
PUBLICATION_FAILED: "Configuration reload could not be applied.",
|
|
2759
|
+
VALIDATION_CLEANUP_FAILED: "Configuration validation could not release its staged catalog.",
|
|
2760
|
+
};
|
|
2761
|
+
|
|
2762
|
+
export class ConfigurationReloadError extends Error {
|
|
2763
|
+
readonly code: ConfigurationReloadErrorCode;
|
|
2764
|
+
readonly retryable: boolean;
|
|
2765
|
+
|
|
2766
|
+
constructor(code: ConfigurationReloadErrorCode, cause?: unknown, options?: { readonly retryable?: boolean }) {
|
|
2767
|
+
super(CONFIGURATION_RELOAD_ERROR_MESSAGES[code], cause === undefined ? undefined : { cause });
|
|
2768
|
+
this.name = "ConfigurationReloadError";
|
|
2769
|
+
this.code = code;
|
|
2770
|
+
this.retryable = options?.retryable === true;
|
|
2771
|
+
}
|
|
2772
|
+
}
|
|
2773
|
+
|
|
2684
2774
|
/**
|
|
2685
2775
|
* Fire-and-forget continuations (auto-compaction retries, queued follow-ups) race a
|
|
2686
2776
|
* still-busy agent whose current turn can legitimately hold the run for minutes.
|
|
@@ -2787,6 +2877,11 @@ export class AgentSession {
|
|
|
2787
2877
|
#unavailableModelProfile: string | undefined;
|
|
2788
2878
|
#activeProfileInstalledRoles = new Map<string, ModelSelectorValue | undefined>();
|
|
2789
2879
|
#activeProfileInstalledAgentOverrides = new Map<string, ModelSelectorValue | undefined>();
|
|
2880
|
+
#activeProfileInstalledRoleValues = new Map<string, ModelSelectorValue>();
|
|
2881
|
+
#activeProfileInstalledAgentOverrideValues = new Map<string, ModelSelectorValue>();
|
|
2882
|
+
#activeProfileManualModelRoles = new Set<string>();
|
|
2883
|
+
#activeProfileManualAgentModelOverrides = new Set<string>();
|
|
2884
|
+
#activeProfileConfiguredAgentModelOverrides = new Set<string>();
|
|
2790
2885
|
#preProfileModel: Model | undefined;
|
|
2791
2886
|
#sessionAdmissionQueue: SessionAdmissionEntry[] = [];
|
|
2792
2887
|
#activeSessionAdmission: SessionAdmissionEntry | undefined;
|
|
@@ -2797,6 +2892,7 @@ export class AgentSession {
|
|
|
2797
2892
|
#successorSessionAdmissionContext = new AsyncLocalStorage<SuccessorSessionAdmission>();
|
|
2798
2893
|
#selectionFenceGenerationContext = new AsyncLocalStorage<number>();
|
|
2799
2894
|
#selectionFenceTail: Promise<void> = Promise.resolve();
|
|
2895
|
+
#configurationReloadControllers = new Set<AbortController>();
|
|
2800
2896
|
#pendingSelectionFences = 0;
|
|
2801
2897
|
#selectionAwaitingMutationTransaction: symbol | undefined;
|
|
2802
2898
|
#authorizedClosedSelectionTransaction: symbol | undefined;
|
|
@@ -3716,7 +3812,7 @@ export class AgentSession {
|
|
|
3716
3812
|
}
|
|
3717
3813
|
: undefined;
|
|
3718
3814
|
const allowArtifact = ownedCompletion === undefined || isOwnedCompletionEnvelopeAllowed(ownedCompletion);
|
|
3719
|
-
void
|
|
3815
|
+
void formatAsyncResultForFollowUp(this.sessionManager, disposition.text, allowArtifact)
|
|
3720
3816
|
.then(formattedResult => {
|
|
3721
3817
|
const manager = this.#ownedAsyncJobManager;
|
|
3722
3818
|
if (manager?.isDeliverySuppressed(job.id, job.generation)) {
|
|
@@ -4235,6 +4331,10 @@ export class AgentSession {
|
|
|
4235
4331
|
* exposes an unowned history rewrite.
|
|
4236
4332
|
*/
|
|
4237
4333
|
#sessionTransitionKind: string | undefined;
|
|
4334
|
+
// Detects transitions that begin and finish while an async preflight is staging.
|
|
4335
|
+
#sessionTransitionGeneration = 0;
|
|
4336
|
+
#sessionTransitionCompletion: Promise<void> = Promise.resolve();
|
|
4337
|
+
#resolveSessionTransitionCompletion: (() => void) | undefined;
|
|
4238
4338
|
#queuedDeliveryPendingWhileTransition = false;
|
|
4239
4339
|
#deferredAutoContinueDuringTransition: (() => void) | undefined;
|
|
4240
4340
|
#coordinatorPersistGeneration = 0;
|
|
@@ -4312,11 +4412,24 @@ export class AgentSession {
|
|
|
4312
4412
|
{ code: "busy" },
|
|
4313
4413
|
);
|
|
4314
4414
|
}
|
|
4415
|
+
if (["new-session", "switch-session", "clear-context", "fork", "branch", "navigate-tree"].includes(kind)) {
|
|
4416
|
+
for (const controller of this.#configurationReloadControllers) controller.abort();
|
|
4417
|
+
}
|
|
4418
|
+
this.#sessionTransitionGeneration += 1;
|
|
4315
4419
|
this.#sessionTransitionKind = kind;
|
|
4420
|
+
const completion = Promise.withResolvers<void>();
|
|
4421
|
+
this.#sessionTransitionCompletion = completion.promise;
|
|
4422
|
+
this.#resolveSessionTransitionCompletion = completion.resolve;
|
|
4316
4423
|
this.#coordinatorPersistGeneration += 1;
|
|
4317
4424
|
this.#wakeFollowUpReservationTransitionWaiters();
|
|
4318
4425
|
}
|
|
4319
4426
|
|
|
4427
|
+
async #waitForSessionTransitionEnd(signal: AbortSignal): Promise<void> {
|
|
4428
|
+
while (this.#sessionTransitionKind !== undefined) {
|
|
4429
|
+
await awaitPromptInvocationPreflight(this.#sessionTransitionCompletion, signal);
|
|
4430
|
+
}
|
|
4431
|
+
}
|
|
4432
|
+
|
|
4320
4433
|
async #settleActivePromptForHistoryTransition(): Promise<void> {
|
|
4321
4434
|
if (this.#livePromptsInFlight() > 0 || this.agent.state.isStreaming) {
|
|
4322
4435
|
await this.abort({ cause: "session_switch" });
|
|
@@ -4337,6 +4450,10 @@ export class AgentSession {
|
|
|
4337
4450
|
|
|
4338
4451
|
#endSessionTransition(): void {
|
|
4339
4452
|
this.#sessionTransitionKind = undefined;
|
|
4453
|
+
const resolveCompletion = this.#resolveSessionTransitionCompletion;
|
|
4454
|
+
this.#resolveSessionTransitionCompletion = undefined;
|
|
4455
|
+
this.#sessionTransitionCompletion = Promise.resolve();
|
|
4456
|
+
resolveCompletion?.();
|
|
4340
4457
|
const deferredAutoContinue = this.#deferredAutoContinueDuringTransition;
|
|
4341
4458
|
this.#deferredAutoContinueDuringTransition = undefined;
|
|
4342
4459
|
if (!this.#isDisposed && !this.#sessionAdmissionClosing) deferredAutoContinue?.();
|
|
@@ -4476,6 +4593,7 @@ export class AgentSession {
|
|
|
4476
4593
|
allowPromptContinuationReentry?: boolean;
|
|
4477
4594
|
idleDelivery?: boolean;
|
|
4478
4595
|
startupPromptWaiterRelease?: () => void;
|
|
4596
|
+
skipModelRegistryLease?: boolean;
|
|
4479
4597
|
},
|
|
4480
4598
|
): Promise<T> {
|
|
4481
4599
|
const owner = this.#sessionAdmissionContext.getStore();
|
|
@@ -4589,11 +4707,13 @@ export class AgentSession {
|
|
|
4589
4707
|
{ code: "busy" },
|
|
4590
4708
|
);
|
|
4591
4709
|
}
|
|
4592
|
-
|
|
4593
4710
|
const release = () => {
|
|
4594
4711
|
releaseEntry();
|
|
4595
4712
|
};
|
|
4596
|
-
|
|
4713
|
+
const admittedBody = () => this.#sessionAdmissionContext.run(entry, () => body({ release }));
|
|
4714
|
+
return await (options?.skipModelRegistryLease
|
|
4715
|
+
? admittedBody()
|
|
4716
|
+
: this.#modelRegistry.withConsumerLease(admittedBody, signal));
|
|
4597
4717
|
} finally {
|
|
4598
4718
|
releaseEntry();
|
|
4599
4719
|
}
|
|
@@ -5175,16 +5295,26 @@ export class AgentSession {
|
|
|
5175
5295
|
}
|
|
5176
5296
|
|
|
5177
5297
|
#flushPendingAgentEnd(): void {
|
|
5298
|
+
// Collect SDK agent_ends to publish before modifying the set to avoid iterator issues (PR #6388).
|
|
5299
|
+
// Publish only SDK agent_ends that have been decided by their handler.
|
|
5300
|
+
const sdkPendingsToPublish: Array<[AgentSessionEvent, RunResourceProducerLease | undefined]> = [];
|
|
5178
5301
|
for (const sdkPending of this.#pendingSdkAgentEnds) {
|
|
5302
|
+
// Skip if the handler hasn't decided yet (decision is recorded by handler finally block).
|
|
5179
5303
|
if (!this.#agentEndContinuationDecisions.has(sdkPending)) continue;
|
|
5304
|
+
// Also skip if it's being held for a continuation
|
|
5180
5305
|
if ([...this.#pendingAgentEndContinuationHolds.values()].includes(sdkPending)) continue;
|
|
5306
|
+
const sdkLease = this.#deferredAgentEndLeases.get(sdkPending);
|
|
5307
|
+
sdkPendingsToPublish.push([sdkPending, sdkLease]);
|
|
5308
|
+
}
|
|
5309
|
+
// Now publish collected items.
|
|
5310
|
+
for (const [sdkPending, sdkLease] of sdkPendingsToPublish) {
|
|
5181
5311
|
this.#pendingSdkAgentEnds.delete(sdkPending);
|
|
5182
5312
|
if (this.#pendingAgentEndEmit === sdkPending) this.#pendingAgentEndEmit = undefined;
|
|
5183
|
-
const sdkLease = this.#deferredAgentEndLeases.get(sdkPending);
|
|
5184
5313
|
this.#deferredAgentEndLeases.delete(sdkPending);
|
|
5185
5314
|
this.#startAgentEndPublication(sdkPending, sdkLease);
|
|
5186
5315
|
}
|
|
5187
5316
|
const pending = this.#pendingAgentEndEmit;
|
|
5317
|
+
// Skip fallback publish if there are still SDK agent_ends pending (they will be handled by the loop above once decided).
|
|
5188
5318
|
if (pending && this.#pendingSdkAgentEnds.has(pending)) return;
|
|
5189
5319
|
if (
|
|
5190
5320
|
this.#livePromptsInFlight() > 0 ||
|
|
@@ -6237,7 +6367,7 @@ export class AgentSession {
|
|
|
6237
6367
|
for (const name of previous) this.#toolRegistry.delete(name);
|
|
6238
6368
|
const getCustomToolContext = () => this.#getCustomToolContext();
|
|
6239
6369
|
const added: string[] = [];
|
|
6240
|
-
for (const customTool of nextTools) {
|
|
6370
|
+
for (const customTool of omitPluginMcpNameShadows(nextTools)) {
|
|
6241
6371
|
const wrapped = CustomToolAdapter.wrap(customTool, getCustomToolContext) as AgentTool;
|
|
6242
6372
|
const finalTool = (
|
|
6243
6373
|
this.#extensionRunner ? new ExtensionToolWrapper(wrapped, this.#extensionRunner) : wrapped
|
|
@@ -6245,11 +6375,15 @@ export class AgentSession {
|
|
|
6245
6375
|
this.#toolRegistry.set(finalTool.name, finalTool);
|
|
6246
6376
|
added.push(finalTool.name);
|
|
6247
6377
|
}
|
|
6248
|
-
|
|
6249
|
-
|
|
6250
|
-
|
|
6251
|
-
|
|
6252
|
-
|
|
6378
|
+
const requestedMandatory = options?.mandatoryMCPToolNames ?? [...this.#mandatoryMCPToolNames];
|
|
6379
|
+
const retainedMandatory = retainPluginMcpMandatoryNames(nextTools, requestedMandatory).map(name =>
|
|
6380
|
+
name.toLowerCase(),
|
|
6381
|
+
);
|
|
6382
|
+
this.#mandatoryMCPToolNames = new Set(
|
|
6383
|
+
options?.mandatoryMCPToolNames !== undefined
|
|
6384
|
+
? retainedMandatory.filter(name => this.#toolRegistry.has(name))
|
|
6385
|
+
: retainedMandatory,
|
|
6386
|
+
);
|
|
6253
6387
|
this.#setDiscoverableMCPTools(this.#collectDiscoverableMCPToolsFromRegistry());
|
|
6254
6388
|
await this.#applyActiveToolsByName(
|
|
6255
6389
|
[
|
|
@@ -6886,7 +7020,11 @@ export class AgentSession {
|
|
|
6886
7020
|
throw new Error("Owned async jobs did not settle before artifact retirement.");
|
|
6887
7021
|
committed = true;
|
|
6888
7022
|
} finally {
|
|
6889
|
-
|
|
7023
|
+
if (committed) {
|
|
7024
|
+
manager.finishOwnerSubagentShutdown(lease, "commit");
|
|
7025
|
+
} else {
|
|
7026
|
+
manager.finishOwnerSubagentShutdown(lease, "release", { drainResumeQueue: !this.#isDisposed });
|
|
7027
|
+
}
|
|
6890
7028
|
}
|
|
6891
7029
|
}
|
|
6892
7030
|
|
|
@@ -7494,7 +7632,14 @@ export class AgentSession {
|
|
|
7494
7632
|
const artifactId = await this.sessionManager.saveArtifact(fullText, "tool-result");
|
|
7495
7633
|
if (!artifactId) return;
|
|
7496
7634
|
const digest = crypto.createHash("sha256").update(fullText).digest("hex");
|
|
7497
|
-
|
|
7635
|
+
// Cut the preview from redacted text. The artifact stores fullText.
|
|
7636
|
+
const obfuscator = this.#obfuscator;
|
|
7637
|
+
const preview = createPreAdmissionArtifactSpillPreview(
|
|
7638
|
+
fullText,
|
|
7639
|
+
artifactId,
|
|
7640
|
+
digest,
|
|
7641
|
+
obfuscator?.hasSecrets() ? text => obfuscator.obfuscate(text) : undefined,
|
|
7642
|
+
);
|
|
7498
7643
|
const spillMeta = outputMeta()
|
|
7499
7644
|
.truncationFromText(preview, {
|
|
7500
7645
|
direction: "middle",
|
|
@@ -10457,6 +10602,8 @@ export class AgentSession {
|
|
|
10457
10602
|
const publicationFailure = Object.assign(new Error("Session disposed before SDK terminal publication."), {
|
|
10458
10603
|
code: "cancelled",
|
|
10459
10604
|
});
|
|
10605
|
+
// Reject every SDK caller even if internal terminal or extension delivery is
|
|
10606
|
+
// already in flight; the publication task can still drain independently.
|
|
10460
10607
|
for (const publication of this.#sdkTerminalPublications.values()) publication.reject(publicationFailure);
|
|
10461
10608
|
this.#sdkTerminalPublications.clear();
|
|
10462
10609
|
// Invalidate every coordinator event admitted before disposal. Handlers may
|
|
@@ -12337,7 +12484,7 @@ export class AgentSession {
|
|
|
12337
12484
|
},
|
|
12338
12485
|
});
|
|
12339
12486
|
|
|
12340
|
-
for (const customTool of mcpTools) {
|
|
12487
|
+
for (const customTool of omitPluginMcpNameShadows(mcpTools)) {
|
|
12341
12488
|
const wrapped = CustomToolAdapter.wrap(customTool, getCustomToolContext) as AgentTool;
|
|
12342
12489
|
const finalTool = (
|
|
12343
12490
|
this.#extensionRunner ? new ExtensionToolWrapper(wrapped, this.#extensionRunner) : wrapped
|
|
@@ -12346,11 +12493,15 @@ export class AgentSession {
|
|
|
12346
12493
|
}
|
|
12347
12494
|
|
|
12348
12495
|
this.#setDiscoverableMCPTools(this.#collectDiscoverableMCPToolsFromRegistry());
|
|
12349
|
-
|
|
12350
|
-
|
|
12351
|
-
|
|
12352
|
-
|
|
12353
|
-
|
|
12496
|
+
const requestedMandatory = options.mandatoryMCPToolNames ?? [...this.#mandatoryMCPToolNames];
|
|
12497
|
+
const retainedMandatory = retainPluginMcpMandatoryNames(mcpTools, requestedMandatory).map(name =>
|
|
12498
|
+
name.toLowerCase(),
|
|
12499
|
+
);
|
|
12500
|
+
this.#mandatoryMCPToolNames = new Set(
|
|
12501
|
+
options.mandatoryMCPToolNames !== undefined
|
|
12502
|
+
? retainedMandatory.filter(name => this.#toolRegistry.has(name))
|
|
12503
|
+
: retainedMandatory,
|
|
12504
|
+
);
|
|
12354
12505
|
this.#pruneSelectedMCPToolNames();
|
|
12355
12506
|
const hasPersistedMCPToolSelection = this.buildDisplaySessionContext().hasPersistedMCPToolSelection;
|
|
12356
12507
|
if (options.selectedMCPToolNames) {
|
|
@@ -17840,7 +17991,7 @@ export class AgentSession {
|
|
|
17840
17991
|
"Unable to confirm owned subagent cleanup; session was not replaced. Wait for or inspect remaining subagents, then retry /new.",
|
|
17841
17992
|
"new-session-subagent-cleanup",
|
|
17842
17993
|
);
|
|
17843
|
-
manager.finishOwnerSubagentShutdown(lease, "release");
|
|
17994
|
+
manager.finishOwnerSubagentShutdown(lease, "release", { drainResumeQueue: !this.#isDisposed });
|
|
17844
17995
|
return false;
|
|
17845
17996
|
}
|
|
17846
17997
|
} catch {
|
|
@@ -17849,7 +18000,7 @@ export class AgentSession {
|
|
|
17849
18000
|
"Unable to confirm owned subagent cleanup; session was not replaced. Wait for or inspect remaining subagents, then retry /new.",
|
|
17850
18001
|
"new-session-subagent-cleanup",
|
|
17851
18002
|
);
|
|
17852
|
-
manager.finishOwnerSubagentShutdown(lease, "release");
|
|
18003
|
+
manager.finishOwnerSubagentShutdown(lease, "release", { drainResumeQueue: !this.#isDisposed });
|
|
17853
18004
|
return false;
|
|
17854
18005
|
}
|
|
17855
18006
|
|
|
@@ -17907,10 +18058,11 @@ export class AgentSession {
|
|
|
17907
18058
|
manager.finishOwnerSubagentShutdown(lease, "commit");
|
|
17908
18059
|
return true;
|
|
17909
18060
|
} catch (error) {
|
|
17910
|
-
|
|
17911
|
-
lease,
|
|
17912
|
-
|
|
17913
|
-
|
|
18061
|
+
if (this.sessionManager.getSessionId() !== previousSessionIdentity) {
|
|
18062
|
+
manager.finishOwnerSubagentShutdown(lease, "commit");
|
|
18063
|
+
} else {
|
|
18064
|
+
manager.finishOwnerSubagentShutdown(lease, "release", { drainResumeQueue: !this.#isDisposed });
|
|
18065
|
+
}
|
|
17914
18066
|
throw error;
|
|
17915
18067
|
}
|
|
17916
18068
|
}
|
|
@@ -17949,7 +18101,7 @@ export class AgentSession {
|
|
|
17949
18101
|
if (this.#activeModelProfile && this.#activeModelProfile !== configuredDefaultProfileIdentity) {
|
|
17950
18102
|
this.settings.clearOverride("modelRoles");
|
|
17951
18103
|
this.settings.clearOverride("task.agentModelOverrides");
|
|
17952
|
-
this
|
|
18104
|
+
this.setActiveModelProfile(undefined);
|
|
17953
18105
|
}
|
|
17954
18106
|
const inheritedThinkingLevel = resolveThinkingLevelForModel(this.model, this.#getInheritedThinkingLevel());
|
|
17955
18107
|
this.#thinkingLevelMutationRevision++;
|
|
@@ -18245,6 +18397,7 @@ export class AgentSession {
|
|
|
18245
18397
|
role,
|
|
18246
18398
|
this.#formatRoleModelValue(role, model, options?.selector, options?.thinkingLevel),
|
|
18247
18399
|
);
|
|
18400
|
+
if (cause === "user-selection") this.markProfileRoleOverrideManual("modelRoles", role);
|
|
18248
18401
|
// Only an explicit user selection starts a new fallback epoch. Internal
|
|
18249
18402
|
// fallback switches must preserve the exhausted-model set while advancing.
|
|
18250
18403
|
if (role === "default" && cause === "user-selection") {
|
|
@@ -18283,12 +18436,22 @@ export class AgentSession {
|
|
|
18283
18436
|
}
|
|
18284
18437
|
|
|
18285
18438
|
setActiveModelProfile(name: string | undefined): void {
|
|
18439
|
+
if (name !== this.#activeModelProfile) {
|
|
18440
|
+
this.#activeProfileManualModelRoles.clear();
|
|
18441
|
+
this.#activeProfileManualAgentModelOverrides.clear();
|
|
18442
|
+
}
|
|
18286
18443
|
this.#activeModelProfile = name;
|
|
18287
18444
|
if (name !== undefined) {
|
|
18288
18445
|
this.#unavailableModelProfile = undefined;
|
|
18289
18446
|
}
|
|
18290
18447
|
}
|
|
18291
18448
|
|
|
18449
|
+
markProfileRoleOverrideManual(path: "modelRoles" | "task.agentModelOverrides", role: string): void {
|
|
18450
|
+
if (this.#activeModelProfile === undefined) return;
|
|
18451
|
+
if (path === "modelRoles") this.#activeProfileManualModelRoles.add(role);
|
|
18452
|
+
else this.#activeProfileManualAgentModelOverrides.add(role);
|
|
18453
|
+
}
|
|
18454
|
+
|
|
18292
18455
|
getActiveModelProfile(): string | undefined {
|
|
18293
18456
|
return this.#activeModelProfile;
|
|
18294
18457
|
}
|
|
@@ -18319,6 +18482,592 @@ export class AgentSession {
|
|
|
18319
18482
|
this.#userModelSelectionRevision++;
|
|
18320
18483
|
}
|
|
18321
18484
|
|
|
18485
|
+
getConfigurationPaths(): ConfigHotReloadPaths | undefined {
|
|
18486
|
+
const configPath = this.settings.getGlobalConfigPath();
|
|
18487
|
+
const modelsPath = this.#modelRegistry.getModelsConfigPath();
|
|
18488
|
+
if (!configPath || !modelsPath) return undefined;
|
|
18489
|
+
return {
|
|
18490
|
+
configPath: path.resolve(configPath),
|
|
18491
|
+
modelsPath: path.resolve(modelsPath),
|
|
18492
|
+
};
|
|
18493
|
+
}
|
|
18494
|
+
|
|
18495
|
+
#assertConfigurationReloadAvailable(candidate: ConfigHotReloadCandidate): void {
|
|
18496
|
+
if (this.#sessionAdmissionClosing || this.#sessionAdmissionClosed || this.#isDisposed) {
|
|
18497
|
+
throw new ConfigurationReloadError("SESSION_UNAVAILABLE");
|
|
18498
|
+
}
|
|
18499
|
+
const paths = this.getConfigurationPaths();
|
|
18500
|
+
if (
|
|
18501
|
+
!paths ||
|
|
18502
|
+
path.resolve(candidate.config.path) !== paths.configPath ||
|
|
18503
|
+
path.resolve(candidate.models.path) !== paths.modelsPath
|
|
18504
|
+
) {
|
|
18505
|
+
throw new ConfigurationReloadError(paths ? "PATH_CHANGED" : "SESSION_UNAVAILABLE");
|
|
18506
|
+
}
|
|
18507
|
+
}
|
|
18508
|
+
|
|
18509
|
+
#readOnlyModelsCandidate(candidate: ModelsConfigReloadCandidate): ModelRegistry {
|
|
18510
|
+
const liveRegistry = this.#modelRegistry;
|
|
18511
|
+
return new Proxy(candidate.registry, {
|
|
18512
|
+
get(target, property) {
|
|
18513
|
+
if (property === "getSessionCanonicalVariant") {
|
|
18514
|
+
return liveRegistry.getSessionCanonicalVariant.bind(liveRegistry);
|
|
18515
|
+
}
|
|
18516
|
+
if (
|
|
18517
|
+
property === "clearCanonicalVariant" ||
|
|
18518
|
+
property === "restoreSessionCanonicalVariant" ||
|
|
18519
|
+
property === "seedCanonicalVariant"
|
|
18520
|
+
) {
|
|
18521
|
+
return () => undefined;
|
|
18522
|
+
}
|
|
18523
|
+
const value = Reflect.get(target, property, target);
|
|
18524
|
+
return typeof value === "function" ? value.bind(target) : value;
|
|
18525
|
+
},
|
|
18526
|
+
}) as ModelRegistry;
|
|
18527
|
+
}
|
|
18528
|
+
|
|
18529
|
+
#settingsCandidateView(candidate: SettingsGlobalConfigCandidate) {
|
|
18530
|
+
return {
|
|
18531
|
+
get: <P extends SettingPath>(settingPath: P) => candidate.get(settingPath),
|
|
18532
|
+
getGlobal: <P extends SettingPath>(settingPath: P) => candidate.getGlobal(settingPath),
|
|
18533
|
+
getOverride: <P extends SettingPath>(settingPath: P) => this.settings.getOverride(settingPath),
|
|
18534
|
+
getStorage: () => this.settings.getStorage(),
|
|
18535
|
+
};
|
|
18536
|
+
}
|
|
18537
|
+
|
|
18538
|
+
async #preflightActiveProfile(
|
|
18539
|
+
settingsCandidate: SettingsGlobalConfigCandidate,
|
|
18540
|
+
modelsCandidate: ModelsConfigReloadCandidate,
|
|
18541
|
+
options?: {
|
|
18542
|
+
prepareSessionModelSelection?: boolean;
|
|
18543
|
+
settingsForApply?: Settings;
|
|
18544
|
+
signal?: AbortSignal;
|
|
18545
|
+
},
|
|
18546
|
+
): Promise<PreparedModelProfileActivation | undefined> {
|
|
18547
|
+
const activeProfile = this.getActiveModelProfile();
|
|
18548
|
+
if (!activeProfile) return undefined;
|
|
18549
|
+
try {
|
|
18550
|
+
return await prepareModelProfileActivation({
|
|
18551
|
+
session: this,
|
|
18552
|
+
modelRegistry: this.#readOnlyModelsCandidate(modelsCandidate),
|
|
18553
|
+
settings: this.#settingsCandidateView(settingsCandidate),
|
|
18554
|
+
...(options?.settingsForApply ? { settingsForApply: options.settingsForApply } : {}),
|
|
18555
|
+
...(options?.prepareSessionModelSelection ? { prepareSessionModelSelection: true } : {}),
|
|
18556
|
+
preserveManualOverrides: true,
|
|
18557
|
+
preserveCanonicalVariant: true,
|
|
18558
|
+
...(options?.signal ? { signal: options.signal } : {}),
|
|
18559
|
+
profileName: activeProfile,
|
|
18560
|
+
});
|
|
18561
|
+
} catch (error) {
|
|
18562
|
+
throw new ConfigurationReloadError("ACTIVE_PROFILE_INVALID", error);
|
|
18563
|
+
}
|
|
18564
|
+
}
|
|
18565
|
+
|
|
18566
|
+
async validateConfiguration(candidate: ConfigHotReloadCandidate): Promise<void> {
|
|
18567
|
+
while (true) {
|
|
18568
|
+
if (this.#sessionTransitionKind !== undefined) {
|
|
18569
|
+
await this.#waitForSessionTransitionEnd(this.#disposeAbortController.signal);
|
|
18570
|
+
}
|
|
18571
|
+
this.#assertConfigurationReloadAvailable(candidate);
|
|
18572
|
+
const transitionGeneration = this.#sessionTransitionGeneration;
|
|
18573
|
+
let modelsCandidate: ModelsConfigReloadCandidate | undefined;
|
|
18574
|
+
let validationError: ConfigurationReloadError | undefined;
|
|
18575
|
+
try {
|
|
18576
|
+
let settingsCandidate: SettingsGlobalConfigCandidate;
|
|
18577
|
+
try {
|
|
18578
|
+
settingsCandidate = await this.settings.stageGlobalConfigReload(candidate.config);
|
|
18579
|
+
} catch (error) {
|
|
18580
|
+
throw new ConfigurationReloadError("SETTINGS_INVALID", error);
|
|
18581
|
+
}
|
|
18582
|
+
if (!settingsCandidate.diagnostics.valid) throw new ConfigurationReloadError("SETTINGS_INVALID");
|
|
18583
|
+
try {
|
|
18584
|
+
modelsCandidate = await this.#modelRegistry.stageModelsConfigReload(
|
|
18585
|
+
candidate.models,
|
|
18586
|
+
settingsCandidate,
|
|
18587
|
+
);
|
|
18588
|
+
} catch (error) {
|
|
18589
|
+
throw new ConfigurationReloadError("MODELS_INVALID", error);
|
|
18590
|
+
}
|
|
18591
|
+
if (!modelsCandidate.valid || !modelsCandidate.diagnostics.valid) {
|
|
18592
|
+
throw new ConfigurationReloadError("MODELS_INVALID");
|
|
18593
|
+
}
|
|
18594
|
+
const prepared = await this.#preflightActiveProfile(settingsCandidate, modelsCandidate);
|
|
18595
|
+
const chain = this.getConfiguredModelChainState("default");
|
|
18596
|
+
if (!prepared || chain?.origin !== "profile-activation" || chain.identity !== this.getActiveModelProfile()) {
|
|
18597
|
+
this.#updatedCurrentModel(modelsCandidate.registry);
|
|
18598
|
+
}
|
|
18599
|
+
} catch (error) {
|
|
18600
|
+
validationError =
|
|
18601
|
+
error instanceof ConfigurationReloadError
|
|
18602
|
+
? error
|
|
18603
|
+
: new ConfigurationReloadError("ACTIVE_PROFILE_INVALID", error);
|
|
18604
|
+
} finally {
|
|
18605
|
+
if (modelsCandidate) {
|
|
18606
|
+
try {
|
|
18607
|
+
modelsCandidate.rollback();
|
|
18608
|
+
} catch (error) {
|
|
18609
|
+
validationError ??= new ConfigurationReloadError("VALIDATION_CLEANUP_FAILED", error);
|
|
18610
|
+
}
|
|
18611
|
+
}
|
|
18612
|
+
}
|
|
18613
|
+
|
|
18614
|
+
// A transition may have completed while staging, so checking only the active kind is insufficient.
|
|
18615
|
+
if (transitionGeneration !== this.#sessionTransitionGeneration) {
|
|
18616
|
+
await this.#waitForSessionTransitionEnd(this.#disposeAbortController.signal);
|
|
18617
|
+
this.#assertConfigurationReloadAvailable(candidate);
|
|
18618
|
+
continue;
|
|
18619
|
+
}
|
|
18620
|
+
|
|
18621
|
+
this.#assertConfigurationReloadAvailable(candidate);
|
|
18622
|
+
if (validationError) throw validationError;
|
|
18623
|
+
return;
|
|
18624
|
+
}
|
|
18625
|
+
}
|
|
18626
|
+
|
|
18627
|
+
reloadConfiguration(
|
|
18628
|
+
candidate: ConfigHotReloadCandidate,
|
|
18629
|
+
signal: AbortSignal,
|
|
18630
|
+
): Promise<ConfigurationReloadResult> {
|
|
18631
|
+
return this.#reloadConfiguration(
|
|
18632
|
+
candidate,
|
|
18633
|
+
signal,
|
|
18634
|
+
this.getUserModelSelectionRevision(),
|
|
18635
|
+
this.model,
|
|
18636
|
+
);
|
|
18637
|
+
}
|
|
18638
|
+
|
|
18639
|
+
async #reloadConfiguration(
|
|
18640
|
+
candidate: ConfigHotReloadCandidate,
|
|
18641
|
+
signal: AbortSignal,
|
|
18642
|
+
userModelSelectionRevision: number,
|
|
18643
|
+
userModelSelectionModel: Model | undefined,
|
|
18644
|
+
): Promise<ConfigurationReloadResult> {
|
|
18645
|
+
const owner = this.#sessionAdmissionContext.getStore();
|
|
18646
|
+
if (owner && !owner.released) throw new ConfigurationReloadError("SESSION_UNAVAILABLE");
|
|
18647
|
+
if (this.#sessionTransitionKind !== undefined) {
|
|
18648
|
+
await this.#waitForSessionTransitionEnd(AbortSignal.any([signal, this.#disposeAbortController.signal]));
|
|
18649
|
+
}
|
|
18650
|
+
this.#assertConfigurationReloadAvailable(candidate);
|
|
18651
|
+
|
|
18652
|
+
const transitionAbortController = new AbortController();
|
|
18653
|
+
this.#configurationReloadControllers.add(transitionAbortController);
|
|
18654
|
+
const reloadSignal = AbortSignal.any([
|
|
18655
|
+
signal,
|
|
18656
|
+
this.#disposeAbortController.signal,
|
|
18657
|
+
transitionAbortController.signal,
|
|
18658
|
+
]);
|
|
18659
|
+
const expectedSessionId = this.sessionId;
|
|
18660
|
+
const selectionTransaction = Symbol("configuration-reload");
|
|
18661
|
+
const priorSelectionFence = this.#selectionFenceTail;
|
|
18662
|
+
const selectionFence = Promise.withResolvers<void>();
|
|
18663
|
+
this.#selectionFenceGeneration += 1;
|
|
18664
|
+
this.#pendingSelectionFences += 1;
|
|
18665
|
+
const selectionFenceGeneration = this.#selectionFenceGeneration;
|
|
18666
|
+
if (this.#oldestPendingSelectionFenceGeneration === 0) {
|
|
18667
|
+
this.#oldestPendingSelectionFenceGeneration = selectionFenceGeneration;
|
|
18668
|
+
}
|
|
18669
|
+
this.#selectionFenceTail = priorSelectionFence.then(() => selectionFence.promise);
|
|
18670
|
+
void this.#selectionFenceTail.catch(() => {});
|
|
18671
|
+
let publicationFenceRelease: (() => void) | undefined;
|
|
18672
|
+
let stagedModels: ModelsConfigReloadCandidate | undefined;
|
|
18673
|
+
let modelRollbackAttempted = false;
|
|
18674
|
+
let modelCommitSucceeded = false;
|
|
18675
|
+
let retryAfterSessionTransition = false;
|
|
18676
|
+
try {
|
|
18677
|
+
reloadSignal.throwIfAborted();
|
|
18678
|
+
await awaitPromptInvocationPreflight(priorSelectionFence, reloadSignal);
|
|
18679
|
+
await this.#withSessionAdmission("selection", async () => {}, reloadSignal, undefined, {
|
|
18680
|
+
selectionTransaction,
|
|
18681
|
+
skipModelRegistryLease: true,
|
|
18682
|
+
});
|
|
18683
|
+
if (this.sessionId !== expectedSessionId) throw new Error("Session changed during configuration reload");
|
|
18684
|
+
this.#selectionAwaitingMutationTransaction = selectionTransaction;
|
|
18685
|
+
await awaitPromptInvocationPreflight(this.waitForIdle(selectionFenceGeneration), reloadSignal);
|
|
18686
|
+
|
|
18687
|
+
return await this.#withSessionAdmission(
|
|
18688
|
+
"selection",
|
|
18689
|
+
async () => {
|
|
18690
|
+
reloadSignal.throwIfAborted();
|
|
18691
|
+
if (this.sessionId !== expectedSessionId) {
|
|
18692
|
+
throw new ConfigurationReloadError("SESSION_UNAVAILABLE");
|
|
18693
|
+
}
|
|
18694
|
+
this.#assertConfigurationReloadAvailable(candidate);
|
|
18695
|
+
let stagedSettings: SettingsGlobalConfigCandidate;
|
|
18696
|
+
try {
|
|
18697
|
+
// Stage after admission and idle drain so settings written by the prior turn are rebased.
|
|
18698
|
+
stagedSettings = await this.settings.stageGlobalConfigReload(candidate.config);
|
|
18699
|
+
} catch (error) {
|
|
18700
|
+
throw new ConfigurationReloadError("SETTINGS_INVALID", error);
|
|
18701
|
+
}
|
|
18702
|
+
if (!stagedSettings.diagnostics.valid) throw new ConfigurationReloadError("SETTINGS_INVALID");
|
|
18703
|
+
try {
|
|
18704
|
+
stagedModels = await this.#modelRegistry.stageModelsConfigReload(
|
|
18705
|
+
candidate.models,
|
|
18706
|
+
stagedSettings,
|
|
18707
|
+
);
|
|
18708
|
+
} catch (error) {
|
|
18709
|
+
throw new ConfigurationReloadError("MODELS_INVALID", error);
|
|
18710
|
+
}
|
|
18711
|
+
if (!stagedModels.valid || !stagedModels.diagnostics.valid) {
|
|
18712
|
+
throw new ConfigurationReloadError("MODELS_INVALID");
|
|
18713
|
+
}
|
|
18714
|
+
reloadSignal.throwIfAborted();
|
|
18715
|
+
|
|
18716
|
+
const changedSettings = (Object.keys(SETTINGS_SCHEMA) as SettingPath[]).filter(
|
|
18717
|
+
settingPath =>
|
|
18718
|
+
!util.isDeepStrictEqual(this.settings.get(settingPath), stagedSettings.get(settingPath)) ||
|
|
18719
|
+
!util.isDeepStrictEqual(
|
|
18720
|
+
this.settings.getGlobal(settingPath),
|
|
18721
|
+
stagedSettings.getGlobal(settingPath),
|
|
18722
|
+
),
|
|
18723
|
+
);
|
|
18724
|
+
let prepared: PreparedModelProfileActivation | undefined;
|
|
18725
|
+
let preparedLiveModelSelection: PreparedProfileModelSelection | undefined;
|
|
18726
|
+
let preparedLiveModelSelectionRevision = userModelSelectionRevision;
|
|
18727
|
+
let preserveDefaultModelSelection = true;
|
|
18728
|
+
const prepareCandidateSelections = async (modelsCandidate: ModelsConfigReloadCandidate): Promise<void> => {
|
|
18729
|
+
prepared = undefined;
|
|
18730
|
+
preparedLiveModelSelection = undefined;
|
|
18731
|
+
preparedLiveModelSelectionRevision = userModelSelectionRevision;
|
|
18732
|
+
preserveDefaultModelSelection = true;
|
|
18733
|
+
const activeProfile = this.getActiveModelProfile();
|
|
18734
|
+
if (activeProfile) {
|
|
18735
|
+
prepared = await this.#preflightActiveProfile(stagedSettings, modelsCandidate, {
|
|
18736
|
+
prepareSessionModelSelection: true,
|
|
18737
|
+
signal: reloadSignal,
|
|
18738
|
+
settingsForApply: this.settings,
|
|
18739
|
+
});
|
|
18740
|
+
const previousChain = prepared!.previousDefaultChainState;
|
|
18741
|
+
const profileOwnsDefaultChain =
|
|
18742
|
+
previousChain?.origin === "profile-activation" && previousChain.identity === activeProfile;
|
|
18743
|
+
const chainIsUnchanged =
|
|
18744
|
+
prepared!.defaultChain.length === (previousChain?.entries.length ?? 0) &&
|
|
18745
|
+
prepared!.defaultChain.every((selector, index) => selector === previousChain?.entries[index]);
|
|
18746
|
+
preserveDefaultModelSelection =
|
|
18747
|
+
prepared!.defaultChain.length === 0 || !profileOwnsDefaultChain || chainIsUnchanged;
|
|
18748
|
+
}
|
|
18749
|
+
if (
|
|
18750
|
+
preserveDefaultModelSelection &&
|
|
18751
|
+
this.#currentModelConfigurationChanged(modelsCandidate.registry)
|
|
18752
|
+
) {
|
|
18753
|
+
const updatedModel = this.#updatedCurrentModel(modelsCandidate.registry);
|
|
18754
|
+
if (updatedModel) {
|
|
18755
|
+
const previousModel = this.model;
|
|
18756
|
+
const previousThinkingLevel = this.thinkingLevel;
|
|
18757
|
+
const selectionRevision = this.getUserModelSelectionRevision();
|
|
18758
|
+
const preparedSelection = await this.prepareModelSelectionForProfileActivation(
|
|
18759
|
+
updatedModel,
|
|
18760
|
+
previousThinkingLevel,
|
|
18761
|
+
reloadSignal,
|
|
18762
|
+
);
|
|
18763
|
+
if (
|
|
18764
|
+
this.getUserModelSelectionRevision() === selectionRevision &&
|
|
18765
|
+
this.model === previousModel &&
|
|
18766
|
+
this.thinkingLevel === previousThinkingLevel
|
|
18767
|
+
) {
|
|
18768
|
+
preparedLiveModelSelectionRevision = selectionRevision;
|
|
18769
|
+
preparedLiveModelSelection = preparedSelection;
|
|
18770
|
+
}
|
|
18771
|
+
}
|
|
18772
|
+
}
|
|
18773
|
+
};
|
|
18774
|
+
await prepareCandidateSelections(stagedModels);
|
|
18775
|
+
reloadSignal.throwIfAborted();
|
|
18776
|
+
publicationFenceRelease = await this.#modelRegistry.acquirePublicationFence(reloadSignal);
|
|
18777
|
+
reloadSignal.throwIfAborted();
|
|
18778
|
+
let preserveLiveModelSelection = false;
|
|
18779
|
+
const prepareLiveModelSelection = async (modelsCandidate: ModelsConfigReloadCandidate): Promise<void> => {
|
|
18780
|
+
preserveLiveModelSelection =
|
|
18781
|
+
this.getUserModelSelectionRevision() !== userModelSelectionRevision ||
|
|
18782
|
+
this.model !== userModelSelectionModel;
|
|
18783
|
+
if (
|
|
18784
|
+
preparedLiveModelSelection &&
|
|
18785
|
+
(this.getUserModelSelectionRevision() !== preparedLiveModelSelectionRevision ||
|
|
18786
|
+
this.model !== preparedLiveModelSelection.previousModel ||
|
|
18787
|
+
this.thinkingLevel !== preparedLiveModelSelection.previousThinkingLevel)
|
|
18788
|
+
) {
|
|
18789
|
+
preparedLiveModelSelection = undefined;
|
|
18790
|
+
}
|
|
18791
|
+
if (preserveLiveModelSelection && this.model) {
|
|
18792
|
+
const currentModel = this.model;
|
|
18793
|
+
const currentThinkingLevel = this.thinkingLevel;
|
|
18794
|
+
const updatedModel = this.#updatedCurrentModel(modelsCandidate.registry);
|
|
18795
|
+
if (!updatedModel) {
|
|
18796
|
+
throw new ConfigurationReloadError(
|
|
18797
|
+
"MODEL_UNAVAILABLE",
|
|
18798
|
+
new Error("The current model is unavailable in the staged registry"),
|
|
18799
|
+
);
|
|
18800
|
+
}
|
|
18801
|
+
if (!util.isDeepStrictEqual(currentModel, updatedModel)) {
|
|
18802
|
+
const selectionRevision = this.getUserModelSelectionRevision();
|
|
18803
|
+
preparedLiveModelSelection = await this.prepareModelSelectionForProfileActivation(
|
|
18804
|
+
updatedModel,
|
|
18805
|
+
currentThinkingLevel,
|
|
18806
|
+
reloadSignal,
|
|
18807
|
+
);
|
|
18808
|
+
preparedLiveModelSelectionRevision = selectionRevision;
|
|
18809
|
+
reloadSignal.throwIfAborted();
|
|
18810
|
+
if (
|
|
18811
|
+
this.getUserModelSelectionRevision() !== selectionRevision ||
|
|
18812
|
+
this.model !== currentModel ||
|
|
18813
|
+
this.thinkingLevel !== currentThinkingLevel
|
|
18814
|
+
) {
|
|
18815
|
+
throw new ConfigurationReloadError(
|
|
18816
|
+
"PUBLICATION_FAILED",
|
|
18817
|
+
new Error("The live model selection changed while preparing the reload model"),
|
|
18818
|
+
);
|
|
18819
|
+
}
|
|
18820
|
+
}
|
|
18821
|
+
}
|
|
18822
|
+
};
|
|
18823
|
+
let catalogRestages = 0;
|
|
18824
|
+
for (;;) {
|
|
18825
|
+
const modelsCandidate = stagedModels;
|
|
18826
|
+
if (!modelsCandidate) throw new ConfigurationReloadError("MODELS_INVALID");
|
|
18827
|
+
if (!modelsCandidate.isCurrent()) {
|
|
18828
|
+
if (catalogRestages >= 2) {
|
|
18829
|
+
throw new ConfigurationReloadError(
|
|
18830
|
+
"PUBLICATION_FAILED",
|
|
18831
|
+
new Error("The model catalog kept changing during configuration reload preflight"),
|
|
18832
|
+
{ retryable: true },
|
|
18833
|
+
);
|
|
18834
|
+
}
|
|
18835
|
+
catalogRestages++;
|
|
18836
|
+
modelsCandidate.rollback();
|
|
18837
|
+
stagedModels = undefined;
|
|
18838
|
+
let restagedModels: ModelsConfigReloadCandidate;
|
|
18839
|
+
try {
|
|
18840
|
+
restagedModels = await this.#modelRegistry.stageModelsConfigReload(
|
|
18841
|
+
candidate.models,
|
|
18842
|
+
stagedSettings,
|
|
18843
|
+
);
|
|
18844
|
+
} catch (error) {
|
|
18845
|
+
throw new ConfigurationReloadError("MODELS_INVALID", error);
|
|
18846
|
+
}
|
|
18847
|
+
stagedModels = restagedModels;
|
|
18848
|
+
if (!restagedModels.valid || !restagedModels.diagnostics.valid) {
|
|
18849
|
+
throw new ConfigurationReloadError("MODELS_INVALID");
|
|
18850
|
+
}
|
|
18851
|
+
await prepareCandidateSelections(restagedModels);
|
|
18852
|
+
reloadSignal.throwIfAborted();
|
|
18853
|
+
continue;
|
|
18854
|
+
}
|
|
18855
|
+
await prepareLiveModelSelection(modelsCandidate);
|
|
18856
|
+
reloadSignal.throwIfAborted();
|
|
18857
|
+
if (modelsCandidate.isCurrent()) break;
|
|
18858
|
+
}
|
|
18859
|
+
const modelsChanged = stagedModels.changed;
|
|
18860
|
+
const settingsChanged = changedSettings.length > 0;
|
|
18861
|
+
if (!settingsChanged && !modelsChanged) {
|
|
18862
|
+
stagedModels!.rollback();
|
|
18863
|
+
modelRollbackAttempted = true;
|
|
18864
|
+
return { applied: false, settingsChanged: false, modelsChanged: false, changedSettings: [] };
|
|
18865
|
+
}
|
|
18866
|
+
|
|
18867
|
+
let publication: SettingsConfigReloadPublication | undefined;
|
|
18868
|
+
let modelCommitStarted = false;
|
|
18869
|
+
let activationPublicationStarted = false;
|
|
18870
|
+
let liveModelSelectionCommitted = false;
|
|
18871
|
+
let liveModelSelectionCommitFinished = false;
|
|
18872
|
+
let committedThinkingLevelLiveMutationRevision: number | undefined;
|
|
18873
|
+
try {
|
|
18874
|
+
publication = this.settings.publishGlobalConfigReload(stagedSettings, () => {
|
|
18875
|
+
modelCommitStarted = true;
|
|
18876
|
+
stagedModels!.commit();
|
|
18877
|
+
if (prepared) {
|
|
18878
|
+
activationPublicationStarted = true;
|
|
18879
|
+
publishPreparedModelProfileActivation(prepared, {
|
|
18880
|
+
preserveDefaultModelSelection,
|
|
18881
|
+
preserveLiveModelSelection,
|
|
18882
|
+
});
|
|
18883
|
+
}
|
|
18884
|
+
if (preparedLiveModelSelection) {
|
|
18885
|
+
this.#assertPreparedProfileModelSelectionCurrent(preparedLiveModelSelection);
|
|
18886
|
+
liveModelSelectionCommitted = true;
|
|
18887
|
+
this.commitPreparedProfileModelSelection(
|
|
18888
|
+
preparedLiveModelSelection,
|
|
18889
|
+
mutationRevision => {
|
|
18890
|
+
committedThinkingLevelLiveMutationRevision = mutationRevision;
|
|
18891
|
+
},
|
|
18892
|
+
);
|
|
18893
|
+
liveModelSelectionCommitFinished = true;
|
|
18894
|
+
}
|
|
18895
|
+
});
|
|
18896
|
+
stagedModels.finalize();
|
|
18897
|
+
publication.finalize();
|
|
18898
|
+
modelCommitSucceeded = true;
|
|
18899
|
+
} catch (error) {
|
|
18900
|
+
const rollbackErrors: unknown[] = [];
|
|
18901
|
+
if (liveModelSelectionCommitted && committedThinkingLevelLiveMutationRevision === undefined) {
|
|
18902
|
+
committedThinkingLevelLiveMutationRevision = this.#thinkingLevelLiveMutationRevision;
|
|
18903
|
+
}
|
|
18904
|
+
try {
|
|
18905
|
+
publication?.rollback();
|
|
18906
|
+
} catch (rollbackError) {
|
|
18907
|
+
rollbackErrors.push(rollbackError);
|
|
18908
|
+
}
|
|
18909
|
+
if (prepared && activationPublicationStarted) {
|
|
18910
|
+
try {
|
|
18911
|
+
await rollbackPreparedModelProfileActivation(prepared, {
|
|
18912
|
+
preserveDefaultModelSelection,
|
|
18913
|
+
preserveLiveModelSelection,
|
|
18914
|
+
isCurrent: () => this.getUserModelSelectionRevision() === userModelSelectionRevision,
|
|
18915
|
+
});
|
|
18916
|
+
} catch (rollbackError) {
|
|
18917
|
+
rollbackErrors.push(rollbackError);
|
|
18918
|
+
}
|
|
18919
|
+
}
|
|
18920
|
+
const thinkingLevelMutationSuperseded =
|
|
18921
|
+
committedThinkingLevelLiveMutationRevision !== undefined &&
|
|
18922
|
+
this.#thinkingLevelLiveMutationRevision !== committedThinkingLevelLiveMutationRevision;
|
|
18923
|
+
if (
|
|
18924
|
+
liveModelSelectionCommitted &&
|
|
18925
|
+
preparedLiveModelSelection &&
|
|
18926
|
+
this.getUserModelSelectionRevision() === preparedLiveModelSelectionRevision &&
|
|
18927
|
+
this.model === preparedLiveModelSelection.model &&
|
|
18928
|
+
(!liveModelSelectionCommitFinished ||
|
|
18929
|
+
thinkingLevelMutationSuperseded ||
|
|
18930
|
+
this.thinkingLevel === preparedLiveModelSelection.committedThinkingLevel)
|
|
18931
|
+
) {
|
|
18932
|
+
try {
|
|
18933
|
+
await this.restoreModelSelectionForRollback(
|
|
18934
|
+
preparedLiveModelSelection.previousModel,
|
|
18935
|
+
thinkingLevelMutationSuperseded
|
|
18936
|
+
? this.thinkingLevel
|
|
18937
|
+
: preparedLiveModelSelection.previousThinkingLevel,
|
|
18938
|
+
);
|
|
18939
|
+
} catch (rollbackError) {
|
|
18940
|
+
rollbackErrors.push(rollbackError);
|
|
18941
|
+
}
|
|
18942
|
+
}
|
|
18943
|
+
if (modelCommitStarted) {
|
|
18944
|
+
const selectedModelBeforeRollback = this.model;
|
|
18945
|
+
const selectedThinkingLevelBeforeRollback = this.thinkingLevel;
|
|
18946
|
+
const selectionRevisionBeforeRollback = this.getUserModelSelectionRevision();
|
|
18947
|
+
try {
|
|
18948
|
+
stagedModels!.rollback();
|
|
18949
|
+
modelRollbackAttempted = true;
|
|
18950
|
+
if (
|
|
18951
|
+
selectedModelBeforeRollback &&
|
|
18952
|
+
this.model === selectedModelBeforeRollback &&
|
|
18953
|
+
this.getUserModelSelectionRevision() === selectionRevisionBeforeRollback
|
|
18954
|
+
) {
|
|
18955
|
+
const restoredModel = this.#modelRegistry
|
|
18956
|
+
.getAvailable()
|
|
18957
|
+
.find(
|
|
18958
|
+
model =>
|
|
18959
|
+
model.provider === selectedModelBeforeRollback.provider &&
|
|
18960
|
+
model.id === selectedModelBeforeRollback.id,
|
|
18961
|
+
);
|
|
18962
|
+
if (restoredModel && !util.isDeepStrictEqual(selectedModelBeforeRollback, restoredModel)) {
|
|
18963
|
+
await this.restoreModelSelectionForRollback(
|
|
18964
|
+
restoredModel,
|
|
18965
|
+
selectedThinkingLevelBeforeRollback,
|
|
18966
|
+
);
|
|
18967
|
+
}
|
|
18968
|
+
}
|
|
18969
|
+
} catch (rollbackError) {
|
|
18970
|
+
rollbackErrors.push(rollbackError);
|
|
18971
|
+
modelRollbackAttempted = true;
|
|
18972
|
+
}
|
|
18973
|
+
}
|
|
18974
|
+
if (rollbackErrors.length > 0) {
|
|
18975
|
+
throw new ConfigurationReloadError(
|
|
18976
|
+
"PUBLICATION_FAILED",
|
|
18977
|
+
new AggregateError([error, ...rollbackErrors]),
|
|
18978
|
+
);
|
|
18979
|
+
}
|
|
18980
|
+
throw new ConfigurationReloadError("PUBLICATION_FAILED", error);
|
|
18981
|
+
} finally {
|
|
18982
|
+
publicationFenceRelease?.();
|
|
18983
|
+
publicationFenceRelease = undefined;
|
|
18984
|
+
}
|
|
18985
|
+
const preparedActivation = prepared;
|
|
18986
|
+
if (preparedActivation) {
|
|
18987
|
+
await this.#modelRegistry.withConsumerLease(async () => {
|
|
18988
|
+
await finishPreparedModelProfileActivation(preparedActivation);
|
|
18989
|
+
});
|
|
18990
|
+
}
|
|
18991
|
+
if (preparedLiveModelSelection) {
|
|
18992
|
+
try {
|
|
18993
|
+
await this.finishPreparedProfileModelSelection(preparedLiveModelSelection);
|
|
18994
|
+
} catch (error) {
|
|
18995
|
+
logger.warn("Failed to refresh session tools after model catalog reload", {
|
|
18996
|
+
error: error instanceof Error ? error.message : String(error),
|
|
18997
|
+
});
|
|
18998
|
+
}
|
|
18999
|
+
}
|
|
19000
|
+
return {
|
|
19001
|
+
applied: true,
|
|
19002
|
+
settingsChanged,
|
|
19003
|
+
modelsChanged,
|
|
19004
|
+
changedSettings,
|
|
19005
|
+
};
|
|
19006
|
+
},
|
|
19007
|
+
reloadSignal,
|
|
19008
|
+
undefined,
|
|
19009
|
+
{ selectionTransaction, skipModelRegistryLease: true },
|
|
19010
|
+
);
|
|
19011
|
+
} catch (error) {
|
|
19012
|
+
if (stagedModels && !modelRollbackAttempted && !modelCommitSucceeded) {
|
|
19013
|
+
try {
|
|
19014
|
+
stagedModels.rollback();
|
|
19015
|
+
} catch (rollbackError) {
|
|
19016
|
+
throw new ConfigurationReloadError("PUBLICATION_FAILED", new AggregateError([error, rollbackError]));
|
|
19017
|
+
}
|
|
19018
|
+
}
|
|
19019
|
+
if (
|
|
19020
|
+
transitionAbortController.signal.aborted &&
|
|
19021
|
+
!signal.aborted &&
|
|
19022
|
+
!this.#disposeAbortController.signal.aborted
|
|
19023
|
+
) {
|
|
19024
|
+
retryAfterSessionTransition = true;
|
|
19025
|
+
} else if (error instanceof ConfigurationReloadError) {
|
|
19026
|
+
throw error;
|
|
19027
|
+
} else {
|
|
19028
|
+
throw new ConfigurationReloadError("PUBLICATION_FAILED", error);
|
|
19029
|
+
}
|
|
19030
|
+
} finally {
|
|
19031
|
+
publicationFenceRelease?.();
|
|
19032
|
+
if (this.#selectionAwaitingMutationTransaction === selectionTransaction) {
|
|
19033
|
+
this.#selectionAwaitingMutationTransaction = undefined;
|
|
19034
|
+
}
|
|
19035
|
+
selectionFence.resolve();
|
|
19036
|
+
this.#pendingSelectionFences -= 1;
|
|
19037
|
+
if (this.#oldestPendingSelectionFenceGeneration === selectionFenceGeneration) {
|
|
19038
|
+
this.#oldestPendingSelectionFenceGeneration = 0;
|
|
19039
|
+
}
|
|
19040
|
+
this.#configurationReloadControllers.delete(transitionAbortController);
|
|
19041
|
+
this.#resolveSessionSettlement();
|
|
19042
|
+
}
|
|
19043
|
+
if (retryAfterSessionTransition) {
|
|
19044
|
+
await this.#waitForSessionTransitionEnd(AbortSignal.any([signal, this.#disposeAbortController.signal]));
|
|
19045
|
+
return await this.#reloadConfiguration(
|
|
19046
|
+
candidate,
|
|
19047
|
+
signal,
|
|
19048
|
+
userModelSelectionRevision,
|
|
19049
|
+
userModelSelectionModel,
|
|
19050
|
+
);
|
|
19051
|
+
}
|
|
19052
|
+
throw new Error("Configuration reload exited without a result or a session-transition retry.");
|
|
19053
|
+
}
|
|
19054
|
+
|
|
19055
|
+
#updatedCurrentModel(registry: ModelRegistry): Model | undefined {
|
|
19056
|
+
const current = this.model;
|
|
19057
|
+
if (!current) return undefined;
|
|
19058
|
+
const updated = registry
|
|
19059
|
+
.getAvailable()
|
|
19060
|
+
.find(model => model.provider === current.provider && model.id === current.id);
|
|
19061
|
+
if (!updated) throw new ConfigurationReloadError("MODEL_UNAVAILABLE");
|
|
19062
|
+
return updated;
|
|
19063
|
+
}
|
|
19064
|
+
|
|
19065
|
+
#currentModelConfigurationChanged(registry: ModelRegistry): boolean {
|
|
19066
|
+
const current = this.model;
|
|
19067
|
+
const updated = this.#updatedCurrentModel(registry);
|
|
19068
|
+
return current !== undefined && updated !== undefined && !util.isDeepStrictEqual(current, updated);
|
|
19069
|
+
}
|
|
19070
|
+
|
|
18322
19071
|
/**
|
|
18323
19072
|
* Re-apply vendor-separated delegation after a profile activation changed the
|
|
18324
19073
|
* role layer. `eagerTasks` is resolved at prompt build time, but under
|
|
@@ -18412,10 +19161,28 @@ export class AgentSession {
|
|
|
18412
19161
|
const modelRoles = { ...this.settings.get("modelRoles") };
|
|
18413
19162
|
const agentOverrides = { ...this.settings.get("task.agentModelOverrides") };
|
|
18414
19163
|
for (const [role, baseline] of this.#activeProfileInstalledRoles) {
|
|
19164
|
+
const installed = this.#activeProfileInstalledRoleValues.get(role);
|
|
19165
|
+
if (
|
|
19166
|
+
installed !== undefined &&
|
|
19167
|
+
!util.isDeepStrictEqual(
|
|
19168
|
+
normalizeModelSelectorValue(modelRoles[role]),
|
|
19169
|
+
normalizeModelSelectorValue(installed),
|
|
19170
|
+
)
|
|
19171
|
+
)
|
|
19172
|
+
continue;
|
|
18415
19173
|
if (role === "default" || baseline === undefined) delete modelRoles[role];
|
|
18416
19174
|
else modelRoles[role] = baseline;
|
|
18417
19175
|
}
|
|
18418
19176
|
for (const [role, baseline] of this.#activeProfileInstalledAgentOverrides) {
|
|
19177
|
+
const installed = this.#activeProfileInstalledAgentOverrideValues.get(role);
|
|
19178
|
+
if (
|
|
19179
|
+
installed !== undefined &&
|
|
19180
|
+
!util.isDeepStrictEqual(
|
|
19181
|
+
normalizeModelSelectorValue(agentOverrides[role]),
|
|
19182
|
+
normalizeModelSelectorValue(installed),
|
|
19183
|
+
)
|
|
19184
|
+
)
|
|
19185
|
+
continue;
|
|
18419
19186
|
if (baseline === undefined) delete agentOverrides[role];
|
|
18420
19187
|
else agentOverrides[role] = baseline;
|
|
18421
19188
|
}
|
|
@@ -18423,6 +19190,9 @@ export class AgentSession {
|
|
|
18423
19190
|
this.settings.override("task.agentModelOverrides", agentOverrides);
|
|
18424
19191
|
this.#activeProfileInstalledRoles.clear();
|
|
18425
19192
|
this.#activeProfileInstalledAgentOverrides.clear();
|
|
19193
|
+
this.#activeProfileInstalledRoleValues.clear();
|
|
19194
|
+
this.#activeProfileInstalledAgentOverrideValues.clear();
|
|
19195
|
+
this.#activeProfileConfiguredAgentModelOverrides.clear();
|
|
18426
19196
|
}
|
|
18427
19197
|
if (hadInstalledKeys || this.getActiveModelProfile() !== undefined) {
|
|
18428
19198
|
this.#modelRegistry.reapplyConfiguredModelBindings(this.settings);
|
|
@@ -18440,31 +19210,106 @@ export class AgentSession {
|
|
|
18440
19210
|
modelRoles: readonly string[],
|
|
18441
19211
|
agentModelOverrides: readonly string[],
|
|
18442
19212
|
preProfileModel: Model | undefined,
|
|
19213
|
+
baseline?: {
|
|
19214
|
+
modelRoles: Readonly<Record<string, ModelSelectorValue>>;
|
|
19215
|
+
agentModelOverrides: Readonly<Record<string, ModelSelectorValue>>;
|
|
19216
|
+
},
|
|
19217
|
+
installed?: {
|
|
19218
|
+
modelRoles: Readonly<Record<string, ModelSelectorValue>>;
|
|
19219
|
+
agentModelOverrides: Readonly<Record<string, ModelSelectorValue>>;
|
|
19220
|
+
},
|
|
19221
|
+
manual?: { modelRoles: readonly string[]; agentModelOverrides: readonly string[] },
|
|
18443
19222
|
): void {
|
|
18444
19223
|
const bindings = this.#modelRegistry.getConfiguredModelBindings?.();
|
|
18445
19224
|
if (this.#preProfileModel === undefined) this.#preProfileModel = preProfileModel;
|
|
19225
|
+
const modelRoleSet = new Set(modelRoles);
|
|
19226
|
+
const agentOverrideSet = new Set(agentModelOverrides);
|
|
19227
|
+
for (const role of this.#activeProfileInstalledRoles.keys()) {
|
|
19228
|
+
if (!modelRoleSet.has(role)) {
|
|
19229
|
+
this.#activeProfileInstalledRoles.delete(role);
|
|
19230
|
+
this.#activeProfileInstalledRoleValues.delete(role);
|
|
19231
|
+
}
|
|
19232
|
+
}
|
|
19233
|
+
for (const role of this.#activeProfileInstalledAgentOverrides.keys()) {
|
|
19234
|
+
if (!agentOverrideSet.has(role)) {
|
|
19235
|
+
this.#activeProfileInstalledAgentOverrides.delete(role);
|
|
19236
|
+
this.#activeProfileInstalledAgentOverrideValues.delete(role);
|
|
19237
|
+
this.#activeProfileConfiguredAgentModelOverrides.delete(role);
|
|
19238
|
+
}
|
|
19239
|
+
}
|
|
18446
19240
|
for (const role of modelRoles) {
|
|
18447
|
-
if (this.#activeProfileInstalledRoles.has(role))
|
|
18448
|
-
|
|
18449
|
-
|
|
18450
|
-
|
|
18451
|
-
|
|
18452
|
-
|
|
19241
|
+
if (!this.#activeProfileInstalledRoles.has(role)) {
|
|
19242
|
+
const bindingValue = bindings?.modelRoles?.[role];
|
|
19243
|
+
this.#activeProfileInstalledRoles.set(
|
|
19244
|
+
role,
|
|
19245
|
+
baseline
|
|
19246
|
+
? Object.hasOwn(baseline.modelRoles, role)
|
|
19247
|
+
? baseline.modelRoles[role]
|
|
19248
|
+
: undefined
|
|
19249
|
+
: (bindingValue ?? this.settings.getGlobal("modelRoles")?.[role as never]),
|
|
19250
|
+
);
|
|
19251
|
+
}
|
|
19252
|
+
const installedValue = installed?.modelRoles[role] ?? this.settings.get("modelRoles")[role];
|
|
19253
|
+
if (installedValue !== undefined) this.#activeProfileInstalledRoleValues.set(role, installedValue);
|
|
18453
19254
|
}
|
|
18454
19255
|
for (const role of agentModelOverrides) {
|
|
18455
|
-
if (
|
|
18456
|
-
|
|
18457
|
-
|
|
18458
|
-
role
|
|
18459
|
-
|
|
18460
|
-
)
|
|
19256
|
+
if (Object.hasOwn(bindings?.agentModelOverrides ?? {}, role)) {
|
|
19257
|
+
this.#activeProfileConfiguredAgentModelOverrides.add(role);
|
|
19258
|
+
} else {
|
|
19259
|
+
this.#activeProfileConfiguredAgentModelOverrides.delete(role);
|
|
19260
|
+
}
|
|
19261
|
+
if (!this.#activeProfileInstalledAgentOverrides.has(role)) {
|
|
19262
|
+
const bindingValue = bindings?.agentModelOverrides?.[role];
|
|
19263
|
+
this.#activeProfileInstalledAgentOverrides.set(
|
|
19264
|
+
role,
|
|
19265
|
+
baseline
|
|
19266
|
+
? Object.hasOwn(baseline.agentModelOverrides, role)
|
|
19267
|
+
? baseline.agentModelOverrides[role]
|
|
19268
|
+
: undefined
|
|
19269
|
+
: (bindingValue ?? this.settings.getGlobal("task.agentModelOverrides")?.[role as never]),
|
|
19270
|
+
);
|
|
19271
|
+
}
|
|
19272
|
+
const installedValue =
|
|
19273
|
+
installed?.agentModelOverrides[role] ?? this.settings.get("task.agentModelOverrides")[role];
|
|
19274
|
+
if (installedValue !== undefined) this.#activeProfileInstalledAgentOverrideValues.set(role, installedValue);
|
|
18461
19275
|
}
|
|
19276
|
+
this.#activeProfileManualModelRoles = new Set(manual?.modelRoles ?? []);
|
|
19277
|
+
this.#activeProfileManualAgentModelOverrides = new Set(manual?.agentModelOverrides ?? []);
|
|
19278
|
+
}
|
|
19279
|
+
|
|
19280
|
+
getProfileInstalledOverrideState(): AgentSessionProfileInstalledOverrideState {
|
|
19281
|
+
return {
|
|
19282
|
+
modelRoles: new Map(this.#activeProfileInstalledRoles),
|
|
19283
|
+
agentModelOverrides: new Map(this.#activeProfileInstalledAgentOverrides),
|
|
19284
|
+
installedModelRoles: new Map(this.#activeProfileInstalledRoleValues),
|
|
19285
|
+
installedAgentModelOverrides: new Map(this.#activeProfileInstalledAgentOverrideValues),
|
|
19286
|
+
manualModelRoles: new Set(this.#activeProfileManualModelRoles),
|
|
19287
|
+
manualAgentModelOverrides: new Set(this.#activeProfileManualAgentModelOverrides),
|
|
19288
|
+
configuredAgentModelOverrides: new Set(this.#activeProfileConfiguredAgentModelOverrides),
|
|
19289
|
+
preProfileModel: this.#preProfileModel,
|
|
19290
|
+
};
|
|
19291
|
+
}
|
|
19292
|
+
|
|
19293
|
+
restoreProfileInstalledOverrideState(state: AgentSessionProfileInstalledOverrideState): void {
|
|
19294
|
+
this.#activeProfileInstalledRoles = new Map(state.modelRoles);
|
|
19295
|
+
this.#activeProfileInstalledAgentOverrides = new Map(state.agentModelOverrides);
|
|
19296
|
+
this.#activeProfileInstalledRoleValues = new Map(state.installedModelRoles);
|
|
19297
|
+
this.#activeProfileInstalledAgentOverrideValues = new Map(state.installedAgentModelOverrides);
|
|
19298
|
+
this.#activeProfileManualModelRoles = new Set(state.manualModelRoles);
|
|
19299
|
+
this.#activeProfileManualAgentModelOverrides = new Set(state.manualAgentModelOverrides);
|
|
19300
|
+
this.#activeProfileConfiguredAgentModelOverrides = new Set(state.configuredAgentModelOverrides);
|
|
19301
|
+
this.#preProfileModel = state.preProfileModel;
|
|
18462
19302
|
}
|
|
18463
19303
|
|
|
18464
19304
|
/** Drop the recorded profile-installed override keys after materialization. */
|
|
18465
19305
|
clearProfileInstalledOverrides(): void {
|
|
18466
19306
|
this.#activeProfileInstalledRoles.clear();
|
|
18467
19307
|
this.#activeProfileInstalledAgentOverrides.clear();
|
|
19308
|
+
this.#activeProfileInstalledRoleValues.clear();
|
|
19309
|
+
this.#activeProfileInstalledAgentOverrideValues.clear();
|
|
19310
|
+
this.#activeProfileManualModelRoles.clear();
|
|
19311
|
+
this.#activeProfileManualAgentModelOverrides.clear();
|
|
19312
|
+
this.#activeProfileConfiguredAgentModelOverrides.clear();
|
|
18468
19313
|
}
|
|
18469
19314
|
|
|
18470
19315
|
/** Current profile-installed override keys, for deriving the activation base. */
|
|
@@ -18897,6 +19742,60 @@ export class AgentSession {
|
|
|
18897
19742
|
return scope;
|
|
18898
19743
|
}
|
|
18899
19744
|
|
|
19745
|
+
async prepareModelSelectionForProfileActivation(
|
|
19746
|
+
model: Model,
|
|
19747
|
+
thinkingLevel: ThinkingLevel | undefined,
|
|
19748
|
+
signal?: AbortSignal,
|
|
19749
|
+
): Promise<PreparedProfileModelSelection> {
|
|
19750
|
+
signal?.throwIfAborted();
|
|
19751
|
+
const sessionId = this.sessionId;
|
|
19752
|
+
const previousModel = this.model;
|
|
19753
|
+
const previousThinkingLevel = this.thinkingLevel;
|
|
19754
|
+
const previousEditMode = this.#resolveActiveEditMode();
|
|
19755
|
+
const committedThinkingLevel = resolveThinkingLevelForModel(
|
|
19756
|
+
model,
|
|
19757
|
+
thinkingLevel ?? model.thinking?.defaultLevel ?? previousThinkingLevel,
|
|
19758
|
+
);
|
|
19759
|
+
if (this.sessionId !== sessionId) throw new Error("Session changed while preparing profile model selection");
|
|
19760
|
+
return {
|
|
19761
|
+
sessionId,
|
|
19762
|
+
model,
|
|
19763
|
+
thinkingLevel,
|
|
19764
|
+
committedThinkingLevel,
|
|
19765
|
+
previousModel,
|
|
19766
|
+
previousThinkingLevel,
|
|
19767
|
+
previousEditMode,
|
|
19768
|
+
signal,
|
|
19769
|
+
};
|
|
19770
|
+
}
|
|
19771
|
+
|
|
19772
|
+
commitPreparedProfileModelSelection(
|
|
19773
|
+
prepared: PreparedProfileModelSelection,
|
|
19774
|
+
onThinkingLevelApplied?: (mutationRevision: number) => void,
|
|
19775
|
+
): void {
|
|
19776
|
+
this.#assertPreparedProfileModelSelectionCurrent(prepared);
|
|
19777
|
+
this.#setModelAuthoritatively(prepared.model, "profile-activation");
|
|
19778
|
+
this.#syncAppendOnlyContext(prepared.model);
|
|
19779
|
+
this.sessionManager.appendModelChange(`${prepared.model.provider}/${prepared.model.id}`, "temporary");
|
|
19780
|
+
this.settings.getStorage()?.recordModelUsage(`${prepared.model.provider}/${prepared.model.id}`);
|
|
19781
|
+
this.#applyThinkingLevel(prepared.committedThinkingLevel, false, false, onThinkingLevelApplied);
|
|
19782
|
+
}
|
|
19783
|
+
|
|
19784
|
+
#assertPreparedProfileModelSelectionCurrent(prepared: PreparedProfileModelSelection): void {
|
|
19785
|
+
prepared.signal?.throwIfAborted();
|
|
19786
|
+
if (
|
|
19787
|
+
this.sessionId !== prepared.sessionId ||
|
|
19788
|
+
this.model !== prepared.previousModel ||
|
|
19789
|
+
this.thinkingLevel !== prepared.previousThinkingLevel
|
|
19790
|
+
) {
|
|
19791
|
+
throw new Error("Session model changed while preparing profile activation");
|
|
19792
|
+
}
|
|
19793
|
+
}
|
|
19794
|
+
|
|
19795
|
+
finishPreparedProfileModelSelection(prepared: PreparedProfileModelSelection): Promise<void> {
|
|
19796
|
+
return this.#syncEditToolModeAfterModelChange(prepared.previousEditMode);
|
|
19797
|
+
}
|
|
19798
|
+
|
|
18900
19799
|
/** Restore the exact live-model state captured before a failed selector transaction. */
|
|
18901
19800
|
async restoreModelSelectionForRollback(
|
|
18902
19801
|
model: Model | undefined,
|
|
@@ -19449,7 +20348,12 @@ export class AgentSession {
|
|
|
19449
20348
|
* effort (issue #4695): only control surfaces may pass true, so model-default
|
|
19450
20349
|
* appends never mint `session` scope in {@link getThinkingScopeForControl}.
|
|
19451
20350
|
*/
|
|
19452
|
-
#applyThinkingLevel(
|
|
20351
|
+
#applyThinkingLevel(
|
|
20352
|
+
level: ThinkingLevel | undefined,
|
|
20353
|
+
persist: boolean,
|
|
20354
|
+
operatorIntent: boolean,
|
|
20355
|
+
onApplied?: (mutationRevision: number) => void,
|
|
20356
|
+
): void {
|
|
19453
20357
|
if (persist) this.#assertDurableSettingsWritable();
|
|
19454
20358
|
this.#thinkingLevelMutationRevision++;
|
|
19455
20359
|
this.#thinkingLevelLiveMutationRevision++;
|
|
@@ -19459,6 +20363,7 @@ export class AgentSession {
|
|
|
19459
20363
|
const isChanging = effectiveLevel !== this.#thinkingLevel;
|
|
19460
20364
|
|
|
19461
20365
|
this.#thinkingLevel = effectiveLevel;
|
|
20366
|
+
onApplied?.(this.#thinkingLevelLiveMutationRevision);
|
|
19462
20367
|
this.agent.setThinkingLevel(toReasoningEffort(effectiveLevel));
|
|
19463
20368
|
if (isChanging) this.#defaultModelSelectionMutationRevision++;
|
|
19464
20369
|
|
|
@@ -20474,7 +21379,7 @@ export class AgentSession {
|
|
|
20474
21379
|
promptOverride: compactionPrep.hookPrompt,
|
|
20475
21380
|
extraContext: compactionPrep.hookContext,
|
|
20476
21381
|
remoteInstructions: this.#baseSystemPrompt.join("\n\n"),
|
|
20477
|
-
convertToLlm,
|
|
21382
|
+
convertToLlm: (messages) => this.#summaryConvertToLlm(messages),
|
|
20478
21383
|
},
|
|
20479
21384
|
);
|
|
20480
21385
|
summary = result.summary;
|
|
@@ -20725,7 +21630,7 @@ export class AgentSession {
|
|
|
20725
21630
|
tools: this.agent.state.tools,
|
|
20726
21631
|
customInstructions,
|
|
20727
21632
|
promptExtension: this.settings.get("compaction.handoffPromptExtension") || undefined,
|
|
20728
|
-
convertToLlm,
|
|
21633
|
+
convertToLlm: (messages) => this.#summaryConvertToLlm(messages),
|
|
20729
21634
|
initiatorOverride: "agent",
|
|
20730
21635
|
metadata: this.agent.metadataForProvider(model.provider),
|
|
20731
21636
|
telemetry: resolveTelemetry(this.agent.telemetry, this.sessionId),
|
|
@@ -21273,6 +22178,20 @@ export class AgentSession {
|
|
|
21273
22178
|
if (compactionStatus.kind === "aborted") {
|
|
21274
22179
|
return compactionStatus.source === "hook" ? result("not-needed") : result("aborted");
|
|
21275
22180
|
}
|
|
22181
|
+
// A skipped compaction (most often: nothing before the keep window to
|
|
22182
|
+
// summarize, e.g. one oversized early message) leaves the context
|
|
22183
|
+
// unchanged. Crossing the threshold is not an overflow: while the
|
|
22184
|
+
// unchanged context still fits the model window with its reserve, send
|
|
22185
|
+
// it, as the agent_end threshold path does. Failing here would end every
|
|
22186
|
+
// later turn of the session with the same error. A prune committed above
|
|
22187
|
+
// has already rewritten durable history, so the run must restart on it
|
|
22188
|
+
// rather than send its pre-prune snapshot.
|
|
22189
|
+
if (
|
|
22190
|
+
compactionStatus.kind === "skipped" &&
|
|
22191
|
+
contextTokens + effectiveReserveTokens(contextWindow, compactionSettings) <= contextWindow
|
|
22192
|
+
) {
|
|
22193
|
+
return pruneResult?.committed ? result("pruned", true) : result("not-needed");
|
|
22194
|
+
}
|
|
21276
22195
|
return result(
|
|
21277
22196
|
"failed",
|
|
21278
22197
|
false,
|
|
@@ -22485,6 +23404,18 @@ export class AgentSession {
|
|
|
22485
23404
|
};
|
|
22486
23405
|
}
|
|
22487
23406
|
|
|
23407
|
+
/**
|
|
23408
|
+
* Manual compact, auto compact, handoff, and branch summary keep the
|
|
23409
|
+
* existing conversion and obfuscate with the session registry prompt turns
|
|
23410
|
+
* already use.
|
|
23411
|
+
*/
|
|
23412
|
+
#summaryConvertToLlm(messages: AgentMessage[]): Message[] {
|
|
23413
|
+
const converted = convertToLlm(messages);
|
|
23414
|
+
const obfuscator = this.#obfuscator;
|
|
23415
|
+
if (!obfuscator?.hasSecrets()) return converted;
|
|
23416
|
+
return obfuscateMessages(obfuscator, converted);
|
|
23417
|
+
}
|
|
23418
|
+
|
|
22488
23419
|
async #compactWithFallbackModel(
|
|
22489
23420
|
preparation: CompactionPreparation,
|
|
22490
23421
|
customInstructions: string | undefined,
|
|
@@ -22513,7 +23444,7 @@ export class AgentSession {
|
|
|
22513
23444
|
...options,
|
|
22514
23445
|
...this.#maintenanceProviderTransport(),
|
|
22515
23446
|
metadata: this.agent.metadataForProvider(candidate.provider),
|
|
22516
|
-
convertToLlm,
|
|
23447
|
+
convertToLlm: (messages) => this.#summaryConvertToLlm(messages),
|
|
22517
23448
|
telemetry,
|
|
22518
23449
|
authCredentialType: this.#modelRegistry.getSessionCredentialType(
|
|
22519
23450
|
candidate.provider,
|
|
@@ -23038,7 +23969,7 @@ export class AgentSession {
|
|
|
23038
23969
|
remoteInstructions: this.#baseSystemPrompt.join("\n\n"),
|
|
23039
23970
|
metadata: this.agent.metadataForProvider(candidate.provider),
|
|
23040
23971
|
initiatorOverride: "agent",
|
|
23041
|
-
convertToLlm,
|
|
23972
|
+
convertToLlm: (messages) => this.#summaryConvertToLlm(messages),
|
|
23042
23973
|
telemetry,
|
|
23043
23974
|
authCredentialType: this.#modelRegistry.getSessionCredentialType(
|
|
23044
23975
|
candidate.provider,
|
|
@@ -26719,6 +27650,7 @@ export class AgentSession {
|
|
|
26719
27650
|
let ownerShutdownManager: AsyncJobManager | undefined;
|
|
26720
27651
|
let ownerShutdownLease: OwnerSubagentShutdownLease | undefined;
|
|
26721
27652
|
let ownerShutdownTransitionCommitted = false;
|
|
27653
|
+
let ownerShutdownCanDrainResumes = false;
|
|
26722
27654
|
let ownerShutdownFinalizationDeferred = false;
|
|
26723
27655
|
this.#beginSessionTransition("switch-session");
|
|
26724
27656
|
try {
|
|
@@ -26757,6 +27689,8 @@ export class AgentSession {
|
|
|
26757
27689
|
if (lease && asyncManager) {
|
|
26758
27690
|
ownerShutdownManager = asyncManager;
|
|
26759
27691
|
ownerShutdownLease = lease;
|
|
27692
|
+
// Early failures leave the predecessor live, so releasing its fence may drain resumes.
|
|
27693
|
+
ownerShutdownCanDrainResumes = true;
|
|
26760
27694
|
}
|
|
26761
27695
|
await this.abort();
|
|
26762
27696
|
if (this.isCompacting) {
|
|
@@ -26787,6 +27721,15 @@ export class AgentSession {
|
|
|
26787
27721
|
);
|
|
26788
27722
|
const previousActiveProfileInstalledRoles = new Map(this.#activeProfileInstalledRoles);
|
|
26789
27723
|
const previousActiveProfileInstalledAgentOverrides = new Map(this.#activeProfileInstalledAgentOverrides);
|
|
27724
|
+
const previousActiveProfileInstalledRoleValues = new Map(this.#activeProfileInstalledRoleValues);
|
|
27725
|
+
const previousActiveProfileInstalledAgentOverrideValues = new Map(
|
|
27726
|
+
this.#activeProfileInstalledAgentOverrideValues,
|
|
27727
|
+
);
|
|
27728
|
+
const previousActiveProfileManualModelRoles = new Set(this.#activeProfileManualModelRoles);
|
|
27729
|
+
const previousActiveProfileManualAgentModelOverrides = new Set(this.#activeProfileManualAgentModelOverrides);
|
|
27730
|
+
const previousActiveProfileConfiguredAgentModelOverrides = new Set(
|
|
27731
|
+
this.#activeProfileConfiguredAgentModelOverrides,
|
|
27732
|
+
);
|
|
26790
27733
|
const previousPreProfileModel = this.#preProfileModel;
|
|
26791
27734
|
const previousServiceTier = this.agent.serviceTier;
|
|
26792
27735
|
const previousSelectedMCPToolNames = new Set(this.#selectedMCPToolNames);
|
|
@@ -26801,6 +27744,8 @@ export class AgentSession {
|
|
|
26801
27744
|
const previousActiveAttemptScope = this.#activeAttemptScope;
|
|
26802
27745
|
const previousActiveLogicalRunId = this.#activeLogicalRunId;
|
|
26803
27746
|
|
|
27747
|
+
// Once session state starts changing, resumes are safe only after rollback completes.
|
|
27748
|
+
ownerShutdownCanDrainResumes = false;
|
|
26804
27749
|
this.#steeringMessages = [];
|
|
26805
27750
|
this.#followUpMessages = [];
|
|
26806
27751
|
this.#pendingNextTurnMessages = [];
|
|
@@ -26918,7 +27863,7 @@ export class AgentSession {
|
|
|
26918
27863
|
? settingsDefaultEntries
|
|
26919
27864
|
: (configuredDefaultChain?.entries ??
|
|
26920
27865
|
(sessionContext.models.default ? [sessionContext.models.default] : []));
|
|
26921
|
-
this
|
|
27866
|
+
this.setActiveModelProfile(targetActiveModelProfile);
|
|
26922
27867
|
this.#defaultFallbackController = undefined;
|
|
26923
27868
|
if (defaultEntries.length > 0) {
|
|
26924
27869
|
const resolution = await resolveModelChainWithAuth(
|
|
@@ -27014,7 +27959,7 @@ export class AgentSession {
|
|
|
27014
27959
|
// a cross-file transition must not advertise that profile after its
|
|
27015
27960
|
// runtime role layer was removed. Otherwise delegation prompt state and
|
|
27016
27961
|
// actual role routing diverge.
|
|
27017
|
-
if (!targetProfileRuntimeInstalled) this
|
|
27962
|
+
if (!targetProfileRuntimeInstalled) this.setActiveModelProfile(undefined);
|
|
27018
27963
|
|
|
27019
27964
|
const hasThinkingEntry = this.sessionManager
|
|
27020
27965
|
.getBranch()
|
|
@@ -27217,6 +28162,15 @@ export class AgentSession {
|
|
|
27217
28162
|
else this.settings.override("task.agentModelOverrides", previousAgentModelOverridesOverride);
|
|
27218
28163
|
this.#activeProfileInstalledRoles = new Map(previousActiveProfileInstalledRoles);
|
|
27219
28164
|
this.#activeProfileInstalledAgentOverrides = new Map(previousActiveProfileInstalledAgentOverrides);
|
|
28165
|
+
this.#activeProfileInstalledRoleValues = new Map(previousActiveProfileInstalledRoleValues);
|
|
28166
|
+
this.#activeProfileInstalledAgentOverrideValues = new Map(
|
|
28167
|
+
previousActiveProfileInstalledAgentOverrideValues,
|
|
28168
|
+
);
|
|
28169
|
+
this.#activeProfileManualModelRoles = new Set(previousActiveProfileManualModelRoles);
|
|
28170
|
+
this.#activeProfileManualAgentModelOverrides = new Set(previousActiveProfileManualAgentModelOverrides);
|
|
28171
|
+
this.#activeProfileConfiguredAgentModelOverrides = new Set(
|
|
28172
|
+
previousActiveProfileConfiguredAgentModelOverrides,
|
|
28173
|
+
);
|
|
27220
28174
|
this.#preProfileModel = previousPreProfileModel;
|
|
27221
28175
|
this.#activeModelProfile = previousActiveModelProfile;
|
|
27222
28176
|
this.#restoreWorkflowGateEmitter(suspendedWorkflowGateEmitter);
|
|
@@ -27280,6 +28234,7 @@ export class AgentSession {
|
|
|
27280
28234
|
}
|
|
27281
28235
|
throw new AggregateError([error, rollbackErrors[0]], "Session switch rollback failed");
|
|
27282
28236
|
}
|
|
28237
|
+
ownerShutdownCanDrainResumes = true;
|
|
27283
28238
|
if (unavailableDefaultChainMessage) {
|
|
27284
28239
|
this.emitNotice(
|
|
27285
28240
|
"error",
|
|
@@ -27292,10 +28247,17 @@ export class AgentSession {
|
|
|
27292
28247
|
}
|
|
27293
28248
|
} finally {
|
|
27294
28249
|
if (ownerShutdownManager && ownerShutdownLease && !ownerShutdownFinalizationDeferred) {
|
|
27295
|
-
|
|
27296
|
-
ownerShutdownLease,
|
|
27297
|
-
|
|
27298
|
-
|
|
28250
|
+
if (ownerShutdownTransitionCommitted) {
|
|
28251
|
+
ownerShutdownManager.finishOwnerSubagentShutdown(ownerShutdownLease, "commit");
|
|
28252
|
+
} else {
|
|
28253
|
+
// A pre-mutation flush or snapshot failure can leave the predecessor
|
|
28254
|
+
// live after the Agent event bridge was disconnected. Reconnect before
|
|
28255
|
+
// releasing the lease, which may synchronously start queued resumes.
|
|
28256
|
+
if (!this.#isDisposed) this.#reconnectToAgent();
|
|
28257
|
+
ownerShutdownManager.finishOwnerSubagentShutdown(ownerShutdownLease, "release", {
|
|
28258
|
+
drainResumeQueue: ownerShutdownCanDrainResumes && !this.#isDisposed,
|
|
28259
|
+
});
|
|
28260
|
+
}
|
|
27299
28261
|
}
|
|
27300
28262
|
this.#endSessionTransition();
|
|
27301
28263
|
}
|
|
@@ -27526,7 +28488,7 @@ export class AgentSession {
|
|
|
27526
28488
|
customInstructions: options.customInstructions,
|
|
27527
28489
|
reserveTokens: branchSummarySettings.reserveTokens,
|
|
27528
28490
|
metadata: this.agent.metadataForProvider(model.provider),
|
|
27529
|
-
convertToLlm,
|
|
28491
|
+
convertToLlm: (messages) => this.#summaryConvertToLlm(messages),
|
|
27530
28492
|
telemetry: resolveTelemetry(this.agent.telemetry, this.sessionId),
|
|
27531
28493
|
});
|
|
27532
28494
|
this.#branchSummaryAbortController = undefined;
|