From e1b0e675b9285e84348282268909d30feb6f4ca4 Mon Sep 17 00:00:00 2001 From: AstroHan Date: Wed, 19 Aug 2026 20:09:58 +0800 Subject: [PATCH 1/7] refactor: retire 'fake' from the live backend surface MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `BackendKind` was doing two jobs: naming the backend a build may select, and typing the backend value read back from durable state. The in-process FakeBackend stopped shipping in #3226, so the first job narrows to `'ai-sdk'`; the second becomes `PersistedBackendKind`, which still admits `'fake'` and now types the session header, the session-catalog wire projection, the run header, the Automation template, the workspace defaults, and the backend registry that dispatches off them. No data migration. Sessions, runs and Automations written by builds that shipped FakeBackend keep `'fake'` on disk: narrowing the decode guards would make those rows read back as malformed, and rewriting them to `'ai-sdk'` would make an unrunnable task look runnable, since their `llmConnectionSlug` still points at nothing. Activation already refuses them with the product's `fake_backend` reason. `backendKindOf` no longer answers `'fake'` for an unrecognized `providerType` — it throws. That fallback was the last live producer of the value, and there is no honest backend to name for a provider this build cannot describe; the non-throwing question ("can this connection be used?") is `isRealConnection` / `isConnectionReady`. With no provider declaring `'fake'`, the former `isFakeBackend` collapses to "is this providerType one the build knows", so it is named for what it tests. The send projection's dead `slug === 'fake'` branches go with it: a session carrying that slug is refused one line earlier by its backend, and a connection carrying it is refused by the readiness gate. Also deletes `apps/desktop/src/main/chat-readiness.ts`. Its send gate — `requireReadyConnection`, `assertSessionCanSend`, `ensureSessionCanSendOrRebind`, and a third copy of the Chinese connection error copy — had no caller left once the gate moved to Runtime Host; the only live import was the one-line `errorMessage` helper, now inlined at its single use site. Part of #3211 Generated-by: Claude Code --- apps/desktop/src/main/search/thread-search.ts | 12 ++++--- .../src/renderer/model-catalog-choices.ts | 7 ++--- .../settings/provider-connection-detail.tsx | 2 +- .../src/__tests__/llm-connections.test.ts | 7 ++++- packages/core/src/agent-run.ts | 12 ++++--- packages/core/src/backend-types.ts | 4 +-- packages/core/src/chat-model-choice.ts | 2 +- packages/core/src/connection-readiness.ts | 31 +++++++++---------- packages/core/src/llm-connections.ts | 18 ++++++++--- packages/core/src/model-catalog.ts | 2 +- packages/core/src/provider-auth.ts | 2 +- packages/core/src/runtime-inputs.ts | 4 +-- packages/core/src/scheduled-task.ts | 7 +++-- packages/core/src/session-send-projection.ts | 18 +++++------ packages/core/src/session.ts | 27 ++++++++++++++-- packages/core/src/workspace.ts | 4 +-- .../src/protocol/scheduled-task.ts | 11 +++++-- .../src/protocol/session-catalog.ts | 6 +++- packages/runtime/src/model-fetcher.ts | 2 +- packages/runtime/src/model-runtime.ts | 2 +- packages/runtime/src/session-manager.ts | 12 +++---- packages/runtime/src/test-connection.ts | 2 +- .../runtime/src/test-only/fake-backend.ts | 4 +-- .../src/__tests__/session-store.test.ts | 26 ++++++++++++++++ packages/storage/src/session-store.ts | 9 ++++-- 25 files changed, 158 insertions(+), 75 deletions(-) diff --git a/apps/desktop/src/main/search/thread-search.ts b/apps/desktop/src/main/search/thread-search.ts index c8b4e39fd5..9c00cc29a0 100644 --- a/apps/desktop/src/main/search/thread-search.ts +++ b/apps/desktop/src/main/search/thread-search.ts @@ -13,8 +13,9 @@ * UserMessage / AssistantMessage / ToolCallMessage / ToolResultMessage. * Excluded: SystemNoteMessage / TokenUsageMessage / TurnStateMessage / * PermissionDecisionMessage. - * - Excludes sessions with `backend === 'fake'` (e2e-fixture fixtures) and - * archived sessions (managed in Settings › 活动 › 已归档任务). + * - Excludes sessions with `backend === 'fake'` (retired local simulation, + * plus the e2e fixtures that still seed it) and archived sessions + * (managed in Settings › 活动 › 已归档任务). * - Snippets are redacted via `@maka/core/redaction.redactSecrets()`. * - `ToolResultMessage.content` is JSON-serialized for scan and capped to * the first `TOOL_RESULT_SCAN_CAP_BYTES` bytes (worst-case bound). @@ -160,8 +161,11 @@ export async function runThreadSearch( const maxResults = limitResult.value; const sessions = collapseSessionRevisions(await deps.listSessions()) - // Exclude fake-backend sessions — e2e-fixture fixtures and - // similar dev-only state should not surface as real chat hits. + // Exclude fake-backend sessions. The rail still shows them (marked + // stale) because they are task records, but their transcripts are + // simulator output — returning fabricated text as a hit on the user's own + // history is worse than returning nothing. Retiring the backend (#3211) + // did not make that content real, so the filter stays. // // Archived tasks are excluded for the same reason the command palette // skips them: archiving a task says it is out of the working set, and a diff --git a/apps/desktop/src/renderer/model-catalog-choices.ts b/apps/desktop/src/renderer/model-catalog-choices.ts index e2bba7c712..90f2058a2c 100644 --- a/apps/desktop/src/renderer/model-catalog-choices.ts +++ b/apps/desktop/src/renderer/model-catalog-choices.ts @@ -138,13 +138,10 @@ function dailyReviewModelDisplayLabel( } function isModelConsumerConnection(connection: Pick): boolean { - const defaults = PROVIDER_DEFAULTS[connection.providerType]; // Unknown providerType (legacy seed, or a connection persisted on a branch // that registers a provider this build doesn't know) → not a model consumer. - // Mirrors `isFakeBackend` in connection-readiness.ts; without this guard the - // `.backendKind` read below throws on load for an orphan connection. - if (!connection.enabled || !defaults || defaults.backendKind !== 'ai-sdk') return false; - return true; + // Mirrors `isRealConnection` in connection-readiness.ts. + return connection.enabled && PROVIDER_DEFAULTS[connection.providerType] !== undefined; } function enabledProviderCounts(connections: readonly LlmConnection[]): Map { diff --git a/apps/desktop/src/renderer/settings/provider-connection-detail.tsx b/apps/desktop/src/renderer/settings/provider-connection-detail.tsx index 1e8bc22210..886e273ecf 100644 --- a/apps/desktop/src/renderer/settings/provider-connection-detail.tsx +++ b/apps/desktop/src/renderer/settings/provider-connection-detail.tsx @@ -61,7 +61,7 @@ export function ConnectionDetail(props: ConnectionDetailProps) { // Unknown providerType (a connection persisted on a branch that registers a // provider this build doesn't know) → render a non-actionable fallback so // opening the orphan connection doesn't crash on `.authKind`/`.baseUrl`. - // Mirrors `isFakeBackend` in @maka/core/connection-readiness.ts. + // Mirrors `isRealConnection` in @maka/core/connection-readiness.ts. if (!defaults) return ; return ; } diff --git a/packages/core/src/__tests__/llm-connections.test.ts b/packages/core/src/__tests__/llm-connections.test.ts index e6cf144aed..347dc7c35d 100644 --- a/packages/core/src/__tests__/llm-connections.test.ts +++ b/packages/core/src/__tests__/llm-connections.test.ts @@ -17,6 +17,7 @@ import { validateConnectionBaseUrl, type ProviderType, } from '../llm-connections.js'; +import { isRealConnection } from '../connection-readiness.js'; test('connection base URLs allow HTTP(S) and reject unsafe or malformed inputs', () => { assert.equal(validateConnectionBaseUrl(undefined), null); @@ -58,7 +59,11 @@ test('base URL normalization preserves clear intent and rejects untrusted runtim test('unknown provider ids fail closed without breaking persisted connections', () => { const unknown = 'branch-only-provider' as ProviderType; - assert.equal(backendKindOf({ providerType: unknown }), 'fake'); + // `backendKindOf` no longer invents a backend for a provider this build + // cannot describe (#3211); the readiness projection is the non-throwing + // answer to "can this connection be used?". + assert.throws(() => backendKindOf({ providerType: unknown }), /Unknown providerType/); + assert.equal(isRealConnection({ providerType: unknown }), false); assert.equal( effectiveBaseUrl({ providerType: unknown, baseUrl: 'https://example.test/v1' }), 'https://example.test/v1', diff --git a/packages/core/src/agent-run.ts b/packages/core/src/agent-run.ts index d793670ba4..1d4459df4a 100644 --- a/packages/core/src/agent-run.ts +++ b/packages/core/src/agent-run.ts @@ -8,7 +8,7 @@ import { type EffectiveOrchestrationSource, type OrchestrationMode, } from './orchestration.js'; -import type { BackendKind } from './session.js'; +import type { PersistedBackendKind } from './session.js'; import { defineObjectShape, hasExactShape, @@ -129,7 +129,7 @@ export interface AgentRunHeader { sessionId: string; turnId: string; status: AgentRunStatus; - backendKind: BackendKind; + backendKind: PersistedBackendKind; llmConnectionSlug: string; modelId: string; cwd: string; @@ -593,7 +593,7 @@ export function decodeAgentRunHeader(value: unknown): AgentRunHeader { typeof value.sessionId === 'string' && typeof value.turnId === 'string' && (AGENT_RUN_STATUSES as readonly unknown[]).includes(status) && - isBackendKind(value.backendKind) && + isPersistedBackendKind(value.backendKind) && typeof value.llmConnectionSlug === 'string' && typeof value.modelId === 'string' && typeof value.cwd === 'string' && @@ -708,7 +708,11 @@ export function decodeAgentRunEvent(value: unknown): AgentRunEvent { return value as unknown as AgentRunEvent; } -function isBackendKind(value: unknown): value is BackendKind { +/** + * Decode guard for a durable run header. `'fake'` stays accepted: runs written + * by builds that shipped FakeBackend must keep decoding (#3211). + */ +function isPersistedBackendKind(value: unknown): value is PersistedBackendKind { return value === 'ai-sdk' || value === 'fake'; } diff --git a/packages/core/src/backend-types.ts b/packages/core/src/backend-types.ts index c42648b50b..d62adb4952 100644 --- a/packages/core/src/backend-types.ts +++ b/packages/core/src/backend-types.ts @@ -19,7 +19,7 @@ import type { import type { InteractionClosureReason } from './interaction.js'; import type { RuntimeEvent } from './runtime-event.js'; import type { SandboxBoundaryResponse, SandboxBoundarySettlement } from './sandbox-boundary.js'; -import type { StoredMessage, BackendKind } from './session.js'; +import type { StoredMessage, PersistedBackendKind } from './session.js'; import type { UserQuestionResponse } from './user-question.js'; import type { ContextBudgetDiagnostic } from './usage-stats/types.js'; import type { EffectiveOrchestration } from './orchestration.js'; @@ -181,7 +181,7 @@ export type BackendSessionEvent = Exclude< >; export interface AgentBackend { - readonly kind: BackendKind; + readonly kind: PersistedBackendKind; readonly sessionId: string; send(input: BackendSendInput): AsyncIterable; compactHistory?(input: BackendCompactHistoryInput): Promise; diff --git a/packages/core/src/chat-model-choice.ts b/packages/core/src/chat-model-choice.ts index aa886b275b..30cb6c75fc 100644 --- a/packages/core/src/chat-model-choice.ts +++ b/packages/core/src/chat-model-choice.ts @@ -40,7 +40,7 @@ export function buildChatModelChoices(connections: readonly LlmConnection[]): Ch for (const rawConnection of connections) { const connection = normalizeOpenAiCodexConnection(rawConnection); const provider = PROVIDER_DEFAULTS[connection.providerType]; - if (!connection.enabled || !provider || provider.backendKind !== 'ai-sdk') { + if (!connection.enabled || !provider) { continue; } const enabledModelIds = new Set(connectionEnabledModelIds(connection)); diff --git a/packages/core/src/connection-readiness.ts b/packages/core/src/connection-readiness.ts index 4e0b4d630f..6d701d53f2 100644 --- a/packages/core/src/connection-readiness.ts +++ b/packages/core/src/connection-readiness.ts @@ -9,7 +9,7 @@ * touches the credential store, filesystem, or IPC. * * The single helper here is the only place these criteria live: - * - real backend (not `fake`) + * - the provider is one this build knows * - `enabled === true` * - has usable secret OR provider's `authKind === 'none'` * - effective model exists (caller's `requestedModel` if provided, @@ -84,7 +84,7 @@ export interface IsConnectionReadyInput { * contract change. * * Order: - * 1. backend is `fake` → `fake_backend` + * 1. `providerType` is not in the registry → `fake_backend` * 2. `enabled === false` → `connection_disabled` * 3. `authKind !== 'none' && !hasSecret` → `missing_api_key` * 4. effective model is empty/missing → `missing_model` @@ -99,7 +99,7 @@ export interface IsConnectionReadyInput { export function isConnectionReady(input: IsConnectionReadyInput): IsConnectionReadyResult { const { connection, hasSecret, requestedModel } = input; - if (isFakeBackend(connection)) { + if (!isKnownProvider(connection)) { return { ready: false, reason: 'fake_backend' }; } if (!connection.enabled) { @@ -167,23 +167,22 @@ export function normalizeOpenAiCodexConnection(connection: LlmConnection): LlmCo } /** - * Whether a connection is backed by a real LLM provider (anything - * whose `backendKind === 'ai-sdk'`), as opposed to the in-process - * `fake` backend or an unrecognized legacy provider type. + * Whether a connection is backed by a real LLM provider. + * + * Since the in-process `fake` backend was retired (#3211) every registered + * provider runs on `ai-sdk`, so this is exactly "is this `providerType` one + * the build knows". An unknown one (legacy seed, future provider not yet in + * PROVIDER_DEFAULTS) is treated as non-real — onboarding then routes the user + * to the add-provider flow which will rebuild a real connection. * * @kenji PR110a review gate: telemetry / lastTestStatus must NOT - * influence this judgment. A `fake` connection that happens to have - * `lastTestStatus: 'verified'` is still fake. An unknown providerType - * (legacy seed, future provider not yet in PROVIDER_DEFAULTS) is also - * treated as non-real — onboarding then routes the user to the - * add-provider flow which will rebuild a real connection. + * influence this judgment. A connection that cannot describe its provider is + * still unusable when it happens to carry `lastTestStatus: 'verified'`. */ export function isRealConnection(connection: Pick): boolean { - return !isFakeBackend(connection); + return isKnownProvider(connection); } -function isFakeBackend(connection: Pick): boolean { - const defaults = PROVIDER_DEFAULTS[connection.providerType]; - if (!defaults) return true; // unknown providerType → treat as non-real - return defaults.backendKind === 'fake'; +function isKnownProvider(connection: Pick): boolean { + return PROVIDER_DEFAULTS[connection.providerType] !== undefined; } diff --git a/packages/core/src/llm-connections.ts b/packages/core/src/llm-connections.ts index 5f0cf88707..fd7de025b7 100644 --- a/packages/core/src/llm-connections.ts +++ b/packages/core/src/llm-connections.ts @@ -370,11 +370,21 @@ export function providerSupportsModelDiscovery(providerType: ProviderType): bool return discovery !== undefined && discovery.kind !== 'fallback'; } +/** + * The backend that runs a connection. + * + * Throws for an unknown `providerType` (a legacy seed, or a connection + * persisted on a branch that registers a provider this build doesn't know). + * It used to answer `'fake'` there, which was the last live producer of that + * value (#3211); there is no honest backend to name for a provider this build + * cannot describe. Callers that need a non-throwing answer are asking whether + * the connection is usable, not which backend runs it — use `isRealConnection` + * / `isConnectionReady` from `connection-readiness.ts`. + */ export function backendKindOf(c: Pick): BackendKind { - // Unknown providerType (legacy seed, or a connection persisted on a branch - // that registers a provider this build doesn't know) → treat as non-real, - // matching `isFakeBackend` in connection-readiness.ts. - return PROVIDER_DEFAULTS[c.providerType]?.backendKind ?? 'fake'; + const defaults = PROVIDER_DEFAULTS[c.providerType]; + if (!defaults) throw new Error(`Unknown providerType: ${c.providerType}`); + return defaults.backendKind; } export function effectiveBaseUrl(c: Pick): string { diff --git a/packages/core/src/model-catalog.ts b/packages/core/src/model-catalog.ts index bbb789cb0b..060fa2a1ba 100644 --- a/packages/core/src/model-catalog.ts +++ b/packages/core/src/model-catalog.ts @@ -206,7 +206,7 @@ export function buildConnectionModelCatalogEntries( const defaults = PROVIDER_DEFAULTS[connection.providerType]; // Unknown providerType (legacy seed, or a connection persisted on a branch // that registers a provider this build doesn't know) → no catalog entries. - // Mirrors `isFakeBackend` in connection-readiness.ts. + // Mirrors `isRealConnection` in connection-readiness.ts. if (!defaults) return []; const supportsModelDiscovery = providerSupportsModelDiscovery(connection.providerType); const catalogFallbackModels = curatedCatalogFallbackModelsForProvider(connection.providerType); diff --git a/packages/core/src/provider-auth.ts b/packages/core/src/provider-auth.ts index 0c78455f54..ab13f8cce6 100644 --- a/packages/core/src/provider-auth.ts +++ b/packages/core/src/provider-auth.ts @@ -66,7 +66,7 @@ export function deriveProviderAuthContract(input: ProviderAuthContractInput): Pr // Unknown providerType (legacy seed, or a connection persisted on a branch // that registers a provider this build doesn't know) → surface a non-real, // non-actionable contract so the settings row renders instead of crashing. - // Mirrors `isFakeBackend` in connection-readiness.ts. + // Mirrors `isRealConnection` in connection-readiness.ts. if (!defaults) { return { providerType: input.providerType, diff --git a/packages/core/src/runtime-inputs.ts b/packages/core/src/runtime-inputs.ts index 6104b785bf..06c249fd65 100644 --- a/packages/core/src/runtime-inputs.ts +++ b/packages/core/src/runtime-inputs.ts @@ -4,7 +4,7 @@ import type { MessageContent } from './events.js'; import type { - BackendKind, + PersistedBackendKind, SessionBlockedReason, SessionStatus, SessionToolProfile, @@ -35,7 +35,7 @@ export interface CreateSessionInput { projectId?: string | null; /** If omitted, runtime auto-derives a placeholder; users may rename later. */ name?: string; - backend: BackendKind; + backend: PersistedBackendKind; llmConnectionSlug: string; /** Falls back to the connection's defaultModel if omitted. */ model?: string; diff --git a/packages/core/src/scheduled-task.ts b/packages/core/src/scheduled-task.ts index 18c1ccddea..b556128620 100644 --- a/packages/core/src/scheduled-task.ts +++ b/packages/core/src/scheduled-task.ts @@ -11,7 +11,7 @@ import { isOrchestrationMode, type OrchestrationMode } from './orchestration.js' import { isThinkingLevel, type ThinkingLevel } from './model-thinking.js'; import { isPermissionMode, type PermissionMode } from './permission.js'; import { isBotDeliveryProvider, type BotProvider } from './bot-chat-settings.js'; -import type { BackendKind } from './session.js'; +import type { PersistedBackendKind } from './session.js'; export const SCHEDULED_TASK_TITLE_MAX_CHARS = 120; export const SCHEDULED_TASK_INTENT_MAX_CHARS = 8_000; @@ -52,7 +52,7 @@ export type ScheduledTaskEffect = export interface ScheduledTaskExecutionTemplate { readonly cwd: string; readonly projectId?: string | null; - readonly backend: BackendKind; + readonly backend: PersistedBackendKind; readonly llmConnectionSlug: string; readonly model: string; readonly thinkingLevel?: ThinkingLevel; @@ -489,6 +489,9 @@ function normalizeExecution( if (!isObject(value)) return fail('agent_run requires execution template'); if (typeof value.cwd !== 'string' || !value.cwd.trim()) return fail('execution.cwd is required'); if (typeof value.backend !== 'string') return fail('execution.backend is required'); + // `'fake'` stays accepted on decode: Automations frozen by builds that + // shipped FakeBackend must keep loading (#3211). Activation refuses them + // with the product's `fake_backend` reason. if (value.backend !== 'ai-sdk' && value.backend !== 'fake') { return fail('execution.backend is invalid'); } diff --git a/packages/core/src/session-send-projection.ts b/packages/core/src/session-send-projection.ts index 3958aa312c..33b16b36d3 100644 --- a/packages/core/src/session-send-projection.ts +++ b/packages/core/src/session-send-projection.ts @@ -32,9 +32,9 @@ import type { LlmConnection } from './llm-connections.js'; export interface SessionSendProjectionSession { /** - * Session backend kind. `string` (not `BackendKind`) so legacy on-disk - * values like `'claude'` are surfaced exactly as the JSONL stored them; - * only `'fake'` is special-cased, everything else goes through the + * Session backend kind. `string` (not `PersistedBackendKind`) so legacy + * on-disk values like `'claude'` are surfaced exactly as the JSONL stored + * them; only `'fake'` is special-cased, everything else goes through the * normal connection readiness gate. */ backend: string; @@ -80,7 +80,7 @@ export function projectSessionSendOutcome( } for (const slug of new Set([defaultSlug, ...connections.map((connection) => connection.slug)])) { - if (!slug || slug === 'fake') continue; + if (!slug) continue; const connection = connections.find((entry) => entry.slug === slug); if (!connection) continue; const normalized = normalizeOpenAiCodexConnection(connection); @@ -106,9 +106,12 @@ function sessionOwnConnectionBlockReason( ownConnection: LlmConnection | null, hasSecret: (slug: string) => boolean, ): ChatConfigurationReason | undefined { + // Sessions written by builds that shipped FakeBackend keep `'fake'` on disk + // forever (#3211). They are refused here — and at activation — rather than + // rewritten, because their `llmConnectionSlug` still points at nothing. if (session.backend === 'fake') return 'fake_backend'; const slug = session.llmConnectionSlug; - if (!slug || slug === 'fake') return 'missing_default_connection'; + if (!slug) return 'missing_default_connection'; if (!ownConnection) return 'connection_missing'; const normalized = normalizeOpenAiCodexConnection(ownConnection); const verdict = isConnectionReady({ @@ -124,10 +127,7 @@ function ownConnectionBlockReason( connections: readonly LlmConnection[], hasSecret: (slug: string) => boolean, ): ChatConfigurationReason | undefined { - const own = - session.backend === 'fake' - ? null - : (connections.find((entry) => entry.slug === session.llmConnectionSlug) ?? null); + const own = connections.find((entry) => entry.slug === session.llmConnectionSlug) ?? null; return sessionOwnConnectionBlockReason(session, own, hasSecret); } diff --git a/packages/core/src/session.ts b/packages/core/src/session.ts index 4ea17a49cd..3711a20632 100644 --- a/packages/core/src/session.ts +++ b/packages/core/src/session.ts @@ -239,7 +239,7 @@ export interface SessionHeader { hasUnread: boolean; // Backend / model config - backend: BackendKind; + backend: PersistedBackendKind; llmConnectionSlug: string; /** True after first UserMessage is flushed. Storage self-heals (§5.2). */ connectionLocked: boolean; @@ -266,7 +266,28 @@ export type SessionHeaderPatch = Partial> & { readonly isArchived?: never; }; -export type BackendKind = 'ai-sdk' | 'fake'; +/** + * The backend a live build may select. + * + * `'fake'` was retired with the in-process FakeBackend (#3211): nothing in a + * shipped build may choose it, so it is not a member here. Values read back + * from durable state use {@link PersistedBackendKind} instead. + */ +export type BackendKind = 'ai-sdk'; + +/** + * The backend value a persisted record may carry. + * + * Sessions, runs and Automations written by builds that still shipped + * FakeBackend hold `'fake'` forever. Decode keeps accepting it so those rows + * stay readable — rewriting them to `'ai-sdk'` would only make an unrunnable + * task look runnable, since their `llmConnectionSlug` still points at nothing. + * Activation refuses them with the product's `fake_backend` reason (see the + * refusal registered in `execution-composition.ts`). + * + * Never write this type: writers take {@link BackendKind}. + */ +export type PersistedBackendKind = BackendKind | 'fake'; export interface SessionSummary { id: string; @@ -315,7 +336,7 @@ export interface SessionSummary { revisionOfTurnId?: string; revisionIndex?: number; revisionState?: 'preparing' | 'committed'; - backend: BackendKind; + backend: PersistedBackendKind; llmConnectionSlug: string; /** * True once the session has user messages — its connection/model is diff --git a/packages/core/src/workspace.ts b/packages/core/src/workspace.ts index 3e49e450d4..a7b9f82cf7 100644 --- a/packages/core/src/workspace.ts +++ b/packages/core/src/workspace.ts @@ -2,7 +2,7 @@ * Workspace config types. */ -import type { BackendKind } from './session.js'; +import type { PersistedBackendKind } from './session.js'; import type { PermissionMode } from './permission.js'; export interface WorkspaceConfig { @@ -13,7 +13,7 @@ export interface WorkspaceConfig { createdAt: number; defaults: { permissionMode: PermissionMode; - backend: BackendKind; + backend: PersistedBackendKind; llmConnectionSlug?: string; model?: string; }; diff --git a/packages/runtime-host/src/protocol/scheduled-task.ts b/packages/runtime-host/src/protocol/scheduled-task.ts index 694b87fc37..2d37bb6e8a 100644 --- a/packages/runtime-host/src/protocol/scheduled-task.ts +++ b/packages/runtime-host/src/protocol/scheduled-task.ts @@ -24,7 +24,7 @@ import { type UpdateScheduledTaskInput, } from '@maka/core/scheduled-task'; import { isThinkingLevel } from '@maka/core/model-thinking'; -import type { BackendKind } from '@maka/core/session'; +import type { PersistedBackendKind } from '@maka/core/session'; import { requireCount, requireEncodedByteLimit, @@ -525,7 +525,7 @@ function decodeExecution(value: unknown): ScheduledTaskExecutionTemplate { ], ['projectId', 'thinkingLevel'], ); - if (!isBackendKind(execution.backend)) + if (!isPersistedBackendKind(execution.backend)) throw invalidProtocolFrame('Invalid ScheduledTask backend'); if (!isPermissionMode(execution.permissionMode)) { throw invalidProtocolFrame('Invalid ScheduledTask permission mode'); @@ -635,6 +635,11 @@ function nullablePositiveCount(value: unknown, label: string): number | null { return count; } -function isBackendKind(value: unknown): value is BackendKind { +/** + * Decode guard for a durable Automation template. `'fake'` stays accepted: + * Automations frozen by builds that shipped FakeBackend must keep decoding + * (#3211); activation refuses them with the product's `fake_backend` reason. + */ +function isPersistedBackendKind(value: unknown): value is PersistedBackendKind { return value === 'ai-sdk' || value === 'fake'; } diff --git a/packages/runtime-host/src/protocol/session-catalog.ts b/packages/runtime-host/src/protocol/session-catalog.ts index 353f666a89..2a832de532 100644 --- a/packages/runtime-host/src/protocol/session-catalog.ts +++ b/packages/runtime-host/src/protocol/session-catalog.ts @@ -5,6 +5,7 @@ import { isSessionStartMode, type SessionStartMode } from '@maka/core/explore-ag import { isSessionBlockedReason, isSessionToolProfile, + type PersistedBackendKind, type SessionBlockedReason, type SessionStatus, type SessionSubagentProjection, @@ -213,7 +214,7 @@ export interface SessionCatalogProjection { readonly revisionOfTurnId?: string; readonly revisionIndex?: number; readonly revisionState?: 'preparing' | 'committed'; - readonly backend: 'ai-sdk' | 'fake'; + readonly backend: PersistedBackendKind; readonly llmConnectionSlug: string; readonly connectionLocked: boolean; readonly model: string; @@ -866,6 +867,9 @@ function optionalThinkingLevel( return { thinkingLevel: thinkingLevel(record.thinkingLevel) }; } +// `'fake'` stays accepted on decode: the projection carries the session +// header's durable backend, and rows written by builds that shipped +// FakeBackend still hold it (#3211). function backend(value: unknown): SessionCatalogProjection['backend'] { if (value !== 'ai-sdk' && value !== 'fake') { throw invalidProtocolFrame('Invalid Session backend'); diff --git a/packages/runtime/src/model-fetcher.ts b/packages/runtime/src/model-fetcher.ts index 7a599438dc..2af026c8f1 100644 --- a/packages/runtime/src/model-fetcher.ts +++ b/packages/runtime/src/model-fetcher.ts @@ -164,7 +164,7 @@ async function fetchProviderModelsStrict( const definition = PROVIDER_DEFAULTS[connection.providerType]; // Unknown providerType → no discovery path. Throw a clear error (caught and // generalized by the caller) rather than crashing on `.modelDiscovery`. - // Mirrors `isFakeBackend` in @maka/core/connection-readiness.ts. + // Mirrors `isRealConnection` in @maka/core/connection-readiness.ts. if (!definition) { throw new Error(`Unknown provider type "${connection.providerType}"`); } diff --git a/packages/runtime/src/model-runtime.ts b/packages/runtime/src/model-runtime.ts index 6929c8fb13..39d6cec3b3 100644 --- a/packages/runtime/src/model-runtime.ts +++ b/packages/runtime/src/model-runtime.ts @@ -49,7 +49,7 @@ export function resolveModelRuntime( const defaults = PROVIDER_DEFAULTS[connection.providerType]; // Unknown providerType with no per-model override → can't resolve an adapter. // Throw a clear error rather than crashing on `.runtimeAdapter`. Mirrors - // `isFakeBackend` in @maka/core/connection-readiness.ts. + // `isRealConnection` in @maka/core/connection-readiness.ts. if (!override && !defaults) { throw new Error( `Unknown provider type "${connection.providerType}"; cannot resolve model runtime.`, diff --git a/packages/runtime/src/session-manager.ts b/packages/runtime/src/session-manager.ts index b14b96c9e9..cd6f9d701c 100644 --- a/packages/runtime/src/session-manager.ts +++ b/packages/runtime/src/session-manager.ts @@ -39,7 +39,7 @@ import type { UserMessage, PermissionDecisionMessage, SystemNoteMessage, - BackendKind, + PersistedBackendKind, } from '@maka/core/session'; import type { AgentSpec, @@ -700,7 +700,7 @@ export interface StrictRecoveryStores { } // ============================================================================ -// BackendRegistry — factory dispatch by BackendKind +// BackendRegistry — factory dispatch by the session header's durable backend // ============================================================================ export interface BackendFactoryContext { @@ -772,19 +772,19 @@ export interface BackendFactoryContext { export type BackendFactory = (ctx: BackendFactoryContext) => AgentBackend | Promise; export class BackendRegistry { - private readonly factories = new Map(); + private readonly factories = new Map(); - register(kind: BackendKind, factory: BackendFactory): void { + register(kind: PersistedBackendKind, factory: BackendFactory): void { this.factories.set(kind, factory); } - async build(kind: BackendKind, ctx: BackendFactoryContext): Promise { + async build(kind: PersistedBackendKind, ctx: BackendFactoryContext): Promise { const f = this.factories.get(kind); if (!f) throw new Error(`No backend factory registered for kind="${kind}"`); return await f(ctx); } - has(kind: BackendKind): boolean { + has(kind: PersistedBackendKind): boolean { return this.factories.has(kind); } } diff --git a/packages/runtime/src/test-connection.ts b/packages/runtime/src/test-connection.ts index 723e347962..07277741c1 100644 --- a/packages/runtime/src/test-connection.ts +++ b/packages/runtime/src/test-connection.ts @@ -129,7 +129,7 @@ async function testConnectionStrict( ): Promise { const defaults = PROVIDER_DEFAULTS[connection.providerType]; // Unknown providerType → can't pick an auth path or fallback model. Return a - // clear failure rather than crashing. Mirrors `isFakeBackend`. + // clear failure rather than crashing. Mirrors `isRealConnection`. if (!defaults) { return { ok: false, errorMessage: `Unknown provider type "${connection.providerType}"` }; } diff --git a/packages/runtime/src/test-only/fake-backend.ts b/packages/runtime/src/test-only/fake-backend.ts index 8bbb9552b6..0a8df15488 100644 --- a/packages/runtime/src/test-only/fake-backend.ts +++ b/packages/runtime/src/test-only/fake-backend.ts @@ -1,5 +1,5 @@ import { randomUUID } from 'node:crypto'; -import type { BackendKind, SessionHeader, StoredMessage } from '@maka/core/session'; +import type { PersistedBackendKind, SessionHeader, StoredMessage } from '@maka/core/session'; import type { SessionEvent } from '@maka/core/events'; import type { AgentBackend, @@ -46,7 +46,7 @@ type PendingSandboxBoundary = { }; export class FakeBackend implements AgentBackend { - readonly kind: BackendKind = 'fake'; + readonly kind: PersistedBackendKind = 'fake'; readonly sessionId: string; private stopped = false; private pendingQuestion: PendingQuestion | undefined; diff --git a/packages/storage/src/__tests__/session-store.test.ts b/packages/storage/src/__tests__/session-store.test.ts index ce45685b92..703ade6e05 100644 --- a/packages/storage/src/__tests__/session-store.test.ts +++ b/packages/storage/src/__tests__/session-store.test.ts @@ -999,6 +999,32 @@ describe('SQLite SessionStore', () => { } }); + test('reads back a legacy fake-backend session instead of migrating or rejecting it', async () => { + // #3211: `'fake'` was retired as a live backend but never migrated out of + // storage. Narrowing the header validator would make these rows decode as + // malformed and rewriting them to `'ai-sdk'` would make an unrunnable task + // look runnable, since `llmConnectionSlug` still points at nothing. + const root = await mkdtemp(join(tmpdir(), 'maka-session-legacy-fake-')); + const store = createSessionStore(root); + try { + const session = await store.create(makeInput({ backend: 'fake', llmConnectionSlug: 'fake' })); + assert.equal(session.backend, 'fake'); + assert.equal((await store.readHeaderSnapshot(session.id)).backend, 'fake'); + } finally { + await store.close?.(); + } + + const reopened = createSessionStore(root); + try { + const [header] = await reopened.listHeaders(); + assert.equal(header?.backend, 'fake'); + assert.equal(header?.llmConnectionSlug, 'fake'); + } finally { + await reopened.close?.(); + await rm(root, { recursive: true, force: true }); + } + }); + test('deletes metadata and messages through the same transaction boundary', async () => { const root = await mkdtemp(join(tmpdir(), 'maka-session-delete-')); const store = createSessionStore(root); diff --git a/packages/storage/src/session-store.ts b/packages/storage/src/session-store.ts index 3664a3fa32..a8370c1ad2 100644 --- a/packages/storage/src/session-store.ts +++ b/packages/storage/src/session-store.ts @@ -1090,7 +1090,7 @@ export function normalizeSessionHeader( isValidSessionExternalOrigin(header.externalOrigin) && (header.lastReadMessageId === undefined || typeof header.lastReadMessageId === 'string') && typeof header.hasUnread === 'boolean' && - isBackendKind(header.backend) && + isPersistedBackendKind(header.backend) && typeof header.llmConnectionSlug === 'string' && typeof header.connectionLocked === 'boolean' && typeof header.model === 'string' && @@ -1220,7 +1220,12 @@ function isValidSubagentSessionLineage(header: SessionHeader): boolean { ); } -function isBackendKind(value: unknown): value is SessionHeader['backend'] { +/** + * Decode guard for a durable session header. `'fake'` stays accepted: + * narrowing it here would make every session written by a build that shipped + * FakeBackend fail `normalizeSessionHeader` and read back as malformed (#3211). + */ +function isPersistedBackendKind(value: unknown): value is SessionHeader['backend'] { return value === 'ai-sdk' || value === 'fake'; } From 3356b6ce18cda2c7281a270fe7e43d1050c7982d Mon Sep 17 00:00:00 2001 From: AstroHan Date: Wed, 19 Aug 2026 20:50:43 +0800 Subject: [PATCH 2/7] fix(desktop): stop the pending chat view from claiming a retired backend MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The placeholder `SessionSummary` the chat view shows between "a session id became active" and "its real summary arrived" was hardcoded to `backend: 'fake'` / `model: 'fake-model'` / `llmConnectionSlug: 'default'`, directly above a comment claiming it matched the configured default. It did not. The composer reads those fields straight off this object — the model switcher's current value and the quote companion's inherited-model line — so the switcher matched no offered choice and the companion named a model no session ever had. The `'fake'` there was also the last live write of the retired backend value in product code, standing in for "not loaded yet". The placeholder now carries what the next new task would start on, falling back to the default connection slug and then to no model, which is what the comment always claimed. Extracted to `pending-session-view.ts` so the shape is testable outside the shell component. Closes #3211 Generated-by: Claude Code --- .../__tests__/pending-session-view.test.ts | 46 +++++++++++++++++++ apps/desktop/src/renderer/app-shell.tsx | 25 ++++------ .../src/renderer/pending-session-view.ts | 39 ++++++++++++++++ 3 files changed, 94 insertions(+), 16 deletions(-) create mode 100644 apps/desktop/src/main/__tests__/pending-session-view.test.ts create mode 100644 apps/desktop/src/renderer/pending-session-view.ts diff --git a/apps/desktop/src/main/__tests__/pending-session-view.test.ts b/apps/desktop/src/main/__tests__/pending-session-view.test.ts new file mode 100644 index 0000000000..f8e9d696c0 --- /dev/null +++ b/apps/desktop/src/main/__tests__/pending-session-view.test.ts @@ -0,0 +1,46 @@ +import assert from 'node:assert/strict'; +import test from 'node:test'; +import { pendingSessionView } from '../../renderer/pending-session-view.js'; + +test('the pending chat view starts on the model the next new task would use', () => { + const view = pendingSessionView({ + sessionId: 'session-1', + name: '新任务', + permissionMode: 'ask', + newChatModel: { llmConnectionSlug: 'anthropic', model: 'claude-sonnet-4-5-20250929' }, + defaultConnectionSlug: 'openai', + }); + + // #3211: the placeholder used to claim `backend: 'fake'` / `model: + // 'fake-model'`, so the model switcher matched no choice and the quote + // companion targeted a model that never existed. + assert.equal(view.backend, 'ai-sdk'); + assert.equal(view.llmConnectionSlug, 'anthropic'); + assert.equal(view.model, 'claude-sonnet-4-5-20250929'); + assert.equal(view.id, 'session-1'); + assert.equal(view.permissionMode, 'ask'); + assert.equal(view.connectionLocked, false); +}); + +test('the pending chat view falls back to the default connection, then to no model', () => { + const withDefault = pendingSessionView({ + sessionId: 'session-2', + name: '新任务', + permissionMode: 'execute', + newChatModel: undefined, + defaultConnectionSlug: 'openai', + }); + assert.equal(withDefault.llmConnectionSlug, 'openai'); + assert.equal(withDefault.model, ''); + + const withNothing = pendingSessionView({ + sessionId: 'session-3', + name: '新任务', + permissionMode: 'ask', + newChatModel: undefined, + defaultConnectionSlug: null, + }); + assert.equal(withNothing.backend, 'ai-sdk'); + assert.equal(withNothing.llmConnectionSlug, ''); + assert.equal(withNothing.model, ''); +}); diff --git a/apps/desktop/src/renderer/app-shell.tsx b/apps/desktop/src/renderer/app-shell.tsx index d16786aee4..410a57d33d 100644 --- a/apps/desktop/src/renderer/app-shell.tsx +++ b/apps/desktop/src/renderer/app-shell.tsx @@ -125,6 +125,7 @@ import { useShellAppearance } from './use-shell-appearance'; import { useShellSearch } from './use-shell-search'; import { useSessionGoal } from './use-session-goal'; import { deriveStaleSessionIds } from './stale-sessions'; +import { pendingSessionView } from './pending-session-view'; import { deriveProjectGroups, deriveWorktreeSessionIds } from './session-project-grouping'; import { deriveSessionRail } from './session-rail'; import { useAppShellTurnPresentation } from './app-shell-turn-view-model'; @@ -1270,26 +1271,18 @@ function AppShellContent({ }; }, [railParentSession]); + // Transient placeholder while the real SessionSummary loads, so the composer + // does not flash a value the session never had. const activeSessionForView: SessionSummary | undefined = activeSession ?? (activeId - ? { - id: activeId, + ? pendingSessionView({ + sessionId: activeId, name: shellCopy.newConversation, - isFlagged: false, - isArchived: false, - labels: [], - hasUnread: false, - status: 'active', - backend: 'fake', - llmConnectionSlug: 'default', - connectionLocked: false, - model: 'fake-model', - // Transient placeholder while the real SessionSummary loads -- - // matches the configured default so the composer doesn't flash a - // hardcoded value before the real session data settles. - permissionMode: defaultPermissionMode, - } + permissionMode: defaultPermissionMode, + newChatModel, + defaultConnectionSlug: defaultConnection, + }) : undefined); const { boundary: activeExecutionBoundary, diff --git a/apps/desktop/src/renderer/pending-session-view.ts b/apps/desktop/src/renderer/pending-session-view.ts new file mode 100644 index 0000000000..df9e5656c8 --- /dev/null +++ b/apps/desktop/src/renderer/pending-session-view.ts @@ -0,0 +1,39 @@ +import type { PermissionMode } from '@maka/core/permission'; +import type { SessionSummary } from '@maka/core/session'; +import type { NewChatModel } from './shell-chat-model-selection'; + +export interface PendingSessionViewInput { + sessionId: string; + name: string; + permissionMode: PermissionMode; + /** Connection + model the next new task would start on, when one is offered. */ + newChatModel: NewChatModel | undefined; + defaultConnectionSlug: string | null; +} + +/** + * The `SessionSummary` the chat view shows between "a session id became active" + * and "its real summary arrived". + * + * The composer reads the connection and model straight off this object — the + * model switcher's current value, the quote companion's target — so the + * placeholder carries what the next new task would use. It used to claim + * `backend: 'fake'` / `model: 'fake-model'`: a retired backend (#3211) and a + * model no session ever had, which the switcher then failed to match. + */ +export function pendingSessionView(input: PendingSessionViewInput): SessionSummary { + return { + id: input.sessionId, + name: input.name, + isFlagged: false, + isArchived: false, + labels: [], + hasUnread: false, + status: 'active', + backend: 'ai-sdk', + llmConnectionSlug: input.newChatModel?.llmConnectionSlug ?? input.defaultConnectionSlug ?? '', + connectionLocked: false, + model: input.newChatModel?.model ?? '', + permissionMode: input.permissionMode, + }; +} From 2ddb813f6abdc6deec635b7cb9e6c56331f319a0 Mon Sep 17 00:00:00 2001 From: AstroHan Date: Wed, 19 Aug 2026 23:30:29 +0800 Subject: [PATCH 3/7] refactor(core): give provider recognition one own-property-safe owner MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `PROVIDER_DEFAULTS` is an object literal, so `PROVIDER_DEFAULTS[providerType]` resolves inherited members: `'__proto__'`, `'toString'` and `'constructor'` all read back truthy and pass as registered providers. Every recognition site was doing that lookup itself, and two of them had been holding the leak closed by accident. `backendKindOf` ended `?? 'fake'`, and the model-choice gates tested `backendKind !== 'ai-sdk'` — both fell through to "not usable" for an inherited member. Narrowing those in the previous commit removed the accident without replacing it: `backendKindOf` began returning `undefined` typed as `BackendKind`, and an inherited member started reading as a model-consumer connection. Calling that check tautological was wrong; it was tautological in the type system only. `providerDefaultsOf` now owns the question and answers it with `Object.hasOwn`. The connection-catalog codec's `PROVIDER_TYPES` set — until now the only site that got this right, via `Object.keys` — folds into it, so provider recognition has one implementation rather than a correct one and several approximations. Part of #3211 Generated-by: Claude Code --- .../src/renderer/model-catalog-choices.ts | 3 +- .../src/__tests__/llm-connections.test.ts | 31 +++++++++++++++++++ packages/core/src/chat-model-choice.ts | 4 +-- packages/core/src/connection-readiness.ts | 3 +- packages/core/src/llm-connections.ts | 18 ++++++++++- .../connection-catalog-codec.ts | 11 ++++--- 6 files changed, 61 insertions(+), 9 deletions(-) diff --git a/apps/desktop/src/renderer/model-catalog-choices.ts b/apps/desktop/src/renderer/model-catalog-choices.ts index 90f2058a2c..fef518a109 100644 --- a/apps/desktop/src/renderer/model-catalog-choices.ts +++ b/apps/desktop/src/renderer/model-catalog-choices.ts @@ -7,6 +7,7 @@ import { CODEX_SUBSCRIPTION_UNSUPPORTED_CHATGPT_MODELS, PROVIDER_DEFAULTS, connectionEnabledModelIds, + providerDefaultsOf, } from '@maka/core/llm-connections'; import type { LlmConnection, ProviderType } from '@maka/core/llm-connections'; import type { UiLocale } from '@maka/core/ui-locale'; @@ -141,7 +142,7 @@ function isModelConsumerConnection(connection: Pick { diff --git a/packages/core/src/__tests__/llm-connections.test.ts b/packages/core/src/__tests__/llm-connections.test.ts index 347dc7c35d..e6d1f75cd4 100644 --- a/packages/core/src/__tests__/llm-connections.test.ts +++ b/packages/core/src/__tests__/llm-connections.test.ts @@ -12,12 +12,14 @@ import { normalizeConnectionBaseUrl, persistedBaseUrl, providerAuthRequiresSecret, + providerDefaultsOf, providerAuthSupportsApiKey, reconcileConnectionAfterModelFetch, validateConnectionBaseUrl, type ProviderType, } from '../llm-connections.js'; import { isRealConnection } from '../connection-readiness.js'; +import { buildChatModelChoices } from '../chat-model-choice.js'; test('connection base URLs allow HTTP(S) and reject unsafe or malformed inputs', () => { assert.equal(validateConnectionBaseUrl(undefined), null); @@ -64,6 +66,7 @@ test('unknown provider ids fail closed without breaking persisted connections', // answer to "can this connection be used?". assert.throws(() => backendKindOf({ providerType: unknown }), /Unknown providerType/); assert.equal(isRealConnection({ providerType: unknown }), false); + assert.equal(providerDefaultsOf(unknown), undefined); assert.equal( effectiveBaseUrl({ providerType: unknown, baseUrl: 'https://example.test/v1' }), 'https://example.test/v1', @@ -199,3 +202,31 @@ test('the alias table is selected by provider and names only renames', () => { assert.notEqual(lookupModelMetadata('anthropic', renamed).lifecycle, 'deprecated'); } }); + +test('provider recognition does not resolve inherited object members', () => { + // `PROVIDER_DEFAULTS` is an object literal, so plain indexing answers truthy + // for `__proto__` / `toString` / `constructor` and they would read as + // registered providers. #3211 made `backendKindOf` throw for unknown types, + // which turns that leak from a wrong-but-closed `'fake'` into an `undefined` + // masquerading as a BackendKind — so recognition owns the own-property check. + for (const inherited of ['__proto__', 'toString', 'constructor', 'valueOf']) { + const providerType = inherited as ProviderType; + assert.equal(providerDefaultsOf(inherited), undefined, inherited); + assert.equal(isRealConnection({ providerType }), false, inherited); + assert.throws(() => backendKindOf({ providerType }), /Unknown providerType/, inherited); + assert.deepEqual( + buildChatModelChoices([ + { + slug: 'inherited', + name: 'inherited', + providerType, + enabled: true, + defaultModel: 'm', + models: [{ id: 'm' }], + } as unknown as Parameters[0][number], + ]), + [], + inherited, + ); + } +}); diff --git a/packages/core/src/chat-model-choice.ts b/packages/core/src/chat-model-choice.ts index 30cb6c75fc..e87fd19160 100644 --- a/packages/core/src/chat-model-choice.ts +++ b/packages/core/src/chat-model-choice.ts @@ -3,8 +3,8 @@ import { buildConnectionModelCatalogEntries } from './model-catalog.js'; import { thinkingVariantsForConnection, type ThinkingLevel } from './model-thinking.js'; import { CODEX_SUBSCRIPTION_UNSUPPORTED_CHATGPT_MODELS, - PROVIDER_DEFAULTS, connectionEnabledModelIds, + providerDefaultsOf, type LlmConnection, type ProviderType, } from './llm-connections.js'; @@ -39,7 +39,7 @@ export function buildChatModelChoices(connections: readonly LlmConnection[]): Ch const choices: ChatModelChoice[] = []; for (const rawConnection of connections) { const connection = normalizeOpenAiCodexConnection(rawConnection); - const provider = PROVIDER_DEFAULTS[connection.providerType]; + const provider = providerDefaultsOf(connection.providerType); if (!connection.enabled || !provider) { continue; } diff --git a/packages/core/src/connection-readiness.ts b/packages/core/src/connection-readiness.ts index 6d701d53f2..43de415b7d 100644 --- a/packages/core/src/connection-readiness.ts +++ b/packages/core/src/connection-readiness.ts @@ -27,6 +27,7 @@ import { PROVIDER_DEFAULTS, connectionEnabledModelIds, providerAuthRequiresSecret, + providerDefaultsOf, type LlmConnection, } from './llm-connections.js'; import { isModelExplicitlyUnsupportedForChat } from './model-catalog.js'; @@ -184,5 +185,5 @@ export function isRealConnection(connection: Pick } function isKnownProvider(connection: Pick): boolean { - return PROVIDER_DEFAULTS[connection.providerType] !== undefined; + return providerDefaultsOf(connection.providerType) !== undefined; } diff --git a/packages/core/src/llm-connections.ts b/packages/core/src/llm-connections.ts index fd7de025b7..66653b63ca 100644 --- a/packages/core/src/llm-connections.ts +++ b/packages/core/src/llm-connections.ts @@ -349,6 +349,22 @@ export interface ConnectionTestResult { export const PROVIDER_DEFAULTS = PROVIDER_REGISTRY; +/** + * The registry entry for a provider, or `undefined` when this build does not + * register one. + * + * Sole owner of the question "is this `providerType` one we know". Plain + * indexing cannot answer it: `PROVIDER_DEFAULTS` is an object literal, so + * `PROVIDER_DEFAULTS['__proto__']` and `['toString']` resolve to inherited + * members and read as registered providers. Every recognition site goes + * through here rather than repeating the own-property check. + */ +export function providerDefaultsOf(providerType: string): ProviderDefaults | undefined { + return Object.hasOwn(PROVIDER_DEFAULTS, providerType) + ? PROVIDER_DEFAULTS[providerType as ProviderType] + : undefined; +} + export function defaultEnabledModelIdsWhenOmitted( providerType: ProviderType, ): readonly string[] | undefined { @@ -382,7 +398,7 @@ export function providerSupportsModelDiscovery(providerType: ProviderType): bool * / `isConnectionReady` from `connection-readiness.ts`. */ export function backendKindOf(c: Pick): BackendKind { - const defaults = PROVIDER_DEFAULTS[c.providerType]; + const defaults = providerDefaultsOf(c.providerType); if (!defaults) throw new Error(`Unknown providerType: ${c.providerType}`); return defaults.backendKind; } diff --git a/packages/core/src/runtime-policy/connection-catalog-codec.ts b/packages/core/src/runtime-policy/connection-catalog-codec.ts index 9164336979..306b8e184c 100644 --- a/packages/core/src/runtime-policy/connection-catalog-codec.ts +++ b/packages/core/src/runtime-policy/connection-catalog-codec.ts @@ -1,4 +1,9 @@ -import { PROVIDER_DEFAULTS, validateSlug, type ProviderType } from '../llm-connections.js'; +import { + PROVIDER_DEFAULTS, + providerDefaultsOf, + validateSlug, + type ProviderType, +} from '../llm-connections.js'; import { DECLARABLE_RELAY_THINKING_LEVELS, isThinkingLevel, @@ -37,8 +42,6 @@ import { stringValue, } from './domain-codec.js'; -const PROVIDER_TYPES = new Set(Object.keys(PROVIDER_DEFAULTS)); - export const CONNECTION_CATALOG_MAX_CONNECTIONS = 1_024; export const CONNECTION_CATALOG_MAX_MODELS_PER_CONNECTION = 2_048; export const CONNECTION_CATALOG_MAX_ENABLED_MODEL_IDS = 512; @@ -566,7 +569,7 @@ function decodeConnectionModelIds(value: unknown): string[] { } export function decodeProviderType(value: unknown): ProviderType { - if (typeof value !== 'string' || !PROVIDER_TYPES.has(value)) { + if (typeof value !== 'string' || providerDefaultsOf(value) === undefined) { throw domainError('connection provider type is not registered'); } return value as ProviderType; From 798e39274b5b1d0c860882fb4de6e2e8bc293d6e Mon Sep 17 00:00:00 2001 From: AstroHan Date: Wed, 19 Aug 2026 23:30:41 +0800 Subject: [PATCH 4/7] fix: stop promising a rebind that retired-backend sessions cannot take MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `shouldRebindSessionToDefault` listed `fake_backend`, so an unlocked session on the retired backend projected as `{kind:'rebind'}`. Nothing performs that rebind — the only executor was `ensureSessionCanSendOrRebind`, which had no caller and is now deleted, and neither Runtime nor Runtime Host has an equivalent. The two consumers of `'rebind'` only use it to suppress a notice. It could not have worked anyway. Every other reason on that list names a broken *connection*, which another connection can stand in for. A retired backend is not: activation dispatches off the session header's own `backend`, so pointing the session at a healthy connection still leaves `'fake'` in the header and still gets refused. The cost was paid elsewhere. Because the projection said "recoverable" for rows that are not, the surfaces that must answer "is this task usable?" bypassed it and read `session.backend` themselves — the rail's stale marker and the composer's connection/model labels. Removing `fake_backend` from the list makes the projection answer `blocked` for these rows, and both surfaces drop their workaround and read the reason like any other. The `'任务已过期 · 请先配置真实模型'` notice, written long ago and suppressed for unlocked rows ever since, now shows. Part of #3211 Generated-by: Claude Code --- .../src/main/__tests__/stale-sessions.test.ts | 21 ++++++--- apps/desktop/src/renderer/stale-sessions.ts | 20 ++++---- .../src/renderer/use-shell-chat-model.ts | 13 ++++-- .../__tests__/session-send-projection.test.ts | 46 +++++++++++++++---- packages/core/src/session-send-projection.ts | 10 +++- 5 files changed, 81 insertions(+), 29 deletions(-) diff --git a/apps/desktop/src/main/__tests__/stale-sessions.test.ts b/apps/desktop/src/main/__tests__/stale-sessions.test.ts index 4872cc4310..9e5a5e1017 100644 --- a/apps/desktop/src/main/__tests__/stale-sessions.test.ts +++ b/apps/desktop/src/main/__tests__/stale-sessions.test.ts @@ -4,10 +4,10 @@ import { deriveStaleSessionIds } from '../../renderer/stale-sessions.js'; test('derives stale rows from each Session Host readiness projection', () => { const sessions = [ - session('local-ready'), - session('remote-missing'), - session('remote-rebind'), - session('legacy-fake', 'fake'), + { id: 'local-ready' }, + { id: 'remote-missing' }, + { id: 'remote-rebind' }, + { id: 'legacy-fake' }, ]; assert.deepEqual( @@ -25,12 +25,19 @@ test('derives stale rows from each Session Host readiness projection', () => { connectionSlug: 'replacement', model: 'model', }, + // #3211: a retired backend reaches the rail as a projection reason like + // any other. The row is no longer identified by reading its `backend`. + 'legacy-fake': { + kind: 'blocked', + reason: 'fake_backend', + connectionLocked: false, + }, }, })], ['remote-missing', 'legacy-fake'], ); }); -function session(id: string, backend = 'ai-sdk') { - return { id, backend }; -} +test('a row whose readiness has not arrived yet is not called stale', () => { + assert.deepEqual([...deriveStaleSessionIds({ sessions: [{ id: 'unknown' }], sendOutcomes: {} })], []); +}); diff --git a/apps/desktop/src/renderer/stale-sessions.ts b/apps/desktop/src/renderer/stale-sessions.ts index 04a631daef..ac121750d0 100644 --- a/apps/desktop/src/renderer/stale-sessions.ts +++ b/apps/desktop/src/renderer/stale-sessions.ts @@ -4,7 +4,6 @@ export interface StaleSessionsInput { /** Sessions visible in the sidebar (already filtered + grouped). */ sessions: ReadonlyArray<{ id: string; - backend: string; }>; /** Readiness already resolved by each Session's owning Runtime Host. */ sendOutcomes: Readonly>; @@ -13,17 +12,22 @@ export interface StaleSessionsInput { export function deriveStaleSessionIds(input: StaleSessionsInput): Set { const stale = new Set(); for (const session of input.sessions) { - if (isStale(session, input.sendOutcomes[session.id])) { + if (isStale(input.sendOutcomes[session.id])) { stale.add(session.id); } } return stale; } -function isStale( - session: { backend: string }, - outcome: SessionSendProjection | undefined, -): boolean { - if (session.backend === 'fake') return true; - return outcome?.kind === 'blocked' && outcome.reason === 'connection_missing'; +/** + * A row is stale when its owning Runtime Host says the next send cannot go + * anywhere the user can fix from the rail. + * + * `fake_backend` is read from the projection rather than from `session.backend` + * (#3211): the readiness projection is the single authority on whether a task + * is usable, and a retired backend is one of its answers like any other. + */ +function isStale(outcome: SessionSendProjection | undefined): boolean { + if (outcome?.kind !== 'blocked') return false; + return outcome.reason === 'connection_missing' || outcome.reason === 'fake_backend'; } diff --git a/apps/desktop/src/renderer/use-shell-chat-model.ts b/apps/desktop/src/renderer/use-shell-chat-model.ts index 724753700c..c2a65279cb 100644 --- a/apps/desktop/src/renderer/use-shell-chat-model.ts +++ b/apps/desktop/src/renderer/use-shell-chat-model.ts @@ -119,13 +119,20 @@ export function useShellChatModel(options: { catalogDefault: catalogDefaultNewChatModel, choices: chatModelChoices, }); - const activeConnectionLabel = activeSession?.backend === 'fake' + // A task whose backend was retired has no model to name (#3211). That verdict + // comes from the readiness projection, not from reading `activeSession.backend` + // here: the projection is the single authority on whether a task is usable, + // and it already answers `fake_backend` for these rows. + const isRetiredBackend = + options.sessionSendOutcome?.kind === 'blocked' && + options.sessionSendOutcome.reason === 'fake_backend'; + const activeConnectionLabel = isRetiredBackend ? conversationCopy.model.fakeBackendLabel : activeConnection?.name ?? activeSession?.llmConnectionSlug; - const activeModel = activeSession?.backend === 'fake' + const activeModel = isRetiredBackend ? undefined : activeSession?.model || activeConnection?.defaultModel; - const activeModelLabel = activeSession?.backend === 'fake' + const activeModelLabel = isRetiredBackend ? undefined : chatModelChoiceLabel(chatModelChoices, activeSession?.llmConnectionSlug, activeModel); const activeThinkingLevels = useMemo( diff --git a/packages/core/src/__tests__/session-send-projection.test.ts b/packages/core/src/__tests__/session-send-projection.test.ts index aad8d13e48..f241a8f855 100644 --- a/packages/core/src/__tests__/session-send-projection.test.ts +++ b/packages/core/src/__tests__/session-send-projection.test.ts @@ -166,8 +166,8 @@ describe('projectSessionSendOutcome — silent rebind walk', () => { const outcome = projectSessionSendOutcome( input({ session: { - backend: 'fake', - llmConnectionSlug: 'fake', + backend: 'ai-sdk', + llmConnectionSlug: 'deleted-connection', model: 'fake-model', connectionLocked: false, }, @@ -180,8 +180,8 @@ describe('projectSessionSendOutcome — silent rebind walk', () => { const outcome = projectSessionSendOutcome( input({ session: { - backend: 'fake', - llmConnectionSlug: 'fake', + backend: 'ai-sdk', + llmConnectionSlug: 'deleted-connection', model: 'fake-model', connectionLocked: false, }, @@ -203,7 +203,11 @@ describe('projectSessionSendOutcome — silent rebind walk', () => { }); }); - it('rebind requires a secret on the candidate connection', () => { + it('a retired backend never rebinds, even unlocked with a ready connection available', () => { + // #3211: activation dispatches off the session header's own `backend`, so + // pointing this session at a healthy connection would still leave `'fake'` + // in the header and still be refused. Promising a rebind nothing performs + // is what pushed the rail and the composer to read `backend` directly. const outcome = projectSessionSendOutcome( input({ session: { @@ -212,11 +216,33 @@ describe('projectSessionSendOutcome — silent rebind walk', () => { model: 'fake-model', connectionLocked: false, }, + }), + ); + assert.deepEqual(outcome, { + kind: 'blocked', + reason: 'fake_backend', + connectionLocked: false, + }); + }); + + it('rebind requires a secret on the candidate connection', () => { + const outcome = projectSessionSendOutcome( + input({ + session: { + backend: 'ai-sdk', + llmConnectionSlug: 'deleted-connection', + model: 'fake-model', + connectionLocked: false, + }, hasSecret: () => false, }), ); // Default exists and is enabled but has no key → send still fails. - assert.deepEqual(outcome, { kind: 'blocked', reason: 'fake_backend', connectionLocked: false }); + assert.deepEqual(outcome, { + kind: 'blocked', + reason: 'connection_missing', + connectionLocked: false, + }); }); it('non-rebindable failure blocks even when another ready connection exists', () => { @@ -236,8 +262,8 @@ describe('projectSessionSendOutcome — silent rebind walk', () => { const outcome = projectSessionSendOutcome( input({ session: { - backend: 'fake', - llmConnectionSlug: 'fake', + backend: 'ai-sdk', + llmConnectionSlug: 'deleted-connection', model: 'fake-model', connectionLocked: false, }, @@ -253,8 +279,8 @@ describe('projectSessionSendOutcome — codex normalization', () => { const outcome = projectSessionSendOutcome( input({ session: { - backend: 'fake', - llmConnectionSlug: 'fake', + backend: 'ai-sdk', + llmConnectionSlug: 'deleted-connection', model: 'fake-model', connectionLocked: false, }, diff --git a/packages/core/src/session-send-projection.ts b/packages/core/src/session-send-projection.ts index 33b16b36d3..282a53b6df 100644 --- a/packages/core/src/session-send-projection.ts +++ b/packages/core/src/session-send-projection.ts @@ -137,10 +137,18 @@ function ownConnectionBlockReason( * listed here (e.g. `missing_api_key`, `connection_disabled`) stay blocked * even when unlocked because masking an explicitly configured connection * would make the health/readiness UI misleading. + * + * `fake_backend` is deliberately absent (#3211). Every reason listed here + * names a broken *connection*, which another connection can stand in for. A + * retired backend is not: activation dispatches off the session header's own + * `backend`, so pointing the session at a healthy connection still leaves + * `'fake'` in the header and still gets refused. Claiming a rebind for these + * rows made the projection promise a recovery nothing performs — and the + * surfaces that must answer "is this task usable?" had to bypass the + * projection and read `backend` themselves to work around it. */ function shouldRebindSessionToDefault(reason: string | undefined): boolean { return ( - reason === 'fake_backend' || reason === 'connection_missing' || reason === 'missing_model' || reason === 'empty_model_list' || From ad908a4115c2727615f87a094a6985411761e925 Mon Sep 17 00:00:00 2001 From: AstroHan Date: Wed, 19 Aug 2026 23:30:50 +0800 Subject: [PATCH 5/7] fix(desktop): keep the pending chat view from naming a model it cannot know MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The previous commit replaced the placeholder's `model: 'fake-model'` with the connection and model the next new task would start on. That was wrong in a way the literal was not. This fallback covers every active id whose summary has not arrived, not only a freshly created task, so the session behind it is usually an existing one bound to some other model. Naming the new-chat default made the composer assert that configuration as the session's own: the model switcher shows it as current, and its no-op guard compares against it — so a user switching onto that very model has the change silently dropped against a session that was never on it. `'fake-model'` avoided this by accident: it matched no offered choice, which is exactly how the switcher spells "not known yet". The placeholder keeps that property and states it deliberately, with an empty connection/model pair, and without borrowing a retired backend name to mean "not loaded". Withholding in-session model selection entirely until the summary lands is the better answer, but it belongs to the switcher's own loading contract in `@maka/ui`, not to this cleanup. Part of #3211 Generated-by: Claude Code --- .../__tests__/pending-session-view.test.ts | 45 +++++++++---------- apps/desktop/src/renderer/app-shell.tsx | 2 - .../src/renderer/pending-session-view.ts | 24 +++++----- 3 files changed, 35 insertions(+), 36 deletions(-) diff --git a/apps/desktop/src/main/__tests__/pending-session-view.test.ts b/apps/desktop/src/main/__tests__/pending-session-view.test.ts index f8e9d696c0..30b1f601a3 100644 --- a/apps/desktop/src/main/__tests__/pending-session-view.test.ts +++ b/apps/desktop/src/main/__tests__/pending-session-view.test.ts @@ -2,45 +2,44 @@ import assert from 'node:assert/strict'; import test from 'node:test'; import { pendingSessionView } from '../../renderer/pending-session-view.js'; -test('the pending chat view starts on the model the next new task would use', () => { +test('the pending chat view names no connection or model it cannot know', () => { const view = pendingSessionView({ sessionId: 'session-1', name: '新任务', permissionMode: 'ask', - newChatModel: { llmConnectionSlug: 'anthropic', model: 'claude-sonnet-4-5-20250929' }, - defaultConnectionSlug: 'openai', }); // #3211: the placeholder used to claim `backend: 'fake'` / `model: - // 'fake-model'`, so the model switcher matched no choice and the quote - // companion targeted a model that never existed. + // 'fake-model'`, borrowing a retired backend to mean "not loaded". assert.equal(view.backend, 'ai-sdk'); - assert.equal(view.llmConnectionSlug, 'anthropic'); - assert.equal(view.model, 'claude-sonnet-4-5-20250929'); assert.equal(view.id, 'session-1'); assert.equal(view.permissionMode, 'ask'); assert.equal(view.connectionLocked, false); + + // The empty pair is load-bearing: this fallback covers any active id whose + // summary has not arrived, so naming a plausible connection/model would let + // the model switcher drop a real switch onto that model as a no-op against a + // session that was never on it. + assert.equal(view.llmConnectionSlug, ''); + assert.equal(view.model, ''); }); -test('the pending chat view falls back to the default connection, then to no model', () => { - const withDefault = pendingSessionView({ +test('the pending chat view matches no offered model choice', () => { + const view = pendingSessionView({ sessionId: 'session-2', name: '新任务', permissionMode: 'execute', - newChatModel: undefined, - defaultConnectionSlug: 'openai', }); - assert.equal(withDefault.llmConnectionSlug, 'openai'); - assert.equal(withDefault.model, ''); + const offered = [ + { connectionSlug: 'anthropic', model: 'claude-sonnet-4-5-20250929' }, + { connectionSlug: 'openai', model: 'gpt-5' }, + ]; - const withNothing = pendingSessionView({ - sessionId: 'session-3', - name: '新任务', - permissionMode: 'ask', - newChatModel: undefined, - defaultConnectionSlug: null, - }); - assert.equal(withNothing.backend, 'ai-sdk'); - assert.equal(withNothing.llmConnectionSlug, ''); - assert.equal(withNothing.model, ''); + assert.equal( + offered.some( + (choice) => + choice.connectionSlug === view.llmConnectionSlug && choice.model === view.model, + ), + false, + ); }); diff --git a/apps/desktop/src/renderer/app-shell.tsx b/apps/desktop/src/renderer/app-shell.tsx index 410a57d33d..ca451850ff 100644 --- a/apps/desktop/src/renderer/app-shell.tsx +++ b/apps/desktop/src/renderer/app-shell.tsx @@ -1280,8 +1280,6 @@ function AppShellContent({ sessionId: activeId, name: shellCopy.newConversation, permissionMode: defaultPermissionMode, - newChatModel, - defaultConnectionSlug: defaultConnection, }) : undefined); const { diff --git a/apps/desktop/src/renderer/pending-session-view.ts b/apps/desktop/src/renderer/pending-session-view.ts index df9e5656c8..5b10443065 100644 --- a/apps/desktop/src/renderer/pending-session-view.ts +++ b/apps/desktop/src/renderer/pending-session-view.ts @@ -1,25 +1,27 @@ import type { PermissionMode } from '@maka/core/permission'; import type { SessionSummary } from '@maka/core/session'; -import type { NewChatModel } from './shell-chat-model-selection'; export interface PendingSessionViewInput { sessionId: string; name: string; permissionMode: PermissionMode; - /** Connection + model the next new task would start on, when one is offered. */ - newChatModel: NewChatModel | undefined; - defaultConnectionSlug: string | null; } /** * The `SessionSummary` the chat view shows between "a session id became active" * and "its real summary arrived". * - * The composer reads the connection and model straight off this object — the - * model switcher's current value, the quote companion's target — so the - * placeholder carries what the next new task would use. It used to claim - * `backend: 'fake'` / `model: 'fake-model'`: a retired backend (#3211) and a - * model no session ever had, which the switcher then failed to match. + * The connection and model read empty because they are genuinely unknown: this + * fallback covers every active id without a loaded summary, not just a freshly + * created task, so the session behind it may be an existing one bound to any + * model. An empty pair matches no offered choice, which is how the model + * switcher's current-value and no-op comparisons read "not yet known" — naming + * a plausible model instead would let a switch onto that model be silently + * dropped as a no-op against a session that was never on it. + * + * It used to say `backend: 'fake'` / `model: 'fake-model'`, borrowing a retired + * backend (#3211) to mean "not loaded". The unknown-ness is the same; the + * borrowed name is gone. */ export function pendingSessionView(input: PendingSessionViewInput): SessionSummary { return { @@ -31,9 +33,9 @@ export function pendingSessionView(input: PendingSessionViewInput): SessionSumma hasUnread: false, status: 'active', backend: 'ai-sdk', - llmConnectionSlug: input.newChatModel?.llmConnectionSlug ?? input.defaultConnectionSlug ?? '', + llmConnectionSlug: '', connectionLocked: false, - model: input.newChatModel?.model ?? '', + model: '', permissionMode: input.permissionMode, }; } From 33a5519d5f8ada8b47cf2798269a7e5500a51d42 Mon Sep 17 00:00:00 2001 From: AstroHan Date: Wed, 19 Aug 2026 23:31:00 +0800 Subject: [PATCH 6/7] fix: refuse the retired backend on Automation writes, not just at activation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two validators accepted `'fake'` under a comment claiming they were decoders keeping frozen Automations readable. Neither is a decoder. `normalizeExecution` in core is reached only from `normalizeCreateScheduledTaskInput` / `normalizeUpdateScheduledTaskInput`; stored Automations are read back with `JSON.parse` in `scheduled-task-store.ts` and never pass through it. It now refuses `'fake'` outright. The protocol's `decodeExecution` genuinely serves both directions — reading a stored task and validating an inbound create/update — but those carry different backend invariants, so one shared answer had to be wrong for one of them. It takes the direction as an argument: `'stored'` still accepts the retired value, `'mutation'` does not. Without this a client could write a brand new Automation guaranteed to fail later at activation. The storage regression test seeded its legacy row through `store.create`, which is the write path this is closing. It now writes the row underneath the store, which is also the only way such a row was ever produced — and the test fixture stops defaulting every session in the file to the retired backend, so narrowing the header guard fails one named test instead of all seventeen. Part of #3211 Generated-by: Claude Code --- .../core/src/__tests__/scheduled-task.test.ts | 30 ++++++++++++++++ packages/core/src/scheduled-task.ts | 9 ++--- .../__tests__/scheduled-task-protocol.test.ts | 34 ++++++++++++++++++ .../src/protocol/scheduled-task.ts | 29 ++++++++------- .../src/__tests__/session-store.test.ts | 35 +++++++++++++++---- 5 files changed, 115 insertions(+), 22 deletions(-) diff --git a/packages/core/src/__tests__/scheduled-task.test.ts b/packages/core/src/__tests__/scheduled-task.test.ts index e070779b0f..249164a722 100644 --- a/packages/core/src/__tests__/scheduled-task.test.ts +++ b/packages/core/src/__tests__/scheduled-task.test.ts @@ -147,6 +147,36 @@ describe('scheduled-task catalog', () => { assert.deepEqual(result, { ok: false, message: 'Schedule must fire before expiresAt' }); }); + it('refuses to create an Automation on the retired backend', () => { + // #3211: this is create/update input, not a decoder — stored Automations + // are read back with JSON.parse and never reach here. Accepting `'fake'` + // would let a brand new Automation be written that can only fail later at + // activation. + const now = Date.UTC(2026, 0, 5, 8, 0, 0); + const execution = { + cwd: '/tmp/project', + llmConnectionSlug: 'anthropic', + model: 'claude-sonnet-4-5-20250929', + permissionMode: 'ask', + collaborationMode: 'agent', + orchestrationMode: 'default', + }; + const create = (backend: string) => + normalizeCreateScheduledTaskInput( + { + title: 'Nightly run', + intentBody: 'do the thing', + schedule: { kind: 'once', runAt: now + 60_000 }, + effect: { kind: 'agent_run', execution: { ...execution, backend } }, + createdBy: { kind: 'user' }, + }, + now, + ); + + assert.deepEqual(create('fake'), { ok: false, message: 'execution.backend is invalid' }); + assert.equal(create('ai-sdk').ok, true); + }); + it('rejects future recurrence anchors outside the scheduling horizon', () => { const now = Date.UTC(2026, 0, 5, 8, 0, 0); for (const schedule of [ diff --git a/packages/core/src/scheduled-task.ts b/packages/core/src/scheduled-task.ts index b556128620..8409b712a3 100644 --- a/packages/core/src/scheduled-task.ts +++ b/packages/core/src/scheduled-task.ts @@ -489,10 +489,11 @@ function normalizeExecution( if (!isObject(value)) return fail('agent_run requires execution template'); if (typeof value.cwd !== 'string' || !value.cwd.trim()) return fail('execution.cwd is required'); if (typeof value.backend !== 'string') return fail('execution.backend is required'); - // `'fake'` stays accepted on decode: Automations frozen by builds that - // shipped FakeBackend must keep loading (#3211). Activation refuses them - // with the product's `fake_backend` reason. - if (value.backend !== 'ai-sdk' && value.backend !== 'fake') { + // Create/update input, not a decoder: stored Automations are read back with + // `JSON.parse` in scheduled-task-store.ts and never pass through here. So the + // retired `'fake'` is refused (#3211) — accepting it would let a brand new + // Automation be written that can only fail later at activation. + if (value.backend !== 'ai-sdk') { return fail('execution.backend is invalid'); } if (typeof value.llmConnectionSlug !== 'string' || !value.llmConnectionSlug.trim()) { diff --git a/packages/runtime-host/src/__tests__/scheduled-task-protocol.test.ts b/packages/runtime-host/src/__tests__/scheduled-task-protocol.test.ts index 27e9df3e63..bf1f4b10ef 100644 --- a/packages/runtime-host/src/__tests__/scheduled-task-protocol.test.ts +++ b/packages/runtime-host/src/__tests__/scheduled-task-protocol.test.ts @@ -12,6 +12,7 @@ import { authorizeRuntimeHostOperation, createRuntimeHostConnectionAuthority, } from '../server/connection-authority.js'; +import { decodeScheduledTask, decodeScheduledTaskMutateInput } from '../protocol/scheduled-task.js'; describe('ScheduledTask protocol', () => { test('requires Host-path authority only when a mutation submits a Host path', () => { @@ -34,6 +35,39 @@ describe('ScheduledTask protocol', () => { } }); + test('a retired backend decodes on the way out but not on the way in', () => { + // #3211: the same execution decoder serves both directions, and they carry + // different backend invariants. A stored Automation frozen by a build that + // shipped FakeBackend must stay readable; a live create/update may not + // introduce the retired value. + const retired = (effect: ScheduledTaskEffect): ScheduledTaskEffect => + effect.kind === 'agent_run' + ? { ...effect, execution: { ...effect.execution, backend: 'fake' } } + : effect; + + const stored = { ...scheduledTask('task-1'), effect: retired(agentRunEffect('project-1')) }; + assert.equal(decodeScheduledTask(stored).effect.kind, 'agent_run'); + + for (const input of [ + { + kind: 'create' as const, + input: { + title: 'Inspect workspace', + intentBody: 'Summarize the workspace.', + schedule: { kind: 'once' as const, runAt: 1 }, + effect: retired(agentRunEffect('project-1')), + }, + }, + { + kind: 'update' as const, + taskId: 'task-1', + patch: { effect: retired(agentRunEffect('project-1')) }, + }, + ]) { + assert.throws(() => decodeScheduledTaskMutateInput(input), /Invalid ScheduledTask backend/); + } + }); + test('accepts signal-only catalog changes', () => { const frame = { kind: 'scheduled-task.changed' as const, diff --git a/packages/runtime-host/src/protocol/scheduled-task.ts b/packages/runtime-host/src/protocol/scheduled-task.ts index 2d37bb6e8a..2768ae584f 100644 --- a/packages/runtime-host/src/protocol/scheduled-task.ts +++ b/packages/runtime-host/src/protocol/scheduled-task.ts @@ -304,7 +304,7 @@ export function decodeScheduledTask(value: unknown): ScheduledTask { body: boundedText(intent.body, 'ScheduledTask intent body', SCHEDULED_TASK_INTENT_MAX_CHARS), }, schedule: decodeSchedule(task.schedule), - effect: decodeEffect(task.effect), + effect: decodeEffect(task.effect, 'stored'), status: task.status, nextFireAt: nullableCount(task.nextFireAt, 'ScheduledTask nextFireAt'), lastFireAt: nullableCount(task.lastFireAt, 'ScheduledTask lastFireAt'), @@ -341,7 +341,7 @@ function decodeCreateInput(value: unknown): Omit { // storage. Narrowing the header validator would make these rows decode as // malformed and rewriting them to `'ai-sdk'` would make an unrunnable task // look runnable, since `llmConnectionSlug` still points at nothing. + // + // The legacy row is seeded under the writer, not through `create`: `'fake'` + // is a value only an older build could write, so a test that asks today's + // creation path for one would be asserting a write that must not exist. const root = await mkdtemp(join(tmpdir(), 'maka-session-legacy-fake-')); const store = createSessionStore(root); + let sessionId: string; try { - const session = await store.create(makeInput({ backend: 'fake', llmConnectionSlug: 'fake' })); - assert.equal(session.backend, 'fake'); - assert.equal((await store.readHeaderSnapshot(session.id)).backend, 'fake'); + sessionId = (await store.create(makeInput())).id; } finally { await store.close?.(); } + const legacy = new DatabaseSync(join(root, OPERATIONAL_STATE_DATABASE_NAME)); + try { + const row = legacy + .prepare(`SELECT payload_json FROM session_metadata WHERE session_id = ?`) + .get(sessionId) as { payload_json: string }; + const payload = JSON.parse(row.payload_json) as Record; + payload.backend = 'fake'; + payload.llmConnectionSlug = 'fake'; + legacy + .prepare( + `UPDATE session_metadata + SET payload_json = ?, backend = ?, llm_connection_slug = ? + WHERE session_id = ?`, + ) + .run(JSON.stringify(payload), 'fake', 'fake', sessionId); + } finally { + legacy.close(); + } + const reopened = createSessionStore(root); try { const [header] = await reopened.listHeaders(); assert.equal(header?.backend, 'fake'); assert.equal(header?.llmConnectionSlug, 'fake'); + assert.equal((await reopened.readHeaderSnapshot(sessionId)).backend, 'fake'); } finally { await reopened.close?.(); await rm(root, { recursive: true, force: true }); @@ -1056,9 +1079,9 @@ describe('SQLite SessionStore', () => { function makeInput(overrides: Partial = {}): CreateSessionInput { return { cwd: '/tmp/cwd', - backend: 'fake', - llmConnectionSlug: 'fake', - model: 'fake-model', + backend: 'ai-sdk', + llmConnectionSlug: 'test-connection', + model: 'test-model', permissionMode: 'ask', name: 'Session', labels: [], From 629535977c8dfea856931a3ad80a115a2f289e49 Mon Sep 17 00:00:00 2001 From: AstroHan Date: Thu, 20 Aug 2026 02:40:19 +0800 Subject: [PATCH 7/7] refactor: stop letting callers choose a session backend MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `CreateSessionInput.backend` was the last live path that could write the retired `'fake'` into a new session. Narrowing it to `BackendKind` would only move the question: four derivation sites in `session-manager.ts` plus the conversation copy in `session-revision-coordinator.ts` inherit the source header's backend, so each would need an answer for what deriving from a legacy row means. Deleting the field dissolves that question. A live build has exactly one backend, so the field carried no choice — the store stamps `'ai-sdk'` on every new header and no caller names a backend at all. A session derived from a legacy row is now a real session whose connection slug resolves to nothing, which is what the readiness projection already says about that row. Two backend-kind guards go with it, for separate reasons: - `runtimeToolBoundaryProtocol` withheld the T1 durable tool boundary from non-`ai-sdk` headers. The branch was reachable: nothing checks the backend kind between `sendMessage` and the kernel, and `AgentRun.begin()` persists the run record, user message, turn state and the `connectionLocked` flip before the refusal fires inside `reserveRun`. What the gate withheld was only the protocol marker on the initial event of a run that dies before tool dispatch, and every marker consumer treats a marked run with zero tool operations the same as an unmarked one. The marker stays truthful; the gate bought nothing. - `sessions:create` rejected a non-`ai-sdk` backend arriving over IPC. That one really is unrepresentable now: the field is gone from the request type. Tests that dispatched through a `'fake'` registry key now register the live one, which is how they would be written today. The activation-refusal regression test seeded its legacy row through `createSession({backend: 'fake'})` — the very write this removes — and now seeds it under the writer, which is the only way such a row was ever produced. Generated-by: Claude Code --- apps/desktop/e2e/fixtures.ts | 2 - .../runtime-host-session-catalog-ipc-main.ts | 3 - .../runtime-host-run-command.test.ts | 12 - .../runtime-host-session-driver.test.ts | 5 - packages/cli/src/activation-command.ts | 1 - packages/cli/src/run-command-core.ts | 1 - packages/core/src/runtime-inputs.ts | 10 +- .../__tests__/artifact-two-client-uds.test.ts | 2 - .../deep-research-two-client-uds.test.ts | 1 - .../__tests__/execution-composition.test.ts | 65 ++- .../__tests__/execution-inspect-uds.test.ts | 1 - .../execution-model-composition.test.ts | 6 - .../external-session-coordinator.test.ts | 4 - .../fixtures/execution-host-suite.ts | 3 - .../src/__tests__/goal-coordinator.test.ts | 4 - .../src/__tests__/goal-root-authority.test.ts | 3 +- .../__tests__/interaction-coordinator.test.ts | 4 - .../src/__tests__/plan-two-client-uds.test.ts | 1 - .../__tests__/root-turn-coordinator.test.ts | 140 ++--- .../runtime-policy-coordinator.test.ts | 3 - .../session-catalog-two-client-uds.test.ts | 5 - .../session-retirement-coordinator.test.ts | 1 - .../session-revision-two-client-uds.test.ts | 8 - .../session-transcript-reader.test.ts | 1 - .../src/server/session-catalog-coordinator.ts | 2 - .../server/session-revision-coordinator.ts | 1 - .../agent-run-steering-recovery.test.ts | 8 - .../src/__tests__/execution-inspect.test.ts | 1 - .../__tests__/latest-context-commit.test.ts | 3 +- .../runtime-continuation-crash.test.ts | 5 +- .../runtime-kernel-interaction.test.ts | 16 +- .../__tests__/runtime-ledger-repair.test.ts | 1 - .../sandbox-boundary-restart-recovery.test.ts | 1 - .../session-manager-terminal-ledger.test.ts | 21 +- .../src/__tests__/session-manager.test.ts | 523 ++++++++---------- .../__tests__/shell-run-tool-result.test.ts | 1 - .../stream-graph-coordinator.test.ts | 6 +- packages/runtime/src/runtime-kernel.ts | 23 +- packages/runtime/src/session-manager.ts | 4 - .../external-session-importer.test.ts | 1 - .../src/__tests__/goal-authority.test.ts | 1 - .../operational-state-backup.test.ts | 1 - .../__tests__/session-bundle-policy.test.ts | 1 - .../src/__tests__/session-store.test.ts | 1 - .../__tests__/settings-store-usage.test.ts | 2 - packages/storage/src/session-store.ts | 2 +- 46 files changed, 384 insertions(+), 527 deletions(-) diff --git a/apps/desktop/e2e/fixtures.ts b/apps/desktop/e2e/fixtures.ts index 885c83d7e8..0c0e85a38a 100644 --- a/apps/desktop/e2e/fixtures.ts +++ b/apps/desktop/e2e/fixtures.ts @@ -135,7 +135,6 @@ async function seedParentRemovalSessions(userDataDir: string): Promise { try { const parent = await store.create({ cwd: path.join(userDataDir, 'project'), - backend: 'fake', llmConnectionSlug: 'e2e', model: 'claude-sonnet-4-5-20250929', permissionMode: 'ask', @@ -144,7 +143,6 @@ async function seedParentRemovalSessions(userDataDir: string): Promise { }); await store.createSubagent({ cwd: path.join(userDataDir, 'project'), - backend: 'fake', llmConnectionSlug: 'e2e', model: 'claude-sonnet-4-5-20250929', permissionMode: 'execute', diff --git a/apps/desktop/src/main/runtime-host-session-catalog-ipc-main.ts b/apps/desktop/src/main/runtime-host-session-catalog-ipc-main.ts index 96f2169258..7ba474b507 100644 --- a/apps/desktop/src/main/runtime-host-session-catalog-ipc-main.ts +++ b/apps/desktop/src/main/runtime-host-session-catalog-ipc-main.ts @@ -96,9 +96,6 @@ export function registerRuntimeHostSessionCatalogIpc( pendingCleanup.add(sessionId); }); ipcMain.handle('sessions:create', async (_event, input?: CreateSessionRequestInput) => { - if (input?.backend !== undefined && input.backend !== 'ai-sdk') { - throw new Error('Unsupported Runtime Host Session backend'); - } const request = resolveCreateSessionRequest(input); const workspace = await deps.resolveCreateProject({ ...(input?.cwd === undefined ? {} : { cwd: input.cwd }), diff --git a/packages/cli/src/__tests__/runtime-host-run-command.test.ts b/packages/cli/src/__tests__/runtime-host-run-command.test.ts index 99f636cdaf..298e820ad4 100644 --- a/packages/cli/src/__tests__/runtime-host-run-command.test.ts +++ b/packages/cli/src/__tests__/runtime-host-run-command.test.ts @@ -205,7 +205,6 @@ describe('Runtime Host maka run adapter', () => { const fixture = runFixture({ observed, graph: true, graphProjectionRace: true }); const session = await fixture.context.runtime.createSession({ cwd: '/workspace', - backend: 'ai-sdk', llmConnectionSlug: 'openai-main', model: 'gpt-5', permissionMode: 'ask', @@ -229,7 +228,6 @@ describe('Runtime Host maka run adapter', () => { const fixture = runFixture({ observed, graph: true }); const session = await fixture.context.runtime.createSession({ cwd: '/workspace', - backend: 'ai-sdk', llmConnectionSlug: 'openai-main', model: 'gpt-5', permissionMode: 'ask', @@ -253,7 +251,6 @@ describe('Runtime Host maka run adapter', () => { const fixture = runFixture({ observed, graph: true, graphMultiWakeRace: true }); const session = await fixture.context.runtime.createSession({ cwd: '/workspace', - backend: 'ai-sdk', llmConnectionSlug: 'openai-main', model: 'gpt-5', permissionMode: 'ask', @@ -282,7 +279,6 @@ describe('Runtime Host maka run adapter', () => { const context = fixture.context; const session = await context.runtime.createSession({ cwd: '/workspace', - backend: 'ai-sdk', llmConnectionSlug: 'openai-main', model: 'gpt-5', permissionMode: 'ask', @@ -315,7 +311,6 @@ describe('Runtime Host maka run adapter', () => { }); const session = await fixture.context.runtime.createSession({ cwd: '/workspace', - backend: 'ai-sdk', llmConnectionSlug: 'openai-main', model: 'gpt-5', permissionMode: 'ask', @@ -338,7 +333,6 @@ describe('Runtime Host maka run adapter', () => { const fixture = runFixture({ maxSteps: 3 }); const session = await fixture.context.runtime.createSession({ cwd: '/workspace', - backend: 'ai-sdk', llmConnectionSlug: 'openai-main', model: 'gpt-5', permissionMode: 'ask', @@ -390,7 +384,6 @@ describe('Runtime Host maka run adapter', () => { const fixture = runFixture({ graph: true }); const session = await fixture.context.runtime.createSession({ cwd: '/workspace', - backend: 'ai-sdk', llmConnectionSlug: 'openai-main', model: 'gpt-5', permissionMode: 'ask', @@ -412,7 +405,6 @@ describe('Runtime Host maka run adapter', () => { }); const session = await fixture.context.runtime.createSession({ cwd: '/workspace', - backend: 'ai-sdk', llmConnectionSlug: 'openai-main', model: 'gpt-5', permissionMode: 'ask', @@ -444,7 +436,6 @@ describe('Runtime Host maka run adapter', () => { }); const session = await fixture.context.runtime.createSession({ cwd: '/workspace', - backend: 'ai-sdk', llmConnectionSlug: 'openai-main', model: 'gpt-5', permissionMode: 'ask', @@ -471,7 +462,6 @@ describe('Runtime Host maka run adapter', () => { }); const session = await fixture.context.runtime.createSession({ cwd: '/workspace', - backend: 'ai-sdk', llmConnectionSlug: 'openai-main', model: 'gpt-5', permissionMode: 'ask', @@ -499,7 +489,6 @@ describe('Runtime Host maka run adapter', () => { }); const session = await fixture.context.runtime.createSession({ cwd: '/workspace', - backend: 'ai-sdk', llmConnectionSlug: 'openai-main', model: 'gpt-5', permissionMode: 'ask', @@ -545,7 +534,6 @@ describe('Runtime Host maka run adapter', () => { }); const session = await fixture.context.runtime.createSession({ cwd: '/workspace', - backend: 'ai-sdk', llmConnectionSlug: 'openai-main', model: 'gpt-5', permissionMode: 'ask', diff --git a/packages/cli/src/__tests__/runtime-host-session-driver.test.ts b/packages/cli/src/__tests__/runtime-host-session-driver.test.ts index 7dda7d111c..4671af76c0 100644 --- a/packages/cli/src/__tests__/runtime-host-session-driver.test.ts +++ b/packages/cli/src/__tests__/runtime-host-session-driver.test.ts @@ -60,7 +60,6 @@ describe('Runtime Host Maka Session driver', () => { cwd: '/client/workspace', llmConnectionSlug: 'openai-main', model: 'gpt-5', - backend: 'ai-sdk', permissionMode: 'ask', }), /requires an explicit Project/, @@ -92,7 +91,6 @@ describe('Runtime Host Maka Session driver', () => { await driver.createSession({ cwd: '/repo', - backend: 'ai-sdk', llmConnectionSlug: 'openai-main', model: 'gpt-5', permissionMode: 'ask', @@ -174,7 +172,6 @@ describe('Runtime Host Maka Session driver', () => { await driver.createSession({ cwd: '/repo', - backend: 'ai-sdk', llmConnectionSlug: 'openai-main', model: 'gpt-5', permissionMode: 'ask', @@ -304,7 +301,6 @@ describe('Runtime Host Maka Session driver', () => { await driver.createSession({ cwd: candidate.cwd, ...('projectId' in candidate ? { projectId: candidate.projectId } : {}), - backend: 'ai-sdk', llmConnectionSlug: 'openai-main', model: 'gpt-5', permissionMode: 'ask', @@ -364,7 +360,6 @@ describe('Runtime Host Maka Session driver', () => { cwd: '/repo', llmConnectionSlug: 'openai-main', model: 'gpt-5', - backend: 'ai-sdk', permissionMode: 'ask', }); connection.executionBoundary = { kind: 'bypass', revision: 2 }; diff --git a/packages/cli/src/activation-command.ts b/packages/cli/src/activation-command.ts index cfa5f194b7..fbdb10f246 100644 --- a/packages/cli/src/activation-command.ts +++ b/packages/cli/src/activation-command.ts @@ -449,7 +449,6 @@ export async function runMakaActivationCli( session = await context.runtime.createSession({ cwd: roots.workspaceRoot, name: `Cloud activation ${request.activationId}`.slice(0, 80), - backend: 'ai-sdk', llmConnectionSlug: context.target.connection.slug, model: context.target.model, permissionMode: options.permissionMode ?? 'explore', diff --git a/packages/cli/src/run-command-core.ts b/packages/cli/src/run-command-core.ts index a47d2881cb..e46a5c9249 100644 --- a/packages/cli/src/run-command-core.ts +++ b/packages/cli/src/run-command-core.ts @@ -314,7 +314,6 @@ export async function runMakaTextCliCore( : await context.runtime.createSession({ cwd: selection.cwd, name: makaRunSessionName(prompt), - backend: 'ai-sdk', llmConnectionSlug: context.target.connection.slug, model: context.target.model, permissionMode: parsed.options.yolo ? 'bypass' : 'ask', diff --git a/packages/core/src/runtime-inputs.ts b/packages/core/src/runtime-inputs.ts index 06c249fd65..e7e3281d15 100644 --- a/packages/core/src/runtime-inputs.ts +++ b/packages/core/src/runtime-inputs.ts @@ -4,7 +4,6 @@ import type { MessageContent } from './events.js'; import type { - PersistedBackendKind, SessionBlockedReason, SessionStatus, SessionToolProfile, @@ -35,7 +34,14 @@ export interface CreateSessionInput { projectId?: string | null; /** If omitted, runtime auto-derives a placeholder; users may rename later. */ name?: string; - backend: PersistedBackendKind; + /** + * No `backend`: a live build has exactly one, so the field carried no choice + * — only the chance of writing the retired `'fake'` into a new row (#3211). + * The store stamps every new header instead. Sessions derived from an older + * one (branch, revision, subagent) no longer inherit its backend; a copy of a + * legacy row is a real session whose connection slug resolves to nothing, + * which is what the readiness projection already says about it. + */ llmConnectionSlug: string; /** Falls back to the connection's defaultModel if omitted. */ model?: string; diff --git a/packages/runtime-host/src/__tests__/artifact-two-client-uds.test.ts b/packages/runtime-host/src/__tests__/artifact-two-client-uds.test.ts index 2269a2cca5..135cfb4bc4 100644 --- a/packages/runtime-host/src/__tests__/artifact-two-client-uds.test.ts +++ b/packages/runtime-host/src/__tests__/artifact-two-client-uds.test.ts @@ -273,14 +273,12 @@ async function seedExecutionRoot( stores = await openInteractiveExecutionStoresForWrite(owner.lease); const session = await stores.sessionStore.create({ cwd: root, - backend: 'fake', llmConnectionSlug: 'fake', model: 'fake-model', permissionMode: 'ask', }); const otherSession = await stores.sessionStore.create({ cwd: root, - backend: 'fake', llmConnectionSlug: 'fake', model: 'fake-model', permissionMode: 'ask', diff --git a/packages/runtime-host/src/__tests__/deep-research-two-client-uds.test.ts b/packages/runtime-host/src/__tests__/deep-research-two-client-uds.test.ts index 8dbd7b3fb1..a85f50ae84 100644 --- a/packages/runtime-host/src/__tests__/deep-research-two-client-uds.test.ts +++ b/packages/runtime-host/src/__tests__/deep-research-two-client-uds.test.ts @@ -36,7 +36,6 @@ test('two Clients and a restarted production Host share one Deep Research projec const deepResearch = await openInteractiveDeepResearchStoreForWrite(owner.lease); const session = await setupStores.sessionStore.create({ cwd: root, - backend: 'fake', llmConnectionSlug: 'fake', model: 'fake-model', permissionMode: 'explore', diff --git a/packages/runtime-host/src/__tests__/execution-composition.test.ts b/packages/runtime-host/src/__tests__/execution-composition.test.ts index cab65c95da..ad6ca2bb11 100644 --- a/packages/runtime-host/src/__tests__/execution-composition.test.ts +++ b/packages/runtime-host/src/__tests__/execution-composition.test.ts @@ -4,6 +4,7 @@ import { createRequire } from 'node:module'; import { mkdir, mkdtemp, rm, stat, writeFile } from 'node:fs/promises'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; +import { DatabaseSync } from 'node:sqlite'; import { test } from 'node:test'; import type { AgentGraphIntentClaim, @@ -81,7 +82,6 @@ test('production composition closes long-term memory after a later startup failu const stores = await openInteractiveExecutionStoresForWrite(owner.lease); const session = await stores.sessionStore.create({ cwd: root, - backend: 'ai-sdk', llmConnectionSlug: 'fake', model: 'fake-model', permissionMode: 'ask', @@ -121,14 +121,12 @@ test('production recovery preserves legacy Automation history and closes an orph const stores = await openInteractiveExecutionStoresForWrite(owner.lease); const historical = await stores.sessionStore.create({ cwd: root, - backend: 'ai-sdk', llmConnectionSlug: 'fake', model: 'fake-model', permissionMode: 'ask', }); const pending = await stores.sessionStore.create({ cwd: root, - backend: 'ai-sdk', llmConnectionSlug: 'fake', model: 'fake-model', permissionMode: 'ask', @@ -349,7 +347,6 @@ test('production composition commits automatic titles through Host-owned Session try { const session = await manager.createSession({ cwd: root, - backend: 'ai-sdk', llmConnectionSlug: 'fake', model: 'fake-model', permissionMode: 'ask', @@ -381,20 +378,16 @@ test('production composition commits automatic titles through Host-owned Session test('a legacy fake-backend session is refused with the product reason, not a registry error', async () => { await withCompositionRoot(async ({ root, owner }) => { - const { composition, manager } = await createCapturedExecutionComposition(owner); + // Written by an older build: no creation path here can produce `fake`, so + // the row is seeded under the writer — which is the only way it was ever + // produced — and then read back by a Host that starts up against it, since + // activation dispatches straight off the durable header. + const legacyId = await seedLegacyFakeBackendSession(root, owner); + const { composition } = await createCapturedExecutionComposition(owner); try { - // Written by an older build: this one never produces `fake`, but the - // durable header survives and activation dispatches straight off it. - const legacy = await manager.createSession({ - cwd: root, - backend: 'fake', - llmConnectionSlug: 'fake', - model: 'fake-model', - permissionMode: 'ask', - }); const failure = await composition.handlers['turn.start']( { - sessionId: legacy.id, + sessionId: legacyId, turnId: 'turn-legacy-fake', content: { text: 'resume a retired local simulation' }, }, @@ -423,7 +416,6 @@ test('production composition orphans ownerless ShellRuns before serving Resource const stores = await openInteractiveExecutionStoresForWrite(owner.lease); const session = await stores.sessionStore.create({ cwd: root, - backend: 'ai-sdk', llmConnectionSlug: 'fake', model: 'fake-model', permissionMode: 'ask', @@ -471,7 +463,6 @@ test('production Skill catalog resolves a Graph child durable tool surface', asy const stores = await openInteractiveExecutionStoresForWrite(owner.lease); const parent = await stores.sessionStore.create({ cwd: root, - backend: 'ai-sdk', llmConnectionSlug: 'fake', model: 'fake-model', permissionMode: 'ask', @@ -639,7 +630,6 @@ test('Skill capability previews omit unavailable Tavily search surfaces', async const stores = await openInteractiveExecutionStoresForWrite(owner.lease); const session = await stores.sessionStore.create({ cwd: root, - backend: 'ai-sdk', llmConnectionSlug: connection.slug, model: 'fake-model', permissionMode: 'bypass', @@ -696,7 +686,6 @@ test('production composition validates graph stop before aborting a claimed chil const claims = createAgentGraphControlStore(root); const parent = await stores.sessionStore.create({ cwd: root, - backend: 'ai-sdk', llmConnectionSlug: 'fake', model: 'fake-model', permissionMode: 'ask', @@ -900,6 +889,43 @@ function shellRunRecord( }; } +/** + * Writes one session whose durable header says `backend: 'fake'`. + * + * Nothing in this build can write that value, so the row goes in underneath the + * session writer: create a normal row through the real store, then rewrite the + * persisted backend the way an older build left it on disk. The database + * filename is `OPERATIONAL_STATE_DATABASE_NAME` in `@maka/storage`, which the + * package does not export. + */ +async function seedLegacyFakeBackendSession( + root: string, + owner: InteractiveRootOwner, +): Promise { + const stores = await openInteractiveExecutionStoresForWrite(owner.lease); + const { id: sessionId } = await stores.sessionStore.create({ + cwd: root, + llmConnectionSlug: 'fake', + model: 'fake-model', + permissionMode: 'ask', + }); + + const legacy = new DatabaseSync(join(root, 'runtime.sqlite')); + try { + const row = legacy + .prepare(`SELECT payload_json FROM session_metadata WHERE session_id = ?`) + .get(sessionId) as { payload_json: string }; + const payload = JSON.parse(row.payload_json) as Record; + payload.backend = 'fake'; + legacy + .prepare(`UPDATE session_metadata SET payload_json = ?, backend = ? WHERE session_id = ?`) + .run(JSON.stringify(payload), 'fake', sessionId); + } finally { + legacy.close(); + } + return sessionId; +} + async function createCapturedExecutionComposition(owner: InteractiveRootOwner): Promise<{ composition: Awaited>; manager: SessionManager; @@ -939,7 +965,6 @@ async function createClaimedGraphChild(input: { const child = await input.stores.sessionStore.createSubagent({ cwd: input.root, name: `Graph operator ${input.suffix}`, - backend: 'ai-sdk', llmConnectionSlug: 'fake', model: 'fake-model', permissionMode: 'explore', diff --git a/packages/runtime-host/src/__tests__/execution-inspect-uds.test.ts b/packages/runtime-host/src/__tests__/execution-inspect-uds.test.ts index 633dcbad83..b9af046ec1 100644 --- a/packages/runtime-host/src/__tests__/execution-inspect-uds.test.ts +++ b/packages/runtime-host/src/__tests__/execution-inspect-uds.test.ts @@ -31,7 +31,6 @@ test('a live Host serves Interactive inspection over its real endpoint while ret const session = await stores.sessionStore.create({ cwd: root, name: 'Live inspection', - backend: 'fake', llmConnectionSlug: 'fake', model: 'fake-model', permissionMode: 'ask', diff --git a/packages/runtime-host/src/__tests__/execution-model-composition.test.ts b/packages/runtime-host/src/__tests__/execution-model-composition.test.ts index 4507920821..2f6695fcfa 100644 --- a/packages/runtime-host/src/__tests__/execution-model-composition.test.ts +++ b/packages/runtime-host/src/__tests__/execution-model-composition.test.ts @@ -1059,7 +1059,6 @@ test('production Host executes a canonical ai-sdk Session against a real provide const execution = await openInteractiveExecutionStoresForWrite(owner.lease); const session = await execution.sessionStore.create({ cwd: root, - backend: 'ai-sdk', llmConnectionSlug: 'hosted-real-provider', model: MODEL_ID, permissionMode: 'ask', @@ -1375,7 +1374,6 @@ test('production Host executes and durably supervises an Agent Graph over a real const execution = await openInteractiveExecutionStoresForWrite(owner.lease); const session = await execution.sessionStore.create({ cwd: project, - backend: 'ai-sdk', llmConnectionSlug: 'hosted-graph-provider', model: MODEL_ID, permissionMode: 'bypass', @@ -1576,7 +1574,6 @@ test('production Host executes a durable runnable child with an exact tool ceili const execution = await openInteractiveExecutionStoresForWrite(owner.lease); const parent = await execution.sessionStore.create({ cwd: project, - backend: 'ai-sdk', llmConnectionSlug: 'hosted-child-provider', model: MODEL_ID, permissionMode: 'bypass', @@ -1774,7 +1771,6 @@ test('production Host publishes and retires an implementation child patch', asyn const execution = await openInteractiveExecutionStoresForWrite(owner.lease); const parent = await execution.sessionStore.create({ cwd: project, - backend: 'ai-sdk', llmConnectionSlug: 'hosted-child-provider', model: MODEL_ID, permissionMode: 'bypass', @@ -2018,7 +2014,6 @@ test('Host auxiliary calls preserve resolved DeepSeek reasoning settings', async await publishConnectionModel(policy, connection.connectionId, 'deepseek-v4-flash'); const session = await execution.sessionStore.create({ cwd: capability.canonicalPath, - backend: 'ai-sdk', llmConnectionSlug: 'deepseek-auxiliary', model: 'deepseek-v4-flash', thinkingLevel: 'high', @@ -2097,7 +2092,6 @@ test('Host auxiliary models meter provider usage and abort physical requests', { await publishConnectionModel(policy, connection.connectionId, MODEL_ID); const session = await execution.sessionStore.create({ cwd: capability.canonicalPath, - backend: 'ai-sdk', llmConnectionSlug: 'goal-evaluator-provider', model: MODEL_ID, permissionMode: 'ask', diff --git a/packages/runtime-host/src/__tests__/external-session-coordinator.test.ts b/packages/runtime-host/src/__tests__/external-session-coordinator.test.ts index 1c0d8d2ba8..7848588f26 100644 --- a/packages/runtime-host/src/__tests__/external-session-coordinator.test.ts +++ b/packages/runtime-host/src/__tests__/external-session-coordinator.test.ts @@ -240,7 +240,6 @@ test('imports through the generic importer and treats repeats as independent cop fixture.creates.map(({ input, messages, externalOrigin }) => ({ cwd: input.cwd, name: input.name, - backend: input.backend, messageTypes: messages.map(({ type }) => type), externalOrigin, })), @@ -248,14 +247,12 @@ test('imports through the generic importer and treats repeats as independent cop { cwd: '/external', name: 'Source 0', - backend: 'ai-sdk', messageTypes: ['user'], externalOrigin: { adapterId: 'codex', sourceSessionId: 'source-0' }, }, { cwd: '/external', name: 'Source 0', - backend: 'ai-sdk', messageTypes: ['user'], externalOrigin: { adapterId: 'codex', sourceSessionId: 'source-0' }, }, @@ -567,7 +564,6 @@ function coordinatorFixture( defaultCreate( { cwd: '/external', - backend: 'ai-sdk', llmConnectionSlug: 'default', model: 'gpt-5', permissionMode: 'ask', diff --git a/packages/runtime-host/src/__tests__/fixtures/execution-host-suite.ts b/packages/runtime-host/src/__tests__/fixtures/execution-host-suite.ts index c22b242dcb..ca60aaca76 100644 --- a/packages/runtime-host/src/__tests__/fixtures/execution-host-suite.ts +++ b/packages/runtime-host/src/__tests__/fixtures/execution-host-suite.ts @@ -125,7 +125,6 @@ export class ExecutionFixture { stores = await openInteractiveExecutionStoresForWrite(owner.lease); const session = await stores.sessionStore.create({ cwd: this.root, - backend: 'ai-sdk', llmConnectionSlug: 'fake', model: 'fake-model', permissionMode: 'ask', @@ -475,7 +474,6 @@ export class ExecutionFixture { const child = await stores.sessionStore.createSubagent({ cwd: this.root, name: `${agentName} ${kind}`, - backend: 'ai-sdk', llmConnectionSlug: 'fake', model: 'fake-model', permissionMode: 'explore', @@ -1025,7 +1023,6 @@ export async function withExecutionRoot( stores = await openInteractiveExecutionStoresForWrite(owner.lease); const session = await stores.sessionStore.create({ cwd: root, - backend: 'ai-sdk', llmConnectionSlug: 'fake', model: 'fake-model', permissionMode: 'ask', diff --git a/packages/runtime-host/src/__tests__/goal-coordinator.test.ts b/packages/runtime-host/src/__tests__/goal-coordinator.test.ts index 648d4d6337..09bf58ef89 100644 --- a/packages/runtime-host/src/__tests__/goal-coordinator.test.ts +++ b/packages/runtime-host/src/__tests__/goal-coordinator.test.ts @@ -27,7 +27,6 @@ test('one Host Goal is shared across clients with CAS control and crash-clear re const goalStore = await openInteractiveGoalAuthorityForWrite(owner.lease); const session = await stores.sessionStore.create({ cwd: capability.canonicalPath, - backend: 'fake', llmConnectionSlug: 'fake', model: 'fake-model', permissionMode: 'ask', @@ -225,7 +224,6 @@ test('session retirement forgets a terminal Goal without recreating deleted auth try { const session = await stores.sessionStore.create({ cwd: capability.canonicalPath, - backend: 'fake', llmConnectionSlug: 'fake', model: 'fake-model', permissionMode: 'ask', @@ -308,7 +306,6 @@ test('restart settles the durable current Goal execution through Hosted Executio try { const session = await stores.sessionStore.create({ cwd: capability.canonicalPath, - backend: 'fake', llmConnectionSlug: 'fake', model: 'fake-model', permissionMode: 'ask', @@ -425,7 +422,6 @@ test('restart replaces a stale current execution with the current durable Goal i try { const session = await stores.sessionStore.create({ cwd: capability.canonicalPath, - backend: 'fake', llmConnectionSlug: 'fake', model: 'fake-model', permissionMode: 'ask', diff --git a/packages/runtime-host/src/__tests__/goal-root-authority.test.ts b/packages/runtime-host/src/__tests__/goal-root-authority.test.ts index 8ca97aafba..3f52a6f918 100644 --- a/packages/runtime-host/src/__tests__/goal-root-authority.test.ts +++ b/packages/runtime-host/src/__tests__/goal-root-authority.test.ts @@ -521,7 +521,6 @@ async function createFixture(options: { recoverAdmissions?: boolean } = {}): Pro const goalStore = await openInteractiveGoalAuthorityForWrite(owner.lease); const session = await stores.sessionStore.create({ cwd: capability.canonicalPath, - backend: 'fake', llmConnectionSlug: 'fake', model: 'fake-model', permissionMode: 'ask', @@ -599,7 +598,7 @@ async function createFixture(options: { recoverAdmissions?: boolean } = {}): Pro onSandboxBoundarySettled: async () => {}, }); const backends = new BackendRegistry(); - backends.register('fake', (context) => new FakeBackend(context)); + backends.register('ai-sdk', (context) => new FakeBackend(context)); const authority: RuntimeHostedRootAuthority = { bindRun: (identity) => messages.bindRun(identity), executeRoot: (input) => diff --git a/packages/runtime-host/src/__tests__/interaction-coordinator.test.ts b/packages/runtime-host/src/__tests__/interaction-coordinator.test.ts index 5169d17863..d30ba09b86 100644 --- a/packages/runtime-host/src/__tests__/interaction-coordinator.test.ts +++ b/packages/runtime-host/src/__tests__/interaction-coordinator.test.ts @@ -105,7 +105,6 @@ describe('HostInteractionCoordinator', () => { await mkdir(workspace); const session = await stores.sessionStore.create({ cwd: workspace, - backend: 'fake', llmConnectionSlug: 'fake', model: 'fake-model', permissionMode: 'ask', @@ -237,7 +236,6 @@ describe('HostInteractionCoordinator', () => { await mkdir(workspace); const session = await stores.sessionStore.create({ cwd: workspace, - backend: 'fake', llmConnectionSlug: 'fake', model: 'fake-model', permissionMode: 'ask', @@ -309,7 +307,6 @@ describe('HostInteractionCoordinator', () => { await mkdir(workspace); const session = await stores.sessionStore.create({ cwd: workspace, - backend: 'fake', llmConnectionSlug: 'fake', model: 'fake-model', permissionMode: 'ask', @@ -485,7 +482,6 @@ describe('HostInteractionCoordinator', () => { await mkdir(workspace); const session = await stores.sessionStore.create({ cwd: workspace, - backend: 'fake', llmConnectionSlug: 'fake', model: 'fake-model', permissionMode: 'ask', diff --git a/packages/runtime-host/src/__tests__/plan-two-client-uds.test.ts b/packages/runtime-host/src/__tests__/plan-two-client-uds.test.ts index 00e8f0930b..eb28f54f2f 100644 --- a/packages/runtime-host/src/__tests__/plan-two-client-uds.test.ts +++ b/packages/runtime-host/src/__tests__/plan-two-client-uds.test.ts @@ -37,7 +37,6 @@ test('two Clients and a restarted production Host share one retry-safe Plan auth const planStore = await openInteractivePlanStoreForWrite(owner.lease); const session = await setupStores.sessionStore.create({ cwd: root, - backend: 'ai-sdk', llmConnectionSlug: 'fake', model: 'fake-model', permissionMode: 'explore', diff --git a/packages/runtime-host/src/__tests__/root-turn-coordinator.test.ts b/packages/runtime-host/src/__tests__/root-turn-coordinator.test.ts index 739f60973a..91c7079371 100644 --- a/packages/runtime-host/src/__tests__/root-turn-coordinator.test.ts +++ b/packages/runtime-host/src/__tests__/root-turn-coordinator.test.ts @@ -100,7 +100,8 @@ test('prepares a fresh Agent Graph epoch before durable external Turn admission' let fixture!: FailureFixture; let cutovers = 0; fixture = await createFailureFixture({ - registerBackend: (backends) => backends.register('fake', (context) => new FakeBackend(context)), + registerBackend: (backends) => + backends.register('ai-sdk', (context) => new FakeBackend(context)), agentGraphEpochs: { currentGraphId: async (rootSessionId) => agentGraphIdForRootSession(rootSessionId), beginNextGraphEpoch: async (rootSessionId) => { @@ -137,7 +138,8 @@ test('prepares a fresh Agent Graph epoch before durable external Turn admission' test('does not advance a finished graph for an ordinary default Turn', async () => { let cutovers = 0; const fixture = await createFailureFixture({ - registerBackend: (backends) => backends.register('fake', (context) => new FakeBackend(context)), + registerBackend: (backends) => + backends.register('ai-sdk', (context) => new FakeBackend(context)), agentGraphEpochs: { currentGraphId: async (rootSessionId) => agentGraphIdForRootSession(rootSessionId), beginNextGraphEpoch: async (rootSessionId) => { @@ -168,7 +170,8 @@ test('does not advance a finished graph for an ordinary default Turn', async () test('startup recovery replays one admitted safe-boundary continuation without a UserMessage', async () => { const workspaceIdentity = 'workspace-safe-boundary-recovery'; const fixture = await createFailureFixture({ - registerBackend: (backends) => backends.register('fake', (context) => new FakeBackend(context)), + registerBackend: (backends) => + backends.register('ai-sdk', (context) => new FakeBackend(context)), continuationSafety: { workspaceIdentity, availableToolNames: [] }, }); let observer: ReturnType | undefined; @@ -254,7 +257,8 @@ test('a failed exact Capability retry does not poison the parked continuation bi } }, }, - registerBackend: (backends) => backends.register('fake', (context) => new FakeBackend(context)), + registerBackend: (backends) => + backends.register('ai-sdk', (context) => new FakeBackend(context)), }); const seedConnection = capabilities.attachConnection( clientCapabilityConnectionIdentity('provider-seed'), @@ -395,7 +399,8 @@ test('resume query preserves Session-before-activation lock ordering', async () } }, }, - registerBackend: (backends) => backends.register('fake', (context) => new FakeBackend(context)), + registerBackend: (backends) => + backends.register('ai-sdk', (context) => new FakeBackend(context)), }); const connection = capabilities.attachConnection( clientCapabilityConnectionIdentity('provider-lock-order'), @@ -455,7 +460,8 @@ test('resume query preserves Session-before-activation lock ordering', async () test('turn.start durably applies one exact per-Turn orchestration override', async () => { const fixture = await createFailureFixture({ - registerBackend: (backends) => backends.register('fake', (context) => new FakeBackend(context)), + registerBackend: (backends) => + backends.register('ai-sdk', (context) => new FakeBackend(context)), }); const input = { sessionId: fixture.sessionId, @@ -518,7 +524,8 @@ test('turn.start resolves explicit Skills once before durable admission and repl { send: async () => {} }, ); const fixture = await createFailureFixture({ - registerBackend: (backends) => backends.register('fake', (context) => new FakeBackend(context)), + registerBackend: (backends) => + backends.register('ai-sdk', (context) => new FakeBackend(context)), clientCapabilities: capabilities, prepareSkillInvocation: async ({ sessionId }): Promise => { preparationCount += 1; @@ -635,7 +642,8 @@ test('turn.start durably replays an all-failed invocation without creating a Tur ], }; const fixture = await createFailureFixture({ - registerBackend: (backends) => backends.register('fake', (context) => new FakeBackend(context)), + registerBackend: (backends) => + backends.register('ai-sdk', (context) => new FakeBackend(context)), prepareSkillInvocation: async (): Promise => { preparationCount += 1; return { disposition: 'blocked', skillInvocation }; @@ -669,7 +677,8 @@ test('turn.start durably replays an all-failed invocation without creating a Tur test('idle turn.message.submit applies hosted Skill preparation before durable admission', async () => { let preparationCount = 0; const fixture = await createFailureFixture({ - registerBackend: (backends) => backends.register('fake', (context) => new FakeBackend(context)), + registerBackend: (backends) => + backends.register('ai-sdk', (context) => new FakeBackend(context)), prepareSkillInvocation: async (): Promise => { preparationCount += 1; return { @@ -734,7 +743,8 @@ test('turn.start rejects oversized preparation before admission and preserves no let preparationCount = 0; let preparation: 'blocked' | 'oversized_content' | 'oversized_feedback' = 'blocked'; const fixture = await createFailureFixture({ - registerBackend: (backends) => backends.register('fake', (context) => new FakeBackend(context)), + registerBackend: (backends) => + backends.register('ai-sdk', (context) => new FakeBackend(context)), prepareSkillInvocation: async () => { preparationCount += 1; if (preparation === 'blocked') { @@ -859,7 +869,8 @@ test('turn.start rejects oversized preparation before admission and preserves no test('turn.start admits only canonical live Session Artifact attachments', async () => { const fixture = await createFailureFixture({ - registerBackend: (backends) => backends.register('fake', (context) => new FakeBackend(context)), + registerBackend: (backends) => + backends.register('ai-sdk', (context) => new FakeBackend(context)), withArtifacts: true, }); try { @@ -989,13 +1000,13 @@ test('safe-boundary continuation safety identity uses the exact canonical tool c test('linked child Sessions reject public safe-boundary continuation', async () => { let recoveryCoordinator: RootTurnCoordinator | undefined; const fixture = await createFailureFixture({ - registerBackend: (backends) => backends.register('fake', (context) => new FakeBackend(context)), + registerBackend: (backends) => + backends.register('ai-sdk', (context) => new FakeBackend(context)), }); try { const parent = await fixture.stores.sessionStore.readHeaderSnapshot(fixture.sessionId); const { header: child } = await fixture.stores.sessionStore.createSubagent({ cwd: parent.cwd, - backend: 'fake', llmConnectionSlug: 'fake', model: 'fake-model', permissionMode: 'execute', @@ -1121,7 +1132,7 @@ test('worktree child Sessions reject roots outside managed child execution', asy let recoveryCoordinator: RootTurnCoordinator | undefined; const fixture = await createFailureFixture({ registerBackend: (backends) => - backends.register('fake', (context) => { + backends.register('ai-sdk', (context) => { backend = new LinkedChildAuthorityBackend(context.sessionId); return backend; }), @@ -1139,7 +1150,6 @@ test('worktree child Sessions reject roots outside managed child execution', asy try { const { header: child } = await fixture.stores.sessionStore.createSubagent({ cwd: binding.worktreePath, - backend: 'fake', llmConnectionSlug: 'fake', model: 'fake-model', permissionMode: 'execute', @@ -1304,7 +1314,7 @@ test('Agent Graph supervisor stop owns only graph-capable root Turns', async () let backend: LinkedChildAuthorityBackend | undefined; const fixture = await createFailureFixture({ registerBackend: (backends) => - backends.register('fake', (context) => { + backends.register('ai-sdk', (context) => { backend = new LinkedChildAuthorityBackend(context.sessionId); return backend; }), @@ -1367,7 +1377,7 @@ test('Agent Graph supervisor stop rejects a stale graph identity inside session let currentGraphId = 'graph-before-rollover'; const fixture = await createFailureFixture({ registerBackend: (backends) => - backends.register('fake', (context) => { + backends.register('ai-sdk', (context) => { backend = new LinkedChildAuthorityBackend(context.sessionId); return backend; }), @@ -1419,7 +1429,7 @@ test('Agent Graph supervisor stop owns a graph safe-boundary continuation', asyn const fixture = await createFailureFixture({ continuationSafety: { workspaceIdentity, availableToolNames: [] }, registerBackend: (backends) => - backends.register('fake', (context) => { + backends.register('ai-sdk', (context) => { backend = new BlockingRootBackend(context.sessionId); return backend; }), @@ -1457,7 +1467,7 @@ test('Agent Graph supervisor wake waits for root idle and binds one durable exec let backend: LinkedChildAuthorityBackend | undefined; const fixture = await createFailureFixture({ registerBackend: (backends) => - backends.register('fake', (context) => { + backends.register('ai-sdk', (context) => { backend = new LinkedChildAuthorityBackend(context.sessionId); return backend; }), @@ -1551,7 +1561,7 @@ test('Agent Graph supervisor wake waits for root idle and binds one durable exec test('Agent Graph supervisor wake preserves structured context-overflow outcomes', async () => { const fixture = await createFailureFixture({ registerBackend: (backends) => - backends.register('fake', (context) => new ContextFailureBackend(context.sessionId)), + backends.register('ai-sdk', (context) => new ContextFailureBackend(context.sessionId)), }); const graphId = agentGraphIdForRootSession(fixture.sessionId); try { @@ -1595,7 +1605,7 @@ test('Agent Graph context recovery fences competing root turns while compaction let backend: BlockingContextRecoveryBackend | undefined; const fixture = await createFailureFixture({ registerBackend: (backends) => - backends.register('fake', (context) => { + backends.register('ai-sdk', (context) => { backend = new BlockingContextRecoveryBackend(context.sessionId); return backend; }), @@ -1654,7 +1664,7 @@ test('manual context compact uses durable root query, stop, and exact retry auth let backend: BlockingContextRecoveryBackend | undefined; const fixture = await createFailureFixture({ registerBackend: (backends) => - backends.register('fake', (context) => { + backends.register('ai-sdk', (context) => { backend = new BlockingContextRecoveryBackend(context.sessionId); return backend; }), @@ -1727,7 +1737,7 @@ test('startup recovery replays an admitted context compact with its exact Run id let backend: BlockingContextRecoveryBackend | undefined; const fixture = await createFailureFixture({ registerBackend: (backends) => - backends.register('fake', (context) => { + backends.register('ai-sdk', (context) => { backend = new BlockingContextRecoveryBackend(context.sessionId); return backend; }), @@ -1785,7 +1795,7 @@ test('Agent Graph context recovery abort stops compaction and releases Host clos let backend: BlockingContextRecoveryBackend | undefined; const fixture = await createFailureFixture({ registerBackend: (backends) => - backends.register('fake', (context) => { + backends.register('ai-sdk', (context) => { backend = new BlockingContextRecoveryBackend(context.sessionId); return backend; }), @@ -1823,7 +1833,7 @@ test('Agent Graph context recovery waits for a confirmed follow-up root', async let backend: GraphFollowupRecoveryBackend | undefined; const fixture = await createFailureFixture({ registerBackend: (backends) => - backends.register('fake', (context) => { + backends.register('ai-sdk', (context) => { backend = new GraphFollowupRecoveryBackend(context.sessionId); return backend; }), @@ -1894,7 +1904,8 @@ test('Agent Graph context recovery waits for a confirmed follow-up root', async test('Agent Graph supervisor wake revalidates freshness before durable root admission', async () => { const fixture = await createFailureFixture({ - registerBackend: (backends) => backends.register('fake', (context) => new FakeBackend(context)), + registerBackend: (backends) => + backends.register('ai-sdk', (context) => new FakeBackend(context)), }); const graphId = agentGraphIdForRootSession(fixture.sessionId); const turnId = 'turn-stale-graph-supervisor-wake'; @@ -1937,7 +1948,8 @@ test('Agent Graph supervisor wake revalidates freshness before durable root admi test('Agent Graph supervisor recovery closes a durable admission that has no Run', async () => { const fixture = await createFailureFixture({ - registerBackend: (backends) => backends.register('fake', (context) => new FakeBackend(context)), + registerBackend: (backends) => + backends.register('ai-sdk', (context) => new FakeBackend(context)), }); const graphId = agentGraphIdForRootSession(fixture.sessionId); const wakeId = `${graphId}:snapshot-recovery`; @@ -2007,7 +2019,8 @@ test('Agent Graph supervisor recovery closes a durable admission that has no Run test('hosted root target unavailability is retryable without poisoning the Host', async () => { const fixture = await createFailureFixture({ - registerBackend: (backends) => backends.register('fake', (context) => new FakeBackend(context)), + registerBackend: (backends) => + backends.register('ai-sdk', (context) => new FakeBackend(context)), }); try { await assert.rejects( @@ -2047,7 +2060,6 @@ test('hosted linked child roots share admission, message, terminal, and stop aut const stores = await openInteractiveExecutionStoresForWrite(owner.lease); const parent = await stores.sessionStore.create({ cwd: capability.canonicalPath, - backend: 'fake', llmConnectionSlug: 'fake', model: 'fake-model', permissionMode: 'ask', @@ -2147,7 +2159,7 @@ test('hosted linked child roots share admission, message, terminal, and stop aut }; const backends = new BackendRegistry(); const linkedBackends = new Map(); - backends.register('fake', (context) => { + backends.register('ai-sdk', (context) => { if (!context.header.subagentRuntime) { return new QuestionWaitingBackend(context.sessionId); } @@ -2653,7 +2665,7 @@ test('pre-bind startup failure fail-stops without orphaning an admitted queued M const releaseBackendFactory = deferred(); const fixture = await createFailureFixture({ registerBackend: (backends) => { - backends.register('fake', async () => { + backends.register('ai-sdk', async () => { backendFactoryEntered.resolve(); await releaseBackendFactory.promise; throw new Error('injected backend startup failure'); @@ -2751,7 +2763,7 @@ test('successor admission failure retains the terminal transition and its confir let backend: LinkedChildAuthorityBackend | undefined; const fixture = await createFailureFixture({ registerBackend: (backends) => { - backends.register('fake', (context) => { + backends.register('ai-sdk', (context) => { backend = new LinkedChildAuthorityBackend(context.sessionId); return backend; }); @@ -2878,7 +2890,7 @@ test('shutdown contains a successor backend start rejected by Interaction drain' store.listRootTurnAdmissionsForRecovery(sessionId), }), registerBackend: (backends) => { - backends.register('fake', (context) => { + backends.register('ai-sdk', (context) => { backend = new LinkedChildAuthorityBackend(context.sessionId); return backend; }); @@ -2968,7 +2980,7 @@ test('Client Capability ambiguity fails before durable root admission', async () const fixture = await createFailureFixture({ clientCapabilities, registerBackend: (backends) => { - backends.register('fake', (context) => new FakeBackend(context)); + backends.register('ai-sdk', (context) => new FakeBackend(context)); }, }); const first = clientCapabilities.attachConnection( @@ -3048,7 +3060,7 @@ test('an exact active retry preserves the Client Capability admission binding', const fixture = await createFailureFixture({ clientCapabilities, registerBackend: (backends) => { - backends.register('fake', (context) => { + backends.register('ai-sdk', (context) => { backend = new LinkedChildAuthorityBackend(context.sessionId); return backend; }); @@ -3143,7 +3155,7 @@ test('mixed-Client queued follow-ups preserve each submitting connection through const fixture = await createFailureFixture({ clientCapabilities, registerBackend: (backends) => { - backends.register('fake', (context) => { + backends.register('ai-sdk', (context) => { backend = new LinkedChildAuthorityBackend(context.sessionId); return backend; }); @@ -3288,7 +3300,7 @@ async function assertFollowupCapabilityRebinding(affinity: 'call' | 'turn'): Pro const fixture = await createFailureFixture({ clientCapabilities, registerBackend: (backends) => { - backends.register('fake', (context) => { + backends.register('ai-sdk', (context) => { backend = new LinkedChildAuthorityBackend(context.sessionId); return backend; }); @@ -3479,7 +3491,7 @@ test('an exact terminal retry does not require a live Client Capability binding' const fixture = await createFailureFixture({ clientCapabilities, registerBackend: (backends) => { - backends.register('fake', (context) => new FakeBackend(context)); + backends.register('ai-sdk', (context) => new FakeBackend(context)); }, }); const provider = clientCapabilities.attachConnection( @@ -3571,7 +3583,7 @@ test('turn.start returns a published fast terminal before backend iterator clean let backend: TerminalThenCleanupBackend | undefined; const fixture = await createFailureFixture({ registerBackend: (backends) => { - backends.register('fake', (context) => { + backends.register('ai-sdk', (context) => { backend = new TerminalThenCleanupBackend(context.sessionId); return backend; }); @@ -3629,7 +3641,7 @@ test('public turn.stop rejects an admission queued behind its exact-Run closure const fixture = await createFailureFixture({ withInteractions: true, registerBackend: (backends) => { - backends.register('fake', (context) => { + backends.register('ai-sdk', (context) => { backend = new QueuedAdmissionBackend(context.sessionId); return backend; }); @@ -3704,7 +3716,7 @@ test('public turn.interrupt contains a question admission rejected by its own st const fixture = await createFailureFixture({ withInteractions: true, registerBackend: (backends) => { - backends.register('fake', (context) => { + backends.register('ai-sdk', (context) => { backend = new StopReleasedAdmissionBackend(context.sessionId); return backend; }); @@ -3759,7 +3771,7 @@ test('public turn.interrupt releases the Session lane while a queried Run is sti const fixture = await createFailureFixture({ withInteractions: true, registerBackend: (backends) => { - backends.register('fake', async (context) => { + backends.register('ai-sdk', async (context) => { backendFactoryEntered.resolve(); return await new Promise((_resolve, reject) => { const abort = () => { @@ -3849,7 +3861,7 @@ test('Runtime stop lets a running admission publish before its exact-Run closure await releasePreflight.promise; }, registerBackend: (backends) => { - backends.register('fake', (context) => { + backends.register('ai-sdk', (context) => { backend = new RunningAdmissionBackend(context.sessionId); return backend; }); @@ -3904,7 +3916,7 @@ test('post-start backend failure closes its owner without draining an unrelated const fixture = await createFailureFixture({ withInteractions: true, registerBackend: (backends) => { - backends.register('fake', (context) => { + backends.register('ai-sdk', (context) => { if (context.sessionId !== failingSessionId) { unrelatedBackend = new LinkedChildAuthorityBackend(context.sessionId); return unrelatedBackend; @@ -3920,7 +3932,6 @@ test('post-start backend failure closes its owner without draining an unrelated try { const unrelatedSession = await fixture.stores.sessionStore.create({ cwd: '/tmp/unrelated-active-root', - backend: 'fake', llmConnectionSlug: 'fake', model: 'fake-model', permissionMode: 'ask', @@ -4012,7 +4023,7 @@ test('claimed graph backend failure is contained after its failed terminal trans const fixture = await createFailureFixture({ withInteractions: true, registerBackend: (backends) => { - backends.register('fake', (context) => { + backends.register('ai-sdk', (context) => { backend = new AdmissionThenFailureBackend(context.sessionId); backendReady.resolve(backend); return backend; @@ -4070,7 +4081,7 @@ test('failed claimed graph Run identity mismatch drains instead of being contain const fixture = await createFailureFixture({ withInteractions: true, registerBackend: (backends) => { - backends.register('fake', (context) => { + backends.register('ai-sdk', (context) => { backend = new AdmissionThenFailureBackend(context.sessionId); return backend; }); @@ -4119,7 +4130,7 @@ test('post-start backend AggregateError is contained after its failed terminal t const fixture = await createFailureFixture({ withInteractions: true, registerBackend: (backends) => { - backends.register('fake', (context) => { + backends.register('ai-sdk', (context) => { backend = new AdmissionThenFailureBackend(context.sessionId, aggregateFailure); return backend; }); @@ -4192,7 +4203,7 @@ test('post-start message owner cleanup failure drains after its failed terminal const cleanupFailure = new Error('message owner release failed'); const fixture = await createFailureFixture({ registerBackend: (backends) => { - backends.register('fake', (context) => { + backends.register('ai-sdk', (context) => { backend = new LinkedChildAuthorityBackend(context.sessionId); return backend; }); @@ -4257,7 +4268,7 @@ test('public turn.stop wins the Session lane before a wire answer for the same R const fixture = await createFailureFixture({ withInteractions: true, registerBackend: (backends) => { - backends.register('fake', (context) => { + backends.register('ai-sdk', (context) => { backend = new PendingQuestionBackend(context.sessionId); return backend; }); @@ -4337,7 +4348,7 @@ test('public turn.stop takes over an earlier closure claim queued behind its lea const fixture = await createFailureFixture({ withInteractions: true, registerBackend: (backends) => { - backends.register('fake', (context) => { + backends.register('ai-sdk', (context) => { backend = new TakeoverClosureBackend(context.sessionId); return backend; }); @@ -4656,7 +4667,6 @@ async function createFailureFixture(options: { await artifacts?.recover(); const session = await stores.sessionStore.create({ cwd: capability.canonicalPath, - backend: 'fake', llmConnectionSlug: 'fake', model: 'fake-model', permissionMode: 'ask', @@ -4957,7 +4967,7 @@ async function waitUntil( } class LinkedChildAuthorityBackend implements AgentBackend { - readonly kind = 'fake' as const; + readonly kind = 'ai-sdk' as const; readonly externalHoldStarted = deferred(); readonly questionStarted = deferred(); sendCount = 0; @@ -5052,7 +5062,7 @@ class LinkedChildAuthorityBackend implements AgentBackend { } class BlockingRootBackend implements AgentBackend { - readonly kind = 'fake' as const; + readonly kind = 'ai-sdk' as const; readonly started = deferred(); readonly #released = deferred(); stopCount = 0; @@ -5088,7 +5098,7 @@ class BlockingRootBackend implements AgentBackend { } class ContextFailureBackend implements AgentBackend { - readonly kind = 'fake' as const; + readonly kind = 'ai-sdk' as const; constructor(readonly sessionId: string) {} @@ -5128,7 +5138,7 @@ class ContextFailureBackend implements AgentBackend { } class BlockingContextRecoveryBackend implements AgentBackend { - readonly kind = 'fake' as const; + readonly kind = 'ai-sdk' as const; readonly compactStarted = deferred(); readonly #compactReleased = deferred(); compactInput: BackendCompactHistoryInput | undefined; @@ -5168,7 +5178,7 @@ class BlockingContextRecoveryBackend implements AgentBackend { } class GraphFollowupRecoveryBackend implements AgentBackend { - readonly kind = 'fake' as const; + readonly kind = 'ai-sdk' as const; readonly graphTurnStarted = deferred(); readonly followupStarted = deferred(); readonly compactStarted = deferred(); @@ -5246,7 +5256,7 @@ class GraphFollowupRecoveryBackend implements AgentBackend { } class QueuedAdmissionBackend implements AgentBackend { - readonly kind = 'fake' as const; + readonly kind = 'ai-sdk' as const; readonly readyForAdmission = deferred(); readonly admissionQueued = deferred(); readonly admissionFailure = deferred(); @@ -5328,7 +5338,7 @@ class QueuedAdmissionBackend implements AgentBackend { } class StopReleasedAdmissionBackend implements AgentBackend { - readonly kind = 'fake' as const; + readonly kind = 'ai-sdk' as const; readonly ready = deferred(); private readonly stopped = deferred(); @@ -5383,7 +5393,7 @@ class StopReleasedAdmissionBackend implements AgentBackend { } class RunningAdmissionBackend implements AgentBackend { - readonly kind = 'fake' as const; + readonly kind = 'ai-sdk' as const; readonly admitted = deferred(); readonly stopRequested = deferred(); readonly closureReasons: string[] = []; @@ -5456,7 +5466,7 @@ class RunningAdmissionBackend implements AgentBackend { } class AdmissionThenFailureBackend implements AgentBackend { - readonly kind = 'fake' as const; + readonly kind = 'ai-sdk' as const; readonly admitted = deferred(); readonly closureReasons: string[] = []; private readonly fail = deferred(); @@ -5512,7 +5522,7 @@ class AdmissionThenFailureBackend implements AgentBackend { } class TerminalThenCleanupBackend implements AgentBackend { - readonly kind = 'fake' as const; + readonly kind = 'ai-sdk' as const; cleanupReleased = false; private readonly cleanup = deferred(); @@ -5544,7 +5554,7 @@ class TerminalThenCleanupBackend implements AgentBackend { } class PendingQuestionBackend implements AgentBackend { - readonly kind = 'fake' as const; + readonly kind = 'ai-sdk' as const; readonly pendingRequest = deferred(); readonly closureReasons: string[] = []; answerApplications = 0; @@ -5615,7 +5625,7 @@ class PendingQuestionBackend implements AgentBackend { } class TakeoverClosureBackend implements AgentBackend { - readonly kind = 'fake' as const; + readonly kind = 'ai-sdk' as const; readonly sendStarted = deferred(); readonly stopStarted = deferred(); private readonly sendReleased = deferred(); @@ -5665,7 +5675,7 @@ class TakeoverClosureBackend implements AgentBackend { } class QuestionWaitingBackend implements AgentBackend { - readonly kind = 'fake' as const; + readonly kind = 'ai-sdk' as const; private stopped = false; private resolveAnswer: ((answers: readonly (string | null)[] | null) => void) | undefined; private releaseAfterAnswer: (() => void) | undefined; diff --git a/packages/runtime-host/src/__tests__/runtime-policy-coordinator.test.ts b/packages/runtime-host/src/__tests__/runtime-policy-coordinator.test.ts index 5f01fe125a..3f67ec5cba 100644 --- a/packages/runtime-host/src/__tests__/runtime-policy-coordinator.test.ts +++ b/packages/runtime-host/src/__tests__/runtime-policy-coordinator.test.ts @@ -105,7 +105,6 @@ test('production composition shares one gate across mutation and backend activat const setupStores = await openInteractiveExecutionStoresForWrite(owner.lease); const session = await setupStores.sessionStore.create({ cwd: root, - backend: 'ai-sdk', llmConnectionSlug: 'fake', model: 'fake-model', permissionMode: 'ask', @@ -209,7 +208,6 @@ test('production mutation releases the gate before active-turn backend disposal const setupStores = await openInteractiveExecutionStoresForWrite(owner.lease); const session = await setupStores.sessionStore.create({ cwd: root, - backend: 'ai-sdk', llmConnectionSlug: 'fake', model: 'fake-model', permissionMode: 'ask', @@ -310,7 +308,6 @@ test('production policy mutation drains and poisons activation when cached backe const setupStores = await openInteractiveExecutionStoresForWrite(owner.lease); const session = await setupStores.sessionStore.create({ cwd: root, - backend: 'ai-sdk', llmConnectionSlug: 'fake', model: 'fake-model', permissionMode: 'ask', diff --git a/packages/runtime-host/src/__tests__/session-catalog-two-client-uds.test.ts b/packages/runtime-host/src/__tests__/session-catalog-two-client-uds.test.ts index 414df2ca45..12dd3d53bd 100644 --- a/packages/runtime-host/src/__tests__/session-catalog-two-client-uds.test.ts +++ b/packages/runtime-host/src/__tests__/session-catalog-two-client-uds.test.ts @@ -714,7 +714,6 @@ async function seedAuthority( const execution = await openInteractiveExecutionStoresForWrite(owner.lease); const unread = await execution.sessionStore.create({ cwd: root, - backend: 'fake', llmConnectionSlug: 'fake', model: 'fake-model', permissionMode: 'ask', @@ -751,7 +750,6 @@ async function seedAuthority( 'visible', ...Array.from({ length: 700 }, (_, index) => `label-${index}`), ], - backend: 'fake', llmConnectionSlug: 'fake', model: 'fake-model', permissionMode: 'ask', @@ -759,7 +757,6 @@ async function seedAuthority( const oversized = await execution.sessionStore.create({ cwd: root, projectId: 'p'.repeat(257), - backend: 'fake', llmConnectionSlug: 'fake', model: 'fake-model', permissionMode: 'ask', @@ -767,7 +764,6 @@ async function seedAuthority( const retirement = await execution.sessionStore.create({ cwd: root, name: 'Retirement sidecars', - backend: 'fake', llmConnectionSlug: 'fake', model: 'fake-model', permissionMode: 'ask', @@ -775,7 +771,6 @@ async function seedAuthority( const recovery = await execution.sessionStore.create({ cwd: root, name: 'Retirement recovery', - backend: 'fake', llmConnectionSlug: 'fake', model: 'fake-model', permissionMode: 'ask', diff --git a/packages/runtime-host/src/__tests__/session-retirement-coordinator.test.ts b/packages/runtime-host/src/__tests__/session-retirement-coordinator.test.ts index 471eed4950..08ded21cf7 100644 --- a/packages/runtime-host/src/__tests__/session-retirement-coordinator.test.ts +++ b/packages/runtime-host/src/__tests__/session-retirement-coordinator.test.ts @@ -1237,7 +1237,6 @@ function sessionInput( ): CreateSessionInput { return { cwd: '/workspace', - backend: 'fake', llmConnectionSlug: 'fake', model: 'fake-model', permissionMode: 'ask', diff --git a/packages/runtime-host/src/__tests__/session-revision-two-client-uds.test.ts b/packages/runtime-host/src/__tests__/session-revision-two-client-uds.test.ts index 68021f80ba..7fcc8ee974 100644 --- a/packages/runtime-host/src/__tests__/session-revision-two-client-uds.test.ts +++ b/packages/runtime-host/src/__tests__/session-revision-two-client-uds.test.ts @@ -584,7 +584,6 @@ async function seedSource( const source = await execution.sessionStore.create({ cwd: root, name: 'Source Session', - backend: 'ai-sdk', llmConnectionSlug: 'fake', model: 'fake-model', permissionMode: 'ask', @@ -592,7 +591,6 @@ async function seedSource( const busy = await execution.sessionStore.create({ cwd: root, name: 'Busy Session', - backend: 'ai-sdk', llmConnectionSlug: 'fake', model: 'fake-model', permissionMode: 'ask', @@ -600,7 +598,6 @@ async function seedSource( const linkedChildSource = await execution.sessionStore.create({ cwd: root, name: 'Linked Child Source Session', - backend: 'ai-sdk', llmConnectionSlug: 'fake', model: 'fake-model', permissionMode: 'ask', @@ -608,7 +605,6 @@ async function seedSource( const metadataLinkedSource = await execution.sessionStore.create({ cwd: root, name: 'Metadata-linked Source Session', - backend: 'ai-sdk', llmConnectionSlug: 'fake', model: 'fake-model', permissionMode: 'ask', @@ -616,7 +612,6 @@ async function seedSource( const archivedOwnedSource = await execution.sessionStore.create({ cwd: root, name: 'Archived-owned Source Session', - backend: 'ai-sdk', llmConnectionSlug: 'fake', model: 'fake-model', permissionMode: 'ask', @@ -624,7 +619,6 @@ async function seedSource( const continuationSource = await execution.sessionStore.create({ cwd: root, name: 'Continuation Source Session', - backend: 'ai-sdk', llmConnectionSlug: 'fake', model: 'fake-model', permissionMode: 'ask', @@ -894,7 +888,6 @@ async function seedSource( { cwd: root, name: 'Graph Worker', - backend: 'ai-sdk', llmConnectionSlug: 'fake', model: 'fake-model', permissionMode: 'ask', @@ -1132,7 +1125,6 @@ async function seedSource( const ordinaryLinkedChild = await execution.sessionStore.createSubagent({ cwd: root, name: 'Metadata-linked Child Session', - backend: 'ai-sdk', llmConnectionSlug: 'fake', model: 'fake-model', permissionMode: 'ask', diff --git a/packages/runtime-host/src/__tests__/session-transcript-reader.test.ts b/packages/runtime-host/src/__tests__/session-transcript-reader.test.ts index c5f9a79fc4..d4ee4998b6 100644 --- a/packages/runtime-host/src/__tests__/session-transcript-reader.test.ts +++ b/packages/runtime-host/src/__tests__/session-transcript-reader.test.ts @@ -25,7 +25,6 @@ test('keeps durable history separate from the canonical active overlay', async ( const stores = await openInteractiveExecutionStoresForWrite(owner.lease); const session = await stores.sessionStore.create({ cwd: capability.canonicalPath, - backend: 'fake', llmConnectionSlug: 'fake', model: 'fake-model', permissionMode: 'ask', diff --git a/packages/runtime-host/src/server/session-catalog-coordinator.ts b/packages/runtime-host/src/server/session-catalog-coordinator.ts index 7c1383d999..fe3cc1b43d 100644 --- a/packages/runtime-host/src/server/session-catalog-coordinator.ts +++ b/packages/runtime-host/src/server/session-catalog-coordinator.ts @@ -161,7 +161,6 @@ export class HostSessionCatalogCoordinator { this.#readRuntimePolicy(), ]); return { - backend: 'ai-sdk', llmConnectionSlug: model.connectionSlug, model: model.model, permissionMode: policy.policy.chatDefaults.permissionMode, @@ -346,7 +345,6 @@ export class HostSessionCatalogCoordinator { ...(workspace.projectId === null ? {} : { projectId: workspace.projectId }), name: prepared.name, labels: [...prepared.labels], - backend: 'ai-sdk', llmConnectionSlug: model.connectionSlug, model: model.model, ...(input.thinkingLevel === undefined ? {} : { thinkingLevel: input.thinkingLevel }), diff --git a/packages/runtime-host/src/server/session-revision-coordinator.ts b/packages/runtime-host/src/server/session-revision-coordinator.ts index e9282ea7fd..280915a6c7 100644 --- a/packages/runtime-host/src/server/session-revision-coordinator.ts +++ b/packages/runtime-host/src/server/session-revision-coordinator.ts @@ -552,7 +552,6 @@ export class HostSessionRevisionCoordinator { const common: ConversationCopyCreateInput = { cwd: source.cwd, ...(source.projectId !== undefined ? { projectId: source.projectId } : {}), - backend: source.backend, llmConnectionSlug: source.llmConnectionSlug, model: source.model, ...(source.thinkingLevel !== undefined ? { thinkingLevel: source.thinkingLevel } : {}), diff --git a/packages/runtime/src/__tests__/agent-run-steering-recovery.test.ts b/packages/runtime/src/__tests__/agent-run-steering-recovery.test.ts index 06bc6812df..0da6eaff2e 100644 --- a/packages/runtime/src/__tests__/agent-run-steering-recovery.test.ts +++ b/packages/runtime/src/__tests__/agent-run-steering-recovery.test.ts @@ -21,7 +21,6 @@ test('rejects an invalid tool mode before a durable AgentRun can be created', as const store = createSessionStore(root); const session = await store.create({ cwd: '/tmp/cwd', - backend: 'fake', llmConnectionSlug: 'fake', model: 'fake-model', permissionMode: 'ask', @@ -65,7 +64,6 @@ test('does not re-append atomically committed tool facts through the generic eve const store = createSessionStore(root); const session = await store.create({ cwd: '/tmp/cwd', - backend: 'fake', llmConnectionSlug: 'fake', model: 'fake-model', permissionMode: 'ask', @@ -140,7 +138,6 @@ test('acks a steering event whose canonical append preceded proof publication fa const store = createSessionStore(root); const session = await store.create({ cwd: '/tmp/cwd', - backend: 'fake', llmConnectionSlug: 'fake', model: 'fake-model', permissionMode: 'ask', @@ -218,7 +215,6 @@ test('materializes a durable steering event into the transcript exactly once', a const store = createSessionStore(root); const session = await store.create({ cwd: '/tmp/cwd', - backend: 'fake', llmConnectionSlug: 'fake', model: 'fake-model', permissionMode: 'ask', @@ -298,7 +294,6 @@ test('recovers a steering transcript message from the committed RuntimeEvent led const store = createSessionStore(root); const session = await store.create({ cwd: '/tmp/cwd', - backend: 'fake', llmConnectionSlug: 'fake', model: 'fake-model', permissionMode: 'ask', @@ -387,7 +382,6 @@ test('awaits canonical Run status persistence before accepting an interaction re const store = createSessionStore(root); const session = await store.create({ cwd: '/tmp/cwd', - backend: 'fake', llmConnectionSlug: 'fake', model: 'fake-model', permissionMode: 'ask', @@ -490,7 +484,6 @@ test('required interaction resume recovers a failed best-effort Run Store latch const store = createSessionStore(root); const session = await store.create({ cwd: '/tmp/cwd', - backend: 'fake', llmConnectionSlug: 'fake', model: 'fake-model', permissionMode: 'ask', @@ -605,7 +598,6 @@ test('required interaction resume stays fail-closed until a later required write const store = createSessionStore(root); const session = await store.create({ cwd: '/tmp/cwd', - backend: 'fake', llmConnectionSlug: 'fake', model: 'fake-model', permissionMode: 'ask', diff --git a/packages/runtime/src/__tests__/execution-inspect.test.ts b/packages/runtime/src/__tests__/execution-inspect.test.ts index 61e12bb2c0..cf4e4b3100 100644 --- a/packages/runtime/src/__tests__/execution-inspect.test.ts +++ b/packages/runtime/src/__tests__/execution-inspect.test.ts @@ -22,7 +22,6 @@ describe('versioned execution inspect documents', () => { const runtimeStore = createWorkspaceRuntimeStore(root); const session = await sessionStore.create({ cwd: '/tmp/workspace', - backend: 'fake', llmConnectionSlug: 'fake', model: 'fake-model', permissionMode: 'ask', diff --git a/packages/runtime/src/__tests__/latest-context-commit.test.ts b/packages/runtime/src/__tests__/latest-context-commit.test.ts index ecff35ce09..96c41bcdb0 100644 --- a/packages/runtime/src/__tests__/latest-context-commit.test.ts +++ b/packages/runtime/src/__tests__/latest-context-commit.test.ts @@ -44,7 +44,7 @@ test('a real send seals its row all the way into SQLite, with nothing injected', let clock = 1_000; const now = () => (clock += 1); - backends.register('fake', (ctx) => + backends.register('ai-sdk', (ctx) => createTestAiSdkBackend({ sessionId: ctx.sessionId, header: ctx.header, @@ -79,7 +79,6 @@ test('a real send seals its row all the way into SQLite, with nothing injected', }); const session = await manager.createSession({ cwd: root, - backend: 'fake', llmConnectionSlug: 'mock-main', permissionMode: 'bypass', }); diff --git a/packages/runtime/src/__tests__/runtime-continuation-crash.test.ts b/packages/runtime/src/__tests__/runtime-continuation-crash.test.ts index 013f974376..0a69757089 100644 --- a/packages/runtime/src/__tests__/runtime-continuation-crash.test.ts +++ b/packages/runtime/src/__tests__/runtime-continuation-crash.test.ts @@ -135,7 +135,7 @@ async function runCrashChild(): Promise { const runtimeEventStore = createCrashRuntimeStore(workspaceRoot); const backends = new BackendRegistry(); backends.register( - 'fake', + 'ai-sdk', (ctx) => new FakeBackend({ sessionId: ctx.sessionId, @@ -173,7 +173,6 @@ async function runCrashChild(): Promise { }); const session = await manager.createSession({ cwd: workspaceRoot, - backend: 'fake', llmConnectionSlug: 'fake', model: 'fake-model', permissionMode: 'execute', @@ -229,7 +228,7 @@ function createManager(workspaceRoot: string): { const runtimeEventStore = createCrashRuntimeStore(workspaceRoot); const backends = new BackendRegistry(); backends.register( - 'fake', + 'ai-sdk', (ctx) => new FakeBackend({ sessionId: ctx.sessionId, diff --git a/packages/runtime/src/__tests__/runtime-kernel-interaction.test.ts b/packages/runtime/src/__tests__/runtime-kernel-interaction.test.ts index c8dbbabcdf..b1461b3422 100644 --- a/packages/runtime/src/__tests__/runtime-kernel-interaction.test.ts +++ b/packages/runtime/src/__tests__/runtime-kernel-interaction.test.ts @@ -25,7 +25,7 @@ describe('RuntimeKernel Interaction close cleanup', () => { const updateHeader = store.updateHeader; const backends = new BackendRegistry(); const backend = new BlockingBackend(SESSION_ID, {}); - backends.register('fake', () => backend); + backends.register('ai-sdk', () => backend); const startupFailure = new Error('mark running rejected after reservation'); let stoppedFailure: Promise | undefined; let kernel!: RuntimeKernel; @@ -63,7 +63,7 @@ describe('RuntimeKernel Interaction close cleanup', () => { const store = memoryStore(); const backends = new BackendRegistry(); const backend = new BlockingBackend(SESSION_ID, {}); - backends.register('fake', () => backend); + backends.register('ai-sdk', () => backend); const closeFailure = new Error('bind-race close rejected'); const closeStarted = deferred(); const releaseClose = deferred(); @@ -261,7 +261,7 @@ describe('RuntimeKernel Interaction close cleanup', () => { releaseSendOnStop: false, releaseSendOnDispose: false, }); - backends.register('fake', () => backend); + backends.register('ai-sdk', () => backend); const secondReserved = deferred(); const releaseSecond = deferred(); let reservations = 0; @@ -311,7 +311,7 @@ describe('RuntimeKernel Interaction close cleanup', () => { const store = memoryStore(); const backends = new BackendRegistry(); const built: BlockingBackend[] = []; - backends.register('fake', () => { + backends.register('ai-sdk', () => { const backend = new BlockingBackend( SESSION_ID, built.length === 0 @@ -380,7 +380,7 @@ describe('RuntimeKernel Interaction close cleanup', () => { const built: BlockingBackend[] = []; const stopFailure = new Error('backend stop failed'); const disposeFailure = new Error('backend disposal failed'); - backends.register('fake', () => { + backends.register('ai-sdk', () => { const backend = new BlockingBackend(SESSION_ID, { stopFailure, disposeFailure, @@ -447,7 +447,7 @@ function runtimeFixture(options: RuntimeFixtureOptions = {}): { const store = memoryStore(); const backends = new BackendRegistry(); const backend = new BlockingBackend(SESSION_ID, options); - backends.register('fake', () => backend); + backends.register('ai-sdk', () => backend); const closeFailure = new Error('durable close rejected'); let markCloseStarted!: () => void; const closeStarted = new Promise((resolve) => { @@ -510,7 +510,7 @@ function runtimeFixture(options: RuntimeFixtureOptions = {}): { } class BlockingBackend implements AgentBackend { - readonly kind = 'fake' as const; + readonly kind = 'ai-sdk' as const; readonly stopCalls: Array<{ reason: 'user_stop' | 'redirect'; mode: BackendStopMode | undefined; @@ -610,7 +610,7 @@ function memoryStore(): SessionStore { status: 'active', statusUpdatedAt: 1, hasUnread: false, - backend: 'fake', + backend: 'ai-sdk', llmConnectionSlug: 'test', connectionLocked: true, model: 'test', diff --git a/packages/runtime/src/__tests__/runtime-ledger-repair.test.ts b/packages/runtime/src/__tests__/runtime-ledger-repair.test.ts index 8f5d0312a5..d3903ed1fd 100644 --- a/packages/runtime/src/__tests__/runtime-ledger-repair.test.ts +++ b/packages/runtime/src/__tests__/runtime-ledger-repair.test.ts @@ -70,7 +70,6 @@ test('repairs imported transcript turns into provider-neutral canonical history' const session = await sessions.createImportedSession( { cwd: '/repo', - backend: 'fake', llmConnectionSlug: 'deepseek', model: 'deepseek-v4-flash', permissionMode: 'ask', diff --git a/packages/runtime/src/__tests__/sandbox-boundary-restart-recovery.test.ts b/packages/runtime/src/__tests__/sandbox-boundary-restart-recovery.test.ts index 77eff8a38f..69357e17b9 100644 --- a/packages/runtime/src/__tests__/sandbox-boundary-restart-recovery.test.ts +++ b/packages/runtime/src/__tests__/sandbox-boundary-restart-recovery.test.ts @@ -232,7 +232,6 @@ function runEvent(sessionId: string): EmittedAgentRunEvent { function sessionInput(cwd: string): CreateSessionInput { return { cwd, - backend: 'fake', llmConnectionSlug: 'fake', model: 'fake-model', permissionMode: 'ask', diff --git a/packages/runtime/src/__tests__/session-manager-terminal-ledger.test.ts b/packages/runtime/src/__tests__/session-manager-terminal-ledger.test.ts index ea88125ed3..bf484a033b 100644 --- a/packages/runtime/src/__tests__/session-manager-terminal-ledger.test.ts +++ b/packages/runtime/src/__tests__/session-manager-terminal-ledger.test.ts @@ -220,7 +220,7 @@ describe('SessionManager terminal ledger invariants', () => { const runStore = new TinyAgentRunStore(); const backends = new BackendRegistry(); let backend: StopDuringSendBackend | undefined; - backends.register('fake', (ctx) => { + backends.register('ai-sdk', (ctx) => { backend = new StopDuringSendBackend(ctx); return backend; }); @@ -264,7 +264,7 @@ describe('SessionManager terminal ledger invariants', () => { const store = new TinySessionStore(); const runStore = new TinyAgentRunStore(); const backends = new BackendRegistry(); - backends.register('fake', (ctx) => new NeverEndingBackend(ctx)); + backends.register('ai-sdk', (ctx) => new NeverEndingBackend(ctx)); const manager = new SessionManager({ store, runStore, @@ -305,7 +305,7 @@ describe('SessionManager terminal ledger invariants', () => { }, }); const backends = new BackendRegistry(); - backends.register('fake', (ctx) => new NeverEndingBackend(ctx)); + backends.register('ai-sdk', (ctx) => new NeverEndingBackend(ctx)); const manager = new SessionManager({ store, runStore, @@ -338,7 +338,7 @@ describe('SessionManager terminal ledger invariants', () => { const store = new TinySessionStore(); const runStore = new TinyAgentRunStore(); const backends = new BackendRegistry(); - backends.register('fake', (ctx) => new NeverEndingBackend(ctx)); + backends.register('ai-sdk', (ctx) => new NeverEndingBackend(ctx)); const manager = new SessionManager({ store, runStore, @@ -378,7 +378,7 @@ describe('SessionManager terminal ledger invariants', () => { }); const backends = new BackendRegistry(); backends.register( - 'fake', + 'ai-sdk', (ctx) => new ScriptBackend(ctx, [{ type: 'complete', stopReason: 'step_limit' }]), ); const manager = new SessionManager({ @@ -2137,7 +2137,7 @@ async function makeHarness( const store = options.store ?? new TinySessionStore(); const runStore = new TinyAgentRunStore(); const backends = new BackendRegistry(); - backends.register('fake', (ctx) => new ScriptBackend(ctx, events)); + backends.register('ai-sdk', (ctx) => new ScriptBackend(ctx, events)); const manager = new SessionManager({ store, runStore, @@ -2152,7 +2152,7 @@ async function makeHarness( } class ScriptBackend implements AgentBackend { - readonly kind = 'fake' as const; + readonly kind = 'ai-sdk' as const; readonly sessionId: string; constructor( @@ -2181,7 +2181,7 @@ class ScriptBackend implements AgentBackend { } class StopDuringSendBackend implements AgentBackend { - readonly kind = 'fake' as const; + readonly kind = 'ai-sdk' as const; readonly sessionId: string; private readonly stopStarted = deferred(); private readonly stopReturned = deferred(); @@ -2228,7 +2228,7 @@ class StopDuringSendBackend implements AgentBackend { * can finalize the run. */ class NeverEndingBackend implements AgentBackend { - readonly kind = 'fake' as const; + readonly kind = 'ai-sdk' as const; readonly sessionId: string; constructor(ctx: BackendFactoryContext) { @@ -2282,7 +2282,7 @@ class TinySessionStore implements SessionStore { ...(input.parentSessionId ? { parentSessionId: input.parentSessionId } : {}), ...(input.branchOfTurnId ? { branchOfTurnId: input.branchOfTurnId } : {}), hasUnread: false, - backend: input.backend, + backend: 'ai-sdk', llmConnectionSlug: input.llmConnectionSlug, connectionLocked: false, model: input.model ?? 'fake-model', @@ -2565,7 +2565,6 @@ class BatchingRuntimeEventStore implements RuntimeEventStore { function makeInput(overrides: Partial = {}): CreateSessionInput { return { cwd: '/tmp/cwd', - backend: 'fake', llmConnectionSlug: 'fake', model: 'fake-model', permissionMode: 'ask', diff --git a/packages/runtime/src/__tests__/session-manager.test.ts b/packages/runtime/src/__tests__/session-manager.test.ts index 2835b575b5..be00dd12f6 100644 --- a/packages/runtime/src/__tests__/session-manager.test.ts +++ b/packages/runtime/src/__tests__/session-manager.test.ts @@ -446,7 +446,7 @@ describe('SessionManager graph operator provisioning', () => { const runStore = new MemoryAgentRunStore(); const parentGate = makeGate(); const backends = new BackendRegistry(); - backends.register('fake', (ctx) => new TestBackend(ctx, parentGate)); + backends.register('ai-sdk', (ctx) => new TestBackend(ctx, parentGate)); const manager = new SessionManager({ store, runStore, @@ -1106,7 +1106,7 @@ describe('SessionManager claimed graph intent execution', () => { const runStore = new MemoryAgentRunStore(); const backends = new BackendRegistry(); let backendBuilds = 0; - backends.register('fake', (ctx) => { + backends.register('ai-sdk', (ctx) => { backendBuilds += 1; return new TestBackend(ctx); }); @@ -1138,7 +1138,7 @@ describe('SessionManager claimed graph intent execution', () => { const store = new MemorySessionStore(); const runStore = new MemoryAgentRunStore(); const backends = new BackendRegistry(); - backends.register('fake', (ctx) => new TestBackend(ctx)); + backends.register('ai-sdk', (ctx) => new TestBackend(ctx)); const executions: Parameters[0][] = []; const authority = hostedRootAuthority(); authority.executeRoot = async (input) => { @@ -1232,7 +1232,7 @@ describe('SessionManager claimed graph intent execution', () => { const runStore = new MemoryAgentRunStore(); const backends = new BackendRegistry(); let backendBuilds = 0; - backends.register('fake', (ctx) => { + backends.register('ai-sdk', (ctx) => { backendBuilds += 1; return new TestBackend(ctx); }); @@ -1311,7 +1311,7 @@ describe('SessionManager claimed graph intent execution', () => { const store = new MemorySessionStore(); const runStore = new MemoryAgentRunStore(); const backends = new BackendRegistry(); - backends.register('fake', (ctx) => new TestBackend(ctx)); + backends.register('ai-sdk', (ctx) => new TestBackend(ctx)); const parent = await store.create(makeInput({ name: 'Supervisor' })); const child = await createGraphOperatorSession(store, parent.id); const claim = graphIntentClaim( @@ -1383,7 +1383,7 @@ describe('SessionManager claimed graph intent execution', () => { const runStore = new MemoryAgentRunStore(); const backends = new BackendRegistry(); let backendBuilds = 0; - backends.register('fake', (ctx) => { + backends.register('ai-sdk', (ctx) => { backendBuilds += 1; return new TestBackend(ctx); }); @@ -1446,7 +1446,7 @@ describe('SessionManager claimed graph intent execution', () => { const backends = new BackendRegistry(); const backendGate = makeGate(); const ready = makeGate(); - backends.register('fake', (ctx) => new TestBackend(ctx, backendGate)); + backends.register('ai-sdk', (ctx) => new TestBackend(ctx, backendGate)); const stoppedRoots: RuntimeMessageRunIdentity[] = []; let stoppedSessions = 0; const authority = hostedRootAuthority(); @@ -1562,7 +1562,7 @@ describe('SessionManager claimed graph intent execution', () => { const runStore = new MemoryAgentRunStore(); const backends = new BackendRegistry(); const backendsBySession = new Map(); - backends.register('fake', (ctx) => { + backends.register('ai-sdk', (ctx) => { const backend = new TestBackend(ctx); backendsBySession.set(ctx.sessionId, backend); return backend; @@ -1659,7 +1659,7 @@ describe('SessionManager claimed graph intent execution', () => { const childGate = makeGate(); const started = makeGate(); let childBackend: TestBackend | undefined; - backends.register('fake', (ctx) => { + backends.register('ai-sdk', (ctx) => { childBackend = new TestBackend(ctx, childGate); return childBackend; }); @@ -1743,7 +1743,7 @@ describe('SessionManager claimed graph intent execution', () => { const firstGate = makeGate(); const firstReady = makeGate(); let backend: TestBackend | undefined; - backends.register('fake', (ctx) => { + backends.register('ai-sdk', (ctx) => { backend = new TestBackend(ctx, firstGate); return backend; }); @@ -1801,7 +1801,7 @@ describe('SessionManager claimed graph intent execution', () => { const admissionStarted = makeGate(); const releaseAdmission = makeGate(); let backend: TestBackend | undefined; - backends.register('fake', (ctx) => { + backends.register('ai-sdk', (ctx) => { backend = new TestBackend(ctx); return backend; }); @@ -1913,7 +1913,7 @@ describe('SessionManager claimed graph intent execution', () => { const runStore = new MemoryAgentRunStore(); const backends = new BackendRegistry(); let backendBuilds = 0; - backends.register('fake', (ctx) => { + backends.register('ai-sdk', (ctx) => { backendBuilds += 1; return new TestBackend(ctx); }); @@ -1967,7 +1967,7 @@ describe('SessionManager claimed graph intent execution', () => { const runStore = new MemoryAgentRunStore(); const backends = new BackendRegistry(); let backend: TestBackend | undefined; - backends.register('fake', (ctx) => { + backends.register('ai-sdk', (ctx) => { backend = new TestBackend(ctx); return backend; }); @@ -2018,7 +2018,7 @@ describe('SessionManager claimed graph intent execution', () => { const runStore = new MemoryAgentRunStore(); const backends = new BackendRegistry(); let backendBuilds = 0; - backends.register('fake', (ctx) => { + backends.register('ai-sdk', (ctx) => { backendBuilds += 1; return new TestBackend(ctx); }); @@ -2133,7 +2133,7 @@ describe('SessionManager child-session runtime primitive', () => { const contexts: BackendFactoryContext[] = []; const backendActivationSessions: string[] = []; const backendsBySession = new Map(); - backends.register('fake', (ctx) => { + backends.register('ai-sdk', (ctx) => { contexts.push(ctx); const backend = new TestBackend(ctx, ctx.header.subagentRuntime ? undefined : parentGate); backendsBySession.set(ctx.sessionId, backend); @@ -2191,7 +2191,6 @@ describe('SessionManager child-session runtime primitive', () => { expect(childHeader.cwd).toBe('/tmp/project'); expect(childHeader.projectId).toBe('project-1'); expect(childHeader.workspaceRoot).toBe((await store.readHeader(parent.id)).workspaceRoot); - expect(childHeader.backend).toBe('fake'); expect(childHeader.llmConnectionSlug).toBe('connection-1'); expect(childHeader.model).toBe('model-1'); expect(childHeader.thinkingLevel).toBe('medium'); @@ -2306,7 +2305,7 @@ describe('SessionManager child-session runtime primitive', () => { const backends = new BackendRegistry(); const parentGate = makeGate(); backends.register( - 'fake', + 'ai-sdk', (ctx) => new TestBackend(ctx, ctx.header.subagentRuntime ? undefined : parentGate), ); const manager = new SessionManager({ @@ -2391,7 +2390,7 @@ describe('SessionManager child-session runtime primitive', () => { const backends = new BackendRegistry(); const parentGate = makeGate(); backends.register( - 'fake', + 'ai-sdk', (ctx) => new TestBackend(ctx, ctx.header.subagentRuntime ? undefined : parentGate), ); const manager = new SessionManager({ @@ -2433,7 +2432,7 @@ describe('SessionManager child-session runtime primitive', () => { const backends = new BackendRegistry(); const parentGate = makeGate(); const backendsBySession = new Map(); - backends.register('fake', (ctx) => { + backends.register('ai-sdk', (ctx) => { const backend = new TestBackend(ctx, ctx.header.subagentRuntime ? undefined : parentGate); backendsBySession.set(ctx.sessionId, backend); return backend; @@ -2504,7 +2503,7 @@ describe('SessionManager child-session runtime primitive', () => { const backends = new BackendRegistry(); const parentGate = makeGate(); backends.register( - 'fake', + 'ai-sdk', (ctx) => new TestBackend(ctx, ctx.header.subagentRuntime ? undefined : parentGate), ); const manager = new SessionManager({ @@ -2556,7 +2555,7 @@ describe('SessionManager child-session runtime primitive', () => { const childInputs: BackendSendInput[] = []; let childAttempts = 0; let failClaimOnce = true; - backends.register('fake', (ctx) => { + backends.register('ai-sdk', (ctx) => { if (!ctx.header.subagentRuntime) return new TestBackend(ctx, parentGate); return { kind: 'fake' as const, @@ -2815,7 +2814,7 @@ describe('SessionManager child-session runtime primitive', () => { const backends = new BackendRegistry(); const parentGate = makeGate(); const childInputs: BackendSendInput[] = []; - backends.register('fake', (ctx) => { + backends.register('ai-sdk', (ctx) => { if (!ctx.header.subagentRuntime) return new TestBackend(ctx, parentGate); return { kind: 'fake' as const, @@ -2933,7 +2932,7 @@ describe('SessionManager child-session runtime primitive', () => { const parentGate = makeGate(); const childGate = makeGate(); const backendsBySession = new Map(); - backends.register('fake', (ctx) => { + backends.register('ai-sdk', (ctx) => { const backend = new TestBackend(ctx, ctx.header.subagentRuntime ? childGate : parentGate); backendsBySession.set(ctx.sessionId, backend); return backend; @@ -3011,7 +3010,7 @@ describe('SessionManager child-session runtime primitive', () => { const backends = new BackendRegistry(); const parentGate = makeGate(); backends.register( - 'fake', + 'ai-sdk', (ctx) => new TestBackend(ctx, ctx.header.subagentRuntime ? undefined : parentGate), ); const manager = new SessionManager({ @@ -3088,7 +3087,7 @@ describe('SessionManager child-session runtime primitive', () => { const backends = new BackendRegistry(); const parentGate = makeGate(); backends.register( - 'fake', + 'ai-sdk', (ctx) => new TestBackend(ctx, ctx.header.subagentRuntime ? undefined : parentGate), ); const manager = new SessionManager({ @@ -3227,7 +3226,7 @@ describe('SessionManager child-session runtime primitive', () => { const backends = new BackendRegistry(); const parentGate = makeGate(); const contexts: BackendFactoryContext[] = []; - backends.register('fake', (ctx) => { + backends.register('ai-sdk', (ctx) => { contexts.push(ctx); return new TestBackend(ctx, ctx.header.subagentRuntime ? undefined : parentGate); }); @@ -3321,7 +3320,7 @@ describe('SessionManager child-session runtime primitive', () => { const parentGate = makeGate(); const compactCalls: Array<{ turnId: string; runtimeContextCount: number }> = []; const childBackends: LifecycleChildBackend[] = []; - backends.register('fake', (ctx) => { + backends.register('ai-sdk', (ctx) => { if (!ctx.header.subagentRuntime) return new TestBackend(ctx, parentGate); const backend = new LifecycleChildBackend(ctx, compactCalls); childBackends.push(backend); @@ -3418,7 +3417,7 @@ describe('SessionManager child-session runtime primitive', () => { const runStore = new MemoryAgentRunStore(); const backends = new BackendRegistry(); const parentGate = makeGate(); - backends.register('fake', (ctx) => new TestBackend(ctx, parentGate)); + backends.register('ai-sdk', (ctx) => new TestBackend(ctx, parentGate)); const manager = new SessionManager({ store, runStore, @@ -3533,7 +3532,7 @@ describe('SessionManager child-session runtime primitive', () => { const store = new MemorySessionStore(); const runStore = new MemoryAgentRunStore(); const backends = new BackendRegistry(); - backends.register('fake', (ctx) => new TestBackend(ctx)); + backends.register('ai-sdk', (ctx) => new TestBackend(ctx)); const manager = new SessionManager({ store, runStore, @@ -3569,7 +3568,7 @@ describe('SessionManager child-session runtime primitive', () => { const store = new MemorySessionStore(); const runStore = new MemoryAgentRunStore(); const backends = new BackendRegistry(); - backends.register('fake', (ctx) => new TestBackend(ctx)); + backends.register('ai-sdk', (ctx) => new TestBackend(ctx)); let externalParent: | { sessionId: string; @@ -3623,7 +3622,7 @@ describe('SessionManager child-session runtime primitive', () => { const childGates = [makeGate(), makeGate()]; let childGateIndex = 0; const backendsBySession = new Map(); - backends.register('fake', (ctx) => { + backends.register('ai-sdk', (ctx) => { const gate = ctx.header.subagentRuntime ? childGates[childGateIndex++] : parentGate; const backend = new TestBackend(ctx, gate); backendsBySession.set(ctx.sessionId, backend); @@ -3709,7 +3708,7 @@ describe('SessionManager child-session runtime primitive', () => { const store = new MemorySessionStore(); const runStore = new MemoryAgentRunStore(); const backends = new BackendRegistry(); - backends.register('fake', (ctx) => new TestBackend(ctx)); + backends.register('ai-sdk', (ctx) => new TestBackend(ctx)); const manager = new SessionManager({ store, runStore, @@ -3815,7 +3814,7 @@ describe('SessionManager automatic titles', () => { test('falls back once on generation failure', async () => { const store = new MemorySessionStore(); const backends = new BackendRegistry(); - backends.register('fake', (ctx) => new TestBackend(ctx)); + backends.register('ai-sdk', (ctx) => new TestBackend(ctx)); const changed = makeGate(); let calls = 0; const manager = new SessionManager({ @@ -3844,7 +3843,7 @@ describe('SessionManager automatic titles', () => { test('does not overwrite or notify after a racing manual rename', async () => { const store = new MemorySessionStore(); const backends = new BackendRegistry(); - backends.register('fake', (ctx) => new TestBackend(ctx)); + backends.register('ai-sdk', (ctx) => new TestBackend(ctx)); const release = makeGate(); const attempted = makeGate(); store.generatedTitleAttempted = attempted; @@ -3880,7 +3879,7 @@ describe('SessionManager manual compaction and quiescent session changes', () => const runStore = new OrderingAgentRunStore(); const backends = new BackendRegistry(); const compactCalls: Array<{ turnId: string; runtimeContextCount: number }> = []; - backends.register('fake', (ctx) => new CompactingTestBackend(ctx, compactCalls)); + backends.register('ai-sdk', (ctx) => new CompactingTestBackend(ctx, compactCalls)); const manager = new SessionManager({ store, runStore, @@ -3889,9 +3888,7 @@ describe('SessionManager manual compaction and quiescent session changes', () => newId: nextId(), now: nextNow(10_000), }); - const session = await manager.createSession( - makeInput({ backend: 'fake', permissionMode: 'bypass' }), - ); + const session = await manager.createSession(makeInput({ permissionMode: 'bypass' })); await drain(manager.sendMessage(session.id, { turnId: 'turn-1', text: 'hello' })); runStore.operations = []; @@ -3964,7 +3961,7 @@ describe('SessionManager manual compaction and quiescent session changes', () => warnings: [], }, }); - backends.register('fake', (ctx) => + backends.register('ai-sdk', (ctx) => createTestAiSdkBackend({ sessionId: ctx.sessionId, header: ctx.header, @@ -4025,9 +4022,7 @@ describe('SessionManager manual compaction and quiescent session changes', () => newId: nextId(), now: nextNow(13_000), }); - const session = await manager.createSession( - makeInput({ backend: 'fake', permissionMode: 'bypass' }), - ); + const session = await manager.createSession(makeInput({ permissionMode: 'bypass' })); await drain(manager.sendMessage(session.id, { turnId: 'turn-1', text: 'first '.repeat(400) })); await drain(manager.sendMessage(session.id, { turnId: 'turn-2', text: 'second' })); @@ -4056,7 +4051,7 @@ describe('SessionManager manual compaction and quiescent session changes', () => const store = new MemorySessionStore(); const runStore = new MemoryAgentRunStore(); const backends = new BackendRegistry(); - backends.register('fake', (ctx) => new FailOpenCompactingBackend(ctx)); + backends.register('ai-sdk', (ctx) => new FailOpenCompactingBackend(ctx)); const manager = new SessionManager({ store, runStore, @@ -4065,9 +4060,7 @@ describe('SessionManager manual compaction and quiescent session changes', () => newId: nextId(), now: nextNow(12_000), }); - const session = await manager.createSession( - makeInput({ backend: 'fake', permissionMode: 'bypass' }), - ); + const session = await manager.createSession(makeInput({ permissionMode: 'bypass' })); await drain(manager.sendMessage(session.id, { turnId: 'turn-1', text: 'hello' })); await drain(manager.compactSession(session.id, { turnId: 'turn-compact' })); @@ -4095,7 +4088,7 @@ describe('SessionManager manual compaction and quiescent session changes', () => }); const backends = new BackendRegistry(); const compactCalls: Array<{ turnId: string; runtimeContextCount: number }> = []; - backends.register('fake', (ctx) => new CompactingTestBackend(ctx, compactCalls)); + backends.register('ai-sdk', (ctx) => new CompactingTestBackend(ctx, compactCalls)); const manager = new SessionManager({ store, runStore, @@ -4104,9 +4097,7 @@ describe('SessionManager manual compaction and quiescent session changes', () => newId: nextId(), now: nextNow(15_000), }); - const session = await manager.createSession( - makeInput({ backend: 'fake', permissionMode: 'bypass' }), - ); + const session = await manager.createSession(makeInput({ permissionMode: 'bypass' })); await drain(manager.sendMessage(session.id, { turnId: 'turn-1', text: 'hello' })); blockPriorRead = true; @@ -4141,7 +4132,7 @@ describe('SessionManager manual compaction and quiescent session changes', () => const backends = new BackendRegistry(); const factoryStarts = new Map>(); const factoryModes = new Map(); - backends.register('fake', async (ctx) => { + backends.register('ai-sdk', async (ctx) => { const started = factoryStarts.get(ctx.sessionId); const mode = factoryModes.get(ctx.sessionId); if (!started || !mode) throw new Error('cold compact factory was not configured'); @@ -4168,12 +4159,8 @@ describe('SessionManager manual compaction and quiescent session changes', () => newId: nextId(), now: nextNow(15_500), }); - const cancelledSession = await manager.createSession( - makeInput({ backend: 'fake', permissionMode: 'bypass' }), - ); - const abortErrorSession = await manager.createSession( - makeInput({ backend: 'fake', permissionMode: 'bypass' }), - ); + const cancelledSession = await manager.createSession(makeInput({ permissionMode: 'bypass' })); + const abortErrorSession = await manager.createSession(makeInput({ permissionMode: 'bypass' })); const cancelledStart = makeGate(); const abortErrorStart = makeGate(); factoryStarts.set(cancelledSession.id, cancelledStart); @@ -4212,7 +4199,7 @@ describe('SessionManager manual compaction and quiescent session changes', () => const runStore = new MemoryAgentRunStore(); const backends = new BackendRegistry(); const compactCalls: Array<{ turnId: string; runtimeContextCount: number }> = []; - backends.register('fake', (ctx) => new CompactingTestBackend(ctx, compactCalls)); + backends.register('ai-sdk', (ctx) => new CompactingTestBackend(ctx, compactCalls)); const manager = new SessionManager({ store, runStore, @@ -4221,9 +4208,7 @@ describe('SessionManager manual compaction and quiescent session changes', () => newId: nextId(), now: nextNow(17_000), }); - const session = await manager.createSession( - makeInput({ backend: 'fake', permissionMode: 'bypass' }), - ); + const session = await manager.createSession(makeInput({ permissionMode: 'bypass' })); const readStarted = makeGate(); const releaseRead = makeGate(); store.nextReadHeaderGate = { started: readStarted, release: releaseRead }; @@ -4260,7 +4245,7 @@ describe('SessionManager manual compaction and quiescent session changes', () => let compactingBackend: BlockingCompactBackend | undefined; const backends = new BackendRegistry(); backends.register( - 'fake', + 'ai-sdk', (ctx) => new BlockingCompactBackend(ctx, { compactGate, @@ -4278,9 +4263,7 @@ describe('SessionManager manual compaction and quiescent session changes', () => newId: nextId(), now: nextNow(20_000), }); - const session = await manager.createSession( - makeInput({ backend: 'fake', permissionMode: 'bypass' }), - ); + const session = await manager.createSession(makeInput({ permissionMode: 'bypass' })); await drain(manager.sendMessage(session.id, { turnId: 'turn-1', text: 'hello' })); const compactPromise = collectSessionEvents( @@ -4307,7 +4290,7 @@ describe('SessionManager manual compaction and quiescent session changes', () => const compactCalls: Array<{ turnId: string; runtimeContextCount: number }> = []; const backends = new BackendRegistry(); backends.register( - 'fake', + 'ai-sdk', (ctx) => new ActiveTurnBackend(ctx, { turnStarted, sendGate, compactCalls }), ); const manager = new SessionManager({ @@ -4318,9 +4301,7 @@ describe('SessionManager manual compaction and quiescent session changes', () => newId: nextId(), now: nextNow(25_000), }); - const session = await manager.createSession( - makeInput({ backend: 'fake', permissionMode: 'bypass' }), - ); + const session = await manager.createSession(makeInput({ permissionMode: 'bypass' })); const sendPromise = (async () => { for await (const _event of manager.sendMessage(session.id, { @@ -4486,7 +4467,7 @@ describe('SessionManager manual compaction and quiescent session changes', () => const readStarted = makeGate(); const releaseRead = makeGate(); const backends = new BackendRegistry(); - backends.register('fake', (ctx) => new TestBackend(ctx)); + backends.register('ai-sdk', (ctx) => new TestBackend(ctx)); const manager = new SessionManager({ store, backends, @@ -4533,7 +4514,7 @@ describe('SessionManager manual compaction and quiescent session changes', () => const releaseUpdate = makeGate(); const activatedModels: string[] = []; const backends = new BackendRegistry(); - backends.register('fake', (ctx) => { + backends.register('ai-sdk', (ctx) => { activatedModels.push(ctx.header.model); return new TestBackend(ctx); }); @@ -4583,7 +4564,7 @@ describe('SessionManager manual compaction and quiescent session changes', () => const turnStarted = makeGate(); const backends = new BackendRegistry(); backends.register( - 'fake', + 'ai-sdk', (ctx) => new (class extends ActiveTurnBackend { override async dispose(): Promise { @@ -4620,7 +4601,7 @@ describe('SessionManager manual compaction and quiescent session changes', () => let disposeCalls = 0; const backends = new BackendRegistry(); backends.register( - 'fake', + 'ai-sdk', (ctx) => new (class extends TestBackend { override async dispose(): Promise { @@ -4674,7 +4655,7 @@ describe('SessionManager manual compaction and quiescent session changes', () => let disposeCalls = 0; const backends = new BackendRegistry(); backends.register( - 'fake', + 'ai-sdk', (ctx) => new (class extends TestBackend { constructor() { @@ -4752,7 +4733,7 @@ describe('SessionManager manual compaction and quiescent session changes', () => let builds = 0; const disposed: number[] = []; const backends = new BackendRegistry(); - backends.register('fake', (ctx) => { + backends.register('ai-sdk', (ctx) => { const generation = ++builds; return new (class extends TestBackend { override async dispose(): Promise { @@ -4828,7 +4809,7 @@ describe('SessionManager manual compaction and quiescent session changes', () => let builds = 0; const disposed: number[] = []; const backends = new BackendRegistry(); - backends.register('fake', (ctx) => { + backends.register('ai-sdk', (ctx) => { const generation = ++builds; return new (class extends TestBackend { override async dispose(): Promise { @@ -4898,7 +4879,7 @@ describe('SessionManager manual compaction and quiescent session changes', () => const disposed: string[] = []; const backends = new BackendRegistry(); backends.register( - 'fake', + 'ai-sdk', (ctx) => new (class extends TestBackend { override async dispose(): Promise { @@ -4964,7 +4945,7 @@ describe('SessionManager permission mode updates', () => { const store = new AtomicBoundaryMemorySessionStore(); const calls: string[] = []; const backends = new BackendRegistry(); - backends.register('fake', (ctx) => new TestBackend(ctx)); + backends.register('ai-sdk', (ctx) => new TestBackend(ctx)); const manager = new SessionManager({ store, backends, @@ -5078,7 +5059,7 @@ describe('SessionManager permission mode updates', () => { const firstGate = makeGate(); const secondGate = makeGate(); const gates = [firstGate, secondGate]; - backends.register('fake', (ctx) => new TestBackend(ctx, gates.shift())); + backends.register('ai-sdk', (ctx) => new TestBackend(ctx, gates.shift())); const manager = new SessionManager({ store, runStore, @@ -5129,7 +5110,7 @@ describe('SessionManager permission mode updates', () => { test('leaving explore clears the deep research label so visible read-only copy stays truthful', async () => { const store = new MemorySessionStore(); const backends = new BackendRegistry(); - backends.register('fake', (ctx) => new TestBackend(ctx)); + backends.register('ai-sdk', (ctx) => new TestBackend(ctx)); const manager = new SessionManager({ store, backends, newId: nextId(), now: nextNow(6_000) }); const session = await manager.createSession( makeInput({ @@ -5156,7 +5137,7 @@ describe('SessionManager permission mode updates', () => { const store = new MemorySessionStore(); const runStore = new MemoryAgentRunStore(); const backends = new BackendRegistry(); - backends.register('fake', (ctx) => new FinalTextTestBackend(ctx)); + backends.register('ai-sdk', (ctx) => new FinalTextTestBackend(ctx)); const manager = new SessionManager({ store, runStore, @@ -5189,7 +5170,7 @@ describe('SessionManager permission mode updates', () => { const runStore = new MemoryAgentRunStore(); const backends = new BackendRegistry(); let inspectionCalls = 0; - backends.register('fake', (ctx) => new FinalTextTestBackend(ctx)); + backends.register('ai-sdk', (ctx) => new FinalTextTestBackend(ctx)); const manager = new SessionManager({ store, runStore, @@ -5221,39 +5202,6 @@ describe('SessionManager permission mode updates', () => { expect(run?.workspaceIdentity).toBeUndefined(); }); - test('does not declare the T1 protocol for a backend without the durable tool boundary', async () => { - const store = new MemorySessionStore(); - const runStore = new MemoryAgentRunStore(); - const backends = new BackendRegistry(); - backends.register('fake', (ctx) => new CountingFinalTextBackend(ctx, () => {})); - const runtimeEventStore = Object.assign(runStore, { - toolBoundaryProtocol: 't1_after_preflight_v1' as const, - }); - const manager = new SessionManager({ - store, - runStore, - runtimeEventStore, - toolBoundaryProtocol: 't1_after_preflight_v1', - backends, - newId: nextId(), - now: nextNow(6_050), - runtimeSource: 'test', - }); - const session = await manager.createSession(makeInput()); - - await collectSessionEvents( - manager.sendMessage(session.id, { - turnId: 'turn-no-tool-boundary', - text: 'hello', - }), - ); - - const [run] = await runStore.listSessionRuns(session.id); - if (!run) throw new Error('expected run'); - const events = await runStore.readRuntimeEvents(session.id, run.runId); - expect(events[0]?.actions?.runtimeProtocol).toBeUndefined(); - }); - test('declares the T1 protocol for an AiSdk run when the host wires the durable boundary', async () => { const store = new MemorySessionStore(); const runStore = new MemoryAgentRunStore(); @@ -5269,7 +5217,7 @@ describe('SessionManager permission mode updates', () => { now: nextNow(6_060), runtimeSource: 'test', }); - const session = await manager.createSession(makeInput({ backend: 'ai-sdk' })); + const session = await manager.createSession(makeInput()); await collectSessionEvents( manager.sendMessage(session.id, { @@ -5501,7 +5449,7 @@ describe('SessionManager permission mode updates', () => { const backends = new BackendRegistry(); const observed: InvocationResult[] = []; let backend: FinalTextTestBackend | undefined; - backends.register('fake', (ctx) => { + backends.register('ai-sdk', (ctx) => { backend = new FinalTextTestBackend(ctx); return backend; }); @@ -5591,7 +5539,7 @@ describe('SessionManager permission mode updates', () => { now: nextNow(6_550), runtimeSource: 'test', }); - const session = await manager.createSession(makeInput({ backend: 'ai-sdk' })); + const session = await manager.createSession(makeInput()); const header = await store.readHeader(session.id); const sourceRunId = 'source-run'; const sourceTurnId = 'source-turn'; @@ -5731,7 +5679,7 @@ describe('SessionManager permission mode updates', () => { const sendGate = makeGate(); const backends = new BackendRegistry(); backends.register( - 'fake', + 'ai-sdk', (ctx) => new ActiveTurnBackend(ctx, { turnStarted, @@ -5839,7 +5787,7 @@ describe('SessionManager permission mode updates', () => { const store = new MemorySessionStore(); const runStore = new MemoryAgentRunStore(); const backends = new BackendRegistry(); - backends.register('fake', (ctx) => new FinalTextTestBackend(ctx)); + backends.register('ai-sdk', (ctx) => new FinalTextTestBackend(ctx)); const manager = new SessionManager({ store, runStore, @@ -5984,7 +5932,7 @@ describe('SessionManager permission mode updates', () => { const store = new MemorySessionStore(); const runStore = new MemoryAgentRunStore(); const backends = new BackendRegistry(); - backends.register('fake', (ctx) => new FinalTextTestBackend(ctx)); + backends.register('ai-sdk', (ctx) => new FinalTextTestBackend(ctx)); const manager = new SessionManager({ store, runStore, @@ -6098,7 +6046,7 @@ describe('SessionManager permission mode updates', () => { const backends = new BackendRegistry(); let backendCalls = 0; backends.register( - 'fake', + 'ai-sdk', (ctx) => new CountingFinalTextBackend(ctx, () => { backendCalls += 1; @@ -6185,7 +6133,7 @@ describe('SessionManager permission mode updates', () => { const backends = new BackendRegistry(); let backendCalls = 0; backends.register( - 'fake', + 'ai-sdk', (ctx) => new CountingFinalTextBackend(ctx, () => { backendCalls += 1; @@ -6280,7 +6228,7 @@ describe('SessionManager permission mode updates', () => { const backends = new BackendRegistry(); let backendCalls = 0; backends.register( - 'fake', + 'ai-sdk', (ctx) => new CountingFinalTextBackend(ctx, () => { backendCalls += 1; @@ -6391,7 +6339,7 @@ describe('SessionManager permission mode updates', () => { const releaseContinuationCommit = makeGate(); let backendCalls = 0; backends.register( - 'fake', + 'ai-sdk', (ctx) => new CountingFinalTextBackend(ctx, () => { backendCalls += 1; @@ -6489,7 +6437,7 @@ describe('SessionManager permission mode updates', () => { const backends = new BackendRegistry(); let backendCalls = 0; backends.register( - 'fake', + 'ai-sdk', (ctx) => new CountingFinalTextBackend(ctx, () => { backendCalls += 1; @@ -6571,7 +6519,7 @@ describe('SessionManager permission mode updates', () => { const backends = new BackendRegistry(); let backendCalls = 0; backends.register( - 'fake', + 'ai-sdk', (ctx) => new CountingFinalTextBackend(ctx, () => { backendCalls += 1; @@ -6659,7 +6607,7 @@ describe('SessionManager permission mode updates', () => { let backendCalls = 0; let failOnce = true; backends.register( - 'fake', + 'ai-sdk', (ctx) => new CountingFinalTextBackend(ctx, () => { backendCalls += 1; @@ -6803,7 +6751,7 @@ describe('SessionManager permission mode updates', () => { let workspaceIdentity = 'workspace-1'; const lifecycleEvents: Array<{ type: string; errorClass?: string }> = []; backends.register( - 'fake', + 'ai-sdk', (ctx) => new CountingFinalTextBackend(ctx, () => { backendCalls += 1; @@ -6907,7 +6855,7 @@ describe('SessionManager permission mode updates', () => { let backendCalls = 0; let availableToolNames: readonly string[] = scenario.planned; backends.register( - 'fake', + 'ai-sdk', (ctx) => new CountingFinalTextBackend(ctx, () => { backendCalls += 1; @@ -6995,7 +6943,7 @@ describe('SessionManager permission mode updates', () => { let backendCalls = 0; let availableToolNames: readonly string[] = ['Write']; backends.register( - 'fake', + 'ai-sdk', (ctx) => new CountingFinalTextBackend(ctx, () => { backendCalls += 1; @@ -7103,7 +7051,7 @@ describe('SessionManager permission mode updates', () => { const backends = new BackendRegistry(); let backendCalls = 0; backends.register( - 'fake', + 'ai-sdk', (ctx) => new CountingFinalTextBackend(ctx, () => { backendCalls += 1; @@ -7181,7 +7129,7 @@ describe('SessionManager permission mode updates', () => { const runStore = new MemoryAgentRunStore(); const backends = new BackendRegistry(); let backend: TestBackend | undefined; - backends.register('fake', (ctx) => { + backends.register('ai-sdk', (ctx) => { backend = new TestBackend(ctx); return backend; }); @@ -7291,7 +7239,7 @@ describe('SessionManager permission mode updates', () => { const runStore = new MemoryAgentRunStore({ failRuntimeEventAppendAfter: 3 }); const backends = new BackendRegistry(); let backend: TestBackend | undefined; - backends.register('fake', (ctx) => { + backends.register('ai-sdk', (ctx) => { backend = new TestBackend(ctx); return backend; }); @@ -7418,7 +7366,7 @@ describe('SessionManager permission mode updates', () => { const store = new MemorySessionStore(); const runStore = new MemoryAgentRunStore(); const backends = new BackendRegistry(); - backends.register('fake', (ctx) => new TestBackend(ctx)); + backends.register('ai-sdk', (ctx) => new TestBackend(ctx)); const manager = new SessionManager({ store, runStore, @@ -7444,7 +7392,7 @@ describe('SessionManager permission mode updates', () => { const runStore = new MemoryAgentRunStore(); const backends = new BackendRegistry(); let backend: TestBackend | undefined; - backends.register('fake', (ctx) => { + backends.register('ai-sdk', (ctx) => { backend = new TestBackend(ctx); return backend; }); @@ -7537,7 +7485,7 @@ describe('SessionManager permission mode updates', () => { const runStore = new MemoryAgentRunStore(); const backends = new BackendRegistry(); let backend: TestBackend | undefined; - backends.register('fake', (ctx) => { + backends.register('ai-sdk', (ctx) => { backend = new TestBackend(ctx); return backend; }); @@ -9026,7 +8974,7 @@ describe('SessionManager permission mode updates', () => { const runStore = new MemoryAgentRunStore(); const backends = new BackendRegistry(); backends.register( - 'fake', + 'ai-sdk', (ctx) => new EventBackend(ctx, [{ type: 'complete', stopReason: 'end_turn' }]), ); const manager = new SessionManager({ @@ -9076,7 +9024,7 @@ describe('SessionManager permission mode updates', () => { }); const backends = new BackendRegistry(); let backend: TestBackend | undefined; - backends.register('fake', (ctx) => { + backends.register('ai-sdk', (ctx) => { backend = new TestBackend(ctx); return backend; }); @@ -9132,7 +9080,7 @@ describe('SessionManager permission mode updates', () => { const runStore = new MemoryAgentRunStore(); const backends = new BackendRegistry(); backends.register( - 'fake', + 'ai-sdk', (ctx) => new EventBackend(ctx, [{ type: 'complete', stopReason: 'end_turn' }]), ); const manager = new SessionManager({ @@ -9245,7 +9193,7 @@ describe('SessionManager permission mode updates', () => { const runStore = new MemoryAgentRunStore(); const backends = new BackendRegistry(); const backendInstances: TestBackend[] = []; - backends.register('fake', (ctx) => { + backends.register('ai-sdk', (ctx) => { const backend = new TestBackend(ctx); backendInstances.push(backend); return backend; @@ -9370,7 +9318,7 @@ describe('SessionManager permission mode updates', () => { const runStore = new MemoryAgentRunStore(); const backends = new BackendRegistry(); const backendInstances: TestBackend[] = []; - backends.register('fake', (ctx) => { + backends.register('ai-sdk', (ctx) => { const backend = new TestBackend(ctx); backendInstances.push(backend); return backend; @@ -9540,7 +9488,7 @@ describe('SessionManager permission mode updates', () => { const runStore = new MemoryAgentRunStore(); const backends = new BackendRegistry(); const backendInstances: TestBackend[] = []; - backends.register('fake', (ctx) => { + backends.register('ai-sdk', (ctx) => { const backend = new TestBackend(ctx); backendInstances.push(backend); return backend; @@ -9623,7 +9571,7 @@ describe('SessionManager permission mode updates', () => { const runStore = new MemoryAgentRunStore({ failUpdateRunStatusOnce: 'failed' }); const backends = new BackendRegistry(); let backend: TurnScriptBackend | undefined; - backends.register('fake', (ctx) => { + backends.register('ai-sdk', (ctx) => { backend = new TurnScriptBackend(ctx, [ [{ type: 'complete', stopReason: 'error' }], [ @@ -9677,7 +9625,7 @@ describe('SessionManager permission mode updates', () => { const runStore = new MemoryAgentRunStore(); const backends = new BackendRegistry(); const backendInstances: TestBackend[] = []; - backends.register('fake', (ctx) => { + backends.register('ai-sdk', (ctx) => { const backend = new TestBackend(ctx); backendInstances.push(backend); return backend; @@ -9766,7 +9714,7 @@ describe('SessionManager permission mode updates', () => { const runStore = new MemoryAgentRunStore(); const backends = new BackendRegistry(); const backendInstances: TestBackend[] = []; - backends.register('fake', (ctx) => { + backends.register('ai-sdk', (ctx) => { const backend = new TestBackend(ctx); backendInstances.push(backend); return backend; @@ -9812,7 +9760,7 @@ describe('SessionManager permission mode updates', () => { const runStore = new MemoryAgentRunStore(); const backends = new BackendRegistry(); const childBackends: TestBackend[] = []; - backends.register('fake', (ctx) => { + backends.register('ai-sdk', (ctx) => { const backend = new TestBackend(ctx); if (ctx.systemPrompt) childBackends.push(backend); return backend; @@ -9890,7 +9838,7 @@ describe('SessionManager permission mode updates', () => { const store = new MemorySessionStore(); const runStore = new MemoryAgentRunStore(); const backends = new BackendRegistry(); - backends.register('fake', (ctx) => new TestBackend(ctx)); + backends.register('ai-sdk', (ctx) => new TestBackend(ctx)); const manager = new SessionManager({ store, runStore, @@ -10006,7 +9954,7 @@ describe('SessionManager permission mode updates', () => { async respondToSandboxBoundary(): Promise {}, async dispose(): Promise {}, }); - backends.register('fake', (ctx) => { + backends.register('ai-sdk', (ctx) => { contexts.push(ctx); return createBackend(ctx); }); @@ -10179,7 +10127,7 @@ describe('SessionManager permission mode updates', () => { const runStore = new MemoryAgentRunStore(); const backends = new BackendRegistry(); let providerDispatches = 0; - backends.register('fake', (ctx) => ({ + backends.register('ai-sdk', (ctx) => ({ kind: 'fake' as const, sessionId: ctx.sessionId, async *send(input: BackendSendInput): AsyncIterable { @@ -10321,7 +10269,7 @@ describe('SessionManager permission mode updates', () => { yield* super.send(input); } } - backends.register('fake', (ctx) => { + backends.register('ai-sdk', (ctx) => { contexts.push(ctx); return new SeamProbeBackend(ctx); }); @@ -10371,7 +10319,7 @@ describe('SessionManager permission mode updates', () => { const runStore = new MemoryAgentRunStore(); const backends = new BackendRegistry(); const contexts: BackendFactoryContext[] = []; - backends.register('fake', (ctx) => { + backends.register('ai-sdk', (ctx) => { contexts.push(ctx); return new TestBackend(ctx); }); @@ -10414,7 +10362,7 @@ describe('SessionManager permission mode updates', () => { const runStore = new MemoryAgentRunStore(); const backends = new BackendRegistry(); const contexts: BackendFactoryContext[] = []; - backends.register('fake', (ctx) => { + backends.register('ai-sdk', (ctx) => { contexts.push(ctx); return new TestBackend(ctx); }); @@ -10478,7 +10426,7 @@ describe('SessionManager permission mode updates', () => { const store = new MemorySessionStore(); const runStore = new MemoryAgentRunStore({ failUpdateRunStatusOnce: 'completed' }); const backends = new BackendRegistry(); - backends.register('fake', (ctx) => new TestBackend(ctx)); + backends.register('ai-sdk', (ctx) => new TestBackend(ctx)); const manager = new SessionManager({ store, runStore, @@ -10533,7 +10481,7 @@ describe('SessionManager permission mode updates', () => { const store = new MemorySessionStore(); const backends = new BackendRegistry(); backends.register( - 'fake', + 'ai-sdk', (ctx) => new EventBackend(ctx, [{ type: 'complete', stopReason: 'step_limit' }]), ); const manager = new SessionManager({ @@ -10561,7 +10509,7 @@ describe('SessionManager permission mode updates', () => { const store = new MemorySessionStore(); const runStore = new MemoryAgentRunStore(); const backends = new BackendRegistry(); - backends.register('fake', (ctx) => new HighVolumeDeltaBackend(ctx, 512)); + backends.register('ai-sdk', (ctx) => new HighVolumeDeltaBackend(ctx, 512)); const manager = new SessionManager({ store, runStore, @@ -10603,7 +10551,7 @@ describe('SessionManager permission mode updates', () => { const backends = new BackendRegistry(); const childGate = makeGate(); const backendInstances: TestBackend[] = []; - backends.register('fake', (ctx) => { + backends.register('ai-sdk', (ctx) => { const backend = new TestBackend( ctx, ctx.header.permissionMode === 'explore' ? childGate : undefined, @@ -10658,7 +10606,7 @@ describe('SessionManager permission mode updates', () => { const runStore = new MemoryAgentRunStore(); const backends = new BackendRegistry(); let childBackend: TestBackend | undefined; - backends.register('fake', (ctx) => { + backends.register('ai-sdk', (ctx) => { childBackend = new TestBackend(ctx); return childBackend; }); @@ -10711,7 +10659,7 @@ describe('SessionManager permission mode updates', () => { const backends = new BackendRegistry(); const activeGate = makeGate(); let backend: TestBackend | undefined; - backends.register('fake', (ctx) => { + backends.register('ai-sdk', (ctx) => { backend = new TestBackend(ctx, activeGate); return backend; }); @@ -10766,7 +10714,7 @@ describe('SessionManager permission mode updates', () => { const runStore = new MemoryAgentRunStore(); const backends = new BackendRegistry(); let backend: TestBackend | undefined; - backends.register('fake', (ctx) => { + backends.register('ai-sdk', (ctx) => { backend = new TestBackend(ctx); return backend; }); @@ -10819,7 +10767,7 @@ describe('SessionManager permission mode updates', () => { const backends = new BackendRegistry(); const buildStarted = makeGate(); const releaseBuild = makeGate(); - backends.register('fake', async () => { + backends.register('ai-sdk', async () => { buildStarted.release(); await releaseBuild.promise; const error = new Error('backend activation timed out'); @@ -10871,7 +10819,7 @@ describe('SessionManager permission mode updates', () => { const buildStarted = makeGate(); let factorySignal: AbortSignal | undefined; let dispatches = 0; - backends.register('fake', async (ctx) => { + backends.register('ai-sdk', async (ctx) => { factorySignal = ctx.abortSignal; if (!factorySignal) throw new Error('backend factory did not receive an abort signal'); buildStarted.release(); @@ -10916,7 +10864,7 @@ describe('SessionManager permission mode updates', () => { const runStore = new MemoryAgentRunStore(); const backends = new BackendRegistry(); const buildStarted = makeGate(); - backends.register('fake', async (ctx) => { + backends.register('ai-sdk', async (ctx) => { if (!ctx.abortSignal) throw new Error('backend factory did not receive an abort signal'); buildStarted.release(); await timerDelay(60_000, undefined, { signal: ctx.abortSignal }); @@ -10958,7 +10906,7 @@ describe('SessionManager permission mode updates', () => { let builds = 0; let firstDisposeCalls = 0; let firstDispatches = 0; - backends.register('fake', async (ctx) => { + backends.register('ai-sdk', async (ctx) => { builds += 1; if (builds === 1) { buildStarted.release(); @@ -11025,7 +10973,7 @@ describe('SessionManager permission mode updates', () => { const releaseBuild = makeGate(); let builds = 0; let disposeCalls = 0; - backends.register('fake', async (ctx) => { + backends.register('ai-sdk', async (ctx) => { builds += 1; buildStarted.release(); await releaseBuild.promise; @@ -11099,7 +11047,7 @@ describe('SessionManager permission mode updates', () => { const backends = new BackendRegistry(); const childGate = makeGate(); let childBackend: RetryStopBackend | undefined; - backends.register('fake', (ctx) => { + backends.register('ai-sdk', (ctx) => { if (ctx.header.permissionMode !== 'explore') return new TestBackend(ctx); childBackend = new RetryStopBackend(ctx, childGate); return childBackend; @@ -11152,7 +11100,7 @@ describe('SessionManager permission mode updates', () => { const stopStarted = makeGate(); const releaseStop = makeGate(); let backend: ConcurrentStopBackend | undefined; - backends.register('fake', (ctx) => { + backends.register('ai-sdk', (ctx) => { backend = new ConcurrentStopBackend(ctx, stopStarted, releaseStop); return backend; }); @@ -11204,7 +11152,7 @@ describe('SessionManager permission mode updates', () => { const runStore = new MemoryAgentRunStore(); const backends = new BackendRegistry(); let backend: TestBackend | undefined; - backends.register('fake', (ctx) => { + backends.register('ai-sdk', (ctx) => { backend = new TestBackend(ctx); return backend; }); @@ -11262,7 +11210,7 @@ describe('SessionManager permission mode updates', () => { const runStore = new MemoryAgentRunStore(); const backends = new BackendRegistry(); let backend: TestBackend | undefined; - backends.register('fake', (ctx) => { + backends.register('ai-sdk', (ctx) => { backend = new TestBackend(ctx); return backend; }); @@ -11312,7 +11260,7 @@ describe('SessionManager permission mode updates', () => { const releaseSend = makeGate(); let builds = 0; let backend: PermissionBroadcastBackend | undefined; - backends.register('fake', async (ctx) => { + backends.register('ai-sdk', async (ctx) => { builds += 1; buildStarted.release(); await releaseBuild.promise; @@ -11368,7 +11316,7 @@ describe('SessionManager permission mode updates', () => { const childGate = makeGate(); let parentBackend: SandboxBoundaryWaitBackend | undefined; let childBackend: PermissionBroadcastBackend | undefined; - backends.register('fake', (ctx) => { + backends.register('ai-sdk', (ctx) => { if (ctx.header.permissionMode === 'explore') { childBackend = new PermissionBroadcastBackend(ctx, childGate); return childBackend; @@ -11422,7 +11370,7 @@ describe('SessionManager permission mode updates', () => { const childGate = makeGate(); let parentBackend: CountingStopBackend | undefined; let childBackend: RetryStopBackend | undefined; - backends.register('fake', (ctx) => { + backends.register('ai-sdk', (ctx) => { if (ctx.header.permissionMode === 'explore') { childBackend = new RetryStopBackend(ctx, childGate); return childBackend; @@ -11480,7 +11428,7 @@ describe('SessionManager permission mode updates', () => { const backends = new BackendRegistry(); const sendGate = makeGate(); let backend: CountingStopBackend | undefined; - backends.register('fake', (ctx) => { + backends.register('ai-sdk', (ctx) => { backend = new CountingStopBackend(ctx, sendGate); return backend; }); @@ -11530,7 +11478,7 @@ describe('SessionManager permission mode updates', () => { const backends = new BackendRegistry(); const sendGate = makeGate(); let backend: CountingStopBackend | undefined; - backends.register('fake', (ctx) => { + backends.register('ai-sdk', (ctx) => { backend = new CountingStopBackend(ctx, sendGate); return backend; }); @@ -11580,7 +11528,7 @@ describe('SessionManager permission mode updates', () => { const runStore = new MemoryAgentRunStore(); const backends = new BackendRegistry(); const backendInstances: TestBackend[] = []; - backends.register('fake', (ctx) => { + backends.register('ai-sdk', (ctx) => { const backend = new TestBackend(ctx); backendInstances.push(backend); return backend; @@ -11642,7 +11590,7 @@ describe('SessionManager permission mode updates', () => { const store = new MemorySessionStore(); const runStore = new MemoryAgentRunStore(); const backends = new BackendRegistry(); - backends.register('fake', (ctx) => new TestBackend(ctx)); + backends.register('ai-sdk', (ctx) => new TestBackend(ctx)); const manager = new SessionManager({ store, runStore, @@ -11844,7 +11792,7 @@ describe('SessionManager permission mode updates', () => { const store = new MemorySessionStore(); const runStore = new MemoryAgentRunStore(); const backends = new BackendRegistry(); - backends.register('fake', (ctx) => new TestBackend(ctx)); + backends.register('ai-sdk', (ctx) => new TestBackend(ctx)); const manager = new SessionManager({ store, runStore, @@ -11932,7 +11880,7 @@ describe('SessionManager permission mode updates', () => { const store = new MemorySessionStore(); const runStore = new MemoryAgentRunStore(); const backends = new BackendRegistry(); - backends.register('fake', (ctx) => new TestBackend(ctx)); + backends.register('ai-sdk', (ctx) => new TestBackend(ctx)); const manager = new SessionManager({ store, runStore, @@ -12019,7 +11967,7 @@ describe('SessionManager permission mode updates', () => { const store = new MemorySessionStore(); const runStore = new MemoryAgentRunStore(); const backends = new BackendRegistry(); - backends.register('fake', (ctx) => new TestBackend(ctx)); + backends.register('ai-sdk', (ctx) => new TestBackend(ctx)); const manager = new SessionManager({ store, runStore, @@ -12096,7 +12044,7 @@ describe('SessionManager permission mode updates', () => { const store = new MemorySessionStore(); const runStore = new MemoryAgentRunStore(); const backends = new BackendRegistry(); - backends.register('fake', () => { + backends.register('ai-sdk', () => { throw new Error('backend init failed'); }); const manager = new SessionManager({ @@ -12145,7 +12093,7 @@ describe('SessionManager permission mode updates', () => { const runStore = new MemoryAgentRunStore(); const backends = new BackendRegistry(); let backend: SandboxBoundaryWaitBackend | undefined; - backends.register('fake', (ctx) => { + backends.register('ai-sdk', (ctx) => { backend = new SandboxBoundaryWaitBackend(ctx); return backend; }); @@ -12192,7 +12140,7 @@ describe('SessionManager permission mode updates', () => { const store = new MemorySessionStore(); const runStore = new MemoryAgentRunStore(); const backends = new BackendRegistry(); - backends.register('fake', (ctx) => new FakeBackend(ctx)); + backends.register('ai-sdk', (ctx) => new FakeBackend(ctx)); const manager = new SessionManager({ store, runStore, @@ -12201,7 +12149,7 @@ describe('SessionManager permission mode updates', () => { newId: nextId(), now: nextNow(9_000), }); - const session = await manager.createSession(makeInput({ backend: 'fake' })); + const session = await manager.createSession(makeInput()); const iterator = manager .sendMessage(session.id, { turnId: 'turn-1', text: FAKE_ASK_USER_QUESTION_PROMPT }) @@ -12248,7 +12196,7 @@ describe('SessionManager permission mode updates', () => { const store = new MemorySessionStore(); const runStore = new MemoryAgentRunStore(); const backends = new BackendRegistry(); - backends.register('fake', (ctx) => new FakeBackend(ctx)); + backends.register('ai-sdk', (ctx) => new FakeBackend(ctx)); const manager = new SessionManager({ store, runStore, @@ -12257,7 +12205,7 @@ describe('SessionManager permission mode updates', () => { newId: nextId(), now: nextNow(9_000), }); - const session = await manager.createSession(makeInput({ backend: 'fake' })); + const session = await manager.createSession(makeInput()); const iterator = manager .sendMessage(session.id, { turnId: 'turn-1', text: FAKE_ASK_USER_QUESTION_PROMPT }) @@ -12282,7 +12230,7 @@ describe('SessionManager permission mode updates', () => { const store = new MemorySessionStore(); const runStore = new MemoryAgentRunStore(); const backends = new BackendRegistry(); - backends.register('fake', (ctx) => new SandboxBoundaryWaitBackend(ctx)); + backends.register('ai-sdk', (ctx) => new SandboxBoundaryWaitBackend(ctx)); const manager = new SessionManager({ store, runStore, @@ -12317,7 +12265,7 @@ describe('SessionManager permission mode updates', () => { const store = new MemorySessionStore(); const runStore = new MemoryAgentRunStore(); const backends = new BackendRegistry(); - backends.register('fake', (ctx) => new UnmappedSessionEventBackend(ctx)); + backends.register('ai-sdk', (ctx) => new UnmappedSessionEventBackend(ctx)); const manager = new SessionManager({ store, runStore, @@ -12359,7 +12307,7 @@ describe('SessionManager permission mode updates', () => { const store = new MemorySessionStore(); const runStore = new MemoryAgentRunStore(); const backends = new BackendRegistry(); - backends.register('fake', (ctx) => new UnmappedSessionEventBackend(ctx)); + backends.register('ai-sdk', (ctx) => new UnmappedSessionEventBackend(ctx)); const manager = new SessionManager({ store, runStore, @@ -12410,7 +12358,7 @@ describe('SessionManager permission mode updates', () => { const runStore = new MemoryAgentRunStore(); const backends = new BackendRegistry(); backends.register( - 'fake', + 'ai-sdk', (ctx) => new EventBackend(ctx, [{ type: 'complete', stopReason: 'error' }]), ); const manager = new SessionManager({ @@ -12437,7 +12385,7 @@ describe('SessionManager permission mode updates', () => { const runStore = new MemoryAgentRunStore(); const backends = new BackendRegistry(); backends.register( - 'fake', + 'ai-sdk', (ctx) => new EventBackend(ctx, [{ type: 'complete', stopReason: 'step_limit' }]), ); const manager = new SessionManager({ @@ -12473,7 +12421,7 @@ describe('SessionManager permission mode updates', () => { const store = new MemorySessionStore(); const backends = new BackendRegistry(); backends.register( - 'fake', + 'ai-sdk', (ctx) => new EventBackend(ctx, [ { type: 'error', recoverable: false, reason: 'tool_failed', message: 'Tool failed' }, @@ -12501,7 +12449,7 @@ describe('SessionManager permission mode updates', () => { const store = new MemorySessionStore(); const backends = new BackendRegistry(); const gate = makeGate(); - backends.register('fake', (ctx) => new TestBackend(ctx, gate)); + backends.register('ai-sdk', (ctx) => new TestBackend(ctx, gate)); const manager = new SessionManager({ store, backends, newId: nextId(), now: nextNow(12_500) }); const session = await manager.createSession(makeInput()); @@ -12527,7 +12475,7 @@ describe('SessionManager permission mode updates', () => { const runStore = new MemoryAgentRunStore(); const backends = new BackendRegistry(); let backend: StopControlledAbortBackend | undefined; - backends.register('fake', (ctx) => { + backends.register('ai-sdk', (ctx) => { backend = new StopControlledAbortBackend(ctx); return backend; }); @@ -12579,7 +12527,7 @@ describe('SessionManager permission mode updates', () => { const runStore = new MemoryAgentRunStore(); const backends = new BackendRegistry(); backends.register( - 'fake', + 'ai-sdk', (ctx) => new EventBackend(ctx, [ { type: 'text_delta', messageId: 'm1', text: 'before' }, @@ -12632,7 +12580,7 @@ describe('SessionManager permission mode updates', () => { const store = new MemorySessionStore(); const runStore = new MemoryAgentRunStore(); const backends = new BackendRegistry(); - backends.register('fake', (ctx) => new ThrowAfterTerminalBackend(ctx)); + backends.register('ai-sdk', (ctx) => new ThrowAfterTerminalBackend(ctx)); const manager = new SessionManager({ store, runStore, @@ -12675,7 +12623,7 @@ describe('SessionManager permission mode updates', () => { const runStore = new MemoryAgentRunStore(); const backends = new BackendRegistry(); const gate = makeGate(); - backends.register('fake', (ctx) => new LateErrorBackend(ctx, gate)); + backends.register('ai-sdk', (ctx) => new LateErrorBackend(ctx, gate)); const manager = new SessionManager({ store, runStore, @@ -12713,7 +12661,7 @@ describe('SessionManager permission mode updates', () => { const store = new MemorySessionStore(); const runStore = new MemoryAgentRunStore(); const backends = new BackendRegistry(); - backends.register('fake', (ctx) => new TraceBackend(ctx)); + backends.register('ai-sdk', (ctx) => new TraceBackend(ctx)); const manager = new SessionManager({ store, runStore, @@ -12727,7 +12675,7 @@ describe('SessionManager permission mode updates', () => { await drain(manager.sendMessage(session.id, { turnId: 'turn-1', text: 'hello' })); const [run] = await runStore.listSessionRuns(session.id); - expect(run?.backendKind).toBe('fake'); + expect(run?.backendKind).toBe('ai-sdk'); expect(run?.llmConnectionSlug).toBe('fake'); expect(run?.modelId).toBe('fake-model'); expect(run?.permissionMode).toBe('ask'); @@ -12755,7 +12703,7 @@ describe('SessionManager permission mode updates', () => { }); const backends = new BackendRegistry(); backends.register( - 'fake', + 'ai-sdk', (ctx) => new ProviderCaptureAfterAttemptFailureBackend( ctx, @@ -12806,7 +12754,7 @@ describe('SessionManager permission mode updates', () => { }, }); const backends = new BackendRegistry(); - backends.register('fake', (ctx) => new ProviderRequestTraceBackend(ctx)); + backends.register('ai-sdk', (ctx) => new ProviderRequestTraceBackend(ctx)); const manager = new SessionManager({ store, runStore, @@ -12842,7 +12790,7 @@ describe('SessionManager permission mode updates', () => { }); const backends = new BackendRegistry(); backends.register( - 'fake', + 'ai-sdk', (ctx) => new ProviderCaptureGateBackend(ctx, () => (providerDispatches += 1)), ); const manager = new SessionManager({ @@ -12871,7 +12819,7 @@ describe('SessionManager permission mode updates', () => { const cleanupCalled = makeGate(); let observedEventIds: string[] = []; const backends = new BackendRegistry(); - backends.register('fake', (ctx) => new HistoryCompactCheckpointBackend(ctx)); + backends.register('ai-sdk', (ctx) => new HistoryCompactCheckpointBackend(ctx)); const manager = new SessionManager({ store, runStore, @@ -13008,7 +12956,7 @@ describe('SessionManager permission mode updates', () => { const observedCheckpointIds: Array = []; const backends = new BackendRegistry(); backends.register( - 'fake', + 'ai-sdk', (ctx) => new HistoryCompactCheckpointCacheProbeBackend(ctx, observedCheckpointIds), ); const manager = new SessionManager({ @@ -13054,7 +13002,7 @@ describe('SessionManager permission mode updates', () => { const observedCoverage: Array = []; const backends = new BackendRegistry(); backends.register( - 'fake', + 'ai-sdk', (ctx) => new HistoryCompactCheckpointMonotonicProbeBackend(ctx, observedCoverage), ); const manager = new SessionManager({ @@ -13109,7 +13057,7 @@ describe('SessionManager permission mode updates', () => { const writeOutcomes: string[] = []; const backends = new BackendRegistry(); backends.register( - 'fake', + 'ai-sdk', (ctx) => new SameCoverageCheckpointReplacementProbeBackend(ctx, writeOutcomes), ); const manager = new SessionManager({ @@ -13166,7 +13114,7 @@ describe('SessionManager permission mode updates', () => { }); const backends = new BackendRegistry(); backends.register( - 'fake', + 'ai-sdk', (ctx) => new SerializedCheckpointProbeBackend(ctx, childRecorderCalled, recorderReturnedPromises), ); @@ -13234,7 +13182,7 @@ describe('SessionManager permission mode updates', () => { }); const backends = new BackendRegistry(); backends.register( - 'fake', + 'ai-sdk', (ctx) => new RecoveringCheckpointWriteProbeBackend( ctx, @@ -13307,7 +13255,7 @@ describe('SessionManager permission mode updates', () => { }); const backends = new BackendRegistry(); backends.register( - 'fake', + 'ai-sdk', (ctx) => new CheckpointRecorderContractProbeBackend( ctx, @@ -13350,7 +13298,7 @@ describe('SessionManager permission mode updates', () => { }); const backends = new BackendRegistry(); backends.register( - 'fake', + 'ai-sdk', (ctx) => new CheckpointRecorderContractProbeBackend( ctx, @@ -13418,7 +13366,7 @@ describe('SessionManager permission mode updates', () => { }); const backends = new BackendRegistry(); backends.register( - 'fake', + 'ai-sdk', (ctx) => new InitialCheckpointLoadRaceProbeBackend( ctx, @@ -13522,7 +13470,7 @@ describe('SessionManager permission mode updates', () => { const writeOutcomes: string[] = []; const backends = new BackendRegistry(); backends.register( - 'fake', + 'ai-sdk', (ctx) => new CheckpointRecorderContractProbeBackend(ctx, async () => {}, writeOutcomes), ); const manager = new SessionManager({ @@ -13598,7 +13546,7 @@ describe('SessionManager permission mode updates', () => { test('startup recovery marks persisted running turns as failed instead of leaving them stuck', async () => { const store = new MemorySessionStore(); const backends = new BackendRegistry(); - backends.register('fake', (ctx) => new TestBackend(ctx)); + backends.register('ai-sdk', (ctx) => new TestBackend(ctx)); const manager = new SessionManager({ store, backends, newId: nextId(), now: nextNow(12_800) }); const running = await manager.createSession(makeInput({ status: 'running' })); const waiting = await manager.createSession(makeInput({ status: 'waiting_for_user' })); @@ -13729,7 +13677,7 @@ describe('SessionManager permission mode updates', () => { const store = new MemorySessionStore(); const runStore = new MemoryAgentRunStore(); const backends = new BackendRegistry(); - backends.register('fake', (ctx) => new TestBackend(ctx)); + backends.register('ai-sdk', (ctx) => new TestBackend(ctx)); let sessionId = ''; let outcomeCommitFailuresRemaining = 3; let outcomeCommitAttempts = 0; @@ -13862,7 +13810,7 @@ describe('SessionManager permission mode updates', () => { const store = new MemorySessionStore(); const runStore = new MemoryAgentRunStore(); const backends = new BackendRegistry(); - backends.register('fake', (ctx) => new TestBackend(ctx)); + backends.register('ai-sdk', (ctx) => new TestBackend(ctx)); const manager = new SessionManager({ store, runStore, @@ -13982,7 +13930,7 @@ describe('SessionManager permission mode updates', () => { const runStore = new MemoryAgentRunStore(); const backends = new BackendRegistry(); backends.register( - 'fake', + 'ai-sdk', (ctx) => new EventBackend(ctx, [{ type: 'complete', stopReason: 'end_turn' }]), ); const manager = new SessionManager({ @@ -14015,7 +13963,7 @@ describe('SessionManager permission mode updates', () => { const runStore = new MemoryAgentRunStore(); const backends = new BackendRegistry(); backends.register( - 'fake', + 'ai-sdk', (ctx) => new EventBackend(ctx, [{ type: 'complete', stopReason: 'end_turn' }]), ); const manager = new SessionManager({ @@ -14561,7 +14509,7 @@ describe('SessionManager permission mode updates', () => { const runStore = new MemoryAgentRunStore(); const backends = new BackendRegistry(); backends.register( - 'fake', + 'ai-sdk', (ctx) => new EventBackend(ctx, [{ type: 'complete', stopReason: 'end_turn' }]), ); const manager = new SessionManager({ @@ -14634,7 +14582,7 @@ describe('SessionManager permission mode updates', () => { const runStore = new MemoryAgentRunStore(); const backends = new BackendRegistry(); backends.register( - 'fake', + 'ai-sdk', (ctx) => new EventBackend(ctx, [{ type: 'complete', stopReason: 'end_turn' }]), ); const manager = new SessionManager({ @@ -14673,7 +14621,7 @@ describe('SessionManager permission mode updates', () => { const runStore = new MemoryAgentRunStore(); const backends = new BackendRegistry(); backends.register( - 'fake', + 'ai-sdk', (ctx) => new EventBackend(ctx, [{ type: 'complete', stopReason: 'end_turn' }]), ); const manager = new SessionManager({ @@ -14704,7 +14652,7 @@ describe('SessionManager steering and followup queues', () => { const store = new MemorySessionStore(); const runStore = new MemoryAgentRunStore(); const backends = new BackendRegistry(); - backends.register('fake', (ctx) => new FakeBackend(ctx)); + backends.register('ai-sdk', (ctx) => new FakeBackend(ctx)); const manager = new SessionManager({ store, runStore, @@ -14741,7 +14689,7 @@ describe('SessionManager steering and followup queues', () => { const store = new MemorySessionStore(); const runStore = new MemoryAgentRunStore(); const backends = new BackendRegistry(); - backends.register('fake', (ctx) => new FakeBackend(ctx)); + backends.register('ai-sdk', (ctx) => new FakeBackend(ctx)); const identities: RuntimeMessageRunIdentity[] = []; const acked: string[] = []; const nacked: string[] = []; @@ -14779,9 +14727,7 @@ describe('SessionManager steering and followup queues', () => { }, }, }); - const session = await manager.createSession( - makeInput({ backend: 'fake', permissionMode: 'bypass' }), - ); + const session = await manager.createSession(makeInput({ permissionMode: 'bypass' })); const events = await drainAll( manager.sendMessage(session.id, { turnId: 'turn-host-message', text: 'start' }), @@ -14822,7 +14768,7 @@ describe('SessionManager steering and followup queues', () => { const store = new MemorySessionStore(); const runStore = new MemoryAgentRunStore(); const backends = new BackendRegistry(); - backends.register('fake', (ctx) => new FakeBackend(ctx)); + backends.register('ai-sdk', (ctx) => new FakeBackend(ctx)); const identities: RuntimeInteractionRunIdentity[] = []; const lifecycle: string[] = []; const manager = new SessionManager({ @@ -14848,9 +14794,7 @@ describe('SessionManager steering and followup queues', () => { }, canonicalPermissionOutcomes: noCanonicalPermissionOutcomes, }); - const session = await manager.createSession( - makeInput({ backend: 'fake', permissionMode: 'bypass' }), - ); + const session = await manager.createSession(makeInput({ permissionMode: 'bypass' })); await drainAll( manager.sendMessage(session.id, { turnId: 'turn-host-interaction', text: 'go' }), @@ -14866,7 +14810,7 @@ describe('SessionManager steering and followup queues', () => { const store = new MemorySessionStore(); const runStore = new MemoryAgentRunStore(); const backends = new BackendRegistry(); - backends.register('fake', (ctx) => new FakeBackend(ctx)); + backends.register('ai-sdk', (ctx) => new FakeBackend(ctx)); const lifecycle: string[] = []; let question: RuntimeUserQuestionContinuation | undefined; const manager = new SessionManager({ @@ -14893,9 +14837,7 @@ describe('SessionManager steering and followup queues', () => { }, canonicalPermissionOutcomes: noCanonicalPermissionOutcomes, }); - const session = await manager.createSession( - makeInput({ backend: 'fake', permissionMode: 'ask' }), - ); + const session = await manager.createSession(makeInput({ permissionMode: 'ask' })); const iterator = manager .sendMessage(session.id, { turnId: 'turn-host-question-abandoned', @@ -14920,7 +14862,7 @@ describe('SessionManager steering and followup queues', () => { const store = new MemorySessionStore(); const runStore = new MemoryAgentRunStore(); const backends = new BackendRegistry(); - backends.register('fake', (ctx) => new UnadmittedQuestionBackend(ctx)); + backends.register('ai-sdk', (ctx) => new UnadmittedQuestionBackend(ctx)); let releases = 0; const manager = new SessionManager({ store, @@ -14942,9 +14884,7 @@ describe('SessionManager steering and followup queues', () => { }, canonicalPermissionOutcomes: noCanonicalPermissionOutcomes, }); - const session = await manager.createSession( - makeInput({ backend: 'fake', permissionMode: 'ask' }), - ); + const session = await manager.createSession(makeInput({ permissionMode: 'ask' })); let failure: unknown; try { @@ -14960,7 +14900,7 @@ describe('SessionManager steering and followup queues', () => { const store = new MemorySessionStore(); const runStore = new MemoryAgentRunStore(); const backends = new BackendRegistry(); - backends.register('fake', (ctx) => new ThrowBeforeTerminalBackend(ctx)); + backends.register('ai-sdk', (ctx) => new ThrowBeforeTerminalBackend(ctx)); let releases = 0; const manager = new SessionManager({ store, @@ -14981,9 +14921,7 @@ describe('SessionManager steering and followup queues', () => { }), }, }); - const session = await manager.createSession( - makeInput({ backend: 'fake', permissionMode: 'bypass' }), - ); + const session = await manager.createSession(makeInput({ permissionMode: 'bypass' })); let failure: unknown; try { @@ -15002,7 +14940,7 @@ describe('SessionManager steering and followup queues', () => { const runStore = new MemoryAgentRunStore(); const backends = new BackendRegistry(); let failBuilds = 1; - backends.register('fake', (ctx) => { + backends.register('ai-sdk', (ctx) => { if (failBuilds > 0) { failBuilds -= 1; throw new Error('backend build failed'); @@ -15017,9 +14955,7 @@ describe('SessionManager steering and followup queues', () => { newId: nextId(), now: nextNow(1_000), }); - const session = await manager.createSession( - makeInput({ backend: 'fake', permissionMode: 'bypass' }), - ); + const session = await manager.createSession(makeInput({ permissionMode: 'bypass' })); let failed: unknown; try { @@ -15057,7 +14993,7 @@ describe('SessionManager steering and followup queues', () => { const runStore = new MemoryAgentRunStore(); const backends = new BackendRegistry(); let backend: GatedSteeringBackend | undefined; - backends.register('fake', (ctx) => { + backends.register('ai-sdk', (ctx) => { backend = new GatedSteeringBackend(ctx); return backend; }); @@ -15069,9 +15005,7 @@ describe('SessionManager steering and followup queues', () => { newId: nextId(), now: nextNow(1_000), }); - const session = await manager.createSession( - makeInput({ backend: 'fake', permissionMode: 'bypass' }), - ); + const session = await manager.createSession(makeInput({ permissionMode: 'bypass' })); const first = drainAll(manager.sendMessage(session.id, { turnId: 'turn-a', text: 'first' })); await waitUntil(() => backend?.gates.has('turn-a') === true); @@ -15306,7 +15240,7 @@ describe('SessionManager steering and followup queues', () => { const store = new MemorySessionStore(); const backends = new BackendRegistry(); let backend: GatedSteeringBackend | undefined; - backends.register('fake', (ctx) => { + backends.register('ai-sdk', (ctx) => { backend = new GatedSteeringBackend(ctx); return backend; }); @@ -15316,9 +15250,7 @@ describe('SessionManager steering and followup queues', () => { newId: nextId(), now: nextNow(1_000), }); - const session = await manager.createSession( - makeInput({ backend: 'fake', permissionMode: 'bypass' }), - ); + const session = await manager.createSession(makeInput({ permissionMode: 'bypass' })); const turn = drainAll(manager.sendMessage(session.id, { turnId: 'turn-1', text: 'go' })); await waitUntil(() => backend?.gates.has('turn-1') === true); @@ -15468,7 +15400,7 @@ describe('SessionManager steering and followup queues', () => { const store = new MemorySessionStore(); const runStore = new MemoryAgentRunStore(); const backends = new BackendRegistry(); - backends.register('fake', (ctx) => new ForgingQueueBackend(ctx)); + backends.register('ai-sdk', (ctx) => new ForgingQueueBackend(ctx)); const manager = new SessionManager({ store, runStore, @@ -15477,9 +15409,7 @@ describe('SessionManager steering and followup queues', () => { newId: nextId(), now: nextNow(1_000), }); - const session = await manager.createSession( - makeInput({ backend: 'fake', permissionMode: 'bypass' }), - ); + const session = await manager.createSession(makeInput({ permissionMode: 'bypass' })); const events = await drainAll( manager.sendMessage(session.id, { turnId: 'turn-1', text: 'go' }), @@ -15504,7 +15434,7 @@ describe('SessionManager steering and followup queues', () => { const store = new MemorySessionStore(); const runStore = new MemoryAgentRunStore(); const backends = new BackendRegistry(); - backends.register('fake', (ctx) => new ProviderRetryProgressBackend(ctx)); + backends.register('ai-sdk', (ctx) => new ProviderRetryProgressBackend(ctx)); const manager = new SessionManager({ store, runStore, @@ -15513,9 +15443,7 @@ describe('SessionManager steering and followup queues', () => { newId: nextId(), now: nextNow(1_000), }); - const session = await manager.createSession( - makeInput({ backend: 'fake', permissionMode: 'bypass' }), - ); + const session = await manager.createSession(makeInput({ permissionMode: 'bypass' })); const events = await drainAll( manager.sendMessage(session.id, { turnId: 'turn-1', text: 'go' }), @@ -15601,7 +15529,7 @@ describe('SessionManager steering and followup queues', () => { const runStore = new MemoryAgentRunStore(); const backends = new BackendRegistry(); let backend: GatedSteeringBackend | undefined; - backends.register('fake', (ctx) => { + backends.register('ai-sdk', (ctx) => { backend = new GatedSteeringBackend(ctx); return backend; }); @@ -15613,9 +15541,7 @@ describe('SessionManager steering and followup queues', () => { newId: nextId(), now: nextNow(1_000), }); - const session = await manager.createSession( - makeInput({ backend: 'fake', permissionMode: 'bypass' }), - ); + const session = await manager.createSession(makeInput({ permissionMode: 'bypass' })); const turn = drainAll(manager.sendMessage(session.id, { turnId: 'turn-1', text: 'go' })); await waitUntil(() => backend?.gates.has('turn-1') === true); @@ -15704,7 +15630,7 @@ async function steeringDeliverySession( const backends = new BackendRegistry(); let manager!: SessionManager; let sessionId = ''; - backends.register('fake', (ctx) => + backends.register('ai-sdk', (ctx) => createTestAiSdkBackend({ sessionId: ctx.sessionId, header: ctx.header, @@ -15743,9 +15669,7 @@ async function steeringDeliverySession( now: nextNow(1_000), }; manager = new SessionManager(managerDeps); - const session = await manager.createSession( - makeInput({ backend: 'fake', permissionMode: 'bypass' }), - ); + const session = await manager.createSession(makeInput({ permissionMode: 'bypass' })); sessionId = session.id; return { manager, session, store }; } @@ -15757,7 +15681,7 @@ async function steeringDeliverySession( * land after the final step boundary (stranded steering). */ class GatedSteeringBackend implements AgentBackend { - readonly kind = 'fake' as const; + readonly kind = 'ai-sdk' as const; readonly sessionId: string; readonly gates = new Map(); readonly pullDone = new Map(); @@ -15955,7 +15879,7 @@ class DelegatingRuntimeKernel implements RuntimeKernelLike { } class UnadmittedQuestionBackend implements AgentBackend { - readonly kind = 'fake' as const; + readonly kind = 'ai-sdk' as const; readonly sessionId: string; constructor(ctx: BackendFactoryContext) { @@ -15987,7 +15911,7 @@ class UnadmittedQuestionBackend implements AgentBackend { } class TestBackend implements AgentBackend { - readonly kind = 'fake' as const; + readonly kind = 'ai-sdk' as const; readonly sessionId: string; readonly sendInputs: BackendSendInput[] = []; stopCalls = 0; @@ -16279,7 +16203,7 @@ function compactHistoryFailOpenResult() { } class HighVolumeDeltaBackend implements AgentBackend { - readonly kind = 'fake' as const; + readonly kind = 'ai-sdk' as const; readonly sessionId: string; constructor( @@ -16316,7 +16240,7 @@ class HighVolumeDeltaBackend implements AgentBackend { } class LateErrorBackend implements AgentBackend { - readonly kind = 'fake' as const; + readonly kind = 'ai-sdk' as const; readonly sessionId: string; constructor( @@ -16357,7 +16281,7 @@ class LateErrorBackend implements AgentBackend { } class StopControlledAbortBackend implements AgentBackend { - readonly kind = 'fake' as const; + readonly kind = 'ai-sdk' as const; readonly sessionId: string; private releaseAbort: () => void = () => {}; private releaseStop: () => void = () => {}; @@ -16419,7 +16343,7 @@ type PartialEvent = | Omit, 'id' | 'turnId' | 'ts'>; class TurnScriptBackend implements AgentBackend { - readonly kind = 'fake' as const; + readonly kind = 'ai-sdk' as const; readonly sessionId: string; readonly sendInputs: BackendSendInput[] = []; @@ -16454,7 +16378,7 @@ class TurnScriptBackend implements AgentBackend { } class EventBackend implements AgentBackend { - readonly kind = 'fake' as const; + readonly kind = 'ai-sdk' as const; readonly sessionId: string; constructor( @@ -16483,7 +16407,7 @@ class EventBackend implements AgentBackend { } class SandboxBoundaryWaitBackend implements AgentBackend { - readonly kind = 'fake' as const; + readonly kind = 'ai-sdk' as const; readonly sessionId: string; readonly responses: SandboxBoundaryResponse[] = []; private readonly responseGate = makeGate(); @@ -16548,7 +16472,7 @@ class SandboxBoundaryWaitBackend implements AgentBackend { * it into a control-only RuntimeEvent, and the ledger keeps it. */ class UnmappedSessionEventBackend implements AgentBackend { - readonly kind = 'fake' as const; + readonly kind = 'ai-sdk' as const; readonly sessionId: string; constructor(ctx: BackendFactoryContext) { @@ -16587,7 +16511,7 @@ class UnmappedSessionEventBackend implements AgentBackend { } class ThrowAfterTerminalBackend implements AgentBackend { - readonly kind = 'fake' as const; + readonly kind = 'ai-sdk' as const; readonly sessionId: string; constructor(ctx: BackendFactoryContext) { @@ -16619,7 +16543,7 @@ class ThrowAfterTerminalBackend implements AgentBackend { } class ThrowBeforeTerminalBackend implements AgentBackend { - readonly kind = 'fake' as const; + readonly kind = 'ai-sdk' as const; readonly sessionId: string; constructor(ctx: BackendFactoryContext) { @@ -16644,7 +16568,7 @@ class ThrowBeforeTerminalBackend implements AgentBackend { } class TraceBackend implements AgentBackend { - readonly kind = 'fake' as const; + readonly kind = 'ai-sdk' as const; readonly sessionId: string; constructor(private readonly ctx: BackendFactoryContext) { @@ -16698,7 +16622,7 @@ class TraceBackend implements AgentBackend { } class ProviderRequestTraceBackend implements AgentBackend { - readonly kind = 'fake' as const; + readonly kind = 'ai-sdk' as const; readonly sessionId: string; constructor(private readonly ctx: BackendFactoryContext) { @@ -16760,7 +16684,7 @@ class ProviderRequestTraceBackend implements AgentBackend { } class ProviderCaptureGateBackend implements AgentBackend { - readonly kind = 'fake' as const; + readonly kind = 'ai-sdk' as const; readonly sessionId: string; constructor( @@ -16801,7 +16725,7 @@ class ProviderCaptureGateBackend implements AgentBackend { } class ProviderCaptureAfterAttemptFailureBackend implements AgentBackend { - readonly kind = 'fake' as const; + readonly kind = 'ai-sdk' as const; readonly sessionId: string; constructor( @@ -16884,7 +16808,7 @@ class ProviderCaptureAfterAttemptFailureBackend implements AgentBackend { } class HistoryCompactCheckpointBackend implements AgentBackend { - readonly kind = 'fake' as const; + readonly kind = 'ai-sdk' as const; readonly sessionId: string; constructor(private readonly ctx: BackendFactoryContext) { @@ -16930,7 +16854,7 @@ class HistoryCompactCheckpointBackend implements AgentBackend { } class HistoryCompactCheckpointCacheProbeBackend implements AgentBackend { - readonly kind = 'fake' as const; + readonly kind = 'ai-sdk' as const; readonly sessionId: string; constructor( @@ -16983,7 +16907,7 @@ class HistoryCompactCheckpointCacheProbeBackend implements AgentBackend { } class HistoryCompactCheckpointMonotonicProbeBackend implements AgentBackend { - readonly kind = 'fake' as const; + readonly kind = 'ai-sdk' as const; readonly sessionId: string; constructor( @@ -17041,7 +16965,7 @@ class HistoryCompactCheckpointMonotonicProbeBackend implements AgentBackend { } class SameCoverageCheckpointReplacementProbeBackend implements AgentBackend { - readonly kind = 'fake' as const; + readonly kind = 'ai-sdk' as const; readonly sessionId: string; constructor( @@ -17097,7 +17021,7 @@ class SameCoverageCheckpointReplacementProbeBackend implements AgentBackend { } class SerializedCheckpointProbeBackend implements AgentBackend { - readonly kind = 'fake' as const; + readonly kind = 'ai-sdk' as const; readonly sessionId: string; constructor( @@ -17156,7 +17080,7 @@ class SerializedCheckpointProbeBackend implements AgentBackend { } class RecoveringCheckpointWriteProbeBackend implements AgentBackend { - readonly kind = 'fake' as const; + readonly kind = 'ai-sdk' as const; readonly sessionId: string; constructor( @@ -17220,7 +17144,7 @@ class RecoveringCheckpointWriteProbeBackend implements AgentBackend { } class CheckpointRecorderContractProbeBackend implements AgentBackend { - readonly kind = 'fake' as const; + readonly kind = 'ai-sdk' as const; readonly sessionId: string; constructor( @@ -17278,7 +17202,7 @@ class CheckpointRecorderContractProbeBackend implements AgentBackend { } class InitialCheckpointLoadRaceProbeBackend implements AgentBackend { - readonly kind = 'fake' as const; + readonly kind = 'ai-sdk' as const; readonly sessionId: string; constructor( @@ -17448,7 +17372,7 @@ class MemorySessionStore implements SessionStore { ...(input.revisionIndex !== undefined ? { revisionIndex: input.revisionIndex } : {}), ...(input.revisionState ? { revisionState: input.revisionState } : {}), hasUnread: false, - backend: input.backend, + backend: 'ai-sdk', llmConnectionSlug: input.llmConnectionSlug, connectionLocked: false, model: input.model ?? 'fake-model', @@ -18134,7 +18058,7 @@ class ContinuationClaimBarrierRunStore extends MemoryAgentRunStore { /** Yields a forged queue_update before completing — round-6 R3's attacker. */ class ForgingQueueBackend implements AgentBackend { - readonly kind = 'fake' as const; + readonly kind = 'ai-sdk' as const; readonly sessionId: string; constructor(ctx: BackendFactoryContext) { @@ -18175,7 +18099,7 @@ class ForgingQueueBackend implements AgentBackend { } class ProviderRetryProgressBackend implements AgentBackend { - readonly kind = 'fake' as const; + readonly kind = 'ai-sdk' as const; readonly sessionId: string; constructor(ctx: BackendFactoryContext) { @@ -18396,7 +18320,6 @@ function hostedRootAuthority(): RuntimeHostedRootAuthority { function makeInput(overrides: Partial = {}): CreateSessionInput { return { cwd: '/tmp/cwd', - backend: 'fake', llmConnectionSlug: 'fake', model: 'fake-model', permissionMode: 'ask', @@ -18520,7 +18443,7 @@ async function createQueuedGraphScenario(firstAbortSignal?: AbortSignal) { const firstReady = makeGate(); const stopStarted = makeGate(); let backend!: TestBackend; - backends.register('fake', (ctx) => { + backends.register('ai-sdk', (ctx) => { backend = new (class extends TestBackend { override async stop( reason: 'user_stop' | 'redirect', @@ -18615,7 +18538,7 @@ function makeRunHeader(overrides: Partial = {}): AgentRunHeader sessionId: 'session-1', turnId: 'turn-1', status: 'running', - backendKind: 'fake', + backendKind: 'ai-sdk', llmConnectionSlug: 'fake', modelId: 'fake-model', cwd: '/tmp/cwd', @@ -18643,7 +18566,7 @@ function makeManagerForReadCutover( runStore: AgentRunStore & RuntimeEventStore, ): SessionManager { const backends = new BackendRegistry(); - backends.register('fake', (ctx) => new TestBackend(ctx)); + backends.register('ai-sdk', (ctx) => new TestBackend(ctx)); return new SessionManager({ store, runStore, @@ -19029,7 +18952,7 @@ async function seedBoundaryRestartSession(input: { const store = new MemorySessionStore(); const runStore = new MemoryAgentRunStore(); const backends = new BackendRegistry(); - backends.register('fake', (ctx) => new TestBackend(ctx)); + backends.register('ai-sdk', (ctx) => new TestBackend(ctx)); const managerDeps = { store, runStore, diff --git a/packages/runtime/src/__tests__/shell-run-tool-result.test.ts b/packages/runtime/src/__tests__/shell-run-tool-result.test.ts index 4084c1917d..7578b6642c 100644 --- a/packages/runtime/src/__tests__/shell-run-tool-result.test.ts +++ b/packages/runtime/src/__tests__/shell-run-tool-result.test.ts @@ -116,7 +116,6 @@ describe('shell run sandbox denial projection', () => { const store = createSessionStore(root); const session = await store.create({ cwd: '/workspace', - backend: 'fake', llmConnectionSlug: 'fake', model: 'fake-model', permissionMode: 'ask', diff --git a/packages/runtime/src/__tests__/stream-graph-coordinator.test.ts b/packages/runtime/src/__tests__/stream-graph-coordinator.test.ts index aab0a61fac..208336dab3 100644 --- a/packages/runtime/src/__tests__/stream-graph-coordinator.test.ts +++ b/packages/runtime/src/__tests__/stream-graph-coordinator.test.ts @@ -258,7 +258,7 @@ describe('host-managed agent graph coordinator', () => { }, }); const backends = new BackendRegistry(); - backends.register('fake', (context) => new FakeBackend(context)); + backends.register('ai-sdk', (context) => new FakeBackend(context)); const manager = new SessionManager({ store: sessionStore, runStore, @@ -279,7 +279,6 @@ describe('host-managed agent graph coordinator', () => { try { const rootSession = await manager.createSession({ cwd: root, - backend: 'fake', llmConnectionSlug: 'fake', permissionMode: 'ask', name: 'Graph supervisor', @@ -1075,7 +1074,7 @@ describe('host-managed agent graph coordinator', () => { join(root, OPERATIONAL_STATE_DATABASE_NAME), ); const backends = new BackendRegistry(); - backends.register('fake', (context) => new FakeBackend(context)); + backends.register('ai-sdk', (context) => new FakeBackend(context)); const manager = new SessionManager({ store: sessionStore, runStore, @@ -1090,7 +1089,6 @@ describe('host-managed agent graph coordinator', () => { try { const rootSession = await manager.createSession({ cwd: root, - backend: 'fake', llmConnectionSlug: 'fake', permissionMode: 'ask', orchestrationMode: 'swarm', diff --git a/packages/runtime/src/runtime-kernel.ts b/packages/runtime/src/runtime-kernel.ts index 23b612ffec..18ea7e1082 100644 --- a/packages/runtime/src/runtime-kernel.ts +++ b/packages/runtime/src/runtime-kernel.ts @@ -692,8 +692,8 @@ export class RuntimeKernel implements RuntimeKernelLike { store: this.deps.store, runStore: this.deps.runStore, runtimeEventStore: this.deps.runtimeEventStore, - ...(runtimeToolBoundaryProtocol(this.deps, header) - ? { toolBoundaryProtocol: runtimeToolBoundaryProtocol(this.deps, header) } + ...(this.deps.toolBoundaryProtocol + ? { toolBoundaryProtocol: this.deps.toolBoundaryProtocol } : {}), repairRunRuntimeLedger: this.deps.repairRunRuntimeLedger, newId: this.deps.newId, @@ -835,7 +835,7 @@ export class RuntimeKernel implements RuntimeKernelLike { ); } - const continuationToolBoundaryProtocol = runtimeToolBoundaryProtocol(this.deps, header); + const continuationToolBoundaryProtocol = this.deps.toolBoundaryProtocol; const run = new AgentRun({ sessionId: continuation.sessionId, header, @@ -1003,8 +1003,8 @@ export class RuntimeKernel implements RuntimeKernelLike { store: this.deps.store, runStore: this.deps.runStore, runtimeEventStore: this.deps.runtimeEventStore, - ...(runtimeToolBoundaryProtocol(this.deps, header) - ? { toolBoundaryProtocol: runtimeToolBoundaryProtocol(this.deps, header) } + ...(this.deps.toolBoundaryProtocol + ? { toolBoundaryProtocol: this.deps.toolBoundaryProtocol } : {}), repairRunRuntimeLedger: this.deps.repairRunRuntimeLedger, newId: this.deps.newId, @@ -1182,8 +1182,8 @@ export class RuntimeKernel implements RuntimeKernelLike { store: this.deps.store, runStore: this.deps.runStore, runtimeEventStore: this.deps.runtimeEventStore, - ...(runtimeToolBoundaryProtocol(this.deps, childHeader) - ? { toolBoundaryProtocol: runtimeToolBoundaryProtocol(this.deps, childHeader) } + ...(this.deps.toolBoundaryProtocol + ? { toolBoundaryProtocol: this.deps.toolBoundaryProtocol } : {}), repairRunRuntimeLedger: this.deps.repairRunRuntimeLedger, newId: this.deps.newId, @@ -1329,7 +1329,7 @@ export class RuntimeKernel implements RuntimeKernelLike { ); } await this.deps.continuationFailpoint?.('after_continuation_claim_committed'); - const continuationToolBoundaryProtocol = runtimeToolBoundaryProtocol(this.deps, childHeader); + const continuationToolBoundaryProtocol = this.deps.toolBoundaryProtocol; const durableAdmission: { claimedRunHeader: AgentRunHeader; commitContinuationStart: ( @@ -3752,13 +3752,6 @@ function childActiveKey(sessionId: string, turnId: string): string { return `${sessionId}:${turnId}`; } -function runtimeToolBoundaryProtocol( - deps: Pick, - header: Pick, -): ToolBoundaryProtocol | undefined { - return header.backend === 'ai-sdk' ? deps.toolBoundaryProtocol : undefined; -} - function effectiveOrchestrationForRun( run: AgentRunHeader, session: SessionHeader, diff --git a/packages/runtime/src/session-manager.ts b/packages/runtime/src/session-manager.ts index cd6f9d701c..fa04e7fa07 100644 --- a/packages/runtime/src/session-manager.ts +++ b/packages/runtime/src/session-manager.ts @@ -2550,7 +2550,6 @@ export class SessionManager { cwd: workspace?.worktreePath ?? parentHeader.cwd, ...(parentHeader.projectId !== undefined ? { projectId: parentHeader.projectId } : {}), name: resolvedPreset?.name ?? definition.name, - backend: parentHeader.backend, llmConnectionSlug: resolvedPreset?.connectionSlug ?? parentHeader.llmConnectionSlug, model: resolvedPreset?.model ?? parentHeader.model, ...(resolvedPreset @@ -3089,7 +3088,6 @@ export class SessionManager { cwd: workspace?.worktreePath ?? parentHeader.cwd, ...(parentHeader.projectId !== undefined ? { projectId: parentHeader.projectId } : {}), name: input.name ?? input.resolvedPreset?.name ?? definition.name, - backend: parentHeader.backend, llmConnectionSlug: input.resolvedPreset?.connectionSlug ?? parentHeader.llmConnectionSlug, model: input.resolvedPreset?.model ?? parentHeader.model, ...(input.resolvedPreset @@ -4953,7 +4951,6 @@ export class SessionManager { { cwd: header.cwd, ...(header.projectId !== undefined ? { projectId: header.projectId } : {}), - backend: header.backend, llmConnectionSlug: header.llmConnectionSlug, model: header.model, thinkingLevel: header.thinkingLevel, @@ -5016,7 +5013,6 @@ export class SessionManager { { cwd: header.cwd, ...(header.projectId !== undefined ? { projectId: header.projectId } : {}), - backend: header.backend, llmConnectionSlug: header.llmConnectionSlug, model: header.model, thinkingLevel: header.thinkingLevel, diff --git a/packages/storage/src/__tests__/external-session-importer.test.ts b/packages/storage/src/__tests__/external-session-importer.test.ts index dc849c0849..565368057f 100644 --- a/packages/storage/src/__tests__/external-session-importer.test.ts +++ b/packages/storage/src/__tests__/external-session-importer.test.ts @@ -150,7 +150,6 @@ describe('ExternalSessionImporter', () => { function target(overrides: Partial = {}): ExternalSessionImportTarget { return { - backend: 'fake', llmConnectionSlug: 'fake', model: 'maka-model', permissionMode: 'ask', diff --git a/packages/storage/src/__tests__/goal-authority.test.ts b/packages/storage/src/__tests__/goal-authority.test.ts index 662e31a62c..1be86d8957 100644 --- a/packages/storage/src/__tests__/goal-authority.test.ts +++ b/packages/storage/src/__tests__/goal-authority.test.ts @@ -117,7 +117,6 @@ test('Session retirement atomically removes its Goal authority', async () => { ['archive', 'remove'].map((name) => stores.sessionStore.create({ cwd: capability.canonicalPath, - backend: 'fake', llmConnectionSlug: 'fake', model: 'fake-model', permissionMode: 'ask', diff --git a/packages/storage/src/__tests__/operational-state-backup.test.ts b/packages/storage/src/__tests__/operational-state-backup.test.ts index 3502fb2f0d..3c88be9674 100644 --- a/packages/storage/src/__tests__/operational-state-backup.test.ts +++ b/packages/storage/src/__tests__/operational-state-backup.test.ts @@ -36,7 +36,6 @@ test('backs up and restores runtime.sqlite plus artifact bytes', async () => { const session = await sessions.create({ projectId: project.id, cwd: '/tmp/cwd', - backend: 'fake', llmConnectionSlug: 'fake', model: 'fake-model', permissionMode: 'ask', diff --git a/packages/storage/src/__tests__/session-bundle-policy.test.ts b/packages/storage/src/__tests__/session-bundle-policy.test.ts index 206eddb94b..9e833dfcb5 100644 --- a/packages/storage/src/__tests__/session-bundle-policy.test.ts +++ b/packages/storage/src/__tests__/session-bundle-policy.test.ts @@ -141,7 +141,6 @@ test('retains only the selected Session partial stream segments', async () => { function input(name: string): CreateSessionInput { return { cwd: '/tmp/cwd', - backend: 'fake', llmConnectionSlug: 'fake', model: 'fake-model', permissionMode: 'ask' as const, diff --git a/packages/storage/src/__tests__/session-store.test.ts b/packages/storage/src/__tests__/session-store.test.ts index 883a06f537..87d853d0d1 100644 --- a/packages/storage/src/__tests__/session-store.test.ts +++ b/packages/storage/src/__tests__/session-store.test.ts @@ -1079,7 +1079,6 @@ describe('SQLite SessionStore', () => { function makeInput(overrides: Partial = {}): CreateSessionInput { return { cwd: '/tmp/cwd', - backend: 'ai-sdk', llmConnectionSlug: 'test-connection', model: 'test-model', permissionMode: 'ask', diff --git a/packages/storage/src/__tests__/settings-store-usage.test.ts b/packages/storage/src/__tests__/settings-store-usage.test.ts index d1cfc90247..7f88b83cef 100644 --- a/packages/storage/src/__tests__/settings-store-usage.test.ts +++ b/packages/storage/src/__tests__/settings-store-usage.test.ts @@ -13,7 +13,6 @@ async function seedSession(workspaceRoot: string, messages: StoredMessage[]): Pr try { const header = await sessions.create({ cwd: '/tmp/maka-workspace', - backend: 'ai-sdk', llmConnectionSlug: 'anthropic', model: 'claude-sonnet-4', permissionMode: 'ask', @@ -77,7 +76,6 @@ describe('SettingsStore.usageStats request logs', () => { try { const header = await sessions.create({ cwd: '/tmp/maka-workspace', - backend: 'ai-sdk', llmConnectionSlug: 'sqlite-provider', model: 'sqlite-default-model', permissionMode: 'ask', diff --git a/packages/storage/src/session-store.ts b/packages/storage/src/session-store.ts index a8370c1ad2..4dc5e36d5b 100644 --- a/packages/storage/src/session-store.ts +++ b/packages/storage/src/session-store.ts @@ -1036,7 +1036,7 @@ function buildSessionHeader( ...(input.revisionIndex !== undefined ? { revisionIndex: input.revisionIndex } : {}), ...(input.revisionState ? { revisionState: input.revisionState } : {}), hasUnread: false, - backend: input.backend, + backend: 'ai-sdk', llmConnectionSlug: input.llmConnectionSlug, connectionLocked: false, model: input.model ?? 'default',