@roberttlange/agentlens 0.2.2 → 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 (48) hide show
  1. package/dist/browser.js +154 -20
  2. package/dist/browser.js.map +1 -1
  3. package/dist/main.test.js +138 -1
  4. package/dist/main.test.js.map +1 -1
  5. package/node_modules/@agentlens/contracts/dist/index.d.ts +120 -0
  6. package/node_modules/@agentlens/core/dist/__tests__/config.test.js +67 -2
  7. package/node_modules/@agentlens/core/dist/__tests__/config.test.js.map +1 -1
  8. package/node_modules/@agentlens/core/dist/__tests__/index.test.js +590 -2
  9. package/node_modules/@agentlens/core/dist/__tests__/index.test.js.map +1 -1
  10. package/node_modules/@agentlens/core/dist/config.js +95 -5
  11. package/node_modules/@agentlens/core/dist/config.js.map +1 -1
  12. package/node_modules/@agentlens/core/dist/generatedPricing.d.ts +3 -0
  13. package/node_modules/@agentlens/core/dist/generatedPricing.js +131 -0
  14. package/node_modules/@agentlens/core/dist/generatedPricing.js.map +1 -0
  15. package/node_modules/@agentlens/core/dist/metrics.d.ts +13 -0
  16. package/node_modules/@agentlens/core/dist/metrics.js +227 -54
  17. package/node_modules/@agentlens/core/dist/metrics.js.map +1 -1
  18. package/node_modules/@agentlens/core/dist/pricing.d.ts +15 -0
  19. package/node_modules/@agentlens/core/dist/pricing.js +133 -0
  20. package/node_modules/@agentlens/core/dist/pricing.js.map +1 -0
  21. package/node_modules/@agentlens/core/dist/pricing.test.d.ts +1 -0
  22. package/node_modules/@agentlens/core/dist/pricing.test.js +109 -0
  23. package/node_modules/@agentlens/core/dist/pricing.test.js.map +1 -0
  24. package/node_modules/@agentlens/core/dist/sourceProfiles.js +7 -67
  25. package/node_modules/@agentlens/core/dist/sourceProfiles.js.map +1 -1
  26. package/node_modules/@agentlens/core/dist/traceIndex.d.ts +34 -1
  27. package/node_modules/@agentlens/core/dist/traceIndex.js +374 -15
  28. package/node_modules/@agentlens/core/dist/traceIndex.js.map +1 -1
  29. package/node_modules/@agentlens/server/dist/activity-cache.d.ts +32 -0
  30. package/node_modules/@agentlens/server/dist/activity-cache.js +63 -0
  31. package/node_modules/@agentlens/server/dist/activity-cache.js.map +1 -0
  32. package/node_modules/@agentlens/server/dist/activity-cache.test.d.ts +1 -0
  33. package/node_modules/@agentlens/server/dist/activity-cache.test.js +170 -0
  34. package/node_modules/@agentlens/server/dist/activity-cache.test.js.map +1 -0
  35. package/node_modules/@agentlens/server/dist/activity.d.ts +31 -1
  36. package/node_modules/@agentlens/server/dist/activity.js +532 -34
  37. package/node_modules/@agentlens/server/dist/activity.js.map +1 -1
  38. package/node_modules/@agentlens/server/dist/app.d.ts +4 -2
  39. package/node_modules/@agentlens/server/dist/app.js +248 -5
  40. package/node_modules/@agentlens/server/dist/app.js.map +1 -1
  41. package/node_modules/@agentlens/server/dist/app.test.js +670 -9
  42. package/node_modules/@agentlens/server/dist/app.test.js.map +1 -1
  43. package/node_modules/@agentlens/server/dist/web/assets/index-CTFOBaBt.css +1 -0
  44. package/node_modules/@agentlens/server/dist/web/assets/index-CVf00w06.js +52 -0
  45. package/node_modules/@agentlens/server/dist/web/index.html +2 -2
  46. package/package.json +1 -1
  47. package/node_modules/@agentlens/server/dist/web/assets/index-Ci8okH8M.js +0 -52
  48. package/node_modules/@agentlens/server/dist/web/assets/index-Cj3kmsFf.css +0 -1
