mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-20 05:31:59 +08:00
fix(cron): preserve isolated agent turn payload message (#91230)
Summary: - The PR changes isolated cron agent prompt construction to read agentTurn text from `job.payload.message` and adds regression coverage for malformed dispatch messages plus SQLite-rehydrated manual runs. - PR surface: Source +8, Tests +60. Total +68 across 3 files. - Reproducibility: yes. source-level: current main interpolates `input.message` into the isolated cron prompt, ... release report supplies operator repro evidence; I did not run it locally because this review is read-only. Automerge notes: - PR branch already contained follow-up commit before automerge: fix(cron): preserve isolated agent turn payload message Validation: - ClawSweeper review passed for head4d33607efd. - Required merge gates passed before the squash merge. Prepared head SHA:4d33607efdReview: https://github.com/openclaw/openclaw/pull/91230#issuecomment-4643779241 Co-authored-by: 宇宙熊Yzx <53250620+849261680@users.noreply.github.com> Co-authored-by: clawsweeper <274271284+clawsweeper[bot]@users.noreply.github.com> Co-authored-by: clawsweeper[bot] <274271284+clawsweeper[bot]@users.noreply.github.com> Approved-by: takhoffman Co-authored-by: takhoffman <781889+takhoffman@users.noreply.github.com>
This commit is contained in:
@@ -39,6 +39,31 @@ function requireModelFallbackRequest(): {
|
||||
describe("runCronIsolatedAgentTurn — payload.fallbacks", () => {
|
||||
setupRunCronIsolatedAgentTurnSuite({ fast: true });
|
||||
|
||||
it("uses the persisted agentTurn payload message when the dispatch message is malformed", async () => {
|
||||
mockRunCronFallbackPassthrough();
|
||||
const dispatchMessage = "SERIALIZATION_PROBE should not be wrapped";
|
||||
|
||||
const result = await runCronIsolatedAgentTurn(
|
||||
makeIsolatedAgentTurnParams({
|
||||
job: makeIsolatedAgentTurnJob({
|
||||
payload: {
|
||||
kind: "agentTurn",
|
||||
message:
|
||||
"SERIALIZATION_PROBE: reply exactly with the marker token you received and nothing else.",
|
||||
},
|
||||
}),
|
||||
message: { message: dispatchMessage } as unknown as string,
|
||||
}),
|
||||
);
|
||||
|
||||
expect(result.status).toBe("ok");
|
||||
expect(runEmbeddedAgentMock).toHaveBeenCalledOnce();
|
||||
const request = runEmbeddedAgentMock.mock.calls[0]?.[0] as { prompt?: unknown } | undefined;
|
||||
expect(request?.prompt).toContain("SERIALIZATION_PROBE: reply exactly");
|
||||
expect(request?.prompt).not.toContain(dispatchMessage);
|
||||
expect(request?.prompt).not.toContain("[object Object]");
|
||||
});
|
||||
|
||||
it.each([
|
||||
{
|
||||
name: "passes payload.fallbacks as fallbacksOverride when defined",
|
||||
|
||||
@@ -456,6 +456,13 @@ type RunCronAgentTurnParams = {
|
||||
lane?: string;
|
||||
};
|
||||
|
||||
function resolveCronAgentTurnMessage(input: RunCronAgentTurnParams): string {
|
||||
if (input.job.payload.kind === "agentTurn") {
|
||||
return input.job.payload.message;
|
||||
}
|
||||
return input.message;
|
||||
}
|
||||
|
||||
type WithRunSession = (
|
||||
result: Omit<RunCronAgentTurnResult, "sessionId" | "sessionKey">,
|
||||
) => RunCronAgentTurnResult;
|
||||
@@ -765,7 +772,8 @@ async function prepareCronRunContext(params: {
|
||||
});
|
||||
|
||||
const { formattedTime, timeLine } = resolveCronStyleNow(input.cfg, now);
|
||||
const base = `[cron:${input.job.id} ${input.job.name}] ${input.message}`.trim();
|
||||
const message = resolveCronAgentTurnMessage(input);
|
||||
const base = `[cron:${input.job.id} ${input.job.name}] ${message}`.trim();
|
||||
const isExternalHook =
|
||||
hookExternalContentSource !== undefined || isExternalHookSession(baseSessionKey);
|
||||
const allowUnsafeExternalContent =
|
||||
@@ -776,7 +784,7 @@ async function prepareCronRunContext(params: {
|
||||
|
||||
if (isExternalHook) {
|
||||
const { detectSuspiciousPatterns } = await loadCronExternalContentRuntime();
|
||||
const suspiciousPatterns = detectSuspiciousPatterns(input.message);
|
||||
const suspiciousPatterns = detectSuspiciousPatterns(message);
|
||||
if (suspiciousPatterns.length > 0) {
|
||||
logWarn(
|
||||
`[security] Suspicious patterns detected in external hook content ` +
|
||||
@@ -789,7 +797,7 @@ async function prepareCronRunContext(params: {
|
||||
const { buildSafeExternalPrompt } = await loadCronExternalContentRuntime();
|
||||
const hookType = mapHookExternalContentSource(hookExternalContentSource ?? "webhook");
|
||||
const safeContent = buildSafeExternalPrompt({
|
||||
content: input.message,
|
||||
content: message,
|
||||
source: hookType,
|
||||
jobName: input.job.name,
|
||||
jobId: input.job.id,
|
||||
|
||||
@@ -264,6 +264,41 @@ describe("cron service ops regressions", () => {
|
||||
expect((staleExecuted?.state.nextRunAtMs ?? 0) > nowMs).toBe(true);
|
||||
});
|
||||
|
||||
it("passes the rehydrated agentTurn payload message to isolated manual runs", async () => {
|
||||
const store = opsRegressionFixtures.makeStorePath();
|
||||
const nowMs = Date.now();
|
||||
const marker =
|
||||
"SERIALIZATION_PROBE: reply exactly with the marker token you received and nothing else.";
|
||||
const job = createIsolatedRegressionJob({
|
||||
id: "manual-payload-message",
|
||||
name: "manual payload message",
|
||||
scheduledAt: nowMs,
|
||||
schedule: { kind: "at", at: new Date(nowMs + 3_600_000).toISOString() },
|
||||
payload: { kind: "agentTurn", message: marker },
|
||||
state: { nextRunAtMs: nowMs + 3_600_000 },
|
||||
});
|
||||
await saveCronStore(store.storePath, { version: 1, jobs: [job] });
|
||||
|
||||
const runIsolatedAgentJob = vi.fn().mockResolvedValue({ status: "ok", summary: "ok" });
|
||||
const state = createCronServiceState({
|
||||
cronEnabled: false,
|
||||
storePath: store.storePath,
|
||||
log: noopLogger,
|
||||
enqueueSystemEvent: vi.fn(),
|
||||
requestHeartbeat: vi.fn(),
|
||||
runIsolatedAgentJob,
|
||||
});
|
||||
|
||||
const runResult = await run(state, job.id, "force");
|
||||
|
||||
expect(runResult).toEqual({ ok: true, ran: true });
|
||||
expect(runIsolatedAgentJob).toHaveBeenCalledOnce();
|
||||
const [params] = requireMockCall(runIsolatedAgentJob, 0, "runIsolatedAgentJob") as [
|
||||
{ message?: unknown }?,
|
||||
];
|
||||
expect(params?.message).toBe(marker);
|
||||
});
|
||||
|
||||
it("applies timeoutSeconds to manual cron.run isolated executions", async () => {
|
||||
vi.useFakeTimers();
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user