@compaction/cli 0.1.4 → 0.3.0
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 +51 -12
- package/dist/cli/commands/activity.d.ts +8 -0
- package/dist/cli/commands/activity.js +35 -0
- package/dist/cli/commands/apply-context.js +68 -0
- package/dist/cli/commands/billing-delta.js +12 -1
- package/dist/cli/commands/capture-claude-code.d.ts +44 -0
- package/dist/cli/commands/capture-claude-code.js +206 -0
- package/dist/cli/commands/capture.js +283 -1
- package/dist/cli/commands/compact.js +24 -11
- package/dist/cli/commands/context.d.ts +2 -0
- package/dist/cli/commands/context.js +130 -0
- package/dist/cli/commands/dev.d.ts +17 -0
- package/dist/cli/commands/dev.js +131 -0
- package/dist/cli/commands/gateway.d.ts +31 -0
- package/dist/cli/commands/gateway.js +312 -0
- package/dist/cli/commands/hooks.d.ts +5 -0
- package/dist/cli/commands/hooks.js +181 -0
- package/dist/cli/commands/import.js +3 -1
- package/dist/cli/commands/init.js +422 -17
- package/dist/cli/commands/input-compaction-ab.d.ts +2 -0
- package/dist/cli/commands/input-compaction-ab.js +125 -0
- package/dist/cli/commands/optimize-hosted.d.ts +54 -0
- package/dist/cli/commands/optimize-hosted.js +123 -0
- package/dist/cli/commands/optimize.js +8 -0
- package/dist/cli/commands/output-shaping-ab.d.ts +2 -0
- package/dist/cli/commands/output-shaping-ab.js +132 -0
- package/dist/cli/commands/output-shaping.d.ts +7 -0
- package/dist/cli/commands/output-shaping.js +43 -0
- package/dist/cli/commands/policies.d.ts +2 -0
- package/dist/cli/commands/policies.js +80 -0
- package/dist/cli/commands/precall.d.ts +19 -0
- package/dist/cli/commands/precall.js +436 -0
- package/dist/cli/commands/recommend.js +1 -1
- package/dist/cli/commands/run.js +310 -18
- package/dist/cli/commands/upgrade-status.d.ts +42 -0
- package/dist/cli/commands/upgrade-status.js +152 -0
- package/dist/cli/cursor-export-read.d.ts +13 -0
- package/dist/cli/cursor-export-read.js +53 -0
- package/dist/cli/cursor-live-preflight.d.ts +19 -0
- package/dist/cli/cursor-live-preflight.js +46 -0
- package/dist/cli/index.js +39 -2
- package/dist/cli/onboarding/GatewayTui.d.ts +33 -0
- package/dist/cli/onboarding/GatewayTui.js +113 -0
- package/dist/cli/onboarding/model.d.ts +32 -1
- package/dist/cli/onboarding/model.js +27 -2
- package/dist/cli/terminal-logo.d.ts +27 -0
- package/dist/cli/terminal-logo.js +49 -0
- package/dist/core/activity-event.d.ts +107 -0
- package/dist/core/activity-event.js +182 -0
- package/dist/core/activity-store.d.ts +63 -0
- package/dist/core/activity-store.js +254 -0
- package/dist/core/activity-view.d.ts +76 -0
- package/dist/core/activity-view.js +120 -0
- package/dist/core/api-client/index.d.ts +3 -1
- package/dist/core/api-client/index.js +4 -0
- package/dist/core/api-client/payload.d.ts +5 -1
- package/dist/core/api-client/payload.js +8 -6
- package/dist/core/api-client/persisted-config.d.ts +57 -0
- package/dist/core/api-client/persisted-config.js +156 -0
- package/dist/core/api-client/tool.d.ts +29 -0
- package/dist/core/api-client/tool.js +50 -0
- package/dist/core/api-client/types.d.ts +15 -4
- package/dist/core/auto-apply-ask.d.ts +58 -0
- package/dist/core/auto-apply-ask.js +105 -0
- package/dist/core/auto-apply-gates.d.ts +76 -0
- package/dist/core/auto-apply-gates.js +113 -0
- package/dist/core/before-call-activity.d.ts +35 -0
- package/dist/core/before-call-activity.js +103 -0
- package/dist/core/before-call-recovery.d.ts +19 -0
- package/dist/core/before-call-recovery.js +46 -0
- package/dist/core/before-call-stdin.d.ts +59 -0
- package/dist/core/before-call-stdin.js +78 -0
- package/dist/core/before-call.d.ts +149 -0
- package/dist/core/before-call.js +358 -0
- package/dist/core/billing-delta/billing-delta-record.d.ts +28 -0
- package/dist/core/billing-delta/billing-delta-record.js +26 -0
- package/dist/core/capture-record.d.ts +40 -0
- package/dist/core/capture-record.js +55 -0
- package/dist/core/claude-code-before-call.d.ts +37 -0
- package/dist/core/claude-code-before-call.js +120 -0
- package/dist/core/claude-code-connect.d.ts +31 -0
- package/dist/core/claude-code-connect.js +87 -0
- package/dist/core/claude-code-hook-record.d.ts +71 -0
- package/dist/core/claude-code-hook-record.js +0 -0
- package/dist/core/claude-code-hooks.d.ts +77 -0
- package/dist/core/claude-code-hooks.js +141 -0
- package/dist/core/codex-capture.d.ts +45 -0
- package/dist/core/codex-capture.js +204 -0
- package/dist/core/command-runner.js +5 -1
- package/dist/core/compaction-artifacts.d.ts +51 -0
- package/dist/core/compaction-artifacts.js +242 -0
- package/dist/core/compactor.d.ts +9 -0
- package/dist/core/compactor.js +95 -0
- package/dist/core/context-store-eval-cases.d.ts +6 -0
- package/dist/core/context-store-eval-cases.js +331 -0
- package/dist/core/context-store-eval.d.ts +140 -0
- package/dist/core/context-store-eval.js +138 -0
- package/dist/core/context-store-fs.d.ts +73 -0
- package/dist/core/context-store-fs.js +157 -0
- package/dist/core/context-store-sufficiency.d.ts +98 -0
- package/dist/core/context-store-sufficiency.js +135 -0
- package/dist/core/context-store.d.ts +155 -0
- package/dist/core/context-store.js +228 -0
- package/dist/core/cross-surface-event.d.ts +306 -0
- package/dist/core/cross-surface-event.js +330 -0
- package/dist/core/cursor-capture.d.ts +54 -0
- package/dist/core/cursor-capture.js +215 -0
- package/dist/core/cursor-preflight-probe.d.ts +8 -0
- package/dist/core/cursor-preflight-probe.js +88 -0
- package/dist/core/cursor-preflight.d.ts +86 -0
- package/dist/core/cursor-preflight.js +126 -0
- package/dist/core/gateway/apply-activation.d.ts +39 -0
- package/dist/core/gateway/apply-activation.js +84 -0
- package/dist/core/gateway/apply-policy.d.ts +64 -0
- package/dist/core/gateway/apply-policy.js +221 -0
- package/dist/core/gateway/apply-receipt.d.ts +36 -0
- package/dist/core/gateway/apply-receipt.js +75 -0
- package/dist/core/gateway/cache-proof.d.ts +45 -0
- package/dist/core/gateway/cache-proof.js +65 -0
- package/dist/core/gateway/configure.d.ts +50 -0
- package/dist/core/gateway/configure.js +169 -0
- package/dist/core/gateway/openai-usage.d.ts +56 -0
- package/dist/core/gateway/openai-usage.js +128 -0
- package/dist/core/gateway/receipt.d.ts +138 -0
- package/dist/core/gateway/receipt.js +120 -0
- package/dist/core/gateway/recovery.d.ts +23 -0
- package/dist/core/gateway/recovery.js +68 -0
- package/dist/core/gateway/server.d.ts +51 -0
- package/dist/core/gateway/server.js +276 -0
- package/dist/core/gateway/status.d.ts +45 -0
- package/dist/core/gateway/status.js +109 -0
- package/dist/core/hook-usage-aggregate.d.ts +47 -0
- package/dist/core/hook-usage-aggregate.js +161 -0
- package/dist/core/input-compaction-ab.d.ts +111 -0
- package/dist/core/input-compaction-ab.js +158 -0
- package/dist/core/local-run-record.d.ts +109 -0
- package/dist/core/local-run-record.js +223 -0
- package/dist/core/output-shaping-ab.d.ts +140 -0
- package/dist/core/output-shaping-ab.js +146 -0
- package/dist/core/output-shaping-attach.d.ts +31 -0
- package/dist/core/output-shaping-attach.js +57 -0
- package/dist/core/output-shaping.d.ts +56 -0
- package/dist/core/output-shaping.js +89 -0
- package/dist/core/policy-middleware.d.ts +121 -0
- package/dist/core/policy-middleware.js +919 -0
- package/dist/core/policy-preferences.d.ts +99 -0
- package/dist/core/policy-preferences.js +232 -0
- package/dist/core/report-generator.d.ts +19 -1
- package/dist/core/report-generator.js +51 -0
- package/dist/core/run-aggregator.d.ts +56 -1
- package/dist/core/run-aggregator.js +93 -2
- package/dist/core/run-flow-report.d.ts +82 -0
- package/dist/core/run-flow-report.js +71 -0
- package/dist/core/safety-report.js +8 -1
- package/dist/core/shim-capture-bridge.d.ts +32 -0
- package/dist/core/shim-capture-bridge.js +88 -0
- package/dist/core/skill-injection-policy.d.ts +72 -0
- package/dist/core/skill-injection-policy.js +183 -0
- package/dist/core/spend-attribution.js +1 -1
- package/dist/core/token-accounting.d.ts +1 -1
- package/dist/core/tool-shim.d.ts +129 -0
- package/dist/core/tool-shim.js +447 -0
- package/dist/core/trace-adapters.d.ts +8 -0
- package/dist/core/trace-adapters.js +4 -0
- package/dist/core/trace-parser.d.ts +13 -13
- package/dist/core/trace-parser.js +6 -6
- package/dist/core/types.d.ts +45 -2
- package/dist/core/waste-detector.d.ts +20 -0
- package/dist/core/waste-detector.js +160 -6
- package/package.json +1 -1
package/dist/core/types.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export type TraceRole = "system" | "user" | "assistant" | "tool" | "stdout" | "stderr";
|
|
2
|
-
export type TraceSource = "manual" | "cli_wrapper" | "local_command" | "demo" | "real_captured" | "provider_usage" | "codex_import";
|
|
2
|
+
export type TraceSource = "manual" | "cli_wrapper" | "local_command" | "demo" | "real_captured" | "provider_usage" | "codex_import" | "cursor_import";
|
|
3
3
|
export interface TraceCommandMetadata {
|
|
4
4
|
command: string;
|
|
5
5
|
args: string[];
|
|
@@ -43,7 +43,17 @@ export interface CostEstimate {
|
|
|
43
43
|
totalCostUsd: number;
|
|
44
44
|
}
|
|
45
45
|
export interface WasteFinding {
|
|
46
|
-
|
|
46
|
+
/**
|
|
47
|
+
* `superseded_same_source_read` (input-compaction category 1, accepted design
|
|
48
|
+
* docs/design/input-compaction-policy-reach.md D2): the SAME source was read multiple times and
|
|
49
|
+
* a LATER read is authoritative; an EARLIER full copy whose content GENUINELY DIFFERS (whitespace-
|
|
50
|
+
* normalized) from the latest can be replaced by a recoverable pointer to the retained original.
|
|
51
|
+
* ADDITIVE to `repeated_tool_output` (which handles identical-up-to-whitespace repeats) and never
|
|
52
|
+
* weakens it: an identical earlier copy is LEFT to `repeated_tool_output`, never category-1-compacted.
|
|
53
|
+
* Convention for this category's `messageIds`: `[0]` = the KEPT latest authoritative read (verbatim),
|
|
54
|
+
* `[1..]` = the earlier superseded reads to compact (each recoverable from the retained original).
|
|
55
|
+
*/
|
|
56
|
+
category: "repeated_tool_output" | "stale_context" | "verbose_observation" | "repeated_skill_injection" | "superseded_same_source_read";
|
|
47
57
|
messageIds: string[];
|
|
48
58
|
summary: string;
|
|
49
59
|
estimatedTokens: number;
|
|
@@ -247,6 +257,39 @@ export interface CompactionReport {
|
|
|
247
257
|
/** Non-sensitive structural count: number of messages hashed. */
|
|
248
258
|
message_count: number;
|
|
249
259
|
};
|
|
260
|
+
/**
|
|
261
|
+
* Composed per-run SAVINGS EVIDENCE record (additive, OPTIONAL, backward-compatible) —
|
|
262
|
+
* the V0.2 "evidence labels per result" surface. Older reports omit it. See
|
|
263
|
+
* `SavingsEvidence`. Composes EXISTING report numbers; makes no new/stronger claim.
|
|
264
|
+
*/
|
|
265
|
+
savings_evidence?: SavingsEvidence;
|
|
266
|
+
}
|
|
267
|
+
/**
|
|
268
|
+
* Composed, per-run savings-evidence record (V0.2 "evidence labels per result"). Ties the
|
|
269
|
+
* evidence chain into ONE place and states, per run, the cost-source label, the
|
|
270
|
+
* recoverability/semantic status, and exactly which `claims-and-evidence-ladder` rung the
|
|
271
|
+
* figure stands on — so no consumer reads a stronger claim than the evidence supports.
|
|
272
|
+
*
|
|
273
|
+
* A single run is rung 1 (local estimate). The measured rung 1.5
|
|
274
|
+
* (`measured_caveated_estimate_delta`) is produced ONLY by the aggregate increment across
|
|
275
|
+
* N≥3 distinct runs, NEVER here. `billing_confirmed` is ALWAYS false; `semantic_preservation`
|
|
276
|
+
* is ALWAYS `not_evaluated` (never auto-certified).
|
|
277
|
+
*/
|
|
278
|
+
export interface SavingsEvidence {
|
|
279
|
+
/** Is the run attributable to a verifiable identity (trace fingerprint present)? */
|
|
280
|
+
trace_identity: "fingerprinted" | "unidentified";
|
|
281
|
+
/** Honest cost-source label for this run's figures (mirrors `CostSource`). */
|
|
282
|
+
cost_source: "provider_reported" | "price_table_estimate" | "local_estimate" | "missing" | "unknown";
|
|
283
|
+
/** Deterministic recoverability status when an eval ran (`compact --eval`); else `not_evaluated`. */
|
|
284
|
+
recoverability: "passed" | "failed" | "not_computed" | "not_evaluated";
|
|
285
|
+
/** Semantic / meaning preservation is NEVER auto-certified — always `not_evaluated`. */
|
|
286
|
+
semantic_preservation: "not_evaluated";
|
|
287
|
+
/** Which claims-and-evidence-ladder rung this single-run figure stands on. */
|
|
288
|
+
evidence_rung: "local_estimate_single_run";
|
|
289
|
+
/** No v0 per-run figure is billing-confirmed. Always `false`. */
|
|
290
|
+
billing_confirmed: false;
|
|
291
|
+
/** One-line honest label (never asserts a stronger claim than the rung). */
|
|
292
|
+
label: string;
|
|
250
293
|
}
|
|
251
294
|
export interface CompactionPolicy {
|
|
252
295
|
policy_name: string;
|
|
@@ -5,4 +5,24 @@ export interface WasteDetectionThresholds {
|
|
|
5
5
|
minDuplicateCharacters?: number;
|
|
6
6
|
}
|
|
7
7
|
export declare function detectWaste(trace: AgentTrace, thresholds?: WasteDetectionThresholds): WasteFinding[];
|
|
8
|
+
/**
|
|
9
|
+
* Input-compaction category 1: detect SUPERSEDED same-source reads.
|
|
10
|
+
*
|
|
11
|
+
* When the SAME source (provable source identity) is read ≥2 times, the LATEST read is authoritative
|
|
12
|
+
* (preserved verbatim). Each EARLIER read whose whitespace-normalized content GENUINELY DIFFERS from
|
|
13
|
+
* the latest's is a superseded candidate that can be replaced by a recoverable pointer to the
|
|
14
|
+
* retained original. Conservative & additive:
|
|
15
|
+
* - Outputs with NO provable source identity are skipped (not eligible).
|
|
16
|
+
* - An earlier output that is identical-up-to-whitespace to the latest is LEFT to the existing
|
|
17
|
+
* `repeated_tool_output` policy (NOT category-1-compacted) — no overlap, no double-compaction.
|
|
18
|
+
* - Each compacted (earlier) output must meet the SAME thresholds detectWaste uses
|
|
19
|
+
* (minDuplicateTokens / minDuplicateCharacters).
|
|
20
|
+
*
|
|
21
|
+
* Finding convention: `messageIds[0]` = KEPT latest authoritative read; `messageIds[1..]` = earlier
|
|
22
|
+
* superseded reads to compact (estimatedTokens = sum of the compacted earlier reads' estimates).
|
|
23
|
+
*
|
|
24
|
+
* NO scoring formula, weight, relevance, or learned logic — only deterministic source-identity
|
|
25
|
+
* equality + whitespace-normalized content difference + the existing min-tokens/chars thresholds.
|
|
26
|
+
*/
|
|
27
|
+
export declare function detectSupersededSameSourceReads(trace: AgentTrace, thresholds?: WasteDetectionThresholds): WasteFinding[];
|
|
8
28
|
export declare function getCompactedMessageIds(findings: WasteFinding[]): Set<string>;
|
|
@@ -35,17 +35,171 @@ export function detectWaste(trace, thresholds = {}) {
|
|
|
35
35
|
}
|
|
36
36
|
firstToolOutputBySignature.set(signature, message);
|
|
37
37
|
}
|
|
38
|
+
// ADDITIVE: input-compaction category 1 (superseded same-source reads). The existing
|
|
39
|
+
// repeated_tool_output logic above is UNCHANGED; category-1 findings are appended so the existing
|
|
40
|
+
// pipeline (getCompactedMessageIds, policy-middleware) can pick them up. Category 1 only emits when
|
|
41
|
+
// an earlier same-source read GENUINELY DIFFERS from the latest, so it never overlaps an
|
|
42
|
+
// identical-up-to-whitespace repeat (which stays a repeated_tool_output finding).
|
|
43
|
+
findings.push(...detectSupersededSameSourceReads(trace, thresholds));
|
|
44
|
+
return findings;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Source identity of a tool OUTPUT for input-compaction category 1 (`superseded_same_source_read`).
|
|
48
|
+
*
|
|
49
|
+
* Identity = `toolName + "::" + normalizedArgs`, where `normalizedArgs` is derived from the
|
|
50
|
+
* originating tool CALL: the nearest PRECEDING assistant message (since the last tool output) with
|
|
51
|
+
* the SAME `toolName` whose `content` parses as the call args. Parsing is deterministic and
|
|
52
|
+
* CONSERVATIVE — two accepted shapes ONLY:
|
|
53
|
+
* 1. a JSON object -> JSON.stringify with sorted keys (so key order does not change identity);
|
|
54
|
+
* 2. a `"Tool call: <cmd>"` string -> the `<cmd>`, whitespace-normalized.
|
|
55
|
+
* Anything else (no preceding same-tool call, unparseable content) yields `null` => the output is
|
|
56
|
+
* NOT eligible for category 1 (conservative: if source identity cannot be PROVEN, do not compact).
|
|
57
|
+
*
|
|
58
|
+
* This is pure deterministic equality of provable source identity. There is NO scoring, threshold
|
|
59
|
+
* (beyond the existing min-tokens/chars in detectWaste), weight, relevance, or learned logic here.
|
|
60
|
+
*/
|
|
61
|
+
function normalizeCallArgs(callContent) {
|
|
62
|
+
const trimmed = callContent.trim();
|
|
63
|
+
// Shape 2: a "Tool call: <cmd>" string (the command form, whitespace-normalized).
|
|
64
|
+
const toolCallMatch = /^Tool call:\s*(.+)$/s.exec(trimmed);
|
|
65
|
+
if (toolCallMatch) {
|
|
66
|
+
const command = toolCallMatch[1].trim().replace(/\s+/g, " ");
|
|
67
|
+
return command.length > 0 ? `cmd:${command}` : null;
|
|
68
|
+
}
|
|
69
|
+
// Shape 1: a JSON object -> stable stringify with sorted keys.
|
|
70
|
+
try {
|
|
71
|
+
const parsed = JSON.parse(trimmed);
|
|
72
|
+
if (parsed !== null && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
73
|
+
const sortedKeys = Object.keys(parsed).sort();
|
|
74
|
+
const stable = JSON.stringify(parsed, sortedKeys);
|
|
75
|
+
return `json:${stable}`;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
// Not JSON — fall through to "not provable".
|
|
80
|
+
}
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Resolve the PROVABLE source identity of a tool output at `outputIndex`. Walks BACKWARD from the
|
|
85
|
+
* output to the nearest preceding assistant message that has the SAME toolName and whose content
|
|
86
|
+
* parses as call args (the originating CALL). Stops at the previous tool output boundary ("since the
|
|
87
|
+
* last tool output") so a call belonging to an EARLIER output is never mis-attributed. Returns the
|
|
88
|
+
* identity `toolName + "::" + normalizedArgs`, or `null` when no such parseable same-tool call exists
|
|
89
|
+
* (source identity unknown/ambiguous => not eligible for category 1).
|
|
90
|
+
*/
|
|
91
|
+
function resolveSourceIdentity(messages, outputIndex) {
|
|
92
|
+
const output = messages[outputIndex];
|
|
93
|
+
const toolName = output.toolName;
|
|
94
|
+
if (!toolName) {
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
for (let i = outputIndex - 1; i >= 0; i -= 1) {
|
|
98
|
+
const candidate = messages[i];
|
|
99
|
+
// Boundary: do not look past the previous tool output (a call before it originated THAT output).
|
|
100
|
+
if (candidate.role === "tool") {
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
if (candidate.role !== "assistant" || candidate.toolName !== toolName) {
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
const normalizedArgs = normalizeCallArgs(candidate.content);
|
|
107
|
+
if (normalizedArgs === null) {
|
|
108
|
+
// A same-tool assistant message that is NOT a parseable call: ambiguous. Conservative -> skip.
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
return `${toolName}::${normalizedArgs}`;
|
|
112
|
+
}
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Input-compaction category 1: detect SUPERSEDED same-source reads.
|
|
117
|
+
*
|
|
118
|
+
* When the SAME source (provable source identity) is read ≥2 times, the LATEST read is authoritative
|
|
119
|
+
* (preserved verbatim). Each EARLIER read whose whitespace-normalized content GENUINELY DIFFERS from
|
|
120
|
+
* the latest's is a superseded candidate that can be replaced by a recoverable pointer to the
|
|
121
|
+
* retained original. Conservative & additive:
|
|
122
|
+
* - Outputs with NO provable source identity are skipped (not eligible).
|
|
123
|
+
* - An earlier output that is identical-up-to-whitespace to the latest is LEFT to the existing
|
|
124
|
+
* `repeated_tool_output` policy (NOT category-1-compacted) — no overlap, no double-compaction.
|
|
125
|
+
* - Each compacted (earlier) output must meet the SAME thresholds detectWaste uses
|
|
126
|
+
* (minDuplicateTokens / minDuplicateCharacters).
|
|
127
|
+
*
|
|
128
|
+
* Finding convention: `messageIds[0]` = KEPT latest authoritative read; `messageIds[1..]` = earlier
|
|
129
|
+
* superseded reads to compact (estimatedTokens = sum of the compacted earlier reads' estimates).
|
|
130
|
+
*
|
|
131
|
+
* NO scoring formula, weight, relevance, or learned logic — only deterministic source-identity
|
|
132
|
+
* equality + whitespace-normalized content difference + the existing min-tokens/chars thresholds.
|
|
133
|
+
*/
|
|
134
|
+
export function detectSupersededSameSourceReads(trace, thresholds = {}) {
|
|
135
|
+
const minTokens = thresholds.minDuplicateTokens ?? 0;
|
|
136
|
+
const minChars = thresholds.minDuplicateCharacters ?? 0;
|
|
137
|
+
// Group eligible tool outputs by provable source identity, preserving order.
|
|
138
|
+
const groups = new Map();
|
|
139
|
+
for (let index = 0; index < trace.messages.length; index += 1) {
|
|
140
|
+
const message = trace.messages[index];
|
|
141
|
+
if (message.role !== "tool") {
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
const identity = resolveSourceIdentity(trace.messages, index);
|
|
145
|
+
if (identity === null) {
|
|
146
|
+
continue; // source identity unknown/ambiguous -> not eligible (conservative)
|
|
147
|
+
}
|
|
148
|
+
const existing = groups.get(identity);
|
|
149
|
+
if (existing) {
|
|
150
|
+
existing.push(message);
|
|
151
|
+
}
|
|
152
|
+
else {
|
|
153
|
+
groups.set(identity, [message]);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
const findings = [];
|
|
157
|
+
for (const outputs of groups.values()) {
|
|
158
|
+
if (outputs.length < 2) {
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
const latest = outputs[outputs.length - 1];
|
|
162
|
+
const latestNormalized = normalizeContent(latest.content);
|
|
163
|
+
const supersededIds = [];
|
|
164
|
+
let estimatedTokens = 0;
|
|
165
|
+
for (let i = 0; i < outputs.length - 1; i += 1) {
|
|
166
|
+
const earlier = outputs[i];
|
|
167
|
+
// GENUINE supersession ONLY: content changed (normalized-different from the latest). An
|
|
168
|
+
// identical-up-to-whitespace earlier copy is the existing repeated_tool_output case -> leave it.
|
|
169
|
+
if (normalizeContent(earlier.content) === latestNormalized) {
|
|
170
|
+
continue;
|
|
171
|
+
}
|
|
172
|
+
// Same thresholds the existing detector uses, applied per compacted (earlier) output.
|
|
173
|
+
const earlierTokens = estimateTextTokens(earlier.content);
|
|
174
|
+
if (earlierTokens < minTokens || earlier.content.length < minChars) {
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
supersededIds.push(earlier.id);
|
|
178
|
+
estimatedTokens += earlierTokens;
|
|
179
|
+
}
|
|
180
|
+
if (supersededIds.length === 0) {
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
findings.push({
|
|
184
|
+
category: "superseded_same_source_read",
|
|
185
|
+
messageIds: [latest.id, ...supersededIds],
|
|
186
|
+
summary: `Superseded ${describeTool(latest)} from the same source. ` +
|
|
187
|
+
`Keep latest ${latest.id} (verbatim) and compact superseded ${supersededIds.join(", ")}.`,
|
|
188
|
+
estimatedTokens
|
|
189
|
+
});
|
|
190
|
+
}
|
|
38
191
|
return findings;
|
|
39
192
|
}
|
|
40
193
|
export function getCompactedMessageIds(findings) {
|
|
41
194
|
const compactedIds = new Set();
|
|
42
195
|
for (const finding of findings) {
|
|
43
|
-
//
|
|
44
|
-
//
|
|
45
|
-
//
|
|
46
|
-
//
|
|
47
|
-
// "
|
|
48
|
-
|
|
196
|
+
// Categories whose later message(s) are compacted (messageIds[0] is the KEPT message):
|
|
197
|
+
// - "repeated_tool_output": keep the first copy, compact the later identical duplicate(s).
|
|
198
|
+
// - "superseded_same_source_read" (category 1): keep the LATEST authoritative read, compact the
|
|
199
|
+
// earlier superseded copies (messageIds[1..]).
|
|
200
|
+
// "repeated_skill_injection" is report-only and is NEVER compacted by this guard (its findings are
|
|
201
|
+
// detected/reported but never enter the compacted set; policy-middleware also independently filters).
|
|
202
|
+
if (finding.category !== "repeated_tool_output" && finding.category !== "superseded_same_source_read") {
|
|
49
203
|
continue;
|
|
50
204
|
}
|
|
51
205
|
const [, ...duplicateIds] = finding.messageIds;
|