@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 +1 -1
- package/unify/conversation/persist.js +11 -0
- package/unify/engine.js +1 -1
- package/unify/tool-folding/index.js +15 -1
- package/unify/web-bridge.js +19 -16
package/package.json
CHANGED
|
@@ -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
|
-
// (
|
|
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 =
|
|
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';
|
package/unify/web-bridge.js
CHANGED
|
@@ -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
|
-
|
|
2990
|
-
|
|
2991
|
-
|
|
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:
|
|
3003
|
+
groupId: entry.groupId || null,
|
|
2998
3004
|
};
|
|
2999
|
-
if (
|
|
3005
|
+
if (entry.speakerVpId) envelopeOpts.vpId = entry.speakerVpId;
|
|
3000
3006
|
sendUnifyOutput({
|
|
3001
3007
|
type: 'assistant',
|
|
3002
|
-
message: { content: [{ type: 'text', text:
|
|
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
|
|
3085
|
-
// assistant text rows.
|
|
3086
|
-
//
|
|
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({
|