feat: add independent conversation branching

Local conversations can now continue in a new branch after stable content is persisted. Each branch uses new conversation and message IDs, keeps its source visible, and remains independent without copying Tasks, queue items, runs, memories, or artifact ownership.

Conversation Task rows are also denser, align status markers at the right edge, and use an accessible checked success marker without redundant completion text.

Release note: 本地会话现在可从“在新会话中继续”创建独立分支并显示来源;Task 子项布局更紧凑,状态统一显示在右侧。
This commit is contained in:
mesalogo
2026-08-20 17:13:57 +08:00
parent 65c3ab9ab0
commit aa339a73d6
25 changed files with 1392 additions and 67 deletions
+37
View File
@@ -3,6 +3,8 @@ import type { AssistantProject } from './assistant-contracts'
import {
builtInDefaultProjectSeedDescription,
builtInDefaultProjectSeedName,
conversationBranchInputSchema,
conversationSnapshotSchema,
isUntouchedBuiltInDefaultProject,
projectCreateSchema,
projectUpdateSchema
@@ -72,3 +74,38 @@ describe('isUntouchedBuiltInDefaultProject', () => {
expect(projectUpdateSchema.safeParse(input).success).toBe(false)
})
})
describe('conversation branch contracts', () => {
it('accepts bounded branch provenance and rejects renderer extras', () => {
const sourceConversationId =
'00000000-0000-4000-8000-000000000201'
expect(
conversationBranchInputSchema.parse({
sourceConversationId,
title: '方案讨论 · 分支'
})
).toEqual({
sourceConversationId,
title: '方案讨论 · 分支'
})
expect(
conversationBranchInputSchema.safeParse({
sourceConversationId,
title: '方案讨论 · 分支',
copyTasks: true
}).success
).toBe(false)
expect(
conversationSnapshotSchema.safeParse({
id: '00000000-0000-4000-8000-000000000202',
branch: {
sourceConversationId,
sourceTitle: '方案讨论'
},
title: '方案讨论 · 分支',
updatedAt: 1,
messages: []
}).success
).toBe(true)
})
})