Menu
popagent
publicLatest change a976edbfb97c44a0e4fbac194e5e94b27b53f1f8 - Create follow-up tasks autonomously from completed scheduled reports by AkurAI Build
import { describe, expect, test } from "bun:test";
import { createFollowUps, followUpPlanSchema, followUpTitleOf, MAX_FOLLOW_UPS } from "./follow-ups";
import type { AgentSchedule, AgentTask } from "./api-types";
const schedule: AgentSchedule = {
id: "s1", sessionId: null, workspaceId: "default", source: "user", name: "Daily report", prompt: "p",
model: "policy/routing", maxAttempts: 3, cron: "0 10 * * *", timezone: "UTC", enabled: true, followUps: true,
nextRunAt: null, lastRunAt: null, createdAt: "", updatedAt: "",
};
const task = { id: "t1", scheduleId: "s1", workspaceId: "default", output: "## Report\n- Reconcile the Project Index\n- Add mail telemetry" } as AgentTask;
describe("follow-up planning", () => {
test("creates deduplicated linked tasks from planned items and announces counts", async () => {
const created: Array<{ workspaceId: string; prompt: string; model: string; followUpOfTaskId: string }> = [];
const store = {
recentFollowUpPrompts: async () => ["Follow-up: Reconcile the project index\n\nolder task"],
createTask: async (input: typeof created[number]) => { created.push(input); return { id: `new-${created.length}`, prompt: input.prompt } as AgentTask; },
};
const result = await createFollowUps(task, schedule, store, async () => ({ items: [
{ title: "Reconcile the Project Index", prompt: "Update popagent/project-index.md to the deployed revision with revision protection.", rationale: "Two pages disagree.", severity: "medium" },
{ title: "Add AkurAI-Mail telemetry collector", prompt: "Design a dns_metrics.rs-style collector for AkurAI-Mail journald lines; no deploy.", rationale: "Zero mail telemetry.", severity: "high" },
] }));
expect(result.planned).toBe(2);
expect(result.skipped.map((item) => item.title)).toEqual(["Reconcile the Project Index"]);
expect(result.created).toHaveLength(1);
expect(created[0]).toMatchObject({ workspaceId: "default", model: "policy/routing", followUpOfTaskId: "t1" });
expect(created[0]!.prompt.startsWith("Follow-up: Add AkurAI-Mail telemetry collector")).toBe(true);
expect(created[0]!.prompt).toContain("do not deploy");
expect(followUpTitleOf(created[0]!.prompt)).toBe("add akurai mail telemetry collector");
});
test("does nothing when the schedule opted out, the run had no output, or the plan is empty", async () => {
let planned = 0;
const store = { recentFollowUpPrompts: async () => [], createTask: async () => { throw new Error("must not create"); } };
const plan = async () => { planned++; return { items: [] }; };
expect((await createFollowUps(task, { ...schedule, followUps: false }, store, plan)).planned).toBe(0);
expect((await createFollowUps({ ...task, output: " " }, schedule, store, plan)).planned).toBe(0);
expect(planned).toBe(0);
expect((await createFollowUps(task, schedule, store, plan)).created).toEqual([]);
expect(planned).toBe(1);
});
test("bounds the plan shape", () => {
expect(followUpPlanSchema.safeParse({ items: Array.from({ length: MAX_FOLLOW_UPS + 1 }, () => ({ title: "x".repeat(10), prompt: "y".repeat(30), rationale: "z".repeat(5), severity: "low" })) }).success).toBe(false);
expect(followUpPlanSchema.safeParse({ items: [{ title: "ok title", prompt: "y".repeat(30), rationale: "because", severity: "critical" }] }).success).toBe(false);
});
});