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
+21 -7
View File
@@ -13,6 +13,10 @@ import {
bufferToImageDataUri,
} from './mindspace-thumbnails.mjs';
import { resolvePublishDir } from './user-publish.mjs';
import {
resolvePageWorkspaceRelativePath,
resolveWorkspaceMindSpacePublicUrl,
} from './mindspace-runtime-config.mjs';
import { prepareHtmlDownloadLinks, inferWorkspaceHtmlRelativePath } from './mindspace-html-download-links.mjs';
import { prepareHtmlPageBrandMarkers } from './mindspace-page-tag.mjs';
import { purgeWorkspacePageArtifacts, extractAssetIdsFromHtml } from './mindspace-page-purge.mjs';
@@ -106,7 +110,14 @@ function extensionForMime(mimeType, filename = '') {
return 'bin';
}
function pageResponse(row, { includeContent = true } = {}) {
function pageResponse(row, { includeContent = true, h5Root = null, env = process.env } = {}) {
const workspaceRelativePath = resolvePageWorkspaceRelativePath(row);
const workspacePublicUrl = resolveWorkspaceMindSpacePublicUrl({
h5Root,
env,
userId: row.user_id,
relativePath: workspaceRelativePath,
});
const response = {
id: row.id,
categoryId: row.category_id,
@@ -123,7 +134,9 @@ function pageResponse(row, { includeContent = true } = {}) {
status: row.status,
visibility: row.visibility,
publicationAccessMode: row.pub_access_mode ?? null,
publicationUrl: row.pub_public_url ?? null,
publicationUrl: workspacePublicUrl ?? row.pub_public_url ?? null,
workspaceRelativePath: workspaceRelativePath ?? null,
workspacePublicUrl,
currentVersionId: row.current_version_id,
versionNo: asNumber(row.version_no),
createdAt: asNumber(row.created_at),
@@ -249,6 +262,7 @@ export function createPageService(pool, options = {}) {
const h5Root = options.h5Root ? path.resolve(options.h5Root) : null;
const idFactory = options.idFactory ?? (() => crypto.randomUUID());
const conversationPackageRegistry = options.conversationPackageRegistry ?? null;
const toPageResponse = (row, opts = {}) => pageResponse(row, { h5Root, ...opts });
const resolveWorkspacePublishDir = async (userId) => {
if (!h5Root) return null;
@@ -624,7 +638,7 @@ export function createPageService(pool, options = {}) {
LIMIT 1`,
[userId, assetId],
);
return rows[0] ? pageResponse(rows[0]) : null;
return rows[0] ? toPageResponse(rows[0]) : null;
};
const findPageBySourceMessage = async (userId, sessionId, messageId) => {
@@ -640,7 +654,7 @@ export function createPageService(pool, options = {}) {
LIMIT 1`,
[userId, sessionId, messageId],
);
return rows[0] ? pageResponse(rows[0]) : null;
return rows[0] ? toPageResponse(rows[0]) : null;
};
const findPageByRelativePath = async (userId, relativePath) => {
@@ -669,7 +683,7 @@ export function createPageService(pool, options = {}) {
LIMIT 1`,
[userId, normalized, normalized, normalized],
);
return rows[0] ? pageResponse(rows[0]) : null;
return rows[0] ? toPageResponse(rows[0]) : null;
};
const findPlazaContextByWorkspacePath = async (userId, relativePath) => {
@@ -734,7 +748,7 @@ export function createPageService(pool, options = {}) {
]);
const total = asNumber(countRows[0]?.total);
return {
items: rows.map((row) => pageResponse(row, { includeContent: false })),
items: rows.map((row) => toPageResponse(row, { includeContent: false })),
total,
limit,
offset,
@@ -761,7 +775,7 @@ export function createPageService(pool, options = {}) {
sourceSnapshotJson: row.source_snapshot_json,
pageType: row.page_type,
});
return pageResponse({ ...row, content });
return toPageResponse({ ...row, content });
}
const listVersions = async (userId, pageId) => {