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