import { ThreadId } from "@t3tools/contracts"; import { TurnId } from "@t3tools/contracts"; import { describe, expect, it } from "vite-plus/test"; import { canSnooze, effectiveSnoozed, threadRaisedHandWhileSnoozed, threadWokeAt, type ThreadSnoozeShell, } from "./threadSettled.ts"; const NOW = "2026-04-10T12:00:00.000Z"; const SNOOZED_AT = "2026-04-10T09:00:00.000Z"; const FUTURE_WAKE = "2026-04-11T09:00:00.000Z"; const PAST_WAKE = "2026-04-10T10:00:00.000Z"; function makeShell(input: { readonly snoozedUntil?: string | null; readonly snoozedAt?: string | null; readonly sessionStatus?: "starting" | "running" | "ready" | "error"; readonly pending?: "approval" | "user-input"; readonly turnCompletedAt?: string | null; }): ThreadSnoozeShell { const threadId = ThreadId.make("thread-1"); return { snoozedUntil: input.snoozedUntil ?? null, snoozedAt: input.snoozedAt ?? (input.snoozedUntil != null ? SNOOZED_AT : null), hasPendingApprovals: input.pending === "approval", hasPendingUserInput: input.pending === "user-input", session: input.sessionStatus === undefined ? null : { threadId, status: input.sessionStatus, providerName: "Codex", runtimeMode: "full-access", activeTurnId: null, lastError: input.sessionStatus === "error" ? "boom" : null, updatedAt: "2026-04-10T11:00:00.000Z", }, latestTurn: input.turnCompletedAt === undefined ? null : { turnId: TurnId.make("turn-1"), state: "completed", requestedAt: SNOOZED_AT, startedAt: null, completedAt: input.turnCompletedAt, assistantMessageId: null, }, }; } describe("effectiveSnoozed", () => { it("hides a thread whose wake time is in the future", () => { expect(effectiveSnoozed(makeShell({ snoozedUntil: FUTURE_WAKE }), { now: NOW })).toBe(true); }); it("stops classifying as snoozed once the wake time passes (timer wake, no event)", () => { expect(effectiveSnoozed(makeShell({ snoozedUntil: PAST_WAKE }), { now: NOW })).toBe(false); }); it("never snoozes a thread with no snooze state", () => { expect(effectiveSnoozed(makeShell({}), { now: NOW })).toBe(false); }); it("never hides on malformed wake data", () => { expect(effectiveSnoozed(makeShell({ snoozedUntil: "not-a-date" }), { now: NOW })).toBe(false); }); it("wakes early when the agent is blocked on the user", () => { expect( effectiveSnoozed(makeShell({ snoozedUntil: FUTURE_WAKE, pending: "approval" }), { now: NOW, }), ).toBe(false); expect( effectiveSnoozed(makeShell({ snoozedUntil: FUTURE_WAKE, pending: "user-input" }), { now: NOW, }), ).toBe(false); }); it("wakes early on a failure that happened after the snooze", () => { // makeShell stamps session.updatedAt at 11:00, after SNOOZED_AT (9:00). expect( effectiveSnoozed(makeShell({ snoozedUntil: FUTURE_WAKE, sessionStatus: "error" }), { now: NOW, }), ).toBe(false); }); it("stays snoozed when the failure predates the snooze — the user saw it", () => { expect( effectiveSnoozed( makeShell({ snoozedUntil: FUTURE_WAKE, sessionStatus: "error", // Snoozed AFTER the error's status edge. snoozedAt: "2026-04-10T11:30:00.000Z", }), { now: NOW }, ), ).toBe(true); }); it("stays snoozed while the session keeps working — snooze never pauses the agent", () => { expect( effectiveSnoozed(makeShell({ snoozedUntil: FUTURE_WAKE, sessionStatus: "running" }), { now: NOW, }), ).toBe(true); }); it("wakes early when a run completes after the snooze was set", () => { expect( effectiveSnoozed( makeShell({ snoozedUntil: FUTURE_WAKE, turnCompletedAt: "2026-04-10T10:30:00.000Z" }), { now: NOW }, ), ).toBe(false); }); it("ignores runs that completed before the snooze — the user saw that result", () => { expect( effectiveSnoozed( makeShell({ snoozedUntil: FUTURE_WAKE, turnCompletedAt: "2026-04-10T08:00:00.000Z" }), { now: NOW }, ), ).toBe(true); }); }); describe("threadRaisedHandWhileSnoozed", () => { it("is false for a quiet snoozed thread", () => { expect(threadRaisedHandWhileSnoozed(makeShell({ snoozedUntil: FUTURE_WAKE }))).toBe(false); }); it("is true for approvals, input, and failures", () => { expect( threadRaisedHandWhileSnoozed(makeShell({ snoozedUntil: FUTURE_WAKE, pending: "approval" })), ).toBe(true); expect( threadRaisedHandWhileSnoozed(makeShell({ snoozedUntil: FUTURE_WAKE, pending: "user-input" })), ).toBe(true); expect( threadRaisedHandWhileSnoozed( makeShell({ snoozedUntil: FUTURE_WAKE, sessionStatus: "error" }), ), ).toBe(true); }); }); describe("canSnooze", () => { it("allows snoozing quiet and working threads alike", () => { expect(canSnooze({ ...makeShell({}), latestUserMessageAt: null }, { now: NOW })).toBe(true); expect( canSnooze( { ...makeShell({ sessionStatus: "running" }), latestUserMessageAt: null }, { now: NOW }, ), ).toBe(true); }); it("refuses blocked-on-you work", () => { expect( canSnooze({ ...makeShell({ pending: "approval" }), latestUserMessageAt: null }, { now: NOW }), ).toBe(false); expect( canSnooze( { ...makeShell({ pending: "user-input" }), latestUserMessageAt: null }, { now: NOW }, ), ).toBe(false); }); it("refuses a queued turn start — same invisible-pending-work rule as settle", () => { // Fresh user message, no turn has adopted it, within the grace window. expect( canSnooze( { ...makeShell({}), latestUserMessageAt: "2026-04-10T11:59:30.000Z" }, { now: NOW }, ), ).toBe(false); // Outside the grace window the message is stale data, not queued work. expect( canSnooze( { ...makeShell({}), latestUserMessageAt: "2026-04-10T11:00:00.000Z" }, { now: NOW }, ), ).toBe(true); }); }); describe("threadWokeAt", () => { it("is null for never-snoozed and still-snoozed threads", () => { expect(threadWokeAt(makeShell({}), { now: NOW })).toBe(null); expect(threadWokeAt(makeShell({ snoozedUntil: FUTURE_WAKE }), { now: NOW })).toBe(null); }); it("reports the wake time for a timer wake", () => { expect(threadWokeAt(makeShell({ snoozedUntil: PAST_WAKE }), { now: NOW })).toBe(PAST_WAKE); }); it("reports the completion time for an early run-completed wake", () => { expect( threadWokeAt( makeShell({ snoozedUntil: FUTURE_WAKE, turnCompletedAt: "2026-04-10T10:30:00.000Z" }), { now: NOW }, ), ).toBe("2026-04-10T10:30:00.000Z"); }); it("falls back to session activity for blocked/failed early wakes", () => { expect( threadWokeAt(makeShell({ snoozedUntil: FUTURE_WAKE, sessionStatus: "error" }), { now: NOW, }), ).toBe("2026-04-10T11:00:00.000Z"); }); it("keeps the early wake authoritative after the scheduled time passes", () => { // Woke early at 10:30 via run-completed; the scheduled wake (PAST_WAKE // 10:00 relative to a later now) has ALSO passed. Reporting the // scheduled time would resurface a Woke pill the user already cleared // by visiting between the early wake and now. expect( threadWokeAt( makeShell({ snoozedUntil: PAST_WAKE, turnCompletedAt: "2026-04-10T09:30:00.000Z" }), { now: NOW }, ), ).toBe("2026-04-10T09:30:00.000Z"); }); });