import { describe, expect, it } from "vite-plus/test";import * as ThreadBackgroundLiveness from "./ThreadBackgroundLiveness.ts"; describe("ThreadBackgroundLiveness", () => { it("does not let status-free progress or metadata restart an idle task", () => { const liveness = ThreadBackgroundLiveness.make(); liveness.recordTaskLiveness({ threadId: "thread", taskId: "task", taskType: undefined, status: undefined, kind: "started", }); liveness.recordTaskLiveness({ threadId: "thread", taskId: "task", taskType: undefined, status: "idle", kind: "updated", }); liveness.recordTaskLiveness({ threadId: "thread", taskId: "task", taskType: undefined, status: undefined, kind: "progress", }); liveness.recordTaskLiveness({ threadId: "thread", taskId: "task", taskType: undefined, status: undefined, kind: "updated", }); expect(liveness.getThreadBackgroundLiveness("thread")).toBeNull(); liveness.recordTaskLiveness({ threadId: "thread", taskId: "completed-task", taskType: undefined, status: undefined, kind: "started", }); liveness.recordTaskLiveness({ threadId: "thread", taskId: "completed-task", taskType: undefined, status: "completed", kind: "completed", }); liveness.recordTaskLiveness({ threadId: "thread", taskId: "completed-task", taskType: undefined, status: undefined, kind: "updated", }); expect(liveness.getThreadBackgroundLiveness("thread")).toBeNull(); }); it("agents present as working; monitors as monitoring; agents win", () => { const liveness = ThreadBackgroundLiveness.make(); const threadId = "t-live-1"; liveness.recordTaskLiveness({ threadId, taskId: "m1", taskType: "local_bash", status: undefined, kind: "started", }); expect(liveness.getThreadBackgroundLiveness(threadId)).toBe("monitoring"); liveness.recordTaskLiveness({ threadId, taskId: "a1", taskType: "subagent", status: undefined, kind: "started", }); expect(liveness.getThreadBackgroundLiveness(threadId)).toBe("working"); liveness.recordTaskLiveness({ threadId, taskId: "a1", taskType: "subagent", status: "completed", kind: "completed", }); expect(liveness.getThreadBackgroundLiveness(threadId)).toBe("monitoring"); liveness.recordTaskLiveness({ threadId, taskId: "m1", taskType: "local_bash", status: "completed", kind: "completed", }); expect(liveness.getThreadBackgroundLiveness(threadId)).toBeNull(); }); it("terminal rows without a taskType still clear monitor entries", () => { const liveness = ThreadBackgroundLiveness.make(); const threadId = "t-live-2"; liveness.recordTaskLiveness({ threadId, taskId: "m1", taskType: "local_bash", status: undefined, kind: "started", }); // Terminal tick arrives with no taskType (common on task.completed). liveness.recordTaskLiveness({ threadId, taskId: "m1", taskType: undefined, status: "completed", kind: "completed", }); expect(liveness.getThreadBackgroundLiveness(threadId)).toBeNull(); }); it("nested agents (agentId + agent taskType) still count toward liveness", () => { const liveness = ThreadBackgroundLiveness.make(); const threadId = "t-live-nested"; liveness.recordTaskLiveness({ threadId, taskId: "n1", taskType: "local_agent", status: undefined, kind: "started", agentId: "owner", }); expect(liveness.getThreadBackgroundLiveness(threadId)).toBe("working"); liveness.recordTaskLiveness({ threadId, taskId: "n1", taskType: "local_agent", status: "completed", kind: "completed", agentId: "owner", }); expect(liveness.getThreadBackgroundLiveness(threadId)).toBeNull(); }); it("untyped rows count as agents; idle is not live; agent-owned tasks are ignored", () => { const liveness = ThreadBackgroundLiveness.make(); const threadId = "t-live-3"; liveness.recordTaskLiveness({ threadId, taskId: "wf:1", taskType: undefined, status: "running", kind: "progress", }); expect(liveness.getThreadBackgroundLiveness(threadId)).toBe("working"); liveness.recordTaskLiveness({ threadId, taskId: "wf:1", taskType: undefined, status: "idle", kind: "updated", }); expect(liveness.getThreadBackgroundLiveness(threadId)).toBeNull(); liveness.recordTaskLiveness({ threadId, taskId: "sh:1", taskType: "local_bash", status: undefined, kind: "started", agentId: "owner", }); expect(liveness.getThreadBackgroundLiveness(threadId)).toBeNull(); }); it("reclassification moves a task between buckets instead of duplicating it", () => { const liveness = ThreadBackgroundLiveness.make(); const threadId = "t-live-reclass"; // First seen without a taskType: counts as an agent. liveness.recordTaskLiveness({ threadId, taskId: "x1", taskType: undefined, status: "running", kind: "started", }); expect(liveness.getThreadBackgroundLiveness(threadId)).toBe("working"); // Later transition reveals it's a shell: downgrade to monitoring, not // a stale duplicate pinning "working". liveness.recordTaskLiveness({ threadId, taskId: "x1", taskType: "local_bash", status: "running", kind: "progress", }); expect(liveness.getThreadBackgroundLiveness(threadId)).toBe("monitoring"); // Turning out to be inert or agent-owned drops the prior entry too. liveness.recordTaskLiveness({ threadId, taskId: "x1", taskType: "local_bash", status: "running", kind: "progress", agentId: "owner", }); expect(liveness.getThreadBackgroundLiveness(threadId)).toBeNull(); }); it("plan tasks are inert; clear removes everything; instances are isolated", () => { const a = ThreadBackgroundLiveness.make(); const b = ThreadBackgroundLiveness.make(); a.recordTaskLiveness({ threadId: "t", taskId: "p1", taskType: "plan", status: undefined, kind: "started", }); expect(a.getThreadBackgroundLiveness("t")).toBeNull(); a.recordTaskLiveness({ threadId: "t", taskId: "a1", taskType: "local_workflow", status: undefined, kind: "started", }); expect(a.getThreadBackgroundLiveness("t")).toBe("working"); expect(b.getThreadBackgroundLiveness("t")).toBeNull(); a.clearThreadLiveness("t"); expect(a.getThreadBackgroundLiveness("t")).toBeNull(); });});
import { describe, expect, it } from "vite-plus/test";
import * as ThreadBackgroundLiveness from "./ThreadBackgroundLiveness.ts";
describe("ThreadBackgroundLiveness", () => {
it("does not let status-free progress or metadata restart an idle task", () => {
const liveness = ThreadBackgroundLiveness.make();
liveness.recordTaskLiveness({
threadId: "thread",
taskId: "task",
taskType: undefined,
status: undefined,
kind: "started",
});
liveness.recordTaskLiveness({
threadId: "thread",
taskId: "task",
taskType: undefined,
status: "idle",
kind: "updated",
});
liveness.recordTaskLiveness({
threadId: "thread",
taskId: "task",
taskType: undefined,
status: undefined,
kind: "progress",
});
liveness.recordTaskLiveness({
threadId: "thread",
taskId: "task",
taskType: undefined,
status: undefined,
kind: "updated",
});
expect(liveness.getThreadBackgroundLiveness("thread")).toBeNull();
liveness.recordTaskLiveness({
threadId: "thread",
taskId: "completed-task",
taskType: undefined,
status: undefined,
kind: "started",
});
liveness.recordTaskLiveness({
threadId: "thread",
taskId: "completed-task",
taskType: undefined,
status: "completed",
kind: "completed",
});
liveness.recordTaskLiveness({
threadId: "thread",
taskId: "completed-task",
taskType: undefined,
status: undefined,
kind: "updated",
});
expect(liveness.getThreadBackgroundLiveness("thread")).toBeNull();
});
it("agents present as working; monitors as monitoring; agents win", () => {
const liveness = ThreadBackgroundLiveness.make();
const threadId = "t-live-1";
liveness.recordTaskLiveness({
threadId,
taskId: "m1",
taskType: "local_bash",
status: undefined,
kind: "started",
});
expect(liveness.getThreadBackgroundLiveness(threadId)).toBe("monitoring");
liveness.recordTaskLiveness({
threadId,
taskId: "a1",
taskType: "subagent",
status: undefined,
kind: "started",
});
expect(liveness.getThreadBackgroundLiveness(threadId)).toBe("working");
liveness.recordTaskLiveness({
threadId,
taskId: "a1",
taskType: "subagent",
status: "completed",
kind: "completed",
});
expect(liveness.getThreadBackgroundLiveness(threadId)).toBe("monitoring");
liveness.recordTaskLiveness({
threadId,
taskId: "m1",
taskType: "local_bash",
status: "completed",
kind: "completed",
});
expect(liveness.getThreadBackgroundLiveness(threadId)).toBeNull();
});
it("terminal rows without a taskType still clear monitor entries", () => {
const liveness = ThreadBackgroundLiveness.make();
const threadId = "t-live-2";
liveness.recordTaskLiveness({
threadId,
taskId: "m1",
taskType: "local_bash",
status: undefined,
kind: "started",
});
// Terminal tick arrives with no taskType (common on task.completed).
liveness.recordTaskLiveness({
threadId,
taskId: "m1",
taskType: undefined,
status: "completed",
kind: "completed",
});
expect(liveness.getThreadBackgroundLiveness(threadId)).toBeNull();
});
it("nested agents (agentId + agent taskType) still count toward liveness", () => {
const liveness = ThreadBackgroundLiveness.make();
const threadId = "t-live-nested";
liveness.recordTaskLiveness({
threadId,
taskId: "n1",
taskType: "local_agent",
status: undefined,
kind: "started",
agentId: "owner",
});
expect(liveness.getThreadBackgroundLiveness(threadId)).toBe("working");
liveness.recordTaskLiveness({
threadId,
taskId: "n1",
taskType: "local_agent",
status: "completed",
kind: "completed",
agentId: "owner",
});
expect(liveness.getThreadBackgroundLiveness(threadId)).toBeNull();
});
it("untyped rows count as agents; idle is not live; agent-owned tasks are ignored", () => {
const liveness = ThreadBackgroundLiveness.make();
const threadId = "t-live-3";
liveness.recordTaskLiveness({
threadId,
taskId: "wf:1",
taskType: undefined,
status: "running",
kind: "progress",
});
expect(liveness.getThreadBackgroundLiveness(threadId)).toBe("working");
liveness.recordTaskLiveness({
threadId,
taskId: "wf:1",
taskType: undefined,
status: "idle",
kind: "updated",
});
expect(liveness.getThreadBackgroundLiveness(threadId)).toBeNull();
liveness.recordTaskLiveness({
threadId,
taskId: "sh:1",
taskType: "local_bash",
status: undefined,
kind: "started",
agentId: "owner",
});
expect(liveness.getThreadBackgroundLiveness(threadId)).toBeNull();
});
it("reclassification moves a task between buckets instead of duplicating it", () => {
const liveness = ThreadBackgroundLiveness.make();
const threadId = "t-live-reclass";
// First seen without a taskType: counts as an agent.
liveness.recordTaskLiveness({
threadId,
taskId: "x1",
taskType: undefined,
status: "running",
kind: "started",
});
expect(liveness.getThreadBackgroundLiveness(threadId)).toBe("working");
// Later transition reveals it's a shell: downgrade to monitoring, not
// a stale duplicate pinning "working".
liveness.recordTaskLiveness({
threadId,
taskId: "x1",
taskType: "local_bash",
status: "running",
kind: "progress",
});
expect(liveness.getThreadBackgroundLiveness(threadId)).toBe("monitoring");
// Turning out to be inert or agent-owned drops the prior entry too.
liveness.recordTaskLiveness({
threadId,
taskId: "x1",
taskType: "local_bash",
status: "running",
kind: "progress",
agentId: "owner",
});
expect(liveness.getThreadBackgroundLiveness(threadId)).toBeNull();
});
it("plan tasks are inert; clear removes everything; instances are isolated", () => {
const a = ThreadBackgroundLiveness.make();
const b = ThreadBackgroundLiveness.make();
a.recordTaskLiveness({
threadId: "t",
taskId: "p1",
taskType: "plan",
status: undefined,
kind: "started",
});
expect(a.getThreadBackgroundLiveness("t")).toBeNull();
a.recordTaskLiveness({
threadId: "t",
taskId: "a1",
taskType: "local_workflow",
status: undefined,
kind: "started",
});
expect(a.getThreadBackgroundLiveness("t")).toBe("working");
expect(b.getThreadBackgroundLiveness("t")).toBeNull();
a.clearThreadLiveness("t");
expect(a.getThreadBackgroundLiveness("t")).toBeNull();
});
});