fix: make Agent updates reliable and visible
Cross-platform packages / Validate source (push) Canceled after 0s
Cross-platform packages / Authorize desktop packaging (push) Canceled after 0s
Cross-platform packages / linux arm64 (push) Canceled after 0s
Cross-platform packages / macos arm64 (push) Canceled after 0s
Cross-platform packages / windows arm64 (push) Canceled after 0s
Cross-platform packages / linux x64 (push) Canceled after 0s
Cross-platform packages / macos x64 (push) Canceled after 0s
Cross-platform packages / windows x64 (push) Canceled after 0s
Cross-platform packages / Publish GitHub and OSS release (push) Canceled after 0s
GoodBuddy Agents / Validate Agent source (push) Canceled after 0s
GoodBuddy Agents / Build Linux arm64 Agent (push) Canceled after 0s
GoodBuddy Agents / Build Linux x64 Agent (push) Canceled after 0s

Valid Agent packages could be rejected when packaged trusted-key JSON used CRLF, and Settings could not show whether a newer compatible Agent existed. Registry formatting is now parsed independently of cryptographic trust, and Settings reads only the signed catalog to report local and latest versions.

New installations use a credential-free local Ollama-compatible profile. The v20 migration changes only untouched historical defaults, while explicit profiles and legacy environment aliases remain compatible. Magic Notes count badges no longer shift navigation rows.

