@yeaft/webchat-agent 0.1.1085 → 0.1.1086

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yeaft/webchat-agent",
3
- "version": "0.1.1085",
3
+ "version": "0.1.1086",
4
4
  "description": "Remote agent for Yeaft WebChat — connects worker machines to the central server",
5
5
  "main": "index.js",
6
6
  "type": "module",
package/yeaft/engine.js CHANGED
@@ -22,7 +22,7 @@ import { promises as fsp } from 'fs';
22
22
  import { join, resolve as resolvePath } from 'path';
23
23
  import { buildSystemPrompt, buildWorkerPrompt } from './prompts.js';
24
24
  import { getRuntimePlatformInfo } from './runtime-platform.js';
25
- import { LLMContextError, LLMAbortError, LLMRateLimitError, LLMServerError } from './llm/adapter.js';
25
+ import { LLMContextError, LLMAbortError, LLMRateLimitError, LLMServerError, LLMStreamIdleTimeoutError } from './llm/adapter.js';
26
26
  import { runMemoryPreflow, buildRelevantScopes } from './sessions/pre-flow.js';
27
27
  import { readProjectDoc, pickProjectDocFile, DEFAULT_PROJECT_DOC_MAX_BYTES } from './sessions/project-doc.js';
28
28
  import { partitionMessages } from './compact/partition.js';
