@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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yeaft/webchat-agent",
3
- "version": "1.0.387",
3
+ "version": "1.0.388",
4
4
  "description": "Remote worker agent for Yeaft Web Code Agent — connects the native Yeaft engine, CLI providers, and workbench tools",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -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 = '' } = {}) {
@@ -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,