fix(cron): retain deleted job history by agent (#122791)

This commit is contained in:
Peter Steinberger
2026-08-12 13:32:15 -07:00
committed by GitHub
parent 7595d6f432
commit 99a9827077
3 changed files with 23 additions and 7 deletions
+4 -6
View File
@@ -4,6 +4,7 @@ import {
normalizeOptionalString,
} from "@openclaw/normalization-core/string-coerce";
import { uniqueValues } from "@openclaw/normalization-core/string-normalization";
import { normalizeAgentId } from "../routing/session-key.js";
import { listTaskRegistryRecordsByRuntimeSourceIdFromSqlite } from "../tasks/task-registry.store.sqlite.js";
import type { TaskRecord } from "../tasks/task-registry.types.js";
import type { CronRunLogEntry } from "./run-log-types.js";
@@ -23,8 +24,7 @@ type ReadCronTaskRunHistoryPageOptions = {
limit?: number;
offset?: number;
jobId?: string;
/** Narrows the page to these job ids (caller-scope filtering). */
jobIds?: readonly string[];
agentId?: string;
runId?: string;
status?: CronRunHistoryStatusFilter;
statuses?: CronRunStatus[];
@@ -127,7 +127,7 @@ export function readCronTaskRunHistoryPage(
const statuses = normalizeStatuses(options);
const deliveryStatuses = normalizeDeliveryStatuses(options);
const runId = normalizeOptionalString(options.runId);
const jobIds = options.jobIds ? new Set(options.jobIds) : undefined;
const agentId = options.agentId ? normalizeAgentId(options.agentId) : undefined;
const query = normalizeLowercaseStringOrEmpty(options.query);
const sortDir: CronRunHistorySortDir = options.sortDir === "asc" ? "asc" : "desc";
const rows = listTaskRegistryRecordsByRuntimeSourceIdFromSqlite({
@@ -135,12 +135,10 @@ export function readCronTaskRunHistoryPage(
sourceId: jobId,
})
.filter((task) => cronTaskRecordStoreKey(task) === options.storeKey)
.filter((task) => !agentId || task.agentId === agentId)
.map((task) => ({ task, entry: cronTaskRecordToRunLogEntry(task) }))
.filter((row): row is { task: TaskRecord; entry: CronRunLogEntry } => row.entry !== null)
.filter(({ entry }) => {
if (jobIds && !jobIds.has(entry.jobId)) {
return false;
}
if (runId && entry.runId !== runId) {
return false;
}
+1 -1
View File
@@ -1192,7 +1192,7 @@ export const cronHandlers: GatewayRequestHandlers = {
const page = readCronTaskRunHistoryPage({
storeKey: cronStoreKey(context.cronStorePath),
...cronRunLogPageFilters(p),
...(p.agentId ? { jobIds: jobs.map((job) => job.id) } : {}),
agentId: p.agentId,
jobNameById,
});
respond(true, page, undefined);
+18
View File
@@ -11,6 +11,7 @@ import { resetConfigRuntimeState } from "../config/config.js";
import { loadCronStore, saveCronStore } from "../cron/store.js";
import type { GuardedFetchOptions } from "../infra/net/fetch-guard.js";
import { peekSystemEvents } from "../infra/system-events.js";
import { listTaskRegistryRecordsByRuntimeSourceIdFromSqlite } from "../tasks/task-registry.store.sqlite.js";
import { getGatewayProcessInstanceId } from "./process-instance.js";
import type { GatewayCronState } from "./server-cron.js";
import {
@@ -1470,6 +1471,23 @@ describe("gateway server cron", () => {
expect.objectContaining({ jobId: writerJobId }),
);
const removeWriter = await directCronReq(cronState, "cron.remove", { id: writerJobId });
expect(removeWriter.ok).toBe(true);
expect(
listTaskRegistryRecordsByRuntimeSourceIdFromSqlite({
runtime: "cron",
sourceId: writerJobId,
}),
).toEqual([expect.objectContaining({ agentId: "writer" })]);
const retainedWriterRuns = await directCronReq(cronState, "cron.runs", {
scope: "all",
agentId: "writer",
});
expect(retainedWriterRuns.payload).toMatchObject({
entries: [expect.objectContaining({ jobId: writerJobId })],
total: 1,
});
const statusRes = await directCronReq(cronState, "cron.status", {});
expect(statusRes.ok).toBe(true);
const statusPayload = statusRes.payload as