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:
+20
-9
@@ -1,5 +1,7 @@
|
||||
import type { Message, MessageContent } from '../types';
|
||||
import { mergeMessageContent } from '../../message-stream.mjs';
|
||||
import { mergeConversationSnapshot as mergeConversationSnapshotCore } from '../../chat-finish-sync.mjs';
|
||||
import { deriveUserFacingText } from '../../conversation-display.mjs';
|
||||
import { stripUserAddressPrefix } from './userAddress';
|
||||
|
||||
const IMAGE_URL_LINE_RE = /^\[图片\d+]: (.+)$/;
|
||||
@@ -153,30 +155,39 @@ export function normalizeConversationMessages(messages: Message[]): Message[] {
|
||||
* Merge an authoritative server conversation snapshot into the currently
|
||||
* displayed messages without ever erasing what the user can already see.
|
||||
*
|
||||
* Used by UpdateConversation SSE and Finish sync. Implementation lives in
|
||||
* chat-finish-sync.mjs (shared with regression tests — do not inline elsewhere).
|
||||
*
|
||||
* `UpdateConversation` snapshots arrive on every SSE (re)connect — which on
|
||||
* mobile happens constantly via the page visibility cycle. A mid-turn snapshot
|
||||
* can be shorter than the live view (e.g. assistant messages not yet flagged
|
||||
* userVisible), so a blind replace would blank an active conversation even
|
||||
* though the backend session is alive and persisted. We therefore treat the
|
||||
* snapshot as authoritative for content/order but keep any locally-displayed
|
||||
* messages the snapshot hasn't caught up to yet (optimistic / in-flight tail).
|
||||
* The authoritative full reload on `Finish` corrects any drift afterwards.
|
||||
* though the backend session is alive and persisted. Finish sync also merges
|
||||
* because Goose may persist assistant turns after the Finish event.
|
||||
*/
|
||||
export function mergeConversationSnapshot(current: Message[], incoming: Message[]): Message[] {
|
||||
if (incoming.length === 0) return current;
|
||||
const incomingIds = new Set(incoming.map((message) => message.id).filter(Boolean));
|
||||
const localOnly = current.filter((message) => message.id && !incomingIds.has(message.id));
|
||||
return localOnly.length ? [...incoming, ...localOnly] : incoming;
|
||||
return mergeConversationSnapshotCore(current, incoming) as Message[];
|
||||
}
|
||||
|
||||
export function getDisplayText(message: Message): string {
|
||||
// REGRESSION GUARD: never show agent-only routing/skill prefixes in the chat UI.
|
||||
if (message.role === 'user') {
|
||||
if ('displayText' in message.metadata && message.metadata.displayText != null) {
|
||||
return stripImageUrlLines(message.metadata.displayText);
|
||||
}
|
||||
const raw = message.content
|
||||
.filter((c): c is Extract<MessageContent, { type: 'text' }> => c.type === 'text')
|
||||
.map((c) => c.text)
|
||||
.join('\n');
|
||||
return stripImageUrlLines(deriveUserFacingText(raw));
|
||||
}
|
||||
if ('displayText' in message.metadata) {
|
||||
return stripImageUrlLines(message.metadata.displayText ?? '');
|
||||
}
|
||||
const systemText = getSystemNotificationText(message);
|
||||
if (systemText) return systemText;
|
||||
const visible = getVisibleText(message);
|
||||
return message.role === 'user' ? stripImageUrlLines(stripUserAddressPrefix(visible)) : visible;
|
||||
return visible;
|
||||
}
|
||||
|
||||
export function pushMessage(messages: Message[], incoming: Message): Message[] {
|
||||
|
||||
Reference in New Issue
Block a user