fix(router): route casual chat to DeepSeek and reserve Cursor for page tasks

Default unmatched conversation to direct_chat, stop blanket Agent session continuation and generic Agent→Cursor promotion; fix WeChat page retry userPublishDir scope and simplify ChatPanel controls.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
john
2026-08-26 21:09:44 +08:00
parent 1ec971515b
commit 9ac9f96167
9 changed files with 853 additions and 215 deletions
+62 -42
View File
@@ -443,14 +443,22 @@ test('classifyWithRules keeps agent session continuation even when llm router fl
assert.match(continued.reason, /Agent 会话确认|延续已有 Agent 会话/);
});
test('classifyWithRules keeps empty pre-created sessions eligible for llm routing', () => {
test('classifyWithRules keeps agent session confirmation on agent path', () => {
const continued = classifyWithRules({
text: '继续聊聊',
sessionId: '20260704_2',
sessionMessageCount: 2,
});
assert.equal(continued.route, CHAT_INTENT_ROUTE.AGENT);
assert.equal(continued.reason, '延续已有 Agent 会话');
assert.equal(continued.route, CHAT_INTENT_ROUTE.DIRECT_CHAT);
assert.match(continued.reason, /轻量聊天/);
const confirmed = classifyWithRules({
text: '可以',
sessionId: '20260704_2',
sessionMessageCount: 2,
});
assert.equal(confirmed.route, CHAT_INTENT_ROUTE.AGENT);
assert.match(confirmed.reason, /Agent 会话确认/);
const fresh = classifyWithRules({
text: '我是 John,你对我的记忆知道多少?',
@@ -461,7 +469,7 @@ test('classifyWithRules keeps empty pre-created sessions eligible for llm routin
assert.match(fresh.reason, /记忆/);
});
test('classifyWithRules keeps memory recall on agent when session already active', () => {
test('classifyWithRules keeps memory recall on direct chat even when session already active', () => {
const result = classifyWithRules({
text: '你记得我说想去哪儿吗',
sessionId: '20260704_9',
@@ -472,8 +480,23 @@ test('classifyWithRules keeps memory recall on agent when session already active
metadata: { displayText: '你记得我说想去哪儿吗' },
},
});
assert.equal(result.route, CHAT_INTENT_ROUTE.AGENT);
assert.match(result.reason, /延续已有 Agent 会话/);
assert.equal(result.route, CHAT_INTENT_ROUTE.DIRECT_CHAT);
assert.match(result.reason, /记忆|历史对话/);
});
test('classifyWithRules keeps casual FAQ chat on direct chat even when session already active', () => {
const result = classifyWithRules({
text: '你觉得人都是为了什么而活',
sessionId: '20260826_39',
sessionMessageCount: 4,
userMessage: {
role: 'user',
content: [{ type: 'text', text: '你觉得人都是为了什么而活' }],
metadata: { displayText: '你觉得人都是为了什么而活' },
},
});
assert.equal(result.route, CHAT_INTENT_ROUTE.DIRECT_CHAT);
assert.match(result.reason, /闲聊|FAQ/);
});
test('classifyWithRules bypasses llm router when user selected summarize skill', async () => {
@@ -559,7 +582,7 @@ test('classifyWithRules bypasses llm router for explicit web skill prompt', asyn
assert.equal(llmCalls.length, 0);
});
test('createChatIntentRouter keeps continued sessions on agent without llm router', async () => {
test('createChatIntentRouter keeps continued sessions on direct chat for general conversation', async () => {
const llmCalls = [];
const router = createChatIntentRouter({
enabled: true,
@@ -590,12 +613,12 @@ test('createChatIntentRouter keeps continued sessions on agent without llm route
});
assert.equal(llmCalls.length, 0);
assert.equal(result.route, CHAT_INTENT_ROUTE.AGENT);
assert.equal(result.route, CHAT_INTENT_ROUTE.DIRECT_CHAT);
assert.equal(result.source, 'rule');
assert.match(result.reason, /延续已有 Agent 会话/);
assert.match(result.reason, /轻量聊天/);
});
test('createChatIntentRouter uses agent fallback for general conversational analysis when llm router disabled', async () => {
test('createChatIntentRouter uses direct chat fallback for general conversational analysis when llm router disabled', async () => {
const llmCalls = [];
const resolveCalls = [];
const router = createChatIntentRouter({
@@ -630,8 +653,8 @@ test('createChatIntentRouter uses agent fallback for general conversational anal
grantedSkills: ['web'],
});
assert.equal(result.route, CHAT_INTENT_ROUTE.AGENT);
assert.equal(result.source, 'fallback');
assert.equal(result.route, CHAT_INTENT_ROUTE.DIRECT_CHAT);
assert.equal(result.source, 'rule');
assert.equal(resolveCalls.length, 0);
assert.equal(llmCalls.length, 0);
});
@@ -775,11 +798,11 @@ test('createManagedChatIntentRouter hot-loads admin config and activates llm rou
},
});
assert.equal(result.route, CHAT_INTENT_ROUTE.DIRECT_CHAT);
assert.equal(result.source, 'llm');
assert.equal(llmCalls.length, 1);
assert.equal(result.source, 'rule');
assert.equal(llmCalls.length, 0);
});
test('createChatIntentRouter rejects low-confidence llm direct_chat and falls back to agent', async () => {
test('createChatIntentRouter rejects low-confidence llm direct_chat and keeps rule default', async () => {
const llmCalls = [];
const router = createChatIntentRouter({
enabled: true,
@@ -809,12 +832,12 @@ test('createChatIntentRouter rejects low-confidence llm direct_chat and falls ba
},
});
assert.equal(result.route, CHAT_INTENT_ROUTE.AGENT);
assert.equal(result.source, 'fallback');
assert.equal(llmCalls.length, 1);
assert.equal(result.route, CHAT_INTENT_ROUTE.DIRECT_CHAT);
assert.equal(result.source, 'rule');
assert.equal(llmCalls.length, 0);
});
test('createChatIntentRouter falls back to agent when llm router fails', async () => {
test('createChatIntentRouter keeps direct chat when llm router fails', async () => {
const llmCalls = [];
const router = createChatIntentRouter({
enabled: true,
@@ -835,12 +858,12 @@ test('createChatIntentRouter falls back to agent when llm router fails', async (
grantedSkills: ['web'],
});
assert.equal(result.route, CHAT_INTENT_ROUTE.AGENT);
assert.equal(result.source, 'fallback');
assert.equal(llmCalls.length, 1);
assert.equal(result.route, CHAT_INTENT_ROUTE.DIRECT_CHAT);
assert.equal(result.source, 'rule');
assert.equal(llmCalls.length, 0);
});
test('createChatIntentRouter uses agent fallback when llm router disabled', async () => {
test('createChatIntentRouter uses direct chat when llm router disabled', async () => {
const llmCalls = [];
const router = createChatIntentRouter({
enabled: false,
@@ -860,8 +883,8 @@ test('createChatIntentRouter uses agent fallback when llm router disabled', asyn
},
});
assert.equal(result.route, CHAT_INTENT_ROUTE.AGENT);
assert.equal(result.source, 'fallback');
assert.equal(result.route, CHAT_INTENT_ROUTE.DIRECT_CHAT);
assert.equal(result.source, 'rule');
assert.equal(llmCalls.length, 0);
});
@@ -1176,13 +1199,13 @@ test('createChatIntentRouter fast-paths world cup without calling router LLM', a
assert.equal(result.source, 'rule');
});
test('resolveChatIntentRouterPolicy coerces direct_chat fallback to agent', () => {
test('resolveChatIntentRouterPolicy defaults fallback route to direct chat', () => {
const policy = resolveChatIntentRouterPolicy({
env: {
MEMIND_CHAT_ROUTER_FALLBACK_ROUTE: 'direct_chat',
},
});
assert.equal(policy.fallbackRoute, CHAT_INTENT_ROUTE.AGENT);
assert.equal(policy.fallbackRoute, CHAT_INTENT_ROUTE.DIRECT_CHAT);
});
test('coerceRealtimeWebSkill replaces search with web for world cup query', () => {
@@ -1259,7 +1282,7 @@ test('createChatIntentRouter coerces LLM search route to web for realtime query'
assert.match(result.agentBrief, /web_search/);
});
test('createChatIntentRouter timeout fallback prefers agent even when policy requests direct_chat', async () => {
test('createChatIntentRouter timeout fallback keeps direct chat default', async () => {
const router = createChatIntentRouter({
enabled: true,
timeoutMs: 40,
@@ -1280,8 +1303,8 @@ test('createChatIntentRouter timeout fallback prefers agent even when policy req
},
});
assert.equal(result.route, CHAT_INTENT_ROUTE.AGENT);
assert.equal(result.source, 'fallback');
assert.equal(result.route, CHAT_INTENT_ROUTE.DIRECT_CHAT);
assert.equal(result.source, 'rule');
});
test('agent memory shadow resolve is independently gated and never marked for injection', async () => {
@@ -1592,14 +1615,14 @@ test('createChatIntentRouter limits llm routing to canary users', async () => {
},
});
assert.equal(control.route, CHAT_INTENT_ROUTE.AGENT);
assert.equal(control.source, 'fallback');
assert.equal(control.route, CHAT_INTENT_ROUTE.DIRECT_CHAT);
assert.equal(control.source, 'rule');
assert.equal(canary.route, CHAT_INTENT_ROUTE.DIRECT_CHAT);
assert.equal(canary.source, 'llm');
assert.equal(llmCalls.length, 1);
assert.equal(canary.source, 'rule');
assert.equal(llmCalls.length, 0);
});
test('createChatIntentRouter shadow mode logs llm suggestion without changing route', async () => {
test('createChatIntentRouter shadow mode skips llm shadow when rule default matches', async () => {
const lines = [];
const logger = { warn(line) { lines.push(line); }, info(line) { lines.push(line); } };
const router = createChatIntentRouter({
@@ -1631,13 +1654,10 @@ test('createChatIntentRouter shadow mode logs llm suggestion without changing ro
},
});
assert.equal(result.route, CHAT_INTENT_ROUTE.AGENT);
assert.equal(result.source, 'fallback');
assert.equal(result.llmShadow?.route, CHAT_INTENT_ROUTE.DIRECT_CHAT);
assert.equal(result.llmShadow?.wouldChangeRoute, true);
assert.equal(lines.length, 1);
assert.match(lines[0], /\[chat-llm-router-shadow\]/);
assert.match(lines[0], /wouldChangeRoute":true/);
assert.equal(result.route, CHAT_INTENT_ROUTE.DIRECT_CHAT);
assert.equal(result.source, 'rule');
assert.equal(result.llmShadow, undefined);
assert.equal(lines.length, 0);
});
test('logChatLlmRouterShadow emits structured payload', () => {