fix(agent-run): align cursor executor path with 103 production hotfixes

Recover requiredExecutor fallback, direct cursor launch in tool gateway,
and user-facing brand sanitization in source so the next portal release
can replace manual bundled edits on 103.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
john
2026-08-27 09:31:36 +08:00
parent b356ae8509
commit a29e1ec5c8
8 changed files with 581 additions and 30 deletions
+151
View File
@@ -21,6 +21,15 @@ test('required code executor is read from run metadata', () => {
}), null);
});
test('required code executor falls back to requiredExecutor metadata', () => {
assert.equal(resolveRequiredCodeExecutor({
metadata: { memindRun: { requiredExecutor: 'cursor' } },
}), 'cursor');
assert.equal(resolveRequiredCodeExecutor({
metadata: { memindRun: { executor: 'aider', requiredExecutor: 'cursor' } },
}), 'aider');
});
test('required Aider executor fails closed when Tool Gateway is unavailable', () => {
assert.doesNotThrow(() => assertRequiredCodeExecutorAvailable('aider', {
enabled: true,
@@ -124,6 +133,21 @@ function createFakePool({ sessionDeliverables = {}, workspaceDeliverables = {} }
const [userId, requestId] = params;
return [[...runs.values()].filter((row) => row.user_id === userId && row.request_id === requestId)];
}
if (
sql.includes('SELECT id, user_message_json, status')
&& sql.includes('WHERE user_id = ? AND status NOT IN')
) {
const [userId] = params;
return [[...runs.values()]
.filter((row) => row.user_id === userId && !['succeeded', 'failed'].includes(row.status))
.sort((a, b) => Number(b.created_at ?? 0) - Number(a.created_at ?? 0))
.slice(0, 20)
.map((row) => ({
id: row.id,
user_message_json: row.user_message_json,
status: row.status,
}))];
}
if (sql.includes('agent_session_id = ?') && sql.includes("status NOT IN ('succeeded', 'failed')")) {
const [sessionId] = params;
const active = [...runs.values()].filter(
@@ -2943,6 +2967,88 @@ test('agent run validates expected tool gateway artifacts before succeeding', as
assert.equal(JSON.parse(validationEvent.dataJson).expectedFiles[0].path, 'RESULT.md');
});
test('cursor executor fallback submits a clean chat message to Goose', async () => {
const previousFallback = process.env.MEMIND_CURSOR_DEEPSEEK_FALLBACK;
process.env.MEMIND_CURSOR_DEEPSEEK_FALLBACK = '1';
try {
const pool = createFakePool();
const submitted = [];
const gateway = createAgentRunGateway({
pool,
userAuth: {
async resolveWorkingDir() {
return '/tmp/memind-user-1';
},
},
tkmindProxy: {
async startSessionForUser() {
return { id: 'session-cursor-fallback' };
},
async submitSessionReplyAndAwaitFinishForUser(userId, sessionId, requestId, userMessage, options) {
submitted.push({ userId, sessionId, requestId, userMessage, options });
return { tokenState: null, toolEvidence: null };
},
},
toolGateway: {
getStatus() {
return { enabled: true, protocol: 'agent-run-v1', executors: ['cursor'] };
},
async executeJob() {
const error = new Error('cursor executor failed');
error.code = 'TOOL_GATEWAY_EXECUTOR_FAILED';
throw error;
},
},
retryDelaysMs: [],
});
const run = await gateway.createRun('user-1', {
requestId: 'req-cursor-fallback',
userMessage: {
role: 'user',
content: [{
type: 'text',
text: '写一段问候文字\n\n[Memind page task via TKMind 智趣 executor]\n不要只回复文字',
}],
metadata: {
displayText: '写一段问候文字',
memindRun: {
executor: 'cursor',
pageCursorDefault: true,
cursorTaskKind: 'page_generation',
suggestedDelivery: 'mindspace_public_html',
toolMode: 'code',
taskType: 'h5_chat_code_task',
},
},
},
toolMode: 'code',
taskType: 'h5_chat_code_task',
});
await waitFor(() => pool.runs.get(run.id)?.status === 'succeeded');
assert.equal(submitted.length, 1);
assert.equal(submitted[0].options.toolMode, 'chat');
assert.equal(submitted[0].userMessage.content[0].text, '写一段问候文字');
assert.equal(submitted[0].userMessage.metadata.memindRun.toolMode, 'chat');
assert.equal(submitted[0].userMessage.metadata.memindRun.executor, undefined);
assert.equal(submitted[0].userMessage.metadata.memindRun.pageCursorDefault, undefined);
assert.equal(submitted[0].userMessage.metadata.memindRun.taskType, undefined);
assert.equal(
pool.events.some(
(event) => event.runId === run.id && event.eventType === 'cursor_executor_fallback_to_deepseek',
),
true,
);
} finally {
if (previousFallback == null) {
delete process.env.MEMIND_CURSOR_DEEPSEEK_FALLBACK;
} else {
process.env.MEMIND_CURSOR_DEEPSEEK_FALLBACK = previousFallback;
}
}
});
test('required Aider run persists a validated result into a chat session', async () => {
const pool = createFakePool();
const workdir = await fs.mkdtemp(path.join(os.tmpdir(), 'memind-aider-delivery-'));
@@ -3767,6 +3873,51 @@ test('createRun rejects with SESSION_RUN_CONFLICT when same session already has
assert.equal(run3.requestId, 'req-conflict-3');
});
test('createRun rejects duplicate active runs with the same user-visible message fingerprint', async () => {
const pool = createFakePool();
const gateway = createAgentRunGateway({
pool,
userAuth: {},
tkmindProxy: {},
autoDispatch: false,
});
const activeRunId = crypto.randomUUID();
const now = Date.now();
const userMessage = {
role: 'user',
content: [{ type: 'text', text: '帮我写一首诗,做成页面123' }],
metadata: { displayText: '帮我写一首诗,做成页面123', userVisible: true },
};
pool.runs.set(activeRunId, {
id: activeRunId,
user_id: 'user-1',
agent_session_id: null,
request_id: 'req-page123',
status: 'running',
attempts: 1,
user_message_json: JSON.stringify(userMessage),
error_message: null,
created_at: now,
updated_at: now,
started_at: now,
completed_at: null,
});
let conflictErr = null;
try {
await gateway.createRun('user-1', {
requestId: 'req-page123-dup',
userMessage,
});
} catch (err) {
conflictErr = err;
}
assert.ok(conflictErr);
assert.equal(conflictErr.code, 'SESSION_RUN_CONFLICT');
assert.equal(conflictErr.existingRunId, activeRunId);
});
test('createRun recovers a stale active run for the same session before accepting a new run', async () => {
const pool = createFakePool();
const gateway = createAgentRunGateway({