e3063ea806
Keep online publication snapshots aligned when public/*.html changes, default static page binds to public access with MindSpace delivery URLs, and split browser-safe workspace helpers out of the Vite import chain. Co-authored-by: Cursor <cursoragent@cursor.com>
137 lines
4.4 KiB
JavaScript
137 lines
4.4 KiB
JavaScript
import fs from 'node:fs/promises';
|
|
import path from 'node:path';
|
|
import {
|
|
buildSelectedAssetDeleteInstructions,
|
|
buildSelectedAssetReadInstructions,
|
|
} from './mindspace-asset-agent-instructions.mjs';
|
|
import { resolveAssetWorkspaceRelativePath } from './mindspace-workspace-relative-path.mjs';
|
|
import { ensureUserZoneDirs } from './user-space.mjs';
|
|
|
|
function sanitizeBasename(value) {
|
|
const normalized = String(value ?? '').replace(/\\/g, '/').trim();
|
|
const basename = normalized.split('/').pop() ?? '';
|
|
return basename.replace(/[^\w.\-()\u4e00-\u9fff]+/g, '_') || 'file';
|
|
}
|
|
|
|
async function materializeAssetToWorkspace({
|
|
assetService,
|
|
resolveWorkspaceRoot,
|
|
userId,
|
|
assetId,
|
|
}) {
|
|
const { asset, path: sourcePath } = await assetService.readAsset(userId, assetId);
|
|
const workspaceRoot = await resolveWorkspaceRoot(userId);
|
|
ensureUserZoneDirs(workspaceRoot);
|
|
const filename = sanitizeBasename(asset.filename ?? asset.displayName ?? asset.id);
|
|
const relativePath =
|
|
resolveAssetWorkspaceRelativePath({
|
|
categoryCode: asset.categoryCode ?? 'oa',
|
|
originalFilename: filename,
|
|
}) ?? `oa/${filename}`;
|
|
const destPath = path.join(workspaceRoot, ...relativePath.split('/'));
|
|
await fs.mkdir(path.dirname(destPath), { recursive: true });
|
|
await fs.copyFile(sourcePath, destPath);
|
|
const stat = await fs.stat(destPath);
|
|
return {
|
|
assetId,
|
|
relativePath,
|
|
sizeBytes: stat.size,
|
|
displayName: asset.displayName ?? filename,
|
|
mimeType: asset.mimeType ?? null,
|
|
};
|
|
}
|
|
|
|
export {
|
|
buildSelectedAssetDeleteInstructions,
|
|
buildSelectedAssetReadInstructions,
|
|
} from './mindspace-asset-agent-instructions.mjs';
|
|
|
|
export function createAssetAgentService({
|
|
assetService,
|
|
resolveUserIdForAgentSession,
|
|
resolveWorkspaceRoot,
|
|
}) {
|
|
if (typeof resolveWorkspaceRoot !== 'function') {
|
|
throw new Error('resolveWorkspaceRoot is required for asset agent service');
|
|
}
|
|
|
|
async function resolveOwnedUserId(sessionId, explicitUserId = null) {
|
|
if (explicitUserId) return explicitUserId;
|
|
const userId = await resolveUserIdForAgentSession(sessionId);
|
|
if (!userId) {
|
|
throw Object.assign(new Error('无效的 Agent 会话'), { code: 'forbidden' });
|
|
}
|
|
return userId;
|
|
}
|
|
|
|
return {
|
|
async applyAgentDelete(input = {}) {
|
|
const sessionId = String(input?.sessionId ?? input?.session_id ?? '').trim();
|
|
const assetId = String(input?.assetId ?? input?.asset_id ?? '').trim();
|
|
const confirmed = input?.confirmed === true || String(input?.confirmed ?? '').toLowerCase() === 'true';
|
|
if (!sessionId || !assetId) {
|
|
throw Object.assign(new Error('缺少 session_id 或 asset_id'), { code: 'invalid_request' });
|
|
}
|
|
if (!confirmed) {
|
|
throw Object.assign(new Error('删除前须已在对话中获得用户确认'), { code: 'confirmation_required' });
|
|
}
|
|
|
|
const userId = await resolveOwnedUserId(sessionId);
|
|
const result = await assetService.deleteAsset(userId, assetId);
|
|
return {
|
|
assetId,
|
|
userId,
|
|
...result,
|
|
};
|
|
},
|
|
|
|
async applyAgentDownload(input = {}) {
|
|
const sessionId = String(input?.sessionId ?? input?.session_id ?? '').trim();
|
|
const assetId = String(input?.assetId ?? input?.asset_id ?? '').trim();
|
|
if (!sessionId || !assetId) {
|
|
throw Object.assign(new Error('缺少 session_id 或 asset_id'), { code: 'invalid_request' });
|
|
}
|
|
|
|
const userId = await resolveOwnedUserId(sessionId);
|
|
const materialized = await materializeAssetToWorkspace({
|
|
assetService,
|
|
resolveWorkspaceRoot,
|
|
userId,
|
|
assetId,
|
|
});
|
|
return {
|
|
userId,
|
|
...materialized,
|
|
};
|
|
},
|
|
|
|
async materializeAssetsForSession({
|
|
sessionId = null,
|
|
userId = null,
|
|
assetIds = [],
|
|
} = {}) {
|
|
const normalizedIds = [...new Set(
|
|
(Array.isArray(assetIds) ? assetIds : [])
|
|
.map((item) => String(item ?? '').trim())
|
|
.filter(Boolean),
|
|
)];
|
|
if (normalizedIds.length === 0) {
|
|
return { materialized: [] };
|
|
}
|
|
const resolvedUserId = await resolveOwnedUserId(sessionId, userId);
|
|
const materialized = [];
|
|
for (const assetId of normalizedIds) {
|
|
materialized.push(
|
|
await materializeAssetToWorkspace({
|
|
assetService,
|
|
resolveWorkspaceRoot,
|
|
userId: resolvedUserId,
|
|
assetId,
|
|
}),
|
|
);
|
|
}
|
|
return { materialized };
|
|
},
|
|
};
|
|
}
|