@yeaft/webchat-agent 1.0.37 → 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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yeaft/webchat-agent",
3
- "version": "1.0.37",
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
  /**