@yeaft/webchat-agent 1.0.33 → 1.0.34

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.33",
3
+ "version": "1.0.34",
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",
@@ -1030,10 +1030,7 @@ export class ConversationStore {
1030
1030
  */
1031
1031
  loadOlderBySession(sessionId, beforeSeq, turnsLimit = DEFAULT_RECENT_TURNS) {
1032
1032
  if (!sessionId) return { messages: [], oldestSeq: null, hasMore: false };
1033
- const hot = this.#loadSessionHotMessages(sessionId);
1034
- const cold = this.#loadSessionColdMessages(sessionId);
1035
- // Cold ids strictly < hot ids by construction → chronological concat.
1036
- const all = [...cold, ...hot];
1033
+ const all = this.#loadSessionMessages(sessionId);
1037
1034
  const cutoff = Number.isFinite(beforeSeq) ? beforeSeq : Infinity;
1038
1035
  const prefix = all.filter(m => m && m.sessionId === sessionId
1039
1036
  && !isHiddenConversationRow(m)
@@ -1072,24 +1069,28 @@ export class ConversationStore {
1072
1069
  if (!sessionId || !(turnsLimit > 0)) return { messages: [], oldestSeq: null, hasMore: false };
1073
1070
 
1074
1071
  const cutoff = Number.isFinite(beforeSeq) ? beforeSeq : Infinity;
1075
- const page = this.#loadVisibleWindowBySession(
1076
- [
1077
- ...this.#sessionMessageDirs('messages', sessionId),
1078
- this.#legacyMsgDir,
1079
- ...this.#sessionMessageDirs('cold', sessionId),
1080
- this.#legacyColdDir,
1081
- ],
1082
- sessionId,
1083
- cutoff,
1084
- turnsLimit
1085
- );
1072
+ const all = this.#loadSessionMessages(sessionId);
1073
+ const visible = all.filter(m => m && m.sessionId === sessionId
1074
+ && !isHiddenConversationRow(m)
1075
+ && (m.role === 'user' || m.role === 'assistant')
1076
+ && parseSeqFromId(m.id) < cutoff);
1077
+
1078
+ if (visible.length === 0) return { messages: [], oldestSeq: null, hasMore: false };
1079
+
1080
+ const sliced = sliceLastNTurns(visible, turnsLimit);
1086
1081
 
1087
- // Visible history is for UI replay, not LLM context. The visible loader
1088
- // already excludes tool-result rows, so running pairSanitize here can
1089
- // incorrectly treat tool-using assistant replies as orphaned tool arcs and
1090
- // drop/trim VP messages. Strip tool-call metadata instead and keep the
1091
- // user-visible assistant text for the conversation pane.
1092
- const messages = page.messages.map(m => {
1082
+ // Turn-based hasMore: an EARLIER turn boundary was dropped.
1083
+ const oldestSlicedSeq = sliced.length ? parseSeqFromId(sliced[0].id) : NaN;
1084
+ const oldestVisibleSeq = parseSeqFromId(visible[0].id);
1085
+ const hasMore = sliced.length > 0
1086
+ && Number.isFinite(oldestSlicedSeq)
1087
+ && Number.isFinite(oldestVisibleSeq)
1088
+ && oldestSlicedSeq > oldestVisibleSeq;
1089
+
1090
+ // Visible history is for UI replay, not LLM context. Strip tool-call
1091
+ // metadata — don't run pairSanitize (it can incorrectly drop assistant
1092
+ // messages that reference tool calls stripped from the UI).
1093
+ const messages = sliced.map(m => {
1093
1094
  if (m && m.role === 'assistant' && Array.isArray(m.toolCalls) && m.toolCalls.length > 0) {
1094
1095
  const { toolCalls, ...rest } = m;
1095
1096
  return { ...rest, toolSummaryCount: toolCalls.length };
@@ -1101,7 +1102,7 @@ export class ConversationStore {
1101
1102
  return {
1102
1103
  messages,
1103
1104
  oldestSeq: Number.isFinite(oldestSeq) ? oldestSeq : null,
1104
- hasMore: page.hasMore,
1105
+ hasMore,
1105
1106
  };
1106
1107
  }
1107
1108
 
@@ -1120,9 +1121,7 @@ export class ConversationStore {
1120
1121
  const limit = Number.isFinite(opts.limit) && opts.limit > 0 ? opts.limit : 500;
1121
1122
  const cutoff = Number.isFinite(afterSeq) && afterSeq >= 0 ? afterSeq : null;
1122
1123
  if (cutoff === null) return { messages: [], latestSeq: null };
1123
- const hot = this.#loadSessionHotMessages(sessionId);
1124
- const cold = this.#loadSessionColdMessages(sessionId);
1125
- const all = [...cold, ...hot].sort(compareMessagesBySeq);
1124
+ const all = this.#loadSessionMessages(sessionId);
1126
1125
  const after = all.filter((m) => {
1127
1126
  if (!m || m.sessionId !== sessionId) return false;
1128
1127
  if (isHiddenConversationRow(m)) return false;
@@ -1707,21 +1706,13 @@ export class ConversationStore {
1707
1706
  }
1708
1707
 
1709
1708
  #loadSessionHotMessages(sessionId = null) {
1710
- return [
1711
- ...this.#loadFromDir(this.#legacyMsgDir, Infinity).filter(m => m?.sessionId),
1712
- ...this.#sessionMessageDirs('messages', sessionId).flatMap(dir => this.#loadFromDir(dir, Infinity)),
1713
- ].sort(compareMessagesBySeq);
1714
- }
1715
-
1716
- #loadSessionColdMessages(sessionId = null) {
1717
- return [
1718
- ...this.#loadFromDir(this.#legacyColdDir, Infinity).filter(m => m?.sessionId),
1719
- ...this.#sessionMessageDirs('cold', sessionId).flatMap(dir => this.#loadFromDir(dir, Infinity)),
1720
- ].sort(compareMessagesBySeq);
1709
+ return this.#sessionMessageDirs('messages', sessionId)
1710
+ .flatMap(dir => this.#loadFromDir(dir, Infinity))
1711
+ .sort(compareMessagesBySeq);
1721
1712
  }
1722
1713
 
1723
1714
  #loadSessionMessages(sessionId = null) {
1724
- return [...this.#loadSessionColdMessages(sessionId), ...this.#loadSessionHotMessages(sessionId)].sort(compareMessagesBySeq);
1715
+ return this.#loadSessionHotMessages(sessionId);
1725
1716
  }
1726
1717
 
1727
1718
  #loadAllMessages() {
@@ -1748,87 +1739,6 @@ export class ConversationStore {
1748
1739
  return total;
1749
1740
  }
1750
1741
 
1751
- #loadVisibleWindowBySession(dirs, sessionId, beforeSeq, turnsLimit) {
1752
- const candidates = [];
1753
- const seen = new Set();
1754
- for (const dir of dirs) {
1755
- if (!existsSync(dir)) continue;
1756
- let files = [];
1757
- try {
1758
- files = readdirSync(dir);
1759
- } catch (err) {
1760
- if (!isPermissionError(err)) throw err;
1761
- continue;
1762
- }
1763
- for (const file of files) {
1764
- if (!file.endsWith('.md')) continue;
1765
- const seq = parseSeqFromId(basename(file, '.md'));
1766
- if (!Number.isFinite(seq) || seq >= beforeSeq) continue;
1767
- const path = join(dir, file);
1768
- if (seen.has(path)) continue;
1769
- seen.add(path);
1770
- candidates.push({ seq, path });
1771
- }
1772
- }
1773
-
1774
- candidates.sort((a, b) => b.seq - a.seq);
1775
-
1776
- const selected = [];
1777
- const pendingBoundaryTail = [];
1778
- let turnsSeen = 0;
1779
- let openCanonical = null;
1780
- let boundaryCanonical = null;
1781
- let hasMore = false;
1782
-
1783
- for (const candidate of candidates) {
1784
- let raw = '';
1785
- try {
1786
- raw = readFileSync(candidate.path, 'utf8');
1787
- } catch (err) {
1788
- if (!isPermissionError(err)) throw err;
1789
- continue;
1790
- }
1791
- if (!raw.includes(`sessionId: ${sessionId}`)) continue;
1792
- if (!raw.includes('role: user') && !raw.includes('role: assistant')) continue;
1793
-
1794
- const parsed = parseMessage(raw);
1795
- if (!parsed || parsed.sessionId !== sessionId) continue;
1796
- if (isHiddenConversationRow(parsed)) continue;
1797
- if (parsed.role !== 'user' && parsed.role !== 'assistant') continue;
1798
-
1799
- if (boundaryCanonical !== null) {
1800
- if (parsed.role !== 'user') {
1801
- pendingBoundaryTail.push(parsed);
1802
- continue;
1803
- }
1804
-
1805
- const canonical = canonicalUserTurnContent(parsed.content);
1806
- if (canonical !== boundaryCanonical) {
1807
- hasMore = true;
1808
- break;
1809
- }
1810
-
1811
- if (pendingBoundaryTail.length > 0) {
1812
- selected.push(...pendingBoundaryTail.splice(0));
1813
- }
1814
- selected.push(parsed);
1815
- continue;
1816
- }
1817
-
1818
- selected.push(parsed);
1819
- if (parsed.role === 'user') {
1820
- const canonical = canonicalUserTurnContent(parsed.content);
1821
- if (canonical != null && canonical !== openCanonical) {
1822
- turnsSeen += 1;
1823
- openCanonical = canonical;
1824
- if (turnsSeen === turnsLimit) boundaryCanonical = canonical;
1825
- }
1826
- }
1827
- }
1828
-
1829
- return { messages: selected.reverse(), hasMore };
1830
- }
1831
-
1832
1742
  // task-314: per-thread sub-directory for forked threads.
1833
1743
  #threadMsgDir(threadId) {
1834
1744
  return join(this.#convDir, 'threads', threadId, 'messages');