import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs' import { tmpdir } from 'node:os' import { join } from 'node:path' const { homedirMock } = vi.hoisted(() => ({ homedirMock: vi.fn<() => string>() })) vi.mock(import('node:os'), async (importOriginal) => { const actual = await importOriginal() return { ...actual, homedir: homedirMock } }) import { readLastJcodeUserPromptFromHookPayload } from './jcode-session-files' describe('shared jcode-session-files', () => { let homeDir: string let sessionsDir: string beforeEach(() => { homeDir = mkdtempSync(join(tmpdir(), 'orca-jcode-sessions-')) homedirMock.mockReturnValue(homeDir) sessionsDir = join(homeDir, '.jcode', 'sessions') mkdirSync(sessionsDir, { recursive: true }) }) afterEach(() => { vi.unstubAllEnvs() vi.clearAllMocks() rmSync(homeDir, { recursive: true, force: true }) }) it('returns the last real user prompt from the live journal', () => { writeFileSync( join(sessionsDir, 'session_a_1.journal.jsonl'), [ JSON.stringify({ meta: { id: 'session_a_1' }, append_messages: [ { id: 'm1', role: 'user', display_role: 'system', content: [{ type: 'text', text: 'injected' }] }, { id: 'm2', role: 'user', content: [{ type: 'text', text: 'fix the flaky test' }] } ] }) ].join('\n') ) const found = readLastJcodeUserPromptFromHookPayload({ session_id: 'session_a_1' }) expect(found?.text).toBe('fix the flaky test') expect(found?.interactionKey).toContain('jcode-transcript') }) it('falls back to the consolidated session doc when the journal is missing', () => { writeFileSync( join(sessionsDir, 'session_b_2.json'), JSON.stringify({ id: 'session_b_2', messages: [ { id: 'm1', role: 'user', display_role: 'system', content: 'injected context' }, { id: 'm2', role: 'user', content: [{ type: 'text', text: 'hello jcode' }] }, { id: 'm3', role: 'assistant', content: 'hi' } ] }) ) const found = readLastJcodeUserPromptFromHookPayload({ session_id: 'session_b_2' }) expect(found?.text).toBe('hello jcode') }) it('skips injected context and returns null when only system content exists', () => { writeFileSync( join(sessionsDir, 'session_c_3.journal.jsonl'), JSON.stringify({ meta: { id: 'session_c_3' }, append_messages: [ { id: 'm1', role: 'user', display_role: 'system', content: [{ type: 'text', text: 'context' }] } ] }) ) expect(readLastJcodeUserPromptFromHookPayload({ session_id: 'session_c_3' })).toBeNull() }) it('keys a prompt by its place in the file, so appends do not remint the key', () => { // EOF-relative windows shift on append; region-local keys reminted unchanged prompts and bypassed turn_end dedupe, duplicating prompt telemetry. const journalPath = join(sessionsDir, 'session_d_4.journal.jsonl') const filler = (index: number) => JSON.stringify({ meta: { id: 'session_d_4' }, append_messages: [], index, pad: 'x'.repeat(400) }) const prompt = JSON.stringify({ meta: { id: 'session_d_4' }, append_messages: [{ id: 'm1', role: 'user', content: [{ type: 'text', text: 'keep me' }] }] }) // Place the prompt deep in the second 64 KiB window so appends change its region-local line index. const before = Array.from({ length: 200 }, (_, index) => filler(index)) const after = Array.from({ length: 100 }, (_, index) => filler(1000 + index)) writeFileSync(journalPath, [...before, prompt, ...after].join('\n')) const firstRead = readLastJcodeUserPromptFromHookPayload({ session_id: 'session_d_4' }) writeFileSync(journalPath, [...before, prompt, ...after, filler(9999)].join('\n')) const afterAppend = readLastJcodeUserPromptFromHookPayload({ session_id: 'session_d_4' }) expect(firstRead?.text).toBe('keep me') expect(afterAppend?.text).toBe('keep me') expect(afterAppend?.interactionKey).toBe(firstRead?.interactionKey) }) it('rejects traversal to a file containing a user prompt', () => { writeFileSync( join(homeDir, 'escaped.json'), JSON.stringify({ messages: [{ role: 'user', content: 'outside the sessions directory' }] }) ) expect(readLastJcodeUserPromptFromHookPayload({ session_id: '../../escaped' })).toBeNull() expect(readLastJcodeUserPromptFromHookPayload({})).toBeNull() }) })