fix: stop chat hang after misrouted themed article requests
Route themed page/article prompts to agent orchestration, reject them from portal direct chat, and finalize regular sessions to idle when a direct chat turn already completed. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -30,6 +30,8 @@ const OBVIOUS_AGENT_PATTERNS = [
|
||||
/MindSpace\/[^/\s]+\/public\/[^\s"'<>]+\.html/i,
|
||||
/(?:生成|创建|制作|做|写|设计|发布).{0,24}(?:H5|h5|HTML|html|网页|页面|活动页|宣传页|落地页|分享页)/u,
|
||||
/(?:H5|h5|HTML|html|网页|页面).{0,24}(?:生成|创建|制作|做|写|设计|发布)/u,
|
||||
/(?:写|做|生成|制作|设计).{0,16}主题(?:页面|文章)/u,
|
||||
/主题(?:页面|文章).{0,16}(?:写|做|生成|制作|设计)/u,
|
||||
];
|
||||
|
||||
const OBVIOUS_DIRECT_PATTERNS = [
|
||||
|
||||
@@ -36,6 +36,19 @@ test('classifyWithRules routes page generation to agent orchestration', () => {
|
||||
assert.equal(result.suggestedSkill, 'static-page-publish');
|
||||
});
|
||||
|
||||
test('classifyWithRules routes themed article requests to agent orchestration', () => {
|
||||
const result = classifyWithRules({
|
||||
text: '帮我写个主题文章',
|
||||
userMessage: {
|
||||
role: 'user',
|
||||
content: [{ type: 'text', text: '帮我写个主题文章' }],
|
||||
metadata: { displayText: '帮我写个主题文章' },
|
||||
},
|
||||
});
|
||||
assert.equal(result.route, CHAT_INTENT_ROUTE.AGENT);
|
||||
assert.equal(result.suggestedSkill, 'static-page-publish');
|
||||
});
|
||||
|
||||
test('classifyWithRules honors forceDeepReasoning bypass', () => {
|
||||
const result = classifyWithRules({
|
||||
text: '你好',
|
||||
|
||||
@@ -20,6 +20,8 @@ const TASK_EXECUTION_REQUIRED_PATTERNS = [
|
||||
/\b(file|page|html|docx|word|asset)\b.{0,40}\b(write|create|generate|publish|download|export|save|edit)\b/i,
|
||||
/(?:生成|创建|制作|做|写|设计|发布|导出|下载|保存|修改|编辑).{0,24}(?:页面|网页|HTML|html|H5|h5|文件|文档|Word|word|docx|下载页|公开页|分享页|落地页|活动页)/u,
|
||||
/(?:页面|网页|HTML|html|H5|h5|文件|文档|Word|word|docx|下载页|公开页|分享页|落地页|活动页).{0,24}(?:生成|创建|制作|做|写|设计|发布|导出|下载|保存|修改|编辑)/u,
|
||||
/(?:写|做|生成|制作|设计).{0,16}主题(?:页面|文章)/u,
|
||||
/主题(?:页面|文章).{0,16}(?:写|做|生成|制作|设计)/u,
|
||||
/MindSpace\/[^/\s]+\/public\/[^\s"'<>]+\.html/i,
|
||||
];
|
||||
|
||||
|
||||
@@ -205,6 +205,30 @@ test('direct chat rejects page generation tasks so the execution backend can han
|
||||
}), false);
|
||||
});
|
||||
|
||||
test('direct chat rejects themed article page tasks without explicit html path', () => {
|
||||
const service = createDirectChatService({
|
||||
enabled: true,
|
||||
userAuth: {},
|
||||
llmProviderService: {},
|
||||
sessionSnapshotService: {},
|
||||
});
|
||||
|
||||
assert.equal(service.canHandle({
|
||||
toolMode: 'chat',
|
||||
userMessage: {
|
||||
role: 'user',
|
||||
content: [{ type: 'text', text: '帮我写个主题文章' }],
|
||||
},
|
||||
}), false);
|
||||
assert.equal(service.explainCanHandle({
|
||||
toolMode: 'chat',
|
||||
userMessage: {
|
||||
role: 'user',
|
||||
content: [{ type: 'text', text: '帮我写个主题文章' }],
|
||||
},
|
||||
}).reason, 'task_execution_required');
|
||||
});
|
||||
|
||||
test('direct chat allows llm-routed replies on regular agent sessions', () => {
|
||||
const service = createDirectChatService({
|
||||
enabled: true,
|
||||
|
||||
@@ -82,6 +82,18 @@ function isDirectChatSessionId(sessionId?: string | null) {
|
||||
return typeof sessionId === 'string' && sessionId.startsWith(DIRECT_CHAT_SESSION_PREFIX);
|
||||
}
|
||||
|
||||
function sessionFinishedViaPortalDirectChat(messages: Message[], userMessage: Message) {
|
||||
const lastAssistant = [...messages].reverse().find((message) => message.role === 'assistant');
|
||||
if (lastAssistant?.metadata?.source !== 'portal-direct-chat') return false;
|
||||
const userText = getDisplayText(userMessage).trim();
|
||||
const userId = userMessage.id;
|
||||
return messages.some(
|
||||
(message) =>
|
||||
message.role === 'user' &&
|
||||
(message.id === userId || getDisplayText(message).trim() === userText),
|
||||
);
|
||||
}
|
||||
|
||||
async function waitForAgentRun(runId: string): Promise<AgentRun> {
|
||||
return await waitForAgentRunWithDirectChatPreview(runId, {});
|
||||
}
|
||||
@@ -1344,8 +1356,33 @@ export function useTKMindChat(
|
||||
activeRequestId.current = null;
|
||||
setChatState('idle');
|
||||
} else {
|
||||
let finishedViaPortalDirectChat = false;
|
||||
try {
|
||||
const detail = await loadSessionDetail(activeSessionId);
|
||||
if (submitToken === connectTokenRef.current) {
|
||||
messagesRef.current = mergeConversationSnapshot(
|
||||
messagesRef.current,
|
||||
detail.messages,
|
||||
);
|
||||
messageHistoryLoadedCountRef.current = messagesRef.current.length;
|
||||
setMessages(messagesRef.current);
|
||||
setSession(detail.session);
|
||||
finishedViaPortalDirectChat = sessionFinishedViaPortalDirectChat(
|
||||
detail.messages,
|
||||
userMessage,
|
||||
);
|
||||
}
|
||||
} catch {
|
||||
// Fall through to agent streaming when the snapshot is not ready yet.
|
||||
}
|
||||
subscribeToSession(activeSessionId);
|
||||
setChatState('streaming');
|
||||
if (finishedViaPortalDirectChat) {
|
||||
clearActiveRequestMissingTimer();
|
||||
activeRequestId.current = null;
|
||||
setChatState('idle');
|
||||
} else {
|
||||
setChatState('streaming');
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
if (activeSessionId && isAmbiguousReplySubmitError(err)) {
|
||||
|
||||
Reference in New Issue
Block a user