1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
|
import { Hono } from "hono";
import { cors } from "hono/cors";
import { AgentManager } from "./agent-manager.js";
import { PermissionManager } from "./permission-manager.js";
import { agentsRoutes } from "./routes/agents.js";
import { configRoutes } from "./routes/config.js";
import { modelsRoutes, startWakeScheduler } from "./routes/models.js";
import { skillsRoutes } from "./routes/skills.js";
import { tabsRoutes } from "./routes/tabs.js";
export const permissionManager = new PermissionManager();
export const agentManager = new AgentManager(permissionManager);
export const app = new Hono();
app.use(
"*",
cors({
origin: (origin) => origin || "*",
credentials: true,
allowHeaders: ["Content-Type", "Authorization"],
allowMethods: ["GET", "POST", "PATCH", "PUT", "DELETE", "OPTIONS"],
}),
);
app.get("/health", (c) => {
return c.json({ ok: true });
});
app.get("/status", (c) => {
return c.json({
status: agentManager.getStatus(),
messageCount: agentManager.getMessageCount(),
statuses: agentManager.getAllStatuses(),
});
});
app.post("/chat", async (c) => {
const body = await c.req.json<{
tabId?: unknown;
message?: unknown;
keyId?: unknown;
modelId?: unknown;
reasoningEffort?: unknown;
workingDirectory?: unknown;
queueId?: unknown;
}>();
const { tabId, message } = body;
if (typeof tabId !== "string" || tabId.trim() === "") {
return c.json({ error: "tabId must be a non-empty string" }, 400);
}
if (typeof message !== "string" || message.trim() === "") {
return c.json({ error: "message must be a non-empty string" }, 400);
}
if (agentManager.getTabStatus(tabId) === "running") {
const queueId = typeof body.queueId === "string" ? body.queueId : undefined;
const { messageId } = agentManager.queueMessage(tabId, message, queueId);
return c.json({ status: "queued", messageId });
}
const keyId = typeof body.keyId === "string" ? body.keyId : undefined;
const modelId = typeof body.modelId === "string" ? body.modelId : undefined;
const agentModels = Array.isArray(body.agentModels) ? body.agentModels : undefined;
const workingDirectory =
typeof body.workingDirectory === "string" ? body.workingDirectory : undefined;
const validEfforts = ["none", "low", "medium", "high", "max"];
const reasoningEffort =
typeof body.reasoningEffort === "string" && validEfforts.includes(body.reasoningEffort)
? (body.reasoningEffort as "none" | "low" | "medium" | "high" | "max")
: undefined;
// Non-blocking — let the agent run in the background
agentManager
.processMessage(tabId, message, keyId, modelId, reasoningEffort, workingDirectory, agentModels)
.catch(console.error);
return c.json({ status: "ok" });
});
app.route("/config", configRoutes);
app.post("/chat/cancel", async (c) => {
const body = await c.req.json();
if (typeof body.tabId !== "string" || typeof body.messageId !== "string") {
return c.json({ error: "tabId and messageId are required strings" }, 400);
}
const tabId = body.tabId;
const messageId = body.messageId;
const cancelled = agentManager.cancelQueuedMessage(tabId, messageId);
return c.json({ success: cancelled });
});
app.route("/skills", skillsRoutes);
app.route("/models", modelsRoutes);
app.route("/tabs", tabsRoutes);
app.route("/agents", agentsRoutes);
// Start the wake scheduler on boot (restores persisted schedule)
startWakeScheduler();
|