@zhushanwen/pi-session-reader 0.13.0 → 0.13.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +5 -5
- package/src/__tests__/entry-anchor.test.ts +704 -324
- package/src/__tests__/family.test.ts +588 -312
- package/src/__tests__/index.test.ts +17 -1
- package/src/__tests__/{cross-package-subagent-core.test.ts → path-encoding.test.ts} +194 -30
- package/src/__tests__/roots-merged.test.ts +4919 -0
- package/src/__tests__/roots.test.ts +1986 -432
- package/src/__tests__/tool-handler.test.ts +872 -3216
- package/src/__tests__/tree.test.ts +738 -101
- package/src/tui/tool-render.ts +3 -28
- package/src/__tests__/archive-ledger.test.ts +0 -447
- package/src/__tests__/execution-tree.test.ts +0 -898
- package/src/__tests__/family-zcode.test.ts +0 -265
- package/src/__tests__/find.test.ts +0 -802
- package/src/__tests__/rebuild-manifest-dual-write.test.ts +0 -188
- package/src/__tests__/render.test.ts +0 -481
- package/src/__tests__/result.test.ts +0 -347
- package/src/__tests__/subagents.test.ts +0 -668
- package/src/__tests__/summary.test.ts +0 -223
- package/src/__tests__/turns.test.ts +0 -144
- package/src/__tests__/zcode-manifest.test.ts +0 -372
- package/src/__tests__/zcode-routing.test.ts +0 -772
|
@@ -1,223 +0,0 @@
|
|
|
1
|
-
import type { Theme } from '@earendil-works/pi-coding-agent'
|
|
2
|
-
import { describe, expect, it } from 'vitest'
|
|
3
|
-
|
|
4
|
-
import type { SessionReadParams, ToolResult } from '../tool-handler.js'
|
|
5
|
-
import { buildSessionReadSummary, withResultSummary } from '../summary.js'
|
|
6
|
-
import { renderSessionReadResult } from '../tui/tool-render.js'
|
|
7
|
-
|
|
8
|
-
// ---- fixtures ----
|
|
9
|
-
|
|
10
|
-
function params(overrides: Partial<SessionReadParams> & { action: SessionReadParams['action'] }): SessionReadParams {
|
|
11
|
-
return overrides as SessionReadParams
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
function toolResult(details: unknown, text = 'body text'): ToolResult {
|
|
15
|
-
return { content: [{ type: 'text', text }], details }
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
// ---- buildSessionReadSummary ----
|
|
19
|
-
|
|
20
|
-
describe('buildSessionReadSummary', () => {
|
|
21
|
-
it('find → Found N sessions matching "<query>"(截断带后缀)', () => {
|
|
22
|
-
const r = toolResult({ matches: [{}, {}, {}], truncated: false })
|
|
23
|
-
expect(buildSessionReadSummary(params({ action: 'find', query: 'login' }), r)).toBe(
|
|
24
|
-
'Found 3 sessions matching "login"',
|
|
25
|
-
)
|
|
26
|
-
const truncated = toolResult({ matches: [{}], truncated: true })
|
|
27
|
-
expect(buildSessionReadSummary(params({ action: 'find', query: 'login' }), truncated)).toBe(
|
|
28
|
-
'Found 1 sessions matching "login" (truncated)',
|
|
29
|
-
)
|
|
30
|
-
})
|
|
31
|
-
|
|
32
|
-
it('find 零匹配 → No sessions matching', () => {
|
|
33
|
-
const r = toolResult({ matches: [], truncated: false })
|
|
34
|
-
expect(buildSessionReadSummary(params({ action: 'find', query: 'gone' }), r)).toBe(
|
|
35
|
-
'No sessions matching "gone"',
|
|
36
|
-
)
|
|
37
|
-
})
|
|
38
|
-
|
|
39
|
-
it('ambiguous(任意 action 的多匹配消歧)→ N candidates', () => {
|
|
40
|
-
const r = toolResult({ ambiguous: true, candidates: [{}, {}] })
|
|
41
|
-
expect(buildSessionReadSummary(params({ action: 'outline', session: 'e6c9' }), r)).toBe(
|
|
42
|
-
'Ambiguous session "e6c9": 2 candidates',
|
|
43
|
-
)
|
|
44
|
-
})
|
|
45
|
-
|
|
46
|
-
it('family flat → N relations(parents/forks/subagents/workflows 合计)', () => {
|
|
47
|
-
const family = {
|
|
48
|
-
root: { sessionId: 's1' },
|
|
49
|
-
parents: [{}],
|
|
50
|
-
forks: [{}, {}],
|
|
51
|
-
subagents: [{}],
|
|
52
|
-
workflows: [],
|
|
53
|
-
}
|
|
54
|
-
const r = toolResult(family)
|
|
55
|
-
expect(buildSessionReadSummary(params({ action: 'family', session: 's1' }), r)).toBe(
|
|
56
|
-
'Family of session s1: 4 relations',
|
|
57
|
-
)
|
|
58
|
-
})
|
|
59
|
-
|
|
60
|
-
it('family recursive(details.tree)→ Execution tree', () => {
|
|
61
|
-
const r = toolResult({ tree: { root: {} } })
|
|
62
|
-
expect(buildSessionReadSummary(params({ action: 'family', session: 's1', recursive: true }), r)).toBe(
|
|
63
|
-
'Execution tree of session s1',
|
|
64
|
-
)
|
|
65
|
-
})
|
|
66
|
-
|
|
67
|
-
it('outline → N turns(stats.totalTurns)', () => {
|
|
68
|
-
const r = toolResult({ turns: [], lines: [], stats: { totalTurns: 32, totalEntries: 90, totalBytes: 1, parsedBytes: 1, skippedLines: 0 }, tokenEstimate: 1 })
|
|
69
|
-
expect(buildSessionReadSummary(params({ action: 'outline', session: 's1' }), r)).toBe(
|
|
70
|
-
'Outline of session s1 · 32 turns',
|
|
71
|
-
)
|
|
72
|
-
})
|
|
73
|
-
|
|
74
|
-
it('expand → T 序号补零 + N entries', () => {
|
|
75
|
-
const r = toolResult({ turn: { index: 13 }, entries: [{}, {}, {}, {}] })
|
|
76
|
-
expect(buildSessionReadSummary(params({ action: 'expand', session: 's1', turn: 'T013' }), r)).toBe(
|
|
77
|
-
'Expanded turn T013 · 4 entries',
|
|
78
|
-
)
|
|
79
|
-
})
|
|
80
|
-
|
|
81
|
-
it('detail → turns 范围标签 + N entries', () => {
|
|
82
|
-
const r = toolResult({ turns: 'T013-T015', entries: [{}, {}, {}] })
|
|
83
|
-
expect(buildSessionReadSummary(params({ action: 'detail', session: 's1', turns: 'T013-T015' }), r)).toBe(
|
|
84
|
-
'Read turns T013-T015 · 3 entries',
|
|
85
|
-
)
|
|
86
|
-
})
|
|
87
|
-
|
|
88
|
-
it('search → Found N hits / No hits', () => {
|
|
89
|
-
const r = toolResult({ hits: [{}, {}], truncated: false })
|
|
90
|
-
expect(buildSessionReadSummary(params({ action: 'search', session: 's1', pattern: 'fix' }), r)).toBe(
|
|
91
|
-
'Found 2 hits for "fix"',
|
|
92
|
-
)
|
|
93
|
-
const empty = toolResult({ hits: [], truncated: false })
|
|
94
|
-
expect(buildSessionReadSummary(params({ action: 'search', session: 's1', pattern: 'fix' }), empty)).toBe(
|
|
95
|
-
'No hits for "fix"',
|
|
96
|
-
)
|
|
97
|
-
})
|
|
98
|
-
|
|
99
|
-
it('export → format + sizeBytes', () => {
|
|
100
|
-
const r = toolResult({ path: '/tmp/x.md', sizeBytes: 2048 })
|
|
101
|
-
expect(buildSessionReadSummary(params({ action: 'export', session: 's1' }), r)).toBe(
|
|
102
|
-
'Exported outline view · 2048 bytes',
|
|
103
|
-
)
|
|
104
|
-
expect(buildSessionReadSummary(params({ action: 'export', session: 's1', format: 'full' }), r)).toBe(
|
|
105
|
-
'Exported full view · 2048 bytes',
|
|
106
|
-
)
|
|
107
|
-
})
|
|
108
|
-
|
|
109
|
-
it('extract → count / 零匹配 / 截断 showing', () => {
|
|
110
|
-
const r = toolResult({ what: 'user-messages', count: 26, shown: 26, truncated: false, items: [] })
|
|
111
|
-
expect(buildSessionReadSummary(params({ action: 'extract', session: 's1', what: 'user-messages' }), r)).toBe(
|
|
112
|
-
'Extracted 26 user-messages',
|
|
113
|
-
)
|
|
114
|
-
const empty = toolResult({ what: 'commands', count: 0, shown: 0, truncated: false, items: [] })
|
|
115
|
-
expect(buildSessionReadSummary(params({ action: 'extract', session: 's1', what: 'commands' }), empty)).toBe(
|
|
116
|
-
'No commands found',
|
|
117
|
-
)
|
|
118
|
-
const clipped = toolResult({ what: 'files', count: 500, shown: 120, truncated: true, items: [] })
|
|
119
|
-
expect(buildSessionReadSummary(params({ action: 'extract', session: 's1', what: 'files' }), clipped)).toBe(
|
|
120
|
-
'Extracted 500 files (showing 120)',
|
|
121
|
-
)
|
|
122
|
-
})
|
|
123
|
-
|
|
124
|
-
it('workflow → runs 计数 / 空 / skipped 后缀', () => {
|
|
125
|
-
expect(buildSessionReadSummary(params({ action: 'workflow', session: 's1' }), toolResult({ runs: [{}, {}], runIds: ['a', 'b'] }))).toBe(
|
|
126
|
-
'Found 2 workflow runs',
|
|
127
|
-
)
|
|
128
|
-
expect(buildSessionReadSummary(params({ action: 'workflow', session: 's1' }), toolResult({ runs: [], runIds: [] }))).toBe(
|
|
129
|
-
'No workflow runs',
|
|
130
|
-
)
|
|
131
|
-
expect(
|
|
132
|
-
buildSessionReadSummary(
|
|
133
|
-
params({ action: 'workflow', session: 's1' }),
|
|
134
|
-
toolResult({ runs: [{}], runIds: ['a'], skippedRuns: [{ runId: 'b', stateFile: 'x', reason: 'snapshot-unreadable' }] }),
|
|
135
|
-
),
|
|
136
|
-
).toBe('Found 1 workflow runs · 1 skipped')
|
|
137
|
-
})
|
|
138
|
-
|
|
139
|
-
it('result 单条 → session + totalChars(截断后缀)', () => {
|
|
140
|
-
const r = toolResult({ session: 'sa-abc', sessionId: 'uuid', sessionFile: '/x', totalChars: 8000, truncated: true, text: '...' })
|
|
141
|
-
expect(buildSessionReadSummary(params({ action: 'result', session: 'sa-abc' }), r)).toBe(
|
|
142
|
-
'Read final result of sa-abc · 8000 chars (truncated)',
|
|
143
|
-
)
|
|
144
|
-
})
|
|
145
|
-
|
|
146
|
-
it('result 批量 → count + items 形态识别', () => {
|
|
147
|
-
const r = toolResult({ count: 3, items: [{}, {}, {}] })
|
|
148
|
-
expect(buildSessionReadSummary(params({ action: 'result', session: 'a,b,c' }), r)).toBe(
|
|
149
|
-
'Read 3 final results',
|
|
150
|
-
)
|
|
151
|
-
})
|
|
152
|
-
|
|
153
|
-
it('doctor → roots 计数', () => {
|
|
154
|
-
const r = toolResult({ environment: {}, roots: [{ kind: 'default' }, { kind: 'legacy' }] })
|
|
155
|
-
expect(buildSessionReadSummary(params({ action: 'doctor' }), r)).toBe(
|
|
156
|
-
'Doctor: 2 roots checked',
|
|
157
|
-
)
|
|
158
|
-
})
|
|
159
|
-
|
|
160
|
-
it('details 形态漂移(空对象)→ 各 action 兜底不抛错', () => {
|
|
161
|
-
const empty = toolResult({})
|
|
162
|
-
expect(buildSessionReadSummary(params({ action: 'outline', session: 's1' }), empty)).toBe(
|
|
163
|
-
'Outline of session s1',
|
|
164
|
-
)
|
|
165
|
-
expect(buildSessionReadSummary(params({ action: 'workflow', session: 's1' }), empty)).toBe(
|
|
166
|
-
'No workflow runs',
|
|
167
|
-
)
|
|
168
|
-
expect(buildSessionReadSummary(params({ action: 'doctor' }), empty)).toBe('Doctor check completed')
|
|
169
|
-
expect(buildSessionReadSummary(params({ action: 'extract', what: 'files' }), empty)).toBe('No files found')
|
|
170
|
-
})
|
|
171
|
-
})
|
|
172
|
-
|
|
173
|
-
// ---- withResultSummary ----
|
|
174
|
-
|
|
175
|
-
describe('withResultSummary', () => {
|
|
176
|
-
it('details 追加 summary,现有字段保留', () => {
|
|
177
|
-
const r = toolResult({ hits: [{}], truncated: false })
|
|
178
|
-
const out = withResultSummary(params({ action: 'search', pattern: 'fix' }), r)
|
|
179
|
-
expect(out.details).toEqual({ hits: [{}], truncated: false, summary: 'Found 1 hits for "fix"' })
|
|
180
|
-
})
|
|
181
|
-
})
|
|
182
|
-
|
|
183
|
-
// ---- renderSessionReadResult(TUI renderResult)----
|
|
184
|
-
|
|
185
|
-
const mockTheme = {
|
|
186
|
-
fg: (_color: string, text: string) => text,
|
|
187
|
-
bg: (_color: string, text: string) => text,
|
|
188
|
-
bold: (text: string) => text,
|
|
189
|
-
italic: (text: string) => text,
|
|
190
|
-
underline: (text: string) => text,
|
|
191
|
-
inverse: (text: string) => text,
|
|
192
|
-
strikethrough: (text: string) => text,
|
|
193
|
-
getFgAnsi: (_color: string) => '',
|
|
194
|
-
getBgAnsi: (_color: string) => '',
|
|
195
|
-
getColorMode: () => 'truecolor' as const,
|
|
196
|
-
getThinkingBorderColor: () => (text: string) => text,
|
|
197
|
-
getBashModeBorderColor: () => (text: string) => text,
|
|
198
|
-
} as unknown as Theme
|
|
199
|
-
|
|
200
|
-
function renderedText(result: unknown, expanded: boolean): string {
|
|
201
|
-
return renderSessionReadResult(result, { expanded }, mockTheme)
|
|
202
|
-
.render(400)
|
|
203
|
-
.map((line) => line.trimEnd())
|
|
204
|
-
.join('\n')
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
describe('renderSessionReadResult', () => {
|
|
208
|
-
it('折叠态 → details.summary 一行', () => {
|
|
209
|
-
const r = toolResult({ matches: [], truncated: false }, 'multi\nline\nbody')
|
|
210
|
-
const withSummary = withResultSummary(params({ action: 'find', query: 'login' }), r)
|
|
211
|
-
expect(renderedText(withSummary, false)).toBe('No sessions matching "login"')
|
|
212
|
-
})
|
|
213
|
-
|
|
214
|
-
it('展开态 → content 纯文本(多行完整)', () => {
|
|
215
|
-
const r = toolResult({ matches: [] }, 'line1\nline2')
|
|
216
|
-
expect(renderedText(r, true)).toBe('line1\nline2')
|
|
217
|
-
})
|
|
218
|
-
|
|
219
|
-
it('无 summary(旧 entry)→ 折叠态回退 content 首行', () => {
|
|
220
|
-
const r = toolResult({}, 'Header line\ndetail body')
|
|
221
|
-
expect(renderedText(r, false)).toBe('Header line')
|
|
222
|
-
})
|
|
223
|
-
})
|
|
@@ -1,144 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest'
|
|
2
|
-
import { segmentTurns } from '../core/turns.js'
|
|
3
|
-
import type { Entry } from '@zhushanwen/session-core'
|
|
4
|
-
import { buildTreeView } from '../core/tree.js'
|
|
5
|
-
|
|
6
|
-
// ---- Entry 构造助手(turns.ts 只消费 type/id/parentId/message.role) ----
|
|
7
|
-
|
|
8
|
-
function msg(
|
|
9
|
-
id: string,
|
|
10
|
-
parentId: string | null,
|
|
11
|
-
role: 'user' | 'assistant' | 'toolResult',
|
|
12
|
-
): Entry {
|
|
13
|
-
return { type: 'message', id, parentId, message: { role, content: '' } }
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
function entry(id: string, parentId: string | null, type: string): Entry {
|
|
17
|
-
return { type, id, parentId }
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
describe('segmentTurns', () => {
|
|
21
|
-
it('1. user 开 turn,后续 assistant/toolResult 并入同一 turn', () => {
|
|
22
|
-
const entries = [msg('U', null, 'user'), msg('A', 'U', 'assistant'), msg('T', 'A', 'toolResult')]
|
|
23
|
-
const turns = segmentTurns(entries, new Set(['U', 'A', 'T']))
|
|
24
|
-
|
|
25
|
-
expect(turns).toHaveLength(1)
|
|
26
|
-
expect(turns[0].index).toBe(0)
|
|
27
|
-
expect(turns[0].isCompaction).toBe(false)
|
|
28
|
-
expect(turns[0].userEntry?.id).toBe('U')
|
|
29
|
-
expect(turns[0].entries.map((e) => e.id)).toEqual(['U', 'A', 'T'])
|
|
30
|
-
})
|
|
31
|
-
|
|
32
|
-
it('2. compaction 开新 turn(isCompaction=true,无 userEntry),后续 user 又开一个 turn', () => {
|
|
33
|
-
const entries = [
|
|
34
|
-
msg('U', null, 'user'),
|
|
35
|
-
msg('A', 'U', 'assistant'),
|
|
36
|
-
entry('C', 'A', 'compaction'),
|
|
37
|
-
msg('U2', 'C', 'user'),
|
|
38
|
-
]
|
|
39
|
-
const turns = segmentTurns(entries, new Set(['U', 'A', 'C', 'U2']))
|
|
40
|
-
|
|
41
|
-
expect(turns).toHaveLength(3)
|
|
42
|
-
// turn 0:user + assistant
|
|
43
|
-
expect(turns[0].userEntry?.id).toBe('U')
|
|
44
|
-
expect(turns[0].isCompaction).toBe(false)
|
|
45
|
-
expect(turns[0].entries.map((e) => e.id)).toEqual(['U', 'A'])
|
|
46
|
-
// turn 1:compaction 独立成 turn
|
|
47
|
-
expect(turns[1].isCompaction).toBe(true)
|
|
48
|
-
expect(turns[1].userEntry).toBeUndefined()
|
|
49
|
-
expect(turns[1].entries.map((e) => e.id)).toEqual(['C'])
|
|
50
|
-
// turn 2:新 user
|
|
51
|
-
expect(turns[2].userEntry?.id).toBe('U2')
|
|
52
|
-
expect(turns[2].isCompaction).toBe(false)
|
|
53
|
-
})
|
|
54
|
-
|
|
55
|
-
it('3. model_change / thinking_level_change / custom 并入当前 turn', () => {
|
|
56
|
-
const entries = [
|
|
57
|
-
msg('U', null, 'user'),
|
|
58
|
-
entry('M', 'U', 'model_change'),
|
|
59
|
-
entry('T', 'M', 'thinking_level_change'),
|
|
60
|
-
entry('X', 'T', 'custom'),
|
|
61
|
-
]
|
|
62
|
-
const turns = segmentTurns(entries, new Set(['U', 'M', 'T', 'X']))
|
|
63
|
-
|
|
64
|
-
expect(turns).toHaveLength(1)
|
|
65
|
-
expect(turns[0].userEntry?.id).toBe('U')
|
|
66
|
-
expect(turns[0].entries.map((e) => e.id)).toEqual(['U', 'M', 'T', 'X'])
|
|
67
|
-
})
|
|
68
|
-
|
|
69
|
-
it('4. branch entry(不在 leafSet)不计入', () => {
|
|
70
|
-
const entries = [
|
|
71
|
-
msg('U', null, 'user'),
|
|
72
|
-
msg('A', 'U', 'assistant'),
|
|
73
|
-
msg('B1', 'U', 'assistant'), // 旁支:不在 leafSet
|
|
74
|
-
msg('B2', 'B1', 'toolResult'), // 旁支子节点
|
|
75
|
-
]
|
|
76
|
-
const turns = segmentTurns(entries, new Set(['U', 'A']))
|
|
77
|
-
|
|
78
|
-
expect(turns).toHaveLength(1)
|
|
79
|
-
expect(turns[0].entries.map((e) => e.id)).toEqual(['U', 'A'])
|
|
80
|
-
})
|
|
81
|
-
|
|
82
|
-
it('5. 孤儿 assistant(首个 entry 是 assistant 无前置 user)→ 并入 turn 0(preface)', () => {
|
|
83
|
-
const entries = [msg('O', null, 'assistant'), msg('U', 'O', 'user')]
|
|
84
|
-
const turns = segmentTurns(entries, new Set(['O', 'U']))
|
|
85
|
-
|
|
86
|
-
expect(turns).toHaveLength(2)
|
|
87
|
-
// turn 0:preface(孤儿 assistant),无 userEntry,非 compaction
|
|
88
|
-
expect(turns[0].isCompaction).toBe(false)
|
|
89
|
-
expect(turns[0].userEntry).toBeUndefined()
|
|
90
|
-
expect(turns[0].entries.map((e) => e.id)).toEqual(['O'])
|
|
91
|
-
// turn 1:首个 user
|
|
92
|
-
expect(turns[1].userEntry?.id).toBe('U')
|
|
93
|
-
expect(turns[1].index).toBe(1)
|
|
94
|
-
})
|
|
95
|
-
|
|
96
|
-
it('6. session header 被忽略(即使在 leafSet 中也不计 turn)', () => {
|
|
97
|
-
const entries = [
|
|
98
|
-
entry('S', null, 'session'),
|
|
99
|
-
msg('U', 'S', 'user'),
|
|
100
|
-
msg('A', 'U', 'assistant'),
|
|
101
|
-
]
|
|
102
|
-
// S 也在 leafSet,验证规则 1 优先于规则 5
|
|
103
|
-
const turns = segmentTurns(entries, new Set(['S', 'U', 'A']))
|
|
104
|
-
|
|
105
|
-
expect(turns).toHaveLength(1)
|
|
106
|
-
expect(turns[0].entries.map((e) => e.id)).toEqual(['U', 'A'])
|
|
107
|
-
})
|
|
108
|
-
|
|
109
|
-
it('7. 空 entries → []', () => {
|
|
110
|
-
expect(segmentTurns([], new Set())).toEqual([])
|
|
111
|
-
})
|
|
112
|
-
|
|
113
|
-
it('startTime 取 turn 首条 entry 的 timestamp', () => {
|
|
114
|
-
const entries = [
|
|
115
|
-
{ ...msg('U', null, 'user'), timestamp: '2026-05-28T03:17:12.844Z' },
|
|
116
|
-
msg('A', 'U', 'assistant'),
|
|
117
|
-
]
|
|
118
|
-
const turns = segmentTurns(entries, new Set(['U', 'A']))
|
|
119
|
-
expect(turns[0].startTime).toBe('2026-05-28T03:17:12.844Z')
|
|
120
|
-
})
|
|
121
|
-
|
|
122
|
-
it('leaf 视图分段结构形态:preface(首 user 前的 model_change)+ user turns + compaction turn', () => {
|
|
123
|
-
// 合成分段结构形态(1 preface + 1 compaction + 2 user turn):
|
|
124
|
-
// 规则 2(compaction 独立成 turn)+ 规则 4(首 user 前的 model_change/thinking_level_change
|
|
125
|
-
// 成 preface turn,无 userEntry)
|
|
126
|
-
const entries = [
|
|
127
|
-
entry('MC', null, 'model_change'),
|
|
128
|
-
msg('U1', 'MC', 'user'),
|
|
129
|
-
msg('A1', 'U1', 'assistant'),
|
|
130
|
-
entry('C1', 'A1', 'compaction'),
|
|
131
|
-
msg('U2', 'C1', 'user'),
|
|
132
|
-
msg('A2', 'U2', 'assistant'),
|
|
133
|
-
]
|
|
134
|
-
const tree = buildTreeView(entries)
|
|
135
|
-
const turns = segmentTurns(entries, new Set(tree.leafPath))
|
|
136
|
-
|
|
137
|
-
expect(turns.length).toBe(4)
|
|
138
|
-
// 首 turn 是 preface(model_change),无 userEntry、非 compaction
|
|
139
|
-
expect(turns[0].userEntry).toBeUndefined()
|
|
140
|
-
expect(turns[0].isCompaction).toBe(false)
|
|
141
|
-
expect(turns.filter((t) => t.isCompaction).length).toBe(1)
|
|
142
|
-
expect(turns.filter((t) => t.userEntry !== undefined).length).toBe(2)
|
|
143
|
-
})
|
|
144
|
-
})
|