065b1588b7
Fall back to workspace share thumbnails so already-published HTML can be sent instead of blocking on REQUIRED_FRESH. Also allow an explicit ALLOW_DIRECT_STABLE_RELEASE path matching the pre-2026-07-23 direct stable packaging flow. Co-authored-by: Cursor <cursoragent@cursor.com>
388 lines
8.5 KiB
JavaScript
388 lines
8.5 KiB
JavaScript
import assert from 'node:assert/strict';
|
|
import fs from 'node:fs';
|
|
import os from 'node:os';
|
|
import path from 'node:path';
|
|
import test from 'node:test';
|
|
import {
|
|
ensureWechatFreshPageThumbnailsAtWorkspace,
|
|
prepareWechatHtmlDeliveryAtWorkspace,
|
|
} from './mindspace-wechat-html-delivery.mjs';
|
|
|
|
function pageHtml({
|
|
title = 'Page',
|
|
cover = '',
|
|
} = {}) {
|
|
const coverField = cover
|
|
? `,"cover":"${cover}"`
|
|
: '';
|
|
return [
|
|
'<!doctype html><html><head>',
|
|
`<title>${title}</title>`,
|
|
`<meta name="mindspace-cover" content='{"tag":"页面","accent":"#3366cc","accent2":"#112233","subtitle":"测试页面"${coverField}}'>`,
|
|
'</head><body><main>',
|
|
'x'.repeat(600),
|
|
'</main></body></html>',
|
|
].join('');
|
|
}
|
|
|
|
function canonical(relativePath) {
|
|
return `https://example.com/MindSpace/user-1/${relativePath}`;
|
|
}
|
|
|
|
test('MindSpace prepares WeChat HTML delivery with logical artifacts only', (t) => {
|
|
const publishDir = fs.mkdtempSync(
|
|
path.join(
|
|
os.tmpdir(),
|
|
'mindspace-wechat-html-',
|
|
),
|
|
);
|
|
t.after(() => {
|
|
fs.rmSync(publishDir, {
|
|
recursive: true,
|
|
force: true,
|
|
});
|
|
});
|
|
const content = pageHtml({
|
|
title: 'Hello',
|
|
});
|
|
const result =
|
|
prepareWechatHtmlDeliveryAtWorkspace({
|
|
publishDir,
|
|
buildCanonicalUrl: canonical,
|
|
requestStartedAt: Date.now() - 100,
|
|
reply: {
|
|
text:
|
|
'页面完成:https://wrong.example/MindSpace/user-1/public/hello.html',
|
|
messages: [
|
|
{
|
|
id: 'assistant-1',
|
|
role: 'assistant',
|
|
content: [
|
|
{
|
|
type: 'toolRequest',
|
|
toolCall: {
|
|
value: {
|
|
name: 'write_file',
|
|
arguments: {
|
|
path:
|
|
'public/hello.html',
|
|
content,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
intent: {
|
|
agentText:
|
|
'生成 public/hello.html',
|
|
},
|
|
});
|
|
|
|
assert.equal(
|
|
fs.readFileSync(
|
|
path.join(
|
|
publishDir,
|
|
'public',
|
|
'hello.html',
|
|
),
|
|
'utf8',
|
|
),
|
|
content,
|
|
);
|
|
assert.equal(
|
|
result.confirmedArtifacts.length,
|
|
1,
|
|
);
|
|
assert.deepEqual(
|
|
result.confirmedArtifacts[0],
|
|
{
|
|
relativePath: 'public/hello.html',
|
|
url: canonical('public/hello.html'),
|
|
canonicalUrl:
|
|
canonical('public/hello.html'),
|
|
exists: true,
|
|
isStub: false,
|
|
isHtmlDocument: true,
|
|
sharePreview: {
|
|
ok: false,
|
|
reason: 'missing_description',
|
|
},
|
|
auxiliary: false,
|
|
sizeBytes:
|
|
Buffer.byteLength(content),
|
|
},
|
|
);
|
|
assert.equal(
|
|
Object.hasOwn(
|
|
result.confirmedArtifacts[0],
|
|
'localPath',
|
|
),
|
|
false,
|
|
);
|
|
assert.equal(result.hasValidReplyLink, true);
|
|
assert.deepEqual(result.validReplyUrls, [
|
|
'https://wrong.example/MindSpace/user-1/public/hello.html',
|
|
]);
|
|
});
|
|
|
|
test('MindSpace copies a generated root HTML file into public before delivery', (t) => {
|
|
const publishDir = fs.mkdtempSync(
|
|
path.join(
|
|
os.tmpdir(),
|
|
'mindspace-wechat-root-html-',
|
|
),
|
|
);
|
|
t.after(() => {
|
|
fs.rmSync(publishDir, {
|
|
recursive: true,
|
|
force: true,
|
|
});
|
|
});
|
|
fs.writeFileSync(
|
|
path.join(publishDir, 'guide.html'),
|
|
pageHtml({ title: 'Guide' }),
|
|
'utf8',
|
|
);
|
|
const result =
|
|
prepareWechatHtmlDeliveryAtWorkspace({
|
|
publishDir,
|
|
buildCanonicalUrl: canonical,
|
|
reply: {
|
|
text: 'done',
|
|
messages: [
|
|
{
|
|
role: 'assistant',
|
|
content: [
|
|
{
|
|
type: 'toolRequest',
|
|
toolCall: {
|
|
value: {
|
|
name: 'developer',
|
|
arguments: {
|
|
action: 'write',
|
|
path: 'guide.html',
|
|
},
|
|
},
|
|
},
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
intent: {
|
|
agentText: '生成 guide.html',
|
|
},
|
|
allowRecentArtifacts: false,
|
|
});
|
|
|
|
assert.equal(
|
|
fs.existsSync(
|
|
path.join(
|
|
publishDir,
|
|
'public',
|
|
'guide.html',
|
|
),
|
|
),
|
|
true,
|
|
);
|
|
assert.equal(
|
|
result.publishedArtifacts[0]
|
|
.relativePath,
|
|
'public/guide.html',
|
|
);
|
|
});
|
|
|
|
test('MindSpace verifies and renders fresh WeChat page thumbnails without returning paths', async (t) => {
|
|
const publishDir = fs.mkdtempSync(
|
|
path.join(
|
|
os.tmpdir(),
|
|
'mindspace-wechat-thumbnail-',
|
|
),
|
|
);
|
|
t.after(() => {
|
|
fs.rmSync(publishDir, {
|
|
recursive: true,
|
|
force: true,
|
|
});
|
|
});
|
|
const relativePath = 'public/fresh.html';
|
|
const htmlPath =
|
|
path.join(publishDir, relativePath);
|
|
fs.mkdirSync(path.dirname(htmlPath), {
|
|
recursive: true,
|
|
});
|
|
fs.writeFileSync(
|
|
htmlPath,
|
|
pageHtml({
|
|
title: 'Fresh',
|
|
cover: 'fresh.png',
|
|
}),
|
|
'utf8',
|
|
);
|
|
const result =
|
|
await ensureWechatFreshPageThumbnailsAtWorkspace(
|
|
{
|
|
publishDir,
|
|
buildCanonicalUrl: canonical,
|
|
artifacts: [
|
|
{
|
|
relativePath,
|
|
exists: true,
|
|
},
|
|
],
|
|
images: [
|
|
{
|
|
jobId: 'job-1',
|
|
purpose: 'hero',
|
|
mimeType: 'image/png',
|
|
htmlSrc: 'fresh.png',
|
|
},
|
|
],
|
|
},
|
|
);
|
|
|
|
assert.equal(result.ok, true);
|
|
assert.deepEqual(
|
|
result.matchRelativePaths,
|
|
[relativePath],
|
|
);
|
|
assert.deepEqual(
|
|
result.thumbnailRelativePaths,
|
|
['public/fresh.thumbnail.svg'],
|
|
);
|
|
assert.equal(
|
|
fs.existsSync(
|
|
path.join(
|
|
publishDir,
|
|
'public',
|
|
'fresh.thumbnail.svg',
|
|
),
|
|
),
|
|
true,
|
|
);
|
|
assert.equal(
|
|
JSON.stringify(result).includes(
|
|
publishDir,
|
|
),
|
|
false,
|
|
);
|
|
});
|
|
|
|
test('MindSpace falls back to workspace thumbnails when fresh image_make is missing', async (t) => {
|
|
const publishDir = fs.mkdtempSync(
|
|
path.join(
|
|
os.tmpdir(),
|
|
'mindspace-wechat-thumbnail-fallback-',
|
|
),
|
|
);
|
|
t.after(() => {
|
|
fs.rmSync(publishDir, {
|
|
recursive: true,
|
|
force: true,
|
|
});
|
|
});
|
|
const relativePath = 'public/fallback.html';
|
|
const htmlPath = path.join(publishDir, relativePath);
|
|
fs.mkdirSync(path.dirname(htmlPath), { recursive: true });
|
|
fs.writeFileSync(
|
|
htmlPath,
|
|
pageHtml({ title: 'Fallback' }),
|
|
'utf8',
|
|
);
|
|
|
|
const result = await ensureWechatFreshPageThumbnailsAtWorkspace({
|
|
publishDir,
|
|
buildCanonicalUrl: canonical,
|
|
artifacts: [{ relativePath, exists: true }],
|
|
images: [],
|
|
});
|
|
|
|
assert.equal(result.ok, true);
|
|
assert.equal(result.reason, 'workspace_thumbnail_fallback');
|
|
assert.deepEqual(result.matchRelativePaths, [relativePath]);
|
|
assert.equal(
|
|
fs.existsSync(path.join(publishDir, 'public', 'fallback.thumbnail.svg')),
|
|
true,
|
|
);
|
|
});
|
|
|
|
test('MindSpace owns unambiguous cover repair before thumbnail delivery', async (t) => {
|
|
const publishDir = fs.mkdtempSync(
|
|
path.join(
|
|
os.tmpdir(),
|
|
'mindspace-wechat-repair-',
|
|
),
|
|
);
|
|
t.after(() => {
|
|
fs.rmSync(publishDir, {
|
|
recursive: true,
|
|
force: true,
|
|
});
|
|
});
|
|
const relativePath =
|
|
'public/repaired.html';
|
|
const htmlPath =
|
|
path.join(publishDir, relativePath);
|
|
fs.mkdirSync(path.dirname(htmlPath), {
|
|
recursive: true,
|
|
});
|
|
const original = pageHtml({
|
|
title: 'Repair',
|
|
});
|
|
fs.writeFileSync(
|
|
htmlPath,
|
|
original,
|
|
'utf8',
|
|
);
|
|
const result =
|
|
await ensureWechatFreshPageThumbnailsAtWorkspace(
|
|
{
|
|
publishDir,
|
|
buildCanonicalUrl: canonical,
|
|
artifacts: [{ relativePath }],
|
|
images: [
|
|
{
|
|
jobId: 'job-repair',
|
|
purpose: 'hero',
|
|
mimeType: 'image/png',
|
|
htmlSrc: 'repair.png',
|
|
},
|
|
],
|
|
messages: [
|
|
{
|
|
role: 'assistant',
|
|
content: [
|
|
{
|
|
type: 'toolRequest',
|
|
toolCall: {
|
|
value: {
|
|
name: 'write_file',
|
|
arguments: {
|
|
path: relativePath,
|
|
content: original,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
],
|
|
},
|
|
],
|
|
repairEnabled: true,
|
|
},
|
|
);
|
|
|
|
assert.equal(result.ok, true);
|
|
assert.equal(result.repair.ok, true);
|
|
assert.equal(
|
|
result.repair.relativePath,
|
|
relativePath,
|
|
);
|
|
assert.match(
|
|
fs.readFileSync(htmlPath, 'utf8'),
|
|
/repair\.png/,
|
|
);
|
|
});
|