32fb2cdeaf
Add chat file/image upload UX, attachment proxying, vision thumbnails, and per-turn image scoping so agents only use the current upload. Extend MindSpace asset context, billing token state, OA/scenario verify scripts, and related runtime config. Co-authored-by: Cursor <cursoragent@cursor.com>
157 lines
5.0 KiB
JavaScript
157 lines
5.0 KiB
JavaScript
import assert from 'node:assert/strict';
|
|
import fs from 'node:fs/promises';
|
|
import os from 'node:os';
|
|
import path from 'node:path';
|
|
import test from 'node:test';
|
|
import {
|
|
buildSelectedAssetDeleteInstructions,
|
|
buildSelectedAssetReadInstructions,
|
|
createAssetAgentService,
|
|
} from './mindspace-asset-agent.mjs';
|
|
|
|
test('buildSelectedAssetReadInstructions uses agent download proxy', () => {
|
|
const lines = buildSelectedAssetReadInstructions({
|
|
selectedAssets: [
|
|
{
|
|
id: 'asset-9',
|
|
displayName: 'sales.xlsx',
|
|
mimeType: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
|
},
|
|
],
|
|
h5ApiBase: 'http://127.0.0.1:8081',
|
|
agentSessionId: 'session-1',
|
|
});
|
|
const text = lines.join('\n');
|
|
assert.match(text, /mindspace_asset_download/);
|
|
assert.match(text, /禁止.*assets\/:id\/download/);
|
|
assert.match(text, /asset-9/);
|
|
assert.match(text, /session-1/);
|
|
assert.match(text, /数据表汇总校验/);
|
|
assert.match(text, /SUM/);
|
|
});
|
|
|
|
test('buildSelectedAssetReadInstructions skips download probe when workspace path exists', () => {
|
|
const lines = buildSelectedAssetReadInstructions({
|
|
selectedAssets: [
|
|
{
|
|
id: 'asset-9',
|
|
displayName: 'sales.xlsx',
|
|
mimeType: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
|
workspaceRelativePath: 'oa/sales.xlsx',
|
|
},
|
|
],
|
|
h5ApiBase: 'http://127.0.0.1:8081',
|
|
agentSessionId: 'session-1',
|
|
});
|
|
const text = lines.join('\n');
|
|
assert.match(text, /已落盘路径/);
|
|
assert.match(text, /read_file oa\/sales\.xlsx/);
|
|
assert.match(text, /禁止.*list_dir/);
|
|
assert.match(text, /禁止.*重复调用 mindspace_asset_download/);
|
|
});
|
|
|
|
test('buildSelectedAssetDeleteInstructions includes agent delete curl', () => {
|
|
const lines = buildSelectedAssetDeleteInstructions({
|
|
selectedAssets: [
|
|
{
|
|
id: 'asset-9',
|
|
displayName: 'sales.xlsx',
|
|
mimeType: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
|
},
|
|
],
|
|
h5ApiBase: 'http://127.0.0.1:8081',
|
|
agentSessionId: 'session-1',
|
|
});
|
|
const text = lines.join('\n');
|
|
assert.match(text, /完整读写删改权限/);
|
|
assert.match(text, /mindspace_asset_delete/);
|
|
assert.match(text, /asset-9/);
|
|
assert.match(text, /session-1/);
|
|
assert.match(text, /不要让用户自己去界面点删除/);
|
|
});
|
|
|
|
test('applyAgentDelete requires confirmation and valid session', async () => {
|
|
const service = createAssetAgentService({
|
|
assetService: {
|
|
deleteAsset: async () => ({ deleted: true }),
|
|
},
|
|
resolveUserIdForAgentSession: async (sessionId) =>
|
|
sessionId === 'session-1' ? 'user-1' : null,
|
|
resolveWorkspaceRoot: async () => '/tmp/workspace',
|
|
});
|
|
|
|
await assert.rejects(
|
|
() => service.applyAgentDelete({ session_id: 'session-1', asset_id: 'asset-9' }),
|
|
(error) => error.code === 'confirmation_required',
|
|
);
|
|
|
|
const result = await service.applyAgentDelete({
|
|
session_id: 'session-1',
|
|
asset_id: 'asset-9',
|
|
confirmed: true,
|
|
});
|
|
assert.equal(result.deleted, true);
|
|
assert.equal(result.userId, 'user-1');
|
|
});
|
|
|
|
test('applyAgentDownload materializes asset into workspace oa/', async () => {
|
|
const workspaceRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'asset-agent-'));
|
|
const sourcePath = path.join(workspaceRoot, 'source.xlsx');
|
|
await fs.writeFile(sourcePath, 'excel-bytes');
|
|
|
|
const service = createAssetAgentService({
|
|
assetService: {
|
|
readAsset: async () => ({
|
|
asset: {
|
|
id: 'asset-9',
|
|
categoryCode: 'oa',
|
|
filename: 'sales.xlsx',
|
|
displayName: 'sales.xlsx',
|
|
mimeType: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
|
},
|
|
path: sourcePath,
|
|
}),
|
|
},
|
|
resolveUserIdForAgentSession: async () => 'user-1',
|
|
resolveWorkspaceRoot: async () => workspaceRoot,
|
|
});
|
|
|
|
const result = await service.applyAgentDownload({
|
|
session_id: 'session-1',
|
|
asset_id: 'asset-9',
|
|
});
|
|
assert.equal(result.relativePath, 'oa/sales.xlsx');
|
|
const copied = await fs.readFile(path.join(workspaceRoot, 'oa/sales.xlsx'), 'utf8');
|
|
assert.equal(copied, 'excel-bytes');
|
|
});
|
|
|
|
test('materializeAssetsForSession accepts explicit userId', async () => {
|
|
const workspaceRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'asset-agent-'));
|
|
const sourcePath = path.join(workspaceRoot, 'source.csv');
|
|
await fs.writeFile(sourcePath, 'a,b\n1,2');
|
|
|
|
const service = createAssetAgentService({
|
|
assetService: {
|
|
readAsset: async (_userId, assetId) => ({
|
|
asset: {
|
|
id: assetId,
|
|
categoryCode: 'oa',
|
|
filename: 'data.csv',
|
|
displayName: 'data.csv',
|
|
mimeType: 'text/csv',
|
|
},
|
|
path: sourcePath,
|
|
}),
|
|
},
|
|
resolveUserIdForAgentSession: async () => null,
|
|
resolveWorkspaceRoot: async () => workspaceRoot,
|
|
});
|
|
|
|
const result = await service.materializeAssetsForSession({
|
|
userId: 'user-1',
|
|
assetIds: ['asset-a'],
|
|
});
|
|
assert.equal(result.materialized.length, 1);
|
|
assert.equal(result.materialized[0].relativePath, 'oa/data.csv');
|
|
});
|