@yeaft/webchat-agent 0.1.1096 → 0.1.1099

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": "0.1.1096",
3
+ "version": "0.1.1099",
4
4
  "description": "Remote agent for Yeaft WebChat — connects worker machines to the central server",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Internal-control conversation row helpers.
3
+ *
4
+ * Some older transcripts persisted VP-only control prompts without reliable
5
+ * `internal: true` metadata. Treat those legacy content signatures as hidden
6
+ * conversation rows so they never replay into user-visible history or future
7
+ * model context.
8
+ */
9
+
10
+ export function isInternalControlContent(content) {
11
+ if (typeof content !== 'string') return false;
12
+ const text = content.trimStart();
13
+ return text.startsWith('<task-result ')
14
+ || /^\[system note\] You have called \S+ with the same arguments \d+ times\./.test(text);
15
+ }
16
+
17
+ export function isHiddenConversationRow(row) {
18
+ if (!row) return true;
19
+ if (row._reflection || row.internal || row.systemOnly || row.systemOnlyMessage) return true;
20
+ if (row.kind === 'compact_summary' || row._compactSummary) return true;
21
+ return isInternalControlContent(row.content);
22
+ }
@@ -28,6 +28,7 @@ import { join, basename } from 'path';
28
28
  import { isPermissionError } from '../init.js';
29
29
  import { pairSanitize } from '../pair-sanitize.js';
30
30
  import { countTurns, indexOfNthTurnFromEnd, sliceLastNTurns, stripVpMentionPrefix } from '../turn-utils.js';
31
+ import { isHiddenConversationRow } from './internal-control.js';
31
32
 
