@caupulican/pi-agent-core 0.81.7 → 0.81.9
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/dist/compaction/compaction.d.ts +1 -1
- package/dist/compaction/compaction.d.ts.map +1 -1
- package/dist/compaction/compaction.js +58 -27
- package/dist/compaction/compaction.js.map +1 -1
- package/dist/compaction/extraction.d.ts +14 -5
- package/dist/compaction/extraction.d.ts.map +1 -1
- package/dist/compaction/extraction.js +127 -36
- package/dist/compaction/extraction.js.map +1 -1
- package/dist/compaction/loop.d.ts +3 -2
- package/dist/compaction/loop.d.ts.map +1 -1
- package/dist/compaction/loop.js +48 -15
- package/dist/compaction/loop.js.map +1 -1
- package/dist/compaction/utils.d.ts +1 -1
- package/dist/compaction/utils.d.ts.map +1 -1
- package/dist/compaction/utils.js +18 -9
- package/dist/compaction/utils.js.map +1 -1
- package/dist/compaction/verification.d.ts +2 -1
- package/dist/compaction/verification.d.ts.map +1 -1
- package/dist/compaction/verification.js +26 -1
- package/dist/compaction/verification.js.map +1 -1
- package/dist/session/session-manager.d.ts.map +1 -1
- package/dist/session/session-manager.js +9 -1
- package/dist/session/session-manager.js.map +1 -1
- package/package.json +2 -2
package/dist/compaction/loop.js
CHANGED
|
@@ -6,6 +6,7 @@ export async function runCompactionLoop(deps) {
|
|
|
6
6
|
let lastParams;
|
|
7
7
|
let lastObservedTokens;
|
|
8
8
|
let appliedResult;
|
|
9
|
+
let lastModelKey;
|
|
9
10
|
let ownTrailingCompactionId;
|
|
10
11
|
let ownTrailingCompactionNeedsRetry = false;
|
|
11
12
|
let baseKeepRecent = deps.getBaseKeepRecentTokens ? deps.getBaseKeepRecentTokens() : DEFAULT_KEEP_RECENT;
|
|
@@ -28,7 +29,7 @@ export async function runCompactionLoop(deps) {
|
|
|
28
29
|
}
|
|
29
30
|
}
|
|
30
31
|
const observedTokens = deps.measureLiveTokens();
|
|
31
|
-
if (
|
|
32
|
+
if (!deps.shouldCompact(observedTokens)) {
|
|
32
33
|
return {
|
|
33
34
|
kind: "skip",
|
|
34
35
|
reason: branch.length > 0 && branch[branch.length - 1]?.type === "compaction"
|
|
@@ -37,9 +38,8 @@ export async function runCompactionLoop(deps) {
|
|
|
37
38
|
};
|
|
38
39
|
}
|
|
39
40
|
const selectedParams = selectCycleParams(cycle, lastCause, lastParams, baseKeepRecent);
|
|
40
|
-
|
|
41
|
+
let params = enforceMonotonicProgress(selectedParams, lastParams, observedTokens, lastObservedTokens, lastCause);
|
|
41
42
|
lastObservedTokens = observedTokens;
|
|
42
|
-
lastParams = params;
|
|
43
43
|
if (params.deterministicOnly || cycle > MAX_LLM_CYCLES) {
|
|
44
44
|
if (deps.signal?.aborted) {
|
|
45
45
|
return { kind: "failed", reason: "aborted", cycles: cycle - 1 };
|
|
@@ -50,7 +50,7 @@ export async function runCompactionLoop(deps) {
|
|
|
50
50
|
return { kind: "success", result, cycles: cycle };
|
|
51
51
|
}
|
|
52
52
|
catch (error) {
|
|
53
|
-
return { kind: "failed", reason: mapFailureCause(error), cycles: cycle };
|
|
53
|
+
return { kind: "failed", reason: mapFailureCause(error).cause, cycles: cycle };
|
|
54
54
|
}
|
|
55
55
|
}
|
|
56
56
|
let modelInfo;
|
|
@@ -67,6 +67,15 @@ export async function runCompactionLoop(deps) {
|
|
|
67
67
|
deps.onTransition({ cycle: cycle + 1, from: "step0", cause: lastCause });
|
|
68
68
|
continue;
|
|
69
69
|
}
|
|
70
|
+
const currentModelKey = modelKey(modelInfo.model);
|
|
71
|
+
if ((lastCause === "gate-failed" || lastCause === "length-stop") &&
|
|
72
|
+
lastParams &&
|
|
73
|
+
params.modelTier !== lastParams.modelTier &&
|
|
74
|
+
lastModelKey === currentModelKey) {
|
|
75
|
+
params = { ...params, modelTier: lastParams.modelTier };
|
|
76
|
+
}
|
|
77
|
+
lastParams = params;
|
|
78
|
+
lastModelKey = currentModelKey;
|
|
70
79
|
let result;
|
|
71
80
|
try {
|
|
72
81
|
({ result } = await deps.summarizeAndVerify(params, modelInfo.model, modelInfo.apiKey, modelInfo.headers, branch));
|
|
@@ -75,14 +84,25 @@ export async function runCompactionLoop(deps) {
|
|
|
75
84
|
if (deps.signal?.aborted) {
|
|
76
85
|
return { kind: "failed", reason: "aborted", cycles: cycle };
|
|
77
86
|
}
|
|
78
|
-
|
|
87
|
+
const failure = mapFailureCause(error);
|
|
88
|
+
lastCause = failure.cause;
|
|
79
89
|
if (lastCause === "aborted") {
|
|
80
90
|
return { kind: "failed", reason: lastCause, cycles: cycle };
|
|
81
91
|
}
|
|
82
92
|
if (lastCause === "provider-failure") {
|
|
83
93
|
return { kind: "failed", reason: error instanceof Error ? error.message : String(error), cycles: cycle };
|
|
84
94
|
}
|
|
85
|
-
|
|
95
|
+
if (lastCause === "deterministic-required") {
|
|
96
|
+
try {
|
|
97
|
+
const { result: deterministicResult } = await Promise.resolve(deps.buildDeterministicCheckpoint());
|
|
98
|
+
await deps.apply(deterministicResult);
|
|
99
|
+
return { kind: "success", result: deterministicResult, cycles: cycle };
|
|
100
|
+
}
|
|
101
|
+
catch (deterministicError) {
|
|
102
|
+
return { kind: "failed", reason: mapFailureCause(deterministicError).cause, cycles: cycle };
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
deps.onTransition({ cycle: cycle + 1, from: "step3", cause: lastCause, detail: failure.detail });
|
|
86
106
|
continue;
|
|
87
107
|
}
|
|
88
108
|
if (deps.signal?.aborted) {
|
|
@@ -98,7 +118,7 @@ export async function runCompactionLoop(deps) {
|
|
|
98
118
|
return { kind: "success", result, cycles: cycle };
|
|
99
119
|
}
|
|
100
120
|
const measuredAfter = deps.measureLiveTokens();
|
|
101
|
-
if (
|
|
121
|
+
if (!deps.shouldCompact(measuredAfter + deps.getPostApplyMargin())) {
|
|
102
122
|
ownTrailingCompactionNeedsRetry = false;
|
|
103
123
|
return { kind: "success", result, cycles: cycle };
|
|
104
124
|
}
|
|
@@ -141,7 +161,7 @@ function selectCycleParams(cycle, cause, lastParams, baseKeepRecent) {
|
|
|
141
161
|
}
|
|
142
162
|
return params;
|
|
143
163
|
}
|
|
144
|
-
function enforceMonotonicProgress(params, lastParams, observedTokens, lastObservedTokens) {
|
|
164
|
+
function enforceMonotonicProgress(params, lastParams, observedTokens, lastObservedTokens, lastCause) {
|
|
145
165
|
if (!lastParams ||
|
|
146
166
|
params.deterministicOnly ||
|
|
147
167
|
(lastObservedTokens !== undefined && observedTokens < lastObservedTokens)) {
|
|
@@ -150,6 +170,9 @@ function enforceMonotonicProgress(params, lastParams, observedTokens, lastObserv
|
|
|
150
170
|
if (!sameParams(params, lastParams)) {
|
|
151
171
|
return params;
|
|
152
172
|
}
|
|
173
|
+
if (lastCause === "gate-failed") {
|
|
174
|
+
return params;
|
|
175
|
+
}
|
|
153
176
|
const keepRecentTokens = Math.max(1, Math.floor(params.keepRecentTokens / 2));
|
|
154
177
|
if (keepRecentTokens !== params.keepRecentTokens) {
|
|
155
178
|
return { ...params, chunked: true, keepRecentTokens };
|
|
@@ -159,6 +182,9 @@ function enforceMonotonicProgress(params, lastParams, observedTokens, lastObserv
|
|
|
159
182
|
}
|
|
160
183
|
return { ...params, modelTier: params.modelTier === "cheap" ? "session" : "cheap" };
|
|
161
184
|
}
|
|
185
|
+
function modelKey(model) {
|
|
186
|
+
return `${model.provider}:${model.id}:${model.api}:${model.baseUrl ?? ""}`;
|
|
187
|
+
}
|
|
162
188
|
function sameParams(a, b) {
|
|
163
189
|
return (a.modelTier === b.modelTier &&
|
|
164
190
|
a.keepRecentTokens === b.keepRecentTokens &&
|
|
@@ -168,18 +194,25 @@ function sameParams(a, b) {
|
|
|
168
194
|
function mapFailureCause(error) {
|
|
169
195
|
const message = error instanceof Error ? error.message : typeof error === "string" ? error : "";
|
|
170
196
|
if (message.includes("gate-failed"))
|
|
171
|
-
return "gate-failed";
|
|
197
|
+
return { cause: "gate-failed", detail: boundedDetail(message) };
|
|
198
|
+
if (message.includes("summary-demand-exceeds-reserve"))
|
|
199
|
+
return { cause: "deterministic-required", detail: boundedDetail(message) };
|
|
172
200
|
if (message.includes("input-overflow"))
|
|
173
|
-
return "input-overflow";
|
|
201
|
+
return { cause: "input-overflow", detail: boundedDetail(message) };
|
|
174
202
|
// A length-stopped summary lost gated sections; escalating the tier buys a larger output cap.
|
|
175
203
|
if (message.includes("summary-length-stop"))
|
|
176
|
-
return "length-stop";
|
|
204
|
+
return { cause: "length-stop", detail: boundedDetail(message) };
|
|
177
205
|
if (/stream stalled|overloaded|rate.?limit|too many requests|service.?unavailable|server.?error|network.?error|fetch failed|timeout|timed out/i.test(message))
|
|
178
|
-
return "provider-failure";
|
|
206
|
+
return { cause: "provider-failure", detail: boundedDetail(message) };
|
|
179
207
|
if (message.includes("auto-compaction-cancelled"))
|
|
180
|
-
return "aborted";
|
|
208
|
+
return { cause: "aborted", detail: boundedDetail(message) };
|
|
181
209
|
if (message.includes("auth") || message.includes("api key") || message.includes("not compacted"))
|
|
182
|
-
return "auth-failed";
|
|
183
|
-
return "unknown-failure";
|
|
210
|
+
return { cause: "auth-failed", detail: boundedDetail(message) };
|
|
211
|
+
return { cause: "unknown-failure", detail: boundedDetail(message) };
|
|
212
|
+
}
|
|
213
|
+
function boundedDetail(message) {
|
|
214
|
+
if (!message)
|
|
215
|
+
return undefined;
|
|
216
|
+
return message.length > 500 ? `${message.slice(0, 500)}…` : message;
|
|
184
217
|
}
|
|
185
218
|
//# sourceMappingURL=loop.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"loop.js","sourceRoot":"","sources":["../../src/compaction/loop.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Model } from \"@caupulican/pi-ai\";\nimport type { SessionEntry } from \"../session/session-manager.ts\";\nimport type { CompactionResult } from \"./compaction.ts\";\n\nexport interface CompactionCycleParams {\n\tmodelTier: \"cheap\" | \"session\";\n\tkeepRecentTokens: number;\n\tchunked: boolean;\n\tdeterministicOnly: boolean;\n}\n\nexport interface ModelAndAuth {\n\tmodel: Model<any>;\n\tapiKey?: string;\n\theaders?: Record<string, string>;\n\tfailure?: string;\n}\n\nexport interface CompactionLoopDeps {\n\tmeasureLiveTokens(): number;\n\tgetTriggerThreshold(): number;\n\tgetMargin(): number;\n\tgetBranch(): SessionEntry[];\n\tresolveModelAndAuth(modelTier: CompactionCycleParams[\"modelTier\"]): Promise<ModelAndAuth>;\n\tsummarizeAndVerify(\n\t\tparams: CompactionCycleParams,\n\t\tmodel: Model<any>,\n\t\tapiKey: string | undefined,\n\t\theaders: Record<string, string> | undefined,\n\t\tbranch: SessionEntry[],\n\t): Promise<{ result: CompactionResult }>;\n\tbuildDeterministicCheckpoint(): Promise<{ result: CompactionResult }> | { result: CompactionResult };\n\tapply(result: CompactionResult): Promise<void> | void;\n\tonTransition(info: { cycle: number; from: string; cause: string }): void;\n\tgetBaseKeepRecentTokens?(): number;\n\tverifyPostApplyEffect?(): boolean;\n\tsignal?: AbortSignal;\n}\n\nexport type CompactionLoopOutcome =\n\t| { kind: \"success\"; result: CompactionResult; cycles: number }\n\t| { kind: \"skip\"; reason: string }\n\t| { kind: \"failed\"; reason: string; cycles: number };\n\nconst MAX_CYCLES = 4;\nconst MAX_LLM_CYCLES = 3;\nconst DEFAULT_KEEP_RECENT = 20_000;\n\nexport async function runCompactionLoop(deps: CompactionLoopDeps): Promise<CompactionLoopOutcome> {\n\tlet lastCause = \"start\";\n\tlet lastParams: CompactionCycleParams | undefined;\n\tlet lastObservedTokens: number | undefined;\n\tlet appliedResult: CompactionResult | undefined;\n\tlet ownTrailingCompactionId: string | undefined;\n\tlet ownTrailingCompactionNeedsRetry = false;\n\tlet baseKeepRecent = deps.getBaseKeepRecentTokens ? deps.getBaseKeepRecentTokens() : DEFAULT_KEEP_RECENT;\n\tif (!Number.isFinite(baseKeepRecent) || baseKeepRecent <= 0) {\n\t\tbaseKeepRecent = DEFAULT_KEEP_RECENT;\n\t}\n\n\tfor (let cycle = 1; cycle <= MAX_CYCLES; cycle++) {\n\t\tif (deps.signal?.aborted) {\n\t\t\treturn { kind: \"failed\", reason: \"aborted\", cycles: cycle - 1 };\n\t\t}\n\n\t\tconst branch = deps.getBranch();\n\t\tconst trailingEntry = branch[branch.length - 1];\n\t\tif (branch.length > 0 && trailingEntry?.type === \"compaction\") {\n\t\t\tif (appliedResult && trailingEntry.id === ownTrailingCompactionId) {\n\t\t\t\tif (!ownTrailingCompactionNeedsRetry) return { kind: \"success\", result: appliedResult, cycles: cycle - 1 };\n\t\t\t} else {\n\t\t\t\treturn { kind: \"skip\", reason: \"already compacted\" };\n\t\t\t}\n\t\t}\n\n\t\tconst observedTokens = deps.measureLiveTokens();\n\t\tif (observedTokens <= deps.getTriggerThreshold()) {\n\t\t\treturn {\n\t\t\t\tkind: \"skip\",\n\t\t\t\treason:\n\t\t\t\t\tbranch.length > 0 && branch[branch.length - 1]?.type === \"compaction\"\n\t\t\t\t\t\t? \"already compacted\"\n\t\t\t\t\t\t: \"within threshold\",\n\t\t\t};\n\t\t}\n\n\t\tconst selectedParams = selectCycleParams(cycle, lastCause, lastParams, baseKeepRecent);\n\t\tconst params = enforceMonotonicProgress(selectedParams, lastParams, observedTokens, lastObservedTokens);\n\t\tlastObservedTokens = observedTokens;\n\t\tlastParams = params;\n\n\t\tif (params.deterministicOnly || cycle > MAX_LLM_CYCLES) {\n\t\t\tif (deps.signal?.aborted) {\n\t\t\t\treturn { kind: \"failed\", reason: \"aborted\", cycles: cycle - 1 };\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst { result } = await Promise.resolve(deps.buildDeterministicCheckpoint());\n\t\t\t\tawait deps.apply(result);\n\t\t\t\treturn { kind: \"success\", result, cycles: cycle };\n\t\t\t} catch (error) {\n\t\t\t\treturn { kind: \"failed\", reason: mapFailureCause(error), cycles: cycle };\n\t\t\t}\n\t\t}\n\n\t\tlet modelInfo: ModelAndAuth;\n\t\ttry {\n\t\t\tmodelInfo = await deps.resolveModelAndAuth(params.modelTier);\n\t\t} catch {\n\t\t\tlastCause = \"auth-failed\";\n\t\t\tdeps.onTransition({ cycle: cycle + 1, from: \"step0\", cause: lastCause });\n\t\t\tcontinue;\n\t\t}\n\t\tif (modelInfo.failure) {\n\t\t\tlastCause = \"auth-failed\";\n\t\t\tdeps.onTransition({ cycle: cycle + 1, from: \"step0\", cause: lastCause });\n\t\t\tcontinue;\n\t\t}\n\n\t\tlet result: CompactionResult;\n\t\ttry {\n\t\t\t({ result } = await deps.summarizeAndVerify(\n\t\t\t\tparams,\n\t\t\t\tmodelInfo.model,\n\t\t\t\tmodelInfo.apiKey,\n\t\t\t\tmodelInfo.headers,\n\t\t\t\tbranch,\n\t\t\t));\n\t\t} catch (error) {\n\t\t\tif (deps.signal?.aborted) {\n\t\t\t\treturn { kind: \"failed\", reason: \"aborted\", cycles: cycle };\n\t\t\t}\n\t\t\tlastCause = mapFailureCause(error);\n\t\t\tif (lastCause === \"aborted\") {\n\t\t\t\treturn { kind: \"failed\", reason: lastCause, cycles: cycle };\n\t\t\t}\n\t\t\tif (lastCause === \"provider-failure\") {\n\t\t\t\treturn { kind: \"failed\", reason: error instanceof Error ? error.message : String(error), cycles: cycle };\n\t\t\t}\n\t\t\tdeps.onTransition({ cycle: cycle + 1, from: \"step3\", cause: lastCause });\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (deps.signal?.aborted) {\n\t\t\treturn { kind: \"failed\", reason: \"aborted\", cycles: cycle };\n\t\t}\n\t\tawait deps.apply(result);\n\t\tappliedResult = result;\n\t\tconst branchAfterApply = deps.getBranch();\n\t\tconst trailingAfterApply = branchAfterApply[branchAfterApply.length - 1];\n\t\townTrailingCompactionId = trailingAfterApply?.type === \"compaction\" ? trailingAfterApply.id : undefined;\n\n\t\tif (deps.verifyPostApplyEffect?.() === false) {\n\t\t\townTrailingCompactionNeedsRetry = false;\n\t\t\treturn { kind: \"success\", result, cycles: cycle };\n\t\t}\n\n\t\tconst measuredAfter = deps.measureLiveTokens();\n\t\tif (measuredAfter <= deps.getTriggerThreshold() - deps.getMargin()) {\n\t\t\townTrailingCompactionNeedsRetry = false;\n\t\t\treturn { kind: \"success\", result, cycles: cycle };\n\t\t}\n\n\t\townTrailingCompactionNeedsRetry = true;\n\t\tlastCause = \"effect-not-restored\";\n\t\tdeps.onTransition({ cycle: cycle + 1, from: \"step5\", cause: lastCause });\n\t}\n\n\treturn { kind: \"failed\", reason: \"exhausted-compaction-cycles\", cycles: MAX_CYCLES };\n}\n\nfunction selectCycleParams(\n\tcycle: number,\n\tcause: string,\n\tlastParams: CompactionCycleParams | undefined,\n\tbaseKeepRecent: number,\n): CompactionCycleParams {\n\tif (cycle >= MAX_CYCLES) {\n\t\treturn {\n\t\t\tmodelTier: lastParams?.modelTier ?? \"session\",\n\t\t\tkeepRecentTokens: Math.max(1, Math.floor((lastParams?.keepRecentTokens ?? baseKeepRecent) / 2)),\n\t\t\tchunked: true,\n\t\t\tdeterministicOnly: true,\n\t\t};\n\t}\n\n\tif (!lastParams) {\n\t\treturn {\n\t\t\tmodelTier: \"cheap\",\n\t\t\tkeepRecentTokens: Math.max(1, baseKeepRecent),\n\t\t\tchunked: false,\n\t\t\tdeterministicOnly: false,\n\t\t};\n\t}\n\n\tconst params: CompactionCycleParams = {\n\t\t...lastParams,\n\t\tdeterministicOnly: false,\n\t};\n\n\tif (cause === \"gate-failed\" || cause === \"auth-failed\" || cause === \"length-stop\") {\n\t\tparams.modelTier = \"session\";\n\t} else if (cause === \"input-overflow\") {\n\t\tparams.chunked = true;\n\t} else if (cause === \"effect-not-restored\") {\n\t\tparams.chunked = true;\n\t\tparams.keepRecentTokens = Math.max(1, Math.floor(lastParams.keepRecentTokens / 2));\n\t}\n\n\treturn params;\n}\n\nfunction enforceMonotonicProgress(\n\tparams: CompactionCycleParams,\n\tlastParams: CompactionCycleParams | undefined,\n\tobservedTokens: number,\n\tlastObservedTokens: number | undefined,\n): CompactionCycleParams {\n\tif (\n\t\t!lastParams ||\n\t\tparams.deterministicOnly ||\n\t\t(lastObservedTokens !== undefined && observedTokens < lastObservedTokens)\n\t) {\n\t\treturn params;\n\t}\n\tif (!sameParams(params, lastParams)) {\n\t\treturn params;\n\t}\n\n\tconst keepRecentTokens = Math.max(1, Math.floor(params.keepRecentTokens / 2));\n\tif (keepRecentTokens !== params.keepRecentTokens) {\n\t\treturn { ...params, chunked: true, keepRecentTokens };\n\t}\n\tif (!params.chunked) {\n\t\treturn { ...params, chunked: true };\n\t}\n\treturn { ...params, modelTier: params.modelTier === \"cheap\" ? \"session\" : \"cheap\" };\n}\n\nfunction sameParams(a: CompactionCycleParams, b: CompactionCycleParams): boolean {\n\treturn (\n\t\ta.modelTier === b.modelTier &&\n\t\ta.keepRecentTokens === b.keepRecentTokens &&\n\t\ta.chunked === b.chunked &&\n\t\ta.deterministicOnly === b.deterministicOnly\n\t);\n}\n\nfunction mapFailureCause(error: unknown): string {\n\tconst message = error instanceof Error ? error.message : typeof error === \"string\" ? error : \"\";\n\tif (message.includes(\"gate-failed\")) return \"gate-failed\";\n\tif (message.includes(\"input-overflow\")) return \"input-overflow\";\n\t// A length-stopped summary lost gated sections; escalating the tier buys a larger output cap.\n\tif (message.includes(\"summary-length-stop\")) return \"length-stop\";\n\tif (\n\t\t/stream stalled|overloaded|rate.?limit|too many requests|service.?unavailable|server.?error|network.?error|fetch failed|timeout|timed out/i.test(\n\t\t\tmessage,\n\t\t)\n\t)\n\t\treturn \"provider-failure\";\n\tif (message.includes(\"auto-compaction-cancelled\")) return \"aborted\";\n\tif (message.includes(\"auth\") || message.includes(\"api key\") || message.includes(\"not compacted\"))\n\t\treturn \"auth-failed\";\n\treturn \"unknown-failure\";\n}\n"]}
|
|
1
|
+
{"version":3,"file":"loop.js","sourceRoot":"","sources":["../../src/compaction/loop.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Model } from \"@caupulican/pi-ai\";\nimport type { SessionEntry } from \"../session/session-manager.ts\";\nimport type { CompactionResult } from \"./compaction.ts\";\n\nexport interface CompactionCycleParams {\n\tmodelTier: \"cheap\" | \"session\";\n\tkeepRecentTokens: number;\n\tchunked: boolean;\n\tdeterministicOnly: boolean;\n}\n\nexport interface ModelAndAuth {\n\tmodel: Model<any>;\n\tapiKey?: string;\n\theaders?: Record<string, string>;\n\tfailure?: string;\n}\n\nexport interface CompactionLoopDeps {\n\tmeasureLiveTokens(): number;\n\tshouldCompact(tokens: number): boolean;\n\tgetPostApplyMargin(): number;\n\tgetBranch(): SessionEntry[];\n\tresolveModelAndAuth(modelTier: CompactionCycleParams[\"modelTier\"]): Promise<ModelAndAuth>;\n\tsummarizeAndVerify(\n\t\tparams: CompactionCycleParams,\n\t\tmodel: Model<any>,\n\t\tapiKey: string | undefined,\n\t\theaders: Record<string, string> | undefined,\n\t\tbranch: SessionEntry[],\n\t): Promise<{ result: CompactionResult }>;\n\tbuildDeterministicCheckpoint(): Promise<{ result: CompactionResult }> | { result: CompactionResult };\n\tapply(result: CompactionResult): Promise<void> | void;\n\tonTransition(info: { cycle: number; from: string; cause: string; detail?: string }): void;\n\tgetBaseKeepRecentTokens?(): number;\n\tverifyPostApplyEffect?(): boolean;\n\tsignal?: AbortSignal;\n}\n\nexport type CompactionLoopOutcome =\n\t| { kind: \"success\"; result: CompactionResult; cycles: number }\n\t| { kind: \"skip\"; reason: string }\n\t| { kind: \"failed\"; reason: string; cycles: number };\n\nconst MAX_CYCLES = 4;\nconst MAX_LLM_CYCLES = 3;\nconst DEFAULT_KEEP_RECENT = 20_000;\n\nexport async function runCompactionLoop(deps: CompactionLoopDeps): Promise<CompactionLoopOutcome> {\n\tlet lastCause = \"start\";\n\tlet lastParams: CompactionCycleParams | undefined;\n\tlet lastObservedTokens: number | undefined;\n\tlet appliedResult: CompactionResult | undefined;\n\tlet lastModelKey: string | undefined;\n\tlet ownTrailingCompactionId: string | undefined;\n\tlet ownTrailingCompactionNeedsRetry = false;\n\tlet baseKeepRecent = deps.getBaseKeepRecentTokens ? deps.getBaseKeepRecentTokens() : DEFAULT_KEEP_RECENT;\n\tif (!Number.isFinite(baseKeepRecent) || baseKeepRecent <= 0) {\n\t\tbaseKeepRecent = DEFAULT_KEEP_RECENT;\n\t}\n\n\tfor (let cycle = 1; cycle <= MAX_CYCLES; cycle++) {\n\t\tif (deps.signal?.aborted) {\n\t\t\treturn { kind: \"failed\", reason: \"aborted\", cycles: cycle - 1 };\n\t\t}\n\n\t\tconst branch = deps.getBranch();\n\t\tconst trailingEntry = branch[branch.length - 1];\n\t\tif (branch.length > 0 && trailingEntry?.type === \"compaction\") {\n\t\t\tif (appliedResult && trailingEntry.id === ownTrailingCompactionId) {\n\t\t\t\tif (!ownTrailingCompactionNeedsRetry) return { kind: \"success\", result: appliedResult, cycles: cycle - 1 };\n\t\t\t} else {\n\t\t\t\treturn { kind: \"skip\", reason: \"already compacted\" };\n\t\t\t}\n\t\t}\n\n\t\tconst observedTokens = deps.measureLiveTokens();\n\t\tif (!deps.shouldCompact(observedTokens)) {\n\t\t\treturn {\n\t\t\t\tkind: \"skip\",\n\t\t\t\treason:\n\t\t\t\t\tbranch.length > 0 && branch[branch.length - 1]?.type === \"compaction\"\n\t\t\t\t\t\t? \"already compacted\"\n\t\t\t\t\t\t: \"within threshold\",\n\t\t\t};\n\t\t}\n\n\t\tconst selectedParams = selectCycleParams(cycle, lastCause, lastParams, baseKeepRecent);\n\t\tlet params = enforceMonotonicProgress(selectedParams, lastParams, observedTokens, lastObservedTokens, lastCause);\n\t\tlastObservedTokens = observedTokens;\n\n\t\tif (params.deterministicOnly || cycle > MAX_LLM_CYCLES) {\n\t\t\tif (deps.signal?.aborted) {\n\t\t\t\treturn { kind: \"failed\", reason: \"aborted\", cycles: cycle - 1 };\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst { result } = await Promise.resolve(deps.buildDeterministicCheckpoint());\n\t\t\t\tawait deps.apply(result);\n\t\t\t\treturn { kind: \"success\", result, cycles: cycle };\n\t\t\t} catch (error) {\n\t\t\t\treturn { kind: \"failed\", reason: mapFailureCause(error).cause, cycles: cycle };\n\t\t\t}\n\t\t}\n\n\t\tlet modelInfo: ModelAndAuth;\n\t\ttry {\n\t\t\tmodelInfo = await deps.resolveModelAndAuth(params.modelTier);\n\t\t} catch {\n\t\t\tlastCause = \"auth-failed\";\n\t\t\tdeps.onTransition({ cycle: cycle + 1, from: \"step0\", cause: lastCause });\n\t\t\tcontinue;\n\t\t}\n\t\tif (modelInfo.failure) {\n\t\t\tlastCause = \"auth-failed\";\n\t\t\tdeps.onTransition({ cycle: cycle + 1, from: \"step0\", cause: lastCause });\n\t\t\tcontinue;\n\t\t}\n\t\tconst currentModelKey = modelKey(modelInfo.model);\n\t\tif (\n\t\t\t(lastCause === \"gate-failed\" || lastCause === \"length-stop\") &&\n\t\t\tlastParams &&\n\t\t\tparams.modelTier !== lastParams.modelTier &&\n\t\t\tlastModelKey === currentModelKey\n\t\t) {\n\t\t\tparams = { ...params, modelTier: lastParams.modelTier };\n\t\t}\n\t\tlastParams = params;\n\t\tlastModelKey = currentModelKey;\n\n\t\tlet result: CompactionResult;\n\t\ttry {\n\t\t\t({ result } = await deps.summarizeAndVerify(\n\t\t\t\tparams,\n\t\t\t\tmodelInfo.model,\n\t\t\t\tmodelInfo.apiKey,\n\t\t\t\tmodelInfo.headers,\n\t\t\t\tbranch,\n\t\t\t));\n\t\t} catch (error) {\n\t\t\tif (deps.signal?.aborted) {\n\t\t\t\treturn { kind: \"failed\", reason: \"aborted\", cycles: cycle };\n\t\t\t}\n\t\t\tconst failure = mapFailureCause(error);\n\t\t\tlastCause = failure.cause;\n\t\t\tif (lastCause === \"aborted\") {\n\t\t\t\treturn { kind: \"failed\", reason: lastCause, cycles: cycle };\n\t\t\t}\n\t\t\tif (lastCause === \"provider-failure\") {\n\t\t\t\treturn { kind: \"failed\", reason: error instanceof Error ? error.message : String(error), cycles: cycle };\n\t\t\t}\n\t\t\tif (lastCause === \"deterministic-required\") {\n\t\t\t\ttry {\n\t\t\t\t\tconst { result: deterministicResult } = await Promise.resolve(deps.buildDeterministicCheckpoint());\n\t\t\t\t\tawait deps.apply(deterministicResult);\n\t\t\t\t\treturn { kind: \"success\", result: deterministicResult, cycles: cycle };\n\t\t\t\t} catch (deterministicError) {\n\t\t\t\t\treturn { kind: \"failed\", reason: mapFailureCause(deterministicError).cause, cycles: cycle };\n\t\t\t\t}\n\t\t\t}\n\t\t\tdeps.onTransition({ cycle: cycle + 1, from: \"step3\", cause: lastCause, detail: failure.detail });\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (deps.signal?.aborted) {\n\t\t\treturn { kind: \"failed\", reason: \"aborted\", cycles: cycle };\n\t\t}\n\t\tawait deps.apply(result);\n\t\tappliedResult = result;\n\t\tconst branchAfterApply = deps.getBranch();\n\t\tconst trailingAfterApply = branchAfterApply[branchAfterApply.length - 1];\n\t\townTrailingCompactionId = trailingAfterApply?.type === \"compaction\" ? trailingAfterApply.id : undefined;\n\n\t\tif (deps.verifyPostApplyEffect?.() === false) {\n\t\t\townTrailingCompactionNeedsRetry = false;\n\t\t\treturn { kind: \"success\", result, cycles: cycle };\n\t\t}\n\n\t\tconst measuredAfter = deps.measureLiveTokens();\n\t\tif (!deps.shouldCompact(measuredAfter + deps.getPostApplyMargin())) {\n\t\t\townTrailingCompactionNeedsRetry = false;\n\t\t\treturn { kind: \"success\", result, cycles: cycle };\n\t\t}\n\n\t\townTrailingCompactionNeedsRetry = true;\n\t\tlastCause = \"effect-not-restored\";\n\t\tdeps.onTransition({ cycle: cycle + 1, from: \"step5\", cause: lastCause });\n\t}\n\n\treturn { kind: \"failed\", reason: \"exhausted-compaction-cycles\", cycles: MAX_CYCLES };\n}\n\nfunction selectCycleParams(\n\tcycle: number,\n\tcause: string,\n\tlastParams: CompactionCycleParams | undefined,\n\tbaseKeepRecent: number,\n): CompactionCycleParams {\n\tif (cycle >= MAX_CYCLES) {\n\t\treturn {\n\t\t\tmodelTier: lastParams?.modelTier ?? \"session\",\n\t\t\tkeepRecentTokens: Math.max(1, Math.floor((lastParams?.keepRecentTokens ?? baseKeepRecent) / 2)),\n\t\t\tchunked: true,\n\t\t\tdeterministicOnly: true,\n\t\t};\n\t}\n\n\tif (!lastParams) {\n\t\treturn {\n\t\t\tmodelTier: \"cheap\",\n\t\t\tkeepRecentTokens: Math.max(1, baseKeepRecent),\n\t\t\tchunked: false,\n\t\t\tdeterministicOnly: false,\n\t\t};\n\t}\n\n\tconst params: CompactionCycleParams = {\n\t\t...lastParams,\n\t\tdeterministicOnly: false,\n\t};\n\n\tif (cause === \"gate-failed\" || cause === \"auth-failed\" || cause === \"length-stop\") {\n\t\tparams.modelTier = \"session\";\n\t} else if (cause === \"input-overflow\") {\n\t\tparams.chunked = true;\n\t} else if (cause === \"effect-not-restored\") {\n\t\tparams.chunked = true;\n\t\tparams.keepRecentTokens = Math.max(1, Math.floor(lastParams.keepRecentTokens / 2));\n\t}\n\n\treturn params;\n}\n\nfunction enforceMonotonicProgress(\n\tparams: CompactionCycleParams,\n\tlastParams: CompactionCycleParams | undefined,\n\tobservedTokens: number,\n\tlastObservedTokens: number | undefined,\n\tlastCause: string,\n): CompactionCycleParams {\n\tif (\n\t\t!lastParams ||\n\t\tparams.deterministicOnly ||\n\t\t(lastObservedTokens !== undefined && observedTokens < lastObservedTokens)\n\t) {\n\t\treturn params;\n\t}\n\tif (!sameParams(params, lastParams)) {\n\t\treturn params;\n\t}\n\tif (lastCause === \"gate-failed\") {\n\t\treturn params;\n\t}\n\n\tconst keepRecentTokens = Math.max(1, Math.floor(params.keepRecentTokens / 2));\n\tif (keepRecentTokens !== params.keepRecentTokens) {\n\t\treturn { ...params, chunked: true, keepRecentTokens };\n\t}\n\tif (!params.chunked) {\n\t\treturn { ...params, chunked: true };\n\t}\n\treturn { ...params, modelTier: params.modelTier === \"cheap\" ? \"session\" : \"cheap\" };\n}\n\nfunction modelKey(model: Model<any>): string {\n\treturn `${model.provider}:${model.id}:${model.api}:${model.baseUrl ?? \"\"}`;\n}\n\nfunction sameParams(a: CompactionCycleParams, b: CompactionCycleParams): boolean {\n\treturn (\n\t\ta.modelTier === b.modelTier &&\n\t\ta.keepRecentTokens === b.keepRecentTokens &&\n\t\ta.chunked === b.chunked &&\n\t\ta.deterministicOnly === b.deterministicOnly\n\t);\n}\n\nfunction mapFailureCause(error: unknown): { cause: string; detail?: string } {\n\tconst message = error instanceof Error ? error.message : typeof error === \"string\" ? error : \"\";\n\tif (message.includes(\"gate-failed\")) return { cause: \"gate-failed\", detail: boundedDetail(message) };\n\tif (message.includes(\"summary-demand-exceeds-reserve\"))\n\t\treturn { cause: \"deterministic-required\", detail: boundedDetail(message) };\n\tif (message.includes(\"input-overflow\")) return { cause: \"input-overflow\", detail: boundedDetail(message) };\n\t// A length-stopped summary lost gated sections; escalating the tier buys a larger output cap.\n\tif (message.includes(\"summary-length-stop\")) return { cause: \"length-stop\", detail: boundedDetail(message) };\n\tif (\n\t\t/stream stalled|overloaded|rate.?limit|too many requests|service.?unavailable|server.?error|network.?error|fetch failed|timeout|timed out/i.test(\n\t\t\tmessage,\n\t\t)\n\t)\n\t\treturn { cause: \"provider-failure\", detail: boundedDetail(message) };\n\tif (message.includes(\"auto-compaction-cancelled\")) return { cause: \"aborted\", detail: boundedDetail(message) };\n\tif (message.includes(\"auth\") || message.includes(\"api key\") || message.includes(\"not compacted\"))\n\t\treturn { cause: \"auth-failed\", detail: boundedDetail(message) };\n\treturn { cause: \"unknown-failure\", detail: boundedDetail(message) };\n}\n\nfunction boundedDetail(message: string): string | undefined {\n\tif (!message) return undefined;\n\treturn message.length > 500 ? `${message.slice(0, 500)}…` : message;\n}\n"]}
|
|
@@ -34,5 +34,5 @@ export declare function formatFileOperations(readFiles: string[], modifiedFiles:
|
|
|
34
34
|
* reasonable token budgets. Full content is not needed for summarization.
|
|
35
35
|
*/
|
|
36
36
|
export declare function serializeConversation(messages: Message[]): string;
|
|
37
|
-
export declare const SUMMARIZATION_SYSTEM_PROMPT = "You are a context checkpointer. Input: a serialized agent conversation. Output: ONLY the checkpoint, exactly in the format below. No preamble. No commentary. Same language as the user. Never include secrets/keys/tokens \u2014 write [REDACTED].\n\nRULES:\n- Recent turns weigh heaviest. Old turns contribute only rules, decisions, and file knowledge.\n- ## Active Task: the user's most recent UNFULFILLED input, near-verbatim. A question awaiting an answer IS an active task. If the user's last signal cancels earlier work (stop/undo/never mind), record the cancellation and DROP the cancelled work everywhere.\n- ### Mandatory Rules: every user prohibition (\"do not X\", \"never Y\", \"stop doing Z\") as one bullet each, imperative, with source turn if known. PRESERVE existing rules verbatim. A user-corrected mistake appears ONLY here as \"DO NOT <mistake>\" \u2014 the mistaken work itself must not survive.\n- ##
|
|
37
|
+
export declare const SUMMARIZATION_SYSTEM_PROMPT = "You are a context checkpointer. Input: a serialized agent conversation. Output: ONLY the checkpoint, exactly in the format below. No preamble. No commentary. Same language as the user. Never include secrets/keys/tokens \u2014 write [REDACTED].\n\nRULES:\n- Recent turns weigh heaviest. Old turns contribute only rules, decisions, and file knowledge.\n- ## Active Task: the user's most recent UNFULFILLED input, near-verbatim. A question awaiting an answer IS an active task. If the user's last signal cancels earlier work (stop/undo/never mind), record the cancellation and DROP the cancelled work everywhere.\n- ### Mandatory Rules: every user prohibition (\"do not X\", \"never Y\", \"stop doing Z\") as one bullet each, imperative, with source turn if known. PRESERVE existing rules verbatim. A user-corrected mistake appears ONLY here as \"DO NOT <mistake>\" \u2014 the mistaken work itself must not survive.\n- ## Working Set: the currently active/recent files \u2014 path \u2014 why they matter.\n- ## Files: bare paths only; modified files must all appear, read files should be recalled when relevant.\n- ## Open Problems: unresolved errors only \u2014 command/operation plus first error line. Drop resolved/transient errors.\n- ## Done: numbered caveman log \u2014 \"N. VERB target \u2014 outcome\". Exact paths, commands, line numbers, error strings.\n- Do NOT carry resolved/transient errors, superseded approaches, or file contents. Record paths and intent, never bodies.\n- Sections with nothing: write \"(none)\".\n\nEXAMPLE INPUT (excerpt):\n[user]: add retry to the fetcher, and do not touch the legacy client\n[assistant]: (edits src/fetcher.ts, adds retry loop)\n[tool write src/fetcher.ts]: ok\n[tool bash npm test]: 2 failed: fetcher.test.ts\n[assistant]: (tries wrapping legacy client instead)\n[user]: no \u2014 stop changing the legacy client, I said don't touch it. Fix the two tests instead.\n\nEXAMPLE OUTPUT:\n## Active Task\nUser: \"Fix the two failing tests\" (fetcher.test.ts) \u2014 retry work continues, legacy-client changes cancelled.\n\n### Mandatory Rules\n- DO NOT touch the legacy client (user, twice)\n\n## Working Set\n- test/fetcher.test.ts \u2014 2 failing, current focus\n- src/fetcher.ts \u2014 retry loop added\n\n## Files\n- src/fetcher.ts\n- test/fetcher.test.ts\n\n## Open Problems\n- TEST npm test: 2 failed: fetcher.test.ts\n\n## Done\n1. EDIT src/fetcher.ts \u2014 added retry loop\n2. TEST npm test \u2014 2 failed: fetcher.test.ts\n\n## Key Decisions\n(none)\n\n## Constraints & Preferences\n(none)\n\n## Critical Context\n(none)\n\nNote what the example TEACHES (not just shows): the legacy-client wrapping attempt (cancelled work) appears nowhere except as the DO-NOT rule; the Active Task is the tail, near-verbatim; Done lines are caveman-format.";
|
|
38
38
|
//# sourceMappingURL=utils.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/compaction/utils.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAMhD,MAAM,WAAW,cAAc;IAC9B,IAAI,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAClB,OAAO,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IACrB,MAAM,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;CACpB;AAED,wBAAgB,aAAa,IAAI,cAAc,CAM9C;AAED;;GAEG;AACH,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,YAAY,EAAE,OAAO,EAAE,cAAc,GAAG,IAAI,CA2B9F;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,cAAc,GAAG;IAAE,SAAS,EAAE,MAAM,EAAE,CAAC;IAAC,aAAa,EAAE,MAAM,EAAE,CAAA;CAAE,CAK1G;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,SAAS,EAAE,MAAM,EAAE,EAAE,aAAa,EAAE,MAAM,EAAE,GAAG,MAAM,CAUzF;
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/compaction/utils.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAMhD,MAAM,WAAW,cAAc;IAC9B,IAAI,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAClB,OAAO,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IACrB,MAAM,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;CACpB;AAED,wBAAgB,aAAa,IAAI,cAAc,CAM9C;AAED;;GAEG;AACH,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,YAAY,EAAE,OAAO,EAAE,cAAc,GAAG,IAAI,CA2B9F;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,cAAc,GAAG;IAAE,SAAS,EAAE,MAAM,EAAE,CAAC;IAAC,aAAa,EAAE,MAAM,EAAE,CAAA;CAAE,CAK1G;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,SAAS,EAAE,MAAM,EAAE,EAAE,aAAa,EAAE,MAAM,EAAE,GAAG,MAAM,CAUzF;AAqBD;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,OAAO,EAAE,GAAG,MAAM,CAuDjE;AAMD,eAAO,MAAM,2BAA2B,uvFAoDkL,CAAC","sourcesContent":["/**\n * Shared utilities for compaction and branch summarization.\n */\n\nimport type { Message } from \"@caupulican/pi-ai\";\nimport type { AgentMessage } from \"../types.ts\";\n\n// ============================================================================\n// File Operation Tracking\n// ============================================================================\n\nexport interface FileOperations {\n\tread: Set<string>;\n\twritten: Set<string>;\n\tedited: Set<string>;\n}\n\nexport function createFileOps(): FileOperations {\n\treturn {\n\t\tread: new Set(),\n\t\twritten: new Set(),\n\t\tedited: new Set(),\n\t};\n}\n\n/**\n * Extract file operations from tool calls in an assistant message.\n */\nexport function extractFileOpsFromMessage(message: AgentMessage, fileOps: FileOperations): void {\n\tif (message.role !== \"assistant\") return;\n\tif (!(\"content\" in message) || !Array.isArray(message.content)) return;\n\n\tfor (const block of message.content) {\n\t\tif (typeof block !== \"object\" || block === null) continue;\n\t\tif (!(\"type\" in block) || block.type !== \"toolCall\") continue;\n\t\tif (!(\"arguments\" in block) || !(\"name\" in block)) continue;\n\n\t\tconst args = block.arguments as Record<string, unknown> | undefined;\n\t\tif (!args) continue;\n\n\t\tconst path = typeof args.path === \"string\" ? args.path : undefined;\n\t\tif (!path) continue;\n\n\t\tswitch (block.name) {\n\t\t\tcase \"read\":\n\t\t\t\tfileOps.read.add(path);\n\t\t\t\tbreak;\n\t\t\tcase \"write\":\n\t\t\t\tfileOps.written.add(path);\n\t\t\t\tbreak;\n\t\t\tcase \"edit\":\n\t\t\t\tfileOps.edited.add(path);\n\t\t\t\tbreak;\n\t\t}\n\t}\n}\n\n/**\n * Compute final file lists from file operations.\n * Returns readFiles (files only read, not modified) and modifiedFiles.\n */\nexport function computeFileLists(fileOps: FileOperations): { readFiles: string[]; modifiedFiles: string[] } {\n\tconst modified = new Set([...fileOps.edited, ...fileOps.written]);\n\tconst readOnly = [...fileOps.read].filter((f) => !modified.has(f)).sort();\n\tconst modifiedFiles = [...modified].sort();\n\treturn { readFiles: readOnly, modifiedFiles };\n}\n\n/**\n * Format file operations as XML tags for summary.\n */\nexport function formatFileOperations(readFiles: string[], modifiedFiles: string[]): string {\n\tconst sections: string[] = [];\n\tif (readFiles.length > 0) {\n\t\tsections.push(`<read-files>\\n${readFiles.join(\"\\n\")}\\n</read-files>`);\n\t}\n\tif (modifiedFiles.length > 0) {\n\t\tsections.push(`<modified-files>\\n${modifiedFiles.join(\"\\n\")}\\n</modified-files>`);\n\t}\n\tif (sections.length === 0) return \"\";\n\treturn `\\n\\n${sections.join(\"\\n\\n\")}`;\n}\n\n// ============================================================================\n// Message Serialization\n// ============================================================================\n\n/** Maximum characters for a tool result in serialized summaries. */\nconst TOOL_RESULT_MAX_CHARS = 2000;\n/** Maximum characters for non-gated assistant thinking in serialized summaries. */\nconst ASSISTANT_THINKING_MAX_CHARS = 2000;\n\n/**\n * Truncate text to a maximum character length for summarization.\n * Keeps the beginning and appends a truncation marker.\n */\nfunction truncateForSummary(text: string, maxChars: number): string {\n\tif (text.length <= maxChars) return text;\n\tconst truncatedChars = text.length - maxChars;\n\treturn `${text.slice(0, maxChars)}\\n\\n[... ${truncatedChars} more characters truncated]`;\n}\n\n/**\n * Serialize LLM messages to text for summarization.\n * This prevents the model from treating it as a conversation to continue.\n * Call convertToLlm() first to handle custom message types.\n *\n * Tool results are truncated to keep the summarization request within\n * reasonable token budgets. Full content is not needed for summarization.\n */\nexport function serializeConversation(messages: Message[]): string {\n\tconst parts: string[] = [];\n\n\tfor (const msg of messages) {\n\t\tif (msg.role === \"user\") {\n\t\t\tconst content =\n\t\t\t\ttypeof msg.content === \"string\"\n\t\t\t\t\t? msg.content\n\t\t\t\t\t: msg.content\n\t\t\t\t\t\t\t.filter((c): c is { type: \"text\"; text: string } => c.type === \"text\")\n\t\t\t\t\t\t\t.map((c) => c.text)\n\t\t\t\t\t\t\t.join(\"\");\n\t\t\tif (content) parts.push(`[User]: ${content}`);\n\t\t} else if (msg.role === \"assistant\") {\n\t\t\tconst textParts: string[] = [];\n\t\t\tconst thinkingParts: string[] = [];\n\t\t\tconst toolCalls: string[] = [];\n\n\t\t\tfor (const block of msg.content) {\n\t\t\t\tif (block.type === \"text\") {\n\t\t\t\t\ttextParts.push(block.text);\n\t\t\t\t} else if (block.type === \"thinking\") {\n\t\t\t\t\tthinkingParts.push(block.thinking);\n\t\t\t\t} else if (block.type === \"toolCall\") {\n\t\t\t\t\tconst args = block.arguments as Record<string, unknown>;\n\t\t\t\t\tconst argsStr = Object.entries(args)\n\t\t\t\t\t\t.map(([k, v]) => `${k}=${JSON.stringify(v)}`)\n\t\t\t\t\t\t.join(\", \");\n\t\t\t\t\ttoolCalls.push(`${block.name}(${argsStr})`);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (thinkingParts.length > 0) {\n\t\t\t\tparts.push(\n\t\t\t\t\t`[Assistant thinking]: ${truncateForSummary(thinkingParts.join(\"\\n\"), ASSISTANT_THINKING_MAX_CHARS)}`,\n\t\t\t\t);\n\t\t\t}\n\t\t\tif (textParts.length > 0) {\n\t\t\t\tparts.push(`[Assistant]: ${textParts.join(\"\\n\")}`);\n\t\t\t}\n\t\t\tif (toolCalls.length > 0) {\n\t\t\t\tparts.push(`[Assistant tool calls]: ${toolCalls.join(\"; \")}`);\n\t\t\t}\n\t\t} else if (msg.role === \"toolResult\") {\n\t\t\tconst content = msg.content\n\t\t\t\t.filter((c): c is { type: \"text\"; text: string } => c.type === \"text\")\n\t\t\t\t.map((c) => c.text)\n\t\t\t\t.join(\"\");\n\t\t\tif (content) {\n\t\t\t\tparts.push(`[Tool result]: ${truncateForSummary(content, TOOL_RESULT_MAX_CHARS)}`);\n\t\t\t}\n\t\t}\n\t}\n\n\treturn parts.join(\"\\n\\n\");\n}\n\n// ============================================================================\n// Summarization System Prompt\n// ============================================================================\n\nexport const SUMMARIZATION_SYSTEM_PROMPT = `You are a context checkpointer. Input: a serialized agent conversation. Output: ONLY the checkpoint, exactly in the format below. No preamble. No commentary. Same language as the user. Never include secrets/keys/tokens — write [REDACTED].\n\nRULES:\n- Recent turns weigh heaviest. Old turns contribute only rules, decisions, and file knowledge.\n- ## Active Task: the user's most recent UNFULFILLED input, near-verbatim. A question awaiting an answer IS an active task. If the user's last signal cancels earlier work (stop/undo/never mind), record the cancellation and DROP the cancelled work everywhere.\n- ### Mandatory Rules: every user prohibition (\"do not X\", \"never Y\", \"stop doing Z\") as one bullet each, imperative, with source turn if known. PRESERVE existing rules verbatim. A user-corrected mistake appears ONLY here as \"DO NOT <mistake>\" — the mistaken work itself must not survive.\n- ## Working Set: the currently active/recent files — path — why they matter.\n- ## Files: bare paths only; modified files must all appear, read files should be recalled when relevant.\n- ## Open Problems: unresolved errors only — command/operation plus first error line. Drop resolved/transient errors.\n- ## Done: numbered caveman log — \"N. VERB target — outcome\". Exact paths, commands, line numbers, error strings.\n- Do NOT carry resolved/transient errors, superseded approaches, or file contents. Record paths and intent, never bodies.\n- Sections with nothing: write \"(none)\".\n\nEXAMPLE INPUT (excerpt):\n[user]: add retry to the fetcher, and do not touch the legacy client\n[assistant]: (edits src/fetcher.ts, adds retry loop)\n[tool write src/fetcher.ts]: ok\n[tool bash npm test]: 2 failed: fetcher.test.ts\n[assistant]: (tries wrapping legacy client instead)\n[user]: no — stop changing the legacy client, I said don't touch it. Fix the two tests instead.\n\nEXAMPLE OUTPUT:\n## Active Task\nUser: \"Fix the two failing tests\" (fetcher.test.ts) — retry work continues, legacy-client changes cancelled.\n\n### Mandatory Rules\n- DO NOT touch the legacy client (user, twice)\n\n## Working Set\n- test/fetcher.test.ts — 2 failing, current focus\n- src/fetcher.ts — retry loop added\n\n## Files\n- src/fetcher.ts\n- test/fetcher.test.ts\n\n## Open Problems\n- TEST npm test: 2 failed: fetcher.test.ts\n\n## Done\n1. EDIT src/fetcher.ts — added retry loop\n2. TEST npm test — 2 failed: fetcher.test.ts\n\n## Key Decisions\n(none)\n\n## Constraints & Preferences\n(none)\n\n## Critical Context\n(none)\n\nNote what the example TEACHES (not just shows): the legacy-client wrapping attempt (cancelled work) appears nowhere except as the DO-NOT rule; the Active Task is the tail, near-verbatim; Done lines are caveman-format.`;\n"]}
|
package/dist/compaction/utils.js
CHANGED
|
@@ -72,6 +72,8 @@ export function formatFileOperations(readFiles, modifiedFiles) {
|
|
|
72
72
|
// ============================================================================
|
|
73
73
|
/** Maximum characters for a tool result in serialized summaries. */
|
|
74
74
|
const TOOL_RESULT_MAX_CHARS = 2000;
|
|
75
|
+
/** Maximum characters for non-gated assistant thinking in serialized summaries. */
|
|
76
|
+
const ASSISTANT_THINKING_MAX_CHARS = 2000;
|
|
75
77
|
/**
|
|
76
78
|
* Truncate text to a maximum character length for summarization.
|
|
77
79
|
* Keeps the beginning and appends a truncation marker.
|
|
@@ -123,7 +125,7 @@ export function serializeConversation(messages) {
|
|
|
123
125
|
}
|
|
124
126
|
}
|
|
125
127
|
if (thinkingParts.length > 0) {
|
|
126
|
-
parts.push(`[Assistant thinking]: ${thinkingParts.join("\n")}`);
|
|
128
|
+
parts.push(`[Assistant thinking]: ${truncateForSummary(thinkingParts.join("\n"), ASSISTANT_THINKING_MAX_CHARS)}`);
|
|
127
129
|
}
|
|
128
130
|
if (textParts.length > 0) {
|
|
129
131
|
parts.push(`[Assistant]: ${textParts.join("\n")}`);
|
|
@@ -153,8 +155,11 @@ RULES:
|
|
|
153
155
|
- Recent turns weigh heaviest. Old turns contribute only rules, decisions, and file knowledge.
|
|
154
156
|
- ## Active Task: the user's most recent UNFULFILLED input, near-verbatim. A question awaiting an answer IS an active task. If the user's last signal cancels earlier work (stop/undo/never mind), record the cancellation and DROP the cancelled work everywhere.
|
|
155
157
|
- ### Mandatory Rules: every user prohibition ("do not X", "never Y", "stop doing Z") as one bullet each, imperative, with source turn if known. PRESERVE existing rules verbatim. A user-corrected mistake appears ONLY here as "DO NOT <mistake>" — the mistaken work itself must not survive.
|
|
156
|
-
- ##
|
|
158
|
+
- ## Working Set: the currently active/recent files — path — why they matter.
|
|
159
|
+
- ## Files: bare paths only; modified files must all appear, read files should be recalled when relevant.
|
|
160
|
+
- ## Open Problems: unresolved errors only — command/operation plus first error line. Drop resolved/transient errors.
|
|
157
161
|
- ## Done: numbered caveman log — "N. VERB target — outcome". Exact paths, commands, line numbers, error strings.
|
|
162
|
+
- Do NOT carry resolved/transient errors, superseded approaches, or file contents. Record paths and intent, never bodies.
|
|
158
163
|
- Sections with nothing: write "(none)".
|
|
159
164
|
|
|
160
165
|
EXAMPLE INPUT (excerpt):
|
|
@@ -172,22 +177,26 @@ User: "Fix the two failing tests" (fetcher.test.ts) — retry work continues, le
|
|
|
172
177
|
### Mandatory Rules
|
|
173
178
|
- DO NOT touch the legacy client (user, twice)
|
|
174
179
|
|
|
180
|
+
## Working Set
|
|
181
|
+
- test/fetcher.test.ts — 2 failing, current focus
|
|
182
|
+
- src/fetcher.ts — retry loop added
|
|
183
|
+
|
|
175
184
|
## Files
|
|
176
|
-
- src/fetcher.ts
|
|
177
|
-
- test/fetcher.test.ts
|
|
185
|
+
- src/fetcher.ts
|
|
186
|
+
- test/fetcher.test.ts
|
|
187
|
+
|
|
188
|
+
## Open Problems
|
|
189
|
+
- TEST npm test: 2 failed: fetcher.test.ts
|
|
178
190
|
|
|
179
191
|
## Done
|
|
180
192
|
1. EDIT src/fetcher.ts — added retry loop
|
|
181
193
|
2. TEST npm test — 2 failed: fetcher.test.ts
|
|
182
194
|
|
|
183
|
-
## Constraints & Preferences
|
|
184
|
-
(none)
|
|
185
|
-
|
|
186
195
|
## Key Decisions
|
|
187
196
|
(none)
|
|
188
197
|
|
|
189
|
-
##
|
|
190
|
-
|
|
198
|
+
## Constraints & Preferences
|
|
199
|
+
(none)
|
|
191
200
|
|
|
192
201
|
## Critical Context
|
|
193
202
|
(none)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.js","sourceRoot":"","sources":["../../src/compaction/utils.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Shared utilities for compaction and branch summarization.\n */\n\nimport type { Message } from \"@caupulican/pi-ai\";\nimport type { AgentMessage } from \"../types.ts\";\n\n// ============================================================================\n// File Operation Tracking\n// ============================================================================\n\nexport interface FileOperations {\n\tread: Set<string>;\n\twritten: Set<string>;\n\tedited: Set<string>;\n}\n\nexport function createFileOps(): FileOperations {\n\treturn {\n\t\tread: new Set(),\n\t\twritten: new Set(),\n\t\tedited: new Set(),\n\t};\n}\n\n/**\n * Extract file operations from tool calls in an assistant message.\n */\nexport function extractFileOpsFromMessage(message: AgentMessage, fileOps: FileOperations): void {\n\tif (message.role !== \"assistant\") return;\n\tif (!(\"content\" in message) || !Array.isArray(message.content)) return;\n\n\tfor (const block of message.content) {\n\t\tif (typeof block !== \"object\" || block === null) continue;\n\t\tif (!(\"type\" in block) || block.type !== \"toolCall\") continue;\n\t\tif (!(\"arguments\" in block) || !(\"name\" in block)) continue;\n\n\t\tconst args = block.arguments as Record<string, unknown> | undefined;\n\t\tif (!args) continue;\n\n\t\tconst path = typeof args.path === \"string\" ? args.path : undefined;\n\t\tif (!path) continue;\n\n\t\tswitch (block.name) {\n\t\t\tcase \"read\":\n\t\t\t\tfileOps.read.add(path);\n\t\t\t\tbreak;\n\t\t\tcase \"write\":\n\t\t\t\tfileOps.written.add(path);\n\t\t\t\tbreak;\n\t\t\tcase \"edit\":\n\t\t\t\tfileOps.edited.add(path);\n\t\t\t\tbreak;\n\t\t}\n\t}\n}\n\n/**\n * Compute final file lists from file operations.\n * Returns readFiles (files only read, not modified) and modifiedFiles.\n */\nexport function computeFileLists(fileOps: FileOperations): { readFiles: string[]; modifiedFiles: string[] } {\n\tconst modified = new Set([...fileOps.edited, ...fileOps.written]);\n\tconst readOnly = [...fileOps.read].filter((f) => !modified.has(f)).sort();\n\tconst modifiedFiles = [...modified].sort();\n\treturn { readFiles: readOnly, modifiedFiles };\n}\n\n/**\n * Format file operations as XML tags for summary.\n */\nexport function formatFileOperations(readFiles: string[], modifiedFiles: string[]): string {\n\tconst sections: string[] = [];\n\tif (readFiles.length > 0) {\n\t\tsections.push(`<read-files>\\n${readFiles.join(\"\\n\")}\\n</read-files>`);\n\t}\n\tif (modifiedFiles.length > 0) {\n\t\tsections.push(`<modified-files>\\n${modifiedFiles.join(\"\\n\")}\\n</modified-files>`);\n\t}\n\tif (sections.length === 0) return \"\";\n\treturn `\\n\\n${sections.join(\"\\n\\n\")}`;\n}\n\n// ============================================================================\n// Message Serialization\n// ============================================================================\n\n/** Maximum characters for a tool result in serialized summaries. */\nconst TOOL_RESULT_MAX_CHARS = 2000;\n\n/**\n * Truncate text to a maximum character length for summarization.\n * Keeps the beginning and appends a truncation marker.\n */\nfunction truncateForSummary(text: string, maxChars: number): string {\n\tif (text.length <= maxChars) return text;\n\tconst truncatedChars = text.length - maxChars;\n\treturn `${text.slice(0, maxChars)}\\n\\n[... ${truncatedChars} more characters truncated]`;\n}\n\n/**\n * Serialize LLM messages to text for summarization.\n * This prevents the model from treating it as a conversation to continue.\n * Call convertToLlm() first to handle custom message types.\n *\n * Tool results are truncated to keep the summarization request within\n * reasonable token budgets. Full content is not needed for summarization.\n */\nexport function serializeConversation(messages: Message[]): string {\n\tconst parts: string[] = [];\n\n\tfor (const msg of messages) {\n\t\tif (msg.role === \"user\") {\n\t\t\tconst content =\n\t\t\t\ttypeof msg.content === \"string\"\n\t\t\t\t\t? msg.content\n\t\t\t\t\t: msg.content\n\t\t\t\t\t\t\t.filter((c): c is { type: \"text\"; text: string } => c.type === \"text\")\n\t\t\t\t\t\t\t.map((c) => c.text)\n\t\t\t\t\t\t\t.join(\"\");\n\t\t\tif (content) parts.push(`[User]: ${content}`);\n\t\t} else if (msg.role === \"assistant\") {\n\t\t\tconst textParts: string[] = [];\n\t\t\tconst thinkingParts: string[] = [];\n\t\t\tconst toolCalls: string[] = [];\n\n\t\t\tfor (const block of msg.content) {\n\t\t\t\tif (block.type === \"text\") {\n\t\t\t\t\ttextParts.push(block.text);\n\t\t\t\t} else if (block.type === \"thinking\") {\n\t\t\t\t\tthinkingParts.push(block.thinking);\n\t\t\t\t} else if (block.type === \"toolCall\") {\n\t\t\t\t\tconst args = block.arguments as Record<string, unknown>;\n\t\t\t\t\tconst argsStr = Object.entries(args)\n\t\t\t\t\t\t.map(([k, v]) => `${k}=${JSON.stringify(v)}`)\n\t\t\t\t\t\t.join(\", \");\n\t\t\t\t\ttoolCalls.push(`${block.name}(${argsStr})`);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (thinkingParts.length > 0) {\n\t\t\t\tparts.push(`[Assistant thinking]: ${thinkingParts.join(\"\\n\")}`);\n\t\t\t}\n\t\t\tif (textParts.length > 0) {\n\t\t\t\tparts.push(`[Assistant]: ${textParts.join(\"\\n\")}`);\n\t\t\t}\n\t\t\tif (toolCalls.length > 0) {\n\t\t\t\tparts.push(`[Assistant tool calls]: ${toolCalls.join(\"; \")}`);\n\t\t\t}\n\t\t} else if (msg.role === \"toolResult\") {\n\t\t\tconst content = msg.content\n\t\t\t\t.filter((c): c is { type: \"text\"; text: string } => c.type === \"text\")\n\t\t\t\t.map((c) => c.text)\n\t\t\t\t.join(\"\");\n\t\t\tif (content) {\n\t\t\t\tparts.push(`[Tool result]: ${truncateForSummary(content, TOOL_RESULT_MAX_CHARS)}`);\n\t\t\t}\n\t\t}\n\t}\n\n\treturn parts.join(\"\\n\\n\");\n}\n\n// ============================================================================\n// Summarization System Prompt\n// ============================================================================\n\nexport const SUMMARIZATION_SYSTEM_PROMPT = `You are a context checkpointer. Input: a serialized agent conversation. Output: ONLY the checkpoint, exactly in the format below. No preamble. No commentary. Same language as the user. Never include secrets/keys/tokens — write [REDACTED].\n\nRULES:\n- Recent turns weigh heaviest. Old turns contribute only rules, decisions, and file knowledge.\n- ## Active Task: the user's most recent UNFULFILLED input, near-verbatim. A question awaiting an answer IS an active task. If the user's last signal cancels earlier work (stop/undo/never mind), record the cancellation and DROP the cancelled work everywhere.\n- ### Mandatory Rules: every user prohibition (\"do not X\", \"never Y\", \"stop doing Z\") as one bullet each, imperative, with source turn if known. PRESERVE existing rules verbatim. A user-corrected mistake appears ONLY here as \"DO NOT <mistake>\" — the mistaken work itself must not survive.\n- ## Files: one line per file that matters — path — why it matters (modified/created/read).\n- ## Done: numbered caveman log — \"N. VERB target — outcome\". Exact paths, commands, line numbers, error strings.\n- Sections with nothing: write \"(none)\".\n\nEXAMPLE INPUT (excerpt):\n[user]: add retry to the fetcher, and do not touch the legacy client\n[assistant]: (edits src/fetcher.ts, adds retry loop)\n[tool write src/fetcher.ts]: ok\n[tool bash npm test]: 2 failed: fetcher.test.ts\n[assistant]: (tries wrapping legacy client instead)\n[user]: no — stop changing the legacy client, I said don't touch it. Fix the two tests instead.\n\nEXAMPLE OUTPUT:\n## Active Task\nUser: \"Fix the two failing tests\" (fetcher.test.ts) — retry work continues, legacy-client changes cancelled.\n\n### Mandatory Rules\n- DO NOT touch the legacy client (user, twice)\n\n## Files\n- src/fetcher.ts — retry loop added (modified)\n- test/fetcher.test.ts — 2 failing, current focus (read)\n\n## Done\n1. EDIT src/fetcher.ts — added retry loop\n2. TEST npm test — 2 failed: fetcher.test.ts\n\n## Constraints & Preferences\n(none)\n\n## Key Decisions\n(none)\n\n## Blocked / Open\n- 2 fetcher tests failing\n\n## Critical Context\n(none)\n\nNote what the example TEACHES (not just shows): the legacy-client wrapping attempt (cancelled work) appears nowhere except as the DO-NOT rule; the Active Task is the tail, near-verbatim; Done lines are caveman-format.`;\n"]}
|
|
1
|
+
{"version":3,"file":"utils.js","sourceRoot":"","sources":["../../src/compaction/utils.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Shared utilities for compaction and branch summarization.\n */\n\nimport type { Message } from \"@caupulican/pi-ai\";\nimport type { AgentMessage } from \"../types.ts\";\n\n// ============================================================================\n// File Operation Tracking\n// ============================================================================\n\nexport interface FileOperations {\n\tread: Set<string>;\n\twritten: Set<string>;\n\tedited: Set<string>;\n}\n\nexport function createFileOps(): FileOperations {\n\treturn {\n\t\tread: new Set(),\n\t\twritten: new Set(),\n\t\tedited: new Set(),\n\t};\n}\n\n/**\n * Extract file operations from tool calls in an assistant message.\n */\nexport function extractFileOpsFromMessage(message: AgentMessage, fileOps: FileOperations): void {\n\tif (message.role !== \"assistant\") return;\n\tif (!(\"content\" in message) || !Array.isArray(message.content)) return;\n\n\tfor (const block of message.content) {\n\t\tif (typeof block !== \"object\" || block === null) continue;\n\t\tif (!(\"type\" in block) || block.type !== \"toolCall\") continue;\n\t\tif (!(\"arguments\" in block) || !(\"name\" in block)) continue;\n\n\t\tconst args = block.arguments as Record<string, unknown> | undefined;\n\t\tif (!args) continue;\n\n\t\tconst path = typeof args.path === \"string\" ? args.path : undefined;\n\t\tif (!path) continue;\n\n\t\tswitch (block.name) {\n\t\t\tcase \"read\":\n\t\t\t\tfileOps.read.add(path);\n\t\t\t\tbreak;\n\t\t\tcase \"write\":\n\t\t\t\tfileOps.written.add(path);\n\t\t\t\tbreak;\n\t\t\tcase \"edit\":\n\t\t\t\tfileOps.edited.add(path);\n\t\t\t\tbreak;\n\t\t}\n\t}\n}\n\n/**\n * Compute final file lists from file operations.\n * Returns readFiles (files only read, not modified) and modifiedFiles.\n */\nexport function computeFileLists(fileOps: FileOperations): { readFiles: string[]; modifiedFiles: string[] } {\n\tconst modified = new Set([...fileOps.edited, ...fileOps.written]);\n\tconst readOnly = [...fileOps.read].filter((f) => !modified.has(f)).sort();\n\tconst modifiedFiles = [...modified].sort();\n\treturn { readFiles: readOnly, modifiedFiles };\n}\n\n/**\n * Format file operations as XML tags for summary.\n */\nexport function formatFileOperations(readFiles: string[], modifiedFiles: string[]): string {\n\tconst sections: string[] = [];\n\tif (readFiles.length > 0) {\n\t\tsections.push(`<read-files>\\n${readFiles.join(\"\\n\")}\\n</read-files>`);\n\t}\n\tif (modifiedFiles.length > 0) {\n\t\tsections.push(`<modified-files>\\n${modifiedFiles.join(\"\\n\")}\\n</modified-files>`);\n\t}\n\tif (sections.length === 0) return \"\";\n\treturn `\\n\\n${sections.join(\"\\n\\n\")}`;\n}\n\n// ============================================================================\n// Message Serialization\n// ============================================================================\n\n/** Maximum characters for a tool result in serialized summaries. */\nconst TOOL_RESULT_MAX_CHARS = 2000;\n/** Maximum characters for non-gated assistant thinking in serialized summaries. */\nconst ASSISTANT_THINKING_MAX_CHARS = 2000;\n\n/**\n * Truncate text to a maximum character length for summarization.\n * Keeps the beginning and appends a truncation marker.\n */\nfunction truncateForSummary(text: string, maxChars: number): string {\n\tif (text.length <= maxChars) return text;\n\tconst truncatedChars = text.length - maxChars;\n\treturn `${text.slice(0, maxChars)}\\n\\n[... ${truncatedChars} more characters truncated]`;\n}\n\n/**\n * Serialize LLM messages to text for summarization.\n * This prevents the model from treating it as a conversation to continue.\n * Call convertToLlm() first to handle custom message types.\n *\n * Tool results are truncated to keep the summarization request within\n * reasonable token budgets. Full content is not needed for summarization.\n */\nexport function serializeConversation(messages: Message[]): string {\n\tconst parts: string[] = [];\n\n\tfor (const msg of messages) {\n\t\tif (msg.role === \"user\") {\n\t\t\tconst content =\n\t\t\t\ttypeof msg.content === \"string\"\n\t\t\t\t\t? msg.content\n\t\t\t\t\t: msg.content\n\t\t\t\t\t\t\t.filter((c): c is { type: \"text\"; text: string } => c.type === \"text\")\n\t\t\t\t\t\t\t.map((c) => c.text)\n\t\t\t\t\t\t\t.join(\"\");\n\t\t\tif (content) parts.push(`[User]: ${content}`);\n\t\t} else if (msg.role === \"assistant\") {\n\t\t\tconst textParts: string[] = [];\n\t\t\tconst thinkingParts: string[] = [];\n\t\t\tconst toolCalls: string[] = [];\n\n\t\t\tfor (const block of msg.content) {\n\t\t\t\tif (block.type === \"text\") {\n\t\t\t\t\ttextParts.push(block.text);\n\t\t\t\t} else if (block.type === \"thinking\") {\n\t\t\t\t\tthinkingParts.push(block.thinking);\n\t\t\t\t} else if (block.type === \"toolCall\") {\n\t\t\t\t\tconst args = block.arguments as Record<string, unknown>;\n\t\t\t\t\tconst argsStr = Object.entries(args)\n\t\t\t\t\t\t.map(([k, v]) => `${k}=${JSON.stringify(v)}`)\n\t\t\t\t\t\t.join(\", \");\n\t\t\t\t\ttoolCalls.push(`${block.name}(${argsStr})`);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (thinkingParts.length > 0) {\n\t\t\t\tparts.push(\n\t\t\t\t\t`[Assistant thinking]: ${truncateForSummary(thinkingParts.join(\"\\n\"), ASSISTANT_THINKING_MAX_CHARS)}`,\n\t\t\t\t);\n\t\t\t}\n\t\t\tif (textParts.length > 0) {\n\t\t\t\tparts.push(`[Assistant]: ${textParts.join(\"\\n\")}`);\n\t\t\t}\n\t\t\tif (toolCalls.length > 0) {\n\t\t\t\tparts.push(`[Assistant tool calls]: ${toolCalls.join(\"; \")}`);\n\t\t\t}\n\t\t} else if (msg.role === \"toolResult\") {\n\t\t\tconst content = msg.content\n\t\t\t\t.filter((c): c is { type: \"text\"; text: string } => c.type === \"text\")\n\t\t\t\t.map((c) => c.text)\n\t\t\t\t.join(\"\");\n\t\t\tif (content) {\n\t\t\t\tparts.push(`[Tool result]: ${truncateForSummary(content, TOOL_RESULT_MAX_CHARS)}`);\n\t\t\t}\n\t\t}\n\t}\n\n\treturn parts.join(\"\\n\\n\");\n}\n\n// ============================================================================\n// Summarization System Prompt\n// ============================================================================\n\nexport const SUMMARIZATION_SYSTEM_PROMPT = `You are a context checkpointer. Input: a serialized agent conversation. Output: ONLY the checkpoint, exactly in the format below. No preamble. No commentary. Same language as the user. Never include secrets/keys/tokens — write [REDACTED].\n\nRULES:\n- Recent turns weigh heaviest. Old turns contribute only rules, decisions, and file knowledge.\n- ## Active Task: the user's most recent UNFULFILLED input, near-verbatim. A question awaiting an answer IS an active task. If the user's last signal cancels earlier work (stop/undo/never mind), record the cancellation and DROP the cancelled work everywhere.\n- ### Mandatory Rules: every user prohibition (\"do not X\", \"never Y\", \"stop doing Z\") as one bullet each, imperative, with source turn if known. PRESERVE existing rules verbatim. A user-corrected mistake appears ONLY here as \"DO NOT <mistake>\" — the mistaken work itself must not survive.\n- ## Working Set: the currently active/recent files — path — why they matter.\n- ## Files: bare paths only; modified files must all appear, read files should be recalled when relevant.\n- ## Open Problems: unresolved errors only — command/operation plus first error line. Drop resolved/transient errors.\n- ## Done: numbered caveman log — \"N. VERB target — outcome\". Exact paths, commands, line numbers, error strings.\n- Do NOT carry resolved/transient errors, superseded approaches, or file contents. Record paths and intent, never bodies.\n- Sections with nothing: write \"(none)\".\n\nEXAMPLE INPUT (excerpt):\n[user]: add retry to the fetcher, and do not touch the legacy client\n[assistant]: (edits src/fetcher.ts, adds retry loop)\n[tool write src/fetcher.ts]: ok\n[tool bash npm test]: 2 failed: fetcher.test.ts\n[assistant]: (tries wrapping legacy client instead)\n[user]: no — stop changing the legacy client, I said don't touch it. Fix the two tests instead.\n\nEXAMPLE OUTPUT:\n## Active Task\nUser: \"Fix the two failing tests\" (fetcher.test.ts) — retry work continues, legacy-client changes cancelled.\n\n### Mandatory Rules\n- DO NOT touch the legacy client (user, twice)\n\n## Working Set\n- test/fetcher.test.ts — 2 failing, current focus\n- src/fetcher.ts — retry loop added\n\n## Files\n- src/fetcher.ts\n- test/fetcher.test.ts\n\n## Open Problems\n- TEST npm test: 2 failed: fetcher.test.ts\n\n## Done\n1. EDIT src/fetcher.ts — added retry loop\n2. TEST npm test — 2 failed: fetcher.test.ts\n\n## Key Decisions\n(none)\n\n## Constraints & Preferences\n(none)\n\n## Critical Context\n(none)\n\nNote what the example TEACHES (not just shows): the legacy-client wrapping attempt (cancelled work) appears nowhere except as the DO-NOT rule; the Active Task is the tail, near-verbatim; Done lines are caveman-format.`;\n"]}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type CompactionFacts } from "./extraction.ts";
|
|
2
2
|
export interface VerificationFailure {
|
|
3
3
|
check: string;
|
|
4
4
|
detail: string;
|
|
@@ -12,6 +12,7 @@ export declare const ACTIVE_TASK_CONTAINMENT_THRESHOLD = 0.9;
|
|
|
12
12
|
export declare const MANDATORY_RULES_RECALL_THRESHOLD = 0.7;
|
|
13
13
|
export declare const CANCELLED_WORK_DROPPED_THRESHOLD = 0.1;
|
|
14
14
|
export declare const ACTIONS_RECALL_THRESHOLD = 0.6;
|
|
15
|
+
export declare const OPEN_ERRORS_RECALL_THRESHOLD = 0.7;
|
|
15
16
|
export declare function verifySummary(summary: string, facts: CompactionFacts): VerificationReport;
|
|
16
17
|
export declare function buildRetryPrompt(report: VerificationReport, previousAttempt?: string): string;
|
|
17
18
|
export declare function tokenSet(text: string): Set<string>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"verification.d.ts","sourceRoot":"","sources":["../../src/compaction/verification.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"verification.d.ts","sourceRoot":"","sources":["../../src/compaction/verification.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgC,KAAK,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAErF,MAAM,WAAW,mBAAmB;IACnC,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,kBAAkB;IAClC,EAAE,EAAE,OAAO,CAAC;IACZ,QAAQ,EAAE,mBAAmB,EAAE,CAAC;CAChC;AAED,eAAO,MAAM,2BAA2B,MAAM,CAAC;AAC/C,eAAO,MAAM,iCAAiC,MAAM,CAAC;AACrD,eAAO,MAAM,gCAAgC,MAAM,CAAC;AACpD,eAAO,MAAM,gCAAgC,MAAM,CAAC;AACpD,eAAO,MAAM,wBAAwB,MAAM,CAAC;AAC5C,eAAO,MAAM,4BAA4B,MAAM,CAAC;AAShD,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,eAAe,GAAG,kBAAkB,CA0GzF;AAED,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,kBAAkB,EAAE,eAAe,CAAC,EAAE,MAAM,GAAG,MAAM,CAI7F;AAED,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,CAQlD;AAED,wBAAgB,WAAW,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,CAAC,EAAE,GAAG,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,MAAM,CAWzE;AAED,wBAAgB,OAAO,CAAC,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,MAAM,CAY9D","sourcesContent":["import { ACTIVE_TASK_SOURCE_MAX_CHARS, type CompactionFacts } from \"./extraction.ts\";\n\nexport interface VerificationFailure {\n\tcheck: string;\n\tdetail: string;\n}\n\nexport interface VerificationReport {\n\tok: boolean;\n\tfailures: VerificationFailure[];\n}\n\nexport const FILES_READ_RECALL_THRESHOLD = 0.8;\nexport const ACTIVE_TASK_CONTAINMENT_THRESHOLD = 0.9;\nexport const MANDATORY_RULES_RECALL_THRESHOLD = 0.7;\nexport const CANCELLED_WORK_DROPPED_THRESHOLD = 0.1;\nexport const ACTIONS_RECALL_THRESHOLD = 0.6;\nexport const OPEN_ERRORS_RECALL_THRESHOLD = 0.7;\n\nconst SECTION_FILES = \"files\";\nconst SECTION_WORKING_SET = \"working set\";\nconst SECTION_OPEN_PROBLEMS = \"open problems\";\nconst SECTION_DONE = \"done\";\nconst SECTION_ACTIVE_TASK = \"active task\";\nconst SECTION_MANDATORY_RULES = \"mandatory rules\";\n\nexport function verifySummary(summary: string, facts: CompactionFacts): VerificationReport {\n\tif (factsAreEmpty(facts)) {\n\t\treturn { ok: true, failures: [] };\n\t}\n\n\tconst sections = extractSections(summary);\n\tconst failures: VerificationFailure[] = [];\n\tconst filesSection = sections[SECTION_FILES] ?? \"\";\n\tconst workingSetSection = sections[SECTION_WORKING_SET] ?? \"\";\n\tconst openProblemsSection = sections[SECTION_OPEN_PROBLEMS] ?? \"\";\n\tconst doneSection = sections[SECTION_DONE] ?? \"\";\n\tconst activeTaskSection = sections[SECTION_ACTIVE_TASK] ?? \"\";\n\tconst mandatoryRulesSection = sections[SECTION_MANDATORY_RULES] ?? \"\";\n\n\tconst modifiedFiles = facts.files.filter((file) => file.kind !== \"read\");\n\tconst missingModifiedFiles = modifiedFiles.map((file) => file.path).filter((path) => !filesSection.includes(path));\n\tif (missingModifiedFiles.length > 0) {\n\t\tfailures.push({\n\t\t\tcheck: \"files-modified-recall\",\n\t\t\tdetail: `Missing modified/created files in ## Files: ${missingModifiedFiles.join(\", \")}`,\n\t\t});\n\t}\n\n\tconst workingSetPaths = facts.workingSet.map((file) => file.path);\n\tconst missingWorkingSetPaths = workingSetPaths.filter((path) => !workingSetSection.includes(path));\n\tif (missingWorkingSetPaths.length > 0) {\n\t\tfailures.push({\n\t\t\tcheck: \"working-set-recall\",\n\t\t\tdetail: `Missing working-set files in ## Working Set: ${missingWorkingSetPaths.join(\", \")}`,\n\t\t});\n\t}\n\n\tconst readPaths = facts.files.filter((file) => file.kind === \"read\").map((file) => file.path);\n\tif (readPaths.length > 0) {\n\t\tconst score = containment(tokenSet(readPaths.join(\"\\n\")), tokenSet(filesSection));\n\t\tif (score < FILES_READ_RECALL_THRESHOLD) {\n\t\t\tfailures.push({\n\t\t\t\tcheck: \"files-read-recall\",\n\t\t\t\tdetail: `Read file recall ${formatScore(score)} below ${FILES_READ_RECALL_THRESHOLD}`,\n\t\t\t});\n\t\t}\n\t}\n\n\tif (facts.activeTaskSource) {\n\t\tconst score = containment(\n\t\t\ttokenSet(facts.activeTaskSource.slice(0, ACTIVE_TASK_SOURCE_MAX_CHARS)),\n\t\t\ttokenSet(activeTaskSection),\n\t\t);\n\t\tif (score < ACTIVE_TASK_CONTAINMENT_THRESHOLD) {\n\t\t\tfailures.push({\n\t\t\t\tcheck: \"active-task-containment\",\n\t\t\t\tdetail: `Active task containment ${formatScore(score)} below ${ACTIVE_TASK_CONTAINMENT_THRESHOLD}`,\n\t\t\t});\n\t\t}\n\t}\n\n\tfor (const prohibition of facts.prohibitions) {\n\t\tconst score = containment(tokenSet(prohibition), tokenSet(mandatoryRulesSection));\n\t\tif (score < MANDATORY_RULES_RECALL_THRESHOLD) {\n\t\t\tfailures.push({\n\t\t\t\tcheck: \"mandatory-rules-recall\",\n\t\t\t\tdetail: `Missing mandatory rule: ${prohibition}`,\n\t\t\t});\n\t\t}\n\t}\n\n\tif (facts.cancelledText) {\n\t\tconst summaryOutsideMandatoryRules = removeSection(summary, SECTION_MANDATORY_RULES);\n\t\t// File paths from the facts are REQUIRED elsewhere (files-modified/read-recall demand them\n\t\t// in ## Files), so counting them as cancelled-work leakage would make the two gates\n\t\t// unsatisfiable together whenever a reversal message references a touched file.\n\t\tconst factPathTokens = tokenSet(facts.files.map((file) => file.path).join(\"\\n\"));\n\t\tconst cancelledTokens = new Set([...tokenSet(facts.cancelledText)].filter((token) => !factPathTokens.has(token)));\n\t\tconst score = containment(cancelledTokens, tokenSet(summaryOutsideMandatoryRules));\n\t\tif (score > CANCELLED_WORK_DROPPED_THRESHOLD) {\n\t\t\tfailures.push({\n\t\t\t\tcheck: \"cancelled-work-dropped\",\n\t\t\t\tdetail: `Cancelled work leakage ${formatScore(score)} above ${CANCELLED_WORK_DROPPED_THRESHOLD}`,\n\t\t\t});\n\t\t}\n\t}\n\n\tfor (const error of facts.errorFacts) {\n\t\tconst score = containment(tokenSet(`${error.operation}: ${error.error}`), tokenSet(openProblemsSection));\n\t\tif (score < OPEN_ERRORS_RECALL_THRESHOLD) {\n\t\t\tfailures.push({\n\t\t\t\tcheck: \"open-errors-recall\",\n\t\t\t\tdetail: `Open error recall ${formatScore(score)} below ${OPEN_ERRORS_RECALL_THRESHOLD}: ${error.operation}`,\n\t\t\t});\n\t\t}\n\t}\n\n\tif (facts.actions.length > 0) {\n\t\t// Asymmetric on purpose: the update path carries prior ## Done items forward (bounded), so a\n\t\t// symmetric overlap metric would punish faithful carry-over — the gate demands only that the\n\t\t// NEW span's actions are recalled in ## Done, however much history rides alongside them.\n\t\tconst score = containment(tokenSet(facts.actions.join(\"\\n\")), tokenSet(doneSection));\n\t\tif (score < ACTIONS_RECALL_THRESHOLD) {\n\t\t\tfailures.push({\n\t\t\t\tcheck: \"actions-recall\",\n\t\t\t\tdetail: `New-action recall in ## Done ${formatScore(score)} below ${ACTIONS_RECALL_THRESHOLD}`,\n\t\t\t});\n\t\t}\n\t}\n\n\treturn { ok: failures.length === 0, failures };\n}\n\nexport function buildRetryPrompt(report: VerificationReport, previousAttempt?: string): string {\n\tconst failures = report.failures.map((failure) => `${failure.check}: ${failure.detail}`).join(\"; \");\n\tconst previous = previousAttempt ? `\\n\\n<previous-attempt>\\n${previousAttempt}\\n</previous-attempt>` : \"\";\n\treturn `Your previous checkpoint failed verification: ${failures}. Fix ONLY these omissions.${previous}`;\n}\n\nexport function tokenSet(text: string): Set<string> {\n\treturn new Set(\n\t\ttext\n\t\t\t.toLowerCase()\n\t\t\t.split(/[^a-z0-9_./-]+/)\n\t\t\t.map((token) => token.trim())\n\t\t\t.filter((token) => token.length >= 3),\n\t);\n}\n\nexport function containment(needle: Set<string>, hay: Set<string>): number {\n\tif (needle.size === 0) {\n\t\treturn 1;\n\t}\n\tlet hits = 0;\n\tfor (const token of needle) {\n\t\tif (hay.has(token)) {\n\t\t\thits += 1;\n\t\t}\n\t}\n\treturn hits / needle.size;\n}\n\nexport function jaccard(a: Set<string>, b: Set<string>): number {\n\tif (a.size === 0 && b.size === 0) {\n\t\treturn 1;\n\t}\n\tlet intersection = 0;\n\tfor (const token of a) {\n\t\tif (b.has(token)) {\n\t\t\tintersection += 1;\n\t\t}\n\t}\n\tconst union = new Set([...a, ...b]).size;\n\treturn union === 0 ? 1 : intersection / union;\n}\n\nfunction factsAreEmpty(facts: CompactionFacts): boolean {\n\treturn (\n\t\tfacts.files.length === 0 &&\n\t\tfacts.workingSet.length === 0 &&\n\t\tfacts.actions.length === 0 &&\n\t\tfacts.errorFacts.length === 0 &&\n\t\tfacts.prohibitions.length === 0 &&\n\t\tfacts.cancelledText === \"\" &&\n\t\tfacts.activeTaskSource === \"\"\n\t);\n}\n\nfunction extractSections(summary: string): Record<string, string> {\n\tconst sections: Record<string, string> = {};\n\tlet current: string | undefined;\n\tlet bucket: string[] = [];\n\n\tconst flush = (): void => {\n\t\tif (current) {\n\t\t\tsections[current] = bucket.join(\"\\n\").trim();\n\t\t}\n\t\tbucket = [];\n\t};\n\n\tfor (const line of summary.split(/\\r?\\n/)) {\n\t\tconst match = /^(?:##|###)\\s+(.+?)\\s*$/.exec(line);\n\t\tif (match) {\n\t\t\tflush();\n\t\t\tcurrent = normalizeHeading(match[1]);\n\t\t\tcontinue;\n\t\t}\n\t\tif (current) {\n\t\t\tbucket.push(line);\n\t\t}\n\t}\n\tflush();\n\treturn sections;\n}\n\nfunction removeSection(summary: string, heading: string): string {\n\tconst normalizedHeading = normalizeHeading(heading);\n\tconst kept: string[] = [];\n\tlet skipping = false;\n\tfor (const line of summary.split(/\\r?\\n/)) {\n\t\tconst match = /^(?:##|###)\\s+(.+?)\\s*$/.exec(line);\n\t\tif (match) {\n\t\t\tskipping = normalizeHeading(match[1]) === normalizedHeading;\n\t\t\tif (skipping) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t}\n\t\tif (!skipping) {\n\t\t\tkept.push(line);\n\t\t}\n\t}\n\treturn kept.join(\"\\n\");\n}\n\nfunction normalizeHeading(heading: string): string {\n\treturn heading.trim().toLowerCase();\n}\n\nfunction formatScore(score: number): string {\n\treturn score.toFixed(2);\n}\n"]}
|
|
@@ -1,9 +1,13 @@
|
|
|
1
|
+
import { ACTIVE_TASK_SOURCE_MAX_CHARS } from "./extraction.js";
|
|
1
2
|
export const FILES_READ_RECALL_THRESHOLD = 0.8;
|
|
2
3
|
export const ACTIVE_TASK_CONTAINMENT_THRESHOLD = 0.9;
|
|
3
4
|
export const MANDATORY_RULES_RECALL_THRESHOLD = 0.7;
|
|
4
5
|
export const CANCELLED_WORK_DROPPED_THRESHOLD = 0.1;
|
|
5
6
|
export const ACTIONS_RECALL_THRESHOLD = 0.6;
|
|
7
|
+
export const OPEN_ERRORS_RECALL_THRESHOLD = 0.7;
|
|
6
8
|
const SECTION_FILES = "files";
|
|
9
|
+
const SECTION_WORKING_SET = "working set";
|
|
10
|
+
const SECTION_OPEN_PROBLEMS = "open problems";
|
|
7
11
|
const SECTION_DONE = "done";
|
|
8
12
|
const SECTION_ACTIVE_TASK = "active task";
|
|
9
13
|
const SECTION_MANDATORY_RULES = "mandatory rules";
|
|
@@ -14,6 +18,8 @@ export function verifySummary(summary, facts) {
|
|
|
14
18
|
const sections = extractSections(summary);
|
|
15
19
|
const failures = [];
|
|
16
20
|
const filesSection = sections[SECTION_FILES] ?? "";
|
|
21
|
+
const workingSetSection = sections[SECTION_WORKING_SET] ?? "";
|
|
22
|
+
const openProblemsSection = sections[SECTION_OPEN_PROBLEMS] ?? "";
|
|
17
23
|
const doneSection = sections[SECTION_DONE] ?? "";
|
|
18
24
|
const activeTaskSection = sections[SECTION_ACTIVE_TASK] ?? "";
|
|
19
25
|
const mandatoryRulesSection = sections[SECTION_MANDATORY_RULES] ?? "";
|
|
@@ -25,6 +31,14 @@ export function verifySummary(summary, facts) {
|
|
|
25
31
|
detail: `Missing modified/created files in ## Files: ${missingModifiedFiles.join(", ")}`,
|
|
26
32
|
});
|
|
27
33
|
}
|
|
34
|
+
const workingSetPaths = facts.workingSet.map((file) => file.path);
|
|
35
|
+
const missingWorkingSetPaths = workingSetPaths.filter((path) => !workingSetSection.includes(path));
|
|
36
|
+
if (missingWorkingSetPaths.length > 0) {
|
|
37
|
+
failures.push({
|
|
38
|
+
check: "working-set-recall",
|
|
39
|
+
detail: `Missing working-set files in ## Working Set: ${missingWorkingSetPaths.join(", ")}`,
|
|
40
|
+
});
|
|
41
|
+
}
|
|
28
42
|
const readPaths = facts.files.filter((file) => file.kind === "read").map((file) => file.path);
|
|
29
43
|
if (readPaths.length > 0) {
|
|
30
44
|
const score = containment(tokenSet(readPaths.join("\n")), tokenSet(filesSection));
|
|
@@ -36,7 +50,7 @@ export function verifySummary(summary, facts) {
|
|
|
36
50
|
}
|
|
37
51
|
}
|
|
38
52
|
if (facts.activeTaskSource) {
|
|
39
|
-
const score = containment(tokenSet(facts.activeTaskSource), tokenSet(activeTaskSection));
|
|
53
|
+
const score = containment(tokenSet(facts.activeTaskSource.slice(0, ACTIVE_TASK_SOURCE_MAX_CHARS)), tokenSet(activeTaskSection));
|
|
40
54
|
if (score < ACTIVE_TASK_CONTAINMENT_THRESHOLD) {
|
|
41
55
|
failures.push({
|
|
42
56
|
check: "active-task-containment",
|
|
@@ -68,6 +82,15 @@ export function verifySummary(summary, facts) {
|
|
|
68
82
|
});
|
|
69
83
|
}
|
|
70
84
|
}
|
|
85
|
+
for (const error of facts.errorFacts) {
|
|
86
|
+
const score = containment(tokenSet(`${error.operation}: ${error.error}`), tokenSet(openProblemsSection));
|
|
87
|
+
if (score < OPEN_ERRORS_RECALL_THRESHOLD) {
|
|
88
|
+
failures.push({
|
|
89
|
+
check: "open-errors-recall",
|
|
90
|
+
detail: `Open error recall ${formatScore(score)} below ${OPEN_ERRORS_RECALL_THRESHOLD}: ${error.operation}`,
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
}
|
|
71
94
|
if (facts.actions.length > 0) {
|
|
72
95
|
// Asymmetric on purpose: the update path carries prior ## Done items forward (bounded), so a
|
|
73
96
|
// symmetric overlap metric would punish faithful carry-over — the gate demands only that the
|
|
@@ -121,7 +144,9 @@ export function jaccard(a, b) {
|
|
|
121
144
|
}
|
|
122
145
|
function factsAreEmpty(facts) {
|
|
123
146
|
return (facts.files.length === 0 &&
|
|
147
|
+
facts.workingSet.length === 0 &&
|
|
124
148
|
facts.actions.length === 0 &&
|
|
149
|
+
facts.errorFacts.length === 0 &&
|
|
125
150
|
facts.prohibitions.length === 0 &&
|
|
126
151
|
facts.cancelledText === "" &&
|
|
127
152
|
facts.activeTaskSource === "");
|