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:
2026-06-12 18:20:21 +02:00
parent 8bc48448c2
commit fb175bc7c5
27 changed files with 1363 additions and 41 deletions

View File

@@ -9,6 +9,7 @@ import type { Config } from './config.js';
import type { Db } from './db/index.js';
import { AuthService, LoginRateLimiter, type AuthContext } from './auth/service.js';
import { PtyManager } from './core/pty-manager.js';
import { DiscoveryService } from './core/discovery-service.js';
import { registerAuthRoutes } from './routes/auth.js';
import { registerSessionRoutes } from './routes/sessions.js';
import { registerWsGateway } from './ws/gateway.js';
@@ -26,13 +27,19 @@ export interface AppBundle {
app: FastifyInstance;
auth: AuthService;
manager: PtyManager;
discovery: DiscoveryService;
}
export function buildApp(config: Config, db: Db, serverVersion: string): AppBundle {
const app = Fastify({ logger: { level: process.env.ARBORETUM_LOG ?? 'info' } });
const auth = new AuthService(db);
const limiter = new LoginRateLimiter();
const manager = new PtyManager(db);
const manager = new PtyManager(db, config.claudeSessionsDir);
const discovery = new DiscoveryService({
ptyManager: manager,
projectsDir: config.claudeProjectsDir,
sessionsDir: config.claudeSessionsDir,
});
void app.register(fastifyCookie);
void app.register(fastifyWebsocket, {
@@ -72,11 +79,11 @@ export function buildApp(config: Config, db: Db, serverVersion: string): AppBund
});
registerAuthRoutes(app, auth, limiter, serverVersion);
registerSessionRoutes(app, manager);
registerSessionRoutes(app, manager, discovery);
// La route websocket doit être déclarée APRÈS le chargement du plugin (contexte
// encapsulé) — sinon le handler reçoit la signature REST (request, reply).
void app.register(async (scoped) => {
registerWsGateway(scoped, manager, serverVersion);
registerWsGateway(scoped, manager, discovery, serverVersion);
});
// SPA buildée embarquée dans le paquet npm (public/) — absente en dev (vite dev sert le front)
@@ -91,5 +98,5 @@ export function buildApp(config: Config, db: Db, serverVersion: string): AppBund
});
}
return { app, auth, manager };
return { app, auth, manager, discovery };
}