mirror of
https://github.com/claude-code-best/claude-code.git
synced 2026-06-18 22:35:51 +00:00
119 lines
4.1 KiB
TypeScript
119 lines
4.1 KiB
TypeScript
import { log, error as logError } from "../../logger";
|
|
import { Hono } from "hono";
|
|
import { uuidAuth } from "../../auth/middleware";
|
|
import { getAutomationStateSnapshot } from "../../services/automationState";
|
|
import {
|
|
createSession,
|
|
getSession,
|
|
isSessionClosedStatus,
|
|
listWebSessionSummariesByOwnerUuid,
|
|
listWebSessionsByOwnerUuid,
|
|
resolveOwnedWebSessionId,
|
|
toWebSessionResponse,
|
|
} from "../../services/session";
|
|
import { storeBindSession, storeGetSessionWorker } from "../../store";
|
|
import { createWorkItem } from "../../services/work-dispatch";
|
|
import { createSSEStream } from "../../transport/sse-writer";
|
|
import { getEventBus } from "../../transport/event-bus";
|
|
|
|
const app = new Hono();
|
|
|
|
/** POST /web/sessions — Create a session from web UI */
|
|
app.post("/sessions", uuidAuth, async (c) => {
|
|
const uuid = c.get("uuid")!;
|
|
const body = await c.req.json();
|
|
const session = createSession({
|
|
environment_id: body.environment_id || null,
|
|
title: body.title || "New Session",
|
|
source: "web",
|
|
permission_mode: body.permission_mode || "default",
|
|
});
|
|
|
|
// Auto-bind to creator's UUID
|
|
storeBindSession(session.id, uuid);
|
|
|
|
// Dispatch work to environment if specified
|
|
if (body.environment_id) {
|
|
try {
|
|
await createWorkItem(body.environment_id, session.id);
|
|
} catch (err) {
|
|
logError(`[RCS] Failed to create work item: ${(err as Error).message}`);
|
|
}
|
|
}
|
|
|
|
return c.json(session, 200);
|
|
});
|
|
|
|
/** GET /web/sessions — List sessions owned by the requesting UUID */
|
|
app.get("/sessions", uuidAuth, async (c) => {
|
|
const uuid = c.get("uuid")!;
|
|
const sessions = listWebSessionsByOwnerUuid(uuid);
|
|
return c.json(sessions, 200);
|
|
});
|
|
|
|
/** GET /web/sessions/all — List sessions owned by the requesting UUID (unowned sessions excluded) */
|
|
app.get("/sessions/all", uuidAuth, async (c) => {
|
|
const uuid = c.get("uuid")!;
|
|
const sessions = listWebSessionSummariesByOwnerUuid(uuid);
|
|
return c.json(sessions, 200);
|
|
});
|
|
|
|
/** GET /web/sessions/:id — Session detail */
|
|
app.get("/sessions/:id", uuidAuth, async (c) => {
|
|
const uuid = c.get("uuid")!;
|
|
const sessionId = resolveOwnedWebSessionId(c.req.param("id")!, uuid);
|
|
if (!sessionId) {
|
|
return c.json({ error: { type: "forbidden", message: "Not your session" } }, 403);
|
|
}
|
|
const session = getSession(sessionId);
|
|
if (!session) {
|
|
return c.json({ error: { type: "not_found", message: "Session not found" } }, 404);
|
|
}
|
|
const worker = storeGetSessionWorker(sessionId);
|
|
const automationState = getAutomationStateSnapshot(worker?.externalMetadata);
|
|
const response = toWebSessionResponse(session);
|
|
return c.json(
|
|
automationState === undefined ? response : { ...response, automation_state: automationState },
|
|
200,
|
|
);
|
|
});
|
|
|
|
/** GET /web/sessions/:id/history — Historical events for session */
|
|
app.get("/sessions/:id/history", uuidAuth, async (c) => {
|
|
const uuid = c.get("uuid")!;
|
|
const sessionId = resolveOwnedWebSessionId(c.req.param("id")!, uuid);
|
|
if (!sessionId) {
|
|
return c.json({ error: { type: "forbidden", message: "Not your session" } }, 403);
|
|
}
|
|
const session = getSession(sessionId);
|
|
if (!session) {
|
|
return c.json({ error: { type: "not_found", message: "Session not found" } }, 404);
|
|
}
|
|
|
|
const bus = getEventBus(sessionId);
|
|
const events = bus.getEventsSince(0);
|
|
return c.json({ events }, 200);
|
|
});
|
|
|
|
/** SSE /web/sessions/:id/events — Real-time event stream */
|
|
app.get("/sessions/:id/events", uuidAuth, async (c) => {
|
|
const uuid = c.get("uuid")!;
|
|
const sessionId = resolveOwnedWebSessionId(c.req.param("id")!, uuid);
|
|
if (!sessionId) {
|
|
return c.json({ error: { type: "forbidden", message: "Not your session" } }, 403);
|
|
}
|
|
const session = getSession(sessionId);
|
|
if (!session) {
|
|
return c.json({ error: { type: "not_found", message: "Session not found" } }, 404);
|
|
}
|
|
if (isSessionClosedStatus(session.status)) {
|
|
return c.json({ error: { type: "session_closed", message: `Session is ${session.status}` } }, 409);
|
|
}
|
|
|
|
const lastEventId = c.req.header("Last-Event-ID");
|
|
const fromSeqNum = lastEventId ? parseInt(lastEventId) : 0;
|
|
return createSSEStream(c, sessionId, fromSeqNum);
|
|
});
|
|
|
|
export default app;
|