import { randomUUID } from "node:crypto"; import { afterAll, afterEach, beforeAll, describe, expect, it } from "vitest"; import { agents, companies, createDb, heartbeatRuns } from "@paperclipai/db"; import { getEmbeddedPostgresTestSupport, startEmbeddedPostgresTestDatabase, } from "./helpers/embedded-postgres.js"; import { heartbeatService } from "../services/heartbeat.ts"; const embeddedPostgresSupport = await getEmbeddedPostgresTestSupport(); const describeEmbeddedPostgres = embeddedPostgresSupport.supported ? describe : describe.skip; if (!embeddedPostgresSupport.supported) { console.warn( `Skipping embedded Postgres heartbeat list tests on this host: ${embeddedPostgresSupport.reason ?? "unsupported environment"}`, ); } describeEmbeddedPostgres("heartbeat list", () => { let db!: ReturnType; let tempDb: Awaited> | null = null; beforeAll(async () => { tempDb = await startEmbeddedPostgresTestDatabase("paperclip-heartbeat-list-"); db = createDb(tempDb.connectionString); }, 20_000); afterEach(async () => { await db.delete(heartbeatRuns); await db.delete(agents); await db.delete(companies); }); afterAll(async () => { await tempDb?.cleanup(); }); it("returns runs even when the linked db schema lacks processGroupId", async () => { const companyId = randomUUID(); const agentId = randomUUID(); const runId = randomUUID(); await db.insert(companies).values({ id: companyId, name: "Paperclip", issuePrefix: `T${companyId.replace(/-/g, "").slice(0, 6).toUpperCase()}`, requireBoardApprovalForNewAgents: false, }); await db.insert(agents).values({ id: agentId, companyId, name: "CodexCoder", role: "engineer", status: "running", adapterType: "codex_local", adapterConfig: {}, runtimeConfig: {}, permissions: {}, }); await db.insert(heartbeatRuns).values({ id: runId, companyId, agentId, invocationSource: "assignment", status: "running", livenessState: "advanced", livenessReason: "run produced action evidence", continuationAttempt: 1, lastUsefulActionAt: new Date("2026-04-18T12:00:00Z"), nextAction: "continue implementation", contextSnapshot: { issueId: randomUUID() }, }); const originalDescriptor = Object.getOwnPropertyDescriptor(heartbeatRuns, "processGroupId"); Object.defineProperty(heartbeatRuns, "processGroupId", { value: undefined, configurable: true, writable: true, }); try { const runs = await heartbeatService(db).list(companyId, agentId, 5); expect(runs).toHaveLength(1); expect(runs[0]?.id).toBe(runId); expect(runs[0]?.processGroupId ?? null).toBeNull(); expect(runs[0]).toMatchObject({ livenessState: "advanced", livenessReason: "run produced action evidence", continuationAttempt: 1, nextAction: "continue implementation", }); expect(runs[0]?.lastUsefulActionAt).toEqual(new Date("2026-04-18T12:00:00Z")); } finally { if (originalDescriptor) { Object.defineProperty(heartbeatRuns, "processGroupId", originalDescriptor); } else { delete (heartbeatRuns as Record).processGroupId; } } }); it("returns small result json payloads unchanged from getRun", async () => { const companyId = randomUUID(); const agentId = randomUUID(); const runId = randomUUID(); await db.insert(companies).values({ id: companyId, name: "Paperclip", issuePrefix: `T${companyId.replace(/-/g, "").slice(0, 6).toUpperCase()}`, requireBoardApprovalForNewAgents: false, }); await db.insert(agents).values({ id: agentId, companyId, name: "CodexCoder", role: "engineer", status: "running", adapterType: "codex_local", adapterConfig: {}, runtimeConfig: {}, permissions: {}, }); await db.insert(heartbeatRuns).values({ id: runId, companyId, agentId, invocationSource: "assignment", status: "succeeded", resultJson: { summary: "done", structured: { ok: true }, }, }); const run = await heartbeatService(db).getRun(runId); expect(run?.resultJson).toEqual({ summary: "done", structured: { ok: true }, }); }); it("bounds oversized legacy result json payloads on getRun", async () => { const companyId = randomUUID(); const agentId = randomUUID(); const runId = randomUUID(); const oversizedStdout = Array.from({ length: 8_000 }, (_, index) => `${index.toString(16).padStart(4, "0")}-${randomUUID()}`, ).join("|"); const oversizedNestedPayload = Array.from({ length: 6_000 }, (_, index) => `${index.toString(16).padStart(4, "0")}:${randomUUID()}`, ).join("|"); await db.insert(companies).values({ id: companyId, name: "Paperclip", issuePrefix: `T${companyId.replace(/-/g, "").slice(0, 6).toUpperCase()}`, requireBoardApprovalForNewAgents: false, }); await db.insert(agents).values({ id: agentId, companyId, name: "CodexCoder", role: "engineer", status: "running", adapterType: "codex_local", adapterConfig: {}, runtimeConfig: {}, permissions: {}, }); await db.insert(heartbeatRuns).values({ id: runId, companyId, agentId, invocationSource: "assignment", status: "succeeded", resultJson: { summary: "completed", stdout: oversizedStdout, nestedHuge: { payload: oversizedNestedPayload }, }, }); const run = await heartbeatService(db).getRun(runId); const result = run?.resultJson as Record | null; expect(result).toMatchObject({ summary: "completed", truncated: true, truncationReason: "oversized_result_json", stdoutTruncated: true, }); expect(typeof result?.stdout).toBe("string"); expect((result?.stdout as string).length).toBeLessThan(oversizedStdout.length); expect(result).not.toHaveProperty("nestedHuge"); }); });