@yeaft/webchat-agent 1.0.446 → 1.0.448

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.
Files changed (44) hide show
  1. package/local-runtime/server/context.js +3 -22
  2. package/local-runtime/server/database.js +1 -0
  3. package/local-runtime/server/db/agent-inventory-db.js +122 -0
  4. package/local-runtime/server/db/connection.js +59 -0
  5. package/local-runtime/server/handlers/agent-sync.js +12 -1
  6. package/local-runtime/server/handlers/client-conversation.js +1 -0
  7. package/local-runtime/server/routes/admin-routes.js +78 -15
  8. package/local-runtime/server/ws-agent.js +41 -3
  9. package/local-runtime/server/ws-client.js +1 -7
  10. package/local-runtime/version.json +1 -1
  11. package/local-runtime/web/app.bundle.js +208 -233
  12. package/local-runtime/web/app.bundle.js.gz +0 -0
  13. package/local-runtime/web/index.html +2 -2
  14. package/local-runtime/web/style.bundle.css +1 -1
  15. package/local-runtime/web/style.bundle.css.gz +0 -0
  16. package/package.json +1 -1
  17. package/yeaft/archive/turn-archive.js +1 -1
  18. package/yeaft/cli.js +15 -17
  19. package/yeaft/config.js +2 -3
  20. package/yeaft/conversation/internal-control.js +2 -0
  21. package/yeaft/conversation/persist.js +22 -216
  22. package/yeaft/conversation/visible-entry.js +1 -1
  23. package/yeaft/effort.js +0 -2
  24. package/yeaft/engine.js +31 -376
  25. package/yeaft/history-window.js +570 -0
  26. package/yeaft/llm/adapter.js +1 -1
  27. package/yeaft/llm/anthropic.js +1 -1
  28. package/yeaft/llm/models-dev.js +2 -1
  29. package/yeaft/llm/openai-responses.js +1 -1
  30. package/yeaft/llm/router.js +2 -3
  31. package/yeaft/llm/usage-accounting.js +2 -2
  32. package/yeaft/pair-sanitize.js +3 -3
  33. package/yeaft/prompts.js +3 -4
  34. package/yeaft/session.js +0 -51
  35. package/yeaft/stdio-protocol.js +0 -1
  36. package/yeaft/stop-hooks.js +5 -6
  37. package/yeaft/turn-utils.js +5 -5
  38. package/yeaft/web-bridge.js +57 -135
  39. package/yeaft/compact/compactor.js +0 -283
  40. package/yeaft/compact/orchestrator.js +0 -141
  41. package/yeaft/compact/partition.js +0 -94
  42. package/yeaft/compact/triggers.js +0 -54
  43. package/yeaft/compact/turn-group.js +0 -85
  44. package/yeaft/history-compact.js +0 -764
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yeaft/webchat-agent",
3
- "version": "1.0.446",
3
+ "version": "1.0.448",
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",
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * archive/turn-archive.js — DESIGN.md §4.2 + §4.4.
3
3
  *
4
- * When compact archives a cooling turn-group, the full content is
4
+ * When a durable archive operation stores a turn-group, the full content is
5
5
  * written to `…/archive/<turnId>.md` so a worker can later replay it via
6
6
  * `message_trace({turnId})`. Each archived turn is one markdown file
7
7
  * with a YAML-ish header followed by JSON-encoded message bodies. We
package/yeaft/cli.js CHANGED
@@ -17,7 +17,7 @@
17
17
  * /history — Show conversation history
18
18
  * /history [n] — Show last N messages from conversation
19
19
  * /search <keyword> — Search conversation history
20
- * /compact — Trigger manual consolidation
20
+ * /compact — Retired for native Yeaft; inspect history or Memory instead
21
21
  * /tools — List registered tools
22
22
  * /skills — List loaded skills
23
23
  * Conversation persistence across REPL sessions
