import { describe, expect, it, beforeEach, afterEach } from 'vitest'; import { mkdtempSync, writeFileSync, rmSync } from 'node:fs'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; import { SessionActivityTracker } from '../src/core/claude-adapter.js'; const PID = 999_999; const sleep = (ms: number): Promise => new Promise((r) => setTimeout(r, ms)); function writeRegistry(dir: string, status: string, waitingFor?: string): void { writeFileSync( join(dir, `${PID}.json`), JSON.stringify({ pid: PID, procStart: '123', sessionId: 'sid', cwd: '/x', status, ...(waitingFor ? { waitingFor } : {}) }), ); } describe('SessionActivityTracker', () => { let dir: string; let tracker: SessionActivityTracker; let changes: number; beforeEach(() => { dir = mkdtempSync(join(tmpdir(), 'arb-adapter-')); changes = 0; tracker = new SessionActivityTracker(PID, dir, () => { changes++; }); }); afterEach(() => { tracker.dispose(); rmSync(dir, { recursive: true, force: true }); }); it('registre busy → idle : transitions d’activité + onChange sur changement uniquement', () => { writeRegistry(dir, 'busy'); tracker.evaluate(); expect(tracker.snapshot().activity).toBe('busy'); expect(changes).toBe(1); tracker.evaluate(); // rien n'a changé expect(changes).toBe(1); writeRegistry(dir, 'idle'); tracker.evaluate(); expect(tracker.snapshot().activity).toBe('idle'); expect(changes).toBe(2); }); it('registre waiting + écran permission → activity waiting, dialog typé permission', async () => { await tracker['reader'].feed('\x1b[2J\x1b[HDo you want to create x.txt?\r\n❯ 1. Yes\r\n2. No\r\nEsc to cancel · Tab to amend\r\n'); await sleep(30); writeRegistry(dir, 'waiting', 'permission prompt'); tracker.evaluate(); const s = tracker.snapshot(); expect(s.activity).toBe('waiting'); expect(s.waitingFor).toBe('permission prompt'); expect(s.dialog?.kind).toBe('permission'); expect(s.dialog?.options.find((o) => o.n === 1)).toMatchObject({ selected: true }); }); it('pas de registre + écran Trust → waiting/trust (le Trust précède le registre)', async () => { await tracker['reader'].feed('\x1b[2J\x1b[HDo you trust the files in this folder?\r\n❯ 1. Yes, I trust this folder\r\n2. No, exit\r\n'); await sleep(30); tracker.evaluate(); const s = tracker.snapshot(); expect(s.activity).toBe('waiting'); expect(s.dialog?.kind).toBe('trust'); }); it('pas de registre + pas de dialogue → état vide (inconnu)', () => { tracker.evaluate(); expect(tracker.snapshot()).toMatchObject({ activity: null, dialog: null }); }); });