import test from 'node:test';
import assert from 'node:assert/strict';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import {
hasRecentOwnPublicHtmlReference,
hasPublicHtmlWriteRequestInSessionEvent,
isStubPublicHtmlContent,
collectOwnPublicHtmlArtifactRefs,
collectOwnPublicHtmlRelativePaths,
materializeMissingPublicHtmlWrites,
materializePublicHtmlWritesFromSessionEvent,
syncPublicDocxDownloads,
syncPublicHtmlAfterFinish,
} from './mindspace-public-finish-sync.mjs';
import { createMindSpacePublicFinishService } from './mindspace-public-finish-service.mjs';
const CURRENT_USER = { id: 'a6fb1e97-2b0f-447b-b138-4561d8e5c53e', username: 'john' };
test('detects a recent own public html link', () => {
const messages = [
{
role: 'assistant',
content: [
{
type: 'text',
text: '[Hello](https://m.tkmind.cn/MindSpace/a6fb1e97-2b0f-447b-b138-4561d8e5c53e/public/hello.html)',
},
],
metadata: { userVisible: true },
},
];
assert.equal(hasRecentOwnPublicHtmlReference(messages, CURRENT_USER), true);
});
test('detects a recent public html tool write hint without a full url', () => {
const messages = [
{
role: 'user',
content: [
{
type: 'toolResponse',
toolResult: {
value: {
content: [{ type: 'text', text: '已写入 public/guizhou-guide.html(862 行)' }],
},
},
},
],
metadata: { userVisible: true, displayText: '已写入 public/guizhou-guide.html(862 行)' },
},
];
assert.equal(hasRecentOwnPublicHtmlReference(messages, CURRENT_USER), true);
});
test('detects a public html tool request even when the message has no text', () => {
const messages = [
{
role: 'assistant',
content: [
{
type: 'toolRequest',
toolCall: {
value: {
name: 'sandbox-fs__write_file',
arguments: {
path: 'public/winter-melon-soup-recipe.html',
content: '
Winter melon soup',
},
},
},
},
],
metadata: { userVisible: true },
},
];
assert.equal(hasRecentOwnPublicHtmlReference(messages, CURRENT_USER), true);
});
test('stream event prefilter detects public HTML writes without filesystem access', () => {
assert.equal(
hasPublicHtmlWriteRequestInSessionEvent({
type: 'Message',
message: {
role: 'assistant',
content: [
{
type: 'toolRequest',
toolCall: {
value: {
name: 'edit_file',
arguments: {
path: 'public/page.html',
old_str: 'before',
new_str: 'after',
},
},
},
},
],
},
}),
true,
);
assert.equal(
hasPublicHtmlWriteRequestInSessionEvent({
type: 'Message',
message: {
role: 'assistant',
content: [{ type: 'text', text: 'done' }],
},
}),
false,
);
});
test('detects public html references beyond the old eight message window', () => {
const messages = [
{
role: 'assistant',
content: [
{
type: 'toolRequest',
toolCall: {
value: {
name: 'sandbox-fs__write_file',
arguments: {
path: 'public/long-flow.html',
content: 'Long flow',
},
},
},
},
],
},
...Array.from({ length: 12 }, (_, index) => ({
role: index % 2 === 0 ? 'assistant' : 'user',
content: [{ type: 'text', text: `validation step ${index + 1}` }],
})),
];
assert.equal(hasRecentOwnPublicHtmlReference(messages, CURRENT_USER), true);
});
test('ignores other users public html links', () => {
const messages = [
{
role: 'assistant',
content: [
{
type: 'text',
text: '[Hello](https://m.tkmind.cn/MindSpace/other-user/public/hello.html)',
},
],
metadata: { userVisible: true },
},
];
assert.equal(hasRecentOwnPublicHtmlReference(messages, CURRENT_USER), false);
});
test('materializeMissingPublicHtmlWrites writes html from sandbox write_file tool calls', () => {
const publishDir = fs.mkdtempSync(path.join(os.tmpdir(), 'public-finish-sync-'));
try {
const messages = [
{
id: 'msg-public-1',
role: 'assistant',
content: [
{
type: 'toolRequest',
toolCall: {
value: {
name: 'sandbox-fs__write_file',
arguments: {
path: 'public/summer-essay.html',
content: 'Summer',
},
},
},
},
],
metadata: { userVisible: true },
},
];
const result = materializeMissingPublicHtmlWrites({ messages, publishDir });
assert.deepEqual(result.materialized, ['public/summer-essay.html']);
assert.equal(
fs.readFileSync(path.join(publishDir, 'public/summer-essay.html'), 'utf8'),
'Summer',
);
} finally {
fs.rmSync(publishDir, { recursive: true, force: true });
}
});
test('syncPublicHtmlAfterFinish forwards session source to workspace sync', async () => {
const publishDir = fs.mkdtempSync(path.join(os.tmpdir(), 'public-finish-sync-'));
try {
const calls = [];
const messages = [
{
id: 'msg-public-1',
role: 'assistant',
content: [
{
type: 'toolRequest',
toolCall: {
value: {
name: 'write_file',
arguments: {
path: 'public/session-page.html',
content: 'Session page',
},
},
},
},
],
},
];
const result = await syncPublicHtmlAfterFinish({
messages,
currentUser: CURRENT_USER,
publishDir,
sessionId: 'session-1',
syncWorkspaceAssets: async (...args) => {
calls.push(args);
},
});
assert.equal(result.synced, true);
assert.deepEqual(calls, [[
CURRENT_USER.id,
{
categoryCode: 'public',
sourceSessionId: 'session-1',
sourceMessageId: 'msg-public-1',
onlyRelativePaths: ['public/session-page.html'],
},
]]);
} finally {
fs.rmSync(publishDir, { recursive: true, force: true });
}
});
test('syncPublicHtmlAfterFinish registers existing public html artifacts for the session', async () => {
const publishDir = fs.mkdtempSync(path.join(os.tmpdir(), 'public-finish-sync-'));
try {
fs.mkdirSync(path.join(publishDir, 'public'), { recursive: true });
fs.writeFileSync(path.join(publishDir, 'public/123.html'), '一二三');
const calls = [];
const messages = [
{
id: 'msg-public-1',
role: 'assistant',
content: [
{
type: 'text',
text: `[一二三](https://m.tkmind.cn/MindSpace/${CURRENT_USER.id}/public/123.html)`,
},
],
},
];
const result = await syncPublicHtmlAfterFinish({
messages,
currentUser: CURRENT_USER,
publishDir,
sessionId: 'session-1',
syncWorkspaceAssets: async () => {},
registerPublicHtmlArtifacts: async (...args) => {
calls.push(args);
},
});
assert.deepEqual(result.publicHtmlRelativePaths, ['public/123.html']);
assert.deepEqual(calls, [
[
CURRENT_USER.id,
{
sessionId: 'session-1',
relativePaths: ['public/123.html'],
artifactRefs: [{ relativePath: 'public/123.html', messageId: 'msg-public-1' }],
},
],
]);
} finally {
fs.rmSync(publishDir, { recursive: true, force: true });
}
});
test('syncPublicHtmlAfterFinish materializes private image assets into public html files', async () => {
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'public-finish-sync-assets-'));
const publishDir = path.join(root, 'MindSpace', CURRENT_USER.id);
const storageRoot = path.join(root, 'storage');
const assetId = '55555555-5555-4555-8555-555555555555';
const storageKey = `users/${CURRENT_USER.id}/assets/${assetId}/versions/v1`;
fs.mkdirSync(path.dirname(path.join(storageRoot, storageKey)), { recursive: true });
fs.writeFileSync(path.join(storageRoot, storageKey), Buffer.from([0xff, 0xd8, 0xff, 0xd9]));
try {
fs.mkdirSync(path.join(publishDir, 'public'), { recursive: true });
fs.writeFileSync(
path.join(publishDir, 'public/tea.html'),
`
`,
);
const messages = [
{
id: 'msg-tea-1',
role: 'assistant',
content: [
{
type: 'text',
text: `[茶馆](https://m.tkmind.cn/MindSpace/${CURRENT_USER.id}/public/tea.html)`,
},
],
},
];
const pool = {
query: async () => [
[{ id: assetId, mime_type: 'image/jpeg', original_filename: 'tea.jpg', storage_key: storageKey }],
],
};
const result = await syncPublicHtmlAfterFinish({
messages,
currentUser: CURRENT_USER,
publishDir,
sessionId: 'session-tea',
pool,
storageRoot,
h5Root: root,
syncWorkspaceAssets: async () => {},
});
assert.equal(result.assetMaterialization.materialized.length, 1);
const saved = fs.readFileSync(path.join(publishDir, 'public/tea.html'), 'utf8');
assert.match(saved, /\.tmp-images\/55555555-5555-4555-8555-555555555555\.jpg/);
assert.ok(fs.existsSync(path.join(publishDir, 'public/.tmp-images', `${assetId}.jpg`)));
} finally {
fs.rmSync(root, { recursive: true, force: true });
}
});
test('collectOwnPublicHtmlArtifactRefs includes source message ids when available', () => {
const publishDir = fs.mkdtempSync(path.join(os.tmpdir(), 'public-finish-sync-'));
try {
fs.mkdirSync(path.join(publishDir, 'public'), { recursive: true });
fs.writeFileSync(path.join(publishDir, 'public/own.html'), 'Own');
const refs = collectOwnPublicHtmlArtifactRefs({
publishDir,
currentUser: CURRENT_USER,
messages: [
{
id: 'assistant-msg-1',
content: [
{
type: 'text',
text: `https://m.tkmind.cn/MindSpace/${CURRENT_USER.id}/public/own.html`,
},
],
},
],
});
assert.deepEqual(refs, [{ relativePath: 'public/own.html', messageId: 'assistant-msg-1' }]);
} finally {
fs.rmSync(publishDir, { recursive: true, force: true });
}
});
test('collectOwnPublicHtmlRelativePaths ignores missing and other-user public html links', () => {
const publishDir = fs.mkdtempSync(path.join(os.tmpdir(), 'public-finish-sync-'));
try {
fs.mkdirSync(path.join(publishDir, 'public'), { recursive: true });
fs.writeFileSync(path.join(publishDir, 'public/own.html'), 'Own');
const paths = collectOwnPublicHtmlRelativePaths({
publishDir,
currentUser: CURRENT_USER,
messages: [
{
content: [
{
type: 'text',
text: [
`https://m.tkmind.cn/MindSpace/${CURRENT_USER.id}/public/own.html`,
`https://m.tkmind.cn/MindSpace/${CURRENT_USER.id}/public/missing.html`,
'https://m.tkmind.cn/MindSpace/other-user/public/other.html',
].join('\n'),
},
],
},
],
});
assert.deepEqual(paths, ['public/own.html']);
} finally {
fs.rmSync(publishDir, { recursive: true, force: true });
}
});
test('materializeMissingPublicHtmlWrites writes html from developer write tool calls', () => {
const publishDir = fs.mkdtempSync(path.join(os.tmpdir(), 'public-finish-sync-'));
try {
const messages = [
{
role: 'assistant',
content: [
{
type: 'toolRequest',
toolCall: {
value: {
name: 'write',
arguments: {
path: 'public/guizhou-guide.html',
content: 'Guizhou',
},
},
},
},
],
},
];
const result = materializeMissingPublicHtmlWrites({ messages, publishDir });
assert.deepEqual(result.materialized, ['public/guizhou-guide.html']);
assert.equal(
fs.readFileSync(path.join(publishDir, 'public/guizhou-guide.html'), 'utf8'),
'Guizhou',
);
} finally {
fs.rmSync(publishDir, { recursive: true, force: true });
}
});
test('materializeMissingPublicHtmlWrites replaces stub placeholder html', () => {
const publishDir = fs.mkdtempSync(path.join(os.tmpdir(), 'public-finish-sync-'));
try {
const stubPath = path.join(publishDir, 'public/guide.html');
fs.mkdirSync(path.dirname(stubPath), { recursive: true });
fs.writeFileSync(stubPath, '根据你的要求临时补出的简版页面', 'utf8');
assert.equal(isStubPublicHtmlContent(fs.readFileSync(stubPath, 'utf8')), true);
const messages = [
{
role: 'assistant',
content: [
{
type: 'toolRequest',
toolCall: {
value: {
name: 'write_file',
arguments: {
path: 'public/guide.html',
content: 'Real GuideFull page',
},
},
},
},
],
},
];
const result = materializeMissingPublicHtmlWrites({ messages, publishDir });
assert.deepEqual(result.materialized, ['public/guide.html']);
assert.match(fs.readFileSync(stubPath, 'utf8'), /Real Guide/);
} finally {
fs.rmSync(publishDir, { recursive: true, force: true });
}
});
test('syncPublicDocxDownloads copies newer complete oa docx into public download targets', () => {
const publishDir = fs.mkdtempSync(path.join(os.tmpdir(), 'public-docx-sync-'));
try {
const oaDir = path.join(publishDir, 'oa');
const publicDir = path.join(publishDir, 'public');
fs.mkdirSync(oaDir, { recursive: true });
fs.mkdirSync(publicDir, { recursive: true });
fs.writeFileSync(path.join(oaDir, 'latest-plan.docx'), 'x'.repeat(9000));
fs.writeFileSync(path.join(publicDir, 'download.html'), 'download');
fs.writeFileSync(path.join(publicDir, 'old-plan.docx'), 'stub');
const result = syncPublicDocxDownloads({ publishDir, minCompleteSize: 8000 });
assert.deepEqual(result.synced, ['public/old-plan.docx']);
assert.equal(result.source, 'latest-plan.docx');
assert.equal(fs.readFileSync(path.join(publicDir, 'old-plan.docx'), 'utf8').length, 9000);
} finally {
fs.rmSync(publishDir, { recursive: true, force: true });
}
});
test('syncPublicDocxDownloads skips public docx that is already up to date', () => {
const publishDir = fs.mkdtempSync(path.join(os.tmpdir(), 'public-docx-sync-'));
try {
const oaDir = path.join(publishDir, 'oa');
const publicDir = path.join(publishDir, 'public');
fs.mkdirSync(oaDir, { recursive: true });
fs.mkdirSync(publicDir, { recursive: true });
fs.writeFileSync(path.join(oaDir, 'latest-plan.docx'), 'x'.repeat(9000));
fs.writeFileSync(path.join(publicDir, 'download.html'), 'download');
fs.writeFileSync(path.join(publicDir, 'old-plan.docx'), 'x'.repeat(9000));
const result = syncPublicDocxDownloads({ publishDir, minCompleteSize: 8000 });
assert.deepEqual(result.synced, []);
assert.deepEqual(result.skipped, ['public/old-plan.docx']);
} finally {
fs.rmSync(publishDir, { recursive: true, force: true });
}
});
test('syncPublicDocxDownloads copies same-named oa docx even when it is small', () => {
const publishDir = fs.mkdtempSync(path.join(os.tmpdir(), 'public-docx-sync-'));
try {
const oaDir = path.join(publishDir, 'oa');
const publicDir = path.join(publishDir, 'public');
fs.mkdirSync(oaDir, { recursive: true });
fs.mkdirSync(publicDir, { recursive: true });
fs.writeFileSync(path.join(oaDir, 'industry-distribution-report.docx'), 'x'.repeat(3294));
fs.writeFileSync(
path.join(publicDir, 'industry-distribution-report.html'),
'download',
);
const result = syncPublicDocxDownloads({ publishDir, minCompleteSize: 1024 });
assert.deepEqual(result.synced, ['public/industry-distribution-report.docx']);
assert.deepEqual(result.missing, []);
assert.equal(result.source, 'industry-distribution-report.docx');
assert.equal(
fs.readFileSync(path.join(publicDir, 'industry-distribution-report.docx'), 'utf8').length,
3294,
);
} finally {
fs.rmSync(publishDir, { recursive: true, force: true });
}
});
test('syncPublicDocxDownloads reports missing public docx when no safe source exists', () => {
const publishDir = fs.mkdtempSync(path.join(os.tmpdir(), 'public-docx-sync-'));
try {
const oaDir = path.join(publishDir, 'oa');
const publicDir = path.join(publishDir, 'public');
fs.mkdirSync(oaDir, { recursive: true });
fs.mkdirSync(publicDir, { recursive: true });
fs.writeFileSync(path.join(oaDir, '暑假计划.docx'), 'x'.repeat(4084));
fs.writeFileSync(path.join(oaDir, '韩国旅游攻略.docx'), 'x'.repeat(5889));
fs.writeFileSync(
path.join(publicDir, 'industry-distribution-report.html'),
'download',
);
const result = syncPublicDocxDownloads({ publishDir, minCompleteSize: 1024 });
assert.deepEqual(result.synced, []);
assert.deepEqual(result.missing, ['public/industry-distribution-report.docx']);
assert.equal(
fs.existsSync(path.join(publicDir, 'industry-distribution-report.docx')),
false,
);
} finally {
fs.rmSync(publishDir, { recursive: true, force: true });
}
});
test('materializeMissingPublicHtmlWrites replaces existing real html when content changed', () => {
const publishDir = fs.mkdtempSync(path.join(os.tmpdir(), 'public-finish-sync-'));
try {
const htmlPath = path.join(publishDir, 'public/guide.html');
fs.mkdirSync(path.dirname(htmlPath), { recursive: true });
fs.writeFileSync(htmlPath, 'Existing', 'utf8');
const result = materializeMissingPublicHtmlWrites({
messages: [
{
role: 'assistant',
content: [
{
type: 'toolRequest',
toolCall: {
value: {
name: 'write_file',
arguments: {
path: 'public/guide.html',
content: 'Replacement',
},
},
},
},
],
},
],
publishDir,
});
assert.deepEqual(result.materialized, ['public/guide.html']);
assert.deepEqual(result.skipped, []);
assert.match(fs.readFileSync(htmlPath, 'utf8'), /Replacement/);
} finally {
fs.rmSync(publishDir, { recursive: true, force: true });
}
});
test('materializeMissingPublicHtmlWrites skips existing real html when content is unchanged', () => {
const publishDir = fs.mkdtempSync(path.join(os.tmpdir(), 'public-finish-sync-'));
try {
const htmlPath = path.join(publishDir, 'public/guide.html');
const html = 'Existing';
fs.mkdirSync(path.dirname(htmlPath), { recursive: true });
fs.writeFileSync(htmlPath, html, 'utf8');
const result = materializeMissingPublicHtmlWrites({
messages: [
{
role: 'assistant',
content: [
{
type: 'toolRequest',
toolCall: {
value: {
name: 'write_file',
arguments: {
path: 'public/guide.html',
content: html,
},
},
},
},
],
},
],
publishDir,
});
assert.deepEqual(result.materialized, []);
assert.deepEqual(result.skipped, ['public/guide.html']);
assert.match(fs.readFileSync(htmlPath, 'utf8'), /Existing/);
} finally {
fs.rmSync(publishDir, { recursive: true, force: true });
}
});
test('detects a recent edit_file tool request on an existing public html page', () => {
const publishDir = fs.mkdtempSync(path.join(os.tmpdir(), 'public-finish-sync-'));
try {
const htmlPath = path.join(publishDir, 'public/guide.html');
fs.mkdirSync(path.dirname(htmlPath), { recursive: true });
fs.writeFileSync(htmlPath, '一二三', 'utf8');
const messages = [
{
role: 'assistant',
content: [
{
type: 'toolRequest',
toolCall: {
value: {
name: 'edit_file',
arguments: {
path: 'public/guide.html',
old_str: '一二三',
new_str: '二三四',
},
},
},
},
],
metadata: { userVisible: true },
},
];
assert.equal(hasRecentOwnPublicHtmlReference(messages, CURRENT_USER, { publishDir }), true);
} finally {
fs.rmSync(publishDir, { recursive: true, force: true });
}
});
test('materializeMissingPublicHtmlWrites applies edit_file patches onto existing public html', () => {
const publishDir = fs.mkdtempSync(path.join(os.tmpdir(), 'public-finish-sync-'));
try {
const htmlPath = path.join(publishDir, 'public/guide.html');
fs.mkdirSync(path.dirname(htmlPath), { recursive: true });
fs.writeFileSync(htmlPath, 'Existing一二三', 'utf8');
const result = materializeMissingPublicHtmlWrites({
messages: [
{
role: 'assistant',
content: [
{
type: 'toolRequest',
toolCall: {
value: {
name: 'edit_file',
arguments: {
path: 'public/guide.html',
old_str: '一二三',
new_str: '二三四',
},
},
},
},
],
},
],
publishDir,
});
assert.deepEqual(result.materialized, ['public/guide.html']);
assert.match(fs.readFileSync(htmlPath, 'utf8'), /二三四/);
assert.doesNotMatch(fs.readFileSync(htmlPath, 'utf8'), /一二三/);
} finally {
fs.rmSync(publishDir, { recursive: true, force: true });
}
});
test('materializeMissingPublicHtmlWrites chains multiple edit_file patches for the same html', () => {
const publishDir = fs.mkdtempSync(path.join(os.tmpdir(), 'public-finish-sync-'));
try {
const htmlPath = path.join(publishDir, 'public/guide.html');
fs.mkdirSync(path.dirname(htmlPath), { recursive: true });
fs.writeFileSync(htmlPath, '一 · 二三', 'utf8');
const result = materializeMissingPublicHtmlWrites({
messages: [
{
role: 'assistant',
content: [
{
type: 'toolRequest',
toolCall: {
value: {
name: 'edit_file',
arguments: {
path: 'public/guide.html',
old_str: '一',
new_str: '二',
},
},
},
},
{
type: 'toolRequest',
toolCall: {
value: {
name: 'edit_file',
arguments: {
path: 'public/guide.html',
old_str: '二三',
new_str: '三四',
},
},
},
},
],
},
],
publishDir,
});
assert.deepEqual(result.materialized, ['public/guide.html']);
assert.match(fs.readFileSync(htmlPath, 'utf8'), /二 · 三四/);
} finally {
fs.rmSync(publishDir, { recursive: true, force: true });
}
});
test('materializePublicHtmlWritesFromSessionEvent writes public html from message event before finish', () => {
const publishDir = fs.mkdtempSync(path.join(os.tmpdir(), 'public-event-sync-'));
try {
const result = materializePublicHtmlWritesFromSessionEvent(
{
type: 'Message',
message: {
id: 'assistant-1',
role: 'assistant',
metadata: { userVisible: true },
content: [
{
type: 'toolRequest',
toolCall: {
value: {
name: 'write',
arguments: {
path: 'public/shenwu-gate.html',
content: '神口门',
},
},
},
},
],
},
},
{ publishDir },
);
assert.deepEqual(result.materialized, ['public/shenwu-gate.html']);
assert.match(
fs.readFileSync(path.join(publishDir, 'public/shenwu-gate.html'), 'utf8'),
/神口门/,
);
} finally {
fs.rmSync(publishDir, { recursive: true, force: true });
}
});
function createPublicFinishService(overrides = {}) {
const calls = [];
const service = createMindSpacePublicFinishService({
pool: { query: async () => [[]] },
h5Root: '/srv/h5',
storageRoot: '/srv/storage',
env: {
H5_PUBLIC_BASE_URL: 'https://example.com',
},
conversationArtifactService: {
async registerPublicHtmlArtifacts(input) {
calls.push({ kind: 'artifacts', input });
return [];
},
},
resolveMindSpaceUserPublishDirFn() {
return '/srv/h5/MindSpace/user-1';
},
buildMindSpacePublicUrlForUserFn({
user,
relativePath,
}) {
return `https://example.com/MindSpace/${user.id}/${relativePath}`;
},
evaluateH5HtmlFinishGuardFn(input) {
calls.push({
kind: 'delivery-evaluation',
input,
});
return {
needsRepair: false,
missingHtml: [],
missingAssets: [],
browserStorageViolations: [],
htmlGenerationNeedsRetry: false,
linkExists() {
return true;
},
};
},
async preparePageDataAfterFinishFn(
input,
) {
calls.push({
kind: 'page-data-preparation',
input,
});
return {
autoBind: {
bound: [],
skipped: [],
errors: [],
},
evaluation: {
structuralPageData: false,
relevantFiles: [
{
relativePath:
'public/page.html',
absolutePath:
'/srv/h5/MindSpace/user-1/public/page.html',
content: '',
},
],
htmlIssues: [],
unboundFiles: [],
needsRepair: false,
},
};
},
...overrides,
});
return { calls, service };
}
test('public finish service resolves stream writes and canonical URLs inside MindSpace', async () => {
const calls = [];
const setup = createPublicFinishService({
materializeSessionEventFn(event, options) {
calls.push({ kind: 'materialize', event, options });
return {
materialized: ['public/page.html'],
skipped: [],
};
},
collectArtifactRefsFn(input) {
calls.push({ kind: 'collect', input });
return [
{
relativePath: 'public/page.html',
messageId: 'message-1',
},
];
},
});
const event = {
type: 'Message',
message: {
id: 'message-1',
role: 'assistant',
},
};
const result =
await setup.service.materializeSessionEvent({
userId: 'user-1',
event,
});
assert.equal(
calls[0].options.publishDir,
'/srv/h5/MindSpace/user-1',
);
assert.deepEqual(result.publicHtmlRelativePaths, [
'public/page.html',
]);
assert.equal(
result.publicHtmlArtifacts[0].canonicalUrl,
'https://example.com/MindSpace/user-1/public/page.html',
);
});
test('public finish service owns WeChat HTML preparation, sync, and package registration', async () => {
const calls = [];
const setup = createPublicFinishService({
prepareWechatHtmlDeliveryAtWorkspaceFn(
input,
) {
calls.push({
kind: 'prepare-html',
input,
});
return {
materialization: {
materialized: [
'public/page.html',
],
skipped: [],
},
publishedArtifacts: [
{
relativePath:
'public/page.html',
url:
input.buildCanonicalUrl(
'public/page.html',
),
exists: true,
isStub: false,
},
],
expectedArtifacts: [],
recentArtifacts: [],
confirmedArtifacts: [
{
relativePath:
'public/page.html',
url:
input.buildCanonicalUrl(
'public/page.html',
),
exists: true,
isStub: false,
},
],
verifiedArtifacts: [],
validReplyUrls: [],
hasValidReplyLink: false,
};
},
async syncAfterFinishFn(input) {
calls.push({
kind: 'finish-sync',
input,
});
return { synced: true };
},
async syncWorkspaceAssets(
userId,
options,
) {
calls.push({
kind: 'asset-sync',
userId,
options,
});
},
conversationArtifactService: {
async registerPublicHtmlArtifacts(
input,
) {
calls.push({
kind: 'artifact-register',
input,
});
return [];
},
},
});
const result =
await setup.service
.prepareWechatHtmlDelivery({
userId: 'user-1',
sessionId: 'session-1',
reply: {
text: 'done',
messages: [
{
role: 'assistant',
},
],
},
intent: {
agentText: 'build page',
},
});
assert.equal(
calls[0].input.publishDir,
'/srv/h5/MindSpace/user-1',
);
assert.equal(
result.confirmedArtifacts[0].url,
'https://example.com/MindSpace/user-1/public/page.html',
);
assert.equal(
Object.hasOwn(
result.confirmedArtifacts[0],
'localPath',
),
false,
);
assert.ok(
calls.some(
(call) =>
call.kind === 'asset-sync' &&
call.options.onlyRelativePaths
.includes('public/page.html'),
),
);
assert.ok(
calls.some(
(call) =>
call.kind ===
'artifact-register' &&
call.input.sessionId ===
'session-1',
),
);
});
test('public finish service owns WeChat fresh thumbnail rendering and metadata sync', async () => {
const calls = [];
const setup = createPublicFinishService({
async ensureWechatFreshPageThumbnailsAtWorkspaceFn(
input,
) {
calls.push({
kind: 'fresh-thumbnail',
input,
});
return {
ok: true,
reason: null,
matchRelativePaths: [
'public/page.html',
],
thumbnailRelativePaths: [
'public/page.thumbnail.svg',
],
};
},
async syncWorkspaceAssets(
userId,
options,
) {
calls.push({
kind: 'asset-sync',
userId,
options,
});
},
conversationArtifactService: {
async registerPublicHtmlArtifacts(
input,
) {
calls.push({
kind: 'artifact-register',
input,
});
return [];
},
},
});
const result =
await setup.service
.ensureWechatFreshPageThumbnails({
userId: 'user-1',
sessionId: 'session-1',
artifacts: [
{
relativePath:
'public/page.html',
},
],
images: [
{
jobId: 'job-1',
},
],
repairEnabled: true,
});
assert.equal(result.ok, true);
assert.deepEqual(
calls.find(
(call) =>
call.kind === 'asset-sync',
).options.onlyRelativePaths,
[
'public/page.html',
'public/page.thumbnail.svg',
],
);
assert.deepEqual(
calls.find(
(call) =>
call.kind ===
'artifact-register',
).input.relativePaths,
['public/page.html'],
);
});
test('public finish service owns WeChat Page Data delivery preparation', async () => {
const calls = [];
let outcomeCount = 0;
const setup = createPublicFinishService({
createPageServiceFn(pool, options) {
calls.push({
kind: 'page-service',
pool,
options,
});
return {
async findPageByRelativePath() {
return { id: 'page-1' };
},
};
},
async resolvePageDataCollectOutcomeAsyncFn(
input,
) {
calls.push({ kind: 'outcome', input });
outcomeCount += 1;
return outcomeCount === 1
? { action: 'retry' }
: {
action: 'send',
evaluation: {
relevantFiles: [
{
relativePath:
'public/page.html',
absolutePath:
'/srv/h5/MindSpace/user-1/public/page.html',
content: '',
},
],
},
};
},
async maybeAutoBindPageDataHtmlPagesFn(
input,
) {
calls.push({ kind: 'auto-bind', input });
return {
bound: [
{
relativePath: 'public/page.html',
workspaceUrl:
'https://example.com/MindSpace/user-1/public/page.html',
},
],
skipped: [],
errors: [],
};
},
buildPageDataDeliveryArtifactsFromBindResultFn(
autoBind,
publishDir,
options,
) {
calls.push({
kind: 'artifacts',
autoBind,
publishDir,
options,
});
return [
{
relativePath: 'public/page.html',
localPath:
'/srv/h5/MindSpace/user-1/public/page.html',
url: 'https://example.com/MindSpace/user-1/public/page.html',
},
];
},
async ensurePageDataDeliveryReadyFn(input) {
calls.push({
kind: 'delivery-check',
input,
});
return { ok: true, failures: [] };
},
rewritePageDataDeliveryLinksFn(
text,
artifacts,
) {
calls.push({
kind: 'rewrite',
text,
artifacts,
});
return '已发布:workspace-url';
},
});
const result =
await setup.service
.prepareWechatPageDataDelivery({
userId: 'user-1',
reply: {
text: '已发布:legacy-url',
messages: [],
},
intent: {
agentText: 'build survey',
},
publicBaseUrl:
'https://example.com',
requestStartedAt: 123,
});
assert.deepEqual(
calls
.map((call) => call.kind),
[
'outcome',
'page-service',
'auto-bind',
'outcome',
'artifacts',
'delivery-check',
'rewrite',
],
);
assert.equal(
calls[0].input.publishDir,
'/srv/h5/MindSpace/user-1',
);
assert.equal(
calls[2].input.storageRoot,
'/srv/storage',
);
assert.equal(result.outcome.action, 'send');
assert.equal(result.deliveryCheck.ok, true);
assert.equal(
result.rewrittenText,
'已发布:workspace-url',
);
assert.equal(
Object.hasOwn(
result.deliveryArtifacts[0],
'localPath',
),
false,
);
assert.equal(
Object.hasOwn(
result.outcome.evaluation
.relevantFiles[0],
'absolutePath',
),
false,
);
});
test('public finish service keeps workspace sync and artifact registration inside MindSpace', async () => {
const calls = [];
const setup = createPublicFinishService({
async syncWorkspaceAssets(userId, options) {
calls.push({
kind: 'workspace-assets',
userId,
options,
});
},
async syncAfterFinishFn(input) {
calls.push({ kind: 'finish', input });
await input.syncWorkspaceAssets('user-1', {
categoryCode: 'public',
});
await input.registerPublicHtmlArtifacts(
'user-1',
{
sessionId: 'session-1',
relativePaths: ['public/page.html'],
artifactRefs: [
{
relativePath: 'public/page.html',
messageId: 'message-1',
},
],
},
);
return {
materialized: ['public/page.html'],
skipped: [],
publicHtmlRelativePaths: [
'public/page.html',
],
publicHtmlArtifactRefs: [
{
relativePath: 'public/page.html',
messageId: 'message-1',
},
],
};
},
});
const result = await setup.service.syncAfterFinish({
userId: 'user-1',
sessionId: 'session-1',
messages: [],
});
assert.equal(calls[0].kind, 'finish');
assert.equal(
calls[0].input.publishDir,
'/srv/h5/MindSpace/user-1',
);
assert.equal(calls[0].input.storageRoot, '/srv/storage');
assert.equal(calls[1].kind, 'workspace-assets');
assert.equal(setup.calls[0].kind, 'artifacts');
assert.equal(
setup.calls[0].input.userId,
'user-1',
);
assert.equal(
result.publicHtmlArtifacts[0].canonicalUrl,
'https://example.com/MindSpace/user-1/public/page.html',
);
assert.equal(
result.deliveryEvaluation.needsRepair,
false,
);
assert.equal(
Object.hasOwn(
result.deliveryEvaluation,
'linkExists',
),
false,
);
const pageDataPreparation =
await setup.service.preparePageDataAfterFinish({
userId: 'user-1',
messages: [],
userText: 'build page',
});
assert.equal(
pageDataPreparation.evaluation
.relevantFiles[0].relativePath,
'public/page.html',
);
assert.equal(
Object.hasOwn(
pageDataPreparation.evaluation
.relevantFiles[0],
'absolutePath',
),
false,
);
assert.equal(
Object.hasOwn(
pageDataPreparation.evaluation
.relevantFiles[0],
'content',
),
false,
);
const preparationCall = setup.calls.find(
(call) =>
call.kind === 'page-data-preparation',
);
assert.equal(
preparationCall.input.publishDir,
'/srv/h5/MindSpace/user-1',
);
assert.equal(
preparationCall.input.storageRoot,
'/srv/storage',
);
assert.equal(
preparationCall.input.userText,
'build page',
);
});