7f8d692d16
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>
299 lines
8.8 KiB
JavaScript
299 lines
8.8 KiB
JavaScript
import assert from 'node:assert/strict';
|
||
import test from 'node:test';
|
||
import {
|
||
buildContextPrefix,
|
||
buildMindSpaceChatContext,
|
||
buildSelectedAssetGreeting,
|
||
formatContextChip,
|
||
mindspaceChatContextInternals,
|
||
} from './mindspace-chat-context.mjs';
|
||
|
||
const sampleSpace = {
|
||
id: 'space-1',
|
||
name: '我的空间',
|
||
categories: [
|
||
{ code: 'oa', name: 'OA 工作区', itemCount: 12 },
|
||
{ code: 'draft', name: '页面草稿', itemCount: 5 },
|
||
],
|
||
};
|
||
|
||
const sampleCategory = {
|
||
id: 'cat-oa',
|
||
code: 'oa',
|
||
name: 'OA 工作区',
|
||
itemCount: 12,
|
||
};
|
||
|
||
const samplePage = {
|
||
id: 'page-1',
|
||
title: '项目周报',
|
||
summary: '本周项目进展与风险汇总',
|
||
status: 'draft',
|
||
versionNo: 3,
|
||
categoryCode: 'draft',
|
||
templateId: 'report',
|
||
contentFormat: 'markdown',
|
||
pageType: 'article',
|
||
content: '# 周报\n\n- 完成 A 模块\n- 风险:进度偏紧',
|
||
};
|
||
|
||
test('buildMindSpaceChatContext resolves home, category, and page views', () => {
|
||
const home = buildMindSpaceChatContext({
|
||
space: sampleSpace,
|
||
ownerUsername: 'john',
|
||
selectedCategory: null,
|
||
selectedPageId: null,
|
||
pages: [samplePage],
|
||
route: '/space',
|
||
});
|
||
assert.equal(home.view, 'home');
|
||
assert.equal(home.ownerUsername, 'john');
|
||
assert.equal(home.homeCategories?.length, 2);
|
||
assert.equal(home.recentPages?.[0]?.title, '项目周报');
|
||
|
||
const category = buildMindSpaceChatContext({
|
||
space: sampleSpace,
|
||
selectedCategory: sampleCategory,
|
||
selectedPageId: null,
|
||
pages: [],
|
||
assets: [
|
||
{
|
||
id: 'asset-1',
|
||
displayName: 'report.xlsx',
|
||
mimeType: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||
},
|
||
],
|
||
route: '/space?category=oa',
|
||
});
|
||
assert.equal(category.view, 'category');
|
||
assert.deepEqual(category.category?.assets, [
|
||
{
|
||
id: 'asset-1',
|
||
displayName: 'report.xlsx',
|
||
mimeType: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||
},
|
||
]);
|
||
|
||
const page = buildMindSpaceChatContext({
|
||
space: sampleSpace,
|
||
selectedCategory: null,
|
||
selectedPageId: 'page-1',
|
||
pages: [samplePage],
|
||
pageLive: {
|
||
page: samplePage,
|
||
title: '项目周报(编辑中)',
|
||
summary: samplePage.summary,
|
||
content: samplePage.content,
|
||
},
|
||
route: '/space/page/page-1',
|
||
});
|
||
assert.equal(page.view, 'page');
|
||
assert.equal(page.page.title, '项目周报(编辑中)');
|
||
assert.match(page.page.contentExcerpt ?? '', /完成 A 模块/);
|
||
assert.equal(page.category?.code, 'draft');
|
||
});
|
||
|
||
test('buildContextPrefix includes page excerpt and location hint', () => {
|
||
const prefix = buildContextPrefix(
|
||
buildMindSpaceChatContext({
|
||
space: sampleSpace,
|
||
ownerUsername: 'john',
|
||
selectedCategory: null,
|
||
selectedPageId: 'page-1',
|
||
pages: [samplePage],
|
||
pageLive: {
|
||
page: samplePage,
|
||
title: samplePage.title,
|
||
summary: samplePage.summary,
|
||
content: samplePage.content,
|
||
},
|
||
route: '/space/page/page-1',
|
||
}),
|
||
);
|
||
|
||
assert.match(prefix, /- 账号:john/);
|
||
assert.match(prefix, /- 页面摘要:本周项目进展与风险汇总/);
|
||
assert.match(prefix, /- 页面模板:分析报告/);
|
||
assert.match(prefix, /- 当前正文摘录/);
|
||
assert.match(prefix, /完成 A 模块/);
|
||
assert.match(prefix, /【定位说明】/);
|
||
assert.match(prefix, /指代「这个页面」「改这里」时默认指该页面/);
|
||
assert.match(prefix, /\n---\n\n$/);
|
||
});
|
||
|
||
test('buildContextPrefix lists category assets on category view', () => {
|
||
const prefix = buildContextPrefix(
|
||
buildMindSpaceChatContext({
|
||
space: sampleSpace,
|
||
selectedCategory: sampleCategory,
|
||
selectedPageId: null,
|
||
pages: [],
|
||
assets: [{ id: 'asset-1', displayName: 'report.xlsx', mimeType: 'text/csv' }],
|
||
route: '/space?category=oa',
|
||
}),
|
||
);
|
||
|
||
assert.match(prefix, /- 分类:OA 工作区(oa,共 12 项)/);
|
||
assert.match(prefix, /report\.xlsx(id: asset-1/);
|
||
assert.match(prefix, /指代「这里的文件」时默认指该分类下的资料/);
|
||
});
|
||
|
||
test('formatContextChip prefers page, then category with count, then space name', () => {
|
||
assert.equal(
|
||
formatContextChip({
|
||
spaceName: '我的空间',
|
||
view: 'page',
|
||
route: '/space/page/page-1',
|
||
page: { id: 'page-1', title: '项目周报', versionNo: 3 },
|
||
}),
|
||
'项目周报 · v3',
|
||
);
|
||
assert.equal(
|
||
formatContextChip({
|
||
spaceName: '我的空间',
|
||
view: 'category',
|
||
route: '/space?category=oa',
|
||
category: { code: 'oa', name: 'OA 工作区', itemCount: 12 },
|
||
}),
|
||
'OA 工作区 · 12 项',
|
||
);
|
||
});
|
||
|
||
test('excerptContent strips html and truncates long text', () => {
|
||
const html = '<h1>标题</h1><p>正文内容</p><script>alert(1)</script>';
|
||
assert.equal(
|
||
mindspaceChatContextInternals.excerptContent(html, 'html'),
|
||
'标题 正文内容',
|
||
);
|
||
assert.equal(
|
||
mindspaceChatContextInternals.excerptContent('a'.repeat(20), 'markdown', 10),
|
||
`${'a'.repeat(9)}…`,
|
||
);
|
||
});
|
||
|
||
test('buildMindSpaceChatContext exposes single selected asset as focused asset', () => {
|
||
const asset = {
|
||
id: 'asset-9',
|
||
displayName: 'Q1-report.pdf',
|
||
mimeType: 'application/pdf',
|
||
filename: 'Q1-report.pdf',
|
||
sizeBytes: 1200,
|
||
categoryCode: 'oa',
|
||
assetType: 'file',
|
||
};
|
||
const context = buildMindSpaceChatContext({
|
||
space: sampleSpace,
|
||
selectedCategory: sampleCategory,
|
||
selectedPageId: null,
|
||
pages: [],
|
||
assets: [asset],
|
||
selectedAssets: [asset],
|
||
route: '/space?category=oa',
|
||
});
|
||
|
||
assert.equal(context.selectedAssets?.length, 1);
|
||
assert.equal(context.focusedAsset?.id, 'asset-9');
|
||
assert.equal(formatContextChip(context), '已选 · Q1-report.pdf');
|
||
});
|
||
|
||
test('buildContextPrefix binds deictic references to a single checked asset', () => {
|
||
const asset = {
|
||
id: 'asset-9',
|
||
displayName: 'sales.xlsx',
|
||
mimeType: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||
filename: 'sales.xlsx',
|
||
categoryCode: 'oa',
|
||
};
|
||
const prefix = buildContextPrefix(
|
||
buildMindSpaceChatContext({
|
||
space: sampleSpace,
|
||
selectedCategory: sampleCategory,
|
||
selectedPageId: null,
|
||
pages: [],
|
||
assets: [asset],
|
||
selectedAssets: [asset],
|
||
route: '/space?category=oa',
|
||
h5ApiBase: 'http://127.0.0.1:8081',
|
||
agentSessionId: 'session-1',
|
||
}),
|
||
);
|
||
|
||
assert.match(prefix, /【界面勾选资料(硬性)】/);
|
||
assert.match(prefix, /sales\.xlsx(id: asset-9/);
|
||
assert.match(prefix, /【勾选资料读取(硬性)】/);
|
||
assert.match(prefix, /mindspace_asset_download/);
|
||
assert.match(prefix, /【勾选资料删除(硬性)】/);
|
||
assert.match(prefix, /mindspace_asset_delete/);
|
||
assert.match(prefix, /完整读写删改权限/);
|
||
assert.match(prefix, /指代「这个文件」「这份报告」「这个数据」时\*\*必须\*\*指该勾选资料/);
|
||
});
|
||
|
||
test('buildContextPrefix includes delete fallback when agent session id is missing', () => {
|
||
const asset = {
|
||
id: 'asset-9',
|
||
displayName: 'sales.xlsx',
|
||
mimeType: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||
};
|
||
const prefix = buildContextPrefix(
|
||
buildMindSpaceChatContext({
|
||
space: sampleSpace,
|
||
selectedCategory: sampleCategory,
|
||
selectedPageId: null,
|
||
pages: [],
|
||
assets: [asset],
|
||
selectedAssets: [asset],
|
||
route: '/space?category=oa',
|
||
h5ApiBase: 'http://127.0.0.1:8081',
|
||
}),
|
||
);
|
||
assert.match(prefix, /CURRENT_AGENT_SESSION/);
|
||
});
|
||
|
||
test('buildContextPrefix requires clarification when multiple assets are checked', () => {
|
||
const assets = [
|
||
{ id: 'asset-a', displayName: 'a.pdf', mimeType: 'application/pdf' },
|
||
{ id: 'asset-b', displayName: 'b.csv', mimeType: 'text/csv' },
|
||
];
|
||
const prefix = buildContextPrefix(
|
||
buildMindSpaceChatContext({
|
||
space: sampleSpace,
|
||
selectedCategory: sampleCategory,
|
||
selectedPageId: null,
|
||
pages: [],
|
||
assets,
|
||
selectedAssets: assets,
|
||
route: '/space?category=oa',
|
||
}),
|
||
);
|
||
|
||
assert.match(prefix, /同时勾选了 2 份资料/);
|
||
assert.match(prefix, /必须先追问/);
|
||
assert.doesNotMatch(prefix, /当前聚焦资料/);
|
||
assert.equal(formatContextChip(buildMindSpaceChatContext({
|
||
space: sampleSpace,
|
||
selectedCategory: sampleCategory,
|
||
selectedPageId: null,
|
||
pages: [],
|
||
assets,
|
||
selectedAssets: assets,
|
||
route: '/space?category=oa',
|
||
})), '已选 2 项资料');
|
||
});
|
||
|
||
test('buildSelectedAssetGreeting adapts wording to asset kind', () => {
|
||
assert.match(
|
||
buildSelectedAssetGreeting({
|
||
displayName: 'sales.xlsx',
|
||
mimeType: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||
}),
|
||
/这份数据表/,
|
||
);
|
||
assert.match(
|
||
buildSelectedAssetGreeting({
|
||
displayName: 'review.pdf',
|
||
mimeType: 'application/pdf',
|
||
}),
|
||
/这份报告/,
|
||
);
|
||
});
|