@compaction/cli 0.1.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 (143) hide show
  1. package/LICENSE +663 -0
  2. package/README.md +319 -0
  3. package/dist/cli/commands/adapter.d.ts +2 -0
  4. package/dist/cli/commands/adapter.js +49 -0
  5. package/dist/cli/commands/aggregate.d.ts +2 -0
  6. package/dist/cli/commands/aggregate.js +60 -0
  7. package/dist/cli/commands/analyze.d.ts +2 -0
  8. package/dist/cli/commands/analyze.js +55 -0
  9. package/dist/cli/commands/apply-context.d.ts +12 -0
  10. package/dist/cli/commands/apply-context.js +80 -0
  11. package/dist/cli/commands/apply.d.ts +2 -0
  12. package/dist/cli/commands/apply.js +65 -0
  13. package/dist/cli/commands/approve.d.ts +2 -0
  14. package/dist/cli/commands/approve.js +55 -0
  15. package/dist/cli/commands/audit.d.ts +2 -0
  16. package/dist/cli/commands/audit.js +63 -0
  17. package/dist/cli/commands/billing-delta.d.ts +2 -0
  18. package/dist/cli/commands/billing-delta.js +246 -0
  19. package/dist/cli/commands/capture-claude-code.d.ts +19 -0
  20. package/dist/cli/commands/capture-claude-code.js +198 -0
  21. package/dist/cli/commands/capture-provider-usage.d.ts +35 -0
  22. package/dist/cli/commands/capture-provider-usage.js +113 -0
  23. package/dist/cli/commands/capture.d.ts +2 -0
  24. package/dist/cli/commands/capture.js +76 -0
  25. package/dist/cli/commands/compact.d.ts +2 -0
  26. package/dist/cli/commands/compact.js +206 -0
  27. package/dist/cli/commands/eval.d.ts +2 -0
  28. package/dist/cli/commands/eval.js +66 -0
  29. package/dist/cli/commands/export-session-seed.d.ts +11 -0
  30. package/dist/cli/commands/export-session-seed.js +42 -0
  31. package/dist/cli/commands/feedback.d.ts +2 -0
  32. package/dist/cli/commands/feedback.js +233 -0
  33. package/dist/cli/commands/import.d.ts +2 -0
  34. package/dist/cli/commands/import.js +60 -0
  35. package/dist/cli/commands/init.d.ts +2 -0
  36. package/dist/cli/commands/init.js +216 -0
  37. package/dist/cli/commands/integrations.d.ts +2 -0
  38. package/dist/cli/commands/integrations.js +29 -0
  39. package/dist/cli/commands/launch-treatment-session.d.ts +14 -0
  40. package/dist/cli/commands/launch-treatment-session.js +50 -0
  41. package/dist/cli/commands/optimize.d.ts +2 -0
  42. package/dist/cli/commands/optimize.js +33 -0
  43. package/dist/cli/commands/recommend.d.ts +4 -0
  44. package/dist/cli/commands/recommend.js +66 -0
  45. package/dist/cli/commands/review.d.ts +2 -0
  46. package/dist/cli/commands/review.js +60 -0
  47. package/dist/cli/commands/run.d.ts +2 -0
  48. package/dist/cli/commands/run.js +49 -0
  49. package/dist/cli/commands/spend.d.ts +2 -0
  50. package/dist/cli/commands/spend.js +24 -0
  51. package/dist/cli/commands/summary.d.ts +2 -0
  52. package/dist/cli/commands/summary.js +19 -0
  53. package/dist/cli/engine-degrade.d.ts +40 -0
  54. package/dist/cli/engine-degrade.js +59 -0
  55. package/dist/cli/index.d.ts +2 -0
  56. package/dist/cli/index.js +59 -0
  57. package/dist/core/adapter-validation.d.ts +32 -0
  58. package/dist/core/adapter-validation.js +185 -0
  59. package/dist/core/adapters/claude-code-adapter.d.ts +51 -0
  60. package/dist/core/adapters/claude-code-adapter.js +539 -0
  61. package/dist/core/adapters/claude-code-discovery.d.ts +40 -0
  62. package/dist/core/adapters/claude-code-discovery.js +100 -0
  63. package/dist/core/aggregate-format.d.ts +62 -0
  64. package/dist/core/aggregate-format.js +349 -0
  65. package/dist/core/api-client/client.d.ts +43 -0
  66. package/dist/core/api-client/client.js +143 -0
  67. package/dist/core/api-client/config.d.ts +36 -0
  68. package/dist/core/api-client/config.js +44 -0
  69. package/dist/core/api-client/index.d.ts +13 -0
  70. package/dist/core/api-client/index.js +14 -0
  71. package/dist/core/api-client/payload.d.ts +93 -0
  72. package/dist/core/api-client/payload.js +180 -0
  73. package/dist/core/api-client/types.d.ts +95 -0
  74. package/dist/core/api-client/types.js +21 -0
  75. package/dist/core/artifact-writer.d.ts +2 -0
  76. package/dist/core/artifact-writer.js +14 -0
  77. package/dist/core/audit-report.d.ts +91 -0
  78. package/dist/core/audit-report.js +422 -0
  79. package/dist/core/billing-delta/billing-delta-record.d.ts +290 -0
  80. package/dist/core/billing-delta/billing-delta-record.js +394 -0
  81. package/dist/core/capture-adapter.d.ts +74 -0
  82. package/dist/core/capture-adapter.js +2 -0
  83. package/dist/core/command-runner.d.ts +29 -0
  84. package/dist/core/command-runner.js +96 -0
  85. package/dist/core/content-hash.d.ts +1 -0
  86. package/dist/core/content-hash.js +5 -0
  87. package/dist/core/cost-calculator.d.ts +6 -0
  88. package/dist/core/cost-calculator.js +20 -0
  89. package/dist/core/feedback-bundle.d.ts +159 -0
  90. package/dist/core/feedback-bundle.js +406 -0
  91. package/dist/core/openai-agents-capture.d.ts +66 -0
  92. package/dist/core/openai-agents-capture.js +475 -0
  93. package/dist/core/policy-types.d.ts +85 -0
  94. package/dist/core/policy-types.js +29 -0
  95. package/dist/core/pricing.d.ts +23 -0
  96. package/dist/core/pricing.js +55 -0
  97. package/dist/core/provider-usage/credential-redaction.d.ts +23 -0
  98. package/dist/core/provider-usage/credential-redaction.js +60 -0
  99. package/dist/core/provider-usage/provider-usage-adapter.d.ts +65 -0
  100. package/dist/core/provider-usage/provider-usage-adapter.js +147 -0
  101. package/dist/core/provider-usage/provider-usage-client.d.ts +89 -0
  102. package/dist/core/provider-usage/provider-usage-client.js +96 -0
  103. package/dist/core/report-generator.d.ts +7 -0
  104. package/dist/core/report-generator.js +275 -0
  105. package/dist/core/run-aggregator.d.ts +50 -0
  106. package/dist/core/run-aggregator.js +251 -0
  107. package/dist/core/run-labels.d.ts +52 -0
  108. package/dist/core/run-labels.js +80 -0
  109. package/dist/core/run-trace-converter.d.ts +3 -0
  110. package/dist/core/run-trace-converter.js +94 -0
  111. package/dist/core/safety-report.d.ts +52 -0
  112. package/dist/core/safety-report.js +440 -0
  113. package/dist/core/savings.d.ts +22 -0
  114. package/dist/core/savings.js +36 -0
  115. package/dist/core/session-aggregate.d.ts +211 -0
  116. package/dist/core/session-aggregate.js +490 -0
  117. package/dist/core/skill-injection-detector.d.ts +51 -0
  118. package/dist/core/skill-injection-detector.js +146 -0
  119. package/dist/core/spend-attribution.d.ts +169 -0
  120. package/dist/core/spend-attribution.js +611 -0
  121. package/dist/core/standalone-capsule-provenance.d.ts +4 -0
  122. package/dist/core/standalone-capsule-provenance.js +62 -0
  123. package/dist/core/state-capsule.d.ts +3 -0
  124. package/dist/core/state-capsule.js +147 -0
  125. package/dist/core/token-accounting.d.ts +62 -0
  126. package/dist/core/token-accounting.js +81 -0
  127. package/dist/core/token-estimator.d.ts +3 -0
  128. package/dist/core/token-estimator.js +18 -0
  129. package/dist/core/trace-adapters.d.ts +53 -0
  130. package/dist/core/trace-adapters.js +714 -0
  131. package/dist/core/trace-fingerprint.d.ts +79 -0
  132. package/dist/core/trace-fingerprint.js +102 -0
  133. package/dist/core/trace-intake.d.ts +41 -0
  134. package/dist/core/trace-intake.js +164 -0
  135. package/dist/core/trace-parser.d.ts +203 -0
  136. package/dist/core/trace-parser.js +143 -0
  137. package/dist/core/types.d.ts +238 -0
  138. package/dist/core/types.js +2 -0
  139. package/dist/core/usage-metadata.d.ts +51 -0
  140. package/dist/core/usage-metadata.js +122 -0
  141. package/dist/core/waste-detector.d.ts +8 -0
  142. package/dist/core/waste-detector.js +58 -0
  143. package/package.json +90 -0
