- Updated isolated cron jobs to support new delivery modes: `announce` and `none`, improving output management. - Refactored job configuration to remove legacy fields and streamline delivery settings. - Enhanced the `CronJobEditor` UI to reflect changes in delivery options, including a new segmented control for delivery mode selection. - Updated documentation to clarify the new delivery configurations and their implications for job execution. - Improved tests to validate the new delivery behavior and ensure backward compatibility with legacy settings. This update provides users with greater flexibility in managing how isolated jobs deliver their outputs, enhancing overall usability and clarity in job configurations.
235 lines
7.4 KiB
TypeScript
235 lines
7.4 KiB
TypeScript
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
|
|
const callGatewayMock = vi.fn();
|
|
vi.mock("../../gateway/call.js", () => ({
|
|
callGateway: (opts: unknown) => callGatewayMock(opts),
|
|
}));
|
|
|
|
vi.mock("../agent-scope.js", () => ({
|
|
resolveSessionAgentId: () => "agent-123",
|
|
}));
|
|
|
|
import { createCronTool } from "./cron-tool.js";
|
|
|
|
describe("cron tool", () => {
|
|
beforeEach(() => {
|
|
callGatewayMock.mockReset();
|
|
callGatewayMock.mockResolvedValue({ ok: true });
|
|
});
|
|
|
|
it.each([
|
|
[
|
|
"update",
|
|
{ action: "update", jobId: "job-1", patch: { foo: "bar" } },
|
|
{ id: "job-1", patch: { foo: "bar" } },
|
|
],
|
|
[
|
|
"update",
|
|
{ action: "update", id: "job-2", patch: { foo: "bar" } },
|
|
{ id: "job-2", patch: { foo: "bar" } },
|
|
],
|
|
["remove", { action: "remove", jobId: "job-1" }, { id: "job-1" }],
|
|
["remove", { action: "remove", id: "job-2" }, { id: "job-2" }],
|
|
["run", { action: "run", jobId: "job-1" }, { id: "job-1" }],
|
|
["run", { action: "run", id: "job-2" }, { id: "job-2" }],
|
|
["runs", { action: "runs", jobId: "job-1" }, { id: "job-1" }],
|
|
["runs", { action: "runs", id: "job-2" }, { id: "job-2" }],
|
|
])("%s sends id to gateway", async (action, args, expectedParams) => {
|
|
const tool = createCronTool();
|
|
await tool.execute("call1", args);
|
|
|
|
expect(callGatewayMock).toHaveBeenCalledTimes(1);
|
|
const call = callGatewayMock.mock.calls[0]?.[0] as {
|
|
method?: string;
|
|
params?: unknown;
|
|
};
|
|
expect(call.method).toBe(`cron.${action}`);
|
|
expect(call.params).toEqual(expectedParams);
|
|
});
|
|
|
|
it("prefers jobId over id when both are provided", async () => {
|
|
const tool = createCronTool();
|
|
await tool.execute("call1", {
|
|
action: "run",
|
|
jobId: "job-primary",
|
|
id: "job-legacy",
|
|
});
|
|
|
|
const call = callGatewayMock.mock.calls[0]?.[0] as {
|
|
params?: unknown;
|
|
};
|
|
expect(call?.params).toEqual({ id: "job-primary" });
|
|
});
|
|
|
|
it("normalizes cron.add job payloads", async () => {
|
|
const tool = createCronTool();
|
|
await tool.execute("call2", {
|
|
action: "add",
|
|
job: {
|
|
data: {
|
|
name: "wake-up",
|
|
schedule: { atMs: 123 },
|
|
payload: { kind: "systemEvent", text: "hello" },
|
|
},
|
|
},
|
|
});
|
|
|
|
expect(callGatewayMock).toHaveBeenCalledTimes(1);
|
|
const call = callGatewayMock.mock.calls[0]?.[0] as {
|
|
method?: string;
|
|
params?: unknown;
|
|
};
|
|
expect(call.method).toBe("cron.add");
|
|
expect(call.params).toEqual({
|
|
name: "wake-up",
|
|
schedule: { kind: "at", at: new Date(123).toISOString() },
|
|
sessionTarget: "main",
|
|
wakeMode: "next-heartbeat",
|
|
payload: { kind: "systemEvent", text: "hello" },
|
|
});
|
|
});
|
|
|
|
it("does not default agentId when job.agentId is null", async () => {
|
|
const tool = createCronTool({ agentSessionKey: "main" });
|
|
await tool.execute("call-null", {
|
|
action: "add",
|
|
job: {
|
|
name: "wake-up",
|
|
schedule: { at: new Date(123).toISOString() },
|
|
agentId: null,
|
|
},
|
|
});
|
|
|
|
const call = callGatewayMock.mock.calls[0]?.[0] as {
|
|
params?: { agentId?: unknown };
|
|
};
|
|
expect(call?.params?.agentId).toBeNull();
|
|
});
|
|
|
|
it("adds recent context for systemEvent reminders when contextMessages > 0", async () => {
|
|
callGatewayMock
|
|
.mockResolvedValueOnce({
|
|
messages: [
|
|
{ role: "user", content: [{ type: "text", text: "Discussed Q2 budget" }] },
|
|
{
|
|
role: "assistant",
|
|
content: [{ type: "text", text: "We agreed to review on Tuesday." }],
|
|
},
|
|
{ role: "user", content: [{ type: "text", text: "Remind me about the thing at 2pm" }] },
|
|
],
|
|
})
|
|
.mockResolvedValueOnce({ ok: true });
|
|
|
|
const tool = createCronTool({ agentSessionKey: "main" });
|
|
await tool.execute("call3", {
|
|
action: "add",
|
|
contextMessages: 3,
|
|
job: {
|
|
name: "reminder",
|
|
schedule: { at: new Date(123).toISOString() },
|
|
payload: { kind: "systemEvent", text: "Reminder: the thing." },
|
|
},
|
|
});
|
|
|
|
expect(callGatewayMock).toHaveBeenCalledTimes(2);
|
|
const historyCall = callGatewayMock.mock.calls[0]?.[0] as {
|
|
method?: string;
|
|
params?: unknown;
|
|
};
|
|
expect(historyCall.method).toBe("chat.history");
|
|
|
|
const cronCall = callGatewayMock.mock.calls[1]?.[0] as {
|
|
method?: string;
|
|
params?: { payload?: { text?: string } };
|
|
};
|
|
expect(cronCall.method).toBe("cron.add");
|
|
const text = cronCall.params?.payload?.text ?? "";
|
|
expect(text).toContain("Recent context:");
|
|
expect(text).toContain("User: Discussed Q2 budget");
|
|
expect(text).toContain("Assistant: We agreed to review on Tuesday.");
|
|
expect(text).toContain("User: Remind me about the thing at 2pm");
|
|
});
|
|
|
|
it("caps contextMessages at 10", async () => {
|
|
const messages = Array.from({ length: 12 }, (_, idx) => ({
|
|
role: "user",
|
|
content: [{ type: "text", text: `Message ${idx + 1}` }],
|
|
}));
|
|
callGatewayMock.mockResolvedValueOnce({ messages }).mockResolvedValueOnce({ ok: true });
|
|
|
|
const tool = createCronTool({ agentSessionKey: "main" });
|
|
await tool.execute("call5", {
|
|
action: "add",
|
|
contextMessages: 20,
|
|
job: {
|
|
name: "reminder",
|
|
schedule: { at: new Date(123).toISOString() },
|
|
payload: { kind: "systemEvent", text: "Reminder: the thing." },
|
|
},
|
|
});
|
|
|
|
expect(callGatewayMock).toHaveBeenCalledTimes(2);
|
|
const historyCall = callGatewayMock.mock.calls[0]?.[0] as {
|
|
method?: string;
|
|
params?: { limit?: number };
|
|
};
|
|
expect(historyCall.method).toBe("chat.history");
|
|
expect(historyCall.params?.limit).toBe(10);
|
|
|
|
const cronCall = callGatewayMock.mock.calls[1]?.[0] as {
|
|
params?: { payload?: { text?: string } };
|
|
};
|
|
const text = cronCall.params?.payload?.text ?? "";
|
|
expect(text).not.toMatch(/Message 1\\b/);
|
|
expect(text).not.toMatch(/Message 2\\b/);
|
|
expect(text).toContain("Message 3");
|
|
expect(text).toContain("Message 12");
|
|
});
|
|
|
|
it("does not add context when contextMessages is 0 (default)", async () => {
|
|
callGatewayMock.mockResolvedValueOnce({ ok: true });
|
|
|
|
const tool = createCronTool({ agentSessionKey: "main" });
|
|
await tool.execute("call4", {
|
|
action: "add",
|
|
job: {
|
|
name: "reminder",
|
|
schedule: { at: new Date(123).toISOString() },
|
|
payload: { text: "Reminder: the thing." },
|
|
},
|
|
});
|
|
|
|
// Should only call cron.add, not chat.history
|
|
expect(callGatewayMock).toHaveBeenCalledTimes(1);
|
|
const cronCall = callGatewayMock.mock.calls[0]?.[0] as {
|
|
method?: string;
|
|
params?: { payload?: { text?: string } };
|
|
};
|
|
expect(cronCall.method).toBe("cron.add");
|
|
const text = cronCall.params?.payload?.text ?? "";
|
|
expect(text).not.toContain("Recent context:");
|
|
});
|
|
|
|
it("preserves explicit agentId null on add", async () => {
|
|
callGatewayMock.mockResolvedValueOnce({ ok: true });
|
|
|
|
const tool = createCronTool({ agentSessionKey: "main" });
|
|
await tool.execute("call6", {
|
|
action: "add",
|
|
job: {
|
|
name: "reminder",
|
|
schedule: { at: new Date(123).toISOString() },
|
|
agentId: null,
|
|
payload: { kind: "systemEvent", text: "Reminder: the thing." },
|
|
},
|
|
});
|
|
|
|
const call = callGatewayMock.mock.calls[0]?.[0] as {
|
|
method?: string;
|
|
params?: { agentId?: string | null };
|
|
};
|
|
expect(call.method).toBe("cron.add");
|
|
expect(call.params?.agentId).toBeNull();
|
|
});
|
|
});
|