test(cursor): add executor routing and help remediation scenarios
Memind CI / Test, build, and release guards (push) Successful in 10m3s

Add dry-run, e2e, and poem-page remediation scripts for cursor executor
and help escalation verification.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
john
2026-08-27 09:33:51 +08:00
parent 8bdb6e2442
commit a15c4177de
8 changed files with 1333 additions and 0 deletions
+226
View File
@@ -0,0 +1,226 @@
#!/usr/bin/env node
/**
* E2E: Goose 编排 → Cursor executor → MindSpace public/*.html → 会话链接回传
*
* Usage:
* node scripts/test-cursor-executor-e2e.mjs
* node scripts/test-cursor-executor-e2e.mjs --wait-ms=900000
*/
import crypto from 'node:crypto';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { createDbPool } from '../db.mjs';
import { loadH5Environment } from './load-env.mjs';
import {
createReporter,
extractAssistantTexts,
extractPublicLinks,
getSession,
loginViaApi,
resolvePortalBase,
snapshotPublicHtml,
waitForAssistantGrowth,
waitForRunTerminal,
} from './scenario-test-lib.mjs';
loadH5Environment(path.dirname(fileURLToPath(import.meta.url)));
const args = process.argv.slice(2);
const port = Number(
args.find((item) => item.startsWith('--port='))?.slice('--port='.length)
?? process.env.H5_PORT
?? 8081,
);
const waitMs = Number(
args.find((item) => item.startsWith('--wait-ms='))?.slice('--wait-ms='.length)
?? process.env.MEMIND_CURSOR_EXECUTOR_E2E_WAIT_MS
?? 20 * 60 * 1000,
);
const PAGE_BASENAME = `cursor-poem-e2e-${Date.now()}.html`;
const RELATIVE_PATH = `public/${PAGE_BASENAME}`;
const POEM_MESSAGE = [
'帮我写一首关于春日的短诗(四段即可),并做成一个精美的 HTML 页面。',
`产物路径必须是 ${RELATIVE_PATH}`,
].join('\n');
async function fetchRunEvents(pool, runId) {
const [rows] = await pool.query(
`SELECT event_type, data_json, created_at
FROM h5_agent_run_events
WHERE run_id = ?
ORDER BY created_at ASC`,
[runId],
);
return rows.map((row) => ({
eventType: row.event_type,
data: typeof row.data_json === 'string'
? JSON.parse(row.data_json)
: (row.data_json ?? null),
createdAt: Number(row.created_at),
}));
}
async function createCursorCodeRun(baseUrl, cookie, { message, relativePath }) {
const requestId = crypto.randomUUID();
const body = {
request_id: requestId,
user_message: {
id: crypto.randomUUID(),
role: 'user',
content: [{ type: 'text', text: message }],
metadata: {
userVisible: true,
displayText: message,
memindRun: {
selectedChatSkill: 'aider-development',
validation: {
expectedFile: { path: relativePath },
},
},
},
},
};
const response = await fetch(`${baseUrl}/api/agent/runs`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Cookie: cookie,
},
body: JSON.stringify(body),
});
const payload = await response.json().catch(() => ({}));
if (!response.ok) {
throw new Error(`POST /api/agent/runs ${response.status}: ${JSON.stringify(payload)}`);
}
const run = payload.run ?? payload;
return {
runId: run.id,
requestId,
sessionId: run.sessionId ?? run.agent_session_id ?? null,
status: run.status,
payload,
};
}
async function main() {
const reporter = createReporter();
const baseUrl = resolvePortalBase(port);
const account = {
username: process.env.RELEASE_GATE_SCENARIO_USERNAME ?? 'john',
password: process.env.JOHN_PASSWORD ?? process.env.H5_ACCESS_PASSWORD ?? '888888',
};
console.log('==> Cursor Executor E2EGoose 编排 + Cursor 落盘)');
console.log(` Portal: ${baseUrl}`);
console.log(` 目标文件: ${RELATIVE_PATH}`);
console.log(` 超时: ${waitMs}ms\n`);
const statusResponse = await fetch(`${baseUrl}/auth/status`);
if (!statusResponse.ok) {
throw new Error(`Portal 未就绪: ${statusResponse.status}`);
}
const auth = await loginViaApi(baseUrl, account, reporter);
const userId = auth.user?.id;
if (!userId) {
reporter.fail('登录', '缺少 userId');
process.exit(reporter.summary());
}
const htmlBefore = await snapshotPublicHtml(userId);
const run = await createCursorCodeRun(baseUrl, auth.cookie, {
message: POEM_MESSAGE,
relativePath: RELATIVE_PATH,
});
reporter.pass('提交 code run', run.runId);
const terminal = await waitForRunTerminal(baseUrl, auth.cookie, run.runId, waitMs);
const sessionId = terminal.sessionId ?? terminal.agent_session_id ?? run.sessionId ?? null;
reporter.pass('run 终态', `${terminal.status}${terminal.error ? ` (${terminal.error})` : ''}`);
const pool = createDbPool();
try {
const events = await fetchRunEvents(pool, run.runId);
const dispatch = events.find((item) => item.eventType === 'tool_gateway_dispatch');
const result = events.find((item) => item.eventType === 'tool_gateway_result');
const validation = events.find((item) => item.eventType === 'tool_gateway_validation');
if (dispatch?.data) {
reporter.pass('tool_gateway_dispatch', JSON.stringify(dispatch.data));
} else {
reporter.fail('tool_gateway_dispatch', '未找到 dispatch 事件(可能未走 code executor 路径)');
}
if (result?.data?.executor === 'cursor') {
reporter.pass('executor', 'cursor');
} else {
reporter.fail('executor', `期望 cursor,实际 ${result?.data?.executor ?? 'unknown'}`);
}
if (validation?.data) {
reporter.pass('validation', JSON.stringify(validation.data));
} else if (terminal.status === 'succeeded') {
reporter.fail('validation', '缺少 tool_gateway_validation 事件');
}
} finally {
await pool.end();
}
if (terminal.status !== 'succeeded') {
process.exit(reporter.summary());
}
const htmlAfter = await snapshotPublicHtml(userId);
const created = htmlAfter.find((item) => item.relativePublicPath === RELATIVE_PATH)
?? htmlAfter.filter((item) => !htmlBefore.some((before) => before.fullPath === item.fullPath))
.sort((a, b) => b.mtimeMs - a.mtimeMs)[0]
?? null;
if (created) {
reporter.pass('MindSpace HTML', created.relativePublicPath);
} else {
reporter.fail('MindSpace HTML', `未找到 ${RELATIVE_PATH}`);
}
if (sessionId) {
const reply = await waitForAssistantGrowth(baseUrl, auth.cookie, sessionId, {
minChars: 20,
timeoutMs: 120_000,
});
if (reply?.combined) {
const links = extractPublicLinks(reply.combined, baseUrl);
reporter.pass('assistant 回复', `${reply.combined.length} 字 / ${links.length} 链接`);
if (links.length > 0) {
for (const link of links.slice(0, 3)) {
const pageResponse = await fetch(link, {
headers: cookie ? { Cookie: cookie } : {},
redirect: 'follow',
});
const body = await pageResponse.text();
const hit = ['春', '诗', 'html', 'poem', 'spring'].some((kw) => body.includes(kw));
reporter.pass(
`页面 ${link}`,
`HTTP ${pageResponse.status}${hit ? ' / 含诗歌关键词' : ''}`,
);
}
} else {
reporter.fail('公开链接', '回复中未找到 MindSpace 链接');
}
} else {
reporter.fail('assistant 回复', '会话无新 assistant 消息');
}
} else {
reporter.fail('sessionId', 'run 未回填 sessionId');
}
const exitCode = reporter.summary();
process.exit(exitCode);
}
main().catch((error) => {
console.error(error);
process.exit(1);
});