@yeaft/webchat-agent 0.1.683 → 0.1.685

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": "0.1.683",
3
+ "version": "0.1.685",
4
4
  "description": "Remote agent for Yeaft WebChat — connects worker machines to the central server",
5
5
  "main": "index.js",
6
6
  "type": "module",
package/unify/engine.js CHANGED
@@ -1028,6 +1028,43 @@ export class Engine {
1028
1028
  let t1Fired = false;
1029
1029
  const queryNumber = (this.#__queryCounter = (this.#__queryCounter || 0) + 1);
1030
1030
 
1031
+ // feat-6af5f9f1 PR B: a Turn = one user prompt + all AI responses.
1032
+ // `queryTurnId` is the wire-level turn identifier; every event emitted
1033
+ // during this query() carries it as `turnId`. Each LLM call inside
1034
+ // the loop is a `loopNumber` (was wire field `turnNumber`).
1035
+ const queryTurnId = randomUUID();
1036
+ const queryStartedAt = Date.now();
1037
+ const userQuestionPreview = String(prompt || '').slice(0, 200);
1038
+ const queryVpId = vpPersona && typeof vpPersona === 'object'
1039
+ && typeof vpPersona.vpId === 'string'
1040
+ ? vpPersona.vpId
1041
+ : (typeof senderVpId === 'string' ? senderVpId : null);
1042
+
1043
+ yield {
1044
+ type: 'turn_open',
1045
+ turnId: queryTurnId,
1046
+ userPrompt: userQuestionPreview,
1047
+ vpId: queryVpId,
1048
+ groupId: groupId || null,
1049
+ at: queryStartedAt,
1050
+ };
1051
+
1052
+ // Surface memory recall to the debug panel right after turn_open.
1053
+ // recallResult was loaded above; emit a structured `memory_used`
1054
+ // event so the UI can show "loaded N segments" without parsing
1055
+ // the legacy `recall` event (which only carried entryCount).
1056
+ if (recallResult && Array.isArray(recallResult.entries) && recallResult.entries.length > 0) {
1057
+ yield {
1058
+ type: 'memory_used',
1059
+ turnId: queryTurnId,
1060
+ loaded: recallResult.entries.map(e => ({
1061
+ id: e && e.id || null,
1062
+ score: e && typeof e.score === 'number' ? e.score : null,
1063
+ kind: e && e.kind || null,
1064
+ })),
1065
+ };
1066
+ }
1067
+
1031
1068
  const toolDefs = this.#getToolDefs();
1032
1069
  let turnNumber = 0;
1033
1070
  let continueTurns = 0; // auto-continue counter
@@ -1196,16 +1233,23 @@ export class Engine {
1196
1233
  responseText,
1197
1234
  });
1198
1235
 
1199
- // Emit debug_turn for error path too
1236
+ // Emit `loop` event for error path too (was `debug_turn`).
1237
+ const errLoopInputTokens = totalUsage.inputTokens || 0;
1238
+ const errLoopOutputTokens = totalUsage.outputTokens || 0;
1200
1239
  yield {
1201
- type: 'debug_turn',
1202
- turnNumber,
1240
+ type: 'loop',
1241
+ turnId: queryTurnId,
1242
+ loopNumber: turnNumber,
1203
1243
  model: currentModel,
1204
1244
  systemPrompt,
1205
1245
  messages: conversationMessages.map(mapDebugMessage),
1206
1246
  response: responseText || `Error: ${err.message}`,
1207
1247
  toolCalls: toolCalls.map(tc => ({ id: tc.id, name: tc.name, input: tc.input })),
1208
- usage: { inputTokens: totalUsage.inputTokens, outputTokens: totalUsage.outputTokens },
1248
+ usage: {
1249
+ inputTokens: errLoopInputTokens,
1250
+ outputTokens: errLoopOutputTokens,
1251
+ totalTokens: errLoopInputTokens + errLoopOutputTokens,
1252
+ },
1209
1253
  latencyMs,
1210
1254
  ttfbMs,
1211
1255
  stopReason: 'error',
@@ -1270,20 +1314,31 @@ export class Engine {
1270
1314
  responseText,
1271
1315
  });
1272
1316
 
1273
- // Emit debug_turn event for web UI debug panel
1274
- // (conversationMessages does NOT yet include the assistant response at this point)
1275
- // task-331: preserve toolCalls / toolCallId / isError on each message so
1276
- // the Debug panel can render function_call requests and their paired
1277
- // tool_result responses across turns.
1317
+ // Emit `loop` event for the debug panel.
1318
+ // feat-6af5f9f1 PR B: a Loop is one LLM call inside a Turn. The wire
1319
+ // event was historically named `debug_turn` and carried `turnNumber`,
1320
+ // which is misleading it's per-LLM-call, not per-user-prompt.
1321
+ // We emit the new shape (turnId + loopNumber) and keep totalTokens
1322
+ // pre-computed so the UI doesn't have to.
1323
+ // task-331: preserve toolCalls / toolCallId / isError on each message
1324
+ // so the panel can render function_call requests and their paired
1325
+ // tool_result responses across loops.
1326
+ const loopInputTokens = totalUsage.inputTokens || 0;
1327
+ const loopOutputTokens = totalUsage.outputTokens || 0;
1278
1328
  yield {
1279
- type: 'debug_turn',
1280
- turnNumber,
1329
+ type: 'loop',
1330
+ turnId: queryTurnId,
1331
+ loopNumber: turnNumber,
1281
1332
  model: currentModel,
1282
1333
  systemPrompt,
1283
1334
  messages: conversationMessages.map(mapDebugMessage),
1284
1335
  response: responseText,
1285
1336
  toolCalls: toolCalls.map(tc => ({ id: tc.id, name: tc.name, input: tc.input })),
1286
- usage: { inputTokens: totalUsage.inputTokens, outputTokens: totalUsage.outputTokens },
1337
+ usage: {
1338
+ inputTokens: loopInputTokens,
1339
+ outputTokens: loopOutputTokens,
1340
+ totalTokens: loopInputTokens + loopOutputTokens,
1341
+ },
1287
1342
  latencyMs,
1288
1343
  ttfbMs,
1289
1344
  stopReason,
@@ -1390,10 +1445,12 @@ export class Engine {
1390
1445
  });
1391
1446
  if (adjustResult && adjustResult.ran) {
1392
1447
  yield {
1393
- type: 'ams_adjust',
1448
+ type: 'memory_adjust',
1449
+ turnId: queryTurnId,
1394
1450
  groupKey: amsContext.groupKey,
1395
1451
  added: adjustResult.added,
1396
1452
  evicted: adjustResult.evicted,
1453
+ skipped: adjustResult.skipped || 0,
1397
1454
  reason: adjustResult.reason,
1398
1455
  };
1399
1456
  }
@@ -1413,6 +1470,8 @@ export class Engine {
1413
1470
  );
1414
1471
  yield {
1415
1472
  type: 'reflection',
1473
+ turnId: queryTurnId,
1474
+ loopNumber: turnNumber,
1416
1475
  trigger: 't2',
1417
1476
  status: 'pending',
1418
1477
  loopRange: [arcStart, arcEnd],
@@ -1437,6 +1496,7 @@ export class Engine {
1437
1496
  loopRange: [arcStart, arcEnd],
1438
1497
  count: pairs.length,
1439
1498
  originalUserMsg: prompt,
1499
+ originatingTurnId: queryTurnId,
1440
1500
  ready: false,
1441
1501
  result: null,
1442
1502
  error: null,
@@ -1534,6 +1594,20 @@ export class Engine {
1534
1594
 
1535
1595
  const toolDurationMs = Date.now() - toolStartTime;
1536
1596
 
1597
+ // feat-6af5f9f1 PR B: emit a structured `tool_exec` event for the
1598
+ // debug panel. Args/output are already in `conversationMessages`
1599
+ // and will be visible in the next loop's snapshot, so we don't
1600
+ // duplicate them here — only the per-tool timing + status.
1601
+ yield {
1602
+ type: 'tool_exec',
1603
+ turnId: queryTurnId,
1604
+ loopNumber: turnNumber,
1605
+ callId: tc.id,
1606
+ name: tc.name,
1607
+ durationMs: toolDurationMs,
1608
+ isError,
1609
+ };
1610
+
1537
1611
  // Log tool to debug trace
1538
1612
  this.#trace.logTool(turnId, {
1539
1613
  toolName: tc.name,
@@ -1596,6 +1670,8 @@ export class Engine {
1596
1670
  );
1597
1671
  yield {
1598
1672
  type: 'reflection',
1673
+ turnId: queryTurnId,
1674
+ loopNumber: turnNumber,
1599
1675
  trigger: 't1',
1600
1676
  status: 'pending',
1601
1677
  loopRange: [arcStart, arcEnd],
@@ -1616,6 +1692,8 @@ export class Engine {
1616
1692
  for (const m of next) conversationMessages.push(m);
1617
1693
  yield {
1618
1694
  type: 'reflection',
1695
+ turnId: queryTurnId,
1696
+ loopNumber: turnNumber,
1619
1697
  trigger: 't1',
1620
1698
  // PR-L bug fix: keep the same loopRange as the `pending` event
1621
1699
  // so the frontend key stays stable across pending → ready and
@@ -1631,6 +1709,8 @@ export class Engine {
1631
1709
  // continues normally — never block the turn.
1632
1710
  yield {
1633
1711
  type: 'reflection',
1712
+ turnId: queryTurnId,
1713
+ loopNumber: turnNumber,
1634
1714
  trigger: 't1',
1635
1715
  status: 'error',
1636
1716
  error: err && err.message || String(err),
@@ -1656,6 +1736,18 @@ export class Engine {
1656
1736
 
1657
1737
  // Loop back to call adapter again with tool results
1658
1738
  }
1739
+
1740
+ // feat-6af5f9f1 PR B: turn closed. Emits final totals so the debug
1741
+ // panel can show "Turn done · 4 loops · 12.4s · 5.0k tok" without
1742
+ // having to reduce the loops itself. Always fires (every break path
1743
+ // above falls through here).
1744
+ yield {
1745
+ type: 'turn_close',
1746
+ turnId: queryTurnId,
1747
+ totalMs: Date.now() - queryStartedAt,
1748
+ totalTokens: cumulativeInputTokens + cumulativeOutputTokens,
1749
+ loopCount: turnNumber,
1750
+ };
1659
1751
  }
1660
1752
 
1661
1753
  /**
@@ -1753,6 +1845,7 @@ export class Engine {
1753
1845
 
1754
1846
  yield {
1755
1847
  type: 'reflection',
1848
+ turnId: info.originatingTurnId || null,
1756
1849
  trigger,
1757
1850
  status: 'ready',
1758
1851
  loopRange: [startIdx, endIdx],
@@ -535,6 +535,10 @@ function handleEngineEvent(event, hctx) {
535
535
  case 'reflection':
536
536
  sendUnifyEvent({
537
537
  type: 'reflection',
538
+ // feat-6af5f9f1 PR B: stamp turnId/loopNumber so the debug panel
539
+ // can attach reflection cards to the matching loop.
540
+ turnId: event.turnId || null,
541
+ loopNumber: event.loopNumber || null,
538
542
  trigger: event.trigger,
539
543
  status: event.status,
540
544
  loopRange: event.loopRange,
@@ -545,10 +549,66 @@ function handleEngineEvent(event, hctx) {
545
549
  }, envelope);
546
550
  break;
547
551
 
548
- case 'debug_turn':
552
+ case 'turn_open':
549
553
  sendUnifyEvent({
550
- type: 'debug_turn',
551
- turnNumber: event.turnNumber,
554
+ type: 'turn_open',
555
+ turnId: event.turnId,
556
+ userPrompt: event.userPrompt,
557
+ vpId: event.vpId,
558
+ groupId: event.groupId,
559
+ at: event.at,
560
+ }, envelope);
561
+ break;
562
+
563
+ case 'turn_close':
564
+ sendUnifyEvent({
565
+ type: 'turn_close',
566
+ turnId: event.turnId,
567
+ totalMs: event.totalMs,
568
+ totalTokens: event.totalTokens,
569
+ loopCount: event.loopCount,
570
+ }, envelope);
571
+ break;
572
+
573
+ case 'memory_used':
574
+ sendUnifyEvent({
575
+ type: 'memory_used',
576
+ turnId: event.turnId,
577
+ loaded: event.loaded || [],
578
+ }, envelope);
579
+ break;
580
+
581
+ case 'memory_adjust':
582
+ sendUnifyEvent({
583
+ type: 'memory_adjust',
584
+ turnId: event.turnId,
585
+ groupKey: event.groupKey,
586
+ added: event.added,
587
+ evicted: event.evicted,
588
+ skipped: event.skipped,
589
+ reason: event.reason,
590
+ }, envelope);
591
+ break;
592
+
593
+ case 'tool_exec':
594
+ sendUnifyEvent({
595
+ type: 'tool_exec',
596
+ turnId: event.turnId,
597
+ loopNumber: event.loopNumber,
598
+ callId: event.callId,
599
+ name: event.name,
600
+ durationMs: event.durationMs,
601
+ isError: event.isError,
602
+ }, envelope);
603
+ break;
604
+
605
+ case 'loop':
606
+ // feat-6af5f9f1 PR B: replaces the old `debug_turn` event. Same
607
+ // payload shape plus turnId + loopNumber + usage.totalTokens.
608
+ sendUnifyEvent({
609
+ type: 'loop',
610
+ turnId: event.turnId,
611
+ loopNumber: event.loopNumber,
552
612
  model: event.model,
553
613
  systemPrompt: event.systemPrompt,
554
614
  messages: event.messages,