mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-08 00:19:42 +02:00
72 lines
2.9 KiB
TypeScript
72 lines
2.9 KiB
TypeScript
/**
|
|
* @fileoverview Status-telemetry route.
|
|
*
|
|
* Receives Claude Code statusline payloads POSTed by the Codeman-managed
|
|
* statusLine exporter (see `hooks-config.generateStatusLineCommand`) and
|
|
* broadcasts the parsed plan-usage limits (5-hour + weekly) to SSE clients for
|
|
* the header "Plan Usage Limits" chip. Auth-exempt like `/api/hook-event`
|
|
* (localhost-only; hook-secret-gated while a tunnel runs — see middleware/auth).
|
|
*
|
|
* Returns a compact plain-text status string for the exporter to print as the
|
|
* in-terminal footer (print-through), so injecting our statusLine doesn't leave
|
|
* the terminal footer blank.
|
|
*/
|
|
|
|
import { FastifyInstance } from 'fastify';
|
|
import { StatusTelemetrySchema } from '../schemas.js';
|
|
import { parseBody } from '../route-helpers.js';
|
|
import {
|
|
parseStatusTelemetry,
|
|
parseSessionStatus,
|
|
formatSessionStatusText,
|
|
telemetrySignature,
|
|
type RawStatuslinePayload,
|
|
} from '../../usage-telemetry.js';
|
|
import { SessionStatusTelemetry } from '../sse-events.js';
|
|
import { setLatestPlanUsage } from '../plan-usage-latest.js';
|
|
import type { SessionPort, EventPort } from '../ports/index.js';
|
|
|
|
export function registerStatusTelemetryRoutes(app: FastifyInstance, ctx: SessionPort & EventPort): void {
|
|
// Last broadcast telemetry signature per session — the statusline fires on
|
|
// every assistant message, so we only rebroadcast when the value changes.
|
|
const lastSig = new Map<string, string>();
|
|
|
|
app.post('/api/status-telemetry', async (req, reply) => {
|
|
const { sessionId, data } = parseBody(StatusTelemetrySchema, req.body);
|
|
|
|
reply.type('text/plain; charset=utf-8');
|
|
|
|
// Unknown session — minimal footer, no broadcast.
|
|
if (!ctx.sessions.has(sessionId)) {
|
|
lastSig.delete(sessionId);
|
|
return 'codeman';
|
|
}
|
|
|
|
const payload = data as RawStatuslinePayload | undefined;
|
|
|
|
// Plan-usage limits (account-wide) → broadcast to the header chip, when
|
|
// present and changed (the statusline fires on every assistant message).
|
|
const telemetry = parseStatusTelemetry(payload);
|
|
if (telemetry) {
|
|
const sig = telemetrySignature(telemetry);
|
|
if (lastSig.get(sessionId) !== sig) {
|
|
lastSig.set(sessionId, sig);
|
|
// Bound the map across long multi-session runs: prune dead sessions.
|
|
if (lastSig.size > 256) {
|
|
for (const id of [...lastSig.keys()]) {
|
|
if (!ctx.sessions.has(id)) lastSig.delete(id);
|
|
}
|
|
}
|
|
const update = { sessionId, ...telemetry };
|
|
const snapshot = setLatestPlanUsage(update); // replayed in the SSE init snapshot for fresh loads
|
|
ctx.broadcast(SessionStatusTelemetry, snapshot);
|
|
}
|
|
}
|
|
|
|
// In-terminal statusline footer → CURRENT SESSION status (model / tokens /
|
|
// context %), NOT the plan limits. Available from the first render, even
|
|
// before rate_limits appears.
|
|
return formatSessionStatusText(parseSessionStatus(payload));
|
|
});
|
|
}
|