Files
openclaw/src/gateway/protocol/schema/cron.ts
Tak Hoffman 77c3b142a9 Web UI: add full cron edit parity, all-jobs run history, and compact filters (openclaw#24155) thanks @Takhoffman
Verified:
- pnpm install --frozen-lockfile
- pnpm build
- pnpm check
- pnpm test:macmini

Co-authored-by: Takhoffman <781889+Takhoffman@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-02-22 23:05:42 -06:00

331 lines
10 KiB
TypeScript

import { Type, type TSchema } from "@sinclair/typebox";
import { NonEmptyString } from "./primitives.js";
function cronAgentTurnPayloadSchema(params: { message: TSchema }) {
return Type.Object(
{
kind: Type.Literal("agentTurn"),
message: params.message,
model: Type.Optional(Type.String()),
thinking: Type.Optional(Type.String()),
timeoutSeconds: Type.Optional(Type.Integer({ minimum: 0 })),
allowUnsafeExternalContent: Type.Optional(Type.Boolean()),
deliver: Type.Optional(Type.Boolean()),
channel: Type.Optional(Type.String()),
to: Type.Optional(Type.String()),
bestEffortDeliver: Type.Optional(Type.Boolean()),
},
{ additionalProperties: false },
);
}
const CronSessionTargetSchema = Type.Union([Type.Literal("main"), Type.Literal("isolated")]);
const CronWakeModeSchema = Type.Union([Type.Literal("next-heartbeat"), Type.Literal("now")]);
const CronRunStatusSchema = Type.Union([
Type.Literal("ok"),
Type.Literal("error"),
Type.Literal("skipped"),
]);
const CronSortDirSchema = Type.Union([Type.Literal("asc"), Type.Literal("desc")]);
const CronJobsEnabledFilterSchema = Type.Union([
Type.Literal("all"),
Type.Literal("enabled"),
Type.Literal("disabled"),
]);
const CronJobsSortBySchema = Type.Union([
Type.Literal("nextRunAtMs"),
Type.Literal("updatedAtMs"),
Type.Literal("name"),
]);
const CronRunsStatusFilterSchema = Type.Union([
Type.Literal("all"),
Type.Literal("ok"),
Type.Literal("error"),
Type.Literal("skipped"),
]);
const CronRunsStatusValueSchema = Type.Union([
Type.Literal("ok"),
Type.Literal("error"),
Type.Literal("skipped"),
]);
const CronDeliveryStatusSchema = Type.Union([
Type.Literal("delivered"),
Type.Literal("not-delivered"),
Type.Literal("unknown"),
Type.Literal("not-requested"),
]);
const CronCommonOptionalFields = {
agentId: Type.Optional(Type.Union([NonEmptyString, Type.Null()])),
sessionKey: Type.Optional(Type.Union([NonEmptyString, Type.Null()])),
description: Type.Optional(Type.String()),
enabled: Type.Optional(Type.Boolean()),
deleteAfterRun: Type.Optional(Type.Boolean()),
};
function cronIdOrJobIdParams(extraFields: Record<string, TSchema>) {
return Type.Union([
Type.Object(
{
id: NonEmptyString,
...extraFields,
},
{ additionalProperties: false },
),
Type.Object(
{
jobId: NonEmptyString,
...extraFields,
},
{ additionalProperties: false },
),
]);
}
const CronRunLogJobIdSchema = Type.String({
minLength: 1,
// Prevent path traversal via separators in cron.runs id/jobId.
pattern: "^[^/\\\\]+$",
});
export const CronScheduleSchema = Type.Union([
Type.Object(
{
kind: Type.Literal("at"),
at: NonEmptyString,
},
{ additionalProperties: false },
),
Type.Object(
{
kind: Type.Literal("every"),
everyMs: Type.Integer({ minimum: 1 }),
anchorMs: Type.Optional(Type.Integer({ minimum: 0 })),
},
{ additionalProperties: false },
),
Type.Object(
{
kind: Type.Literal("cron"),
expr: NonEmptyString,
tz: Type.Optional(Type.String()),
staggerMs: Type.Optional(Type.Integer({ minimum: 0 })),
},
{ additionalProperties: false },
),
]);
export const CronPayloadSchema = Type.Union([
Type.Object(
{
kind: Type.Literal("systemEvent"),
text: NonEmptyString,
},
{ additionalProperties: false },
),
cronAgentTurnPayloadSchema({ message: NonEmptyString }),
]);
export const CronPayloadPatchSchema = Type.Union([
Type.Object(
{
kind: Type.Literal("systemEvent"),
text: Type.Optional(NonEmptyString),
},
{ additionalProperties: false },
),
cronAgentTurnPayloadSchema({ message: Type.Optional(NonEmptyString) }),
]);
const CronDeliverySharedProperties = {
channel: Type.Optional(Type.Union([Type.Literal("last"), NonEmptyString])),
bestEffort: Type.Optional(Type.Boolean()),
};
const CronDeliveryNoopSchema = Type.Object(
{
mode: Type.Literal("none"),
...CronDeliverySharedProperties,
to: Type.Optional(Type.String()),
},
{ additionalProperties: false },
);
const CronDeliveryAnnounceSchema = Type.Object(
{
mode: Type.Literal("announce"),
...CronDeliverySharedProperties,
to: Type.Optional(Type.String()),
},
{ additionalProperties: false },
);
const CronDeliveryWebhookSchema = Type.Object(
{
mode: Type.Literal("webhook"),
...CronDeliverySharedProperties,
to: NonEmptyString,
},
{ additionalProperties: false },
);
export const CronDeliverySchema = Type.Union([
CronDeliveryNoopSchema,
CronDeliveryAnnounceSchema,
CronDeliveryWebhookSchema,
]);
export const CronDeliveryPatchSchema = Type.Object(
{
mode: Type.Optional(
Type.Union([Type.Literal("none"), Type.Literal("announce"), Type.Literal("webhook")]),
),
...CronDeliverySharedProperties,
to: Type.Optional(Type.String()),
},
{ additionalProperties: false },
);
export const CronJobStateSchema = Type.Object(
{
nextRunAtMs: Type.Optional(Type.Integer({ minimum: 0 })),
runningAtMs: Type.Optional(Type.Integer({ minimum: 0 })),
lastRunAtMs: Type.Optional(Type.Integer({ minimum: 0 })),
lastRunStatus: Type.Optional(CronRunStatusSchema),
lastStatus: Type.Optional(CronRunStatusSchema),
lastError: Type.Optional(Type.String()),
lastDurationMs: Type.Optional(Type.Integer({ minimum: 0 })),
consecutiveErrors: Type.Optional(Type.Integer({ minimum: 0 })),
lastDelivered: Type.Optional(Type.Boolean()),
lastDeliveryStatus: Type.Optional(CronDeliveryStatusSchema),
lastDeliveryError: Type.Optional(Type.String()),
},
{ additionalProperties: false },
);
export const CronJobSchema = Type.Object(
{
id: NonEmptyString,
agentId: Type.Optional(NonEmptyString),
sessionKey: Type.Optional(NonEmptyString),
name: NonEmptyString,
description: Type.Optional(Type.String()),
enabled: Type.Boolean(),
deleteAfterRun: Type.Optional(Type.Boolean()),
createdAtMs: Type.Integer({ minimum: 0 }),
updatedAtMs: Type.Integer({ minimum: 0 }),
schedule: CronScheduleSchema,
sessionTarget: CronSessionTargetSchema,
wakeMode: CronWakeModeSchema,
payload: CronPayloadSchema,
delivery: Type.Optional(CronDeliverySchema),
state: CronJobStateSchema,
},
{ additionalProperties: false },
);
export const CronListParamsSchema = Type.Object(
{
includeDisabled: Type.Optional(Type.Boolean()),
limit: Type.Optional(Type.Integer({ minimum: 1, maximum: 200 })),
offset: Type.Optional(Type.Integer({ minimum: 0 })),
query: Type.Optional(Type.String()),
enabled: Type.Optional(CronJobsEnabledFilterSchema),
sortBy: Type.Optional(CronJobsSortBySchema),
sortDir: Type.Optional(CronSortDirSchema),
},
{ additionalProperties: false },
);
export const CronStatusParamsSchema = Type.Object({}, { additionalProperties: false });
export const CronAddParamsSchema = Type.Object(
{
name: NonEmptyString,
...CronCommonOptionalFields,
schedule: CronScheduleSchema,
sessionTarget: CronSessionTargetSchema,
wakeMode: CronWakeModeSchema,
payload: CronPayloadSchema,
delivery: Type.Optional(CronDeliverySchema),
},
{ additionalProperties: false },
);
export const CronJobPatchSchema = Type.Object(
{
name: Type.Optional(NonEmptyString),
...CronCommonOptionalFields,
schedule: Type.Optional(CronScheduleSchema),
sessionTarget: Type.Optional(CronSessionTargetSchema),
wakeMode: Type.Optional(CronWakeModeSchema),
payload: Type.Optional(CronPayloadPatchSchema),
delivery: Type.Optional(CronDeliveryPatchSchema),
state: Type.Optional(Type.Partial(CronJobStateSchema)),
},
{ additionalProperties: false },
);
export const CronUpdateParamsSchema = cronIdOrJobIdParams({
patch: CronJobPatchSchema,
});
export const CronRemoveParamsSchema = cronIdOrJobIdParams({});
export const CronRunParamsSchema = cronIdOrJobIdParams({
mode: Type.Optional(Type.Union([Type.Literal("due"), Type.Literal("force")])),
});
export const CronRunsParamsSchema = Type.Object(
{
scope: Type.Optional(Type.Union([Type.Literal("job"), Type.Literal("all")])),
id: Type.Optional(CronRunLogJobIdSchema),
jobId: Type.Optional(CronRunLogJobIdSchema),
limit: Type.Optional(Type.Integer({ minimum: 1, maximum: 200 })),
offset: Type.Optional(Type.Integer({ minimum: 0 })),
statuses: Type.Optional(Type.Array(CronRunsStatusValueSchema, { minItems: 1, maxItems: 3 })),
status: Type.Optional(CronRunsStatusFilterSchema),
deliveryStatuses: Type.Optional(
Type.Array(CronDeliveryStatusSchema, { minItems: 1, maxItems: 4 }),
),
deliveryStatus: Type.Optional(CronDeliveryStatusSchema),
query: Type.Optional(Type.String()),
sortDir: Type.Optional(CronSortDirSchema),
},
{ additionalProperties: false },
);
export const CronRunLogEntrySchema = Type.Object(
{
ts: Type.Integer({ minimum: 0 }),
jobId: NonEmptyString,
action: Type.Literal("finished"),
status: Type.Optional(CronRunStatusSchema),
error: Type.Optional(Type.String()),
summary: Type.Optional(Type.String()),
delivered: Type.Optional(Type.Boolean()),
deliveryStatus: Type.Optional(CronDeliveryStatusSchema),
deliveryError: Type.Optional(Type.String()),
sessionId: Type.Optional(NonEmptyString),
sessionKey: Type.Optional(NonEmptyString),
runAtMs: Type.Optional(Type.Integer({ minimum: 0 })),
durationMs: Type.Optional(Type.Integer({ minimum: 0 })),
nextRunAtMs: Type.Optional(Type.Integer({ minimum: 0 })),
model: Type.Optional(Type.String()),
provider: Type.Optional(Type.String()),
usage: Type.Optional(
Type.Object(
{
input_tokens: Type.Optional(Type.Number()),
output_tokens: Type.Optional(Type.Number()),
total_tokens: Type.Optional(Type.Number()),
cache_read_tokens: Type.Optional(Type.Number()),
cache_write_tokens: Type.Optional(Type.Number()),
},
{ additionalProperties: false },
),
),
jobName: Type.Optional(Type.String()),
},
{ additionalProperties: false },
);