fix(ui): session dropdown shows label instead of key (#45130)

Merged via squash.

Prepared head SHA: 0255e3971b
Co-authored-by: luzhidong <15848762+luzhidong@users.noreply.github.com>
Co-authored-by: altaywtf <9790196+altaywtf@users.noreply.github.com>
Reviewed-by: @altaywtf
This commit is contained in:
luzhidong 2026-03-14 19:36:46 +08:00 committed by GitHub
parent 64e6df7eea
commit 40c81e9cd3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 144 additions and 11 deletions

View File

@ -12,6 +12,7 @@ Docs: https://docs.openclaw.ai
### Fixes
- Z.AI/onboarding: detect a working default model even for explicit `zai-coding-*` endpoint choices, so Coding Plan setup can keep the selected endpoint while defaulting to `glm-5` when available or `glm-4.7` as fallback. (#45969)
- Control UI/chat sessions: show human-readable labels in the grouped session dropdown again, keep unique scoped fallbacks when metadata is missing, and disambiguate duplicate labels only when needed. (#45130) thanks @luzhidong.
## 2026.3.13

View File

@ -575,6 +575,7 @@ export function isCronSessionKey(key: string): boolean {
type SessionOptionEntry = {
key: string;
label: string;
scopeLabel: string;
title: string;
};
@ -625,10 +626,12 @@ export function resolveSessionOptionGroups(
resolveAgentGroupLabel(state, parsed.agentId),
)
: ensureGroup("other", "Other Sessions");
const scopeLabel = parsed?.rest?.trim() || key;
const label = resolveSessionScopedOptionLabel(key, row, parsed?.rest);
group.options.push({
key,
label,
scopeLabel,
title: key,
});
};
@ -643,6 +646,19 @@ export function resolveSessionOptionGroups(
addOption(row.key);
}
addOption(sessionKey);
for (const group of groups.values()) {
const counts = new Map<string, number>();
for (const option of group.options) {
counts.set(option.label, (counts.get(option.label) ?? 0) + 1);
}
for (const option of group.options) {
if ((counts.get(option.label) ?? 0) > 1 && option.scopeLabel !== option.label) {
option.label = `${option.label} · ${option.scopeLabel}`;
}
}
}
return Array.from(groups.values());
}
@ -673,18 +689,14 @@ function resolveSessionScopedOptionLabel(
if (!row) {
return base;
}
const displayName =
typeof row.displayName === "string" && row.displayName.trim().length > 0
? row.displayName.trim()
: null;
const label = typeof row.label === "string" ? row.label.trim() : "";
const showDisplayName = Boolean(
displayName && displayName !== key && displayName !== label && displayName !== base,
);
if (!showDisplayName) {
return base;
const label = row.label?.trim() || "";
const displayName = row.displayName?.trim() || "";
if ((label && label !== key) || (displayName && displayName !== key)) {
return resolveSessionDisplayName(key, row);
}
return `${base} · ${displayName}`;
return base;
}
type ThemeOption = { id: ThemeName; label: string; icon: string };

View File

@ -647,4 +647,124 @@ describe("chat view", () => {
expect(rerendered?.value).toBe("gpt-5-mini");
vi.unstubAllGlobals();
});
it("prefers the session label over displayName in the grouped chat session selector", () => {
const { state } = createChatHeaderState({ omitSessionFromList: true });
state.sessionKey = "agent:main:subagent:4f2146de-887b-4176-9abe-91140082959b";
state.settings.sessionKey = state.sessionKey;
state.sessionsResult = {
ts: 0,
path: "",
count: 1,
defaults: { model: "gpt-5", contextTokens: null },
sessions: [
{
key: state.sessionKey,
kind: "direct",
updatedAt: null,
label: "cron-config-check",
displayName: "webchat:g-agent-main-subagent-4f2146de-887b-4176-9abe-91140082959b",
},
],
};
const container = document.createElement("div");
render(renderChatSessionSelect(state), container);
const [sessionSelect] = Array.from(container.querySelectorAll<HTMLSelectElement>("select"));
const labels = Array.from(sessionSelect?.querySelectorAll("option") ?? []).map((option) =>
option.textContent?.trim(),
);
expect(labels).toContain("Subagent: cron-config-check");
expect(labels).not.toContain(state.sessionKey);
expect(labels).not.toContain(
"subagent:4f2146de-887b-4176-9abe-91140082959b · webchat:g-agent-main-subagent-4f2146de-887b-4176-9abe-91140082959b",
);
});
it("keeps a unique scoped fallback when the current grouped session is missing from sessions.list", () => {
const { state } = createChatHeaderState({ omitSessionFromList: true });
state.sessionKey = "agent:main:subagent:4f2146de-887b-4176-9abe-91140082959b";
state.settings.sessionKey = state.sessionKey;
const container = document.createElement("div");
render(renderChatSessionSelect(state), container);
const [sessionSelect] = Array.from(container.querySelectorAll<HTMLSelectElement>("select"));
const labels = Array.from(sessionSelect?.querySelectorAll("option") ?? []).map((option) =>
option.textContent?.trim(),
);
expect(labels).toContain("subagent:4f2146de-887b-4176-9abe-91140082959b");
expect(labels).not.toContain("Subagent:");
});
it("keeps a unique scoped fallback when a grouped session row has no label or displayName", () => {
const { state } = createChatHeaderState({ omitSessionFromList: true });
state.sessionKey = "agent:main:subagent:4f2146de-887b-4176-9abe-91140082959b";
state.settings.sessionKey = state.sessionKey;
state.sessionsResult = {
ts: 0,
path: "",
count: 1,
defaults: { model: "gpt-5", contextTokens: null },
sessions: [
{
key: state.sessionKey,
kind: "direct",
updatedAt: null,
},
],
};
const container = document.createElement("div");
render(renderChatSessionSelect(state), container);
const [sessionSelect] = Array.from(container.querySelectorAll<HTMLSelectElement>("select"));
const labels = Array.from(sessionSelect?.querySelectorAll("option") ?? []).map((option) =>
option.textContent?.trim(),
);
expect(labels).toContain("subagent:4f2146de-887b-4176-9abe-91140082959b");
expect(labels).not.toContain("Subagent:");
});
it("disambiguates duplicate grouped labels with the scoped key suffix", () => {
const { state } = createChatHeaderState({ omitSessionFromList: true });
state.sessionKey = "agent:main:subagent:4f2146de-887b-4176-9abe-91140082959b";
state.settings.sessionKey = state.sessionKey;
state.sessionsResult = {
ts: 0,
path: "",
count: 2,
defaults: { model: "gpt-5", contextTokens: null },
sessions: [
{
key: "agent:main:subagent:4f2146de-887b-4176-9abe-91140082959b",
kind: "direct",
updatedAt: null,
label: "cron-config-check",
},
{
key: "agent:main:subagent:6fb8b84b-c31f-410f-b7df-1553c82e43c9",
kind: "direct",
updatedAt: null,
label: "cron-config-check",
},
],
};
const container = document.createElement("div");
render(renderChatSessionSelect(state), container);
const [sessionSelect] = Array.from(container.querySelectorAll<HTMLSelectElement>("select"));
const labels = Array.from(sessionSelect?.querySelectorAll("option") ?? []).map((option) =>
option.textContent?.trim(),
);
expect(labels).toContain(
"Subagent: cron-config-check · subagent:4f2146de-887b-4176-9abe-91140082959b",
);
expect(labels).toContain(
"Subagent: cron-config-check · subagent:6fb8b84b-c31f-410f-b7df-1553c82e43c9",
);
expect(labels).not.toContain("Subagent: cron-config-check");
});
});