@yeaft/webchat-agent 1.0.22 → 1.0.24

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.22",
3
+ "version": "1.0.24",
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",
@@ -17,6 +17,11 @@ export const VALID_STATES = new Set([
17
17
  ]);
18
18
 
19
19
  const RUNNING_STATES = new Set(['typing', 'thinking', 'streaming', 'tool']);
20
+
21
+ export function isVpStatusRunning(state) {
22
+ return RUNNING_STATES.has(state || 'idle');
23
+ }
24
+
20
25
  const STATE_PRIORITY = ['tool', 'streaming', 'thinking', 'typing', 'error', 'idle'];
21
26
  const MAX_RETAINED_THREADS_PER_VP = 20;
22
27
  const COMPLETED_TTL_MS = 30 * 60 * 1000;
@@ -52,7 +57,7 @@ export function createVpStatusBroker({ send, now = Date.now } = {}) {
52
57
  }
53
58
  }
54
59
 
55
- const runningThreadCount = rows.filter(r => RUNNING_STATES.has(r.state)).length;
60
+ const runningThreadCount = rows.filter(r => isVpStatusRunning(r.state)).length;
56
61
  const latest = rows[0] || null;
57
62
  return {
58
63
  sessionId: sessionId || null,
@@ -83,14 +88,14 @@ export function createVpStatusBroker({ send, now = Date.now } = {}) {
83
88
  }
84
89
  const cutoff = now() - COMPLETED_TTL_MS;
85
90
  for (const [key, row] of rows) {
86
- if (RUNNING_STATES.has(row.state)) continue;
91
+ if (isVpStatusRunning(row.state)) continue;
87
92
  if ((row.updatedAt || row.since || 0) < cutoff) threads.delete(key);
88
93
  }
89
94
  const remaining = rows
90
95
  .filter(([key]) => threads.has(key))
91
96
  .sort((a, b) => (b[1].updatedAt || b[1].since || 0) - (a[1].updatedAt || a[1].since || 0));
92
97
  for (const [key, row] of remaining.slice(MAX_RETAINED_THREADS_PER_VP)) {
93
- if (!RUNNING_STATES.has(row.state)) threads.delete(key);
98
+ if (!isVpStatusRunning(row.state)) threads.delete(key);
94
99
  }
95
100
  }
96
101
 
@@ -66,7 +66,7 @@ import { isHiddenConversationRow } from './conversation/internal-control.js';
66
66
  import { sliceLastNTurns } from './turn-utils.js';
67
67
  import { pairSanitize } from './pair-sanitize.js';
68
68
  import { filterSnapshotForVp } from './snapshot-filter.js';
69
- import { createVpStatusBroker } from './vp-status-broker.js';
69
+ import { createVpStatusBroker, isVpStatusRunning } from './vp-status-broker.js';
70
70
  import { classifyThread as defaultClassifyThread, fallbackTitle } from './vp/thread-classifier.js';
71
71
  import { listMcpServers, upsertMcpServer, removeMcpServer } from './config-api.js';
72
72
  import { buildMcpFlattenedTools } from './tools/mcp-tools.js';
@@ -433,6 +433,32 @@ function buildVpPromptPayload(vpId, envelope) {
433
433
  return { text, prompt, promptParts };
434
434
  }
435
435
 
