94 lines
3.2 KiB
TypeScript
94 lines
3.2 KiB
TypeScript
import { appendFileSync, mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs'
|
|
import * as os from 'node:os'
|
|
import * as path from 'node:path'
|
|
import { afterEach, describe, expect, it } from 'vitest'
|
|
import { watchForCodexSession } from './codexSessionWatcher'
|
|
|
|
const tempDirs: string[] = []
|
|
|
|
afterEach(() => {
|
|
while (tempDirs.length > 0) {
|
|
const dir = tempDirs.pop()
|
|
if (dir)
|
|
rmSync(dir, { recursive: true, force: true })
|
|
}
|
|
})
|
|
|
|
function tempDir(prefix: string): string {
|
|
const dir = mkdtempSync(path.join(os.tmpdir(), prefix))
|
|
tempDirs.push(dir)
|
|
return dir
|
|
}
|
|
|
|
const MAIN_ID = '01a0e3bb-61e3-7c31-adf3-2d2a746a3282'
|
|
const SUB_ID = '01a0e3bc-2a65-7490-84a6-e963505d9bff'
|
|
const OTHER_ID = '01a0e3be-4665-7991-b89e-f6881609c36b'
|
|
|
|
function metaLine(id: string, cwd: string, source: unknown): string {
|
|
return `${JSON.stringify({ timestamp: '2026-09-27T16:38:24.015Z', type: 'session_meta', payload: { id, cwd, source } })}\n`
|
|
}
|
|
|
|
function rolloutPath(baseDir: string, id: string): string {
|
|
const day = path.join(baseDir, '2026', '09', '28')
|
|
mkdirSync(day, { recursive: true })
|
|
return path.join(day, `rollout-2026-09-28T00-38-23-${id}.jsonl`)
|
|
}
|
|
|
|
const tick = (ms = 50): Promise<void> => new Promise(r => setTimeout(r, ms))
|
|
|
|
describe('watchForCodexSession', () => {
|
|
it('只认本目录的主会话,跳过别的目录和子助手会话', async () => {
|
|
const baseDir = tempDir('codex-sessions-')
|
|
const cwd = tempDir('worktree-')
|
|
const ac = new AbortController()
|
|
const found = watchForCodexSession({ baseDir, cwd, signal: ac.signal })
|
|
await tick()
|
|
|
|
writeFileSync(rolloutPath(baseDir, OTHER_ID), metaLine(OTHER_ID, '/somewhere/else', 'cli'))
|
|
writeFileSync(rolloutPath(baseDir, SUB_ID), metaLine(SUB_ID, cwd, { subagent: { thread_spawn: { parent_thread_id: MAIN_ID } } }))
|
|
await tick()
|
|
writeFileSync(rolloutPath(baseDir, MAIN_ID), metaLine(MAIN_ID, cwd, 'cli'))
|
|
|
|
await expect(found).resolves.toBe(MAIN_ID)
|
|
})
|
|
|
|
it('没有时间上限,文件晚出现也能认到', async () => {
|
|
const baseDir = tempDir('codex-sessions-')
|
|
const cwd = tempDir('worktree-')
|
|
const ac = new AbortController()
|
|
const found = watchForCodexSession({ baseDir, cwd, signal: ac.signal })
|
|
await tick(300)
|
|
|
|
writeFileSync(rolloutPath(baseDir, MAIN_ID), metaLine(MAIN_ID, cwd, 'cli'))
|
|
|
|
await expect(found).resolves.toBe(MAIN_ID)
|
|
})
|
|
|
|
it('第一行还没写完时等它写完再认', async () => {
|
|
const baseDir = tempDir('codex-sessions-')
|
|
const cwd = tempDir('worktree-')
|
|
const ac = new AbortController()
|
|
const found = watchForCodexSession({ baseDir, cwd, signal: ac.signal })
|
|
await tick()
|
|
|
|
const file = rolloutPath(baseDir, MAIN_ID)
|
|
const line = metaLine(MAIN_ID, cwd, 'cli')
|
|
writeFileSync(file, line.slice(0, 20))
|
|
await tick()
|
|
appendFileSync(file, line.slice(20))
|
|
|
|
await expect(found).resolves.toBe(MAIN_ID)
|
|
})
|
|
|
|
it('中止后返回 null', async () => {
|
|
const baseDir = tempDir('codex-sessions-')
|
|
const cwd = tempDir('worktree-')
|
|
const ac = new AbortController()
|
|
const found = watchForCodexSession({ baseDir, cwd, signal: ac.signal })
|
|
await tick()
|
|
ac.abort()
|
|
|
|
await expect(found).resolves.toBeNull()
|
|
})
|
|
})
|