Display native OpenCode and Continue checklists alongside scheduled tasks, preserving request ownership, explicit clears and saved progress. Bundle both remote runtimes and support managed Continue installation, model routing, cancellation and restart continuation. Validation: real local and Linux remote model checks, full Electron remote Continue flows, typecheck and lint passed. Full suite: 4707 passed, 67 skipped, one OpenCode compaction failure passed targeted rerun. Release note: Display native execution checklists above conversations and support managed remote Continue projects with saved progress and cancellation recovery.
317 lines
8.7 KiB
TypeScript
317 lines
8.7 KiB
TypeScript
import { z } from 'zod'
|
|
import { agentPlatformSchema, agentTargetKey, type AgentPlatform } from './agent-target'
|
|
import {
|
|
agentArchitectureSchema,
|
|
agentProtocolVersionSchema
|
|
} from './agent-installation-contracts'
|
|
|
|
export const AGENT_PACKAGE_FORMAT = 'goodbuddy-agent-package'
|
|
export const AGENT_PACKAGE_FORMAT_VERSION = 1
|
|
|
|
export function agentPackageArchiveName(
|
|
version: string,
|
|
architecture: 'x64' | 'arm64',
|
|
platform: AgentPlatform = 'linux'
|
|
): string {
|
|
return `goodbuddy-agent-${version}-${agentTargetKey({ platform, architecture })}.gbagent`
|
|
}
|
|
|
|
const semanticVersionSchema = z
|
|
.string()
|
|
.min(1)
|
|
.max(128)
|
|
.regex(
|
|
/^(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)(?:-(?:(?:0|[1-9]\d*)|(?:\d*[A-Za-z-][0-9A-Za-z-]*))(?:\.(?:(?:0|[1-9]\d*)|(?:\d*[A-Za-z-][0-9A-Za-z-]*)))*)?$/u
|
|
)
|
|
const sha256Schema = z.string().regex(/^[a-f0-9]{64}$/u)
|
|
const sha256DigestSchema = z.string().regex(/^sha256:[a-f0-9]{64}$/u)
|
|
const signingKeyIdSchema = z
|
|
.string()
|
|
.regex(/^[a-z0-9][a-z0-9._-]{0,63}$/u)
|
|
const safeArchiveNameSchema = z
|
|
.string()
|
|
.min(1)
|
|
.max(255)
|
|
.regex(
|
|
/^goodbuddy-agent-(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)(?:-[0-9A-Za-z.-]+)?-(?:linux-(?:x64|arm64)|darwin-arm64)\.gbagent$/u
|
|
)
|
|
|
|
const windowsReservedNamePattern =
|
|
/^(?:con|prn|aux|nul|com[1-9]|lpt[1-9])(?:\.|$)/iu
|
|
|
|
export function isSafeAgentPackagePath(path: string): boolean {
|
|
return (
|
|
path.length > 0 &&
|
|
path.length <= 512 &&
|
|
!path.startsWith('/') &&
|
|
!path.endsWith('/') &&
|
|
!path.includes('\\') &&
|
|
path.split('/').every(
|
|
(part) =>
|
|
part.length > 0 &&
|
|
part !== '.' &&
|
|
part !== '..' &&
|
|
!part.endsWith('.') &&
|
|
!part.endsWith(' ') &&
|
|
!part.includes(':') &&
|
|
[...part].every(
|
|
(character) => character.charCodeAt(0) > 0x1f
|
|
) &&
|
|
!windowsReservedNamePattern.test(part)
|
|
)
|
|
)
|
|
}
|
|
|
|
const packageFilePathSchema = z
|
|
.string()
|
|
.min(1)
|
|
.max(512)
|
|
.refine(
|
|
isSafeAgentPackagePath,
|
|
'Agent package file path is unsafe'
|
|
)
|
|
|
|
export const agentPackageFileSchema = z
|
|
.object({
|
|
path: packageFilePathSchema,
|
|
size: z.number().int().nonnegative().safe(),
|
|
sha256: sha256Schema,
|
|
mode: z.enum(['0644', '0755'])
|
|
})
|
|
.strict()
|
|
|
|
const packagedRuntimeSchema = z.object({
|
|
runtimeId: z.enum(['opencode', 'continue']),
|
|
provider: z.enum(['opencode', 'continue']),
|
|
version: semanticVersionSchema,
|
|
bundleDigest: sha256DigestSchema,
|
|
protocol: agentProtocolVersionSchema
|
|
}).strict().refine(value => value.runtimeId === value.provider, {
|
|
message: 'Runtime provider must match its identity'
|
|
})
|
|
|
|
const agentPackageDescriptorObjectSchema = z
|
|
.object({
|
|
format: z.literal(AGENT_PACKAGE_FORMAT),
|
|
formatVersion: z.literal(AGENT_PACKAGE_FORMAT_VERSION),
|
|
product: z.literal('GoodBuddy'),
|
|
component: z.literal('agent'),
|
|
version: semanticVersionSchema,
|
|
minimumDesktopVersion: semanticVersionSchema,
|
|
platform: agentPlatformSchema,
|
|
architecture: agentArchitectureSchema,
|
|
signingKeyId: signingKeyIdSchema,
|
|
agentProtocol: agentProtocolVersionSchema,
|
|
remoteRuntime: packagedRuntimeSchema,
|
|
additionalRuntimes: z.array(packagedRuntimeSchema).max(1).optional(),
|
|
contentDigest: sha256DigestSchema,
|
|
files: z
|
|
.array(agentPackageFileSchema)
|
|
.min(1)
|
|
.max(50_000)
|
|
})
|
|
.strict()
|
|
|
|
export const agentPackageDescriptorSchema =
|
|
agentPackageDescriptorObjectSchema
|
|
.superRefine((descriptor, context) => {
|
|
if (descriptor.additionalRuntimes?.some(runtime => runtime.runtimeId === descriptor.remoteRuntime.runtimeId)) {
|
|
context.addIssue({ code: 'custom', path: ['additionalRuntimes'], message: 'Duplicate packaged Runtime' })
|
|
}
|
|
const paths = descriptor.files.map((file) => file.path)
|
|
if (new Set(paths).size !== paths.length) {
|
|
context.addIssue({
|
|
code: 'custom',
|
|
path: ['files'],
|
|
message: 'Agent package contains duplicate file paths'
|
|
})
|
|
}
|
|
})
|
|
|
|
export type AgentPackageDescriptor = z.infer<
|
|
typeof agentPackageDescriptorSchema
|
|
>
|
|
|
|
export const agentPackageDownloadSchema = z
|
|
.object({
|
|
url: z.url().max(2_048)
|
|
})
|
|
.strict()
|
|
|
|
export const agentPackageCatalogEntrySchema =
|
|
agentPackageDescriptorObjectSchema
|
|
.omit({
|
|
contentDigest: true,
|
|
files: true,
|
|
signingKeyId: true
|
|
})
|
|
.extend({
|
|
archive: safeArchiveNameSchema,
|
|
size: z.number().int().positive().safe(),
|
|
sha256: sha256Schema,
|
|
downloads: z
|
|
.object({
|
|
github: agentPackageDownloadSchema,
|
|
mirror: agentPackageDownloadSchema
|
|
})
|
|
.strict()
|
|
})
|
|
.strict()
|
|
.superRefine((entry, context) => {
|
|
if (entry.additionalRuntimes?.some(runtime => runtime.runtimeId === entry.remoteRuntime.runtimeId)) {
|
|
context.addIssue({ code: 'custom', path: ['additionalRuntimes'], message: 'Duplicate packaged Runtime' })
|
|
}
|
|
if (
|
|
entry.archive !==
|
|
agentPackageArchiveName(entry.version, entry.architecture, entry.platform)
|
|
) {
|
|
context.addIssue({
|
|
code: 'custom',
|
|
path: ['archive'],
|
|
message:
|
|
'Agent package archive name does not match its identity'
|
|
})
|
|
}
|
|
})
|
|
|
|
export type AgentPackageCatalogEntry = z.infer<
|
|
typeof agentPackageCatalogEntrySchema
|
|
>
|
|
|
|
export const agentPackageCatalogSchema = z
|
|
.object({
|
|
formatVersion: z.literal(1),
|
|
product: z.literal('GoodBuddy'),
|
|
component: z.literal('agent'),
|
|
signingKeyId: signingKeyIdSchema,
|
|
generatedAt: z.string().datetime(),
|
|
entries: z
|
|
.array(agentPackageCatalogEntrySchema)
|
|
.min(1)
|
|
.max(200)
|
|
})
|
|
.strict()
|
|
.superRefine((catalog, context) => {
|
|
const identities = catalog.entries.map(
|
|
(entry) => `${entry.version}:${entry.platform}:${entry.architecture}`
|
|
)
|
|
if (new Set(identities).size !== identities.length) {
|
|
context.addIssue({
|
|
code: 'custom',
|
|
path: ['entries'],
|
|
message: 'Agent package catalog contains duplicate entries'
|
|
})
|
|
}
|
|
})
|
|
|
|
export type AgentPackageCatalog = z.infer<
|
|
typeof agentPackageCatalogSchema
|
|
>
|
|
|
|
export const agentPackageStateSchema = z.enum([
|
|
'not-downloaded',
|
|
'verified',
|
|
'invalid'
|
|
])
|
|
|
|
export const agentPackageInventoryEntrySchema = z
|
|
.object({
|
|
platform: agentPlatformSchema,
|
|
architecture: agentArchitectureSchema,
|
|
state: agentPackageStateSchema,
|
|
version: semanticVersionSchema.nullable(),
|
|
latestVersion: semanticVersionSchema.nullable(),
|
|
updateAvailable: z.boolean(),
|
|
remoteRuntimeVersion: semanticVersionSchema.nullable(),
|
|
agentProtocol: agentProtocolVersionSchema.nullable()
|
|
})
|
|
.strict()
|
|
|
|
export const agentPackageCatalogStatusSchema = z
|
|
.discriminatedUnion('state', [
|
|
z
|
|
.object({
|
|
state: z.literal('not-checked'),
|
|
checkedAt: z.null(),
|
|
error: z.null()
|
|
})
|
|
.strict(),
|
|
z
|
|
.object({
|
|
state: z.literal('available'),
|
|
checkedAt: z.string().datetime(),
|
|
error: z.null()
|
|
})
|
|
.strict(),
|
|
z
|
|
.object({
|
|
state: z.literal('unavailable'),
|
|
checkedAt: z.string().datetime(),
|
|
error: z.string().trim().min(1).max(2_000)
|
|
})
|
|
.strict()
|
|
])
|
|
|
|
export const agentPackageInventorySchema = z
|
|
.object({
|
|
checkedAt: z.string().datetime(),
|
|
catalog: agentPackageCatalogStatusSchema,
|
|
entries: z
|
|
.array(agentPackageInventoryEntrySchema)
|
|
.min(1).max(3)
|
|
})
|
|
.strict()
|
|
.superRefine((inventory, context) => {
|
|
const architectures = inventory.entries.map(
|
|
(entry) => `${entry.platform}-${entry.architecture}`
|
|
)
|
|
if (new Set(architectures).size !== architectures.length) {
|
|
context.addIssue({
|
|
code: 'custom',
|
|
path: ['entries'],
|
|
message:
|
|
'Agent package inventory contains duplicate architectures'
|
|
})
|
|
}
|
|
})
|
|
|
|
export type AgentPackageInventory = z.infer<
|
|
typeof agentPackageInventorySchema
|
|
>
|
|
|
|
export const agentPackageInventoryRequestSchema = z
|
|
.object({
|
|
refresh: z.boolean().default(false)
|
|
})
|
|
.strict()
|
|
|
|
export const agentPackageArchitectureRequestSchema = z
|
|
.object({
|
|
architecture: agentArchitectureSchema,
|
|
platform: agentPlatformSchema.default('linux')
|
|
})
|
|
.strict()
|
|
|
|
export type AgentPackageArchitectureRequest = z.infer<
|
|
typeof agentPackageArchitectureRequestSchema
|
|
>
|
|
|
|
export const agentPackageDownloadProgressSchema = z
|
|
.object({
|
|
architecture: agentArchitectureSchema,
|
|
platform: agentPlatformSchema.default('linux'),
|
|
phase: z.enum([
|
|
'catalog',
|
|
'downloading',
|
|
'verifying',
|
|
'installing'
|
|
]),
|
|
completedBytes: z.number().int().nonnegative().safe(),
|
|
totalBytes: z.number().int().positive().safe().nullable()
|
|
})
|
|
.strict()
|
|
|
|
export type AgentPackageDownloadProgress = z.infer<
|
|
typeof agentPackageDownloadProgressSchema
|
|
>
|