From 2f9e87c8bbdf26509d48c5ac4d1accd82ebd60d1 Mon Sep 17 00:00:00 2001 From: Rahul Pal Date: Sat, 11 Apr 2026 01:37:01 +0530 Subject: [PATCH] fix: don't bleed top-level interval/prompt into heartbeat task parsing --- src/auto-reply/heartbeat.test.ts | 17 +++++++++++++++++ src/auto-reply/heartbeat.ts | 10 ++++++++-- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/src/auto-reply/heartbeat.test.ts b/src/auto-reply/heartbeat.test.ts index f7544bfa2dd..3b0d5499444 100644 --- a/src/auto-reply/heartbeat.test.ts +++ b/src/auto-reply/heartbeat.test.ts @@ -2,6 +2,7 @@ import { describe, expect, it } from "vitest"; import { DEFAULT_HEARTBEAT_ACK_MAX_CHARS, isHeartbeatContentEffectivelyEmpty, + parseHeartbeatTasks, stripHeartbeatToken, } from "./heartbeat.js"; import { HEARTBEAT_TOKEN } from "./tokens.js"; @@ -25,6 +26,22 @@ describe("stripHeartbeatToken", () => { }); }); + it("does not bleed top-level interval/prompt fields into task parsing", () => { + const content = ` + tasks:- name: email-check + interval: 30m + prompt: Check for urgent emails +interval: should-not-bleed +`; + expect(parseHeartbeatTasks(content)).toEqual([ + { + name: "email-check", + interval: "30m", + prompt: "Check for urgent emails", + }, + ]); + }); + it("drops heartbeats with small junk in heartbeat mode", () => { expect(stripHeartbeatToken("HEARTBEAT_OK 🦞", { mode: "heartbeat" })).toEqual({ shouldSkip: true, diff --git a/src/auto-reply/heartbeat.ts b/src/auto-reply/heartbeat.ts index 34601a8528c..8c3bda3043e 100644 --- a/src/auto-reply/heartbeat.ts +++ b/src/auto-reply/heartbeat.ts @@ -249,12 +249,18 @@ export function parseHeartbeatTasks(content: string): HeartbeatTask[] { } // Check for task fields BEFORE checking for end of block - if (nextTrimmed.startsWith("interval:")) { + if ( + nextTrimmed.startsWith("interval:") && + (nextLine.startsWith(" ") || nextLine.startsWith("\t")) + ) { interval = nextTrimmed .replace("interval:", "") .trim() .replace(/^["']|["']$/g, ""); - } else if (nextTrimmed.startsWith("prompt:")) { + } else if ( + nextTrimmed.startsWith("prompt:") && + (nextLine.startsWith(" ") || nextLine.startsWith("\t")) + ) { prompt = nextTrimmed .replace("prompt:", "") .trim()