diff --git a/electron/utils/agent-config.ts b/electron/utils/agent-config.ts index fd6ae31e1..64637ac95 100644 --- a/electron/utils/agent-config.ts +++ b/electron/utils/agent-config.ts @@ -297,30 +297,6 @@ function upsertBindingsForChannel( return nextBindings.length > 0 ? nextBindings : undefined; } -function assertAgentNotBoundToOtherChannel( - bindings: unknown, - agentId: string, - nextChannelType: string, -): void { - if (!Array.isArray(bindings)) return; - const normalizedAgentId = normalizeAgentIdForBinding(agentId); - if (!normalizedAgentId) return; - - const conflictChannels = new Set(); - for (const binding of bindings) { - if (!isChannelBinding(binding)) continue; - if (normalizeAgentIdForBinding(binding.agentId || '') !== normalizedAgentId) continue; - const boundChannel = binding.match?.channel; - if (!boundChannel || boundChannel === nextChannelType) continue; - conflictChannels.add(boundChannel); - } - - if (conflictChannels.size > 0) { - const channels = Array.from(conflictChannels).sort().join(', '); - throw new Error(`Agent "${agentId}" is already bound to channel(s): ${channels}. One agent can only bind one channel.`); - } -} - async function listExistingAgentIdsOnDisk(): Promise> { const ids = new Set(); const agentsDir = join(getOpenClawConfigDir(), 'agents'); @@ -477,7 +453,7 @@ async function buildSnapshotFromConfig(config: AgentConfigDocument): Promise { }; expect(feishu.accounts?.test2).toBeDefined(); }); + + it('allows the same agent to bind multiple different channels', async () => { + await writeOpenClawJson({ + agents: { + list: [ + { id: 'main', name: 'Main', default: true }, + ], + }, + channels: { + feishu: { enabled: true }, + telegram: { enabled: true }, + }, + }); + + const { assignChannelAccountToAgent, listAgentsSnapshot } = await import('@electron/utils/agent-config'); + + await assignChannelAccountToAgent('main', 'feishu', 'default'); + await assignChannelAccountToAgent('main', 'telegram', 'default'); + + const snapshot = await listAgentsSnapshot(); + expect(snapshot.channelAccountOwners['feishu:default']).toBe('main'); + expect(snapshot.channelAccountOwners['telegram:default']).toBe('main'); + }); + + it('replaces previous account binding for the same agent and channel', async () => { + await writeOpenClawJson({ + agents: { + list: [ + { id: 'main', name: 'Main', default: true }, + ], + }, + channels: { + feishu: { + enabled: true, + defaultAccount: 'default', + accounts: { + default: { enabled: true, appId: 'main-app' }, + alt: { enabled: true, appId: 'alt-app' }, + }, + }, + }, + }); + + const { assignChannelAccountToAgent, listAgentsSnapshot } = await import('@electron/utils/agent-config'); + + await assignChannelAccountToAgent('main', 'feishu', 'default'); + await assignChannelAccountToAgent('main', 'feishu', 'alt'); + + const snapshot = await listAgentsSnapshot(); + expect(snapshot.channelAccountOwners['feishu:default']).toBeUndefined(); + expect(snapshot.channelAccountOwners['feishu:alt']).toBe('main'); + }); + + it('keeps a single owner for the same channel account', async () => { + await writeOpenClawJson({ + agents: { + list: [ + { id: 'main', name: 'Main', default: true }, + { id: 'test2', name: 'test2' }, + ], + }, + channels: { + feishu: { + enabled: true, + accounts: { + default: { enabled: true, appId: 'main-app' }, + }, + }, + }, + }); + + const { assignChannelAccountToAgent, listAgentsSnapshot } = await import('@electron/utils/agent-config'); + + await assignChannelAccountToAgent('main', 'feishu', 'default'); + await assignChannelAccountToAgent('test2', 'feishu', 'default'); + + const snapshot = await listAgentsSnapshot(); + expect(snapshot.channelAccountOwners['feishu:default']).toBe('test2'); + }); + + it('can clear one channel account binding without affecting another channel on the same agent', async () => { + await writeOpenClawJson({ + agents: { + list: [ + { id: 'main', name: 'Main', default: true }, + ], + }, + channels: { + feishu: { enabled: true }, + telegram: { enabled: true }, + }, + }); + + const { assignChannelAccountToAgent, clearChannelBinding, listAgentsSnapshot } = await import('@electron/utils/agent-config'); + + await assignChannelAccountToAgent('main', 'feishu', 'default'); + await assignChannelAccountToAgent('main', 'telegram', 'default'); + await clearChannelBinding('feishu', 'default'); + + const snapshot = await listAgentsSnapshot(); + expect(snapshot.channelAccountOwners['feishu:default']).toBeUndefined(); + expect(snapshot.channelAccountOwners['telegram:default']).toBe('main'); + }); });