@yeaft/webchat-agent 1.0.13 → 1.0.15
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/yeaft/web-bridge.js +98 -5
package/package.json
CHANGED
package/yeaft/web-bridge.js
CHANGED
|
@@ -2355,6 +2355,89 @@ function maybeTransitionVpStatus(hctx, state) {
|
|
|
2355
2355
|
}
|
|
2356
2356
|
}
|
|
2357
2357
|
|
|
2358
|
+
const STREAM_TEXT_BATCH_MAX_CHARS = 200;
|
|
2359
|
+
const STREAM_TEXT_BATCH_MAX_MS = 200;
|
|
2360
|
+
|
|
2361
|
+
function createStreamTextBatch() {
|
|
2362
|
+
return {
|
|
2363
|
+
parts: [],
|
|
2364
|
+
charCount: 0,
|
|
2365
|
+
timer: null,
|
|
2366
|
+
envelope: null,
|
|
2367
|
+
immediateNext: true,
|
|
2368
|
+
};
|
|
2369
|
+
}
|
|
2370
|
+
|
|
2371
|
+
function getStreamTextBatch(hctx) {
|
|
2372
|
+
if (!hctx) return null;
|
|
2373
|
+
if (!hctx.streamTextBatch) hctx.streamTextBatch = createStreamTextBatch();
|
|
2374
|
+
return hctx.streamTextBatch;
|
|
2375
|
+
}
|
|
2376
|
+
|
|
2377
|
+
function clearStreamTextBatchTimer(batch) {
|
|
2378
|
+
if (!batch?.timer) return;
|
|
2379
|
+
clearTimeout(batch.timer);
|
|
2380
|
+
batch.timer = null;
|
|
2381
|
+
}
|
|
2382
|
+
|
|
2383
|
+
function sendAssistantTextFrame(text, envelope) {
|
|
2384
|
+
if (!text) return;
|
|
2385
|
+
sendSessionOutputFrame({
|
|
2386
|
+
type: 'assistant',
|
|
2387
|
+
message: { content: [{ type: 'text', text }] },
|
|
2388
|
+
}, envelope);
|
|
2389
|
+
}
|
|
2390
|
+
|
|
2391
|
+
function flushStreamTextBatch(hctx, envelope, { resetImmediate = false } = {}) {
|
|
2392
|
+
const batch = hctx?.streamTextBatch;
|
|
2393
|
+
if (!batch) return false;
|
|
2394
|
+
clearStreamTextBatchTimer(batch);
|
|
2395
|
+
const text = batch.parts.join('');
|
|
2396
|
+
batch.parts = [];
|
|
2397
|
+
batch.charCount = 0;
|
|
2398
|
+
const flushEnvelope = envelope || batch.envelope;
|
|
2399
|
+
batch.envelope = flushEnvelope || null;
|
|
2400
|
+
if (resetImmediate) batch.immediateNext = true;
|
|
2401
|
+
if (!text) return false;
|
|
2402
|
+
sendAssistantTextFrame(text, flushEnvelope);
|
|
2403
|
+
return true;
|
|
2404
|
+
}
|
|
2405
|
+
|
|
2406
|
+
function scheduleStreamTextBatchFlush(hctx, batch) {
|
|
2407
|
+
if (!hctx || batch.timer) return;
|
|
2408
|
+
batch.timer = setTimeout(() => {
|
|
2409
|
+
batch.timer = null;
|
|
2410
|
+
flushStreamTextBatch(hctx, batch.envelope);
|
|
2411
|
+
}, STREAM_TEXT_BATCH_MAX_MS);
|
|
2412
|
+
if (batch.timer && typeof batch.timer.unref === 'function') {
|
|
2413
|
+
batch.timer.unref();
|
|
2414
|
+
}
|
|
2415
|
+
}
|
|
2416
|
+
|
|
2417
|
+
function queueStreamTextDelta(hctx, text, envelope) {
|
|
2418
|
+
if (typeof text !== 'string' || text.length === 0) return;
|
|
2419
|
+
const batch = getStreamTextBatch(hctx);
|
|
2420
|
+
if (!batch) {
|
|
2421
|
+
sendAssistantTextFrame(text, envelope);
|
|
2422
|
+
return;
|
|
2423
|
+
}
|
|
2424
|
+
|
|
2425
|
+
batch.envelope = envelope;
|
|
2426
|
+
if (batch.immediateNext) {
|
|
2427
|
+
batch.immediateNext = false;
|
|
2428
|
+
sendAssistantTextFrame(text, envelope);
|
|
2429
|
+
return;
|
|
2430
|
+
}
|
|
2431
|
+
|
|
2432
|
+
batch.parts.push(text);
|
|
2433
|
+
batch.charCount += text.length;
|
|
2434
|
+
if (batch.charCount >= STREAM_TEXT_BATCH_MAX_CHARS) {
|
|
2435
|
+
flushStreamTextBatch(hctx, envelope);
|
|
2436
|
+
return;
|
|
2437
|
+
}
|
|
2438
|
+
scheduleStreamTextBatchFlush(hctx, batch);
|
|
2439
|
+
}
|
|
2440
|
+
|
|
2358
2441
|
/**
|
|
2359
2442
|
* Handle a single engine event unwrapped from an `engine_event` envelope.
|
|
2360
2443
|
* Stamps threadId on every outgoing frame so frontend grouping, tools,
|
|
@@ -2376,13 +2459,17 @@ function handleEngineEvent(event, hctx) {
|
|
|
2376
2459
|
threadId: hctx.threadId || event.threadId,
|
|
2377
2460
|
};
|
|
2378
2461
|
|
|
2462
|
+
if (event.type !== 'text_delta') {
|
|
2463
|
+
// Preserve wire order. Any boundary/metadata/tool event must see all text
|
|
2464
|
+
// accepted before it flushed first; otherwise the browser can render a tool
|
|
2465
|
+
// call or terminal result before the text that led to it.
|
|
2466
|
+
flushStreamTextBatch(hctx, envelope, { resetImmediate: true });
|
|
2467
|
+
}
|
|
2468
|
+
|
|
2379
2469
|
switch (event.type) {
|
|
2380
2470
|
case 'text_delta':
|
|
2381
2471
|
hctx.assistantTextParts.push(event.text);
|
|
2382
|
-
|
|
2383
|
-
type: 'assistant',
|
|
2384
|
-
message: { content: [{ type: 'text', text: event.text }] },
|
|
2385
|
-
}, envelope);
|
|
2472
|
+
queueStreamTextDelta(hctx, event.text, envelope);
|
|
2386
2473
|
// vp-status: first text-delta of a (thinking|tool) phase flips
|
|
2387
2474
|
// the row to 'streaming'. transition() is a no-op when already
|
|
2388
2475
|
// streaming, so subsequent deltas are cheap.
|
|
@@ -3411,6 +3498,7 @@ async function runVpTurn({ prompt, promptParts = null, sessionId, vpId, threadId
|
|
|
3411
3498
|
let turnEndReason = 'end_turn';
|
|
3412
3499
|
let turnEndEmitted = false;
|
|
3413
3500
|
let turnEndDetail = null;
|
|
3501
|
+
let handlerCtx = null;
|
|
3414
3502
|
const markTurnEnd = (reason) => { turnEndEmitted = true; turnEndReason = reason; };
|
|
3415
3503
|
const emitVpTurnEnd = (reason, detail = null) => {
|
|
3416
3504
|
if (turnEndEmitted) return;
|
|
@@ -3484,7 +3572,7 @@ async function runVpTurn({ prompt, promptParts = null, sessionId, vpId, threadId
|
|
|
3484
3572
|
vpEngine = getOrCreateVpEngine(sessionId, vpId, threadId);
|
|
3485
3573
|
if (thread) thread.engine = vpEngine;
|
|
3486
3574
|
|
|
3487
|
-
|
|
3575
|
+
handlerCtx = {
|
|
3488
3576
|
assistantTextParts,
|
|
3489
3577
|
toolCallsAccum,
|
|
3490
3578
|
toolResultsAccum,
|
|
@@ -3530,6 +3618,8 @@ async function runVpTurn({ prompt, promptParts = null, sessionId, vpId, threadId
|
|
|
3530
3618
|
handleEngineEvent(event, handlerCtx);
|
|
3531
3619
|
}
|
|
3532
3620
|
|
|
3621
|
+
flushStreamTextBatch(handlerCtx, envelope, { resetImmediate: true });
|
|
3622
|
+
|
|
3533
3623
|
// Turn completed — atomically append this VP's output to shared history.
|
|
3534
3624
|
// route_forward handoff text is an internal trigger, already visible as
|
|
3535
3625
|
// the source VP's tool action. Do not append it as a visible prompt for
|
|
@@ -3558,6 +3648,7 @@ async function runVpTurn({ prompt, promptParts = null, sessionId, vpId, threadId
|
|
|
3558
3648
|
} catch (err) {
|
|
3559
3649
|
const isAbort = err && (err.name === 'AbortError' || err.name === 'LLMAbortError');
|
|
3560
3650
|
if (isAbort) {
|
|
3651
|
+
flushStreamTextBatch(handlerCtx, envelope, { resetImmediate: true });
|
|
3561
3652
|
sendSessionOutputFrame({
|
|
3562
3653
|
type: 'result',
|
|
3563
3654
|
result_text: '',
|
|
@@ -3582,6 +3673,8 @@ async function runVpTurn({ prompt, promptParts = null, sessionId, vpId, threadId
|
|
|
3582
3673
|
console.warn('[Yeaft] vp-status error transition failed:', brokerErr?.message || brokerErr);
|
|
3583
3674
|
}
|
|
3584
3675
|
|
|
3676
|
+
flushStreamTextBatch(handlerCtx, envelope, { resetImmediate: true });
|
|
3677
|
+
|
|
3585
3678
|
if (isPermissionErrorMsg(err.message)) {
|
|
3586
3679
|
if (!_permissionDiagnosticSent) {
|
|
3587
3680
|
_permissionDiagnosticSent = true;
|