From 722745271202d576e35797e120d9ec36335cca35 Mon Sep 17 00:00:00 2001 From: zerob13 Date: Mon, 13 Jul 2026 15:14:58 +0800 Subject: [PATCH] fix(acp): refresh direct runtime on config --- src/main/presenter/configPresenter/index.ts | 38 +++++++++- .../configPresenter/fontSizeSettings.test.ts | 73 +++++++++++++++++++ 2 files changed, 109 insertions(+), 2 deletions(-) diff --git a/src/main/presenter/configPresenter/index.ts b/src/main/presenter/configPresenter/index.ts index 9d69d6fad..259459703 100644 --- a/src/main/presenter/configPresenter/index.ts +++ b/src/main/presenter/configPresenter/index.ts @@ -45,6 +45,7 @@ import { import ElectronStore from 'electron-store' import { DEFAULT_PROVIDERS } from './providers' import path from 'path' +import { isDeepStrictEqual } from 'node:util' import { app, nativeTheme, shell, safeStorage } from 'electron' import fs from 'fs' import { CONFIG_EVENTS, MCP_EVENTS } from '@/events' @@ -205,6 +206,16 @@ const MEMORY_MAINTENANCE_TRIGGER_CONFIG_KEYS: readonly (keyof DeepChatAgentConfi 'defaultModelPreset' ] +const findChangedAcpRegistryAgentIds = ( + previousAgents: AcpRegistryAgent[], + nextAgents: AcpRegistryAgent[] +): string[] => { + const nextById = new Map(nextAgents.map((agent) => [agent.id, agent] as const)) + return previousAgents + .filter((agent) => !isDeepStrictEqual(agent, nextById.get(agent.id))) + .map((agent) => agent.id) +} + const hasMemoryMaintenanceTriggerConfigUpdate = ( updates: Partial | null | undefined ): boolean => { @@ -560,10 +571,24 @@ export class ConfigPresenter implements IConfigPresenter { path.join(this.userDataPath, 'acp-registry') ) this.syncAcpProviderEnabled(this.acpCatalogConfigAdapter.getGlobalEnabled()) + let registryAgentsBeforeInitialization: AcpRegistryAgent[] = [] + try { + registryAgentsBeforeInitialization = this.acpRegistryService.listAgents() + } catch { + // Initialization will report the missing registry snapshot below. + } void this.acpRegistryService .initialize() - .then(() => { + .then(async () => { + const registryAgents = this.acpRegistryService.listAgents() this.syncRegistryAgentsToRepository() + const changedAgentIds = findChangedAcpRegistryAgentIds( + registryAgentsBeforeInitialization, + registryAgents + ) + if (changedAgentIds.length > 0) { + await this.refreshAcpProviderAgents(changedAgentIds) + } this.notifyAcpAgentsChanged() }) .catch((error) => { @@ -2450,10 +2475,14 @@ export class ConfigPresenter implements IConfigPresenter { } async setAcpEnabled(enabled: boolean): Promise { + const enabledAgentIds = enabled ? [] : (await this.getAcpAgents()).map((agent) => agent.id) const changed = this.acpCatalogConfigAdapter.setGlobalEnabled(enabled) if (!changed) return logger.info('[ACP] setAcpEnabled: updating global toggle to', enabled) + if (!enabled && enabledAgentIds.length > 0) { + await this.refreshAcpProviderAgents(enabledAgentIds) + } this.syncAcpProviderEnabled(enabled) if (!enabled) { @@ -2486,8 +2515,13 @@ export class ConfigPresenter implements IConfigPresenter { } async refreshAcpRegistry(force = true): Promise { - await this.acpRegistryService.refresh(force) + const previousAgents = this.acpRegistryService.listAgents() + const refreshedAgents = await this.acpRegistryService.refresh(force) this.syncRegistryAgentsToRepository() + const changedAgentIds = findChangedAcpRegistryAgentIds(previousAgents, refreshedAgents) + if (changedAgentIds.length > 0) { + await this.refreshAcpProviderAgents(changedAgentIds) + } const agents = await this.listAcpRegistryAgents() this.notifyAcpAgentsChanged() return agents diff --git a/test/main/presenter/configPresenter/fontSizeSettings.test.ts b/test/main/presenter/configPresenter/fontSizeSettings.test.ts index 892b840cc..7b16c46db 100644 --- a/test/main/presenter/configPresenter/fontSizeSettings.test.ts +++ b/test/main/presenter/configPresenter/fontSizeSettings.test.ts @@ -35,6 +35,7 @@ import { eventBus } from '@/eventbus' import { CONFIG_EVENTS } from '@/events' import { ConfigPresenter } from '@/presenter/configPresenter' import { emitAgentCatalogChanged } from '@/presenter/configPresenter/eventPublishers' +import type { AcpRegistryAgent } from '@shared/presenter' function attachCatalogSink(presenter: ConfigPresenter): void { Object.assign(presenter, { @@ -222,6 +223,78 @@ describe('ConfigPresenter ACP agent notifications', () => { ]) }) + it('closes enabled direct ACP agents before disabling the compatibility provider', async () => { + const sequence: string[] = [] + const refreshAgents = vi.fn(async () => { + sequence.push('runtime-refresh') + }) + presenterMocks.getProviderInstance.mockReturnValue({ refreshAgents }) + const presenter = Object.assign(Object.create(ConfigPresenter.prototype), { + acpCatalogConfigAdapter: { + setGlobalEnabled: vi.fn(() => { + sequence.push('catalog-disable') + return true + }) + }, + getAcpAgents: vi.fn(async () => { + sequence.push('list-enabled-agents') + return [{ id: 'agent-1' }, { id: 'agent-2' }] + }), + syncAcpProviderEnabled: vi.fn(() => sequence.push('provider-disable')), + providerModelHelper: { setProviderModels: vi.fn() }, + clearProviderModelStatusCache: vi.fn(), + notifyAcpAgentsChanged: vi.fn() + }) as ConfigPresenter + + await presenter.setAcpEnabled(false) + + expect(refreshAgents).toHaveBeenCalledWith(['agent-1', 'agent-2']) + expect(sequence).toEqual([ + 'list-enabled-agents', + 'catalog-disable', + 'runtime-refresh', + 'provider-disable' + ]) + }) + + it('refreshes direct ACP agents whose registry descriptors changed', async () => { + const previousAgents: AcpRegistryAgent[] = [ + { + id: 'agent-1', + name: 'Agent 1', + version: '1.0.0', + description: 'Before', + distribution: { npx: { package: '@example/agent-1' } }, + source: 'registry', + enabled: true + }, + { + id: 'agent-2', + name: 'Agent 2', + version: '1.0.0', + distribution: { npx: { package: '@example/agent-2' } }, + source: 'registry', + enabled: true + } + ] + const refreshedAgents = [{ ...previousAgents[0], description: 'After' }, previousAgents[1]] + const refreshAgents = vi.fn(async () => undefined) + presenterMocks.getProviderInstance.mockReturnValue({ refreshAgents }) + const presenter = Object.assign(Object.create(ConfigPresenter.prototype), { + acpRegistryService: { + listAgents: vi.fn(() => previousAgents), + refresh: vi.fn(async () => refreshedAgents) + }, + syncRegistryAgentsToRepository: vi.fn(), + listAcpRegistryAgents: vi.fn(async () => refreshedAgents), + notifyAcpAgentsChanged: vi.fn() + }) as ConfigPresenter + + await presenter.refreshAcpRegistry(true) + + expect(refreshAgents).toHaveBeenCalledWith(['agent-1']) + }) + it('defers ACP startup notification until the agent repository is attached', async () => { const presenter = Object.assign(Object.create(ConfigPresenter.prototype), { agentRepository: null,