@yeaft/webchat-agent 0.1.823 → 0.1.825
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/debug-trace.js +58 -8
- package/unify/dream-v2/session-wiring.js +158 -34
- package/unify/web-bridge.js +18 -7
package/package.json
CHANGED
package/unify/debug-trace.js
CHANGED
|
@@ -125,6 +125,38 @@ function truncate(str, max) {
|
|
|
125
125
|
return str.slice(0, max) + '... [truncated]';
|
|
126
126
|
}
|
|
127
127
|
|
|
128
|
+
function truncatedJsonSentinel(originalBytes) {
|
|
129
|
+
return {
|
|
130
|
+
__truncated: true,
|
|
131
|
+
originalBytes,
|
|
132
|
+
maxBytes: MAX_LOOP_PAYLOAD,
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function truncateJsonValue(value) {
|
|
137
|
+
if (value == null) return value;
|
|
138
|
+
if (typeof value === 'string') return truncate(value, MAX_LOOP_PAYLOAD);
|
|
139
|
+
try {
|
|
140
|
+
const s = JSON.stringify(value);
|
|
141
|
+
if (s.length <= MAX_LOOP_PAYLOAD) return value;
|
|
142
|
+
return truncatedJsonSentinel(s.length);
|
|
143
|
+
} catch {
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function boundDreamEventData(eventType, eventData) {
|
|
149
|
+
if (eventType !== 'dream_loop' || !eventData || typeof eventData !== 'object') return eventData;
|
|
150
|
+
return {
|
|
151
|
+
...eventData,
|
|
152
|
+
systemPrompt: truncateJsonValue(eventData.systemPrompt),
|
|
153
|
+
messages: truncateJsonValue(eventData.messages),
|
|
154
|
+
response: truncateJsonValue(eventData.response),
|
|
155
|
+
rawRequest: truncateJsonValue(eventData.rawRequest),
|
|
156
|
+
rawResponse: truncateJsonValue(eventData.rawResponse),
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
|
|
128
160
|
/**
|
|
129
161
|
* DebugTrace — SQLite-backed debug trace.
|
|
130
162
|
*/
|
|
@@ -229,11 +261,7 @@ export class DebugTrace {
|
|
|
229
261
|
try {
|
|
230
262
|
const s = JSON.stringify(v);
|
|
231
263
|
if (s.length <= MAX_LOOP_PAYLOAD) return s;
|
|
232
|
-
return JSON.stringify(
|
|
233
|
-
__truncated: true,
|
|
234
|
-
originalBytes: s.length,
|
|
235
|
-
maxBytes: MAX_LOOP_PAYLOAD,
|
|
236
|
-
});
|
|
264
|
+
return JSON.stringify(truncatedJsonSentinel(s.length));
|
|
237
265
|
} catch { return null; }
|
|
238
266
|
};
|
|
239
267
|
// For raw request/response, accept either a pre-stringified blob
|
|
@@ -304,7 +332,8 @@ export class DebugTrace {
|
|
|
304
332
|
logEvent({ traceId, eventType, eventData = null }) {
|
|
305
333
|
const id = randomUUID();
|
|
306
334
|
const now = Date.now();
|
|
307
|
-
const
|
|
335
|
+
const boundedData = boundDreamEventData(eventType, eventData);
|
|
336
|
+
const data = boundedData != null ? JSON.stringify(boundedData) : null;
|
|
308
337
|
this.#prepare('insertEvent', `
|
|
309
338
|
INSERT INTO trace_events (id, trace_id, event_type, event_data, created_at)
|
|
310
339
|
VALUES (?, ?, ?, ?, ?)
|
|
@@ -312,6 +341,19 @@ export class DebugTrace {
|
|
|
312
341
|
return id;
|
|
313
342
|
}
|
|
314
343
|
|
|
344
|
+
/**
|
|
345
|
+
* Compatibility helper used by older engine/dream call sites.
|
|
346
|
+
* @param {string} eventType
|
|
347
|
+
* @param {unknown} eventData
|
|
348
|
+
* @returns {string}
|
|
349
|
+
*/
|
|
350
|
+
event(eventType, eventData = null) {
|
|
351
|
+
const traceId = (eventData && typeof eventData === 'object' && (eventData.turnId || eventData.runId))
|
|
352
|
+
? String(eventData.turnId || eventData.runId)
|
|
353
|
+
: String(eventType || 'event');
|
|
354
|
+
return this.logEvent({ traceId, eventType, eventData });
|
|
355
|
+
}
|
|
356
|
+
|
|
315
357
|
// ─── Read API ────────────────────────────────────────────────
|
|
316
358
|
|
|
317
359
|
/**
|
|
@@ -477,7 +519,9 @@ export class DebugTrace {
|
|
|
477
519
|
const dreamEvents = [];
|
|
478
520
|
if (dreamLim > 0) {
|
|
479
521
|
const eventRows = this.#db.prepare(`
|
|
480
|
-
SELECT * FROM trace_events
|
|
522
|
+
SELECT * FROM trace_events
|
|
523
|
+
WHERE event_type IN ('dream_progress', 'dream_loop', 'dream_turn_open', 'dream_turn_close', 'dream_run')
|
|
524
|
+
ORDER BY created_at DESC, rowid DESC LIMIT ?
|
|
481
525
|
`).all(Math.max(dreamLim * 5, dreamLim));
|
|
482
526
|
for (const er of eventRows) {
|
|
483
527
|
const data = parseJsonSafe(er.event_data) || {};
|
|
@@ -488,7 +532,12 @@ export class DebugTrace {
|
|
|
488
532
|
const isThisGroup = evtGroupId === groupId || target === `group/${groupId}`;
|
|
489
533
|
if (!isBroadcast && !isThisGroup) continue;
|
|
490
534
|
}
|
|
491
|
-
dreamEvents.push({
|
|
535
|
+
dreamEvents.push({
|
|
536
|
+
type: data.type || (er.event_type === 'dream_progress' ? 'dream_progress' : er.event_type),
|
|
537
|
+
...data,
|
|
538
|
+
at: er.created_at,
|
|
539
|
+
ts: data.ts || data.at || er.created_at,
|
|
540
|
+
});
|
|
492
541
|
if (dreamEvents.length >= dreamLim) break;
|
|
493
542
|
}
|
|
494
543
|
dreamEvents.reverse();
|
|
@@ -638,6 +687,7 @@ export class NullTrace {
|
|
|
638
687
|
endTurn() {}
|
|
639
688
|
logTool() { return 'null'; }
|
|
640
689
|
logEvent() { return 'null'; }
|
|
690
|
+
event() { return 'null'; }
|
|
641
691
|
queryByMessage() { return { turns: [], tools: [], events: [] }; }
|
|
642
692
|
queryByTrace() { return { turns: [], tools: [], events: [] }; }
|
|
643
693
|
queryRecent() { return []; }
|
|
@@ -128,29 +128,128 @@ function makeLlm(session) {
|
|
|
128
128
|
});
|
|
129
129
|
|
|
130
130
|
// Emit complete loop event (request + response) to the debug panel.
|
|
131
|
+
const latencyMs = Date.now() - startedMs;
|
|
132
|
+
const usage = normalizeDreamUsage(r?.usage || {});
|
|
133
|
+
if (!session._dreamMetrics) session._dreamMetrics = createDreamMetrics({ turnId });
|
|
134
|
+
session._dreamMetrics.llmCallCount += 1;
|
|
135
|
+
session._dreamMetrics.inputTokens += usage.inputTokens;
|
|
136
|
+
session._dreamMetrics.outputTokens += usage.outputTokens;
|
|
137
|
+
session._dreamMetrics.totalTokens += usage.totalTokens;
|
|
138
|
+
session._dreamMetrics.byPass[pass] = session._dreamMetrics.byPass[pass] || createDreamPassMetrics();
|
|
139
|
+
session._dreamMetrics.byPass[pass].llmCallCount += 1;
|
|
140
|
+
session._dreamMetrics.byPass[pass].inputTokens += usage.inputTokens;
|
|
141
|
+
session._dreamMetrics.byPass[pass].outputTokens += usage.outputTokens;
|
|
142
|
+
session._dreamMetrics.byPass[pass].totalTokens += usage.totalTokens;
|
|
143
|
+
session._dreamMetrics.byPass[pass].durationMs += latencyMs;
|
|
144
|
+
const loopEvent = stampDreamScope(session, {
|
|
145
|
+
type: 'loop',
|
|
146
|
+
turnId,
|
|
147
|
+
loopNumber,
|
|
148
|
+
pass,
|
|
149
|
+
model: model || 'unknown',
|
|
150
|
+
systemPrompt: effectiveSystem,
|
|
151
|
+
messages: [{ role: 'user', content: prompt }],
|
|
152
|
+
response: typeof r?.text === 'string' ? r.text : '',
|
|
153
|
+
toolCalls: [],
|
|
154
|
+
usage,
|
|
155
|
+
latencyMs,
|
|
156
|
+
ttfbMs: null,
|
|
157
|
+
stopReason: 'end_turn',
|
|
158
|
+
rawRequest: null,
|
|
159
|
+
rawResponse: null,
|
|
160
|
+
});
|
|
161
|
+
persistDreamTrace(session, 'dream_loop', loopEvent);
|
|
131
162
|
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
|
-
});
|
|
163
|
+
session._dreamProgressSink(loopEvent);
|
|
148
164
|
}
|
|
149
165
|
|
|
150
166
|
return (r && r.text) ? r.text : '';
|
|
151
167
|
};
|
|
152
168
|
}
|
|
153
169
|
|
|
170
|
+
|
|
171
|
+
function stampDreamScope(session, evt) {
|
|
172
|
+
if (!evt || typeof evt !== 'object') return evt;
|
|
173
|
+
if (evt.groupId || !session?._dreamActiveGroupId) return evt;
|
|
174
|
+
return { ...evt, groupId: session._dreamActiveGroupId };
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function finiteNumber(v) {
|
|
178
|
+
const n = Number(v);
|
|
179
|
+
return Number.isFinite(n) && n > 0 ? Math.floor(n) : 0;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function normalizeDreamUsage(usage = {}) {
|
|
183
|
+
const inputTokens = finiteNumber(
|
|
184
|
+
usage.inputTokens ?? usage.input_tokens ?? usage.promptTokens ?? usage.prompt_tokens,
|
|
185
|
+
);
|
|
186
|
+
const outputTokens = finiteNumber(
|
|
187
|
+
usage.outputTokens ?? usage.output_tokens ?? usage.completionTokens ?? usage.completion_tokens,
|
|
188
|
+
);
|
|
189
|
+
const explicitTotal = finiteNumber(usage.totalTokens ?? usage.total_tokens);
|
|
190
|
+
return {
|
|
191
|
+
inputTokens,
|
|
192
|
+
outputTokens,
|
|
193
|
+
totalTokens: explicitTotal || inputTokens + outputTokens,
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function createDreamPassMetrics() {
|
|
198
|
+
return { llmCallCount: 0, inputTokens: 0, outputTokens: 0, totalTokens: 0, durationMs: 0 };
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function createDreamMetrics({ turnId, startedAt = Date.now() } = {}) {
|
|
202
|
+
return {
|
|
203
|
+
turnId: turnId || 'dream',
|
|
204
|
+
startedAt,
|
|
205
|
+
durationMs: 0,
|
|
206
|
+
llmCallCount: 0,
|
|
207
|
+
inputTokens: 0,
|
|
208
|
+
outputTokens: 0,
|
|
209
|
+
totalTokens: 0,
|
|
210
|
+
byPass: {},
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function finalizeDreamMetrics(metrics, durationMs) {
|
|
215
|
+
const m = metrics || createDreamMetrics();
|
|
216
|
+
return {
|
|
217
|
+
turnId: m.turnId || 'dream',
|
|
218
|
+
startedAt: m.startedAt || null,
|
|
219
|
+
durationMs: finiteNumber(durationMs),
|
|
220
|
+
llmCallCount: finiteNumber(m.llmCallCount),
|
|
221
|
+
inputTokens: finiteNumber(m.inputTokens),
|
|
222
|
+
outputTokens: finiteNumber(m.outputTokens),
|
|
223
|
+
totalTokens: finiteNumber(m.totalTokens),
|
|
224
|
+
passBreakdown: Object.fromEntries(Object.entries(m.byPass || {}).map(([pass, rec]) => [pass, {
|
|
225
|
+
llmCallCount: finiteNumber(rec.llmCallCount),
|
|
226
|
+
inputTokens: finiteNumber(rec.inputTokens),
|
|
227
|
+
outputTokens: finiteNumber(rec.outputTokens),
|
|
228
|
+
totalTokens: finiteNumber(rec.totalTokens),
|
|
229
|
+
durationMs: finiteNumber(rec.durationMs),
|
|
230
|
+
}])),
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function summarizeDreamResult(result = {}) {
|
|
235
|
+
return {
|
|
236
|
+
groups: Array.isArray(result.groups) ? result.groups.length : 0,
|
|
237
|
+
targets: Array.isArray(result.targets) ? result.targets.length : 0,
|
|
238
|
+
error: result.error || null,
|
|
239
|
+
skipped: !!result.skipped,
|
|
240
|
+
skippedReason: result.skippedReason || null,
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function persistDreamTrace(session, eventType, eventData) {
|
|
245
|
+
try {
|
|
246
|
+
if (typeof session?.trace?.event === 'function') session.trace.event(eventType, eventData);
|
|
247
|
+
else if (typeof session?.trace?.logEvent === 'function') {
|
|
248
|
+
session.trace.logEvent({ traceId: String(eventData?.turnId || eventData?.runId || eventType), eventType, eventData });
|
|
249
|
+
}
|
|
250
|
+
} catch { /* trace must never break dream */ }
|
|
251
|
+
}
|
|
252
|
+
|
|
154
253
|
/**
|
|
155
254
|
* Create a v2 dream scheduler bound to a session and wire its progress
|
|
156
255
|
* events into the engine's sub-agent event sink (web-bridge translates
|
|
@@ -162,20 +261,21 @@ function makeLlm(session) {
|
|
|
162
261
|
export function createV2DreamScheduler(session) {
|
|
163
262
|
const onProgress = (evt) => {
|
|
164
263
|
try {
|
|
264
|
+
const stampedEvt = stampDreamScope(session, evt);
|
|
165
265
|
const sink = session.engine?.subAgentEventSink || null;
|
|
166
266
|
// We don't have a sub-agent id; emit on the engine's standard
|
|
167
267
|
// event channel instead. session.engine exposes setSubAgentEventSink
|
|
168
268
|
// for nested events; for top-level dream, we route through trace.
|
|
169
269
|
if (typeof session.trace?.event === 'function') {
|
|
170
|
-
session.trace.event('dream_progress',
|
|
270
|
+
session.trace.event('dream_progress', stampedEvt);
|
|
171
271
|
}
|
|
172
272
|
if (typeof session._dreamProgressSink === 'function') {
|
|
173
|
-
session._dreamProgressSink(
|
|
273
|
+
session._dreamProgressSink(stampedEvt);
|
|
174
274
|
}
|
|
175
275
|
// Best-effort console for debug builds.
|
|
176
276
|
if (session.config?.debug) {
|
|
177
277
|
// eslint-disable-next-line no-console
|
|
178
|
-
console.log('[dream-v2]',
|
|
278
|
+
console.log('[dream-v2]', stampedEvt);
|
|
179
279
|
}
|
|
180
280
|
} catch { /* never let progress reporting kill the run */ }
|
|
181
281
|
};
|
|
@@ -190,16 +290,19 @@ export function createV2DreamScheduler(session) {
|
|
|
190
290
|
const startedAt = Date.now();
|
|
191
291
|
session._dreamTurnId = turnId;
|
|
192
292
|
session._dreamLoopCounter = 0;
|
|
293
|
+
session._dreamMetrics = createDreamMetrics({ turnId, startedAt });
|
|
193
294
|
|
|
295
|
+
const turnOpen = stampDreamScope(session, {
|
|
296
|
+
type: 'turn_open',
|
|
297
|
+
turnId,
|
|
298
|
+
userPrompt: '[dream] automatic memory consolidation',
|
|
299
|
+
vpId: null,
|
|
300
|
+
groupId: null,
|
|
301
|
+
at: startedAt,
|
|
302
|
+
});
|
|
303
|
+
persistDreamTrace(session, 'dream_turn_open', turnOpen);
|
|
194
304
|
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
|
-
});
|
|
305
|
+
session._dreamProgressSink(turnOpen);
|
|
203
306
|
}
|
|
204
307
|
|
|
205
308
|
return runDream({
|
|
@@ -208,14 +311,35 @@ export function createV2DreamScheduler(session) {
|
|
|
208
311
|
scopeFilter: Array.isArray(opts.scopeFilter) ? opts.scopeFilter : undefined,
|
|
209
312
|
}).then((result) => {
|
|
210
313
|
// Bug 2: emit turn_close when the dream pass completes.
|
|
314
|
+
result.trigger = opts.manual ? 'manual' : 'auto';
|
|
315
|
+
if (session._dreamActiveGroupId && !result.groupId) result.groupId = session._dreamActiveGroupId;
|
|
316
|
+
const metrics = finalizeDreamMetrics(session._dreamMetrics, Date.now() - startedAt);
|
|
317
|
+
result.metrics = metrics;
|
|
318
|
+
result.durationMs = metrics.durationMs;
|
|
319
|
+
result.llmCallCount = metrics.llmCallCount;
|
|
320
|
+
result.inputTokens = metrics.inputTokens;
|
|
321
|
+
result.outputTokens = metrics.outputTokens;
|
|
322
|
+
result.totalTokens = metrics.totalTokens;
|
|
323
|
+
result.passBreakdown = metrics.passBreakdown;
|
|
324
|
+
const turnClose = stampDreamScope(session, {
|
|
325
|
+
type: 'turn_close',
|
|
326
|
+
turnId,
|
|
327
|
+
totalMs: metrics.durationMs,
|
|
328
|
+
totalTokens: metrics.totalTokens,
|
|
329
|
+
loopCount: metrics.llmCallCount,
|
|
330
|
+
metrics,
|
|
331
|
+
});
|
|
332
|
+
persistDreamTrace(session, 'dream_turn_close', turnClose);
|
|
333
|
+
persistDreamTrace(session, 'dream_run', stampDreamScope(session, {
|
|
334
|
+
type: 'dream_run',
|
|
335
|
+
turnId,
|
|
336
|
+
phase: 'result',
|
|
337
|
+
status: result?.error ? 'error' : 'done',
|
|
338
|
+
metrics,
|
|
339
|
+
resultSummary: summarizeDreamResult(result),
|
|
340
|
+
}));
|
|
211
341
|
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
|
-
});
|
|
342
|
+
session._dreamProgressSink(turnClose);
|
|
219
343
|
}
|
|
220
344
|
return result;
|
|
221
345
|
});
|
package/unify/web-bridge.js
CHANGED
|
@@ -3222,6 +3222,13 @@ export function normalizeDreamResult(result) {
|
|
|
3222
3222
|
|
|
3223
3223
|
return {
|
|
3224
3224
|
success,
|
|
3225
|
+
durationMs: Number.isFinite(Number(result?.durationMs)) ? Number(result.durationMs) : 0,
|
|
3226
|
+
llmCallCount: Number.isFinite(Number(result?.llmCallCount)) ? Number(result.llmCallCount) : 0,
|
|
3227
|
+
inputTokens: Number.isFinite(Number(result?.inputTokens)) ? Number(result.inputTokens) : 0,
|
|
3228
|
+
outputTokens: Number.isFinite(Number(result?.outputTokens)) ? Number(result.outputTokens) : 0,
|
|
3229
|
+
totalTokens: Number.isFinite(Number(result?.totalTokens)) ? Number(result.totalTokens) : 0,
|
|
3230
|
+
metrics: result?.metrics || null,
|
|
3231
|
+
passBreakdown: result?.passBreakdown || result?.metrics?.passBreakdown || null,
|
|
3225
3232
|
skipped,
|
|
3226
3233
|
skippedReason,
|
|
3227
3234
|
groupsProcessed,
|
|
@@ -3260,13 +3267,15 @@ export async function handleUnifyDreamTrigger(msg = {}) {
|
|
|
3260
3267
|
// group or different) overlapping the same inflight pass used to set
|
|
3261
3268
|
// the module-level groupId slot, race the sink wrapping, and let the
|
|
3262
3269
|
// second `finally` restore the original sink while the first run was
|
|
3263
|
-
// still emitting events. We now refuse
|
|
3264
|
-
//
|
|
3265
|
-
//
|
|
3266
|
-
//
|
|
3267
|
-
//
|
|
3268
|
-
//
|
|
3269
|
-
|
|
3270
|
+
// still emitting events. We now refuse scoped triggers while ANY dream
|
|
3271
|
+
// pass is already running: a scoped manual click during an unscoped
|
|
3272
|
+
// auto run must not install `_dreamActiveGroupId` or wrap the sink,
|
|
3273
|
+
// otherwise auto-run events can be persisted under the clicked group.
|
|
3274
|
+
// The scheduler also short-circuits the underlying run for same-group,
|
|
3275
|
+
// and a different group's filter would have been silently dropped
|
|
3276
|
+
// anyway (see dream-v2/schedule.js inflight reuse), so the user-facing
|
|
3277
|
+
// semantics are unchanged ("you already asked").
|
|
3278
|
+
if (groupId && (inflightScopedDreamGroups.size > 0 || session.dreamScheduler.isRunning)) {
|
|
3270
3279
|
const skippedResult = {
|
|
3271
3280
|
skipped: true,
|
|
3272
3281
|
skippedReason: 'already-running',
|
|
@@ -3289,6 +3298,7 @@ export async function handleUnifyDreamTrigger(msg = {}) {
|
|
|
3289
3298
|
// OTHER groupIds chain (last-installed wins) but each restoration
|
|
3290
3299
|
// unwinds back to its predecessor.
|
|
3291
3300
|
const originalSink = session?._dreamProgressSink;
|
|
3301
|
+
if (groupId) session._dreamActiveGroupId = groupId;
|
|
3292
3302
|
if (groupId && typeof originalSink === 'function') {
|
|
3293
3303
|
inflightScopedDreamGroups.add(groupId);
|
|
3294
3304
|
session._dreamProgressSink = (evt) => {
|
|
@@ -3345,6 +3355,7 @@ export async function handleUnifyDreamTrigger(msg = {}) {
|
|
|
3345
3355
|
});
|
|
3346
3356
|
} finally {
|
|
3347
3357
|
// Restore the original sink and release the per-group inflight lock.
|
|
3358
|
+
if (groupId && session?._dreamActiveGroupId === groupId) session._dreamActiveGroupId = null;
|
|
3348
3359
|
if (groupId && typeof originalSink === 'function') {
|
|
3349
3360
|
session._dreamProgressSink = originalSink;
|
|
3350
3361
|
inflightScopedDreamGroups.delete(groupId);
|