@caupulican/pi-agent-core 0.81.39 → 0.81.41
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/README.md +10 -5
- package/dist/agent-loop.d.ts.map +1 -1
- package/dist/agent-loop.js +77 -89
- package/dist/agent-loop.js.map +1 -1
- package/dist/compaction/branch-summarization.d.ts +4 -2
- package/dist/compaction/branch-summarization.d.ts.map +1 -1
- package/dist/compaction/branch-summarization.js +4 -0
- package/dist/compaction/branch-summarization.js.map +1 -1
- package/dist/compaction/compaction.d.ts +32 -1
- package/dist/compaction/compaction.d.ts.map +1 -1
- package/dist/compaction/compaction.js +172 -101
- package/dist/compaction/compaction.js.map +1 -1
- package/dist/compaction/extraction.d.ts.map +1 -1
- package/dist/compaction/extraction.js +1 -1
- package/dist/compaction/extraction.js.map +1 -1
- package/dist/compaction/loop.d.ts +1 -1
- package/dist/compaction/loop.d.ts.map +1 -1
- package/dist/compaction/loop.js +24 -7
- package/dist/compaction/loop.js.map +1 -1
- package/dist/compaction/utils.d.ts.map +1 -1
- package/dist/compaction/utils.js +3 -1
- package/dist/compaction/utils.js.map +1 -1
- package/dist/compaction/verification.d.ts +11 -0
- package/dist/compaction/verification.d.ts.map +1 -1
- package/dist/compaction/verification.js +93 -11
- package/dist/compaction/verification.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/proxy.d.ts +1 -1
- package/dist/proxy.d.ts.map +1 -1
- package/dist/proxy.js +1 -0
- package/dist/proxy.js.map +1 -1
- package/dist/reliability/classifier.d.ts.map +1 -1
- package/dist/reliability/classifier.js +3 -3
- package/dist/reliability/classifier.js.map +1 -1
- package/dist/session/session-manager.d.ts +18 -6
- package/dist/session/session-manager.d.ts.map +1 -1
- package/dist/session/session-manager.js +68 -61
- package/dist/session/session-manager.js.map +1 -1
- package/dist/tool-failure-memory.d.ts +35 -0
- package/dist/tool-failure-memory.d.ts.map +1 -0
- package/dist/tool-failure-memory.js +290 -0
- package/dist/tool-failure-memory.js.map +1 -0
- package/dist/types.d.ts +5 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/usage.d.ts +5 -0
- package/dist/usage.d.ts.map +1 -1
- package/dist/usage.js +32 -0
- package/dist/usage.js.map +1 -1
- package/dist/uuid.d.ts +1 -1
- package/dist/uuid.d.ts.map +1 -1
- package/dist/uuid.js +1 -49
- package/dist/uuid.js.map +1 -1
- package/package.json +2 -2
|
@@ -4,12 +4,13 @@
|
|
|
4
4
|
* Pure functions for compaction logic. The session manager handles I/O,
|
|
5
5
|
* and after compaction the session is reloaded.
|
|
6
6
|
*/
|
|
7
|
-
import { completeSimple } from "@caupulican/pi-ai";
|
|
7
|
+
import { completeSimple, uuidv7 } from "@caupulican/pi-ai";
|
|
8
8
|
import { convertToLlm, createBranchSummaryMessage, createCompactionSummaryMessage, createCustomMessage, } from "../messages.js";
|
|
9
9
|
import { buildSessionContext } from "../session/session-manager.js";
|
|
10
|
+
import { addUsage, combineUsage, createEmptyUsage } from "../usage.js";
|
|
10
11
|
import { extractCompactionFacts, renderFactsBlock } from "./extraction.js";
|
|
11
12
|
import { computeFileLists, createFileOps, extractFileOpsFromMessage, SUMMARIZATION_SYSTEM_PROMPT, serializeConversation, } from "./utils.js";
|
|
12
|
-
import { buildRetryPrompt, deterministicallyFillSummaryGaps, isCompactionSummaryStructurallyUsable, verifySummary, } from "./verification.js";
|
|
13
|
+
import { buildRetryPrompt, CompactionVerificationError, deterministicallyFillSummaryGaps, isCompactionSummaryStructurallyUsable, verifySummary, } from "./verification.js";
|
|
13
14
|
/**
|
|
14
15
|
* Extract file operations from messages and previous compaction entries.
|
|
15
16
|
*/
|
|
@@ -65,6 +66,60 @@ function getMessageFromEntryForCompaction(entry) {
|
|
|
65
66
|
}
|
|
66
67
|
return getMessageFromEntry(entry);
|
|
67
68
|
}
|
|
69
|
+
/**
|
|
70
|
+
* Carry failed LLM verification attempts into the result that the retry ladder eventually applies.
|
|
71
|
+
* Only bounded numeric/check identifiers are persisted in details; raw facts stay in the in-memory reports.
|
|
72
|
+
*/
|
|
73
|
+
export function mergeCompactionVerificationReports(result, reports) {
|
|
74
|
+
if (reports.length === 0)
|
|
75
|
+
return result;
|
|
76
|
+
const combinedReports = [
|
|
77
|
+
...reports.map(cloneVerificationReport),
|
|
78
|
+
...(result.verificationGateFailures ?? []).map(cloneVerificationReport),
|
|
79
|
+
];
|
|
80
|
+
result.verificationGateFailures = combinedReports;
|
|
81
|
+
if (result.details === undefined || isPlainRecord(result.details)) {
|
|
82
|
+
const details = result.details ?? {};
|
|
83
|
+
result.details = {
|
|
84
|
+
...details,
|
|
85
|
+
verificationGateFailures: combinedReports.length,
|
|
86
|
+
verificationGateChecks: aggregateVerificationChecks(combinedReports),
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
return result;
|
|
90
|
+
}
|
|
91
|
+
function cloneVerificationReport(report) {
|
|
92
|
+
return {
|
|
93
|
+
ok: report.ok,
|
|
94
|
+
failures: report.failures.map((failure) => ({ ...failure })),
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
function isPlainRecord(value) {
|
|
98
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
99
|
+
return false;
|
|
100
|
+
const prototype = Object.getPrototypeOf(value);
|
|
101
|
+
return prototype === Object.prototype || prototype === null;
|
|
102
|
+
}
|
|
103
|
+
function aggregateVerificationChecks(reports) {
|
|
104
|
+
const checks = new Map();
|
|
105
|
+
for (const report of reports) {
|
|
106
|
+
for (const failure of report.failures) {
|
|
107
|
+
const current = checks.get(failure.check) ?? { failures: 0 };
|
|
108
|
+
current.failures++;
|
|
109
|
+
if (failure.score !== undefined && Number.isFinite(failure.score)) {
|
|
110
|
+
current.minScore = Math.min(current.minScore ?? failure.score, failure.score);
|
|
111
|
+
current.maxScore = Math.max(current.maxScore ?? failure.score, failure.score);
|
|
112
|
+
}
|
|
113
|
+
if (failure.threshold !== undefined && Number.isFinite(failure.threshold)) {
|
|
114
|
+
current.threshold = failure.threshold;
|
|
115
|
+
}
|
|
116
|
+
if (failure.comparator)
|
|
117
|
+
current.comparator = failure.comparator;
|
|
118
|
+
checks.set(failure.check, current);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return Object.fromEntries(checks);
|
|
122
|
+
}
|
|
68
123
|
export const DEFAULT_COMPACTION_SETTINGS = {
|
|
69
124
|
enabled: true,
|
|
70
125
|
reserveTokens: 16384,
|
|
@@ -108,20 +163,33 @@ export function getLastAssistantUsage(entries) {
|
|
|
108
163
|
}
|
|
109
164
|
return undefined;
|
|
110
165
|
}
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
166
|
+
/**
|
|
167
|
+
* Find the newest assistant usage that still describes the current message prefix.
|
|
168
|
+
*
|
|
169
|
+
* Compaction can insert a newer summary before retained older messages. Usage recorded by an
|
|
170
|
+
* assistant before that inserted message describes the pre-compaction prefix and must not anchor
|
|
171
|
+
* the rebuilt context estimate. Walking forward lets the newest timestamp in the prefix invalidate
|
|
172
|
+
* those stale usage blocks while still accepting the first response produced after compaction.
|
|
173
|
+
*/
|
|
174
|
+
export function getApplicableAssistantUsageInfo(messages) {
|
|
175
|
+
let latestPrefixTimestamp = Number.NEGATIVE_INFINITY;
|
|
176
|
+
let usageInfo;
|
|
177
|
+
for (let i = 0; i < messages.length; i++) {
|
|
178
|
+
const message = messages[i];
|
|
179
|
+
const usage = getAssistantUsage(message);
|
|
180
|
+
if (usage && message.timestamp >= latestPrefixTimestamp) {
|
|
181
|
+
usageInfo = { usage, index: i };
|
|
182
|
+
}
|
|
183
|
+
latestPrefixTimestamp = Math.max(latestPrefixTimestamp, message.timestamp);
|
|
116
184
|
}
|
|
117
|
-
return
|
|
185
|
+
return usageInfo;
|
|
118
186
|
}
|
|
119
187
|
/**
|
|
120
188
|
* Estimate context tokens from messages, using the last assistant usage when available.
|
|
121
189
|
* If there are messages after the last usage, estimate their tokens with estimateTokens.
|
|
122
190
|
*/
|
|
123
191
|
export function estimateContextTokens(messages) {
|
|
124
|
-
const usageInfo =
|
|
192
|
+
const usageInfo = getApplicableAssistantUsageInfo(messages);
|
|
125
193
|
if (!usageInfo) {
|
|
126
194
|
let estimated = 0;
|
|
127
195
|
for (const message of messages) {
|
|
@@ -418,11 +486,16 @@ Same section order. Verification checklist (the verifier checks exactly these ch
|
|
|
418
486
|
|
|
419
487
|
Budget: ~{BUDGET} tokens.`;
|
|
420
488
|
function createSummarizationOptions(model, maxTokens, apiKey, headers, signal, thinkingLevel) {
|
|
421
|
-
//
|
|
422
|
-
//
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
489
|
+
// Summaries are one-shot prompts. A fresh affinity identity prevents them from contaminating the
|
|
490
|
+
// foreground continuation cache, while "none" prevents cache writes that cannot be reused.
|
|
491
|
+
const options = {
|
|
492
|
+
maxTokens,
|
|
493
|
+
signal,
|
|
494
|
+
apiKey,
|
|
495
|
+
headers,
|
|
496
|
+
cacheRetention: "none",
|
|
497
|
+
sessionId: uuidv7(),
|
|
498
|
+
};
|
|
426
499
|
if (model.reasoning && thinkingLevel && thinkingLevel !== "off") {
|
|
427
500
|
options.reasoning = thinkingLevel;
|
|
428
501
|
}
|
|
@@ -467,7 +540,11 @@ async function prepareSummarizationConversationText(currentMessages, preDigest,
|
|
|
467
540
|
* the same message span across multiple attempts (a verification-gate retry), compute this once
|
|
468
541
|
* via {@link prepareSummarizationConversationText} and pass it to every attempt.
|
|
469
542
|
*/
|
|
470
|
-
export async function generateSummary(currentMessages, model, reserveTokens, apiKey, headers, signal, customInstructions, previousSummary, thinkingLevel, streamFn, preDigest, factsBlock = "verification demands:\nfiles-modified-recall (must appear in ## Files):\nfiles-read-recall (must appear in ## Files,
|
|
543
|
+
export async function generateSummary(currentMessages, model, reserveTokens, apiKey, headers, signal, customInstructions, previousSummary, thinkingLevel, streamFn, preDigest, factsBlock = "verification demands:\nfiles-modified-recall (must appear in ## Files):\nfiles-read-recall (must appear as exact paths in ## Files, path recall threshold applies):\nworking-set-recall (must appear in ## Working Set):\nopen-errors-recall (must appear in ## Open Problems):\nactions-recall (must appear in ## Done):\nmandatory-rules-recall (must appear in ### Mandatory Rules):\nactive-task-containment (must appear in ## Active Task):\ncancelled-work-dropped (must NOT appear outside ### Mandatory Rules):", chunked = false, precomputedConversationText) {
|
|
544
|
+
return (await generateSummaryWithUsage(currentMessages, model, reserveTokens, apiKey, headers, signal, customInstructions, previousSummary, thinkingLevel, streamFn, preDigest, factsBlock, chunked, precomputedConversationText)).text;
|
|
545
|
+
}
|
|
546
|
+
export async function generateSummaryWithUsage(currentMessages, model, reserveTokens, apiKey, headers, signal, customInstructions, previousSummary, thinkingLevel, streamFn, preDigest, factsBlock = "verification demands:\nfiles-modified-recall (must appear in ## Files):\nfiles-read-recall (must appear as exact paths in ## Files, path recall threshold applies):\nworking-set-recall (must appear in ## Working Set):\nopen-errors-recall (must appear in ## Open Problems):\nactions-recall (must appear in ## Done):\nmandatory-rules-recall (must appear in ### Mandatory Rules):\nactive-task-containment (must appear in ## Active Task):\ncancelled-work-dropped (must NOT appear outside ### Mandatory Rules):", chunked = false, precomputedConversationText) {
|
|
547
|
+
const usage = createEmptyUsage();
|
|
471
548
|
const summaryBudget = getSummaryBudget(reserveTokens, model, factsBlock);
|
|
472
549
|
const maxTokens = summaryBudget;
|
|
473
550
|
let promptSuffix = fillPromptTemplate(previousSummary ? UPDATE_SUMMARIZATION_PROMPT : SUMMARIZATION_PROMPT, factsBlock, summaryBudget);
|
|
@@ -483,7 +560,7 @@ export async function generateSummary(currentMessages, model, reserveTokens, api
|
|
|
483
560
|
if (!chunked) {
|
|
484
561
|
throw new Error("input-overflow: summarization request exceeds summarizer window");
|
|
485
562
|
}
|
|
486
|
-
conversationText = await summarizeChunks(conversationText, model, maxTokens, apiKey, headers, signal, thinkingLevel, streamFn, inputBound, previousSummary, promptSuffix);
|
|
563
|
+
conversationText = await summarizeChunks(conversationText, model, maxTokens, apiKey, headers, signal, thinkingLevel, streamFn, inputBound, previousSummary, promptSuffix, usage);
|
|
487
564
|
}
|
|
488
565
|
const promptText = buildSummarizationPrompt(conversationText, previousSummary, promptSuffix);
|
|
489
566
|
if (estimateStringTokens(promptText) > inputBound) {
|
|
@@ -499,6 +576,7 @@ export async function generateSummary(currentMessages, model, reserveTokens, api
|
|
|
499
576
|
},
|
|
500
577
|
],
|
|
501
578
|
}, createSummarizationOptions(model, maxTokens, apiKey, headers, signal, thinkingLevel), streamFn);
|
|
579
|
+
addUsage(usage, response.usage);
|
|
502
580
|
if (response.stopReason === "error") {
|
|
503
581
|
throw new Error(`Summarization failed: ${response.errorMessage || "Unknown error"}`);
|
|
504
582
|
}
|
|
@@ -507,7 +585,7 @@ export async function generateSummary(currentMessages, model, reserveTokens, api
|
|
|
507
585
|
if (response.stopReason === "length") {
|
|
508
586
|
throw new Error("summary-length-stop: summarizer hit its output cap before completing the checkpoint");
|
|
509
587
|
}
|
|
510
|
-
return truncateSummaryToBudget(extractTextContent(response), summaryBudget);
|
|
588
|
+
return { text: truncateSummaryToBudget(extractTextContent(response), summaryBudget), usage };
|
|
511
589
|
}
|
|
512
590
|
function fillPromptTemplate(template, factsBlock, budget) {
|
|
513
591
|
return template.replaceAll("{FACTS_BLOCK}", factsBlock).replaceAll("{BUDGET}", String(budget));
|
|
@@ -569,10 +647,10 @@ export function getChunkSummarizationTokenBudget(inputBound) {
|
|
|
569
647
|
export function buildChunkSummarizationPrompt(chunk, index, total) {
|
|
570
648
|
return `<conversation-chunk index="${index}" total="${total}">\n${chunk}\n</conversation-chunk>\n\nSummarize this chunk for a later checkpoint merge. Preserve exact file paths, commands, errors, user prohibitions, and active work. Output concise notes only.`;
|
|
571
649
|
}
|
|
572
|
-
async function summarizeChunks(conversationText, model, maxTokens, apiKey, headers, signal, thinkingLevel, streamFn, inputBound, previousSummary, promptSuffix) {
|
|
650
|
+
async function summarizeChunks(conversationText, model, maxTokens, apiKey, headers, signal, thinkingLevel, streamFn, inputBound, previousSummary, promptSuffix, usage) {
|
|
573
651
|
let reducedText = conversationText;
|
|
574
652
|
for (let pass = 0; pass < 3; pass++) {
|
|
575
|
-
const summary = await summarizeChunkPass(reducedText, model, maxTokens, apiKey, headers, signal, thinkingLevel, streamFn, inputBound);
|
|
653
|
+
const summary = await summarizeChunkPass(reducedText, model, maxTokens, apiKey, headers, signal, thinkingLevel, streamFn, inputBound, usage);
|
|
576
654
|
if (estimateStringTokens(buildSummarizationPrompt(summary, previousSummary, promptSuffix)) <= inputBound) {
|
|
577
655
|
return summary;
|
|
578
656
|
}
|
|
@@ -580,7 +658,7 @@ async function summarizeChunks(conversationText, model, maxTokens, apiKey, heade
|
|
|
580
658
|
}
|
|
581
659
|
throw new Error("input-overflow: chunked summarization merge still exceeds summarizer window");
|
|
582
660
|
}
|
|
583
|
-
async function summarizeChunkPass(conversationText, model, maxTokens, apiKey, headers, signal, thinkingLevel, streamFn, inputBound) {
|
|
661
|
+
async function summarizeChunkPass(conversationText, model, maxTokens, apiKey, headers, signal, thinkingLevel, streamFn, inputBound, usage) {
|
|
584
662
|
const maxChunkTokens = getChunkSummarizationTokenBudget(inputBound);
|
|
585
663
|
const maxChunkChars = Math.max(1, maxChunkTokens * 4);
|
|
586
664
|
const chunks = splitText(conversationText, maxChunkChars);
|
|
@@ -597,6 +675,7 @@ async function summarizeChunkPass(conversationText, model, maxTokens, apiKey, he
|
|
|
597
675
|
},
|
|
598
676
|
],
|
|
599
677
|
}, createSummarizationOptions(model, maxTokens, apiKey, headers, signal, thinkingLevel), streamFn);
|
|
678
|
+
addUsage(usage, response.usage);
|
|
600
679
|
if (response.stopReason === "error") {
|
|
601
680
|
throw new Error(`Summarization failed: ${response.errorMessage || "Unknown error"}`);
|
|
602
681
|
}
|
|
@@ -744,6 +823,40 @@ Summarize the prefix to provide context for the retained suffix:
|
|
|
744
823
|
- [Information needed to understand the retained recent work]
|
|
745
824
|
|
|
746
825
|
Be concise. Focus on what's needed to understand the kept suffix.`;
|
|
826
|
+
async function generateVerifiedSummary(options) {
|
|
827
|
+
let retryInstructions = options.customInstructions;
|
|
828
|
+
const usage = createEmptyUsage();
|
|
829
|
+
const verificationGateFailures = [];
|
|
830
|
+
const precomputedConversationText = await prepareSummarizationConversationText(options.messages, options.preDigest, options.signal);
|
|
831
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
832
|
+
const generated = await generateSummaryWithUsage(options.messages, options.model, options.reserveTokens, options.apiKey, options.headers, options.signal, retryInstructions, options.previousSummary, options.thinkingLevel, options.streamFn, options.preDigest, options.factsBlock, options.chunked, precomputedConversationText);
|
|
833
|
+
addUsage(usage, generated.usage);
|
|
834
|
+
const summary = generated.text;
|
|
835
|
+
const verification = verifySummary(summary, options.facts);
|
|
836
|
+
if (verification.ok) {
|
|
837
|
+
return { summary, usage, verification, verificationGateFailures, deterministicGapFills: 0 };
|
|
838
|
+
}
|
|
839
|
+
verificationGateFailures.push(verification);
|
|
840
|
+
if (!isCompactionSummaryStructurallyUsable(summary)) {
|
|
841
|
+
if (attempt >= 1)
|
|
842
|
+
throw new CompactionVerificationError(verificationGateFailures);
|
|
843
|
+
retryInstructions = buildRetryPrompt(verification, summary);
|
|
844
|
+
continue;
|
|
845
|
+
}
|
|
846
|
+
const filled = deterministicallyFillSummaryGaps(summary, options.facts);
|
|
847
|
+
if (filled.verification.ok) {
|
|
848
|
+
return {
|
|
849
|
+
summary: filled.summary,
|
|
850
|
+
usage,
|
|
851
|
+
verification: filled.verification,
|
|
852
|
+
verificationGateFailures,
|
|
853
|
+
deterministicGapFills: filled.changed ? 1 : 0,
|
|
854
|
+
};
|
|
855
|
+
}
|
|
856
|
+
throw new CompactionVerificationError(verificationGateFailures);
|
|
857
|
+
}
|
|
858
|
+
throw new CompactionVerificationError(verificationGateFailures);
|
|
859
|
+
}
|
|
747
860
|
/**
|
|
748
861
|
* Generate summaries for compaction using prepared data.
|
|
749
862
|
* Returns CompactionResult - SessionManager adds uuid/parentUuid when saving.
|
|
@@ -764,95 +877,50 @@ export async function compact(preparation, model, apiKey, headers, customInstruc
|
|
|
764
877
|
delegatedWorkerFacts: [],
|
|
765
878
|
};
|
|
766
879
|
const factsBlock = renderFactsBlock(facts);
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
}
|
|
792
|
-
verificationGateFailures.push(verification);
|
|
793
|
-
const filled = deterministicallyFillSummaryGaps(historySummary, facts);
|
|
794
|
-
if (filled.verification.ok) {
|
|
795
|
-
historySummary = filled.summary;
|
|
796
|
-
verification = filled.verification;
|
|
797
|
-
if (filled.changed)
|
|
798
|
-
deterministicGapFills++;
|
|
799
|
-
break;
|
|
800
|
-
}
|
|
801
|
-
throw new Error(`gate-failed: ${formatVerificationFailures(filled.verification)}`);
|
|
802
|
-
}
|
|
803
|
-
const turnPrefixSummary = await generateTurnPrefixSummary(turnPrefixMessages, model, settings.reserveTokens, apiKey, headers, signal, thinkingLevel, streamFn);
|
|
804
|
-
summary = `${historySummary}\n\n---\n\n**Turn Context (split turn):**\n\n${turnPrefixSummary}`;
|
|
805
|
-
}
|
|
806
|
-
else {
|
|
807
|
-
let customSummaryInstructions = customInstructions;
|
|
808
|
-
// See the isSplitTurn branch above: same span/preDigest across attempts, computed once.
|
|
809
|
-
const precomputedConversationText = await prepareSummarizationConversationText(messagesToSummarize, preDigest, signal);
|
|
810
|
-
for (let attempt = 0; attempt < 2; attempt++) {
|
|
811
|
-
summary = await generateSummary(messagesToSummarize, model, settings.reserveTokens, apiKey, headers, signal, customSummaryInstructions, previousSummary, thinkingLevel, streamFn, preDigest, factsBlock, executionOptions?.chunked ?? false, precomputedConversationText);
|
|
812
|
-
verification = verifySummary(summary, facts);
|
|
813
|
-
if (verification.ok) {
|
|
814
|
-
break;
|
|
815
|
-
}
|
|
816
|
-
if (!isCompactionSummaryStructurallyUsable(summary)) {
|
|
817
|
-
if (attempt >= 1) {
|
|
818
|
-
throw new Error(`gate-failed: ${formatVerificationFailures(verification)}`);
|
|
819
|
-
}
|
|
820
|
-
customSummaryInstructions = buildRetryPrompt(verification, summary);
|
|
821
|
-
continue;
|
|
822
|
-
}
|
|
823
|
-
verificationGateFailures.push(verification);
|
|
824
|
-
const filled = deterministicallyFillSummaryGaps(summary, facts);
|
|
825
|
-
if (filled.verification.ok) {
|
|
826
|
-
summary = filled.summary;
|
|
827
|
-
verification = filled.verification;
|
|
828
|
-
if (filled.changed)
|
|
829
|
-
deterministicGapFills++;
|
|
830
|
-
break;
|
|
831
|
-
}
|
|
832
|
-
throw new Error(`gate-failed: ${formatVerificationFailures(filled.verification)}`);
|
|
833
|
-
}
|
|
880
|
+
const verified = isSplitTurn && messagesToSummarize.length === 0
|
|
881
|
+
? undefined
|
|
882
|
+
: await generateVerifiedSummary({
|
|
883
|
+
messages: messagesToSummarize,
|
|
884
|
+
model,
|
|
885
|
+
reserveTokens: settings.reserveTokens,
|
|
886
|
+
apiKey,
|
|
887
|
+
headers,
|
|
888
|
+
signal,
|
|
889
|
+
customInstructions,
|
|
890
|
+
previousSummary,
|
|
891
|
+
thinkingLevel,
|
|
892
|
+
streamFn,
|
|
893
|
+
preDigest,
|
|
894
|
+
facts,
|
|
895
|
+
factsBlock,
|
|
896
|
+
chunked: executionOptions?.chunked ?? false,
|
|
897
|
+
});
|
|
898
|
+
let summary = verified?.summary ?? "No prior history.";
|
|
899
|
+
let summaryUsage = verified?.usage;
|
|
900
|
+
if (isSplitTurn) {
|
|
901
|
+
const turnPrefix = await generateTurnPrefixSummary(turnPrefixMessages, model, settings.reserveTokens, apiKey, headers, signal, thinkingLevel, streamFn);
|
|
902
|
+
summary = `${summary}\n\n---\n\n**Turn Context (split turn):**\n\n${turnPrefix.text}`;
|
|
903
|
+
summaryUsage = combineUsage(summaryUsage, turnPrefix.usage);
|
|
834
904
|
}
|
|
835
905
|
const { readFiles, modifiedFiles } = computeFileLists(fileOps);
|
|
836
906
|
if (!firstKeptEntryId) {
|
|
837
907
|
throw new Error("First kept entry has no UUID - session may need migration");
|
|
838
908
|
}
|
|
839
|
-
return {
|
|
909
|
+
return mergeCompactionVerificationReports({
|
|
840
910
|
summary,
|
|
841
911
|
firstKeptEntryId,
|
|
842
912
|
tokensBefore,
|
|
913
|
+
usage: summaryUsage,
|
|
843
914
|
details: {
|
|
844
915
|
readFiles,
|
|
845
916
|
modifiedFiles,
|
|
846
|
-
verificationGateFailures:
|
|
847
|
-
deterministicGapFills,
|
|
917
|
+
verificationGateFailures: 0,
|
|
918
|
+
deterministicGapFills: verified?.deterministicGapFills ?? 0,
|
|
848
919
|
},
|
|
849
|
-
verification,
|
|
850
|
-
verificationGateFailures,
|
|
851
|
-
deterministicGapFills,
|
|
852
|
-
};
|
|
853
|
-
}
|
|
854
|
-
function formatVerificationFailures(verification) {
|
|
855
|
-
return verification.failures.map((failure) => `${failure.check}: ${failure.detail}`).join(", ");
|
|
920
|
+
verification: verified?.verification,
|
|
921
|
+
verificationGateFailures: [],
|
|
922
|
+
deterministicGapFills: verified?.deterministicGapFills ?? 0,
|
|
923
|
+
}, verified?.verificationGateFailures ?? []);
|
|
856
924
|
}
|
|
857
925
|
export function createDeterministicCompaction(preparation) {
|
|
858
926
|
const { firstKeptEntryId, tokensBefore, fileOps, facts } = preparation;
|
|
@@ -945,9 +1013,12 @@ async function generateTurnPrefixSummary(messages, model, reserveTokens, apiKey,
|
|
|
945
1013
|
if (response.stopReason === "error") {
|
|
946
1014
|
throw new Error(`Turn prefix summarization failed: ${response.errorMessage || "Unknown error"}`);
|
|
947
1015
|
}
|
|
948
|
-
return
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
1016
|
+
return {
|
|
1017
|
+
text: response.content
|
|
1018
|
+
.filter((c) => c.type === "text")
|
|
1019
|
+
.map((c) => c.text)
|
|
1020
|
+
.join("\n"),
|
|
1021
|
+
usage: response.usage,
|
|
1022
|
+
};
|
|
952
1023
|
}
|
|
953
1024
|
//# sourceMappingURL=compaction.js.map
|