Add per-feature Cursor channel toggles for admin and runtime.
Memind CI / Test, build, and release guards (push) Has been cancelled

Expose page/data/scheduled-task/chat-bridge switches in the智趣体验通道 config so Tang can roll out Cursor paths independently with DeepSeek fallback.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
john
2026-08-31 09:40:52 +08:00
parent ac2c87be43
commit 617ff0d1dd
21 changed files with 915 additions and 127 deletions
+134 -61
View File
@@ -10,6 +10,7 @@ function sleep(ms) {
function buildWechatCursorUserMessage({
displayText,
intentKind,
channel = 'wechat_mp',
}) {
const taskText = String(displayText ?? '').trim();
return {
@@ -18,9 +19,9 @@ function buildWechatCursorUserMessage({
metadata: {
displayText: taskText,
memindRun: {
channel: 'wechat_mp',
wechatCursorChannel: true,
taskType: 'wechat_page_generate',
channel,
wechatCursorChannel: channel === 'wechat_mp',
taskType: channel === 'wechat_mp' ? 'wechat_page_generate' : 'h5_chat_code_task',
toolMode: 'code',
requiredExecutor: 'cursor',
intentKind: String(intentKind ?? '').trim() || null,
@@ -42,6 +43,123 @@ async function readCursorCompletionText(agentRunGateway, userId, runId) {
});
}
export async function executeCursorChannelCodeRun({
agentRunGateway,
userId,
sessionId = null,
requestId,
displayText,
agentPrompt = null,
intentKind = 'page.generate',
channel = 'wechat_mp',
taskType = null,
policy = null,
forceCursorExecutor = false,
timeoutMs = 15 * 60 * 1000,
pollMs = DEFAULT_POLL_MS,
logger = console,
} = {}) {
if (!agentRunGateway?.createRun || !agentRunGateway?.dispatchRun || !agentRunGateway?.getRunForUser) {
throw Object.assign(new Error('Cursor 执行网关不可用'), {
code: 'CURSOR_CHANNEL_GATEWAY_UNAVAILABLE',
});
}
const promptText = String(agentPrompt ?? displayText ?? '').trim();
let userMessage = buildWechatCursorUserMessage({
displayText: String(displayText ?? promptText).trim(),
intentKind,
channel,
});
if (promptText) {
userMessage = {
...userMessage,
content: [{ type: 'text', text: promptText }],
};
}
const resolvedTaskType = taskType
?? (channel === 'wechat_mp' ? 'wechat_page_generate' : 'h5_chat_code_task');
let cursorRuntime = enforcePageGenerationCursorRuntime(userMessage, {
rawToolMode: 'code',
taskType: resolvedTaskType,
env: process.env,
channelEligible: true,
policy,
});
if (forceCursorExecutor && !cursorRuntime.requiredExecutor) {
cursorRuntime = {
...cursorRuntime,
rawToolMode: 'code',
taskType: resolvedTaskType,
requiredExecutor: 'cursor',
};
userMessage = {
...userMessage,
metadata: {
...(userMessage.metadata ?? {}),
memindRun: {
...(userMessage.metadata?.memindRun ?? {}),
requiredExecutor: 'cursor',
executor: 'cursor',
toolMode: 'code',
},
},
};
} else {
userMessage = cursorRuntime.userMessage;
}
const run = await agentRunGateway.createRun(userId, {
sessionId,
requestId,
userMessage,
toolMode: 'code',
taskType: cursorRuntime.taskType ?? resolvedTaskType,
});
agentRunGateway.dispatchRun(run.id);
const deadline = Date.now() + Math.max(0, Number(timeoutMs) || 0);
while (Date.now() <= deadline) {
const latest = await agentRunGateway.getRunForUser(userId, run.id);
if (!latest) {
throw Object.assign(new Error('Cursor 任务丢失'), {
code: 'CURSOR_CHANNEL_RUN_MISSING',
});
}
if (latest.status === 'succeeded') {
const text = await readCursorCompletionText(agentRunGateway, userId, run.id);
logger.info?.('[cursor-channel] run succeeded', {
userId,
runId: run.id,
requestId,
channel,
});
return {
text,
runId: run.id,
tokenState: null,
messages: [{
role: 'assistant',
content: [{ type: 'text', text }],
metadata: { userVisible: true, source: 'cursor-channel-agent-run', channel },
}],
requestMessages: [],
};
}
if (latest.status === 'failed') {
const error = new Error(latest.error || 'Cursor 执行失败');
error.code = 'CURSOR_CHANNEL_RUN_FAILED';
throw error;
}
await sleep(Math.max(500, Number(pollMs) || DEFAULT_POLL_MS));
}
throw Object.assign(new Error('Cursor 执行超时'), {
code: 'CURSOR_CHANNEL_RUN_TIMEOUT',
});
}
export async function executeWechatCursorAgentRun({
agentRunGateway,
userId,
@@ -50,69 +168,24 @@ export async function executeWechatCursorAgentRun({
displayText,
agentPrompt,
intentKind = 'page.generate',
policy = null,
timeoutMs = 15 * 60 * 1000,
pollMs = DEFAULT_POLL_MS,
logger = console,
} = {}) {
if (!agentRunGateway?.createRun || !agentRunGateway?.dispatchRun || !agentRunGateway?.getRunForUser) {
throw Object.assign(new Error('WeChat Cursor 执行网关不可用'), {
code: 'WECHAT_CURSOR_GATEWAY_UNAVAILABLE',
});
}
let userMessage = buildWechatCursorUserMessage({ displayText, intentKind });
const cursorRuntime = enforcePageGenerationCursorRuntime(userMessage, {
rawToolMode: 'code',
taskType: 'wechat_page_generate',
env: process.env,
channelEligible: true,
});
userMessage = cursorRuntime.userMessage;
const run = await agentRunGateway.createRun(userId, {
return executeCursorChannelCodeRun({
agentRunGateway,
userId,
sessionId,
requestId,
userMessage,
toolMode: 'code',
taskType: cursorRuntime.taskType ?? 'h5_chat_code_task',
});
agentRunGateway.dispatchRun(run.id);
const deadline = Date.now() + Math.max(0, Number(timeoutMs) || 0);
while (Date.now() <= deadline) {
const latest = await agentRunGateway.getRunForUser(userId, run.id);
if (!latest) {
throw Object.assign(new Error('WeChat Cursor 任务丢失'), {
code: 'WECHAT_CURSOR_RUN_MISSING',
});
}
if (latest.status === 'succeeded') {
const text = await readCursorCompletionText(agentRunGateway, userId, run.id);
logger.info?.('[wechat-cursor] run succeeded', {
userId,
runId: run.id,
requestId,
});
return {
text,
tokenState: null,
messages: [{
role: 'assistant',
content: [{ type: 'text', text }],
metadata: { userVisible: true, source: 'wechat-cursor-agent-run' },
}],
requestMessages: [],
};
}
if (latest.status === 'failed') {
const error = new Error(latest.error || 'WeChat Cursor 执行失败');
error.code = 'WECHAT_CURSOR_RUN_FAILED';
throw error;
}
await sleep(Math.max(500, Number(pollMs) || DEFAULT_POLL_MS));
}
throw Object.assign(new Error('WeChat Cursor 执行超时'), {
code: 'WECHAT_CURSOR_RUN_TIMEOUT',
displayText,
agentPrompt,
intentKind,
channel: 'wechat_mp',
taskType: 'wechat_page_generate',
policy,
timeoutMs,
pollMs,
logger,
});
}