@yeaft/webchat-agent 0.1.1092 → 0.1.1093

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.
@@ -38,7 +38,7 @@ import { loadMcpServers, updateMcpConfig } from '../mcp.js';
38
38
  import { getLlmConfig, updateLlmConfig, getYeaftSettings, updateYeaftSettings, getSearchSettings, updateSearchSettings, fetchTavilyUsage } from '../yeaft/config-api.js';
39
39
  import { discoverLlmModels } from '../llm-model-discovery.js';
40
40
  import { fetchModelsDev } from '../yeaft/llm/models-dev.js';
41
- import { handleYeaftSessionSend, handleYeaftModeSwitch, handleYeaftModelSwitch, resetYeaftSession, handleYeaftLoadHistory, handleYeaftLoadMoreHistory, handleYeaftAbortThread, handleYeaftAbortAll, handleYeaftAbortTurn, handleYeaftVpSubscribe, handleYeaftVpCreate, handleYeaftVpUpdate, handleYeaftVpDelete, handleYeaftVpRead, handleYeaftListSessions, handleYeaftCreateSession, handleYeaftRenameSession, handleYeaftUpdateSession, handleYeaftUpdateSessionConfig, handleYeaftArchiveSession, handleYeaftDeleteSession, handleYeaftSessionAddMember, handleYeaftSessionRemoveMember, handleYeaftSessionSetDefaultVp, handleYeaftScanWorkdirSessions, handleYeaftRestoreSession, handleYeaftDreamTrigger, handleYeaftFetchToolStats, handleYeaftFetchDebugHistory, handleYeaftMcpList, handleYeaftMcpAdd, handleYeaftMcpRemove, handleYeaftMcpReload, broadcastLanguageChange, broadcastYeaftSessionSnapshotEager } from '../yeaft/web-bridge.js';
41
+ import { handleYeaftSessionSend, handleYeaftSubAgentPrompt, handleYeaftModeSwitch, handleYeaftModelSwitch, resetYeaftSession, handleYeaftLoadHistory, handleYeaftLoadMoreHistory, handleYeaftAbortThread, handleYeaftAbortAll, handleYeaftAbortTurn, handleYeaftVpSubscribe, handleYeaftVpCreate, handleYeaftVpUpdate, handleYeaftVpDelete, handleYeaftVpRead, handleYeaftListSessions, handleYeaftCreateSession, handleYeaftRenameSession, handleYeaftUpdateSession, handleYeaftUpdateSessionConfig, handleYeaftArchiveSession, handleYeaftDeleteSession, handleYeaftSessionAddMember, handleYeaftSessionRemoveMember, handleYeaftSessionSetDefaultVp, handleYeaftScanWorkdirSessions, handleYeaftRestoreSession, handleYeaftDreamTrigger, handleYeaftFetchToolStats, handleYeaftFetchDebugHistory, handleYeaftMcpList, handleYeaftMcpAdd, handleYeaftMcpRemove, handleYeaftMcpReload, broadcastLanguageChange, broadcastYeaftSessionSnapshotEager } from '../yeaft/web-bridge.js';
42
42
  import { startYeaftStatusRefresh, refreshYeaftStatus } from '../yeaft/status-cache.js';
43
43
 