@@ -380,13 +380,17 @@ async function runREPL(config, args) {
380
380
 
381
381
  // Load persisted conversation as initial messages. `loadRecent` is now
382
382
  // turn-based (one user round-trip = one turn; multi-VP fan-out collapses
383
- // into one turn). 20 turns is the bootstrap window — the engine-level
384
- // compactor in `history-compact.js` is the authoritative size limiter.
383
+ // into one turn). 20 turns is the bootstrap window. Provider requests
384
+ // apply deterministic history-window trimming; persisted history stays
385
+ // authoritative and no LLM conversation summary is generated.
385
386
  let conversationMessages = conversationStore.loadRecent().map(m => ({
386
387
  role: m.role,
387
388
  content: m.content,
388
389
  ...(m.toolCallId && { toolCallId: m.toolCallId }),
389
390
  ...(m.toolCalls && { toolCalls: m.toolCalls }),
391
+ ...(Array.isArray(m.thinkingBlocks) && m.thinkingBlocks.length > 0
392
+ ? { thinkingBlocks: m.thinkingBlocks.map(block => ({ ...block })) }
393
+ : {}),
390
394
  }));
391
395
 
392
396
  const hotCount = conversationStore.countHot();
@@ -445,7 +449,7 @@ async function runREPL(config, args) {
445
449
  console.log(' /trace <stats|recent> — Query debug trace');
446
450
  console.log(' /history [n] — Show last N messages');
447
451
  console.log(' /search <keyword> — Search conversation history');
448
- console.log(' /compact — Trigger consolidation');
452
+ console.log(' /compact — Retired; native Yeaft keeps transcript + Memory');
449
453
  console.log(' /context — Show context info');
450
454
  console.log(' /dry-run — Toggle dry-run mode');
451
455
  console.log(' /stats — Show session stats');
@@ -516,7 +520,7 @@ async function runREPL(config, args) {
516
520
  }
517
521
 
518
522
  case 'compact': {
519
- console.log('The /compact REPL command is retired. Compaction is driven automatically by the engine when the hot-window budget is exceeded.');
523
+ console.log('The /compact REPL command is retired. Native Yeaft keeps the transcript authoritative and uses Memory plus a deterministic provider history window.');
520
524
  break;
521
525
  }
522
526
 
@@ -756,13 +760,6 @@ async function runREPL(config, args) {
756
760
  process.stderr.write(`[recall] ${event.entryCount} entries${event.cached ? ' (cached)' : ''}\n`);
757
761
  }
758
762
  break;
759
- case 'consolidate':
760
- if (session.config.debug) {
761
- process.stderr.write(`[consolidate] archived=${event.archivedCount}, extracted=${event.extractedCount}\n`);
762
- } else {
763
- process.stderr.write(`[compact] Memory consolidated\n`);
764
- }
765
- break;
766
763
  case 'fallback':
767
764
  process.stderr.write(`\n[fallback] ${event.from} → ${event.to}: ${event.reason}\n`);
768
765
  break;
@@ -911,6 +908,9 @@ async function runStreamJson(config, args) {
911
908
  content: message.content,
912
909
  ...(message.toolCallId && { toolCallId: message.toolCallId }),
913
910
  ...(message.toolCalls && { toolCalls: message.toolCalls }),
911
+ ...(Array.isArray(message.thinkingBlocks) && message.thinkingBlocks.length > 0
912
+ ? { thinkingBlocks: message.thinkingBlocks.map(block => ({ ...block })) }
913
+ : {}),
914
914
  }));
915
915
 
916
916
  // An ad-hoc stream-json conversation has exactly one Engine and no VP roster.
@@ -1263,6 +1263,9 @@ async function runOnce(config, args) {
1263
1263
  content: m.content,
1264
1264
  ...(m.toolCallId && { toolCallId: m.toolCallId }),
1265
1265
  ...(m.toolCalls && { toolCalls: m.toolCalls }),
1266
+ ...(Array.isArray(m.thinkingBlocks) && m.thinkingBlocks.length > 0
1267
+ ? { thinkingBlocks: m.thinkingBlocks.map(block => ({ ...block })) }
1268
+ : {}),
1266
1269
  }));
