feat(coding): add conversation archive and first-message titles

This commit is contained in:
2026-09-20 12:43:51 +08:00
parent d61226532a
commit fd0293fe3d
22 changed files with 698 additions and 69 deletions

View File

@@ -10,6 +10,8 @@ export const CODING_CONVERSATIONS_PATH = '.makelore/conversations.json';
export interface CodingConversationV2 extends CodingConversationMetadata, ConversationModelState {
piSessionId?: string;
sessionKey?: string;
titleMode?: 'automatic' | 'manual';
autoTitleSet?: boolean;
}
export interface CodingConversationFileV2 {
@@ -22,6 +24,7 @@ export interface CreateCodingConversationInput {
title: string;
model: ProductModelRef | null;
modelResolution: 'resolved' | 'required';
titleMode?: 'automatic' | 'manual';
}
export interface PiSessionBinding {
@@ -83,6 +86,8 @@ function normalizeConversation(value: unknown): CodingConversationV2 {
id,
agentId,
title,
titleMode: record.titleMode === 'automatic' ? 'automatic' : 'manual',
autoTitleSet: record.titleMode === 'automatic' ? record.autoTitleSet === true : true,
...normalizeModelState(record),
...(piSessionId ? { piSessionId } : {}),
...(sessionKey ? { sessionKey } : {}),
@@ -139,7 +144,9 @@ export function createCodingConversationStore(
file: CodingConversationFileV2;
}>): Promise<T> {
const execute = async () => {
const change = await operation(await readNow());
const current = await readNow();
const change = await operation(current);
if (change.file === current) return change.result;
const normalized = normalizeCodingConversationFileV2(change.file);
await writer(conversationFilePath(projectPath), normalized);
return change.result;
@@ -167,6 +174,8 @@ export function createCodingConversationStore(
id: createId(),
agentId: input.agentId,
title: input.title,
titleMode: input.titleMode ?? 'manual',
autoTitleSet: input.titleMode !== 'automatic',
model: input.model,
modelResolution: input.modelResolution,
archivedAt: null,
@@ -192,10 +201,10 @@ export function createCodingConversationStore(
if (!current) throw new Error('Conversation does not exist');
const updated = normalizeConversation({
...current,
...(patch.title !== undefined ? { title: patch.title } : {}),
...(patch.title !== undefined ? { title: patch.title, titleMode: 'manual', autoTitleSet: true } : {}),
...(patch.archivedAt !== undefined ? { archivedAt: patch.archivedAt } : {}),
...(patch.unread !== undefined ? { unread: patch.unread } : {}),
updatedAt: now(),
updatedAt: patch.archivedAt !== undefined || patch.unread !== undefined ? now() : current.updatedAt,
});
return {
result: updated,
@@ -207,6 +216,16 @@ export function createCodingConversationStore(
});
},
async setFirstMessageTitle(conversationId: string, title: string) {
return await mutate(async (file) => {
const current = file.conversations.find((item) => item.id === conversationId);
if (!current || current.titleMode !== 'automatic' || current.autoTitleSet) return { result: null, file };
const updated = { ...current, title, autoTitleSet: true };
return { result: updated, file: { ...file,
conversations: file.conversations.map((item) => item.id === conversationId ? updated : item) } };
});
},
async setModelState(
conversationId: string,
modelState: ConversationModelState,