cc-viewer 1.7.14 → 1.7.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assets/{App-D11UR-a4.js → App-BzhZff4n.js} +2 -2
- package/dist/assets/{MdxEditorPanel-Dd__S_61.js → MdxEditorPanel-DDD8Of6g.js} +1 -1
- package/dist/assets/{Mobile-DozTru1m.js → Mobile-S2A7u-mh.js} +1 -1
- package/dist/assets/{ProxyStatsModal-BKZHRUOy.js → ProxyStatsModal-C8l4YL-L.js} +1 -1
- package/dist/assets/index-8EGcVOKG.js +2 -0
- package/dist/assets/seqResourceLoaders-C7Vn-EYS.js +2 -0
- package/dist/assets/{seqResourceLoaders-pBUn3c_a.css → seqResourceLoaders-bfmeQ_vN.css} +2 -2
- package/dist/index.html +1 -1
- package/package.json +1 -1
- package/server/lib/delta-reconstructor.js +3 -0
- package/server/lib/v2/adapter.js +44 -2
- package/server/lib/v2/agent-id.js +55 -0
- package/server/lib/v2/live-feed.js +6 -5
- package/server/lib/v2/meta-rows.js +5 -3
- package/server/lib/v2/v2-writer.js +6 -0
- package/server/lib/v2-transcript-normalizer.js +519 -0
- package/src/utils/contentFilter.js +17 -3
- package/src/utils/identityHeal.js +9 -0
- package/src/utils/requestType.js +30 -0
- package/src/utils/sessionManager.js +15 -4
- package/src/utils/sessionMerge.js +62 -3
- package/src/utils/toolResultBuilder.js +19 -1
- package/src/utils/toolResultCore.js +27 -0
- package/src/utils/v3Assembler.js +12 -0
- package/dist/assets/index-CPLS-pSI.js +0 -2
- package/dist/assets/seqResourceLoaders-BV8bzDPn.js +0 -2
|
@@ -18,6 +18,10 @@ export { messageFingerprint };
|
|
|
18
18
|
* 无 live-port 配置下"提问气泡请求时即显示"依赖这一行为。
|
|
19
19
|
* AppBase 的 SSE 与批量两个 merge 入口、以及单测共用此谓词,防三处逻辑漂移。
|
|
20
20
|
*
|
|
21
|
+
* 批量路径的降级例外见 shouldDegradeBrokenMerge:当 broken 条目恰好是它那个
|
|
22
|
+
* session 的唯一载体(slim 只留最后一条 main)时,其 messages 是可信前缀,
|
|
23
|
+
* 降级合并比整会话空白更可取(会话打 _partialData 标记,ChatView 显示提示条)。
|
|
24
|
+
*
|
|
21
25
|
* @param {object} entry
|
|
22
26
|
* @param {object} [options]
|
|
23
27
|
* @param {boolean} [options.batch=false] - 批量(强刷/历史加载)路径
|
|
@@ -30,6 +34,36 @@ export function isMergeBlockedEntry(entry, options = {}) {
|
|
|
30
34
|
return false;
|
|
31
35
|
}
|
|
32
36
|
|
|
37
|
+
/**
|
|
38
|
+
* 批量路径降级例外:broken 载体能否以"可信前缀"身份合并进 mainAgentSessions。
|
|
39
|
+
*
|
|
40
|
+
* 仅在两条安全路径放行(两条都只会走进 mergeMainAgentSessions 的创建分支,
|
|
41
|
+
* 不会触发 anchor/rebuild 等可能翻倍的对齐逻辑):
|
|
42
|
+
* - prevSessions 为空:会话直接创建,无对齐风险;
|
|
43
|
+
* - 双方都有 _seqEpoch 且不同:确定性会话边界,走 epochChanged append 分支
|
|
44
|
+
* (三部分守卫防 v1 无 epoch 的条目被误放行)。
|
|
45
|
+
*
|
|
46
|
+
* `_staleReorder` 明确不降级(流内稍后有权威副本自建会话);inProgress 不降级
|
|
47
|
+
* (批量下其 messages 是裸切片,不是可信前缀)。v1 / 无 epoch 会话的 broken
|
|
48
|
+
* 载体也刻意不降级(放松守卫会让 broken 条目掉进同会话分支,错位前缀 → 翻倍)。
|
|
49
|
+
*
|
|
50
|
+
* @param {object} entry
|
|
51
|
+
* @param {Array} prevSessions - 当前已合并的 sessions
|
|
52
|
+
* @returns {boolean} true = 允许降级合并
|
|
53
|
+
*/
|
|
54
|
+
export function shouldDegradeBrokenMerge(entry, prevSessions) {
|
|
55
|
+
if (!entry || !entry._reconstructBroken) return false;
|
|
56
|
+
if (entry._staleReorder || entry.inProgress) return false;
|
|
57
|
+
if (!Array.isArray(prevSessions) || prevSessions.length === 0) return true;
|
|
58
|
+
const e = entry._seqEpoch;
|
|
59
|
+
if (!e) return false; // v1 / epoch-less carriers never degrade
|
|
60
|
+
// Reject when ANY already-merged session shares the entry's epoch (a same-
|
|
61
|
+
// session broken carrier preceded by a healthy one must not degrade into the
|
|
62
|
+
// same-session merge branch). Today batches are single-session per epoch, so
|
|
63
|
+
// this is a defensive closure for future non-monotonic epoch streams.
|
|
64
|
+
return prevSessions.every((s) => !s._seqEpoch || s._seqEpoch !== e);
|
|
65
|
+
}
|
|
66
|
+
|
|
33
67
|
/**
|
|
34
68
|
* 增量合并 mainAgent sessions。
|
|
35
69
|
*
|
|
@@ -39,6 +73,8 @@ export function isMergeBlockedEntry(entry, options = {}) {
|
|
|
39
73
|
* - 命中且 overlapLen < newLen:push `newMessages[overlapLen..]`,引用稳定。
|
|
40
74
|
* - 未命中:newLen<curLen → rebuild(/compact summary);newLen===curLen → 整段 append(Plan Mode 全新片段);
|
|
41
75
|
* newLen>curLen → 严格前缀扩展语义(push tail),fp 加固后真正存在重叠的窗口必被 anchor 命中。
|
|
76
|
+
* - 降级 partial 基底(lastSession._partialData)且 anchor 未命中:任何长度的全量条目都整段替换
|
|
77
|
+
* (partial 前缀有中段缺口、不可信,push tail 会翻倍)。
|
|
42
78
|
*
|
|
43
79
|
* 顶部守卫(isPostClearCheckpoint / userId / transient filter)维持 1.6.245 行为不变。
|
|
44
80
|
*
|
|
@@ -68,7 +104,13 @@ export function mergeMainAgentSessions(prevSessions, entry, options = {}) {
|
|
|
68
104
|
const entryModel = getEffectiveModel(entry);
|
|
69
105
|
|
|
70
106
|
if (prevSessions.length === 0) {
|
|
71
|
-
return [{
|
|
107
|
+
return [{
|
|
108
|
+
userId, messages: newMessages, response: newResponse, entryTimestamp, model: entryModel, _seqEpoch: seqEpoch,
|
|
109
|
+
// Degraded-broken carrier: truthful prefix with a mid-session hole. Only
|
|
110
|
+
// the create branches carry it (a same-session merge onto a partial base
|
|
111
|
+
// is defused by the whole-replace below); ChatView renders the banner.
|
|
112
|
+
...(entry._partialData === true && { _partialData: true }),
|
|
113
|
+
}];
|
|
72
114
|
}
|
|
73
115
|
|
|
74
116
|
const lastSession = prevSessions[prevSessions.length - 1];
|
|
@@ -90,7 +132,10 @@ export function mergeMainAgentSessions(prevSessions, entry, options = {}) {
|
|
|
90
132
|
for (let i = 0; i < newMessages.length; i++) {
|
|
91
133
|
if (!newMessages[i]._timestamp) newMessages[i]._timestamp = entryTimestamp;
|
|
92
134
|
}
|
|
93
|
-
return [...prevSessions, {
|
|
135
|
+
return [...prevSessions, {
|
|
136
|
+
userId, messages: newMessages, response: newResponse, entryTimestamp, model: entryModel, _seqEpoch: seqEpoch,
|
|
137
|
+
...(entry._partialData === true && { _partialData: true }),
|
|
138
|
+
}];
|
|
94
139
|
}
|
|
95
140
|
|
|
96
141
|
if (!options.skipTransientFilter && isNewConversation && newMessages.length <= 4 && prevMsgCount > 4) {
|
|
@@ -119,6 +164,17 @@ export function mergeMainAgentSessions(prevSessions, entry, options = {}) {
|
|
|
119
164
|
lastSession.messages.push(newMessages[i]);
|
|
120
165
|
}
|
|
121
166
|
}
|
|
167
|
+
// Anchor 对齐成功:新全量条目与 partial 前缀吻合,数据已恢复完整。
|
|
168
|
+
delete lastSession._partialData;
|
|
169
|
+
} else if (lastSession._partialData) {
|
|
170
|
+
// 降级合并的 partial 会话(中段缺口,前缀不可信):anchor 未命中时任何
|
|
171
|
+
// 同长/更长的全量条目都是权威真值,整段替换而非前缀扩展——否则会 push 出
|
|
172
|
+
// 尾部重复(partial 里已含 416..418,新全量 419 会被 append 一遍)。
|
|
173
|
+
for (let i = 0; i < newLen; i++) {
|
|
174
|
+
if (!newMessages[i]._timestamp) newMessages[i]._timestamp = entryTimestamp;
|
|
175
|
+
}
|
|
176
|
+
lastSession.messages = newMessages;
|
|
177
|
+
delete lastSession._partialData;
|
|
122
178
|
} else if (newLen < curLen) {
|
|
123
179
|
// /compact summary 等真重建:替换 messages 引用。
|
|
124
180
|
for (let i = 0; i < newLen; i++) {
|
|
@@ -175,6 +231,9 @@ export function mergeMainAgentSessions(prevSessions, entry, options = {}) {
|
|
|
175
231
|
if (entryModel) lastSession.model = entryModel; // latest wins; model-less entries keep the stamp
|
|
176
232
|
return [...prevSessions];
|
|
177
233
|
} else {
|
|
178
|
-
return [...prevSessions, {
|
|
234
|
+
return [...prevSessions, {
|
|
235
|
+
userId, messages: newMessages, response: newResponse, entryTimestamp, model: entryModel, _seqEpoch: seqEpoch,
|
|
236
|
+
...(entry._partialData === true && { _partialData: true }),
|
|
237
|
+
}];
|
|
179
238
|
}
|
|
180
239
|
}
|
|
@@ -188,8 +188,26 @@ export function buildSubAgentResultMap(req, globalIndex) {
|
|
|
188
188
|
return { ...localState.toolResultMap, ...filled };
|
|
189
189
|
}
|
|
190
190
|
|
|
191
|
-
export function appendToolResultMap(state, messages, startIndex) {
|
|
191
|
+
export function appendToolResultMap(state, messages, startIndex, extraToolUses) {
|
|
192
192
|
const { toolUseMap, toolResultMap, readContentMap, editSnapshotMap, askAnswerMap, planApprovalMap, _fileState } = state;
|
|
193
|
+
// V2 live: tool_use blocks merged into an already-scanned message by a later
|
|
194
|
+
// split row are surfaced on the synthetic entry's _toolUses. Register them so
|
|
195
|
+
// their tool_result pairs with a real matchedTool (label / input / Read/Edit
|
|
196
|
+
// state). Side-effect tracking (Write/Edit/Ask/ExitPlanMode) stays in the
|
|
197
|
+
// message loop below — these blocks were already seen there when first merged.
|
|
198
|
+
if (Array.isArray(extraToolUses)) {
|
|
199
|
+
for (const block of extraToolUses) {
|
|
200
|
+
if (!block || !block.id || block.id in toolUseMap) continue;
|
|
201
|
+
let parsed = block;
|
|
202
|
+
if (typeof block.input === 'string') {
|
|
203
|
+
try {
|
|
204
|
+
const cleaned = block.input.replace(/^\[object Object\]/, '');
|
|
205
|
+
parsed = { ...block, input: JSON.parse(cleaned) };
|
|
206
|
+
} catch {}
|
|
207
|
+
}
|
|
208
|
+
toolUseMap[parsed.id] = parsed;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
193
211
|
for (let i = startIndex; i < messages.length; i++) {
|
|
194
212
|
const msg = messages[i];
|
|
195
213
|
if (msg.role === 'assistant' && Array.isArray(msg.content)) {
|
|
@@ -165,3 +165,30 @@ export function compactResultPreview(entry, opts = {}) {
|
|
|
165
165
|
if (!hasImages && !text) return null;
|
|
166
166
|
return { text, images: hasImages ? images : null };
|
|
167
167
|
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* 简化模式是否应在消息流内联 tool_result 图片。
|
|
171
|
+
* 与 compactResultPreview 的跳过条件(isPermissionDenied / isInputValidationError)
|
|
172
|
+
* 保持同一语义;另外要求至少一张「可渲染」的图(有 src 的非 oversized),
|
|
173
|
+
* 纯 oversized 占位不内联(否则消息流里全是虚线占位噪音)。
|
|
174
|
+
*
|
|
175
|
+
* @param {object|null} entry - toolResultMap 条目(可能为 undefined:SubAgent 末轮
|
|
176
|
+
* tool_use 的 result 未到位 / WebSearch / 历史计数缺口等窗口)
|
|
177
|
+
* @returns {boolean}
|
|
178
|
+
*/
|
|
179
|
+
export function shouldInlineToolImages(entry) {
|
|
180
|
+
if (!entry || typeof entry !== 'object') return false;
|
|
181
|
+
if (entry.isPermissionDenied || entry.isInputValidationError) return false;
|
|
182
|
+
if (!Array.isArray(entry.images) || entry.images.length === 0) return false;
|
|
183
|
+
return entry.images.some((img) => img && img.src && !img.oversized);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* oversized 图片占位文案(3 处复用:ToolResultView / ChatMessage Popover / 内联块)。
|
|
188
|
+
* `[image png · 512 KB · too large to preview]`
|
|
189
|
+
*/
|
|
190
|
+
export function formatOversizedImagePlaceholder(img) {
|
|
191
|
+
const mediaType = (img && img.mediaType ? String(img.mediaType).replace('image/', '') : 'image');
|
|
192
|
+
const sizeKB = img && typeof img.sizeBytes === 'number' ? Math.round(img.sizeBytes / 1024) : 0;
|
|
193
|
+
return `[image ${mediaType} · ${sizeKB} KB · too large to preview]`;
|
|
194
|
+
}
|
package/src/utils/v3Assembler.js
CHANGED
|
@@ -80,8 +80,20 @@ export function createV3Assembler() {
|
|
|
80
80
|
_seqEpoch: `v2:${row.sessionId}`,
|
|
81
81
|
_totalMessageCount: messages.length,
|
|
82
82
|
_v3Assembled: true,
|
|
83
|
+
// The SOURCE row — lets chat classification read the server-side
|
|
84
|
+
// typeTag directly (same source the request list uses), instead of
|
|
85
|
+
// re-deriving from a body stripped of system/tools. Known limitation:
|
|
86
|
+
// AppBase._applyV3Delta dedups by (sessionId, seq, inProgress), so a
|
|
87
|
+
// classification-correction re-send (Preflight/Plan lookahead) does NOT
|
|
88
|
+
// rebuild this entry — the row ref stays the FIRST one until a cold
|
|
89
|
+
// reload. Only lookahead-dependent rows are affected (Teammate/SubAgent
|
|
90
|
+
// tags are lookahead-free); tracked as backlog.
|
|
91
|
+
_v3Row: row,
|
|
83
92
|
};
|
|
84
93
|
if (row.teammate) entry.teammate = row.teammate;
|
|
94
|
+
// Wire agent identity (x-claude-code-agent-id) — display name for native
|
|
95
|
+
// teammates, persisted server-side (see agent-id.js).
|
|
96
|
+
if (row.agent) entry.agent = row.agent;
|
|
85
97
|
if (row.proxyUrl) entry.proxyUrl = row.proxyUrl;
|
|
86
98
|
if (isSnapshot) entry._isCheckpoint = true;
|
|
87
99
|
if (row.inProgress) {
|