@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.
Files changed (153) hide show
  1. package/README.md +41 -12
  2. package/dist/cli/commands/activity.d.ts +8 -0
  3. package/dist/cli/commands/activity.js +35 -0
  4. package/dist/cli/commands/apply-context.js +68 -0
  5. package/dist/cli/commands/billing-delta.js +12 -1
  6. package/dist/cli/commands/capture-claude-code.d.ts +44 -0
  7. package/dist/cli/commands/capture-claude-code.js +206 -0
  8. package/dist/cli/commands/capture.js +283 -1
  9. package/dist/cli/commands/compact.js +9 -5
  10. package/dist/cli/commands/dev.d.ts +17 -0
  11. package/dist/cli/commands/dev.js +131 -0
  12. package/dist/cli/commands/gateway.d.ts +31 -0
  13. package/dist/cli/commands/gateway.js +312 -0
  14. package/dist/cli/commands/hooks.d.ts +5 -0
  15. package/dist/cli/commands/hooks.js +181 -0
  16. package/dist/cli/commands/init.js +422 -17
  17. package/dist/cli/commands/input-compaction-ab.d.ts +2 -0
  18. package/dist/cli/commands/input-compaction-ab.js +125 -0
  19. package/dist/cli/commands/optimize-hosted.d.ts +54 -0
  20. package/dist/cli/commands/optimize-hosted.js +123 -0
  21. package/dist/cli/commands/optimize.js +8 -0
  22. package/dist/cli/commands/output-shaping-ab.d.ts +2 -0
  23. package/dist/cli/commands/output-shaping-ab.js +132 -0
  24. package/dist/cli/commands/output-shaping.d.ts +7 -0
  25. package/dist/cli/commands/output-shaping.js +43 -0
  26. package/dist/cli/commands/policies.d.ts +2 -0
  27. package/dist/cli/commands/policies.js +80 -0
  28. package/dist/cli/commands/precall.d.ts +19 -0
  29. package/dist/cli/commands/precall.js +436 -0
  30. package/dist/cli/commands/recommend.js +1 -1
  31. package/dist/cli/commands/run.js +297 -12
  32. package/dist/cli/commands/upgrade-status.d.ts +42 -0
  33. package/dist/cli/commands/upgrade-status.js +152 -0
  34. package/dist/cli/cursor-export-read.d.ts +13 -0
  35. package/dist/cli/cursor-export-read.js +53 -0
  36. package/dist/cli/cursor-live-preflight.d.ts +19 -0
  37. package/dist/cli/cursor-live-preflight.js +46 -0
  38. package/dist/cli/index.js +36 -2
  39. package/dist/cli/onboarding/GatewayTui.d.ts +33 -0
  40. package/dist/cli/onboarding/GatewayTui.js +113 -0
  41. package/dist/cli/onboarding/model.d.ts +32 -1
  42. package/dist/cli/onboarding/model.js +27 -2
  43. package/dist/cli/terminal-logo.d.ts +27 -0
  44. package/dist/cli/terminal-logo.js +49 -0
  45. package/dist/core/activity-event.d.ts +107 -0
  46. package/dist/core/activity-event.js +182 -0
  47. package/dist/core/activity-store.d.ts +63 -0
  48. package/dist/core/activity-store.js +254 -0
  49. package/dist/core/activity-view.d.ts +76 -0
  50. package/dist/core/activity-view.js +120 -0
  51. package/dist/core/api-client/index.d.ts +3 -1
  52. package/dist/core/api-client/index.js +4 -0
  53. package/dist/core/api-client/payload.d.ts +5 -1
  54. package/dist/core/api-client/payload.js +8 -6
  55. package/dist/core/api-client/persisted-config.d.ts +57 -0
  56. package/dist/core/api-client/persisted-config.js +156 -0
  57. package/dist/core/api-client/tool.d.ts +29 -0
  58. package/dist/core/api-client/tool.js +50 -0
  59. package/dist/core/api-client/types.d.ts +15 -4
  60. package/dist/core/auto-apply-ask.d.ts +58 -0
  61. package/dist/core/auto-apply-ask.js +105 -0
  62. package/dist/core/auto-apply-gates.d.ts +76 -0
  63. package/dist/core/auto-apply-gates.js +113 -0
  64. package/dist/core/before-call-activity.d.ts +35 -0
  65. package/dist/core/before-call-activity.js +103 -0
  66. package/dist/core/before-call-recovery.d.ts +19 -0
  67. package/dist/core/before-call-recovery.js +46 -0
  68. package/dist/core/before-call-stdin.d.ts +59 -0
  69. package/dist/core/before-call-stdin.js +78 -0
  70. package/dist/core/before-call.d.ts +149 -0
  71. package/dist/core/before-call.js +358 -0
  72. package/dist/core/billing-delta/billing-delta-record.d.ts +28 -0
  73. package/dist/core/billing-delta/billing-delta-record.js +26 -0
  74. package/dist/core/capture-record.d.ts +40 -0
  75. package/dist/core/capture-record.js +55 -0
  76. package/dist/core/claude-code-before-call.d.ts +37 -0
  77. package/dist/core/claude-code-before-call.js +120 -0
  78. package/dist/core/claude-code-connect.d.ts +31 -0
  79. package/dist/core/claude-code-connect.js +87 -0
  80. package/dist/core/claude-code-hook-record.d.ts +71 -0
  81. package/dist/core/claude-code-hook-record.js +0 -0
  82. package/dist/core/claude-code-hooks.d.ts +77 -0
  83. package/dist/core/claude-code-hooks.js +141 -0
  84. package/dist/core/codex-capture.d.ts +45 -0
  85. package/dist/core/codex-capture.js +204 -0
  86. package/dist/core/command-runner.js +5 -1
  87. package/dist/core/compaction-artifacts.d.ts +51 -0
  88. package/dist/core/compaction-artifacts.js +242 -0
  89. package/dist/core/compactor.d.ts +9 -0
  90. package/dist/core/compactor.js +95 -0
  91. package/dist/core/cross-surface-event.d.ts +306 -0
  92. package/dist/core/cross-surface-event.js +330 -0
  93. package/dist/core/cursor-capture.d.ts +54 -0
  94. package/dist/core/cursor-capture.js +215 -0
  95. package/dist/core/cursor-preflight-probe.d.ts +8 -0
  96. package/dist/core/cursor-preflight-probe.js +88 -0
  97. package/dist/core/cursor-preflight.d.ts +86 -0
  98. package/dist/core/cursor-preflight.js +126 -0
  99. package/dist/core/gateway/apply-activation.d.ts +39 -0
  100. package/dist/core/gateway/apply-activation.js +84 -0
  101. package/dist/core/gateway/apply-policy.d.ts +64 -0
  102. package/dist/core/gateway/apply-policy.js +221 -0
  103. package/dist/core/gateway/apply-receipt.d.ts +36 -0
  104. package/dist/core/gateway/apply-receipt.js +75 -0
  105. package/dist/core/gateway/cache-proof.d.ts +45 -0
  106. package/dist/core/gateway/cache-proof.js +65 -0
  107. package/dist/core/gateway/configure.d.ts +50 -0
  108. package/dist/core/gateway/configure.js +169 -0
  109. package/dist/core/gateway/openai-usage.d.ts +56 -0
  110. package/dist/core/gateway/openai-usage.js +128 -0
  111. package/dist/core/gateway/receipt.d.ts +138 -0
  112. package/dist/core/gateway/receipt.js +120 -0
  113. package/dist/core/gateway/recovery.d.ts +23 -0
  114. package/dist/core/gateway/recovery.js +68 -0
  115. package/dist/core/gateway/server.d.ts +51 -0
  116. package/dist/core/gateway/server.js +276 -0
  117. package/dist/core/gateway/status.d.ts +45 -0
  118. package/dist/core/gateway/status.js +109 -0
  119. package/dist/core/hook-usage-aggregate.d.ts +47 -0
  120. package/dist/core/hook-usage-aggregate.js +161 -0
  121. package/dist/core/input-compaction-ab.d.ts +111 -0
  122. package/dist/core/input-compaction-ab.js +158 -0
  123. package/dist/core/local-run-record.d.ts +109 -0
  124. package/dist/core/local-run-record.js +223 -0
  125. package/dist/core/output-shaping-ab.d.ts +140 -0
  126. package/dist/core/output-shaping-ab.js +146 -0
  127. package/dist/core/output-shaping-attach.d.ts +31 -0
  128. package/dist/core/output-shaping-attach.js +57 -0
  129. package/dist/core/output-shaping.d.ts +56 -0
  130. package/dist/core/output-shaping.js +89 -0
  131. package/dist/core/policy-middleware.d.ts +121 -0
  132. package/dist/core/policy-middleware.js +919 -0
  133. package/dist/core/policy-preferences.d.ts +99 -0
  134. package/dist/core/policy-preferences.js +232 -0
  135. package/dist/core/run-aggregator.d.ts +11 -1
  136. package/dist/core/run-aggregator.js +29 -2
  137. package/dist/core/run-flow-report.d.ts +82 -0
  138. package/dist/core/run-flow-report.js +71 -0
  139. package/dist/core/safety-report.js +8 -1
  140. package/dist/core/shim-capture-bridge.d.ts +32 -0
  141. package/dist/core/shim-capture-bridge.js +88 -0
  142. package/dist/core/skill-injection-policy.d.ts +72 -0
  143. package/dist/core/skill-injection-policy.js +183 -0
  144. package/dist/core/spend-attribution.js +1 -1
  145. package/dist/core/token-accounting.d.ts +1 -1
  146. package/dist/core/tool-shim.d.ts +129 -0
  147. package/dist/core/tool-shim.js +447 -0
  148. package/dist/core/trace-parser.d.ts +13 -13
  149. package/dist/core/trace-parser.js +6 -6
  150. package/dist/core/types.d.ts +12 -2
  151. package/dist/core/waste-detector.d.ts +20 -0
  152. package/dist/core/waste-detector.js +160 -6
  153. 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