diff --git a/src/config/config.legacy-config-detection.accepts-imessage-dmpolicy.test.ts b/src/config/config.legacy-config-detection.accepts-imessage-dmpolicy.test.ts index 87df6130336..89632bbc543 100644 --- a/src/config/config.legacy-config-detection.accepts-imessage-dmpolicy.test.ts +++ b/src/config/config.legacy-config-detection.accepts-imessage-dmpolicy.test.ts @@ -274,6 +274,15 @@ describe("legacy config detection", () => { }, ); }); + it("flags top-level heartbeat as legacy in snapshot", async () => { + await withSnapshotForConfig( + { heartbeat: { model: "anthropic/claude-3-5-haiku-20241022", every: "30m" } }, + async (ctx) => { + expect(ctx.snapshot.valid).toBe(false); + expect(ctx.snapshot.legacyIssues.some((issue) => issue.path === "heartbeat")).toBe(true); + }, + ); + }); it("flags legacy provider sections in snapshot", async () => { await withSnapshotForConfig({ whatsapp: { allowFrom: ["+1555"] } }, async (ctx) => { expect(ctx.snapshot.valid).toBe(false); diff --git a/src/config/legacy.rules.ts b/src/config/legacy.rules.ts index 9f4ef6098be..f61ae6ccdbb 100644 --- a/src/config/legacy.rules.ts +++ b/src/config/legacy.rules.ts @@ -204,4 +204,9 @@ export const LEGACY_CONFIG_RULES: LegacyConfigRule[] = [ match: (value) => isLegacyGatewayBindHostAlias(value), requireSourceLiteral: true, }, + { + path: ["heartbeat"], + message: + "top-level heartbeat is not a valid config path; use agents.defaults.heartbeat instead.", + }, ];