Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,7 @@ describe('composer first-send cleanup', () => {
const deps = {
...createActionsDeps(),
newChatModel: {
llmConnectionId: 'connection-1',
llmConnectionSlug: 'opencode-free',
model: 'mimo-v2.5-free',
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ function session(id: string): DesktopSessionSummary {
hasUnread: false,
status: 'active',
backend: 'fake',
llmConnectionId: 'connection-1',
llmConnectionSlug: 'e2e',
connectionLocked: true,
model: 'claude-sonnet',
Expand Down Expand Up @@ -216,6 +217,7 @@ describe('AppShell session settings actions', () => {
const harness = createHarness();

const modelChange = harness.actions.setSessionModel({
llmConnectionId: 'connection-1',
llmConnectionSlug: 'e2e',
model: 'claude-opus',
});
Expand All @@ -242,6 +244,7 @@ describe('AppShell session settings actions', () => {
});

const modelChange = harness.actions.setSessionModel({
llmConnectionId: 'connection-1',
llmConnectionSlug: 'e2e',
model: 'claude-opus',
});
Expand All @@ -260,6 +263,7 @@ describe('AppShell session settings actions', () => {
const harness = createHarness();

const modelChange = harness.actions.setSessionModel({
llmConnectionId: 'connection-1',
llmConnectionSlug: 'e2e',
model: 'claude-opus',
});
Expand All @@ -278,11 +282,13 @@ describe('AppShell session settings actions', () => {
});

const modelChange = harness.actions.setSessionModel({
llmConnectionId: 'connection-1',
llmConnectionSlug: 'relay',
model: 'claude-sonnet',
});
harness.modelResult.resolve({
...session('session-a'),
llmConnectionId: 'connection-1',
llmConnectionSlug: 'relay',
});
await modelChange;
Expand All @@ -297,6 +303,7 @@ describe('AppShell session settings actions', () => {
const harness = createHarness();

const modelChange = harness.actions.setSessionModel({
llmConnectionId: 'connection-1',
llmConnectionSlug: 'e2e',
model: 'claude-opus',
});
Expand All @@ -318,6 +325,7 @@ describe('AppShell session settings actions', () => {

const thinkingChange = harness.actions.setSessionThinkingLevel('high');
await harness.actions.setSessionModel({
llmConnectionId: 'connection-1',
llmConnectionSlug: 'e2e',
model: 'claude-opus',
});
Expand All @@ -344,6 +352,7 @@ describe('AppShell session settings actions', () => {
assert.deepEqual(harness.errorTargets, [{ sessionId: 'session-a' }]);

const modelChange = harness.actions.setSessionModel({
llmConnectionId: 'connection-1',
llmConnectionSlug: 'e2e',
model: 'claude-opus',
});
Expand Down
16 changes: 12 additions & 4 deletions apps/desktop/src/main/__tests__/model-catalog-choices.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,16 @@

import { strict as assert } from 'node:assert';
import { describe, it } from 'node:test';
import type { LlmConnection } from '@maka/core/llm-connections';
import type { IdentifiedLlmConnection } from '@maka/core/llm-connections';
import { buildChatModelChoices } from '@maka/core/chat-model-choice';
import { pickNewChatModel } from '../../renderer/shell-chat-model-selection.js';

function connection(
overrides: Partial<LlmConnection> & Pick<LlmConnection, 'slug' | 'providerType'>,
): LlmConnection {
overrides: Partial<IdentifiedLlmConnection> &
Pick<IdentifiedLlmConnection, 'slug' | 'providerType'>,
): IdentifiedLlmConnection {
return {
connectionId: `connection-${overrides.slug}`,
name: overrides.slug,
defaultModel: '',
enabled: true,
Expand All @@ -49,6 +51,7 @@ describe('model catalog picker helpers', () => {
catalogDefault: undefined,
choices: [
{
connectionId: 'connection-missing',
connectionSlug: 'missing-key-first',
providerType: 'anthropic',
providerLabel: 'Anthropic',
Expand All @@ -58,6 +61,7 @@ describe('model catalog picker helpers', () => {
thinkingLevels: [],
},
{
connectionId: 'connection-ready',
connectionSlug: 'ready-second',
providerType: 'opencode-free',
providerLabel: 'OpenCode Zen',
Expand All @@ -68,7 +72,11 @@ describe('model catalog picker helpers', () => {
},
],
}),
{ llmConnectionSlug: 'ready-second', model: 'ready-model' },
{
llmConnectionId: 'connection-ready',
llmConnectionSlug: 'ready-second',
model: 'ready-model',
},
);
});
it('keeps API connection labels while redacting OAuth account identities', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -426,6 +426,7 @@ function session(
hasUnread: false,
status: 'active',
backend: 'ai-sdk',
llmConnectionId: 'connection-1',
llmConnectionSlug: 'test-connection',
connectionLocked: false,
model: 'test-model',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,7 @@ test('settles revision cleanup when abandon observes an already absent target',
assert.equal(await client.removeSessionCopy('revision-copy'), 'removed');
});

test('merges a configuration patch into each fresh CAS projection', async () => {
test('replays one exact model patch across fresh CAS projections', async () => {
const { client, requests } = clientWithResponses([
{ kind: 'session', session: session('session-1', 10) },
{ kind: 'revision_conflict', expectedRevision: 10, actualRevision: 11 },
Expand All @@ -240,16 +240,22 @@ test('merges a configuration patch into each fresh CAS projection', async () =>
kind: 'committed',
session: session('session-1', 12, {
collaborationMode: 'plan',
permissionMode: 'ask',
llmConnectionId: 'connection-b',
model: 'model-b',
}),
},
]);

const updated = await client.updateSessionConfiguration('session-1', {
permissionMode: 'ask',
modelTarget: {
kind: 'explicit',
connectionId: 'connection-b',
connectionSlug: 'test-connection',
model: 'model-b',
},
});

assert.equal(updated.permissionMode, 'ask');
assert.equal(updated.llmConnectionId, 'connection-b');
assert.equal(updated.collaborationMode, 'plan');
assert.deepEqual(
requests
Expand All @@ -259,31 +265,25 @@ test('merges a configuration patch into each fresh CAS projection', async () =>
{
sessionId: 'session-1',
expectedRevision: 10,
configuration: {
patch: {
modelTarget: {
kind: 'explicit',
connectionId: 'connection-b',
connectionSlug: 'test-connection',
model: 'test-model',
model: 'model-b',
},
thinkingLevel: null,
permissionMode: 'ask',
collaborationMode: 'agent',
orchestrationMode: 'default',
},
},
{
sessionId: 'session-1',
expectedRevision: 11,
configuration: {
patch: {
modelTarget: {
kind: 'explicit',
connectionId: 'connection-b',
connectionSlug: 'test-connection',
model: 'test-model',
model: 'model-b',
},
thinkingLevel: null,
permissionMode: 'ask',
collaborationMode: 'plan',
orchestrationMode: 'default',
},
},
],
Expand Down Expand Up @@ -1002,6 +1002,7 @@ function session(
hasUnread: false,
status: 'active',
backend: 'ai-sdk',
llmConnectionId: 'connection-1',
llmConnectionSlug: 'test-connection',
connectionLocked: true,
model: 'test-model',
Expand Down
20 changes: 14 additions & 6 deletions apps/desktop/src/main/__tests__/runtime-host-client-uds.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -175,15 +175,22 @@ test('drives the renderer Session catalog facade through real UDS framing', asyn
'session.configuration.update': async (input) => {
assert.ok(projected);
assert.equal(input.expectedRevision, projected.revision);
const { thinkingLevel: _thinkingLevel, ...withoutThinkingLevel } = projected;
projected = session(projected.id, {
...projected,
...(input.patch.thinkingLevel === null ? withoutThinkingLevel : projected),
revision: projected.revision + 1,
permissionMode: input.configuration.permissionMode,
collaborationMode: input.configuration.collaborationMode,
orchestrationMode: input.configuration.orchestrationMode,
...(input.configuration.thinkingLevel === null
...(input.patch.permissionMode === undefined
? {}
: { permissionMode: input.patch.permissionMode }),
...(input.patch.collaborationMode === undefined
? {}
: { collaborationMode: input.patch.collaborationMode }),
...(input.patch.orchestrationMode === undefined
? {}
: { orchestrationMode: input.patch.orchestrationMode }),
...(input.patch.thinkingLevel === null || input.patch.thinkingLevel === undefined
? {}
: { thinkingLevel: input.configuration.thinkingLevel }),
: { thinkingLevel: input.patch.thinkingLevel }),
});
return { ok: true, result: { kind: 'committed', session: projected } };
},
Expand Down Expand Up @@ -610,6 +617,7 @@ function session(
hasUnread: false,
status: 'active',
backend: 'ai-sdk',
llmConnectionId: 'connection-1',
llmConnectionSlug: 'test-connection',
connectionLocked: true,
model: 'test-model',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1240,6 +1240,7 @@ function session(id: string): SessionCatalogProjection {
hasUnread: false,
status: 'active',
backend: 'ai-sdk',
llmConnectionId: 'connection-1',
llmConnectionSlug: 'test-connection',
connectionLocked: true,
model: 'test-model',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,7 @@ function session(id: string): SessionCatalogProjection {
hasUnread: false,
status: 'active',
backend: 'ai-sdk',
llmConnectionId: 'connection-1',
llmConnectionSlug: 'default',
connectionLocked: true,
model: 'gpt-5',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,7 @@ function catalogSession(id: string, name: string): SessionCatalogProjection {
hasUnread: false,
status: 'active',
backend: 'ai-sdk',
llmConnectionId: 'connection-1',
llmConnectionSlug: 'zai-live',
connectionLocked: true,
model: 'glm-5.1',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ function projection(overrides: Partial<SessionCatalogProjection> = {}): SessionC
hasUnread: false,
status: 'active',
backend: 'ai-sdk',
llmConnectionId: 'connection-1',
llmConnectionSlug: 'openai-main',
connectionLocked: true,
model: 'gpt-5',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ function session(id: string): SessionCatalogProjection {
hasUnread: false,
status: 'active',
backend: 'fake',
llmConnectionId: null,
llmConnectionSlug: 'fake',
connectionLocked: true,
model: 'fake-model',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1557,6 +1557,7 @@ function session(cwd = "/workspace", id = 'session-1'): SessionCatalogProjection
hasUnread: false,
status: "active",
backend: "ai-sdk",
llmConnectionId: "connection-1",
llmConnectionSlug: "test-connection",
connectionLocked: true,
model: "test-model",
Expand Down
8 changes: 4 additions & 4 deletions apps/desktop/src/main/__tests__/stale-sessions.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,9 @@ test('derives stale rows from each Session Host readiness projection', () => {
connectionLocked: true,
},
'remote-rebind': {
kind: 'rebind',
connectionSlug: 'replacement',
model: 'model',
kind: 'blocked',
reason: 'connection_missing',
connectionLocked: false,
},
// #3211: a retired backend reaches the rail as a projection reason like
// any other. The row is no longer identified by reading its `backend`.
Expand All @@ -53,7 +53,7 @@ test('derives stale rows from each Session Host readiness projection', () => {
},
},
})],
['remote-missing', 'legacy-fake'],
['remote-missing', 'remote-rebind', 'legacy-fake'],
);
});

Expand Down
6 changes: 3 additions & 3 deletions apps/desktop/src/main/__tests__/task-readiness-notice.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,14 @@ import {
resolveTaskReadinessModelTarget,
} from '../../renderer/task-readiness-notice.js';

test('an unlocked stale session checks the send projection rebind target', () => {
test('an unlocked stale session keeps its stored target until explicit recovery', () => {
assert.deepEqual(
resolveTaskReadinessModelTarget(
{ llmConnectionSlug: 'stale', model: 'removed-model' },
{ kind: 'rebind', connectionSlug: 'healthy', model: 'ready-model' },
{ kind: 'blocked', reason: 'connection_missing', connectionLocked: false },
undefined,
),
{ connectionSlug: 'healthy', model: 'ready-model' },
{ connectionSlug: 'stale', model: 'removed-model' },
);
});

Expand Down
9 changes: 4 additions & 5 deletions apps/desktop/src/main/onboarding-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ import { projectSessionSendOutcome, type SessionSendProjection } from '@maka/cor

import { type SessionSummary } from '@maka/core/session';
import { buildChatModelChoices, type ChatModelChoice } from '@maka/core/chat-model-choice';
import type { LlmConnection } from '@maka/core/llm-connections';
import type { IdentifiedLlmConnection, LlmConnection } from '@maka/core/llm-connections';

export interface OnboardingSnapshot {
state: OnboardingState;
Expand All @@ -71,14 +71,14 @@ export interface OnboardingSnapshot {
*/
sessions: SessionSummary[];
/** Default Host connection projection used to seed the shell. */
connections: LlmConnection[];
connections: IdentifiedLlmConnection[];
defaultSlug: string | null;
chatModelChoices: ChatModelChoice[];
sessionSendOutcomes: Record<string, SessionSendProjection>;
}

export interface OnboardingServiceDeps {
listConnections(): Promise<LlmConnection[]>;
listConnections(): Promise<IdentifiedLlmConnection[]>;
getDefaultSlug(): Promise<string | null>;
listSessions(): Promise<SessionSummary[]>;
getMilestones(): Promise<OnboardingMilestone[]>;
Expand Down Expand Up @@ -196,7 +196,7 @@ function buildSnapshot(
state: OnboardingState,
milestones: OnboardingMilestone[],
sessions: SessionSummary[],
connections: LlmConnection[],
connections: IdentifiedLlmConnection[],
defaultSlug: string | null,
secrets: Readonly<Record<string, boolean>>,
): OnboardingSnapshot {
Expand All @@ -213,7 +213,6 @@ function buildSnapshot(
projectSessionSendOutcome({
session,
connections,
defaultSlug,
hasSecret: (slug) => secrets[slug] ?? false,
}),
]),
Expand Down
Loading
Loading