105 lines · 3.7 KB · TypeScript
RawDownload
import { describe, expect, it } from "@effect/vitest";import { EnvironmentId, ThreadId } from "@t3tools/contracts";import type { RelayAgentActivityState } from "@t3tools/contracts/relay";import { makeAggregateState } from "./agentActivityAggregate.ts";import {  attentionTransitionRows,  terminalTransitionRows,  shouldAlertForActivity,} from "./agentActivityAlerts.ts"; const state: RelayAgentActivityState = {  environmentId: EnvironmentId.make("env"),  threadId: ThreadId.make("thread"),  projectTitle: "Project",  threadTitle: "Thread",  modelTitle: "Model",  headline: "Working",  phase: "running",  updatedAt: "1970-01-01T00:00:00.000Z",  deepLink: "/threads/env/thread",};const preferences = {  notificationsEnabled: true,  liveActivitiesEnabled: true,  notifyOnApproval: true,  notifyOnInput: true,  notifyOnCompletion: true,  notifyOnFailure: true,};const aggregate = (states: RelayAgentActivityState[]) =>  makeAggregateState({ activeStates: states, terminalState: null, nowMs: 0 })!; describe("shared agent activity policy", () => {  it.each(["waiting_for_approval", "waiting_for_input"] as const)(    "keeps an older %s ahead of five running rows",    (phase) => {      const running = Array.from({ length: 5 }, (_, i) => ({        ...state,        threadId: ThreadId.make(`running-${i}`),      }));      const waiting = { ...state, phase, updatedAt: "1969-12-31T23:59:00.000Z" };      const next = aggregate([...running, waiting]);      expect(next.activities[0]?.threadId).toBe(state.threadId);      expect(next.activeCount).toBe(6);      expect(next.activities).toHaveLength(5);      expect(        attentionTransitionRows({          previousAggregate: aggregate(running),          nextAggregate: next,          preferences,        }),      ).toMatchObject([{ threadId: state.threadId }]);    },  );   it("allows fresh unobserved completions for events, but not reconciliation or repeats", () => {    const previousAggregate = aggregate([      { ...state, threadId: ThreadId.make("old"), phase: "completed" },    ]);    const nextAggregate = aggregate([{ ...state, phase: "completed" }]);    const input = { previousAggregate, nextAggregate, preferences, nowMs: 0 };    expect(terminalTransitionRows(input)).toEqual([]);    expect(terminalTransitionRows({ ...input, includeUnobserved: true })).toHaveLength(1);    expect(      terminalTransitionRows({        ...input,        includeUnobserved: true,        previousAggregate: nextAggregate,      }),    ).toEqual([]);    expect(terminalTransitionRows({ ...input, includeUnobserved: true, nowMs: 180_000 })).toEqual(      [],    );  });   it("distinguishes equal thread IDs across environments", () => {    const waiting = { ...state, phase: "waiting_for_input" as const };    const other = { ...waiting, environmentId: EnvironmentId.make("other") };    expect(      attentionTransitionRows({        previousAggregate: aggregate([waiting]),        nextAggregate: aggregate([waiting, other]),        preferences,      }),    ).toMatchObject([{ environmentId: other.environmentId }]);  });   it("checks current permission, event preferences, and freshness together", () => {    const input = { ...state, phase: "completed" as const, preferences, nowMs: 0 };    expect(shouldAlertForActivity(input)).toBe(true);    expect(      shouldAlertForActivity({        ...input,        preferences: { ...preferences, notificationsEnabled: false },      }),    ).toBe(false);    expect(      shouldAlertForActivity({        ...input,        preferences: { ...preferences, notifyOnCompletion: false },      }),    ).toBe(false);    expect(shouldAlertForActivity({ ...input, nowMs: 180_000 })).toBe(false);  });});