fix(cron): preserve untrusted awareness event labels (#68210)

* fix(cron): preserve untrusted awareness event labels

Keep isolated cron awareness summaries untrusted when they are promoted into the main session, and forward explicit trust downgrades through the gateway cron wrapper. Add focused regression coverage for both paths.

* changelog: note cron awareness untrusted-label preservation (#68210)
This commit is contained in:
Devin Robison
2026-04-17 12:43:48 -06:00
committed by GitHub
parent 2745e5b3bd
commit f61896b03c
6 changed files with 50 additions and 2 deletions

View File

@@ -32,6 +32,7 @@ Docs: https://docs.openclaw.ai
- Models status/OAuth health: align OAuth health reporting with the same effective credential view runtime uses, so expired refreshable sessions stop showing healthy by default and fresher imported Codex CLI credentials surface correctly in `models status`, doctor, and gateway auth status. Thanks @vincentkoc.
- Twitch/setup: load Twitch through the bundled setup-entry discovery path and keep setup/status account detection aligned with runtime config. (#68008) Thanks @gumadeiras.
- Feishu/card actions: resolve card-action chat type from the Feishu chat API when stored context is missing, preferring `chat_mode` over `chat_type`, so DM-originated card actions no longer bypass `dmPolicy` by falling through to the group handling path. (#68201)
- Cron/isolated-agent: preserve `trusted: false` on isolated cron awareness events mirrored into the main session, and forward the optional `trusted` flag through the gateway cron wrapper so explicit trust downgrades survive session-key scoping. (#68210)
## 2026.4.15

View File

@@ -324,6 +324,7 @@ describe("dispatchCronDelivery — double-announce guard", () => {
expect(enqueueSystemEvent).toHaveBeenCalledWith("Morning briefing complete.", {
sessionKey: "agent:main:main",
contextKey: "cron-direct-delivery:v1:run-123:telegram::123456:",
trusted: false,
});
});

View File

@@ -351,6 +351,7 @@ async function queueCronAwarenessSystemEvent(params: {
agentId: params.agentId,
}),
contextKey: params.deliveryIdempotencyKey,
trusted: false,
});
} catch (err) {
await logCronDeliveryWarn(

View File

@@ -62,7 +62,7 @@ export type CronServiceDeps = {
maxMissedJobsPerRestart?: number;
enqueueSystemEvent: (
text: string,
opts?: { agentId?: string; sessionKey?: string; contextKey?: string },
opts?: { agentId?: string; sessionKey?: string; contextKey?: string; trusted?: boolean },
) => void;
requestHeartbeatNow: (opts?: { reason?: string; agentId?: string; sessionKey?: string }) => void;
runHeartbeatOnce?: (opts?: {

View File

@@ -140,6 +140,47 @@ describe("buildGatewayCronService", () => {
}
});
it("preserves trust downgrades when cron enqueues system events", () => {
const cfg = createCronConfig("server-cron-untrusted");
loadConfigMock.mockReturnValue(cfg);
const state = buildGatewayCronService({
cfg,
deps: {} as CliDeps,
broadcast: () => {},
});
try {
const cronDeps = (
state.cron as unknown as {
state?: {
deps?: {
enqueueSystemEvent?: (optsText: string, opts?: {
agentId?: string;
sessionKey?: string;
contextKey?: string;
trusted?: boolean;
}) => void;
};
};
}
).state?.deps;
cronDeps?.enqueueSystemEvent?.("hello", {
sessionKey: "discord:channel:ops",
contextKey: "cron:test",
trusted: false,
});
expect(enqueueSystemEventMock).toHaveBeenCalledWith("hello", {
sessionKey: "agent:main:discord:channel:ops",
contextKey: "cron:test",
trusted: false,
});
} finally {
state.cron.stop();
}
});
it("blocks private webhook URLs via SSRF-guarded fetch", async () => {
const cfg = createCronConfig("server-cron-ssrf");
loadConfigMock.mockReturnValue(cfg);

View File

@@ -285,7 +285,11 @@ export function buildGatewayCronService(params: {
agentId,
requestedSessionKey: opts?.sessionKey,
});
enqueueSystemEvent(text, { sessionKey, contextKey: opts?.contextKey });
enqueueSystemEvent(text, {
sessionKey,
contextKey: opts?.contextKey,
trusted: opts?.trusted,
});
},
requestHeartbeatNow: (opts) => {
const { agentId, sessionKey } = resolveCronWakeTarget(opts);