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:
@@ -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({
|
||||
|
||||
Reference in New Issue
Block a user