@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.
- package/package.json +1 -1
- package/unify/engine.js +128 -1
package/package.json
CHANGED
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
|
*
|