cc-viewer 1.7.15 → 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-AKaEDUQK.js → App-BzhZff4n.js} +2 -2
- package/dist/assets/{MdxEditorPanel-blRIkN3n.js → MdxEditorPanel-DDD8Of6g.js} +1 -1
- package/dist/assets/{Mobile-C5Gl5Nls.js → Mobile-S2A7u-mh.js} +1 -1
- package/dist/assets/{ProxyStatsModal-hmslmc8e.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-eNDfclvk.css → seqResourceLoaders-bfmeQ_vN.css} +1 -1
- package/dist/index.html +1 -1
- package/package.json +1 -1
- 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/src/utils/contentFilter.js +17 -3
- package/src/utils/identityHeal.js +9 -0
- package/src/utils/requestType.js +30 -0
- package/src/utils/sessionMerge.js +62 -3
- package/src/utils/v3Assembler.js +12 -0
- package/dist/assets/index-BXWDyOHS.js +0 -2
- package/dist/assets/seqResourceLoaders-BwnpYLaI.js +0 -2
|
@@ -76,6 +76,15 @@ export function refreshResolvedModelInfo(items, resolveModelInfo) {
|
|
|
76
76
|
export function healUnresolvedTeammateEntries(entries) {
|
|
77
77
|
for (const entry of entries) {
|
|
78
78
|
if (!entry || !entry.unresolved || !entry.req || !entry.req.teammate) continue;
|
|
79
|
+
// Wire-v3 entries carry the SERVER-side typeTag (via _v3Row). When the
|
|
80
|
+
// tag has a real Teammate name (extractTeammateName / persisted agent
|
|
81
|
+
// name), it may differ from req.teammate (the heuristic registry) —
|
|
82
|
+
// overwriting would DOWNGRADE a good server label ("Teammate:
|
|
83
|
+
// frontend-reviewer" → "Teammate: X"), so skip those. When the tag is
|
|
84
|
+
// Teammate WITHOUT a name (old journals, no persisted agent), let the heal
|
|
85
|
+
// upgrade from the registry's real name.
|
|
86
|
+
const tag = entry.req._v3Row && entry.req._v3Row.typeTag;
|
|
87
|
+
if (tag && tag.type === 'Teammate' && tag.subType) continue;
|
|
79
88
|
const label = formatTeammateLabel(entry.req.teammate, entry.req.body?.model);
|
|
80
89
|
if (label !== entry.label) entry.label = label;
|
|
81
90
|
entry.unresolved = false;
|
package/src/utils/requestType.js
CHANGED
|
@@ -148,6 +148,17 @@ function isPreflightRequest(req, nextReq) {
|
|
|
148
148
|
* @param {object} [nextReq] - 下一条请求(用于 Preflight 判断)
|
|
149
149
|
*/
|
|
150
150
|
export function classifyRequest(req, nextReq) {
|
|
151
|
+
// Wire-v3 assembled entry: the SERVER already classified this row on the
|
|
152
|
+
// full body (system/tools intact) — trust it verbatim. The assembled body
|
|
153
|
+
// strips system/tools, so a client re-derivation (isTeammate / isMainAgent /
|
|
154
|
+
// isPreflightRequest) would mis-tag every teammate as SubAgent. No
|
|
155
|
+
// Plan/Preflight exclusion: those need nextReq lookahead, which the server
|
|
156
|
+
// already applied (cold Pass B / live correction); the client cannot
|
|
157
|
+
// re-derive them from the stripped body either.
|
|
158
|
+
if (req && req._v3Row && req._v3Row.typeTag) {
|
|
159
|
+
return req._v3Row.typeTag;
|
|
160
|
+
}
|
|
161
|
+
|
|
151
162
|
// Teammate 子进程的请求优先识别(收敛于 contentFilter.isTeammate)
|
|
152
163
|
if (isTeammate(req)) {
|
|
153
164
|
if (req.teammate) return { type: 'Teammate', subType: req.teammate };
|
|
@@ -206,3 +217,22 @@ export function formatTeammateLabel(name, model) {
|
|
|
206
217
|
const short = model.replace(/^claude-/i, '').replace(/-\d{8}$/, '');
|
|
207
218
|
return `Teammate: ${displayName}(${short})`;
|
|
208
219
|
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Normalize a classifyRequest tag, then surface the persisted agent name as the
|
|
223
|
+
* Teammate subType (ONLY for Teammate rows — a SubAgent subType is a role tag
|
|
224
|
+
* like "Bash" and must not be overwritten). Shared by the server's meta-rows
|
|
225
|
+
* Pass B and live-feed row emission so the list/chat labels never drift.
|
|
226
|
+
* KEEP IN SYNC: both server callers import this from here.
|
|
227
|
+
*
|
|
228
|
+
* @param {{type: string, subType: string|null}|null} tag
|
|
229
|
+
* @param {object} [entry] - entry carrying `agent` ({agentName, named})
|
|
230
|
+
* @returns {{type: string, subType: string|null}|null}
|
|
231
|
+
*/
|
|
232
|
+
export function withAgentNameSubType(tag, entry) {
|
|
233
|
+
const base = tag ? { type: tag.type, subType: tag.subType ?? null } : null;
|
|
234
|
+
if (tag && tag.type === 'Teammate' && entry && entry.agent && entry.agent.agentName) {
|
|
235
|
+
return { type: tag.type, subType: entry.agent.agentName };
|
|
236
|
+
}
|
|
237
|
+
return base;
|
|
238
|
+
}
|
|
@@ -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
|
}
|
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) {
|