1- import { describe , expect , it } from "bun:test" ;
1+ import { afterEach , describe , expect , it , mock , spyOn } from "bun:test" ;
22import { readFileSync } from "node:fs" ;
33import { join } from "node:path" ;
44import {
5+ getChannel2NudgeClaimedAt ,
56 getChannel2NudgeState ,
67 setChannel2NudgeState ,
78} from "@magic-context/core/features/magic-context/storage" ;
9+ import * as loggerModule from "@magic-context/core/shared/logger" ;
810import {
911 clearPiChannel1State ,
1012 computeTailTokenEstimatePi ,
@@ -19,6 +21,10 @@ function toolResultMsg(text: string) {
1921 return { role : "toolResult" , content : [ { type : "text" , text } ] } ;
2022}
2123
24+ afterEach ( ( ) => {
25+ mock . restore ( ) ;
26+ } ) ;
27+
2228describe ( "computeTailToolTokensPi" , ( ) => {
2329 it ( "sums non-dropped toolResult text, excludes sentinels" , ( ) => {
2430 const big = "x" . repeat ( 40_000 ) ; // ~10k tokens
@@ -262,6 +268,90 @@ describe("maybeDeliverChannel2Pi", () => {
262268 expect ( getChannel2NudgeState ( db , SESSION ) ) . toBe ( "pending" ) ;
263269 } ) ;
264270
271+ it ( "returns false and leaves the claim healable when claimed→pending CAS throws" , async ( ) => {
272+ const db = createTestDb ( ) ;
273+ const session = "ses-ch2-pi-revert-throw" ;
274+ setChannel2NudgeState ( db , session , "pending" ) ;
275+ armStrongBaseline ( session ) ;
276+
277+ const originalPrepare = db . prepare . bind ( db ) ;
278+ ( db as unknown as { prepare : typeof db . prepare } ) . prepare = (
279+ sql : string ,
280+ ) => {
281+ const statement = originalPrepare ( sql ) ;
282+ if (
283+ sql ===
284+ "UPDATE session_meta SET channel2_nudge_state = ?, channel2_nudge_claimed_at = ? WHERE session_id = ? AND channel2_nudge_state = ?"
285+ ) {
286+ return {
287+ ...statement ,
288+ run : ( ...args : unknown [ ] ) => {
289+ if (
290+ args [ 0 ] === "pending" &&
291+ args [ 1 ] === 0 &&
292+ args [ 2 ] === session &&
293+ args [ 3 ] === "claimed"
294+ ) {
295+ throw new Error ( "SQLITE_BUSY: database is locked" ) ;
296+ }
297+ return statement . run (
298+ ...( args as [ unknown , unknown , unknown , unknown ] ) ,
299+ ) ;
300+ } ,
301+ } as typeof statement ;
302+ }
303+ return statement ;
304+ } ;
305+
306+ const delivered = maybeDeliverChannel2Pi (
307+ {
308+ sendUserMessage : ( ) => {
309+ throw new Error ( "transient" ) ;
310+ } ,
311+ } ,
312+ db ,
313+ session ,
314+ ) ;
315+
316+ expect ( delivered ) . toBe ( false ) ;
317+ expect ( getChannel2NudgeState ( db , session ) ) . toBe ( "claimed" ) ;
318+ expect ( getChannel2NudgeClaimedAt ( db , session ) ) . toBeGreaterThan ( 0 ) ;
319+ } ) ;
320+
321+ it ( "preserves a sibling's delivered claim and logs the duplicate window distinctly" , async ( ) => {
322+ const db = createTestDb ( ) ;
323+ const session = "ses-ch2-pi-duplicate" ;
324+ setChannel2NudgeState ( db , session , "pending" ) ;
325+ armStrongBaseline ( session ) ;
326+
327+ const sessionLog = spyOn ( loggerModule , "sessionLog" ) . mockImplementation (
328+ ( ) => { } ,
329+ ) ;
330+
331+ const delivered = maybeDeliverChannel2Pi (
332+ {
333+ sendUserMessage : ( ) => {
334+ db . prepare (
335+ "UPDATE session_meta SET channel2_nudge_state = 'delivered', channel2_nudge_claimed_at = 0 WHERE session_id = ?" ,
336+ ) . run ( session ) ;
337+ } ,
338+ } ,
339+ db ,
340+ session ,
341+ ) ;
342+
343+ expect ( delivered ) . toBe ( false ) ;
344+ expect ( getChannel2NudgeState ( db , session ) ) . toBe ( "delivered" ) ;
345+ expect (
346+ sessionLog . mock . calls . some (
347+ ( call ) =>
348+ call [ 0 ] === session &&
349+ typeof call [ 1 ] === "string" &&
350+ call [ 1 ] . includes ( "duplicate window" ) ,
351+ ) ,
352+ ) . toBe ( true ) ;
353+ } ) ;
354+
265355 it ( "does not re-deliver after success (one nudge per lifetime)" , ( ) => {
266356 const db = createTestDb ( ) ;
267357 setChannel2NudgeState ( db , SESSION , "delivered" ) ;
0 commit comments