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
+124 -1
View File
@@ -472,6 +472,74 @@ describe('compound Agent packages', () => {
).rejects.toThrow('does not match its Agent bundle')
})
it('accepts a signed package whose key registry uses CRLF', async () => {
const files = unzipSync(
new Uint8Array(readFileSync(packagePath))
)
const registryBytes = Buffer.from(
Buffer.from(files['agent-release-keys.json']!)
.toString('utf8')
.replace(/\n/gu, '\r\n')
)
files['agent-release-keys.json'] =
new Uint8Array(registryBytes)
const descriptor = agentPackageDescriptorSchema.parse(
JSON.parse(
Buffer.from(files['agent-package.json']!).toString('utf8')
)
)
const updatedDescriptor = {
...descriptor,
files: descriptor.files.map((file) =>
file.path === 'agent-release-keys.json'
? {
...file,
size: registryBytes.byteLength,
sha256: createHash('sha256')
.update(registryBytes)
.digest('hex')
}
: file
)
}
updatedDescriptor.contentDigest =
agentPackage.descriptorContentDigest(updatedDescriptor)
const descriptorBytes =
agentPackage.descriptorBytes(updatedDescriptor)
files['agent-package.json'] = new Uint8Array(descriptorBytes)
files['agent-package.sig'] = new Uint8Array(
Buffer.from(
`${sign(
null,
Buffer.concat([
agentPackage.signatureDomain,
descriptorBytes
]),
privateKey
).toString('base64')}\n`
)
)
const crlfPath = join(
temporaryRoot,
'crlf-registry.gbagent'
)
writeFileSync(crlfPath, Buffer.from(zipSync(files)))
await expect(
extractAndVerifyAgentPackage({
archivePath: crlfPath,
destinationDirectory: join(
temporaryRoot,
'crlf-registry-output'
),
desktopVersion: '0.11.0',
trustedRegistry: registry
})
).resolves.toMatchObject({
descriptor: { architecture: 'x64' }
})
})
it('orders SemVer prereleases and selects the latest compatible protocol', () => {
expect(compareSemanticVersions('1.0.0-e2e.10', '1.0.0-e2e.9'))
.toBeGreaterThan(0)
@@ -715,7 +783,7 @@ describe('compound Agent packages', () => {
})
it.each(['github', 'mirror'] as const)(
'follows the selected %s source only after a manual download',
'checks the selected %s catalog without downloading until requested',
async (source) => {
const root = join(temporaryRoot, `online-${source}`)
mkdirSync(root)
@@ -723,6 +791,7 @@ describe('compound Agent packages', () => {
writeFileSync(
keyRegistryPath,
`${JSON.stringify(registry, null, 2)}\n`
.replace(/\n/gu, '\r\n')
)
const catalog = signedCatalog()
const requested: string[] = []
@@ -774,6 +843,42 @@ describe('compound Agent packages', () => {
await manager.getInventory()
expect(requested).toEqual([])
const snapshot = await manager.getSnapshot()
expect(snapshot.catalog).toMatchObject({
state: 'available',
error: null
})
expect(
snapshot.entries.find(
(entry) => entry.architecture === 'x64'
)
).toMatchObject({
state: 'not-downloaded',
latestVersion: agentLock.agentVersion,
updateAvailable: false
})
expect(requested).toContain(
source === 'github'
? 'https://api.github.com/repos/mesalogo/goodbuddy/releases?per_page=100'
: 'https://goodbuddy.oss-cn-beijing.aliyuncs.com/agent-releases/latest.json'
)
expect(requested).not.toContain(
source === 'github'
? packageEntry().downloads.github.url
: packageEntry().downloads.mirror.url
)
await manager.download('x64')
const installedSnapshot = await manager.getInventory()
expect(
installedSnapshot.entries.find(
(entry) => entry.architecture === 'x64'
)
).toMatchObject({
state: 'verified',
version: agentLock.agentVersion,
latestVersion: agentLock.agentVersion,
updateAvailable: false
})
await manager.download('x64')
expect(requested).toContain(
source === 'github'
@@ -785,6 +890,16 @@ describe('compound Agent packages', () => {
? packageEntry().downloads.github.url
: packageEntry().downloads.mirror.url
)
expect(
requested.filter(
(url) =>
url === (
source === 'github'
? packageEntry().downloads.github.url
: packageEntry().downloads.mirror.url
)
)
).toHaveLength(1)
}
)
@@ -838,6 +953,14 @@ describe('compound Agent packages', () => {
fetch: transport as typeof fetch
})
await expect(manager.getSnapshot()).resolves.toMatchObject({
catalog: {
state: 'unavailable',
error: 'Agent 发布目录签名校验失败'
},
entries: expect.any(Array)
})
expect(packageRequested).toBe(false)
await expect(manager.download('x64')).rejects.toThrow(
'Agent 发布目录签名校验失败'
)