@@ -300,9 +300,10 @@ export function shouldAllowGroupReflection({
300
300
  * @typedef {{ type: 'consolidate', archivedCount: number, extractedCount: number }} ConsolidateEvent
301
301
  * @typedef {{ type: 'recall', entryCount: number, cached: boolean }} RecallEvent
302
302
  * @typedef {{ type: 'fallback', from: string, to: string, reason: string }} FallbackEvent
303
- * @typedef {{ type: 'llm_retry', attempt: number, maxRetries: number, delayMs: number, reason: 'rate_limit_retry_after'|'rate_limit_backoff'|'transient_backoff', errorName: string, statusCode: number|null, message: string }} LlmRetryEvent
303
+ * @typedef {{ type: 'llm_retry', attempt: number, maxRetries: number, delayMs: number, reason: 'rate_limit_retry_after'|'rate_limit_backoff'|'transient_backoff'|'stream_idle_timeout', errorName: string, statusCode: number|null, message: string }} LlmRetryEvent
304
+ * @typedef {{ type: 'error', error: Error, retryable: boolean, reason?: 'stream_idle_timeout', retryExhausted?: boolean }} ErrorEvent
304
305
  *
305
- * @typedef {import('./llm/adapter.js').StreamEvent | TurnStartEvent | TurnEndEvent | ToolStartEvent | ToolEndEvent | ConsolidateEvent | RecallEvent | FallbackEvent | LlmRetryEvent} EngineEvent
306
+ * @typedef {import('./llm/adapter.js').StreamEvent | TurnStartEvent | TurnEndEvent | ToolStartEvent | ToolEndEvent | ConsolidateEvent | RecallEvent | FallbackEvent | LlmRetryEvent | ErrorEvent} EngineEvent
306
307
  */
307
308
 
308
309
  // ─── Engine ──────────────────────────────────────────────────────
@@ -2322,12 +2323,13 @@ export class Engine {
2322
2323
  }
2323
2324
  }
2324
2325
 
2325
- // ─── Rate-limit / transient retry ─────────────────
2326
+ // ─── Rate-limit / transient / stream-idle retry ───
2326
2327
  // Honour server-supplied Retry-After for 429/529; fall back to
2327
- // exponential backoff for 5xx and transport failures wrapped as
2328
- // LLMServerError. Counts against retryPolicy.maxRetries; on
2329
- // exhaustion we fall through to the fallback-model path (and
2330
- // ultimately the error event) without further waiting.
2328
+ // exponential backoff for 5xx, transport failures, and stream-idle
2329
+ // timeouts wrapped as LLMServerError. Counts against
2330
+ // retryPolicy.maxRetries; on exhaustion we fall through to the
2331
+ // fallback-model path (and ultimately the error event) without
2332
+ // further waiting.
2331
2333
  const isRateLimit = err instanceof LLMRateLimitError;
2332
2334
  const isTransient = err instanceof LLMServerError;
2333
2335
  if (isRateLimit || isTransient) {
@@ -2349,7 +2351,9 @@ export class Engine {
2349
2351
  reason = 'rate_limit_backoff';
2350
2352
  } else {
2351
2353
  delayMs = computeBackoffDelay(retryPolicy, consecutiveRetryableErrors - 1);
2352
- reason = 'transient_backoff';
2354
+ reason = err instanceof LLMStreamIdleTimeoutError
2355
+ ? 'stream_idle_timeout'
2356
+ : 'transient_backoff';
2353
2357
  }
2354
2358
  yield {
2355
2359
  type: 'llm_retry',
@@ -2376,7 +2380,7 @@ export class Engine {
2376
2380
  // ─── Fallback model ──────────────────────────────
2377
2381
  const fallbackModel = this.#config.fallbackModel;
2378
2382
  if (fallbackModel && fallbackModel !== currentModel &&
2379
- (err.name === 'LLMRateLimitError' || err.name === 'LLMServerError')) {
2383
+ (err instanceof LLMRateLimitError || err instanceof LLMServerError)) {
2380
2384
  yield { type: 'fallback', from: currentModel, to: fallbackModel, reason: err.message };
2381
2385
  currentModel = fallbackModel;
2382
2386
  consecutiveRetryableErrors = 0; // new model, fresh retry budget
@@ -2384,11 +2388,17 @@ export class Engine {
2384
2388
  continue; // retry with fallback model
2385
2389
  }
2386
2390
 
2387
- yield {
2391
+ const isRetryableError = err instanceof LLMRateLimitError || err instanceof LLMServerError;
2392
+ const errorEvent = {
2388
2393
  type: 'error',
2389
2394
  error: err,
2390
- retryable: err.name === 'LLMRateLimitError' || err.name === 'LLMServerError',
2395
+ retryable: isRetryableError,
2391
2396
  };
2397
+ if (err instanceof LLMStreamIdleTimeoutError) {
2398
+ errorEvent.reason = 'stream_idle_timeout';
2399
+ errorEvent.retryExhausted = consecutiveRetryableErrors >= retryPolicy.maxRetries;
2400
+ }
2401
+ yield errorEvent;
2392
2402
  yield { type: 'turn_end', turnNumber, stopReason: 'error', threadId };
2393
2403
  break;
2394
2404
  }
@@ -2421,8 +2421,9 @@ function handleEngineEvent(event, hctx) {
2421
2421
 
2422
2422
  case 'llm_retry':
2423
2423
  // Engine paused before re-issuing the same turn because the LLM
2424
- // returned a retryable error (rate limit / 5xx / transient network).
2425
- // Surface to the client so the UI can show "retrying in Xs (1/3)"
2424
+ // returned a retryable error (rate limit / 5xx / transient network /
2425
+ // stream idle timeout). Surface to the client so the UI can show
2426
+ // "retrying in Xs (1/3)"
2426
2427
  // instead of looking frozen mid-turn.
2427
2428
  sendSessionEvent({
2428
2429
  type: 'llm_retry',
@@ -2589,6 +2590,14 @@ function handleEngineEvent(event, hctx) {
2589
2590
 
2590
2591
  case 'error': {
2591
2592
  const errMsg = event.error?.message || 'Unknown error';
2593
+ sendSessionEvent({
2594
+ type: 'error',
2595
+ message: errMsg,
2596
+ errorName: event.error?.name || null,
2597
+ retryable: !!event.retryable,
2598
+ ...(event.reason ? { reason: event.reason } : {}),
2599
+ ...(event.retryExhausted !== undefined ? { retryExhausted: !!event.retryExhausted } : {}),
2600
+ }, envelope);
2592
2601
  if (isPermissionErrorMsg(errMsg)) {
2593
2602
  if (!_permissionDiagnosticSent) {
2594
2603
  _permissionDiagnosticSent = true;