P2: découverte & reprise des sessions Claude
Arboretum découvre désormais toutes les sessions Claude de la machine (scan ~/.claude/projects + registre ~/.claude/sessions), distingue vivantes/mortes par pid+procStart, et permet de reprendre une morte (--resume dans son cwd d'origine) ou forker une vivante sans la corrompre. - shared: SessionSummary enrichi (source, claudeSessionId, pid, resumable, attachable, registryStatus) — additif, PROTOCOL_VERSION inchangé ; types REST resume/fork. - db: migration id:2 (claude_session_id, resumed_from). - core: jsonl-discovery (parseur tolérant, scan asynchrone non bloquant), session-registry (vivacité pid+procStart), discovery-service (cache + refresh périodique + diff/broadcast), pty-manager (resume/fork + capture du claudeSessionId via le registre). - routes: /sessions/:id/resume (garde-fou 409 anti-corruption sur session vivante) et /fork ; GET fusionné managées + découvertes ; relais WS. - web: badges managed/discovered + busy/idle/waiting, actions conditionnelles (Open/Observe/Kill vs Fork/View vs Resume/Fork), vue read-only des sessions externes, i18n EN/FR. - tests: jsonl-discovery, session-registry, discovery-service + resume/fork (130 verts) ; acceptation E2E acceptance-p2.mjs (sans quota) ALL GREEN. Conforme aux verdicts S1 (resume dans cwd d'origine, vivacité pid+procStart) et S4 (munge cwd, parseur tête+queue, priorité de titre). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
67
packages/server/test/session-registry.test.ts
Normal file
67
packages/server/test/session-registry.test.ts
Normal file
@@ -0,0 +1,67 @@
|
||||
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 { readProcStart, isLive, readRegistry, findByPid } from '../src/core/session-registry.js';
|
||||
|
||||
const DEAD_PID = 2 ** 31 - 1; // pid quasi impossible à attribuer
|
||||
|
||||
describe('readProcStart / isLive', () => {
|
||||
it('lit le procStart du process courant et le valide', () => {
|
||||
const ps = readProcStart(process.pid);
|
||||
expect(ps).not.toBeNull();
|
||||
expect(isLive(process.pid, ps as string)).toBe(true);
|
||||
});
|
||||
it('process absent → mort', () => {
|
||||
expect(readProcStart(DEAD_PID)).toBeNull();
|
||||
expect(isLive(DEAD_PID, '123')).toBe(false);
|
||||
});
|
||||
it('procStart discordant (pid recyclé) → stale → mort', () => {
|
||||
expect(isLive(process.pid, 'definitely-not-the-real-starttime')).toBe(false);
|
||||
});
|
||||
it('procStart inconnu mais process vivant → incertain → vivant', () => {
|
||||
expect(isLive(process.pid, null)).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('readRegistry', () => {
|
||||
let dir: string;
|
||||
beforeEach(() => {
|
||||
dir = mkdtempSync(join(tmpdir(), 'arb-reg-'));
|
||||
});
|
||||
afterEach(() => {
|
||||
rmSync(dir, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
it('dossier absent → vide', () => {
|
||||
expect(readRegistry(join(dir, 'nope'))).toEqual([]);
|
||||
});
|
||||
|
||||
it('calcule live par pid+procStart et ignore les fichiers malformés', () => {
|
||||
const ps = readProcStart(process.pid) as string;
|
||||
writeFileSync(
|
||||
join(dir, `${process.pid}.json`),
|
||||
JSON.stringify({ pid: process.pid, procStart: ps, sessionId: 'live-sid', cwd: '/x', status: 'busy' }),
|
||||
);
|
||||
writeFileSync(
|
||||
join(dir, `${DEAD_PID}.json`),
|
||||
JSON.stringify({ pid: DEAD_PID, procStart: '999', sessionId: 'dead-sid', cwd: '/y', status: 'idle' }),
|
||||
);
|
||||
writeFileSync(join(dir, 'broken.json'), '{ not json');
|
||||
|
||||
const entries = readRegistry(dir);
|
||||
expect(entries).toHaveLength(2);
|
||||
const live = entries.find((e) => e.pid === process.pid);
|
||||
expect(live?.live).toBe(true);
|
||||
expect(live?.status).toBe('busy');
|
||||
expect(live?.claudeSessionId).toBe('live-sid');
|
||||
const dead = entries.find((e) => e.pid === DEAD_PID);
|
||||
expect(dead?.live).toBe(false);
|
||||
expect(findByPid(dir, process.pid)?.claudeSessionId).toBe('live-sid');
|
||||
});
|
||||
|
||||
it('normalise un status inconnu en null', () => {
|
||||
writeFileSync(join(dir, `${DEAD_PID}.json`), JSON.stringify({ pid: DEAD_PID, status: 'weird' }));
|
||||
expect(readRegistry(dir)[0].status).toBeNull();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user