1267
1270
 
1268
1271
  let terminalEngineError = null;
@@ -1292,11 +1295,6 @@ async function runOnce(config, args) {
1292
1295
  process.stderr.write(`[recall] ${event.entryCount} entries${event.cached ? ' (cached)' : ''}\n`);
1293
1296
  }
1294
1297
  break;
1295
- case 'consolidate':
1296
- if (args.verbose || args.debug) {
1297
- process.stderr.write(`[consolidate] archived=${event.archivedCount}, extracted=${event.extractedCount}\n`);
1298
- }
1299
- break;
1300
1298
  case 'fallback':
1301
1299
  process.stderr.write(`\n[fallback] ${event.from} → ${event.to}: ${event.reason}\n`);
1302
1300
  break;
package/yeaft/config.js CHANGED
@@ -51,9 +51,8 @@ const DEFAULTS = {
51
51
  yeaftAutoArchiveIdleDays: 30,
52
52
  // Cold-start replay window: how many of the most recent turns
53
53
  // ConversationStore.loadRecentBySession / loadSessionHistoryForVp
54
- // bring back when no compact summary exists for the session. Raise
55
- // this if your sessions routinely outgrow the 20-turn window
56
- // before compaction fires. Range: 1–500.
54
+ // bring back after boot or reconnect. Older transcript remains available
55
+ // through history pagination/search. Range: 1–500.
57
56
  yeaftRecentTurnsLimit: 20,
58
57
  // CLAUDE.md / AGENTS.md project-doc cap, in bytes. Mirrors Codex's
59
58
  // `project_doc_max_bytes`. 0 disables the feature (no project-doc
@@ -18,6 +18,8 @@ export function isInternalControlContent(content) {
18
18
  export function isHiddenConversationRow(row) {
19
19
  if (!row) return true;
20
20
  if (row._reflection || row.internal || row.systemOnly || row.systemOnlyMessage) return true;
21
+ // Legacy compact-control rows remain hidden when reading old stores. The
22
+ // current runtime never creates or injects them.
21
23
  if (row.kind === 'compact_summary' || row._compactSummary) return true;
22
24
  return isInternalControlContent(row.content);
23
25
  }
@@ -43,9 +43,9 @@ import { markConversationDirty } from './history-index-state.js';
43
43
  * message boundary, which is pair-safe by construction.
44
44
  *
45
45
  * 20 turns is the bootstrap window the user signed off on (2026-05-01).
46
- * The session-level compactor in `history-compact.js` is the authoritative
47
- * size limiter once the engine is running; this is just the cold-start
48
- * replay window after a fresh boot or reconnect.
46
+ * It is the cold-start replay window after a fresh boot or reconnect. Runtime
47
+ * provider requests apply a separate deterministic history-window transform;
48
+ * no LLM summary is required for recovery.
49
49
  *
50
50
  * Configurable via `~/.yeaft/config.json` → `yeaft.recentTurnsLimit`.
51
51
  * Session boot calls `setDefaultRecentTurnsLimit()` once with the
@@ -212,25 +212,23 @@ export function __resetTruncationWarned() {
212
212
 
213
213
  /**
214
214
  * Warn (once per session per process) when the cold-start replay window
215
- * truncated history AND no compact summary exists to cover the dropped
216
- * turns. The user is then losing context with no UX signal otherwise.
215
+ * truncates history. The persisted transcript remains available through
216
+ * pagination/search; this warning only describes the model bootstrap window.
217
217
  *
218
218
  * @param {string} sessionId
219
219
  * @param {string} storeDir
220
220
  * @param {number} recentTurnsLimit — configured recent-turn window
221
- * @param {boolean} hasCompactSummary
222
221
  */
223
- function maybeWarnHistoryTruncated(sessionId, storeDir, recentTurnsLimit, hasCompactSummary) {
222
+ function maybeWarnHistoryTruncated(sessionId, storeDir, recentTurnsLimit) {
224
223
  if (!sessionId || !storeDir) return;
225
- if (hasCompactSummary) return;
226
224
  const key = `${storeDir}::${sessionId}`;
227
225
  if (_truncationWarned.has(key)) return;
228
226
  _truncationWarned.add(key);
229
227
  // eslint-disable-next-line no-console
230
228
  console.warn(
231
- `[Yeaft] history for session ${sessionId} truncated to ${recentTurnsLimit} recent turns (recentTurnsLimit=${DEFAULT_RECENT_TURNS}); ` +
232
- `no compact summary exists, so older context is dropped. ` +
233
- `Raise yeaft.recentTurnsLimit in ~/.yeaft/config.json if this is a problem.`
229
+ `[Yeaft] history for session ${sessionId} bootstrapped with ${recentTurnsLimit} recent turns ` +
230
+ `(recentTurnsLimit=${DEFAULT_RECENT_TURNS}); older transcript remains available through history pagination/search. ` +
231
+ `Raise yeaft.recentTurnsLimit in ~/.yeaft/config.json to send more recent context after boot.`
234
232
  );
235
233
  }
236
234
 
@@ -1116,12 +1114,10 @@ class SegmentStore {
1116
1114
  * chat/ — one-to-one chat mode history
1117
1115
  * index.json
1118
1116
  * segments/
1119
- * compact.md
1120
1117
  * blobs/
1121
1118
  * sessions/<sessionId>/conversation/
1122
1119
  * index.json
1123
1120
  * segments/
1124
- * compact/
1125
1121
  * blobs/
1126
1122
  *
1127
1123
  * Legacy compatibility: ~/.yeaft/conversation is read as an old mixed store,
@@ -1139,9 +1135,6 @@ export class ConversationStore {
1139
1135
  #msgDir; // default hot messages dir: ~/.yeaft/chat/messages
1140
1136
  #coldDir; // default cold messages dir: ~/.yeaft/chat/cold
1141
1137
  #indexPath; // ~/.yeaft/chat/index.md
1142
- #compactPath; // ~/.yeaft/chat/compact.md
1143
- #legacyCompactPath;
1144
- #legacyCompactScopedDir;
1145
1138
  #chatMsgDir;
1146
1139
  #chatColdDir;
1147
1140
  #legacyMsgDir;
@@ -1163,18 +1156,12 @@ export class ConversationStore {
1163
1156
  this.#msgDir = join(this.#chatDir, 'messages');
1164
1157
  this.#coldDir = join(this.#chatDir, 'cold');
1165
1158
  this.#indexPath = join(this.#chatDir, 'index.md');
1166
- this.#compactPath = join(this.#chatDir, 'compact.md');
1167
- this.#legacyCompactPath = join(this.#legacyConvDir, 'compact.md');
1168
1159
 
1169
1160
  this.#chatMsgDir = this.#msgDir;
1170
1161
  this.#chatColdDir = this.#coldDir;
1171
1162
  this.#legacyMsgDir = join(this.#legacyConvDir, 'messages');
1172
1163
  this.#legacyColdDir = join(this.#legacyConvDir, 'cold');
1173
1164
 
1174
- // Per-(sessionId, vpId) compact summary files live under that session's
1175
- // conversation directory. The legacy ~/.yeaft/conversation/compact directory
1176
- // is read for compatibility.
1177
- this.#legacyCompactScopedDir = join(this.#legacyConvDir, 'compact');
1178
1165
  this.#nextSeq = null;
1179
1166
  this.#nextSeqByThread = new Map();
1180
1167
 
@@ -1390,50 +1377,8 @@ export class ConversationStore {
1390
1377
  }
1391
1378
 
1392
1379
  /**
1393
- * Rewrite the compact summary in place.
1394
- *
1395
- * Compact's semantics are "the running summary of everything older than
1396
- * the hot window". Each compact pass already received the previous
1397
- * summary text as input and produced a *new* cumulative summary — so
1398
- * we overwrite, we never append. Appending was the original behaviour
1399
- * (kept around as a diary), but the engine reads the whole file back
1400
- * into `<conversation_summary>` on every turn, so appending grows the
1401
- * per-turn prompt without bound until it defeats compaction itself.
1402
- *
1403
- * @param {string} summary — the new, complete summary to persist
1404
- */
1405
- replaceCompactSummary(summary) {
1406
- if (typeof summary !== 'string' || !summary) return;
1407
- try {
1408
- writeFileSync(this.#compactPath, summary, { encoding: 'utf8', mode: 0o644 });
1409
- } catch (err) {
1410
- if (isPermissionError(err)) {
1411
- if (!_permissionWarned) {
1412
- console.warn(`[Yeaft] Cannot write compact summary: ${err.code}`);
1413
- _permissionWarned = true;
1414
- }
1415
- } else {
1416
- throw err;
1417
- }
1418
- }
1419
- }
1420
-
1421
- /**
1422
- * Read the compact summary.
1423
- *
1424
- * @returns {string}
1425
- */
1426
- readCompactSummary() {
1427
- if (existsSync(this.#compactPath)) return readFileSync(this.#compactPath, 'utf8');
1428
- if (existsSync(this.#legacyCompactPath)) return readFileSync(this.#legacyCompactPath, 'utf8');
1429
- return '';
1430
- }
1431
-
1432
- /**
1433
- * Sanitize one id (sessionId or vpId) into a safe filename component.
1380
+ * Sanitize one id into a safe directory component.
1434
1381
  * Anything outside `[A-Za-z0-9._-]` collapses to `_`; max 120 chars.
1435
- * For directory path components, use `#safeDirComponent` instead; this
1436
- * helper intentionally preserves historical compact-summary filenames.
1437
1382
  *
1438
1383
  * @param {string} s
1439
1384
  * @returns {string}
@@ -1447,101 +1392,6 @@ export class ConversationStore {
1447
1392
  return safe || '_';
1448
1393
  }
1449
1394
 
1450
- /**
1451
- * Sanitize a (sessionId, vpId) pair into a safe filename. We accept
1452
- * arbitrary user strings here (sessionIds and vpIds are user-set), so
1453
- * the result is purely a basename — never parsed back.
1454
- *
1455
- * @param {string} sessionId
1456
- * @param {string} vpId
1457
- * @returns {string|null} — full path, or null if either id missing
1458
- */
1459
- #scopedCompactPath(sessionId, vpId) {
1460
- if (!sessionId || !vpId) return null;
1461
- const compactDir = join(this.#sessionConversationDir(sessionId, { create: true }), 'compact');
1462
- return join(compactDir, `${this.#safeIdComponent(vpId)}.md`);
1463
- }
1464
-
1465
- #legacyScopedCompactPath(sessionId, vpId) {
1466
- if (!sessionId || !vpId) return null;
1467
- return join(this.#legacyCompactScopedDir, `${this.#safeIdComponent(sessionId)}__${this.#safeIdComponent(vpId)}.md`);
1468
- }
1469
-
1470
- /**
1471
- * Read a per-(sessionId, vpId) compact summary. Returns '' if no summary
1472
- * has been written yet. Falls back to nothing — callers that need the
1473
- * legacy global file should call `readCompactSummary()` explicitly.
1474
- *
1475
- * The (group, vp) scoping was introduced after we noticed the legacy
1476
- * single-file `compact.md` was shared across every group AND every VP
1477
- * in a session — so each new compact would clobber/append on top of
1478
- * unrelated content and every VP read the same merged blob. See
1479
- * `engine.#runOrchestratorCompact`.
1480
- *
1481
- * @param {string} sessionId
1482
- * @param {string} vpId
1483
- * @returns {string}
1484
- */
1485
- readCompactSummaryFor(sessionId, vpId) {
1486
- const path = this.#scopedCompactPath(sessionId, vpId);
1487
- const legacyPath = this.#legacyScopedCompactPath(sessionId, vpId);
1488
- for (const candidate of [path, legacyPath]) {
1489
- if (!candidate || !existsSync(candidate)) continue;
1490
- try { return readFileSync(candidate, 'utf8'); }
1491
- catch { return ''; }
1492
- }
1493
- return '';
1494
- }
1495
-
1496
- /**
1497
- * Rewrite a per-(sessionId, vpId) compact summary in place. See
1498
- * `replaceCompactSummary` for the rationale — same reason, scoped file.
1499
- *
1500
- * @param {string} sessionId
1501
- * @param {string} vpId
1502
- * @param {string} summary
1503
- */
1504
- replaceCompactSummaryFor(sessionId, vpId, summary) {
1505
- if (typeof summary !== 'string' || !summary) return;
1506
- const path = this.#scopedCompactPath(sessionId, vpId);
1507
- if (!path) return;
1508
- try {
1509
- writeFileSync(path, summary, { encoding: 'utf8', mode: 0o644 });
1510
- } catch (err) {
1511
- if (isPermissionError(err)) {
1512
- if (!_permissionWarned) {
1513
- console.warn(`[Yeaft] Cannot write scoped compact summary: ${err.code}`);
1514
- _permissionWarned = true;
1515
- }
1516
- } else {
1517
- throw err;
1518
- }
1519
- }
1520
- }
1521
-
1522
- /**
1523
- * Check whether ANY per-(session, vp) compact summary exists for `sessionId`.
1524
- * Used by the history-replay path to decide whether to flag
1525
- * `hasCompactSummary` for the UI without committing to one VP's view.
1526
- *
1527
- * @param {string} sessionId
1528
- * @returns {boolean}
1529
- */
1530
- hasAnyCompactSummaryForSession(sessionId) {
1531
- if (!sessionId) return false;
1532
- const compactDir = join(this.#sessionConversationDir(sessionId), 'compact');
1533
- for (const dir of [compactDir, this.#legacyCompactScopedDir]) {
1534
- if (!existsSync(dir)) continue;
1535
- try {
1536
- for (const f of readdirSync(dir)) {
1537
- if (dir === compactDir && f.endsWith('.md')) return true;
1538
- if (dir === this.#legacyCompactScopedDir && f.startsWith(`${this.#safeIdComponent(sessionId)}__`) && f.endsWith('.md')) return true;
1539
- }
1540
- } catch { /* best-effort */ }
1541
- }
1542
- return false;
1543
- }
1544
-
1545
1395
  /**
1546
1396
  * Update the conversation index.md with current state.
1547
1397
  *
@@ -1581,7 +1431,7 @@ export class ConversationStore {
1581
1431
  }
1582
1432
 
1583
1433
  /**
1584
- * Clear all messages (hot + cold + compact).
1434
+ * Clear all persisted transcript messages (hot + cold).
1585
1435
  */
1586
1436
  clear() {
1587
1437
  for (const dir of [this.#chatMsgDir, this.#chatColdDir, ...this.#sessionMessageDirs('messages'), ...this.#sessionMessageDirs('cold')]) {
@@ -1600,16 +1450,6 @@ export class ConversationStore {
1600
1450
  for (const dir of [this.#chatDir, ...this.#sessionConversationDirs({ primaryOnly: true })]) {
1601
1451
  this.#segmentStoreForConversationDir(dir).clear();
1602
1452
  }
1603
- // Reset compact files in the new chat/session stores. Legacy
1604
- // ~/.yeaft/conversation is intentionally left untouched.
1605
- for (const path of [this.#compactPath]) {
1606
- if (!existsSync(path)) continue;
1607
- try {
1608
- writeFileSync(path, '', { encoding: 'utf8', mode: 0o644 });
1609
- } catch (err) {
1610
- if (!isPermissionError(err)) throw err;
1611
- }
1612
- }
1613
1453
  this.#nextSeq = 1;
1614
1454
  this.updateIndex({ totalMessages: 0, lastMessageId: null });
1615
1455
  this.#markAllDirty('clear');
@@ -1709,8 +1549,7 @@ export class ConversationStore {
1709
1549
  includeReflections,
1710
1550
  });
1711
1551
  if (truncated) {
1712
- const hasCompact = this.hasAnyCompactSummaryForSession(sessionId);
1713
- maybeWarnHistoryTruncated(sessionId, this.#dir, turnsLimit, hasCompact);
1552
+ maybeWarnHistoryTruncated(sessionId, this.#dir, turnsLimit);
1714
1553
  }
1715
1554
  return pairSanitize(messages);
1716
1555
  }
@@ -1726,12 +1565,11 @@ export class ConversationStore {
1726
1565
  }
1727
1566
 
1728
1567
  /**
1729
- * VP-scoped view of group history, used by per-VP post-turn compact.
1568
+ * VP-scoped view of Session history, used to build a pair-safe provider
1569
+ * snapshot for one VP. It operates on what the VP can actually see, not
1570
+ * the union of every VP's tool calls/results.
1730
1571
  *
1731
- * Compact must operate on what the VP actually *saw* in its context,
1732
- * not the union of every VP's tool calls/results — otherwise compact
1733
- * tries to summarize tool transcripts that were never in this VP's
1734
- * prompt window. The rule we settled on (with the user, 2026-06-01):
1572
+ * The rule we settled on (with the user, 2026-06-01):
1735
1573
  *
1736
1574
  * - User rows (no speakerVpId): KEEP — every VP sees the prompt.
1737
1575
  * - This VP's own assistant rows + their paired tool rows: KEEP.
@@ -1741,8 +1579,8 @@ export class ConversationStore {
1741
1579
  * - OTHER VPs' tool result rows (role:'tool'): DROP — they pair with
1742
1580
  * stripped tool_use ids and would orphan on replay.
1743
1581
  * - Hidden conversation rows (`_reflection`, `internal`, `systemOnly`,
1744
- * `systemOnlyMessage`, compact summaries, and legacy internal-control
1745
- * content signatures): DROP — they are engine-private and never enter
1582
+ * `systemOnlyMessage`, and legacy internal-control content signatures):
1583
+ * DROP — they are engine-private and never enter
1746
1584
  * visible history or another VP's context.
1747
1585
  *
1748
1586
  * The output is pair-safe by construction for THIS VP's tool arcs and
@@ -1774,7 +1612,7 @@ export class ConversationStore {
1774
1612
  } else {
1775
1613
  // Other VP's assistant text only — drop their toolCalls so the
1776
1614
  // following role:'tool' rows (which we also drop) don't leave
1777
- // orphan tool_use ids in the compact input.
1615
+ // orphan tool_use ids in the provider input.
1778
1616
  const copy = { ...m };
1779
1617
  delete copy.toolCalls;
1780
1618
  delete copy.thinkingBlocks;
@@ -1790,10 +1628,8 @@ export class ConversationStore {
1790
1628
  continue;
1791
1629
  }
1792
1630
  }
1793
- // Note: we don't run `sliceLastNTurns` here. The caller
1794
- // (#runOrchestratorCompact) decides what's "cooling" via
1795
- // `partitionMessages`, and we don't want to pre-truncate before that
1796
- // budget calc sees the full picture.
1631
+ // Do not truncate here. The provider-request history window owns
1632
+ // deterministic budget trimming after this VP-scoped view is built.
1797
1633
  return pairSanitize(out);
1798
1634
  }
1799
1635
 
@@ -2696,36 +2532,6 @@ export class ConversationStore {
2696
2532
  .filter(dir => existsSync(dir));
2697
2533
  }
2698
2534
 
2699
- /** Per-chat scoped compact summary path. */
2700
- #scopedChatCompactPath(chatId, vpId) {
2701
- if (!chatId || !vpId) return null;
2702
- const dir = join(this.#chatConversationDir(chatId, { create: true }), 'compact');
2703
- return join(dir, `${this.#safeIdComponent(vpId)}.md`);
2704
- }
2705
-
2706
- /** Read per-(chatId, vpId) compact summary. */
2707
- readCompactSummaryForChat(chatId, vpId) {
2708
- const p = this.#scopedChatCompactPath(chatId, vpId);
2709
- if (!p || !existsSync(p)) return '';
2710
- try { return readFileSync(p, 'utf8'); } catch { return ''; }
2711
- }
2712
-
2713
- /** Rewrite per-(chatId, vpId) compact summary. */
2714
- replaceCompactSummaryForChat(chatId, vpId, summary) {
2715
- if (typeof summary !== 'string' || !summary) return;
2716
- const p = this.#scopedChatCompactPath(chatId, vpId);
2717
- if (!p) return;
2718
- try { writeFileSync(p, summary, { encoding: 'utf8', mode: 0o644 }); }
2719
- catch (err) {
2720
- if (isPermissionError(err)) {
2721
- if (!_permissionWarned) {
2722
- console.warn(`[Yeaft] Cannot write scoped chat compact summary: ${err.code}`);
2723
- _permissionWarned = true;
2724
- }
2725
- } else throw err;
2726
- }
2727
- }
2728
-
2729
2535
  /** Recent messages for a chat — chat mode mirror of loadRecentBySession. */
2730
2536
  loadRecentByChat(chatId, turnsLimit = DEFAULT_RECENT_TURNS) {
2731
2537
  if (!chatId) return [];
@@ -2773,7 +2579,7 @@ export class ConversationStore {
2773
2579
  }
2774
2580
 
2775
2581
  #ensureConversationDirs(dir) {
2776
- for (const d of [dir, join(dir, 'blobs'), join(dir, SEGMENT_DIR), join(dir, 'messages'), join(dir, 'cold'), join(dir, 'compact')]) {
2582
+ for (const d of [dir, join(dir, 'blobs'), join(dir, SEGMENT_DIR), join(dir, 'messages'), join(dir, 'cold')]) {
2777
2583
  if (!existsSync(d)) mkdirSync(d, { recursive: true, mode: 0o755 });
2778
2584
  }
2779
2585
  }
@@ -101,7 +101,7 @@ function flushAssistantEntries(sessionId, entries, boundaryMessageId = null) {
101
101
  * bucket above it. That boundary lets interleaved VP rows (A-B-A) coalesce by
102
102
  * explicit turnId + speaker without scanning the whole transcript. Legacy
103
103
  * assistant rows without a turnId remain separate because guessing from
104
- * adjacency would make their identity change after prepend or compaction.
104
+ * adjacency would make their identity change after prepend or transcript edits.
105
105
  */
106
106
  export function* iterateCanonicalVisibleEntriesNewestFirst(messages, sessionId) {
107
107
  const seenMessageIds = new Set();
package/yeaft/effort.js CHANGED
@@ -38,7 +38,6 @@ export const LONG_LOOP_TURN_THRESHOLD = 8;
38
38
  * Tiers (6 scenarios per architect spec):
39
39
  * chat → max (default interactive pair-programming turn —
40
40
  * quality over latency; per user 2026-05-22)
41
- * consolidate → max (memory compaction — quality matters, runs once)
42
41
  * dream → max (memory maintenance — same rationale)
43
42
  * sub_agent → max (coordinator spawns + merges)
44
43
  * long_loop → max (auto-bumped when toolLoopTurns >= threshold)
@@ -49,7 +48,6 @@ export const LONG_LOOP_TURN_THRESHOLD = 8;
49
48
  */
50
49
  export const SCENARIO_EFFORT = Object.freeze({
51
50
  chat: 'max',
52
- consolidate: 'max',
53
51
  dream: 'max',
54
52
  sub_agent: 'max',
55
53
  long_loop: 'max',