fix(wechat): enforce current-turn image paths in page delivery
Memind CI / Test, build, and release guards (push) Successful in 6m58s
Memind CI / Test, build, and release guards (push) Successful in 6m58s
Materialize wechat-mp uploads into dated public/images paths during vision preprocessing and fail closed when delivered HTML references stale workspace images instead of the current turn allowlist. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+36
-3
@@ -39,6 +39,10 @@ import {
|
||||
extractCurrentTurnImageUrls,
|
||||
scrubConversationHistoricalImageAttachments,
|
||||
} from './chat-image-turn-scope.mjs';
|
||||
import {
|
||||
collectEmbedAssetKeys,
|
||||
materializeWechatPublicImageForEmbed,
|
||||
} from './chat-image-materialize.mjs';
|
||||
import { repairConversationToolHistory } from './chat-tool-history-repair.mjs';
|
||||
import { buildVisionThumbnailBuffer } from './vision-image-thumb.mjs';
|
||||
import {
|
||||
@@ -864,6 +868,7 @@ export async function buildVisionPayload({
|
||||
userMessage,
|
||||
userId,
|
||||
publishLayout,
|
||||
publishDir = null,
|
||||
localFetchAsset,
|
||||
llmProviderService,
|
||||
imgproxySigner = null,
|
||||
@@ -922,6 +927,14 @@ export async function buildVisionPayload({
|
||||
const parsed = new URL(rawUrl);
|
||||
relativePath = parsed.pathname + parsed.search;
|
||||
} catch { /* keep rawUrl */ }
|
||||
const materialized = publishDir
|
||||
? materializeWechatPublicImageForEmbed({
|
||||
publishDir,
|
||||
rawUrl,
|
||||
buffer,
|
||||
mimeType,
|
||||
})
|
||||
: null;
|
||||
const publicStandardUrl = buildPublicStandardImageUrl(rawUrl, userId, publishLayout);
|
||||
let visionBuffer = buffer;
|
||||
let visionMimeType = mimeType;
|
||||
@@ -940,7 +953,12 @@ export async function buildVisionPayload({
|
||||
data: visionBuffer.toString('base64'),
|
||||
relativePath,
|
||||
rawUrl,
|
||||
embedUrl: publicStandardUrl ?? relativePath,
|
||||
embedUrl: materialized?.embedUrl ?? publicStandardUrl ?? relativePath,
|
||||
allowedEmbedKeys: materialized?.assetKeys ?? collectEmbedAssetKeys([
|
||||
publicStandardUrl,
|
||||
relativePath,
|
||||
rawUrl,
|
||||
]),
|
||||
});
|
||||
} catch (err) {
|
||||
console.warn('Vision image fetch skipped:', err instanceof Error ? err.message : err);
|
||||
@@ -1009,6 +1027,11 @@ export async function buildVisionPayload({
|
||||
const canonicalImageUrls = imageItems
|
||||
.map((item) => item.embedUrl ?? item.rawUrl)
|
||||
.filter((url) => typeof url === 'string' && url.trim());
|
||||
const allowedPageImageEmbedKeys = [
|
||||
...new Set(
|
||||
imageItems.flatMap((item) => [...(item.allowedEmbedKeys ?? [])]),
|
||||
),
|
||||
];
|
||||
|
||||
return {
|
||||
userMessage: detachCurrentTurnImagesForTextProvider(
|
||||
@@ -1019,6 +1042,9 @@ export async function buildVisionPayload({
|
||||
...(userMessage.metadata ?? {}),
|
||||
...(originalDisplayText ? { displayText: originalDisplayText } : {}),
|
||||
...(canonicalImageUrls.length ? { imageUrls: canonicalImageUrls } : {}),
|
||||
...(allowedPageImageEmbedKeys.length
|
||||
? { allowedPageImageEmbedKeys }
|
||||
: {}),
|
||||
},
|
||||
},
|
||||
canonicalImageUrls,
|
||||
@@ -1618,11 +1644,12 @@ export function createTkmindProxy({
|
||||
// Step 2 — Inject Qwen's text description + server-relative image paths into the
|
||||
// user_message that goes to DeepSeek via Goose. DeepSeek retains full
|
||||
// tool-calling capability (write_file, etc.) and creates the page properly.
|
||||
async function buildVisionBody(userMessage, userId, publishLayout) {
|
||||
async function buildVisionBody(userMessage, userId, publishLayout, publishDir = null) {
|
||||
return buildVisionPayload({
|
||||
userMessage,
|
||||
userId,
|
||||
publishLayout,
|
||||
publishDir,
|
||||
localFetchAsset,
|
||||
llmProviderService,
|
||||
imgproxySigner,
|
||||
@@ -2039,7 +2066,13 @@ export function createTkmindProxy({
|
||||
let finalUserMessage = userMessage;
|
||||
if (llmProviderService && messageHasImages(userMessage) && await llmProviderService.hasVisionKey()) {
|
||||
const publishLayout = await userAuth.getUserPublishLayout(userId).catch(() => null);
|
||||
const visionResult = await buildVisionBody(userMessage, userId, publishLayout).catch(() => null);
|
||||
const publishDir = await userAuth.resolveWorkingDir(userId).catch(() => null);
|
||||
const visionResult = await buildVisionBody(
|
||||
userMessage,
|
||||
userId,
|
||||
publishLayout,
|
||||
publishDir,
|
||||
).catch(() => null);
|
||||
if (visionResult?.userMessage) {
|
||||
finalUserMessage = visionResult.userMessage;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user