refactor: consolidate chat state and task bridges
Centralize session scope, chat projections and typed task bridges. Keep historical interaction replies authoritative and publish terminal state to subscribers. Validate Server contracts before replacing generated artifacts.
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
import { toSessionScope } from "../sessions/session-scope.js";
|
||||
import { registerChatTopicRoutes } from "./chat-topic-routes.js";
|
||||
import { matchHistoryText } from "../sessions/history-search.js";
|
||||
import { credentialRefreshCoordinator } from "../product/credential-refresh.js";
|
||||
@@ -107,11 +108,6 @@ type RegisterChatSessionRoutesOptions = {
|
||||
sessionUiStateStore: SessionUiStateStore;
|
||||
};
|
||||
|
||||
const toSessionUiStateContext = (sessionRecord: SessionRecord) => ({
|
||||
sessionId: sessionRecord.sessionId,
|
||||
actorKey: sessionRecord.actorKey,
|
||||
projectKey: sessionRecord.projectKey,
|
||||
});
|
||||
|
||||
const toSse = (event: string, data: Record<string, unknown>) =>
|
||||
`event: ${event}\ndata: ${JSON.stringify(data)}\n\n`;
|
||||
@@ -176,6 +172,12 @@ export const registerChatSessionRoutes = (
|
||||
const { activeRuns, lastRunStatuses } = runState;
|
||||
const getSessionRunStatus = runState.getStatus;
|
||||
|
||||
const readMessages = (record: SessionRecord, state: { messages: unknown[] } | null) => {
|
||||
if (readPurpose(record) === "task_collaboration") return state?.messages ?? [];
|
||||
const run = activeRuns.get(record.sessionId);
|
||||
return run?.uiMessages?.() ?? run?.messages ?? state?.messages ?? [];
|
||||
};
|
||||
|
||||
const withInteractionReplies = async (record: SessionRecord, messages: unknown[]) => {
|
||||
if (!interactionRequestStore) return messages;
|
||||
const ids = new Set(messages.flatMap((message) => {
|
||||
@@ -183,7 +185,7 @@ export const registerChatSessionRoutes = (
|
||||
const value = message as Record<string, unknown>;
|
||||
return value.role === "user" && typeof value.id === "string" ? [value.id] : [];
|
||||
}));
|
||||
const replies = await interactionRequestStore.forMessages(toSessionUiStateContext(record), ids);
|
||||
const replies = await interactionRequestStore.forMessages(toSessionScope(record), ids);
|
||||
return projectInteractionReplyMessages(messages, replies);
|
||||
};
|
||||
|
||||
@@ -192,7 +194,7 @@ export const registerChatSessionRoutes = (
|
||||
|| !isGenericTaskCollaborationTitle(record.title)) return record;
|
||||
if ((!credential && !knownTaskTitle) || signal?.aborted) return record;
|
||||
try {
|
||||
const state = await sessionUiStateStore.read(toSessionUiStateContext(record));
|
||||
const state = await sessionUiStateStore.read(toSessionScope(record));
|
||||
if (state?.isTitleManuallyEdited) return record;
|
||||
let taskTitle = knownTaskTitle;
|
||||
if (!taskTitle && credential) {
|
||||
@@ -206,7 +208,7 @@ export const registerChatSessionRoutes = (
|
||||
}
|
||||
}
|
||||
if (!taskTitle?.trim()) return record;
|
||||
const latestState = await sessionUiStateStore.read(toSessionUiStateContext(record));
|
||||
const latestState = await sessionUiStateStore.read(toSessionScope(record));
|
||||
if (latestState?.isTitleManuallyEdited) return record;
|
||||
return await sessionMetadataStore.touch(record, { title: taskTitle }, {
|
||||
preserveUpdatedAt: true, expectedTitle: record.title ?? "",
|
||||
@@ -297,7 +299,7 @@ export const registerChatSessionRoutes = (
|
||||
sessionId,
|
||||
);
|
||||
if (!record) return null;
|
||||
const state = await sessionUiStateStore.read(toSessionUiStateContext(record));
|
||||
const state = await sessionUiStateStore.read(toSessionScope(record));
|
||||
if (!state) return null;
|
||||
return { record, state, taskTitle };
|
||||
};
|
||||
@@ -603,7 +605,7 @@ export const registerChatSessionRoutes = (
|
||||
const titled = await sessionMetadataStore.touch(record, { title: `${history.taskTitle} · 调试` });
|
||||
const messages = await withInteractionReplies(history.record, history.state.messages ?? []);
|
||||
await sessionUiStateStore.write(
|
||||
toSessionUiStateContext(titled),
|
||||
toSessionScope(titled),
|
||||
buildForkedSessionUiState({ messages }, {
|
||||
keepMessageCount: messages.length,
|
||||
targetSessionId: titled.sessionId,
|
||||
@@ -771,8 +773,8 @@ export const registerChatSessionRoutes = (
|
||||
const searchable: Array<SessionRecord & { searchSnippet?: string }> = [];
|
||||
for (const record of records) {
|
||||
const titleMatches = (record.title ?? "新对话").toLocaleLowerCase().includes((query.data.q ?? "").toLocaleLowerCase());
|
||||
const state = query.data.q && !titleMatches ? await sessionUiStateStore.read(toSessionUiStateContext(record)) : null;
|
||||
const messages = activeRuns.get(record.sessionId)?.messages ?? state?.messages ?? [];
|
||||
const state = query.data.q && !titleMatches ? await sessionUiStateStore.read(toSessionScope(record)) : null;
|
||||
const messages = readMessages(record, state);
|
||||
searchable.push({ ...record, searchSnippet: query.data.q && !titleMatches ? matchHistoryText(messages, query.data.q) : undefined });
|
||||
}
|
||||
let page;
|
||||
@@ -828,9 +830,8 @@ export const registerChatSessionRoutes = (
|
||||
const context = getLocalAgentContext(req);
|
||||
const record = await sessionMetadataStore.get(context, req.params.session_id);
|
||||
if (!record) { res.status(404).json({ message: "session not found" }); return; }
|
||||
const state = await sessionUiStateStore.read(toSessionUiStateContext(record));
|
||||
const messages = readPurpose(record) === "task_collaboration" ? state?.messages ?? []
|
||||
: activeRuns.get(record.sessionId)?.messages ?? state?.messages ?? [];
|
||||
const state = await sessionUiStateStore.read(toSessionScope(record));
|
||||
const messages = readMessages(record, state);
|
||||
try {
|
||||
const page = sessionMessagePage(messages, { scope: historyScope([record.actorKey, record.projectKey, record.sessionId]), ...query.data });
|
||||
res.json({ ...page, messages: await withInteractionReplies(record, page.messages) });
|
||||
@@ -871,10 +872,9 @@ export const registerChatSessionRoutes = (
|
||||
sessionRecord = await refreshCollaborationTitle(sessionRecord, getTrustedProductCredential(req));
|
||||
|
||||
const state = await sessionUiStateStore.read(
|
||||
toSessionUiStateContext(sessionRecord),
|
||||
toSessionScope(sessionRecord),
|
||||
);
|
||||
const messages = readPurpose(sessionRecord) === "task_collaboration" ? state?.messages ?? []
|
||||
: activeRuns.get(sessionRecord.sessionId)?.messages ?? state?.messages ?? [];
|
||||
const messages = readMessages(sessionRecord, state);
|
||||
const messagePayload = query.data.message_limit ? sessionMessagePage(messages, {
|
||||
scope: historyScope([sessionRecord.actorKey, sessionRecord.projectKey, sessionId]), limit: query.data.message_limit,
|
||||
}) : { messages };
|
||||
@@ -929,9 +929,8 @@ export const registerChatSessionRoutes = (
|
||||
res.flushHeaders?.();
|
||||
|
||||
const run = activeRuns.get(sessionRecord.sessionId);
|
||||
const state = await sessionUiStateStore.read(toSessionUiStateContext(sessionRecord));
|
||||
const messages = readPurpose(sessionRecord) === "task_collaboration" ? state?.messages ?? []
|
||||
: run?.messages ?? state?.messages ?? [];
|
||||
const state = await sessionUiStateStore.read(toSessionScope(sessionRecord));
|
||||
const messages = readMessages(sessionRecord, state);
|
||||
const messagePayload = query.data.message_limit ? sessionMessagePage(messages, {
|
||||
scope: historyScope([sessionRecord.actorKey, sessionRecord.projectKey, sessionId]), limit: query.data.message_limit,
|
||||
}) : { messages };
|
||||
@@ -1005,11 +1004,11 @@ export const registerChatSessionRoutes = (
|
||||
title,
|
||||
});
|
||||
const state = await sessionUiStateStore.read(
|
||||
toSessionUiStateContext(nextSessionRecord),
|
||||
toSessionScope(nextSessionRecord),
|
||||
);
|
||||
if (state) {
|
||||
await sessionUiStateStore.write(
|
||||
toSessionUiStateContext(nextSessionRecord),
|
||||
toSessionScope(nextSessionRecord),
|
||||
{
|
||||
...state,
|
||||
isTitleManuallyEdited:
|
||||
@@ -1134,7 +1133,7 @@ export const registerChatSessionRoutes = (
|
||||
}
|
||||
const sourceState = sourceSessionRecord
|
||||
? await sessionUiStateStore.read(
|
||||
toSessionUiStateContext(sourceSessionRecord),
|
||||
toSessionScope(sourceSessionRecord),
|
||||
)
|
||||
: null;
|
||||
const forkTitle = sourceSessionRecord?.title
|
||||
@@ -1145,7 +1144,7 @@ export const registerChatSessionRoutes = (
|
||||
{ title: forkTitle },
|
||||
);
|
||||
await sessionUiStateStore.write(
|
||||
toSessionUiStateContext(titledTargetSessionRecord),
|
||||
toSessionScope(titledTargetSessionRecord),
|
||||
buildForkedSessionUiState(sourceState, {
|
||||
keepMessageCount: parsed.data.keep_message_count,
|
||||
targetSessionId: nextSessionId,
|
||||
|
||||
Reference in New Issue
Block a user