Files
memind/agent-run-deliverable-check.test.mjs
john 7f8d692d16 fix(agent): recover stale runs and improve new-user OA delivery
Fix DEV logout cookie clearing, materialize selected MindSpace OA assets before agent runs, and recover zombie runs from synced workspace pages. Add client run wait timeout, harness retry limits, page-edit asset forwarding, and logout/john2 scenario tests.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-10 20:05:52 +08:00

188 lines
6.1 KiB
JavaScript

import test from 'node:test';
import assert from 'node:assert/strict';
import {
detectSessionDeliverables,
detectWorkspaceSyncedDeliverables,
hasRecoverableSessionDeliverables,
isRecoverableFinishError,
mergeDeliverableSummaries,
prepareAndDetectSessionDeliverables,
tryRecoverRunFromDeliverables,
tryRecoverRunFromSessionDeliverables,
} from './agent-run-deliverable-check.mjs';
test('isRecoverableFinishError recognizes finish and run timeout codes', () => {
assert.equal(isRecoverableFinishError({ code: 'SESSION_REPLY_INCOMPLETE' }), true);
assert.equal(isRecoverableFinishError({ code: 'SESSION_REPLY_TIMEOUT' }), true);
assert.equal(isRecoverableFinishError({ code: 'AGENT_RUN_TIMEOUT' }), true);
assert.equal(isRecoverableFinishError({ code: 'TOOL_GATEWAY_VALIDATION_FAILED' }), false);
});
test('detectSessionDeliverables counts pages and online publications for a session', async () => {
const pool = {
async query(sql, params) {
assert.match(sql, /source_session_id/);
assert.deepEqual(params, ['user-1', 'session-1']);
return [[
{
page_id: 'page-front',
title: '前台',
publication_id: 'pub-front',
publication_status: 'online',
public_url: 'https://example.com/u/john/pages/page-front',
},
{
page_id: 'page-admin',
title: '后台',
publication_id: null,
publication_status: null,
public_url: null,
},
]];
},
};
const summary = await detectSessionDeliverables(pool, 'user-1', 'session-1');
assert.equal(summary.pageCount, 2);
assert.equal(summary.publicationCount, 1);
assert.equal(summary.pages[0].publicUrl, 'https://example.com/u/john/pages/page-front');
});
test('tryRecoverRunFromSessionDeliverables succeeds when pages exist despite finish timeout', async () => {
const pool = {
async query() {
return [[{ page_id: 'page-1', title: 'Survey', publication_id: 'pub-1', public_url: '/u/j/x' }]];
},
};
const recovered = await tryRecoverRunFromSessionDeliverables({
pool,
userId: 'user-1',
sessionId: 'session-1',
error: Object.assign(new Error('session reply timed out'), { code: 'SESSION_REPLY_TIMEOUT' }),
});
assert.ok(recovered);
assert.equal(recovered.deliverables.pageCount, 1);
assert.equal(recovered.originalError.code, 'SESSION_REPLY_TIMEOUT');
});
test('tryRecoverRunFromSessionDeliverables returns null when no deliverables exist', async () => {
const pool = { async query() { return [[]]; } };
const recovered = await tryRecoverRunFromSessionDeliverables({
pool,
userId: 'user-1',
sessionId: 'session-1',
error: Object.assign(new Error('stream ended'), { code: 'SESSION_REPLY_INCOMPLETE' }),
});
assert.equal(recovered, null);
});
test('hasRecoverableSessionDeliverables accepts pages even without online publication', () => {
assert.equal(hasRecoverableSessionDeliverables({ pageCount: 1, publicationCount: 0 }), true);
assert.equal(hasRecoverableSessionDeliverables({ pageCount: 0, publicationCount: 0 }), false);
});
test('detectWorkspaceSyncedDeliverables filters auto-synced public html pages', async () => {
const pool = {
async query(sql, params) {
assert.match(sql, /auto_synced/);
assert.deepEqual(params, ['user-1', 1000]);
return [[{
page_id: 'page-html',
title: '攻略',
publication_id: null,
publication_status: null,
public_url: null,
}]];
},
};
const summary = await detectWorkspaceSyncedDeliverables(pool, 'user-1', { sinceMs: 1000 });
assert.equal(summary.pageCount, 1);
});
test('prepareAndDetectSessionDeliverables merges session and workspace pages after prepare hook', async () => {
const prepareCalls = [];
const pool = {
async query(sql, params) {
if (sql.includes('source_session_id')) {
return [[{
page_id: 'page-session',
title: 'Session Page',
publication_id: null,
publication_status: null,
public_url: null,
}]];
}
if (sql.includes('auto_synced')) {
return [[{
page_id: 'page-workspace',
title: 'Workspace Page',
publication_id: 'pub-workspace',
publication_status: 'online',
public_url: '/u/john/pages/page-workspace',
}]];
}
throw new Error(`Unexpected SQL: ${sql}`);
},
};
const summary = await prepareAndDetectSessionDeliverables({
pool,
userId: 'user-1',
sessionId: 'session-1',
runStartedAtMs: 2000,
prepareDeliverables: async ({ userId, sessionId }) => {
prepareCalls.push({ userId, sessionId });
},
});
assert.equal(prepareCalls.length, 1);
assert.equal(summary.pageCount, 2);
assert.equal(summary.publicationCount, 1);
});
test('tryRecoverRunFromDeliverables succeeds for stale recovery without recoverable finish error', async () => {
const pool = {
async query(sql) {
if (sql.includes('source_session_id')) return [[]];
if (sql.includes('auto_synced')) {
return [[{
page_id: 'page-1',
title: 'Survey',
publication_id: 'pub-1',
public_url: '/u/j/x',
}]];
}
throw new Error(`Unexpected SQL: ${sql}`);
},
};
const recovered = await tryRecoverRunFromDeliverables({
pool,
userId: 'user-1',
sessionId: 'session-1',
error: Object.assign(new Error('stale'), { code: 'AGENT_RUN_STALE_RECOVERY' }),
requireRecoverableError: false,
});
assert.ok(recovered);
assert.equal(recovered.deliverables.pageCount, 1);
});
test('mergeDeliverableSummaries deduplicates pages by id', () => {
const merged = mergeDeliverableSummaries(
{
pageCount: 1,
publicationCount: 0,
pages: [{ pageId: 'page-1', title: 'A', publicationId: null, publicationStatus: null, publicUrl: null }],
},
{
pageCount: 1,
publicationCount: 1,
pages: [{
pageId: 'page-1',
title: 'A',
publicationId: 'pub-1',
publicationStatus: 'online',
publicUrl: '/u/j/x',
}],
},
);
assert.equal(merged.pageCount, 1);
assert.equal(merged.publicationCount, 1);
});