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:
@@ -121,7 +121,7 @@ if [[ "${SKIP_TESTS}" -ne 1 ]]; then
|
||||
(
|
||||
cd "${ROOT}"
|
||||
npm test -- --test-name-pattern='publish|space|billing' >/dev/null
|
||||
node --test mindspace-public-finish-sync.test.mjs >/dev/null
|
||||
npm run verify:mindspace-publish-guards >/dev/null
|
||||
)
|
||||
fi
|
||||
|
||||
@@ -154,10 +154,10 @@ verify_runtime_artifact() {
|
||||
|
||||
verify_runtime_artifact
|
||||
|
||||
say "验证公开 HTML 完成同步守卫"
|
||||
say "验证 MindSpace 发布与聊天 Finish 回归守卫"
|
||||
(
|
||||
cd "${ROOT}"
|
||||
npm run verify:public-finish-sync-runtime
|
||||
npm run verify:mindspace-publish-guards:full
|
||||
)
|
||||
|
||||
verify_remote_goosed_dependency() {
|
||||
|
||||
@@ -116,6 +116,7 @@ if [[ "${SKIP_TESTS}" -ne 1 ]]; then
|
||||
(
|
||||
cd "${ROOT}"
|
||||
npm test -- --test-name-pattern='publish|space|billing' >/dev/null
|
||||
npm run verify:mindspace-publish-guards >/dev/null
|
||||
)
|
||||
fi
|
||||
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
|
||||
const root = path.resolve(new URL('..', import.meta.url).pathname);
|
||||
|
||||
function read(relativePath) {
|
||||
const absolutePath = path.join(root, relativePath);
|
||||
assert.ok(fs.existsSync(absolutePath), `missing guarded file: ${relativePath}`);
|
||||
return fs.readFileSync(absolutePath, 'utf8');
|
||||
}
|
||||
|
||||
function assertIncludes(source, snippet, label) {
|
||||
assert.ok(source.includes(snippet), `${label} must include guarded snippet: ${snippet}`);
|
||||
}
|
||||
|
||||
function assertExcludes(source, snippet, label) {
|
||||
assert.ok(!source.includes(snippet), `${label} must not regress to: ${snippet}`);
|
||||
}
|
||||
|
||||
const useTKMindChat = read('src/hooks/useTKMindChat.ts');
|
||||
assertIncludes(useTKMindChat, 'FINISH_SYNC_RETRY_DELAYS_MS', 'useTKMindChat.ts');
|
||||
assertIncludes(
|
||||
useTKMindChat,
|
||||
'mergeConversationSnapshot(localMessages, detail.messages)',
|
||||
'useTKMindChat.ts',
|
||||
);
|
||||
assertIncludes(useTKMindChat, 'chat-finish-sync.mjs', 'useTKMindChat.ts');
|
||||
assertExcludes(
|
||||
useTKMindChat,
|
||||
'messagesRef.current = detail.messages;\n messageHistoryLoadedCountRef.current = detail.messages.length',
|
||||
'useTKMindChat.ts Finish sync',
|
||||
);
|
||||
|
||||
const messageTs = read('src/utils/message.ts');
|
||||
assertIncludes(messageTs, 'deriveUserFacingText', 'message.ts');
|
||||
assertIncludes(messageTs, 'chat-finish-sync.mjs', 'message.ts');
|
||||
|
||||
const conversationDisplay = read('conversation-display.mjs');
|
||||
assertIncludes(conversationDisplay, 'TASK_ROUTING_HINT_RE', 'conversation-display.mjs');
|
||||
assertIncludes(conversationDisplay, 'deriveUserFacingText', 'conversation-display.mjs');
|
||||
|
||||
const chatSkills = read('chat-skills.mjs');
|
||||
assertIncludes(chatSkills, 'stripKnownChatSkillPrompt', 'chat-skills.mjs');
|
||||
|
||||
const server = read('server.mjs');
|
||||
assertIncludes(server, 'canUseSnapshotCache', 'server.mjs');
|
||||
assertIncludes(server, 'hintMc != null && hintUa != null', 'server.mjs');
|
||||
|
||||
const publicFinishSync = read('mindspace-public-finish-sync.mjs');
|
||||
assertIncludes(publicFinishSync, 'applyPublicHtmlEdit', 'mindspace-public-finish-sync.mjs');
|
||||
assertIncludes(publicFinishSync, 'isEditLikeHtmlTool', 'mindspace-public-finish-sync.mjs');
|
||||
|
||||
console.log('chat finish sync source guards ok');
|
||||
@@ -0,0 +1,35 @@
|
||||
import { spawnSync } from 'node:child_process';
|
||||
import path from 'node:path';
|
||||
|
||||
const root = path.resolve(new URL('..', import.meta.url).pathname);
|
||||
const withRuntime =
|
||||
process.argv.includes('--with-runtime') || process.env.MINDSPACE_VERIFY_RUNTIME === '1';
|
||||
|
||||
function run(label, args) {
|
||||
const result = spawnSync(process.execPath, args, {
|
||||
cwd: root,
|
||||
stdio: 'inherit',
|
||||
});
|
||||
if (result.status !== 0) {
|
||||
throw new Error(`${label} failed with exit code ${result.status ?? 'unknown'}`);
|
||||
}
|
||||
}
|
||||
|
||||
run('MindSpace publish + chat finish unit tests', [
|
||||
'--test',
|
||||
'mindspace-public-finish-sync.test.mjs',
|
||||
'conversation-display.test.mjs',
|
||||
'chat-finish-sync.test.mjs',
|
||||
]);
|
||||
|
||||
run('chat finish sync source guards', ['scripts/verify-chat-finish-sync.mjs']);
|
||||
|
||||
if (withRuntime) {
|
||||
run('public finish sync runtime guard', ['scripts/verify-public-finish-sync-runtime.mjs']);
|
||||
}
|
||||
|
||||
console.log(
|
||||
withRuntime
|
||||
? 'mindspace publish + chat finish regression guards ok (with runtime)'
|
||||
: 'mindspace publish + chat finish regression guards ok',
|
||||
);
|
||||
@@ -65,8 +65,10 @@ const runtimeSource = fs.readFileSync(runtimeServer, 'utf8');
|
||||
const requiredRuntimeSnippets = [
|
||||
'normalizedName === "write"',
|
||||
'normalizedName.endsWith("__write")',
|
||||
'normalizedName === "edit_file"',
|
||||
'applyPublicHtmlEdit',
|
||||
'recentCount = 80',
|
||||
'if (extractPublicHtmlWriteArtifacts([message]).length > 0) {',
|
||||
'if (extractPublicHtmlWriteArtifacts([message], { publishDir }).length > 0) {',
|
||||
];
|
||||
|
||||
for (const snippet of requiredRuntimeSnippets) {
|
||||
|
||||
Reference in New Issue
Block a user