Fix SSE cleanup and add prompt creation endpoint
- Fix SSE cancel handler to properly capture controller in closure - Remove error JSON messages before WebSocket close (close reason is sufficient) - Add POST /api/sessions/:sessionId/prompts endpoint for prompt creation - Add SSE client cleanup on broadcast errors - Add createPrompt and getSession imports - Add prompt message type to ClientMessage for CLI prompt reporting - Add prompt message handler in WebSocket to create and broadcast prompts
This commit is contained in:
parent
b4ac7beead
commit
65b8acf5f8
2 changed files with 58 additions and 14 deletions
|
|
@ -3,11 +3,13 @@
|
|||
import type { ServerWebSocket } from "bun";
|
||||
import {
|
||||
appendOutput,
|
||||
createPrompt,
|
||||
createSession,
|
||||
endSession,
|
||||
getActiveSessions,
|
||||
getDeviceBySecret,
|
||||
getPrompt,
|
||||
getSession,
|
||||
initDb,
|
||||
respondToPrompt,
|
||||
updateLastSeen,
|
||||
|
|
@ -30,8 +32,9 @@ function broadcastSSE(event: SSEEvent): void {
|
|||
try {
|
||||
controller.enqueue(eventStr);
|
||||
} catch (error) {
|
||||
// Client disconnected, will be cleaned up
|
||||
// Client disconnected, clean up
|
||||
console.debug("SSE client write error:", error);
|
||||
sseClients.delete(controller);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -59,16 +62,16 @@ const server = Bun.serve<SessionData>({
|
|||
|
||||
// SSE endpoint for dashboard
|
||||
if (url.pathname === "/events") {
|
||||
let ctrl: ReadableStreamDefaultController<string>;
|
||||
const stream = new ReadableStream<string>({
|
||||
start(controller) {
|
||||
ctrl = controller;
|
||||
sseClients.add(controller);
|
||||
// Send initial headers
|
||||
controller.enqueue(": connected\n\n");
|
||||
},
|
||||
cancel() {
|
||||
sseClients.delete(
|
||||
this as unknown as ReadableStreamDefaultController<string>,
|
||||
);
|
||||
sseClients.delete(ctrl);
|
||||
},
|
||||
});
|
||||
|
||||
|
|
@ -87,6 +90,41 @@ const server = Bun.serve<SessionData>({
|
|||
return Response.json(sessions);
|
||||
}
|
||||
|
||||
// Create prompt for a session
|
||||
if (
|
||||
url.pathname.match(/^\/api\/sessions\/\d+\/prompts$/) &&
|
||||
req.method === "POST"
|
||||
) {
|
||||
const parts = url.pathname.split("/");
|
||||
const sessionId = Number.parseInt(parts[3] || "", 10);
|
||||
|
||||
if (Number.isNaN(sessionId)) {
|
||||
return new Response("Invalid session ID", { status: 400 });
|
||||
}
|
||||
|
||||
const session = getSession(sessionId);
|
||||
if (!session) {
|
||||
return new Response("Session not found", { status: 404 });
|
||||
}
|
||||
|
||||
const body = (await req.json()) as { prompt_text?: unknown };
|
||||
if (!body.prompt_text || typeof body.prompt_text !== "string") {
|
||||
return new Response("Missing or invalid prompt_text", { status: 400 });
|
||||
}
|
||||
|
||||
const prompt = createPrompt(sessionId, body.prompt_text);
|
||||
|
||||
// Broadcast to dashboards
|
||||
broadcastSSE({
|
||||
type: "prompt",
|
||||
prompt_id: prompt.id,
|
||||
session_id: sessionId,
|
||||
prompt_text: prompt.prompt_text,
|
||||
});
|
||||
|
||||
return Response.json(prompt);
|
||||
}
|
||||
|
||||
if (url.pathname.startsWith("/api/prompts/")) {
|
||||
const parts = url.pathname.split("/");
|
||||
const promptId = Number.parseInt(parts[3] || "", 10);
|
||||
|
|
@ -180,9 +218,6 @@ const server = Bun.serve<SessionData>({
|
|||
if (msg.type === "auth") {
|
||||
const device = getDeviceBySecret(msg.secret);
|
||||
if (!device) {
|
||||
ws.send(
|
||||
JSON.stringify({ type: "error", message: "Invalid secret" }),
|
||||
);
|
||||
ws.close(1008, "Invalid secret");
|
||||
return;
|
||||
}
|
||||
|
|
@ -219,9 +254,7 @@ const server = Bun.serve<SessionData>({
|
|||
|
||||
// All other messages require authentication
|
||||
if (!ws.data.sessionId) {
|
||||
ws.send(
|
||||
JSON.stringify({ type: "error", message: "Not authenticated" }),
|
||||
);
|
||||
ws.close(1008, "Not authenticated");
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -236,6 +269,18 @@ const server = Bun.serve<SessionData>({
|
|||
return;
|
||||
}
|
||||
|
||||
// Handle prompt message
|
||||
if (msg.type === "prompt") {
|
||||
const prompt = createPrompt(msg.session_id, msg.prompt_text);
|
||||
broadcastSSE({
|
||||
type: "prompt",
|
||||
prompt_id: prompt.id,
|
||||
session_id: msg.session_id,
|
||||
prompt_text: msg.prompt_text,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle resize message
|
||||
if (msg.type === "resize") {
|
||||
// Store resize info if needed (not yet implemented)
|
||||
|
|
@ -265,9 +310,7 @@ const server = Bun.serve<SessionData>({
|
|||
}
|
||||
} catch (error) {
|
||||
console.error("WebSocket message error:", error);
|
||||
ws.send(
|
||||
JSON.stringify({ type: "error", message: "Invalid message format" }),
|
||||
);
|
||||
ws.close(1008, "Invalid message format");
|
||||
}
|
||||
},
|
||||
|
||||
|
|
|
|||
|
|
@ -41,7 +41,8 @@ export type ClientMessage =
|
|||
| { type: "auth"; secret: string; cwd?: string; command?: string }
|
||||
| { type: "output"; data: string }
|
||||
| { type: "resize"; cols: number; rows: number }
|
||||
| { type: "exit"; code: number };
|
||||
| { type: "exit"; code: number }
|
||||
| { type: "prompt"; session_id: number; prompt_text: string };
|
||||
|
||||
export type ServerMessage =
|
||||
| { type: "input"; data: string }
|
||||
|
|
|
|||
Loading…
Reference in a new issue