diff --git a/backend/api/server.py b/backend/api/server.py index 96747e8..904d3e5 100644 --- a/backend/api/server.py +++ b/backend/api/server.py @@ -2382,6 +2382,19 @@ def create_app(state: AgentState) -> FastAPI: ) or uuid4().hex account_key = state._account_key(request.account_id) prompt = request.prompt.strip() + session_directory = state.account_paths(request.account_id)['sessions'] + if ( + request.resume_session_id is None + and run_record is None + and _agent_session_exists(session_directory, requested_session_id) + ): + raise HTTPException( + status_code=409, + detail=( + 'Session already exists; pass resume_session_id to continue ' + 'or create a fresh session_id.' + ), + ) if run_record is None: run_record = state.run_manager.start( account_key, @@ -2398,7 +2411,6 @@ def create_app(state: AgentState) -> FastAPI: run_lock = state.run_lock_for(request.account_id, requested_session_id) agent = state.agent_for(request.account_id, requested_session_id) config = state.config_for(request.account_id) - session_directory = state.account_paths(request.account_id)['sessions'] jupyter_runtime = _jupyter_runtime_for_session( state, request.account_id, @@ -2658,6 +2670,18 @@ def create_app(state: AgentState) -> FastAPI: ) or uuid4().hex account_key = state._account_key(request.account_id) prompt = request.prompt.strip() + session_directory = state.account_paths(request.account_id)['sessions'] + if ( + request.resume_session_id is None + and _agent_session_exists(session_directory, requested_session_id) + ): + raise HTTPException( + status_code=409, + detail=( + 'Session already exists; pass resume_session_id to continue ' + 'or create a fresh session_id.' + ), + ) run_record = state.run_manager.start(account_key, requested_session_id, prompt) state.run_state_store.start( run_id=run_record.run_id, @@ -2672,7 +2696,7 @@ def create_app(state: AgentState) -> FastAPI: try: agent = state.agent_for(request.account_id, requested_session_id) _save_in_progress_session( - directory=state.account_paths(request.account_id)['sessions'], + directory=session_directory, agent=agent, session_id=requested_session_id, prompt=prompt, @@ -3347,6 +3371,14 @@ def _save_in_progress_session( return None +def _agent_session_exists(directory: Path, session_id: str) -> bool: + try: + load_agent_session(session_id, directory=directory) + except (FileNotFoundError, OSError, json.JSONDecodeError): + return False + return True + + def _derive_initial_session_title(prompt: str) -> str | None: # 第一条消息刚发出时先给侧边栏一个可读标题,后续再由模型摘要精修。 stripped = _strip_session_context(prompt) diff --git a/frontend/app/app/api/chat/route.ts b/frontend/app/app/api/chat/route.ts index d3048e0..0435e5a 100644 --- a/frontend/app/app/api/chat/route.ts +++ b/frontend/app/app/api/chat/route.ts @@ -102,7 +102,7 @@ export async function POST(req: Request) { if (!account) return new Response("请先登录账号", { status: 401 }); const { id, messages, resumeSessionId }: ChatRequestBody = await req.json(); - const resumeId = resumeSessionId ?? getLastSessionId(messages); + const resumeId = resumeSessionId; const sessionId = safeSessionId(resumeId ?? id ?? crypto.randomUUID()); await ensureSessionDirectories(account.id, sessionId); const userPrompt = await getLastUserText(messages, account.id, sessionId); @@ -362,18 +362,6 @@ function renderSessionRuntimeContext(accountId: string, sessionId: string) { ].join("\n\n"); } -function getLastSessionId(messages: UIMessage[]) { - for (const message of [...messages].reverse()) { - if (message.role !== "assistant") continue; - const metadata = message.metadata; - if (metadata && typeof metadata === "object" && "sessionId" in metadata) { - const sessionId = (metadata as { sessionId?: unknown }).sessionId; - if (typeof sessionId === "string" && sessionId) return sessionId; - } - } - return undefined; -} - async function saveFilePart( part: Extract, accountId: string, diff --git a/frontend/app/app/assistant.tsx b/frontend/app/app/assistant.tsx index 182190d..633f5af 100644 --- a/frontend/app/app/assistant.tsx +++ b/frontend/app/app/assistant.tsx @@ -30,6 +30,7 @@ import { import { ACTIVE_SESSION_CHANGED_EVENT, consumeFreshLocalId, + createLocalSessionId, readActiveSessionId, readPendingWorkspaceSessionId, writeActiveSessionId, @@ -60,11 +61,7 @@ export const Assistant = ({ initialSessionId }: AssistantProps = {}) => { const body = options.body as Record; const messages = options.messages as UIMessage[]; const selectedSessionId = - readSessionIdFromUrl() ?? - readActiveSessionId() ?? - initialSessionId?.trim() ?? - null; - const lastSessionId = getLastSessionId(messages); + readSessionIdFromUrl() ?? readActiveSessionId() ?? null; const pendingWorkspaceSessionId = !selectedSessionId && messages.length <= 1 ? readPendingWorkspaceSessionId() @@ -73,8 +70,7 @@ export const Assistant = ({ initialSessionId }: AssistantProps = {}) => { const outgoingSessionId = selectedResumeSessionId ?? pendingWorkspaceSessionId ?? - lastSessionId ?? - options.id; + createLocalSessionId(); const lastUserMessage = [...messages] .reverse() .find((message) => message.role === "user"); @@ -86,13 +82,12 @@ export const Assistant = ({ initialSessionId }: AssistantProps = {}) => { ...body, id: outgoingSessionId, messages: lastUserMessage ? [lastUserMessage] : [], - resumeSessionId: - selectedResumeSessionId ?? lastSessionId ?? undefined, + resumeSessionId: selectedResumeSessionId ?? undefined, }, }; }, }), - [initialSessionId], + [], ); const runtime = useChatRuntime({ transport, @@ -410,15 +405,3 @@ function ActivityDrawerTrigger() { ); } - -function getLastSessionId(messages: UIMessage[]) { - for (const message of [...messages].reverse()) { - if (message.role !== "assistant") continue; - const metadata = message.metadata; - if (metadata && typeof metadata === "object" && "sessionId" in metadata) { - const sessionId = (metadata as { sessionId?: unknown }).sessionId; - if (typeof sessionId === "string" && sessionId) return sessionId; - } - } - return undefined; -} diff --git a/frontend/app/lib/claw-active-session.ts b/frontend/app/lib/claw-active-session.ts index 7345325..9588c3a 100644 --- a/frontend/app/lib/claw-active-session.ts +++ b/frontend/app/lib/claw-active-session.ts @@ -50,6 +50,13 @@ export function clearActiveSessionId() { ); } +export function createLocalSessionId() { + if (typeof crypto !== "undefined" && "randomUUID" in crypto) { + return `__LOCALID_${crypto.randomUUID().replace(/-/g, "").slice(0, 8)}`; + } + return `__LOCALID_${Math.random().toString(36).slice(2, 10)}`; +} + export function writePendingWorkspaceSessionId( sessionId: string | null | undefined, ) {