32
33
  /**
33
34
  * Default cold-start "recent window" size, expressed in TURNS (not raw
@@ -899,7 +900,7 @@ export class ConversationStore {
899
900
  loadRecentBySession(sessionId, turnsLimit = DEFAULT_RECENT_TURNS) {
900
901
  if (!sessionId) return [];
901
902
  const all = this.#loadSessionMessages(sessionId);
902
- const filtered = all.filter(m => m && m.sessionId === sessionId);
903
+ const filtered = all.filter(m => m && m.sessionId === sessionId && !isHiddenConversationRow(m));
903
904
  if (turnsLimit === Infinity || turnsLimit < 0) return pairSanitize(filtered);
904
905
  const sliced = sliceLastNTurns(filtered, turnsLimit);
905
906
  // Warn once per (sessionId, storeDir) when truncation drops turns
@@ -943,8 +944,10 @@ export class ConversationStore {
943
944
  * block contract and would never appear in another VP's context).
944
945
  * - OTHER VPs' tool result rows (role:'tool'): DROP — they pair with
945
946
  * stripped tool_use ids and would orphan on replay.
946
- * - Rows with `_reflection` / `internal` / `systemOnly`: DROP — they
947
- * are engine-private and never enter another VP's context.
947
+ * - Hidden conversation rows (`_reflection`, `internal`, `systemOnly`,
948
+ * `systemOnlyMessage`, compact summaries, and legacy internal-control
949
+ * content signatures): DROP — they are engine-private and never enter
950
+ * visible history or another VP's context.
948
951
  *
949
952
  * The output is pair-safe by construction for THIS VP's tool arcs and
950
953
  * carries only summary-relevant text for the other VPs.
@@ -959,7 +962,7 @@ export class ConversationStore {
959
962
  const out = [];
960
963
  for (const m of all) {
961
964
  if (!m || m.sessionId !== sessionId) continue;
962
- if (m._reflection || m.internal || m.systemOnly || m.systemOnlyMessage) continue;
965
+ if (isHiddenConversationRow(m)) continue;
963
966
  if (m.role === 'user') {
964
967
  out.push(m);
965
968
  continue;
@@ -1033,6 +1036,7 @@ export class ConversationStore {
1033
1036
  const all = [...cold, ...hot];
1034
1037
  const cutoff = Number.isFinite(beforeSeq) ? beforeSeq : Infinity;
1035
1038
  const prefix = all.filter(m => m && m.sessionId === sessionId
1039
+ && !isHiddenConversationRow(m)
1036
1040
  && parseSeqFromId(m.id) < cutoff);
1037
1041
  if (prefix.length === 0) return { messages: [], oldestSeq: null, hasMore: false };
1038
1042
  const sliced = pairSanitize(sliceLastNTurns(prefix, turnsLimit));
@@ -1121,7 +1125,7 @@ export class ConversationStore {
1121
1125
  const all = [...cold, ...hot].sort(compareMessagesBySeq);
1122
1126
  const after = all.filter((m) => {
1123
1127
  if (!m || m.sessionId !== sessionId) return false;
1124
- if (m._reflection || m.internal || m.systemOnly || m.systemOnlyMessage) return false;
1128
+ if (isHiddenConversationRow(m)) return false;
1125
1129
  const seq = parseSeqFromId(m.id);
1126
1130
  return Number.isFinite(seq) && seq > cutoff;
1127
1131
  });
@@ -1605,7 +1609,7 @@ export class ConversationStore {
1605
1609
  ...this.#chatMessageDirs('messages', chatId).flatMap(dir => this.#loadFromDir(dir, Infinity)),
1606
1610
  ...this.#chatMessageDirs('cold', chatId).flatMap(dir => this.#loadFromDir(dir, Infinity)),
1607
1611
  ].sort(compareMessagesBySeq);
1608
- const filtered = all.filter(m => m && m.chatId === chatId);
1612
+ const filtered = all.filter(m => m && m.chatId === chatId && !isHiddenConversationRow(m));
1609
1613
  if (turnsLimit === Infinity || turnsLimit < 0) return pairSanitize(filtered);
1610
1614
  return pairSanitize(sliceLastNTurns(filtered, turnsLimit));
1611
1615
  }
@@ -1620,7 +1624,7 @@ export class ConversationStore {
1620
1624
  const out = [];
1621
1625
  for (const m of all) {
1622
1626
  if (!m || m.chatId !== chatId) continue;
1623
- if (m._reflection || m.internal || m.systemOnly || m.systemOnlyMessage) continue;
1627
+ if (isHiddenConversationRow(m)) continue;
1624
1628
  if (m.role === 'user') { out.push(m); continue; }
1625
1629
  if (m.role === 'assistant') {
1626
1630
  // Chat is 1:1 — every assistant row is "ours".
@@ -1789,7 +1793,7 @@ export class ConversationStore {
1789
1793
 
1790
1794
  const parsed = parseMessage(raw);
1791
1795
  if (!parsed || parsed.sessionId !== sessionId) continue;
1792
- if (parsed._reflection || parsed.internal || parsed.systemOnly || parsed.systemOnlyMessage) continue;
1796
+ if (isHiddenConversationRow(parsed)) continue;
1793
1797
  if (parsed.role !== 'user' && parsed.role !== 'assistant') continue;
1794
1798
 
1795
1799
  if (boundaryCanonical !== null) {
@@ -62,6 +62,7 @@ import {
62
62
  } from './history-compact.js';
63
63
  import { persistYeaftAttachments, attachmentsForPersistence, persistedAttachmentPreviewPayload } from './attachments.js';
64
64
  import { ConversationStore, parseSeqFromId } from './conversation/persist.js';
65
+ import { isHiddenConversationRow } from './conversation/internal-control.js';
65
66
  import { sliceLastNTurns } from './turn-utils.js';
66
67
  import { pairSanitize } from './pair-sanitize.js';
67
68
  import { filterSnapshotForVp } from './snapshot-filter.js';
@@ -664,10 +665,7 @@ export function __testNormalizePersistedVisibleContent(content) {
664
665
  }
665
666
 
666
667
  function isPersistedInternalMessage(m) {
667
- if (!m) return true;
668
- if (m._reflection || m.internal || m.systemOnly || m.systemOnlyMessage) return true;
669
- if (m.kind === 'compact_summary' || m._compactSummary) return true;
670
- return false;
668
+ return isHiddenConversationRow(m);
671
669
  }
672
670
 
673
671
  /**
@@ -5119,6 +5117,7 @@ export async function handleYeaftMcpReload(msg = {}) {
5119
5117
  }
5120
5118
 
5121
5119
  export const __testHooks = {
5120
+ loadVisibleGroupHistoryPage,
5122
5121
  setSessionForTest(nextSession) {
5123
5122
  session = nextSession || null;
5124
5123
  },