196 lines · 6.7 KB · TypeScript
RawDownload
import { describe, expect, it } from "vite-plus/test";import {  ProviderInstanceId,  ThreadId,  ProjectId,  TurnId,  type OrchestrationThreadShell,} from "@t3tools/contracts";import { resolveAutoSettlementAt } from "./ThreadSettlementPolicy.ts"; const NOW = "2026-08-28T12:00:00.000Z";const makeThread = (  overrides: Partial<OrchestrationThreadShell> = {},): OrchestrationThreadShell => ({  id: ThreadId.make("thread-1"),  projectId: ProjectId.make("project-1"),  title: "Thread",  modelSelection: { instanceId: ProviderInstanceId.make("codex"), model: "gpt-5" },  runtimeMode: "full-access",  interactionMode: "default",  branch: "feature",  worktreePath: "/repo",  latestTurn: null,  createdAt: "2026-08-01T00:00:00.000Z",  updatedAt: "2026-08-20T00:00:00.000Z",  archivedAt: null,  settledOverride: null,  settledAt: null,  session: null,  latestUserMessageAt: "2026-08-20T00:00:00.000Z",  hasPendingApprovals: false,  hasPendingUserInput: false,  hasActionableProposedPlan: false,  ...overrides,}); const decide = (  thread: OrchestrationThreadShell,  pullRequest: { state: "open" | "closed" | "merged"; updatedAt: string | null } | null = null,  settings: { days?: number | null; merge?: boolean } = {},) =>  resolveAutoSettlementAt({    thread,    pullRequest,    now: NOW,    autoSettleAfterDays: settings.days === undefined ? 3 : settings.days,    autoSettleOnMerge: settings.merge ?? true,  }) !== null; describe("resolveAutoSettlementAt", () => {  it("returns the last activity time for persisted settlement", () => {    expect(      resolveAutoSettlementAt({        thread: makeThread({          latestTurn: {            turnId: TurnId.make("turn-terminal"),            state: "completed",            requestedAt: "2026-08-19T00:00:00.000Z",            startedAt: "2026-08-19T00:01:00.000Z",            completedAt: "2026-08-21T00:00:00.000Z",            assistantMessageId: null,          },        }),        pullRequest: null,        now: NOW,        autoSettleAfterDays: 3,        autoSettleOnMerge: true,      }),    ).toBe("2026-08-21T00:00:00.000Z");  });   it("uses creation time for PR settlement when the thread has no activity", () => {    expect(      resolveAutoSettlementAt({        thread: makeThread({          latestUserMessageAt: null,          latestTurn: null,          updatedAt: "2026-08-27T00:00:00.000Z",        }),        pullRequest: { state: "closed", updatedAt: NOW },        now: NOW,        autoSettleAfterDays: null,        autoSettleOnMerge: true,      }),    ).toBe("2026-08-01T00:00:00.000Z");  });   it("settles inactive threads and leaves never-used threads active", () => {    expect(decide(makeThread())).toBe(true);    expect(decide(makeThread({ latestUserMessageAt: null }))).toBe(false);    expect(decide(makeThread(), null, { days: null })).toBe(false);  });   it("keeps a thread active at the exact inactivity boundary", () => {    expect(decide(makeThread({ latestUserMessageAt: "2026-08-25T12:00:00.000Z" }))).toBe(false);  });   it("settles inactive threads with open pull requests", () => {    expect(decide(makeThread(), { state: "open", updatedAt: NOW })).toBe(true);  });   it("settles closed requests and honors the merge setting", () => {    expect(decide(makeThread(), { state: "closed", updatedAt: NOW }, { merge: false })).toBe(true);    expect(decide(makeThread(), { state: "merged", updatedAt: NOW }, { merge: false })).toBe(true);    expect(      decide(makeThread(), { state: "merged", updatedAt: NOW }, { merge: false, days: null }),    ).toBe(false);  });   it("does not settle again after user activity newer than the PR", () => {    expect(      decide(        makeThread({ latestUserMessageAt: "2026-08-27T00:00:00.000Z" }),        { state: "merged", updatedAt: "2026-08-26T00:00:00.000Z" },        { days: null },      ),    ).toBe(false);  });   it("does not inherit a terminal pull request older than the thread", () => {    expect(      decide(        makeThread({ createdAt: "2026-08-20T00:00:00.000Z", latestUserMessageAt: null }),        { state: "closed", updatedAt: "2026-08-19T00:00:00.000Z" },        { days: null },      ),    ).toBe(false);  });   it("requires a comparable PR timestamp for immediate settlement", () => {    const recentThread = makeThread({ latestUserMessageAt: "2026-08-27T00:00:00.000Z" });    expect(decide(recentThread, { state: "closed", updatedAt: null })).toBe(false);    expect(decide(recentThread, { state: "merged", updatedAt: "unknown" })).toBe(false);    expect(decide(makeThread(), { state: "closed", updatedAt: null })).toBe(true);  });   it("uses user request time instead of completion time as the PR anchor", () => {    const thread = makeThread({      latestTurn: {        turnId: TurnId.make("turn-1"),        state: "completed",        requestedAt: "2026-08-25T00:00:00.000Z",        startedAt: "2026-08-25T00:01:00.000Z",        completedAt: "2026-08-27T00:00:00.000Z",        assistantMessageId: null,      },    });    expect(decide(thread, { state: "merged", updatedAt: "2026-08-26T00:00:00.000Z" })).toBe(true);  });   it("blocks pins, snooze, pending work, live sessions, and queued starts", () => {    expect(decide(makeThread({ settledOverride: "active" }))).toBe(false);    expect(decide(makeThread({ snoozedUntil: "2026-08-29T00:00:00.000Z" }))).toBe(false);    expect(decide(makeThread({ hasPendingApprovals: true }))).toBe(false);    expect(decide(makeThread({ hasPendingUserInput: true }))).toBe(false);    expect(decide(makeThread({ backgroundLiveness: "working" }))).toBe(false);    expect(decide(makeThread({ backgroundLiveness: "monitoring" }))).toBe(false);    expect(      decide(        makeThread({          session: {            threadId: ThreadId.make("thread-1"),            status: "running",            providerName: "codex",            runtimeMode: "full-access",            activeTurnId: TurnId.make("turn-1"),            lastError: null,            updatedAt: NOW,          },        }),      ),    ).toBe(false);    expect(      decide(makeThread({ latestUserMessageAt: "2026-08-28T11:59:00.000Z", latestTurn: null })),    ).toBe(false);  });   it("allows a fresh completion to wake snooze before settlement", () => {    expect(      decide(        makeThread({          snoozedAt: "2026-08-19T00:00:00.000Z",          snoozedUntil: "2026-08-29T00:00:00.000Z",          latestTurn: {            turnId: TurnId.make("turn-woke"),            state: "completed",            requestedAt: "2026-08-18T00:00:00.000Z",            startedAt: "2026-08-18T00:01:00.000Z",            completedAt: "2026-08-20T00:00:00.000Z",            assistantMessageId: null,          },        }),      ),    ).toBe(true);  });});