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:
@@ -0,0 +1,252 @@
|
||||
import {
|
||||
AIDER_DEVELOPMENT_SKILL_NAME,
|
||||
EXCEL_ANALYST_SKILL_NAME,
|
||||
PAGE_DATA_COLLECT_SKILL_NAME,
|
||||
extractAiderDevelopmentTask,
|
||||
isExcelAnalysisIntent,
|
||||
isGenericPageGenerationRequest,
|
||||
isPageDataDevIntent,
|
||||
isPageDataIntent,
|
||||
isPageGenerationIntent,
|
||||
} from './chat-skills.mjs';
|
||||
import {
|
||||
cursorAgentTasksDefaultEnabled,
|
||||
cursorPageTasksDefaultEnabled,
|
||||
resolvePreferredCodeExecutor,
|
||||
} from './cursor-agent-launch.mjs';
|
||||
|
||||
const PUBLISH_SKILL_NAME = 'static-page-publish';
|
||||
|
||||
const PAGE_CURSOR_TASK_TYPE = 'h5_chat_code_task';
|
||||
|
||||
const CURSOR_TASK_KIND = {
|
||||
PAGE_GENERATION: 'page_generation',
|
||||
PAGE_DATA: 'page_data',
|
||||
EXCEL_ANALYSIS: 'excel_analysis',
|
||||
};
|
||||
|
||||
function selectedChatSkill(userMessage) {
|
||||
const metadata = userMessage?.metadata;
|
||||
const runMetadata = metadata?.memindRun ?? metadata?.agentRun ?? {};
|
||||
return String(runMetadata.selectedChatSkill ?? '').trim();
|
||||
}
|
||||
|
||||
export function resolveCursorTaskKind(taskText, skill = '') {
|
||||
const normalized = String(taskText ?? '').trim();
|
||||
if (!normalized || isPageDataDevIntent(normalized)) return null;
|
||||
|
||||
const selectedSkill = String(skill ?? '').trim();
|
||||
if (
|
||||
selectedSkill === PAGE_DATA_COLLECT_SKILL_NAME
|
||||
|| isPageDataIntent(normalized)
|
||||
) {
|
||||
return CURSOR_TASK_KIND.PAGE_DATA;
|
||||
}
|
||||
if (
|
||||
selectedSkill === EXCEL_ANALYST_SKILL_NAME
|
||||
|| isExcelAnalysisIntent(normalized)
|
||||
) {
|
||||
return CURSOR_TASK_KIND.EXCEL_ANALYSIS;
|
||||
}
|
||||
if (
|
||||
selectedSkill === PUBLISH_SKILL_NAME
|
||||
|| isPageGenerationIntent(normalized)
|
||||
) {
|
||||
if (isGenericPageGenerationRequest(normalized)) return null;
|
||||
return CURSOR_TASK_KIND.PAGE_GENERATION;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export function isPageCursorDefaultCandidate(userMessage, {
|
||||
rawToolMode = 'chat',
|
||||
env = process.env,
|
||||
} = {}) {
|
||||
if (!cursorPageTasksDefaultEnabled(env)) return false;
|
||||
if (String(rawToolMode ?? '').trim().toLowerCase() === 'code') return false;
|
||||
|
||||
const skill = selectedChatSkill(userMessage);
|
||||
if (skill === AIDER_DEVELOPMENT_SKILL_NAME) return false;
|
||||
|
||||
const runMetadata = userMessage?.metadata?.memindRun ?? userMessage?.metadata?.agentRun ?? {};
|
||||
if (runMetadata.pageTemplateSkill || runMetadata.pageTemplateStrict) return false;
|
||||
|
||||
const taskText = extractAiderDevelopmentTask(userMessage);
|
||||
if (!taskText) return false;
|
||||
|
||||
return resolveCursorTaskKind(taskText, skill) !== null;
|
||||
}
|
||||
|
||||
function buildCursorPageInstruction(taskText) {
|
||||
return [
|
||||
taskText,
|
||||
'',
|
||||
'[Memind page task via TKMind 智趣 executor]',
|
||||
'在当前用户 MindSpace 工作区生成或更新 public/*.html 页面。',
|
||||
'完成后确保页面可通过 /MindSpace/<userId>/public/<file>.html 访问。',
|
||||
'不要只回复文字,必须实际落盘 HTML 文件。',
|
||||
].join('\n');
|
||||
}
|
||||
|
||||
function buildCursorPageDataInstruction(taskText) {
|
||||
return [
|
||||
taskText,
|
||||
'',
|
||||
'[Memind Page Data survey task via TKMind 智趣 executor]',
|
||||
'在当前用户 MindSpace 工作区完成可提交、可持久化的问卷/数据收集与结果分析页面。',
|
||||
'必须先阅读 docs/page-data-api-usage.md 与 docs/examples/page-data-survey-test.md。',
|
||||
'需要:1) 创建/更新 public/*.html 问卷页(含 MindSpacePageData.createClient);',
|
||||
'2) 按需创建后台分析页;3) 注册 dataset 与 page policy(可参考 scripts/setup-page-data-survey-demo.mjs);',
|
||||
'4) 数据必须持久化到 PG(通过 Page Data API),不要旁路自建 API。',
|
||||
'完成后确保页面可通过 /MindSpace/<userId>/public/<file>.html 访问。',
|
||||
'不要只回复文字,必须实际落盘 HTML 与相关配置文件。',
|
||||
].join('\n');
|
||||
}
|
||||
|
||||
function buildCursorExcelInstruction(taskText) {
|
||||
return [
|
||||
taskText,
|
||||
'',
|
||||
'[Memind Excel analysis task via TKMind 智趣 executor]',
|
||||
'在当前用户 MindSpace 工作区分析用户上传的 Excel/CSV 等表格文件。',
|
||||
'先在工作区 attachments/、uploads/ 或用户消息提及的路径定位附件文件,读取并分析数据。',
|
||||
'输出分析结论,并生成可访问的结果页面 public/*.html 或分析报告文件。',
|
||||
'不要只回复文字,必须实际读取文件并产出可交付结果。',
|
||||
].join('\n');
|
||||
}
|
||||
|
||||
function buildCursorTaskInstruction(taskText, taskKind) {
|
||||
switch (taskKind) {
|
||||
case CURSOR_TASK_KIND.PAGE_DATA:
|
||||
return buildCursorPageDataInstruction(taskText);
|
||||
case CURSOR_TASK_KIND.EXCEL_ANALYSIS:
|
||||
return buildCursorExcelInstruction(taskText);
|
||||
case CURSOR_TASK_KIND.PAGE_GENERATION:
|
||||
default:
|
||||
return buildCursorPageInstruction(taskText);
|
||||
}
|
||||
}
|
||||
|
||||
function resolveCursorTaskSkill(taskKind, runMetadataSkill = '') {
|
||||
const existing = String(runMetadataSkill ?? '').trim();
|
||||
if (existing && existing !== AIDER_DEVELOPMENT_SKILL_NAME) return existing;
|
||||
switch (taskKind) {
|
||||
case CURSOR_TASK_KIND.PAGE_DATA:
|
||||
return PAGE_DATA_COLLECT_SKILL_NAME;
|
||||
case CURSOR_TASK_KIND.EXCEL_ANALYSIS:
|
||||
return EXCEL_ANALYST_SKILL_NAME;
|
||||
case CURSOR_TASK_KIND.PAGE_GENERATION:
|
||||
default:
|
||||
return PUBLISH_SKILL_NAME;
|
||||
}
|
||||
}
|
||||
|
||||
function resolveCursorSuggestedDelivery(taskKind) {
|
||||
switch (taskKind) {
|
||||
case CURSOR_TASK_KIND.PAGE_DATA:
|
||||
return 'mindspace_page_data';
|
||||
case CURSOR_TASK_KIND.EXCEL_ANALYSIS:
|
||||
return 'mindspace_analysis_html';
|
||||
case CURSOR_TASK_KIND.PAGE_GENERATION:
|
||||
default:
|
||||
return 'mindspace_public_html';
|
||||
}
|
||||
}
|
||||
|
||||
function rewriteUserMessageForCursorTask(userMessage, taskText, taskKind) {
|
||||
const message = userMessage && typeof userMessage === 'object' && !Array.isArray(userMessage)
|
||||
? { ...userMessage }
|
||||
: { value: userMessage };
|
||||
const metadata = message.metadata && typeof message.metadata === 'object' && !Array.isArray(message.metadata)
|
||||
? { ...message.metadata }
|
||||
: {};
|
||||
const instruction = buildCursorTaskInstruction(taskText, taskKind);
|
||||
metadata.displayText = taskText;
|
||||
metadata.userVisible = true;
|
||||
const content = Array.isArray(message.content)
|
||||
? message.content.map((item) => (
|
||||
item?.type === 'text'
|
||||
? { ...item, text: instruction }
|
||||
: item
|
||||
))
|
||||
: [{ type: 'text', text: instruction }];
|
||||
return { ...message, metadata, content };
|
||||
}
|
||||
|
||||
export function enforcePageGenerationCursorRuntime(userMessage, {
|
||||
rawToolMode = 'chat',
|
||||
taskType = null,
|
||||
forceDeepReasoning = false,
|
||||
env = process.env,
|
||||
} = {}) {
|
||||
if (!isPageCursorDefaultCandidate(userMessage, { rawToolMode, env })) {
|
||||
return {
|
||||
userMessage,
|
||||
rawToolMode,
|
||||
taskType,
|
||||
forceDeepReasoning,
|
||||
requiredExecutor: null,
|
||||
};
|
||||
}
|
||||
|
||||
const taskText = extractAiderDevelopmentTask(userMessage);
|
||||
const skill = selectedChatSkill(userMessage);
|
||||
const taskKind = resolveCursorTaskKind(taskText, skill);
|
||||
if (!taskKind) {
|
||||
return {
|
||||
userMessage,
|
||||
rawToolMode,
|
||||
taskType,
|
||||
forceDeepReasoning,
|
||||
requiredExecutor: null,
|
||||
};
|
||||
}
|
||||
|
||||
const codeExecutor = resolvePreferredCodeExecutor(env);
|
||||
const message = rewriteUserMessageForCursorTask(userMessage, taskText, taskKind);
|
||||
const metadata = message.metadata && typeof message.metadata === 'object' && !Array.isArray(message.metadata)
|
||||
? { ...message.metadata }
|
||||
: {};
|
||||
const runMetadata = metadata.memindRun && typeof metadata.memindRun === 'object' && !Array.isArray(metadata.memindRun)
|
||||
? { ...metadata.memindRun }
|
||||
: {};
|
||||
|
||||
metadata.memindRun = {
|
||||
...runMetadata,
|
||||
executor: codeExecutor,
|
||||
pageCursorDefault: true,
|
||||
cursorTaskKind: taskKind,
|
||||
suggestedDelivery: resolveCursorSuggestedDelivery(taskKind),
|
||||
selectedChatSkill: resolveCursorTaskSkill(taskKind, runMetadata.selectedChatSkill),
|
||||
};
|
||||
|
||||
return {
|
||||
userMessage: { ...message, metadata },
|
||||
rawToolMode: 'code',
|
||||
taskType: PAGE_CURSOR_TASK_TYPE,
|
||||
forceDeepReasoning: true,
|
||||
requiredExecutor: codeExecutor,
|
||||
};
|
||||
}
|
||||
|
||||
export function applyCursorFirstAgentExecution(userMessage, runOptions = {}, {
|
||||
routingDecision = null,
|
||||
env = process.env,
|
||||
} = {}) {
|
||||
// Cursor 仅服务于显式页面/代码任务(enforcePageGenerationCursorRuntime)。
|
||||
// 普通 Agent 编排走 Goose/DeepSeek,不因 MEMIND_CURSOR_AGENT_TASKS_DEFAULT 全量切 Cursor。
|
||||
void userMessage;
|
||||
void runOptions;
|
||||
void routingDecision;
|
||||
void env;
|
||||
return { userMessage, runOptions, cursorFirst: false };
|
||||
}
|
||||
|
||||
export function __cursorPageRoutingTestExports() {
|
||||
return {
|
||||
PAGE_CURSOR_TASK_TYPE,
|
||||
CURSOR_TASK_KIND,
|
||||
cursorPageTasksDefaultEnabled,
|
||||
cursorAgentTasksDefaultEnabled,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user