fix: remove redundant confirmation for configuration changes

Configuration apply previously required a separate native confirmation even in Agent Runtime Execute mode, and failed in clients without an approval callback. Use existing work-mode and tool authorization instead, preserving Ask read-only access and request-scoped plan validation. Update regression tests, tool descriptions and documentation.

Validation: 267 focused tests passed; typecheck and changed-file lint passed. Full tests timed out with failures in other modules; repository lint reported errors in unchanged files.

Release note: Execute 模式下通过 Agent Runtime 修改 GoodBuddy 配置不再额外弹出确认框,Ask 模式仍保持只读。
This commit is contained in:
mesalogo
2026-10-02 19:39:44 +08:00
parent dc635d185e
commit 02a6f5c4e7
14 changed files with 152 additions and 247 deletions
+56 -5
View File
@@ -26,6 +26,9 @@ import { browserTabIdSchema } from '../../shared/contracts'
import { BrowserService } from '../browser/browser-service'
import { createHash } from 'node:crypto'
import { DesktopDiagnostics, type DesktopDiagnosticFailureObserver } from '../desktop-diagnostics'
import { ApplicationSettingsStore } from '../application-settings-store'
import { CapabilityService } from '../capabilities/capability-service'
import { GoodBuddyConfigService } from '../goodbuddy-config-service'
const firstLibraryId = '11111111-1111-4111-8111-111111111111'
const secondLibraryId = '22222222-2222-4222-8222-222222222222'
@@ -640,7 +643,6 @@ describe('KnowledgeMcpGateway', () => {
'none',
{ access: 'read', workspacePath: process.cwd() }
)!
const authorizeApply = vi.fn(async () => true)
const writeToken = gateway.grant(
'config-write',
[],
@@ -648,8 +650,7 @@ describe('KnowledgeMcpGateway', () => {
'none',
{
access: 'write',
workspacePath: process.cwd(),
authorizeApply
workspacePath: process.cwd()
}
)!
@@ -685,13 +686,63 @@ describe('KnowledgeMcpGateway', () => {
expect(configService.apply).toHaveBeenCalledWith(
'config-write',
expect.any(Object),
expect.any(AbortSignal),
authorizeApply
expect.any(AbortSignal)
)
gateway.revoke(writeToken)
expect(configService.revokeRequest).toHaveBeenCalledWith('config-write')
})
it('applies a real configuration plan over MCP without an authorizer', async () => {
const directory = await mkdtemp(join(tmpdir(), 'config-gateway-'))
temporaryDirectories.push(directory)
const application = new ApplicationSettingsStore(join(directory, 'application.json'))
const capabilities = new CapabilityService(
join(directory, 'capabilities.json'),
join(directory, 'builtin'),
join(directory, 'imported'),
{
isAvailable: () => true,
encrypt: value => Buffer.from(value),
decrypt: value => value.toString()
}
)
const configService = new GoodBuddyConfigService(application, capabilities)
const gateway = new KnowledgeMcpGateway(createService().service, { configService })
gateways.push(gateway)
const token = gateway.grant('config-mcp', [], new AbortController().signal, 'none', {
access: 'write', workspacePath: directory
})!
await gateway.start()
const client = new Client({ name: 'config-apply-test', version: '1.0.0' })
try {
await client.connect(new StreamableHTTPClientTransport(new URL(gateway.getEndpoint()!), {
requestInit: { headers: { Authorization: `Bearer ${token}` } }
}))
const planned = await client.callTool({
name: 'goodbuddy_config_plan',
arguments: { operations: [{
operation: 'application.update', updates: { checkUpdatesOnStartup: false }
}] }
})
expect(planned.isError).not.toBe(true)
const content = planned.content as Array<{ type: string; text: string }>
const { plan } = JSON.parse(content[0]!.text)
expect(plan).not.toHaveProperty('requiresApproval')
expect((await application.get()).checkUpdatesOnStartup).toBe(true)
const applied = await client.callTool({
name: 'goodbuddy_config_apply', arguments: { planId: plan.planId }
})
expect(applied.isError).not.toBe(true)
expect((await application.get()).checkUpdatesOnStartup).toBe(false)
expect(configService.takePendingReload('config-mcp')).toBe('after-current-request')
await expect(client.callTool({
name: 'goodbuddy_config_apply', arguments: { planId: plan.planId }
})).rejects.toThrow('不存在')
} finally {
await client.close()
}
})
it('keeps scope server-side, strips markup, bounds model arguments, and drains references', async () => {
const { service, retrieveMany } = createService()
const gateway = new KnowledgeMcpGateway(service)