Files
openclaw/src/cron/persisted-shape.trigger.test.ts
Peter Steinberger a6768d9de5 feat(cron): event triggers — polled condition-watcher scripts via code mode (#101195)
* feat(cron): add headless code-mode driver and trigger-script evaluator

Part A of cron event triggers: runCodeModeScriptHeadless runs a script
to completion (exec/settle/resume, no snapshots, no session), plus the
cron-facing evaluator with per-job tool catalog cache, semaphore, 16KB
state cap, and closed failure taxonomy.

* fix(cron): correct trigger-script bootstrap flags and cache narrowing

* feat(cron): add event triggers (polled condition-watcher scripts)

Part B: trigger field on cron jobs gated by cron.triggers.enabled;
timer evaluates the script each due tick, quiet ticks leave no run
history, fired runs append the script message to the payload; once
semantics, min-interval floor, SQLite columns, RPC/CLI/agent-tool
surfaces, and docs.

* fix(cron): propagate triggerEval through startup catch-up outcomes

* fix(cron): honor cron staggering on quiet trigger ticks; fix trigger test types

* fix(cron): reject with Error reason in trigger-script abort test

* fix(cron): regenerate protocol/docs/snapshot artifacts and break madge cycle for trigger types

CI fixes for #101195: trigger evaluator result types move to the cron
types leaf (madge cycle), cron tool schema inventory gains trigger,
Swift protocol bindings + docs map + Linux prompt snapshots regenerated.

* fix(cron): drop underscore-dangle names in trigger code sync assert

* fix(cron): adopt registerHeadlessToolSearchCatalog after tool-search symbol localization

Upstream #101831 made registerToolSearchCatalog module-private; fold the
headless ref-only catalog registration into one public seam.
2026-07-07 19:12:38 +01:00

47 lines
1.4 KiB
TypeScript

import { describe, expect, it } from "vitest";
import { getInvalidPersistedCronJobReason } from "./persisted-shape.js";
function candidate(overrides: Record<string, unknown> = {}) {
return {
id: "job-1",
schedule: { kind: "every", everyMs: 30_000 },
trigger: { script: "json({ fire: true })" },
payload: { kind: "systemEvent", text: "changed" },
sessionTarget: "main",
...overrides,
};
}
describe("persisted cron trigger shape", () => {
it("accepts a non-empty trigger on recurring schedules", () => {
expect(getInvalidPersistedCronJobReason(candidate())).toBeNull();
expect(
getInvalidPersistedCronJobReason(
candidate({ schedule: { kind: "cron", expr: "* * * * *" } }),
),
).toBeNull();
});
it("rejects empty scripts and non-object triggers", () => {
expect(getInvalidPersistedCronJobReason(candidate({ trigger: { script: " " } }))).toBe(
"invalid-trigger",
);
expect(getInvalidPersistedCronJobReason(candidate({ trigger: "script" }))).toBe(
"invalid-trigger",
);
});
it("rejects triggers on at and on-exit schedules", () => {
expect(
getInvalidPersistedCronJobReason(
candidate({ schedule: { kind: "at", at: "2026-08-01T00:00:00.000Z" } }),
),
).toBe("invalid-trigger");
expect(
getInvalidPersistedCronJobReason(
candidate({ schedule: { kind: "on-exit", command: "make build" } }),
),
).toBe("invalid-trigger");
});
});