@yeaft/webchat-agent 1.0.224 → 1.0.227

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.224",
3
+ "version": "1.0.227",
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",
package/terminal.js CHANGED
@@ -58,11 +58,19 @@ export async function loadNodePty() {
58
58
  }
59
59
  }
60
60
 
61
+ function terminalRoutingFields(source) {
62
+ return {
63
+ ...(source?._requestUserId ? { _requestUserId: source._requestUserId } : {}),
64
+ ...(source?._requestClientId ? { _requestClientId: source._requestClientId } : {}),
65
+ };
66
+ }
67
+
61
68
  export async function handleTerminalCreate(msg) {
62
69
  const { conversationId, cols, rows } = msg;
63
70
  const terminalId = msg.terminalId || conversationId;
64
71
  const conv = ctx.conversations.get(conversationId);
65
72
  const workDir = conv?.workDir || ctx.CONFIG.workDir;
73
+ const routingFields = terminalRoutingFields(msg);
66
74
 
67
75
  // 如果已存在终端,先关闭
68
76
  if (ctx.terminals.has(terminalId)) {
@@ -80,7 +88,8 @@ export async function handleTerminalCreate(msg) {
80
88
  type: 'terminal_error',
81
89
  conversationId,
82
90
  terminalId,
83
- message: 'Terminal backend is not installed. Run: npm install'
91
+ message: 'Terminal backend is not installed. Run: npm install',
92
+ ...routingFields,
84
93
  });
85
94
  return;
86
95
  }
@@ -123,7 +132,8 @@ export async function handleTerminalCreate(msg) {
123
132
  type: 'terminal_output',
124
133
  conversationId,
125
134
  terminalId,
126
- data: buffer
135
+ data: buffer,
136
+ ...routingFields,
127
137
  });
128
138
  buffer = '';
129
139
  timer = null;
@@ -138,7 +148,8 @@ export async function handleTerminalCreate(msg) {
138
148
  type: 'terminal_output',
139
149
  conversationId,
140
150
  terminalId,
141
- data: buffer
151
+ data: buffer,
152
+ ...routingFields,
142
153
  });
143
154
  buffer = '';
144
155
  }
@@ -149,7 +160,8 @@ export async function handleTerminalCreate(msg) {
149
160
  ctx.sendToServer({
150
161
  type: 'terminal_closed',
151
162
  conversationId,
152
- terminalId
163
+ terminalId,
164
+ ...routingFields,
153
165
  });
154
166
  });
155
167
 
@@ -159,7 +171,8 @@ export async function handleTerminalCreate(msg) {
159
171
  cols: cols || 80,
160
172
  rows: rows || 24,
161
173
  buffer: '',
162
- timer: null
174
+ timer: null,
175
+ ...routingFields,
163
176
  });
164
177
 
165
178
  console.log(`[PTY] Created terminal ${terminalId} for ${conversationId} in ${workDir}`);
@@ -167,7 +180,8 @@ export async function handleTerminalCreate(msg) {
167
180
  type: 'terminal_created',
168
181
  conversationId,
169
182
  terminalId,
170
- success: true
183
+ success: true,
184
+ ...routingFields,
171
185
  });
172
186
  } catch (e) {
173
187
  console.error(`[PTY] Failed to create terminal:`, e.message);
@@ -175,7 +189,8 @@ export async function handleTerminalCreate(msg) {
175
189
  type: 'terminal_error',
176
190
  conversationId,
177
191
  terminalId,
178
- message: `Failed to create terminal: ${e.message}`
192
+ message: `Failed to create terminal: ${e.message}`,
193
+ ...routingFields,
179
194
  });
180
195
  }
181
196
  }
@@ -220,7 +235,8 @@ export function handleTerminalClose(msg) {
220
235
  ctx.sendToServer({
221
236
  type: 'terminal_closed',
222
237
  conversationId: term.conversationId || msg.conversationId,
223
- terminalId
238
+ terminalId,
239
+ ...terminalRoutingFields(term),
224
240
  });
225
241
  }
226
242
  }
@@ -20,3 +20,13 @@ export function isHiddenConversationRow(row) {
20
20
  if (row.kind === 'compact_summary' || row._compactSummary) return true;
21
21
  return isInternalControlContent(row.content);
22
22
  }
