P1 spine: monorepo, shared WS protocol, server daemon
- npm workspaces (shared / server / web), TS strict, project refs - @arboretum/shared: multiplexed WS protocol (JSON control + binary output frames: 1B type + u32le channel), flow-control constants (ACK 256K, HIGH 384K, LOW 128K, lagging 2M), REST types - git-arboretum server: Fastify 5 + node:sqlite (single native dep: node-pty prebuilt), token auth (sha256 at rest, HMAC cookie, global login rate limit + backoff), strict Origin check on /api and /ws, PtyManager (2MiB ring with monotonic offset, resync replay = reset + 256KiB tail, pause/resume only when ALL interactive clients exceed HIGH, observers never throttle, lagging clients resync), WS gateway (attach/stdin/resize/ack, heartbeat 30s), SIGTERM→SIGKILL 5s grace - CLI: arboretum [--port 7317] [--bind 127.0.0.1] — non-loopback bind requires an explicit safety flag - Smoke-tested: login/401/403-origin/spawn bash/kill/grace-SIGKILL all green
This commit is contained in:
35
packages/server/src/routes/sessions.ts
Normal file
35
packages/server/src/routes/sessions.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import type { FastifyInstance } from 'fastify';
|
||||
import type { CreateSessionRequest, SessionResponse, SessionsListResponse } from '@arboretum/shared';
|
||||
import type { PtyManager } from '../core/pty-manager.js';
|
||||
|
||||
export function registerSessionRoutes(app: FastifyInstance, manager: PtyManager): void {
|
||||
app.get('/api/v1/sessions', async (): Promise<SessionsListResponse> => {
|
||||
return { sessions: manager.list() };
|
||||
});
|
||||
|
||||
app.post('/api/v1/sessions', async (req, reply) => {
|
||||
const body = req.body as Partial<CreateSessionRequest> | null;
|
||||
if (!body || typeof body.cwd !== 'string' || !body.cwd.startsWith('/')) {
|
||||
return reply.status(400).send({ error: { code: 'BAD_REQUEST', message: 'cwd (absolute path) is required' } });
|
||||
}
|
||||
if (body.command !== undefined && body.command !== 'claude' && body.command !== 'bash') {
|
||||
return reply.status(400).send({ error: { code: 'BAD_REQUEST', message: 'command must be claude or bash' } });
|
||||
}
|
||||
try {
|
||||
const session = manager.spawn({ cwd: body.cwd, ...(body.command ? { command: body.command } : {}) });
|
||||
const res: SessionResponse = { session };
|
||||
return reply.status(201).send(res);
|
||||
} catch (err) {
|
||||
const statusCode = (err as { statusCode?: number }).statusCode ?? 500;
|
||||
return reply.status(statusCode).send({ error: { code: 'SPAWN_FAILED', message: (err as Error).message } });
|
||||
}
|
||||
});
|
||||
|
||||
app.delete('/api/v1/sessions/:id', async (req, reply) => {
|
||||
const { id } = req.params as { id: string };
|
||||
if (!manager.kill(id)) {
|
||||
return reply.status(404).send({ error: { code: 'NOT_FOUND', message: 'No live session with this id' } });
|
||||
}
|
||||
return reply.send({ ok: true });
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user