diff --git a/static/src/app/methods/resources.ts b/static/src/app/methods/resources.ts index 8559c839..66d08903 100644 --- a/static/src/app/methods/resources.ts +++ b/static/src/app/methods/resources.ts @@ -132,10 +132,15 @@ export const resourceMethods = { // - 非空对话:以对话 meta 为准(enterConversation bootstrap 恢复), // status 快照不得覆盖(terminal 状态可能滞后于对话加载) // - 空对话/首页:terminal 状态即权威,从 status 同步 + // - 例外:显式新建路由(/new、/multiagent/new)上 terminal 状态属于上一个 + // 对话的残留上下文,不同步(个性化默认值是这里的权威,见 loadInitialData) const convId = this.currentConversationId; const hasConversation = typeof convId === 'string' && convId.length > 0 && !convId.startsWith('temp_'); - if (!hasConversation) { + const onExplicitNewRoute = + typeof this.isExplicitNewConversationRoute === 'function' && + this.isExplicitNewConversationRoute(); + if (!hasConversation && !onExplicitNewRoute) { if (status && typeof status.thinking_mode !== 'undefined') { this.thinkingMode = !!status.thinking_mode; } diff --git a/static/src/app/methods/ui/socket.ts b/static/src/app/methods/ui/socket.ts index c3090049..f12f26f4 100644 --- a/static/src/app/methods/ui/socket.ts +++ b/static/src/app/methods/ui/socket.ts @@ -266,12 +266,22 @@ export const socketMethods = { // 仅在“无当前对话”的新会话入口应用默认模型/模式; // 对于已存在对话(含刷新恢复/URL指定对话),必须保留对话自身设置。 + // 注意:显式新建路由(/new、/multiagent/new)上,后端的“当前对话” + // 只是上一个对话的残留上下文,不等于本页要创建新对话的意图, + // 因此此时也必须应用个性化默认值(与“新建空对话”按钮行为一致)。 const statusConversationIdForDefaults = statusData?.conversation?.current_id; const hasActiveConversation = typeof statusConversationIdForDefaults === 'string' && statusConversationIdForDefaults.length > 0 && !statusConversationIdForDefaults.startsWith('temp_'); - if (personalizationStore.loaded && !hasActiveConversation && !this.currentConversationId) { + const isExplicitNewRouteForDefaults = + typeof this.isExplicitNewConversationRoute === 'function' && + this.isExplicitNewConversationRoute(); + if ( + personalizationStore.loaded && + !this.currentConversationId && + (!hasActiveConversation || isExplicitNewRouteForDefaults) + ) { const defaultRunMode = personalizationStore.form.default_run_mode; const defaultModel = personalizationStore.form.default_model; const defaultReasoningEffort = personalizationStore.form.default_reasoning_effort; @@ -324,15 +334,16 @@ export const socketMethods = { const runningConversationId = await this.getRunningTaskConversationId(); const statusConversationId = statusData.conversation && statusData.conversation.current_id; const resumeConversationId = statusConversationId || runningConversationId; - const shouldResumeOnNewRoute = isExplicitNewRoute && !!runningConversationId; const currentPath = window.location.pathname.replace(/^\/+/, ''); const isMultiAgentNewRoute = currentPath === 'multiagent/new' || currentPath === 'multiagent'; + // 显式新建对话路由(/new、/multiagent/new)永不自动恢复运行中的对话: + // 用户主动点击"新建对话"就是明确要空白页,运行中任务可从侧边栏随时切回。 if ( resumeConversationId && !this.currentConversationId && !isMultiAgentNewRoute && - (!isExplicitNewRoute || shouldResumeOnNewRoute) + !isExplicitNewRoute ) { this.skipConversationHistoryReload = true; // 首次从状态恢复对话时,避免 socket 的 conversation_loaded 再次触发历史加载 diff --git a/static/src/composables/useLegacySocket.ts b/static/src/composables/useLegacySocket.ts index 9a9bc2fd..635bd0ed 100644 --- a/static/src/composables/useLegacySocket.ts +++ b/static/src/composables/useLegacySocket.ts @@ -772,8 +772,16 @@ export async function initializeLegacySocket(ctx: any) { // 初始化期间不修改 currentConversationId,避免与 bootstrapRoute 冲突 if (ctx.initialRouteResolved) { - ctx.currentConversationId = data.conversation_id; - ctx.currentConversationTitle = data.title || ''; + // 用户停留在显式新建对话页(/new、/multiagent/new)时,不被其他对话的 + // 广播事件(如运行中任务的标题生成)拽走视图,仅同步列表数据。 + const stayOnNewRoute = + !data.cleared && + typeof ctx.isExplicitNewConversationRoute === 'function' && + ctx.isExplicitNewConversationRoute(); + if (!stayOnNewRoute) { + ctx.currentConversationId = data.conversation_id; + ctx.currentConversationTitle = data.title || ''; + } // 更新对话列表中的标题 if (data.title && Array.isArray(ctx.conversations)) { @@ -897,18 +905,26 @@ export async function initializeLegacySocket(ctx: any) { // 监听状态更新事件 ctx.socket.on('status_update', (status) => { ctx.applyStatusSnapshot(status); + // 显式新建路由(/new、/multiagent/new):status 快照反映的是后端 terminal + // 残留的旧对话上下文,既不能把它设成当前对话(防跳回),也不能让它 + // 覆盖刚应用的个性化默认运行模式。 + const onExplicitNewRoute = + typeof ctx.isExplicitNewConversationRoute === 'function' && + ctx.isExplicitNewConversationRoute(); // 只有在初始化完成后,才允许 status_update 修改 currentConversationId // 避免与 bootstrapRoute 冲突 if (status.conversation && status.conversation.current_id) { - if (ctx.initialRouteResolved && !ctx.currentConversationId) { + if (ctx.initialRouteResolved && !ctx.currentConversationId && !onExplicitNewRoute) { // 初始化完成且当前没有对话ID,才设置 ctx.currentConversationId = status.conversation.current_id; } } - if (typeof status.run_mode === 'string') { - ctx.runMode = status.run_mode; - } else if (typeof status.thinking_mode !== 'undefined') { - ctx.runMode = status.thinking_mode ? 'thinking' : 'fast'; + if (!onExplicitNewRoute) { + if (typeof status.run_mode === 'string') { + ctx.runMode = status.run_mode; + } else if (typeof status.thinking_mode !== 'undefined') { + ctx.runMode = status.thinking_mode ? 'thinking' : 'fast'; + } } }); @@ -1068,7 +1084,12 @@ export async function initializeLegacySocket(ctx: any) { }); // AI消息开始(已废弃,改用 REST API 轮询) - ctx.socket.on('ai_message_start', () => { + ctx.socket.on('ai_message_start', (data) => { + // 用户级广播:只响应当前对话的 AI 消息开始事件。 + if (data?.conversation_id && data.conversation_id !== ctx.currentConversationId) { + socketLog('跳过ai_message_start(对话不匹配)', data.conversation_id); + return; + } // 只处理子智能体模式 if (ctx.waitingForSubAgent) { ctx.waitingForSubAgent = false; @@ -1600,6 +1621,11 @@ export async function initializeLegacySocket(ctx: any) { // 停止请求确认 ctx.socket.on('stop_requested', (data) => { + // 用户级广播:只清理当前对话的 pending 工具状态。 + if (data?.conversation_id && data.conversation_id !== ctx.currentConversationId) { + socketLog('跳过stop_requested(对话不匹配)', data.conversation_id); + return; + } socketLog('停止请求已接收:', data.message); // 可以显示提示信息 try { @@ -1613,6 +1639,13 @@ export async function initializeLegacySocket(ctx: any) { // 任务停止 ctx.socket.on('task_stopped', (data) => { + // 多对话并行运行:任务生命周期事件是用户级广播,只响应当前对话的, + // 否则其他对话的停止会误清本对话的运行态(侧边栏标识由 + // refreshRunningWorkspaceTasks 独立轮询维护,不受影响)。 + if (data?.conversation_id && data.conversation_id !== ctx.currentConversationId) { + socketLog('跳过task_stopped(对话不匹配)', data.conversation_id); + return; + } socketLog('任务已停止:', data.message); const hasRunningSubAgents = !!data?.has_running_sub_agents; const hasRunningBackgroundCommands = !!data?.has_running_background_commands; @@ -1634,6 +1667,11 @@ export async function initializeLegacySocket(ctx: any) { // 任务完成(重点:更新Token统计) ctx.socket.on('task_complete', (data) => { + // 同 task_stopped:用户级广播,只响应当前对话的任务完成事件。 + if (data?.conversation_id && data.conversation_id !== ctx.currentConversationId) { + socketLog('跳过task_complete(对话不匹配)', data.conversation_id); + return; + } socketLog('任务完成', data); const hasRunningMultiAgent = !!data?.has_running_multi_agent; @@ -1660,6 +1698,12 @@ export async function initializeLegacySocket(ctx: any) { // 子智能体等待状态 ctx.socket.on('sub_agent_waiting', (data) => { + // 用户级广播:只响应当前对话的等待事件,避免把其他对话的 + // waitingForSubAgent 标志安到本对话上(会阻塞 resetAllStates)。 + if (data?.conversation_id && data.conversation_id !== ctx.currentConversationId) { + socketLog('跳过sub_agent_waiting(对话不匹配)', data.conversation_id); + return; + } socketLog('等待子智能体完成:', data); // 多智能体模式下,子智能体 idle/running 是常态,不阻塞主对话输入区 @@ -1711,6 +1755,11 @@ export async function initializeLegacySocket(ctx: any) { if (!data || !data.content) { return; } + // 用户级广播:其他对话的系统提示不应串进当前对话视图。 + if (data.conversation_id && data.conversation_id !== ctx.currentConversationId) { + socketLog('跳过system_message(对话不匹配)', data.conversation_id); + return; + } ctx.appendSystemAction(data.content); });