@yeaft/webchat-agent 1.0.36 → 1.0.38

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/conversation.js CHANGED
@@ -5,7 +5,7 @@ import ctx from './context.js';
5
5
  import { query } from './sdk/index.js';
6
6
  import { loadSessionHistory } from './history.js';
7
7
  import { startClaudeQuery } from './claude.js';
8
- import { crewSessions, loadCrewIndex } from './crew.js';
8
+ import { crewSessions } from './crew.js';
9
9
  import { getProvider, DEFAULT_PROVIDER, isValidProvider } from './providers/index.js';
10
10
 
11
11
  // 不支持的斜杠命令(真正需要交互式 CLI 的命令)
@@ -283,7 +283,7 @@ export function parseSlashCommand(message) {
283
283
  return { type: null, message };
284
284
  }
285
285
 
286
- // 发送 conversation 列表(含活跃 crew sessions + 索引中已停止的 crew sessions)
286
+ // 发送 conversation 列表(仅含活跃 crew sessions;历史 Crew 索引按需通过 list_crew_sessions 加载)
287
287
  export async function sendConversationList() {
288
288
  const list = [];
289
289
  for (const [id, state] of ctx.conversations) {
@@ -305,10 +305,9 @@ export async function sendConversationList() {
305
305
  };
306
306
  list.push(entry);
307
307
  }
308
- // 追加活跃 crew sessions
309
- const activeCrewIds = new Set();
308
+ // 追加活跃 crew sessions。历史 Crew 索引可能触发磁盘读取,保持按需加载,
309
+ // 由显式 list_crew_sessions 请求处理,避免普通 Chat/Yeaft 列表提前加载 Crew。
310
310
  for (const [id, session] of crewSessions) {
311
- activeCrewIds.add(id);
312
311
  list.push({
313
312
  id,
314
313
  workDir: session.projectDir,
@@ -319,25 +318,6 @@ export async function sendConversationList() {
319
318
  type: 'crew',
320
319
  });
321
320
  }
322
- // 追加索引中已停止的 crew sessions(不重复)
323
- try {
324
- const index = await loadCrewIndex();
325
- for (const entry of index) {
326
- if (!activeCrewIds.has(entry.sessionId)) {
327
- list.push({
328
- id: entry.sessionId,
329
- workDir: entry.projectDir,
330
- createdAt: entry.createdAt,
331
- processing: false,
332
- userId: entry.userId,
333
- username: entry.username,
334
- type: 'crew'
335
- });
336
- }
337
- }
338
- } catch (e) {
339
- console.warn('[sendConversationList] Failed to load crew index:', e.message);
340
- }
341
321
  ctx.sendToServer({
342
322
  type: 'conversation_list',
343
323
  conversations: list
package/crew/session.js CHANGED
@@ -325,18 +325,20 @@ export async function handleListCrewSessions(msg) {
325
325
  ? index.filter(e => !e.agentId || e.agentId === agentId)
326
326
  : index;
327
327
 
328
- for (const entry of filtered) {
328
+ const sessions = filtered.map(entry => {
329
329
  const active = crewSessions.get(entry.sessionId);
330
- if (active) {
331
- entry.status = active.status;
332
- }
333
- }
330
+ return {
331
+ ...entry,
332
+ active: !!active,
333
+ status: active ? active.status : 'stopped'
334
+ };
335
+ });
334
336
 
335
337
  ctx.sendToServer({
336
338
  type: 'crew_sessions_list',
337
339
  requestId,
338
340
  _requestClientId,
339
- sessions: filtered
341
+ sessions
340
342
  });
341
343
  }
342
344
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yeaft/webchat-agent",
3
- "version": "1.0.36",
3
+ "version": "1.0.38",
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",
@@ -85,10 +85,10 @@ export function extractPriorPlan(messages, vpId) {
85
85
  }
86
86
 
87
87
  /**
88
- * Return a copy of the messages array with `_meta` stripped from every
89
- * message. The serialisers (anthropic/openai-responses) read this; it is
90
- * NEVER part of the wire payload. Cheap because we only shallow-clone the
91
- * messages that actually have `_meta`.
88
+ * Return a copy of the messages array with engine-private metadata stripped
89
+ * from every message. The serialisers (anthropic/openai-responses) read this;
90
+ * these fields are NEVER part of the wire payload. Cheap because we only
91
+ * shallow-clone the messages that actually have private fields.
92
92
  *
93
93
  * @param {object[]} messages
94
94
  * @returns {object[]}
@@ -97,9 +97,10 @@ export function stripMetaForWire(messages) {
97
97
  if (!Array.isArray(messages)) return messages;
98
98
  let mutated = false;
99
99
  const out = messages.map(m => {
100
- if (m && typeof m === 'object' && '_meta' in m) {
100
+ if (m && typeof m === 'object'
101
+ && ('_meta' in m || '_runtimeTurnId' in m || '_partialTurn' in m)) {
101
102
  mutated = true;
102
- const { _meta, ...rest } = m;
103
+ const { _meta, _runtimeTurnId, _partialTurn, ...rest } = m;
103
104
  return rest;
104
105
  }
105
106
  return m;
@@ -2577,6 +2577,23 @@ function handleEngineEvent(event, hctx) {
2577
2577
  isError: !!event.isError,
2578
2578
  });
2579
2579
  }
2580
+ if (hctx.sessionId && hctx.turnId && !hctx.skipPartialHistory) {
2581
+ const appendedPrompts = Array.isArray(hctx.appendedUserPrompts) ? hctx.appendedUserPrompts : [];
2582
+ const prompts = hctx.includeInitialPrompt && typeof hctx.prompt === 'string'
2583
+ ? [hctx.prompt, ...appendedPrompts]
2584
+ : appendedPrompts;
2585
+ appendTurnToSessionHistory(
2586
+ hctx.sessionId,
2587
+ hctx.threadId || event.threadId || 'main',
2588
+ hctx.vpId,
2589
+ prompts,
2590
+ hctx.assistantTextParts || [],
2591
+ hctx.toolCallsAccum || [],
2592
+ hctx.toolResultsAccum || [],
2593
+ hctx.thinkingBlocksAccum || [],
2594
+ { turnId: hctx.turnId, partial: true },
2595
+ );
2596
+ }
2580
2597
  sendSessionOutputFrame({
2581
2598
  type: 'user',
2582
2599
  tool_use_result: [{
@@ -3598,6 +3615,9 @@ async function runVpTurn({ prompt, promptParts = null, sessionId, vpId, threadId
3598
3615
  vpEngine = getOrCreateVpEngine(sessionId, vpId, threadId);
3599
3616
  if (thread) thread.engine = vpEngine;
3600
3617
 
3618
+ const inboundInjectedBy = inboundEnvelope?.msg?.meta?.injectedBy;
3619
+ const inboundIsInternal = inboundInjectedBy === 'route_forward' || inboundInjectedBy === 'task_result';
3620
+
3601
3621
  handlerCtx = {
3602
3622
  assistantTextParts,
3603
3623
  toolCallsAccum,
@@ -3610,6 +3630,9 @@ async function runVpTurn({ prompt, promptParts = null, sessionId, vpId, threadId
3610
3630
  threadId,
3611
3631
  thread,
3612
3632
  appendedUserPrompts,
3633
+ prompt,
3634
+ includeInitialPrompt: !inboundIsInternal,
3635
+ skipPartialHistory: false,
3613
3636
  markTurnEnd,
3614
3637
  };
3615
3638
  // Always trim the snapshot before passing to engine.query. This is
@@ -3651,10 +3674,8 @@ async function runVpTurn({ prompt, promptParts = null, sessionId, vpId, threadId
3651
3674
  // the source VP's tool action. Do not append it as a visible prompt for
3652
3675
  // the target VP turn; otherwise UI replay can show a trailing handoff
3653
3676
  // block after the target response.
3654
- const inboundInjectedBy = inboundEnvelope?.msg?.meta?.injectedBy;
3655
- const inboundIsInternal = inboundInjectedBy === 'route_forward' || inboundInjectedBy === 'task_result';
3656
3677
  const visiblePrompts = inboundIsInternal ? appendedUserPrompts : [prompt, ...appendedUserPrompts];
3657
- appendTurnToSessionHistory(sessionId, threadId, vpId, visiblePrompts, assistantTextParts, toolCallsAccum, toolResultsAccum, thinkingBlocksAccum);
3678
+ appendTurnToSessionHistory(sessionId, threadId, vpId, visiblePrompts, assistantTextParts, toolCallsAccum, toolResultsAccum, thinkingBlocksAccum, { turnId });
3658
3679
 
3659
3680
  sendSessionOutputFrame({
3660
3681
  type: 'assistant',
@@ -3766,8 +3787,9 @@ async function runVpTurn({ prompt, promptParts = null, sessionId, vpId, threadId
3766
3787
  }
3767
3788
 
3768
3789
  /**
3769
- * Atomically append a completed VP-turn's messages to the GROUP'S
3770
- * conversation history. Called once at turn end (not during streaming).
3790
+ * Atomically append a completed or partial VP-turn's messages to the GROUP'S
3791
+ * conversation history. Partial writes are replaced by the final write when
3792
+ * the same runtime turn completes.
3771
3793
  *
3772
3794
  * Note: this does NOT see the engine's collapsed form — it appends the
3773
3795
  * raw user prompt(s) + the per-VP assistant text + tool results. Related
@@ -3779,15 +3801,20 @@ async function runVpTurn({ prompt, promptParts = null, sessionId, vpId, threadId
3779
3801
  * a session, this in-memory tape carries the un-collapsed form — which
3780
3802
  * is fine because each VP turn's `engine.query` re-collapses on the fly.
3781
3803
  */
3782
- function appendTurnToSessionHistory(sessionId, threadId, vpId, prompts, assistantTextParts, toolCallsAccum, toolResultsAccum, thinkingBlocksAccum) {
3783
- if (!sessionId) return;
3784
- const history = getOrCreateSessionHistory(sessionId);
3804
+ function buildTurnHistoryEntries(threadId, vpId, prompts, assistantTextParts, toolCallsAccum, toolResultsAccum, thinkingBlocksAccum, opts = {}) {
3805
+ const entries = [];
3806
+ const runtimeTurnId = typeof opts.turnId === 'string' && opts.turnId ? opts.turnId : null;
3807
+ const markEntry = (entry) => {
3808
+ if (runtimeTurnId) entry._runtimeTurnId = runtimeTurnId;
3809
+ if (opts.partial) entry._partialTurn = true;
3810
+ return entry;
3811
+ };
3785
3812
  const promptList = Array.isArray(prompts) ? prompts : [prompts];
3786
3813
  for (const prompt of promptList) {
3787
3814
  if (typeof prompt === 'string' && prompt.trim()) {
3788
3815
  // user rows intentionally carry NO speakerVpId — every VP in the
3789
3816
  // session should see the prompt in their history.
3790
- history.push({ role: 'user', content: prompt, threadId: threadId || 'main' });
3817
+ entries.push(markEntry({ role: 'user', content: prompt, threadId: threadId || 'main' }));
3791
3818
  }
3792
3819
  }
3793
3820
 
@@ -3821,7 +3848,7 @@ function appendTurnToSessionHistory(sessionId, threadId, vpId, prompts, assistan
3821
3848
  : { thinking: tb.thinking, signature: tb.signature }
3822
3849
  ));
3823
3850
  }
3824
- history.push(assistantMsg);
3851
+ entries.push(markEntry(assistantMsg));
3825
3852
 
3826
3853
  for (const tr of toolResultsAccum) {
3827
3854
  const toolMsg = {
@@ -3832,9 +3859,31 @@ function appendTurnToSessionHistory(sessionId, threadId, vpId, prompts, assistan
3832
3859
  threadId: threadId || 'main',
3833
3860
  };
3834
3861
  if (vpId) toolMsg.speakerVpId = vpId;
3835
- history.push(toolMsg);
3862
+ entries.push(markEntry(toolMsg));
3836
3863
  }
3837
3864
  }
3865
+ return entries;
3866
+ }
3867
+
3868
+ function appendTurnToSessionHistory(sessionId, threadId, vpId, prompts, assistantTextParts, toolCallsAccum, toolResultsAccum, thinkingBlocksAccum, opts = {}) {
3869
+ if (!sessionId) return;
3870
+ const history = getOrCreateSessionHistory(sessionId);
3871
+ const nextEntries = buildTurnHistoryEntries(threadId, vpId, prompts, assistantTextParts, toolCallsAccum, toolResultsAccum, thinkingBlocksAccum, opts);
3872
+ if (nextEntries.length === 0) return;
3873
+ const runtimeTurnId = typeof opts.turnId === 'string' && opts.turnId ? opts.turnId : null;
3874
+ if (runtimeTurnId) {
3875
+ let insertAt = history.length;
3876
+ for (let i = history.length - 1; i >= 0; i--) {
3877
+ if (history[i]?._runtimeTurnId === runtimeTurnId) {
3878
+ insertAt = i;
3879
+ history.splice(i, 1);
3880
+ }
3881
+ }
3882
+ if (insertAt > history.length) insertAt = history.length;
3883
+ history.splice(insertAt, 0, ...nextEntries);
3884
+ } else {
3885
+ history.push(...nextEntries);
3886
+ }
3838
3887
  }
3839
3888
 
3840
3889
  /**