@yeaft/webchat-agent 0.1.787 → 0.1.789

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.787",
3
+ "version": "0.1.789",
4
4
  "description": "Remote agent for Yeaft WebChat — connects worker machines to the central server",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -107,6 +107,13 @@ function serializeMessage(msg) {
107
107
  // the speaker so the UI can render the message on the correct VP track.
108
108
  // For real user messages this is unset.
109
109
  if (msg.speakerVpId) fm.push(`speakerVpId: ${msg.speakerVpId}`);
110
+ // Internal/synthetic rows must round-trip so refresh/history replay can
111
+ // keep them out of the user-visible conversation. Reflection folding uses
112
+ // `_reflection`; other engine-only rows may use one of the explicit flags.
113
+ if (msg._reflection) fm.push('_reflection: true');
114
+ if (msg.internal) fm.push('internal: true');
115
+ if (msg.systemOnly) fm.push('systemOnly: true');
116
+ if (msg.systemOnlyMessage) fm.push('systemOnlyMessage: true');
110
117
 
111
118
  // Token estimate
112
119
  const content = msg.content || '';
@@ -179,6 +186,10 @@ export function parseMessage(raw) {
179
186
  case 'sourceThreadId': msg.sourceThreadId = value; break;
180
187
  case 'groupId': msg.groupId = value; break;
181
188
  case 'speakerVpId': msg.speakerVpId = value; break;
189
+ case '_reflection': msg._reflection = value === 'true'; break;
190
+ case 'internal': msg.internal = value === 'true'; break;
191
+ case 'systemOnly': msg.systemOnly = value === 'true'; break;
192
+ case 'systemOnlyMessage': msg.systemOnlyMessage = value === 'true'; break;
182
193
  // toolCalls are multi-line YAML — handled separately below
183
194
  }
184
195
  }
