mirror of
https://github.com/openclaw/openclaw.git
synced 2026-03-12 15:30:39 +00:00
fix(heartbeat): respect custom heartbeat prompt
This commit is contained in:
@@ -1018,6 +1018,7 @@ describe("runHeartbeatOnce", () => {
|
||||
reason?: "interval" | "wake";
|
||||
queueCronEvent?: boolean;
|
||||
replyText?: string;
|
||||
cfgOverrides?: Partial<OpenClawConfig>;
|
||||
}) {
|
||||
const tmpDir = await createCaseDir("openclaw-hb");
|
||||
const storePath = path.join(tmpDir, "sessions.json");
|
||||
@@ -1041,7 +1042,7 @@ describe("runHeartbeatOnce", () => {
|
||||
await fs.mkdir(path.join(workspaceDir, "HEARTBEAT.md"), { recursive: true });
|
||||
}
|
||||
|
||||
const cfg: OpenClawConfig = {
|
||||
const cfgBase: OpenClawConfig = {
|
||||
agents: {
|
||||
defaults: {
|
||||
workspace: workspaceDir,
|
||||
@@ -1051,6 +1052,9 @@ describe("runHeartbeatOnce", () => {
|
||||
channels: { whatsapp: { allowFrom: ["*"] } },
|
||||
session: { store: storePath },
|
||||
};
|
||||
const cfg: OpenClawConfig = params.cfgOverrides
|
||||
? { ...cfgBase, ...params.cfgOverrides }
|
||||
: cfgBase;
|
||||
const sessionKey = resolveMainSessionKey(cfg);
|
||||
await fs.writeFile(
|
||||
storePath,
|
||||
@@ -1083,7 +1087,7 @@ describe("runHeartbeatOnce", () => {
|
||||
return { res, replySpy, sendWhatsApp, workspaceDir };
|
||||
}
|
||||
|
||||
it("adds explicit workspace HEARTBEAT.md path guidance to heartbeat prompts", async () => {
|
||||
it("adds explicit workspace HEARTBEAT.md path guidance to default heartbeat prompts", async () => {
|
||||
const { res, replySpy, sendWhatsApp, workspaceDir } = await runHeartbeatFileScenario({
|
||||
fileState: "actionable",
|
||||
reason: "interval",
|
||||
@@ -1102,6 +1106,35 @@ describe("runHeartbeatOnce", () => {
|
||||
}
|
||||
});
|
||||
|
||||
it("does not mutate a custom heartbeat prompt", async () => {
|
||||
const customPrompt =
|
||||
"Read HEARTBEAT.md if it exists (workspace context). Use the system prompt only.";
|
||||
const { res, replySpy } = await runHeartbeatFileScenario({
|
||||
fileState: "actionable",
|
||||
reason: "interval",
|
||||
replyText: "Checked logs and PRs",
|
||||
cfgOverrides: {
|
||||
agents: {
|
||||
defaults: {
|
||||
heartbeat: {
|
||||
prompt: customPrompt,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
try {
|
||||
expect(res.status).toBe("ran");
|
||||
expect(replySpy).toHaveBeenCalledTimes(1);
|
||||
const calledCtx = replySpy.mock.calls[0]?.[0] as { Body?: string };
|
||||
expect(calledCtx.Body).toContain(customPrompt);
|
||||
expect(calledCtx.Body).not.toContain("Do not read docs/heartbeat.md.");
|
||||
expect(calledCtx.Body).not.toContain("use workspace file");
|
||||
} finally {
|
||||
replySpy.mockRestore();
|
||||
}
|
||||
});
|
||||
|
||||
it("applies HEARTBEAT.md gating rules across file states and triggers", async () => {
|
||||
const cases: Array<{
|
||||
name: string;
|
||||
|
||||
@@ -560,7 +560,15 @@ type HeartbeatPromptResolution = {
|
||||
hasCronEvents: boolean;
|
||||
};
|
||||
|
||||
function appendHeartbeatWorkspacePathHint(prompt: string, workspaceDir: string): string {
|
||||
function appendHeartbeatWorkspacePathHint(params: {
|
||||
prompt: string;
|
||||
workspaceDir: string;
|
||||
shouldAppend: boolean;
|
||||
}): string {
|
||||
const { prompt, workspaceDir, shouldAppend } = params;
|
||||
if (!shouldAppend) {
|
||||
return prompt;
|
||||
}
|
||||
if (!/heartbeat\.md/i.test(prompt)) {
|
||||
return prompt;
|
||||
}
|
||||
@@ -597,7 +605,16 @@ function resolveHeartbeatRunPrompt(params: {
|
||||
: hasCronEvents
|
||||
? buildCronEventPrompt(cronEvents, { deliverToUser: params.canRelayToUser })
|
||||
: resolveHeartbeatPrompt(params.cfg, params.heartbeat);
|
||||
const prompt = appendHeartbeatWorkspacePathHint(basePrompt, params.workspaceDir);
|
||||
const hasExplicitPrompt = Boolean(
|
||||
params.heartbeat?.prompt?.trim() || params.cfg.agents?.defaults?.heartbeat?.prompt?.trim(),
|
||||
);
|
||||
const prompt = appendHeartbeatWorkspacePathHint({
|
||||
prompt: basePrompt,
|
||||
workspaceDir: params.workspaceDir,
|
||||
// Only append the hint when using the built-in default heartbeat prompt.
|
||||
// If the user configured a custom prompt, do not mutate it.
|
||||
shouldAppend: !hasExplicitPrompt && !hasExecCompletion && !hasCronEvents,
|
||||
});
|
||||
|
||||
return { prompt, hasExecCompletion, hasCronEvents };
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user