@yeaft/webchat-agent 1.0.668 → 1.0.670

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": "1.0.668",
3
+ "version": "1.0.670",
4
4
  "description": "Remote worker agent for Yeaft Web Code Agent — connects the native Yeaft engine, CLI providers, and workbench tools",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -667,7 +667,11 @@ export class OpenAIResponsesAdapter extends LLMAdapter {
667
667
  }
668
668
  if (!sawTerminalEvent) {
669
669
  if (hasToolItems()) {
670
- throw new LLMToolInputError('OpenAI tool stream ended before terminal event');
670
+ // Calls remain buffered until terminal validation, so clean EOF here
671
+ // is a transport failure, not proof of invalid tool input. Let the
672
+ // engine apply its bounded retry/continuation policy; known malformed
673
+ // batches above (and in catch) still fail closed without replay.
674
+ throw new LLMServerError('OpenAI tool stream ended before terminal event', 0);
671
675
  }
672
676
  throw new LLMServerError('OpenAI stream ended before terminal event', 0);
673
677
  }
@@ -181,6 +181,13 @@ export class ScheduledTaskScheduler {
181
181
  }
182
182
 
183
183
  observe(sessionId, event) {
184
+ // Stop can arrive before boot/classification creates any VP turn controller
185
+ // or terminal event. Retire those claims once their ingress has drained.
186
+ if (event.type === 'yeaft_aborted' && event.all) {
187
+ for (const [id, active] of this.runs) {
188
+ if (!sessionId || id === sessionId) active.interrupted = true;
189
+ }
190
+ }
184
191
  const active = this.runs.get(sessionId);
185
192
  if (!active) return;
186
193
  if (event.type === 'error') active.error = event.message || event.error?.message || String(event.error || 'Execution failed');
@@ -510,6 +510,36 @@ const vpAborts = new Map();
510
510
  */
511
511
  const sessionCopyEpochs = new Map();
512
512
  const modelReentryActivity = new Map();
513
+ // Pending ingress has no VP AbortController yet. Keep per-request tokens so
514
+ // Stop fences existing boot/classification awaits, not later inputs in the
515
+ // same activity epoch (or inputs belonging to another Session).
516
+ const pendingSessionIngress = new Map();
517
+
518
+ function beginSessionIngress(sessionId) {
519
+ let pending = pendingSessionIngress.get(sessionId);
520
+ if (!pending) {
521
+ pending = new Set();
522
+ pendingSessionIngress.set(sessionId, pending);
523
+ }
524
+ const ingress = {
525
+ cancelled: false,
526
+ release() {
527
+ pending.delete(ingress);
528
+ if (pending.size === 0 && pendingSessionIngress.get(sessionId) === pending) {
529
+ pendingSessionIngress.delete(sessionId);
530
+ }
531
+ },
532
+ };
533
+ pending.add(ingress);
534
+ return ingress;
535
+ }
536
+
537
+ function cancelSessionIngress(sessionId = null) {
538
+ for (const [id, pending] of pendingSessionIngress) {
539
+ if (sessionId && id !== sessionId) continue;
540
+ for (const ingress of pending) ingress.cancelled = true;
541
+ }
542
+ }
513
543
 
514
544
  function hasSessionRuntimeWork(sessionId) {
515
545
  const prefix = `${sessionId}::`;
@@ -2273,14 +2303,16 @@ function scheduleTaskResultReentry(event) {
2273
2303
 
2274
2304
  async function routeEnvelopeToVpThread(sessionId, vpId, envelope) {
2275
2305
  const releaseActivity = beginSessionActivity(sessionId);
2306
+ const ingress = beginSessionIngress(sessionId);
2276
2307
  try {
2277
- return await routeActiveEnvelopeToVpThread(sessionId, vpId, envelope);
2308
+ return await routeActiveEnvelopeToVpThread(sessionId, vpId, envelope, ingress);
2278
2309
  } finally {
2310
+ ingress.release();
2279
2311
  releaseActivity();
2280
2312
  }
2281
2313
  }
2282
2314
 
2283
- async function routeActiveEnvelopeToVpThread(sessionId, vpId, envelope) {
2315
+ async function routeActiveEnvelopeToVpThread(sessionId, vpId, envelope, ingress) {
2284
2316
  const routeStart = perfNowMs();
2285
2317
  const { text, prompt, promptParts } = buildVpPromptPayload(vpId, envelope);
2286
2318
  const runningThreads = getRunningThreads(sessionId, vpId);
@@ -2313,6 +2345,7 @@ async function routeActiveEnvelopeToVpThread(sessionId, vpId, envelope) {
2313
2345
  // promise would leave it visible in history without ever starting a turn.
2314
2346
  console.warn('[Yeaft] VP thread classification failed; starting a new thread:', err?.message || err);
2315
2347
  }
2348
+ if (ingress.cancelled) return null;
2316
2349
  const targetIsRunning = decision
2317
2350
  ? runningThreads.some((t) => t.threadId === decision.targetThreadId)
2318
2351
  : false;
@@ -5214,22 +5247,25 @@ async function runYeaftSessionSend(msg) {
5214
5247
  traceDuration('session_send.open_session', openSessionStart, { ok: !!sessionHandle });
5215
5248
 
5216
5249
  const releaseActivity = beginSessionActivity(sessionId);
5250
+ const ingress = beginSessionIngress(sessionId);
5217
5251
  try {
5218
5252
  return await dispatchAcceptedSessionSend({
5219
- msg, text, mentions, quote, sessionId, yeaftDir, perfTraceId, perfStart,
5253
+ msg, text, mentions, quote, sessionId, yeaftDir, perfTraceId, perfStart, ingress,
5220
5254
  tracePerf, traceDuration, turnConfig, sessionHandle, sessionRoot, sessionMetaForRuntime,
5221
5255
  });
5222
5256
  } finally {
5257
+ ingress.release();
5223
5258
  releaseActivity();
5224
5259
  }
5225
5260
  }
5226
5261
 
5227
5262
  async function dispatchAcceptedSessionSend({
5228
- msg, text, mentions, quote, sessionId, yeaftDir, perfTraceId, perfStart,
5263
+ msg, text, mentions, quote, sessionId, yeaftDir, perfTraceId, perfStart, ingress,
5229
5264
  tracePerf, traceDuration, turnConfig, sessionHandle, sessionRoot, sessionMetaForRuntime,
5230
5265
  }) {
5231
5266
  const ensureSessionStart = perfNowMs();
5232
5267
  await ensureSessionLoaded({ sessionMeta: sessionMetaForRuntime, perfTraceId });
5268
+ if (ingress.cancelled) return;
5233
5269
  // Runtime boot may await I/O. Re-read the claim immediately before ingress
5234
5270
  // so edits, disable, archive or deletion during boot cannot execute stale work.
5235
5271
  msg[SCHEDULED_TASK_DISPATCH_GUARD]?.();
@@ -6742,6 +6778,7 @@ export function handleYeaftAbortThread(_msg = {}) {
6742
6778
  export function handleYeaftAbortAll(msg = {}) {
6743
6779
  const sessionId = typeof msg.sessionId === 'string' && msg.sessionId ? msg.sessionId : null;
6744
6780
  const aborted = [];
6781
+ cancelSessionIngress(sessionId);
6745
6782
  if (!sessionId && currentAbortCtrl && !currentAbortCtrl.signal.aborted) {
6746
6783
  try { currentAbortCtrl.abort(); aborted.push('main'); } catch { /* best-effort */ }
6747
6784
  }