feat(booterm): add PTY session registry + listing endpoint

In-memory SessionMeta registry tracks active terminal sessions with
paneId, sessionId, projectPath, title, createdAt, lastActivityAt.
GET /api/term/sessions returns all active sessions as JSON array.
Registry is updated on WS attach and cleaned up on disconnect.
This commit is contained in:
2026-06-07 22:40:27 +00:00
parent 89a6ffe8a0
commit 7ef479639a
4 changed files with 68 additions and 0 deletions

View File

@@ -4,6 +4,7 @@ import { loadConfig } from './config.js';
import { getPool, closeDb } from './db.js';
import { registerHealthRoutes } from './routes/health.js';
import { registerTerminalRoutes } from './routes/terminals.js';
import { registerSessionRoutes } from './routes/sessions.js';
import { registerWsAttachRoute } from './ws/attach.js';
async function main(): Promise<void> {
@@ -33,6 +34,7 @@ async function main(): Promise<void> {
registerHealthRoutes(app);
registerTerminalRoutes(app, config.TMUX_CONF_PATH);
registerSessionRoutes(app);
registerWsAttachRoute(app, config.TMUX_CONF_PATH);
const shutdown = async (signal: string) => {

View File

@@ -0,0 +1,44 @@
export interface SessionMeta {
paneId: string;
sessionId: string;
projectPath: string;
title?: string;
createdAt: Date;
lastActivityAt: Date;
}
const sessions = new Map<string, SessionMeta>();
export function register(
sessionId: string,
paneId: string,
projectPath: string,
title?: string,
): void {
const now = new Date();
const existing = sessions.get(paneId);
if (existing) {
existing.lastActivityAt = now;
return;
}
sessions.set(paneId, {
paneId,
sessionId,
projectPath,
title,
createdAt: now,
lastActivityAt: now,
});
}
export function unregister(paneId: string): void {
sessions.delete(paneId);
}
export function list(): SessionMeta[] {
return Array.from(sessions.values());
}
export function get(paneId: string): SessionMeta | undefined {
return sessions.get(paneId);
}

View File

@@ -0,0 +1,18 @@
import type { FastifyInstance } from 'fastify';
import { list } from '../pty/registry.js';
export function registerSessionRoutes(app: FastifyInstance): void {
app.get('/api/term/sessions', async (_req, reply) => {
const active = list();
return reply.code(200).send({
sessions: active.map((s) => ({
paneId: s.paneId,
sessionId: s.sessionId,
projectPath: s.projectPath,
title: s.title ?? null,
createdAt: s.createdAt.toISOString(),
lastActivityAt: s.lastActivityAt.toISOString(),
})),
});
});
}

View File

@@ -9,6 +9,7 @@ import {
} from '../pty/manager.js';
import { attachPty } from '../pty/pty.js';
import { getUser } from '../auth.js';
import { register, unregister } from '../pty/registry.js';
export function registerWsAttachRoute(app: FastifyInstance, tmuxConfPath: string): void {
app.get<{
@@ -57,6 +58,8 @@ export function registerWsAttachRoute(app: FastifyInstance, tmuxConfPath: string
return;
}
register(sid, pid, session.project_path);
let handle: IPty;
try {
handle = attachPty({
@@ -157,6 +160,7 @@ export function registerWsAttachRoute(app: FastifyInstance, tmuxConfPath: string
// teardown happens via the /kill route called from the frontend when the
// user closes the pane.
socket.on('close', () => {
unregister(pid);
try {
handle.kill();
} catch {