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: 'Winter melon soup', }, }, }, }, ], 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: '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 }); } });