From 19f87f0a89fbd0cf7f168139556c7c11f6222ee7 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Fri, 26 Dec 2025 01:34:46 +0100 Subject: [PATCH] feat: allow hour durations --- docs/configuration.md | 2 +- docs/heartbeat.md | 2 +- src/cli/parse-duration.test.ts | 4 ++++ src/cli/parse-duration.ts | 13 +++++++++---- src/config/config.ts | 2 +- 5 files changed, 16 insertions(+), 7 deletions(-) diff --git a/docs/configuration.md b/docs/configuration.md index f044574fb..10f9482dc 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -148,7 +148,7 @@ If you omit the provider, CLAWDIS currently assumes `anthropic` as a temporary deprecation fallback. `agent.heartbeat` configures periodic heartbeat runs: -- `every`: duration string (`ms`, `s`, `m`); default unit minutes. Omit or set +- `every`: duration string (`ms`, `s`, `m`, `h`); default unit minutes. Omit or set `0m` to disable. - `model`: optional override model for heartbeat runs (`provider/model`). diff --git a/docs/heartbeat.md b/docs/heartbeat.md index 39e9b1a47..779f95bf6 100644 --- a/docs/heartbeat.md +++ b/docs/heartbeat.md @@ -13,7 +13,7 @@ Goal: add a simple heartbeat poll for the embedded agent that only notifies user ## Config & defaults - New config key: `agent.heartbeat` with: - - `every`: duration string (`ms`, `s`, `m`; default unit minutes). `0m` disables. + - `every`: duration string (`ms`, `s`, `m`, `h`; default unit minutes). `0m` disables. - `model`: optional override model (`provider/model`) for heartbeat runs. - Default: disabled unless `agent.heartbeat.every` is set. - New optional idle override for heartbeats: `session.heartbeatIdleMinutes` (defaults to `idleMinutes`). Heartbeat skips do **not** update the session `updatedAt` so idle expiry still works. diff --git a/src/cli/parse-duration.test.ts b/src/cli/parse-duration.test.ts index 5bedee82d..b72a00626 100644 --- a/src/cli/parse-duration.test.ts +++ b/src/cli/parse-duration.test.ts @@ -15,6 +15,10 @@ describe("parseDurationMs", () => { expect(parseDurationMs("1m")).toBe(60_000); }); + it("parses hours suffix", () => { + expect(parseDurationMs("2h")).toBe(7_200_000); + }); + it("supports decimals", () => { expect(parseDurationMs("0.5s")).toBe(500); }); diff --git a/src/cli/parse-duration.ts b/src/cli/parse-duration.ts index da115ac23..f8ba413b1 100644 --- a/src/cli/parse-duration.ts +++ b/src/cli/parse-duration.ts @@ -1,5 +1,5 @@ export type DurationMsParseOptions = { - defaultUnit?: "ms" | "s" | "m"; + defaultUnit?: "ms" | "s" | "m" | "h"; }; export function parseDurationMs( @@ -11,7 +11,7 @@ export function parseDurationMs( .toLowerCase(); if (!trimmed) throw new Error("invalid duration (empty)"); - const m = /^(\d+(?:\.\d+)?)(ms|s|m)?$/.exec(trimmed); + const m = /^(\d+(?:\.\d+)?)(ms|s|m|h)?$/.exec(trimmed); if (!m) throw new Error(`invalid duration: ${raw}`); const value = Number(m[1]); @@ -19,8 +19,13 @@ export function parseDurationMs( throw new Error(`invalid duration: ${raw}`); } - const unit = (m[2] ?? opts?.defaultUnit ?? "ms") as "ms" | "s" | "m"; - const multiplier = unit === "ms" ? 1 : unit === "s" ? 1000 : 60_000; + const unit = (m[2] ?? opts?.defaultUnit ?? "ms") as + | "ms" + | "s" + | "m" + | "h"; + const multiplier = + unit === "ms" ? 1 : unit === "s" ? 1000 : unit === "m" ? 60_000 : 3_600_000; const ms = Math.round(value * multiplier); if (!Number.isFinite(ms)) throw new Error(`invalid duration: ${raw}`); return ms; diff --git a/src/config/config.ts b/src/config/config.ts index c615b0886..0436190a8 100644 --- a/src/config/config.ts +++ b/src/config/config.ts @@ -463,7 +463,7 @@ const HeartbeatSchema = z ctx.addIssue({ code: z.ZodIssueCode.custom, path: ["every"], - message: "invalid duration (use ms, s, m)", + message: "invalid duration (use ms, s, m, h)", }); } })