Files
memind/mindspace-asset-agent.mjs
john e3063ea806 fix(mindspace): refresh published HTML after workspace sync
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>
2026-07-10 20:44:04 +08:00

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 };
},
};
}