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 模式仍保持只读。
259 lines
7.8 KiB
TypeScript
259 lines
7.8 KiB
TypeScript
import { describe, expect, it } from 'vitest'
|
|
import {
|
|
goodbuddyConfigApplyInputSchema,
|
|
goodbuddyConfigCapabilities,
|
|
goodbuddyConfigCapabilitiesOutputSchema,
|
|
goodbuddyConfigCommonExamples,
|
|
goodbuddyConfigGetOutputSchema,
|
|
goodbuddyConfigOperationNameSchema,
|
|
goodbuddyConfigOperationSchema,
|
|
goodbuddyConfigPlanInputSchema,
|
|
goodbuddyConfigPlanOutputSchema
|
|
} from './goodbuddy-config-contracts'
|
|
import {
|
|
goodbuddyConfigToolByName,
|
|
goodbuddyConfigTools
|
|
} from './goodbuddy-config-tools'
|
|
import { defaultLocalToolEnvironmentSettings } from './local-tool-environment-contracts'
|
|
import { defaultApplicationNavigation } from './application-settings-contracts'
|
|
|
|
describe('GoodBuddy configuration contracts', () => {
|
|
it('uses the authoritative full application order for configuration-tool writes', () => {
|
|
const operation = {
|
|
operation: 'application.update',
|
|
updates: { applicationNavigation: {
|
|
...defaultApplicationNavigation,
|
|
order: ['local-inference', 'heartbeat', 'magic-notes', 'knowledge', 'device-sharing'],
|
|
} },
|
|
}
|
|
expect(goodbuddyConfigOperationSchema.parse(operation)).toEqual(operation)
|
|
for (const order of [['magic-notes', 'local-inference'], ['knowledge', 'heartbeat', 'magic-notes', 'magic-notes'], ['knowledge', 'heartbeat', 'magic-notes', 'invalid']]) {
|
|
expect(goodbuddyConfigOperationSchema.safeParse({ ...operation, updates: {
|
|
applicationNavigation: { ...defaultApplicationNavigation, order },
|
|
} }).success).toBe(false)
|
|
}
|
|
})
|
|
|
|
it('publishes one valid generated example for every operation', () => {
|
|
const operationNames = goodbuddyConfigOperationNameSchema.options
|
|
|
|
expect(goodbuddyConfigCommonExamples).toHaveLength(
|
|
operationNames.length
|
|
)
|
|
expect(
|
|
goodbuddyConfigCommonExamples.map(({ operation }) => operation)
|
|
).toEqual(operationNames)
|
|
for (const example of goodbuddyConfigCommonExamples) {
|
|
expect(goodbuddyConfigOperationSchema.parse(example)).toEqual(
|
|
example
|
|
)
|
|
}
|
|
expect(
|
|
goodbuddyConfigCapabilitiesOutputSchema.parse(
|
|
goodbuddyConfigCapabilities
|
|
)
|
|
).toEqual(goodbuddyConfigCapabilities)
|
|
})
|
|
|
|
it('accepts a bounded sequence of strongly typed operations', () => {
|
|
const input = {
|
|
operations: [
|
|
{
|
|
operation: 'application.update',
|
|
updates: {
|
|
magicNotesEnabled: true,
|
|
magicNoteCommentMode: 'after-save-manual',
|
|
magicNoteCanvasPageCount: 8
|
|
}
|
|
},
|
|
{
|
|
operation: 'skill.setAssignments',
|
|
skillId: 'document-writing',
|
|
assignments: ['model', 'continue']
|
|
},
|
|
{
|
|
operation: 'mcp.add',
|
|
connection: {
|
|
name: 'Project MCP',
|
|
description: '',
|
|
allowDynamicTools: false,
|
|
transport: 'http',
|
|
url: 'https://mcp.example.com/tools'
|
|
},
|
|
enabled: false,
|
|
assignments: ['model']
|
|
}
|
|
]
|
|
}
|
|
|
|
expect(goodbuddyConfigPlanInputSchema.parse(input)).toEqual(input)
|
|
})
|
|
|
|
it('keeps custom MCP assignments limited to the direct model', () => {
|
|
expect(() =>
|
|
goodbuddyConfigOperationSchema.parse({
|
|
operation: 'mcp.add',
|
|
connection: {
|
|
name: 'Project MCP',
|
|
description: '',
|
|
allowDynamicTools: false,
|
|
transport: 'http',
|
|
url: 'https://mcp.example.com/tools'
|
|
},
|
|
enabled: true,
|
|
assignments: ['continue']
|
|
})
|
|
).toThrow()
|
|
})
|
|
|
|
it('never accepts MCP secrets or credential-bearing remote URLs', () => {
|
|
const baseConnection = {
|
|
name: 'Project MCP',
|
|
description: '',
|
|
allowDynamicTools: false,
|
|
transport: 'http',
|
|
url: 'https://mcp.example.com/tools'
|
|
}
|
|
|
|
expect(() =>
|
|
goodbuddyConfigOperationSchema.parse({
|
|
operation: 'mcp.add',
|
|
connection: {
|
|
...baseConnection,
|
|
secret: { action: 'replace', value: 'do-not-accept' }
|
|
},
|
|
enabled: true,
|
|
assignments: ['model']
|
|
})
|
|
).toThrow()
|
|
expect(() =>
|
|
goodbuddyConfigOperationSchema.parse({
|
|
operation: 'mcp.update',
|
|
serverId: '00000000-0000-4000-8000-000000000001',
|
|
connection: {
|
|
...baseConnection,
|
|
url: 'https://user:password@mcp.example.com/tools'
|
|
}
|
|
})
|
|
).toThrow()
|
|
expect(() =>
|
|
goodbuddyConfigOperationSchema.parse({
|
|
operation: 'mcp.update',
|
|
serverId: '00000000-0000-4000-8000-000000000001',
|
|
connection: {
|
|
...baseConnection,
|
|
url: 'https://mcp.example.com/tools?token=secret'
|
|
}
|
|
})
|
|
).toThrow()
|
|
})
|
|
|
|
it('only exposes redacted MCP summaries from get', () => {
|
|
const snapshot = {
|
|
application: {
|
|
checkUpdatesOnStartup: true,
|
|
desktopNotificationsEnabled: true,
|
|
transparentFrostedEffectEnabled: false,
|
|
updateSource: 'github',
|
|
modelDownloadSource: 'modelscope',
|
|
localToolEnvironment: defaultLocalToolEnvironmentSettings,
|
|
applicationNavigation: defaultApplicationNavigation,
|
|
localInferenceEnabled: true,
|
|
deviceSharingEnabled: false,
|
|
conversationHtmlRenderingEnabled: true,
|
|
remoteProjectsEnabled: false,
|
|
magicNotesEnabled: true,
|
|
magicNotesShowIncompleteTodoCount: true,
|
|
magicNoteCommentMode: 'immediate',
|
|
magicNoteCommentFormat: 'combined',
|
|
magicNoteCanvasPageCount: 1
|
|
},
|
|
skills: [],
|
|
mcpServers: [
|
|
{
|
|
id: '00000000-0000-4000-8000-000000000001',
|
|
name: 'Project MCP',
|
|
description: '',
|
|
enabled: true,
|
|
allowDynamicTools: false,
|
|
assignments: ['model'],
|
|
secretConfigured: true,
|
|
transport: 'http'
|
|
}
|
|
]
|
|
}
|
|
|
|
expect(goodbuddyConfigGetOutputSchema.parse(snapshot)).toEqual(
|
|
snapshot
|
|
)
|
|
expect(() =>
|
|
goodbuddyConfigGetOutputSchema.parse({
|
|
...snapshot,
|
|
mcpServers: [
|
|
{
|
|
...snapshot.mcpServers[0],
|
|
url: 'https://mcp.example.com?token=secret',
|
|
secret: 'secret'
|
|
}
|
|
]
|
|
})
|
|
).toThrow()
|
|
})
|
|
|
|
it('requires an expiring plan without a standalone approval flag and applies only its ID', () => {
|
|
const operation = goodbuddyConfigCommonExamples[0]!
|
|
expect(
|
|
goodbuddyConfigPlanOutputSchema.parse({
|
|
planId: '00000000-0000-4000-8000-000000000002',
|
|
expiresAt: '2030-01-01T00:00:00.000Z',
|
|
operations: [operation],
|
|
steps: [
|
|
{
|
|
index: 0,
|
|
operation: operation.operation,
|
|
summary: 'Update startup checks.',
|
|
risk: 'low',
|
|
reload: 'none',
|
|
destructive: false
|
|
}
|
|
],
|
|
overallRisk: 'low',
|
|
reload: 'none'
|
|
})
|
|
).not.toHaveProperty('requiresApproval')
|
|
expect(goodbuddyConfigCapabilities).not.toHaveProperty('applyRequiresApproval')
|
|
expect(
|
|
goodbuddyConfigApplyInputSchema.parse({
|
|
planId: '00000000-0000-4000-8000-000000000002'
|
|
})
|
|
).toEqual({
|
|
planId: '00000000-0000-4000-8000-000000000002'
|
|
})
|
|
expect(() =>
|
|
goodbuddyConfigApplyInputSchema.parse({
|
|
planId: '00000000-0000-4000-8000-000000000002',
|
|
operations: [operation]
|
|
})
|
|
).toThrow()
|
|
})
|
|
|
|
it('catalogs the four request-scoped MCP tools', () => {
|
|
expect(goodbuddyConfigTools.map(({ name }) => name)).toEqual([
|
|
'goodbuddy_config_capabilities',
|
|
'goodbuddy_config_get',
|
|
'goodbuddy_config_plan',
|
|
'goodbuddy_config_apply'
|
|
])
|
|
expect(
|
|
goodbuddyConfigToolByName.get('goodbuddy_config_apply')?.access
|
|
).toBe(
|
|
'write'
|
|
)
|
|
expect(
|
|
goodbuddyConfigTools
|
|
.filter(({ name }) => name !== 'goodbuddy_config_apply')
|
|
.every(({ access }) => access === 'read')
|
|
).toBe(true)
|
|
})
|
|
})
|