fix: refactor cron edit payload patches
Co-authored-by: Felix Krause <869950+KrauseFx@users.noreply.github.com>
This commit is contained in:
@@ -52,6 +52,30 @@ export const CronPayloadSchema = Type.Union([
|
||||
),
|
||||
]);
|
||||
|
||||
export const CronPayloadPatchSchema = Type.Union([
|
||||
Type.Object(
|
||||
{
|
||||
kind: Type.Literal("systemEvent"),
|
||||
text: Type.Optional(NonEmptyString),
|
||||
},
|
||||
{ additionalProperties: false },
|
||||
),
|
||||
Type.Object(
|
||||
{
|
||||
kind: Type.Literal("agentTurn"),
|
||||
message: Type.Optional(NonEmptyString),
|
||||
model: Type.Optional(Type.String()),
|
||||
thinking: Type.Optional(Type.String()),
|
||||
timeoutSeconds: Type.Optional(Type.Integer({ minimum: 1 })),
|
||||
deliver: Type.Optional(Type.Boolean()),
|
||||
channel: Type.Optional(Type.Union([Type.Literal("last"), NonEmptyString])),
|
||||
to: Type.Optional(Type.String()),
|
||||
bestEffortDeliver: Type.Optional(Type.Boolean()),
|
||||
},
|
||||
{ additionalProperties: false },
|
||||
),
|
||||
]);
|
||||
|
||||
export const CronIsolationSchema = Type.Object(
|
||||
{
|
||||
postToMainPrefix: Type.Optional(Type.String()),
|
||||
@@ -120,18 +144,35 @@ export const CronAddParamsSchema = Type.Object(
|
||||
{ additionalProperties: false },
|
||||
);
|
||||
|
||||
export const CronJobPatchSchema = Type.Object(
|
||||
{
|
||||
name: Type.Optional(NonEmptyString),
|
||||
agentId: Type.Optional(Type.Union([NonEmptyString, Type.Null()])),
|
||||
description: Type.Optional(Type.String()),
|
||||
enabled: Type.Optional(Type.Boolean()),
|
||||
deleteAfterRun: Type.Optional(Type.Boolean()),
|
||||
schedule: Type.Optional(CronScheduleSchema),
|
||||
sessionTarget: Type.Optional(Type.Union([Type.Literal("main"), Type.Literal("isolated")])),
|
||||
wakeMode: Type.Optional(Type.Union([Type.Literal("next-heartbeat"), Type.Literal("now")])),
|
||||
payload: Type.Optional(CronPayloadPatchSchema),
|
||||
isolation: Type.Optional(CronIsolationSchema),
|
||||
state: Type.Optional(Type.Partial(CronJobStateSchema)),
|
||||
},
|
||||
{ additionalProperties: false },
|
||||
);
|
||||
|
||||
export const CronUpdateParamsSchema = Type.Union([
|
||||
Type.Object(
|
||||
{
|
||||
id: NonEmptyString,
|
||||
patch: Type.Partial(CronAddParamsSchema),
|
||||
patch: CronJobPatchSchema,
|
||||
},
|
||||
{ additionalProperties: false },
|
||||
),
|
||||
Type.Object(
|
||||
{
|
||||
jobId: NonEmptyString,
|
||||
patch: Type.Partial(CronAddParamsSchema),
|
||||
patch: CronJobPatchSchema,
|
||||
},
|
||||
{ additionalProperties: false },
|
||||
),
|
||||
|
||||
@@ -122,7 +122,7 @@ describe("gateway server cron", () => {
|
||||
data: {
|
||||
name: "wrapped",
|
||||
schedule: { atMs },
|
||||
payload: { text: "hello" },
|
||||
payload: { kind: "systemEvent", text: "hello" },
|
||||
},
|
||||
});
|
||||
expect(addRes.ok).toBe(true);
|
||||
@@ -166,7 +166,7 @@ describe("gateway server cron", () => {
|
||||
id: jobId,
|
||||
patch: {
|
||||
schedule: { atMs },
|
||||
payload: { text: "updated" },
|
||||
payload: { kind: "systemEvent", text: "updated" },
|
||||
},
|
||||
});
|
||||
expect(updateRes.ok).toBe(true);
|
||||
@@ -182,6 +182,96 @@ describe("gateway server cron", () => {
|
||||
testState.cronStorePath = undefined;
|
||||
});
|
||||
|
||||
test("merges agentTurn payload patches", async () => {
|
||||
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdbot-gw-cron-"));
|
||||
testState.cronStorePath = path.join(dir, "cron", "jobs.json");
|
||||
await fs.mkdir(path.dirname(testState.cronStorePath), { recursive: true });
|
||||
await fs.writeFile(testState.cronStorePath, JSON.stringify({ version: 1, jobs: [] }));
|
||||
|
||||
const { server, ws } = await startServerWithClient();
|
||||
await connectOk(ws);
|
||||
|
||||
const addRes = await rpcReq(ws, "cron.add", {
|
||||
name: "patch merge",
|
||||
enabled: true,
|
||||
schedule: { kind: "every", everyMs: 60_000 },
|
||||
sessionTarget: "isolated",
|
||||
wakeMode: "next-heartbeat",
|
||||
payload: { kind: "agentTurn", message: "hello", model: "opus" },
|
||||
});
|
||||
expect(addRes.ok).toBe(true);
|
||||
const jobIdValue = (addRes.payload as { id?: unknown } | null)?.id;
|
||||
const jobId = typeof jobIdValue === "string" ? jobIdValue : "";
|
||||
expect(jobId.length > 0).toBe(true);
|
||||
|
||||
const updateRes = await rpcReq(ws, "cron.update", {
|
||||
id: jobId,
|
||||
patch: {
|
||||
payload: { kind: "agentTurn", deliver: true, channel: "telegram", to: "19098680" },
|
||||
},
|
||||
});
|
||||
expect(updateRes.ok).toBe(true);
|
||||
const updated = updateRes.payload as
|
||||
| {
|
||||
payload?: {
|
||||
kind?: unknown;
|
||||
message?: unknown;
|
||||
model?: unknown;
|
||||
deliver?: unknown;
|
||||
channel?: unknown;
|
||||
to?: unknown;
|
||||
};
|
||||
}
|
||||
| undefined;
|
||||
expect(updated?.payload?.kind).toBe("agentTurn");
|
||||
expect(updated?.payload?.message).toBe("hello");
|
||||
expect(updated?.payload?.model).toBe("opus");
|
||||
expect(updated?.payload?.deliver).toBe(true);
|
||||
expect(updated?.payload?.channel).toBe("telegram");
|
||||
expect(updated?.payload?.to).toBe("19098680");
|
||||
|
||||
ws.close();
|
||||
await server.close();
|
||||
await rmTempDir(dir);
|
||||
testState.cronStorePath = undefined;
|
||||
});
|
||||
|
||||
test("rejects payload kind changes without required fields", async () => {
|
||||
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdbot-gw-cron-"));
|
||||
testState.cronStorePath = path.join(dir, "cron", "jobs.json");
|
||||
await fs.mkdir(path.dirname(testState.cronStorePath), { recursive: true });
|
||||
await fs.writeFile(testState.cronStorePath, JSON.stringify({ version: 1, jobs: [] }));
|
||||
|
||||
const { server, ws } = await startServerWithClient();
|
||||
await connectOk(ws);
|
||||
|
||||
const addRes = await rpcReq(ws, "cron.add", {
|
||||
name: "patch reject",
|
||||
enabled: true,
|
||||
schedule: { kind: "every", everyMs: 60_000 },
|
||||
sessionTarget: "main",
|
||||
wakeMode: "next-heartbeat",
|
||||
payload: { kind: "systemEvent", text: "hello" },
|
||||
});
|
||||
expect(addRes.ok).toBe(true);
|
||||
const jobIdValue = (addRes.payload as { id?: unknown } | null)?.id;
|
||||
const jobId = typeof jobIdValue === "string" ? jobIdValue : "";
|
||||
expect(jobId.length > 0).toBe(true);
|
||||
|
||||
const updateRes = await rpcReq(ws, "cron.update", {
|
||||
id: jobId,
|
||||
patch: {
|
||||
payload: { kind: "agentTurn", deliver: true },
|
||||
},
|
||||
});
|
||||
expect(updateRes.ok).toBe(false);
|
||||
|
||||
ws.close();
|
||||
await server.close();
|
||||
await rmTempDir(dir);
|
||||
testState.cronStorePath = undefined;
|
||||
});
|
||||
|
||||
test("accepts jobId for cron.update", async () => {
|
||||
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdbot-gw-cron-"));
|
||||
testState.cronStorePath = path.join(dir, "cron", "jobs.json");
|
||||
@@ -210,7 +300,7 @@ describe("gateway server cron", () => {
|
||||
jobId,
|
||||
patch: {
|
||||
schedule: { atMs },
|
||||
payload: { text: "updated" },
|
||||
payload: { kind: "systemEvent", text: "updated" },
|
||||
},
|
||||
});
|
||||
expect(updateRes.ok).toBe(true);
|
||||
|
||||
Reference in New Issue
Block a user