23
+
24
+ /**
25
+ * Whether a persisted row may be shown as a human-authored conversation turn.
26
+ * Legacy user rows predate provenance metadata and remain visible; new Engine
27
+ * protocol rows carry `userAuthored: false` and are model-only.
28
+ */
29
+ export function isVisibleConversationRow(row) {
30
+ if (isHiddenConversationRow(row)) return false;
31
+ return row?.role !== 'user' || row.userAuthored !== false;
32
+ }
@@ -23,7 +23,7 @@ import { isPermissionError } from '../init.js';
23
23
  import { writeAtomic } from '../storage/atomic.js';
24
24
  import { pairSanitize } from '../pair-sanitize.js';
25
25
  import { sliceLastNTurns, stripVpMentionPrefix } from '../turn-utils.js';
26
- import { isHiddenConversationRow } from './internal-control.js';
26
+ import { isHiddenConversationRow, isVisibleConversationRow } from './internal-control.js';
27
27
 
28
28
  /**
29
29
  * Default cold-start "recent window" size, expressed in TURNS (not raw
@@ -317,6 +317,7 @@ export function projectVisibleSessionMessages(messages) {
317
317
  const visible = [];
318
318
  for (const row of rows) {
319
319
  if (!row || (row.role !== 'user' && row.role !== 'assistant')) continue;
320
+ if (!isVisibleConversationRow(row)) continue;
320
321
  if (row.role !== 'assistant' || !Array.isArray(row.toolCalls) || row.toolCalls.length === 0) {
321
322
  if (row.role === 'assistant' && !row.content && !row.attachments && !row.images
322
323
  && !row.toolSummaryCount && !row.askUserResults) continue;
@@ -385,6 +386,7 @@ function serializeMessage(msg) {
385
386
  if (msg.sessionId) fm.push(`sessionId: ${msg.sessionId}`);
386
387
  if (msg.chatId) fm.push(`chatId: ${msg.chatId}`);
387
388
  if (msg.clientMessageId) fm.push(`clientMessageId: ${msg.clientMessageId}`);
389
+ if (typeof msg.userAuthored === 'boolean') fm.push(`userAuthored: ${msg.userAuthored}`);
388
390
  if (msg.incomplete) fm.push('incomplete: true');
389
391
  if (msg.stopReason) fm.push(`stopReason: ${msg.stopReason}`);
390
392
  // Session attribution: when a VP authors an assistant turn (either
@@ -516,6 +518,7 @@ export function parseMessage(raw) {
516
518
  case 'sessionId': msg.sessionId = value; break;
517
519
  case 'chatId': msg.chatId = value; break;
518
520
  case 'clientMessageId': msg.clientMessageId = value; break;
521
+ case 'userAuthored': msg.userAuthored = value === 'true'; break;
519
522
  case 'incomplete': msg.incomplete = value === 'true'; break;
520
523
  case 'stopReason': msg.stopReason = value; break;
521
524
  case 'speakerVpId': msg.speakerVpId = value; break;
@@ -1524,7 +1527,7 @@ export class ConversationStore {
1524
1527
  if (!sessionId) return { messages: [], oldestSeq: null, hasMore: false };
1525
1528
  const cutoff = Number.isFinite(beforeSeq) ? beforeSeq : Infinity;
1526
1529
  const prefix = this.#readSessionRows(sessionId, { beforeSeq: cutoff })
1527
- .filter(m => m && m.sessionId === sessionId && !isHiddenConversationRow(m));
1530
+ .filter(m => m && m.sessionId === sessionId && isVisibleConversationRow(m));
1528
1531
  if (prefix.length === 0) return { messages: [], oldestSeq: null, hasMore: false };
1529
1532
  const sliced = pairSanitize(sliceLastNTurns(prefix, turnsLimit));
1530
1533
  // Turn-based hasMore: there's an EARLIER turn boundary we didn't keep.
@@ -1563,6 +1566,7 @@ export class ConversationStore {
1563
1566
  beforeSeq: cutoff,
1564
1567
  roles: null,
1565
1568
  stripAssistantToolCalls: false,
1569
+ visibleOnly: true,
1566
1570
  });
1567
1571
  const messages = projectVisibleSessionMessages(page.messages);
1568
1572
  if (messages.length === 0) return { messages: [], oldestSeq: null, hasMore: page.truncated };
@@ -1638,7 +1642,7 @@ export class ConversationStore {
1638
1642
  for (const m of this.#iterateSessionRows(sessionId, { afterSeq: cutoff, desc: false })) {
1639
1643
  if (!m || m.sessionId !== sessionId) continue;
1640
1644
  const seq = parseSeqFromId(m.id);
1641
- const hidden = isHiddenConversationRow(m);
1645
+ const hidden = !isVisibleConversationRow(m);
1642
1646
  if (!hidden) {
1643
1647
  // Keep every outstanding call open across interleaved VP rows. Session
1644
1648
  // persistence is globally sequenced, so a sibling VP may append visible
@@ -1794,13 +1798,14 @@ export class ConversationStore {
1794
1798
  beforeSeq: anchorSeq + 1,
1795
1799
  roles: null,
1796
1800
  stripAssistantToolCalls: false,
1801
+ visibleOnly: true,
1797
1802
  });
1798
1803
  const messages = beforeRaw.messages.slice();
1799
1804
  const seen = new Set(messages.map(message => message?.id).filter(Boolean));
1800
1805
  let followingUserTurns = 0;
1801
1806
 
1802
1807
  for (const message of this.#iterateSessionRows(sessionId, { afterSeq: anchorSeq, desc: false })) {
1803
- if (!message || message.sessionId !== sessionId || isHiddenConversationRow(message)) continue;
1808
+ if (!message || message.sessionId !== sessionId || !isVisibleConversationRow(message)) continue;
1804
1809
  if (message.role === 'user') {
1805
1810
  followingUserTurns += 1;
1806
1811
  if (followingUserTurns > afterTurns) break;
@@ -2452,6 +2457,7 @@ export class ConversationStore {
2452
2457
  roles = null,
2453
2458
  stripAssistantToolCalls = false,
2454
2459
  includeReflections = false,
2460
+ visibleOnly = false,
2455
2461
  } = {}) {
2456
2462
  const kept = [];
2457
2463
  const pendingBoundaryRows = [];
@@ -2498,7 +2504,9 @@ export class ConversationStore {
2498
2504
  if (!m || m.sessionId !== sessionId) continue;
2499
2505
 
2500
2506
  const boundaryComplete = turnsFromEnd >= turnsLimit;
2501
- if (isHiddenConversationRow(m) && !(includeReflections && m._reflection === true)) {
2507
+ const hidden = isHiddenConversationRow(m)
2508
+ || (visibleOnly && !isVisibleConversationRow(m));
2509
+ if (hidden && !(includeReflections && m._reflection === true)) {
2502
2510
  if (boundaryComplete) {
2503
2511
  truncated = true;
2504
2512
  break;
@@ -2551,7 +2559,7 @@ export class ConversationStore {
2551
2559
  let current = null;
2552
2560
 
2553
2561
  const visibleRow = (message) => {
2554
- if (!message || message.sessionId !== sessionId || isHiddenConversationRow(message)) return null;
2562
+ if (!message || message.sessionId !== sessionId || !isVisibleConversationRow(message)) return null;
2555
2563
  if (message.role !== 'user' && message.role !== 'assistant') return null;
2556
2564
  if (!message.id || seen.has(message.id)) return null;
2557
2565
  seen.add(message.id);
package/yeaft/engine.js CHANGED
@@ -1401,6 +1401,8 @@ export class Engine {
1401
1401
  if (message.isError) record.isError = true;
1402
1402
  if (message.imageAssetAnchor) record.imageAssetAnchor = true;
1403
1403
  if (message._reflection) record._reflection = true;
1404
+ if (message.role === 'user') record.userAuthored = message.userAuthored === true;
1405
+ if (message.internal === true) record.internal = true;
1404
1406
  if (Array.isArray(message.foldedMessageIds) && message.foldedMessageIds.length > 0) {
1405
1407
  record.foldedMessageIds = [...message.foldedMessageIds];
1406
1408
  }
@@ -1702,7 +1704,11 @@ export class Engine {
1702
1704
 
1703
1705
  #persistAppendedUserMessage(item, sessionId) {
1704
1706
  if (!item || item.persisted || item.internal) return;
1705
- this.#persistConversationMessage({ role: 'user', content: item.content }, { sessionId });
1707
+ this.#persistConversationMessage({
1708
+ role: 'user',
1709
+ content: item.content,
1710
+ userAuthored: true,
1711
+ }, { sessionId });
1706
1712
  item.persisted = true;
1707
1713
  }
1708
1714
 
@@ -1912,7 +1918,7 @@ export class Engine {
1912
1918
  // already writes one shared user row before multi-VP fan-out, so those
1913
1919
  // callers set userAlreadyPersisted and every VP skips this append.
1914
1920
  if (!userAlreadyPersisted) {
1915
- this.#persistConversationMessage({ role: 'user', content: prompt }, {
1921
+ this.#persistConversationMessage({ role: 'user', content: prompt, userAuthored: true }, {
1916
1922
  sessionId: runtimeSessionId,
1917
1923
  });
1918
1924
  }
@@ -98,9 +98,10 @@ export function stripMetaForWire(messages) {
98
98
  let mutated = false;
99
99
  const out = messages.map(m => {
100
100
  if (m && typeof m === 'object'
101
- && ('_meta' in m || '_runtimeTurnId' in m || '_partialTurn' in m || '_persistedMessageId' in m)) {
101
+ && ('_meta' in m || '_runtimeTurnId' in m || '_partialTurn' in m
102
+ || '_persistedMessageId' in m || 'userAuthored' in m)) {
102
103
  mutated = true;
103
- const { _meta, _runtimeTurnId, _partialTurn, _persistedMessageId, ...rest } = m;
104
+ const { _meta, _runtimeTurnId, _partialTurn, _persistedMessageId, userAuthored, ...rest } = m;
104
105
  return rest;
105
106
  }
106
107
  return m;
@@ -62,7 +62,7 @@ import {
62
62
  } from './history-compact.js';
63
63
  import { persistYeaftAttachments, attachmentsForPersistence, persistedAttachmentPreviewPayload } from './attachments.js';
64
64
  import { ConversationStore, parseSeqFromId, projectVisibleSessionMessages } from './conversation/persist.js';
65
- import { isHiddenConversationRow } from './conversation/internal-control.js';
65
+ import { isHiddenConversationRow, isVisibleConversationRow } from './conversation/internal-control.js';
66
66
  import { imageMetadataForPersistence } from './image-assets.js';
67
67
  import { sliceLastNTurns } from './turn-utils.js';
68
68
  import { pairSanitize } from './pair-sanitize.js';
@@ -1215,6 +1215,7 @@ function projectPersistedToHistoryEntry(m, { includeReflections = false } = {})
1215
1215
  }
1216
1216
 
1217
1217
  function projectPersistedToVisibleHistoryEntry(m) {
1218
+ if (!isVisibleConversationRow(m)) return null;
1218
1219
  const entry = projectPersistedToHistoryEntry(m);
1219
1220
  return entry && (entry.role === 'user' || entry.role === 'assistant') ? entry : null;
1220
1221
  }
@@ -3703,6 +3704,7 @@ function handleEngineEvent(event, hctx) {
3703
3704
  }
3704
3705
  sendSessionOutputFrame({
3705
3706
  type: 'user',
3707
+ userAuthored: false,
3706
3708
  tool_use_result: [{
3707
3709
  type: 'tool_result',
3708
3710
  tool_use_id: event.id,
@@ -3735,6 +3737,7 @@ function handleEngineEvent(event, hctx) {
3735
3737
  }
3736
3738
  sendSessionOutputFrame({
3737
3739
  type: 'user',
3740
+ userAuthored: false,
3738
3741
  tool_use_result: [{
3739
3742
  type: 'tool_result',
3740
3743
  tool_use_id: event.toolCallId,
@@ -5461,8 +5464,11 @@ function persistInboundMessageOnceByMsgId({ msgId, text, sessionId, threadId = '
5461
5464
  threadId: threadId || 'main',
5462
5465
  };
5463
5466
  if (sessionId) record.sessionId = sessionId;
5464
- if (persistRole === 'user' && clientMessageId && typeof clientMessageId === 'string') {
5465
- record.clientMessageId = clientMessageId;
5467
+ if (persistRole === 'user') {
5468
+ record.userAuthored = true;
5469
+ if (clientMessageId && typeof clientMessageId === 'string') {
5470
+ record.clientMessageId = clientMessageId;
5471
+ }
5466
5472
  }
5467
5473
  // Stamp speakerVpId so the UI's loadHistory replay can route the row
5468
5474
  // to the correct VP block. Only meaningful when role='assistant'; for