@yeaft/webchat-agent 1.0.289 → 1.0.292

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.
@@ -297,3 +297,167 @@ export async function runStreamTurn({
297
297
  write(result);
298
298
  return result;
299
299
  }
300
+
301
+ /**
302
+ * Run one prompt through the multi-VP CLI Session runner. Engine events keep
303
+ * the stream-json shape used by runStreamTurn and add `vp_id`, allowing
304
+ * concurrent VP streams to be demultiplexed without imposing output order.
305
+ */
306
+ export async function runStreamSessionTurn({
307
+ runner,
308
+ prompt,
309
+ sessionId,
310
+ workDir = process.cwd(),
311
+ model = null,
312
+ modelEffort = null,
313
+ input = null,
314
+ write,
315
+ }) {
316
+ const clientTurnId = randomUUID();
317
+ const states = new Map();
318
+ let failed = null;
319
+
320
+ const stateFor = (vpId) => {
321
+ let state = states.get(vpId);
322
+ if (!state) {
323
+ state = {
324
+ turnId: clientTurnId,
325
+ resultText: '',
326
+ stopReason: 'end_turn',
327
+ usage: { inputTokens: 0, outputTokens: 0, cacheReadTokens: 0, cacheWriteTokens: 0, cacheInputDeltaTokens: 0 },
328
+ };
329
+ states.set(vpId, state);
330
+ }
331
+ return state;
332
+ };
333
+
334
+ const askUser = async ({ question, options }, vpId = null, turnId = clientTurnId) => {
335
+ const requestId = randomUUID();
336
+ write({
337
+ type: 'ask_user', subtype: 'request', request_id: requestId,
338
+ session_id: sessionId, turn_id: turnId, vp_id: vpId,
339
+ question, options: Array.isArray(options) ? options : [],
340
+ });
341
+ if (!input) throw new Error('AskUser requires --input-format stream-json');
342
+ const answers = await input.waitForAnswer(requestId);
343
+ write({
344
+ type: 'ask_user', subtype: 'response', request_id: requestId,
345
+ session_id: sessionId, turn_id: turnId, vp_id: vpId, answers,
346
+ });
347
+ return answers;
348
+ };
349
+
350
+ const onEvent = async ({ vpId, event, turnId: fallbackTurnId }) => {
351
+ const state = stateFor(vpId);
352
+ if (event.type === 'turn_open' && event.turnId) state.turnId = event.turnId;
353
+ const turnId = state.turnId || fallbackTurnId || clientTurnId;
354
+ const base = { session_id: sessionId, turn_id: turnId, vp_id: vpId };
355
+ switch (event.type) {
356
+ case 'turn_open':
357
+ write({ ...event, type: 'turn', subtype: 'start', ...base, model, model_effort: modelEffort, cwd: workDir });
358
+ break;
359
+ case 'text_delta':
360
+ state.resultText += event.text || '';
361
+ write({ type: 'assistant', subtype: 'text_delta', ...base, delta: { type: 'text_delta', text: event.text || '' } });
362
+ break;
363
+ case 'thinking_delta':
364
+ write({ type: 'assistant', subtype: 'thinking_delta', ...base, delta: { type: 'thinking_delta', thinking: event.text || '' } });
365
+ break;
366
+ case 'skill_loaded':
367
+ write({ type: 'skill', subtype: 'loaded', ...base, skill: event.skill });
368
+ break;
369
+ case 'skill_error':
370
+ write({ type: 'skill', subtype: 'error', ...base, skill_name: event.skillName, error: event.message });
371
+ break;
372
+ case 'tool_call':
373
+ write({ type: 'assistant', subtype: 'tool_use', ...base, content: [{ type: 'tool_use', id: event.id, name: event.name, input: event.input }] });
374
+ if (event.name === 'TodoWrite') {
375
+ write({ type: 'todo', subtype: 'update', ...base, tool_use_id: event.id, todos: event.input?.todos || [] });
376
+ }
377
+ break;
378
+ case 'tool_start':
379
+ write({ type: 'tool', subtype: 'start', ...base, tool_use_id: event.id, name: event.name, input: event.input });
380
+ break;
381
+ case 'tool_end':
382
+ write({ type: 'tool', subtype: 'result', ...base, tool_use_id: event.id, name: event.name, content: event.output, is_error: !!event.isError });
383
+ break;
384
+ case 'usage': {
385
+ const cacheDelta = event.cacheTokensAreIncludedInInput ? 0 : (event.cacheReadTokens || 0) + (event.cacheWriteTokens || 0);
386
+ state.usage.inputTokens += event.inputTokens || 0;
387
+ state.usage.outputTokens += event.outputTokens || 0;
388
+ state.usage.cacheReadTokens += event.cacheReadTokens || 0;
389
+ state.usage.cacheWriteTokens += event.cacheWriteTokens || 0;
390
+ state.usage.cacheInputDeltaTokens += cacheDelta;
391
+ write({ type: 'usage', ...base, usage: snakeUsage(event) });
392
+ break;
393
+ }
394
+ case 'stop':
395
+ case 'turn_end':
396
+ if (event.stopReason && TERMINAL_STOP_REASONS.has(event.stopReason)) state.stopReason = event.stopReason;
397
+ break;
398
+ case 'turn_close':
399
+ write({ type: 'turn', subtype: 'stop', ...base, duration_ms: event.totalMs, loop_count: event.loopCount, total_tokens: event.totalTokens });
400
+ break;
401
+ case 'error':
402
+ failed ||= event.error instanceof Error ? event.error : new Error(event.error?.message || String(event.error || 'Unknown error'));
403
+ write({ type: 'error', ...base, error: { name: failed.name, message: failed.message }, retryable: !!event.retryable });
404
+ break;
405
+ case 'fallback':
406
+ case 'llm_retry':
407
+ case 'memory_used':
408
+ case 'recall':
409
+ case 'consolidate':
410
+ case 'reflection':
411
+ write({ ...event, ...base });
412
+ break;
413
+ }
414
+ };
415
+
416
+ let outcome;
417
+ try {
418
+ outcome = await runner.run(prompt, {
419
+ modelEffort,
420
+ onEvent,
421
+ askUser,
422
+ });
423
+ } catch (error) {
424
+ failed = error;
425
+ write({ type: 'error', session_id: sessionId, turn_id: clientTurnId, error: { name: error.name || 'Error', message: error.message || String(error) } });
426
+ outcome = { report: { dispatched: [] }, results: [] };
427
+ }
428
+
429
+ const perVp = Array.from(states, ([vpId, state]) => ({
430
+ vp_id: vpId,
431
+ turn_id: state.turnId,
432
+ stop_reason: state.stopReason,
433
+ result: state.resultText,
434
+ usage: {
435
+ input_tokens: state.usage.inputTokens,
436
+ output_tokens: state.usage.outputTokens,
437
+ cache_read_input_tokens: state.usage.cacheReadTokens,
438
+ cache_creation_input_tokens: state.usage.cacheWriteTokens,
439
+ total_input_tokens: state.usage.inputTokens + state.usage.cacheInputDeltaTokens,
440
+ total_tokens: state.usage.inputTokens + state.usage.cacheInputDeltaTokens + state.usage.outputTokens,
441
+ },
442
+ }));
443
+ const stopReasons = perVp.map(item => item.stop_reason);
444
+ const aggregateStopReason = failed || stopReasons.includes('error')
445
+ ? 'error'
446
+ : stopReasons.length > 0 && stopReasons.every(reason => reason === 'aborted')
447
+ ? 'aborted'
448
+ : 'end_turn';
449
+ const isError = !!failed || aggregateStopReason === 'error';
450
+ const result = {
451
+ type: 'result', subtype: isError ? 'error' : 'success',
452
+ session_id: sessionId, turn_id: clientTurnId,
453
+ model, model_effort: modelEffort,
454
+ stop_reason: aggregateStopReason,
455
+ is_error: isError,
456
+ result: perVp.map(item => item.result).filter(Boolean).join('\n'),
457
+ dispatched_vp_ids: outcome?.report?.dispatched || [],
458
+ vp_results: perVp,
459
+ ...(failed ? { error: failed.message || String(failed) } : {}),
460
+ };
461
+ write(result);
462
+ return result;
463
+ }
@@ -3978,6 +3978,7 @@ function handleEngineEvent(event, hctx) {
3978
3978
  type: 'memory_used',
3979
3979
  turnId: event.turnId,
3980
3980
  loaded: event.loaded || [],
3981
+ meta: event.meta || null,
3981
3982
  }, envelope);
3982
3983
  break;
3983
3984