749 lines
24 KiB
JavaScript
749 lines
24 KiB
JavaScript
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,
|
||
isStubPublicHtmlContent,
|
||
collectOwnPublicHtmlArtifactRefs,
|
||
collectOwnPublicHtmlRelativePaths,
|
||
materializeMissingPublicHtmlWrites,
|
||
materializePublicHtmlWritesFromSessionEvent,
|
||
syncPublicDocxDownloads,
|
||
syncPublicHtmlAfterFinish,
|
||
} from './mindspace-public-finish-sync.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: '<!doctype html><title>Winter melon soup</title>',
|
||
},
|
||
},
|
||
},
|
||
},
|
||
],
|
||
metadata: { userVisible: true },
|
||
},
|
||
];
|
||
assert.equal(hasRecentOwnPublicHtmlReference(messages, CURRENT_USER), true);
|
||
});
|
||
|
||
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: '<!doctype html><title>Long flow</title>',
|
||
},
|
||
},
|
||
},
|
||
},
|
||
],
|
||
},
|
||
...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: '<!doctype html><title>Summer</title>',
|
||
},
|
||
},
|
||
},
|
||
},
|
||
],
|
||
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'),
|
||
'<!doctype html><title>Summer</title>',
|
||
);
|
||
} 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: '<!doctype html><title>Session page</title>',
|
||
},
|
||
},
|
||
},
|
||
},
|
||
],
|
||
},
|
||
];
|
||
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'), '<!doctype html><title>一二三</title>');
|
||
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'),
|
||
`<!doctype html><img src="/api/mindspace/v1/assets/${assetId}/download?inline=1">`,
|
||
);
|
||
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'), '<!doctype html><title>Own</title>');
|
||
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'), '<!doctype html><title>Own</title>');
|
||
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: '<!doctype html><title>Guizhou</title>',
|
||
},
|
||
},
|
||
},
|
||
},
|
||
],
|
||
},
|
||
];
|
||
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'),
|
||
'<!doctype html><title>Guizhou</title>',
|
||
);
|
||
} 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, '<html>根据你的要求临时补出的简版页面</html>', '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: '<!doctype html><title>Real Guide</title><body>Full page</body>',
|
||
},
|
||
},
|
||
},
|
||
},
|
||
],
|
||
},
|
||
];
|
||
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'), '<a href="old-plan.docx">download</a>');
|
||
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'), '<a href="old-plan.docx">download</a>');
|
||
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'),
|
||
'<a href="industry-distribution-report.docx" download>download</a>',
|
||
);
|
||
|
||
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'),
|
||
'<a href="industry-distribution-report.docx" download>download</a>',
|
||
);
|
||
|
||
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, '<!doctype html><title>Existing</title>', 'utf8');
|
||
const result = materializeMissingPublicHtmlWrites({
|
||
messages: [
|
||
{
|
||
role: 'assistant',
|
||
content: [
|
||
{
|
||
type: 'toolRequest',
|
||
toolCall: {
|
||
value: {
|
||
name: 'write_file',
|
||
arguments: {
|
||
path: 'public/guide.html',
|
||
content: '<!doctype html><title>Replacement</title>',
|
||
},
|
||
},
|
||
},
|
||
},
|
||
],
|
||
},
|
||
],
|
||
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 = '<!doctype html><title>Existing</title>';
|
||
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, '<body>一二三</body>', '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, '<!doctype html><title>Existing</title><body>一二三</body>', '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, '<body>一 · 二三</body>', '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: '<!doctype html><html><body>神口门</body></html>',
|
||
},
|
||
},
|
||
},
|
||
},
|
||
],
|
||
},
|
||
},
|
||
{ 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 });
|
||
}
|
||
});
|