Release note: 修复 Agent 下载兼容性并在设置中显示更新状态;新安装默认连接本地模型,保留显式用户配置;魔法笔记待办数字不再改变侧栏菜单高度。
This commit is contained in:
mesalogo
2026-08-26 15:43:41 +08:00
parent f9137f0463
commit de64e9768a
33 changed files with 884 additions and 188 deletions
+76 -14
View File
@@ -80,6 +80,63 @@ afterEach(async () => {
})
describe('RuntimeSettingsStore', () => {
it('seeds a local provider-neutral model connection', async () => {
const { store } = await createStore()
await expect(store.getPublicSettings()).resolves.toMatchObject({
modelBaseUrl: 'http://127.0.0.1:11434/v1',
modelName: 'qwen3',
modelProtocol: 'openai-chat-completions',
modelAuthentication: 'none',
modelProfiles: [
expect.objectContaining({
id: '00000000-0000-4000-8000-000000000001',
baseUrl: 'http://127.0.0.1:11434/v1',
modelName: 'qwen3',
authentication: 'none'
})
]
})
})
it('replaces only the untouched legacy BigToken seed', async () => {
const { filePath, store } = await createStore()
await store.update(settings())
const version19 = JSON.parse(
await readFile(filePath, 'utf8')
) as Record<string, unknown>
version19.version = 19
await writeFile(filePath, JSON.stringify(version19), 'utf8')
const migrated = new RuntimeSettingsStore(filePath, cipher, {})
await expect(migrated.getPublicSettings()).resolves.toMatchObject({
modelBaseUrl: 'http://127.0.0.1:11434/v1',
modelName: 'qwen3',
modelAuthentication: 'none'
})
})
it('preserves an explicitly credentialed BigToken connection', async () => {
const { filePath, store } = await createStore()
await store.update(
settings({
apiKey: { action: 'replace', value: 'explicit-key' }
})
)
const version19 = JSON.parse(
await readFile(filePath, 'utf8')
) as Record<string, unknown>
version19.version = 19
await writeFile(filePath, JSON.stringify(version19), 'utf8')
const migrated = new RuntimeSettingsStore(filePath, cipher, {})
await expect(migrated.getResolvedSettings()).resolves.toMatchObject({
modelBaseUrl: 'https://bigtoken.ai',
modelName: 'sonnet-5',
apiKey: 'explicit-key'
})
})
it('restores an exact credential-bearing snapshot after a failed activation', async () => {
const { store } = await createStore()
await store.update(
@@ -382,7 +439,7 @@ describe('RuntimeSettingsStore', () => {
})
await expect(store.getResolvedSettings()).resolves.toMatchObject({
modelProtocol: 'anthropic-messages',
modelProtocol: 'openai-chat-completions',
deepseekHarnessModelProfile: {
id: 'goodbuddy-platform-harness',
name: '管理员预置模型',
@@ -398,7 +455,9 @@ describe('RuntimeSettingsStore', () => {
const publicSettings = await store.getPublicSettings()
expect(JSON.stringify(publicSettings)).not.toContain(apiKey)
expect(publicSettings.modelProtocol).toBe('anthropic-messages')
expect(publicSettings.modelProtocol).toBe(
'openai-chat-completions'
)
})
it.each([
@@ -481,7 +540,7 @@ describe('RuntimeSettingsStore', () => {
const persisted = JSON.parse(
await readFile(filePath, 'utf8')
) as Record<string, unknown>
expect(persisted.version).toBe(19)
expect(persisted.version).toBe(20)
expect(persisted).not.toHaveProperty(
'deepseekHarnessBinaryPath'
)
@@ -760,7 +819,7 @@ describe('RuntimeSettingsStore', () => {
const persisted = JSON.parse(await readFile(filePath, 'utf8')) as {
version: number
}
expect(persisted.version).toBe(19)
expect(persisted.version).toBe(20)
})
it('migrates version 11 and removes the obsolete intranet toggle', async () => {
@@ -780,7 +839,7 @@ describe('RuntimeSettingsStore', () => {
version: number
intranetCompatibilityEnabled?: boolean
}
expect(persisted.version).toBe(19)
expect(persisted.version).toBe(20)
expect(persisted).not.toHaveProperty('intranetCompatibilityEnabled')
})
@@ -1058,7 +1117,7 @@ describe('RuntimeSettingsStore', () => {
(JSON.parse(await readFile(filePath, 'utf8')) as {
version: number
}).version
).toBe(19)
).toBe(20)
})
it('migrates version 13 with reranking disabled by default', async () => {
@@ -1565,7 +1624,7 @@ describe('RuntimeSettingsStore', () => {
version: number
modelProfiles: Array<Record<string, unknown>>
}
expect(persisted.version).toBe(19)
expect(persisted.version).toBe(20)
expect(persisted.modelProfiles).toContainEqual(
expect.objectContaining({
id: imageId,
@@ -1653,7 +1712,7 @@ describe('RuntimeSettingsStore', () => {
})
})
it('does not mix an environment key with a stored base URL', async () => {
it('keeps provider-specific environment aliases for existing deployments', async () => {
const { filePath, store } = await createStore()
await store.update(
settings({
@@ -1663,11 +1722,14 @@ describe('RuntimeSettingsStore', () => {
)
const environmentStore = new RuntimeSettingsStore(filePath, cipher, {
GOODBUDDY_BIGTOKEN_API_KEY: 'YOUR_API_KEY_HERE'
GOODBUDDY_BIGTOKEN_API_KEY: 'legacy-key',
GOODBUDDY_BIGTOKEN_BASE_URL: 'https://legacy.example',
GOODBUDDY_BIGTOKEN_MODEL: 'legacy-model'
})
await expect(environmentStore.getResolvedSettings()).resolves.toMatchObject({
apiKey: 'YOUR_API_KEY_HERE',
modelBaseUrl: 'https://bigtoken.ai'
apiKey: 'legacy-key',
modelBaseUrl: 'https://legacy.example',
modelName: 'legacy-model'
})
})
@@ -1724,7 +1786,7 @@ describe('RuntimeSettingsStore', () => {
})
})
it('prefers generic model environment variables over legacy fallbacks', async () => {
it('prefers generic model environment variables over legacy aliases', async () => {
const { filePath } = await createStore()
const store = new RuntimeSettingsStore(filePath, cipher, {
GOODBUDDY_MODEL_API_KEY: 'generic-key',
@@ -1864,7 +1926,7 @@ describe('RuntimeSettingsStore', () => {
unknown
>
expect(saved).toMatchObject({
version: 19,
version: 20,
provider: 'model',
continueBinaryPath: '',
continueMode: 'chat',
@@ -2143,7 +2205,7 @@ describe('RuntimeSettingsStore', () => {
version: number
modelProfiles: Array<Record<string, unknown>>
}
expect(persisted.version).toBe(19)
expect(persisted.version).toBe(20)
expect(persisted.modelProfiles[0]).not.toHaveProperty('credential')
})