Skip to content

Commit a98660e

Browse files
authored
fix(cron): preserve runtime snapshot for isolated delivery
Fix isolated cron delivery so agent-default derivation keeps using the paired runtime config snapshot, preserving resolved channel credentials such as Discord SecretRefs. Fixes #86545.
1 parent c55bee5 commit a98660e

2 files changed

Lines changed: 77 additions & 6 deletions

File tree

src/cron/isolated-agent.direct-delivery-core-channels.test.ts

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import "./isolated-agent.mocks.js";
2-
import { beforeEach, describe, expect, it } from "vitest";
2+
import { afterEach, beforeEach, describe, expect, it } from "vitest";
33
import { runSubagentAnnounceFlow } from "../agents/subagent-announce.js";
44
import type { ChannelOutboundAdapter, ChannelOutboundContext } from "../channels/plugins/types.js";
55
import type { CliDeps } from "../cli/deps.js";
6+
import { clearRuntimeConfigSnapshot, setRuntimeConfigSnapshot } from "../config/config.js";
67
import { resolveOutboundSendDep } from "../infra/outbound/send-deps.js";
78
import { setActivePluginRegistry } from "../plugins/runtime.js";
89
import { createOutboundTestPlugin, createTestRegistry } from "../test-utils/channel-plugins.js";
@@ -297,6 +298,10 @@ describe("runCronIsolatedAgentTurn core-channel direct delivery", () => {
297298
);
298299
});
299300

301+
afterEach(() => {
302+
clearRuntimeConfigSnapshot();
303+
});
304+
300305
for (const testCase of CASES) {
301306
it(`routes ${testCase.name} text-only announce delivery through the outbound adapter`, async () => {
302307
await expectCoreChannelAnnounceDelivery({
@@ -316,6 +321,51 @@ describe("runCronIsolatedAgentTurn core-channel direct delivery", () => {
316321
});
317322

318323
if (testCase.channel === "discord") {
324+
it("keeps isolated Discord delivery on the active runtime snapshot after agent-default derivation", async () => {
325+
await withTempCronHome(async (home) => {
326+
const storePath = await writeSessionStore(home, { lastProvider: "webchat", lastTo: "" });
327+
const sourceCfg = makeCfg(home, storePath, {
328+
channels: {
329+
discord: {
330+
accounts: {
331+
default: {
332+
token: { provider: "default", source: "env", id: "DISCORD_BOT_TOKEN" },
333+
},
334+
},
335+
},
336+
},
337+
});
338+
const runtimeCfg = makeCfg(home, storePath, {
339+
channels: {
340+
discord: {
341+
accounts: { default: { token: "resolved-discord-token" } },
342+
},
343+
},
344+
});
345+
setRuntimeConfigSnapshot(runtimeCfg, sourceCfg);
346+
const deps = createCliDeps();
347+
mockAgentPayloads([{ text: "hello from cron" }]);
348+
349+
const res = await runExplicitAnnounceTurn({
350+
cfg: sourceCfg,
351+
deps,
352+
channel: "discord",
353+
to: testCase.to,
354+
});
355+
356+
expect(res.status).toBe("ok");
357+
expect(res.delivered).toBe(true);
358+
expect(deps.sendMessageDiscord).toHaveBeenCalledTimes(1);
359+
expect(deps.sendMessageDiscord).toHaveBeenCalledWith(
360+
testCase.expectedTo,
361+
"hello from cron",
362+
expect.objectContaining({
363+
cfg: expect.objectContaining({ channels: runtimeCfg.channels }),
364+
}),
365+
);
366+
});
367+
});
368+
319369
it("collapses Discord text-only announce delivery to the final assistant text", async () => {
320370
await expectCoreChannelAnnounceDelivery({
321371
testCase,

src/cron/isolated-agent/run.ts

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,11 @@ import { retireSessionMcpRuntime } from "../../agents/pi-bundle-mcp-tools.js";
55
import type { SkillSnapshot } from "../../agents/skills.js";
66
import type { ThinkLevel } from "../../auto-reply/thinking.js";
77
import type { CliDeps } from "../../cli/outbound-send-deps.js";
8+
import {
9+
getRuntimeConfigSnapshot,
10+
getRuntimeConfigSourceSnapshot,
11+
selectApplicableRuntimeConfig,
12+
} from "../../config/config.js";
813
import type { AgentDefaultsConfig } from "../../config/types.agent-defaults.js";
914
import type { OpenClawConfig } from "../../config/types.openclaw.js";
1015
import { clearAgentRunContext } from "../../infra/agent-events.js";
@@ -480,12 +485,28 @@ type CronPreparationResult =
480485
| { ok: true; context: PreparedCronRunContext }
481486
| { ok: false; result: RunCronAgentTurnResult };
482487

488+
function resolveCronActiveRuntimeConfig(cfg: OpenClawConfig): OpenClawConfig {
489+
const runtimeConfig = getRuntimeConfigSnapshot();
490+
const runtimeSourceConfig = getRuntimeConfigSourceSnapshot();
491+
if (!runtimeConfig || !runtimeSourceConfig) {
492+
return cfg;
493+
}
494+
return (
495+
selectApplicableRuntimeConfig({
496+
inputConfig: cfg,
497+
runtimeConfig,
498+
runtimeSourceConfig,
499+
}) ?? cfg
500+
);
501+
}
502+
483503
async function prepareCronRunContext(params: {
484504
input: RunCronAgentTurnParams;
485505
isFastTestEnv: boolean;
486506
}): Promise<CronPreparationResult> {
487507
const { input } = params;
488-
const defaultAgentId = resolveDefaultAgentId(input.cfg);
508+
const runtimeCfg = resolveCronActiveRuntimeConfig(input.cfg);
509+
const defaultAgentId = resolveDefaultAgentId(runtimeCfg);
489510
const requestedAgentId =
490511
typeof input.agentId === "string" && input.agentId.trim()
491512
? input.agentId
@@ -494,16 +515,16 @@ async function prepareCronRunContext(params: {
494515
: undefined;
495516
const normalizedRequested = requestedAgentId ? normalizeAgentId(requestedAgentId) : undefined;
496517
const agentConfigOverride = normalizedRequested
497-
? resolveAgentConfig(input.cfg, normalizedRequested)
518+
? resolveAgentConfig(runtimeCfg, normalizedRequested)
498519
: undefined;
499520
const agentId = normalizedRequested ?? defaultAgentId;
500521
const agentCfg: AgentDefaultsConfig = buildCronAgentDefaultsConfig({
501-
defaults: input.cfg.agents?.defaults,
522+
defaults: runtimeCfg.agents?.defaults,
502523
agentConfigOverride,
503524
});
504525
const cfgWithAgentDefaults: OpenClawConfig = {
505-
...input.cfg,
506-
agents: Object.assign({}, input.cfg.agents, { defaults: agentCfg }),
526+
...runtimeCfg,
527+
agents: Object.assign({}, runtimeCfg.agents, { defaults: agentCfg }),
507528
};
508529
let catalog: Awaited<ReturnType<CronModelCatalogRuntime["loadModelCatalog"]>> | undefined;
509530
const loadCatalog = async () => {

0 commit comments

Comments
 (0)