From 916b8889b7ba84db7ebc7f3d3c86665d878bb371 Mon Sep 17 00:00:00 2001 From: 7418 <7418@openclaw.ai> Date: Fri, 13 Feb 2026 15:37:51 +0800 Subject: [PATCH] fix: require valid project directory for session creation - Backend: POST /api/chat/sessions now validates working_directory exists (400 MISSING_DIRECTORY / INVALID_DIRECTORY) - db.ts: remove process.cwd() fallback in createSession - ChatListPanel: validate last-working-directory before use, open FolderPicker when invalid; add SSR guard for localStorage - NewChatPage: block sending without a project directory selected Bump version to 0.10.5. Co-Authored-By: Claude Opus 4.6 --- package-lock.json | 4 ++-- package.json | 2 +- src/app/api/chat/sessions/route.ts | 20 ++++++++++++++++++ src/app/chat/page.tsx | 20 ++++++++++++++---- src/components/layout/ChatListPanel.tsx | 27 +++++++++++++++++++++---- src/lib/db.ts | 2 +- 6 files changed, 63 insertions(+), 12 deletions(-) diff --git a/package-lock.json b/package-lock.json index 37437c3..b544fc5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "codepilot", - "version": "0.10.4", + "version": "0.10.5", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "codepilot", - "version": "0.10.4", + "version": "0.10.5", "dependencies": { "@anthropic-ai/claude-agent-sdk": "^0.2.33", "@hugeicons/core-free-icons": "^3.1.1", diff --git a/package.json b/package.json index 3135c85..f433d9f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "codepilot", - "version": "0.10.4", + "version": "0.10.5", "private": true, "author": { "name": "op7418", diff --git a/src/app/api/chat/sessions/route.ts b/src/app/api/chat/sessions/route.ts index 8ae1050..6787393 100644 --- a/src/app/api/chat/sessions/route.ts +++ b/src/app/api/chat/sessions/route.ts @@ -1,4 +1,5 @@ import { NextRequest } from 'next/server'; +import fs from 'fs/promises'; import { getAllSessions, createSession } from '@/lib/db'; import type { CreateSessionRequest, SessionsResponse, SessionResponse } from '@/types'; @@ -17,6 +18,25 @@ export async function GET() { export async function POST(request: NextRequest) { try { const body: CreateSessionRequest = await request.json(); + + // Validate working_directory is provided + if (!body.working_directory) { + return Response.json( + { error: 'Working directory is required', code: 'MISSING_DIRECTORY' }, + { status: 400 }, + ); + } + + // Validate directory actually exists on disk + try { + await fs.access(body.working_directory); + } catch { + return Response.json( + { error: 'Directory does not exist', code: 'INVALID_DIRECTORY' }, + { status: 400 }, + ); + } + const session = createSession( body.title, body.model, diff --git a/src/app/chat/page.tsx b/src/app/chat/page.tsx index 6dc671b..4df2f44 100644 --- a/src/app/chat/page.tsx +++ b/src/app/chat/page.tsx @@ -78,6 +78,20 @@ export default function NewChatPage() { async (content: string) => { if (isStreaming) return; + // Require a project directory before sending + if (!workingDir.trim()) { + const hint: Message = { + id: 'hint-' + Date.now(), + session_id: '', + role: 'assistant', + content: '**Please select a project directory first.** Use the folder picker in the toolbar below to choose a working directory before sending a message.', + created_at: new Date().toISOString(), + token_usage: null, + }; + setMessages((prev) => [...prev, hint]); + return; + } + setIsStreaming(true); setStreamingContent(''); setToolUses([]); @@ -90,14 +104,12 @@ export default function NewChatPage() { let sessionId = ''; try { - // Create a new session with optional working directory + // Create a new session with working directory const createBody: Record = { title: content.slice(0, 50), mode, + working_directory: workingDir.trim(), }; - if (workingDir.trim()) { - createBody.working_directory = workingDir.trim(); - } const createRes = await fetch('/api/chat/sessions', { method: 'POST', diff --git a/src/components/layout/ChatListPanel.tsx b/src/components/layout/ChatListPanel.tsx index 4bc0b1d..4dd6bf4 100644 --- a/src/components/layout/ChatListPanel.tsx +++ b/src/components/layout/ChatListPanel.tsx @@ -53,6 +53,7 @@ function formatRelativeTime(dateStr: string): string { const COLLAPSED_PROJECTS_KEY = "codepilot:collapsed-projects"; function loadCollapsedProjects(): Set { + if (typeof window === 'undefined') return new Set(); try { const raw = localStorage.getItem(COLLAPSED_PROJECTS_KEY); if (raw) return new Set(JSON.parse(raw)); @@ -129,27 +130,45 @@ export function ChatListPanel({ open, width }: ChatListPanelProps) { const [creatingChat, setCreatingChat] = useState(false); const handleNewChat = useCallback(async () => { - const lastDir = localStorage.getItem("codepilot:last-working-directory"); + const lastDir = typeof window !== 'undefined' + ? localStorage.getItem("codepilot:last-working-directory") + : null; + if (!lastDir) { - router.push("/chat"); + // No saved directory — let user pick one + setFolderPickerOpen(true); return; } + + // Validate the saved directory still exists setCreatingChat(true); try { + const checkRes = await fetch( + `/api/files/browse?dir=${encodeURIComponent(lastDir)}` + ); + if (!checkRes.ok) { + // Directory is gone — clear stale value and prompt user + localStorage.removeItem("codepilot:last-working-directory"); + setFolderPickerOpen(true); + return; + } + const res = await fetch("/api/chat/sessions", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ working_directory: lastDir }), }); if (!res.ok) { - router.push("/chat"); + // Backend rejected it (e.g. INVALID_DIRECTORY) — prompt user + localStorage.removeItem("codepilot:last-working-directory"); + setFolderPickerOpen(true); return; } const data = await res.json(); router.push(`/chat/${data.session.id}`); window.dispatchEvent(new CustomEvent("session-created")); } catch { - router.push("/chat"); + setFolderPickerOpen(true); } finally { setCreatingChat(false); } diff --git a/src/lib/db.ts b/src/lib/db.ts index 2c93cd5..7e4af0a 100644 --- a/src/lib/db.ts +++ b/src/lib/db.ts @@ -229,7 +229,7 @@ export function createSession( const db = getDb(); const id = crypto.randomBytes(16).toString('hex'); const now = new Date().toISOString().replace('T', ' ').split('.')[0]; - const wd = workingDirectory || process.cwd(); + const wd = workingDirectory || ''; const projectName = path.basename(wd); db.prepare(