@@ -1,12 +1,49 @@
1
- import { appendFile, mkdtemp, mkdir, writeFile } from "node:fs/promises";
1
+ import { appendFile, mkdtemp, mkdir, utimes, writeFile } from "node:fs/promises";
2
2
  import os from "node:os";
3
3
  import path from "node:path";
4
- import { describe, expect, it } from "vitest";
4
+ import { describe, expect, it, vi } from "vitest";
5
5
  import { mergeConfig } from "../config.js";
6
6
  import { TraceIndex } from "../traceIndex.js";
7
7
  async function createTempRoot() {
8
8
  return mkdtemp(path.join(os.tmpdir(), "agentlens-core-"));
9
9
  }
10
+ function buildCodexTraceLog(sessionId, sequence) {
11
+ const firstTs = new Date(Date.UTC(2026, 1, 11, 10, 0, sequence)).toISOString();
12
+ const secondTs = new Date(Date.UTC(2026, 1, 11, 10, 0, sequence + 1)).toISOString();
13
+ return [
14
+ JSON.stringify({
15
+ timestamp: firstTs,
16
+ type: "session_meta",
17
+ payload: { id: sessionId, cwd: "/tmp/project", cli_version: "0.1.0" },
18
+ }),
19
+ JSON.stringify({
20
+ timestamp: secondTs,
21
+ type: "response_item",
22
+ payload: {
23
+ type: "message",
24
+ role: "assistant",
25
+ content: [{ type: "output_text", text: `trace ${sequence}` }],
26
+ },
27
+ }),
28
+ ].join("\n");
29
+ }
30
+ async function waitForCondition(predicate, options = {}) {
31
+ const timeoutMs = options.timeoutMs ?? 5_000;
32
+ const intervalMs = options.intervalMs ?? 25;
33
+ const deadline = Date.now() + timeoutMs;
34
+ for (;;) {
35
+ try {
36
+ predicate();
37
+ return;
38
+ }
39
+ catch (error) {
40
+ if (Date.now() >= deadline) {
41
+ throw error;
42
+ }
43
+ await new Promise((resolve) => setTimeout(resolve, intervalMs));
44
+ }
45
+ }
46
+ }
10
47
  describe("trace index", () => {
11
48
  it("defaults sessionLogDirectories to common agent homes with explicit log types", () => {
12
49
  const config = mergeConfig();
@@ -48,6 +85,113 @@ describe("trace index", () => {
48
85
  { directory: "~/.pi", logType: "pi" },
49
86
  ]);
50
87
  });
88
+ it("arms the watcher before bootstrap hydration during start", async () => {
89
+ const baseConfig = mergeConfig({ sessionLogDirectories: [] });
90
+ const config = {
91
+ ...baseConfig,
92
+ sessionLogDirectories: [],
93
+ sources: Object.fromEntries(Object.entries(baseConfig.sources).map(([key, value]) => [
94
+ key,
95
+ {
96
+ ...value,
97
+ enabled: false,
98
+ roots: [],
99
+ },
100
+ ])),
101
+ };
102
+ const index = new TraceIndex(config);
103
+ const internal = index;
104
+ const calls = [];
105
+ const bootstrapSpy = vi.spyOn(internal, "bootstrapRecentTraces").mockImplementation(async () => {
106
+ calls.push("bootstrap");
107
+ });
108
+ const restartWatcherSpy = vi.spyOn(internal, "restartWatcher").mockImplementation(async () => {
109
+ calls.push("watcher");
110
+ });
111
+ try {
112
+ await index.start();
113
+ expect(calls).toEqual(["watcher", "bootstrap"]);
114
+ }
115
+ finally {
116
+ bootstrapSpy.mockRestore();
117
+ restartWatcherSpy.mockRestore();
118
+ index.stop();
119
+ }
120
+ });
121
+ it("becomes inspector-ready before full hydration finishes during start", async () => {
122
+ const root = await createTempRoot();
123
+ const codexDir = path.join(root, ".codex", "sessions", "2026", "02", "11");
124
+ await mkdir(codexDir, { recursive: true });
125
+ for (let traceIndex = 0; traceIndex < 130; traceIndex += 1) {
126
+ const tracePath = path.join(codexDir, `bootstrap-${String(traceIndex).padStart(3, "0")}.jsonl`);
127
+ await writeFile(tracePath, buildCodexTraceLog(`bootstrap-${traceIndex}`, traceIndex), "utf8");
128
+ const mtime = new Date(Date.UTC(2026, 1, 11, 10, 0, 0) + (130 - traceIndex) * 1_000);
129
+ await utimes(tracePath, mtime, mtime);
130
+ }
131
+ const config = mergeConfig({
132
+ sessionLogDirectories: [],
133
+ sources: {
134
+ codex_home: {
135
+ name: "codex_home",
136
+ enabled: true,
137
+ roots: [path.join(root, ".codex", "sessions")],
138
+ includeGlobs: ["**/*.jsonl"],
139
+ excludeGlobs: [],
140
+ maxDepth: 8,
141
+ agentHint: "codex",
142
+ },
143
+ claude_projects: {
144
+ name: "claude_projects",
145
+ enabled: false,
146
+ roots: [],
147
+ includeGlobs: ["**/*.jsonl"],
148
+ excludeGlobs: [],
149
+ maxDepth: 8,
150
+ agentHint: "claude",
151
+ },
152
+ claude_history: {
153
+ name: "claude_history",
154
+ enabled: false,
155
+ roots: [],
156
+ includeGlobs: ["history.jsonl"],
157
+ excludeGlobs: [],
158
+ maxDepth: 8,
159
+ agentHint: "claude",
160
+ },
161
+ },
162
+ });
163
+ const index = new TraceIndex(config);
164
+ try {
165
+ await index.start();
166
+ const startup = index.getStartupState();
167
+ expect(startup.inspectorReady).toBe(true);
168
+ expect(startup.fullReady).toBe(false);
169
+ expect(startup.isPartial).toBe(true);
170
+ expect(startup.discoveredTraceCount).toBe(130);
171
+ expect(startup.hydratedTraceCount).toBeLessThan(130);
172
+ expect(index.getSummaries()).toHaveLength(startup.hydratedTraceCount);
173
+ const allProgress = index.getHydrationProgress(0);
174
+ expect(allProgress).toEqual({
175
+ ready: false,
176
+ relevantDiscoveredCount: 130,
177
+ relevantHydratedCount: startup.hydratedTraceCount,
178
+ percent: Math.round((startup.hydratedTraceCount / 130) * 100),
179
+ });
180
+ const newestHydratedMtime = Math.min(...index.getSummaries().map((summary) => summary.mtimeMs));
181
+ const newestWindowProgress = index.getHydrationProgress(newestHydratedMtime);
182
+ expect(newestWindowProgress.ready).toBe(true);
183
+ await waitForCondition(() => {
184
+ const ready = index.getStartupState();
185
+ expect(ready.fullReady).toBe(true);
186
+ expect(ready.isPartial).toBe(false);
187
+ expect(ready.hydratedTraceCount).toBe(130);
188
+ });
189
+ expect(index.getSummaries()).toHaveLength(130);
190
+ }
191
+ finally {
192
+ index.stop();
193
+ }
194
+ });
51
195
  it("parses codex home session files and computes overview", async () => {
52
196
  const root = await createTempRoot();
53
197
  const codexDir = path.join(root, ".codex", "sessions", "2026", "02", "11");
@@ -1713,6 +1857,135 @@ describe("trace index", () => {
1713
1857
  expect(summary?.costEstimateUsd).not.toBeNull();
1714
1858
  expect((summary?.costEstimateUsd ?? 0) > 0).toBe(true);
1715
1859
  });
1860
+ it("derives timestamped codex usage points from token_count rows", async () => {
1861
+ const root = await createTempRoot();
1862
+ const codexDir = path.join(root, ".codex", "sessions", "2026", "02", "13");
1863
+ await mkdir(codexDir, { recursive: true });
1864
+ const codexPath = path.join(codexDir, "rollout-usage-points.jsonl");
1865
+ await writeFile(codexPath, [
1866
+ JSON.stringify({
1867
+ timestamp: "2026-02-13T23:59:00.000Z",
1868
+ type: "session_meta",
1869
+ payload: { id: "sess-usage-points", cwd: "/tmp/project", cli_version: "0.1.0" },
1870
+ }),
1871
+ JSON.stringify({
1872
+ timestamp: "2026-02-13T23:59:01.000Z",
1873
+ type: "turn_context",
1874
+ payload: { model: "gpt-5.3-codex" },
1875
+ }),
1876
+ JSON.stringify({
1877
+ timestamp: "2026-02-13T23:59:02.000Z",
1878
+ type: "event_msg",
1879
+ payload: {
1880
+ type: "token_count",
1881
+ info: {
1882
+ total_token_usage: {
1883
+ input_tokens: 100,
1884
+ cached_input_tokens: 20,
1885
+ output_tokens: 10,
1886
+ reasoning_output_tokens: 0,
1887
+ total_tokens: 130,
1888
+ },
1889
+ last_token_usage: {
1890
+ input_tokens: 100,
1891
+ cached_input_tokens: 20,
1892
+ output_tokens: 10,
1893
+ reasoning_output_tokens: 0,
1894
+ total_tokens: 130,
1895
+ },
1896
+ },
1897
+ },
1898
+ }),
1899
+ JSON.stringify({
1900
+ timestamp: "2026-02-14T00:00:01.000Z",
1901
+ type: "event_msg",
1902
+ payload: {
1903
+ type: "token_count",
1904
+ info: {
1905
+ total_token_usage: {
1906
+ input_tokens: 140,
1907
+ cached_input_tokens: 30,
1908
+ output_tokens: 40,
1909
+ reasoning_output_tokens: 5,
1910
+ total_tokens: 215,
1911
+ },
1912
+ last_token_usage: {
1913
+ input_tokens: 40,
1914
+ cached_input_tokens: 10,
1915
+ output_tokens: 30,
1916
+ reasoning_output_tokens: 5,
1917
+ total_tokens: 85,
1918
+ },
1919
+ },
1920
+ },
1921
+ }),
1922
+ ].join("\n"), "utf8");
1923
+ const config = mergeConfig({
1924
+ sessionLogDirectories: [],
1925
+ cost: {
1926
+ enabled: true,
1927
+ currency: "USD",
1928
+ unknownModelPolicy: "n_a",
1929
+ modelRates: [
1930
+ {
1931
+ model: "gpt-5.3-codex",
1932
+ inputPer1MUsd: 1,
1933
+ outputPer1MUsd: 1,
1934
+ cachedReadPer1MUsd: 1,
1935
+ cachedCreatePer1MUsd: 1,
1936
+ reasoningOutputPer1MUsd: 1,
1937
+ },
1938
+ ],
1939
+ },
1940
+ sources: {
1941
+ codex_home: {
1942
+ name: "codex_home",
1943
+ enabled: true,
1944
+ roots: [path.join(root, ".codex", "sessions")],
1945
+ includeGlobs: ["**/*.jsonl"],
1946
+ excludeGlobs: [],
1947
+ maxDepth: 8,
1948
+ agentHint: "codex",
1949
+ },
1950
+ claude_projects: {
1951
+ name: "claude_projects",
1952
+ enabled: false,
1953
+ roots: [],
1954
+ includeGlobs: ["**/*.jsonl"],
1955
+ excludeGlobs: [],
1956
+ maxDepth: 8,
1957
+ agentHint: "claude",
1958
+ },
1959
+ claude_history: {
1960
+ name: "claude_history",
1961
+ enabled: false,
1962
+ roots: [],
1963
+ includeGlobs: ["history.jsonl"],
1964
+ excludeGlobs: [],
1965
+ maxDepth: 8,
1966
+ agentHint: "claude",
1967
+ },
1968
+ },
1969
+ });
1970
+ const index = new TraceIndex(config);
1971
+ await index.refresh();
1972
+ const summary = index.getSummaries()[0];
1973
+ const usageArtifacts = index.getSessionUsageArtifacts(summary.id);
1974
+ expect(usageArtifacts.usagePoints).toHaveLength(2);
1975
+ expect(usageArtifacts.usagePoints[0]).toMatchObject({
1976
+ timestampMs: Date.UTC(2026, 1, 13, 23, 59, 2),
1977
+ inputTokens: 100,
1978
+ cachedReadTokens: 20,
1979
+ outputTokens: 10,
1980
+ });
1981
+ expect(usageArtifacts.usagePoints[1]).toMatchObject({
1982
+ timestampMs: Date.UTC(2026, 1, 14, 0, 0, 1),
1983
+ inputTokens: 40,
1984
+ cachedReadTokens: 10,
1985
+ outputTokens: 30,
1986
+ reasoningOutputTokens: 5,
1987
+ });
1988
+ });
1716
1989
  it("does not double bill codex cached input tokens in cost estimate", async () => {
1717
1990
  const root = await createTempRoot();
1718
1991
  const codexDir = path.join(root, ".codex", "sessions", "2026", "02", "13");
@@ -1806,6 +2079,132 @@ describe("trace index", () => {
1806
2079
  const summary = index.getSummaries()[0];
1807
2080
  expect(summary?.costEstimateUsd).toBe(0.0001);
1808
2081
  });
2082
+ it("applies GPT-5.4 long-context pricing per codex token-count row", async () => {
2083
+ const root = await createTempRoot();
2084
+ const codexDir = path.join(root, ".codex", "sessions", "2026", "02", "13");
2085
+ await mkdir(codexDir, { recursive: true });
2086
+ const codexPath = path.join(codexDir, "rollout-gpt54-tiered.jsonl");
2087
+ await writeFile(codexPath, [
2088
+ JSON.stringify({
2089
+ timestamp: "2026-02-13T12:00:00.000Z",
2090
+ type: "session_meta",
2091
+ payload: { id: "sess-gpt54-tiered", cwd: "/tmp/project", cli_version: "0.1.0" },
2092
+ }),
2093
+ JSON.stringify({
2094
+ timestamp: "2026-02-13T12:00:01.000Z",
2095
+ type: "turn_context",
2096
+ payload: { model: "gpt-5.4-2026-02-28" },
2097
+ }),
2098
+ JSON.stringify({
2099
+ timestamp: "2026-02-13T12:00:02.000Z",
2100
+ type: "event_msg",
2101
+ payload: {
2102
+ type: "token_count",
2103
+ info: {
2104
+ total_token_usage: {
2105
+ input_tokens: 100_000,
2106
+ cached_input_tokens: 10_000,
2107
+ output_tokens: 10_000,
2108
+ reasoning_output_tokens: 0,
2109
+ total_tokens: 120_000,
2110
+ },
2111
+ last_token_usage: {
2112
+ input_tokens: 100_000,
2113
+ cached_input_tokens: 10_000,
2114
+ output_tokens: 10_000,
2115
+ reasoning_output_tokens: 0,
2116
+ total_tokens: 120_000,
2117
+ },
2118
+ model_context_window: 1_050_000,
2119
+ },
2120
+ },
2121
+ }),
2122
+ JSON.stringify({
2123
+ timestamp: "2026-02-13T12:00:03.000Z",
2124
+ type: "event_msg",
2125
+ payload: {
2126
+ type: "token_count",
2127
+ info: {
2128
+ total_token_usage: {
2129
+ input_tokens: 400_000,
2130
+ cached_input_tokens: 30_000,
2131
+ output_tokens: 20_000,
2132
+ reasoning_output_tokens: 0,
2133
+ total_tokens: 450_000,
2134
+ },
2135
+ last_token_usage: {
2136
+ input_tokens: 300_000,
2137
+ cached_input_tokens: 20_000,
2138
+ output_tokens: 10_000,
2139
+ reasoning_output_tokens: 0,
2140
+ total_tokens: 330_000,
2141
+ },
2142
+ model_context_window: 1_050_000,
2143
+ },
2144
+ },
2145
+ }),
2146
+ ].join("\n"), "utf8");
2147
+ const config = mergeConfig({
2148
+ sessionLogDirectories: [],
2149
+ cost: {
2150
+ enabled: true,
2151
+ currency: "USD",
2152
+ unknownModelPolicy: "n_a",
2153
+ modelRates: [
2154
+ {
2155
+ model: "gpt-5.4",
2156
+ inputPer1MUsd: 1.25,
2157
+ outputPer1MUsd: 7.5,
2158
+ cachedReadPer1MUsd: 0.125,
2159
+ cachedCreatePer1MUsd: 0,
2160
+ reasoningOutputPer1MUsd: 0,
2161
+ longContextThresholdTokens: 272_000,
2162
+ longContextInputPer1MUsd: 2.5,
2163
+ longContextOutputPer1MUsd: 11.25,
2164
+ contextWindowTokens: 1_050_000,
2165
+ },
2166
+ ],
2167
+ },
2168
+ models: {
2169
+ defaultContextWindowTokens: 200_000,
2170
+ contextWindows: [{ model: "gpt-5.4", contextWindowTokens: 1_050_000 }],
2171
+ },
2172
+ sources: {
2173
+ codex_home: {
2174
+ name: "codex_home",
2175
+ enabled: true,
2176
+ roots: [path.join(root, ".codex", "sessions")],
2177
+ includeGlobs: ["**/*.jsonl"],
2178
+ excludeGlobs: [],
2179
+ maxDepth: 8,
2180
+ agentHint: "codex",
2181
+ },
2182
+ claude_projects: {
2183
+ name: "claude_projects",
2184
+ enabled: false,
2185
+ roots: [],
2186
+ includeGlobs: ["**/*.jsonl"],
2187
+ excludeGlobs: [],
2188
+ maxDepth: 8,
2189
+ agentHint: "claude",
2190
+ },
2191
+ claude_history: {
2192
+ name: "claude_history",
2193
+ enabled: false,
2194
+ roots: [],
2195
+ includeGlobs: ["history.jsonl"],
2196
+ excludeGlobs: [],
2197
+ maxDepth: 8,
2198
+ agentHint: "claude",
2199
+ },
2200
+ },
2201
+ });
2202
+ const index = new TraceIndex(config);
2203
+ await index.refresh();
2204
+ const summary = index.getSummaries()[0];
2205
+ expect(summary?.contextWindowPct).toBeCloseTo(30.47619, 5);
2206
+ expect(summary?.costEstimateUsd).toBe(1.06625);
2207
+ });
1809
2208
  it("deduplicates repeated claude usage rows by request id for cost estimation", async () => {
1810
2209
  const root = await createTempRoot();
1811
2210
  const claudeDir = path.join(root, ".claude", "projects", "proj");
@@ -1925,6 +2324,100 @@ describe("trace index", () => {
1925
2324
  const summary = index.getSummaries()[0];
1926
2325
  expect(summary?.costEstimateUsd).toBe(0.001065);
1927
2326
  });
2327
+ it("applies Claude long-context pricing and alias normalization from assistant usage rows", async () => {
2328
+ const root = await createTempRoot();
2329
+ const claudeDir = path.join(root, ".claude", "projects", "proj");
2330
+ await mkdir(claudeDir, { recursive: true });
2331
+ const claudePath = path.join(claudeDir, "session-cost-tiered.jsonl");
2332
+ await writeFile(claudePath, [
2333
+ JSON.stringify({
2334
+ type: "assistant",
2335
+ sessionId: "claude-tiered-sess",
2336
+ uuid: "u1",
2337
+ requestId: "req_tiered_1",
2338
+ message: {
2339
+ model: "claude-sonnet-4-6-20260219",
2340
+ id: "msg_1",
2341
+ type: "message",
2342
+ role: "assistant",
2343
+ content: [{ type: "text", text: "tiered" }],
2344
+ usage: {
2345
+ input_tokens: 100_000,
2346
+ cache_creation_input_tokens: 10_000,
2347
+ cache_read_input_tokens: 120_000,
2348
+ cache_creation: { ephemeral_5m_input_tokens: 5_000, ephemeral_1h_input_tokens: 5_000 },
2349
+ output_tokens: 50_000,
2350
+ },
2351
+ },
2352
+ }),
2353
+ ].join("\n"), "utf8");
2354
+ const config = mergeConfig({
2355
+ sessionLogDirectories: [],
2356
+ cost: {
2357
+ enabled: true,
2358
+ currency: "USD",
2359
+ unknownModelPolicy: "n_a",
2360
+ modelRates: [
2361
+ {
2362
+ model: "claude-sonnet-4.6",
2363
+ inputPer1MUsd: 3,
2364
+ outputPer1MUsd: 15,
2365
+ cachedReadPer1MUsd: 0.3,
2366
+ cachedCreatePer1MUsd: 3.75,
2367
+ cachedCreate5mPer1MUsd: 3.75,
2368
+ cachedCreate1hPer1MUsd: 6,
2369
+ reasoningOutputPer1MUsd: 0,
2370
+ longContextThresholdTokens: 200_000,
2371
+ longContextInputPer1MUsd: 6,
2372
+ longContextOutputPer1MUsd: 22.5,
2373
+ longContextCachedReadPer1MUsd: 0.6,
2374
+ longContextCachedCreatePer1MUsd: 7.5,
2375
+ longContextCachedCreate5mPer1MUsd: 7.5,
2376
+ longContextCachedCreate1hPer1MUsd: 12,
2377
+ contextWindowTokens: 1_000_000,
2378
+ },
2379
+ ],
2380
+ },
2381
+ models: {
2382
+ defaultContextWindowTokens: 200_000,
2383
+ contextWindows: [{ model: "claude-sonnet-4.6", contextWindowTokens: 1_000_000 }],
2384
+ },
2385
+ sources: {
2386
+ claude_projects: {
2387
+ name: "claude_projects",
2388
+ enabled: true,
2389
+ roots: [path.join(root, ".claude", "projects")],
2390
+ includeGlobs: ["**/*.jsonl"],
2391
+ excludeGlobs: [],
2392
+ maxDepth: 8,
2393
+ agentHint: "claude",
2394
+ },
2395
+ codex_home: {
2396
+ name: "codex_home",
2397
+ enabled: false,
2398
+ roots: [],
2399
+ includeGlobs: ["**/*.jsonl"],
2400
+ excludeGlobs: [],
2401
+ maxDepth: 8,
2402
+ agentHint: "codex",
2403
+ },
2404
+ claude_history: {
2405
+ name: "claude_history",
2406
+ enabled: false,
2407
+ roots: [],
2408
+ includeGlobs: ["history.jsonl"],
2409
+ excludeGlobs: [],
2410
+ maxDepth: 8,
2411
+ agentHint: "claude",
2412
+ },
2413
+ },
2414
+ });
2415
+ const index = new TraceIndex(config);
2416
+ await index.refresh();
2417
+ const summary = index.getSummaries()[0];
2418
+ expect(summary?.contextWindowPct).toBe(23);
2419
+ expect(summary?.costEstimateUsd).toBe(1.8945);
2420
+ });
1928
2421
  it("redacts secret-like values from event previews and raw payloads", async () => {
1929
2422
  const root = await createTempRoot();
1930
2423
  const codexDir = path.join(root, ".codex", "sessions", "2026", "02", "13");
@@ -2108,6 +2601,101 @@ describe("trace index", () => {
2108
2601
  index.stop();
2109
2602
  }
2110
2603
  });
2604
+ it("serves activity artifacts for cold traces without materializing full events", async () => {
2605
+ const root = await createTempRoot();
2606
+ const codexDir = path.join(root, ".codex", "sessions", "2026", "02", "13");
2607
+ await mkdir(codexDir, { recursive: true });
2608
+ for (const [index, filename] of ["rollout-a.jsonl", "rollout-b.jsonl", "rollout-c.jsonl"].entries()) {
2609
+ await writeFile(path.join(codexDir, filename), [
2610
+ JSON.stringify({
2611
+ timestamp: `2026-02-13T10:00:0${index}.000Z`,
2612
+ type: "session_meta",
2613
+ payload: { id: `sess-${index}`, cwd: "/tmp/project", cli_version: "0.1.0" },
2614
+ }),
2615
+ JSON.stringify({
2616
+ timestamp: `2026-02-13T10:00:1${index}.000Z`,
2617
+ type: "response_item",
2618
+ payload: {
2619
+ type: "message",
2620
+ role: "assistant",
2621
+ content: [{ type: "output_text", text: `trace-${index}` }],
2622
+ },
2623
+ }),
2624
+ ].join("\n") + "\n", "utf8");
2625
+ }
2626
+ const config = mergeConfig({
2627
+ retention: {
2628
+ strategy: "aggressive_recency",
2629
+ hotTraceCount: 1,
2630
+ warmTraceCount: 0,
2631
+ maxResidentEventsPerHotTrace: 1000,
2632
+ maxResidentEventsPerWarmTrace: 50,
2633
+ detailLoadMode: "lazy_from_disk",
2634
+ },
2635
+ sessionLogDirectories: [],
2636
+ sources: {
2637
+ codex_home: {
2638
+ name: "codex_home",
2639
+ enabled: true,
2640
+ roots: [path.join(root, ".codex", "sessions")],
2641
+ includeGlobs: ["**/*.jsonl"],
2642
+ excludeGlobs: [],
2643
+ maxDepth: 8,
2644
+ agentHint: "codex",
2645
+ },
2646
+ claude_projects: {
2647
+ name: "claude_projects",
2648
+ enabled: false,
2649
+ roots: [],
2650
+ includeGlobs: ["**/*.jsonl"],
2651
+ excludeGlobs: [],
2652
+ maxDepth: 8,
2653
+ agentHint: "claude",
2654
+ },
2655
+ claude_history: {
2656
+ name: "claude_history",
2657
+ enabled: false,
2658
+ roots: [],
2659
+ includeGlobs: ["history.jsonl"],
2660
+ excludeGlobs: [],
2661
+ maxDepth: 8,
2662
+ agentHint: "claude",
2663
+ },
2664
+ },
2665
+ });
2666
+ const index = new TraceIndex(config);
2667
+ await index.start();
2668
+ try {
2669
+ const summaries = index.getSummaries();
2670
+ const coldTraceId = summaries[2]?.id;
2671
+ expect(coldTraceId).toBeTruthy();
2672
+ if (!coldTraceId) {
2673
+ throw new Error("missing cold trace");
2674
+ }
2675
+ const internal = index;
2676
+ const entry = internal.entries.get(coldTraceId);
2677
+ expect(entry?.cachedFullEvents).toBeNull();
2678
+ expect(entry?.cachedRawEvents).toBeNull();
2679
+ expect(entry?.activityArtifacts).toMatchObject({
2680
+ eventCount: 2,
2681
+ eventTimestamps: [],
2682
+ });
2683
+ const parseFileSyncSpy = vi.spyOn(internal.parserRegistry, "parseFileSync");
2684
+ const artifacts = index.getSessionActivityArtifacts(coldTraceId);
2685
+ expect(artifacts.eventCount).toBe(2);
2686
+ expect(artifacts.eventTimestamps).toEqual([]);
2687
+ expect(entry?.cachedFullEvents).toBeNull();
2688
+ expect(entry?.cachedRawEvents).toBeNull();
2689
+ expect(entry?.activityArtifacts).toMatchObject({
2690
+ eventCount: 2,
2691
+ eventTimestamps: [],
2692
+ });
2693
+ expect(parseFileSyncSpy).not.toHaveBeenCalled();
2694
+ }
2695
+ finally {
2696
+ index.stop();
2697
+ }
2698
+ });
2111
2699
  it("keeps codex model and cost metrics after appends with strict redaction", async () => {
2112
2700
  const root = await createTempRoot();
2113
2701
  const codexDir = path.join(root, ".codex", "sessions", "2026", "02", "13");