package/unify/engine.js CHANGED
@@ -1782,7 +1782,7 @@ export class Engine {
1782
1782
 
1783
1783
  // PR-L: T2 end-of-turn (asynchronous) reflection. Fires when the
1784
1784
  // total tool count for this query() exceeds TURN_SUMMARY_THRESHOLD
1785
- // (5) AND no T1 has actually rewritten the arc yet. Kicks off the
1785
+ // (8) AND no T1 has actually rewritten the arc yet. Kicks off the
1786
1786
  // primary-model call without await; the next query()'s
1787
1787
  // `#applyPendingT2Reflections` carries the result forward.
1788
1788
  //
@@ -10,16 +10,30 @@
10
10
  *
11
11
  * The constants are NOT config-driven — V7 design freezes them in code.
12
12
  *
13
+ * Invariant: TURN_SUMMARY_THRESHOLD < TOOL_BATCH_SIZE. T1 runs inside the
14
+ * turn and collapses history in place; T2 fires at end_turn and is gated
15
+ * by `t1CollapsesDone === 0` (engine.js). If T2 were ever set ≥ T1, T1
16
+ * would collapse first and T2 could never fire — silently disabling the
17
+ * end-of-turn reflection path. Keep a usefully wide gap between the two
18
+ * so the (T2, T1) band where T2-alone applies stays meaningful.
19
+ *
13
20
  * TOOL_BATCH_SIZE history: was 13 originally; raised to 30 (2026-05-15)
14
21
  * after user feedback that 13 fired too often inside a single task and
15
22
  * fragmented otherwise-coherent tool arcs into multiple reflections. 30
16
23
  * keeps the periodic-reflection contract (it still fires every N tools,
17
24
  * not just once) but gives a single task arc room to breathe before the
18
25
  * arc gets collapsed.
26
+ *
27
+ * TURN_SUMMARY_THRESHOLD history: was 5 originally; raised to 8
28
+ * (2026-05-18). 5 was too aggressive — small "read a few files, edit one,
29
+ * run tests" turns crossed it and produced a T2 reflection card the user
30
+ * didn't want. 8 lets short-to-medium turns finish without a reflection
31
+ * while still folding the long ones that genuinely benefit from a summary
32
+ * before the next turn's history grows.
19
33
  */
20
34
 
21
35
  export const TOOL_BATCH_SIZE = 30;
22
- export const TURN_SUMMARY_THRESHOLD = 5;
36
+ export const TURN_SUMMARY_THRESHOLD = 8;
23
37
  export const DUP_TOOL_THRESHOLD = 3;
24
38
 
25
39
  export { ExecLog, buildEntry, argsHashOf } from './exec-log.js';
@@ -366,7 +366,11 @@ function makeGroupContextStub() {
366
366
  function projectPersistedToHistoryEntry(m) {
367
367
  if (!m) return null;
368
368
  if (m.role !== 'user' && m.role !== 'assistant' && m.role !== 'tool') return null;
369
+ if (m._reflection || m.internal || m.systemOnly || m.systemOnlyMessage) return null;
369
370
  const entry = { role: m.role, content: m.content };
371
+ if (m.id) entry.id = m.id;
372
+ if (m.groupId) entry.groupId = m.groupId;
373
+ if (m.speakerVpId) entry.speakerVpId = m.speakerVpId;
370
374
  if (m.toolCallId) entry.toolCallId = m.toolCallId;
371
375
  if (Array.isArray(m.toolCalls) && m.toolCalls.length > 0) {
372
376
  entry.toolCalls = m.toolCalls.map(tc => ({
@@ -2986,20 +2990,22 @@ export async function handleUnifyLoadHistory(msg) {
2986
2990
  const compactSummary = session.conversationStore.readCompactSummary();
2987
2991
 
2988
2992
  for (const m of messages) {
2989
- if (m.role === 'user') {
2990
- sendUnifyOutput({ type: 'user', message: { content: m.content } }, { groupId: m.groupId || null });
2991
- } else if (m.role === 'assistant') {
2993
+ const entry = projectPersistedToHistoryEntry(m);
2994
+ if (!entry) continue;
2995
+ if (entry.role === 'user') {
2996
+ sendUnifyOutput({ type: 'user', message: { content: entry.content, id: entry.id || null } }, { groupId: entry.groupId || null });
2997
+ } else if (entry.role === 'assistant') {
2992
2998
  // speakerVpId rides on the envelope so the frontend can route this
2993
2999
  // replayed assistant text to the correct VP track. Without it, the
2994
3000
  // history replay would merge replies from different VPs onto one
2995
3001
  // anonymous assistant turn.
2996
3002
  const envelopeOpts = {
2997
- groupId: m.groupId || null,
3003
+ groupId: entry.groupId || null,
2998
3004
  };
2999
- if (m.speakerVpId) envelopeOpts.vpId = m.speakerVpId;
3005
+ if (entry.speakerVpId) envelopeOpts.vpId = entry.speakerVpId;
3000
3006
  sendUnifyOutput({
3001
3007
  type: 'assistant',
3002
- message: { content: [{ type: 'text', text: m.content }] },
3008
+ message: { id: entry.id || null, content: [{ type: 'text', text: entry.content }] },
3003
3009
  }, envelopeOpts);
3004
3010
  sendUnifyOutput({ type: 'result', result_text: '' }, envelopeOpts);
3005
3011
  }
@@ -3081,22 +3087,19 @@ export async function handleUnifyLoadMoreHistory(msg) {
3081
3087
  result = { messages: [], oldestSeq: null, hasMore: false };
3082
3088
  }
3083
3089
 
3084
- // Wire shape mirrors handleUnifyLoadHistory's projection: only user /
3085
- // assistant text rows. Tool_use / tool_result replay is out of scope
3086
- // for this PR (today's bootstrap path drops them too).
3087
- //
3088
- // We intentionally do NOT ship `id` or `time` over the wire:
3089
- // `handleUnifyHistoryChunk` reads only role / content / groupId. The
3090
- // pagination cursor (`oldestSeq`) is sent once at the envelope level,
3091
- // so per-row ids are dead weight. `time` would be useful if we render
3092
- // "5 days ago" stamps on older history rows — when that ships, add it
3093
- // back here and consume it in conversationHandler.
3090
+ // Wire shape mirrors handleUnifyLoadHistory's projection: only visible
3091
+ // user / assistant text rows. Internal reflection/system-only rows stay
3092
+ // server-side, and stable ids + speaker attribution ride with each row
3093
+ // so older-history prepend renders exactly like refresh replay.
3094
3094
  const projected = (result.messages || [])
3095
+ .map(projectPersistedToHistoryEntry)
3095
3096
  .filter(m => m && (m.role === 'user' || m.role === 'assistant'))
3096
3097
  .map(m => ({
3098
+ ...(m.id ? { id: m.id } : {}),
3097
3099
  role: m.role,
3098
3100
  content: m.content,
3099
3101
  groupId: m.groupId || null,
3102
+ ...(m.speakerVpId ? { speakerVpId: m.speakerVpId } : {}),
3100
3103
  }));
3101
3104
 
3102
3105
  emit({