feat: ship usable local v1 with demo, workers, approvals, and docker support
This commit is contained in:
@@ -4,7 +4,37 @@ import fastifyStatic from "@fastify/static";
|
||||
import { BossEngine } from "./engine.js";
|
||||
|
||||
const engine = new BossEngine();
|
||||
const app = Fastify({ logger: true });
|
||||
const app = Fastify({ logger: process.env.BOSS_DEBUG === "1" });
|
||||
|
||||
app.setErrorHandler((error, request, reply) => {
|
||||
const message =
|
||||
typeof error === "object" && error !== null && "message" in error
|
||||
? String(error.message)
|
||||
: "Internal Server Error";
|
||||
const normalized = message.toLowerCase();
|
||||
|
||||
if (normalized.includes("not found")) {
|
||||
return reply.status(404).send({ error: "Not Found", message });
|
||||
}
|
||||
|
||||
if (
|
||||
normalized.includes("is not assigned to worker") ||
|
||||
normalized.includes("is not currently executing") ||
|
||||
normalized.includes("does not accept worker updates")
|
||||
) {
|
||||
return reply.status(409).send({ error: "Conflict", message });
|
||||
}
|
||||
|
||||
if (
|
||||
normalized.includes("required") ||
|
||||
normalized.includes("archived")
|
||||
) {
|
||||
return reply.status(400).send({ error: "Bad Request", message });
|
||||
}
|
||||
|
||||
request.log.error(error);
|
||||
return reply.status(500).send({ error: "Internal Server Error", message });
|
||||
});
|
||||
|
||||
await app.register(fastifyStatic, {
|
||||
root: path.resolve(process.cwd(), "public"),
|
||||
@@ -23,6 +53,12 @@ app.get("/api/health", async () => ({
|
||||
|
||||
app.get("/api/bootstrap", async () => engine.bootstrap());
|
||||
|
||||
app.get("/api/events", async (request) => {
|
||||
const query = request.query as { limit?: string };
|
||||
const limit = Number(query.limit ?? 100);
|
||||
return engine.listEvents(Number.isFinite(limit) ? limit : 100);
|
||||
});
|
||||
|
||||
app.get("/api/sessions", async () => engine.listSessions());
|
||||
|
||||
app.post("/api/sessions", async (request) => {
|
||||
@@ -30,11 +66,21 @@ app.post("/api/sessions", async (request) => {
|
||||
return engine.createSession(body.title);
|
||||
});
|
||||
|
||||
app.post("/api/sessions/:sessionId/archive", async (request) => {
|
||||
const params = request.params as { sessionId: string };
|
||||
return engine.archiveSession(params.sessionId);
|
||||
});
|
||||
|
||||
app.get("/api/sessions/:sessionId", async (request) => {
|
||||
const params = request.params as { sessionId: string };
|
||||
return engine.getSession(params.sessionId);
|
||||
});
|
||||
|
||||
app.get("/api/tasks/:taskId", async (request) => {
|
||||
const params = request.params as { taskId: string };
|
||||
return engine.getTask(params.taskId);
|
||||
});
|
||||
|
||||
app.post("/api/sessions/:sessionId/messages", async (request) => {
|
||||
const params = request.params as { sessionId: string };
|
||||
const body = (request.body ?? {}) as { content?: string; channel?: string };
|
||||
@@ -64,6 +110,11 @@ app.get("/api/events/stream", async (_request, reply) => {
|
||||
|
||||
app.get("/api/workers", async () => engine.getState().workers);
|
||||
|
||||
app.get("/api/workers/:workerId", async (request) => {
|
||||
const params = request.params as { workerId: string };
|
||||
return engine.getWorker(params.workerId);
|
||||
});
|
||||
|
||||
app.post("/api/workers/register", async (request) => {
|
||||
const body = request.body as {
|
||||
name?: string;
|
||||
@@ -83,6 +134,11 @@ app.post("/api/workers/:workerId/heartbeat", async (request) => {
|
||||
return engine.heartbeat(params.workerId, body.load ?? 0);
|
||||
});
|
||||
|
||||
app.post("/api/workers/:workerId/offline", async (request) => {
|
||||
const params = request.params as { workerId: string };
|
||||
return engine.markWorkerOffline(params.workerId);
|
||||
});
|
||||
|
||||
app.post("/api/workers/:workerId/claim-next", async (request) => {
|
||||
const params = request.params as { workerId: string };
|
||||
return {
|
||||
@@ -135,6 +191,16 @@ app.post("/api/tasks/:taskId/cancel", async (request) => {
|
||||
return engine.cancelTask(params.taskId);
|
||||
});
|
||||
|
||||
app.post("/api/tasks/:taskId/resume", async (request) => {
|
||||
const params = request.params as { taskId: string };
|
||||
return engine.resumeTask(params.taskId);
|
||||
});
|
||||
|
||||
app.post("/api/tasks/:taskId/requeue", async (request) => {
|
||||
const params = request.params as { taskId: string };
|
||||
return engine.requeueTask(params.taskId);
|
||||
});
|
||||
|
||||
app.post("/api/approvals/:approvalId/respond", async (request) => {
|
||||
const params = request.params as { approvalId: string };
|
||||
const body = request.body as {
|
||||
@@ -149,5 +215,7 @@ app.post("/api/demo/reset", async () => {
|
||||
return ok();
|
||||
});
|
||||
|
||||
app.post("/api/reconcile", async () => engine.reconcileNow());
|
||||
|
||||
const port = Number(process.env.PORT ?? 43210);
|
||||
await app.listen({ port, host: "0.0.0.0" });
|
||||
|
||||
Reference in New Issue
Block a user