feat: chat uploads, vision turn isolation, and MindSpace agent improvements

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>
This commit is contained in:
john
2026-07-11 00:23:01 +08:00
parent e3063ea806
commit 32fb2cdeaf
51 changed files with 4588 additions and 186 deletions
+28 -3
View File
@@ -67,8 +67,17 @@ function resolvePageRecord(selectedPageId, pages, pageLive) {
};
}
function normalizeWorkspaceRelativePath(value) {
const trimmed = String(value ?? '').trim().replace(/^\.?\//, '');
if (!trimmed || trimmed.includes('..')) return null;
return trimmed;
}
function normalizeSelectedAsset(asset) {
if (!asset?.id) return null;
const workspaceRelativePath = normalizeWorkspaceRelativePath(
asset.workspaceRelativePath ?? asset.workspace_relative_path,
);
return {
id: asset.id,
displayName: asset.displayName ?? asset.filename ?? asset.id,
@@ -77,9 +86,18 @@ function normalizeSelectedAsset(asset) {
...(typeof asset.sizeBytes === 'number' ? { sizeBytes: asset.sizeBytes } : {}),
...(asset.categoryCode ? { categoryCode: asset.categoryCode } : {}),
...(asset.assetType ? { assetType: asset.assetType } : {}),
...(workspaceRelativePath ? { workspaceRelativePath } : {}),
};
}
function resolveCategoryItemCount(category, view, assets) {
const listed = typeof category?.itemCount === 'number' ? category.itemCount : 0;
if (view !== 'category' || !Array.isArray(assets) || assets.length === 0) {
return category?.itemCount;
}
return Math.max(listed, assets.length);
}
function inferAssetKindLabel(asset) {
const mime = String(asset?.mimeType ?? '').toLowerCase();
const name = String(asset?.displayName ?? asset?.filename ?? '').toLowerCase();
@@ -107,10 +125,15 @@ function buildSelectedAssetInstructions(context) {
if (selected.length === 1) {
const asset = selected[0];
const kind = inferAssetKindLabel(asset);
const workspacePath = normalizeWorkspaceRelativePath(
asset.workspaceRelativePath ?? asset.workspace_relative_path,
);
lines.push(
`- 用户在界面勾选了 1 份${kind}:「${asset.displayName}」(id: ${asset.id}${asset.mimeType}`,
'- 用户说「这个 / 这份 / 这篇 / 这份报告 / 这个数据 / 帮我分析 / 帮我删除」等指代词时,**必须**指这份勾选资料,不得猜测其它文件。',
'- 读取/分析:优先 `list_dir oa` / `read_file oa/文件名`;若工作区尚无副本,见下方「勾选资料读取」用 Agent 代理下载落盘。',
workspacePath
? `- 读取/分析:文件已落盘 \`${workspacePath}\`,直接 read/脚本读取;见下方「勾选资料读取」。`
: '- 读取/分析:优先 `list_dir oa` / `read_file oa/文件名`;若工作区尚无副本,见下方「勾选资料读取」用 Agent 代理下载落盘。',
'- 删除:须先与用户确认,确认后由你直接执行删除(见下方「勾选资料删除」);你对该用户空间有完整读写删改权限,**禁止**推给用户自行去界面删除。',
);
if (context.h5ApiBase) {
@@ -184,7 +207,7 @@ export function buildMindSpaceChatContext(input) {
category: {
code: category.code,
name: category.name,
itemCount: category.itemCount,
itemCount: resolveCategoryItemCount(category, view, assets),
...(view === 'category' && assets.length
? {
assets: assets.slice(0, ASSET_PREVIEW_LIMIT).map((asset) => ({
@@ -344,7 +367,9 @@ export function buildContextPrefix(context) {
if (context.page.summary?.trim()) {
lines.push(`- 页面摘要:${context.page.summary.trim()}`);
}
if (context.page.publicationUrl) {
if (context.page.workspacePublicUrl) {
lines.push(`- 公开地址(MindSpace):${context.page.workspacePublicUrl}`);
} else if (context.page.publicationUrl) {
lines.push(`- 公开地址:${context.page.publicationUrl}`);
}
if (context.page.contentExcerpt) {