mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 06:20:43 +00:00
fix(cli): paginate cron show lookup
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import { Command } from "commander";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import type { CronJob } from "../cron/types.js";
|
||||
import { registerCronCli } from "./cron-cli.js";
|
||||
|
||||
const CRON_CLI_TEST_TIMEOUT_MS = 15_000;
|
||||
@@ -93,6 +94,22 @@ function buildProgram() {
|
||||
return program;
|
||||
}
|
||||
|
||||
function createCronJob(id: string, name: string): CronJob {
|
||||
const now = Date.now();
|
||||
return {
|
||||
id,
|
||||
name,
|
||||
enabled: true,
|
||||
createdAtMs: now,
|
||||
updatedAtMs: now,
|
||||
schedule: { kind: "at", at: new Date(now + 3_600_000).toISOString() },
|
||||
sessionTarget: "isolated",
|
||||
wakeMode: "next-heartbeat",
|
||||
payload: { kind: "agentTurn", message: "hello" },
|
||||
state: {},
|
||||
};
|
||||
}
|
||||
|
||||
function resetGatewayMock() {
|
||||
callGatewayFromCli.mockClear();
|
||||
callGatewayFromCli.mockImplementation(defaultGatewayMock);
|
||||
@@ -387,6 +404,54 @@ describe("cron cli", () => {
|
||||
expect(patch.enabled).toBe(expectedEnabled);
|
||||
});
|
||||
|
||||
it("paginates cron show lookups", async () => {
|
||||
resetGatewayMock();
|
||||
callGatewayFromCli.mockImplementation(
|
||||
async (method: string, _opts: unknown, params?: unknown) => {
|
||||
if (method === "cron.status") {
|
||||
return { enabled: true };
|
||||
}
|
||||
if (method === "cron.list") {
|
||||
const offset = (params as { offset?: number }).offset ?? 0;
|
||||
if (offset === 0) {
|
||||
return {
|
||||
jobs: [createCronJob("first-page", "First Page")],
|
||||
hasMore: true,
|
||||
nextOffset: 200,
|
||||
};
|
||||
}
|
||||
return {
|
||||
jobs: [createCronJob("target-job", "Target Job")],
|
||||
hasMore: false,
|
||||
nextOffset: null,
|
||||
deliveryPreviews: {
|
||||
"target-job": {
|
||||
label: "announce -> telegram:-100",
|
||||
detail: "resolved from last, main session",
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
return { ok: true, params };
|
||||
},
|
||||
);
|
||||
|
||||
const program = buildProgram();
|
||||
await program.parseAsync(["cron", "show", "Target Job"], { from: "user" });
|
||||
|
||||
const listParams = callGatewayFromCli.mock.calls
|
||||
.filter((call) => call[0] === "cron.list")
|
||||
.map((call) => call[2]);
|
||||
expect(listParams).toEqual([
|
||||
{ includeDisabled: true, limit: 200, offset: 0 },
|
||||
{ includeDisabled: true, limit: 200, offset: 200 },
|
||||
]);
|
||||
expect(defaultRuntime.log).toHaveBeenCalledWith("id: target-job");
|
||||
expect(defaultRuntime.log).toHaveBeenCalledWith(
|
||||
"delivery: announce -> telegram:-100 (resolved from last, main session)",
|
||||
);
|
||||
});
|
||||
|
||||
it("sends agent id on cron add", async () => {
|
||||
await runCronCommand([
|
||||
"cron",
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import type { Command } from "commander";
|
||||
import type { CronJob } from "../../cron/types.js";
|
||||
import type { CronDeliveryPreview, CronJob } from "../../cron/types.js";
|
||||
import { defaultRuntime } from "../../runtime.js";
|
||||
import { normalizeLowercaseStringOrEmpty } from "../../shared/string-coerce.js";
|
||||
import type { GatewayRpcOpts } from "../gateway-rpc.js";
|
||||
import { addGatewayClientOptions, callGatewayFromCli } from "../gateway-rpc.js";
|
||||
import {
|
||||
coerceCronDeliveryPreviews,
|
||||
@@ -11,7 +12,9 @@ import {
|
||||
warnIfCronSchedulerDisabled,
|
||||
} from "./shared.js";
|
||||
|
||||
function findCronJobForShow(jobs: CronJob[], idOrName: string): CronJob | undefined {
|
||||
const CRON_SHOW_PAGE_SIZE = 200;
|
||||
|
||||
function findCronJobInPage(jobs: CronJob[], idOrName: string): CronJob | undefined {
|
||||
const needle = normalizeLowercaseStringOrEmpty(idOrName);
|
||||
return jobs.find(
|
||||
(job) =>
|
||||
@@ -20,6 +23,34 @@ function findCronJobForShow(jobs: CronJob[], idOrName: string): CronJob | undefi
|
||||
);
|
||||
}
|
||||
|
||||
async function loadCronJobForShow(
|
||||
opts: GatewayRpcOpts,
|
||||
idOrName: string,
|
||||
): Promise<{ job?: CronJob; deliveryPreview?: CronDeliveryPreview }> {
|
||||
let offset = 0;
|
||||
for (;;) {
|
||||
const res = await callGatewayFromCli("cron.list", opts, {
|
||||
includeDisabled: true,
|
||||
limit: CRON_SHOW_PAGE_SIZE,
|
||||
offset,
|
||||
});
|
||||
const page = res as {
|
||||
jobs?: CronJob[];
|
||||
hasMore?: boolean;
|
||||
nextOffset?: number | null;
|
||||
};
|
||||
const jobs = page.jobs ?? [];
|
||||
const job = findCronJobInPage(jobs, idOrName);
|
||||
if (job) {
|
||||
return { job, deliveryPreview: coerceCronDeliveryPreviews(res).get(job.id) };
|
||||
}
|
||||
if (!page.hasMore || typeof page.nextOffset !== "number") {
|
||||
return {};
|
||||
}
|
||||
offset = page.nextOffset;
|
||||
}
|
||||
}
|
||||
|
||||
function registerCronToggleCommand(params: {
|
||||
cron: Command;
|
||||
name: "enable" | "disable";
|
||||
@@ -86,9 +117,7 @@ export function registerCronSimpleCommands(cron: Command) {
|
||||
.option("--json", "Output JSON", false)
|
||||
.action(async (id, opts) => {
|
||||
try {
|
||||
const res = await callGatewayFromCli("cron.list", opts, { includeDisabled: true });
|
||||
const jobs = (res as { jobs?: CronJob[] } | null)?.jobs ?? [];
|
||||
const job = findCronJobForShow(jobs, String(id));
|
||||
const { job, deliveryPreview } = await loadCronJobForShow(opts, String(id));
|
||||
if (!job) {
|
||||
throw new Error(`cron job not found: ${String(id)}`);
|
||||
}
|
||||
@@ -96,8 +125,7 @@ export function registerCronSimpleCommands(cron: Command) {
|
||||
printCronJson(job);
|
||||
return;
|
||||
}
|
||||
const deliveryPreviews = coerceCronDeliveryPreviews(res);
|
||||
printCronShow(job, defaultRuntime, { deliveryPreview: deliveryPreviews.get(job.id) });
|
||||
printCronShow(job, defaultRuntime, { deliveryPreview });
|
||||
} catch (err) {
|
||||
handleCronCliError(err);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user