Prevent stale session overwrite on new tasks

This commit is contained in:
wuyang6
2026-06-12 16:51:17 +08:00
parent a2f39e7312
commit 384e92e961
4 changed files with 47 additions and 37 deletions
+34 -2
View File
@@ -2382,6 +2382,19 @@ def create_app(state: AgentState) -> FastAPI:
) or uuid4().hex ) or uuid4().hex
account_key = state._account_key(request.account_id) account_key = state._account_key(request.account_id)
prompt = request.prompt.strip() 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: if run_record is None:
run_record = state.run_manager.start( run_record = state.run_manager.start(
account_key, account_key,
@@ -2398,7 +2411,6 @@ def create_app(state: AgentState) -> FastAPI:
run_lock = state.run_lock_for(request.account_id, requested_session_id) run_lock = state.run_lock_for(request.account_id, requested_session_id)
agent = state.agent_for(request.account_id, requested_session_id) agent = state.agent_for(request.account_id, requested_session_id)
config = state.config_for(request.account_id) config = state.config_for(request.account_id)
session_directory = state.account_paths(request.account_id)['sessions']
jupyter_runtime = _jupyter_runtime_for_session( jupyter_runtime = _jupyter_runtime_for_session(
state, state,
request.account_id, request.account_id,
@@ -2658,6 +2670,18 @@ def create_app(state: AgentState) -> FastAPI:
) or uuid4().hex ) or uuid4().hex
account_key = state._account_key(request.account_id) account_key = state._account_key(request.account_id)
prompt = request.prompt.strip() 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) run_record = state.run_manager.start(account_key, requested_session_id, prompt)
state.run_state_store.start( state.run_state_store.start(
run_id=run_record.run_id, run_id=run_record.run_id,
@@ -2672,7 +2696,7 @@ def create_app(state: AgentState) -> FastAPI:
try: try:
agent = state.agent_for(request.account_id, requested_session_id) agent = state.agent_for(request.account_id, requested_session_id)
_save_in_progress_session( _save_in_progress_session(
directory=state.account_paths(request.account_id)['sessions'], directory=session_directory,
agent=agent, agent=agent,
session_id=requested_session_id, session_id=requested_session_id,
prompt=prompt, prompt=prompt,
@@ -3347,6 +3371,14 @@ def _save_in_progress_session(
return None 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: def _derive_initial_session_title(prompt: str) -> str | None:
# 第一条消息刚发出时先给侧边栏一个可读标题,后续再由模型摘要精修。 # 第一条消息刚发出时先给侧边栏一个可读标题,后续再由模型摘要精修。
stripped = _strip_session_context(prompt) stripped = _strip_session_context(prompt)
+1 -13
View File
@@ -102,7 +102,7 @@ export async function POST(req: Request) {
if (!account) return new Response("请先登录账号", { status: 401 }); if (!account) return new Response("请先登录账号", { status: 401 });
const { id, messages, resumeSessionId }: ChatRequestBody = await req.json(); const { id, messages, resumeSessionId }: ChatRequestBody = await req.json();
const resumeId = resumeSessionId ?? getLastSessionId(messages); const resumeId = resumeSessionId;
const sessionId = safeSessionId(resumeId ?? id ?? crypto.randomUUID()); const sessionId = safeSessionId(resumeId ?? id ?? crypto.randomUUID());
await ensureSessionDirectories(account.id, sessionId); await ensureSessionDirectories(account.id, sessionId);
const userPrompt = await getLastUserText(messages, account.id, sessionId); const userPrompt = await getLastUserText(messages, account.id, sessionId);
@@ -362,18 +362,6 @@ function renderSessionRuntimeContext(accountId: string, sessionId: string) {
].join("\n\n"); ].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( async function saveFilePart(
part: Extract<UIMessage["parts"][number], { type: "file" }>, part: Extract<UIMessage["parts"][number], { type: "file" }>,
accountId: string, accountId: string,
+5 -22
View File
@@ -30,6 +30,7 @@ import {
import { import {
ACTIVE_SESSION_CHANGED_EVENT, ACTIVE_SESSION_CHANGED_EVENT,
consumeFreshLocalId, consumeFreshLocalId,
createLocalSessionId,
readActiveSessionId, readActiveSessionId,
readPendingWorkspaceSessionId, readPendingWorkspaceSessionId,
writeActiveSessionId, writeActiveSessionId,
@@ -60,11 +61,7 @@ export const Assistant = ({ initialSessionId }: AssistantProps = {}) => {
const body = options.body as Record<string, unknown>; const body = options.body as Record<string, unknown>;
const messages = options.messages as UIMessage[]; const messages = options.messages as UIMessage[];
const selectedSessionId = const selectedSessionId =
readSessionIdFromUrl() ?? readSessionIdFromUrl() ?? readActiveSessionId() ?? null;
readActiveSessionId() ??
initialSessionId?.trim() ??
null;
const lastSessionId = getLastSessionId(messages);
const pendingWorkspaceSessionId = const pendingWorkspaceSessionId =
!selectedSessionId && messages.length <= 1 !selectedSessionId && messages.length <= 1
? readPendingWorkspaceSessionId() ? readPendingWorkspaceSessionId()
@@ -73,8 +70,7 @@ export const Assistant = ({ initialSessionId }: AssistantProps = {}) => {
const outgoingSessionId = const outgoingSessionId =
selectedResumeSessionId ?? selectedResumeSessionId ??
pendingWorkspaceSessionId ?? pendingWorkspaceSessionId ??
lastSessionId ?? createLocalSessionId();
options.id;
const lastUserMessage = [...messages] const lastUserMessage = [...messages]
.reverse() .reverse()
.find((message) => message.role === "user"); .find((message) => message.role === "user");
@@ -86,13 +82,12 @@ export const Assistant = ({ initialSessionId }: AssistantProps = {}) => {
...body, ...body,
id: outgoingSessionId, id: outgoingSessionId,
messages: lastUserMessage ? [lastUserMessage] : [], messages: lastUserMessage ? [lastUserMessage] : [],
resumeSessionId: resumeSessionId: selectedResumeSessionId ?? undefined,
selectedResumeSessionId ?? lastSessionId ?? undefined,
}, },
}; };
}, },
}), }),
[initialSessionId], [],
); );
const runtime = useChatRuntime({ const runtime = useChatRuntime({
transport, transport,
@@ -410,15 +405,3 @@ function ActivityDrawerTrigger() {
</Button> </Button>
); );
} }
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;
}
+7
View File
@@ -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( export function writePendingWorkspaceSessionId(
sessionId: string | null | undefined, sessionId: string | null | undefined,
) { ) {