import { createHash, createPrivateKey, sign, type KeyObject } from 'node:crypto' import { execFileSync } from 'node:child_process' import { chmodSync, lstatSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, renameSync, rmSync, symlinkSync, writeFileSync } from 'node:fs' import { createRequire } from 'node:module' import { tmpdir } from 'node:os' import { basename, dirname, join, resolve } from 'node:path' import { c as createTar } from 'tar' import { unzipSync, zipSync } from 'fflate' import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest' import { agentPackageCatalogSchema, agentPackageDescriptorSchema, agentPackageArchiveName, type AgentPackageCatalog, type AgentPackageCatalogEntry, type AgentPackageDescriptor } from '../src/shared/agent-package-contracts' import type { AgentReleaseKeyRegistry, AgentRuntimeLock } from '../src/shared/agent-installation-contracts' import type { RemoteRuntimeLock } from '../src/shared/remote-runtime-launch-contracts' import { compareSemanticVersions, extractAndVerifyAgentPackage } from '../src/main/remote-agent/agent-package-verifier' import { AgentPackageManager, selectLatestCompatibleEntry } from '../src/main/remote-agent/agent-package-manager' import { createOpenCodeConfigFixture } from './support/opencode-config-fixture' type AgentBundleModule = { canonicalManifestBytes(manifest: unknown): Buffer createManifest( directory: string, metadata: Record ): AgentPackageDescriptor publicKeySpkiBase64(key: KeyObject): string signManifestForTest(bytes: Buffer, key: KeyObject): Buffer } type RuntimeBundleModule = { buildRuntimeBundle(options: { projectRoot: string architecture: 'x64' | 'arm64' runtimeArchive: string outputRoot: string lock: RemoteRuntimeLock registry: AgentReleaseKeyRegistry testSigningIdentity: { keyId: string privateKey: KeyObject } enforceFilesystemMode: false }): { bundleDirectory: string } } type AgentPackageModule = { assembleAgentPackage(options: { projectRoot: string architecture: 'x64' | 'arm64' minimumDesktopVersion: string output: string agentBundle: string runtimeBundle: string agentLock: AgentRuntimeLock runtimeLock: RemoteRuntimeLock registry: AgentReleaseKeyRegistry testSigningIdentity: { keyId: string privateKey: KeyObject } }): { descriptor: AgentPackageDescriptor archive: string size: number sha256: string } descriptorBytes(descriptor: AgentPackageDescriptor): Buffer descriptorContentDigest( descriptor: AgentPackageDescriptor ): string signatureDomain: Buffer } type AgentCatalogModule = { readPackageMetadata( archivePath: string, registry: AgentReleaseKeyRegistry ): { descriptor: AgentPackageDescriptor size: number sha256: string } createCatalog(options: { projectRoot: string x64Package: string arm64Package: string previousCatalog?: string previousSignature?: string outputCatalog: string outputSignature: string generatedAt: string registry: AgentReleaseKeyRegistry signingIdentity: { keyId: string privateKey: KeyObject } }): { version: string catalog: string signature: string entries: number } readVerifiedCatalog( catalogPath: string, signaturePath: string, registry: AgentReleaseKeyRegistry ): AgentPackageCatalog } type PackageInstallerModule = { preparePackage(options: { operationRoot: string archive: string expectedSha256: string homeDirectory: string emit?: (event: unknown) => void }): { type: 'result' command: 'prepare' status: 'prepared' archiveSha256: string agent: { installationId: string agentVersion: string manifestSha256: string binaryDigest: string platform: 'linux' architecture: 'x64' | 'arm64' protocol: { major: number; minor: number } supervisor: 'detached-on-demand' } runtime: { runtimeId: 'opencode' runtimeVersion: string bundleDigest: string manifestDigest: string runtimeAdapterDigest: string acpCapabilitiesDigest: string platform: 'linux' architecture: 'x64' | 'arm64' protocol: { major: number; minor: number } } } commitPackage(options: { operationRoot: string archive: string expectedSha256: string homeDirectory: string emit?: (event: unknown) => void }): { command: 'commit' status: 'committed' agent: PackageInstallerModule[ 'preparePackage' ] extends (...args: never[]) => infer Result ? Result extends { agent: infer Agent } ? Agent : never : never runtime: PackageInstallerModule[ 'preparePackage' ] extends (...args: never[]) => infer Result ? Result extends { runtime: infer Runtime } ? Runtime : never : never } } const require = createRequire(import.meta.url) const agentBundle = require( '../build/agent-bundle.cjs' ) as AgentBundleModule const runtimeBundle = require( '../build/remote-runtime-bundle.cjs' ) as RuntimeBundleModule const agentPackage = require( '../build/agent-package.cjs' ) as AgentPackageModule const agentCatalog = require( '../build/agent-catalog.cjs' ) as AgentCatalogModule const catalogSignatureDomain = Buffer.from( 'GoodBuddy Agent Package Catalog Signature v1\0', 'utf8' ) let temporaryRoot = '' let privateKey: KeyObject let registry: AgentReleaseKeyRegistry let agentLock: AgentRuntimeLock let remoteRuntimeLock: RemoteRuntimeLock let packagePath = '' let arm64PackagePath = '' let packageResult: ReturnType< AgentPackageModule['assembleAgentPackage'] > let arm64PackageResult: ReturnType< AgentPackageModule['assembleAgentPackage'] > let packageInstaller: PackageInstallerModule let installerBundle = '' beforeAll(() => { temporaryRoot = resolve( mkdtempSync(join(tmpdir(), 'goodbuddy-agent-package-test-')) ) installerBundle = join( temporaryRoot, 'package-installer-fixture.cjs' ) execFileSync(process.execPath, [ '-e', [ "require('esbuild').buildSync({", `entryPoints:[${JSON.stringify( join( process.cwd(), 'src', 'main', 'remote-agent', 'control-plane-package-installer.ts' ) )}],`, `outfile:${JSON.stringify(installerBundle)},`, "bundle:true,platform:'node',target:['node24.19'],", "format:'cjs',logLevel:'silent'})" ].join('') ]) packageInstaller = require(installerBundle) as PackageInstallerModule privateKey = createPrivateKey({ key: Buffer.from( '302e020100300506032b657004220420404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f', 'hex' ), format: 'der', type: 'pkcs8' }) registry = { formatVersion: 1, keys: [{ keyId: 'agent-package-fixture', publicKeySpkiBase64: agentBundle.publicKeySpkiBase64(privateKey), environment: 'production' }], revocations: [] } agentLock = JSON.parse( readFileSync( join(process.cwd(), 'agent-runtime-lock.json'), 'utf8' ) ) as AgentRuntimeLock const agentDirectory = join(temporaryRoot, 'agent') const arm64AgentDirectory = join(temporaryRoot, 'agent-arm64') writeAgentBundle(agentDirectory, 'x64') writeAgentBundle(arm64AgentDirectory, 'arm64') const runtimeInput = createRuntimeInput('x64') const arm64RuntimeInput = createRuntimeInput('arm64') remoteRuntimeLock = createRemoteRuntimeLock( runtimeInput, arm64RuntimeInput ) const buildRuntime = ( architecture: 'x64' | 'arm64', archive: string ) => runtimeBundle.buildRuntimeBundle({ projectRoot: temporaryRoot, architecture, runtimeArchive: archive, outputRoot: join(temporaryRoot, 'runtime'), lock: remoteRuntimeLock, registry, testSigningIdentity: { keyId: 'agent-package-fixture', privateKey }, enforceFilesystemMode: false }) createOpenCodeConfigFixture(temporaryRoot) const builtRuntime = buildRuntime('x64', runtimeInput) const builtArm64Runtime = buildRuntime( 'arm64', arm64RuntimeInput ) packagePath = join( temporaryRoot, agentPackageArchiveName(agentLock.agentVersion, 'x64') ) packageResult = agentPackage.assembleAgentPackage({ projectRoot: process.cwd(), architecture: 'x64', minimumDesktopVersion: '0.11.0', output: packagePath, agentBundle: agentDirectory, runtimeBundle: builtRuntime.bundleDirectory, agentLock, runtimeLock: remoteRuntimeLock, registry, testSigningIdentity: { keyId: 'agent-package-fixture', privateKey } }) arm64PackagePath = join( temporaryRoot, agentPackageArchiveName(agentLock.agentVersion, 'arm64') ) arm64PackageResult = agentPackage.assembleAgentPackage({ projectRoot: process.cwd(), architecture: 'arm64', minimumDesktopVersion: '0.11.0', output: arm64PackagePath, agentBundle: arm64AgentDirectory, runtimeBundle: builtArm64Runtime.bundleDirectory, agentLock, runtimeLock: remoteRuntimeLock, registry, testSigningIdentity: { keyId: 'agent-package-fixture', privateKey } }) }) afterAll(() => { rmSync(temporaryRoot, { recursive: true, force: true }) }) describe('compound Agent packages', () => { it('verifies and installs offline dependency inventories larger than one MiB', async () => { const projectRoot = join(temporaryRoot, 'large-offline-config') const config = createOpenCodeConfigFixture(projectRoot) const inventory = join(config, 'inventory') mkdirSync(inventory) for (let index = 0; index < 3000; index++) { const path = join(inventory, `${String(index).padStart(4, '0')}-${'x'.repeat(100)}.js`) writeFileSync(path, '// fixture\n') } const runtime = runtimeBundle.buildRuntimeBundle({ projectRoot, architecture: 'x64', runtimeArchive: createRuntimeInput('x64'), outputRoot: join(projectRoot, 'runtime'), lock: remoteRuntimeLock, registry, testSigningIdentity: { keyId: 'agent-package-fixture', privateKey }, enforceFilesystemMode: false }) const archive = join(projectRoot, agentPackageArchiveName(agentLock.agentVersion, 'x64')) const result = agentPackage.assembleAgentPackage({ projectRoot, architecture: 'x64', minimumDesktopVersion: '0.11.0', output: archive, agentBundle: join(temporaryRoot, 'agent'), runtimeBundle: runtime.bundleDirectory, agentLock, runtimeLock: remoteRuntimeLock, registry, testSigningIdentity: { keyId: 'agent-package-fixture', privateKey } }) const descriptorBytes = agentPackage.descriptorBytes(result.descriptor) expect(descriptorBytes.length).toBeGreaterThan(1024 * 1024) expect(descriptorBytes.length).toBeLessThan(4 * 1024 * 1024) expect(agentCatalog.readPackageMetadata(archive, registry).sha256).toBe(result.sha256) await expect(extractAndVerifyAgentPackage({ archivePath: archive, destinationDirectory: join(projectRoot, 'verified'), architecture: 'x64', desktopVersion: '0.11.0', trustedRegistry: registry })).resolves.toMatchObject({ descriptor: { contentDigest: result.descriptor.contentDigest } }) const options = { operationRoot: join(projectRoot, 'operation'), archive, expectedSha256: result.sha256, homeDirectory: join(projectRoot, 'home') } mkdirSync(options.homeDirectory, { mode: 0o700 }) expect(packageInstaller.preparePackage(options).status).toBe('prepared') expect(packageInstaller.commitPackage(options).status).toBe('committed') }, 60_000) it('still rejects descriptors larger than four MiB before parsing', async () => { const archive = join(temporaryRoot, 'oversized-descriptor.zip') writeFileSync(archive, zipSync({ 'agent-package.json': new Uint8Array(4 * 1024 * 1024 + 1) }, { level: 0 })) await expect(extractAndVerifyAgentPackage({ archivePath: archive, destinationDirectory: join(temporaryRoot, 'oversized-descriptor'), desktopVersion: '0.11.0', trustedRegistry: registry })).rejects.toThrow(/limit|large/iu) }) it('assembles deterministic signed archives with private extraction', async () => { const secondRoot = join(temporaryRoot, 'second') mkdirSync(secondRoot) const secondPath = join( secondRoot, agentPackageArchiveName(agentLock.agentVersion, 'x64') ) const archive = unzipSync( new Uint8Array(readFileSync(packagePath)) ) expect(Object.keys(archive).sort()).toEqual( expect.arrayContaining([ 'agent-package.json', 'agent-package.sig', 'agent-release-keys.json', 'agent-runtime-lock.json', 'remote-runtime-lock.json' ]) ) expect(packageResult.descriptor.files).toContainEqual( expect.objectContaining({ path: 'agent/helpers/fixture-helper', mode: '0755' }) ) for (const [result, archivePath] of [ [packageResult, packagePath], [arm64PackageResult, arm64PackagePath] ] as const) { expect(result.descriptor.files).toContainEqual( expect.objectContaining({ path: 'agent/node', mode: '0755' }) ) expect(result.descriptor.files).not.toContainEqual( expect.objectContaining({ path: 'agent/lib/package-installer.cjs' }) ) expect( Object.keys( unzipSync(new Uint8Array(readFileSync(archivePath))) ) ).not.toContain('agent/lib/package-installer.cjs') } const contentRoot = join(temporaryRoot, 'verified') const verified = await extractAndVerifyAgentPackage({ archivePath: packagePath, destinationDirectory: contentRoot, architecture: 'x64', desktopVersion: '0.11.0', trustedRegistry: registry }) expect(verified).toMatchObject({ descriptor: { version: agentLock.agentVersion, architecture: 'x64', remoteRuntime: { version: remoteRuntimeLock.runtimes.opencode.version } } }) if (process.platform !== 'win32') { for (const directory of [verified.rootDirectory]) { expect(lstatSync(directory).mode & 0o777).toBe(0o700) } } const second = agentPackage.assembleAgentPackage({ projectRoot: process.cwd(), architecture: 'x64', minimumDesktopVersion: '0.11.0', output: secondPath, agentBundle: join(temporaryRoot, 'agent'), runtimeBundle: findRuntimeBundleDirectory(), agentLock, runtimeLock: remoteRuntimeLock, registry, testSigningIdentity: { keyId: 'agent-package-fixture', privateKey } }) expect(second.archive).toBe( agentPackageArchiveName(agentLock.agentVersion, 'x64') ) expect(readFileSync(secondPath).equals( readFileSync(packagePath) )).toBe(true) }) it('prepares and commits a format-v1 package without an embedded installer', () => { const operationRoot = join(temporaryRoot, 'remote-operation') const homeDirectory = join(temporaryRoot, 'remote-home') const globalMetadata = seedGlobalManagedMetadata( homeDirectory, 'a' ) const events: unknown[] = [] const prepared = packageInstaller.preparePackage({ operationRoot, archive: packagePath, expectedSha256: packageResult.sha256, homeDirectory, emit: (event) => events.push(event) }) expect(prepared).toMatchObject({ type: 'result', command: 'prepare', status: 'prepared', archiveSha256: packageResult.sha256, agent: { installationId: expect.stringMatching(/^agent-[a-f0-9]{64}$/u), agentVersion: agentLock.agentVersion, architecture: 'x64', protocol: agentLock.protocol }, runtime: { runtimeId: 'opencode', runtimeVersion: remoteRuntimeLock.runtimes.opencode.version, bundleDigest: packageResult.descriptor.remoteRuntime.bundleDigest, architecture: 'x64' } }) expect(events.at(-1)).toEqual(prepared) expect(readdirSync(join(operationRoot, 'prepared'))) .toEqual([ 'agent-package.json', 'agent-package.sig', 'agent-release-keys.json', 'agent-runtime-lock.json', 'payload', 'remote-runtime-lock.json', 'result.json' ]) expectGlobalMetadataUnchanged(globalMetadata) const cliOperationRoot = join(temporaryRoot, 'remote-cli-operation') const cliLines = execFileSync( process.execPath, [ installerBundle, 'prepare', '--operation-root', cliOperationRoot, '--archive', packagePath, '--expected-sha256', packageResult.sha256 ], { encoding: 'utf8' } ) .trim() .split('\n') .map((line) => JSON.parse(line) as Record) expect(cliLines[0]).toMatchObject({ type: 'progress', command: 'prepare', phase: 'validating' }) expect(cliLines.at(-1)).toMatchObject({ type: 'result', command: 'prepare', status: 'prepared', agent: prepared.agent, runtime: prepared.runtime }) const commitEvents: Array> = [] const committed = packageInstaller.commitPackage({ operationRoot, archive: packagePath, expectedSha256: packageResult.sha256, homeDirectory, emit: (event) => { commitEvents.push(event as Record) } }) expect(committed).toMatchObject({ command: 'commit', status: 'committed', agent: prepared.agent, runtime: prepared.runtime }) const agentDestination = join( homeDirectory, '.goodbuddy', 'agent', 'installations', prepared.agent.installationId ) const runtimeDestination = join( homeDirectory, '.goodbuddy', 'runtimes', 'opencode', prepared.runtime.bundleDigest.slice('sha256:'.length) ) expect(lstatSync(join(agentDestination, 'lib', 'agent.cjs')).isFile()) .toBe(true) expect(lstatSync(join(runtimeDestination, 'bin', 'opencode')).isFile()) .toBe(true) expect( commitEvents.filter( (event) => event.type === 'progress' && event.phase === 'publishing-content' ) ).toHaveLength(1) expect(readdirSync(join(operationRoot, 'prepared'))) .toEqual([ 'agent-package.json', 'agent-package.sig', 'agent-release-keys.json', 'agent-runtime-lock.json', 'payload', 'remote-runtime-lock.json', 'result.json' ]) expect(readdirSync(operationRoot)).toEqual(['prepared']) expectRegistriesUnchangedAndPackageTrustPublished( globalMetadata ) packageInstaller.preparePackage({ operationRoot, archive: packagePath, expectedSha256: packageResult.sha256, homeDirectory }) expect(() => packageInstaller.commitPackage({ operationRoot, archive: packagePath, expectedSha256: packageResult.sha256, homeDirectory }) ).not.toThrow() expectRegistriesUnchangedAndPackageTrustPublished( globalMetadata ) }) it('resumes commit after an Agent directory was already published', () => { const operationRoot = join( temporaryRoot, 'partial-commit-operation' ) const homeDirectory = join( temporaryRoot, 'partial-commit-home' ) const prepared = packageInstaller.preparePackage({ operationRoot, archive: packagePath, expectedSha256: packageResult.sha256, homeDirectory }) const agentSource = join( operationRoot, 'prepared', 'payload', 'agent' ) const agentDestination = join( homeDirectory, '.goodbuddy', 'agent', 'installations', prepared.agent.installationId ) mkdirSync(dirname(agentDestination), { recursive: true, mode: 0o700 }) renameSync(agentSource, agentDestination) expect( packageInstaller.commitPackage({ operationRoot, archive: packagePath, expectedSha256: packageResult.sha256, homeDirectory }) ).toMatchObject({ status: 'committed', agent: prepared.agent, runtime: prepared.runtime }) expect( lstatSync(join(agentDestination, 'goodbuddy-agent')) .isFile() ).toBe(true) expect( lstatSync( join( homeDirectory, '.goodbuddy', 'runtimes', 'opencode', prepared.runtime.bundleDigest.slice( 'sha256:'.length ), 'bin', 'opencode' ) ).isFile() ).toBe(true) }) it('requires untampered prepared state before commit mutation', () => { const homeDirectory = join(temporaryRoot, 'prepared-state-home') const tamperedOperation = join( temporaryRoot, 'tampered-prepared-state-operation' ) packageInstaller.preparePackage({ operationRoot: tamperedOperation, archive: packagePath, expectedSha256: packageResult.sha256, homeDirectory }) const statePath = join(tamperedOperation, 'prepared', 'result.json') const state = JSON.parse( readFileSync(statePath, 'utf8') ) as Record const stateAgent = state.agent as Record stateAgent.agentVersion = '99.0.0' writeFileSync(statePath, `${JSON.stringify(state, null, 2)}\n`) expect(() => packageInstaller.commitPackage({ operationRoot: tamperedOperation, archive: packagePath, expectedSha256: packageResult.sha256, homeDirectory }) ).toThrow('does not match the authenticated archive') expect(readdirSync(tamperedOperation)).toEqual(['prepared']) const missingOperation = join( temporaryRoot, 'missing-prepared-state-operation' ) packageInstaller.preparePackage({ operationRoot: missingOperation, archive: packagePath, expectedSha256: packageResult.sha256, homeDirectory }) rmSync(join(missingOperation, 'prepared', 'result.json')) expect(() => packageInstaller.commitPackage({ operationRoot: missingOperation, archive: packagePath, expectedSha256: packageResult.sha256, homeDirectory }) ).toThrow() expect(readdirSync(missingOperation)).toEqual(['prepared']) }) it('preserves current registries when commit fails', () => { const operationRoot = join(temporaryRoot, 'failed-commit-operation') const homeDirectory = join(temporaryRoot, 'failed-commit-home') const globalMetadata = seedGlobalManagedMetadata( homeDirectory, 'e' ) const prepared = packageInstaller.preparePackage({ operationRoot, archive: packagePath, expectedSha256: packageResult.sha256, homeDirectory }) const conflictingAgentDestination = join( homeDirectory, '.goodbuddy', 'agent', 'installations', prepared.agent.installationId ) mkdirSync(dirname(conflictingAgentDestination), { recursive: true, mode: 0o700 }) writeFileSync( conflictingAgentDestination, 'different' ) expect(() => packageInstaller.commitPackage({ operationRoot, archive: packagePath, expectedSha256: packageResult.sha256, homeDirectory }) ).toThrow('conflicting content') expectGlobalMetadataUnchanged(globalMetadata) }) it.runIf(process.platform !== 'win32')( 'rejects symbolic-link ancestors before publishing managed payloads', () => { const cases = [ { name: 'managed-root', link(homeDirectory: string, linkTarget: string) { symlinkSync( linkTarget, join(homeDirectory, '.goodbuddy') ) } }, { name: 'agent-installations', link(homeDirectory: string, linkTarget: string) { const agentRoot = join( homeDirectory, '.goodbuddy', 'agent' ) mkdirSync(agentRoot, { recursive: true, mode: 0o700 }) symlinkSync( linkTarget, join(agentRoot, 'installations') ) } }, { name: 'runtime-root', link(homeDirectory: string, linkTarget: string) { const runtimes = join( homeDirectory, '.goodbuddy', 'runtimes' ) mkdirSync(runtimes, { recursive: true, mode: 0o700 }) symlinkSync( linkTarget, join(runtimes, 'opencode') ) } } ] for (const fixture of cases) { const operationRoot = join( temporaryRoot, `symlink-${fixture.name}-operation` ) const homeDirectory = join( temporaryRoot, `symlink-${fixture.name}-home` ) const linkTarget = join( temporaryRoot, `symlink-${fixture.name}-target` ) mkdirSync(homeDirectory, { mode: 0o700 }) mkdirSync(linkTarget, { mode: 0o700 }) fixture.link(homeDirectory, linkTarget) packageInstaller.preparePackage({ operationRoot, archive: packagePath, expectedSha256: packageResult.sha256, homeDirectory }) expect(() => packageInstaller.commitPackage({ operationRoot, archive: packagePath, expectedSha256: packageResult.sha256, homeDirectory }) ).toThrow('Managed installation directory is unsafe') expect(readdirSync(linkTarget)).toEqual([]) } } ) it('replaces a conflicting managed digest directory during explicit repair', () => { const operationRoot = join(temporaryRoot, 'repair-commit-operation') const homeDirectory = join(temporaryRoot, 'repair-commit-home') const globalMetadata = seedGlobalManagedMetadata( homeDirectory, 'f' ) const prepared = packageInstaller.preparePackage({ operationRoot, archive: packagePath, expectedSha256: packageResult.sha256, homeDirectory }) const agentDestination = join( homeDirectory, '.goodbuddy', 'agent', 'installations', prepared.agent.installationId ) mkdirSync(agentDestination, { recursive: true, mode: 0o700 }) writeFileSync( join(agentDestination, 'conflicting-content'), 'different' ) expect( packageInstaller.commitPackage({ operationRoot, archive: packagePath, expectedSha256: packageResult.sha256, homeDirectory }) ).toMatchObject({ status: 'committed', agent: prepared.agent, runtime: prepared.runtime }) expect( readdirSync(agentDestination) ).not.toContain('conflicting-content') expect( readFileSync(join(agentDestination, 'manifest.json')) ).toBeTruthy() expectRegistriesUnchangedAndPackageTrustPublished( globalMetadata ) }) it('restores a replaced Agent directory when Runtime publication fails', () => { const operationRoot = join(temporaryRoot, 'repair-rollback-operation') const homeDirectory = join(temporaryRoot, 'repair-rollback-home') const prepared = packageInstaller.preparePackage({ operationRoot, archive: packagePath, expectedSha256: packageResult.sha256, homeDirectory }) const agentDestination = join( homeDirectory, '.goodbuddy', 'agent', 'installations', prepared.agent.installationId ) mkdirSync(agentDestination, { recursive: true, mode: 0o700 }) writeFileSync(join(agentDestination, 'old-marker'), 'old') const runtimeDestination = join( homeDirectory, '.goodbuddy', 'runtimes', 'opencode', prepared.runtime.bundleDigest.slice('sha256:'.length) ) mkdirSync(dirname(runtimeDestination), { recursive: true, mode: 0o700 }) writeFileSync(runtimeDestination, 'unsafe destination type') expect(() => packageInstaller.commitPackage({ operationRoot, archive: packagePath, expectedSha256: packageResult.sha256, homeDirectory }) ).toThrow('conflicting content') expect(readdirSync(agentDestination)).toEqual(['old-marker']) }) it('publishes each Agent payload independently of existing installations', () => { const variantAgent = join(temporaryRoot, 'reuse-variant-agent') writeAgentBundle(variantAgent, 'x64') writeFileSync( join(variantAgent, 'lib', 'agent.cjs'), 'module.exports={variant:true}\n' ) signAgentBundleManifest(variantAgent, 'x64') const variantPath = join( temporaryRoot, 'reuse-variant', agentPackageArchiveName(agentLock.agentVersion, 'x64') ) mkdirSync(dirname(variantPath), { recursive: true }) const variant = agentPackage.assembleAgentPackage({ projectRoot: process.cwd(), architecture: 'x64', minimumDesktopVersion: '0.11.0', output: variantPath, agentBundle: variantAgent, runtimeBundle: findRuntimeBundleDirectory(), agentLock, runtimeLock: remoteRuntimeLock, registry, testSigningIdentity: { keyId: 'agent-package-fixture', privateKey } }) const changedNodeAgent = join(temporaryRoot, 'reuse-changed-node-agent') writeAgentBundle(changedNodeAgent, 'x64') writeFileSync( join(changedNodeAgent, 'node'), Buffer.concat([elf('x64'), Buffer.from('different Node payload')]) ) chmodSync(join(changedNodeAgent, 'node'), 0o755) signAgentBundleManifest(changedNodeAgent, 'x64') const changedNodePath = join( temporaryRoot, 'reuse-changed-node', agentPackageArchiveName(agentLock.agentVersion, 'x64') ) mkdirSync(dirname(changedNodePath), { recursive: true }) const changedNodeVariant = agentPackage.assembleAgentPackage({ projectRoot: process.cwd(), architecture: 'x64', minimumDesktopVersion: '0.11.0', output: changedNodePath, agentBundle: changedNodeAgent, runtimeBundle: findRuntimeBundleDirectory(), agentLock, runtimeLock: remoteRuntimeLock, registry, testSigningIdentity: { keyId: 'agent-package-fixture', privateKey } }) const prepareAndCommit = ( options: Parameters[0] ) => { packageInstaller.preparePackage(options) return packageInstaller.commitPackage(options) } const installCurrent = ( name: string ): { homeDirectory: string currentNode: string globalMetadata: Map } => { const homeDirectory = join(temporaryRoot, `reuse-${name}-home`) const globalMetadata = seedGlobalManagedMetadata(homeDirectory, 'b') const current = prepareAndCommit({ operationRoot: join(temporaryRoot, `reuse-${name}-current-operation`), archive: packagePath, expectedSha256: packageResult.sha256, homeDirectory }) const registryPath = join( homeDirectory, '.goodbuddy', 'agent', 'registry.json' ) const registryBytes = Buffer.from( `${JSON.stringify({ formatVersion: 1, current: { installationId: current.agent.installationId, agentVersion: current.agent.agentVersion, manifestSha256: current.agent.manifestSha256, arch: current.agent.architecture } }, null, 2)}\n` ) writeFileSync(registryPath, registryBytes) globalMetadata.set(registryPath, registryBytes) return { homeDirectory, currentNode: join( homeDirectory, '.goodbuddy', 'agent', 'installations', current.agent.installationId, 'node' ), globalMetadata } } const commitVariant = (name: string, homeDirectory: string) => prepareAndCommit({ operationRoot: join(temporaryRoot, `reuse-${name}-variant-operation`), archive: variantPath, expectedSha256: variant.sha256, homeDirectory }) const installedVariantNode = ( homeDirectory: string, installationId: string ) => join( homeDirectory, '.goodbuddy', 'agent', 'installations', installationId, 'node' ) const identical = installCurrent('identical') const identicalResult = commitVariant( 'identical', identical.homeDirectory ) expect( lstatSync(identical.currentNode).ino ).not.toBe( lstatSync( installedVariantNode( identical.homeDirectory, identicalResult.agent.installationId ) ).ino ) expectRegistriesUnchangedAndPackageTrustPublished( identical.globalMetadata ) const changed = installCurrent('changed') const changedResult = prepareAndCommit({ operationRoot: join( temporaryRoot, 'reuse-changed-variant-operation' ), archive: changedNodePath, expectedSha256: changedNodeVariant.sha256, homeDirectory: changed.homeDirectory }) expect( lstatSync(changed.currentNode).ino ).not.toBe( lstatSync( installedVariantNode( changed.homeDirectory, changedResult.agent.installationId ) ).ino ) expectRegistriesUnchangedAndPackageTrustPublished( changed.globalMetadata ) const corrupt = installCurrent('corrupt') writeFileSync(corrupt.currentNode, Buffer.from('corrupt Node')) const corruptResult = commitVariant('corrupt', corrupt.homeDirectory) expect( lstatSync(corrupt.currentNode).ino ).not.toBe( lstatSync( installedVariantNode( corrupt.homeDirectory, corruptResult.agent.installationId ) ).ino ) expectRegistriesUnchangedAndPackageTrustPublished( corrupt.globalMetadata ) const untrusted = installCurrent('untrusted') const currentRoot = dirname(untrusted.currentNode) const signaturePath = join(currentRoot, 'manifest.sig') const signature = readFileSync(signaturePath) signature[0] = signature[0] === 65 ? 66 : 65 writeFileSync(signaturePath, signature) const untrustedResult = commitVariant('untrusted', untrusted.homeDirectory) expect( lstatSync(untrusted.currentNode).ino ).not.toBe( lstatSync( installedVariantNode( untrusted.homeDirectory, untrustedResult.agent.installationId ) ).ino ) expectRegistriesUnchangedAndPackageTrustPublished( untrusted.globalMetadata ) }) it('fails the remote installer closed on traversal, duplicates, corruption, and hash mismatch', () => { const run = (archive: string, expectedSha256?: string) => packageInstaller.preparePackage({ operationRoot: join( temporaryRoot, `rejected-${basename(archive)}` ), archive, expectedSha256: expectedSha256 ?? createHash('sha256').update(readFileSync(archive)).digest('hex'), homeDirectory: join(temporaryRoot, 'rejected-home') }) const traversal = join(temporaryRoot, 'installer-traversal.gbagent') writeFileSync( traversal, renameStoredZipEntry( readFileSync(packagePath), 'agent/node', '../outside' ) ) expect(() => run(traversal)).toThrow(/unsafe|unordered/u) const duplicate = join(temporaryRoot, 'installer-duplicate.gbagent') const names = Object.keys( unzipSync(new Uint8Array(readFileSync(packagePath))) ) const duplicatePair = names .flatMap((left) => names.map((right) => [left, right] as const) ) .find( ([left, right]) => left !== right && Buffer.byteLength(left) === Buffer.byteLength(right) ) expect(duplicatePair).toBeDefined() writeFileSync( duplicate, renameStoredZipEntry( readFileSync(packagePath), duplicatePair![0], duplicatePair![1] ) ) expect(() => run(duplicate)).toThrow(/duplicate|unordered/u) const corrupt = join(temporaryRoot, 'installer-corrupt.gbagent') const corruptBytes = corruptStoredZipEntry( readFileSync(packagePath), 'agent/lib/agent.cjs' ) writeFileSync(corrupt, corruptBytes) expect(() => run(corrupt)).toThrow(/checksum|digest|header/u) expect(() => run(packagePath, '0'.repeat(64)) ).toThrow('SHA-256 does not match') }) it('rejects traversal, undeclared payload, and untrusted outer signatures', async () => { const traversal = join(temporaryRoot, 'traversal.gbagent') writeFileSync( traversal, Buffer.from( zipSync({ '../escape': new Uint8Array([1]) }) ) ) await expect( extractAndVerifyAgentPackage({ archivePath: traversal, destinationDirectory: join( temporaryRoot, 'traversal-output' ), desktopVersion: '0.11.0', trustedRegistry: registry }) ).rejects.toThrow('unsafe') const files = unzipSync( new Uint8Array(readFileSync(packagePath)) ) files['undeclared.txt'] = new Uint8Array([1]) const undeclared = join(temporaryRoot, 'undeclared.gbagent') writeFileSync(undeclared, Buffer.from(zipSync(files))) await expect( extractAndVerifyAgentPackage({ archivePath: undeclared, destinationDirectory: join( temporaryRoot, 'undeclared-output' ), desktopVersion: '0.11.0', trustedRegistry: registry }) ).rejects.toThrow('undeclared or missing') await expect( extractAndVerifyAgentPackage({ archivePath: packagePath, destinationDirectory: join( temporaryRoot, 'untrusted-output' ), desktopVersion: '0.11.0', trustedRegistry: { formatVersion: 1, keys: [], revocations: [] } }) ).rejects.toThrow('signing key is not trusted') }) it('enforces desktop compatibility and descriptor-to-inner-bundle identity', async () => { await expect( extractAndVerifyAgentPackage({ archivePath: packagePath, destinationDirectory: join( temporaryRoot, 'old-desktop-output' ), desktopVersion: '0.10.9', trustedRegistry: registry }) ).rejects.toThrow('not compatible') const files = unzipSync( new Uint8Array(readFileSync(packagePath)) ) const descriptor = agentPackageDescriptorSchema.parse( JSON.parse( Buffer.from(files['agent-package.json']!).toString('utf8') ) ) const mismatched = { ...descriptor, version: descriptor.version === '999.0.0' ? '999.0.1' : '999.0.0' } mismatched.contentDigest = agentPackage.descriptorContentDigest(mismatched) const descriptorBytes = agentPackage.descriptorBytes(mismatched) 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 mismatchPath = join(temporaryRoot, 'mismatch.gbagent') writeFileSync(mismatchPath, Buffer.from(zipSync(files))) await expect( extractAndVerifyAgentPackage({ archivePath: mismatchPath, destinationDirectory: join( temporaryRoot, 'mismatch-output' ), desktopVersion: '0.11.0', trustedRegistry: registry }) ).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) expect(compareSemanticVersions('1.0.0', '1.0.0-rc.1')) .toBeGreaterThan(0) expect(compareSemanticVersions('1.0.0-1', '1.0.0-alpha')) .toBeLessThan(0) const compatible = catalogEntry(packageResult.descriptor, { version: '1.1.0', minimumDesktopVersion: '0.11.0' }) const tooNew = catalogEntry(packageResult.descriptor, { version: '2.0.0', minimumDesktopVersion: '0.12.0' }) const incompatibleProtocol = catalogEntry( packageResult.descriptor, { version: '3.0.0', minimumDesktopVersion: '0.11.0', agentProtocol: { ...packageResult.descriptor.agentProtocol, major: packageResult.descriptor.agentProtocol.major + 1 } } ) const catalog = agentPackageCatalogSchema.parse({ formatVersion: 1, product: 'GoodBuddy', component: 'agent', signingKeyId: 'agent-package-fixture', generatedAt: '2026-08-24T00:00:00.000Z', entries: [tooNew, incompatibleProtocol, compatible] }) expect( selectLatestCompatibleEntry(catalog, 'x64', '0.11.0') .version ).toBe('1.1.0') }) it('builds and verifies a cumulative immutable dual-architecture catalog', () => { const firstRoot = join(temporaryRoot, 'catalog-first') const secondRoot = join(temporaryRoot, 'catalog-second') mkdirSync(firstRoot) mkdirSync(secondRoot) const firstCatalog = join(firstRoot, 'agent-catalog.json') const firstSignature = join(firstRoot, 'agent-catalog.sig') const result = agentCatalog.createCatalog({ projectRoot: process.cwd(), x64Package: packagePath, arm64Package: arm64PackagePath, outputCatalog: firstCatalog, outputSignature: firstSignature, generatedAt: '2026-08-24T00:00:00.000Z', registry, signingIdentity: { keyId: 'agent-package-fixture', privateKey } }) expect(result).toEqual({ version: agentLock.agentVersion, catalog: 'agent-catalog.json', signature: 'agent-catalog.sig', entries: 2 }) const catalog = agentCatalog.readVerifiedCatalog( firstCatalog, firstSignature, registry ) expect(catalog.entries.map((entry) => entry.architecture)) .toEqual(['arm64', 'x64']) expect(catalog.entries).toEqual( expect.arrayContaining([ expect.objectContaining({ architecture: 'x64', sha256: packageResult.sha256 }), expect.objectContaining({ architecture: 'arm64', sha256: arm64PackageResult.sha256 }) ]) ) const secondCatalog = join(secondRoot, 'agent-catalog.json') const secondSignature = join(secondRoot, 'agent-catalog.sig') agentCatalog.createCatalog({ projectRoot: process.cwd(), x64Package: packagePath, arm64Package: arm64PackagePath, previousCatalog: firstCatalog, previousSignature: firstSignature, outputCatalog: secondCatalog, outputSignature: secondSignature, generatedAt: '2026-08-24T00:00:00.000Z', registry, signingIdentity: { keyId: 'agent-package-fixture', privateKey } }) expect(readFileSync(secondCatalog)).toEqual( readFileSync(firstCatalog) ) expect(readFileSync(secondSignature)).toEqual( readFileSync(firstSignature) ) const changedRoot = join(temporaryRoot, 'changed-package') mkdirSync(changedRoot) const changedPath = join( changedRoot, agentPackageArchiveName(agentLock.agentVersion, 'x64') ) const originalPackage = readFileSync(packagePath) const changedPackage = Buffer.concat([ originalPackage, Buffer.from([0]) ]) changedPackage.writeUInt16LE(1, originalPackage.length - 2) writeFileSync( changedPath, changedPackage ) expect(() => agentCatalog.createCatalog({ projectRoot: process.cwd(), x64Package: changedPath, arm64Package: arm64PackagePath, previousCatalog: firstCatalog, previousSignature: firstSignature, outputCatalog: join( temporaryRoot, 'changed-catalog.json' ), outputSignature: join( temporaryRoot, 'changed-catalog.sig' ), generatedAt: '2026-08-24T00:00:00.000Z', registry, signingIdentity: { keyId: 'agent-package-fixture', privateKey } }) ).toThrow('identity is immutable') }) it('verifies stored payload bytes that contain a ZIP descriptor signature', () => { expect( agentCatalog.readPackageMetadata(packagePath, registry) ).toMatchObject({ descriptor: { architecture: 'x64' }, size: packageResult.size, sha256: packageResult.sha256 }) }) it('imports and exports a fully verified offline package atomically', async () => { const keyRegistryPath = join( temporaryRoot, 'trusted-registry.json' ) writeFileSync( keyRegistryPath, `${JSON.stringify(registry, null, 2)}\n` ) const manager = new AgentPackageManager({ userDataPath: join(temporaryRoot, 'user-data'), desktopVersion: '0.11.0', keyRegistryPath, getUpdateSource: vi.fn(async () => 'github' as const) }) expect( (await manager.getInventory()).entries.find( (entry) => entry.architecture === 'x64' ) ).toMatchObject({ architecture: 'x64', state: 'not-downloaded' }) const invalidArm64 = join( temporaryRoot, 'user-data', 'remote-components', 'agent-packages', 'linux-arm64', 'broken' ) mkdirSync(invalidArm64, { recursive: true }) writeFileSync(join(invalidArm64, 'junk'), 'invalid') expect( ( await manager.getInventory({ refresh: true }) ).entries.find( (entry) => entry.architecture === 'arm64' ) ).toMatchObject({ architecture: 'arm64', state: 'invalid' }) await manager.importArchive(packagePath) expect( (await manager.getInventory()).entries.find( (entry) => entry.architecture === 'x64' ) ).toMatchObject({ architecture: 'x64', state: 'verified', version: agentLock.agentVersion, remoteRuntimeVersion: remoteRuntimeLock.runtimes.opencode.version }) const exported = join(temporaryRoot, 'offline-export.gbagent') writeFileSync(exported, 'previous') await manager.exportArchive('x64', exported) expect(readFileSync(exported)).toEqual(readFileSync(packagePath)) }) it.each(['github', 'mirror'] as const)( 'checks the selected %s catalog without downloading until requested', async (source) => { const root = join(temporaryRoot, `online-${source}`) mkdirSync(root) const keyRegistryPath = join(root, 'registry.json') writeFileSync( keyRegistryPath, `${JSON.stringify(registry, null, 2)}\n` .replace(/\n/gu, '\r\n') ) const catalog = signedCatalog() const requested: string[] = [] const transport = vi.fn(async (input: string | URL) => { const url = input.toString() requested.push(url) if (url.includes('/releases?per_page=10&page=1')) { return jsonResponse([{ tag_name: `agent-v${agentLock.agentVersion}`, draft: false, prerelease: false, assets: [ { name: 'agent-catalog.json', browser_download_url: 'https://github.com/mesalogo/goodbuddy/releases/download/' + `agent-v${agentLock.agentVersion}/agent-catalog.json` }, { name: 'agent-catalog.sig', browser_download_url: 'https://github.com/mesalogo/goodbuddy/releases/download/' + `agent-v${agentLock.agentVersion}/agent-catalog.sig` } ] }]) } if (url.endsWith('/latest.json')) { return bytesResponse(mirrorPointer()) } if (url.endsWith('/agent-catalog.json')) { return bytesResponse(catalog.bytes) } if (url.endsWith('/agent-catalog.sig')) { return bytesResponse(catalog.signature) } if (url.endsWith(packageResult.archive)) { return bytesResponse(readFileSync(packagePath)) } return new Response(null, { status: 404 }) }) const manager = new AgentPackageManager({ userDataPath: join(root, 'user-data'), desktopVersion: '0.11.0', keyRegistryPath, getUpdateSource: vi.fn(async () => source), fetch: transport as typeof fetch }) 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=10&page=1' : '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' ? 'https://api.github.com/repos/mesalogo/goodbuddy/releases?per_page=10&page=1' : 'https://goodbuddy.oss-cn-beijing.aliyuncs.com/agent-releases/latest.json' ) expect(requested).toContain( source === 'github' ? 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) } ) it('rejects a catalog with an invalid signature before package download', async () => { const root = join(temporaryRoot, 'invalid-catalog') mkdirSync(root) const keyRegistryPath = join(root, 'registry.json') writeFileSync( keyRegistryPath, `${JSON.stringify(registry, null, 2)}\n` ) const catalog = signedCatalog() let packageRequested = false const transport = vi.fn(async (input: string | URL) => { const url = input.toString() if (url.includes('/releases?per_page=10&page=1')) { return jsonResponse([{ tag_name: `agent-v${agentLock.agentVersion}`, draft: false, prerelease: false, assets: [ { name: 'agent-catalog.json', browser_download_url: 'https://github.com/mesalogo/goodbuddy/releases/download/' + `agent-v${agentLock.agentVersion}/agent-catalog.json` }, { name: 'agent-catalog.sig', browser_download_url: 'https://github.com/mesalogo/goodbuddy/releases/download/' + `agent-v${agentLock.agentVersion}/agent-catalog.sig` } ] }]) } if (url.endsWith('/agent-catalog.json')) { return bytesResponse(catalog.bytes) } if (url.endsWith('/agent-catalog.sig')) { return bytesResponse(Buffer.from('invalid\n')) } packageRequested = true return bytesResponse(readFileSync(packagePath)) }) const manager = new AgentPackageManager({ userDataPath: join(root, 'user-data'), desktopVersion: '0.11.0', keyRegistryPath, getUpdateSource: vi.fn(async () => 'github' as const), 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 发布目录签名校验失败' ) expect(packageRequested).toBe(false) }) it('rejects an online catalog rollback before package download', async () => { const root = join(temporaryRoot, 'rollback-catalog') mkdirSync(root) const keyRegistryPath = join(root, 'registry.json') writeFileSync( keyRegistryPath, `${JSON.stringify(registry, null, 2)}\n` ) const rollback = signedCatalog([ catalogEntry(packageResult.descriptor, { version: '0.11.1', minimumDesktopVersion: '0.11.0' }) ]) let packageRequested = false const transport = vi.fn(async (input: string | URL) => { const url = input.toString() if (url.includes('/releases?per_page=10&page=1')) { return jsonResponse([{ tag_name: `agent-v${agentLock.agentVersion}`, draft: false, prerelease: false, assets: [ { name: 'agent-catalog.json', browser_download_url: 'https://github.com/mesalogo/goodbuddy/releases/download/' + `agent-v${agentLock.agentVersion}/agent-catalog.json` }, { name: 'agent-catalog.sig', browser_download_url: 'https://github.com/mesalogo/goodbuddy/releases/download/' + `agent-v${agentLock.agentVersion}/agent-catalog.sig` } ] }]) } if (url.endsWith('/agent-catalog.json')) { return bytesResponse(rollback.bytes) } if (url.endsWith('/agent-catalog.sig')) { return bytesResponse(rollback.signature) } packageRequested = true return bytesResponse(readFileSync(packagePath)) }) const manager = new AgentPackageManager({ userDataPath: join(root, 'user-data'), desktopVersion: '0.11.0', keyRegistryPath, getUpdateSource: vi.fn(async () => 'github' as const), fetch: transport as typeof fetch }) await manager.importArchive(packagePath) await expect(manager.download('x64')).rejects.toThrow( 'Agent 发布目录不能降级' ) expect(packageRequested).toBe(false) }) }) function writeAgentBundle( directory: string, architecture: 'x64' | 'arm64' ): void { mkdirSync(join(directory, 'lib'), { recursive: true }) mkdirSync(join(directory, 'licenses'), { recursive: true }) mkdirSync(join(directory, 'helpers'), { recursive: true }) writeFileSync(join(directory, 'node'), elf(architecture)) writeFileSync( join(directory, 'goodbuddy-agent'), '#!/bin/sh\n' ) writeFileSync( join(directory, 'lib', 'agent.cjs'), 'module.exports={}\n' ) writeFileSync( join(directory, 'helpers', 'fixture-helper'), '#!/bin/sh\n' ) for (const [name, contents] of [ ['GoodBuddy-0BSD.txt', '0BSD'], ['Node.js-MIT.txt', 'MIT'], ['zod-MIT.txt', 'MIT'], ['koffi-MIT.txt', 'MIT'], ['koffi-native-MIT.txt', 'MIT'] ] as const) { writeFileSync(join(directory, 'licenses', name), contents) } writeKoffiPayload(directory, architecture) for (const path of [ 'node', 'goodbuddy-agent', 'helpers/fixture-helper' ]) { chmodSync(join(directory, path), 0o755) } signAgentBundleManifest(directory, architecture) } function signAgentBundleManifest( directory: string, architecture: 'x64' | 'arm64' ): void { const manifest = agentBundle.createManifest(directory, { agentVersion: agentLock.agentVersion, nodeVersion: agentLock.node.version, zodVersion: '4.4.3', koffiVersion: agentLock.koffi.version, koffiNativePackage: `@koromix/koffi-linux-${architecture}`, arch: architecture, protocol: agentLock.protocol, signingKeyId: 'agent-package-fixture' }) const bytes = agentBundle.canonicalManifestBytes(manifest) writeFileSync(join(directory, 'manifest.json'), bytes) writeFileSync( join(directory, 'manifest.sig'), `${agentBundle .signManifestForTest(bytes, privateKey) .toString('base64')}\n` ) chmodSync(join(directory, 'manifest.json'), 0o644) chmodSync(join(directory, 'manifest.sig'), 0o644) } function writeKoffiPayload( directory: string, architecture: 'x64' | 'arm64' ): void { const packageRoot = join( directory, 'lib', 'node_modules', 'koffi' ) const nativePackage = `@koromix/koffi-linux-${architecture}` const nativeRoot = join( directory, 'lib', 'node_modules', ...nativePackage.split('/') ) mkdirSync(join(packageRoot, 'src', 'koffi', 'src'), { recursive: true }) mkdirSync(join(nativeRoot, `linux_${architecture}`), { recursive: true }) mkdirSync(join(nativeRoot, `musl_${architecture}`), { recursive: true }) const files = new Map([ [ join(packageRoot, 'package.json'), JSON.stringify({ name: 'koffi', version: agentLock.koffi.version, type: 'module', exports: { '.': { import: './index.js' } } }) ], [ join(packageRoot, 'index.js'), 'export { default } from "./src/koffi/index.js";\n' ], [ join(packageRoot, 'src', 'koffi', 'index.js'), 'export default {};\n' ], [ join(packageRoot, 'src', 'koffi', 'src', 'static.js'), 'export function loadStatic() { return null }\n' ], [ join(nativeRoot, 'package.json'), JSON.stringify({ name: nativePackage, version: agentLock.koffi.version, main: './index.js', os: ['linux'], cpu: [architecture] }) ], [ join(nativeRoot, 'index.js'), `module.exports = require('./linux_${architecture}/koffi.node');\n` ], [ join(nativeRoot, `linux_${architecture}`, 'koffi.node'), elf(architecture) ], [ join(nativeRoot, `musl_${architecture}`, 'koffi.node'), elf(architecture) ] ]) for (const [filePath, contents] of files) { writeFileSync(filePath, contents) chmodSync(filePath, 0o644) } } function createRuntimeInput( architecture: 'x64' | 'arm64' ): string { const sourceRoot = join( temporaryRoot, `runtime-input-${architecture}` ) const packageRoot = join(sourceRoot, 'package') mkdirSync(join(packageRoot, 'bin'), { recursive: true }) writeFileSync( join(packageRoot, 'package.json'), `${JSON.stringify({ name: architecture === 'x64' ? 'opencode-linux-x64-baseline' : 'opencode-linux-arm64', version: '1.18.29' })}\n` ) writeFileSync( join(packageRoot, 'bin', 'opencode'), Buffer.concat([ elf(architecture), Buffer.from([0x50, 0x4b, 0x07, 0x08]), Buffer.from('runtime payload') ]) ) chmodSync(join(packageRoot, 'bin', 'opencode'), 0o755) const archive = join( temporaryRoot, architecture === 'x64' ? 'opencode-linux-x64-baseline-1.18.29.tgz' : 'opencode-linux-arm64-1.18.29.tgz' ) createTar( { cwd: sourceRoot, file: archive, sync: true, portable: true, noPax: true, gzip: true, mtime: new Date(0) }, ['package/package.json', 'package/bin/opencode'] ) return archive } function createRemoteRuntimeLock( runtimeArchive: string, arm64RuntimeArchive: string ): RemoteRuntimeLock { return { formatVersion: 1, runtimes: { opencode: { version: '1.18.29', provider: 'opencode', entrypoint: 'bin/opencode', entrypointIdentity: 'opencode-acp', argvPrefix: ['acp'], allowedEnvironmentNames: [ 'HOME', 'LANG', 'LC_ALL', 'PATH', 'TMPDIR', 'XDG_CACHE_HOME', 'XDG_CONFIG_HOME', 'XDG_DATA_HOME', 'XDG_STATE_HOME' ], protocol: { major: 1, minor: 0 }, targets: { x64: { package: 'opencode-linux-x64-baseline', integrity: `sha512-${createHash('sha512') .update(readFileSync(runtimeArchive)) .digest('base64')}` }, arm64: { package: 'opencode-linux-arm64', integrity: `sha512-${createHash('sha512') .update(readFileSync(arm64RuntimeArchive)) .digest('base64')}` } } } } } } function elf(architecture: 'x64' | 'arm64'): Buffer { const contents = Buffer.alloc(64) contents.set([0x7f, 0x45, 0x4c, 0x46]) contents[5] = 1 contents.writeUInt16LE(architecture === 'x64' ? 62 : 183, 18) return contents } function findRuntimeBundleDirectory(): string { const digest = packageResult.descriptor.remoteRuntime.bundleDigest .slice('sha256:'.length) return join( temporaryRoot, 'runtime', 'linux-x64', 'opencode', digest ) } function packageEntry(): AgentPackageCatalogEntry { return catalogEntry(packageResult.descriptor, { archive: packageResult.archive, size: packageResult.size, sha256: packageResult.sha256 }) } function catalogEntry( descriptor: AgentPackageDescriptor, overrides: Partial = {} ): AgentPackageCatalogEntry { const { contentDigest: _contentDigest, files: _files, signingKeyId: _signingKeyId, ...identity } = descriptor void _contentDigest void _files void _signingKeyId const version = overrides.version ?? identity.version const architecture = overrides.architecture ?? identity.architecture const archive = overrides.archive ?? agentPackageArchiveName(version, architecture) return { ...identity, ...overrides, version, architecture, archive, size: overrides.size ?? packageResult.size, sha256: overrides.sha256 ?? packageResult.sha256, downloads: overrides.downloads ?? { github: { url: 'https://github.com/mesalogo/goodbuddy/releases/download/' + `agent-v${version}/${archive}` }, mirror: { url: 'https://goodbuddy.oss-cn-beijing.aliyuncs.com/' + `agent-releases/v${version}/${archive}` } } } } function signedCatalog( entries: AgentPackageCatalogEntry[] = [packageEntry()] ): { catalog: AgentPackageCatalog bytes: Buffer signature: Buffer } { const catalog = agentPackageCatalogSchema.parse({ formatVersion: 1, product: 'GoodBuddy', component: 'agent', signingKeyId: 'agent-package-fixture', generatedAt: '2026-08-24T00:00:00.000Z', entries }) const bytes = Buffer.from( `${JSON.stringify(catalog, null, 2)}\n` ) const signature = Buffer.from( `${sign( null, Buffer.concat([catalogSignatureDomain, bytes]), privateKey ).toString('base64')}\n` ) return { catalog, bytes, signature } } function bytesResponse(bytes: Buffer): Response { return new Response(new Uint8Array(bytes), { status: 200, headers: { 'content-length': String(bytes.byteLength) } }) } function jsonResponse(value: unknown): Response { return bytesResponse(Buffer.from(JSON.stringify(value))) } function mirrorPointer(): Buffer { return Buffer.from( `${JSON.stringify({ formatVersion: 1, version: agentLock.agentVersion, catalog: `v${agentLock.agentVersion}/agent-catalog.json`, signature: `v${agentLock.agentVersion}/agent-catalog.sig` }, null, 2)}\n` ) } function renameStoredZipEntry( archive: Buffer, oldName: string, newName: string ): Buffer { const oldBytes = Buffer.from(oldName, 'utf8') const newBytes = Buffer.from(newName, 'utf8') if (oldBytes.length !== newBytes.length) { throw new Error('ZIP fixture names must have equal byte lengths') } const output = Buffer.from(archive) let replacements = 0 for (let offset = 0; offset <= output.length - 46; offset += 1) { const signature = output.readUInt32LE(offset) const nameOffset = signature === 0x04034b50 ? offset + 30 : signature === 0x02014b50 ? offset + 46 : -1 const lengthOffset = signature === 0x04034b50 ? offset + 26 : signature === 0x02014b50 ? offset + 28 : -1 if ( nameOffset >= 0 && output.readUInt16LE(lengthOffset) === oldBytes.length && output.subarray(nameOffset, nameOffset + oldBytes.length) .equals(oldBytes) ) { newBytes.copy(output, nameOffset) replacements += 1 } } if (replacements !== 2) { throw new Error( `Expected local and central ZIP names, received ${replacements}` ) } return output } function corruptStoredZipEntry( archive: Buffer, name: string ): Buffer { const output = Buffer.from(archive) const nameBytes = Buffer.from(name, 'utf8') for (let offset = 0; offset <= output.length - 30; offset += 1) { if (output.readUInt32LE(offset) !== 0x04034b50) { continue } const nameLength = output.readUInt16LE(offset + 26) const extraLength = output.readUInt16LE(offset + 28) if ( nameLength === nameBytes.length && output.subarray(offset + 30, offset + 30 + nameLength) .equals(nameBytes) ) { const dataOffset = offset + 30 + nameLength + extraLength output[dataOffset] = (output[dataOffset] ?? 0) ^ 1 return output } } throw new Error(`ZIP fixture entry was not found: ${name}`) } function seedGlobalManagedMetadata( homeDirectory: string, digestCharacter: string ): Map { const managedRoot = join(homeDirectory, '.goodbuddy') const files = new Map([ [ join(managedRoot, 'agent', 'release-keys.json'), Buffer.from(`${JSON.stringify(registry, null, 4)}\r\n`) ], [ join(managedRoot, 'agent', 'registry.json'), Buffer.from( `${JSON.stringify({ formatVersion: 1, current: { installationId: `agent-${digestCharacter.repeat(64)}`, agentVersion: '0.10.0', manifestSha256: digestCharacter.repeat(64), arch: 'x64' } }, null, 4)}\r\n` ) ], [ join(managedRoot, 'runtimes', 'release-keys.json'), Buffer.from(`${JSON.stringify(registry, null, 3)}\r\n`) ], [ join( managedRoot, 'runtimes', 'remote-runtime-lock.json' ), Buffer.from(`${JSON.stringify(remoteRuntimeLock, null, 3)}\r\n`) ], [ join(managedRoot, 'runtimes', 'registry.json'), Buffer.from( `${JSON.stringify({ formatVersion: 1, current: [{ runtimeId: 'opencode', runtimeVersion: '1.0.0', architecture: 'x64', bundleDigest: `sha256:${digestCharacter.repeat(64)}`, manifestDigest: `sha256:${'1'.repeat(64)}`, acpCapabilitiesDigest: `sha256:${'2'.repeat(64)}` }] }, null, 4)}\r\n` ) ] ]) for (const [path, bytes] of files) { mkdirSync(dirname(path), { recursive: true, mode: 0o700 }) writeFileSync(path, bytes, { mode: 0o600 }) } return files } function expectGlobalMetadataUnchanged( files: ReadonlyMap ): void { for (const [path, bytes] of files) { expect(readFileSync(path)).toEqual(bytes) } } function expectRegistriesUnchangedAndPackageTrustPublished( files: ReadonlyMap ): void { for (const [path, bytes] of files) { if (path.endsWith('registry.json')) { expect(readFileSync(path)).toEqual(bytes) continue } const actual = JSON.parse(readFileSync(path, 'utf8')) expect(actual).toEqual( path.endsWith('remote-runtime-lock.json') ? remoteRuntimeLock : registry ) } }