Skip to content

Commit a181458

Browse files
authored
fix(ui): preserve dashboard session parent lineage
Preserves the selected Control UI session as the parent when creating dashboard child sessions even if the session list is stale or filtered, while avoiding the synthetic unknown session as a parent.\n\nFixes #90623.\n\nProof: local focused format/lint/Vitest/browser test; autoreview clean; Crabbox AWS run_a2bfdcd2315a UI proof; Crabbox AWS run_ce60fdc546ff check:changed; exact-head GitHub CI green on 03d1c6f.
1 parent ca2410a commit a181458

2 files changed

Lines changed: 78 additions & 5 deletions

File tree

ui/src/ui/app-render.helpers.node.test.ts

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -835,6 +835,42 @@ describe("createChatSession", () => {
835835
expect(loadChatHistoryMock).toHaveBeenCalledWith(state);
836836
});
837837

838+
it("keeps the selected session as parent when the session list is stale", async () => {
839+
const state = createChatSessionState({
840+
sessionsResult: {
841+
ts: 0,
842+
path: "",
843+
count: 1,
844+
defaults: { modelProvider: "openai", model: "gpt-5", contextTokens: null },
845+
sessions: [row({ key: "agent:ops:dashboard:older" })],
846+
},
847+
});
848+
createSessionAndRefreshMock.mockResolvedValue("agent:ops:dashboard:new-chat");
849+
refreshChatAvatarMock.mockResolvedValue(undefined);
850+
refreshSlashCommandsMock.mockResolvedValue(undefined);
851+
loadChatHistoryMock.mockResolvedValue(undefined);
852+
loadSessionsMock.mockResolvedValue(undefined);
853+
854+
await createChatSession(state, { source: "user" });
855+
856+
expect(createSessionAndRefreshMock).toHaveBeenCalledWith(
857+
state,
858+
{
859+
agentId: "ops",
860+
parentSessionKey: "agent:ops:main",
861+
emitCommandHooks: true,
862+
},
863+
{
864+
activeMinutes: 120,
865+
limit: 50,
866+
includeGlobal: true,
867+
includeUnknown: true,
868+
showArchived: false,
869+
agentId: "ops",
870+
},
871+
);
872+
});
873+
838874
it("creates selected global sessions under the same agent used for refresh", async () => {
839875
const state = createChatSessionState({
840876
sessionKey: "global",
@@ -874,6 +910,43 @@ describe("createChatSession", () => {
874910
expect(state.sessionKey).toBe("agent:work:dashboard:new-chat");
875911
});
876912

913+
it("does not use the synthetic unknown session as a parent", async () => {
914+
const state = createChatSessionState({
915+
sessionKey: "unknown",
916+
sessionsResult: {
917+
ts: 0,
918+
path: "",
919+
count: 1,
920+
defaults: { modelProvider: "openai", model: "gpt-5", contextTokens: null },
921+
sessions: [row({ key: "unknown", kind: "unknown" })],
922+
},
923+
});
924+
createSessionAndRefreshMock.mockResolvedValue("agent:main:dashboard:new-chat");
925+
refreshChatAvatarMock.mockResolvedValue(undefined);
926+
refreshSlashCommandsMock.mockResolvedValue(undefined);
927+
loadChatHistoryMock.mockResolvedValue(undefined);
928+
loadSessionsMock.mockResolvedValue(undefined);
929+
930+
await createChatSession(state, { source: "user" });
931+
932+
expect(createSessionAndRefreshMock).toHaveBeenCalledWith(
933+
state,
934+
{
935+
agentId: "main",
936+
parentSessionKey: undefined,
937+
emitCommandHooks: undefined,
938+
},
939+
{
940+
activeMinutes: 120,
941+
limit: 50,
942+
includeGlobal: true,
943+
includeUnknown: true,
944+
showArchived: false,
945+
},
946+
);
947+
expect(state.sessionKey).toBe("agent:main:dashboard:new-chat");
948+
});
949+
877950
it("preserves draft and attachment edits made while session creation is in flight", async () => {
878951
const state = createChatSessionState();
879952
const updatedAttachments = [

ui/src/ui/app-render.helpers.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -731,11 +731,11 @@ export async function createChatSession(
731731
state.lastError = null;
732732
state.chatError = null;
733733
const previousSessionKey = state.sessionKey;
734-
const parentSessionKey = state.sessionsResult?.sessions.some(
735-
(row) => row.key === previousSessionKey,
736-
)
737-
? previousSessionKey
738-
: undefined;
734+
const normalizedPreviousSessionKey = normalizeOptionalString(previousSessionKey);
735+
const parentSessionKey =
736+
normalizeLowercaseStringOrEmpty(normalizedPreviousSessionKey) === "unknown"
737+
? undefined
738+
: normalizedPreviousSessionKey;
739739
const nextSessionKey = await createSessionAndRefresh(
740740
state as unknown as Parameters<typeof createSessionAndRefresh>[0],
741741
{

0 commit comments

Comments
 (0)