fix: filter non-parent events in main NDJSON handler and fix workspace creation path
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.
This commit is contained in:
parent
109b88b93c
commit
92fadd6700
@ -1,5 +1,6 @@
|
||||
import { readdirSync, statSync, type Dirent } from "node:fs";
|
||||
import { join, dirname, resolve } from "node:path";
|
||||
import { homedir } from "node:os";
|
||||
import { resolveWorkspaceRoot } from "@/lib/workspace";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
@ -116,6 +117,10 @@ export async function GET(req: Request) {
|
||||
);
|
||||
}
|
||||
|
||||
if (dir.startsWith("~")) {
|
||||
dir = join(homedir(), dir.slice(1));
|
||||
}
|
||||
|
||||
const resolved = resolve(dir);
|
||||
|
||||
const entries = buildBrowseTree(resolved, 3, 0, showHidden);
|
||||
|
||||
@ -248,6 +248,7 @@ export async function POST(req: Request) {
|
||||
if (workspaceDir.startsWith("~")) {
|
||||
workspaceDir = join(homedir(), workspaceDir.slice(1));
|
||||
}
|
||||
workspaceDir = resolve(workspaceDir);
|
||||
} else {
|
||||
const stateDir = resolveOpenClawStateDir();
|
||||
if (profileName && profileName !== "default") {
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { mkdirSync, existsSync } from "node:fs";
|
||||
import { resolve, normalize } from "node:path";
|
||||
import { safeResolveNewPath } from "@/lib/workspace";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
@ -6,27 +7,44 @@ export const runtime = "nodejs";
|
||||
|
||||
/**
|
||||
* POST /api/workspace/mkdir
|
||||
* Body: { path: string }
|
||||
* Body: { path: string; absolute?: boolean }
|
||||
*
|
||||
* Creates a new directory in the workspace.
|
||||
* 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 };
|
||||
let body: { path?: string; absolute?: boolean };
|
||||
try {
|
||||
body = await req.json();
|
||||
} catch {
|
||||
return Response.json({ error: "Invalid JSON body" }, { status: 400 });
|
||||
}
|
||||
|
||||
const { path: relPath } = body;
|
||||
if (!relPath || typeof relPath !== "string") {
|
||||
const { path: rawPath, absolute: useAbsolute } = body;
|
||||
if (!rawPath || typeof rawPath !== "string") {
|
||||
return Response.json(
|
||||
{ error: "Missing 'path' field" },
|
||||
{ status: 400 },
|
||||
);
|
||||
}
|
||||
|
||||
const absPath = safeResolveNewPath(relPath);
|
||||
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" },
|
||||
@ -43,7 +61,7 @@ export async function POST(req: Request) {
|
||||
|
||||
try {
|
||||
mkdirSync(absPath, { recursive: true });
|
||||
return Response.json({ ok: true, path: relPath });
|
||||
return Response.json({ ok: true, path: absPath });
|
||||
} catch (err) {
|
||||
return Response.json(
|
||||
{ error: err instanceof Error ? err.message : "mkdir failed" },
|
||||
|
||||
@ -369,6 +369,7 @@ export function CreateWorkspaceDialog({ isOpen, onClose, onCreated }: CreateWork
|
||||
open={showDirPicker}
|
||||
onClose={() => setShowDirPicker(false)}
|
||||
onSelect={(path) => setCustomPath(path)}
|
||||
startDir="~"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
@ -151,11 +151,16 @@ export function DirectoryPickerModal({
|
||||
if (!newFolderName.trim() || !displayDir) {return;}
|
||||
const folderPath = `${displayDir}/${newFolderName.trim()}`;
|
||||
try {
|
||||
await fetch("/api/workspace/mkdir", {
|
||||
const res = await fetch("/api/workspace/mkdir", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ path: folderPath }),
|
||||
body: JSON.stringify({ path: folderPath, absolute: true }),
|
||||
});
|
||||
if (!res.ok) {
|
||||
const data = await res.json().catch(() => ({}));
|
||||
setError((data as { error?: string }).error || "Failed to create folder");
|
||||
return;
|
||||
}
|
||||
setCreatingFolder(false);
|
||||
setNewFolderName("");
|
||||
void fetchDir(currentDir);
|
||||
|
||||
@ -803,6 +803,12 @@ function wireChildProcess(run: ActiveRun): void {
|
||||
return;
|
||||
}
|
||||
|
||||
// Skip events from other sessions (e.g. subagent broadcasts that
|
||||
// the gateway delivers on the same WS connection).
|
||||
if (ev.sessionKey && ev.sessionKey !== parentSessionKey) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Track the global event cursor from the gateway for replay on handoff.
|
||||
const gSeq = typeof (ev as Record<string, unknown>).globalSeq === "number"
|
||||
? (ev as Record<string, unknown>).globalSeq as number
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user