Files
openclaw/src/cron/store/trigger-codec.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

62 lines
2.3 KiB
TypeScript

import fs from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { describe, expect, it } from "vitest";
import { openOpenClawStateDatabase } from "../../state/openclaw-state-db.js";
import type { CronJob } from "../types.js";
import {
loadedCronStoreFromRows,
loadCronRows,
replaceCronRows,
updateCronRuntimeRows,
} from "./row-codec.js";
import type { CronJobRow } from "./schema.js";
import { bindTriggerColumns, triggerFromRow } from "./trigger-codec.js";
describe("cron trigger SQLite codec", () => {
it("round-trips trigger columns", () => {
const columns = bindTriggerColumns({ script: "json({ fire: true })", once: true });
expect(columns).toEqual({ trigger_script: "json({ fire: true })", trigger_once: 1 });
expect(triggerFromRow(columns as CronJobRow)).toEqual({
script: "json({ fire: true })",
once: true,
});
expect(triggerFromRow(bindTriggerColumns(undefined) as CronJobRow)).toBeUndefined();
});
it("round-trips trigger state through updateCronRuntimeRows", async () => {
const job = {
id: "job-1",
name: "watcher",
enabled: true,
createdAtMs: 1,
updatedAtMs: 2,
schedule: { kind: "every", everyMs: 30_000 },
trigger: { script: "json({ fire: false })", once: false },
sessionTarget: "main",
wakeMode: "now",
payload: { kind: "systemEvent", text: "changed" },
state: {
lastTriggerEvalAtMs: 10,
triggerEvalCount: 3,
lastTriggerFireAtMs: 8,
triggerState: { status: "green" },
},
} satisfies CronJob;
const fixtureRoot = await fs.mkdtemp(path.join(os.tmpdir(), "cron-trigger-codec-"));
const handle = openOpenClawStateDatabase({ path: path.join(fixtureRoot, "state.sqlite") });
try {
replaceCronRows(handle.db, "test", { version: 1, jobs: [{ ...job, state: {} }] });
updateCronRuntimeRows(handle.db, "test", { version: 1, jobs: [job] });
const [decoded] = loadedCronStoreFromRows(loadCronRows(handle.db, "test")).store.jobs;
expect(decoded?.trigger).toEqual(job.trigger);
expect(decoded?.state).toEqual(job.state);
} finally {
handle.walMaintenance.close();
handle.db.close();
await fs.rm(fixtureRoot, { recursive: true, force: true });
}
});
});