From 11a6f6db2e9bcd68f715effcc42d21afd15c8c23 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sun, 25 Jan 2026 13:05:15 +0000 Subject: [PATCH] fix: wire requester agent override for cron tools --- src/agents/clawdbot-tools.agents.test.ts | 40 ++++++++++++++++ ...n-normalizes-allowlisted-agent-ids.test.ts | 46 +++++++++++++++++++ src/agents/clawdbot-tools.ts | 10 ++-- src/agents/pi-tools.ts | 1 + src/agents/tools/agents-list-tool.ts | 4 +- .../reply/get-reply-inline-actions.ts | 1 + src/gateway/tools-invoke-http.ts | 1 + 7 files changed, 98 insertions(+), 5 deletions(-) diff --git a/src/agents/clawdbot-tools.agents.test.ts b/src/agents/clawdbot-tools.agents.test.ts index 0ae300bfb..bf0a588ef 100644 --- a/src/agents/clawdbot-tools.agents.test.ts +++ b/src/agents/clawdbot-tools.agents.test.ts @@ -157,4 +157,44 @@ describe("agents_list", () => { const research = agents?.find((agent) => agent.id === "research"); expect(research?.configured).toBe(false); }); + + it("uses requesterAgentIdOverride when resolving allowlists", async () => { + configOverride = { + session: { + mainKey: "main", + scope: "per-sender", + }, + agents: { + list: [ + { + id: "cron-owner", + subagents: { + allowAgents: ["research"], + }, + }, + { + id: "research", + name: "Research", + }, + ], + }, + }; + + const tool = createClawdbotTools({ + agentSessionKey: "cron:job-1", + requesterAgentIdOverride: "cron-owner", + }).find((candidate) => candidate.name === "agents_list"); + if (!tool) throw new Error("missing agents_list tool"); + + const result = await tool.execute("call5", {}); + const agents = ( + result.details as { + agents?: Array<{ id: string }>; + } + ).agents; + expect(agents?.map((agent) => agent.id)).toEqual(["cron-owner", "research"]); + expect(result.details).toMatchObject({ + requester: "cron-owner", + }); + }); }); diff --git a/src/agents/clawdbot-tools.subagents.sessions-spawn-normalizes-allowlisted-agent-ids.test.ts b/src/agents/clawdbot-tools.subagents.sessions-spawn-normalizes-allowlisted-agent-ids.test.ts index 3dbfb02b4..029fb3d39 100644 --- a/src/agents/clawdbot-tools.subagents.sessions-spawn-normalizes-allowlisted-agent-ids.test.ts +++ b/src/agents/clawdbot-tools.subagents.sessions-spawn-normalizes-allowlisted-agent-ids.test.ts @@ -87,6 +87,52 @@ describe("clawdbot-tools: subagents", () => { }); expect(childSessionKey?.startsWith("agent:research:subagent:")).toBe(true); }); + + it("sessions_spawn honors requesterAgentIdOverride for cron sessions", async () => { + resetSubagentRegistryForTests(); + callGatewayMock.mockReset(); + configOverride = { + session: { + mainKey: "main", + scope: "per-sender", + }, + agents: { + list: [ + { + id: "cron-owner", + subagents: { + allowAgents: ["research"], + }, + }, + ], + }, + }; + + callGatewayMock.mockImplementation(async (opts: unknown) => { + const request = opts as { method?: string; params?: unknown }; + if (request.method === "agent") { + return { runId: "run-2", status: "accepted", acceptedAt: 5200 }; + } + return {}; + }); + + const tool = createClawdbotTools({ + agentSessionKey: "cron:job-1", + requesterAgentIdOverride: "cron-owner", + agentChannel: "whatsapp", + }).find((candidate) => candidate.name === "sessions_spawn"); + if (!tool) throw new Error("missing sessions_spawn tool"); + + const result = await tool.execute("call11", { + task: "do thing", + agentId: "research", + }); + + expect(result.details).toMatchObject({ + status: "accepted", + runId: "run-2", + }); + }); it("sessions_spawn forbids cross-agent spawning when not allowed", async () => { resetSubagentRegistryForTests(); callGatewayMock.mockReset(); diff --git a/src/agents/clawdbot-tools.ts b/src/agents/clawdbot-tools.ts index b420cad6f..bfa3959f2 100644 --- a/src/agents/clawdbot-tools.ts +++ b/src/agents/clawdbot-tools.ts @@ -150,10 +150,12 @@ export function createClawdbotTools(options?: { config: options?.config, workspaceDir: options?.workspaceDir, agentDir: options?.agentDir, - agentId: resolveSessionAgentId({ - sessionKey: options?.agentSessionKey, - config: options?.config, - }), + agentId: + options?.requesterAgentIdOverride ?? + resolveSessionAgentId({ + sessionKey: options?.agentSessionKey, + config: options?.config, + }), sessionKey: options?.agentSessionKey, messageChannel: options?.agentChannel, agentAccountId: options?.agentAccountId, diff --git a/src/agents/pi-tools.ts b/src/agents/pi-tools.ts index bd745da03..ae346ae28 100644 --- a/src/agents/pi-tools.ts +++ b/src/agents/pi-tools.ts @@ -293,6 +293,7 @@ export function createClawdbotCodingTools(options?: { agentGroupChannel: options?.groupChannel ?? null, agentGroupSpace: options?.groupSpace ?? null, agentDir: options?.agentDir, + requesterAgentIdOverride: agentId, sandboxRoot, workspaceDir: options?.workspaceDir, sandboxed: !!sandbox, diff --git a/src/agents/tools/agents-list-tool.ts b/src/agents/tools/agents-list-tool.ts index fe64e9558..28f136844 100644 --- a/src/agents/tools/agents-list-tool.ts +++ b/src/agents/tools/agents-list-tool.ts @@ -41,7 +41,9 @@ export function createAgentsListTool(opts?: { }) : alias; const requesterAgentId = normalizeAgentId( - opts?.requesterAgentIdOverride ?? parseAgentSessionKey(requesterInternalKey)?.agentId ?? DEFAULT_AGENT_ID, + opts?.requesterAgentIdOverride ?? + parseAgentSessionKey(requesterInternalKey)?.agentId ?? + DEFAULT_AGENT_ID, ); const allowAgents = resolveAgentConfig(cfg, requesterAgentId)?.subagents?.allowAgents ?? []; diff --git a/src/auto-reply/reply/get-reply-inline-actions.ts b/src/auto-reply/reply/get-reply-inline-actions.ts index aa41858d5..3bb853104 100644 --- a/src/auto-reply/reply/get-reply-inline-actions.ts +++ b/src/auto-reply/reply/get-reply-inline-actions.ts @@ -170,6 +170,7 @@ export async function handleInlineActions(params: { agentAccountId: (ctx as { AccountId?: string }).AccountId, agentTo: ctx.OriginatingTo ?? ctx.To, agentThreadId: ctx.MessageThreadId ?? undefined, + requesterAgentIdOverride: agentId, agentDir, workspaceDir, config: cfg, diff --git a/src/gateway/tools-invoke-http.ts b/src/gateway/tools-invoke-http.ts index 80e2f295e..881de7973 100644 --- a/src/gateway/tools-invoke-http.ts +++ b/src/gateway/tools-invoke-http.ts @@ -148,6 +148,7 @@ export async function handleToolsInvokeHttpRequest( agentSessionKey: sessionKey, agentChannel: messageChannel ?? undefined, agentAccountId: accountId, + requesterAgentIdOverride: agentId, config: cfg, pluginToolAllowlist: collectExplicitAllowlist([ profilePolicy,