@yeaft/webchat-agent 0.1.599 → 0.1.600

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 (2) hide show
  1. package/package.json +1 -1
  2. package/unify/engine.js +128 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yeaft/webchat-agent",
3
- "version": "0.1.599",
3
+ "version": "0.1.600",
4
4
  "description": "Remote agent for Yeaft WebChat — connects worker machines to the central server",
5
5
  "main": "index.js",
6
6
  "type": "module",
package/unify/engine.js CHANGED
@@ -21,7 +21,10 @@ import { randomUUID } from 'crypto';
21
21
  import { buildSystemPrompt, buildWorkerPrompt } from './prompts.js';
22
22
  import { LLMContextError, LLMAbortError } from './llm/adapter.js';
23
23
  import { recallR6, formatForInjection } from './memory/recall-r6.js';
24
- import { shouldConsolidate, consolidate } from './memory/consolidate.js';
24
+ import { shouldConsolidate, consolidate, partitionMessages } from './memory/consolidate.js';
25
+ import { extractMemories } from './memory/extract.js';
26
+ import { runCompact as runCompactOrchestrator } from './compact/orchestrator.js';
27
+ import { evaluateCompactTriggers } from './compact/triggers.js';
25
28
  import { buildMemoryInjection } from './memory/layout.js';
26
29
  import { buildUserProfile } from './memory/user-memory-store.js';
27
30
  import { readSummary as readScopeSummary } from './memory/scope-tree.js';
@@ -530,6 +533,35 @@ export class Engine {
530
533
  if (this.#config._readOnly) return null;
531
534
 
532
535
  const budget = this.#config.messageTokenBudget || 8192;
536
+ const compactCfg = (this.#config && this.#config.compact) || {};
537
+
538
+ // Phase 8 PR-D: orchestrator opt-in. evaluateCompactTriggers (DESIGN
539
+ // §4.1) and runCompact (DESIGN §4.2) are the new path. Existing
540
+ // shouldConsolidate / consolidate stays as the default to preserve
541
+ // production behaviour; flip via config.compact.useOrchestrator=true.
542
+ if (compactCfg.useOrchestrator) {
543
+ return this.#runOrchestratorCompact(budget, compactCfg);
544
+ }
545
+
546
+ // Default path — surface trigger reasons via trace for observability
547
+ // even when we still fall through to the legacy consolidate.
548
+ try {
549
+ const messages = this.#conversationStore.loadAll();
550
+ const tokenCount = this.#conversationStore.hotTokens();
551
+ const trig = evaluateCompactTriggers({
552
+ messages,
553
+ tokenCount,
554
+ contextLimit: this.#config.maxContextTokens || 200000,
555
+ tokenRatio: compactCfg.tokenRatio,
556
+ maxMessages: compactCfg.maxMessages,
557
+ });
558
+ this.#trace.logEvent && this.#trace.logEvent({
559
+ traceId: 'compact_triggers_eval',
560
+ eventType: 'compact_triggers_eval',
561
+ eventData: { trigger: trig.trigger, reasons: trig.reasons },
562
+ });
563
+ } catch { /* observability only */ }
564
+
533
565
  if (!shouldConsolidate(this.#conversationStore, budget)) return null;
534
566
 
535
567
  try {
@@ -547,6 +579,101 @@ export class Engine {
547
579
  }
548
580
  }
549
581
 
582
+ /**
583
+ * Phase 8 PR-D: run compact via the new orchestrator (DESIGN §4.2).
584
+ * Hooks adapt the orchestrator's injectable contract to the existing
585
+ * conversationStore / memoryStore primitives, so behaviour matches
586
+ * the legacy `consolidate` path 1:1 while exercising the new
587
+ * triggers / turn-group / orchestrator code on the live path.
588
+ *
589
+ * @param {number} budget
590
+ * @param {object} compactCfg
591
+ * @returns {Promise<{archivedCount:number, extractedCount:number}|null>}
592
+ */
593
+ async #runOrchestratorCompact(budget, _compactCfg) {
594
+ const conversationStore = this.#conversationStore;
595
+ const memoryStore = this.#memoryStore;
596
+ const adapter = this.#adapter;
597
+ const fastConfig = this.#fastConfig;
598
+
599
+ let messages;
600
+ try {
601
+ messages = conversationStore.loadAll();
602
+ } catch { return null; }
603
+ if (!Array.isArray(messages) || messages.length === 0) return null;
604
+
605
+ const tokenCount = conversationStore.hotTokens();
606
+ const trig = evaluateCompactTriggers({
607
+ messages,
608
+ tokenCount,
609
+ contextLimit: this.#config.maxContextTokens || 200000,
610
+ });
611
+ if (!trig.trigger) return null;
612
+
613
+ // Use partitionMessages (the legacy primitive) to decide what is
614
+ // "cooling": orchestrator's own keepHot is a count, but we want to
615
+ // honour the token-budget partitioning the rest of the system uses.
616
+ const { toArchive } = partitionMessages(messages, budget);
617
+ if (toArchive.length === 0) return null;
618
+
619
+ const archiveIds = [];
620
+
621
+ const hooks = {
622
+ summarise: async () => {
623
+ // Reuse the legacy consolidate path's summary technique by
624
+ // invoking adapter directly with a fresh prompt. We keep the
625
+ // orchestrator's contract honoured: it takes the cooling slice
626
+ // and returns a string.
627
+ try {
628
+ const result = await adapter.call({
629
+ model: fastConfig.model,
630
+ system: 'You are a conversation summarizer. Summarize concisely in 2–3 paragraphs, preserving decisions, facts, and context.',
631
+ messages: [{ role: 'user', content: `Summarize:\n\n${toArchive.map(m => `[${m.role}] ${(m.content || '').slice(0, 500)}`).join('\n\n')}` }],
632
+ maxTokens: 1024,
633
+ });
634
+ return (result.text || '').trim();
635
+ } catch {
636
+ return '';
637
+ }
638
+ },
639
+ archive: async (_groupIdx, groupMsgs) => {
640
+ for (const m of groupMsgs) if (m.id) archiveIds.push(m.id);
641
+ return { turnId: groupMsgs[0]?.id || `g_${Date.now()}` };
642
+ },
643
+ extract: async (coolingMessages) => {
644
+ try {
645
+ const extracted = await extractMemories({
646
+ messages: coolingMessages, adapter, config: fastConfig,
647
+ });
648
+ for (const e of extracted) memoryStore.writeEntry(e);
649
+ if (extracted.length > 0) memoryStore.rebuildScopes();
650
+ return { written: extracted.length };
651
+ } catch {
652
+ return { written: 0 };
653
+ }
654
+ },
655
+ };
656
+
657
+ try {
658
+ const out = await runCompactOrchestrator({
659
+ messages, keepHot: 10, hooks,
660
+ });
661
+ // Apply side effects to the conversation store: move archived
662
+ // ids to cold, persist compact summary, update index.
663
+ if (archiveIds.length > 0) conversationStore.moveToColdBatch(archiveIds);
664
+ if (out.compactSummary) conversationStore.updateCompactSummary(out.compactSummary);
665
+ const lastKept = messages[messages.length - 1];
666
+ conversationStore.updateIndex({ lastMessageId: lastKept?.id || null });
667
+
668
+ return {
669
+ archivedCount: out.archivedMessages,
670
+ extractedCount: out.extractedCount,
671
+ };
672
+ } catch {
673
+ return null;
674
+ }
675
+ }
676
+
550
677
  /**
551
678
  * Run a query — the main loop.
552
679
  *