diff --git a/CHANGELOG.md b/CHANGELOG.md index 766bc80fe..59660a242 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -77,6 +77,7 @@ Docs: https://docs.openclaw.ai - Providers/OpenRouter: pass through provider routing parameters from model params.provider to OpenRouter request payloads for provider selection controls. (#17148) Thanks @carrotRakko. - Providers/OpenRouter: preserve model allowlist entries containing OpenRouter preset paths (for example `openrouter/@preset/...`) by treating `/model ...@profile` auth-profile parsing as a suffix-only override. (#14120) Thanks @NotMainstream. - Cron/Auth: propagate auth-profile resolution to isolated cron sessions so provider API keys are resolved the same way as main sessions, fixing 401 errors when using providers configured via auth-profiles. (#20689) Thanks @lailoo. +- Cron/Follow-up: pass resolved `agentDir` through isolated cron and queued follow-up embedded runs so auth/profile lookups stay scoped to the correct agent directory. (#22845) Thanks @seilk. - Telegram/Webhook: keep webhook monitors alive until gateway abort signals fire, preventing false channel exits and immediate webhook auto-restart loops. - Telegram/Polling: retry recoverable setup-time network failures in monitor startup and await runner teardown before retry to avoid overlapping polling sessions. - Telegram/Polling: clear Telegram webhooks (`deleteWebhook`) before starting long-poll `getUpdates`, including retry handling for transient cleanup failures. diff --git a/src/auto-reply/reply/followup-runner.test.ts b/src/auto-reply/reply/followup-runner.test.ts index fb183d76f..0da9b1ff7 100644 --- a/src/auto-reply/reply/followup-runner.test.ts +++ b/src/auto-reply/reply/followup-runner.test.ts @@ -279,3 +279,34 @@ describe("createFollowupRunner messaging tool dedupe", () => { expect(store[sessionKey]?.outputTokens).toBe(50); }); }); + +describe("createFollowupRunner agentDir forwarding", () => { + it("passes queued run agentDir to runEmbeddedPiAgent", async () => { + runEmbeddedPiAgentMock.mockClear(); + const onBlockReply = vi.fn(async () => {}); + runEmbeddedPiAgentMock.mockResolvedValueOnce({ + payloads: [{ text: "hello world!" }], + messagingToolSentTexts: ["different message"], + meta: {}, + }); + const runner = createFollowupRunner({ + opts: { onBlockReply }, + typing: createMockTypingController(), + typingMode: "instant", + defaultModel: "anthropic/claude-opus-4-5", + }); + const agentDir = path.join("/tmp", "agent-dir"); + const queued = baseQueuedRun(); + await runner({ + ...queued, + run: { + ...queued.run, + agentDir, + }, + }); + + expect(runEmbeddedPiAgentMock).toHaveBeenCalledTimes(1); + const call = runEmbeddedPiAgentMock.mock.calls.at(-1)?.[0] as { agentDir?: string }; + expect(call?.agentDir).toBe(agentDir); + }); +}); diff --git a/src/auto-reply/reply/followup-runner.ts b/src/auto-reply/reply/followup-runner.ts index 91f9e38c2..cdae8d014 100644 --- a/src/auto-reply/reply/followup-runner.ts +++ b/src/auto-reply/reply/followup-runner.ts @@ -154,6 +154,7 @@ export function createFollowupRunner(params: { senderE164: queued.run.senderE164, senderIsOwner: queued.run.senderIsOwner, sessionFile: queued.run.sessionFile, + agentDir: queued.run.agentDir, workspaceDir: queued.run.workspaceDir, config: queued.run.config, skillsSnapshot: queued.run.skillsSnapshot, diff --git a/src/cron/isolated-agent.uses-last-non-empty-agent-text-as.test.ts b/src/cron/isolated-agent.uses-last-non-empty-agent-text-as.test.ts index d94de8a64..7842d55b5 100644 --- a/src/cron/isolated-agent.uses-last-non-empty-agent-text-as.test.ts +++ b/src/cron/isolated-agent.uses-last-non-empty-agent-text-as.test.ts @@ -185,6 +185,20 @@ describe("runCronIsolatedAgentTurn", () => { }); }); + it("passes resolved agentDir to runEmbeddedPiAgent", async () => { + await withTempHome(async (home) => { + const { res } = await runCronTurn(home, { + jobPayload: DEFAULT_AGENT_TURN_PAYLOAD, + }); + + expect(res.status).toBe("ok"); + const call = vi.mocked(runEmbeddedPiAgent).mock.calls.at(-1)?.[0] as { + agentDir?: string; + }; + expect(call?.agentDir).toBe(path.join(home, ".openclaw", "agents", "main", "agent")); + }); + }); + it("appends current time after the cron header line", async () => { await withTempHome(async (home) => { await runCronTurn(home, { diff --git a/src/cron/isolated-agent/run.ts b/src/cron/isolated-agent/run.ts index 7b9cf439b..91106a149 100644 --- a/src/cron/isolated-agent/run.ts +++ b/src/cron/isolated-agent/run.ts @@ -501,6 +501,7 @@ export async function runCronIsolatedAgentTurn(params: { messageChannel, agentAccountId: resolvedDelivery.accountId, sessionFile, + agentDir, workspaceDir, config: cfgWithAgentDefaults, skillsSnapshot,