Showing the changes to apps/server/src/usage/usageTranscripts.test.ts (1 of 3 files). Show all
Unified · Split
| @@ -134,6 +134,106 @@ describe("parseCodexLine", () => { · 133 unmodified lines | ||
| 134 | 134 | parseCodexLine(turnContext, state); |
| 135 | 135 | expect(parseCodexLine(tokenCount(100, 0, 10, 0), state)).not.toBeNull(); |
| 136 | 136 | }); |
| 137 | + | |
| 138 | + // A forked/subagent rollout opens with the parent's history copied in and | |
| 139 | + // every line re-stamped to the fork instant, then the ancestors' session | |
| 140 | + // metas. Counting those again multiplied usage ~1.85x on real data (#5758). | |
| 141 | + describe("forked rollouts", () => { | |
| 142 | + const meta = (overrides: { | |
| 143 | + id: string; | |
| 144 | + timestamp: string; | |
| 145 | + forkedFromId?: string; | |
| 146 | + spawnParentId?: string; | |
| 147 | + }) => | |
| 148 | + JSON.stringify({ | |
| 149 | + type: "session_meta", | |
| 150 | + timestamp: overrides.timestamp, | |
| 151 | + payload: { | |
| 152 | + type: "session_meta", | |
| 153 | + id: overrides.id, | |
| 154 | + ...(overrides.forkedFromId === undefined | |
| 155 | + ? {} | |
| 156 | + : { forked_from_id: overrides.forkedFromId }), | |
| 157 | + ...(overrides.spawnParentId === undefined | |
| 158 | + ? {} | |
| 159 | + : { | |
| 160 | + source: { | |
| 161 | + subagent: { thread_spawn: { parent_thread_id: overrides.spawnParentId } }, | |
| 162 | + }, | |
| 163 | + }), | |
| 164 | + }, | |
| 165 | + }); | |
| 166 | + const stamped = (timestamp: string, line: string) => { | |
| 167 | + const parsed = JSON.parse(line) as { timestamp: string }; | |
| 168 | + parsed.timestamp = timestamp; | |
| 169 | + return JSON.stringify(parsed); | |
| 170 | + }; | |
| 171 | + | |
| 172 | + it("keeps the child session id over copied ancestor metas", () => { | |
| 173 | + const state = initialCodexScanState(); | |
| 174 | + parseCodexLine(meta({ id: "child", timestamp: "2026-08-01T05:00:00.000Z" }), state); | |
| 175 | + parseCodexLine(meta({ id: "parent", timestamp: "2026-08-01T05:00:00.000Z" }), state); | |
| 176 | + parseCodexLine(turnContext, state); | |
| 177 | + const record = parseCodexLine(tokenCount(100, 0, 10, 0), state); | |
| 178 | + | |
| 179 | + expect(record?.sessionId).toBe("child"); | |
| 180 | + }); | |
| 181 | + | |
| 182 | + it("drops the re-stamped copied burst and keeps the first real event", () => { | |
| 183 | + const state = initialCodexScanState(); | |
| 184 | + const forkInstant = "2026-08-01T05:00:00.000Z"; | |
| 185 | + parseCodexLine(meta({ id: "child", timestamp: forkInstant, forkedFromId: "parent" }), state); | |
| 186 | + parseCodexLine(meta({ id: "parent", timestamp: forkInstant }), state); | |
| 187 | + parseCodexLine(stamped(forkInstant, turnContext), state); | |
| 188 | + | |
| 189 | + // Copied history: written in one burst at the fork instant. | |
| 190 | + expect( | |
| 191 | + parseCodexLine(stamped("2026-08-01T05:00:00.001Z", tokenCount(100, 0, 10, 0)), state), | |
| 192 | + ).toBeNull(); | |
| 193 | + expect( | |
| 194 | + parseCodexLine(stamped("2026-08-01T05:00:00.002Z", tokenCount(200, 0, 20, 0)), state), | |
| 195 | + ).toBeNull(); | |
| 196 | + | |
| 197 | + // The child's first genuine turn lands seconds later and must count. | |
| 198 | + const real = parseCodexLine( | |
| 199 | + stamped("2026-08-01T05:00:06.000Z", tokenCount(300, 0, 30, 0)), | |
| 200 | + state, | |
| 201 | + ); | |
| 202 | + expect(real).not.toBeNull(); | |
| 203 | + expect(real?.totals.outputTokens).toBe(30); | |
| 204 | + | |
| 205 | + // Suppression never restarts, even for closely spaced later events. | |
| 206 | + const next = parseCodexLine( | |
| 207 | + stamped("2026-08-01T05:00:06.100Z", tokenCount(400, 0, 40, 0)), | |
| 208 | + state, | |
| 209 | + ); | |
| 210 | + expect(next).not.toBeNull(); | |
| 211 | + }); | |
| 212 | + | |
| 213 | + it("recognizes subagent spawns without forked_from_id", () => { | |
| 214 | + const state = initialCodexScanState(); | |
| 215 | + const spawnInstant = "2026-08-01T05:00:00.000Z"; | |
| 216 | + parseCodexLine( | |
| 217 | + meta({ id: "child", timestamp: spawnInstant, spawnParentId: "parent" }), | |
| 218 | + state, | |
| 219 | + ); | |
| 220 | + parseCodexLine(stamped(spawnInstant, turnContext), state); | |
| 221 | + expect( | |
| 222 | + parseCodexLine(stamped("2026-08-01T05:00:00.001Z", tokenCount(100, 0, 10, 0)), state), | |
| 223 | + ).toBeNull(); | |
| 224 | + }); | |
| 225 | + | |
| 226 | + it("does not suppress anything in a rollout that is not a fork", () => { | |
| 227 | + const state = initialCodexScanState(); | |
| 228 | + parseCodexLine(meta({ id: "root", timestamp: "2026-08-01T05:00:00.000Z" }), state); | |
| 229 | + parseCodexLine(stamped("2026-08-01T05:00:00.100Z", turnContext), state); | |
| 230 | + const record = parseCodexLine( | |
| 231 | + stamped("2026-08-01T05:00:00.200Z", tokenCount(100, 0, 10, 0)), | |
| 232 | + state, | |
| 233 | + ); | |
| 234 | + expect(record).not.toBeNull(); | |
| 235 | + }); | |
| 236 | + }); | |
| 137 | 237 | }); |
| 138 | 238 | |
| 139 | 239 | describe("totalTokens", () => { |