mirror of
https://github.com/openclaw/openclaw.git
synced 2026-03-12 15:30:39 +00:00
Doctor: warn on implicit heartbeat directPolicy (#36789)
* Changelog: note heartbeat directPolicy doctor warning * Tests: cover heartbeat directPolicy doctor warning * Doctor: warn on implicit heartbeat directPolicy * Tests: cover per-agent heartbeat directPolicy warning * Update CHANGELOG.md
This commit is contained in:
@@ -170,6 +170,7 @@ Docs: https://docs.openclaw.ai
|
||||
|
||||
- Agents/failover cooldown classification: stop treating generic `cooling down` text as provider `rate_limit` so healthy models no longer show false global cooldown/rate-limit warnings while explicit `model_cooldown` markers still trigger failover. (#32972) thanks @stakeswky.
|
||||
- Agents/failover service-unavailable handling: stop treating bare proxy/CDN `service unavailable` errors as provider overload while keeping them retryable via the timeout/failover path, so transient outages no longer show false rate-limit warnings or block fallback. (#36646) thanks @jnMetaCode.
|
||||
- Doctor/Heartbeat upgrade diagnostics: warn when heartbeat delivery is configured with an implicit `directPolicy` so upgrades pin direct/DM behavior explicitly instead of relying on the current default. (#36789) Thanks @vincentkoc.
|
||||
- Agents/current-time UTC anchor: append a machine-readable UTC suffix alongside local `Current time:` lines in shared cron-style prompt contexts so agents can compare UTC-stamped workspace timestamps without doing timezone math. (#32423) thanks @jriff.
|
||||
- TUI/webchat command-owner scope alignment: treat internal-channel gateway sessions with `operator.admin` as owner-authorized in command auth, restoring cron/gateway/connector tool access for affected TUI/webchat sessions while keeping external channels on identity-based owner checks. (from #35666, #35673, #35704) Thanks @Naylenv, @Octane0411, and @Sid-Qin.
|
||||
- Discord/inbound timeout isolation: separate inbound worker timeout tracking from listener timeout budgets so queued Discord replies are no longer dropped when listener watchdog windows expire mid-run. (#36602) Thanks @dutifulbob.
|
||||
|
||||
@@ -135,4 +135,66 @@ describe("noteSecurityWarnings gateway exposure", () => {
|
||||
expect(message).toContain("exec-approvals.json");
|
||||
expect(message).toContain("openclaw approvals get --gateway");
|
||||
});
|
||||
|
||||
it("warns when heartbeat delivery relies on implicit directPolicy defaults", async () => {
|
||||
const cfg = {
|
||||
agents: {
|
||||
defaults: {
|
||||
heartbeat: {
|
||||
target: "last",
|
||||
},
|
||||
},
|
||||
},
|
||||
} as OpenClawConfig;
|
||||
await noteSecurityWarnings(cfg);
|
||||
const message = lastMessage();
|
||||
expect(message).toContain("Heartbeat defaults");
|
||||
expect(message).toContain("agents.defaults.heartbeat.directPolicy");
|
||||
expect(message).toContain("direct/DM targets by default");
|
||||
});
|
||||
|
||||
it("warns when a per-agent heartbeat relies on implicit directPolicy", async () => {
|
||||
const cfg = {
|
||||
agents: {
|
||||
list: [
|
||||
{
|
||||
id: "ops",
|
||||
heartbeat: {
|
||||
target: "last",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
} as OpenClawConfig;
|
||||
await noteSecurityWarnings(cfg);
|
||||
const message = lastMessage();
|
||||
expect(message).toContain('Heartbeat agent "ops"');
|
||||
expect(message).toContain('heartbeat.directPolicy for agent "ops"');
|
||||
expect(message).toContain("direct/DM targets by default");
|
||||
});
|
||||
|
||||
it("skips heartbeat directPolicy warning when delivery is internal-only or explicit", async () => {
|
||||
const cfg = {
|
||||
agents: {
|
||||
defaults: {
|
||||
heartbeat: {
|
||||
target: "none",
|
||||
},
|
||||
},
|
||||
list: [
|
||||
{
|
||||
id: "ops",
|
||||
heartbeat: {
|
||||
target: "last",
|
||||
directPolicy: "block",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
} as OpenClawConfig;
|
||||
await noteSecurityWarnings(cfg);
|
||||
const message = lastMessage();
|
||||
expect(message).not.toContain("Heartbeat defaults");
|
||||
expect(message).not.toContain('Heartbeat agent "ops"');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2,6 +2,7 @@ import { listChannelPlugins } from "../channels/plugins/index.js";
|
||||
import type { ChannelId } from "../channels/plugins/types.js";
|
||||
import { formatCliCommand } from "../cli/command-format.js";
|
||||
import type { OpenClawConfig, GatewayBindMode } from "../config/config.js";
|
||||
import type { AgentConfig } from "../config/types.agents.js";
|
||||
import { hasConfiguredSecretInput } from "../config/types.secrets.js";
|
||||
import { resolveGatewayAuth } from "../gateway/auth.js";
|
||||
import { isLoopbackHost, resolveGatewayBindHost } from "../gateway/net.js";
|
||||
@@ -9,6 +10,44 @@ import { resolveDmAllowState } from "../security/dm-policy-shared.js";
|
||||
import { note } from "../terminal/note.js";
|
||||
import { resolveDefaultChannelAccountContext } from "./channel-account-context.js";
|
||||
|
||||
function collectImplicitHeartbeatDirectPolicyWarnings(cfg: OpenClawConfig): string[] {
|
||||
const warnings: string[] = [];
|
||||
|
||||
const maybeWarn = (params: {
|
||||
label: string;
|
||||
heartbeat: AgentConfig["heartbeat"] | undefined;
|
||||
pathHint: string;
|
||||
}) => {
|
||||
const heartbeat = params.heartbeat;
|
||||
if (!heartbeat || heartbeat.target === undefined || heartbeat.target === "none") {
|
||||
return;
|
||||
}
|
||||
if (heartbeat.directPolicy !== undefined) {
|
||||
return;
|
||||
}
|
||||
warnings.push(
|
||||
`- ${params.label}: heartbeat delivery is configured while ${params.pathHint} is unset.`,
|
||||
' Heartbeat now allows direct/DM targets by default. Set it explicitly to "allow" or "block" to pin upgrade behavior.',
|
||||
);
|
||||
};
|
||||
|
||||
maybeWarn({
|
||||
label: "Heartbeat defaults",
|
||||
heartbeat: cfg.agents?.defaults?.heartbeat,
|
||||
pathHint: "agents.defaults.heartbeat.directPolicy",
|
||||
});
|
||||
|
||||
for (const agent of cfg.agents?.list ?? []) {
|
||||
maybeWarn({
|
||||
label: `Heartbeat agent "${agent.id}"`,
|
||||
heartbeat: agent.heartbeat,
|
||||
pathHint: `heartbeat.directPolicy for agent "${agent.id}"`,
|
||||
});
|
||||
}
|
||||
|
||||
return warnings;
|
||||
}
|
||||
|
||||
export async function noteSecurityWarnings(cfg: OpenClawConfig) {
|
||||
const warnings: string[] = [];
|
||||
const auditHint = `- Run: ${formatCliCommand("openclaw security audit --deep")}`;
|
||||
@@ -21,6 +60,8 @@ export async function noteSecurityWarnings(cfg: OpenClawConfig) {
|
||||
);
|
||||
}
|
||||
|
||||
warnings.push(...collectImplicitHeartbeatDirectPolicyWarnings(cfg));
|
||||
|
||||
// ===========================================
|
||||
// GATEWAY NETWORK EXPOSURE CHECK
|
||||
// ===========================================
|
||||
|
||||
Reference in New Issue
Block a user