diff --git a/src/auto-reply/reply/channel-context.ts b/src/auto-reply/reply/channel-context.ts index 39cc45b1b2f..7b1ca290102 100644 --- a/src/auto-reply/reply/channel-context.ts +++ b/src/auto-reply/reply/channel-context.ts @@ -1,3 +1,6 @@ +import type { OpenClawConfig } from "../../config/config.js"; +import { getActivePluginChannelRegistry } from "../../plugins/runtime.js"; + type CommandSurfaceParams = { ctx: { OriginatingChannel?: string; @@ -11,9 +14,16 @@ type CommandSurfaceParams = { }; type ChannelAccountParams = { + cfg: OpenClawConfig; ctx: { + OriginatingChannel?: string; + Surface?: string; + Provider?: string; AccountId?: string; }; + command: { + channel?: string; + }; }; export function resolveCommandSurfaceChannel(params: CommandSurfaceParams): string { @@ -29,5 +39,13 @@ export function resolveCommandSurfaceChannel(params: CommandSurfaceParams): stri export function resolveChannelAccountId(params: ChannelAccountParams): string { const accountId = typeof params.ctx.AccountId === "string" ? params.ctx.AccountId.trim() : ""; - return accountId || "default"; + if (accountId) { + return accountId; + } + const channel = resolveCommandSurfaceChannel(params); + const plugin = getActivePluginChannelRegistry()?.channels.find( + (entry) => entry.plugin.id === channel, + )?.plugin; + const configuredDefault = plugin?.config.defaultAccountId?.(params.cfg)?.trim(); + return configuredDefault || "default"; } diff --git a/src/auto-reply/reply/commands-subagents-focus.test.ts b/src/auto-reply/reply/commands-subagents-focus.test.ts index e6b48828fb9..6d246fbd532 100644 --- a/src/auto-reply/reply/commands-subagents-focus.test.ts +++ b/src/auto-reply/reply/commands-subagents-focus.test.ts @@ -227,6 +227,18 @@ function createThreadCommandParams(commandBody: string) { return params; } +function createThreadCommandParamsWithoutAccountId(commandBody: string, cfg: OpenClawConfig = baseCfg) { + const params = buildCommandTestParams(commandBody, cfg, { + Provider: THREAD_CHANNEL, + Surface: THREAD_CHANNEL, + OriginatingChannel: THREAD_CHANNEL, + OriginatingTo: "channel:parent-1", + MessageThreadId: "thread-1", + }); + params.command.senderId = "user-1"; + return params; +} + function createTopicCommandParams(commandBody: string) { const params = buildCommandTestParams(commandBody, baseCfg, { Provider: TOPIC_CHANNEL, @@ -676,6 +688,47 @@ describe("/focus, /unfocus, /agents", () => { expect(text).toContain("binding:thread-persistent-1"); }); + it("/agents honors the plugin default account when AccountId is omitted", async () => { + hoisted.runtimeChannelRegistry.channels[0]!.plugin.config.defaultAccountId = () => "work"; + addSubagentRunForTests({ + runId: "run-work-1", + childSessionKey: "agent:main:subagent:work-1", + requesterSessionKey: "agent:main:main", + requesterDisplayKey: "main", + task: "work task", + cleanup: "keep", + label: "work-1", + createdAt: Date.now(), + }); + + hoisted.sessionBindingListBySessionMock.mockImplementation((sessionKey: string) => { + if (sessionKey !== "agent:main:subagent:work-1") { + return []; + } + return [ + createSessionBindingRecord({ + bindingId: "work:thread-work-1", + targetSessionKey: sessionKey, + targetKind: "subagent", + conversation: { + channel: THREAD_CHANNEL, + accountId: "work", + conversationId: "thread-work-1", + }, + }), + ]; + }); + + const result = await handleSubagentsCommand( + createThreadCommandParamsWithoutAccountId("/agents"), + true, + ); + const text = result?.reply?.text ?? ""; + + expect(text).toContain("work-1"); + expect(text).toContain("binding:thread-work-1"); + }); + it("/focus rejects unsupported channels", async () => { const params = buildCommandTestParams("/focus codex-acp", baseCfg); const result = await handleSubagentsCommand(params, true);