惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

WordPress大学
WordPress大学
酷 壳 – CoolShell
酷 壳 – CoolShell
小众软件
小众软件
Vercel News
Vercel News
Last Week in AI
Last Week in AI
H
Help Net Security
The Cloudflare Blog
L
LangChain Blog
Microsoft Security Blog
Microsoft Security Blog
B
Blog RSS Feed
云风的 BLOG
云风的 BLOG
I
InfoQ
U
Unit 42
美团技术团队
人人都是产品经理
人人都是产品经理
雷峰网
雷峰网
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
博客园 - 叶小钗
Y
Y Combinator Blog
Hugging Face - Blog
Hugging Face - Blog
A
About on SuperTechFans
宝玉的分享
宝玉的分享
量子位
博客园_首页

Recent Commits to openclaw:main

test: merge chat side-result checks · openclaw/openclaw@ddd2c2a test: merge cron history checks · openclaw/openclaw@f7eb746 test: merge responsive navigation shell checks · openclaw/openclaw@c2e4b47 docs(changelog): add codex oauth fixes · openclaw/openclaw@628e6cd test: merge navigation routing cases · openclaw/openclaw@5d8cecb Tests: mock channel registry bundled fallback · openclaw/openclaw@2b08233 Secrets: avoid broad web search discovery for single plugin config · openclaw/openclaw@a464f59 test: merge config view browser checks · openclaw/openclaw@20cf511 fix(status): align oauth health with runtime · openclaw/openclaw@eed7116 feat: add macOS screen snapshots for monitor preview (#67954) thanks … · openclaw/openclaw@f377db1 fix: report shared auth scopes in hello-ok (#67810) thanks @BunsDev · openclaw/openclaw@0b6c39b Auto-reply: avoid eager bundled route fallback · openclaw/openclaw@3ea1bf4 Tests: narrow session binding contract setup · openclaw/openclaw@54e4e16 fix(macOS): enable undo/redo in webchat composer text input (#34962) · openclaw/openclaw@00951dc Tests: speed up channel setup promotion · openclaw/openclaw@82b529a Docs: refresh agent instructions · openclaw/openclaw@5775fe2 fix(auth): serialize OAuth refresh across agents to fix #26322 (#67876) · openclaw/openclaw@8e79080 test: allow ollama public surface boundary test · openclaw/openclaw@7d4f1a6 Docs: add test performance guardrails · openclaw/openclaw@89706d3 Tests: restore context-engine usage proof · openclaw/openclaw@e4c4f95 Tests: slim context engine runtime coverage · openclaw/openclaw@74c198f ci: retry failed custom checkouts · openclaw/openclaw@0ee5baf test: trim duplicate provider auth onboarding cases · openclaw/openclaw@1ffc02e matrix: fix sessions_spawn --thread subagent session spawning (#67643) · openclaw/openclaw@1ce2596 test: reduce auth choice fixture churn · openclaw/openclaw@857b9cd test: mock health status config boundaries · openclaw/openclaw@9d5ab4a test: mock onboard config io boundary · openclaw/openclaw@299694d test: mock legacy state plugin boundaries · openclaw/openclaw@2713089 test: mock channel install boundaries · openclaw/openclaw@b945248 test: mock doctor preview channel boundaries · openclaw/openclaw@b1a3ad4
fix: bound default heartbeat run timeout (#88133) · openc...
steipete · 2026-05-30 · via Recent Commits to openclaw:main
Original file line numberDiff line numberDiff line change

@@ -617,7 +617,7 @@ Periodic heartbeat runs.

617617

- `every`: duration string (ms/s/m/h). Default: `30m` (API-key auth) or `1h` (OAuth auth). Set to `0m` to disable.

618618

- `includeSystemPromptSection`: when false, omits the Heartbeat section from the system prompt and skips `HEARTBEAT.md` injection into bootstrap context. Default: `true`.

619619

- `suppressToolErrorWarnings`: when true, suppresses tool error warning payloads during heartbeat runs.

620-

- `timeoutSeconds`: maximum time in seconds allowed for a heartbeat agent turn before it is aborted. Leave unset to use `agents.defaults.timeoutSeconds`.

620+

- `timeoutSeconds`: maximum time in seconds allowed for a heartbeat agent turn before it is aborted. Leave unset to use `agents.defaults.timeoutSeconds` when set, otherwise the heartbeat cadence capped at 600 seconds.

621621

- `directPolicy`: direct/DM delivery policy. `allow` (default) permits direct-target delivery. `block` suppresses direct-target delivery and emits `reason=dm-blocked`.

622622

- `lightContext`: when true, heartbeat runs use lightweight bootstrap context and keep only `HEARTBEAT.md` from workspace bootstrap files.

623623

- `isolatedSession`: when true, each heartbeat runs in a fresh session with no prior conversation history. Same isolation pattern as cron `sessionTarget: "isolated"`. Reduces per-heartbeat token cost from ~100K to ~2-5K tokens.

Original file line numberDiff line numberDiff line change

@@ -63,6 +63,7 @@ Example config:

6363
6464

- Interval: `30m` (or `1h` when Anthropic OAuth/token auth is the detected auth mode, including Claude CLI reuse). Set `agents.defaults.heartbeat.every` or per-agent `agents.list[].heartbeat.every`; use `0m` to disable.

6565

- Prompt body (configurable via `agents.defaults.heartbeat.prompt`): `Read HEARTBEAT.md if it exists (workspace context). Follow it strictly. Do not infer or repeat old tasks from prior chats. If nothing needs attention, reply HEARTBEAT_OK.`

66+

- Timeout: unset heartbeat turns use `agents.defaults.timeoutSeconds` when set. Otherwise, they use the heartbeat cadence capped at 600 seconds. Set `agents.defaults.heartbeat.timeoutSeconds` or per-agent `agents.list[].heartbeat.timeoutSeconds` for longer heartbeat work.

6667

- The heartbeat prompt is sent **verbatim** as the user message. The system prompt includes a "Heartbeat" section only when heartbeats are enabled for the default agent, and the run is flagged internally.

6768

- When heartbeats are disabled with `0m`, normal runs also omit `HEARTBEAT.md` from bootstrap context so the model does not see heartbeat-only instructions.

6869

- Active hours (`heartbeat.activeHours`) are checked in the configured timezone. Outside the window, heartbeats are skipped until the next tick inside the window.

@@ -274,6 +275,10 @@ Use `accountId` to target a specific account on multi-account channels like Tele

274275

<ParamField path="suppressToolErrorWarnings" type="boolean">

275276

When true, suppresses tool error warning payloads during heartbeat runs.

276277
278+

</ParamField>

279+

<ParamField path="timeoutSeconds" type="number" default="global timeout or min(every, 600)">

280+

Maximum seconds allowed for a heartbeat agent turn before it is aborted. Leave unset to use `agents.defaults.timeoutSeconds` when set, otherwise the heartbeat cadence capped at 600 seconds.

281+
277282

</ParamField>

278283

<ParamField path="activeHours" type="object">

279284

Restricts heartbeat runs to a time window. Object with `start` (HH:MM, inclusive; use `00:00` for start-of-day), `end` (HH:MM exclusive; `24:00` allowed for end-of-day), and optional `timezone`.

Original file line numberDiff line numberDiff line change

@@ -306,9 +306,9 @@ export const FIELD_HELP: Record<string, string> = {

306306

"agents.list[].heartbeat.suppressToolErrorWarnings":

307307

"Suppress tool error warning payloads during heartbeat runs.",

308308

"agents.defaults.heartbeat.timeoutSeconds":

309-

"Maximum time in seconds allowed for a heartbeat agent turn before it is aborted. Leave unset to use agents.defaults.timeoutSeconds.",

309+

"Maximum time in seconds allowed for a heartbeat agent turn before it is aborted. Leave unset to use agents.defaults.timeoutSeconds when set, otherwise the heartbeat cadence capped at 600 seconds.",

310310

"agents.list[].heartbeat.timeoutSeconds":

311-

"Per-agent maximum time in seconds allowed for a heartbeat agent turn before it is aborted. Leave unset to inherit the merged heartbeat/default agent timeout.",

311+

"Per-agent maximum time in seconds allowed for a heartbeat agent turn before it is aborted. Leave unset to inherit the merged heartbeat timeout, then agents.defaults.timeoutSeconds when set, otherwise the heartbeat cadence capped at 600 seconds.",

312312

"agents.defaults.heartbeat.skipWhenBusy":

313313

"When true, defer heartbeat turns on this agent's extra busy lanes: its own session-keyed subagent or nested command work. Cron lanes always defer heartbeat turns.",

314314

"agents.list[].heartbeat.skipWhenBusy":

Original file line numberDiff line numberDiff line change

@@ -407,7 +407,7 @@ export type AgentDefaultsConfig = {

407407

ackMaxChars?: number;

408408

/** Suppress tool error warning payloads during heartbeat runs. */

409409

suppressToolErrorWarnings?: boolean;

410-

/** Run timeout in seconds for heartbeat agent turns. */

410+

/** Run timeout in seconds for heartbeat agent turns. Unset uses global timeout or heartbeat cadence capped at 600 seconds. */

411411

timeoutSeconds?: number;

412412

/**

413413

* If true, run heartbeat turns with lightweight bootstrap context.

Original file line numberDiff line numberDiff line change

@@ -96,6 +96,8 @@ describe("runHeartbeatOnce – heartbeat model override", () => {

9696

}

9797
9898

async function runDefaultsHeartbeat(params: {

99+

every?: string;

100+

defaultTimeoutSeconds?: number;

99101

model?: string;

100102

suppressToolErrorWarnings?: boolean;

101103

timeoutSeconds?: number;

@@ -107,8 +109,9 @@ describe("runHeartbeatOnce – heartbeat model override", () => {

107109

agents: {

108110

defaults: {

109111

workspace: tmpDir,

112+

timeoutSeconds: params.defaultTimeoutSeconds,

110113

heartbeat: {

111-

every: "5m",

114+

every: params.every ?? "5m",

112115

target: "whatsapp",

113116

model: params.model,

114117

suppressToolErrorWarnings: params.suppressToolErrorWarnings,

@@ -209,6 +212,30 @@ describe("runHeartbeatOnce – heartbeat model override", () => {

209212

});

210213

});

211214
215+

it("uses heartbeat cadence as the default reply-run timeout override", async () => {

216+

const replyOpts = await runDefaultsHeartbeat({});

217+

expectReplyOptions(replyOpts, {

218+

isHeartbeat: true,

219+

timeoutOverrideSeconds: 300,

220+

});

221+

});

222+
223+

it("caps the default heartbeat reply-run timeout override", async () => {

224+

const replyOpts = await runDefaultsHeartbeat({ every: "30m" });

225+

expectReplyOptions(replyOpts, {

226+

isHeartbeat: true,

227+

timeoutOverrideSeconds: 600,

228+

});

229+

});

230+
231+

it("preserves explicit default agent timeout for heartbeat runs", async () => {

232+

const replyOpts = await runDefaultsHeartbeat({ defaultTimeoutSeconds: 60, every: "30m" });

233+

expectReplyOptions(replyOpts, {

234+

isHeartbeat: true,

235+

timeoutOverrideSeconds: 60,

236+

});

237+

});

238+
212239

it("passes bootstrapContextMode when heartbeat lightContext is enabled", async () => {

213240

const replyOpts = await runDefaultsHeartbeat({ lightContext: true });

214241

expectReplyOptions(replyOpts, {

Original file line numberDiff line numberDiff line change

@@ -166,6 +166,7 @@ function loadHeartbeatRunnerRuntime() {

166166

}

167167
168168

const HEARTBEAT_ALWAYS_BUSY_LANES = [CommandLane.Cron, CommandLane.CronNested] as const;

169+

const DEFAULT_HEARTBEAT_TIMEOUT_SECONDS = 10 * 60;

169170
170171

function hasQueuedWorkInLanes(

171172

lanes: readonly string[],

@@ -244,6 +245,26 @@ function resolveHeartbeatChannelPlugin(channel: string): ChannelPlugin | undefin

244245

return activePlugin ?? getChannelPlugin(channel as ChannelId);

245246

}

246247
248+

function resolveHeartbeatTimeoutOverrideSeconds(cfg: OpenClawConfig, heartbeat?: HeartbeatConfig) {

249+

if (typeof heartbeat?.timeoutSeconds === "number") {

250+

return heartbeat.timeoutSeconds;

251+

}

252+

const agentDefaultTimeoutSeconds = cfg.agents?.defaults?.timeoutSeconds;

253+

if (

254+

typeof agentDefaultTimeoutSeconds === "number" &&

255+

Number.isFinite(agentDefaultTimeoutSeconds)

256+

) {

257+

return Math.max(1, Math.floor(agentDefaultTimeoutSeconds));

258+

}

259+

// The wake dispatcher awaits heartbeat turns serially. Keep unset heartbeat

260+

// timeouts tied to the cadence instead of the 48h built-in agent default.

261+

const intervalMs = resolveHeartbeatIntervalMs(cfg, undefined, heartbeat);

262+

if (!intervalMs) {

263+

return DEFAULT_HEARTBEAT_TIMEOUT_SECONDS;

264+

}

265+

return Math.max(1, Math.min(DEFAULT_HEARTBEAT_TIMEOUT_SECONDS, Math.ceil(intervalMs / 1000)));

266+

}

267+
247268

export { areHeartbeatsEnabled, setHeartbeatsEnabled };

248269

export {

249270

isHeartbeatEnabledForAgent,

@@ -1752,8 +1773,7 @@ export async function runHeartbeatOnce(opts: {

17521773

await heartbeatTyping?.onReplyStart();

17531774

const heartbeatModelOverride = normalizeOptionalString(heartbeat?.model);

17541775

const suppressToolErrorWarnings = heartbeat?.suppressToolErrorWarnings === true;

1755-

const timeoutOverrideSeconds =

1756-

typeof heartbeat?.timeoutSeconds === "number" ? heartbeat.timeoutSeconds : undefined;

1776+

const timeoutOverrideSeconds = resolveHeartbeatTimeoutOverrideSeconds(cfg, heartbeat);

17571777

const bootstrapContextMode: "lightweight" | undefined =

17581778

heartbeat?.lightContext === true ? "lightweight" : undefined;

17591779

const replyOpts = {