@compaction/cli 0.2.0 → 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 +41 -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 +9 -5
- 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/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 +297 -12
- 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 +36 -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/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/run-aggregator.d.ts +11 -1
- package/dist/core/run-aggregator.js +29 -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-parser.d.ts +13 -13
- package/dist/core/trace-parser.js +6 -6
- package/dist/core/types.d.ts +12 -2
- package/dist/core/waste-detector.d.ts +20 -0
- package/dist/core/waste-detector.js +160 -6
- package/package.json +1 -1
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { type UsageMetadata } from "./usage-metadata.js";
|
|
2
|
+
import type { AgentTrace } from "./types.js";
|
|
3
|
+
import { type LocalCommandRun } from "./command-runner.js";
|
|
4
|
+
/**
|
|
5
|
+
* Placeholder used when the captured event stream carried NO model field — an honest unknown
|
|
6
|
+
* marker, never a real model id. Exported so downstream writers (e.g. the cross-surface event on
|
|
7
|
+
* the local run record) can map it to their canonical "unknown" instead of presenting a
|
|
8
|
+
* placeholder as if it were a reported model.
|
|
9
|
+
*/
|
|
10
|
+
export declare const CODEX_UNKNOWN_MODEL = "codex-unknown-model";
|
|
11
|
+
interface ParsedEvent {
|
|
12
|
+
value: Record<string, unknown>;
|
|
13
|
+
lineNumber: number;
|
|
14
|
+
}
|
|
15
|
+
/** Parse a `codex exec --json` JSONL stream into event records (one object per line). */
|
|
16
|
+
export declare function parseCodexEvents(rawOutput: string): ParsedEvent[];
|
|
17
|
+
export interface CodexNormalizationResult {
|
|
18
|
+
trace: AgentTrace;
|
|
19
|
+
usageMetadata: UsageMetadata;
|
|
20
|
+
eventsCaptured: number;
|
|
21
|
+
messagesCaptured: number;
|
|
22
|
+
toolOutputsCaptured: number;
|
|
23
|
+
/** "present" when a turn.completed.usage block was found, else "missing" (never invented). */
|
|
24
|
+
tokenMetadataStatus: "present" | "missing";
|
|
25
|
+
warnings: string[];
|
|
26
|
+
}
|
|
27
|
+
/** Build an `AgentTrace` + provider-reported usage from a captured `codex exec --json` stream. */
|
|
28
|
+
export declare function normalizeCodexExecEvents(params: {
|
|
29
|
+
captureId: string;
|
|
30
|
+
rawOutput: string;
|
|
31
|
+
commandRun?: LocalCommandRun;
|
|
32
|
+
generatedAt?: string;
|
|
33
|
+
}): CodexNormalizationResult;
|
|
34
|
+
export interface CodexCaptureResult extends CodexNormalizationResult {
|
|
35
|
+
commandRun?: LocalCommandRun;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Live wrapper: spawn `codex exec --json …` (no manual export), capture its stdout, normalize.
|
|
39
|
+
* The command parts are the FULL command (e.g. ["codex","exec","--json","do the thing"]). No engine,
|
|
40
|
+
* no upload. A non-zero Codex exit is preserved on the trace; capture still returns what was produced.
|
|
41
|
+
*/
|
|
42
|
+
export declare function captureCodexCommand(commandParts: string[], generatedAt?: string): Promise<CodexCaptureResult>;
|
|
43
|
+
/** Offline/fallback: normalize a previously-saved `codex exec --json` JSONL export file's contents. */
|
|
44
|
+
export declare function captureCodexExport(rawOutput: string, generatedAt?: string): CodexCaptureResult;
|
|
45
|
+
export {};
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Codex live-wrapper capture (PUBLIC CLI/SDK code — engine-free, ships in the npm package).
|
|
3
|
+
*
|
|
4
|
+
* Wraps a real `codex exec --json` run (near-live, no manual export) and normalizes its JSONL event
|
|
5
|
+
* stream to an `AgentTrace`. Design: `docs/design/codex-live-wrapper.md` (accepted 2026-06-28).
|
|
6
|
+
*
|
|
7
|
+
* Token honesty: `codex exec --json` emits a `turn.completed` event carrying a provider `usage` block
|
|
8
|
+
* ({input_tokens, cached_input_tokens, output_tokens, reasoning_output_tokens}). When present, those are
|
|
9
|
+
* recorded as **provider-reported** (NOT estimates). When absent, usage is left missing — never invented.
|
|
10
|
+
*
|
|
11
|
+
* Content-free posture: this reads the captured stdout to build the trace LOCALLY; only counts +
|
|
12
|
+
* estimate/provider labels flow into any downstream record. The captured stream may contain code/output,
|
|
13
|
+
* so the produced artifact carries the same "review before sharing" caution as other captures.
|
|
14
|
+
*
|
|
15
|
+
* Evidence tier: a wrapped command is `source: "local_command"` — the same `imported_local` tier as the
|
|
16
|
+
* import path (below `real_captured`); the hosted record attributes `tool: "codex"` / `provider: "openai"`.
|
|
17
|
+
*/
|
|
18
|
+
import { CURRENT_AGENT_TRACE_ARTIFACT_VERSION } from "./trace-parser.js";
|
|
19
|
+
import { createUsageMetadata, missingUsageMetadata } from "./usage-metadata.js";
|
|
20
|
+
import { executeLocalCommand, parseRunCommand } from "./command-runner.js";
|
|
21
|
+
const CODEX_PROVIDER = "openai";
|
|
22
|
+
/**
|
|
23
|
+
* Placeholder used when the captured event stream carried NO model field — an honest unknown
|
|
24
|
+
* marker, never a real model id. Exported so downstream writers (e.g. the cross-surface event on
|
|
25
|
+
* the local run record) can map it to their canonical "unknown" instead of presenting a
|
|
26
|
+
* placeholder as if it were a reported model.
|
|
27
|
+
*/
|
|
28
|
+
export const CODEX_UNKNOWN_MODEL = "codex-unknown-model";
|
|
29
|
+
/* ---------------- small local helpers (no engine import) ---------------- */
|
|
30
|
+
function toRecord(value) {
|
|
31
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
|
|
32
|
+
}
|
|
33
|
+
function stringValue(value) {
|
|
34
|
+
return typeof value === "string" && value.trim() !== "" ? value : undefined;
|
|
35
|
+
}
|
|
36
|
+
function numericValue(value) {
|
|
37
|
+
return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : undefined;
|
|
38
|
+
}
|
|
39
|
+
/** Parse a `codex exec --json` JSONL stream into event records (one object per line). */
|
|
40
|
+
export function parseCodexEvents(rawOutput) {
|
|
41
|
+
return rawOutput
|
|
42
|
+
.split(/\r?\n/)
|
|
43
|
+
.map((line, index) => ({ line: line.trim(), lineNumber: index + 1 }))
|
|
44
|
+
.filter(({ line }) => line.startsWith("{") && line.endsWith("}"))
|
|
45
|
+
.flatMap(({ line, lineNumber }) => {
|
|
46
|
+
try {
|
|
47
|
+
const record = toRecord(JSON.parse(line));
|
|
48
|
+
return record ? [{ value: record, lineNumber }] : [];
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
return [];
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
function addMessage(messages, role, content, timestamp, metadata, name) {
|
|
56
|
+
messages.push({
|
|
57
|
+
id: `codex_msg_${messages.length + 1}`,
|
|
58
|
+
role,
|
|
59
|
+
content,
|
|
60
|
+
timestamp,
|
|
61
|
+
...(name ? { toolName: name } : {}),
|
|
62
|
+
metadata
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
/** Build an `AgentTrace` + provider-reported usage from a captured `codex exec --json` stream. */
|
|
66
|
+
export function normalizeCodexExecEvents(params) {
|
|
67
|
+
const generatedAt = params.generatedAt ?? new Date().toISOString();
|
|
68
|
+
const startedAt = params.commandRun?.startedAt ?? generatedAt;
|
|
69
|
+
const events = parseCodexEvents(params.rawOutput);
|
|
70
|
+
const warnings = [];
|
|
71
|
+
const messages = [];
|
|
72
|
+
let toolOutputsCaptured = 0;
|
|
73
|
+
let threadId;
|
|
74
|
+
let model;
|
|
75
|
+
addMessage(messages, "system", "Codex exec capture (live command wrapper).", startedAt, {
|
|
76
|
+
integration: "codex",
|
|
77
|
+
captureMode: "command wrapper"
|
|
78
|
+
});
|
|
79
|
+
// Provider-reported usage is summed across turn.completed events; never invented.
|
|
80
|
+
let inputTokens = 0;
|
|
81
|
+
let outputTokens = 0;
|
|
82
|
+
let cachedInputTokens = 0;
|
|
83
|
+
let reasoningOutputTokens = 0;
|
|
84
|
+
let sawUsage = false;
|
|
85
|
+
for (const { value, lineNumber } of events) {
|
|
86
|
+
const type = stringValue(value.type) ?? "event";
|
|
87
|
+
threadId = threadId ?? stringValue(value.thread_id);
|
|
88
|
+
model = model ?? stringValue(value.model) ?? stringValue(toRecord(value.item)?.model);
|
|
89
|
+
if (type === "item.completed" || type === "item.started") {
|
|
90
|
+
const item = toRecord(value.item);
|
|
91
|
+
if (!item)
|
|
92
|
+
continue;
|
|
93
|
+
const itemType = stringValue(item.type);
|
|
94
|
+
const ts = stringValue(value.timestamp) ?? startedAt;
|
|
95
|
+
if (itemType === "agent_message" && type === "item.completed") {
|
|
96
|
+
// Primary key is the vendor field (`text`); `content` is a fallback so the committed
|
|
97
|
+
// SYNTHETIC demo fixture (src/examples/codex-exec-demo.jsonl) is consumable too. Real
|
|
98
|
+
// `codex exec --json` exports carry `text`, so the fallback never changes their parsing.
|
|
99
|
+
const text = stringValue(item.text) ?? stringValue(item.content);
|
|
100
|
+
if (text)
|
|
101
|
+
addMessage(messages, "assistant", text, ts, { sourceLine: lineNumber, eventType: itemType });
|
|
102
|
+
}
|
|
103
|
+
else if (itemType === "command_execution") {
|
|
104
|
+
const command = stringValue(item.command);
|
|
105
|
+
if (command && type === "item.started") {
|
|
106
|
+
addMessage(messages, "assistant", `Tool call: ${command}`, ts, { sourceLine: lineNumber, eventType: "tool_call" });
|
|
107
|
+
}
|
|
108
|
+
// `aggregated_output` is the vendor field; `stdout` is a fixture-compat fallback (same
|
|
109
|
+
// rationale as above — never consulted when the vendor field is present).
|
|
110
|
+
const output = stringValue(item.aggregated_output) ?? stringValue(item.output) ?? stringValue(item.stdout);
|
|
111
|
+
if (output && type === "item.completed") {
|
|
112
|
+
addMessage(messages, "tool", output, ts, { sourceLine: lineNumber, eventType: "tool_output" }, command ?? "codex.command");
|
|
113
|
+
toolOutputsCaptured += 1;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
else if (type === "turn.completed") {
|
|
118
|
+
const usage = toRecord(value.usage);
|
|
119
|
+
if (usage) {
|
|
120
|
+
const i = numericValue(usage.input_tokens);
|
|
121
|
+
const o = numericValue(usage.output_tokens);
|
|
122
|
+
const c = numericValue(usage.cached_input_tokens);
|
|
123
|
+
const r = numericValue(usage.reasoning_output_tokens);
|
|
124
|
+
if (i !== undefined || o !== undefined)
|
|
125
|
+
sawUsage = true;
|
|
126
|
+
inputTokens += i ?? 0;
|
|
127
|
+
outputTokens += o ?? 0;
|
|
128
|
+
cachedInputTokens += c ?? 0;
|
|
129
|
+
reasoningOutputTokens += r ?? 0;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
if (events.length === 0)
|
|
134
|
+
warnings.push("No codex exec --json events were found in the captured output.");
|
|
135
|
+
if (!sawUsage)
|
|
136
|
+
warnings.push("No turn.completed.usage block was found; provider token usage is missing and was not invented.");
|
|
137
|
+
const resolvedModel = model ?? CODEX_UNKNOWN_MODEL;
|
|
138
|
+
const usageMetadata = sawUsage
|
|
139
|
+
? createUsageMetadata({
|
|
140
|
+
inputTokens,
|
|
141
|
+
outputTokens,
|
|
142
|
+
// reasoning tokens are part of the output charge; surface in total, not double-counted as output.
|
|
143
|
+
totalTokens: inputTokens + outputTokens + reasoningOutputTokens,
|
|
144
|
+
cacheReadInputTokens: cachedInputTokens,
|
|
145
|
+
providerReportedTokens: true,
|
|
146
|
+
estimatedTokens: false,
|
|
147
|
+
model: resolvedModel,
|
|
148
|
+
provider: CODEX_PROVIDER,
|
|
149
|
+
limitations: [
|
|
150
|
+
"Provider-reported token usage from codex exec --json turn.completed.usage — NOT billing-confirmed.",
|
|
151
|
+
...(reasoningOutputTokens > 0 ? ["Reasoning output tokens are included in the total; they are part of the output charge."] : [])
|
|
152
|
+
]
|
|
153
|
+
})
|
|
154
|
+
: missingUsageMetadata({
|
|
155
|
+
model: resolvedModel,
|
|
156
|
+
provider: CODEX_PROVIDER,
|
|
157
|
+
limitations: ["Captured codex exec events carried no usage block; token usage is missing and was not invented."]
|
|
158
|
+
});
|
|
159
|
+
const trace = {
|
|
160
|
+
id: threadId ?? `trace_codex_${params.captureId}`,
|
|
161
|
+
title: "Codex exec captured trace",
|
|
162
|
+
artifactVersion: CURRENT_AGENT_TRACE_ARTIFACT_VERSION,
|
|
163
|
+
source: "local_command",
|
|
164
|
+
createdAt: startedAt,
|
|
165
|
+
generatedAt,
|
|
166
|
+
model: resolvedModel,
|
|
167
|
+
command: params.commandRun
|
|
168
|
+
? { command: params.commandRun.command.executable, args: params.commandRun.command.args, cwd: process.cwd() }
|
|
169
|
+
: { command: "codex-export", args: [], cwd: process.cwd() },
|
|
170
|
+
durationMs: params.commandRun?.durationMs ?? 0,
|
|
171
|
+
exitCode: params.commandRun?.exitCode ?? 0,
|
|
172
|
+
messages
|
|
173
|
+
};
|
|
174
|
+
return {
|
|
175
|
+
trace,
|
|
176
|
+
usageMetadata,
|
|
177
|
+
eventsCaptured: events.length,
|
|
178
|
+
messagesCaptured: messages.filter((m) => ["system", "user", "assistant"].includes(m.role)).length,
|
|
179
|
+
toolOutputsCaptured,
|
|
180
|
+
tokenMetadataStatus: sawUsage ? "present" : "missing",
|
|
181
|
+
warnings
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Live wrapper: spawn `codex exec --json …` (no manual export), capture its stdout, normalize.
|
|
186
|
+
* The command parts are the FULL command (e.g. ["codex","exec","--json","do the thing"]). No engine,
|
|
187
|
+
* no upload. A non-zero Codex exit is preserved on the trace; capture still returns what was produced.
|
|
188
|
+
*/
|
|
189
|
+
export async function captureCodexCommand(commandParts, generatedAt) {
|
|
190
|
+
const parsed = parseRunCommand(commandParts);
|
|
191
|
+
const commandRun = await executeLocalCommand(parsed);
|
|
192
|
+
const result = normalizeCodexExecEvents({
|
|
193
|
+
captureId: `${parsed.executable}-${commandRun.startedAt}`,
|
|
194
|
+
rawOutput: commandRun.rawOutput,
|
|
195
|
+
commandRun,
|
|
196
|
+
generatedAt
|
|
197
|
+
});
|
|
198
|
+
return { ...result, commandRun };
|
|
199
|
+
}
|
|
200
|
+
/** Offline/fallback: normalize a previously-saved `codex exec --json` JSONL export file's contents. */
|
|
201
|
+
export function captureCodexExport(rawOutput, generatedAt) {
|
|
202
|
+
return normalizeCodexExecEvents({ captureId: "codex-export", rawOutput, generatedAt });
|
|
203
|
+
}
|
|
204
|
+
//# sourceMappingURL=codex-capture.js.map
|
|
@@ -22,7 +22,11 @@ export async function executeLocalCommand(command, runId = createRunId()) {
|
|
|
22
22
|
return new Promise((resolve) => {
|
|
23
23
|
const child = spawn(command.executable, command.args, {
|
|
24
24
|
shell: false,
|
|
25
|
-
windowsHide: true
|
|
25
|
+
windowsHide: true,
|
|
26
|
+
// Capture is non-interactive: give the child a closed (/dev/null) stdin so tools that probe stdin
|
|
27
|
+
// (e.g. `codex exec`, which otherwise blocks "Reading additional input from stdin...") see EOF and
|
|
28
|
+
// proceed with the prompt argument. stdout/stderr stay piped so we can read the event stream.
|
|
29
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
26
30
|
});
|
|
27
31
|
const pid = child.pid ?? null;
|
|
28
32
|
let spawnError;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { type PolicyMiddlewareResult } from "./policy-middleware.js";
|
|
2
|
+
import type { AgentTrace, CompactionPolicy, CompactionReport, StateCapsule } from "./types.js";
|
|
3
|
+
export declare const COMPACTION_ARTIFACT_NAMES: readonly ["report.json", "report.md", "policy.json", "state-capsule.json", "compacted-trace.json", "safety-report.json", "safety-report.md", "pr-comment.md"];
|
|
4
|
+
export interface CompactionArtifactPaths {
|
|
5
|
+
reportPath: string;
|
|
6
|
+
markdownReportPath: string;
|
|
7
|
+
policyPath: string;
|
|
8
|
+
capsulePath: string;
|
|
9
|
+
compactedTracePath: string;
|
|
10
|
+
safetyReportPath: string;
|
|
11
|
+
safetyMarkdownReportPath: string;
|
|
12
|
+
prCommentReportPath: string;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Concise applied-result summary for the approval-gated skill-injection rung. Present on every
|
|
16
|
+
* result; `applied` is false (counts 0) unless `approveSkillInjectionPolicy` enabled the rung AND
|
|
17
|
+
* it compacted ≥1 byte-identical same-skill injection. Estimated tokens are the chars/4 heuristic
|
|
18
|
+
* (NOT billing-confirmed, NOT realized savings). Near-identical (divergent ARGUMENTS) is excluded
|
|
19
|
+
* by construction in the core policy and never counted here.
|
|
20
|
+
*/
|
|
21
|
+
export interface SkillInjectionCompactionSummary {
|
|
22
|
+
applied: boolean;
|
|
23
|
+
compacted_count: number;
|
|
24
|
+
estimated_tokens_removed: number;
|
|
25
|
+
estimate_label: string;
|
|
26
|
+
}
|
|
27
|
+
export interface CompactionArtifactResult {
|
|
28
|
+
report: CompactionReport;
|
|
29
|
+
policy: CompactionPolicy;
|
|
30
|
+
capsule: StateCapsule;
|
|
31
|
+
compactedTrace: AgentTrace;
|
|
32
|
+
consoleReport: string;
|
|
33
|
+
safetyReport: import("./safety-report.js").SafetyReport;
|
|
34
|
+
skillInjectionCompaction: SkillInjectionCompactionSummary;
|
|
35
|
+
paths: CompactionArtifactPaths;
|
|
36
|
+
}
|
|
37
|
+
export interface CompactionArtifactOptions {
|
|
38
|
+
reviewerType?: import("./safety-report.js").ReviewerType;
|
|
39
|
+
reviewSummaryPresent?: boolean;
|
|
40
|
+
/**
|
|
41
|
+
* APPROVAL-GATED (design §4). When true, ALSO apply the approval-required
|
|
42
|
+
* `repeated_skill_injection_to_state_capsule` policy via `applyCompactionPolicy`'s existing
|
|
43
|
+
* `compactSkillInjections` input — compacting byte-identical same-skill later role:user skill
|
|
44
|
+
* injections into state capsules pointing at the first copy. DEFAULT (undefined/false): the
|
|
45
|
+
* compaction run compacts NO skill injections; only the tool-output policy runs. Passing the
|
|
46
|
+
* `--approve-skill-injection-policy` flag on `compact` IS the explicit operator approval gesture.
|
|
47
|
+
* This is NOT threaded into any unattended/auto-apply path.
|
|
48
|
+
*/
|
|
49
|
+
approveSkillInjectionPolicy?: boolean;
|
|
50
|
+
}
|
|
51
|
+
export declare function writeCompactionArtifacts(trace: AgentTrace, outputDirectory: string, runId: string, providedPolicyResult?: PolicyMiddlewareResult, generatedAt?: string, options?: CompactionArtifactOptions): Promise<CompactionArtifactResult>;
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
import { writeJsonArtifact, writeTextArtifact } from "./artifact-writer.js";
|
|
2
|
+
import { createCompactionPolicy } from "./compactor.js";
|
|
3
|
+
import { formatCompactionMarkdownReport, formatCompactionReport, formatPrCommentReport } from "./report-generator.js";
|
|
4
|
+
import { applyCompactionPolicy } from "./policy-middleware.js";
|
|
5
|
+
import { createSafetyReport, formatSafetyMarkdownReport } from "./safety-report.js";
|
|
6
|
+
import { createStateCapsule } from "./state-capsule.js";
|
|
7
|
+
import { buildSpendSummaryFromTrace, loadEngineOptimizationDeltaProvider } from "./spend-attribution.js";
|
|
8
|
+
import { detectWaste, buildSkillInjectionAdvisory } from "./waste-detector.js";
|
|
9
|
+
import { calculateSavings } from "./savings.js";
|
|
10
|
+
import { calculateCost } from "./cost-calculator.js";
|
|
11
|
+
export const COMPACTION_ARTIFACT_NAMES = [
|
|
12
|
+
"report.json",
|
|
13
|
+
"report.md",
|
|
14
|
+
"policy.json",
|
|
15
|
+
"state-capsule.json",
|
|
16
|
+
"compacted-trace.json",
|
|
17
|
+
"safety-report.json",
|
|
18
|
+
"safety-report.md",
|
|
19
|
+
"pr-comment.md"
|
|
20
|
+
];
|
|
21
|
+
function primaryFinding(findings) {
|
|
22
|
+
return findings.find((finding) => finding.category === "repeated_tool_output") ?? findings[0];
|
|
23
|
+
}
|
|
24
|
+
function countRepeatedOccurrences(findings, primarySource) {
|
|
25
|
+
if (!primarySource)
|
|
26
|
+
return 0;
|
|
27
|
+
// Count all duplicate findings that share this source, then add 1 for the preserved original.
|
|
28
|
+
const duplicateCount = findings.filter((f) => f.category === "repeated_tool_output" && f.messageIds[0] === primarySource).length;
|
|
29
|
+
return duplicateCount > 0 ? duplicateCount + 1 : 0;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Build the report-only skill-injection advisory for report.json. This is ADVISORY:
|
|
33
|
+
* it sits alongside the tool-output compaction summary and nothing here is compacted.
|
|
34
|
+
* The cost figure is a price-table ESTIMATE of the addressable token volume, not a
|
|
35
|
+
* realized saving and not yet applied.
|
|
36
|
+
*/
|
|
37
|
+
function skillInjectionAdvisoryForReport(trace) {
|
|
38
|
+
const advisory = buildSkillInjectionAdvisory(trace);
|
|
39
|
+
const costEquivalent = Number(calculateCost(trace.model, {
|
|
40
|
+
inputTokens: advisory.total_addressable_estimated_tokens,
|
|
41
|
+
outputTokens: 0,
|
|
42
|
+
totalTokens: advisory.total_addressable_estimated_tokens
|
|
43
|
+
}).totalCostUsd.toFixed(6));
|
|
44
|
+
return {
|
|
45
|
+
report_only: true,
|
|
46
|
+
skills: advisory.skills,
|
|
47
|
+
total_redundant_byte_identical_copies: advisory.total_redundant_byte_identical_copies,
|
|
48
|
+
total_addressable_estimated_tokens: advisory.total_addressable_estimated_tokens,
|
|
49
|
+
estimated_cost_equivalent_usd: costEquivalent,
|
|
50
|
+
first_copy_count: advisory.first_copy_count,
|
|
51
|
+
estimate_label: advisory.estimate_label,
|
|
52
|
+
recommendation: advisory.recommendation
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Classify the already-computed token/cost deltas against the four ROADMAP
|
|
57
|
+
* "MVP value path" value-proof claims (strongest last, never conflated). This
|
|
58
|
+
* adds NO new numbers and makes NO new or stronger savings claim — it only labels
|
|
59
|
+
* the existing tokens_saved / cost deltas with their honest tier and states plainly
|
|
60
|
+
* that billing-confirmed (tier 3) is NOT claimed and fixed-plan workflow-extension
|
|
61
|
+
* value (tier 4) is NOT quantified here.
|
|
62
|
+
*/
|
|
63
|
+
function buildValueProof(tokensSaved, percentReduction, savingPerRun) {
|
|
64
|
+
return {
|
|
65
|
+
trace_token_reduction: {
|
|
66
|
+
claim: "trace-token reduction (estimated, local heuristic)",
|
|
67
|
+
tier: 1,
|
|
68
|
+
estimated: true,
|
|
69
|
+
tokens_saved: tokensSaved,
|
|
70
|
+
percent_reduction: percentReduction
|
|
71
|
+
},
|
|
72
|
+
estimated_provider_cost_reduction: {
|
|
73
|
+
claim: "estimated provider cost reduction (price-table estimate, NOT billing-confirmed)",
|
|
74
|
+
tier: 2,
|
|
75
|
+
estimated: true,
|
|
76
|
+
billing_confirmed: false,
|
|
77
|
+
saving_per_run_usd: savingPerRun
|
|
78
|
+
},
|
|
79
|
+
billing_confirmed_savings: {
|
|
80
|
+
claim: "billing-confirmed savings (measured provider usage/billing delta)",
|
|
81
|
+
tier: 3,
|
|
82
|
+
claimed: false,
|
|
83
|
+
billing_confirmed: false,
|
|
84
|
+
note: "NOT claimed: no provider usage/billing delta is measured by this artifact."
|
|
85
|
+
},
|
|
86
|
+
fixed_plan_workflow_extension_value: {
|
|
87
|
+
claim: "fixed-plan workflow-extension value",
|
|
88
|
+
tier: 4,
|
|
89
|
+
quantified: false,
|
|
90
|
+
note: "NOT quantified here: this artifact does not measure additional work completed under a fixed-price cap."
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Build the concise "where spend came from" summary for report.json by REUSING the
|
|
96
|
+
* spend-attribution computation (the SpendSummary already produced by buildSpendSummaryFromTrace).
|
|
97
|
+
* This selects top buckets only; it does not recompute attribution. All figures are local
|
|
98
|
+
* estimates (chars/4 trace tokens / price-table cost), NOT billing-confirmed and NOT realized savings.
|
|
99
|
+
*/
|
|
100
|
+
function buildSpendBySource(spend) {
|
|
101
|
+
return {
|
|
102
|
+
estimated: true,
|
|
103
|
+
billing_confirmed: false,
|
|
104
|
+
estimate_label: "ESTIMATED: chars/4 trace-token heuristic and price-table cost — NOT billing-confirmed, NOT realized savings.",
|
|
105
|
+
top_roles: spend.spend_by_role.slice(0, 3).map((bucket) => ({
|
|
106
|
+
name: bucket.name,
|
|
107
|
+
estimated_tokens: bucket.total_tokens,
|
|
108
|
+
estimated_cost_usd: bucket.estimated_cost
|
|
109
|
+
})),
|
|
110
|
+
top_tool_outputs: spend.spend_by_tool_output.slice(0, 3).map((bucket) => ({
|
|
111
|
+
tool_name: bucket.tool_name,
|
|
112
|
+
estimated_tokens: bucket.total_tokens,
|
|
113
|
+
repeated_count: bucket.repeated_count,
|
|
114
|
+
estimated_cost_usd: bucket.estimated_cost
|
|
115
|
+
})),
|
|
116
|
+
top_policy_candidate: spend.top_policy_candidate
|
|
117
|
+
? {
|
|
118
|
+
policy_name: spend.top_policy_candidate.policy_name,
|
|
119
|
+
estimated_tokens_saved: spend.top_policy_candidate.estimated_tokens_saved,
|
|
120
|
+
estimated_saving_per_run_usd: spend.top_policy_candidate.estimated_saving_per_run
|
|
121
|
+
}
|
|
122
|
+
: null
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
function reportFromPolicyResult(trace, runId, generatedAt, policyResult, findings) {
|
|
126
|
+
const savings = calculateSavings({
|
|
127
|
+
scope: "policy_level",
|
|
128
|
+
beforeTokens: policyResult.tokenEstimateBefore,
|
|
129
|
+
afterTokens: policyResult.tokenEstimateAfter,
|
|
130
|
+
beforeCost: policyResult.costEstimateBefore,
|
|
131
|
+
afterCost: policyResult.costEstimateAfter
|
|
132
|
+
});
|
|
133
|
+
const finding = primaryFinding(findings);
|
|
134
|
+
const primarySource = finding?.messageIds[0] ?? policyResult.stateCapsules[0]?.sourcePointer.messageId;
|
|
135
|
+
return {
|
|
136
|
+
run_id: runId,
|
|
137
|
+
trace_title: trace.title,
|
|
138
|
+
model: trace.model,
|
|
139
|
+
original_input_tokens: savings.input_tokens_before,
|
|
140
|
+
compacted_input_tokens: savings.input_tokens_after,
|
|
141
|
+
tokens_saved: savings.tokens_saved,
|
|
142
|
+
percent_reduction: savings.percent_reduction,
|
|
143
|
+
savings_scope: savings.savings_scope,
|
|
144
|
+
cost_before_per_run: savings.estimated_cost_before,
|
|
145
|
+
cost_after_per_run: savings.estimated_cost_after,
|
|
146
|
+
saving_per_run: savings.estimated_saving_per_run,
|
|
147
|
+
policy_name: policyResult.appliedPolicyName,
|
|
148
|
+
waste_pattern: finding?.category ?? null,
|
|
149
|
+
source_message_id: primarySource ?? null,
|
|
150
|
+
repeated_count: countRepeatedOccurrences(findings, primarySource),
|
|
151
|
+
compacted_message_ids: policyResult.compactedMessageIds,
|
|
152
|
+
artifact_version: "trace-compactor-v0",
|
|
153
|
+
created_at: trace.createdAt,
|
|
154
|
+
generated_at: generatedAt,
|
|
155
|
+
// Populated from SafetyReport after it is generated; see writeCompactionArtifacts.
|
|
156
|
+
approval_readiness_status: "conditional",
|
|
157
|
+
approval_readiness_reason: "",
|
|
158
|
+
// Value-proof tier classification of the deltas above; classifies existing
|
|
159
|
+
// numbers only, no new/stronger claim. See ValueProof.
|
|
160
|
+
value_proof: buildValueProof(savings.tokens_saved, savings.percent_reduction, savings.estimated_saving_per_run),
|
|
161
|
+
// Report-only advisory; nothing here is compacted. See SkillInjectionAdvisorySummary.
|
|
162
|
+
skill_injection_advisory: skillInjectionAdvisoryForReport(trace)
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Summarize the applied skill-injection compaction from the policy result for operator-facing
|
|
167
|
+
* surfacing. Counts the skill-injection capsules and sums their estimated original token counts
|
|
168
|
+
* (chars/4 heuristic). When the rung was not enabled there are no such capsules, so this returns
|
|
169
|
+
* a zero/not-applied summary and default output is unchanged.
|
|
170
|
+
*/
|
|
171
|
+
function summarizeSkillInjectionCompaction(policyResult) {
|
|
172
|
+
const capsules = policyResult.skillInjectionCapsules;
|
|
173
|
+
const estimatedTokensRemoved = capsules.reduce((total, capsule) => total + capsule.originalPayloadTokenCount, 0);
|
|
174
|
+
return {
|
|
175
|
+
applied: capsules.length > 0,
|
|
176
|
+
compacted_count: capsules.length,
|
|
177
|
+
estimated_tokens_removed: estimatedTokensRemoved,
|
|
178
|
+
estimate_label: "ESTIMATED: chars/4 trace-token heuristic — NOT billing-confirmed, NOT realized savings."
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
export async function writeCompactionArtifacts(trace, outputDirectory, runId, providedPolicyResult, generatedAt = new Date().toISOString(), options = {}) {
|
|
182
|
+
const findings = detectWaste(trace);
|
|
183
|
+
const policyResult = providedPolicyResult ??
|
|
184
|
+
applyCompactionPolicy({ trace, compactSkillInjections: options.approveSkillInjectionPolicy === true });
|
|
185
|
+
const report = reportFromPolicyResult(trace, runId, generatedAt, policyResult, findings);
|
|
186
|
+
// Surface "where spend came from" by reusing the spend-attribution computation.
|
|
187
|
+
// Additive and optional; estimates only (chars/4 / price-table), not billing-confirmed.
|
|
188
|
+
// The engine optimization-delta provider is loaded lazily via spend-attribution (present in the
|
|
189
|
+
// in-repo/API build, absent in the public package) — pass it so the top policy candidate is
|
|
190
|
+
// populated exactly as before, degrading cleanly when the engine provider is unavailable.
|
|
191
|
+
const spendSummary = await buildSpendSummaryFromTrace(trace, generatedAt, null, (await loadEngineOptimizationDeltaProvider()) ?? undefined);
|
|
192
|
+
report.spend_by_source = buildSpendBySource(spendSummary);
|
|
193
|
+
const policy = createCompactionPolicy(report);
|
|
194
|
+
const capsule = createStateCapsule(trace, findings, policyResult.stateCapsules);
|
|
195
|
+
const compactedTrace = policyResult.compactedTrace ?? trace;
|
|
196
|
+
const safetyReport = createSafetyReport({
|
|
197
|
+
runId,
|
|
198
|
+
generatedAt: report.generated_at,
|
|
199
|
+
originalTrace: trace,
|
|
200
|
+
compactedMessages: policyResult.compactedMessages,
|
|
201
|
+
stateCapsules: policyResult.stateCapsules,
|
|
202
|
+
compactedMessageIds: policyResult.compactedMessageIds,
|
|
203
|
+
tokensSaved: policyResult.tokensSaved,
|
|
204
|
+
policyName: policyResult.appliedPolicyName,
|
|
205
|
+
reviewerType: options.reviewerType,
|
|
206
|
+
reviewSummaryPresent: options.reviewSummaryPresent
|
|
207
|
+
});
|
|
208
|
+
// Populate approval readiness from the generated SafetyReport.
|
|
209
|
+
report.approval_readiness_status = safetyReport.approval_readiness_status;
|
|
210
|
+
report.approval_readiness_reason = safetyReport.approval_readiness_reason;
|
|
211
|
+
const safetyMarkdownReport = formatSafetyMarkdownReport(safetyReport);
|
|
212
|
+
const markdownReport = formatCompactionMarkdownReport(report, policy);
|
|
213
|
+
const prCommentReport = formatPrCommentReport(report, policy, [...COMPACTION_ARTIFACT_NAMES]);
|
|
214
|
+
const reportPath = await writeJsonArtifact(outputDirectory, "report.json", report);
|
|
215
|
+
const markdownReportPath = await writeTextArtifact(outputDirectory, "report.md", markdownReport);
|
|
216
|
+
const policyPath = await writeJsonArtifact(outputDirectory, "policy.json", policy);
|
|
217
|
+
const capsulePath = await writeJsonArtifact(outputDirectory, "state-capsule.json", capsule);
|
|
218
|
+
const compactedTracePath = await writeJsonArtifact(outputDirectory, "compacted-trace.json", compactedTrace);
|
|
219
|
+
const safetyReportPath = await writeJsonArtifact(outputDirectory, "safety-report.json", safetyReport);
|
|
220
|
+
const safetyMarkdownReportPath = await writeTextArtifact(outputDirectory, "safety-report.md", safetyMarkdownReport);
|
|
221
|
+
const prCommentReportPath = await writeTextArtifact(outputDirectory, "pr-comment.md", prCommentReport);
|
|
222
|
+
return {
|
|
223
|
+
report,
|
|
224
|
+
policy,
|
|
225
|
+
capsule,
|
|
226
|
+
compactedTrace,
|
|
227
|
+
consoleReport: formatCompactionReport(report),
|
|
228
|
+
safetyReport,
|
|
229
|
+
skillInjectionCompaction: summarizeSkillInjectionCompaction(policyResult),
|
|
230
|
+
paths: {
|
|
231
|
+
reportPath,
|
|
232
|
+
markdownReportPath,
|
|
233
|
+
policyPath,
|
|
234
|
+
capsulePath,
|
|
235
|
+
compactedTracePath,
|
|
236
|
+
safetyReportPath,
|
|
237
|
+
safetyMarkdownReportPath,
|
|
238
|
+
prCommentReportPath
|
|
239
|
+
}
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
//# sourceMappingURL=compaction-artifacts.js.map
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { AgentTrace, CompactionPolicy, CompactionReport } from "./types.js";
|
|
2
|
+
interface CompactTraceOptions {
|
|
3
|
+
generatedAt?: string;
|
|
4
|
+
runId?: string;
|
|
5
|
+
}
|
|
6
|
+
export declare function createCompactedTrace(trace: AgentTrace): AgentTrace;
|
|
7
|
+
export declare function compactTrace(trace: AgentTrace, options?: CompactTraceOptions): CompactionReport;
|
|
8
|
+
export declare function createCompactionPolicy(report: CompactionReport): CompactionPolicy;
|
|
9
|
+
export {};
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { calculateCost } from "./cost-calculator.js";
|
|
2
|
+
import { estimateTraceTokens } from "./token-estimator.js";
|
|
3
|
+
import { detectWaste, getCompactedMessageIds } from "./waste-detector.js";
|
|
4
|
+
const ARTIFACT_VERSION = "trace-compactor-v0";
|
|
5
|
+
const POLICY_NAME = "stale_tool_output_to_state_capsule";
|
|
6
|
+
const POLICY_VERSION = "0.1.0";
|
|
7
|
+
function roundCurrency(value) {
|
|
8
|
+
return Number(value.toFixed(6));
|
|
9
|
+
}
|
|
10
|
+
function roundPercent(value) {
|
|
11
|
+
return Number(value.toFixed(2));
|
|
12
|
+
}
|
|
13
|
+
function createCompactedTraceFromIds(trace, compactedMessageIds) {
|
|
14
|
+
return {
|
|
15
|
+
...trace,
|
|
16
|
+
id: `${trace.id}_compacted`,
|
|
17
|
+
title: `${trace.title} (compacted)`,
|
|
18
|
+
messages: trace.messages.filter((message) => !compactedMessageIds.has(message.id))
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
function findPrimaryRepeatedOutputFinding(findings) {
|
|
22
|
+
// The repeated-output policy is authoritative for the headline waste_pattern when present. When
|
|
23
|
+
// only the ADDITIVE input-compaction category 1 (superseded same-source reads) fired, surface it
|
|
24
|
+
// so the report's `waste_pattern` honestly reflects what was compacted (both categories are
|
|
25
|
+
// compacted via getCompactedMessageIds).
|
|
26
|
+
return (findings.find((finding) => finding.category === "repeated_tool_output") ??
|
|
27
|
+
findings.find((finding) => finding.category === "superseded_same_source_read"));
|
|
28
|
+
}
|
|
29
|
+
export function createCompactedTrace(trace) {
|
|
30
|
+
const findings = detectWaste(trace);
|
|
31
|
+
return createCompactedTraceFromIds(trace, getCompactedMessageIds(findings));
|
|
32
|
+
}
|
|
33
|
+
function countRepeatedOccurrences(findings, primarySource) {
|
|
34
|
+
if (!primarySource)
|
|
35
|
+
return 0;
|
|
36
|
+
const duplicateCount = findings.filter((f) => f.category === "repeated_tool_output" && f.messageIds[0] === primarySource).length;
|
|
37
|
+
return duplicateCount > 0 ? duplicateCount + 1 : 0;
|
|
38
|
+
}
|
|
39
|
+
export function compactTrace(trace, options = {}) {
|
|
40
|
+
const findings = detectWaste(trace);
|
|
41
|
+
const compactedMessageIds = getCompactedMessageIds(findings);
|
|
42
|
+
const compactedTrace = createCompactedTraceFromIds(trace, compactedMessageIds);
|
|
43
|
+
const primaryFinding = findPrimaryRepeatedOutputFinding(findings);
|
|
44
|
+
const before = estimateTraceTokens(trace);
|
|
45
|
+
const after = estimateTraceTokens(compactedTrace);
|
|
46
|
+
const beforeCost = calculateCost(trace.model, before);
|
|
47
|
+
const afterCost = calculateCost(trace.model, after);
|
|
48
|
+
const tokensSaved = Math.max(0, before.inputTokens - after.inputTokens);
|
|
49
|
+
const savingPerRun = Math.max(0, beforeCost.totalCostUsd - afterCost.totalCostUsd);
|
|
50
|
+
return {
|
|
51
|
+
run_id: options.runId ?? trace.id,
|
|
52
|
+
trace_title: trace.title,
|
|
53
|
+
model: trace.model,
|
|
54
|
+
original_input_tokens: before.inputTokens,
|
|
55
|
+
compacted_input_tokens: after.inputTokens,
|
|
56
|
+
tokens_saved: tokensSaved,
|
|
57
|
+
percent_reduction: before.inputTokens === 0 ? 0 : roundPercent((tokensSaved / before.inputTokens) * 100),
|
|
58
|
+
savings_scope: "trace_level",
|
|
59
|
+
cost_before_per_run: roundCurrency(beforeCost.totalCostUsd),
|
|
60
|
+
cost_after_per_run: roundCurrency(afterCost.totalCostUsd),
|
|
61
|
+
saving_per_run: roundCurrency(savingPerRun),
|
|
62
|
+
policy_name: POLICY_NAME,
|
|
63
|
+
waste_pattern: primaryFinding?.category ?? null,
|
|
64
|
+
source_message_id: primaryFinding?.messageIds[0] ?? null,
|
|
65
|
+
repeated_count: countRepeatedOccurrences(findings, primaryFinding?.messageIds[0]),
|
|
66
|
+
compacted_message_ids: [...compactedMessageIds],
|
|
67
|
+
artifact_version: ARTIFACT_VERSION,
|
|
68
|
+
created_at: trace.createdAt,
|
|
69
|
+
generated_at: options.generatedAt ?? new Date().toISOString(),
|
|
70
|
+
approval_readiness_status: "not_ready",
|
|
71
|
+
approval_readiness_reason: "Approval readiness requires a SafetyReport. Run compact with writeCompactionArtifacts to generate safety evidence."
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
export function createCompactionPolicy(report) {
|
|
75
|
+
return {
|
|
76
|
+
policy_name: POLICY_NAME,
|
|
77
|
+
policy_version: POLICY_VERSION,
|
|
78
|
+
trigger: "Before sending a trace segment to a model, scan local tool outputs for exact repeated normalized content.",
|
|
79
|
+
condition: "A later tool message has the same tool name and normalized content as an earlier retained tool message.",
|
|
80
|
+
action: "Keep the first copy, remove later duplicate tool-output messages from the compacted trace, and preserve the retained facts in the state capsule.",
|
|
81
|
+
safety_guarantees: [
|
|
82
|
+
"Only duplicate tool outputs are removed by this v0 policy.",
|
|
83
|
+
"The earliest matching tool output remains in the compacted trace.",
|
|
84
|
+
"Assistant, user, and system messages are not removed by this policy.",
|
|
85
|
+
"A state capsule records retained facts, verification evidence, and safety notes."
|
|
86
|
+
],
|
|
87
|
+
expected_savings: `${report.tokens_saved} input tokens per run; approximately $${report.saving_per_run.toFixed(6)} per run (estimated).`,
|
|
88
|
+
risk_notes: [
|
|
89
|
+
"Token and cost numbers are deterministic local estimates, not provider billing records.",
|
|
90
|
+
"The policy does not summarize near-duplicates or semantically similar outputs yet.",
|
|
91
|
+
"Review the compacted trace before applying the policy to higher-risk workflows."
|
|
92
|
+
]
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
//# sourceMappingURL=compactor.js.map
|