@yeaft/webchat-agent 0.1.486 → 0.1.488
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/cli.js +5 -28
- package/unify/engine.js +8 -20
- package/unify/eval/cases/tool-use.js +1 -22
- package/unify/pipeline/dispatcher.js +373 -0
- package/unify/session.js +21 -0
- package/unify/skills.js +15 -9
- package/unify/tools/agent.js +0 -1
- package/unify/tools/apply-patch.js +0 -1
- package/unify/tools/ask-user.js +0 -1
- package/unify/tools/bash.js +0 -1
- package/unify/tools/close-agent.js +0 -1
- package/unify/tools/enter-worktree.js +0 -1
- package/unify/tools/exit-worktree.js +0 -1
- package/unify/tools/file-edit.js +0 -1
- package/unify/tools/file-read.js +0 -1
- package/unify/tools/file-write.js +0 -1
- package/unify/tools/glob.js +0 -1
- package/unify/tools/grep.js +0 -1
- package/unify/tools/history-search.js +0 -1
- package/unify/tools/image-generation.js +0 -1
- package/unify/tools/js-repl.js +0 -2
- package/unify/tools/list-agents.js +0 -1
- package/unify/tools/list-dir.js +0 -1
- package/unify/tools/mcp-tools.js +0 -2
- package/unify/tools/memory-query.js +0 -1
- package/unify/tools/memory-read.js +0 -1
- package/unify/tools/memory-search.js +0 -1
- package/unify/tools/memory-write.js +0 -1
- package/unify/tools/notebook-edit.js +0 -1
- package/unify/tools/request-permissions.js +0 -1
- package/unify/tools/send-message.js +0 -1
- package/unify/tools/skill.js +0 -1
- package/unify/tools/task-tools.js +0 -8
- package/unify/tools/thread-tools.js +0 -7
- package/unify/tools/tool-search.js +47 -56
- package/unify/tools/types.js +3 -6
- package/unify/tools/view-image.js +0 -1
- package/unify/tools/wait-agent.js +0 -1
- package/unify/tools/web-fetch.js +0 -1
- package/unify/tools/web-search.js +0 -1
- package/unify/tools/write-stdin.js +0 -1
- package/unify/web-bridge.js +281 -181
package/unify/tools/web-fetch.js
CHANGED
package/unify/web-bridge.js
CHANGED
|
@@ -116,6 +116,254 @@ const THREAD_MUTATING_TOOLS = new Set([
|
|
|
116
116
|
'AttachThreadToTask',
|
|
117
117
|
]);
|
|
118
118
|
|
|
119
|
+
/**
|
|
120
|
+
* task-310: parse a leading `@thread-<id>` or `@thread-<name>` marker on
|
|
121
|
+
* the user's input and return it as a dispatcher override. The marker
|
|
122
|
+
* itself is STRIPPED from the prompt before it reaches the engine —
|
|
123
|
+
* users don't want to see `@thread-foo` echoed back into their
|
|
124
|
+
* conversation.
|
|
125
|
+
*
|
|
126
|
+
* Thread IDs are `main` or `thr-<8 hex>`, so the match captures the id
|
|
127
|
+
* name AFTER the literal `@thread-`. The returned `override.threadId`
|
|
128
|
+
* is the fully-qualified thread id (e.g. `thread-main`, `thread-thr-abcd1234`).
|
|
129
|
+
*
|
|
130
|
+
* Returns { prompt, override? } where override = { threadId } if matched.
|
|
131
|
+
*/
|
|
132
|
+
export function parseThreadPrefix(text) {
|
|
133
|
+
if (!text || typeof text !== 'string') return { prompt: text || '', override: null };
|
|
134
|
+
// Capture the id portion after the literal `@thread-` prefix.
|
|
135
|
+
const m = text.match(/^\s*@thread-([A-Za-z0-9_-]+)\b\s*/);
|
|
136
|
+
if (!m) return { prompt: text, override: null };
|
|
137
|
+
const rest = text.slice(m[0].length);
|
|
138
|
+
// The captured id may already include a `thr-` sub-prefix (for non-main
|
|
139
|
+
// threads). For the canonical `main` thread, the override is the bare
|
|
140
|
+
// string `main`; for `thr-xxxxxxxx` threads, pass through verbatim.
|
|
141
|
+
const threadId = m[1];
|
|
142
|
+
return { prompt: rest || text, override: { threadId } };
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Translate a pipeline event (from Dispatcher) into web-bridge outputs.
|
|
147
|
+
* Pipeline events are distinct from engine events — they carry queue /
|
|
148
|
+
* routing state for the UI. Engine events are unwrapped and forwarded
|
|
149
|
+
* through the existing sendUnifyOutput / sendUnifyEvent path.
|
|
150
|
+
*
|
|
151
|
+
* Returns whether the pipeline is complete (terminal error / no more).
|
|
152
|
+
*/
|
|
153
|
+
function forwardPipelineEvent(ev, ctx) {
|
|
154
|
+
if (!ev || typeof ev !== 'object') return false;
|
|
155
|
+
switch (ev.type) {
|
|
156
|
+
case 'input_queue_updated':
|
|
157
|
+
sendUnifyEvent({
|
|
158
|
+
type: 'input_queue_updated',
|
|
159
|
+
total: ev.total,
|
|
160
|
+
pending: ev.pending,
|
|
161
|
+
routing: ev.routing,
|
|
162
|
+
dispatched: ev.dispatched,
|
|
163
|
+
head: ev.head,
|
|
164
|
+
});
|
|
165
|
+
return false;
|
|
166
|
+
case 'routing_decision':
|
|
167
|
+
sendUnifyEvent({
|
|
168
|
+
type: 'routing_decision',
|
|
169
|
+
entryId: ev.entryId,
|
|
170
|
+
action: ev.action,
|
|
171
|
+
targetThreadId: ev.targetThreadId,
|
|
172
|
+
source: ev.source,
|
|
173
|
+
reason: ev.reason,
|
|
174
|
+
});
|
|
175
|
+
return false;
|
|
176
|
+
case 'thread_list_updated':
|
|
177
|
+
// Dispatcher built it already; just forward.
|
|
178
|
+
sendUnifyEvent({
|
|
179
|
+
type: 'thread_list_updated',
|
|
180
|
+
threads: ev.threads,
|
|
181
|
+
currentThreadId: ev.currentThreadId,
|
|
182
|
+
});
|
|
183
|
+
return false;
|
|
184
|
+
case 'engine_event':
|
|
185
|
+
ctx.onEngineEvent(ev.event, ev.threadId);
|
|
186
|
+
return false;
|
|
187
|
+
case 'error':
|
|
188
|
+
ctx.onError(ev.error);
|
|
189
|
+
return true;
|
|
190
|
+
default:
|
|
191
|
+
return false;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Handle a single engine event unwrapped from an `engine_event` pipeline
|
|
197
|
+
* envelope. Contains the event-type switch previously inlined in the
|
|
198
|
+
* streaming loop. `threadId` is propagated onto tool_use / tool_result
|
|
199
|
+
* blocks so the UI can render per-thread bubbles.
|
|
200
|
+
*
|
|
201
|
+
* @param {object} event — engine event (text_delta / tool_call / …)
|
|
202
|
+
* @param {string} threadId — owning thread id (from envelope)
|
|
203
|
+
* @param {{assistantTextParts:string[], resetQueryTimer:Function}} hctx
|
|
204
|
+
*/
|
|
205
|
+
function handleEngineEvent(event, threadId, hctx) {
|
|
206
|
+
hctx.resetQueryTimer();
|
|
207
|
+
switch (event.type) {
|
|
208
|
+
case 'text_delta':
|
|
209
|
+
hctx.assistantTextParts.push(event.text);
|
|
210
|
+
sendUnifyOutput({
|
|
211
|
+
type: 'assistant',
|
|
212
|
+
message: { content: [{ type: 'text', text: event.text }] },
|
|
213
|
+
threadId,
|
|
214
|
+
});
|
|
215
|
+
break;
|
|
216
|
+
|
|
217
|
+
case 'thinking_delta':
|
|
218
|
+
sendUnifyEvent({ type: 'thinking_delta', text: event.text, threadId });
|
|
219
|
+
break;
|
|
220
|
+
|
|
221
|
+
case 'tool_call':
|
|
222
|
+
// Finish any in-progress text streaming so UI shows typing dots
|
|
223
|
+
sendUnifyOutput({
|
|
224
|
+
type: 'assistant',
|
|
225
|
+
message: { content: [] },
|
|
226
|
+
threadId,
|
|
227
|
+
});
|
|
228
|
+
sendUnifyOutput({
|
|
229
|
+
type: 'assistant',
|
|
230
|
+
message: {
|
|
231
|
+
content: [{
|
|
232
|
+
type: 'tool_use',
|
|
233
|
+
id: event.id,
|
|
234
|
+
name: event.name,
|
|
235
|
+
input: event.input,
|
|
236
|
+
}],
|
|
237
|
+
},
|
|
238
|
+
threadId: event.threadId || threadId,
|
|
239
|
+
});
|
|
240
|
+
break;
|
|
241
|
+
|
|
242
|
+
case 'tool_start':
|
|
243
|
+
sendUnifyEvent({
|
|
244
|
+
type: 'tool_start',
|
|
245
|
+
id: event.id,
|
|
246
|
+
name: event.name,
|
|
247
|
+
threadId: event.threadId || threadId,
|
|
248
|
+
});
|
|
249
|
+
break;
|
|
250
|
+
|
|
251
|
+
case 'tool_end':
|
|
252
|
+
sendUnifyOutput({
|
|
253
|
+
type: 'user',
|
|
254
|
+
tool_use_result: [{
|
|
255
|
+
type: 'tool_result',
|
|
256
|
+
tool_use_id: event.id,
|
|
257
|
+
content: event.output || '',
|
|
258
|
+
is_error: event.isError || false,
|
|
259
|
+
}],
|
|
260
|
+
threadId: event.threadId || threadId,
|
|
261
|
+
});
|
|
262
|
+
if (THREAD_MUTATING_TOOLS.has(event.name)) {
|
|
263
|
+
sendThreadListUpdate();
|
|
264
|
+
}
|
|
265
|
+
break;
|
|
266
|
+
|
|
267
|
+
case 'turn_start':
|
|
268
|
+
case 'turn_end':
|
|
269
|
+
case 'stop':
|
|
270
|
+
// No UI action needed; outer loop sends the final result.
|
|
271
|
+
break;
|
|
272
|
+
|
|
273
|
+
case 'usage':
|
|
274
|
+
sendUnifyEvent({
|
|
275
|
+
type: 'context_usage',
|
|
276
|
+
inputTokens: event.inputTokens,
|
|
277
|
+
outputTokens: event.outputTokens,
|
|
278
|
+
threadId,
|
|
279
|
+
});
|
|
280
|
+
break;
|
|
281
|
+
|
|
282
|
+
case 'recall':
|
|
283
|
+
sendUnifyEvent({
|
|
284
|
+
type: 'recall',
|
|
285
|
+
entryCount: event.entryCount,
|
|
286
|
+
cached: event.cached,
|
|
287
|
+
threadId,
|
|
288
|
+
});
|
|
289
|
+
break;
|
|
290
|
+
|
|
291
|
+
case 'consolidate':
|
|
292
|
+
// Engine compressed the context — clear our accumulated history.
|
|
293
|
+
conversationMessages = [];
|
|
294
|
+
sendUnifyEvent({
|
|
295
|
+
type: 'consolidate',
|
|
296
|
+
archivedCount: event.archivedCount,
|
|
297
|
+
extractedCount: event.extractedCount,
|
|
298
|
+
threadId,
|
|
299
|
+
});
|
|
300
|
+
break;
|
|
301
|
+
|
|
302
|
+
case 'fallback':
|
|
303
|
+
sendUnifyEvent({
|
|
304
|
+
type: 'fallback',
|
|
305
|
+
from: event.from,
|
|
306
|
+
to: event.to,
|
|
307
|
+
reason: event.reason,
|
|
308
|
+
threadId,
|
|
309
|
+
});
|
|
310
|
+
break;
|
|
311
|
+
|
|
312
|
+
case 'debug_turn':
|
|
313
|
+
sendUnifyEvent({
|
|
314
|
+
type: 'debug_turn',
|
|
315
|
+
turnNumber: event.turnNumber,
|
|
316
|
+
model: event.model,
|
|
317
|
+
systemPrompt: event.systemPrompt,
|
|
318
|
+
messages: event.messages,
|
|
319
|
+
response: event.response,
|
|
320
|
+
toolCalls: event.toolCalls,
|
|
321
|
+
usage: event.usage,
|
|
322
|
+
latencyMs: event.latencyMs,
|
|
323
|
+
ttfbMs: event.ttfbMs,
|
|
324
|
+
stopReason: event.stopReason,
|
|
325
|
+
threadId,
|
|
326
|
+
});
|
|
327
|
+
break;
|
|
328
|
+
|
|
329
|
+
case 'error': {
|
|
330
|
+
const errMsg = event.error?.message || 'Unknown error';
|
|
331
|
+
// Filter permission errors: show friendly one-time diagnostic
|
|
332
|
+
// instead of raw error. Subsequent permission errors are suppressed
|
|
333
|
+
// — the user already saw the actionable message once.
|
|
334
|
+
if (isPermissionErrorMsg(errMsg)) {
|
|
335
|
+
if (!_permissionDiagnosticSent) {
|
|
336
|
+
_permissionDiagnosticSent = true;
|
|
337
|
+
sendUnifyOutput({
|
|
338
|
+
type: 'assistant',
|
|
339
|
+
message: {
|
|
340
|
+
content: [{
|
|
341
|
+
type: 'text',
|
|
342
|
+
text: '⚠️ Cannot write to ~/.yeaft/ directory — some features (memory, history) are unavailable. Please check directory permissions: `chmod -R u+rw ~/.yeaft/`',
|
|
343
|
+
}],
|
|
344
|
+
},
|
|
345
|
+
threadId,
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
// Don't show subsequent permission errors.
|
|
349
|
+
} else {
|
|
350
|
+
sendUnifyOutput({
|
|
351
|
+
type: 'assistant',
|
|
352
|
+
message: {
|
|
353
|
+
content: [{ type: 'text', text: `⚠️ Error: ${errMsg}` }],
|
|
354
|
+
},
|
|
355
|
+
threadId,
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
break;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
default:
|
|
362
|
+
// Silently consume unknown events.
|
|
363
|
+
break;
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
119
367
|
/**
|
|
120
368
|
* Handle a unify_chat message from the web UI.
|
|
121
369
|
*
|
|
@@ -193,192 +441,44 @@ export async function handleUnifyChat(msg) {
|
|
|
193
441
|
// ─── Collect assistant response for conversation history ──
|
|
194
442
|
let assistantTextParts = [];
|
|
195
443
|
|
|
196
|
-
//
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
})
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
// Currently not rendered in UI, but forward for future use
|
|
219
|
-
sendUnifyEvent({ type: 'thinking_delta', text: event.text });
|
|
220
|
-
break;
|
|
444
|
+
// task-310: route via Dispatcher pipeline (queue → router → registry →
|
|
445
|
+
// EngineInstance). The input is enqueued first so the UI observes the
|
|
446
|
+
// `input_queue_updated` snapshot before the router runs. An explicit
|
|
447
|
+
// `@thread-xxx` prefix on the message or an `override` field on the
|
|
448
|
+
// `unify_chat` payload becomes a dispatcher override — skipping the LLM.
|
|
449
|
+
const { prompt: cleanedPrompt, override: prefixOverride } = parseThreadPrefix(prompt);
|
|
450
|
+
const override = msg.override && typeof msg.override === 'object' && msg.override.threadId
|
|
451
|
+
? msg.override
|
|
452
|
+
: prefixOverride;
|
|
453
|
+
|
|
454
|
+
const { entry } = session.dispatcher.submit(cleanedPrompt, {
|
|
455
|
+
messageId: msg.messageId,
|
|
456
|
+
override: override || undefined,
|
|
457
|
+
});
|
|
458
|
+
sendUnifyEvent({
|
|
459
|
+
type: 'input_queue_updated',
|
|
460
|
+
total: 1,
|
|
461
|
+
pending: 1,
|
|
462
|
+
routing: 0,
|
|
463
|
+
dispatched: 0,
|
|
464
|
+
head: { id: entry.id, status: entry.status, text: entry.text.slice(0, 80) },
|
|
465
|
+
});
|
|
221
466
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
// Send tool_use block
|
|
230
|
-
sendUnifyOutput({
|
|
231
|
-
type: 'assistant',
|
|
232
|
-
message: {
|
|
233
|
-
content: [{
|
|
234
|
-
type: 'tool_use',
|
|
235
|
-
id: event.id,
|
|
236
|
-
name: event.name,
|
|
237
|
-
input: event.input,
|
|
238
|
-
}],
|
|
239
|
-
},
|
|
240
|
-
threadId: event.threadId,
|
|
241
|
-
});
|
|
242
|
-
break;
|
|
243
|
-
|
|
244
|
-
// ── Tool execution started ──
|
|
245
|
-
case 'tool_start':
|
|
246
|
-
// Tool is running — the UI already shows it from tool_use block above.
|
|
247
|
-
// Forward threadId so the UI can group tool activity by thread (Phase 1).
|
|
248
|
-
sendUnifyEvent({ type: 'tool_start', id: event.id, name: event.name, threadId: event.threadId });
|
|
249
|
-
break;
|
|
250
|
-
|
|
251
|
-
// ── Tool execution completed ──
|
|
252
|
-
case 'tool_end':
|
|
253
|
-
// Send tool_result as a user message (matches Claude CLI format)
|
|
254
|
-
sendUnifyOutput({
|
|
255
|
-
type: 'user',
|
|
256
|
-
tool_use_result: [{
|
|
257
|
-
type: 'tool_result',
|
|
258
|
-
tool_use_id: event.id,
|
|
259
|
-
content: event.output || '',
|
|
260
|
-
is_error: event.isError || false,
|
|
261
|
-
}],
|
|
262
|
-
threadId: event.threadId,
|
|
263
|
-
});
|
|
264
|
-
// task-301 Part 2: if this tool mutates ThreadStore, push a
|
|
265
|
-
// fresh snapshot to the sidebar immediately.
|
|
266
|
-
if (THREAD_MUTATING_TOOLS.has(event.name)) {
|
|
267
|
-
sendThreadListUpdate();
|
|
268
|
-
}
|
|
269
|
-
break;
|
|
270
|
-
|
|
271
|
-
// ── Turn boundaries ──
|
|
272
|
-
case 'turn_start':
|
|
273
|
-
// No UI action needed
|
|
274
|
-
break;
|
|
275
|
-
|
|
276
|
-
case 'turn_end':
|
|
277
|
-
// Don't send result/done here — wait for the outermost loop to finish
|
|
278
|
-
break;
|
|
279
|
-
|
|
280
|
-
// ── Token usage ──
|
|
281
|
-
case 'usage':
|
|
282
|
-
sendUnifyEvent({
|
|
283
|
-
type: 'context_usage',
|
|
284
|
-
inputTokens: event.inputTokens,
|
|
285
|
-
outputTokens: event.outputTokens,
|
|
286
|
-
});
|
|
287
|
-
break;
|
|
288
|
-
|
|
289
|
-
// ── Stop reason from LLM ──
|
|
290
|
-
case 'stop':
|
|
291
|
-
// Intermediate signal — final done is sent after the loop
|
|
292
|
-
break;
|
|
293
|
-
|
|
294
|
-
// ── Memory recall ──
|
|
295
|
-
case 'recall':
|
|
296
|
-
sendUnifyEvent({
|
|
297
|
-
type: 'recall',
|
|
298
|
-
entryCount: event.entryCount,
|
|
299
|
-
cached: event.cached,
|
|
300
|
-
});
|
|
301
|
-
break;
|
|
302
|
-
|
|
303
|
-
// ── Context consolidation ──
|
|
304
|
-
case 'consolidate':
|
|
305
|
-
// Engine has compressed the context — clear our accumulated history.
|
|
306
|
-
// The engine's compactSummary will provide context on next query.
|
|
307
|
-
conversationMessages = [];
|
|
308
|
-
sendUnifyEvent({
|
|
309
|
-
type: 'consolidate',
|
|
310
|
-
archivedCount: event.archivedCount,
|
|
311
|
-
extractedCount: event.extractedCount,
|
|
312
|
-
});
|
|
313
|
-
break;
|
|
314
|
-
|
|
315
|
-
// ── Model fallback ──
|
|
316
|
-
case 'fallback':
|
|
317
|
-
sendUnifyEvent({
|
|
318
|
-
type: 'fallback',
|
|
319
|
-
from: event.from,
|
|
320
|
-
to: event.to,
|
|
321
|
-
reason: event.reason,
|
|
322
|
-
});
|
|
323
|
-
break;
|
|
324
|
-
|
|
325
|
-
// ── Debug turn data for web debug panel ──
|
|
326
|
-
case 'debug_turn':
|
|
327
|
-
sendUnifyEvent({
|
|
328
|
-
type: 'debug_turn',
|
|
329
|
-
turnNumber: event.turnNumber,
|
|
330
|
-
model: event.model,
|
|
331
|
-
systemPrompt: event.systemPrompt,
|
|
332
|
-
messages: event.messages,
|
|
333
|
-
response: event.response,
|
|
334
|
-
toolCalls: event.toolCalls,
|
|
335
|
-
usage: event.usage,
|
|
336
|
-
latencyMs: event.latencyMs,
|
|
337
|
-
ttfbMs: event.ttfbMs,
|
|
338
|
-
stopReason: event.stopReason,
|
|
339
|
-
});
|
|
340
|
-
break;
|
|
341
|
-
|
|
342
|
-
// ── Errors ──
|
|
343
|
-
case 'error': {
|
|
344
|
-
const errMsg = event.error?.message || 'Unknown error';
|
|
345
|
-
// Filter permission errors: show friendly one-time diagnostic instead of raw error
|
|
346
|
-
if (isPermissionErrorMsg(errMsg)) {
|
|
347
|
-
if (!_permissionDiagnosticSent) {
|
|
348
|
-
_permissionDiagnosticSent = true;
|
|
349
|
-
sendUnifyOutput({
|
|
350
|
-
type: 'assistant',
|
|
351
|
-
message: {
|
|
352
|
-
content: [{
|
|
353
|
-
type: 'text',
|
|
354
|
-
text: '⚠️ Cannot write to ~/.yeaft/ directory — some features (memory, history) are unavailable. Please check directory permissions: `chmod -R u+rw ~/.yeaft/`',
|
|
355
|
-
}],
|
|
356
|
-
},
|
|
357
|
-
});
|
|
358
|
-
}
|
|
359
|
-
// Don't show subsequent permission errors
|
|
360
|
-
} else {
|
|
361
|
-
sendUnifyOutput({
|
|
362
|
-
type: 'assistant',
|
|
363
|
-
message: {
|
|
364
|
-
content: [{
|
|
365
|
-
type: 'text',
|
|
366
|
-
text: `⚠️ Error: ${errMsg}`,
|
|
367
|
-
}],
|
|
368
|
-
},
|
|
369
|
-
});
|
|
370
|
-
}
|
|
371
|
-
break;
|
|
372
|
-
}
|
|
467
|
+
const pipelineCtx = {
|
|
468
|
+
onEngineEvent: (event, threadId) => handleEngineEvent(event, threadId, {
|
|
469
|
+
assistantTextParts,
|
|
470
|
+
resetQueryTimer,
|
|
471
|
+
}),
|
|
472
|
+
onError: (err) => { throw err; },
|
|
473
|
+
};
|
|
373
474
|
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
}
|
|
475
|
+
for await (const pev of session.dispatcher.drain({ signal: currentAbort.signal })) {
|
|
476
|
+
resetQueryTimer();
|
|
477
|
+
forwardPipelineEvent(pev, pipelineCtx);
|
|
378
478
|
}
|
|
379
479
|
|
|
380
480
|
// ─── Query complete — accumulate messages for context continuity ──
|
|
381
|
-
conversationMessages.push({ role: 'user', content:
|
|
481
|
+
conversationMessages.push({ role: 'user', content: cleanedPrompt });
|
|
382
482
|
|
|
383
483
|
const fullText = assistantTextParts.join('');
|
|
384
484
|
if (fullText) {
|