Bug 1: Subagent events from gateway broadcasts were processed as parent events because the sessionKey filter was accidentally removed during the subagent decoupling refactor. Re-add the filter in wireChildProcess. Bug 2: Creating workspaces at custom paths failed because: - mkdir API rejected absolute paths outside workspace root - Directory picker started at workspace root, not home - Error responses from mkdir were silently swallowed Add absolute path support to mkdir, handle errors in picker UI, start picker at home dir, and normalize init route paths.
72 lines
1.9 KiB
TypeScript
72 lines
1.9 KiB
TypeScript
import { mkdirSync, existsSync } from "node:fs";
|
|
import { resolve, normalize } from "node:path";
|
|
import { safeResolveNewPath } from "@/lib/workspace";
|
|
|
|
export const dynamic = "force-dynamic";
|
|
export const runtime = "nodejs";
|
|
|
|
/**
|
|
* POST /api/workspace/mkdir
|
|
* Body: { path: string; absolute?: boolean }
|
|
*
|
|
* Creates a new directory. By default paths are resolved relative to the
|
|
* workspace root. When `absolute` is true the path is treated as a
|
|
* filesystem-absolute path (used by the directory picker for workspace
|
|
* creation outside the current workspace).
|
|
*/
|
|
export async function POST(req: Request) {
|
|
let body: { path?: string; absolute?: boolean };
|
|
try {
|
|
body = await req.json();
|
|
} catch {
|
|
return Response.json({ error: "Invalid JSON body" }, { status: 400 });
|
|
}
|
|
|
|
const { path: rawPath, absolute: useAbsolute } = body;
|
|
if (!rawPath || typeof rawPath !== "string") {
|
|
return Response.json(
|
|
{ error: "Missing 'path' field" },
|
|
{ status: 400 },
|
|
);
|
|
}
|
|
|
|
let absPath: string | null;
|
|
|
|
if (useAbsolute) {
|
|
const normalized = normalize(rawPath);
|
|
if (normalized.includes("/../") || normalized.includes("/..")) {
|
|
return Response.json(
|
|
{ error: "Path traversal rejected" },
|
|
{ status: 400 },
|
|
);
|
|
}
|
|
absPath = resolve(normalized);
|
|
} else {
|
|
absPath = safeResolveNewPath(rawPath);
|
|
}
|
|
|
|
if (!absPath) {
|
|
return Response.json(
|
|
{ error: "Invalid path or path traversal rejected" },
|
|
{ status: 400 },
|
|
);
|
|
}
|
|
|
|
if (existsSync(absPath)) {
|
|
return Response.json(
|
|
{ error: "Directory already exists" },
|
|
{ status: 409 },
|
|
);
|
|
}
|
|
|
|
try {
|
|
mkdirSync(absPath, { recursive: true });
|
|
return Response.json({ ok: true, path: absPath });
|
|
} catch (err) {
|
|
return Response.json(
|
|
{ error: err instanceof Error ? err.message : "mkdir failed" },
|
|
{ status: 500 },
|
|
);
|
|
}
|
|
}
|