UNPKG

rigjs

Version:

A multi-repos dev tool based on yarn and git.Rigjs is intended to be the simplest way to develop,share and deliver codes between different developers or different projects.

347 lines (318 loc) 17.1 kB
import fs from 'fs'; import os from 'os'; import path from 'path'; import { findLatestCodexTranscript, intakeCodexTranscript, readCodexTranscriptPage } from './codex-transcript'; import { writeCodexLatestPointer } from './codex-pointer'; describe('Codex rollout intake', () => { let dir: string; let transcript: string; beforeEach(() => { dir = fs.mkdtempSync(path.join(os.tmpdir(), 'rig-codex-transcript-')); transcript = path.join(dir, 'sessions', '2026', '07', '20', 'rollout-root.jsonl'); fs.mkdirSync(path.dirname(transcript), { recursive: true }); writeRows(transcript, fixtureRows()); fs.appendFileSync(transcript, '{malformed\n'); }); afterEach(() => fs.rmSync(dir, { recursive: true, force: true })); test('recovers dialogue, paired tools, file edits, and unfinished state newest-first', () => { const intake = intakeCodexTranscript(transcript, { limit: 20, maxChars: 1000 }); expect(intake.source).toBe('codex-rollout'); expect(intake.session).toEqual(expect.objectContaining({ threadId: 'root-id', cwd: '/repo/项目' })); expect(intake.stats.malformedLines).toBe(1); expect(intake.stats.omitted.reasoning).toBeGreaterThan(0); expect(intake.stats.omitted.telemetry).toBeGreaterThan(0); expect(intake.compaction).toEqual(expect.objectContaining({ readableCheckpoint: false })); expect(intake.currentTurn).toEqual(expect.objectContaining({ turnId: 'turn-1', completed: true, hasFinalAnswer: true })); expect(intake.workspaceEvidence.editedFiles).toEqual([ expect.objectContaining({ path: '/repo/项目/file.ts', changeType: 'update' }), ]); const entries = intake.page.entries; expect(entries[0].line).toBeGreaterThan(entries[entries.length - 1].line); expect(entries.filter((entry: any) => entry.type === 'message' && entry.role === 'user')).toHaveLength(1); expect(entries).toEqual(expect.arrayContaining([ expect.objectContaining({ type: 'tool-exchange', callId: 'call-1', tool: 'functions.exec', arguments: { cmd: 'git status' }, output: { ok: true }, }), expect.objectContaining({ type: 'message', role: 'assistant', phase: 'final_answer' }), ])); const encoded = JSON.stringify(intake); expect(encoded).not.toContain('PRIVATE-REASONING'); expect(encoded).not.toContain('ENCRYPTED-BLOB'); expect(encoded).not.toContain('DEVELOPER-SECRET'); expect(encoded).not.toContain('WORLD-SECRET'); expect(encoded).not.toContain('RATE-SECRET'); }); test('uses an exclusive raw-line cursor with stable newest-first pages', () => { const first = intakeCodexTranscript(transcript, { limit: 2 }); expect(first.page.entries).toHaveLength(2); expect(first.page.hasOlder).toBe(true); const second = intakeCodexTranscript(transcript, { limit: 2, before: first.page.nextBeforeLine }); expect(second.page.entries.every((entry: any) => entry.line < first.page.nextBeforeLine)).toBe(true); }); test('diagnostic read also omits private/runtime-only rows', () => { const page = readCodexTranscriptPage(transcript, { limit: 100 }); const encoded = JSON.stringify(page); expect(encoded).not.toContain('PRIVATE-REASONING'); expect(encoded).not.toContain('DEVELOPER-SECRET'); expect(encoded).not.toContain('WORLD-SECRET'); expect(encoded).toContain('tool-call'); }); test('latest selection prefers the saved pointer and excludes child rollouts', () => { const sessions = path.join(dir, 'sessions'); const child = path.join(sessions, '2026', '07', '20', 'rollout-child-id.jsonl'); writeRows(child, [{ timestamp: '2026-07-20T02:00:00Z', type: 'session_meta', payload: { id: 'child-id', session_id: 'root-id', cwd: '/repo/项目', parent_thread_id: 'root-id', agent_path: '/root/review' }, }]); const future = new Date(Date.now() + 10000); fs.utimesSync(child, future, future); const childBySource = path.join(sessions, '2026', '07', '20', 'rollout-child-source.jsonl'); writeRows(childBySource, [{ timestamp: '2026-07-20T02:01:00Z', type: 'session_meta', payload: { id: 'child-source', cwd: '/repo/项目', parent_thread_id: 'root-id', thread_source: 'subagent' }, }]); const fartherFuture = new Date(Date.now() + 20000); fs.utimesSync(childBySource, fartherFuture, fartherFuture); const pointer = path.join(dir, 'handoff', 'codex-latest.json'); writeCodexLatestPointer(pointer, { schemaVersion: 1, sessionId: 'root-id', transcriptPath: transcript, cwd: '/repo/current', updatedAt: '2026-07-20T02:00:00Z', }); expect(findLatestCodexTranscript(sessions, '/repo/current', pointer)).toBe(transcript); expect(findLatestCodexTranscript(sessions, '/repo/项目')).toBe(transcript); expect(findLatestCodexTranscript(sessions, '/different')).toBeNull(); }); test('trusts an exact live pointer before its JSONL has been flushed', () => { const sessions = path.join(dir, 'sessions'); const notFlushed = path.join(sessions, '2026', '07', '20', 'rollout-live.jsonl'); const pointer = path.join(dir, 'handoff', 'codex-latest.json'); writeCodexLatestPointer(pointer, { schemaVersion: 1, sessionId: 'live-id', transcriptPath: notFlushed, cwd: '/repo/live', updatedAt: new Date().toISOString(), }); expect(fs.existsSync(notFlushed)).toBe(false); expect(findLatestCodexTranscript(sessions, '/repo/live', pointer)).toBe(notFlushed); }); test('ignores a stale pointer whose rollout no longer exists', () => { const sessions = path.join(dir, 'empty-sessions'); const missing = path.join(sessions, 'rollout-deleted.jsonl'); const pointer = path.join(dir, 'handoff', 'codex-latest.json'); writeCodexLatestPointer(pointer, { schemaVersion: 1, sessionId: 'deleted-id', transcriptPath: missing, cwd: '/repo/deleted', updatedAt: '2020-01-01T00:00:00Z', }); expect(findLatestCodexTranscript(sessions, '/repo/deleted', pointer)).toBeNull(); }); test('keeps user-created forked root sessions in scan fallback', () => { const sessions = path.join(dir, 'sessions'); const fork = path.join(sessions, '2026', '07', '20', 'rollout-user-fork.jsonl'); writeRows(fork, [{ timestamp: '2026-07-20T04:00:00Z', type: 'session_meta', payload: { id: 'fork-id', cwd: '/repo/fork', forked_from_id: 'root-id' }, }]); const future = new Date(Date.now() + 20000); fs.utimesSync(fork, future, future); expect(findLatestCodexTranscript(sessions, '/repo/fork')).toBe(fork); }); test('does not let an old explicit pointer mask a newer root rollout', () => { const sessions = path.join(dir, 'sessions'); const newer = path.join(sessions, '2026', '07', '20', 'rollout-newer-root.jsonl'); writeRows(newer, [{ timestamp: '2026-07-20T05:00:00Z', type: 'session_meta', payload: { id: 'newer-id', cwd: '/repo/项目' }, }]); const future = new Date(Date.now() + 30000); fs.utimesSync(newer, future, future); const pointer = path.join(dir, 'handoff', 'codex-latest.json'); writeCodexLatestPointer(pointer, { schemaVersion: 1, sessionId: 'root-id', transcriptPath: transcript, cwd: '/repo/项目', updatedAt: '2020-01-01T00:00:00Z', }); expect(findLatestCodexTranscript(sessions, '/repo/项目', pointer)).toBe(newer); }); test('keeps a fresh explicit handoff pointer authoritative within the same cwd', () => { const sessions = path.join(dir, 'sessions'); const otherTask = path.join(sessions, '2026', '07', '20', 'rollout-other-task.jsonl'); writeRows(otherTask, [{ type: 'session_meta', payload: { id: 'other-task', cwd: '/repo/项目' } }]); const future = new Date(Date.now() + 30000); fs.utimesSync(otherTask, future, future); const pointer = path.join(dir, 'handoff', 'codex-latest.json'); writeCodexLatestPointer(pointer, { schemaVersion: 1, sessionId: 'root-id', transcriptPath: transcript, cwd: '/repo/项目', updatedAt: new Date().toISOString(), }); expect(findLatestCodexTranscript(sessions, '/repo/项目', pointer)).toBe(transcript); }); test('ignores newer metadata-less JSONL files during scan fallback', () => { const sessions = path.join(dir, 'sessions'); const empty = path.join(sessions, '2026', '07', '20', 'rollout-unclassified.jsonl'); fs.writeFileSync(empty, '{}\n'); const future = new Date(Date.now() + 40000); fs.utimesSync(empty, future, future); expect(findLatestCodexTranscript(sessions, '/repo/项目')).toBe(transcript); }); test('labels an interrupted current turn and retains safe search/image evidence', () => { const file = path.join(dir, 'sessions', '2026', '07', '20', 'rollout-aborted.jsonl'); const metadata = { internal_chat_message_metadata_passthrough: { turn_id: 'turn-aborted' } }; writeRows(file, [ { type: 'session_meta', payload: { id: 'aborted-id', cwd: '/repo/aborted' } }, { type: 'event_msg', payload: { type: 'task_started', turn_id: 'turn-aborted' } }, { type: 'response_item', payload: { type: 'message', role: 'user', content: [{ type: 'input_text', text: '分析图片并检索资料' }], ...metadata, } }, { type: 'event_msg', payload: { type: 'user_message', turn_id: 'turn-aborted', message: '分析图片并检索资料', local_images: ['/repo/aborted/screen.png', 'https://example.com/remote.png', 'data:image/png;base64,SECRET-IMAGE'], } }, { type: 'response_item', payload: { type: 'web_search_call', id: 'search-1', status: 'completed', action: { type: 'search', query: 'Codex hooks' }, ...metadata, } }, { type: 'response_item', payload: { type: 'tool_search_call', call_id: 'tools-1', status: 'completed', arguments: '{"query":"browser"}', ...metadata, } }, { type: 'response_item', payload: { type: 'tool_search_output', call_id: 'tools-1', status: 'completed', tools: [{ name: 'browser.open', description: 'TOOL-DEFINITION-SECRET' }], ...metadata, } }, { type: 'event_msg', payload: { type: 'image_generation_end', turn_id: 'turn-aborted', call_id: 'image-1', status: 'completed', saved_path: '/repo/aborted/generated.png', result: 'OPAQUE-IMAGE-SECRET', } }, { type: 'event_msg', payload: { type: 'turn_aborted', turn_id: 'turn-aborted', reason: 'interrupted', started_at: 1784527200, completed_at: 1784527201.5, duration_ms: 321, } }, ]); const intake = intakeCodexTranscript(file, { limit: 50 }); expect(intake.currentTurn).toEqual(expect.objectContaining({ turnId: 'turn-aborted', aborted: true, abortReason: 'interrupted', completed: false, })); expect(intake.page.entries).toEqual(expect.arrayContaining([ expect.objectContaining({ type: 'turn-aborted', startedAt: '2026-07-20T06:00:00.000Z', completedAt: '2026-07-20T06:00:01.500Z', }), ])); expect(intake.page.entries).toEqual(expect.arrayContaining([ expect.objectContaining({ type: 'message', role: 'user', localImages: [{ path: '/repo/aborted/screen.png' }], }), expect.objectContaining({ type: 'web-search', action: { type: 'search', query: 'Codex hooks' } }), expect.objectContaining({ type: 'tool-exchange', tool: 'tool_search', output: { status: 'completed', tools: { count: 1, names: ['browser.open'] } }, }), expect.objectContaining({ type: 'generated-image', savedPath: '/repo/aborted/generated.png' }), expect.objectContaining({ type: 'turn-aborted', reason: 'interrupted' }), ])); const encoded = JSON.stringify(intake); expect(encoded).not.toContain('TOOL-DEFINITION-SECRET'); expect(encoded).not.toContain('OPAQUE-IMAGE-SECRET'); expect(encoded).not.toContain('SECRET-IMAGE'); }); test('excludes rolled-back dialogue from the current resumable state', () => { const file = path.join(dir, 'sessions', '2026', '07', '20', 'rollout-rollback.jsonl'); const meta = (turnId: string) => ({ internal_chat_message_metadata_passthrough: { turn_id: turnId } }); writeRows(file, [ { type: 'session_meta', payload: { id: 'rollback-id', cwd: '/repo/rollback' } }, { type: 'event_msg', payload: { type: 'task_started', turn_id: 'turn-kept' } }, { type: 'response_item', payload: { type: 'message', role: 'user', content: [{ type: 'input_text', text: '保留的任务' }], ...meta('turn-kept'), } }, { type: 'event_msg', payload: { type: 'task_complete', turn_id: 'turn-kept' } }, { type: 'event_msg', payload: { type: 'task_started', turn_id: 'turn-stale' } }, { type: 'response_item', payload: { type: 'message', role: 'user', content: [{ type: 'input_text', text: 'STALE-ROLLED-BACK-REQUEST' }], ...meta('turn-stale'), } }, { type: 'event_msg', payload: { type: 'patch_apply_end', turn_id: 'turn-stale', success: true, changes: { '/repo/rollback/stale.ts': { type: 'update' } }, } }, { type: 'event_msg', payload: { type: 'turn_aborted', turn_id: 'turn-stale', reason: 'interrupted' } }, { type: 'event_msg', payload: { type: 'thread_rolled_back', num_turns: 1 } }, ]); const intake = intakeCodexTranscript(file, { limit: 50 }); expect(intake.currentTurn).toEqual(expect.objectContaining({ turnId: 'turn-kept', completed: true, aborted: false, })); expect(intake.rollbacks).toEqual(expect.objectContaining({ count: 1, excludedTurnIds: ['turn-stale'], })); expect(JSON.stringify(intake.page.entries)).not.toContain('STALE-ROLLED-BACK-REQUEST'); expect(intake.workspaceEvidence.editedFiles).toEqual([ expect.objectContaining({ path: '/repo/rollback/stale.ts', rolledBack: true }), ]); }); }); function writeRows(file: string, rows: any[]): void { fs.mkdirSync(path.dirname(file), { recursive: true }); fs.writeFileSync(file, rows.map(row => JSON.stringify(row)).join('\n') + '\n'); } function fixtureRows(): any[] { const metadata = { internal_chat_message_metadata_passthrough: { turn_id: 'turn-1' } }; return [ { timestamp: '2026-07-20T01:00:00Z', type: 'session_meta', payload: { id: 'root-id', session_id: 'root-id', cwd: '/repo/项目', cli_version: '0.137.0', base_instructions: 'DEVELOPER-SECRET', dynamic_tools: ['SECRET-TOOL'], } }, { timestamp: '2026-07-20T01:00:01Z', type: 'event_msg', payload: { type: 'task_started', turn_id: 'turn-1' } }, { timestamp: '2026-07-20T01:00:02Z', type: 'response_item', payload: { type: 'message', role: 'user', content: [{ type: 'input_text', text: '实现双向交接' }], ...metadata, } }, { timestamp: '2026-07-20T01:00:03Z', type: 'event_msg', payload: { type: 'user_message', message: '实现双向交接', turn_id: 'turn-1' } }, { timestamp: '2026-07-20T01:00:04Z', type: 'response_item', payload: { type: 'message', role: 'developer', content: [{ type: 'input_text', text: 'DEVELOPER-SECRET' }], ...metadata, } }, { timestamp: '2026-07-20T01:00:05Z', type: 'response_item', payload: { type: 'reasoning', summary: [{ text: 'PRIVATE-REASONING' }], encrypted_content: 'ENCRYPTED-BLOB', ...metadata, } }, { timestamp: '2026-07-20T01:00:06Z', type: 'response_item', payload: { type: 'function_call', namespace: 'functions', name: 'exec', arguments: '{"cmd":"git status"}', call_id: 'call-1', ...metadata, } }, { timestamp: '2026-07-20T01:00:07Z', type: 'response_item', payload: { type: 'function_call_output', call_id: 'call-1', output: { ok: true }, ...metadata, } }, { timestamp: '2026-07-20T01:00:08Z', type: 'event_msg', payload: { type: 'patch_apply_end', call_id: 'patch-1', turn_id: 'turn-1', success: true, status: 'completed', changes: { '/repo/项目/file.ts': { type: 'update', content: 'DIFF-SECRET' } }, } }, { timestamp: '2026-07-20T01:00:09Z', type: 'world_state', payload: { full: 'WORLD-SECRET' } }, { timestamp: '2026-07-20T01:00:10Z', type: 'event_msg', payload: { type: 'token_count', rate_limits: 'RATE-SECRET' } }, { timestamp: '2026-07-20T01:00:11Z', type: 'compacted', payload: { replacement_history: [{ encrypted_content: 'ENCRYPTED-BLOB' }] } }, { timestamp: '2026-07-20T01:00:12Z', type: 'response_item', payload: { type: 'message', role: 'assistant', phase: 'commentary', content: [{ type: 'output_text', text: '正在处理' }], ...metadata, } }, { timestamp: '2026-07-20T01:00:13Z', type: 'response_item', payload: { type: 'message', role: 'assistant', phase: 'final_answer', content: [{ type: 'output_text', text: '完成实现' }], ...metadata, } }, { timestamp: '2026-07-20T01:00:14Z', type: 'event_msg', payload: { type: 'task_complete', turn_id: 'turn-1' } }, ]; }