@@ -0,0 +1,714 @@
1
+ import { createHash } from "node:crypto";
2
+ import { z } from "zod";
3
+ import { agentTraceSchema, CURRENT_AGENT_TRACE_ARTIFACT_VERSION } from "./trace-parser.js";
4
+ const baseGeneratedAt = "1970-01-01T00:00:00.000Z";
5
+ const supportedSources = ["agent-trace", "messages", "codex-exec-jsonl", "unknown"];
6
+ const roleSchema = z.enum(["system", "user", "assistant", "tool", "stdout", "stderr"]);
7
+ const looseMessageSchema = z
8
+ .object({
9
+ id: z.unknown().optional(),
10
+ role: z.unknown().optional(),
11
+ content: z.unknown().optional(),
12
+ timestamp: z.unknown().optional(),
13
+ createdAt: z.unknown().optional(),
14
+ created_at: z.unknown().optional(),
15
+ toolName: z.unknown().optional(),
16
+ name: z.unknown().optional()
17
+ })
18
+ .passthrough();
19
+ function stableHash(value) {
20
+ return createHash("sha256").update(value).digest("hex").slice(0, 12);
21
+ }
22
+ function stableTimestamp(index) {
23
+ return new Date(Date.UTC(1970, 0, 1, 0, 0, index)).toISOString();
24
+ }
25
+ function isIsoTimestamp(value) {
26
+ return typeof value === "string" && z.string().datetime({ offset: true }).safeParse(value).success;
27
+ }
28
+ function looksLikeAgentTrace(value) {
29
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
30
+ return false;
31
+ }
32
+ const candidate = value;
33
+ return typeof candidate.id === "string" && Array.isArray(candidate.messages) && typeof candidate.model === "string";
34
+ }
35
+ function extractMessageCandidates(value) {
36
+ if (Array.isArray(value)) {
37
+ return value;
38
+ }
39
+ if (!value || typeof value !== "object") {
40
+ return null;
41
+ }
42
+ const candidate = value;
43
+ return Array.isArray(candidate.messages) ? candidate.messages : null;
44
+ }
45
+ function looksLikeMessages(value) {
46
+ const messages = extractMessageCandidates(value);
47
+ return Boolean(messages && messages.some((message) => looseMessageSchema.safeParse(message).success));
48
+ }
49
+ function formatZodIssues(error) {
50
+ return error.issues.map((issue) => {
51
+ const issuePath = issue.path.length > 0 ? issue.path.join(".") : "root";
52
+ return `${issuePath}: ${issue.message}`;
53
+ });
54
+ }
55
+ function resultStatus(warnings, failures) {
56
+ if (failures.length > 0) {
57
+ return "fail";
58
+ }
59
+ return warnings.length > 0 ? "warn" : "pass";
60
+ }
61
+ function adapterSourceMetadata(adapter, extra = {}) {
62
+ return {
63
+ adapter_id: adapter.id,
64
+ adapter_display_name: adapter.displayName,
65
+ supported_source: adapter.supportedSource,
66
+ local_file_normalizer: true,
67
+ live_provider_integration: false,
68
+ ...extra
69
+ };
70
+ }
71
+ const codexSupportedItemTypes = new Set([
72
+ "agent_message",
73
+ "assistant_message",
74
+ "message",
75
+ "command_execution",
76
+ "command",
77
+ "tool_call",
78
+ "mcp_tool_call"
79
+ ]);
80
+ function isRecord(value) {
81
+ return Boolean(value && typeof value === "object" && !Array.isArray(value));
82
+ }
83
+ function getNestedRecord(value, key) {
84
+ const nested = value[key];
85
+ return isRecord(nested) ? nested : null;
86
+ }
87
+ function getStringField(value, keys) {
88
+ for (const key of keys) {
89
+ const candidate = value[key];
90
+ if (typeof candidate === "string" && candidate.trim().length > 0) {
91
+ return candidate;
92
+ }
93
+ }
94
+ return null;
95
+ }
96
+ function getStringArrayText(value, keys) {
97
+ for (const key of keys) {
98
+ const candidate = value[key];
99
+ if (Array.isArray(candidate)) {
100
+ const parts = candidate.filter((part) => typeof part === "string" && part.trim().length > 0);
101
+ if (parts.length > 0) {
102
+ return parts.join("\n");
103
+ }
104
+ }
105
+ }
106
+ return null;
107
+ }
108
+ function getCodexEventType(event) {
109
+ return getStringField(event, ["type", "event", "event_type", "kind"]);
110
+ }
111
+ function getCodexItem(event) {
112
+ return getNestedRecord(event, "item") ?? getNestedRecord(event, "message") ?? getNestedRecord(event, "data");
113
+ }
114
+ function getCodexItemType(event, item) {
115
+ return item ? getStringField(item, ["type", "item_type", "kind"]) ?? getCodexEventType(event) : getCodexEventType(event);
116
+ }
117
+ function looksLikeCodexExecJsonl(value) {
118
+ if (!Array.isArray(value) || value.length === 0) {
119
+ return false;
120
+ }
121
+ return value.some((candidate) => {
122
+ if (!isRecord(candidate)) {
123
+ return false;
124
+ }
125
+ const event = isRecord(candidate.event) ? candidate.event : candidate;
126
+ const eventType = getCodexEventType(event);
127
+ const item = getCodexItem(event);
128
+ const itemType = getCodexItemType(event, item);
129
+ return Boolean(eventType?.startsWith("thread.") ||
130
+ eventType?.startsWith("turn.") ||
131
+ eventType?.startsWith("item.") ||
132
+ eventType === "error" ||
133
+ (itemType && codexSupportedItemTypes.has(itemType)));
134
+ });
135
+ }
136
+ function codexTextFrom(event, item) {
137
+ const candidates = item ? [item, event] : [event];
138
+ for (const candidate of candidates) {
139
+ const direct = getStringField(candidate, ["content", "text", "message", "output", "summary"]);
140
+ if (direct) {
141
+ return direct;
142
+ }
143
+ const joined = getStringArrayText(candidate, ["content", "text", "output"]);
144
+ if (joined) {
145
+ return joined;
146
+ }
147
+ }
148
+ return null;
149
+ }
150
+ function codexCommandText(event, item) {
151
+ const source = item ?? event;
152
+ const command = getStringField(source, ["command", "cmd"]);
153
+ const args = Array.isArray(source.args) ? source.args.filter((arg) => typeof arg === "string") : [];
154
+ const stdout = getStringField(source, ["stdout", "output"]);
155
+ const stderr = getStringField(source, ["stderr", "error"]);
156
+ const parts = [
157
+ command ? `$ ${[command, ...args].join(" ")}` : null,
158
+ stdout ? `stdout:\n${stdout}` : null,
159
+ stderr ? `stderr:\n${stderr}` : null
160
+ ].filter((part) => Boolean(part));
161
+ return parts.length > 0 ? parts.join("\n\n") : codexTextFrom(event, item);
162
+ }
163
+ function normalizeCodexExecJsonl(adapter, value, rawContent, operatorExport) {
164
+ const warnings = [
165
+ "Codex exec JSONL adapter v0 reads only local user-supplied JSONL artifacts and does not launch Codex or call provider APIs.",
166
+ "Token counts, billing, complete prompt assembly, and interactive/IDE/desktop/web Codex exports are not supported by this adapter."
167
+ ];
168
+ const failures = [];
169
+ const normalizationSteps = [
170
+ "Detected a local Codex exec --json JSONL event stream.",
171
+ "Mapped supported agent message and command/tool events into the internal AgentTrace format.",
172
+ "Recorded raw line numbers and event hashes in message metadata when available."
173
+ ];
174
+ const skippedFields = [];
175
+ if (!Array.isArray(value)) {
176
+ failures.push("Codex exec JSONL input must be parsed as a non-empty JSONL event array.");
177
+ return {
178
+ status: "fail",
179
+ trace: null,
180
+ warnings,
181
+ failures,
182
+ normalization_steps: normalizationSteps,
183
+ skipped_fields: skippedFields,
184
+ source_metadata: adapterSourceMetadata(adapter, { detected_shape: "unsupported" })
185
+ };
186
+ }
187
+ const messages = [];
188
+ let lifecycleEvents = 0;
189
+ let unsupportedEvents = 0;
190
+ value.forEach((entry, index) => {
191
+ if (!isRecord(entry)) {
192
+ unsupportedEvents += 1;
193
+ skippedFields.push(`jsonl[${index}]`);
194
+ warnings.push(`Skipped JSONL event at index ${index} because it is not a JSON object.`);
195
+ return;
196
+ }
197
+ const rawLineNumber = typeof entry.rawLineNumber === "number" ? entry.rawLineNumber : index + 1;
198
+ const event = isRecord(entry.event) ? entry.event : entry;
199
+ const eventType = getCodexEventType(event) ?? "unknown";
200
+ const item = getCodexItem(event);
201
+ const itemType = getCodexItemType(event, item) ?? "unknown";
202
+ const rawEventHash = stableHash(JSON.stringify(event));
203
+ const metadata = { source_line: rawLineNumber, raw_event_hash: rawEventHash, event_type: eventType, item_type: itemType };
204
+ if (eventType.startsWith("thread.") || eventType.startsWith("turn.")) {
205
+ lifecycleEvents += 1;
206
+ skippedFields.push(`jsonl[${index}].${eventType}`);
207
+ return;
208
+ }
209
+ const lowerEventType = eventType.toLowerCase();
210
+ const lowerItemType = itemType.toLowerCase();
211
+ const messageId = `codex_${String(messages.length + 1).padStart(3, "0")}`;
212
+ const timestampCandidate = getStringField(event, ["timestamp", "createdAt", "created_at"]);
213
+ const timestamp = isIsoTimestamp(timestampCandidate) ? timestampCandidate : stableTimestamp(messages.length);
214
+ if (!isIsoTimestamp(timestampCandidate)) {
215
+ warnings.push(`Generated deterministic timestamp ${timestamp} for Codex JSONL event at line ${rawLineNumber}.`);
216
+ }
217
+ if (lowerItemType.includes("agent_message") || lowerItemType.includes("assistant_message") || lowerEventType.includes("agent_message")) {
218
+ const content = codexTextFrom(event, item);
219
+ if (!content) {
220
+ warnings.push(`Skipped Codex agent message at line ${rawLineNumber} because it did not include string content.`);
221
+ skippedFields.push(`jsonl[${index}].content`);
222
+ return;
223
+ }
224
+ messages.push({ id: messageId, role: "assistant", content, timestamp, metadata });
225
+ return;
226
+ }
227
+ if (lowerItemType.includes("user_message")) {
228
+ const content = codexTextFrom(event, item);
229
+ if (!content) {
230
+ warnings.push(`Skipped Codex user message at line ${rawLineNumber} because it did not include string content.`);
231
+ skippedFields.push(`jsonl[${index}].content`);
232
+ return;
233
+ }
234
+ messages.push({ id: messageId, role: "user", content, timestamp, metadata });
235
+ return;
236
+ }
237
+ if (lowerItemType.includes("command") || lowerEventType.includes("command")) {
238
+ const content = codexCommandText(event, item);
239
+ if (!content) {
240
+ warnings.push(`Skipped Codex command event at line ${rawLineNumber} because it did not include command text, stdout, or stderr.`);
241
+ skippedFields.push(`jsonl[${index}].command_output`);
242
+ return;
243
+ }
244
+ messages.push({ id: messageId, role: "tool", content, timestamp, toolName: "codex_command_execution", metadata });
245
+ return;
246
+ }
247
+ if (lowerItemType.includes("tool_call") || lowerItemType.includes("mcp_tool_call")) {
248
+ const content = codexTextFrom(event, item);
249
+ if (!content) {
250
+ warnings.push(`Skipped Codex tool event at line ${rawLineNumber} because it did not include string output.`);
251
+ skippedFields.push(`jsonl[${index}].tool_output`);
252
+ return;
253
+ }
254
+ messages.push({ id: messageId, role: "tool", content, timestamp, toolName: "codex_tool_call", metadata });
255
+ return;
256
+ }
257
+ unsupportedEvents += 1;
258
+ skippedFields.push(`jsonl[${index}].${eventType}/${itemType}`);
259
+ warnings.push(`Skipped unsupported Codex JSONL event at line ${rawLineNumber}: event type ${eventType}, item type ${itemType}.`);
260
+ });
261
+ if (messages.length === 0) {
262
+ failures.push("No supported Codex agent message, command, or tool events with string content were found.");
263
+ return {
264
+ status: "fail",
265
+ trace: null,
266
+ warnings,
267
+ failures,
268
+ normalization_steps: normalizationSteps,
269
+ skipped_fields: skippedFields,
270
+ source_metadata: adapterSourceMetadata(adapter, {
271
+ detected_shape: "codex-exec-jsonl",
272
+ input_event_count: value.length,
273
+ lifecycle_event_count: lifecycleEvents,
274
+ unsupported_event_count: unsupportedEvents
275
+ })
276
+ };
277
+ }
278
+ const createdAt = messages[0]?.timestamp ?? baseGeneratedAt;
279
+ // Evidence-tier at the operator-import boundary is opt-in (weakest-honest-default):
280
+ // - WITHOUT an explicit operator assertion, Codex JSONL imports default to the
281
+ // weakest honest tier — `manual` (evidence tier `fixture`). This is the
282
+ // synthetic-safe default: a hand-authored demo/example/test JSONL (e.g.
283
+ // src/examples/codex-exec-demo.jsonl) must NOT be labeled as a real local export.
284
+ // - WITH the explicit operator assertion (`compaction import --operator-export`,
285
+ // threaded here as `operatorExport`), the operator is attesting "this is my own
286
+ // real `codex exec --json` export", and the trace is elevated to `codex_import`
287
+ // (evidence tier `imported_local`). That tier stays STRICTLY below `real_captured`:
288
+ // it never unlocks the `ready` approval rung and caps at `conditional`, with
289
+ // `real_captured === false`, and carries no billing or semantic/commitment claim.
290
+ // Either way the raw line numbers and event hashes recorded in each message's metadata
291
+ // above preserve per-event provenance from the source artifact.
292
+ const trace = {
293
+ id: `codex_exec_${stableHash(rawContent)}`,
294
+ title: "Imported Codex exec JSONL trace",
295
+ artifactVersion: CURRENT_AGENT_TRACE_ARTIFACT_VERSION,
296
+ source: operatorExport ? "codex_import" : "manual",
297
+ createdAt,
298
+ generatedAt: createdAt,
299
+ model: "codex-exec-jsonl-local-export",
300
+ messages
301
+ };
302
+ if (lifecycleEvents > 0) {
303
+ normalizationSteps.push(`Skipped ${lifecycleEvents} lifecycle event(s) after recording them as unsupported AgentTrace fields.`);
304
+ }
305
+ if (unsupportedEvents > 0) {
306
+ normalizationSteps.push(`Skipped ${unsupportedEvents} unsupported Codex event(s) that do not map to AgentTrace messages in v0.`);
307
+ }
308
+ normalizationSteps.push(operatorExport
309
+ ? "Operator asserted local provenance (--operator-export): evidence tier elevated to imported_local (codex_import); stays one rung below the captured-session tier and caps approval readiness at conditional."
310
+ : "No operator provenance assertion: evidence tier defaults to the weakest honest tier (fixture/manual). Re-run with --operator-export only for your own real `codex exec --json` export.");
311
+ return {
312
+ status: resultStatus(warnings, failures),
313
+ trace,
314
+ warnings,
315
+ failures,
316
+ normalization_steps: normalizationSteps,
317
+ skipped_fields: skippedFields,
318
+ source_metadata: adapterSourceMetadata(adapter, {
319
+ detected_shape: "codex-exec-jsonl",
320
+ input_event_count: value.length,
321
+ lifecycle_event_count: lifecycleEvents,
322
+ unsupported_event_count: unsupportedEvents,
323
+ normalized_message_count: messages.length,
324
+ source_format: "jsonl",
325
+ provider_api_calls: false,
326
+ operator_export_asserted: operatorExport,
327
+ evidence_tier: operatorExport ? "imported_local" : "fixture"
328
+ })
329
+ };
330
+ }
331
+ // Evidence tiers that the `agent-trace` import boundary CANNOT honestly confer.
332
+ //
333
+ // `real_captured` is reserved for a trace this tool itself captured from an agent
334
+ // runtime session under controlled conditions (see capture-adapter.ts and
335
+ // evidenceSourceTypeFromTrace in safety-report.ts: only `real_captured` can unlock
336
+ // `approval_readiness_status: "ready"`). The import/adapter path reads a local file the
337
+ // tool did NOT produce or attest, so it cannot verify that provenance — a hand-authored
338
+ // JSON file could simply TYPE `"source": "real_captured"` and otherwise inherit the
339
+ // strongest, approval-unlocking tier. `demo` is likewise a positioning label, not an
340
+ // import provenance the adapter should silently carry. Both are therefore stripped to
341
+ // the weakest honest tier (`manual` -> `fixture`) on import, with the downgrade recorded
342
+ // in the intake report. This closes the import-time provenance hole flagged in
343
+ // docs/ops/real-trace-capture-readiness.md §7 ("a hand-authored JSON could declare
344
+ // source: real_captured and inherit the strongest tier ... without ever having been
345
+ // captured live"). It does NOT touch the genuine capture path (capture-claude-code writes
346
+ // real_captured and is read via parseTraceFile, not this adapter), and it only ever moves
347
+ // a tier DOWN — it can never elevate a label.
348
+ const UNATTESTABLE_IMPORT_SOURCES = new Set(["real_captured", "demo"]);
349
+ function normalizeAgentTrace(adapter, value) {
350
+ const parsed = agentTraceSchema.safeParse(value);
351
+ if (!parsed.success) {
352
+ return {
353
+ status: "fail",
354
+ trace: null,
355
+ warnings: [],
356
+ failures: formatZodIssues(parsed.error),
357
+ normalization_steps: ["Attempted to validate input as the internal AgentTrace JSON shape."],
358
+ skipped_fields: [],
359
+ source_metadata: adapterSourceMetadata(adapter, { detected_shape: looksLikeAgentTrace(value) ? "agent-trace" : "unsupported" })
360
+ };
361
+ }
362
+ const warnings = [];
363
+ const normalizationSteps = [
364
+ "Validated input as the internal AgentTrace JSON shape.",
365
+ "Wrote a normalized AgentTrace copy without mutating the source file."
366
+ ];
367
+ const declaredSource = parsed.data.source;
368
+ let trace = parsed.data;
369
+ let provenanceGuardApplied = false;
370
+ if (UNATTESTABLE_IMPORT_SOURCES.has(declaredSource)) {
371
+ // Downgrade to the weakest honest tier. The import boundary cannot attest a
372
+ // runtime capture, so an unverifiable self-declared `real_captured`/`demo` is
373
+ // recorded as `manual` (-> `fixture`). This NEVER elevates a tier.
374
+ trace = { ...parsed.data, source: "manual" };
375
+ provenanceGuardApplied = true;
376
+ const message = `Import provenance guard: declared source "${declaredSource}" is not attestable through the import boundary ` +
377
+ `(only a tool-mediated capture can earn real_captured). Downgraded evidence tier to fixture (source: manual). ` +
378
+ `To earn real_captured, capture the session with a capture command (e.g. compaction capture claude-code), not import.`;
379
+ warnings.push(message);
380
+ normalizationSteps.push(message);
381
+ }
382
+ return {
383
+ status: provenanceGuardApplied ? "warn" : "pass",
384
+ trace,
385
+ warnings,
386
+ failures: [],
387
+ normalization_steps: normalizationSteps,
388
+ skipped_fields: [],
389
+ source_metadata: adapterSourceMetadata(adapter, {
390
+ detected_shape: "agent-trace",
391
+ message_count: trace.messages.length,
392
+ declared_source: declaredSource,
393
+ effective_source: trace.source,
394
+ provenance_guard_applied: provenanceGuardApplied
395
+ })
396
+ };
397
+ }
398
+ function normalizeMessages(adapter, value, rawContent) {
399
+ const candidates = extractMessageCandidates(value);
400
+ const warnings = [];
401
+ const failures = [];
402
+ const normalizationSteps = ["Detected a simple local messages shape."];
403
+ const skippedFields = [];
404
+ if (!candidates) {
405
+ failures.push("Input is not an array of messages and does not contain a messages array.");
406
+ return {
407
+ status: "fail",
408
+ trace: null,
409
+ warnings,
410
+ failures,
411
+ normalization_steps: normalizationSteps,
412
+ skipped_fields: skippedFields,
413
+ source_metadata: adapterSourceMetadata(adapter, { detected_shape: "unsupported" })
414
+ };
415
+ }
416
+ const messages = [];
417
+ candidates.forEach((candidate, index) => {
418
+ const parsed = looseMessageSchema.safeParse(candidate);
419
+ if (!parsed.success) {
420
+ warnings.push(`Skipped message at index ${index} because it is not a JSON object.`);
421
+ return;
422
+ }
423
+ const message = parsed.data;
424
+ if (typeof message.content !== "string") {
425
+ warnings.push(`Skipped message at index ${index} because content is missing or not a string.`);
426
+ return;
427
+ }
428
+ const roleResult = roleSchema.safeParse(message.role);
429
+ if (!roleResult.success) {
430
+ warnings.push(`Skipped message at index ${index} because role is missing or unsupported.`);
431
+ return;
432
+ }
433
+ const timestampCandidate = message.timestamp ?? message.createdAt ?? message.created_at;
434
+ const timestamp = isIsoTimestamp(timestampCandidate) ? timestampCandidate : stableTimestamp(index);
435
+ const id = typeof message.id === "string" && message.id.length > 0 ? message.id : `msg_${String(index + 1).padStart(3, "0")}`;
436
+ if (typeof message.id !== "string" || message.id.length === 0) {
437
+ warnings.push(`Generated deterministic message id ${id} for message at index ${index}.`);
438
+ normalizationSteps.push(`Generated deterministic message id ${id} for message at index ${index}.`);
439
+ }
440
+ if (!isIsoTimestamp(timestampCandidate)) {
441
+ warnings.push(`Generated deterministic timestamp ${timestamp} for message ${id}.`);
442
+ normalizationSteps.push(`Generated deterministic timestamp ${timestamp} for message ${id}.`);
443
+ }
444
+ const allowedKeys = new Set(["id", "role", "content", "timestamp", "createdAt", "created_at", "toolName", "name"]);
445
+ Object.keys(message).forEach((key) => {
446
+ if (!allowedKeys.has(key)) {
447
+ skippedFields.push(`messages[${index}].${key}`);
448
+ }
449
+ });
450
+ const normalizedMessage = {
451
+ id,
452
+ role: roleResult.data,
453
+ content: message.content,
454
+ timestamp
455
+ };
456
+ const toolName = typeof message.toolName === "string" && message.toolName.length > 0 ? message.toolName : message.name;
457
+ if (typeof toolName === "string" && toolName.length > 0) {
458
+ normalizedMessage.toolName = toolName;
459
+ }
460
+ messages.push(normalizedMessage);
461
+ });
462
+ if (messages.length === 0) {
463
+ failures.push("No supported messages with string content and supported roles were found.");
464
+ return {
465
+ status: "fail",
466
+ trace: null,
467
+ warnings,
468
+ failures,
469
+ normalization_steps: normalizationSteps,
470
+ skipped_fields: skippedFields,
471
+ source_metadata: adapterSourceMetadata(adapter, { detected_shape: "messages", input_message_candidates: candidates.length })
472
+ };
473
+ }
474
+ const traceId = `imported_${stableHash(rawContent)}`;
475
+ const createdAt = messages[0]?.timestamp ?? baseGeneratedAt;
476
+ const trace = {
477
+ id: traceId,
478
+ title: "Imported local messages trace",
479
+ artifactVersion: CURRENT_AGENT_TRACE_ARTIFACT_VERSION,
480
+ source: "manual",
481
+ createdAt,
482
+ generatedAt: createdAt,
483
+ model: "imported-local-trace",
484
+ messages
485
+ };
486
+ normalizationSteps.push("Converted simple messages into the internal AgentTrace format with deterministic local defaults.");
487
+ normalizationSteps.push("Set AgentTrace source to manual because no provider/runtime adapter is used in v0.");
488
+ return {
489
+ status: resultStatus(warnings, failures),
490
+ trace,
491
+ warnings,
492
+ failures,
493
+ normalization_steps: normalizationSteps,
494
+ skipped_fields: skippedFields,
495
+ source_metadata: adapterSourceMetadata(adapter, {
496
+ detected_shape: "messages",
497
+ input_message_candidates: candidates.length,
498
+ normalized_message_count: messages.length
499
+ })
500
+ };
501
+ }
502
+ export const agentTraceAdapter = {
503
+ id: "agent-trace",
504
+ displayName: "Internal AgentTrace",
505
+ description: "Validates a local file that already matches the internal AgentTrace JSON shape.",
506
+ supportedSource: "agent-trace",
507
+ limitations: [
508
+ "Only local JSON files are supported.",
509
+ "Live provider/runtime capture is future work.",
510
+ "Input must validate against the current internal AgentTrace schema."
511
+ ],
512
+ canHandle: looksLikeAgentTrace,
513
+ normalize(input) {
514
+ return normalizeAgentTrace(this, input);
515
+ }
516
+ };
517
+ export const messagesAdapter = {
518
+ id: "messages",
519
+ displayName: "Simple messages",
520
+ description: "Normalizes a local array of role/content messages, or an object with a messages array, into AgentTrace.",
521
+ supportedSource: "messages",
522
+ limitations: [
523
+ "Only local JSON files are supported.",
524
+ "Messages need supported roles and string content.",
525
+ "Missing ids and timestamps are filled with deterministic local defaults.",
526
+ "Live provider/runtime capture is future work."
527
+ ],
528
+ canHandle: looksLikeMessages,
529
+ normalize(input, options) {
530
+ return normalizeMessages(this, input, options.rawContent);
531
+ }
532
+ };
533
+ export const codexExecJsonlAdapter = {
534
+ id: "codex-exec-jsonl",
535
+ displayName: "Codex exec JSONL",
536
+ description: "Normalizes a local JSONL stream produced by Codex exec --json into AgentTrace without launching Codex or calling provider APIs.",
537
+ supportedSource: "codex-exec-jsonl",
538
+ limitations: [
539
+ "Only local user-supplied JSONL files are supported.",
540
+ "This adapter does not launch Codex, call OpenAI APIs, scrape private data, or upload artifacts.",
541
+ "Only non-interactive Codex exec --json style event streams are in scope for v0.",
542
+ "Interactive Codex, Codex IDE, Codex desktop, Codex web, complete prompt reconstruction, token accuracy, and billing accuracy are future work.",
543
+ "Unsupported or unmapped event families are reported as skipped fields."
544
+ ],
545
+ canHandle: looksLikeCodexExecJsonl,
546
+ normalize(input, options) {
547
+ return normalizeCodexExecJsonl(this, input, options.rawContent, options.operatorExport === true);
548
+ }
549
+ };
550
+ function detectConcreteAdapter(input) {
551
+ if (agentTraceAdapter.canHandle(input)) {
552
+ return agentTraceAdapter;
553
+ }
554
+ if (codexExecJsonlAdapter.canHandle(input)) {
555
+ return codexExecJsonlAdapter;
556
+ }
557
+ if (messagesAdapter.canHandle(input)) {
558
+ return messagesAdapter;
559
+ }
560
+ return null;
561
+ }
562
+ export const unknownAdapter = {
563
+ id: "unknown",
564
+ displayName: "Unknown local trace source",
565
+ description: "Detects supported local AgentTrace and simple messages shapes before normalizing with the matching adapter.",
566
+ supportedSource: "unknown",
567
+ limitations: [
568
+ "Detection is shape-based and local-file only.",
569
+ "AgentTrace, simple messages, and Codex exec JSONL shapes are detected in v0.",
570
+ "Live Claude Code, Codex, Cursor, and provider adapters are future work."
571
+ ],
572
+ canHandle(input) {
573
+ return detectConcreteAdapter(input) !== null;
574
+ },
575
+ normalize(input, options) {
576
+ const detectedAdapter = detectConcreteAdapter(input);
577
+ if (!detectedAdapter) {
578
+ return {
579
+ status: "fail",
580
+ trace: null,
581
+ warnings: [],
582
+ failures: ["Unknown source input does not resemble AgentTrace, simple messages JSON, or Codex exec JSONL."],
583
+ normalization_steps: ["Attempted to detect a supported local trace adapter for unknown source input."],
584
+ skipped_fields: [],
585
+ source_metadata: adapterSourceMetadata(this, {
586
+ detected_adapter_id: null,
587
+ detected_shape: "unsupported"
588
+ })
589
+ };
590
+ }
591
+ const result = detectedAdapter.normalize(input, options);
592
+ return {
593
+ ...result,
594
+ normalization_steps: [`Detected ${detectedAdapter.id} input while source was unknown.`, ...result.normalization_steps],
595
+ source_metadata: {
596
+ ...result.source_metadata,
597
+ adapter_id: this.id,
598
+ adapter_display_name: this.displayName,
599
+ delegated_adapter_id: detectedAdapter.id,
600
+ requested_source: "unknown"
601
+ }
602
+ };
603
+ }
604
+ };
605
+ export const traceAdapters = [agentTraceAdapter, messagesAdapter, codexExecJsonlAdapter, unknownAdapter];
606
+ function parseJsonlRawContent(rawContent) {
607
+ const events = [];
608
+ const failures = [];
609
+ const lines = rawContent.split(/\r?\n/);
610
+ lines.forEach((line, index) => {
611
+ if (line.trim().length === 0) {
612
+ return;
613
+ }
614
+ try {
615
+ const parsed = JSON.parse(line);
616
+ if (!isRecord(parsed)) {
617
+ failures.push(`JSONL line ${index + 1} is not a JSON object.`);
618
+ return;
619
+ }
620
+ events.push({ rawLineNumber: index + 1, event: parsed });
621
+ }
622
+ catch (error) {
623
+ failures.push(error instanceof Error ? `JSONL line ${index + 1} is not valid JSON: ${error.message}` : `JSONL line ${index + 1} is not valid JSON.`);
624
+ }
625
+ });
626
+ if (events.length === 0 && failures.length === 0) {
627
+ failures.push("Codex exec JSONL input did not contain any non-empty JSONL events.");
628
+ }
629
+ return {
630
+ input: failures.length > 0 ? null : events,
631
+ failures,
632
+ normalization_steps: ["Parsed local Codex exec JSONL content line by line without mutating the source file."],
633
+ source_metadata: {
634
+ parsed_format: "jsonl",
635
+ non_empty_line_count: events.length + failures.length,
636
+ parsed_event_count: events.length
637
+ }
638
+ };
639
+ }
640
+ export function parseTraceAdapterRawContent(rawContent, source) {
641
+ if (source === "codex-exec-jsonl") {
642
+ return parseJsonlRawContent(rawContent);
643
+ }
644
+ try {
645
+ return {
646
+ input: JSON.parse(rawContent),
647
+ failures: [],
648
+ normalization_steps: ["Parsed local JSON content without mutating the source file."],
649
+ source_metadata: { parsed_format: "json" }
650
+ };
651
+ }
652
+ catch (error) {
653
+ const jsonFailure = error instanceof Error ? `Input is not valid JSON: ${error.message}` : "Input is not valid JSON.";
654
+ if (source === "unknown") {
655
+ const jsonlResult = parseJsonlRawContent(rawContent);
656
+ if (jsonlResult.failures.length === 0 && jsonlResult.input !== null && looksLikeCodexExecJsonl(jsonlResult.input)) {
657
+ return jsonlResult;
658
+ }
659
+ }
660
+ return {
661
+ input: null,
662
+ failures: [jsonFailure],
663
+ normalization_steps: ["Read the local file without mutating it."],
664
+ source_metadata: { parsed_format: "json" }
665
+ };
666
+ }
667
+ }
668
+ export function listTraceAdapters() {
669
+ return [...traceAdapters];
670
+ }
671
+ export function listTraceAdapterSources() {
672
+ return [...supportedSources];
673
+ }
674
+ export function assertTraceAdapterSource(source) {
675
+ if (!supportedSources.includes(source)) {
676
+ throw new Error(`Unsupported source "${source}". Supported sources: ${supportedSources.join(", ")}.`);
677
+ }
678
+ }
679
+ export function getTraceAdapter(source) {
680
+ const adapter = traceAdapters.find((candidate) => candidate.id === source);
681
+ if (!adapter) {
682
+ throw new Error(`No trace adapter registered for source "${source}".`);
683
+ }
684
+ return adapter;
685
+ }
686
+ export function detectTraceAdapter(input, requestedSource = "unknown") {
687
+ const adapter = requestedSource === "unknown" ? detectConcreteAdapter(input) : getTraceAdapter(requestedSource);
688
+ if (!adapter) {
689
+ return {
690
+ status: "fail",
691
+ requested_source: requestedSource,
692
+ adapter_id: null,
693
+ supported_format_detected: false,
694
+ warnings: [],
695
+ failures: ["No registered trace adapter can handle this input shape."],
696
+ normalization_steps: ["Attempted to detect a supported local trace adapter."],
697
+ skipped_fields: [],
698
+ source_metadata: { requested_source: requestedSource, local_file_normalizer: true, live_provider_integration: false }
699
+ };
700
+ }
701
+ const supported = requestedSource === "unknown" ? adapter.canHandle(input) : getTraceAdapter(requestedSource).canHandle(input);
702
+ return {
703
+ status: supported ? "pass" : "fail",
704
+ requested_source: requestedSource,
705
+ adapter_id: adapter.id,
706
+ supported_format_detected: supported,
707
+ warnings: [],
708
+ failures: supported ? [] : [`Input does not match the ${adapter.id} adapter shape.`],
709
+ normalization_steps: [`Selected ${adapter.id} trace adapter for local normalization.`],
710
+ skipped_fields: [],
711
+ source_metadata: adapterSourceMetadata(adapter, { requested_source: requestedSource })
712
+ };
713
+ }
714
+ //# sourceMappingURL=trace-adapters.js.map