fix(mindspace): edit_file 落盘、Finish 聊天 merge 与回归守卫
- finish-sync 支持 edit_file 覆盖 public HTML - Finish 同步 merge 本地流式消息,剥离 agent 内部前缀 - 新增 verify:mindspace-publish-guards 与 AGENTS.md 跨工具说明 - 发版脚本接入回归门禁;103 runtime 发布含备份回退 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -4,7 +4,13 @@ import fs from 'node:fs';
|
||||
import os from 'node:os';
|
||||
import path from 'node:path';
|
||||
|
||||
import { hasRecentOwnPublicHtmlReference, isStubPublicHtmlContent, materializeMissingPublicHtmlWrites, syncPublicDocxDownloads } from './mindspace-public-finish-sync.mjs';
|
||||
import {
|
||||
hasRecentOwnPublicHtmlReference,
|
||||
isStubPublicHtmlContent,
|
||||
materializeMissingPublicHtmlWrites,
|
||||
materializePublicHtmlWritesFromSessionEvent,
|
||||
syncPublicDocxDownloads,
|
||||
} from './mindspace-public-finish-sync.mjs';
|
||||
|
||||
const CURRENT_USER = { id: 'a6fb1e97-2b0f-447b-b138-4561d8e5c53e', username: 'john' };
|
||||
|
||||
@@ -324,3 +330,161 @@ test('materializeMissingPublicHtmlWrites skips existing real html when content i
|
||||
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 });
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user