44
44
  export async function handleMessage(msg) {
@@ -655,6 +655,9 @@ export async function handleMessage(msg) {
655
655
  case 'yeaft_session_send':
656
656
  handleYeaftSessionSend(msg);
657
657
  break;
658
+ case 'yeaft_sub_agent_prompt':
659
+ handleYeaftSubAgentPrompt(msg);
660
+ break;
658
661
 
659
662
  // wave-6b: manual dream trigger from VP detail page
660
663
  case 'yeaft_dream_trigger':
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yeaft/webchat-agent",
3
- "version": "0.1.1092",
3
+ "version": "0.1.1093",
4
4
  "description": "Remote agent for Yeaft WebChat — connects worker machines to the central server",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -274,13 +274,19 @@ async function driveSubAgent(agent, subEngine, vpPersona, deps) {
274
274
  ? deps.idleAbandonMs : IDLE_ABANDON_MS;
275
275
 
276
276
  const wrapEvt = (evt) => ({ ...evt, agentId: agent.id, agentName: agent.name });
277
+ let lastTaskLogRefreshAt = 0;
278
+ const refreshTaskLog = ({ force = false } = {}) => {
279
+ if (!agent.taskId || !deps.taskManager || !agent.parentSessionId) return;
280
+ const now = Date.now();
281
+ if (!force && now - lastTaskLogRefreshAt < 250) return;
282
+ lastTaskLogRefreshAt = now;
283
+ try { deps.taskManager.refreshTaskLog(agent.parentSessionId, agent.taskId); } catch { /* ignore */ }
284
+ };
277
285
 
278
286
  const emit = (evt) => {
279
287
  const wrapped = wrapEvt(evt);
280
288
  try { agent.outputLog?.write(wrapped); } catch { /* ignore log failures */ }
281
- if (agent.taskId && deps.taskManager && agent.parentSessionId) {
282
- try { deps.taskManager.refreshTaskLog(agent.parentSessionId, agent.taskId); } catch { /* ignore */ }
283
- }
289
+ refreshTaskLog({ force: true });
284
290
  if (onEvent) {
285
291
  try { onEvent(agent.id, wrapped); } catch { /* ignore listener errors */ }
286
292
  }
@@ -360,15 +366,14 @@ async function driveSubAgent(agent, subEngine, vpPersona, deps) {
360
366
  // the bump.
361
367
  bumpLivenessFromEvent(agent.liveness, evt);
362
368
 
363
- // Mirror every raw event to the durable log, but do not live-stream
364
- // assistant text to the parent/UI. A sub-agent's answer is consumed
365
- // as one complete result at sub_agent_turn_end / task_result re-entry;
366
- // streaming deltas here make the card look like the result itself
367
- // and can leave users staring at an "idle" preview before the parent
368
- // VP consumes the completed task result.
369
+ // Mirror every raw event to the durable log. We still keep
370
+ // `sub_agent_event` text_delta suppressed so the inline transcript
371
+ // card remains result-oriented, but task-backed sub-agents refresh
372
+ // their task log so the Session status pane can show a live stream.
369
373
  if (agent.outputLog) {
370
374
  try { agent.outputLog.write(wrapEvt(evt)); } catch { /* ignore */ }
371
375
  }
376
+ refreshTaskLog({ force: evt?.type !== 'text_delta' });
372
377
  if (onEvent && evt?.type !== 'text_delta') {
373
378
  try { onEvent(agent.id, wrapEvt(evt)); } catch { /* ignore listener errors */ }
374
379
  }
@@ -460,12 +465,14 @@ async function driveSubAgent(agent, subEngine, vpPersona, deps) {
460
465
  return;
461
466
  }
462
467
 
463
- // Turn complete cleanly. Task-backed sub-agents are one-shot
468
+ // Turn complete cleanly. Task-backed sub-agents are usually one-shot
464
469
  // background tasks: once they produce their mission result, complete
465
470
  // the agent/task instead of letting the idle watchdog later mark the
466
- // already-delivered work as abandoned. Legacy in-process callers with
467
- // no TaskManager keep the old idle/PromptAgent continuation flow.
468
- if (agent.taskId && deps.taskManager && agent.parentSessionId) {
471
+ // already-delivered work as abandoned. If the user queued a follow-up
472
+ // while the turn was running, keep the driver alive and immediately
473
+ // continue into the next prompt instead of dropping that input.
474
+ const hasQueuedFollowUp = Array.isArray(agent.pendingPrompts) && agent.pendingPrompts.length > 0;
475
+ if (agent.taskId && deps.taskManager && agent.parentSessionId && !hasQueuedFollowUp) {
469
476
  transitionTerminal(agent, STATUS.COMPLETED, {
470
477
  diagnostic: 'task_turn_complete',
471
478
  deps,
@@ -473,7 +480,7 @@ async function driveSubAgent(agent, subEngine, vpPersona, deps) {
473
480
  emit({ type: 'sub_agent_turn_end', content: assistantText, status: STATUS.COMPLETED });
474
481
  return;
475
482
  }
476
- emit({ type: 'sub_agent_turn_end', content: assistantText, status: STATUS.IDLE });
483
+ emit({ type: 'sub_agent_turn_end', content: assistantText, status: hasQueuedFollowUp ? STATUS.RUNNING : STATUS.IDLE });
477
484
  }
478
485
  } finally {
479
486
  if (wallTimeWatchdog) clearTimeout(wallTimeWatchdog);
@@ -69,6 +69,8 @@ import { createVpStatusBroker } from './vp-status-broker.js';
69
69
  import { classifyThread as defaultClassifyThread, fallbackTitle } from './vp/thread-classifier.js';
70
70
  import { listMcpServers, upsertMcpServer, removeMcpServer } from './config-api.js';
71
71
  import { buildMcpFlattenedTools } from './tools/mcp-tools.js';
72
+ import { getAgentRegistry, agentBelongsToScope } from './tools/agent.js';
73
+ import { isPromptableAgentStatus } from './sub-agent/status.js';
72
74
 
73
75
  /** @type {import('./session.js').Session | null} */
74
76
  let session = null;
@@ -4314,6 +4316,74 @@ export async function handleYeaftFetchDebugHistory(msg = {}) {
4314
4316
  });
4315
4317
  }
4316
4318
 
4319
+ export function handleYeaftSubAgentPrompt(msg) {
4320
+ const sessionId = typeof msg?.sessionId === 'string' ? msg.sessionId.trim() : '';
4321
+ const taskId = typeof msg?.taskId === 'string' ? msg.taskId.trim() : '';
4322
+ const subAgentId = typeof msg?.subAgentId === 'string' ? msg.subAgentId.trim() : '';
4323
+ const message = typeof msg?.message === 'string' ? msg.message.trim() : '';
4324
+ const clientPromptId = typeof msg?.clientPromptId === 'string' ? msg.clientPromptId.trim() : '';
4325
+ const fail = (error) => {
4326
+ sendSessionEvent({
4327
+ type: 'yeaft_sub_agent_prompt_result',
4328
+ success: false,
4329
+ taskId: taskId || null,
4330
+ subAgentId: subAgentId || null,
4331
+ clientPromptId: clientPromptId || null,
4332
+ error,
4333
+ }, sessionId ? { sessionId } : undefined);
4334
+ };
4335
+
4336
+ if (!sessionId || !taskId || !subAgentId || !message) {
4337
+ fail('sessionId, taskId, subAgentId and message are required');
4338
+ return;
4339
+ }
4340
+ const task = session?.taskManager?.getTask?.(sessionId, taskId) || null;
4341
+ if (!task || task.kind !== 'sub_agent' || task.status !== 'running' || task.runtime?.subAgentId !== subAgentId) {
4342
+ fail('sub-agent task not found');
4343
+ return;
4344
+ }
4345
+
4346
+ const agent = getAgentRegistry().get(subAgentId);
4347
+ const scope = {
4348
+ sessionId,
4349
+ parentVpId: task.ownerVpId || null,
4350
+ parentThreadId: task.source?.threadId || 'main',
4351
+ };
4352
+ if (!agent || !agentBelongsToScope(agent, scope)) {
4353
+ fail('sub-agent not found');
4354
+ return;
4355
+ }
4356
+ if (!isPromptableAgentStatus(agent.status)) {
4357
+ fail(`sub-agent status "${agent.status}" does not accept prompts`);
4358
+ return;
4359
+ }
4360
+
4361
+ if (!Array.isArray(agent.pendingPrompts)) agent.pendingPrompts = [];
4362
+ agent.pendingPrompts.push(message);
4363
+ if (!Array.isArray(agent.messages)) agent.messages = [];
4364
+ agent.messages.push({ role: 'user', content: message, timestamp: Date.now() });
4365
+ if (agent.status === 'idle' || agent.status === 'created') agent.status = 'running';
4366
+
4367
+ try {
4368
+ agent.outputLog?.write?.({
4369
+ type: 'user_prompt',
4370
+ agentId: agent.id,
4371
+ agentName: agent.name,
4372
+ content: message,
4373
+ });
4374
+ session?.taskManager?.refreshTaskLog?.(sessionId, taskId);
4375
+ } catch { /* prompt queueing must not depend on log refresh */ }
4376
+
4377
+ sendSessionEvent({
4378
+ type: 'yeaft_sub_agent_prompt_result',
4379
+ success: true,
4380
+ taskId,
4381
+ subAgentId,
4382
+ clientPromptId: clientPromptId || null,
4383
+ pending: agent.pendingPrompts.length,
4384
+ }, { sessionId, vpId: task.ownerVpId || null, threadId: task.source?.threadId || null });
4385
+ }
4386
+
4317
4387
  /** Deprecated mode switch — Yeaft is single-mode. */
4318
4388
  export function handleYeaftModeSwitch(_msg) {
4319
4389
  console.warn('[Yeaft] yeaft_mode_switch is deprecated and ignored — Yeaft now runs in a single unified mode.');
@@ -5022,6 +5092,9 @@ export async function handleYeaftMcpReload(msg = {}) {
5022
5092
  }
5023
5093
 
5024
5094
  export const __testHooks = {
5095
+ setSessionForTest(nextSession) {
5096
+ session = nextSession || null;
5097
+ },
5025
5098
  resetAbortState() {
5026
5099
  turnAbortCtrls.clear();
5027
5100
  turnAbortMeta.clear();