@yeaft/webchat-agent 1.0.387 → 1.0.388
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/local-runtime/server/context.js +78 -0
- package/local-runtime/server/handlers/agent-output.js +36 -25
- package/local-runtime/server/handlers/client-conversation.js +47 -1
- package/local-runtime/server/ws-client.js +2 -1
- package/local-runtime/version.json +1 -1
- package/local-runtime/web/app.bundle.js +95 -104
- package/local-runtime/web/app.bundle.js.gz +0 -0
- package/local-runtime/web/index.html +2 -2
- package/local-runtime/web/style.bundle.css +1 -1
- package/local-runtime/web/style.bundle.css.gz +0 -0
- package/package.json +1 -1
- package/yeaft/debug-trace.js +168 -1
- package/yeaft/web-bridge.js +7 -0
|
Binary file
|
package/package.json
CHANGED
package/yeaft/debug-trace.js
CHANGED
|
@@ -36,6 +36,10 @@ const TRACE_APPEND_BATCH_MS = 100;
|
|
|
36
36
|
const EVENT_FLUSH_INTERVAL_MS = 30_000;
|
|
37
37
|
const MAX_SEARCH_PATTERN_CHARS = 300;
|
|
38
38
|
const DEFAULT_TRACE_TEXT_MAX_BYTES = 256 * 1024;
|
|
39
|
+
// A turn detail is returned as one WebSocket message. Keep its UI projection
|
|
40
|
+
// comfortably below the Agent connection's 8 MiB outbound queue ceiling while
|
|
41
|
+
// preserving the canonical file-backed trace without any extra truncation.
|
|
42
|
+
const DEBUG_DETAIL_WIRE_MAX_BYTES = 6 * 1024 * 1024;
|
|
39
43
|
|
|
40
44
|
function isPlainObject(value) {
|
|
41
45
|
return value && typeof value === 'object' && !Array.isArray(value);
|
|
@@ -803,6 +807,169 @@ function expandTrace(trace) {
|
|
|
803
807
|
return { loops, turns: Array.from(turnsById.values()) };
|
|
804
808
|
}
|
|
805
809
|
|
|
810
|
+
function debugDetailTextSentinel(value, maxBytes, path, originalBytes = jsonByteLength(value)) {
|
|
811
|
+
const text = value == null ? '' : String(value);
|
|
812
|
+
const budget = Math.max(2, Math.floor(Number(maxBytes) || 0));
|
|
813
|
+
if (originalBytes <= budget) return text;
|
|
814
|
+
const marker = `\n... [wire truncated ${path}; original ${originalBytes} bytes]`;
|
|
815
|
+
const rawBytes = Math.max(1, Buffer.byteLength(text, 'utf8'));
|
|
816
|
+
const expansion = Math.max(1, originalBytes / rawBytes);
|
|
817
|
+
const markerBytes = jsonByteLength(marker) - 2;
|
|
818
|
+
let previewBytes = Math.max(0, Math.floor((budget - markerBytes - 2) / expansion));
|
|
819
|
+
let projected = `${truncateUtf8Text(text, previewBytes).value}${marker}`;
|
|
820
|
+
let projectedBytes = jsonByteLength(projected);
|
|
821
|
+
if (projectedBytes > budget && previewBytes > 0) {
|
|
822
|
+
previewBytes = Math.max(0, Math.floor(previewBytes * (budget / projectedBytes)) - 8);
|
|
823
|
+
projected = `${truncateUtf8Text(text, previewBytes).value}${marker}`;
|
|
824
|
+
projectedBytes = jsonByteLength(projected);
|
|
825
|
+
}
|
|
826
|
+
if (projectedBytes <= budget) return projected;
|
|
827
|
+
if (jsonByteLength(marker) <= budget) return marker;
|
|
828
|
+
return '';
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
function debugDetailJsonSentinel(value, maxBytes, path, originalBytes = jsonByteLength(value)) {
|
|
832
|
+
if (value == null) return value;
|
|
833
|
+
const budget = Math.max(0, Math.floor(Number(maxBytes) || 0));
|
|
834
|
+
if (originalBytes <= budget) return value;
|
|
835
|
+
const base = {
|
|
836
|
+
__truncated: true,
|
|
837
|
+
reason: 'debug_detail_wire_budget',
|
|
838
|
+
path,
|
|
839
|
+
originalBytes,
|
|
840
|
+
maxBytes: budget,
|
|
841
|
+
};
|
|
842
|
+
const baseBytes = jsonByteLength(base);
|
|
843
|
+
if (baseBytes > budget) return budget >= 4 ? null : '';
|
|
844
|
+
let preview = '';
|
|
845
|
+
try {
|
|
846
|
+
const encoded = JSON.stringify(value);
|
|
847
|
+
preview = debugDetailTextSentinel(
|
|
848
|
+
encoded,
|
|
849
|
+
Math.max(2, budget - baseBytes - 16),
|
|
850
|
+
path,
|
|
851
|
+
jsonByteLength(encoded),
|
|
852
|
+
);
|
|
853
|
+
} catch { /* metadata-only sentinel below */ }
|
|
854
|
+
const projected = preview ? { ...base, preview } : base;
|
|
855
|
+
return jsonByteLength(projected) <= budget ? projected : base;
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
function allocateDebugCandidateBudgets(candidates, totalBytes) {
|
|
859
|
+
const sorted = [...candidates].sort((a, b) => a.originalBytes - b.originalBytes || a.path.localeCompare(b.path));
|
|
860
|
+
let remainingBytes = Math.max(0, Math.floor(totalBytes));
|
|
861
|
+
let remainingCount = sorted.length;
|
|
862
|
+
for (let index = 0; index < sorted.length; index += 1) {
|
|
863
|
+
const candidate = sorted[index];
|
|
864
|
+
const fairShare = remainingCount > 0 ? Math.floor(remainingBytes / remainingCount) : 0;
|
|
865
|
+
if (candidate.originalBytes <= fairShare) {
|
|
866
|
+
candidate.budget = candidate.originalBytes;
|
|
867
|
+
remainingBytes -= candidate.budget;
|
|
868
|
+
remainingCount -= 1;
|
|
869
|
+
continue;
|
|
870
|
+
}
|
|
871
|
+
for (let tail = index; tail < sorted.length; tail += 1) {
|
|
872
|
+
const count = sorted.length - tail;
|
|
873
|
+
const budget = count > 0 ? Math.floor(remainingBytes / count) : 0;
|
|
874
|
+
sorted[tail].budget = budget;
|
|
875
|
+
remainingBytes -= budget;
|
|
876
|
+
}
|
|
877
|
+
break;
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
export function projectDebugDetailForWire(detail, maxBytes = DEBUG_DETAIL_WIRE_MAX_BYTES) {
|
|
882
|
+
// `fetchTurnDebug()` hands us a freshly expanded response object; mutate that
|
|
883
|
+
// disposable projection rather than cloning tens or hundreds of cumulative
|
|
884
|
+
// request snapshots. Canonical file records and the request cache are separate.
|
|
885
|
+
const wire = detail && typeof detail === 'object'
|
|
886
|
+
? detail
|
|
887
|
+
: { loops: [], turns: [], dreamEvents: [] };
|
|
888
|
+
const payloadBudget = Math.max(0, maxBytes - 2048);
|
|
889
|
+
const candidates = [];
|
|
890
|
+
const addCandidate = (container, field, path) => {
|
|
891
|
+
if (!container || container[field] == null) return;
|
|
892
|
+
const value = container[field];
|
|
893
|
+
candidates.push({
|
|
894
|
+
container,
|
|
895
|
+
field,
|
|
896
|
+
path,
|
|
897
|
+
value,
|
|
898
|
+
originalBytes: jsonByteLength(value),
|
|
899
|
+
budget: 0,
|
|
900
|
+
});
|
|
901
|
+
// Measure the non-candidate envelope exactly once without serializing every
|
|
902
|
+
// cumulative request again after each replacement.
|
|
903
|
+
container[field] = null;
|
|
904
|
+
};
|
|
905
|
+
|
|
906
|
+
const loopFields = ['rawRequest', 'rawResponse', 'messages', 'requestBase', 'requestDelta', 'toolCalls', 'response', 'systemPrompt'];
|
|
907
|
+
for (const [loopIndex, loop] of (Array.isArray(wire.loops) ? wire.loops : []).entries()) {
|
|
908
|
+
if (!loop || typeof loop !== 'object') continue;
|
|
909
|
+
for (const field of loopFields) addCandidate(loop, field, `loops[${loopIndex}].${field}`);
|
|
910
|
+
}
|
|
911
|
+
for (const [turnIndex, turn] of (Array.isArray(wire.turns) ? wire.turns : []).entries()) {
|
|
912
|
+
if (!turn || typeof turn !== 'object') continue;
|
|
913
|
+
for (const field of ['userPrompt', 'memoryLoaded', 'memoryAdjust']) {
|
|
914
|
+
addCandidate(turn, field, `turns[${turnIndex}].${field}`);
|
|
915
|
+
}
|
|
916
|
+
for (const [toolIndex, tool] of (Array.isArray(turn.tools) ? turn.tools : []).entries()) {
|
|
917
|
+
if (!tool || typeof tool !== 'object') continue;
|
|
918
|
+
addCandidate(tool, 'toolInput', `turns[${turnIndex}].tools[${toolIndex}].toolInput`);
|
|
919
|
+
addCandidate(tool, 'toolOutput', `turns[${turnIndex}].tools[${toolIndex}].toolOutput`);
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
for (const [eventIndex] of (Array.isArray(wire.dreamEvents) ? wire.dreamEvents : []).entries()) {
|
|
923
|
+
addCandidate(wire.dreamEvents, eventIndex, `dreamEvents[${eventIndex}]`);
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
const skeletonBytes = jsonByteLength(wire);
|
|
927
|
+
const candidateBytes = candidates.reduce((sum, candidate) => sum + candidate.originalBytes, 0);
|
|
928
|
+
const originalBytes = skeletonBytes - (4 * candidates.length) + candidateBytes;
|
|
929
|
+
if (originalBytes <= payloadBudget) {
|
|
930
|
+
for (const candidate of candidates) candidate.container[candidate.field] = candidate.value;
|
|
931
|
+
return wire;
|
|
932
|
+
}
|
|
933
|
+
if (candidates.length === 0 || skeletonBytes > payloadBudget) {
|
|
934
|
+
throw new Error(`Debug detail metadata exceeds the ${maxBytes}-byte wire budget`);
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
// Each candidate currently occupies JSON `null` (4 bytes) in the skeleton.
|
|
938
|
+
// Water-fill the exact value budget: small fields stay complete, while large
|
|
939
|
+
// cumulative requests and tool outputs receive fair, independently bounded
|
|
940
|
+
// previews. This is O(total input bytes + candidates log candidates), with
|
|
941
|
+
// one final whole-envelope serialization rather than one per candidate.
|
|
942
|
+
const valueBudget = payloadBudget - skeletonBytes + (4 * candidates.length);
|
|
943
|
+
allocateDebugCandidateBudgets(candidates, valueBudget);
|
|
944
|
+
let truncatedFields = 0;
|
|
945
|
+
for (const candidate of candidates) {
|
|
946
|
+
if (candidate.originalBytes <= candidate.budget) {
|
|
947
|
+
candidate.container[candidate.field] = candidate.value;
|
|
948
|
+
continue;
|
|
949
|
+
}
|
|
950
|
+
const fieldBudget = candidate.budget;
|
|
951
|
+
candidate.container[candidate.field] = typeof candidate.value === 'string'
|
|
952
|
+
? debugDetailTextSentinel(candidate.value, fieldBudget, candidate.path, candidate.originalBytes)
|
|
953
|
+
: debugDetailJsonSentinel(candidate.value, fieldBudget, candidate.path, candidate.originalBytes);
|
|
954
|
+
truncatedFields += 1;
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
wire.projection = {
|
|
958
|
+
truncated: true,
|
|
959
|
+
reason: 'debug_detail_wire_budget',
|
|
960
|
+
maxBytes,
|
|
961
|
+
truncatedFields,
|
|
962
|
+
};
|
|
963
|
+
const projectedBytesBase = jsonByteLength(wire);
|
|
964
|
+
let projectedBytes = projectedBytesBase + Buffer.byteLength(`,\"projectedBytes\":${projectedBytesBase}`, 'utf8');
|
|
965
|
+
projectedBytes = projectedBytesBase + Buffer.byteLength(`,\"projectedBytes\":${projectedBytes}`, 'utf8');
|
|
966
|
+
wire.projection.projectedBytes = projectedBytes;
|
|
967
|
+
if (projectedBytes > maxBytes) {
|
|
968
|
+
throw new Error(`Debug detail projection still exceeds the ${maxBytes}-byte wire budget`);
|
|
969
|
+
}
|
|
970
|
+
return wire;
|
|
971
|
+
}
|
|
972
|
+
|
|
806
973
|
function traceToLegacyRows(trace) {
|
|
807
974
|
let snapshot = null;
|
|
808
975
|
let rawRequest = trace?.baseRequest?.rawRequest ?? null;
|
|
@@ -1249,7 +1416,7 @@ export class DebugTrace {
|
|
|
1249
1416
|
const dreamEvents = this.#readDreamEvents({ sessionId: requestedSessionId, dreamLimit });
|
|
1250
1417
|
if (!trace) return { loops: [], turns: [], dreamEvents, detailTurnId: requestedTurnId };
|
|
1251
1418
|
const expanded = expandTrace(trace);
|
|
1252
|
-
return { ...expanded, dreamEvents, detailTurnId: requestedTurnId };
|
|
1419
|
+
return projectDebugDetailForWire({ ...expanded, dreamEvents, detailTurnId: requestedTurnId });
|
|
1253
1420
|
}
|
|
1254
1421
|
|
|
1255
1422
|
async fetchRecentDebugHistory({ limit = MAX_HISTORY_LIMIT, dreamLimit = 5, sessionId = null, threadId = null, indexOnly = false, detailTurnId = null, search = '' } = {}) {
|
package/yeaft/web-bridge.js
CHANGED
|
@@ -6467,11 +6467,13 @@ export async function handleYeaftFetchDebugHistory(msg = {}) {
|
|
|
6467
6467
|
const search = typeof msg?.search === 'string' ? msg.search.trim() : '';
|
|
6468
6468
|
const requestId = typeof msg?.requestId === 'string' && msg.requestId ? msg.requestId : null;
|
|
6469
6469
|
const requestKind = typeof msg?.requestKind === 'string' && msg.requestKind ? msg.requestKind : null;
|
|
6470
|
+
const requestClientId = typeof msg?._requestClientId === 'string' && msg._requestClientId ? msg._requestClientId : null;
|
|
6470
6471
|
const indexOnly = !!msg?.indexOnly;
|
|
6471
6472
|
const detailTurnId = typeof msg?.detailTurnId === 'string' && msg.detailTurnId ? msg.detailTurnId : null;
|
|
6472
6473
|
let loops = [];
|
|
6473
6474
|
let turns = [];
|
|
6474
6475
|
let dreamEvents = [];
|
|
6476
|
+
let projection = null;
|
|
6475
6477
|
let hasMore = false;
|
|
6476
6478
|
try {
|
|
6477
6479
|
if (session?.trace && detailTurnId && sessionId && typeof session.trace.fetchTurnDebug === 'function') {
|
|
@@ -6479,12 +6481,14 @@ export async function handleYeaftFetchDebugHistory(msg = {}) {
|
|
|
6479
6481
|
loops = Array.isArray(out?.loops) ? out.loops : [];
|
|
6480
6482
|
turns = Array.isArray(out?.turns) ? out.turns : [];
|
|
6481
6483
|
dreamEvents = Array.isArray(out?.dreamEvents) ? out.dreamEvents : [];
|
|
6484
|
+
projection = out?.projection && typeof out.projection === 'object' ? out.projection : null;
|
|
6482
6485
|
hasMore = false;
|
|
6483
6486
|
} else if (session?.trace && typeof session.trace.fetchRecentDebugHistory === 'function') {
|
|
6484
6487
|
const out = await session.trace.fetchRecentDebugHistory({ limit, dreamLimit, sessionId, threadId, indexOnly, detailTurnId, search });
|
|
6485
6488
|
loops = Array.isArray(out?.loops) ? out.loops : [];
|
|
6486
6489
|
turns = Array.isArray(out?.turns) ? out.turns : [];
|
|
6487
6490
|
dreamEvents = Array.isArray(out?.dreamEvents) ? out.dreamEvents : [];
|
|
6491
|
+
projection = out?.projection && typeof out.projection === 'object' ? out.projection : null;
|
|
6488
6492
|
hasMore = !!out?.hasMore;
|
|
6489
6493
|
}
|
|
6490
6494
|
} catch (err) {
|
|
@@ -6495,6 +6499,7 @@ export async function handleYeaftFetchDebugHistory(msg = {}) {
|
|
|
6495
6499
|
dreamEvents: [],
|
|
6496
6500
|
requestId,
|
|
6497
6501
|
requestKind,
|
|
6502
|
+
...(requestClientId ? { _requestClientId: requestClientId } : {}),
|
|
6498
6503
|
sessionId,
|
|
6499
6504
|
threadId,
|
|
6500
6505
|
search,
|
|
@@ -6510,8 +6515,10 @@ export async function handleYeaftFetchDebugHistory(msg = {}) {
|
|
|
6510
6515
|
loops,
|
|
6511
6516
|
turns,
|
|
6512
6517
|
dreamEvents,
|
|
6518
|
+
...(projection ? { projection } : {}),
|
|
6513
6519
|
requestId,
|
|
6514
6520
|
requestKind,
|
|
6521
|
+
...(requestClientId ? { _requestClientId: requestClientId } : {}),
|
|
6515
6522
|
sessionId,
|
|
6516
6523
|
threadId,
|
|
6517
6524
|
search,
|