@yeaft/webchat-agent 0.1.817 → 0.1.821
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 +12 -0
- package/unify/debug-trace.js +28 -5
- package/unify/dream-v2/session-wiring.js +72 -8
- package/unify/web-bridge.js +98 -14
package/package.json
CHANGED
|
@@ -107,6 +107,12 @@ 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
|
+
if (Array.isArray(msg.attachments) && msg.attachments.length > 0) {
|
|
111
|
+
try {
|
|
112
|
+
const b64 = Buffer.from(JSON.stringify(msg.attachments)).toString('base64');
|
|
113
|
+
fm.push(`attachmentsB64: ${b64}`);
|
|
114
|
+
} catch { /* best-effort: attachments are UI metadata, not engine-critical */ }
|
|
115
|
+
}
|
|
110
116
|
// Internal/synthetic rows must round-trip so refresh/history replay can
|
|
111
117
|
// keep them out of the user-visible conversation. Reflection folding uses
|
|
112
118
|
// `_reflection`; other engine-only rows may use one of the explicit flags.
|
|
@@ -213,6 +219,12 @@ export function parseMessage(raw) {
|
|
|
213
219
|
case 'sourceThreadId': msg.sourceThreadId = value; break;
|
|
214
220
|
case 'groupId': msg.groupId = value; break;
|
|
215
221
|
case 'speakerVpId': msg.speakerVpId = value; break;
|
|
222
|
+
case 'attachmentsB64':
|
|
223
|
+
try {
|
|
224
|
+
const parsed = JSON.parse(Buffer.from(value, 'base64').toString('utf8'));
|
|
225
|
+
if (Array.isArray(parsed)) msg.attachments = parsed;
|
|
226
|
+
} catch { /* best-effort: ignore malformed attachment metadata */ }
|
|
227
|
+
break;
|
|
216
228
|
case '_reflection': msg._reflection = value === 'true'; break;
|
|
217
229
|
case 'internal': msg.internal = value === 'true'; break;
|
|
218
230
|
case 'systemOnly': msg.systemOnly = value === 'true'; break;
|
package/unify/debug-trace.js
CHANGED
|
@@ -364,11 +364,14 @@ export class DebugTrace {
|
|
|
364
364
|
* fields the panel expects. JSON columns are parsed; truncated /
|
|
365
365
|
* malformed payloads degrade to null instead of failing the call.
|
|
366
366
|
*
|
|
367
|
-
* @param {{ limit?: number, groupId?: string|null, threadId?: string|null }} [opts]
|
|
368
|
-
* @returns {{ loops: object[], turns: object[] }}
|
|
367
|
+
* @param {{ limit?: number, dreamLimit?: number, groupId?: string|null, threadId?: string|null }} [opts]
|
|
368
|
+
* @returns {{ loops: object[], turns: object[], dreamEvents: object[] }}
|
|
369
369
|
*/
|
|
370
|
-
fetchRecentDebugHistory({ limit = 100, groupId = null, threadId = null } = {}) {
|
|
370
|
+
fetchRecentDebugHistory({ limit = 100, dreamLimit = 5, groupId = null, threadId = null } = {}) {
|
|
371
371
|
const lim = Math.max(1, Math.min(500, Number(limit) || 100));
|
|
372
|
+
const dreamLim = Number.isFinite(Number(dreamLimit))
|
|
373
|
+
? Math.max(0, Math.min(50, Number(dreamLimit)))
|
|
374
|
+
: 5;
|
|
372
375
|
const where = [];
|
|
373
376
|
const args = [];
|
|
374
377
|
if (groupId) { where.push('group_id = ?'); args.push(groupId); }
|
|
@@ -471,10 +474,30 @@ export class DebugTrace {
|
|
|
471
474
|
isError: !!tool.is_error,
|
|
472
475
|
});
|
|
473
476
|
}
|
|
477
|
+
const dreamEvents = [];
|
|
478
|
+
if (dreamLim > 0) {
|
|
479
|
+
const eventRows = this.#db.prepare(`
|
|
480
|
+
SELECT * FROM trace_events WHERE event_type = 'dream_progress' ORDER BY created_at DESC LIMIT ?
|
|
481
|
+
`).all(Math.max(dreamLim * 5, dreamLim));
|
|
482
|
+
for (const er of eventRows) {
|
|
483
|
+
const data = parseJsonSafe(er.event_data) || {};
|
|
484
|
+
const evtGroupId = typeof data.groupId === 'string' && data.groupId ? data.groupId : null;
|
|
485
|
+
const target = typeof data.target === 'string' ? data.target : '';
|
|
486
|
+
if (groupId) {
|
|
487
|
+
const isBroadcast = !evtGroupId && !target;
|
|
488
|
+
const isThisGroup = evtGroupId === groupId || target === `group/${groupId}`;
|
|
489
|
+
if (!isBroadcast && !isThisGroup) continue;
|
|
490
|
+
}
|
|
491
|
+
dreamEvents.push({ type: 'dream_progress', ...data, at: er.created_at, ts: data.ts || er.created_at });
|
|
492
|
+
if (dreamEvents.length >= dreamLim) break;
|
|
493
|
+
}
|
|
494
|
+
dreamEvents.reverse();
|
|
495
|
+
}
|
|
496
|
+
|
|
474
497
|
// Reverse to oldest-first so the panel's existing append-driven UI
|
|
475
498
|
// renders in chronological order on hydration.
|
|
476
499
|
loops.reverse();
|
|
477
|
-
return { loops, turns: Array.from(turnsById.values()) };
|
|
500
|
+
return { loops, turns: Array.from(turnsById.values()), dreamEvents };
|
|
478
501
|
}
|
|
479
502
|
|
|
480
503
|
/**
|
|
@@ -624,7 +647,7 @@ export class NullTrace {
|
|
|
624
647
|
cleanup() { return { deletedTurns: 0, deletedTools: 0, deletedEvents: 0 }; }
|
|
625
648
|
purge() {}
|
|
626
649
|
close() {}
|
|
627
|
-
fetchRecentDebugHistory() { return { loops: [], turns: [] }; }
|
|
650
|
+
fetchRecentDebugHistory() { return { loops: [], turns: [], dreamEvents: [] }; }
|
|
628
651
|
}
|
|
629
652
|
|
|
630
653
|
/**
|
|
@@ -110,14 +110,43 @@ function makeLlm(session) {
|
|
|
110
110
|
if (!adapter || typeof adapter.call !== 'function') {
|
|
111
111
|
throw new Error(`dream-v2: no adapter.call available (pass=${pass})`);
|
|
112
112
|
}
|
|
113
|
+
|
|
114
|
+
// Bug 2: emit loop event so the debug panel shows dream LLM API calls.
|
|
115
|
+
const startedMs = Date.now();
|
|
116
|
+
session._dreamLoopCounter = (session._dreamLoopCounter || 0) + 1;
|
|
117
|
+
const loopNumber = session._dreamLoopCounter;
|
|
118
|
+
const turnId = session._dreamTurnId || 'dream';
|
|
119
|
+
const effectiveSystem = system || (String(session.config?.language || '').toLowerCase().startsWith('zh')
|
|
120
|
+
? `你是梦境流水线 — pass: ${pass}。请用中文生成自然语言内容;JSON key 保持英文。`
|
|
121
|
+
: `You are the dream pipeline — pass: ${pass}.`);
|
|
122
|
+
|
|
113
123
|
const r = await adapter.call({
|
|
114
124
|
model,
|
|
115
|
-
system:
|
|
116
|
-
? `你是梦境流水线 — pass: ${pass}。请用中文生成自然语言内容;JSON key 保持英文。`
|
|
117
|
-
: `You are the dream pipeline — pass: ${pass}.`),
|
|
125
|
+
system: effectiveSystem,
|
|
118
126
|
messages: [{ role: 'user', content: prompt }],
|
|
119
127
|
maxTokens: 2048,
|
|
120
128
|
});
|
|
129
|
+
|
|
130
|
+
// Emit complete loop event (request + response) to the debug panel.
|
|
131
|
+
if (typeof session._dreamProgressSink === 'function') {
|
|
132
|
+
session._dreamProgressSink({
|
|
133
|
+
type: 'loop',
|
|
134
|
+
turnId,
|
|
135
|
+
loopNumber,
|
|
136
|
+
model: model || 'unknown',
|
|
137
|
+
systemPrompt: effectiveSystem,
|
|
138
|
+
messages: [{ role: 'user', content: prompt }],
|
|
139
|
+
response: typeof r?.text === 'string' ? r.text : '',
|
|
140
|
+
toolCalls: [],
|
|
141
|
+
usage: r?.usage || { inputTokens: 0, outputTokens: 0, totalTokens: 0 },
|
|
142
|
+
latencyMs: Date.now() - startedMs,
|
|
143
|
+
ttfbMs: null,
|
|
144
|
+
stopReason: 'end_turn',
|
|
145
|
+
rawRequest: null,
|
|
146
|
+
rawResponse: null,
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
|
|
121
150
|
return (r && r.text) ? r.text : '';
|
|
122
151
|
};
|
|
123
152
|
}
|
|
@@ -151,11 +180,46 @@ export function createV2DreamScheduler(session) {
|
|
|
151
180
|
} catch { /* never let progress reporting kill the run */ }
|
|
152
181
|
};
|
|
153
182
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
183
|
+
let dreamTurnCounter = 0;
|
|
184
|
+
|
|
185
|
+
const run = (opts = {}) => {
|
|
186
|
+
// Bug 2: emit turn_open before the dream pass so the debug panel
|
|
187
|
+
// can group all LLM loop events under a single dream "turn".
|
|
188
|
+
dreamTurnCounter += 1;
|
|
189
|
+
const turnId = `dream-${dreamTurnCounter}-${Date.now()}`;
|
|
190
|
+
const startedAt = Date.now();
|
|
191
|
+
session._dreamTurnId = turnId;
|
|
192
|
+
session._dreamLoopCounter = 0;
|
|
193
|
+
|
|
194
|
+
if (typeof session._dreamProgressSink === 'function') {
|
|
195
|
+
session._dreamProgressSink({
|
|
196
|
+
type: 'turn_open',
|
|
197
|
+
turnId,
|
|
198
|
+
userPrompt: '[dream] automatic memory consolidation',
|
|
199
|
+
vpId: null,
|
|
200
|
+
groupId: null,
|
|
201
|
+
at: startedAt,
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return runDream({
|
|
206
|
+
...buildRunDreamOpts(session, onProgress),
|
|
207
|
+
manual: !!opts.manual,
|
|
208
|
+
scopeFilter: Array.isArray(opts.scopeFilter) ? opts.scopeFilter : undefined,
|
|
209
|
+
}).then((result) => {
|
|
210
|
+
// Bug 2: emit turn_close when the dream pass completes.
|
|
211
|
+
if (typeof session._dreamProgressSink === 'function') {
|
|
212
|
+
session._dreamProgressSink({
|
|
213
|
+
type: 'turn_close',
|
|
214
|
+
turnId,
|
|
215
|
+
totalMs: Date.now() - startedAt,
|
|
216
|
+
totalTokens: 0,
|
|
217
|
+
loopCount: session._dreamLoopCounter || 0,
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
return result;
|
|
221
|
+
});
|
|
222
|
+
};
|
|
159
223
|
|
|
160
224
|
const v2 = createDreamScheduler({
|
|
161
225
|
run,
|
package/unify/web-bridge.js
CHANGED
|
@@ -497,6 +497,56 @@ function makeGroupContextStub() {
|
|
|
497
497
|
};
|
|
498
498
|
}
|
|
499
499
|
|
|
500
|
+
/**
|
|
501
|
+
* Parse persisted content that may have been stringified from provider content
|
|
502
|
+
* blocks, then return only user-visible text. Image/file binary blocks are UI
|
|
503
|
+
* metadata and must never be rendered as bubble text; attachment chips ride in
|
|
504
|
+
* `attachments` instead.
|
|
505
|
+
*
|
|
506
|
+
* @param {unknown} content
|
|
507
|
+
* @returns {string}
|
|
508
|
+
*/
|
|
509
|
+
export function __testNormalizePersistedVisibleContent(content) {
|
|
510
|
+
let value = content;
|
|
511
|
+
if (typeof value === 'string') {
|
|
512
|
+
const trimmed = value.trim();
|
|
513
|
+
if ((trimmed.startsWith('[') && trimmed.endsWith(']')) || (trimmed.startsWith('{') && trimmed.endsWith('}'))) {
|
|
514
|
+
try { value = JSON.parse(trimmed); } catch { value = content; }
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
if (Array.isArray(value)) {
|
|
519
|
+
return value
|
|
520
|
+
.map((part) => {
|
|
521
|
+
if (typeof part === 'string') return part;
|
|
522
|
+
if (!part || typeof part !== 'object') return '';
|
|
523
|
+
if (part.type === 'text' && typeof part.text === 'string') return part.text;
|
|
524
|
+
if (part.type === 'input_text' && typeof part.text === 'string') return part.text;
|
|
525
|
+
return '';
|
|
526
|
+
})
|
|
527
|
+
.join('')
|
|
528
|
+
.replace(/\n\n\[Uploaded files\][\s\S]*$/m, '')
|
|
529
|
+
.trim();
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
if (value && typeof value === 'object') {
|
|
533
|
+
if (typeof value.text === 'string') return value.text.trim();
|
|
534
|
+
if (typeof value.content === 'string') return value.content.trim();
|
|
535
|
+
return '';
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
return typeof value === 'string'
|
|
539
|
+
? value.replace(/\n\n\[Uploaded files\][\s\S]*$/m, '').trim()
|
|
540
|
+
: '';
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
function isPersistedInternalMessage(m) {
|
|
544
|
+
if (!m) return true;
|
|
545
|
+
if (m._reflection || m.internal || m.systemOnly || m.systemOnlyMessage) return true;
|
|
546
|
+
if (m.kind === 'compact_summary' || m._compactSummary) return true;
|
|
547
|
+
return false;
|
|
548
|
+
}
|
|
549
|
+
|
|
500
550
|
/**
|
|
501
551
|
* Project a persisted message record into the in-memory history shape.
|
|
502
552
|
* Accepts `role:'tool'` and preserves `toolCalls`/`toolCallId` so the
|
|
@@ -509,8 +559,8 @@ function makeGroupContextStub() {
|
|
|
509
559
|
function projectPersistedToHistoryEntry(m) {
|
|
510
560
|
if (!m) return null;
|
|
511
561
|
if (m.role !== 'user' && m.role !== 'assistant' && m.role !== 'tool') return null;
|
|
512
|
-
if (m
|
|
513
|
-
const entry = { role: m.role, content: m.content };
|
|
562
|
+
if (isPersistedInternalMessage(m)) return null;
|
|
563
|
+
const entry = { role: m.role, content: m.role === 'tool' ? m.content : __testNormalizePersistedVisibleContent(m.content) };
|
|
514
564
|
if (m.id) entry.id = m.id;
|
|
515
565
|
if (m.groupId) entry.groupId = m.groupId;
|
|
516
566
|
if (m.speakerVpId) entry.speakerVpId = m.speakerVpId;
|
|
@@ -523,6 +573,9 @@ function projectPersistedToHistoryEntry(m) {
|
|
|
523
573
|
}));
|
|
524
574
|
}
|
|
525
575
|
if (m.isError) entry.isError = true;
|
|
576
|
+
if (m.ts) entry.ts = m.ts;
|
|
577
|
+
if (Array.isArray(m.attachments) && m.attachments.length > 0) entry.attachments = m.attachments;
|
|
578
|
+
if ((entry.role === 'user' || entry.role === 'assistant') && !entry.content && !entry.attachments) return null;
|
|
526
579
|
return entry;
|
|
527
580
|
}
|
|
528
581
|
|
|
@@ -902,6 +955,7 @@ async function routeEnvelopeToVpThread(groupId, vpId, envelope) {
|
|
|
902
955
|
threadId: thread.threadId,
|
|
903
956
|
role: envelope?.msg?.meta?.injectedBy === 'route_forward' ? 'assistant' : 'user',
|
|
904
957
|
speakerVpId: envelope?.msg?.meta?.senderVpId || envelope?.msg?.from || null,
|
|
958
|
+
attachments: Array.isArray(envelope?.msg?.meta?.attachments) ? envelope.msg.meta.attachments : [],
|
|
905
959
|
});
|
|
906
960
|
thread.updatedAt = Date.now();
|
|
907
961
|
try {
|
|
@@ -987,6 +1041,7 @@ function ensureDriverRunning(groupId, vpId, threadId = 'main') {
|
|
|
987
1041
|
threadId: thread.threadId,
|
|
988
1042
|
role: isForward ? 'assistant' : 'user',
|
|
989
1043
|
speakerVpId: senderVpId,
|
|
1044
|
+
attachments: Array.isArray(meta.attachments) ? meta.attachments : [],
|
|
990
1045
|
});
|
|
991
1046
|
}
|
|
992
1047
|
} catch { /* never crash WS pipeline */ }
|
|
@@ -1597,11 +1652,19 @@ export function installUnifyRuntimeBridge(s) {
|
|
|
1597
1652
|
// `handleUnifyDreamTrigger` wraps THIS sink for the lifetime of the
|
|
1598
1653
|
// trigger to inject `groupId` per-call (see that function below). The
|
|
1599
1654
|
// base sink is intentionally a pure passthrough.
|
|
1655
|
+
//
|
|
1656
|
+
// Bug 2: also forward turn_open / turn_close / loop events emitted by
|
|
1657
|
+
// the dream pipeline so the debug panel shows dream LLM API calls.
|
|
1600
1658
|
s._dreamProgressSink = (evt) => {
|
|
1601
1659
|
try {
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1660
|
+
if (evt.type === 'turn_open' || evt.type === 'turn_close' || evt.type === 'loop') {
|
|
1661
|
+
const tag = evt && evt.groupId ? { groupId: evt.groupId } : {};
|
|
1662
|
+
sendUnifyEvent(evt, tag);
|
|
1663
|
+
} else {
|
|
1664
|
+
const out = { type: 'dream_progress', ...evt };
|
|
1665
|
+
const tag = evt && evt.groupId ? { groupId: evt.groupId } : {};
|
|
1666
|
+
sendUnifyEvent(out, tag);
|
|
1667
|
+
}
|
|
1605
1668
|
} catch { /* never let event delivery throw */ }
|
|
1606
1669
|
};
|
|
1607
1670
|
|
|
@@ -2440,6 +2503,7 @@ async function ensureSessionLoaded() {
|
|
|
2440
2503
|
skills: session.status.skills,
|
|
2441
2504
|
mcpServers: session.status.mcpServers,
|
|
2442
2505
|
tools: session.status.tools,
|
|
2506
|
+
yeaftDir: ctx.CONFIG?.yeaftDir || null,
|
|
2443
2507
|
});
|
|
2444
2508
|
sendGroupSnapshotBroadcast();
|
|
2445
2509
|
// vp-status: rebuild frontend status table from authoritative agent
|
|
@@ -2848,17 +2912,17 @@ function appendTurnToGroupHistory(groupId, threadId, prompts, assistantTextParts
|
|
|
2848
2912
|
* Best-effort: a write failure does NOT abort the turn — engines can
|
|
2849
2913
|
* still run, and the next user message will trigger another append.
|
|
2850
2914
|
*
|
|
2851
|
-
* Note: we mirror `engine.#persistMessages`'s
|
|
2852
|
-
*
|
|
2853
|
-
*
|
|
2854
|
-
*
|
|
2855
|
-
*
|
|
2915
|
+
* Note: we mirror `engine.#persistMessages`'s core user-row fields
|
|
2916
|
+
* (role/content/threadId/groupId) so existing parsers keep working.
|
|
2917
|
+
* Attachment UI metadata is persisted separately (without base64) so
|
|
2918
|
+
* refresh replay can render chips without leaking image source data into
|
|
2919
|
+
* the message body.
|
|
2856
2920
|
*
|
|
2857
|
-
* @param {{ msgId:string, text:string, groupId:string, role?:string, speakerVpId?:string|null }} args
|
|
2921
|
+
* @param {{ msgId:string, text:string, groupId:string, role?:string, speakerVpId?:string|null, attachments?:Array<object> }} args
|
|
2858
2922
|
* @returns {boolean} true if this call wrote the row, false if a prior
|
|
2859
2923
|
* call already wrote it (dedup hit).
|
|
2860
2924
|
*/
|
|
2861
|
-
function persistInboundMessageOnceByMsgId({ msgId, text, groupId, threadId = 'main', role, speakerVpId }) {
|
|
2925
|
+
function persistInboundMessageOnceByMsgId({ msgId, text, groupId, threadId = 'main', role, speakerVpId, attachments }) {
|
|
2862
2926
|
if (!session?.conversationStore) return false;
|
|
2863
2927
|
// No msgId means no dedup key — caller is responsible for guarding.
|
|
2864
2928
|
// Both call sites already do (`if (envMsgId && text)` and
|
|
@@ -2909,6 +2973,9 @@ function persistInboundMessageOnceByMsgId({ msgId, text, groupId, threadId = 'ma
|
|
|
2909
2973
|
if (persistRole === 'assistant' && speakerVpId && typeof speakerVpId === 'string') {
|
|
2910
2974
|
record.speakerVpId = speakerVpId;
|
|
2911
2975
|
}
|
|
2976
|
+
if (persistRole === 'user' && Array.isArray(attachments) && attachments.length > 0) {
|
|
2977
|
+
record.attachments = attachments;
|
|
2978
|
+
}
|
|
2912
2979
|
session.conversationStore.append(record);
|
|
2913
2980
|
return true;
|
|
2914
2981
|
} catch (err) {
|
|
@@ -3348,21 +3415,25 @@ export async function handleUnifyFetchToolStats(_msg = {}) {
|
|
|
3348
3415
|
*/
|
|
3349
3416
|
export async function handleUnifyFetchDebugHistory(msg = {}) {
|
|
3350
3417
|
const limit = Number.isFinite(msg?.limit) ? Number(msg.limit) : 100;
|
|
3418
|
+
const dreamLimit = Number.isFinite(msg?.dreamLimit) ? Number(msg.dreamLimit) : 5;
|
|
3351
3419
|
const groupId = typeof msg?.groupId === 'string' && msg.groupId ? msg.groupId : null;
|
|
3352
3420
|
const threadId = typeof msg?.threadId === 'string' && msg.threadId ? msg.threadId : null;
|
|
3353
3421
|
let loops = [];
|
|
3354
3422
|
let turns = [];
|
|
3423
|
+
let dreamEvents = [];
|
|
3355
3424
|
try {
|
|
3356
3425
|
if (session?.trace && typeof session.trace.fetchRecentDebugHistory === 'function') {
|
|
3357
|
-
const out = session.trace.fetchRecentDebugHistory({ limit, groupId, threadId });
|
|
3426
|
+
const out = session.trace.fetchRecentDebugHistory({ limit, dreamLimit, groupId, threadId });
|
|
3358
3427
|
loops = Array.isArray(out?.loops) ? out.loops : [];
|
|
3359
3428
|
turns = Array.isArray(out?.turns) ? out.turns : [];
|
|
3429
|
+
dreamEvents = Array.isArray(out?.dreamEvents) ? out.dreamEvents : [];
|
|
3360
3430
|
}
|
|
3361
3431
|
} catch (err) {
|
|
3362
3432
|
sendToServer({
|
|
3363
3433
|
type: 'unify_debug_history',
|
|
3364
3434
|
loops: [],
|
|
3365
3435
|
turns: [],
|
|
3436
|
+
dreamEvents: [],
|
|
3366
3437
|
error: err && err.message ? err.message : String(err),
|
|
3367
3438
|
});
|
|
3368
3439
|
return;
|
|
@@ -3371,6 +3442,7 @@ export async function handleUnifyFetchDebugHistory(msg = {}) {
|
|
|
3371
3442
|
type: 'unify_debug_history',
|
|
3372
3443
|
loops,
|
|
3373
3444
|
turns,
|
|
3445
|
+
dreamEvents,
|
|
3374
3446
|
groupId,
|
|
3375
3447
|
threadId,
|
|
3376
3448
|
});
|
|
@@ -3454,6 +3526,7 @@ export async function handleUnifyLoadHistory(msg) {
|
|
|
3454
3526
|
skills: session.status.skills,
|
|
3455
3527
|
mcpServers: session.status.mcpServers,
|
|
3456
3528
|
tools: session.status.tools,
|
|
3529
|
+
yeaftDir: ctx.CONFIG?.yeaftDir || null,
|
|
3457
3530
|
});
|
|
3458
3531
|
sendGroupSnapshotBroadcast();
|
|
3459
3532
|
// vp-status: replay the authoritative table on reconnect so a refreshed
|
|
@@ -3483,7 +3556,15 @@ export async function handleUnifyLoadHistory(msg) {
|
|
|
3483
3556
|
|
|
3484
3557
|
for (const entry of replayEntries) {
|
|
3485
3558
|
if (entry.role === 'user') {
|
|
3486
|
-
sendUnifyOutput({
|
|
3559
|
+
sendUnifyOutput({
|
|
3560
|
+
type: 'user',
|
|
3561
|
+
message: {
|
|
3562
|
+
content: entry.content,
|
|
3563
|
+
id: entry.id || null,
|
|
3564
|
+
...(Array.isArray(entry.attachments) && entry.attachments.length > 0 ? { attachments: entry.attachments } : {}),
|
|
3565
|
+
},
|
|
3566
|
+
ts: entry.ts || null,
|
|
3567
|
+
}, { groupId: entry.groupId || null });
|
|
3487
3568
|
} else if (entry.role === 'assistant') {
|
|
3488
3569
|
// speakerVpId rides on the envelope so the frontend can route this
|
|
3489
3570
|
// replayed assistant text to the correct VP track. Without it, the
|
|
@@ -3496,6 +3577,7 @@ export async function handleUnifyLoadHistory(msg) {
|
|
|
3496
3577
|
sendUnifyOutput({
|
|
3497
3578
|
type: 'assistant',
|
|
3498
3579
|
message: { id: entry.id || null, content: [{ type: 'text', text: entry.content }] },
|
|
3580
|
+
ts: entry.ts || null,
|
|
3499
3581
|
}, envelopeOpts);
|
|
3500
3582
|
sendUnifyOutput({ type: 'result', result_text: '' }, envelopeOpts);
|
|
3501
3583
|
}
|
|
@@ -3573,6 +3655,7 @@ export async function handleUnifyLoadMoreHistory(msg) {
|
|
|
3573
3655
|
role: m.role,
|
|
3574
3656
|
content: m.content,
|
|
3575
3657
|
groupId: m.groupId || null,
|
|
3658
|
+
...(Array.isArray(m.attachments) && m.attachments.length > 0 ? { attachments: m.attachments } : {}),
|
|
3576
3659
|
...(m.speakerVpId ? { speakerVpId: m.speakerVpId } : {}),
|
|
3577
3660
|
}));
|
|
3578
3661
|
|
|
@@ -3661,6 +3744,7 @@ export async function resetUnifySession() {
|
|
|
3661
3744
|
skills: session.status.skills,
|
|
3662
3745
|
mcpServers: session.status.mcpServers,
|
|
3663
3746
|
tools: session.status.tools,
|
|
3747
|
+
yeaftDir: ctx.CONFIG?.yeaftDir || null,
|
|
3664
3748
|
});
|
|
3665
3749
|
// vp-status: after a forced reset the broker table is still live in
|
|
3666
3750
|
// memory; broadcast so the frontend can rebuild its mirror without
|