@zhushanwen/pi-session-reader 0.13.1 → 0.13.3
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__/{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/__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,26 +1,29 @@
|
|
|
1
|
+
// path-encoding.test.ts —— session-reader C06 簇合并产物(round5-G)。
|
|
2
|
+
// 共同被测业务模块:`core:@zhushanwen/subagent-core/execution/assembly/path-encoding.ts`。mock 相容:全簇零 vi.mock。
|
|
3
|
+
// 顶层正文按来源包 describe 纯前缀分节,it 层名零改动。
|
|
4
|
+
//
|
|
5
|
+
// 来源:
|
|
6
|
+
// - src/__tests__/cross-package-subagent-core.test.ts(2 用例,skip 0)
|
|
7
|
+
// - src/__tests__/rebuild-manifest-dual-write.test.ts(2 用例,skip 0)
|
|
8
|
+
|
|
1
9
|
import * as fs from 'node:fs'
|
|
2
10
|
import * as os from 'node:os'
|
|
3
11
|
import * as path from 'node:path'
|
|
4
12
|
import { afterEach, beforeEach, describe, expect, it } from 'vitest'
|
|
5
|
-
|
|
6
13
|
import { handleSessionRead } from '../tool-handler.js'
|
|
7
14
|
// barrel 符号走包根;ManifestStore / ModelRegistryLike / path-encoding 函数未进 barrel
|
|
8
15
|
// (D3 判定不进),按 subagent-workflow 测试同款包名 .ts 深路径消费,vitest alias 解析。
|
|
9
|
-
import {
|
|
10
|
-
ModelConfigService,
|
|
11
|
-
RecordStore,
|
|
12
|
-
SUBAGENT_RECORD_CUSTOM_TYPE,
|
|
13
|
-
SubagentService,
|
|
14
|
-
type SubagentRecord,
|
|
15
|
-
} from '@zhushanwen/subagent-core'
|
|
16
|
+
import { ModelConfigService, RecordStore, SUBAGENT_RECORD_CUSTOM_TYPE, SubagentService, type SubagentRecord } from '@zhushanwen/subagent-core'
|
|
16
17
|
import { ManifestStore } from '@zhushanwen/subagent-core/execution/persistence/manifest-store.ts'
|
|
17
|
-
import type
|
|
18
|
-
import {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
} from '
|
|
18
|
+
import { type ModelRegistryLike } from '@zhushanwen/subagent-core/execution/assembly/model-resolver.ts'
|
|
19
|
+
import { getSubagentRecordsDir, getSubagentSessionDir } from '@zhushanwen/subagent-core/execution/assembly/path-encoding.ts'
|
|
20
|
+
// barrel 外符号按包名 .ts 深路径消费(vitest alias 重写,同 cross-package 先例)
|
|
21
|
+
import { createRecord } from '@zhushanwen/subagent-core/execution/persistence/execution-record.ts'
|
|
22
|
+
import { listRecordManifests, type RecordManifest } from '../discovery/subagents.js'
|
|
22
23
|
|
|
23
|
-
|
|
24
|
+
// ======== 来源:cross-package-subagent-core.test.ts ========
|
|
25
|
+
describe("cross-package-subagent-core", () => {
|
|
26
|
+
/**
|
|
24
27
|
* W4 跨包集成测试([collect 退役] 改造保留:原「E1 落标出口产出 manifest」驱动侧
|
|
25
28
|
* 随 sync 批机制删除,现行真实派生视图出口 = store 重建通道 rebuildIndexes——manifest
|
|
26
29
|
* 是可丢缓存,boot 全量路径按磁盘子文件锚幂等补缺)。「subagent-core 真实 tmpdir
|
|
@@ -42,26 +45,26 @@ import {
|
|
|
42
45
|
* mkdtemp(tmpdir) 自建自删(红线:零真实数据目录触碰)。
|
|
43
46
|
*/
|
|
44
47
|
|
|
45
|
-
/** 剥身份 env(subagent-core 测试纪律同款):身份 env 会让 service 误判自己是
|
|
48
|
+
/** 剥身份 env(subagent-core 测试纪律同款):身份 env 会让 service 误判自己是
|
|
46
49
|
* 子进程跳过恢复扫描。 */
|
|
47
|
-
const ENV_KEYS_TO_STRIP = [
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
] as const
|
|
50
|
+
const ENV_KEYS_TO_STRIP = [
|
|
51
|
+
'PI_SUBAGENT_ROOT_SESSION_ID',
|
|
52
|
+
'PI_SUBAGENT_SELF_RECORD_ID',
|
|
53
|
+
'PI_SUBAGENT_DEPTH',
|
|
54
|
+
'PI_SUBAGENT_ROOT_CWD',
|
|
55
|
+
'PI_SUBAGENT_FORK_DEPTH',
|
|
56
|
+
] as const
|
|
54
57
|
|
|
55
|
-
const ROOT_SESSION = 'root-cross-pkg'
|
|
58
|
+
const ROOT_SESSION = 'root-cross-pkg'
|
|
56
59
|
|
|
57
|
-
/** 驱动 SubagentService 的最小 pi 形状:appendEntry 真写主 session JSONL(pi 落盘
|
|
60
|
+
/** 驱动 SubagentService 的最小 pi 形状:appendEntry 真写主 session JSONL(pi 落盘
|
|
58
61
|
* 形态同构),其余面 no-op(本链路 E1 全终态直达补发,settled 重扫不注册)。 */
|
|
59
|
-
function makeWritingPi(mainFile: string) {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
62
|
+
function makeWritingPi(mainFile: string) {
|
|
63
|
+
return {
|
|
64
|
+
appendEntry(customType: string, data: unknown): void {
|
|
65
|
+
fs.appendFileSync(
|
|
66
|
+
mainFile,
|
|
67
|
+
`${JSON.stringify({
|
|
65
68
|
type: 'custom',
|
|
66
69
|
id: `seed-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
|
|
67
70
|
parentId: null,
|
|
@@ -346,3 +349,164 @@ describe('跨包集成:subagent-core 重建 manifest → session-reader result
|
|
|
346
349
|
expect(d.items.every((i) => !i.truncated)).toBe(true)
|
|
347
350
|
})
|
|
348
351
|
})
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
// ======== 来源:rebuild-manifest-dual-write.test.ts ========
|
|
355
|
+
describe("rebuild-manifest-dual-write", () => {
|
|
356
|
+
const ROOT_SESSION = 'root-dual-write'
|
|
357
|
+
|
|
358
|
+
/** session-reader identity 派生视图(manifestIdentityData)消费的字段集——重建窗口
|
|
359
|
+
* 前后必须逐字段一致(降级 = 任一字段缺失/漂移)。 */
|
|
360
|
+
function identityView(m: RecordManifest) {
|
|
361
|
+
return {
|
|
362
|
+
id: m.id,
|
|
363
|
+
rootSessionId: m.rootSessionId,
|
|
364
|
+
agentName: m.agentName,
|
|
365
|
+
task: m.task,
|
|
366
|
+
slug: m.slug,
|
|
367
|
+
model: m.model,
|
|
368
|
+
status: m.status,
|
|
369
|
+
sessionFile: m.sessionFile,
|
|
370
|
+
parentRecordId: m.parentRecordId,
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
describe('跨包集成:subagent-core rebuildIndexes 重建 manifest → session-reader 投影不降级', () => {
|
|
375
|
+
let dataDir: string
|
|
376
|
+
let agentDir: string
|
|
377
|
+
let sessionsDir: string
|
|
378
|
+
let recordsDir: string
|
|
379
|
+
let childFile: string
|
|
380
|
+
|
|
381
|
+
beforeEach(() => {
|
|
382
|
+
// 生产拓扑复刻([session-store-layout-inversion 域 9]):agentDir = <dataDir>/agent
|
|
383
|
+
//(session-reader 的 agentDir 语义),subagent-core 写侧经新公式落存储树
|
|
384
|
+
// <dataDir>/ext-sessions/<rootSid>/subagents/——listRecordManifests 双根扫描的新根
|
|
385
|
+
//(dirname(agentDir)/ext-sessions)与写侧落点相遇
|
|
386
|
+
dataDir = fs.mkdtempSync(path.join(os.tmpdir(), 'sr-rebuild-dualwrite-'))
|
|
387
|
+
agentDir = path.join(dataDir, 'agent')
|
|
388
|
+
sessionsDir = getSubagentSessionDir(ROOT_SESSION, dataDir)
|
|
389
|
+
recordsDir = getSubagentRecordsDir(ROOT_SESSION, dataDir)
|
|
390
|
+
fs.mkdirSync(sessionsDir, { recursive: true })
|
|
391
|
+
fs.mkdirSync(recordsDir, { recursive: true })
|
|
392
|
+
|
|
393
|
+
// 子 session 文件:session header + model_change(light 重建的 model 源——
|
|
394
|
+
// identity 前途经 entry best-effort 提取)+ identity entry(session-reader
|
|
395
|
+
// 步骤 2 首行 header 探测 + manifest 主路径的数据锚)
|
|
396
|
+
childFile = path.join(sessionsDir, '20260912T000000_sa-dw.jsonl')
|
|
397
|
+
const ts = new Date(1000).toISOString()
|
|
398
|
+
fs.writeFileSync(
|
|
399
|
+
childFile,
|
|
400
|
+
[
|
|
401
|
+
JSON.stringify({ type: 'session', version: 3, id: 'sess-sa-dw', timestamp: ts, cwd: agentDir }),
|
|
402
|
+
JSON.stringify({ type: 'model_change', id: 'mc-1', parentId: null, timestamp: ts, provider: 'prov', modelId: 'm1' }),
|
|
403
|
+
JSON.stringify({
|
|
404
|
+
type: 'custom',
|
|
405
|
+
id: 'id-1',
|
|
406
|
+
parentId: null,
|
|
407
|
+
timestamp: ts,
|
|
408
|
+
customType: 'subagent-identity',
|
|
409
|
+
data: {
|
|
410
|
+
id: 'sa-dw',
|
|
411
|
+
agent: 'worker',
|
|
412
|
+
mode: 'background',
|
|
413
|
+
task: 'dual write task',
|
|
414
|
+
slug: 'dual',
|
|
415
|
+
startedAt: 1000,
|
|
416
|
+
rootSessionId: ROOT_SESSION,
|
|
417
|
+
parentRecordId: 'sa-parent',
|
|
418
|
+
depth: 1,
|
|
419
|
+
},
|
|
420
|
+
}),
|
|
421
|
+
].join('\n') + '\n',
|
|
422
|
+
'utf-8',
|
|
423
|
+
)
|
|
424
|
+
})
|
|
425
|
+
|
|
426
|
+
afterEach(() => {
|
|
427
|
+
fs.rmSync(dataDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 20 })
|
|
428
|
+
})
|
|
429
|
+
|
|
430
|
+
it('S5③: manifest 删除 → rebuildIndexes 重建 → identity 派生视图字段集前后一致(双写字段在场)', async () => {
|
|
431
|
+
// ── 初始 manifest:subagent-core 终态写侧(markFinalized)真实产出 ──
|
|
432
|
+
const record = createRecord('sa-dw', {
|
|
433
|
+
agent: 'worker',
|
|
434
|
+
model: 'prov/m1',
|
|
435
|
+
mode: 'background',
|
|
436
|
+
task: 'dual write task',
|
|
437
|
+
slug: 'dual',
|
|
438
|
+
startedAt: 1000,
|
|
439
|
+
rootSessionId: ROOT_SESSION,
|
|
440
|
+
parentRecordId: 'sa-parent',
|
|
441
|
+
depth: 1,
|
|
442
|
+
chatMode: false,
|
|
443
|
+
controller: new AbortController(),
|
|
444
|
+
})
|
|
445
|
+
Object.assign(record, {
|
|
446
|
+
sessionFile: childFile,
|
|
447
|
+
status: 'closed' as const,
|
|
448
|
+
closedReason: 'gc' as const,
|
|
449
|
+
endedAt: 2000,
|
|
450
|
+
})
|
|
451
|
+
// [u-subagent dir 晚绑定] RecordStore 构造 = 目录取值函数(非目录字符串)
|
|
452
|
+
const writer = new RecordStore(() => sessionsDir, undefined, undefined, () => recordsDir)
|
|
453
|
+
expect(writer.markFinalized(record, 'gc')).toBe(true)
|
|
454
|
+
writer.dispose()
|
|
455
|
+
|
|
456
|
+
const before = (await listRecordManifests(agentDir)).find((m) => m.id === 'sa-dw')
|
|
457
|
+
expect(before).toBeDefined()
|
|
458
|
+
const viewBefore = identityView(before!)
|
|
459
|
+
|
|
460
|
+
// 双写过渡字段在写侧就位(本包不消费,但前向兼容锚要求其在场)。
|
|
461
|
+
// [U2 两态] executionStatus 是内部权威词汇(running|idle 两态,终态概念删除)
|
|
462
|
+
// → 终态化后恒 'idle';status 旧三态是 session-reader 直读派生视图(永久保留)→ 'closed'。
|
|
463
|
+
const rawBefore = JSON.parse(fs.readFileSync(path.join(recordsDir, 'sa-dw.json'), 'utf-8')) as Record<string, unknown>
|
|
464
|
+
expect(rawBefore.executionStatus).toBe('idle')
|
|
465
|
+
expect(rawBefore.status).toBe('closed')
|
|
466
|
+
|
|
467
|
+
// ── S5 场景:人为删除全部 manifest(缓存可丢)→ 全量重建 ──
|
|
468
|
+
for (const f of fs.readdirSync(recordsDir)) fs.rmSync(path.join(recordsDir, f))
|
|
469
|
+
expect((await listRecordManifests(agentDir)).filter((m) => m.id === 'sa-dw')).toHaveLength(0)
|
|
470
|
+
|
|
471
|
+
const rebuilder = new RecordStore(() => sessionsDir, undefined, undefined, () => recordsDir)
|
|
472
|
+
expect(rebuilder.rebuildIndexes()).toBe(1)
|
|
473
|
+
rebuilder.dispose()
|
|
474
|
+
|
|
475
|
+
// ── session-reader 视角:重建后 identity 派生视图不降级 ──
|
|
476
|
+
const after = (await listRecordManifests(agentDir)).find((m) => m.id === 'sa-dw')
|
|
477
|
+
expect(after).toBeDefined()
|
|
478
|
+
// 字段集保真(身份/富字段逐字段一致);仅 legacy status 按 ③ 后重建语义如实
|
|
479
|
+
// 派生 running(终态权威 = 事件流 record-settled 帧,磁盘重建无 closedReason 载体)。
|
|
480
|
+
expect(identityView(after!)).toEqual({ ...viewBefore, status: 'running' })
|
|
481
|
+
|
|
482
|
+
const rawAfter = JSON.parse(fs.readFileSync(path.join(recordsDir, 'sa-dw.json'), 'utf-8')) as Record<string, unknown>
|
|
483
|
+
expect(rawAfter.status).toBe('running')
|
|
484
|
+
expect(rawAfter.executionStatus).toBe('idle')
|
|
485
|
+
// closedReason 无重建载体(`.state` 退场)→ 如实缺席。
|
|
486
|
+
expect(rawAfter.closedReason).toBeUndefined()
|
|
487
|
+
})
|
|
488
|
+
|
|
489
|
+
it('惰性通道:查询结果(collectRecords)补建的 manifest 同样可被本包读取派生视图', async () => {
|
|
490
|
+
// 不经终态写侧(无 manifest 初始形态):磁盘 record 只有 identity + 无 sidecar
|
|
491
|
+
// → 查询触发惰性补建。
|
|
492
|
+
const store = new RecordStore(() => sessionsDir, undefined, undefined, () => recordsDir)
|
|
493
|
+
expect(store.collectRecords(10).map((r) => r.id)).toContain('sa-dw')
|
|
494
|
+
store.dispose()
|
|
495
|
+
|
|
496
|
+
const manifests = await listRecordManifests(agentDir)
|
|
497
|
+
const found = manifests.find((m) => m.id === 'sa-dw')
|
|
498
|
+
expect(found).toBeDefined()
|
|
499
|
+
expect(found!.status).toBe('running')
|
|
500
|
+
expect(found!.task).toBe('dual write task')
|
|
501
|
+
expect(found!.agentName).toBe('worker')
|
|
502
|
+
expect(found!.sessionFile).toBe(childFile)
|
|
503
|
+
expect(found!.parentRecordId).toBe('sa-parent')
|
|
504
|
+
// 双写过渡字段([U3 重建单规则] 无 sidecar 重建一律 idle:executionStatus 如实
|
|
505
|
+
// 派生两态词汇;legacy status 经桥接判据派生——无 closedReason 遗留位的 idle
|
|
506
|
+
// 派生 "running"(session-reader 视角的活跃成员,§3.2.8 下行映射)。
|
|
507
|
+
const raw = JSON.parse(fs.readFileSync(path.join(recordsDir, 'sa-dw.json'), 'utf-8')) as Record<string, unknown>
|
|
508
|
+
expect(raw.executionStatus).toBe('idle')
|
|
509
|
+
expect(raw.status).toBe('running')
|
|
510
|
+
})
|
|
511
|
+
})
|
|
512
|
+
});
|