Skip to content

Commit 7972f12

Browse files
committed
refactor: migrate core web server to Elysia with modular routes
Split the monolithic server into focused route and utility modules so API behavior stays stable while reducing maintenance risk. Also unify onboarding agent detection with the shared agent-check implementation and add regression coverage for web routing contracts.
1 parent 7e99e7a commit 7972f12

File tree

18 files changed

+1143
-891
lines changed

18 files changed

+1143
-891
lines changed

bun.lock

Lines changed: 32 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
"@slack/web-api": "^7.13.0",
3737
"@supabase/supabase-js": "^2.90.1",
3838
"discord.js": "^14.25.1",
39+
"elysia": "^1.4.25",
3940
"ioredis": "^5.9.2",
4041
"pino": "^10.1.1",
4142
"pino-pretty": "^13.1.3",

packages/core/onboarding.ts

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
type OdeConfig,
1111
type WorkspaceConfig,
1212
} from "@/config";
13+
import { getInstalledAgentStatus } from "@/core/web/agent-check";
1314
import { discoverDiscordWorkspace, discoverLarkWorkspace, discoverSlackWorkspace } from "./web/local-settings";
1415

1516
type AgentId = "opencode" | "claudecode" | "codex" | "kimi" | "kiro" | "kilo" | "qwen" | "goose" | "gemini";
@@ -33,13 +34,6 @@ const agentOptions: Omit<AgentOption, "installed">[] = [
3334
{ id: "gemini", label: "Gemini CLI", command: "gemini" },
3435
];
3536

36-
function isAgentCommandAvailable(agent: Omit<AgentOption, "installed">): boolean {
37-
if (agent.id === "qwen") {
38-
return Boolean(Bun.which("qwen") || Bun.which("qwen-code"));
39-
}
40-
return Boolean(Bun.which(agent.command));
41-
}
42-
4337
function isInteractiveTerminal(): boolean {
4438
return Boolean(process.stdin.isTTY && process.stdout.isTTY);
4539
}
@@ -153,9 +147,10 @@ async function askRequired(rl: Interface, prompt: string): Promise<string> {
153147
}
154148

155149
function detectAgents(): AgentOption[] {
150+
const installed = getInstalledAgentStatus();
156151
return agentOptions.map((agent) => ({
157152
...agent,
158-
installed: isAgentCommandAvailable(agent),
153+
installed: installed[agent.id],
159154
}));
160155
}
161156

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
import { describe, expect, it } from "bun:test";
2+
import type { SessionEvent } from "@/config/local/redis";
3+
import { createWebApp } from "@/core/web/app";
4+
import { collapseTextDeltas } from "@/core/web/session-events";
5+
6+
describe("web app routing", () => {
7+
it("redirects /local-setting to root", async () => {
8+
const app = createWebApp();
9+
const response = await app.handle(new Request("http://localhost/local-setting"));
10+
expect(response.status).toBe(307);
11+
expect(response.headers.get("location")).toBe("/");
12+
});
13+
14+
it("redirects /local-setting/* to root-relative path", async () => {
15+
const app = createWebApp();
16+
const response = await app.handle(new Request("http://localhost/local-setting/sessions/abc"));
17+
expect(response.status).toBe(307);
18+
expect(response.headers.get("location")).toBe("/sessions/abc");
19+
});
20+
21+
it("returns 400 for workspace sync without workspaceId", async () => {
22+
const app = createWebApp();
23+
const response = await app.handle(new Request("http://localhost/api/slack-sync", {
24+
method: "POST",
25+
headers: { "content-type": "application/json" },
26+
body: JSON.stringify({}),
27+
}));
28+
29+
expect(response.status).toBe(400);
30+
const payload = await response.json() as { ok: boolean; error?: string };
31+
expect(payload.ok).toBe(false);
32+
expect(payload.error).toBe("Missing workspaceId");
33+
});
34+
35+
it("returns 400 for workspace discover without required credentials", async () => {
36+
const app = createWebApp();
37+
const response = await app.handle(new Request("http://localhost/api/slack-discover", {
38+
method: "POST",
39+
headers: { "content-type": "application/json" },
40+
body: JSON.stringify({}),
41+
}));
42+
43+
expect(response.status).toBe(400);
44+
const payload = await response.json() as { ok: boolean; error?: string };
45+
expect(payload.ok).toBe(false);
46+
expect(payload.error?.startsWith("Missing Slack")).toBe(true);
47+
});
48+
});
49+
50+
describe("collapseTextDeltas", () => {
51+
it("keeps only latest text delta for each part id", () => {
52+
const base = {
53+
sessionId: "s1",
54+
channelId: "C1",
55+
threadId: "T1",
56+
agentProvider: "opencode",
57+
};
58+
const events = [
59+
{
60+
...base,
61+
timestamp: 1,
62+
type: "message.part.updated",
63+
data: { properties: { part: { id: "p1", type: "text", text: "a" } } },
64+
},
65+
{
66+
...base,
67+
timestamp: 2,
68+
type: "message.part.updated",
69+
data: { properties: { part: { id: "p1", type: "text", text: "ab" } } },
70+
},
71+
{
72+
...base,
73+
timestamp: 3,
74+
type: "tool.started",
75+
data: { id: "t1" },
76+
},
77+
{
78+
...base,
79+
timestamp: 4,
80+
type: "message.part.updated",
81+
data: { properties: { part: { id: "p2", type: "text", text: "x" } } },
82+
},
83+
] as SessionEvent[];
84+
85+
const collapsed = collapseTextDeltas(events);
86+
expect(collapsed).toHaveLength(3);
87+
expect(collapsed[0]?.timestamp).toBe(2);
88+
expect(collapsed[1]?.type).toBe("tool.started");
89+
expect(collapsed[2]?.timestamp).toBe(4);
90+
});
91+
});

0 commit comments

Comments
 (0)