Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 36 additions & 2 deletions src/main/presenter/configPresenter/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -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<DeepChatAgentConfig> | null | undefined
): boolean => {
Expand Down Expand Up @@ -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) => {
Expand Down Expand Up @@ -2450,10 +2475,14 @@ export class ConfigPresenter implements IConfigPresenter {
}

async setAcpEnabled(enabled: boolean): Promise<void> {
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) {
Expand Down Expand Up @@ -2486,8 +2515,13 @@ export class ConfigPresenter implements IConfigPresenter {
}

async refreshAcpRegistry(force = true): Promise<AcpRegistryAgent[]> {
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
Expand Down
73 changes: 73 additions & 0 deletions test/main/presenter/configPresenter/fontSizeSettings.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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, {
Expand Down Expand Up @@ -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,
Expand Down
Loading