fix(heartbeat): classify untrusted exec completions

This commit is contained in:
Ayaan Zaidi
2026-04-25 09:03:11 +05:30
parent 724692bb8c
commit a8f9e29e1a
2 changed files with 19 additions and 6 deletions

View File

@@ -347,7 +347,7 @@ describe("Ghost reminder bug (issue #13317)", () => {
reason: "exec-event",
target: "none",
enqueue: (sessionKey) => {
enqueueSystemEvent("exec finished: deploy succeeded", { sessionKey });
enqueueSystemEvent("exec finished: deploy succeeded", { sessionKey, trusted: false });
},
});
@@ -358,6 +358,23 @@ describe("Ghost reminder bug (issue #13317)", () => {
expect(sendTelegram).not.toHaveBeenCalled();
});
it("includes untrusted exec completion details in user-relay prompts", async () => {
const { result, sendTelegram, calledCtx } = await runHeartbeatCase({
tmpPrefix: "openclaw-exec-untrusted-relay-",
replyText: "Deploy succeeded",
reason: "exec-event",
enqueue: (sessionKey) => {
enqueueSystemEvent("exec finished: deploy succeeded", { sessionKey, trusted: false });
},
});
expect(result.status).toBe("ran");
expect(calledCtx?.Provider).toBe("exec-event");
expect(calledCtx?.ForceSenderIsOwnerFalse).toBe(true);
expect(calledCtx?.Body).toContain("exec finished: deploy succeeded");
expect(sendTelegram).toHaveBeenCalled();
});
it("classifies hook:wake exec completions as exec-event prompts", async () => {
const { result, sendTelegram, calledCtx } = await runHeartbeatCase({
tmpPrefix: "openclaw-hook-exec-",

View File

@@ -676,13 +676,12 @@ function resolveHeartbeatRunPrompt(params: {
.map((event) => event.text);
const execEvents = params.preflight.shouldInspectPendingEvents
? pendingEventEntries
.filter((event) => event.trusted !== false && isExecCompletionEvent(event.text))
.filter((event) => isExecCompletionEvent(event.text))
.map((event) => event.text)
: [];
const hasExecCompletion = execEvents.length > 0;
const hasCronEvents = cronEvents.length > 0;
// If tasks are defined, build a batched prompt with due tasks
if (params.preflight.tasks && params.preflight.tasks.length > 0) {
const tasks = params.preflight.tasks;
const dueTasks = tasks.filter((task) =>
@@ -701,7 +700,6 @@ ${taskList}
After completing all due tasks, reply HEARTBEAT_OK.`;
// Preserve HEARTBEAT.md directives (non-task content)
if (params.heartbeatFileContent) {
const directives = params.heartbeatFileContent
.replace(/^[\s\S]*?^tasks:[\s\S]*?(?=^[^\s]|^$)/m, "")
@@ -712,11 +710,9 @@ After completing all due tasks, reply HEARTBEAT_OK.`;
}
return { prompt, hasExecCompletion: false, hasCronEvents: false };
}
// No tasks due - skip this heartbeat to avoid wasteful API calls
return { prompt: null, hasExecCompletion: false, hasCronEvents: false };
}
// Fallback to original behavior
const basePrompt = hasExecCompletion
? buildExecEventPrompt(execEvents, { deliverToUser: params.canRelayToUser })
: hasCronEvents