Skip to content

Commit fcacafd

Browse files
author
IM.codes
committed
fix qwen preset labels in execution routing
1 parent 8388cfe commit fcacafd

10 files changed

Lines changed: 136 additions & 17 deletions

File tree

src/daemon/session-list.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ export interface SessionListItem extends SessionContextBootstrapState {
2727
runtimeType?: string;
2828
providerId?: string;
2929
providerSessionId?: string;
30+
ccPreset?: string;
3031
qwenModel?: string;
3132
requestedModel?: string;
3233
activeModel?: string;
@@ -131,6 +132,7 @@ function baseItem(s: SessionRecord): SessionListItem {
131132
runtimeType: s.runtimeType,
132133
providerId: s.providerId,
133134
providerSessionId: s.providerSessionId,
135+
ccPreset: s.ccPreset,
134136
qwenModel: s.qwenModel,
135137
requestedModel: s.requestedModel,
136138
activeModel: s.activeModel,

test/daemon/session-list.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -346,6 +346,7 @@ describe('buildSessionList', () => {
346346
const sessions = await buildSessionList();
347347
expect(sessions).toHaveLength(1);
348348
expect(sessions[0]).toMatchObject({
349+
ccPreset: 'minimax',
349350
qwenAuthType: 'api-key',
350351
qwenAvailableModels: ['MiniMax-M2.7'],
351352
qwenModel: 'MiniMax-M2.7',

web/src/components/P2pConfigPanel.tsx

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { useState, useEffect, useMemo, useRef } from 'preact/hooks';
66
import { useTranslation } from 'react-i18next';
77
import { usePref } from '../hooks/usePref.js';
88
import { useExecutionRouting } from '../hooks/useExecutionRouting.js';
9+
import { buildExecutionTemplateLabel } from '../execution-template-label.js';
910
import { p2pSessionConfigLegacyPrefKeys, p2pSessionConfigPrefKey } from '../constants/prefs.js';
1011
import { parseP2pSavedConfig, serializeP2pSavedConfig } from '../preferences/p2p-config-pref.js';
1112
import { P2pComboManager } from './P2pComboManager.js';
@@ -58,6 +59,12 @@ interface SessionRow {
5859
state: string;
5960
project?: string | null;
6061
role?: string | null;
62+
label?: string | null;
63+
ccPreset?: string | null;
64+
qwenModel?: string | null;
65+
requestedModel?: string | null;
66+
activeModel?: string | null;
67+
modelDisplay?: string | null;
6168
/** DAEMON-AUTHORITATIVE execution-template eligibility (optional; absent on
6269
* legacy daemons). When present it overrides client-side recomputation. */
6370
executionTemplateEligible?: boolean;
@@ -150,9 +157,15 @@ function isExecutionCloneCandidate(sub: Pick<SubSessionRow, 'executionCloneKind'
150157
}
151158

152159
function formatExecutionTemplateLabel(sub: SubSessionRow): string {
153-
const shortName = sub.label || sub.sessionName.split('_').pop() || sub.sessionName;
154-
const model = sub.modelDisplay || sub.activeModel || sub.requestedModel || sub.qwenModel || null;
155-
return [shortName, sub.type || null, sub.ccPresetId || null, model].filter(Boolean).join(' · ');
160+
return buildExecutionTemplateLabel({
161+
shortName: sub.label || sub.sessionName.split('_').pop() || sub.sessionName,
162+
agentType: sub.type,
163+
ccPreset: sub.ccPresetId,
164+
qwenModel: sub.qwenModel,
165+
requestedModel: sub.requestedModel,
166+
activeModel: sub.activeModel,
167+
modelDisplay: sub.modelDisplay,
168+
});
156169
}
157170

158171
export interface P2pWorkflowLaunchContextInput {

web/src/components/SessionControls.tsx

Lines changed: 36 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import { AtPicker } from './AtPicker.js';
1717
import { MobileDpad, DPAD_ARROW_SEQUENCES } from './MobileDpad.js';
1818
import { P2pConfigPanel, buildP2pWorkflowLaunchEnvelopeFromConfig } from './P2pConfigPanel.js';
1919
import { useExecutionRouting } from '../hooks/useExecutionRouting.js';
20+
import { buildExecutionTemplateLabel } from '../execution-template-label.js';
2021
import {
2122
OpenSpecAutoDeliverCurrentRunEntry,
2223
OpenSpecAutoDeliverDetailsPanel,
@@ -160,6 +161,10 @@ interface Props {
160161
parentSession?: string | null;
161162
executionCloneKind?: string | null;
162163
parentRunId?: string | null;
164+
qwenModel?: string | null;
165+
requestedModel?: string | null;
166+
activeModel?: string | null;
167+
modelDisplay?: string | null;
163168
executionTemplateEligible?: boolean;
164169
executionTemplateIneligibleReason?: string;
165170
}>;
@@ -198,8 +203,13 @@ const OPENSPEC_EXEC_EXCLUDED_TYPES = new Set(['shell', 'script']);
198203

199204
/** OpenSpec Execute candidate label: session short name + SDK (agent type) +
200205
* preset name, so identical-looking workers (w2…wN) are distinguishable. */
201-
function formatExecLabel(shortName: string, sdk?: string | null, preset?: string | null): string {
202-
return [shortName, sdk || null, preset || null].filter(Boolean).join(' · ');
206+
function formatExecLabel(shortName: string, sdk?: string | null, preset?: string | null, model?: string | null): string {
207+
return buildExecutionTemplateLabel({
208+
shortName,
209+
agentType: sdk,
210+
ccPreset: preset,
211+
modelDisplay: model,
212+
});
203213
}
204214

205215
type OpenSpecTaskStatsSummary = {
@@ -2045,7 +2055,15 @@ export function SessionControls({ ws, activeSession, inputRef, onAfterAction, on
20452055
const executionSessionDisplayName = useMemo(() => {
20462056
if (!configuredExecutionSession) return null;
20472057
const sub = (subSessions ?? []).find((s) => s.sessionName === configuredExecutionSession);
2048-
if (sub && !isExecutionCloneTemplateLike(sub)) return sub.label || sub.sessionName.split('_').pop() || sub.sessionName;
2058+
if (sub && !isExecutionCloneTemplateLike(sub)) return buildExecutionTemplateLabel({
2059+
shortName: sub.label || sub.sessionName.split('_').pop() || sub.sessionName,
2060+
agentType: sub.type,
2061+
ccPreset: sub.ccPresetId,
2062+
qwenModel: sub.qwenModel,
2063+
requestedModel: sub.requestedModel,
2064+
activeModel: sub.activeModel,
2065+
modelDisplay: sub.modelDisplay,
2066+
});
20492067
return null;
20502068
}, [configuredExecutionSession, subSessions]);
20512069
const hasValidExecutionDefault = Boolean(
@@ -2068,12 +2086,12 @@ export function SessionControls({ ws, activeSession, inputRef, onAfterAction, on
20682086
// the UI renders the flag and never recomputes eligibility client-side. This
20692087
// mirrors the P2pConfigPanel template scan.
20702088
const openSpecExecutionTemplateScan = useMemo(() => {
2071-
const eligible: Array<{ sessionName: string; shortName: string; sdk: string; preset: string | null }> = [];
2089+
const eligible: Array<{ sessionName: string; shortName: string; sdk: string; preset: string | null; model: string | null }> = [];
20722090
const seen = new Set<string>();
2073-
const push = (sessionName: string, shortName: string, sdk: string, preset: string | null) => {
2091+
const push = (sessionName: string, shortName: string, sdk: string, preset: string | null, model: string | null) => {
20742092
if (seen.has(sessionName)) return;
20752093
seen.add(sessionName);
2076-
eligible.push({ sessionName, shortName, sdk, preset });
2094+
eligible.push({ sessionName, shortName, sdk, preset, model });
20772095
};
20782096
// GROUP-SCOPED: execution targets are attached non-main sub-sessions only.
20792097
// Main sessions (brain/orchestrator/worker `wN`) are not execution templates.
@@ -2085,7 +2103,13 @@ export function SessionControls({ ws, activeSession, inputRef, onAfterAction, on
20852103
if (!rootSession || s.parentSession !== rootSession) continue;
20862104
if (isExecutionCloneTemplateLike(s)) continue;
20872105
if (s.executionTemplateEligible === false) continue;
2088-
push(s.sessionName, s.label || s.sessionName.split('_').pop() || s.sessionName, s.type, s.ccPresetId ?? null);
2106+
push(
2107+
s.sessionName,
2108+
s.label || s.sessionName.split('_').pop() || s.sessionName,
2109+
s.type,
2110+
s.ccPresetId ?? null,
2111+
resolveEffectiveSessionModel(s) ?? null,
2112+
);
20892113
}
20902114
return eligible;
20912115
}, [subSessions, activeSession?.name, rootSession]);
@@ -2098,9 +2122,10 @@ export function SessionControls({ ws, activeSession, inputRef, onAfterAction, on
20982122
if (!sub || isExecutionCloneTemplateLike(sub)) return null;
20992123
return {
21002124
sessionName: configuredExecutionSession,
2101-
shortName: executionSessionDisplayName,
2125+
shortName: sub.label || sub.sessionName.split('_').pop() || sub.sessionName,
21022126
sdk: sub.type,
21032127
preset: sub.ccPresetId ?? null,
2128+
model: resolveEffectiveSessionModel(sub) ?? null,
21042129
};
21052130
}, [hasValidExecutionDefault, configuredExecutionSession, executionSessionDisplayName, subSessions]);
21062131

@@ -2139,7 +2164,7 @@ export function SessionControls({ ws, activeSession, inputRef, onAfterAction, on
21392164
}}
21402165
>
21412166
<span aria-hidden="true"></span>
2142-
{t('openspec.execute.dispatch_to', { name: formatExecLabel(openSpecPinnedExecutionSession.shortName, openSpecPinnedExecutionSession.sdk, openSpecPinnedExecutionSession.preset) })}
2167+
{t('openspec.execute.dispatch_to', { name: formatExecLabel(openSpecPinnedExecutionSession.shortName, openSpecPinnedExecutionSession.sdk, openSpecPinnedExecutionSession.preset, openSpecPinnedExecutionSession.model) })}
21432168
</button>
21442169
)}
21452170
{openSpecExecutionCandidates.map((candidate) => (
@@ -2155,7 +2180,7 @@ export function SessionControls({ ws, activeSession, inputRef, onAfterAction, on
21552180
setOpenSpecOpen(false);
21562181
}}
21572182
>
2158-
{t('openspec.execute.dispatch_to', { name: formatExecLabel(candidate.shortName, candidate.sdk, candidate.preset) })}
2183+
{t('openspec.execute.dispatch_to', { name: formatExecLabel(candidate.shortName, candidate.sdk, candidate.preset, candidate.model) })}
21592184
</button>
21602185
))}
21612186
{!openSpecPinnedExecutionSession && openSpecExecutionCandidates.length === 0 && (
@@ -2177,7 +2202,7 @@ export function SessionControls({ ws, activeSession, inputRef, onAfterAction, on
21772202
setOpenSpecExecuteMenu(null);
21782203
}}
21792204
>
2180-
{t('openspec.execute.set_default', { name: formatExecLabel(candidate.shortName, candidate.sdk, candidate.preset) })}
2205+
{t('openspec.execute.set_default', { name: formatExecLabel(candidate.shortName, candidate.sdk, candidate.preset, candidate.model) })}
21812206
</button>
21822207
))}
21832208
{openSpecPinnedExecutionSession && (
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { resolveEffectiveSessionModel, type SessionModelMetadata } from '@shared/session-model.js';
2+
3+
function nonEmpty(value: string | null | undefined): string | null {
4+
const trimmed = value?.trim();
5+
return trimmed ? trimmed : null;
6+
}
7+
8+
function pushUnique(parts: string[], value: string | null | undefined): void {
9+
const trimmed = nonEmpty(value);
10+
if (!trimmed) return;
11+
if (parts.some((part) => part.toLowerCase() === trimmed.toLowerCase())) return;
12+
parts.push(trimmed);
13+
}
14+
15+
export function buildExecutionTemplateLabel(input: SessionModelMetadata & {
16+
shortName: string;
17+
agentType?: string | null;
18+
ccPreset?: string | null;
19+
}): string {
20+
const parts: string[] = [];
21+
pushUnique(parts, input.shortName);
22+
pushUnique(parts, input.agentType);
23+
pushUnique(parts, input.ccPreset);
24+
pushUnique(parts, resolveEffectiveSessionModel(input));
25+
return parts.join(' · ');
26+
}

web/src/hooks/useSubSessions.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,7 @@ export function useSubSessions(
259259
...(m.state != null && { state: m.state as SubSession['state'] }),
260260
...(m.cwd != null && { cwd: m.cwd }),
261261
...(m.label != null && { label: m.label }),
262+
...(m.ccPresetId !== undefined && { ccPresetId: m.ccPresetId }),
262263
...(m.modelDisplay != null && { modelDisplay: m.modelDisplay }),
263264
...(m.requestedModel !== undefined && { requestedModel: m.requestedModel }),
264265
...(m.activeModel !== undefined && { activeModel: m.activeModel }),
@@ -297,6 +298,7 @@ export function useSubSessions(
297298
providerSessionId: m.providerSessionId ?? null,
298299
cwd: m.cwd || null,
299300
label: m.label || null,
301+
ccPresetId: m.ccPresetId ?? null,
300302
parentSession: m.parentSession || null,
301303
createdAt: now,
302304
updatedAt: now,
@@ -366,6 +368,7 @@ export function useSubSessions(
366368
...(m.state ? { state: m.state as SubSession['state'] } : {}),
367369
...(m.cwd !== undefined ? { cwd: m.cwd } : {}),
368370
...(m.label !== undefined ? { label: m.label } : {}),
371+
...(m.ccPresetId !== undefined ? { ccPresetId: m.ccPresetId } : {}),
369372
...(m.qwenModel !== undefined ? { qwenModel: m.qwenModel } : {}),
370373
...((m.codexAvailableModels != null || (!preserveQuota && m.codexAvailableModels === null)) ? { codexAvailableModels: m.codexAvailableModels } : {}),
371374
...(m.requestedModel !== undefined ? { requestedModel: m.requestedModel } : {}),

web/src/session-list-merge.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ export interface IncomingSessionListEntry {
4848
label?: string | null;
4949
userCreated?: boolean;
5050
description?: string | null;
51+
ccPreset?: string | null;
5152
qwenModel?: string;
5253
requestedModel?: string;
5354
activeModel?: string;
@@ -176,6 +177,7 @@ export function mergeSessionListEntry(
176177
label: incoming.label ?? existing?.label,
177178
userCreated: incoming.userCreated ?? existing?.userCreated,
178179
description: incoming.description ?? existing?.description,
180+
ccPreset: incoming.ccPreset !== undefined ? incoming.ccPreset : existing?.ccPreset,
179181
qwenModel: incoming.qwenModel ?? existing?.qwenModel,
180182
requestedModel: incoming.requestedModel ?? existing?.requestedModel,
181183
activeModel: incoming.activeModel ?? existing?.activeModel,

0 commit comments

Comments
 (0)