feat(sidechat): 侧边对话——每对话一 Tab(Codex 对齐)+ 完整上下文继承 + 保存为新会话 (#286)

三轮实测驱动迭代:种子信封透传(surfaceOp 校验)、每线程一 Tab 交互重做完、四连修(fork 丢 this / 自绘 history+save 图标 / collectOwnEvents 大页回源修重开丢工具行 / descriptor 入种子修目录 corrupt 行 + 前缀过滤保拓扑零噪音)。739 单测 + 挂载冒烟 12/12 全绿。
This commit is contained in:
Atlas Zeng
2026-08-21 15:41:14 +08:00
committed by GitHub
parent 50a888845f
commit 7f4f84c4cf
27 changed files with 3501 additions and 13 deletions
+23
View File
@@ -75,6 +75,29 @@ describe('subagent detection over the sessions list feed', () => {
expect(countSubagentDescendants(cyclic, 'a')).toEqual({ count: 2, runningCount: 0 })
})
it('excludes Side Chat threads (subagent origin, "Side: " label) from counts', () => {
// Side threads ride the subagent origin for list hiding + the RPC fence
// but they are tab-strip conversations, never topology: they must not
// fire the auto-open trigger nor inflate the Subagent page totals.
const byId: SidebarSessionList['byId'] = {
p1: { id: 'p1', displayTitle: 'P1' },
c1: { id: 'c1', displayTitle: 'C1', origin: 'subagent', parentId: 'p1' },
s1: { id: 's1', displayTitle: 'Side: refactor plan', origin: 'subagent', parentId: 'p1', running: true },
}
expect(directSubagentCount(byId, 'p1')).toBe(1)
expect(countSubagentDescendants(byId, 'p1')).toEqual({ count: 1, runningCount: 0 })
// A side thread appearing under an empty session never trips 0 → N.
const before: SidebarSessionList = { current: 'p2', byId: { p2: { id: 'p2', displayTitle: 'P2' } } }
const after: SidebarSessionList = {
current: 'p2',
byId: {
p2: { id: 'p2', displayTitle: 'P2' },
s2: { id: 's2', displayTitle: 'Side: New thread', origin: 'subagent', parentId: 'p2' },
},
}
expect(detectNewDirectSubagent(before, after, 'p2')).toBe(false)
})
it('resolves the main-agent root of the current session tree', () => {
const byId: SidebarSessionList['byId'] = {
main: { id: 'main', displayTitle: 'Main' },