diff --git a/CHANGELOG.md b/CHANGELOG.md index b1856e721..ed7195a9b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -41,6 +41,7 @@ Docs: https://docs.openclaw.ai - Plugins/Discovery: ignore scanned extension backup/disabled directory patterns (for example `.backup-*`, `.bak`, `.disabled*`) and move updater backup directories under `.openclaw-install-backups`, preventing duplicate plugin-id collisions from archived copies. - Dev tooling: prevent `CLAUDE.md` symlink target regressions by excluding CLAUDE symlink sentinels from `oxfmt` and marking them `-text` in `.gitattributes`, so formatter/EOL normalization cannot reintroduce trailing-newline targets. Thanks @vincentkoc. - Cron: honor `cron.maxConcurrentRuns` in the timer loop so due jobs can execute up to the configured parallelism instead of always running serially. (#11595) Thanks @Takhoffman. +- Cron/Isolation: force fresh session IDs for isolated cron runs so `sessionTarget="isolated"` executions never reuse prior run context. (#23470) Thanks @echoVic. - Agents/Compaction: restore embedded compaction safeguard/context-pruning extension loading in production by wiring bundled extension factories into the resource loader instead of runtime file-path resolution. (#22349) Thanks @Glucksberg. - Feishu/Media: for inbound video messages that include both `file_key` (video) and `image_key` (thumbnail), prefer `file_key` when downloading media so video attachments are saved instead of silently failing on thumbnail keys. (#23633) - Hooks/Cron: suppress duplicate main-session events for delivered hook turns and mark `SILENT_REPLY_TOKEN` (`NO_REPLY`) early exits as delivered to prevent hook context pollution. (#20678) Thanks @JonathanWorks. diff --git a/src/cron/isolated-agent/run.skill-filter.test.ts b/src/cron/isolated-agent/run.skill-filter.test.ts index f37b08747..f1f5ac9d6 100644 --- a/src/cron/isolated-agent/run.skill-filter.test.ts +++ b/src/cron/isolated-agent/run.skill-filter.test.ts @@ -327,6 +327,16 @@ describe("runCronIsolatedAgentTurn — skill filter", () => { ]); }); + it("forces a fresh session for isolated cron runs", async () => { + const result = await runCronIsolatedAgentTurn(makeParams()); + + expect(result.status).toBe("ok"); + expect(resolveCronSessionMock).toHaveBeenCalledOnce(); + expect(resolveCronSessionMock.mock.calls[0]?.[0]).toMatchObject({ + forceNew: true, + }); + }); + it("reuses cached snapshot when version and normalized skillFilter are unchanged", async () => { resolveAgentSkillsFilterMock.mockReturnValue([" weather ", "meme-factory", "weather"]); resolveCronSessionMock.mockReturnValue({ diff --git a/src/cron/isolated-agent/run.ts b/src/cron/isolated-agent/run.ts index fce4174da..7b9cf439b 100644 --- a/src/cron/isolated-agent/run.ts +++ b/src/cron/isolated-agent/run.ts @@ -270,6 +270,8 @@ export async function runCronIsolatedAgentTurn(params: { sessionKey: agentSessionKey, agentId, nowMs: now, + // Isolated cron runs must not carry prior turn context across executions. + forceNew: params.job.sessionTarget === "isolated", }); const runSessionId = cronSession.sessionEntry.sessionId; const runSessionKey = baseSessionKey.startsWith("cron:")