@yeaft/webchat-agent 1.0.103 → 1.0.104
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/debug-trace.js +81 -20
- package/yeaft/web-bridge.js +31 -5
package/package.json
CHANGED
package/yeaft/debug-trace.js
CHANGED
|
@@ -232,12 +232,50 @@ function buildRawRequestBase(value) {
|
|
|
232
232
|
}
|
|
233
233
|
|
|
234
234
|
function buildRawMessagesDelta(previousMessages, nextMessages) {
|
|
235
|
-
if (!Array.isArray(
|
|
236
|
-
const
|
|
237
|
-
|
|
235
|
+
if (!Array.isArray(nextMessages)) return null;
|
|
236
|
+
const priorMessages = Array.isArray(previousMessages) ? previousMessages : [];
|
|
237
|
+
const prefix = messagesPrefixLength(priorMessages, nextMessages);
|
|
238
|
+
if (prefix === priorMessages.length && prefix <= nextMessages.length) {
|
|
238
239
|
return { messagesFrom: prefix, messagesAppend: boundRawValue(nextMessages.slice(prefix), 'raw_request_messages_append_budget') };
|
|
239
240
|
}
|
|
240
|
-
return {
|
|
241
|
+
return { messagesFrom: 0, messagesAppend: boundRawValue(nextMessages, 'raw_request_messages_append_budget') };
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function rawRequestMessageKey(body) {
|
|
245
|
+
if (!isPlainObject(body)) return null;
|
|
246
|
+
if (Array.isArray(body.messages)) return 'messages';
|
|
247
|
+
if (Array.isArray(body.input)) return 'input';
|
|
248
|
+
return null;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function buildInitialRawRequestDelta(value) {
|
|
252
|
+
if (value == null) return null;
|
|
253
|
+
if (typeof value === 'string') return { replacement: rawRequestSentinel('raw_request_string_replaced', value) };
|
|
254
|
+
if (!isPlainObject(value)) return { replacement: boundRawValue(value) };
|
|
255
|
+
const delta = { set: {}, body: {} };
|
|
256
|
+
for (const [key, item] of Object.entries(value)) {
|
|
257
|
+
if (key === 'body') continue;
|
|
258
|
+
delta.set[key] = boundRawValue(item, `raw_request_${key}_budget`);
|
|
259
|
+
}
|
|
260
|
+
const body = isPlainObject(value.body) ? value.body : null;
|
|
261
|
+
if (body) {
|
|
262
|
+
for (const [key, bodyValue] of Object.entries(body)) {
|
|
263
|
+
if (key === 'messages' || key === 'input') continue;
|
|
264
|
+
delta.body[key] = boundRawValue(bodyValue, `raw_request_body_${key}_budget`);
|
|
265
|
+
}
|
|
266
|
+
const messageKey = rawRequestMessageKey(body);
|
|
267
|
+
if (messageKey) {
|
|
268
|
+
delta.body.messagesKey = messageKey;
|
|
269
|
+
Object.assign(delta.body, buildRawMessagesDelta([], body[messageKey]) || {});
|
|
270
|
+
}
|
|
271
|
+
} else if (Object.prototype.hasOwnProperty.call(value, 'body')) {
|
|
272
|
+
delta.set.body = rawRequestSentinel('raw_request_body_replaced');
|
|
273
|
+
}
|
|
274
|
+
if (Object.keys(delta.set).length === 0) delete delta.set;
|
|
275
|
+
if (Object.keys(delta.body).length === 0) delete delta.body;
|
|
276
|
+
if (!delta.set && !delta.body) return null;
|
|
277
|
+
if (jsonByteLength(delta) > MAX_RAW_REQUEST_BYTES) return { replacement: rawRequestSentinel('raw_request_delta_budget') };
|
|
278
|
+
return delta;
|
|
241
279
|
}
|
|
242
280
|
|
|
243
281
|
function rawComparableRequest(value) {
|
|
@@ -250,7 +288,7 @@ function rawComparableRequest(value) {
|
|
|
250
288
|
|
|
251
289
|
function buildRawRequestDelta(previous, next) {
|
|
252
290
|
if (next == null) return previous == null ? null : { replacement: null };
|
|
253
|
-
if (previous == null) return
|
|
291
|
+
if (previous == null) return buildInitialRawRequestDelta(next);
|
|
254
292
|
const comparablePrevious = rawComparableRequest(previous);
|
|
255
293
|
const comparableNext = rawComparableRequest(next);
|
|
256
294
|
if (typeof comparablePrevious === 'string' || typeof comparableNext === 'string') {
|
|
@@ -273,13 +311,15 @@ function buildRawRequestDelta(previous, next) {
|
|
|
273
311
|
const nextBody = isPlainObject(comparableNext.body) ? comparableNext.body : null;
|
|
274
312
|
if (prevBody && nextBody) {
|
|
275
313
|
for (const key of Object.keys(nextBody)) {
|
|
276
|
-
if (key === 'messages') continue;
|
|
314
|
+
if (key === 'messages' || key === 'input') continue;
|
|
277
315
|
if (!stableEqual(prevBody[key], nextBody[key])) delta.body[key] = boundRawValue(nextBody[key], `raw_request_body_${key}_budget`);
|
|
278
316
|
}
|
|
279
317
|
for (const key of Object.keys(prevBody)) {
|
|
280
|
-
if (key !== 'messages' && !Object.prototype.hasOwnProperty.call(nextBody, key)) delta.body[key] = null;
|
|
318
|
+
if (key !== 'messages' && key !== 'input' && !Object.prototype.hasOwnProperty.call(nextBody, key)) delta.body[key] = null;
|
|
281
319
|
}
|
|
282
|
-
const
|
|
320
|
+
const prevMessages = Array.isArray(prevBody.messages) ? prevBody.messages : prevBody.input;
|
|
321
|
+
const nextMessages = Array.isArray(nextBody.messages) ? nextBody.messages : nextBody.input;
|
|
322
|
+
const msgDelta = buildRawMessagesDelta(prevMessages, nextMessages);
|
|
283
323
|
if (msgDelta) Object.assign(delta.body, msgDelta);
|
|
284
324
|
} else if (!stableEqual(previous.body, next.body)) {
|
|
285
325
|
delta.set.body = rawRequestSentinel('raw_request_body_replaced');
|
|
@@ -294,7 +334,7 @@ function buildRawRequestDelta(previous, next) {
|
|
|
294
334
|
return delta;
|
|
295
335
|
}
|
|
296
336
|
|
|
297
|
-
function applyRawRequestDelta(previous, delta) {
|
|
337
|
+
export function applyRawRequestDelta(previous, delta) {
|
|
298
338
|
if (!delta) return previous ?? null;
|
|
299
339
|
if (Object.prototype.hasOwnProperty.call(delta, 'base')) return cloneJsonValue(delta.base) ?? delta.base ?? null;
|
|
300
340
|
if (Object.prototype.hasOwnProperty.call(delta, 'replacement')) return cloneJsonValue(delta.replacement) ?? delta.replacement ?? null;
|
|
@@ -305,14 +345,18 @@ function applyRawRequestDelta(previous, delta) {
|
|
|
305
345
|
if (isPlainObject(delta.body)) {
|
|
306
346
|
const body = isPlainObject(next.body) ? { ...next.body } : {};
|
|
307
347
|
for (const [key, value] of Object.entries(delta.body)) {
|
|
308
|
-
if (key === 'messagesFrom' || key === 'messagesAppend' || key === 'messages') continue;
|
|
348
|
+
if (key === 'messagesFrom' || key === 'messagesAppend' || key === 'messages' || key === 'messagesKey') continue;
|
|
309
349
|
body[key] = cloneJsonValue(value) ?? value;
|
|
310
350
|
}
|
|
351
|
+
const messageKey = delta.body.messagesKey === 'messages' || delta.body.messagesKey === 'input'
|
|
352
|
+
? delta.body.messagesKey
|
|
353
|
+
: (Array.isArray(body.messages) || Object.prototype.hasOwnProperty.call(delta.body, 'messages') ? 'messages' : 'input');
|
|
311
354
|
if (Array.isArray(delta.body.messages)) {
|
|
312
|
-
body
|
|
355
|
+
body[messageKey] = cloneJsonValue(delta.body.messages) || [];
|
|
313
356
|
} else if (Array.isArray(delta.body.messagesAppend)) {
|
|
314
|
-
const
|
|
315
|
-
|
|
357
|
+
const existing = Array.isArray(body[messageKey]) ? body[messageKey] : [];
|
|
358
|
+
const from = Number.isFinite(Number(delta.body.messagesFrom)) ? Number(delta.body.messagesFrom) : existing.length;
|
|
359
|
+
body[messageKey] = existing.slice(0, from).concat(cloneJsonValue(delta.body.messagesAppend) || []);
|
|
316
360
|
}
|
|
317
361
|
next.body = body;
|
|
318
362
|
}
|
|
@@ -339,11 +383,14 @@ function buildRequestSnapshot(info = {}) {
|
|
|
339
383
|
|
|
340
384
|
function buildRequestDelta(previous, next) {
|
|
341
385
|
if (!previous) {
|
|
342
|
-
|
|
386
|
+
const delta = {
|
|
343
387
|
base: true,
|
|
344
388
|
systemPrompt: next.systemPrompt || '',
|
|
345
389
|
messages: Array.isArray(next.messages) ? next.messages : [],
|
|
346
390
|
};
|
|
391
|
+
const rawRequestDelta = buildRawRequestDelta(null, next.rawRequest);
|
|
392
|
+
if (rawRequestDelta) delta.rawRequestDelta = rawRequestDelta;
|
|
393
|
+
return delta;
|
|
347
394
|
}
|
|
348
395
|
const delta = {};
|
|
349
396
|
if ((next.systemPrompt || '') !== (previous.systemPrompt || '')) {
|
|
@@ -372,11 +419,15 @@ function applyRequestDelta(previous, delta = {}) {
|
|
|
372
419
|
rawRequest: base.rawRequest ?? null,
|
|
373
420
|
};
|
|
374
421
|
if (delta.base) {
|
|
375
|
-
|
|
422
|
+
const nextBase = {
|
|
376
423
|
systemPrompt: delta.systemPrompt || '',
|
|
377
424
|
messages: Array.isArray(delta.messages) ? delta.messages : [],
|
|
378
|
-
rawRequest:
|
|
425
|
+
rawRequest: null,
|
|
379
426
|
};
|
|
427
|
+
if (Object.prototype.hasOwnProperty.call(delta, 'rawRequestDelta')) {
|
|
428
|
+
nextBase.rawRequest = applyRawRequestDelta(null, delta.rawRequestDelta);
|
|
429
|
+
}
|
|
430
|
+
return nextBase;
|
|
380
431
|
}
|
|
381
432
|
if (typeof delta.systemPrompt === 'string') next.systemPrompt = delta.systemPrompt;
|
|
382
433
|
if (Array.isArray(delta.messages)) {
|
|
@@ -389,6 +440,13 @@ function applyRequestDelta(previous, delta = {}) {
|
|
|
389
440
|
return next;
|
|
390
441
|
}
|
|
391
442
|
|
|
443
|
+
export function reconstructDebugRawRequest(baseRawRequest, requestDelta) {
|
|
444
|
+
if (!requestDelta || !Object.prototype.hasOwnProperty.call(requestDelta, 'rawRequestDelta')) {
|
|
445
|
+
return baseRawRequest ?? null;
|
|
446
|
+
}
|
|
447
|
+
return applyRawRequestDelta(baseRawRequest ?? null, requestDelta.rawRequestDelta);
|
|
448
|
+
}
|
|
449
|
+
|
|
392
450
|
function sessionRequestsDir(rootDir, sessionId) {
|
|
393
451
|
if (sessionId) return join(rootDir, 'sessions', safeDirComponent(sessionId), 'debug', 'requests');
|
|
394
452
|
return join(rootDir, 'debug', 'requests');
|
|
@@ -462,7 +520,7 @@ function expandTrace(trace) {
|
|
|
462
520
|
ttfbMs: loop.ttfbMs || null,
|
|
463
521
|
stopReason: loop.stopReason || null,
|
|
464
522
|
at: loop.at || null,
|
|
465
|
-
rawRequest:
|
|
523
|
+
rawRequest: null,
|
|
466
524
|
rawResponse: loop.rawResponse ?? null,
|
|
467
525
|
requestDelta: loop.requestDelta || {},
|
|
468
526
|
requestBase: trace.baseRequest || null,
|
|
@@ -503,7 +561,7 @@ function traceToLegacyRows(trace) {
|
|
|
503
561
|
tool_calls_json: JSON.stringify(loop.toolCalls || []),
|
|
504
562
|
usage_json: JSON.stringify(u),
|
|
505
563
|
ttfb_ms: loop.ttfbMs || null,
|
|
506
|
-
raw_request:
|
|
564
|
+
raw_request: null,
|
|
507
565
|
raw_response: typeof loop.rawResponse === 'string' ? loop.rawResponse : JSON.stringify(loop.rawResponse ?? null),
|
|
508
566
|
user_prompt: trace.userPrompt || '',
|
|
509
567
|
};
|
|
@@ -674,8 +732,11 @@ export class DebugTrace {
|
|
|
674
732
|
const snapshot = buildRequestSnapshot(info);
|
|
675
733
|
const previousSnapshot = trace._lastSnapshot || this.#reconstructLastSnapshot(trace);
|
|
676
734
|
if (!trace.baseRequest) {
|
|
677
|
-
|
|
678
|
-
|
|
735
|
+
trace.baseRequest = {
|
|
736
|
+
systemPrompt: snapshot.systemPrompt,
|
|
737
|
+
messages: Array.isArray(snapshot.messages) ? cloneJsonValue(snapshot.messages) : [],
|
|
738
|
+
rawRequest: null,
|
|
739
|
+
};
|
|
679
740
|
}
|
|
680
741
|
const loopIndex = (trace.loops || []).findIndex(l => l.turnRowId === turnId);
|
|
681
742
|
const loop = {
|
package/yeaft/web-bridge.js
CHANGED
|
@@ -717,11 +717,33 @@ export function broadcastLanguageChange(language) {
|
|
|
717
717
|
|
|
718
718
|
/** Query timeout in ms — abort if LLM doesn't respond within this window */
|
|
719
719
|
const QUERY_TIMEOUT_MS = 120_000;
|
|
720
|
+
const HIGH_REASONING_QUERY_TIMEOUT_MS = 300_000;
|
|
721
|
+
|
|
722
|
+
function isHighReasoningEffort(effort) {
|
|
723
|
+
const value = typeof effort === 'string' ? effort.trim().toLowerCase() : '';
|
|
724
|
+
return value === 'high' || value === 'xhigh' || value === 'max';
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
function queryTimeoutMsForSessionConfig(config = null) {
|
|
728
|
+
return isHighReasoningEffort(config?.modelEffort) ? HIGH_REASONING_QUERY_TIMEOUT_MS : QUERY_TIMEOUT_MS;
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
function queryTimeoutMsForSession(sessionId = null) {
|
|
732
|
+
if (!sessionId || !session) return queryTimeoutMsForSessionConfig(session?.config);
|
|
733
|
+
try {
|
|
734
|
+
return queryTimeoutMsForSessionConfig(resolveSessionConfig(session.config, loadSessionConfig(liveConfigRoot(), sessionId)));
|
|
735
|
+
} catch {
|
|
736
|
+
return queryTimeoutMsForSessionConfig(session?.config);
|
|
737
|
+
}
|
|
738
|
+
}
|
|
720
739
|
|
|
721
740
|
/**
|
|
722
741
|
* Secondary watchdog grace period (ms).
|
|
723
742
|
*
|
|
724
|
-
* After
|
|
743
|
+
* After the active query timeout of silence the per-VP `vpAbort` is fired.
|
|
744
|
+
* Normal turns use {@link QUERY_TIMEOUT_MS}; high-reasoning session turns use
|
|
745
|
+
* {@link HIGH_REASONING_QUERY_TIMEOUT_MS} because large tool-result arcs can
|
|
746
|
+
* legitimately spend longer before the provider emits the first SSE event.
|
|
725
747
|
* That's enough on its own when adapters / tools cooperate with the
|
|
726
748
|
* AbortSignal — the engine throws `AbortError`, runVpTurn's catch emits
|
|
727
749
|
* `result{stopped:true}`, the driver `finally` emits `vp_typing_end`, and
|
|
@@ -741,7 +763,7 @@ const QUERY_TIMEOUT_MS = 120_000;
|
|
|
741
763
|
*
|
|
742
764
|
* The driver loop wraps `await runVpTurn(...)` in a Promise.race against
|
|
743
765
|
* this grace-window timer. If runVpTurn doesn't return within
|
|
744
|
-
*
|
|
766
|
+
* active query timeout + ESCALATE_AFTER_ABORT_MS, the driver forces its
|
|
745
767
|
* `finally` block (vp_typing_end + group_message), emits a synthetic
|
|
746
768
|
* `result{stopped:true}` so the frontend leaves its in-flight state,
|
|
747
769
|
* and moves on. The hung tool promise leaks (JS lacks cooperative
|
|
@@ -4059,7 +4081,8 @@ function startSessionLoadInBackground({ sessionId = null, sessionMeta = null, pe
|
|
|
4059
4081
|
*/
|
|
4060
4082
|
async function runVpTurnWithEscalation(args) {
|
|
4061
4083
|
const { sessionId, vpId, turnId, threadId, thread } = args;
|
|
4062
|
-
const
|
|
4084
|
+
const queryTimeoutMs = queryTimeoutMsForSession(sessionId);
|
|
4085
|
+
const deadlineMs = queryTimeoutMs + ESCALATE_AFTER_ABORT_MS;
|
|
4063
4086
|
await raceWithEscalation(runVpTurn(args), {
|
|
4064
4087
|
deadlineMs,
|
|
4065
4088
|
onEscalate: () => {
|
|
@@ -4213,14 +4236,15 @@ async function runVpTurn({ prompt, promptParts = null, sessionId, vpId, threadId
|
|
|
4213
4236
|
}
|
|
4214
4237
|
|
|
4215
4238
|
let queryTimer = null;
|
|
4239
|
+
const queryTimeoutMs = queryTimeoutMsForSession(sessionId);
|
|
4216
4240
|
const resetQueryTimer = () => {
|
|
4217
4241
|
if (queryTimer) clearTimeout(queryTimer);
|
|
4218
4242
|
queryTimer = setTimeout(() => {
|
|
4219
4243
|
if (!vpAbort.signal.aborted) {
|
|
4220
|
-
console.error(`[Yeaft] query timeout after ${
|
|
4244
|
+
console.error(`[Yeaft] query timeout after ${queryTimeoutMs / 1000}s of silence — aborting VP ${vpId}`);
|
|
4221
4245
|
try { vpAbort.abort(); } catch { /* best-effort */ }
|
|
4222
4246
|
}
|
|
4223
|
-
},
|
|
4247
|
+
}, queryTimeoutMs);
|
|
4224
4248
|
};
|
|
4225
4249
|
resetQueryTimer();
|
|
4226
4250
|
|
|
@@ -6229,6 +6253,8 @@ export const __testHooks = {
|
|
|
6229
6253
|
projectRuntimeCount() {
|
|
6230
6254
|
return projectRuntimes.size;
|
|
6231
6255
|
},
|
|
6256
|
+
queryTimeoutMsForSessionConfig,
|
|
6257
|
+
queryTimeoutMsForSession,
|
|
6232
6258
|
seedQueuedVpTurn({ sessionId = 'session-test', vpId = 'vp-test', threadId = 'main', turnId = 'turn-test' } = {}) {
|
|
6233
6259
|
const key = threadKey(sessionId, vpId, threadId);
|
|
6234
6260
|
const inbox = vpInboxes.get(key) || [];
|