@gajae-code/coding-agent 0.15.6 → 0.16.1
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 +99 -0
- package/dist/types/async/job-manager.d.ts +32 -1
- package/dist/types/browser-backend.d.ts +12 -0
- package/dist/types/cli/args.d.ts +2 -0
- package/dist/types/cli/root-flags.d.ts +14 -0
- package/dist/types/cli/update-cli.d.ts +14 -0
- package/dist/types/cli.d.ts +7 -0
- package/dist/types/commands/launch.d.ts +10 -0
- package/dist/types/commands/sdk.d.ts +2 -0
- package/dist/types/config/autorouting-tier-map.d.ts +24 -0
- package/dist/types/config/billing-path.d.ts +25 -0
- package/dist/types/config/config-schema-version.d.ts +13 -1
- package/dist/types/config/file-lock.d.ts +5 -0
- package/dist/types/config/model-registry.d.ts +22 -0
- package/dist/types/config/settings-schema.d.ts +27 -8
- package/dist/types/config/settings.d.ts +2 -0
- package/dist/types/coordinator-mcp/projection-scan.d.ts +8 -0
- package/dist/types/crash/record-loader.d.ts +15 -0
- package/dist/types/extensibility/extensions/types.d.ts +8 -3
- package/dist/types/gjc-runtime/launch-worktree-reservation.d.ts +21 -0
- package/dist/types/gjc-runtime/launch-worktree.d.ts +25 -1
- package/dist/types/gjc-runtime/managed-owner-supervisor.d.ts +2 -0
- package/dist/types/gjc-runtime/session-state-lock.d.ts +22 -0
- package/dist/types/gjc-runtime/session-state-sidecar.d.ts +66 -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/main.d.ts +1 -0
- 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/acp/acp-agent.d.ts +21 -21
- package/dist/types/modes/components/frictionless-onboarding-selector.d.ts +10 -0
- package/dist/types/modes/components/model-selector.d.ts +4 -0
- package/dist/types/modes/components/oauth-selector.d.ts +1 -0
- package/dist/types/modes/components/session-selector.d.ts +1 -1
- package/dist/types/modes/components/sessions-dashboard.d.ts +2 -1
- 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/interactive-mode.d.ts +16 -2
- package/dist/types/modes/print-mode.d.ts +17 -0
- package/dist/types/modes/theme/theme.d.ts +2 -1
- package/dist/types/modes/types.d.ts +2 -0
- package/dist/types/modes/ui-language.d.ts +7 -1
- package/dist/types/sdk/broker/broker.d.ts +79 -2
- package/dist/types/sdk/broker/endpoint-authority.d.ts +24 -0
- package/dist/types/sdk/broker/ensure.d.ts +3 -0
- package/dist/types/sdk/broker/index.d.ts +3 -0
- package/dist/types/sdk/broker/lifecycle-ledger.d.ts +6 -0
- package/dist/types/sdk/broker/lifecycle.d.ts +40 -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/startup-budget.d.ts +52 -0
- package/dist/types/sdk/broker/state-version.d.ts +21 -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/discord-daemon.d.ts +1 -1
- package/dist/types/sdk/bus/index.d.ts +9 -1
- package/dist/types/sdk/bus/kind-aware-reconciliation.d.ts +1 -1
- package/dist/types/sdk/bus/prompt-reconciliation.d.ts +1 -1
- package/dist/types/sdk/bus/slack-conversation.d.ts +1 -1
- package/dist/types/sdk/bus/slack-daemon.d.ts +1 -1
- 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 +12 -5
- package/dist/types/sdk/cli/session-cli.d.ts +20 -3
- package/dist/types/sdk/client/client.d.ts +3 -2
- 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/events.d.ts +1 -0
- package/dist/types/sdk/host/host.d.ts +10 -0
- package/dist/types/sdk/host/query/cursor.d.ts +4 -2
- package/dist/types/sdk/host/query/handlers.d.ts +1 -0
- package/dist/types/sdk/host/query/revision-store.d.ts +1 -0
- package/dist/types/sdk/host/session-runtime.d.ts +70 -17
- 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 +3 -1
- package/dist/types/sdk/prompt-deadline-manager.d.ts +2 -5
- package/dist/types/sdk/receipt-state.d.ts +2 -0
- package/dist/types/sdk/router/session-router.d.ts +6 -0
- package/dist/types/sdk/session.d.ts +16 -0
- package/dist/types/sdk/startup-capability.d.ts +2 -1
- package/dist/types/sdk/turn-result.d.ts +6 -0
- package/dist/types/session/agent-session.d.ts +52 -7
- package/dist/types/session/credential-store-errors.d.ts +1 -0
- package/dist/types/session/fold-coordinator.d.ts +29 -2
- package/dist/types/session/session-manager.d.ts +79 -2
- package/dist/types/session/yield-queue.d.ts +5 -4
- package/dist/types/setup/frictionless-onboarding.d.ts +1 -0
- package/dist/types/tools/ask.d.ts +1 -0
- package/dist/types/tools/bash.d.ts +16 -0
- package/dist/types/tools/browser/launch.d.ts +10 -0
- package/dist/types/tools/browser/registry.d.ts +8 -0
- package/dist/types/tools/gh.d.ts +8 -0
- package/dist/types/tools/index.d.ts +10 -10
- package/dist/types/tools/job.d.ts +3 -1
- package/dist/types/utils/external-editor.d.ts +7 -0
- package/examples/extensions/README.md +1 -1
- package/examples/extensions/pirate.ts +7 -4
- package/package.json +7 -7
- package/scripts/generate-sdk-operation-inventory.ts +13 -1
- package/scripts/verify-gjc-sdk-canonicalization.ts +35 -1
- package/src/async/job-manager.ts +107 -9
- package/src/browser-backend.ts +29 -0
- package/src/cli/accounts-cli.ts +9 -5
- package/src/cli/args.ts +17 -0
- package/src/cli/fast-help.ts +1 -0
- package/src/cli/plugin-cli.ts +29 -0
- package/src/cli/root-flags.ts +5 -0
- package/src/cli/update-cli.ts +140 -23
- package/src/commands/launch.ts +86 -18
- package/src/commands/sdk.ts +135 -28
- package/src/config/autorouting-tier-map.ts +12 -0
- package/src/config/billing-path.ts +42 -0
- package/src/config/config-schema-version.ts +18 -1
- package/src/config/file-lock.ts +158 -83
- package/src/config/model-preset-registry.ts +178 -23
- package/src/config/model-profiles.ts +5 -5
- package/src/config/model-registry.ts +291 -42
- package/src/config/model-resolver.ts +5 -0
- package/src/config/settings-schema.ts +27 -8
- package/src/config/settings.ts +51 -15
- package/src/coordinator-mcp/projection-scan.ts +27 -6
- package/src/coordinator-mcp/question-state.ts +9 -11
- package/src/coordinator-mcp/server.ts +115 -21
- package/src/crash/index-store.ts +2 -0
- package/src/crash/record-loader.ts +10 -0
- package/src/defaults/gjc/skills/deep-interview/SKILL.md +1 -1
- package/src/extensibility/extensions/runner.ts +27 -0
- package/src/extensibility/extensions/types.ts +8 -3
- package/src/extensibility/gjc-plugins/installer.ts +51 -11
- package/src/extensibility/runtime-skill-discovery.ts +110 -3
- package/src/extensibility/skills.ts +22 -12
- package/src/gjc-runtime/launch-worktree-reservation.ts +242 -0
- package/src/gjc-runtime/launch-worktree.ts +285 -4
- package/src/gjc-runtime/managed-owner-supervisor.ts +63 -51
- package/src/gjc-runtime/session-state-lock.ts +213 -14
- package/src/gjc-runtime/session-state-sidecar.ts +938 -39
- package/src/gjc-runtime/tmux-owner-isolation-cli.ts +4 -7
- package/src/gjc-runtime/tmux-owner-isolation.ts +18 -3
- package/src/gjc-runtime/tmux-sessions.ts +252 -13
- package/src/hooks/skill-state.ts +6 -0
- package/src/internal-urls/docs-index.generated.ts +15 -15
- package/src/internal-urls/issue-pr-protocol.ts +4 -2
- package/src/main.ts +61 -3
- 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 +610 -161
- package/src/modes/components/footer.ts +4 -2
- package/src/modes/components/frictionless-onboarding-selector.ts +48 -2
- package/src/modes/components/model-selector.ts +76 -8
- package/src/modes/components/oauth-selector.ts +6 -1
- package/src/modes/components/session-selector.ts +23 -15
- package/src/modes/components/sessions-dashboard.ts +8 -5
- 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 +23 -9
- package/src/modes/components/tool-status-header.ts +151 -15
- package/src/modes/controllers/command-controller.ts +1 -1
- package/src/modes/controllers/event-controller.ts +30 -4
- package/src/modes/controllers/extension-ui-controller.ts +13 -13
- package/src/modes/controllers/input-controller.ts +30 -2
- package/src/modes/controllers/selector-controller.ts +200 -98
- package/src/modes/interactive-mode.ts +36 -12
- package/src/modes/print-mode.ts +64 -0
- package/src/modes/prompt-action-autocomplete.ts +30 -4
- package/src/modes/runtime-init.ts +12 -9
- package/src/modes/theme/theme.ts +5 -0
- package/src/modes/types.ts +2 -0
- package/src/modes/ui-language.ts +49 -2
- package/src/modes/utils/ui-helpers.ts +22 -3
- package/src/prompts/agent-fragments/browser-aside.md +22 -0
- package/src/prompts/system/master-mode.md +28 -0
- package/src/prompts/tools/ask.md +1 -1
- package/src/prompts/tools/bash.md +1 -0
- package/src/prompts/tools/job.md +1 -1
- package/src/sdk/acp/adapter.ts +5 -0
- package/src/sdk/broker/broker.ts +1646 -27
- package/src/sdk/broker/endpoint-authority.ts +86 -0
- package/src/sdk/broker/ensure.ts +81 -3
- package/src/sdk/broker/index.ts +3 -0
- package/src/sdk/broker/lifecycle-ledger.ts +6 -0
- package/src/sdk/broker/lifecycle.ts +440 -75
- 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 +591 -0
- package/src/sdk/broker/startup-budget.ts +144 -2
- package/src/sdk/broker/state-version.ts +43 -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 +136 -18
- package/src/sdk/bus/discord-daemon.ts +50 -10
- package/src/sdk/bus/index.ts +84 -7
- package/src/sdk/bus/kind-aware-reconciliation.ts +296 -48
- package/src/sdk/bus/lifecycle-commands.ts +4 -0
- package/src/sdk/bus/notification-service.ts +22 -5
- package/src/sdk/bus/prompt-reconciliation.ts +7 -2
- package/src/sdk/bus/reconciliation-store.ts +55 -31
- package/src/sdk/bus/slack-conversation.ts +1 -1
- package/src/sdk/bus/slack-daemon.ts +72 -38
- 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 +41 -15
- package/src/sdk/cli/session-cli.ts +297 -26
- package/src/sdk/client/client.ts +14 -8
- package/src/sdk/host/control/dispatch.ts +101 -40
- 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/events.ts +1 -0
- package/src/sdk/host/host.ts +29 -2
- package/src/sdk/host/query/cursor.ts +52 -7
- package/src/sdk/host/query/handlers.ts +33 -12
- package/src/sdk/host/query/revision-store.ts +12 -0
- package/src/sdk/host/session-runtime.test.ts +673 -14
- package/src/sdk/host/session-runtime.ts +674 -103
- package/src/sdk/host/surface-policy.ts +3 -3
- package/src/sdk/lifecycle/broker-client.ts +28 -0
- package/src/sdk/lifecycle/service.ts +279 -20
- package/src/sdk/lifecycle-session.ts +17 -4
- package/src/sdk/prompt-deadline-manager.ts +2 -1
- package/src/sdk/protocol/operation-inventory.generated.json +105 -3
- package/src/sdk/protocol/operation-registry.ts +14 -4
- package/src/sdk/receipt-state.ts +10 -0
- package/src/sdk/router/session-router.ts +219 -118
- package/src/sdk/session.ts +416 -195
- package/src/sdk/startup-capability.ts +1 -0
- package/src/sdk/turn-result.ts +22 -0
- package/src/session/agent-session.ts +1628 -318
- package/src/session/blob-store.ts +15 -5
- package/src/session/credential-store-errors.ts +2 -0
- package/src/session/fold-coordinator.ts +40 -3
- package/src/session/internal/managed-session-storage.ts +153 -78
- package/src/session/session-manager.ts +850 -97
- package/src/session/yield-queue.ts +24 -10
- package/src/setup/frictionless-onboarding.ts +2 -0
- package/src/slash-commands/builtin-registry.ts +41 -0
- package/src/task/executor.ts +9 -4
- package/src/telemetry/events.ts +1138 -12
- package/src/tools/ask-contract.test.ts +64 -0
- package/src/tools/ask-contract.ts +36 -1
- package/src/tools/ask.ts +10 -8
- package/src/tools/bash-interactive.ts +11 -2
- package/src/tools/bash.ts +412 -75
- package/src/tools/browser/launch.ts +37 -0
- package/src/tools/browser/registry.ts +99 -9
- package/src/tools/cron.ts +6 -4
- package/src/tools/descriptors.test.ts +15 -0
- package/src/tools/descriptors.ts +5 -1
- package/src/tools/gh.ts +104 -48
- package/src/tools/github-cache.ts +2 -2
- package/src/tools/index.ts +10 -6
- package/src/tools/job.ts +4 -1
- package/src/tools/monitor.ts +12 -9
- package/src/tools/resolve.ts +30 -10
- package/src/tools/tool-catalog.generated.ts +2 -2
- package/src/utils/external-editor.ts +72 -1
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,
|
|
@@ -49,6 +51,7 @@ import {
|
|
|
49
51
|
isBackgroundJobSupportEnabled,
|
|
50
52
|
jobElapsedMs,
|
|
51
53
|
} from "../async";
|
|
54
|
+
import { resolveBrowserBackend } from "../browser-backend";
|
|
52
55
|
import { loadCapability, reset as resetCapabilities } from "../capability";
|
|
53
56
|
import { type Rule, ruleCapability, setActiveRules } from "../capability/rule";
|
|
54
57
|
import type { SourceMeta } from "../capability/types";
|
|
@@ -65,11 +68,13 @@ import {
|
|
|
65
68
|
resolveModelRoleValue,
|
|
66
69
|
type ScopedModelSelection,
|
|
67
70
|
} from "../config/model-resolver";
|
|
71
|
+
import { normalizeModelSelectorValue } from "../config/model-selector-value";
|
|
68
72
|
import { loadPromptTemplates as loadPromptTemplatesInternal, type PromptTemplate } from "../config/prompt-templates";
|
|
69
73
|
import { Settings, type SkillsSettings } from "../config/settings";
|
|
70
74
|
import { resolveEagerTaskDelegation } from "../config/task-delegation";
|
|
71
75
|
import { CursorExecHandlers } from "../cursor";
|
|
72
76
|
import { EditTool } from "../edit";
|
|
77
|
+
import type { MasterModeContext } from "../master-mode/context";
|
|
73
78
|
import { describeFoldReceipt } from "../session/fold-coordinator";
|
|
74
79
|
import type { BashRestrictionProfile } from "../tools/bash-allowed-prefixes";
|
|
75
80
|
import { SearchTool } from "../tools/search";
|
|
@@ -120,7 +125,9 @@ import { normalizePluginHook } from "../hooks/normalize";
|
|
|
120
125
|
import { initializeLocalRoot, LocalProtocolHandler, type LocalProtocolOptions } from "../internal-urls";
|
|
121
126
|
import type { LspStartupServerInfo } from "../lsp";
|
|
122
127
|
import { shutdownAll as shutdownAllLspClients } from "../lsp/client";
|
|
128
|
+
import { createMasterPeerSnapshotContributor, MASTER_PEER_SNAPSHOT_CUSTOM_TYPE } from "../master-mode/first-request";
|
|
123
129
|
import btwUserPrompt from "../prompts/system/btw-user.md" with { type: "text" };
|
|
130
|
+
import masterModeTemplate from "../prompts/system/master-mode.md" with { type: "text" };
|
|
124
131
|
import asyncResultTemplate from "../prompts/tools/async-result.md" with { type: "text" };
|
|
125
132
|
import { AgentRegistry, MAIN_AGENT_ID } from "../registry/agent-registry";
|
|
126
133
|
import { createLazyService } from "../runtime/lazy-service";
|
|
@@ -144,7 +151,7 @@ import { markAutoroutingInactive } from "../sdk/host/internal-autorouting-state"
|
|
|
144
151
|
import { createSdkSessionRuntimeExtension, registerSdkOnlyNotificationCommand } from "../sdk/host/session-runtime";
|
|
145
152
|
import { createSdkWebSocketTransport } from "../sdk/host/websocket-transport";
|
|
146
153
|
import type { SecretObfuscator } from "../secrets";
|
|
147
|
-
import { AgentSession, type ForkContextSeed } from "../session/agent-session";
|
|
154
|
+
import { AgentSession, type ForkContextSeed, isSessionDisposalIncompleteError } from "../session/agent-session";
|
|
148
155
|
import { AuthBrokerClient, AuthStorage, RemoteAuthCredentialStore } from "../session/auth-storage";
|
|
149
156
|
import { type CustomMessage, convertToLlm } from "../session/messages";
|
|
150
157
|
import { createReadonlySessionManager, SessionManager } from "../session/session-manager";
|
|
@@ -189,6 +196,7 @@ import { guardToolForUltragoalAsk } from "../tools/ultragoal-ask-guard";
|
|
|
189
196
|
import { EventBus } from "../utils/event-bus";
|
|
190
197
|
import { buildNamedToolChoice, buildNamedToolChoiceResult } from "../utils/tool-choice";
|
|
191
198
|
import type { WorkspaceTree } from "../workspace-tree";
|
|
199
|
+
import { createSessionLifecycleService } from "./lifecycle/client";
|
|
192
200
|
import {
|
|
193
201
|
attachLifecycleStartupCapability,
|
|
194
202
|
lifecycleMcpStartupTimeoutOption,
|
|
@@ -525,6 +533,8 @@ export interface CreateAgentSessionOptions {
|
|
|
525
533
|
* @internal CLI-only startup optimization; SDK callers retain synchronous loading by default.
|
|
526
534
|
*/
|
|
527
535
|
deferMcpConfigStartup?: boolean;
|
|
536
|
+
/** Process-local master authority. Never persist or pass to lifecycle children. */
|
|
537
|
+
masterModeContext?: MasterModeContext;
|
|
528
538
|
/**
|
|
529
539
|
* Defer memory backend startup until the caller has applied startup model profiles.
|
|
530
540
|
* @internal CLI-only ordering guard; SDK callers retain immediate startup by default.
|
|
@@ -1095,12 +1105,16 @@ function buildMCPPromptCommands(manager: MCPManager): LoadedCustomCommand[] {
|
|
|
1095
1105
|
*/
|
|
1096
1106
|
|
|
1097
1107
|
function withEmbeddedDefaultGjcSkills(skills: Skill[]): Skill[] {
|
|
1098
|
-
const byName = new Map
|
|
1108
|
+
const byName = new Map<string, Skill>();
|
|
1109
|
+
for (const skill of skills) {
|
|
1110
|
+
const key = skill.name.toLowerCase();
|
|
1111
|
+
if (!byName.has(key)) byName.set(key, skill);
|
|
1112
|
+
}
|
|
1099
1113
|
// The four public GJC workflow skills are a product invariant: even if a
|
|
1100
1114
|
// caller-supplied or filesystem skill shares a name, the bundled definition
|
|
1101
1115
|
// wins so workflow routing can never be silently hijacked.
|
|
1102
1116
|
for (const defaultSkill of getEmbeddedDefaultGjcSkills()) {
|
|
1103
|
-
byName.set(defaultSkill.name, defaultSkill);
|
|
1117
|
+
byName.set(defaultSkill.name.toLowerCase(), defaultSkill);
|
|
1104
1118
|
}
|
|
1105
1119
|
return [...byName.values()];
|
|
1106
1120
|
}
|
|
@@ -1282,6 +1296,45 @@ function findDeferredExactMcpToolNameCollisions(
|
|
|
1282
1296
|
return [...collisions];
|
|
1283
1297
|
}
|
|
1284
1298
|
|
|
1299
|
+
/**
|
|
1300
|
+
* Order candidates so each known provider's curated default model is tried
|
|
1301
|
+
* before the rest of the catalog.
|
|
1302
|
+
*
|
|
1303
|
+
* Provider catalogs are not ranked by fitness — a withdrawn model whose ID
|
|
1304
|
+
* carries an older date suffix sorts ahead of its current replacement — so
|
|
1305
|
+
* picking the first credentialed entry can start an unconfigured install on a
|
|
1306
|
+
* model the provider no longer serves. `DEFAULT_MODEL_PER_PROVIDER` is the
|
|
1307
|
+
* curated table `findInitialModel` already sweeps for the same purpose;
|
|
1308
|
+
* reusing it keeps both unconfigured paths on one source of truth. Candidates
|
|
1309
|
+
* that are not a provider default keep their original relative order.
|
|
1310
|
+
*/
|
|
1311
|
+
export function orderByProviderDefaultFirst(
|
|
1312
|
+
candidates: readonly Model[],
|
|
1313
|
+
providerOrder: readonly string[] = Object.keys(DEFAULT_MODEL_PER_PROVIDER),
|
|
1314
|
+
): Model[] {
|
|
1315
|
+
const preferred: Model[] = [];
|
|
1316
|
+
const rest: Model[] = [];
|
|
1317
|
+
const preferredCandidates = new Set<Model>();
|
|
1318
|
+
const seenProviders = new Set<string>();
|
|
1319
|
+
for (const rawProvider of [...providerOrder, ...Object.keys(DEFAULT_MODEL_PER_PROVIDER)]) {
|
|
1320
|
+
const provider = rawProvider.trim().toLowerCase();
|
|
1321
|
+
if (!provider || seenProviders.has(provider) || !(provider in DEFAULT_MODEL_PER_PROVIDER)) continue;
|
|
1322
|
+
seenProviders.add(provider);
|
|
1323
|
+
const defaultId = DEFAULT_MODEL_PER_PROVIDER[provider as KnownProvider];
|
|
1324
|
+
for (const candidate of candidates) {
|
|
1325
|
+
if (candidate.provider.trim().toLowerCase() === provider && candidate.id === defaultId) {
|
|
1326
|
+
preferred.push(candidate);
|
|
1327
|
+
preferredCandidates.add(candidate);
|
|
1328
|
+
}
|
|
1329
|
+
}
|
|
1330
|
+
}
|
|
1331
|
+
if (preferred.length === 0) return [...candidates];
|
|
1332
|
+
for (const candidate of candidates) {
|
|
1333
|
+
if (!preferredCandidates.has(candidate)) rest.push(candidate);
|
|
1334
|
+
}
|
|
1335
|
+
return [...preferred, ...rest];
|
|
1336
|
+
}
|
|
1337
|
+
|
|
1285
1338
|
const AUTOMATION_TOOL_NAMES = new Set<AutomationToolName>(["browser", "computer"]);
|
|
1286
1339
|
|
|
1287
1340
|
function validateAutomationTools(options: CreateAgentSessionOptions): AutomationTools {
|
|
@@ -1307,6 +1360,7 @@ function validateAutomationTools(options: CreateAgentSessionOptions): Automation
|
|
|
1307
1360
|
|
|
1308
1361
|
export async function createAgentSession(options: CreateAgentSessionOptions = {}): Promise<CreateAgentSessionResult> {
|
|
1309
1362
|
const automationTools = validateAutomationTools(options);
|
|
1363
|
+
const masterModeContext = options.masterModeContext;
|
|
1310
1364
|
const lifecycleStartupCapability = (
|
|
1311
1365
|
options as CreateAgentSessionOptions & { [lifecycleStartupCapabilityOption]?: SdkStartupCapability }
|
|
1312
1366
|
)[lifecycleStartupCapabilityOption];
|
|
@@ -1366,6 +1420,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
1366
1420
|
}
|
|
1367
1421
|
|
|
1368
1422
|
let agent: Agent;
|
|
1423
|
+
let sessionAgent: Agent | undefined;
|
|
1369
1424
|
let session!: AgentSession;
|
|
1370
1425
|
let sessionManager!: SessionManager;
|
|
1371
1426
|
let hasSession = false;
|
|
@@ -1783,8 +1838,12 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
1783
1838
|
credentialSessionId,
|
|
1784
1839
|
}),
|
|
1785
1840
|
);
|
|
1841
|
+
const configuredDefaultPatterns = normalizeModelSelectorValue(settings.getModelRole("default"));
|
|
1786
1842
|
let model = options.model;
|
|
1787
1843
|
let modelFallbackMessage: string | undefined;
|
|
1844
|
+
if (!hasExplicitModel && configuredDefaultPatterns.length > 0 && !defaultRoleSpec.model) {
|
|
1845
|
+
modelFallbackMessage = `Model ${configuredDefaultPatterns.join(" -> ")} not found`;
|
|
1846
|
+
}
|
|
1788
1847
|
const resumeModelBehavior = settings.get("session.resumeModelBehavior");
|
|
1789
1848
|
const persistedDefaultChain = existingSession.configuredModelChains.default;
|
|
1790
1849
|
const defaultModelEntries =
|
|
@@ -2384,7 +2443,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
2384
2443
|
"Cannot rescope a session with caller-owned MCP authority; recreate the session at the target cwd.",
|
|
2385
2444
|
);
|
|
2386
2445
|
}
|
|
2387
|
-
return sessionManager.
|
|
2446
|
+
return sessionManager.runExclusiveCwdMoveTransition(async () => {
|
|
2388
2447
|
if (moveConsumed) {
|
|
2389
2448
|
throw new Error(
|
|
2390
2449
|
"This session has already been rescoped; only one agent-invoked move is allowed per session.",
|
|
@@ -2396,174 +2455,240 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
2396
2455
|
);
|
|
2397
2456
|
}
|
|
2398
2457
|
const from = sessionManager.getCwd();
|
|
2399
|
-
const
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
canonicalTarget = await fs.realpath(resolvedPath);
|
|
2405
|
-
} catch {
|
|
2406
|
-
throw new Error(`Directory does not exist or is not a directory: ${resolvedPath}`);
|
|
2407
|
-
}
|
|
2408
|
-
if (!(await fs.stat(canonicalTarget)).isDirectory()) {
|
|
2409
|
-
throw new Error(`Directory does not exist or is not a directory: ${resolvedPath}`);
|
|
2410
|
-
}
|
|
2411
|
-
const relative = path.relative(canonicalFrom, canonicalTarget);
|
|
2412
|
-
if (relative === "") {
|
|
2413
|
-
throw new Error(
|
|
2414
|
-
`Target ${canonicalTarget} is the current session directory; nothing to move.`,
|
|
2415
|
-
);
|
|
2458
|
+
const sourceHandle = await SessionManager.openNoFollowDirectory(from);
|
|
2459
|
+
const sourceOpened = await sourceHandle.stat({ bigint: true });
|
|
2460
|
+
if (!sourceOpened.isDirectory()) {
|
|
2461
|
+
await sourceHandle.close().catch(() => {});
|
|
2462
|
+
throw new Error(`Current session directory is no longer a directory: ${from}`);
|
|
2416
2463
|
}
|
|
2417
|
-
if (relative === ".." || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative)) {
|
|
2418
|
-
throw new Error(
|
|
2419
|
-
`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.`,
|
|
2420
|
-
);
|
|
2421
|
-
}
|
|
2422
|
-
let targetHandle: nodeFs.promises.FileHandle | undefined;
|
|
2423
|
-
let expectedIdentity: { dev: bigint; ino: bigint };
|
|
2424
2464
|
try {
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2465
|
+
const resolvedPath = path.resolve(from, target);
|
|
2466
|
+
let canonicalFrom: string;
|
|
2467
|
+
let canonicalTarget: string;
|
|
2468
|
+
try {
|
|
2469
|
+
canonicalFrom = await fs.realpath(from);
|
|
2470
|
+
canonicalTarget = await fs.realpath(resolvedPath);
|
|
2471
|
+
} catch (error) {
|
|
2472
|
+
// macOS `realpath(3)` reports EACCES on an unreadable
|
|
2473
|
+
// directory where Linux succeeds and fails later at the
|
|
2474
|
+
// `access` probe; keep both platforms on the access message.
|
|
2475
|
+
const code = (error as NodeJS.ErrnoException)?.code;
|
|
2476
|
+
if (code === "EACCES" || code === "EPERM") {
|
|
2477
|
+
throw new Error(
|
|
2478
|
+
`Directory identity or access unavailable: ${resolvedPath}${
|
|
2479
|
+
error instanceof Error ? ` (${error.message})` : ""
|
|
2480
|
+
}`,
|
|
2481
|
+
);
|
|
2482
|
+
}
|
|
2428
2483
|
throw new Error(`Directory does not exist or is not a directory: ${resolvedPath}`);
|
|
2429
2484
|
}
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
} catch (error) {
|
|
2433
|
-
await targetHandle?.close().catch(() => {});
|
|
2434
|
-
if (error instanceof Error && error.message.startsWith("Directory does not exist")) {
|
|
2435
|
-
throw error;
|
|
2485
|
+
if (!(await fs.stat(canonicalTarget)).isDirectory()) {
|
|
2486
|
+
throw new Error(`Directory does not exist or is not a directory: ${resolvedPath}`);
|
|
2436
2487
|
}
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2488
|
+
const relative = path.relative(canonicalFrom, canonicalTarget);
|
|
2489
|
+
if (relative === "") {
|
|
2490
|
+
throw new Error(
|
|
2491
|
+
`Target ${canonicalTarget} is the current session directory; nothing to move.`,
|
|
2492
|
+
);
|
|
2493
|
+
}
|
|
2494
|
+
if (
|
|
2495
|
+
relative === ".." ||
|
|
2496
|
+
relative.startsWith(`..${path.sep}`) ||
|
|
2497
|
+
path.isAbsolute(relative)
|
|
2498
|
+
) {
|
|
2499
|
+
throw new Error(
|
|
2500
|
+
`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.`,
|
|
2501
|
+
);
|
|
2502
|
+
}
|
|
2503
|
+
let targetHandle: nodeFs.promises.FileHandle | undefined;
|
|
2504
|
+
let expectedIdentity: { dev: bigint; ino: bigint };
|
|
2505
|
+
try {
|
|
2506
|
+
targetHandle = await SessionManager.openNoFollowDirectory(canonicalTarget);
|
|
2507
|
+
const opened = await targetHandle.stat({ bigint: true });
|
|
2508
|
+
if (!opened.isDirectory()) {
|
|
2509
|
+
throw new Error(`Directory does not exist or is not a directory: ${resolvedPath}`);
|
|
2458
2510
|
}
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2511
|
+
const revalidatedFrom = await fs.realpath(from);
|
|
2512
|
+
const revalidatedTarget = await fs.realpath(resolvedPath);
|
|
2513
|
+
const revalidatedRelative = path.relative(revalidatedFrom, revalidatedTarget);
|
|
2514
|
+
if (
|
|
2515
|
+
revalidatedFrom !== canonicalFrom ||
|
|
2516
|
+
revalidatedTarget !== canonicalTarget ||
|
|
2517
|
+
revalidatedRelative === "" ||
|
|
2518
|
+
revalidatedRelative === ".." ||
|
|
2519
|
+
revalidatedRelative.startsWith(`..${path.sep}`) ||
|
|
2520
|
+
path.isAbsolute(revalidatedRelative)
|
|
2521
|
+
) {
|
|
2522
|
+
throw new Error(
|
|
2523
|
+
`Directory identity changed during confinement validation: ${resolvedPath}`,
|
|
2463
2524
|
);
|
|
2464
|
-
clearPluginRootsAndCaches(restoreRegistry ? [restoreRegistry] : undefined);
|
|
2465
|
-
} catch (error) {
|
|
2466
|
-
restoreErrors.push(error instanceof Error ? error : new Error(String(error)));
|
|
2467
2525
|
}
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2526
|
+
const named = await fs.lstat(revalidatedTarget, { bigint: true });
|
|
2527
|
+
const namedSource = await fs.lstat(revalidatedFrom, { bigint: true });
|
|
2528
|
+
if (
|
|
2529
|
+
!namedSource.isDirectory() ||
|
|
2530
|
+
namedSource.isSymbolicLink() ||
|
|
2531
|
+
namedSource.dev !== sourceOpened.dev ||
|
|
2532
|
+
namedSource.ino !== sourceOpened.ino ||
|
|
2533
|
+
!named.isDirectory() ||
|
|
2534
|
+
named.isSymbolicLink() ||
|
|
2535
|
+
named.dev !== opened.dev ||
|
|
2536
|
+
named.ino !== opened.ino
|
|
2537
|
+
) {
|
|
2538
|
+
throw new Error(
|
|
2539
|
+
`Directory identity changed during confinement validation: ${resolvedPath}`,
|
|
2540
|
+
);
|
|
2541
|
+
}
|
|
2542
|
+
expectedIdentity = { dev: opened.dev, ino: opened.ino };
|
|
2543
|
+
await fs.access(canonicalTarget, nodeFs.constants.R_OK | nodeFs.constants.X_OK);
|
|
2471
2544
|
} catch (error) {
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
},
|
|
2545
|
+
await targetHandle?.close().catch(() => {});
|
|
2546
|
+
if (error instanceof Error && error.message.startsWith("Directory does not exist")) {
|
|
2547
|
+
throw error;
|
|
2548
|
+
}
|
|
2549
|
+
throw new Error(
|
|
2550
|
+
`Directory identity or access unavailable: ${canonicalTarget}${
|
|
2551
|
+
error instanceof Error ? ` (${error.message})` : ""
|
|
2552
|
+
}`,
|
|
2481
2553
|
);
|
|
2482
2554
|
}
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
//
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
|
|
2555
|
+
// Process-cwd authority is an explicit claim, never inferred from
|
|
2556
|
+
// `process.cwd() === from`: sibling sessions launched at the same
|
|
2557
|
+
// root both satisfy that, so acting on it would chdir the process
|
|
2558
|
+
// and clear process-global caches underneath the sibling.
|
|
2559
|
+
const ownsProcessCwd = SessionManager.isProcessCwdOwner(sessionManager);
|
|
2560
|
+
const restoreLaunchRoot = async (failure: unknown): Promise<never> => {
|
|
2561
|
+
const restoreErrors: Error[] = [];
|
|
2562
|
+
if (ownsProcessCwd) {
|
|
2563
|
+
try {
|
|
2564
|
+
setProjectDir(canonicalFrom);
|
|
2565
|
+
if (path.resolve(process.cwd()) !== path.resolve(canonicalFrom)) {
|
|
2566
|
+
throw new Error("Process cwd did not restore to the launch root.");
|
|
2567
|
+
}
|
|
2568
|
+
await SessionManager.assertProcessCwdIdentity({
|
|
2569
|
+
dev: sourceOpened.dev,
|
|
2570
|
+
ino: sourceOpened.ino,
|
|
2571
|
+
});
|
|
2572
|
+
} catch (error) {
|
|
2573
|
+
restoreErrors.push(error instanceof Error ? error : new Error(String(error)));
|
|
2574
|
+
}
|
|
2575
|
+
try {
|
|
2576
|
+
resetCapabilities();
|
|
2577
|
+
const restoreRegistry = await resolveActiveProjectRegistryPath(
|
|
2578
|
+
canonicalFrom,
|
|
2579
|
+
).catch(() => undefined);
|
|
2580
|
+
clearPluginRootsAndCaches(restoreRegistry ? [restoreRegistry] : undefined);
|
|
2581
|
+
} catch (error) {
|
|
2582
|
+
restoreErrors.push(error instanceof Error ? error : new Error(String(error)));
|
|
2583
|
+
}
|
|
2584
|
+
}
|
|
2492
2585
|
try {
|
|
2493
|
-
|
|
2494
|
-
// landed on the pinned directory, so a path replaced after
|
|
2495
|
-
// the name checks cannot escape the validated descendant.
|
|
2496
|
-
await SessionManager.assertProcessCwdIdentity(expectedIdentity);
|
|
2586
|
+
await rebindCwdCapturingAuthority(canonicalFrom);
|
|
2497
2587
|
} catch (error) {
|
|
2498
|
-
|
|
2499
|
-
throw error;
|
|
2588
|
+
restoreErrors.push(error instanceof Error ? error : new Error(String(error)));
|
|
2500
2589
|
}
|
|
2501
|
-
|
|
2502
|
-
|
|
2590
|
+
if (restoreErrors.length > 0) {
|
|
2591
|
+
throw new AggregateError(
|
|
2592
|
+
restoreErrors,
|
|
2593
|
+
"Failed to restore launch-root rescope authority.",
|
|
2594
|
+
{
|
|
2595
|
+
cause: failure,
|
|
2596
|
+
},
|
|
2597
|
+
);
|
|
2598
|
+
}
|
|
2599
|
+
throw failure;
|
|
2600
|
+
};
|
|
2503
2601
|
try {
|
|
2602
|
+
// Every fallible step that the moved session depends on runs
|
|
2603
|
+
// BEFORE the session-file commit, so a failure here leaves the
|
|
2604
|
+
// session exactly where it was and the tool call is a clean
|
|
2605
|
+
// rejection rather than a half-moved session.
|
|
2504
2606
|
if (ownsProcessCwd) {
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
2607
|
+
setProjectDir(canonicalTarget);
|
|
2608
|
+
try {
|
|
2609
|
+
// `setProjectDir` chdirs a NAME. Confirm the process actually
|
|
2610
|
+
// landed on the pinned directory, so a path replaced after
|
|
2611
|
+
// the name checks cannot escape the validated descendant.
|
|
2612
|
+
await SessionManager.assertProcessCwdIdentity(expectedIdentity);
|
|
2613
|
+
} catch (error) {
|
|
2614
|
+
setProjectDir(canonicalFrom);
|
|
2615
|
+
await SessionManager.assertProcessCwdIdentity({
|
|
2616
|
+
dev: sourceOpened.dev,
|
|
2617
|
+
ino: sourceOpened.ino,
|
|
2618
|
+
});
|
|
2619
|
+
throw error;
|
|
2620
|
+
}
|
|
2621
|
+
}
|
|
2622
|
+
let rescopeFailure: unknown;
|
|
2623
|
+
try {
|
|
2624
|
+
if (ownsProcessCwd) {
|
|
2625
|
+
resetCapabilities();
|
|
2626
|
+
const projectRegistry = await resolveActiveProjectRegistryPath(canonicalTarget);
|
|
2627
|
+
clearPluginRootsAndCaches(projectRegistry ? [projectRegistry] : undefined);
|
|
2628
|
+
}
|
|
2629
|
+
// Plugin/MCP/Python authority must be rebound successfully
|
|
2630
|
+
// before committing; swallowing a failure here is what leaves
|
|
2631
|
+
// a moved session holding launch-root tool authority.
|
|
2632
|
+
await rebindCwdCapturingAuthority(canonicalTarget);
|
|
2633
|
+
} catch (error) {
|
|
2634
|
+
rescopeFailure = error;
|
|
2635
|
+
}
|
|
2636
|
+
if (rescopeFailure !== undefined) {
|
|
2637
|
+
await restoreLaunchRoot(rescopeFailure);
|
|
2638
|
+
}
|
|
2639
|
+
try {
|
|
2640
|
+
await sessionManager.flush();
|
|
2641
|
+
// Commit last: `moveTo` re-validates the pinned identity through
|
|
2642
|
+
// the still-open handle at the state-changing boundary.
|
|
2643
|
+
await sessionManager.moveTo(canonicalTarget, {
|
|
2644
|
+
expectedIdentity,
|
|
2645
|
+
targetHandle,
|
|
2646
|
+
expectedSourceIdentity: { dev: sourceOpened.dev, ino: sourceOpened.ino },
|
|
2647
|
+
sourceHandle,
|
|
2648
|
+
});
|
|
2649
|
+
} catch (error) {
|
|
2650
|
+
const committedCwd = sessionManager.getCwd();
|
|
2651
|
+
const stayedAtLaunchRoot = path.resolve(committedCwd) === path.resolve(from);
|
|
2652
|
+
if (stayedAtLaunchRoot) await restoreLaunchRoot(error);
|
|
2653
|
+
// SessionManager can publish the durable move before a later metadata
|
|
2654
|
+
// write fails. Treat that state as committed rather than reporting a
|
|
2655
|
+
// rejection after the session has moved.
|
|
2656
|
+
moveConsumed = true;
|
|
2657
|
+
logger.warn("Session rescope committed before finalization failed", {
|
|
2658
|
+
error: safeErrorForLog(error),
|
|
2659
|
+
cwd: committedCwd,
|
|
2660
|
+
});
|
|
2508
2661
|
}
|
|
2509
|
-
// Plugin/MCP/Python authority must be rebound successfully
|
|
2510
|
-
// before committing; swallowing a failure here is what leaves
|
|
2511
|
-
// a moved session holding launch-root tool authority.
|
|
2512
|
-
await rebindCwdCapturingAuthority(canonicalTarget);
|
|
2513
|
-
} catch (error) {
|
|
2514
|
-
rescopeFailure = error;
|
|
2515
|
-
}
|
|
2516
|
-
if (rescopeFailure !== undefined) {
|
|
2517
|
-
await restoreLaunchRoot(rescopeFailure);
|
|
2518
|
-
}
|
|
2519
|
-
try {
|
|
2520
|
-
await sessionManager.flush();
|
|
2521
|
-
// Commit last: `moveTo` re-validates the pinned identity through
|
|
2522
|
-
// the still-open handle at the state-changing boundary.
|
|
2523
|
-
await sessionManager.moveTo(canonicalTarget, {
|
|
2524
|
-
expectedIdentity,
|
|
2525
|
-
targetHandle,
|
|
2526
|
-
});
|
|
2527
|
-
} catch (error) {
|
|
2528
|
-
const committedCwd = sessionManager.getCwd();
|
|
2529
|
-
const stayedAtLaunchRoot = path.resolve(committedCwd) === path.resolve(from);
|
|
2530
|
-
if (stayedAtLaunchRoot) await restoreLaunchRoot(error);
|
|
2531
|
-
// SessionManager can publish the durable move before a later metadata
|
|
2532
|
-
// write fails. Treat that state as committed rather than reporting a
|
|
2533
|
-
// rejection after the session has moved.
|
|
2534
2662
|
moveConsumed = true;
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2663
|
+
if (ownsProcessCwd) {
|
|
2664
|
+
try {
|
|
2665
|
+
await shutdownAllLspClients();
|
|
2666
|
+
} catch (error) {
|
|
2667
|
+
logger.warn("Failed to reset launch-root LSP clients after session rescope", {
|
|
2668
|
+
error: safeErrorForLog(error),
|
|
2669
|
+
});
|
|
2670
|
+
}
|
|
2671
|
+
}
|
|
2672
|
+
// Cwd-derived read-only state the prompt and subagents consume.
|
|
2673
|
+
// Best-effort by design: the move is committed, and a failed
|
|
2674
|
+
// re-discovery must not present a committed move as a failure.
|
|
2675
|
+
await applyRescopedReadState(sessionManager.getCwd());
|
|
2542
2676
|
try {
|
|
2543
|
-
await
|
|
2677
|
+
await session?.refreshSshTool({ activateIfAvailable: true });
|
|
2544
2678
|
} catch (error) {
|
|
2545
|
-
|
|
2679
|
+
// Non-fatal: the session has moved; the SSH tool refreshes
|
|
2680
|
+
// on its next activation attempt.
|
|
2681
|
+
logger.warn("Committed session rescope could not refresh the SSH tool", {
|
|
2546
2682
|
error: safeErrorForLog(error),
|
|
2683
|
+
cwd: sessionManager.getCwd(),
|
|
2547
2684
|
});
|
|
2548
2685
|
}
|
|
2686
|
+
return { from, to: sessionManager.getCwd() };
|
|
2687
|
+
} finally {
|
|
2688
|
+
await targetHandle.close().catch(() => {});
|
|
2549
2689
|
}
|
|
2550
|
-
// Cwd-derived read-only state the prompt and subagents consume.
|
|
2551
|
-
// Best-effort by design: the move is committed, and a failed
|
|
2552
|
-
// re-discovery must not present a committed move as a failure.
|
|
2553
|
-
await applyRescopedReadState(sessionManager.getCwd());
|
|
2554
|
-
try {
|
|
2555
|
-
await session?.refreshSshTool({ activateIfAvailable: true });
|
|
2556
|
-
} catch (error) {
|
|
2557
|
-
// Non-fatal: the session has moved; the SSH tool refreshes
|
|
2558
|
-
// on its next activation attempt.
|
|
2559
|
-
logger.warn("Committed session rescope could not refresh the SSH tool", {
|
|
2560
|
-
error: safeErrorForLog(error),
|
|
2561
|
-
cwd: sessionManager.getCwd(),
|
|
2562
|
-
});
|
|
2563
|
-
}
|
|
2564
|
-
return { from, to: sessionManager.getCwd() };
|
|
2565
2690
|
} finally {
|
|
2566
|
-
await
|
|
2691
|
+
await sourceHandle.close().catch(() => {});
|
|
2567
2692
|
}
|
|
2568
2693
|
});
|
|
2569
2694
|
};
|
|
@@ -2593,7 +2718,9 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
2593
2718
|
registerForegroundFoldParticipant: adapter =>
|
|
2594
2719
|
session?.registerForegroundFoldParticipant(adapter) ?? (() => {}),
|
|
2595
2720
|
hasForegroundBashBackgroundRequestHandler: () => session?.hasForegroundBashBackgroundRequestHandler() ?? false,
|
|
2596
|
-
requestForegroundBashBackground: () =>
|
|
2721
|
+
requestForegroundBashBackground: (reason, adapter) =>
|
|
2722
|
+
Promise.resolve(session?.requestForegroundBashBackground(reason, adapter) ?? false),
|
|
2723
|
+
getToolInterruptPolicy: () => agent?.getToolInterruptPolicy() ?? "abort_tools",
|
|
2597
2724
|
|
|
2598
2725
|
getCredentialSessionId: () => session?.credentialSessionId ?? credentialSessionId,
|
|
2599
2726
|
getMcpManager: () => mcpManager ?? options.inheritedMcpManager,
|
|
@@ -2623,6 +2750,8 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
2623
2750
|
getToolForExecution: name => session?.getToolForExecution(name),
|
|
2624
2751
|
agentRegistry,
|
|
2625
2752
|
getSessionSpawns: () => options.spawns ?? "*",
|
|
2753
|
+
getMasterBashCapability: () => masterModeContext?.getCapability(),
|
|
2754
|
+
getMasterOwnerSessionId: () => masterModeContext?.ownerSessionId,
|
|
2626
2755
|
getModelString: () => (hasExplicitModel && model ? formatModelString(model) : undefined),
|
|
2627
2756
|
getActiveModelString,
|
|
2628
2757
|
getPlanModeState: () => session?.getPlanModeState(),
|
|
@@ -2648,16 +2777,6 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
2648
2777
|
return m ? buildNamedToolChoice(name, m) : undefined;
|
|
2649
2778
|
},
|
|
2650
2779
|
buildToolChoiceResult: name => buildNamedToolChoiceResult(name, session.model),
|
|
2651
|
-
steer: msg =>
|
|
2652
|
-
session.agent.steer({
|
|
2653
|
-
role: "custom",
|
|
2654
|
-
customType: msg.customType,
|
|
2655
|
-
content: msg.content,
|
|
2656
|
-
display: false,
|
|
2657
|
-
details: msg.details,
|
|
2658
|
-
attribution: "agent",
|
|
2659
|
-
timestamp: Date.now(),
|
|
2660
|
-
}),
|
|
2661
2780
|
sendCustomMessage: (msg, opts) => session.sendCustomMessage(msg, opts),
|
|
2662
2781
|
purgeQueuedCustomMessages: predicate => session.purgeQueuedCustomMessages(predicate),
|
|
2663
2782
|
peekQueueInvoker: () => session.peekQueueInvoker(),
|
|
@@ -3171,6 +3290,13 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
3171
3290
|
controller: notificationSessionController,
|
|
3172
3291
|
spawnedByGjc,
|
|
3173
3292
|
sdkHostModeSupported: options.sdkHostModeSupported,
|
|
3293
|
+
...(masterModeContext
|
|
3294
|
+
? {
|
|
3295
|
+
masterCapability: masterModeContext.getCapability(),
|
|
3296
|
+
masterAttestationEpoch: masterModeContext.attestationEpoch,
|
|
3297
|
+
masterOwnerSessionId: masterModeContext.ownerSessionId,
|
|
3298
|
+
}
|
|
3299
|
+
: {}),
|
|
3174
3300
|
// INTERNAL terminal-abort seams, threaded directly from the
|
|
3175
3301
|
// owning session (NOT on the public extension context).
|
|
3176
3302
|
terminalAbortSeams: {
|
|
@@ -3218,9 +3344,19 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
3218
3344
|
createSdkSessionRuntimeExtension(api, {
|
|
3219
3345
|
agentDir,
|
|
3220
3346
|
brokerRegistrationRequired: lifecycleStartupCapability !== undefined,
|
|
3347
|
+
...(lifecycleStartupCapability?.lifecycleRequestId
|
|
3348
|
+
? { lifecycleRequestId: lifecycleStartupCapability.lifecycleRequestId }
|
|
3349
|
+
: {}),
|
|
3221
3350
|
createTransport: input => createSdkWebSocketTransport(input),
|
|
3222
3351
|
settings,
|
|
3223
3352
|
configOverrides: new Map(),
|
|
3353
|
+
...(masterModeContext
|
|
3354
|
+
? {
|
|
3355
|
+
masterCapability: masterModeContext.getCapability(),
|
|
3356
|
+
masterAttestationEpoch: masterModeContext.attestationEpoch,
|
|
3357
|
+
masterOwnerSessionId: masterModeContext.ownerSessionId,
|
|
3358
|
+
}
|
|
3359
|
+
: {}),
|
|
3224
3360
|
// INTERNAL terminal-abort seams, threaded directly from the
|
|
3225
3361
|
// owning session (NOT on the public extension context).
|
|
3226
3362
|
terminalAbortSeams: {
|
|
@@ -3419,11 +3555,38 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
3419
3555
|
// Fall back to first available model with a valid API key, honoring the
|
|
3420
3556
|
// path-scoped `enabledModels` allow-list when configured. Skip when the
|
|
3421
3557
|
// user explicitly requested a model via --model that wasn't found.
|
|
3422
|
-
if (
|
|
3558
|
+
if (
|
|
3559
|
+
!model &&
|
|
3560
|
+
!options.modelPattern &&
|
|
3561
|
+
!startupCredentialModelRejected &&
|
|
3562
|
+
(configuredDefaultPatterns.length === 0 || preferredCredentialProvider !== undefined)
|
|
3563
|
+
) {
|
|
3423
3564
|
// Re-resolve the allowed set: extension factories above may have
|
|
3424
3565
|
// registered providers/models that weren't visible at startup.
|
|
3425
|
-
const
|
|
3426
|
-
|
|
3566
|
+
const allowedFallbackCandidates = await resolveAllowedModels(modelRegistry, settings, modelMatchPreferences);
|
|
3567
|
+
// A fresh provider discovery can disprove a bundled model while the
|
|
3568
|
+
// general available catalog retains it for offline/profile compatibility.
|
|
3569
|
+
// Exclude only those positively disproved bundled entries from the
|
|
3570
|
+
// unconfigured startup path; explicit model/profile resolution above keeps
|
|
3571
|
+
// its existing precedence and semantics.
|
|
3572
|
+
const profileAvailableKeys = new Set(
|
|
3573
|
+
modelRegistry
|
|
3574
|
+
.getAvailableForProfileActivation()
|
|
3575
|
+
.map(candidate => `${candidate.provider}\u0000${candidate.id}`),
|
|
3576
|
+
);
|
|
3577
|
+
const fallbackCandidates = allowedFallbackCandidates.filter(candidate =>
|
|
3578
|
+
profileAvailableKeys.has(`${candidate.provider}\u0000${candidate.id}`),
|
|
3579
|
+
);
|
|
3580
|
+
// Candidate order is not a quality signal: catalogs sort retired models
|
|
3581
|
+
// ahead of current ones whenever their IDs carry older date suffixes, so
|
|
3582
|
+
// an unconfigured install would otherwise start on a model its provider
|
|
3583
|
+
// has already withdrawn. Sweep each known provider's curated default
|
|
3584
|
+
// first — the same table `findInitialModel` consults — and only then fall
|
|
3585
|
+
// back to catalog order.
|
|
3586
|
+
for (const candidate of orderByProviderDefaultFirst(
|
|
3587
|
+
fallbackCandidates,
|
|
3588
|
+
modelRegistry.automaticProviderOrder(credentialSessionId),
|
|
3589
|
+
)) {
|
|
3427
3590
|
if (await hasModelApiKey(candidate)) {
|
|
3428
3591
|
model = candidate;
|
|
3429
3592
|
break;
|
|
@@ -3730,7 +3893,9 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
3730
3893
|
? await memoryBackend.buildDeveloperInstructions(agentDir, settings, session)
|
|
3731
3894
|
: undefined;
|
|
3732
3895
|
|
|
3733
|
-
const
|
|
3896
|
+
const browserBackend = resolveBrowserBackend(settings);
|
|
3897
|
+
const appendPrompt =
|
|
3898
|
+
[memoryInstructions, browserBackend.developerInstructions].filter(Boolean).join("\n\n") || undefined;
|
|
3734
3899
|
let pluginSystemAppendices = "";
|
|
3735
3900
|
try {
|
|
3736
3901
|
pluginSystemAppendices = await renderAlwaysOnSystemAppendices({ cwd: getLiveCwd() });
|
|
@@ -3775,16 +3940,20 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
3775
3940
|
if (hasSession) session.configWarnings.push(warning);
|
|
3776
3941
|
logger.warn("Context file discovery warning", { warning });
|
|
3777
3942
|
}
|
|
3943
|
+
// The master doctrine block is appended exactly once, AFTER any custom
|
|
3944
|
+
// system-prompt transformation, and only for the master session itself.
|
|
3945
|
+
const withMasterGuidance = (built: BuildSystemPromptResult): BuildSystemPromptResult =>
|
|
3946
|
+
masterModeContext ? { ...built, systemPrompt: [...built.systemPrompt, masterModeTemplate.trim()] } : built;
|
|
3778
3947
|
if (options.systemPrompt === undefined) {
|
|
3779
|
-
return defaultPrompt;
|
|
3948
|
+
return withMasterGuidance(defaultPrompt);
|
|
3780
3949
|
}
|
|
3781
3950
|
if (Array.isArray(options.systemPrompt)) {
|
|
3782
|
-
return { systemPrompt: options.systemPrompt, warnings: defaultPrompt.warnings };
|
|
3951
|
+
return withMasterGuidance({ systemPrompt: options.systemPrompt, warnings: defaultPrompt.warnings });
|
|
3783
3952
|
}
|
|
3784
|
-
return {
|
|
3953
|
+
return withMasterGuidance({
|
|
3785
3954
|
systemPrompt: options.systemPrompt(defaultPrompt.systemPrompt),
|
|
3786
3955
|
warnings: defaultPrompt.warnings,
|
|
3787
|
-
};
|
|
3956
|
+
});
|
|
3788
3957
|
};
|
|
3789
3958
|
|
|
3790
3959
|
const toolNamesFromRegistry = Array.from(toolRegistry.keys());
|
|
@@ -4107,7 +4276,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
4107
4276
|
transformContext,
|
|
4108
4277
|
steeringMode: settings.get("steeringMode") ?? "one-at-a-time",
|
|
4109
4278
|
followUpMode: settings.get("followUpMode") ?? "one-at-a-time",
|
|
4110
|
-
|
|
4279
|
+
toolInterruptPolicy: settings.get("toolInterruptPolicy") ?? "abort_tools",
|
|
4111
4280
|
thinkingBudgets: settings.getGroup("thinkingBudgets"),
|
|
4112
4281
|
temperature: settings.get("temperature") >= 0 ? settings.get("temperature") : undefined,
|
|
4113
4282
|
topP: settings.get("topP") >= 0 ? settings.get("topP") : undefined,
|
|
@@ -4128,13 +4297,19 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
4128
4297
|
preferWebsockets: preferOpenAICodexWebsockets,
|
|
4129
4298
|
getToolContext: tc => toolContextStore.getContext(tc),
|
|
4130
4299
|
getApiKey: async provider => {
|
|
4131
|
-
//
|
|
4132
|
-
//
|
|
4133
|
-
|
|
4134
|
-
|
|
4300
|
+
// AgentLoop asks by provider, but the active model carries the
|
|
4301
|
+
// model-scoped credential selector. Read it at call time so model
|
|
4302
|
+
// changes are honored after /new, fork, resume, or branch switches.
|
|
4303
|
+
const liveModel = sessionAgent?.state.model ?? model;
|
|
4304
|
+
const key =
|
|
4305
|
+
liveModel?.provider === provider
|
|
4306
|
+
? await modelRegistry.getApiKey(liveModel, credentialSessionId)
|
|
4307
|
+
: undefined;
|
|
4308
|
+
const providerKey = key ?? (await modelRegistry.getApiKeyForProvider(provider, credentialSessionId));
|
|
4309
|
+
if (!providerKey) {
|
|
4135
4310
|
throw new Error(`No API key found for provider "${provider}"`);
|
|
4136
4311
|
}
|
|
4137
|
-
return
|
|
4312
|
+
return providerKey;
|
|
4138
4313
|
},
|
|
4139
4314
|
getAuthCredentialType: provider => modelRegistry.getSessionCredentialType(provider, credentialSessionId),
|
|
4140
4315
|
streamFn: async (streamModel, context, streamOptions) => {
|
|
@@ -4221,6 +4396,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
4221
4396
|
telemetry: options.telemetry,
|
|
4222
4397
|
appendOnlyContext,
|
|
4223
4398
|
});
|
|
4399
|
+
sessionAgent = agent;
|
|
4224
4400
|
|
|
4225
4401
|
cursorEventEmitter = event => agent.emitExternalEvent(event);
|
|
4226
4402
|
|
|
@@ -4343,6 +4519,31 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
4343
4519
|
// carried by the host replay ring through the internal runtime seam above.
|
|
4344
4520
|
if (autoroutingInactive) session.configWarnings.push(AUTOROUTING_INACTIVE_WARNING);
|
|
4345
4521
|
hasSession = true;
|
|
4522
|
+
if (masterModeContext) {
|
|
4523
|
+
// One scoped, no-probe peer snapshot immediately before the FIRST accepted
|
|
4524
|
+
// provider request; see createMasterPeerSnapshotContributor for the
|
|
4525
|
+
// discard/reuse/no-duplicate semantics.
|
|
4526
|
+
const capturedSession = session;
|
|
4527
|
+
session.registerBeforeAgentStartContributor(
|
|
4528
|
+
createMasterPeerSnapshotContributor({
|
|
4529
|
+
lifecycle: createSessionLifecycleService(agentDir),
|
|
4530
|
+
ownerSessionId: masterModeContext.ownerSessionId,
|
|
4531
|
+
getSessionId: () => sessionManager.getSessionId(),
|
|
4532
|
+
scope: masterModeContext.scope,
|
|
4533
|
+
getCwd: getLiveCwd,
|
|
4534
|
+
hasPersistedInjection: () =>
|
|
4535
|
+
capturedSession.sessionManager
|
|
4536
|
+
.getBranch()
|
|
4537
|
+
.some(
|
|
4538
|
+
entry =>
|
|
4539
|
+
entry.type === "message" &&
|
|
4540
|
+
(entry.message as { customType?: string }).customType === MASTER_PEER_SNAPSHOT_CUSTOM_TYPE,
|
|
4541
|
+
),
|
|
4542
|
+
onError: error =>
|
|
4543
|
+
logger.debug("master peer snapshot injection failed", { error: safeErrorForLog(error) }),
|
|
4544
|
+
}),
|
|
4545
|
+
);
|
|
4546
|
+
}
|
|
4346
4547
|
const sessionAsyncJobManager = asyncJobManager;
|
|
4347
4548
|
if (sessionAsyncJobManager) {
|
|
4348
4549
|
session.yieldQueue.register<AsyncResultEntry>("async-result", {
|
|
@@ -4373,38 +4574,49 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
4373
4574
|
|
|
4374
4575
|
// Attach the live session to the pre-registered ref so peers can route IRC
|
|
4375
4576
|
// messages here. Refresh sessionFile in case it was unavailable at pre-register
|
|
4376
|
-
// time. The
|
|
4577
|
+
// time. The registered terminal cleanup below unregisters on teardown.
|
|
4377
4578
|
agentRegistry.attachSession(resolvedAgentId, session, sessionManager.getSessionFile() ?? null);
|
|
4378
4579
|
{
|
|
4379
|
-
|
|
4380
|
-
|
|
4381
|
-
|
|
4382
|
-
|
|
4383
|
-
|
|
4580
|
+
let cleanupPromise: Promise<void> | undefined;
|
|
4581
|
+
const finishCleanup = async (): Promise<void> => {
|
|
4582
|
+
if (cleanupPromise) return cleanupPromise;
|
|
4583
|
+
const cleanup = (async () => {
|
|
4584
|
+
const failures: unknown[] = [];
|
|
4384
4585
|
try {
|
|
4385
4586
|
agentRegistry.unregister(resolvedAgentId);
|
|
4386
4587
|
releaseCredentialDisabledSubscription();
|
|
4387
4588
|
releaseLocalProtocolOverride();
|
|
4589
|
+
} catch (error) {
|
|
4590
|
+
failures.push(error);
|
|
4388
4591
|
} finally {
|
|
4389
|
-
|
|
4390
|
-
|
|
4391
|
-
|
|
4392
|
-
|
|
4393
|
-
|
|
4394
|
-
|
|
4395
|
-
|
|
4396
|
-
|
|
4397
|
-
|
|
4398
|
-
|
|
4399
|
-
|
|
4400
|
-
);
|
|
4401
|
-
|
|
4592
|
+
try {
|
|
4593
|
+
if (!options.parentTaskPrefix) {
|
|
4594
|
+
retireOwnedRegistrationsForEndpoint(
|
|
4595
|
+
AsyncJobManager.endpointIdOf(asyncJobManager) ?? asyncJobEndpointId,
|
|
4596
|
+
);
|
|
4597
|
+
AsyncJobManager.unregisterManager(asyncJobManager);
|
|
4598
|
+
}
|
|
4599
|
+
} catch (error) {
|
|
4600
|
+
failures.push(error);
|
|
4601
|
+
}
|
|
4602
|
+
try {
|
|
4603
|
+
await closeOwnedAuthStorage();
|
|
4604
|
+
} catch (error) {
|
|
4605
|
+
failures.push(error);
|
|
4606
|
+
}
|
|
4607
|
+
try {
|
|
4608
|
+
await closeOwnedSettings();
|
|
4609
|
+
} catch (error) {
|
|
4610
|
+
failures.push(error);
|
|
4402
4611
|
}
|
|
4403
|
-
await closeOwnedAuthStorage();
|
|
4404
|
-
await closeOwnedSettings();
|
|
4405
4612
|
}
|
|
4406
|
-
|
|
4613
|
+
if (failures.length > 0) throw new AggregateError(failures, "SDK disposal cleanup failed.");
|
|
4614
|
+
})();
|
|
4615
|
+
cleanupPromise = cleanup;
|
|
4616
|
+
void cleanup.catch(() => {});
|
|
4617
|
+
return cleanup;
|
|
4407
4618
|
};
|
|
4619
|
+
session.registerToolSessionCleanup(finishCleanup);
|
|
4408
4620
|
}
|
|
4409
4621
|
|
|
4410
4622
|
if (model?.api === "openai-codex-responses") {
|
|
@@ -4632,7 +4844,16 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
4632
4844
|
let cleanupDiagnostic: unknown;
|
|
4633
4845
|
try {
|
|
4634
4846
|
if (hasSession) {
|
|
4635
|
-
|
|
4847
|
+
try {
|
|
4848
|
+
await session.dispose();
|
|
4849
|
+
} catch (disposeError) {
|
|
4850
|
+
if (!isSessionDisposalIncompleteError(disposeError)) {
|
|
4851
|
+
throw disposeError;
|
|
4852
|
+
}
|
|
4853
|
+
// The first call is intentionally bounded for startup callers. Join the
|
|
4854
|
+
// retained teardown before releasing the resources it still owns.
|
|
4855
|
+
await session.awaitDisposeCompletion();
|
|
4856
|
+
}
|
|
4636
4857
|
} else {
|
|
4637
4858
|
if (hasRegistered) agentRegistry.unregister(resolvedAgentId);
|
|
4638
4859
|
// Admission happens before session construction. Any later startup
|