@yeaft/webchat-agent 1.0.53 → 1.0.55
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/package.json +1 -1
- package/yeaft/engine.js +66 -0
- package/yeaft/perf-trace.js +28 -1
- package/yeaft/web-bridge.js +85 -11
package/package.json
CHANGED
package/yeaft/engine.js
CHANGED
|
@@ -34,6 +34,7 @@ import { readSummary as readScopeSummary } from './memory/store.js';
|
|
|
34
34
|
import { runAdjust } from './memory/adjust.js';
|
|
35
35
|
import { isVpSeedBackfillStub } from './memory/seed-backfill.js';
|
|
36
36
|
import { runStopHooks } from './stop-hooks.js';
|
|
37
|
+
import { perfNowMs, recordAgentPerfTrace } from './perf-trace.js';
|
|
37
38
|
// Default thread marker for legacy / non-group flows. Group VP runtime may
|
|
38
39
|
// pass a real threadId per (sessionId, vpId, threadId) engine instance.
|
|
39
40
|
const MAIN_THREAD_ID = 'main';
|
|
@@ -1720,6 +1721,22 @@ export class Engine {
|
|
|
1720
1721
|
const runtimeThreadId = (typeof threadId === 'string' && threadId.trim())
|
|
1721
1722
|
? threadId.trim()
|
|
1722
1723
|
: MAIN_THREAD_ID;
|
|
1724
|
+
const perfTraceId = typeof inboundEnvelope?._perfTraceId === 'string' && inboundEnvelope._perfTraceId.trim()
|
|
1725
|
+
? inboundEnvelope._perfTraceId.trim()
|
|
1726
|
+
: (typeof inboundEnvelope?.perfTraceId === 'string' && inboundEnvelope.perfTraceId.trim() ? inboundEnvelope.perfTraceId.trim() : null);
|
|
1727
|
+
const traceRequest = (phase, extra = {}) => {
|
|
1728
|
+
if (!perfTraceId) return;
|
|
1729
|
+
recordAgentPerfTrace(this.#config, {
|
|
1730
|
+
traceId: perfTraceId,
|
|
1731
|
+
phase,
|
|
1732
|
+
sessionId: runtimeSessionId || null,
|
|
1733
|
+
vpId: this.#vpId || senderVpId || null,
|
|
1734
|
+
turnId: vpTurnId || null,
|
|
1735
|
+
threadId: runtimeThreadId,
|
|
1736
|
+
messageType: 'llm_request',
|
|
1737
|
+
...extra,
|
|
1738
|
+
});
|
|
1739
|
+
};
|
|
1723
1740
|
|
|
1724
1741
|
// ─── Pre-query: FTS5 Memory Recall + AMS snapshot ─────
|
|
1725
1742
|
// Memory has a SINGLE render outlet now (DESIGN-PROMPT §3 ③):
|
|
@@ -2088,6 +2105,16 @@ export class Engine {
|
|
|
2088
2105
|
});
|
|
2089
2106
|
|
|
2090
2107
|
const startTime = Date.now();
|
|
2108
|
+
const requestPerfStart = perfNowMs();
|
|
2109
|
+
let firstEventTraced = false;
|
|
2110
|
+
traceRequest('llm.request_start', {
|
|
2111
|
+
detail: {
|
|
2112
|
+
model: currentModel,
|
|
2113
|
+
loop: turnNumber,
|
|
2114
|
+
messageCount: conversationMessages.length,
|
|
2115
|
+
toolDefCount: toolDefs.length,
|
|
2116
|
+
},
|
|
2117
|
+
});
|
|
2091
2118
|
let ttfbMs = null; // Time to first token
|
|
2092
2119
|
let responseText = '';
|
|
2093
2120
|
const toolCalls = [];
|
|
@@ -2288,6 +2315,13 @@ export class Engine {
|
|
|
2288
2315
|
if (signal?.aborted) {
|
|
2289
2316
|
throw new LLMAbortError();
|
|
2290
2317
|
}
|
|
2318
|
+
if (!firstEventTraced) {
|
|
2319
|
+
firstEventTraced = true;
|
|
2320
|
+
traceRequest('llm.first_event', {
|
|
2321
|
+
durationMs: perfNowMs() - requestPerfStart,
|
|
2322
|
+
detail: { eventType: event.type, model: currentModel },
|
|
2323
|
+
});
|
|
2324
|
+
}
|
|
2291
2325
|
switch (event.type) {
|
|
2292
2326
|
case 'text_delta':
|
|
2293
2327
|
if (ttfbMs === null) ttfbMs = Date.now() - startTime;
|
|
@@ -2360,6 +2394,16 @@ export class Engine {
|
|
|
2360
2394
|
}
|
|
2361
2395
|
}
|
|
2362
2396
|
}
|
|
2397
|
+
traceRequest('llm.request_complete', {
|
|
2398
|
+
durationMs: perfNowMs() - requestPerfStart,
|
|
2399
|
+
ok: true,
|
|
2400
|
+
detail: {
|
|
2401
|
+
model: currentModel,
|
|
2402
|
+
stopReason,
|
|
2403
|
+
inputTokens: totalUsage.inputTokens,
|
|
2404
|
+
outputTokens: totalUsage.outputTokens,
|
|
2405
|
+
},
|
|
2406
|
+
});
|
|
2363
2407
|
// Stream completed without throwing — reset the retry counter so
|
|
2364
2408
|
// the next turn starts with a clean budget. In-band adapter errors
|
|
2365
2409
|
// are converted to throws above so they share the real error path.
|
|
@@ -2402,6 +2446,16 @@ export class Engine {
|
|
|
2402
2446
|
|| err?.name === 'LLMAbortError'
|
|
2403
2447
|
|| (signal?.aborted && /abort/i.test(err?.message || ''));
|
|
2404
2448
|
if (earlyIsAbort || signal?.aborted) {
|
|
2449
|
+
traceRequest('llm.request_abort', {
|
|
2450
|
+
durationMs: perfNowMs() - requestPerfStart,
|
|
2451
|
+
ok: false,
|
|
2452
|
+
detail: {
|
|
2453
|
+
model: currentModel,
|
|
2454
|
+
abortReason: this.#abortReason || 'external',
|
|
2455
|
+
errorName: err?.name || null,
|
|
2456
|
+
signalAborted: !!signal?.aborted,
|
|
2457
|
+
},
|
|
2458
|
+
});
|
|
2405
2459
|
endAttemptTrace('aborted');
|
|
2406
2460
|
yield { type: 'aborted', reason: this.#abortReason || 'external', turnNumber, threadId };
|
|
2407
2461
|
yield { type: 'turn_end', turnNumber, stopReason: 'aborted', threadId };
|
|
@@ -2418,6 +2472,18 @@ export class Engine {
|
|
|
2418
2472
|
}
|
|
2419
2473
|
}
|
|
2420
2474
|
|
|
2475
|
+
traceRequest('llm.request_error', {
|
|
2476
|
+
durationMs: perfNowMs() - requestPerfStart,
|
|
2477
|
+
ok: false,
|
|
2478
|
+
detail: {
|
|
2479
|
+
model: currentModel,
|
|
2480
|
+
errorName: err?.name || null,
|
|
2481
|
+
statusCode: err?.statusCode ?? null,
|
|
2482
|
+
retryable: err instanceof LLMRateLimitError || err instanceof LLMServerError,
|
|
2483
|
+
message: String(err?.message || '').slice(0, 200),
|
|
2484
|
+
},
|
|
2485
|
+
});
|
|
2486
|
+
|
|
2421
2487
|
const earlyIsRateLimit = err instanceof LLMRateLimitError;
|
|
2422
2488
|
const earlyIsTransient = err instanceof LLMServerError;
|
|
2423
2489
|
if (earlyIsRateLimit || earlyIsTransient) {
|
package/yeaft/perf-trace.js
CHANGED
|
@@ -1,7 +1,32 @@
|
|
|
1
|
-
import { appendFileSync, mkdirSync } from 'fs';
|
|
1
|
+
import { appendFileSync, mkdirSync, readdirSync, rmSync } from 'fs';
|
|
2
2
|
import { join } from 'path';
|
|
3
3
|
|
|
4
4
|
const MAX_DETAIL_STRING = 512;
|
|
5
|
+
const DEFAULT_RETENTION_DAYS = 3;
|
|
6
|
+
let lastCleanupDay = null;
|
|
7
|
+
|
|
8
|
+
function retentionDays() {
|
|
9
|
+
const raw = Number(process.env.PERF_TRACE_RETENTION_DAYS || process.env.YEAFT_PERF_TRACE_RETENTION_DAYS || DEFAULT_RETENTION_DAYS);
|
|
10
|
+
return Number.isFinite(raw) && raw > 0 ? Math.floor(raw) : DEFAULT_RETENTION_DAYS;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function cleanupOldTraceFiles(root) {
|
|
14
|
+
const day = new Date().toISOString().slice(0, 10);
|
|
15
|
+
if (lastCleanupDay === day) return;
|
|
16
|
+
lastCleanupDay = day;
|
|
17
|
+
const keepMs = retentionDays() * 24 * 60 * 60 * 1000;
|
|
18
|
+
const cutoff = Date.now() - keepMs;
|
|
19
|
+
try {
|
|
20
|
+
for (const file of readdirSync(root)) {
|
|
21
|
+
const match = file.match(/^(\d{4}-\d{2}-\d{2})\.jsonl$/);
|
|
22
|
+
if (!match) continue;
|
|
23
|
+
const ts = Date.parse(`${match[1]}T00:00:00.000Z`);
|
|
24
|
+
if (Number.isFinite(ts) && ts < cutoff) rmSync(join(root, file), { force: true });
|
|
25
|
+
}
|
|
26
|
+
} catch {
|
|
27
|
+
// best-effort; trace writes must never break the agent
|
|
28
|
+
}
|
|
29
|
+
}
|
|
5
30
|
|
|
6
31
|
function sanitizeString(value, max = MAX_DETAIL_STRING) {
|
|
7
32
|
if (typeof value !== 'string') return value;
|
|
@@ -59,6 +84,7 @@ export function recordAgentPerfTrace(config, event = {}) {
|
|
|
59
84
|
};
|
|
60
85
|
try {
|
|
61
86
|
mkdirSync(root, { recursive: true });
|
|
87
|
+
cleanupOldTraceFiles(root);
|
|
62
88
|
appendFileSync(join(root, `${day}.jsonl`), `${JSON.stringify(row)}\n`);
|
|
63
89
|
return true;
|
|
64
90
|
} catch (err) {
|
|
@@ -71,4 +97,5 @@ export function recordAgentPerfTrace(config, event = {}) {
|
|
|
71
97
|
|
|
72
98
|
export const __perfTraceForTest = {
|
|
73
99
|
sanitizeValue,
|
|
100
|
+
cleanupOldTraceFiles,
|
|
74
101
|
};
|
package/yeaft/web-bridge.js
CHANGED
|
@@ -861,11 +861,12 @@ function projectVisibleHistoryChunkMessages(messages = []) {
|
|
|
861
861
|
}));
|
|
862
862
|
}
|
|
863
863
|
|
|
864
|
-
function emitHistoryChunk({ sessionId, messages, mode = 'older', oldestSeq = null, hasMore = false, latestSeq = null, afterSeq = null, turns = null }) {
|
|
864
|
+
function emitHistoryChunk({ sessionId, messages, mode = 'older', oldestSeq = null, hasMore = false, latestSeq = null, afterSeq = null, turns = null, perfTraceId = null }) {
|
|
865
865
|
const projectedMessages = projectVisibleHistoryChunkMessages(messages);
|
|
866
866
|
sendToServer({
|
|
867
867
|
type: 'yeaft_history_chunk',
|
|
868
868
|
conversationId: yeaftConversationId,
|
|
869
|
+
...(perfTraceId ? { perfTraceId } : {}),
|
|
869
870
|
sessionId,
|
|
870
871
|
mode,
|
|
871
872
|
messages: projectedMessages,
|
|
@@ -921,7 +922,7 @@ function emitLegacyHistoryOutputFrames(replayEntries) {
|
|
|
921
922
|
}
|
|
922
923
|
}
|
|
923
924
|
|
|
924
|
-
function emitVisibleHistoryReplay({ store, sessionId, limit, beforeSeq = null, mode = 'recent' }) {
|
|
925
|
+
function emitVisibleHistoryReplay({ store, sessionId, limit, beforeSeq = null, mode = 'recent', perfTraceId = null }) {
|
|
925
926
|
const visiblePage = sessionId
|
|
926
927
|
? loadVisibleGroupHistoryPage(store, sessionId, limit, beforeSeq)
|
|
927
928
|
: { messages: limit > 0 ? (store.loadRecent?.(limit) || []) : [], oldestSeq: null, hasMore: false };
|
|
@@ -943,6 +944,7 @@ function emitVisibleHistoryReplay({ store, sessionId, limit, beforeSeq = null, m
|
|
|
943
944
|
hasMore: visiblePage.hasMore,
|
|
944
945
|
latestSeq: Number.isFinite(latestSeq) ? latestSeq : null,
|
|
945
946
|
turns: limit,
|
|
947
|
+
perfTraceId,
|
|
946
948
|
});
|
|
947
949
|
sendSessionEvent({
|
|
948
950
|
type: 'history_loaded',
|
|
@@ -952,7 +954,7 @@ function emitVisibleHistoryReplay({ store, sessionId, limit, beforeSeq = null, m
|
|
|
952
954
|
hasMore: visiblePage.hasMore,
|
|
953
955
|
oldestSeq: visiblePage.oldestSeq,
|
|
954
956
|
latestSeq: Number.isFinite(latestSeq) ? latestSeq : null,
|
|
955
|
-
});
|
|
957
|
+
}, perfTraceId ? { sessionId, perfTraceId } : undefined);
|
|
956
958
|
return;
|
|
957
959
|
}
|
|
958
960
|
|
|
@@ -969,7 +971,7 @@ function emitVisibleHistoryReplay({ store, sessionId, limit, beforeSeq = null, m
|
|
|
969
971
|
hasMore: visiblePage.hasMore,
|
|
970
972
|
oldestSeq: visiblePage.oldestSeq,
|
|
971
973
|
latestSeq: Number.isFinite(latestSeq) ? latestSeq : null,
|
|
972
|
-
});
|
|
974
|
+
}, perfTraceId ? { sessionId, perfTraceId } : undefined);
|
|
973
975
|
}
|
|
974
976
|
|
|
975
977
|
/**
|
|
@@ -5041,6 +5043,27 @@ export function handleYeaftModelSwitch(msg) {
|
|
|
5041
5043
|
*/
|
|
5042
5044
|
export async function handleYeaftLoadHistory(msg) {
|
|
5043
5045
|
const sessionId = (msg && typeof msg.sessionId === 'string' && msg.sessionId) || null;
|
|
5046
|
+
const perfTraceId = typeof msg?.perfTraceId === 'string' && msg.perfTraceId.trim() ? msg.perfTraceId.trim() : null;
|
|
5047
|
+
const perfStart = perfNowMs();
|
|
5048
|
+
const tracePerf = (phase, extra = {}) => {
|
|
5049
|
+
if (!perfTraceId) return;
|
|
5050
|
+
recordAgentPerfTrace(ctx.CONFIG, {
|
|
5051
|
+
traceId: perfTraceId,
|
|
5052
|
+
phase,
|
|
5053
|
+
sessionId,
|
|
5054
|
+
messageType: msg?.type || 'yeaft_load_history',
|
|
5055
|
+
...extra,
|
|
5056
|
+
});
|
|
5057
|
+
};
|
|
5058
|
+
const traceDuration = (phase, start, extra = {}) => tracePerf(phase, { durationMs: perfNowMs() - start, ...extra });
|
|
5059
|
+
tracePerf('history.received', {
|
|
5060
|
+
detail: {
|
|
5061
|
+
limit: Number.isFinite(msg?.limit) ? msg.limit : null,
|
|
5062
|
+
afterSeq: Number.isFinite(msg?.afterSeq) ? msg.afterSeq : null,
|
|
5063
|
+
metadataOnly: !!(msg && Number.isFinite(msg.limit) && msg.limit <= 0),
|
|
5064
|
+
sessionLoaded: !!session,
|
|
5065
|
+
},
|
|
5066
|
+
});
|
|
5044
5067
|
const metadataOnly = msg && Number.isFinite(msg.limit) && msg.limit <= 0;
|
|
5045
5068
|
// `lim` is now expressed in TURNS, not raw messages. `loadRecent` and
|
|
5046
5069
|
// `loadRecentBySession` use turn-based slicing so the cut never lands
|
|
@@ -5063,14 +5086,19 @@ export async function handleYeaftLoadHistory(msg) {
|
|
|
5063
5086
|
afterSeq = session.conversationStore.getMessageSeqById(afterMessageId);
|
|
5064
5087
|
}
|
|
5065
5088
|
if (sessionId && afterSeq !== null && typeof session.conversationStore.loadAfterSeqByGroup === 'function') {
|
|
5089
|
+
const loadStart = perfNowMs();
|
|
5066
5090
|
const delta = session.conversationStore.loadAfterSeqByGroup(sessionId, afterSeq);
|
|
5091
|
+
traceDuration('history.store_load_delta', loadStart, { detail: { count: delta.messages?.length || 0, afterSeq } });
|
|
5092
|
+
const emitStart = perfNowMs();
|
|
5067
5093
|
const projectedMessages = emitHistoryChunk({
|
|
5068
5094
|
sessionId,
|
|
5069
5095
|
messages: delta.messages,
|
|
5070
5096
|
mode: 'delta',
|
|
5071
5097
|
latestSeq: delta.latestSeq,
|
|
5072
5098
|
afterSeq,
|
|
5099
|
+
perfTraceId,
|
|
5073
5100
|
});
|
|
5101
|
+
traceDuration('history.emit_chunk', emitStart, { detail: { mode: 'delta', count: projectedMessages.length } });
|
|
5074
5102
|
sendSessionEvent({
|
|
5075
5103
|
type: 'history_loaded',
|
|
5076
5104
|
mode: 'delta',
|
|
@@ -5078,7 +5106,7 @@ export async function handleYeaftLoadHistory(msg) {
|
|
|
5078
5106
|
sessionId,
|
|
5079
5107
|
latestSeq: delta.latestSeq,
|
|
5080
5108
|
afterSeq,
|
|
5081
|
-
});
|
|
5109
|
+
}, perfTraceId ? { sessionId, perfTraceId } : undefined);
|
|
5082
5110
|
return;
|
|
5083
5111
|
}
|
|
5084
5112
|
|
|
@@ -5087,9 +5115,11 @@ export async function handleYeaftLoadHistory(msg) {
|
|
|
5087
5115
|
// opening a group can paint the latest messages quickly; older rows are
|
|
5088
5116
|
// paged via `yeaft_load_more_history` when the user scrolls upward.
|
|
5089
5117
|
const limit = (typeof msg.limit === 'number') ? msg.limit : 10;
|
|
5118
|
+
const loadStart = perfNowMs();
|
|
5090
5119
|
const visiblePage = sessionId
|
|
5091
5120
|
? loadVisibleGroupHistoryPage(session.conversationStore, sessionId, limit)
|
|
5092
5121
|
: { messages: limit > 0 ? pickRecent(session.conversationStore, limit) : [], oldestSeq: null, hasMore: false };
|
|
5122
|
+
traceDuration('history.store_load_recent', loadStart, { detail: { rawCount: visiblePage.messages?.length || 0, limit } });
|
|
5093
5123
|
// Legacy compact.md is a non-group fallback only. For group replay, reading
|
|
5094
5124
|
// it makes every group show "has compact" once any legacy/non-scoped compact
|
|
5095
5125
|
// exists, even when this group has no scoped summary.
|
|
@@ -5107,6 +5137,7 @@ export async function handleYeaftLoadHistory(msg) {
|
|
|
5107
5137
|
}
|
|
5108
5138
|
|
|
5109
5139
|
if (sessionId) {
|
|
5140
|
+
const emitStart = perfNowMs();
|
|
5110
5141
|
emitHistoryChunk({
|
|
5111
5142
|
sessionId,
|
|
5112
5143
|
messages: replayEntries,
|
|
@@ -5115,7 +5146,9 @@ export async function handleYeaftLoadHistory(msg) {
|
|
|
5115
5146
|
hasMore: visiblePage.hasMore,
|
|
5116
5147
|
latestSeq,
|
|
5117
5148
|
turns: limit,
|
|
5149
|
+
perfTraceId,
|
|
5118
5150
|
});
|
|
5151
|
+
traceDuration('history.emit_chunk', emitStart, { detail: { mode: 'recent', count: replayEntries.length } });
|
|
5119
5152
|
} else {
|
|
5120
5153
|
emitLegacyHistoryOutputFrames(replayEntries);
|
|
5121
5154
|
}
|
|
@@ -5152,7 +5185,7 @@ export async function handleYeaftLoadHistory(msg) {
|
|
|
5152
5185
|
hasMore,
|
|
5153
5186
|
oldestSeq,
|
|
5154
5187
|
latestSeq,
|
|
5155
|
-
});
|
|
5188
|
+
}, perfTraceId ? { sessionId, perfTraceId } : undefined);
|
|
5156
5189
|
};
|
|
5157
5190
|
|
|
5158
5191
|
if (!session) {
|
|
@@ -5166,34 +5199,45 @@ export async function handleYeaftLoadHistory(msg) {
|
|
|
5166
5199
|
// skill scans, memory index sync). The conversation markdown store is the
|
|
5167
5200
|
// source of truth and can be opened cheaply, so replay the visible message
|
|
5168
5201
|
// window immediately, then finish loadSession below for actual turns.
|
|
5202
|
+
const coldStoreStart = perfNowMs();
|
|
5169
5203
|
const coldStore = new ConversationStore(yeaftDir);
|
|
5204
|
+
traceDuration('history.cold_store_open', coldStoreStart);
|
|
5170
5205
|
if (sessionId && (afterSeqRaw !== null || afterMessageId)) {
|
|
5171
5206
|
let afterSeq = afterSeqRaw;
|
|
5172
5207
|
if (afterSeq === null && afterMessageId && typeof coldStore.getMessageSeqById === 'function') {
|
|
5173
5208
|
afterSeq = coldStore.getMessageSeqById(afterMessageId);
|
|
5174
5209
|
}
|
|
5210
|
+
const loadStart = perfNowMs();
|
|
5175
5211
|
const delta = afterSeq !== null && typeof coldStore.loadAfterSeqByGroup === 'function'
|
|
5176
5212
|
? coldStore.loadAfterSeqByGroup(sessionId, afterSeq)
|
|
5177
5213
|
: { messages: [], latestSeq: null };
|
|
5214
|
+
traceDuration('history.store_load_delta', loadStart, { detail: { count: delta.messages?.length || 0, afterSeq, cold: true } });
|
|
5215
|
+
const emitStart = perfNowMs();
|
|
5178
5216
|
const projectedMessages = emitHistoryChunk({
|
|
5179
5217
|
sessionId,
|
|
5180
5218
|
messages: delta.messages,
|
|
5181
5219
|
mode: 'delta',
|
|
5182
5220
|
latestSeq: delta.latestSeq,
|
|
5183
5221
|
afterSeq,
|
|
5222
|
+
perfTraceId,
|
|
5184
5223
|
});
|
|
5185
|
-
|
|
5224
|
+
traceDuration('history.emit_chunk', emitStart, { detail: { mode: 'delta', count: projectedMessages.length, cold: true } });
|
|
5225
|
+
sendSessionEvent({ type: 'history_loaded', mode: 'delta', count: projectedMessages.length, sessionId, latestSeq: delta.latestSeq, afterSeq }, perfTraceId ? { sessionId, perfTraceId } : undefined);
|
|
5186
5226
|
} else if (!metadataOnly) {
|
|
5187
|
-
|
|
5227
|
+
const replayStart = perfNowMs();
|
|
5228
|
+
emitVisibleHistoryReplay({ store: coldStore, sessionId, limit, mode: 'recent', perfTraceId });
|
|
5229
|
+
traceDuration('history.cold_replay', replayStart, { detail: { mode: 'recent', limit } });
|
|
5188
5230
|
}
|
|
5189
5231
|
historyAlreadyReplayed = true;
|
|
5190
5232
|
|
|
5233
|
+
const bootStart = perfNowMs();
|
|
5191
5234
|
session = await loadSession({
|
|
5192
5235
|
dir: yeaftDir,
|
|
5193
5236
|
skipMCP: false,
|
|
5194
5237
|
skipSkills: false,
|
|
5195
5238
|
serverMode: true,
|
|
5196
5239
|
});
|
|
5240
|
+
traceDuration('history.load_session_runtime', bootStart);
|
|
5197
5241
|
installYeaftRuntimeBridge(session);
|
|
5198
5242
|
|
|
5199
5243
|
// Per-group history hydrates lazily via getOrCreateSessionHistory.
|
|
@@ -5201,9 +5245,15 @@ export async function handleYeaftLoadHistory(msg) {
|
|
|
5201
5245
|
// group's tape so the next user message sees on-disk state. When
|
|
5202
5246
|
// it doesn't (legacy callers), do nothing — the per-group lazy
|
|
5203
5247
|
// hydration handles it.
|
|
5204
|
-
if (sessionId)
|
|
5248
|
+
if (sessionId) {
|
|
5249
|
+
const hydrateStart = perfNowMs();
|
|
5250
|
+
setGroupHistory(sessionId, hydrateGroupHistory(sessionId));
|
|
5251
|
+
traceDuration('history.hydrate_group_history', hydrateStart);
|
|
5252
|
+
}
|
|
5205
5253
|
} else {
|
|
5254
|
+
const replayStart = perfNowMs();
|
|
5206
5255
|
replayHistoryFromStore();
|
|
5256
|
+
traceDuration('history.replay_from_store', replayStart);
|
|
5207
5257
|
historyAlreadyReplayed = true;
|
|
5208
5258
|
}
|
|
5209
5259
|
|
|
@@ -5211,7 +5261,9 @@ export async function handleYeaftLoadHistory(msg) {
|
|
|
5211
5261
|
// Re-entering an existing session with a (possibly new) group filter:
|
|
5212
5262
|
// re-seed THIS group's history from disk so it doesn't carry stale
|
|
5213
5263
|
// in-memory state into the next turn's context.
|
|
5264
|
+
const hydrateStart = perfNowMs();
|
|
5214
5265
|
setGroupHistory(sessionId, hydrateGroupHistory(sessionId));
|
|
5266
|
+
traceDuration('history.hydrate_group_history_final', hydrateStart);
|
|
5215
5267
|
}
|
|
5216
5268
|
|
|
5217
5269
|
// Always replay session_ready so refresh / reconnect rebuilds UI state, but
|
|
@@ -5220,9 +5272,15 @@ export async function handleYeaftLoadHistory(msg) {
|
|
|
5220
5272
|
// tick so the browser can paint messages before VP/session/dream snapshots.
|
|
5221
5273
|
scheduleYeaftLoadHistoryMetadataReplay(sessionId);
|
|
5222
5274
|
|
|
5223
|
-
if (historyAlreadyReplayed)
|
|
5275
|
+
if (historyAlreadyReplayed) {
|
|
5276
|
+
traceDuration('history.handler_total', perfStart, { ok: true });
|
|
5277
|
+
return;
|
|
5278
|
+
}
|
|
5224
5279
|
|
|
5280
|
+
const replayStart = perfNowMs();
|
|
5225
5281
|
replayHistoryFromStore();
|
|
5282
|
+
traceDuration('history.replay_from_store', replayStart);
|
|
5283
|
+
traceDuration('history.handler_total', perfStart, { ok: true });
|
|
5226
5284
|
}
|
|
5227
5285
|
|
|
5228
5286
|
/**
|
|
@@ -5240,8 +5298,17 @@ export async function handleYeaftLoadHistory(msg) {
|
|
|
5240
5298
|
*/
|
|
5241
5299
|
export async function handleYeaftLoadMoreHistory(msg) {
|
|
5242
5300
|
const sessionId = (msg && typeof msg.sessionId === 'string' && msg.sessionId) || null;
|
|
5301
|
+
const perfTraceId = typeof msg?.perfTraceId === 'string' && msg.perfTraceId.trim() ? msg.perfTraceId.trim() : null;
|
|
5302
|
+
const perfStart = perfNowMs();
|
|
5303
|
+
const tracePerf = (phase, extra = {}) => {
|
|
5304
|
+
if (!perfTraceId) return;
|
|
5305
|
+
recordAgentPerfTrace(ctx.CONFIG, { traceId: perfTraceId, phase, sessionId, messageType: msg?.type || 'yeaft_load_more_history', ...extra });
|
|
5306
|
+
};
|
|
5307
|
+
const traceDuration = (phase, start, extra = {}) => tracePerf(phase, { durationMs: perfNowMs() - start, ...extra });
|
|
5308
|
+
tracePerf('history_more.received');
|
|
5243
5309
|
if (!session || !sessionId) {
|
|
5244
|
-
emitHistoryChunk({ sessionId, messages: [], mode: 'older', oldestSeq: null, hasMore: false });
|
|
5310
|
+
emitHistoryChunk({ sessionId, messages: [], mode: 'older', oldestSeq: null, hasMore: false, perfTraceId });
|
|
5311
|
+
traceDuration('history_more.handler_total', perfStart, { ok: false, detail: { missingSession: !session, missingSessionId: !sessionId } });
|
|
5245
5312
|
return;
|
|
5246
5313
|
}
|
|
5247
5314
|
|
|
@@ -5250,16 +5317,20 @@ export async function handleYeaftLoadMoreHistory(msg) {
|
|
|
5250
5317
|
|
|
5251
5318
|
let result;
|
|
5252
5319
|
try {
|
|
5320
|
+
const loadStart = perfNowMs();
|
|
5253
5321
|
result = loadVisibleGroupHistoryPage(session.conversationStore, sessionId, turns, beforeSeq);
|
|
5322
|
+
traceDuration('history_more.store_load', loadStart, { detail: { count: result.messages?.length || 0, beforeSeq, turns } });
|
|
5254
5323
|
} catch (err) {
|
|
5255
5324
|
console.error('[Yeaft] loadOlderBySession failed:', err.message);
|
|
5256
5325
|
result = { messages: [], oldestSeq: null, hasMore: false };
|
|
5326
|
+
tracePerf('history_more.store_error', { ok: false, detail: { errorName: err?.name || null, errorMessage: err?.message || String(err) } });
|
|
5257
5327
|
}
|
|
5258
5328
|
|
|
5259
5329
|
// Wire shape mirrors handleYeaftLoadHistory's projection: only visible
|
|
5260
5330
|
// user / assistant text rows. Internal reflection/system-only rows stay
|
|
5261
5331
|
// server-side, and stable ids + speaker attribution ride with each row
|
|
5262
5332
|
// so older-history prepend renders exactly like refresh replay.
|
|
5333
|
+
const emitStart = perfNowMs();
|
|
5263
5334
|
emitHistoryChunk({
|
|
5264
5335
|
sessionId,
|
|
5265
5336
|
messages: result.messages || [],
|
|
@@ -5267,7 +5338,10 @@ export async function handleYeaftLoadMoreHistory(msg) {
|
|
|
5267
5338
|
oldestSeq: result.oldestSeq,
|
|
5268
5339
|
hasMore: !!result.hasMore,
|
|
5269
5340
|
turns,
|
|
5341
|
+
perfTraceId,
|
|
5270
5342
|
});
|
|
5343
|
+
traceDuration('history_more.emit_chunk', emitStart, { detail: { count: result.messages?.length || 0 } });
|
|
5344
|
+
traceDuration('history_more.handler_total', perfStart, { ok: true });
|
|
5271
5345
|
}
|
|
5272
5346
|
|
|
5273
5347
|
/**
|