436
+ function buildPendingRescueEnvelope({ sessionId, taskId = null, threadId = 'main', followUpId, leftover, replayText, replayParts = null }) {
437
+ const leftoverIsInternal = Boolean(leftover?.internal);
438
+ const leftoverInjectedBy = leftoverIsInternal && typeof leftover?.injectedBy === 'string'
439
+ ? leftover.injectedBy
440
+ : null;
441
+ return {
442
+ sessionId,
443
+ taskId,
444
+ trigger: 'pending_rescue',
445
+ msg: {
446
+ id: followUpId,
447
+ from: leftoverIsInternal && leftover.senderVpId ? leftover.senderVpId : 'user',
448
+ role: leftoverIsInternal ? 'assistant' : 'user',
449
+ text: replayText,
450
+ meta: {
451
+ rescuedFrom: 'pendingQueries',
452
+ threadId,
453
+ ...(leftoverInjectedBy ? { injectedBy: leftoverInjectedBy } : {}),
454
+ ...(leftoverIsInternal && leftover.senderVpId ? { senderVpId: leftover.senderVpId } : {}),
455
+ ...(leftoverIsInternal && leftover.sourceThreadId ? { sourceThreadId: leftover.sourceThreadId } : {}),
456
+ },
457
+ },
458
+ ...(Array.isArray(replayParts) && replayParts.length > 0 ? { _promptParts: replayParts } : {}),
459
+ };
460
+ }
461
+
436
462
  export function visibleInboundThreadId(envelope, fallbackThreadId = 'main') {
437
463
  const meta = envelope?.msg?.meta || {};
438
464
  if (
@@ -1350,6 +1376,9 @@ async function routeEnvelopeToVpThread(sessionId, vpId, envelope) {
1350
1376
  originalText: text,
1351
1377
  originalParts: Array.isArray(envelope?._promptParts) ? envelope._promptParts : null,
1352
1378
  internal: isInternalAppend,
1379
+ injectedBy: isInternalAppend ? injectedBy : null,
1380
+ senderVpId: isInternalAppend ? (envelope?.msg?.meta?.senderVpId || envelope?.msg?.from || null) : null,
1381
+ sourceThreadId: isInternalAppend ? visibleInboundThreadId(envelope, thread.threadId) : null,
1353
1382
  });
1354
1383
  persistInboundMessageOnceByMsgId({
1355
1384
  msgId: envelope?.msg?.id,
@@ -1534,18 +1563,15 @@ function ensureDriverRunning(sessionId, vpId, threadId = 'main') {
1534
1563
  : null;
1535
1564
  if (!replayText && !replayParts) continue;
1536
1565
  const followUpId = `followup_${Date.now().toString(36)}_${randomUUID().slice(0, 8)}`;
1537
- const followUpEnvelope = {
1566
+ const followUpEnvelope = buildPendingRescueEnvelope({
1538
1567
  sessionId,
1539
1568
  taskId: envelope?.taskId || null,
1540
- trigger: 'pending_rescue',
1541
- msg: {
1542
- id: followUpId,
1543
- from: 'user',
1544
- text: replayText,
1545
- meta: { rescuedFrom: 'pendingQueries', threadId: thread.threadId },
1546
- },
1547
- ...(replayParts ? { _promptParts: replayParts } : {}),
1548
- };
1569
+ threadId: thread.threadId,
1570
+ followUpId,
1571
+ leftover,
1572
+ replayText,
1573
+ replayParts,
1574
+ });
1549
1575
  const followUpTurnId = `${randomUUID().slice(0, 8)}:${vpId}`;
1550
1576
  inbox.push({ envelope: followUpEnvelope, turnId: followUpTurnId, thread });
1551
1577
  try {
@@ -1825,7 +1851,7 @@ function decorateSessionsWithRuntimeState(sessions) {
1825
1851
  const sessionId = status?.sessionId || status?.groupId || null;
1826
1852
  if (!sessionId) continue;
1827
1853
  const state = status.state || 'idle';
1828
- const running = !['idle', 'offline', 'completed', 'failed', 'aborted'].includes(state);
1854
+ const running = isVpStatusRunning(state);
1829
1855
  const updatedAt = status.updatedAt || status.since || Date.now();
1830
1856
  const prev = bySession.get(sessionId) || { running: false, runningVpCount: 0, latestActivityAt: 0 };
1831
1857
  if (running) prev.runningVpCount += 1;
@@ -5260,6 +5286,7 @@ export async function handleYeaftMcpReload(msg = {}) {
5260
5286
  export const __testHooks = {
5261
5287
  loadVisibleGroupHistoryPage,
5262
5288
  persistInboundMessageOnceByMsgId,
5289
+ buildPendingRescueEnvelope,
5263
5290
  setSessionForTest(nextSession) {
5264
5291
  session = nextSession || null;
5265
5292
  },
@@ -5269,6 +5296,13 @@ export const __testHooks = {
5269
5296
  vpAborts.clear();
5270
5297
  vpInboxes.clear();
5271
5298
  },
5299
+ resetVpStatusBroker() {
5300
+ if (vpStatusBroker) vpStatusBroker.reset();
5301
+ },
5302
+ seedVpStatus(status) {
5303
+ return getVpStatusBroker().transition(status);
5304
+ },
5305
+ decorateSessionsWithRuntimeState,
5272
5306
  seedQueuedVpTurn({ sessionId = 'session-test', vpId = 'vp-test', threadId = 'main', turnId = 'turn-test' } = {}) {
5273
5307
  const key = threadKey(sessionId, vpId, threadId);
5274
5308
  const inbox = vpInboxes.get(key) || [];