fix: SACP notifies clients of generated session names (#8983)

Signed-off-by: Matt Toohey <contact@matttoohey.com>
This commit is contained in:
Matt Toohey
2026-05-05 08:00:08 +10:00
committed by GitHub
parent ebe3315bdd
commit 713d9d2010
13 changed files with 364 additions and 33 deletions
@@ -0,0 +1,95 @@
import { beforeEach, describe, expect, it } from "vitest";
import { useChatStore } from "@/features/chat/stores/chatStore";
import { useChatSessionStore } from "@/features/chat/stores/chatSessionStore";
import { handleSessionNotification } from "../acpNotificationHandler";
describe("ACP session info updates", () => {
beforeEach(() => {
useChatStore.setState({
messagesBySession: {},
sessionStateById: {},
queuedMessageBySession: {},
draftsBySession: {},
activeSessionId: null,
isConnected: false,
loadingSessionIds: new Set<string>(),
scrollTargetMessageBySession: {},
});
useChatSessionStore.setState({
sessions: [],
activeSessionId: null,
isLoading: false,
hasHydratedSessions: false,
contextPanelOpenBySession: {},
activeWorkspaceBySession: {},
});
});
it("applies generated session info updates to non-user-named sessions", async () => {
useChatSessionStore.getState().addSession({
id: "goose-session-title",
acpSessionId: "goose-session-title",
title: "New Chat",
createdAt: "2026-01-01T00:00:00.000Z",
updatedAt: "2026-01-01T00:00:00.000Z",
messageCount: 0,
userSetName: false,
});
await handleSessionNotification({
sessionId: "goose-session-title",
update: {
sessionUpdate: "session_info_update",
title: "Generated Test Title",
updatedAt: "2026-01-01T00:01:00.000Z",
_meta: {
messageCount: 1,
userSetName: false,
},
},
} as never);
expect(
useChatSessionStore.getState().getSession("goose-session-title"),
).toMatchObject({
title: "Generated Test Title",
updatedAt: "2026-01-01T00:01:00.000Z",
messageCount: 1,
userSetName: false,
});
});
it("ignores generated titles for user-named sessions", async () => {
useChatSessionStore.getState().addSession({
id: "goose-session-user-title",
acpSessionId: "goose-session-user-title",
title: "My Custom Title",
createdAt: "2026-01-01T00:00:00.000Z",
updatedAt: "2026-01-01T00:00:00.000Z",
messageCount: 0,
userSetName: true,
});
await handleSessionNotification({
sessionId: "goose-session-user-title",
update: {
sessionUpdate: "session_info_update",
title: "Generated Test Title",
updatedAt: "2026-01-01T00:01:00.000Z",
_meta: {
messageCount: 1,
userSetName: true,
},
},
} as never);
expect(
useChatSessionStore.getState().getSession("goose-session-user-title"),
).toMatchObject({
title: "My Custom Title",
updatedAt: "2026-01-01T00:01:00.000Z",
messageCount: 1,
userSetName: true,
});
});
});
@@ -30,6 +30,7 @@ import {
getTrackedReplayAssistantMessageId,
} from "./acpReplayAssistant";
import { getReplayCreated, getReplayMessageId } from "./acpReplayMetadata";
import { handleSessionInfoUpdate } from "./acpSessionInfoUpdate";
import {
getLocalSessionId,
subscribeToSessionRegistration,
@@ -487,17 +488,7 @@ function handleShared(
): void {
switch (update.sessionUpdate) {
case "session_info_update": {
const info = update as SessionUpdate & {
sessionUpdate: "session_info_update";
};
if ("title" in info && info.title) {
const session = useChatSessionStore.getState().getSession(sessionId);
if (session && !session.userSetName) {
useChatSessionStore
.getState()
.updateSession(sessionId, { title: info.title as string });
}
}
handleSessionInfoUpdate(sessionId, update);
break;
}
@@ -0,0 +1,45 @@
import type { SessionUpdate } from "@agentclientprotocol/sdk";
import { useChatSessionStore } from "@/features/chat/stores/chatSessionStore";
type SessionInfoUpdate = SessionUpdate & {
sessionUpdate: "session_info_update";
title?: unknown;
updatedAt?: unknown;
_meta?: unknown;
};
function isRecord(value: unknown): value is Record<string, unknown> {
return typeof value === "object" && value !== null;
}
export function handleSessionInfoUpdate(
sessionId: string,
update: SessionUpdate,
): void {
const info = update as SessionInfoUpdate;
const sessionStore = useChatSessionStore.getState();
const session = sessionStore.getSession(sessionId);
if (!session) {
return;
}
const meta = isRecord(info._meta) ? info._meta : {};
const patch: Parameters<typeof sessionStore.updateSession>[1] = {};
if (typeof info.title === "string" && info.title && !session.userSetName) {
patch.title = info.title;
}
if (typeof info.updatedAt === "string" && info.updatedAt) {
patch.updatedAt = info.updatedAt;
}
if (typeof meta.messageCount === "number") {
patch.messageCount = meta.messageCount;
}
if (typeof meta.userSetName === "boolean") {
patch.userSetName = meta.userSetName;
}
if (Object.keys(patch).length > 0) {
sessionStore.updateSession(sessionId, patch);
}
}