feat(KB-641): merge kb/kb-641 (auto-resolved)

- feat(KB-641): complete Step 5 — add changeset
- feat(KB-641): complete Step 2 — update terminal route errors
This commit is contained in:
gsxdsm
2026-04-01 15:25:08 -07:00
parent e5928d8ec9
commit db18a5af6d
3 changed files with 83 additions and 10 deletions

View File

@@ -2,4 +2,4 @@
"@gsxdsm/fusion": patch "@gsxdsm/fusion": patch
--- ---
Improve terminal session creation error reporting with specific HTTP status codes and actionable messages. Fix terminal session creation errors to return specific messages and HTTP status codes.

View File

@@ -4736,9 +4736,13 @@ describe("Terminal session routes", () => {
}); });
describe("POST /api/terminal/sessions", () => { describe("POST /api/terminal/sessions", () => {
it("returns 503 when max sessions reached (session is null)", async () => { it("returns 503 with specific max sessions error", async () => {
const mockService = { const mockService = {
createSession: vi.fn().mockResolvedValue(null), createSession: vi.fn().mockResolvedValue({
success: false,
code: "max_sessions",
error: "Maximum terminal sessions reached. Please close an existing terminal and try again.",
}),
}; };
vi.spyOn(terminalServiceModule, "getTerminalService").mockReturnValue(mockService as any); vi.spyOn(terminalServiceModule, "getTerminalService").mockReturnValue(mockService as any);
@@ -4751,7 +4755,69 @@ describe("Terminal session routes", () => {
); );
expect(res.status).toBe(503); expect(res.status).toBe(503);
expect(res.body.error).toContain("Max sessions"); expect(res.body).toEqual({
error: "Maximum terminal sessions reached. Please close an existing terminal and try again.",
code: "max_sessions",
});
vi.restoreAllMocks();
});
it.each([
["invalid_shell", 400, "Shell not allowed. Please use a supported shell (bash, zsh, sh, cmd, powershell)."],
["pty_load_failed", 503, "Terminal service unavailable. The PTY module could not be loaded."],
["pty_spawn_failed", 500, "Failed to start terminal shell process."],
] as const)("returns %s errors with the correct status and body", async (code, status, error) => {
const mockService = {
createSession: vi.fn().mockResolvedValue({
success: false,
code,
error,
}),
};
vi.spyOn(terminalServiceModule, "getTerminalService").mockReturnValue(mockService as any);
const res = await REQUEST(
buildApp(),
"POST",
"/api/terminal/sessions",
JSON.stringify({ shell: "/bad/shell" }),
{ "Content-Type": "application/json" },
);
expect(res.status).toBe(status);
expect(res.body).toEqual({ error, code });
vi.restoreAllMocks();
});
it("returns 201 for a successful session creation", async () => {
const mockService = {
createSession: vi.fn().mockResolvedValue({
success: true,
session: {
id: "term-123",
shell: "/bin/zsh",
cwd: "/fake/root",
},
}),
};
vi.spyOn(terminalServiceModule, "getTerminalService").mockReturnValue(mockService as any);
const res = await REQUEST(
buildApp(),
"POST",
"/api/terminal/sessions",
JSON.stringify({}),
{ "Content-Type": "application/json" },
);
expect(res.status).toBe(201);
expect(res.body).toEqual({
sessionId: "term-123",
shell: "/bin/zsh",
cwd: "/fake/root",
});
vi.restoreAllMocks(); vi.restoreAllMocks();
}); });

View File

@@ -4145,21 +4145,28 @@ export function createApiRoutes(store: TaskStore, options?: ServerOptions): Rout
const { cwd, cols, rows } = req.body; const { cwd, cols, rows } = req.body;
const terminalService = getTerminalService(store.getRootDir()); const terminalService = getTerminalService(store.getRootDir());
const session = await terminalService.createSession({ const result = await terminalService.createSession({
cwd, cwd,
cols: typeof cols === "number" ? cols : undefined, cols: typeof cols === "number" ? cols : undefined,
rows: typeof rows === "number" ? rows : undefined, rows: typeof rows === "number" ? rows : undefined,
}); });
if (!session) { if (!result.success) {
res.status(503).json({ error: "Failed to create session. Max sessions may be reached." }); const statusByCode = {
max_sessions: 503,
invalid_shell: 400,
pty_load_failed: 503,
pty_spawn_failed: 500,
} as const;
res.status(statusByCode[result.code]).json({ error: result.error, code: result.code });
return; return;
} }
res.status(201).json({ res.status(201).json({
sessionId: session.id, sessionId: result.session.id,
shell: session.shell, shell: result.session.shell,
cwd: session.cwd, cwd: result.session.cwd,
}); });
} catch (err: any) { } catch (err: any) {
res.status(500).json({ error: err.message || "Failed to create terminal session" }); res.status(500).json({ error: err.message || "Failed to create terminal session" });