@gajae-code/coding-agent 0.15.5 → 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +85 -1
- package/dist/types/async/job-manager.d.ts +83 -0
- package/dist/types/cli/args.d.ts +2 -0
- package/dist/types/cli/auth-gateway-cli.d.ts +17 -0
- package/dist/types/cli/model-presets-cli.d.ts +9 -0
- package/dist/types/cli/root-flags.d.ts +14 -0
- package/dist/types/cli/update-cli.d.ts +15 -0
- package/dist/types/cli.d.ts +7 -0
- package/dist/types/commands/auth-gateway.d.ts +3 -0
- package/dist/types/commands/gc.d.ts +13 -0
- package/dist/types/commands/launch.d.ts +10 -0
- package/dist/types/commands/model-presets.d.ts +21 -0
- package/dist/types/config/autorouting-tier-map.d.ts +120 -0
- package/dist/types/config/file-lock.d.ts +54 -2
- package/dist/types/config/internal/model-preset-registry-test-state.d.ts +4 -0
- package/dist/types/config/internal/model-preset-registry-test-support.d.ts +3 -0
- package/dist/types/config/keybindings.d.ts +3 -2
- package/dist/types/config/model-preset-registry.d.ts +368 -0
- package/dist/types/config/model-profile-activation.d.ts +39 -0
- package/dist/types/config/model-profiles.d.ts +5 -3
- package/dist/types/config/model-registry.d.ts +17 -5
- package/dist/types/config/settings-schema.d.ts +19 -1
- package/dist/types/config/settings.d.ts +2 -0
- package/dist/types/coordinator-mcp/model-preset.d.ts +7 -5
- package/dist/types/coordinator-mcp/projection-scan.d.ts +54 -0
- package/dist/types/crash/record-loader.d.ts +15 -0
- package/dist/types/extensibility/extensions/types.d.ts +2 -5
- package/dist/types/gjc-runtime/empty-delete-gc.d.ts +58 -0
- package/dist/types/gjc-runtime/gc-runtime.d.ts +2 -0
- package/dist/types/gjc-runtime/launch-worktree-reservation.d.ts +21 -0
- package/dist/types/gjc-runtime/managed-owner-supervisor.d.ts +2 -0
- package/dist/types/gjc-runtime/session-state-lock.d.ts +35 -6
- package/dist/types/gjc-runtime/session-state-sidecar.d.ts +72 -1
- package/dist/types/gjc-runtime/tmux-owner-isolation.d.ts +1 -0
- package/dist/types/gjc-runtime/tmux-sessions.d.ts +27 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/main.d.ts +3 -1
- package/dist/types/master-mode/context.d.ts +21 -0
- package/dist/types/master-mode/first-request.d.ts +34 -0
- package/dist/types/master-mode/snapshot.d.ts +32 -0
- package/dist/types/modes/components/frictionless-onboarding-selector.d.ts +10 -0
- package/dist/types/modes/components/jobs-overlay-model.d.ts +4 -2
- package/dist/types/modes/components/oauth-selector.d.ts +1 -0
- package/dist/types/modes/components/status-line/model-name.d.ts +18 -0
- package/dist/types/modes/components/status-line/priority-row.d.ts +34 -0
- package/dist/types/modes/components/status-line/segments.d.ts +10 -0
- package/dist/types/modes/jobs-observer.d.ts +36 -0
- package/dist/types/modes/print-mode.d.ts +17 -0
- package/dist/types/modes/ui-language.d.ts +7 -1
- package/dist/types/sdk/broker/broker.d.ts +80 -1
- package/dist/types/sdk/broker/endpoint-authority.d.ts +24 -0
- package/dist/types/sdk/broker/index.d.ts +3 -0
- package/dist/types/sdk/broker/lifecycle.d.ts +26 -0
- package/dist/types/sdk/broker/master-capability.d.ts +10 -0
- package/dist/types/sdk/broker/session-index.d.ts +40 -11
- package/dist/types/sdk/broker/session-scope.d.ts +72 -0
- package/dist/types/sdk/broker/spawn-authority.d.ts +201 -0
- package/dist/types/sdk/broker/spawn-substrate.d.ts +30 -0
- package/dist/types/sdk/broker/state-version.d.ts +4 -1
- package/dist/types/sdk/broker/worktree-occupancy.d.ts +28 -0
- package/dist/types/sdk/bus/chat-daemon-control.d.ts +3 -0
- package/dist/types/sdk/bus/index.d.ts +6 -0
- package/dist/types/sdk/bus/kind-aware-reconciliation.d.ts +3 -0
- package/dist/types/sdk/bus/prompt-reconciliation.d.ts +4 -0
- package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +1 -1
- package/dist/types/sdk/cli/index.d.ts +1 -0
- package/dist/types/sdk/cli/master-cli.d.ts +63 -0
- package/dist/types/sdk/cli/rows.d.ts +4 -3
- package/dist/types/sdk/cli/session-cli.d.ts +18 -2
- package/dist/types/sdk/client/client.d.ts +3 -2
- package/dist/types/sdk/client/discovery.d.ts +1 -0
- package/dist/types/sdk/host/control/dispatch.d.ts +10 -0
- package/dist/types/sdk/host/control/index.d.ts +1 -1
- package/dist/types/sdk/host/control/operations.d.ts +2 -0
- package/dist/types/sdk/host/control/runtime-gate.d.ts +21 -7
- package/dist/types/sdk/host/host.d.ts +10 -0
- package/dist/types/sdk/host/model-pin.d.ts +7 -2
- package/dist/types/sdk/host/sdk-run-capability.d.ts +1 -0
- package/dist/types/sdk/host/session-runtime.d.ts +67 -1
- package/dist/types/sdk/lifecycle/broker-client.d.ts +6 -0
- package/dist/types/sdk/lifecycle/service.d.ts +15 -3
- package/dist/types/sdk/lifecycle-session.d.ts +2 -0
- package/dist/types/sdk/prompt-deadline-manager.d.ts +11 -1
- package/dist/types/sdk/protocol/adapter-validation.d.ts +2 -0
- package/dist/types/sdk/router/session-router.d.ts +11 -0
- package/dist/types/sdk/session.d.ts +16 -0
- package/dist/types/sdk/startup-capability.d.ts +2 -1
- package/dist/types/session/account-inventory.d.ts +1 -0
- package/dist/types/session/agent-session.d.ts +24 -7
- package/dist/types/session/credential-store-errors.d.ts +1 -0
- package/dist/types/session/fold-coordinator.d.ts +201 -0
- package/dist/types/session/sdk-run-capability-internal.d.ts +7 -0
- package/dist/types/session/sdk-run-capability.d.ts +2 -0
- package/dist/types/session/session-manager.d.ts +65 -0
- package/dist/types/session/yield-queue.d.ts +5 -4
- package/dist/types/setup/frictionless-onboarding.d.ts +1 -0
- package/dist/types/telemetry/control.d.ts +11 -0
- package/dist/types/telemetry/events.d.ts +20 -0
- package/dist/types/telemetry/index.d.ts +3 -0
- package/dist/types/telemetry/transport.d.ts +22 -0
- package/dist/types/tools/bash-interactive.d.ts +23 -0
- package/dist/types/tools/bash.d.ts +4 -0
- package/dist/types/tools/gh.d.ts +8 -0
- package/dist/types/tools/index.d.ts +10 -5
- package/dist/types/web/search/providers/base.d.ts +9 -4
- package/dist/types/web/search/providers/xai.d.ts +2 -1
- package/dist/types/web/search/types.d.ts +14 -0
- package/package.json +10 -7
- package/scripts/generate-hotkeys-docs.ts +4 -0
- package/scripts/generate-sdk-operation-inventory.ts +9 -3
- package/scripts/verify-gjc-sdk-canonicalization.ts +35 -1
- package/src/async/job-manager.ts +671 -39
- package/src/cli/accounts-cli.ts +9 -5
- package/src/cli/args.ts +17 -0
- package/src/cli/auth-broker-cli.ts +40 -12
- package/src/cli/auth-gateway-cli.ts +273 -97
- package/src/cli/config-cli.ts +2 -2
- package/src/cli/fast-help.ts +4 -0
- package/src/cli/model-presets-cli.ts +100 -0
- package/src/cli/root-flags.ts +5 -0
- package/src/cli/update-cli.ts +117 -16
- package/src/cli.ts +6 -2
- package/src/commands/auth-gateway.ts +12 -8
- package/src/commands/gc.ts +19 -0
- package/src/commands/launch.ts +86 -18
- package/src/commands/model-presets.ts +24 -0
- package/src/commands/sdk.ts +113 -21
- package/src/config/autorouting-tier-map.ts +44 -0
- package/src/config/file-lock-gc.ts +9 -4
- package/src/config/file-lock.ts +890 -99
- package/src/config/internal/model-preset-registry-test-state.ts +28 -0
- package/src/config/internal/model-preset-registry-test-support.ts +11 -0
- package/src/config/keybindings.ts +6 -2
- package/src/config/model-preset-registry.ts +2795 -0
- package/src/config/model-profile-activation.ts +115 -28
- package/src/config/model-profile-contract.ts +4 -1
- package/src/config/model-profiles.ts +56 -14
- package/src/config/model-registry.ts +1044 -122
- package/src/config/model-resolver.ts +5 -0
- package/src/config/settings-schema.ts +19 -1
- package/src/config/settings.ts +11 -1
- package/src/coordinator-mcp/model-preset.ts +32 -17
- package/src/coordinator-mcp/projection-scan.ts +592 -0
- package/src/coordinator-mcp/question-state.ts +9 -11
- package/src/coordinator-mcp/server.ts +111 -42
- package/src/coordinator-mcp/session-reaper.ts +9 -3
- package/src/crash/index-store.ts +2 -0
- package/src/crash/record-loader.ts +10 -0
- package/src/extensibility/extensions/loader.ts +0 -1
- package/src/extensibility/extensions/types.ts +2 -3
- package/src/extensibility/gjc-plugins/runtime-adapters.ts +1 -0
- package/src/gjc-runtime/empty-delete-gc.ts +301 -0
- package/src/gjc-runtime/gc-render.ts +29 -0
- package/src/gjc-runtime/gc-runtime.ts +174 -11
- package/src/gjc-runtime/launch-worktree-reservation.ts +242 -0
- package/src/gjc-runtime/managed-owner-supervisor.ts +63 -51
- package/src/gjc-runtime/session-state-lock.ts +1321 -147
- package/src/gjc-runtime/session-state-sidecar.ts +1004 -25
- package/src/gjc-runtime/tmux-owner-isolation-cli.ts +4 -7
- package/src/gjc-runtime/tmux-owner-isolation.ts +2 -2
- package/src/gjc-runtime/tmux-sessions.ts +252 -13
- package/src/hooks/skill-state.ts +6 -0
- package/src/index.ts +1 -0
- package/src/internal-urls/docs-index.generated.ts +14 -13
- package/src/internal-urls/issue-pr-protocol.ts +4 -2
- package/src/lsp/config.ts +4 -3
- package/src/lsp/path-trust.ts +12 -6
- package/src/main.ts +66 -4
- package/src/master-mode/context.ts +50 -0
- package/src/master-mode/first-request.ts +65 -0
- package/src/master-mode/snapshot.ts +205 -0
- package/src/modes/acp/acp-agent.ts +616 -161
- package/src/modes/components/footer.ts +4 -2
- package/src/modes/components/frictionless-onboarding-selector.ts +48 -2
- package/src/modes/components/jobs-overlay-model.ts +99 -16
- package/src/modes/components/model-selector.ts +158 -18
- package/src/modes/components/oauth-selector.ts +6 -1
- package/src/modes/components/settings-selector.ts +1 -0
- package/src/modes/components/status-line/model-name.ts +53 -0
- package/src/modes/components/status-line/priority-row.ts +107 -0
- package/src/modes/components/status-line/segments.ts +29 -10
- package/src/modes/components/tool-status-header.ts +151 -15
- package/src/modes/controllers/extension-ui-controller.ts +1 -1
- package/src/modes/controllers/input-controller.ts +51 -6
- package/src/modes/controllers/selector-controller.ts +191 -96
- package/src/modes/interactive-mode.ts +14 -1
- package/src/modes/jobs-observer.ts +149 -16
- package/src/modes/print-mode.ts +64 -0
- package/src/modes/runtime-init.ts +1 -1
- package/src/modes/ui-language.ts +49 -2
- package/src/modes/utils/injected-user-submission.ts +15 -1
- package/src/prompts/system/master-mode.md +28 -0
- package/src/prompts/tools/bash.md +2 -2
- package/src/prompts/tools/fold-receipt.md +3 -0
- package/src/prompts/tools/job.md +1 -1
- package/src/sdk/acp/adapter.ts +17 -2
- package/src/sdk/broker/broker.ts +1671 -34
- package/src/sdk/broker/endpoint-authority.ts +86 -0
- package/src/sdk/broker/ensure.ts +5 -0
- package/src/sdk/broker/index.ts +3 -0
- package/src/sdk/broker/lifecycle.ts +217 -46
- package/src/sdk/broker/master-capability.ts +232 -0
- package/src/sdk/broker/session-index.ts +121 -17
- package/src/sdk/broker/session-scope.ts +272 -0
- package/src/sdk/broker/spawn-authority.ts +1031 -0
- package/src/sdk/broker/spawn-substrate.ts +548 -0
- package/src/sdk/broker/state-version.ts +26 -14
- package/src/sdk/broker/transport.ts +1 -0
- package/src/sdk/broker/worktree-occupancy.ts +90 -0
- package/src/sdk/bus/chat-daemon-control.ts +5 -2
- package/src/sdk/bus/chat-daemon-runtime.ts +46 -5
- package/src/sdk/bus/index.ts +51 -4
- package/src/sdk/bus/kind-aware-reconciliation.ts +36 -6
- package/src/sdk/bus/prompt-reconciliation.ts +28 -4
- package/src/sdk/bus/slack-conversation.ts +5 -1
- package/src/sdk/bus/telegram-daemon-contract.ts +1 -1
- package/src/sdk/cli/index.ts +1 -0
- package/src/sdk/cli/master-cli.ts +259 -0
- package/src/sdk/cli/rows.ts +11 -8
- package/src/sdk/cli/session-cli.ts +276 -6
- package/src/sdk/client/client.ts +14 -8
- package/src/sdk/client/discovery.ts +3 -1
- package/src/sdk/host/control/dispatch.ts +101 -37
- package/src/sdk/host/control/index.ts +2 -0
- package/src/sdk/host/control/operations.ts +2 -0
- package/src/sdk/host/control/runtime-gate.ts +203 -11
- package/src/sdk/host/host.ts +27 -2
- package/src/sdk/host/model-pin.ts +185 -25
- package/src/sdk/host/sdk-run-capability.ts +1 -0
- package/src/sdk/host/session-runtime.test.ts +581 -43
- package/src/sdk/host/session-runtime.ts +655 -101
- package/src/sdk/lifecycle/broker-client.ts +28 -0
- package/src/sdk/lifecycle/service.ts +279 -20
- package/src/sdk/lifecycle-session.ts +10 -2
- package/src/sdk/prompt-deadline-manager.ts +16 -1
- package/src/sdk/protocol/adapter-validation.ts +27 -0
- package/src/sdk/protocol/operation-inventory.generated.json +53 -2
- package/src/sdk/protocol/operation-registry.ts +17 -4
- package/src/sdk/router/session-router.ts +294 -154
- package/src/sdk/session.ts +440 -167
- package/src/sdk/startup-capability.ts +1 -0
- package/src/session/account-inventory.ts +27 -11
- package/src/session/agent-session.ts +1292 -180
- package/src/session/blob-store.ts +15 -5
- package/src/session/credential-store-errors.ts +2 -0
- package/src/session/fold-coordinator.ts +433 -0
- package/src/session/internal/managed-session-storage.ts +153 -78
- package/src/session/sdk-run-capability-internal.ts +15 -0
- package/src/session/sdk-run-capability.ts +2 -0
- package/src/session/session-manager.ts +342 -28
- package/src/session/yield-queue.ts +24 -10
- package/src/setup/frictionless-onboarding.ts +2 -0
- package/src/setup/provider-presets.json +1 -1
- package/src/slash-commands/helpers/credential-switch.ts +6 -1
- package/src/task/executor.ts +19 -3
- package/src/telemetry/control.ts +26 -0
- package/src/telemetry/events.ts +1268 -0
- package/src/telemetry/index.ts +3 -0
- package/src/telemetry/transport.ts +87 -0
- package/src/tools/bash-interactive.ts +182 -85
- package/src/tools/bash.ts +1028 -109
- package/src/tools/gh.ts +104 -48
- package/src/tools/github-cache.ts +2 -2
- package/src/tools/index.ts +11 -5
- package/src/tools/tool-catalog.generated.ts +1 -1
- package/src/web/search/provider.ts +120 -11
- package/src/web/search/providers/anthropic.ts +89 -17
- package/src/web/search/providers/base.ts +9 -4
- package/src/web/search/providers/gemini.ts +87 -15
- package/src/web/search/providers/openai-compatible.ts +23 -8
- package/src/web/search/providers/xai.ts +98 -12
- package/src/web/search/types.ts +16 -0
package/src/sdk/session.ts
CHANGED
|
@@ -17,6 +17,8 @@ import {
|
|
|
17
17
|
type AuthCredentialSelector,
|
|
18
18
|
type CredentialDisabledEvent,
|
|
19
19
|
codexToolWireName,
|
|
20
|
+
DEFAULT_MODEL_PER_PROVIDER,
|
|
21
|
+
type KnownProvider,
|
|
20
22
|
type Message,
|
|
21
23
|
type Model,
|
|
22
24
|
type ProviderSessionState,
|
|
@@ -65,11 +67,14 @@ import {
|
|
|
65
67
|
resolveModelRoleValue,
|
|
66
68
|
type ScopedModelSelection,
|
|
67
69
|
} from "../config/model-resolver";
|
|
70
|
+
import { normalizeModelSelectorValue } from "../config/model-selector-value";
|
|
68
71
|
import { loadPromptTemplates as loadPromptTemplatesInternal, type PromptTemplate } from "../config/prompt-templates";
|
|
69
72
|
import { Settings, type SkillsSettings } from "../config/settings";
|
|
70
73
|
import { resolveEagerTaskDelegation } from "../config/task-delegation";
|
|
71
74
|
import { CursorExecHandlers } from "../cursor";
|
|
72
75
|
import { EditTool } from "../edit";
|
|
76
|
+
import type { MasterModeContext } from "../master-mode/context";
|
|
77
|
+
import { describeFoldReceipt } from "../session/fold-coordinator";
|
|
73
78
|
import type { BashRestrictionProfile } from "../tools/bash-allowed-prefixes";
|
|
74
79
|
import { SearchTool } from "../tools/search";
|
|
75
80
|
import "../discovery";
|
|
@@ -119,7 +124,9 @@ import { normalizePluginHook } from "../hooks/normalize";
|
|
|
119
124
|
import { initializeLocalRoot, LocalProtocolHandler, type LocalProtocolOptions } from "../internal-urls";
|
|
120
125
|
import type { LspStartupServerInfo } from "../lsp";
|
|
121
126
|
import { shutdownAll as shutdownAllLspClients } from "../lsp/client";
|
|
127
|
+
import { createMasterPeerSnapshotContributor, MASTER_PEER_SNAPSHOT_CUSTOM_TYPE } from "../master-mode/first-request";
|
|
122
128
|
import btwUserPrompt from "../prompts/system/btw-user.md" with { type: "text" };
|
|
129
|
+
import masterModeTemplate from "../prompts/system/master-mode.md" with { type: "text" };
|
|
123
130
|
import asyncResultTemplate from "../prompts/tools/async-result.md" with { type: "text" };
|
|
124
131
|
import { AgentRegistry, MAIN_AGENT_ID } from "../registry/agent-registry";
|
|
125
132
|
import { createLazyService } from "../runtime/lazy-service";
|
|
@@ -188,6 +195,7 @@ import { guardToolForUltragoalAsk } from "../tools/ultragoal-ask-guard";
|
|
|
188
195
|
import { EventBus } from "../utils/event-bus";
|
|
189
196
|
import { buildNamedToolChoice, buildNamedToolChoiceResult } from "../utils/tool-choice";
|
|
190
197
|
import type { WorkspaceTree } from "../workspace-tree";
|
|
198
|
+
import { createSessionLifecycleService } from "./lifecycle/client";
|
|
191
199
|
import {
|
|
192
200
|
attachLifecycleStartupCapability,
|
|
193
201
|
lifecycleMcpStartupTimeoutOption,
|
|
@@ -524,6 +532,8 @@ export interface CreateAgentSessionOptions {
|
|
|
524
532
|
* @internal CLI-only startup optimization; SDK callers retain synchronous loading by default.
|
|
525
533
|
*/
|
|
526
534
|
deferMcpConfigStartup?: boolean;
|
|
535
|
+
/** Process-local master authority. Never persist or pass to lifecycle children. */
|
|
536
|
+
masterModeContext?: MasterModeContext;
|
|
527
537
|
/**
|
|
528
538
|
* Defer memory backend startup until the caller has applied startup model profiles.
|
|
529
539
|
* @internal CLI-only ordering guard; SDK callers retain immediate startup by default.
|
|
@@ -1281,6 +1291,45 @@ function findDeferredExactMcpToolNameCollisions(
|
|
|
1281
1291
|
return [...collisions];
|
|
1282
1292
|
}
|
|
1283
1293
|
|
|
1294
|
+
/**
|
|
1295
|
+
* Order candidates so each known provider's curated default model is tried
|
|
1296
|
+
* before the rest of the catalog.
|
|
1297
|
+
*
|
|
1298
|
+
* Provider catalogs are not ranked by fitness — a withdrawn model whose ID
|
|
1299
|
+
* carries an older date suffix sorts ahead of its current replacement — so
|
|
1300
|
+
* picking the first credentialed entry can start an unconfigured install on a
|
|
1301
|
+
* model the provider no longer serves. `DEFAULT_MODEL_PER_PROVIDER` is the
|
|
1302
|
+
* curated table `findInitialModel` already sweeps for the same purpose;
|
|
1303
|
+
* reusing it keeps both unconfigured paths on one source of truth. Candidates
|
|
1304
|
+
* that are not a provider default keep their original relative order.
|
|
1305
|
+
*/
|
|
1306
|
+
export function orderByProviderDefaultFirst(
|
|
1307
|
+
candidates: readonly Model[],
|
|
1308
|
+
providerOrder: readonly string[] = Object.keys(DEFAULT_MODEL_PER_PROVIDER),
|
|
1309
|
+
): Model[] {
|
|
1310
|
+
const preferred: Model[] = [];
|
|
1311
|
+
const rest: Model[] = [];
|
|
1312
|
+
const preferredCandidates = new Set<Model>();
|
|
1313
|
+
const seenProviders = new Set<string>();
|
|
1314
|
+
for (const rawProvider of [...providerOrder, ...Object.keys(DEFAULT_MODEL_PER_PROVIDER)]) {
|
|
1315
|
+
const provider = rawProvider.trim().toLowerCase();
|
|
1316
|
+
if (!provider || seenProviders.has(provider) || !(provider in DEFAULT_MODEL_PER_PROVIDER)) continue;
|
|
1317
|
+
seenProviders.add(provider);
|
|
1318
|
+
const defaultId = DEFAULT_MODEL_PER_PROVIDER[provider as KnownProvider];
|
|
1319
|
+
for (const candidate of candidates) {
|
|
1320
|
+
if (candidate.provider.trim().toLowerCase() === provider && candidate.id === defaultId) {
|
|
1321
|
+
preferred.push(candidate);
|
|
1322
|
+
preferredCandidates.add(candidate);
|
|
1323
|
+
}
|
|
1324
|
+
}
|
|
1325
|
+
}
|
|
1326
|
+
if (preferred.length === 0) return [...candidates];
|
|
1327
|
+
for (const candidate of candidates) {
|
|
1328
|
+
if (!preferredCandidates.has(candidate)) rest.push(candidate);
|
|
1329
|
+
}
|
|
1330
|
+
return [...preferred, ...rest];
|
|
1331
|
+
}
|
|
1332
|
+
|
|
1284
1333
|
const AUTOMATION_TOOL_NAMES = new Set<AutomationToolName>(["browser", "computer"]);
|
|
1285
1334
|
|
|
1286
1335
|
function validateAutomationTools(options: CreateAgentSessionOptions): AutomationTools {
|
|
@@ -1306,6 +1355,7 @@ function validateAutomationTools(options: CreateAgentSessionOptions): Automation
|
|
|
1306
1355
|
|
|
1307
1356
|
export async function createAgentSession(options: CreateAgentSessionOptions = {}): Promise<CreateAgentSessionResult> {
|
|
1308
1357
|
const automationTools = validateAutomationTools(options);
|
|
1358
|
+
const masterModeContext = options.masterModeContext;
|
|
1309
1359
|
const lifecycleStartupCapability = (
|
|
1310
1360
|
options as CreateAgentSessionOptions & { [lifecycleStartupCapabilityOption]?: SdkStartupCapability }
|
|
1311
1361
|
)[lifecycleStartupCapabilityOption];
|
|
@@ -1345,14 +1395,19 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
1345
1395
|
const startupAuthConfig = hasInjectedAuth
|
|
1346
1396
|
? undefined
|
|
1347
1397
|
: (options.startupAuthConfig ?? (await resolveStartupAuthConfig(agentDir)));
|
|
1398
|
+
const ownsModelRegistry = options.modelRegistry === undefined;
|
|
1348
1399
|
const ownsAuthStorage = options.modelRegistry === undefined && options.authStorage === undefined;
|
|
1349
1400
|
const modelRegistry =
|
|
1350
1401
|
options.modelRegistry ??
|
|
1351
1402
|
new ModelRegistry(
|
|
1352
1403
|
options.authStorage ??
|
|
1353
1404
|
(await logger.time("discoverModels", () => discoverAuthStorage(agentDir, startupAuthConfig))),
|
|
1405
|
+
path.join(agentDir, "models.yml"),
|
|
1406
|
+
undefined,
|
|
1407
|
+
{ agentDir },
|
|
1354
1408
|
);
|
|
1355
1409
|
const authStorage = modelRegistry.authStorage;
|
|
1410
|
+
const authStorageOwner = modelRegistry.getAuthStorageOwner();
|
|
1356
1411
|
if (options.authStorage && options.authStorage !== authStorage) {
|
|
1357
1412
|
throw new Error(
|
|
1358
1413
|
"options.authStorage and options.modelRegistry.authStorage must be the same instance when both are provided",
|
|
@@ -1360,6 +1415,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
1360
1415
|
}
|
|
1361
1416
|
|
|
1362
1417
|
let agent: Agent;
|
|
1418
|
+
let sessionAgent: Agent | undefined;
|
|
1363
1419
|
let session!: AgentSession;
|
|
1364
1420
|
let sessionManager!: SessionManager;
|
|
1365
1421
|
let hasSession = false;
|
|
@@ -1390,7 +1446,8 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
1390
1446
|
let credentialScopeId: string | undefined;
|
|
1391
1447
|
let credentialScopeLeased = false;
|
|
1392
1448
|
let closeOwnedSettings: () => Promise<void> = async () => {};
|
|
1393
|
-
const closeOwnedAuthStorage = (): void => {
|
|
1449
|
+
const closeOwnedAuthStorage = async (): Promise<void> => {
|
|
1450
|
+
if (ownsModelRegistry) await modelRegistry.dispose();
|
|
1394
1451
|
if (!hasSession && credentialScopeLeased && credentialScopeId) {
|
|
1395
1452
|
authStorage.releaseCredentialScope(credentialScopeId);
|
|
1396
1453
|
credentialScopeLeased = false;
|
|
@@ -1422,10 +1479,11 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
1422
1479
|
}
|
|
1423
1480
|
});
|
|
1424
1481
|
const applyCredentialSelector = (scopeId: string, provider: string, selector: AuthCredentialSelector): void => {
|
|
1425
|
-
authStorage.setSessionCredentialSelector(scopeId, provider, selector);
|
|
1482
|
+
authStorage.setSessionCredentialSelector(scopeId, provider, selector, authStorageOwner);
|
|
1426
1483
|
};
|
|
1427
1484
|
const ownsScopedSettings = options.settings === undefined;
|
|
1428
1485
|
const settings = options.settings ?? (await logger.time("settings", Settings.loadForScope, { cwd, agentDir }));
|
|
1486
|
+
if (ownsModelRegistry) modelRegistry.setScopedSettings(settings);
|
|
1429
1487
|
const autoroutingInactive =
|
|
1430
1488
|
settings.get("task.autorouting.enabled") === true && !settings.getEffectiveAutorouting().active;
|
|
1431
1489
|
closeOwnedSettings = async (): Promise<void> => {
|
|
@@ -1695,10 +1753,15 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
1695
1753
|
) {
|
|
1696
1754
|
throw new Error("Durable numeric credential pin authority changed");
|
|
1697
1755
|
}
|
|
1698
|
-
authStorage.setSessionCredentialSelector(
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1756
|
+
authStorage.setSessionCredentialSelector(
|
|
1757
|
+
credentialSessionId,
|
|
1758
|
+
record.provider,
|
|
1759
|
+
{
|
|
1760
|
+
kind: pin.kind,
|
|
1761
|
+
value: pin.value,
|
|
1762
|
+
},
|
|
1763
|
+
authStorageOwner,
|
|
1764
|
+
);
|
|
1702
1765
|
staleDurableCredentialPins.delete(resolveOAuthStorageProvider(record.provider));
|
|
1703
1766
|
}
|
|
1704
1767
|
} catch {
|
|
@@ -1770,8 +1833,12 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
1770
1833
|
credentialSessionId,
|
|
1771
1834
|
}),
|
|
1772
1835
|
);
|
|
1836
|
+
const configuredDefaultPatterns = normalizeModelSelectorValue(settings.getModelRole("default"));
|
|
1773
1837
|
let model = options.model;
|
|
1774
1838
|
let modelFallbackMessage: string | undefined;
|
|
1839
|
+
if (!hasExplicitModel && configuredDefaultPatterns.length > 0 && !defaultRoleSpec.model) {
|
|
1840
|
+
modelFallbackMessage = `Model ${configuredDefaultPatterns.join(" -> ")} not found`;
|
|
1841
|
+
}
|
|
1775
1842
|
const resumeModelBehavior = settings.get("session.resumeModelBehavior");
|
|
1776
1843
|
const persistedDefaultChain = existingSession.configuredModelChains.default;
|
|
1777
1844
|
const defaultModelEntries =
|
|
@@ -2066,13 +2133,49 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
2066
2133
|
if (asyncJobManager!.isDeliverySuppressed(jobId, job?.generation)) return;
|
|
2067
2134
|
const deniedOwnedDelivery =
|
|
2068
2135
|
ownedCompletion !== undefined && !isOwnedCompletionEnvelopeAllowed(ownedCompletion);
|
|
2136
|
+
// Fold disposition is decided by the coordinator's durable slot state.
|
|
2137
|
+
// `parked` means the fold transaction has not finished capturing its
|
|
2138
|
+
// receipt yet; it replays this completion itself, so enqueuing here
|
|
2139
|
+
// would double-deliver.
|
|
2140
|
+
const foldDisposition = job
|
|
2141
|
+
? session.foldCoordinator.onDelivery(job, result)
|
|
2142
|
+
: ({ kind: "ordinary" } as const);
|
|
2143
|
+
if (foldDisposition.kind === "parked") {
|
|
2144
|
+
if (job) asyncJobManager?.retainParkedDelivery(job, result);
|
|
2145
|
+
return;
|
|
2146
|
+
}
|
|
2069
2147
|
const formattedResult = await formatAsyncResultForFollowUp(result, !deniedOwnedDelivery);
|
|
2148
|
+
if (
|
|
2149
|
+
foldDisposition.kind === "receipt" &&
|
|
2150
|
+
job &&
|
|
2151
|
+
!session.foldCoordinator.claimCompletionDelivery(job)
|
|
2152
|
+
)
|
|
2153
|
+
return;
|
|
2154
|
+
// A folded job's result must arrive with its receipt so the wake turn
|
|
2155
|
+
// completes the original task rather than merely reporting output.
|
|
2156
|
+
const deliveredResult =
|
|
2157
|
+
foldDisposition.kind === "receipt"
|
|
2158
|
+
? `${formattedResult}\n\n${describeFoldReceipt(foldDisposition.receipt)}`
|
|
2159
|
+
: formattedResult;
|
|
2160
|
+
// Exactly one transcript notice per completion: a retried delivery
|
|
2161
|
+
// reuses the same job object, so the claim guards against repeats.
|
|
2162
|
+
if (
|
|
2163
|
+
foldDisposition.kind === "receipt" &&
|
|
2164
|
+
job &&
|
|
2165
|
+
session.foldCoordinator.claimCompletionNotice(job)
|
|
2166
|
+
) {
|
|
2167
|
+
session.emitNotice(
|
|
2168
|
+
"info",
|
|
2169
|
+
`Folded job ${foldDisposition.receipt.jobId} (${foldDisposition.receipt.label}) finished.`,
|
|
2170
|
+
"fold",
|
|
2171
|
+
);
|
|
2172
|
+
}
|
|
2070
2173
|
|
|
2071
2174
|
const durationMs = job ? jobElapsedMs(job) : undefined;
|
|
2072
2175
|
session.yieldQueue.enqueue<AsyncResultEntry>("async-result", {
|
|
2073
2176
|
jobId,
|
|
2074
2177
|
generation: job?.generation ?? "",
|
|
2075
|
-
result:
|
|
2178
|
+
result: deliveredResult,
|
|
2076
2179
|
job,
|
|
2077
2180
|
durationMs,
|
|
2078
2181
|
...(ownedCompletion
|
|
@@ -2085,6 +2188,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
2085
2188
|
}
|
|
2086
2189
|
: {}),
|
|
2087
2190
|
});
|
|
2191
|
+
if (job) asyncJobManager?.retainDeliveryClaim(job);
|
|
2088
2192
|
},
|
|
2089
2193
|
})
|
|
2090
2194
|
: options.inheritedAsyncJobManager;
|
|
@@ -2346,174 +2450,240 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
2346
2450
|
);
|
|
2347
2451
|
}
|
|
2348
2452
|
const from = sessionManager.getCwd();
|
|
2349
|
-
const
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
canonicalTarget = await fs.realpath(resolvedPath);
|
|
2355
|
-
} catch {
|
|
2356
|
-
throw new Error(`Directory does not exist or is not a directory: ${resolvedPath}`);
|
|
2357
|
-
}
|
|
2358
|
-
if (!(await fs.stat(canonicalTarget)).isDirectory()) {
|
|
2359
|
-
throw new Error(`Directory does not exist or is not a directory: ${resolvedPath}`);
|
|
2360
|
-
}
|
|
2361
|
-
const relative = path.relative(canonicalFrom, canonicalTarget);
|
|
2362
|
-
if (relative === "") {
|
|
2363
|
-
throw new Error(
|
|
2364
|
-
`Target ${canonicalTarget} is the current session directory; nothing to move.`,
|
|
2365
|
-
);
|
|
2453
|
+
const sourceHandle = await SessionManager.openNoFollowDirectory(from);
|
|
2454
|
+
const sourceOpened = await sourceHandle.stat({ bigint: true });
|
|
2455
|
+
if (!sourceOpened.isDirectory()) {
|
|
2456
|
+
await sourceHandle.close().catch(() => {});
|
|
2457
|
+
throw new Error(`Current session directory is no longer a directory: ${from}`);
|
|
2366
2458
|
}
|
|
2367
|
-
if (relative === ".." || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative)) {
|
|
2368
|
-
throw new Error(
|
|
2369
|
-
`Refusing to rescope outside the current session directory: ${canonicalTarget} is not within ${canonicalFrom}. move_session only narrows the session scope; ask the user to restart or /move for a broader relocation.`,
|
|
2370
|
-
);
|
|
2371
|
-
}
|
|
2372
|
-
let targetHandle: nodeFs.promises.FileHandle | undefined;
|
|
2373
|
-
let expectedIdentity: { dev: bigint; ino: bigint };
|
|
2374
2459
|
try {
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
|
|
2460
|
+
const resolvedPath = path.resolve(from, target);
|
|
2461
|
+
let canonicalFrom: string;
|
|
2462
|
+
let canonicalTarget: string;
|
|
2463
|
+
try {
|
|
2464
|
+
canonicalFrom = await fs.realpath(from);
|
|
2465
|
+
canonicalTarget = await fs.realpath(resolvedPath);
|
|
2466
|
+
} catch (error) {
|
|
2467
|
+
// macOS `realpath(3)` reports EACCES on an unreadable
|
|
2468
|
+
// directory where Linux succeeds and fails later at the
|
|
2469
|
+
// `access` probe; keep both platforms on the access message.
|
|
2470
|
+
const code = (error as NodeJS.ErrnoException)?.code;
|
|
2471
|
+
if (code === "EACCES" || code === "EPERM") {
|
|
2472
|
+
throw new Error(
|
|
2473
|
+
`Directory identity or access unavailable: ${resolvedPath}${
|
|
2474
|
+
error instanceof Error ? ` (${error.message})` : ""
|
|
2475
|
+
}`,
|
|
2476
|
+
);
|
|
2477
|
+
}
|
|
2378
2478
|
throw new Error(`Directory does not exist or is not a directory: ${resolvedPath}`);
|
|
2379
2479
|
}
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
} catch (error) {
|
|
2383
|
-
await targetHandle?.close().catch(() => {});
|
|
2384
|
-
if (error instanceof Error && error.message.startsWith("Directory does not exist")) {
|
|
2385
|
-
throw error;
|
|
2480
|
+
if (!(await fs.stat(canonicalTarget)).isDirectory()) {
|
|
2481
|
+
throw new Error(`Directory does not exist or is not a directory: ${resolvedPath}`);
|
|
2386
2482
|
}
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2483
|
+
const relative = path.relative(canonicalFrom, canonicalTarget);
|
|
2484
|
+
if (relative === "") {
|
|
2485
|
+
throw new Error(
|
|
2486
|
+
`Target ${canonicalTarget} is the current session directory; nothing to move.`,
|
|
2487
|
+
);
|
|
2488
|
+
}
|
|
2489
|
+
if (
|
|
2490
|
+
relative === ".." ||
|
|
2491
|
+
relative.startsWith(`..${path.sep}`) ||
|
|
2492
|
+
path.isAbsolute(relative)
|
|
2493
|
+
) {
|
|
2494
|
+
throw new Error(
|
|
2495
|
+
`Refusing to rescope outside the current session directory: ${canonicalTarget} is not within ${canonicalFrom}. move_session only narrows the session scope; ask the user to restart or /move for a broader relocation.`,
|
|
2496
|
+
);
|
|
2497
|
+
}
|
|
2498
|
+
let targetHandle: nodeFs.promises.FileHandle | undefined;
|
|
2499
|
+
let expectedIdentity: { dev: bigint; ino: bigint };
|
|
2500
|
+
try {
|
|
2501
|
+
targetHandle = await SessionManager.openNoFollowDirectory(canonicalTarget);
|
|
2502
|
+
const opened = await targetHandle.stat({ bigint: true });
|
|
2503
|
+
if (!opened.isDirectory()) {
|
|
2504
|
+
throw new Error(`Directory does not exist or is not a directory: ${resolvedPath}`);
|
|
2408
2505
|
}
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2506
|
+
const revalidatedFrom = await fs.realpath(from);
|
|
2507
|
+
const revalidatedTarget = await fs.realpath(resolvedPath);
|
|
2508
|
+
const revalidatedRelative = path.relative(revalidatedFrom, revalidatedTarget);
|
|
2509
|
+
if (
|
|
2510
|
+
revalidatedFrom !== canonicalFrom ||
|
|
2511
|
+
revalidatedTarget !== canonicalTarget ||
|
|
2512
|
+
revalidatedRelative === "" ||
|
|
2513
|
+
revalidatedRelative === ".." ||
|
|
2514
|
+
revalidatedRelative.startsWith(`..${path.sep}`) ||
|
|
2515
|
+
path.isAbsolute(revalidatedRelative)
|
|
2516
|
+
) {
|
|
2517
|
+
throw new Error(
|
|
2518
|
+
`Directory identity changed during confinement validation: ${resolvedPath}`,
|
|
2413
2519
|
);
|
|
2414
|
-
clearPluginRootsAndCaches(restoreRegistry ? [restoreRegistry] : undefined);
|
|
2415
|
-
} catch (error) {
|
|
2416
|
-
restoreErrors.push(error instanceof Error ? error : new Error(String(error)));
|
|
2417
2520
|
}
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2521
|
+
const named = await fs.lstat(revalidatedTarget, { bigint: true });
|
|
2522
|
+
const namedSource = await fs.lstat(revalidatedFrom, { bigint: true });
|
|
2523
|
+
if (
|
|
2524
|
+
!namedSource.isDirectory() ||
|
|
2525
|
+
namedSource.isSymbolicLink() ||
|
|
2526
|
+
namedSource.dev !== sourceOpened.dev ||
|
|
2527
|
+
namedSource.ino !== sourceOpened.ino ||
|
|
2528
|
+
!named.isDirectory() ||
|
|
2529
|
+
named.isSymbolicLink() ||
|
|
2530
|
+
named.dev !== opened.dev ||
|
|
2531
|
+
named.ino !== opened.ino
|
|
2532
|
+
) {
|
|
2533
|
+
throw new Error(
|
|
2534
|
+
`Directory identity changed during confinement validation: ${resolvedPath}`,
|
|
2535
|
+
);
|
|
2536
|
+
}
|
|
2537
|
+
expectedIdentity = { dev: opened.dev, ino: opened.ino };
|
|
2538
|
+
await fs.access(canonicalTarget, nodeFs.constants.R_OK | nodeFs.constants.X_OK);
|
|
2421
2539
|
} catch (error) {
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
},
|
|
2540
|
+
await targetHandle?.close().catch(() => {});
|
|
2541
|
+
if (error instanceof Error && error.message.startsWith("Directory does not exist")) {
|
|
2542
|
+
throw error;
|
|
2543
|
+
}
|
|
2544
|
+
throw new Error(
|
|
2545
|
+
`Directory identity or access unavailable: ${canonicalTarget}${
|
|
2546
|
+
error instanceof Error ? ` (${error.message})` : ""
|
|
2547
|
+
}`,
|
|
2431
2548
|
);
|
|
2432
2549
|
}
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
//
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2550
|
+
// Process-cwd authority is an explicit claim, never inferred from
|
|
2551
|
+
// `process.cwd() === from`: sibling sessions launched at the same
|
|
2552
|
+
// root both satisfy that, so acting on it would chdir the process
|
|
2553
|
+
// and clear process-global caches underneath the sibling.
|
|
2554
|
+
const ownsProcessCwd = SessionManager.isProcessCwdOwner(sessionManager);
|
|
2555
|
+
const restoreLaunchRoot = async (failure: unknown): Promise<never> => {
|
|
2556
|
+
const restoreErrors: Error[] = [];
|
|
2557
|
+
if (ownsProcessCwd) {
|
|
2558
|
+
try {
|
|
2559
|
+
setProjectDir(canonicalFrom);
|
|
2560
|
+
if (path.resolve(process.cwd()) !== path.resolve(canonicalFrom)) {
|
|
2561
|
+
throw new Error("Process cwd did not restore to the launch root.");
|
|
2562
|
+
}
|
|
2563
|
+
await SessionManager.assertProcessCwdIdentity({
|
|
2564
|
+
dev: sourceOpened.dev,
|
|
2565
|
+
ino: sourceOpened.ino,
|
|
2566
|
+
});
|
|
2567
|
+
} catch (error) {
|
|
2568
|
+
restoreErrors.push(error instanceof Error ? error : new Error(String(error)));
|
|
2569
|
+
}
|
|
2570
|
+
try {
|
|
2571
|
+
resetCapabilities();
|
|
2572
|
+
const restoreRegistry = await resolveActiveProjectRegistryPath(
|
|
2573
|
+
canonicalFrom,
|
|
2574
|
+
).catch(() => undefined);
|
|
2575
|
+
clearPluginRootsAndCaches(restoreRegistry ? [restoreRegistry] : undefined);
|
|
2576
|
+
} catch (error) {
|
|
2577
|
+
restoreErrors.push(error instanceof Error ? error : new Error(String(error)));
|
|
2578
|
+
}
|
|
2579
|
+
}
|
|
2442
2580
|
try {
|
|
2443
|
-
|
|
2444
|
-
// landed on the pinned directory, so a path replaced after
|
|
2445
|
-
// the name checks cannot escape the validated descendant.
|
|
2446
|
-
await SessionManager.assertProcessCwdIdentity(expectedIdentity);
|
|
2581
|
+
await rebindCwdCapturingAuthority(canonicalFrom);
|
|
2447
2582
|
} catch (error) {
|
|
2448
|
-
|
|
2449
|
-
throw error;
|
|
2583
|
+
restoreErrors.push(error instanceof Error ? error : new Error(String(error)));
|
|
2450
2584
|
}
|
|
2451
|
-
|
|
2452
|
-
|
|
2585
|
+
if (restoreErrors.length > 0) {
|
|
2586
|
+
throw new AggregateError(
|
|
2587
|
+
restoreErrors,
|
|
2588
|
+
"Failed to restore launch-root rescope authority.",
|
|
2589
|
+
{
|
|
2590
|
+
cause: failure,
|
|
2591
|
+
},
|
|
2592
|
+
);
|
|
2593
|
+
}
|
|
2594
|
+
throw failure;
|
|
2595
|
+
};
|
|
2453
2596
|
try {
|
|
2597
|
+
// Every fallible step that the moved session depends on runs
|
|
2598
|
+
// BEFORE the session-file commit, so a failure here leaves the
|
|
2599
|
+
// session exactly where it was and the tool call is a clean
|
|
2600
|
+
// rejection rather than a half-moved session.
|
|
2454
2601
|
if (ownsProcessCwd) {
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2602
|
+
setProjectDir(canonicalTarget);
|
|
2603
|
+
try {
|
|
2604
|
+
// `setProjectDir` chdirs a NAME. Confirm the process actually
|
|
2605
|
+
// landed on the pinned directory, so a path replaced after
|
|
2606
|
+
// the name checks cannot escape the validated descendant.
|
|
2607
|
+
await SessionManager.assertProcessCwdIdentity(expectedIdentity);
|
|
2608
|
+
} catch (error) {
|
|
2609
|
+
setProjectDir(canonicalFrom);
|
|
2610
|
+
await SessionManager.assertProcessCwdIdentity({
|
|
2611
|
+
dev: sourceOpened.dev,
|
|
2612
|
+
ino: sourceOpened.ino,
|
|
2613
|
+
});
|
|
2614
|
+
throw error;
|
|
2615
|
+
}
|
|
2616
|
+
}
|
|
2617
|
+
let rescopeFailure: unknown;
|
|
2618
|
+
try {
|
|
2619
|
+
if (ownsProcessCwd) {
|
|
2620
|
+
resetCapabilities();
|
|
2621
|
+
const projectRegistry = await resolveActiveProjectRegistryPath(canonicalTarget);
|
|
2622
|
+
clearPluginRootsAndCaches(projectRegistry ? [projectRegistry] : undefined);
|
|
2623
|
+
}
|
|
2624
|
+
// Plugin/MCP/Python authority must be rebound successfully
|
|
2625
|
+
// before committing; swallowing a failure here is what leaves
|
|
2626
|
+
// a moved session holding launch-root tool authority.
|
|
2627
|
+
await rebindCwdCapturingAuthority(canonicalTarget);
|
|
2628
|
+
} catch (error) {
|
|
2629
|
+
rescopeFailure = error;
|
|
2630
|
+
}
|
|
2631
|
+
if (rescopeFailure !== undefined) {
|
|
2632
|
+
await restoreLaunchRoot(rescopeFailure);
|
|
2633
|
+
}
|
|
2634
|
+
try {
|
|
2635
|
+
await sessionManager.flush();
|
|
2636
|
+
// Commit last: `moveTo` re-validates the pinned identity through
|
|
2637
|
+
// the still-open handle at the state-changing boundary.
|
|
2638
|
+
await sessionManager.moveTo(canonicalTarget, {
|
|
2639
|
+
expectedIdentity,
|
|
2640
|
+
targetHandle,
|
|
2641
|
+
expectedSourceIdentity: { dev: sourceOpened.dev, ino: sourceOpened.ino },
|
|
2642
|
+
sourceHandle,
|
|
2643
|
+
});
|
|
2644
|
+
} catch (error) {
|
|
2645
|
+
const committedCwd = sessionManager.getCwd();
|
|
2646
|
+
const stayedAtLaunchRoot = path.resolve(committedCwd) === path.resolve(from);
|
|
2647
|
+
if (stayedAtLaunchRoot) await restoreLaunchRoot(error);
|
|
2648
|
+
// SessionManager can publish the durable move before a later metadata
|
|
2649
|
+
// write fails. Treat that state as committed rather than reporting a
|
|
2650
|
+
// rejection after the session has moved.
|
|
2651
|
+
moveConsumed = true;
|
|
2652
|
+
logger.warn("Session rescope committed before finalization failed", {
|
|
2653
|
+
error: safeErrorForLog(error),
|
|
2654
|
+
cwd: committedCwd,
|
|
2655
|
+
});
|
|
2458
2656
|
}
|
|
2459
|
-
// Plugin/MCP/Python authority must be rebound successfully
|
|
2460
|
-
// before committing; swallowing a failure here is what leaves
|
|
2461
|
-
// a moved session holding launch-root tool authority.
|
|
2462
|
-
await rebindCwdCapturingAuthority(canonicalTarget);
|
|
2463
|
-
} catch (error) {
|
|
2464
|
-
rescopeFailure = error;
|
|
2465
|
-
}
|
|
2466
|
-
if (rescopeFailure !== undefined) {
|
|
2467
|
-
await restoreLaunchRoot(rescopeFailure);
|
|
2468
|
-
}
|
|
2469
|
-
try {
|
|
2470
|
-
await sessionManager.flush();
|
|
2471
|
-
// Commit last: `moveTo` re-validates the pinned identity through
|
|
2472
|
-
// the still-open handle at the state-changing boundary.
|
|
2473
|
-
await sessionManager.moveTo(canonicalTarget, {
|
|
2474
|
-
expectedIdentity,
|
|
2475
|
-
targetHandle,
|
|
2476
|
-
});
|
|
2477
|
-
} catch (error) {
|
|
2478
|
-
const committedCwd = sessionManager.getCwd();
|
|
2479
|
-
const stayedAtLaunchRoot = path.resolve(committedCwd) === path.resolve(from);
|
|
2480
|
-
if (stayedAtLaunchRoot) await restoreLaunchRoot(error);
|
|
2481
|
-
// SessionManager can publish the durable move before a later metadata
|
|
2482
|
-
// write fails. Treat that state as committed rather than reporting a
|
|
2483
|
-
// rejection after the session has moved.
|
|
2484
2657
|
moveConsumed = true;
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
|
|
2658
|
+
if (ownsProcessCwd) {
|
|
2659
|
+
try {
|
|
2660
|
+
await shutdownAllLspClients();
|
|
2661
|
+
} catch (error) {
|
|
2662
|
+
logger.warn("Failed to reset launch-root LSP clients after session rescope", {
|
|
2663
|
+
error: safeErrorForLog(error),
|
|
2664
|
+
});
|
|
2665
|
+
}
|
|
2666
|
+
}
|
|
2667
|
+
// Cwd-derived read-only state the prompt and subagents consume.
|
|
2668
|
+
// Best-effort by design: the move is committed, and a failed
|
|
2669
|
+
// re-discovery must not present a committed move as a failure.
|
|
2670
|
+
await applyRescopedReadState(sessionManager.getCwd());
|
|
2492
2671
|
try {
|
|
2493
|
-
await
|
|
2672
|
+
await session?.refreshSshTool({ activateIfAvailable: true });
|
|
2494
2673
|
} catch (error) {
|
|
2495
|
-
|
|
2674
|
+
// Non-fatal: the session has moved; the SSH tool refreshes
|
|
2675
|
+
// on its next activation attempt.
|
|
2676
|
+
logger.warn("Committed session rescope could not refresh the SSH tool", {
|
|
2496
2677
|
error: safeErrorForLog(error),
|
|
2678
|
+
cwd: sessionManager.getCwd(),
|
|
2497
2679
|
});
|
|
2498
2680
|
}
|
|
2681
|
+
return { from, to: sessionManager.getCwd() };
|
|
2682
|
+
} finally {
|
|
2683
|
+
await targetHandle.close().catch(() => {});
|
|
2499
2684
|
}
|
|
2500
|
-
// Cwd-derived read-only state the prompt and subagents consume.
|
|
2501
|
-
// Best-effort by design: the move is committed, and a failed
|
|
2502
|
-
// re-discovery must not present a committed move as a failure.
|
|
2503
|
-
await applyRescopedReadState(sessionManager.getCwd());
|
|
2504
|
-
try {
|
|
2505
|
-
await session?.refreshSshTool({ activateIfAvailable: true });
|
|
2506
|
-
} catch (error) {
|
|
2507
|
-
// Non-fatal: the session has moved; the SSH tool refreshes
|
|
2508
|
-
// on its next activation attempt.
|
|
2509
|
-
logger.warn("Committed session rescope could not refresh the SSH tool", {
|
|
2510
|
-
error: safeErrorForLog(error),
|
|
2511
|
-
cwd: sessionManager.getCwd(),
|
|
2512
|
-
});
|
|
2513
|
-
}
|
|
2514
|
-
return { from, to: sessionManager.getCwd() };
|
|
2515
2685
|
} finally {
|
|
2516
|
-
await
|
|
2686
|
+
await sourceHandle.close().catch(() => {});
|
|
2517
2687
|
}
|
|
2518
2688
|
});
|
|
2519
2689
|
};
|
|
@@ -2540,6 +2710,11 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
2540
2710
|
// (review thread P1). For a top-level session this equals the
|
|
2541
2711
|
// session id.
|
|
2542
2712
|
getSessionId: () => AsyncJobManager.endpointIdOf(asyncJobManager) ?? asyncJobEndpointId,
|
|
2713
|
+
registerForegroundFoldParticipant: adapter =>
|
|
2714
|
+
session?.registerForegroundFoldParticipant(adapter) ?? (() => {}),
|
|
2715
|
+
hasForegroundBashBackgroundRequestHandler: () => session?.hasForegroundBashBackgroundRequestHandler() ?? false,
|
|
2716
|
+
requestForegroundBashBackground: () => Promise.resolve(session?.requestForegroundBashBackground() ?? false),
|
|
2717
|
+
|
|
2543
2718
|
getCredentialSessionId: () => session?.credentialSessionId ?? credentialSessionId,
|
|
2544
2719
|
getMcpManager: () => mcpManager ?? options.inheritedMcpManager,
|
|
2545
2720
|
isManagedSessionDestination: () => sessionManager.isManagedDestination(),
|
|
@@ -2568,6 +2743,8 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
2568
2743
|
getToolForExecution: name => session?.getToolForExecution(name),
|
|
2569
2744
|
agentRegistry,
|
|
2570
2745
|
getSessionSpawns: () => options.spawns ?? "*",
|
|
2746
|
+
getMasterBashCapability: () => masterModeContext?.getCapability(),
|
|
2747
|
+
getMasterOwnerSessionId: () => masterModeContext?.ownerSessionId,
|
|
2571
2748
|
getModelString: () => (hasExplicitModel && model ? formatModelString(model) : undefined),
|
|
2572
2749
|
getActiveModelString,
|
|
2573
2750
|
getPlanModeState: () => session?.getPlanModeState(),
|
|
@@ -3116,6 +3293,13 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
3116
3293
|
controller: notificationSessionController,
|
|
3117
3294
|
spawnedByGjc,
|
|
3118
3295
|
sdkHostModeSupported: options.sdkHostModeSupported,
|
|
3296
|
+
...(masterModeContext
|
|
3297
|
+
? {
|
|
3298
|
+
masterCapability: masterModeContext.getCapability(),
|
|
3299
|
+
masterAttestationEpoch: masterModeContext.attestationEpoch,
|
|
3300
|
+
masterOwnerSessionId: masterModeContext.ownerSessionId,
|
|
3301
|
+
}
|
|
3302
|
+
: {}),
|
|
3119
3303
|
// INTERNAL terminal-abort seams, threaded directly from the
|
|
3120
3304
|
// owning session (NOT on the public extension context).
|
|
3121
3305
|
terminalAbortSeams: {
|
|
@@ -3163,9 +3347,19 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
3163
3347
|
createSdkSessionRuntimeExtension(api, {
|
|
3164
3348
|
agentDir,
|
|
3165
3349
|
brokerRegistrationRequired: lifecycleStartupCapability !== undefined,
|
|
3350
|
+
...(lifecycleStartupCapability?.lifecycleRequestId
|
|
3351
|
+
? { lifecycleRequestId: lifecycleStartupCapability.lifecycleRequestId }
|
|
3352
|
+
: {}),
|
|
3166
3353
|
createTransport: input => createSdkWebSocketTransport(input),
|
|
3167
3354
|
settings,
|
|
3168
3355
|
configOverrides: new Map(),
|
|
3356
|
+
...(masterModeContext
|
|
3357
|
+
? {
|
|
3358
|
+
masterCapability: masterModeContext.getCapability(),
|
|
3359
|
+
masterAttestationEpoch: masterModeContext.attestationEpoch,
|
|
3360
|
+
masterOwnerSessionId: masterModeContext.ownerSessionId,
|
|
3361
|
+
}
|
|
3362
|
+
: {}),
|
|
3169
3363
|
// INTERNAL terminal-abort seams, threaded directly from the
|
|
3170
3364
|
// owning session (NOT on the public extension context).
|
|
3171
3365
|
terminalAbortSeams: {
|
|
@@ -3364,11 +3558,38 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
3364
3558
|
// Fall back to first available model with a valid API key, honoring the
|
|
3365
3559
|
// path-scoped `enabledModels` allow-list when configured. Skip when the
|
|
3366
3560
|
// user explicitly requested a model via --model that wasn't found.
|
|
3367
|
-
if (
|
|
3561
|
+
if (
|
|
3562
|
+
!model &&
|
|
3563
|
+
!options.modelPattern &&
|
|
3564
|
+
!startupCredentialModelRejected &&
|
|
3565
|
+
configuredDefaultPatterns.length === 0
|
|
3566
|
+
) {
|
|
3368
3567
|
// Re-resolve the allowed set: extension factories above may have
|
|
3369
3568
|
// registered providers/models that weren't visible at startup.
|
|
3370
|
-
const
|
|
3371
|
-
|
|
3569
|
+
const allowedFallbackCandidates = await resolveAllowedModels(modelRegistry, settings, modelMatchPreferences);
|
|
3570
|
+
// A fresh provider discovery can disprove a bundled model while the
|
|
3571
|
+
// general available catalog retains it for offline/profile compatibility.
|
|
3572
|
+
// Exclude only those positively disproved bundled entries from the
|
|
3573
|
+
// unconfigured startup path; explicit model/profile resolution above keeps
|
|
3574
|
+
// its existing precedence and semantics.
|
|
3575
|
+
const profileAvailableKeys = new Set(
|
|
3576
|
+
modelRegistry
|
|
3577
|
+
.getAvailableForProfileActivation()
|
|
3578
|
+
.map(candidate => `${candidate.provider}\u0000${candidate.id}`),
|
|
3579
|
+
);
|
|
3580
|
+
const fallbackCandidates = allowedFallbackCandidates.filter(candidate =>
|
|
3581
|
+
profileAvailableKeys.has(`${candidate.provider}\u0000${candidate.id}`),
|
|
3582
|
+
);
|
|
3583
|
+
// Candidate order is not a quality signal: catalogs sort retired models
|
|
3584
|
+
// ahead of current ones whenever their IDs carry older date suffixes, so
|
|
3585
|
+
// an unconfigured install would otherwise start on a model its provider
|
|
3586
|
+
// has already withdrawn. Sweep each known provider's curated default
|
|
3587
|
+
// first — the same table `findInitialModel` consults — and only then fall
|
|
3588
|
+
// back to catalog order.
|
|
3589
|
+
for (const candidate of orderByProviderDefaultFirst(
|
|
3590
|
+
fallbackCandidates,
|
|
3591
|
+
modelRegistry.automaticProviderOrder(credentialSessionId),
|
|
3592
|
+
)) {
|
|
3372
3593
|
if (await hasModelApiKey(candidate)) {
|
|
3373
3594
|
model = candidate;
|
|
3374
3595
|
break;
|
|
@@ -3720,16 +3941,20 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
3720
3941
|
if (hasSession) session.configWarnings.push(warning);
|
|
3721
3942
|
logger.warn("Context file discovery warning", { warning });
|
|
3722
3943
|
}
|
|
3944
|
+
// The master doctrine block is appended exactly once, AFTER any custom
|
|
3945
|
+
// system-prompt transformation, and only for the master session itself.
|
|
3946
|
+
const withMasterGuidance = (built: BuildSystemPromptResult): BuildSystemPromptResult =>
|
|
3947
|
+
masterModeContext ? { ...built, systemPrompt: [...built.systemPrompt, masterModeTemplate.trim()] } : built;
|
|
3723
3948
|
if (options.systemPrompt === undefined) {
|
|
3724
|
-
return defaultPrompt;
|
|
3949
|
+
return withMasterGuidance(defaultPrompt);
|
|
3725
3950
|
}
|
|
3726
3951
|
if (Array.isArray(options.systemPrompt)) {
|
|
3727
|
-
return { systemPrompt: options.systemPrompt, warnings: defaultPrompt.warnings };
|
|
3952
|
+
return withMasterGuidance({ systemPrompt: options.systemPrompt, warnings: defaultPrompt.warnings });
|
|
3728
3953
|
}
|
|
3729
|
-
return {
|
|
3954
|
+
return withMasterGuidance({
|
|
3730
3955
|
systemPrompt: options.systemPrompt(defaultPrompt.systemPrompt),
|
|
3731
3956
|
warnings: defaultPrompt.warnings,
|
|
3732
|
-
};
|
|
3957
|
+
});
|
|
3733
3958
|
};
|
|
3734
3959
|
|
|
3735
3960
|
const toolNamesFromRegistry = Array.from(toolRegistry.keys());
|
|
@@ -4073,31 +4298,43 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
4073
4298
|
preferWebsockets: preferOpenAICodexWebsockets,
|
|
4074
4299
|
getToolContext: tc => toolContextStore.getContext(tc),
|
|
4075
4300
|
getApiKey: async provider => {
|
|
4076
|
-
//
|
|
4077
|
-
//
|
|
4078
|
-
|
|
4079
|
-
|
|
4301
|
+
// AgentLoop asks by provider, but the active model carries the
|
|
4302
|
+
// model-scoped credential selector. Read it at call time so model
|
|
4303
|
+
// changes are honored after /new, fork, resume, or branch switches.
|
|
4304
|
+
const liveModel = sessionAgent?.state.model ?? model;
|
|
4305
|
+
const key =
|
|
4306
|
+
liveModel?.provider === provider
|
|
4307
|
+
? await modelRegistry.getApiKey(liveModel, credentialSessionId)
|
|
4308
|
+
: undefined;
|
|
4309
|
+
const providerKey = key ?? (await modelRegistry.getApiKeyForProvider(provider, credentialSessionId));
|
|
4310
|
+
if (!providerKey) {
|
|
4080
4311
|
throw new Error(`No API key found for provider "${provider}"`);
|
|
4081
4312
|
}
|
|
4082
|
-
return
|
|
4313
|
+
return providerKey;
|
|
4083
4314
|
},
|
|
4084
4315
|
getAuthCredentialType: provider => modelRegistry.getSessionCredentialType(provider, credentialSessionId),
|
|
4085
4316
|
streamFn: async (streamModel, context, streamOptions) => {
|
|
4086
4317
|
const requestStartedAt = performance.now();
|
|
4087
4318
|
let stream: Awaited<ReturnType<typeof streamSimple>>;
|
|
4088
4319
|
try {
|
|
4320
|
+
const effectiveApiKey = await modelRegistry.getApiKey(streamModel, credentialSessionId);
|
|
4089
4321
|
stream = await streamSimple(streamModel, context, {
|
|
4090
4322
|
...streamOptions,
|
|
4323
|
+
apiKey: effectiveApiKey,
|
|
4324
|
+
authCredentialType: modelRegistry.getSessionCredentialType(streamModel.provider, credentialSessionId),
|
|
4091
4325
|
onAuthError: async (provider, oldKey, error) => {
|
|
4092
4326
|
await modelRegistry.authStorage.invalidateCredentialMatching(provider, oldKey, {
|
|
4093
4327
|
signal: streamOptions?.signal,
|
|
4094
4328
|
sessionId: credentialSessionId,
|
|
4329
|
+
owner: modelRegistry.getAuthStorageOwner(),
|
|
4095
4330
|
});
|
|
4096
4331
|
logger.debug("Retrying provider request after credential invalidation", {
|
|
4097
4332
|
provider,
|
|
4098
4333
|
error: error instanceof Error ? error.message : String(error),
|
|
4099
4334
|
});
|
|
4100
|
-
return modelRegistry.
|
|
4335
|
+
return modelRegistry.getApiKey(streamModel, credentialSessionId, {
|
|
4336
|
+
signal: streamOptions?.signal,
|
|
4337
|
+
});
|
|
4101
4338
|
},
|
|
4102
4339
|
});
|
|
4103
4340
|
} catch (error) {
|
|
@@ -4160,6 +4397,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
4160
4397
|
telemetry: options.telemetry,
|
|
4161
4398
|
appendOnlyContext,
|
|
4162
4399
|
});
|
|
4400
|
+
sessionAgent = agent;
|
|
4163
4401
|
|
|
4164
4402
|
cursorEventEmitter = event => agent.emitExternalEvent(event);
|
|
4165
4403
|
|
|
@@ -4282,10 +4520,39 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
4282
4520
|
// carried by the host replay ring through the internal runtime seam above.
|
|
4283
4521
|
if (autoroutingInactive) session.configWarnings.push(AUTOROUTING_INACTIVE_WARNING);
|
|
4284
4522
|
hasSession = true;
|
|
4523
|
+
if (masterModeContext) {
|
|
4524
|
+
// One scoped, no-probe peer snapshot immediately before the FIRST accepted
|
|
4525
|
+
// provider request; see createMasterPeerSnapshotContributor for the
|
|
4526
|
+
// discard/reuse/no-duplicate semantics.
|
|
4527
|
+
const capturedSession = session;
|
|
4528
|
+
session.registerBeforeAgentStartContributor(
|
|
4529
|
+
createMasterPeerSnapshotContributor({
|
|
4530
|
+
lifecycle: createSessionLifecycleService(agentDir),
|
|
4531
|
+
ownerSessionId: masterModeContext.ownerSessionId,
|
|
4532
|
+
getSessionId: () => sessionManager.getSessionId(),
|
|
4533
|
+
scope: masterModeContext.scope,
|
|
4534
|
+
getCwd: getLiveCwd,
|
|
4535
|
+
hasPersistedInjection: () =>
|
|
4536
|
+
capturedSession.sessionManager
|
|
4537
|
+
.getBranch()
|
|
4538
|
+
.some(
|
|
4539
|
+
entry =>
|
|
4540
|
+
entry.type === "message" &&
|
|
4541
|
+
(entry.message as { customType?: string }).customType === MASTER_PEER_SNAPSHOT_CUSTOM_TYPE,
|
|
4542
|
+
),
|
|
4543
|
+
onError: error =>
|
|
4544
|
+
logger.debug("master peer snapshot injection failed", { error: safeErrorForLog(error) }),
|
|
4545
|
+
}),
|
|
4546
|
+
);
|
|
4547
|
+
}
|
|
4285
4548
|
const sessionAsyncJobManager = asyncJobManager;
|
|
4286
4549
|
if (sessionAsyncJobManager) {
|
|
4287
4550
|
session.yieldQueue.register<AsyncResultEntry>("async-result", {
|
|
4288
|
-
isStale: entry =>
|
|
4551
|
+
isStale: entry => {
|
|
4552
|
+
const stale = sessionAsyncJobManager.isDeliverySuppressed(entry.jobId, entry.generation);
|
|
4553
|
+
if (stale) sessionAsyncJobManager.releaseDeliveryClaim(entry.generation);
|
|
4554
|
+
return stale;
|
|
4555
|
+
},
|
|
4289
4556
|
// Build one message per ownership origin so an owned-scope drop of
|
|
4290
4557
|
// one turn's message never suppresses other turns'/ordinary
|
|
4291
4558
|
// completions batched in the same flush (review thread P2).
|
|
@@ -4293,7 +4560,13 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
4293
4560
|
entry.ownedCompletion
|
|
4294
4561
|
? `${entry.ownedCompletion.lineageIdHash}\u0000${entry.ownedCompletion.promptAttemptEpoch}`
|
|
4295
4562
|
: "ordinary",
|
|
4296
|
-
build:
|
|
4563
|
+
build: entries => {
|
|
4564
|
+
try {
|
|
4565
|
+
return buildAsyncResultBatchMessage(entries);
|
|
4566
|
+
} finally {
|
|
4567
|
+
for (const entry of entries) sessionAsyncJobManager.releaseDeliveryClaim(entry.generation);
|
|
4568
|
+
}
|
|
4569
|
+
},
|
|
4297
4570
|
});
|
|
4298
4571
|
}
|
|
4299
4572
|
session.yieldQueue.register<McpNotificationEntry>("mcp-notification", {
|
|
@@ -4329,7 +4602,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
4329
4602
|
);
|
|
4330
4603
|
AsyncJobManager.unregisterManager(asyncJobManager);
|
|
4331
4604
|
}
|
|
4332
|
-
closeOwnedAuthStorage();
|
|
4605
|
+
await closeOwnedAuthStorage();
|
|
4333
4606
|
await closeOwnedSettings();
|
|
4334
4607
|
}
|
|
4335
4608
|
}
|
|
@@ -4599,7 +4872,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
4599
4872
|
}
|
|
4600
4873
|
releaseLocalProtocolOverride();
|
|
4601
4874
|
try {
|
|
4602
|
-
closeOwnedAuthStorage();
|
|
4875
|
+
await closeOwnedAuthStorage();
|
|
4603
4876
|
} catch (authCleanupError) {
|
|
4604
4877
|
logger.warn("Failed to close owned auth storage after startup error", { error: authCleanupError });
|
|
4605
4878
|
}
|