feat: expand multi-runtime workflows for 0.10.0

GoodBuddy previously exposed Runtime capabilities, MCP assignments, plugin controls, context compaction, usage reporting, and task-completion behavior through incomplete or inconsistent paths. This release unifies managed OpenCode, Continue, and DeepSeek Harness controls; adds DSH plugin and image workflows; strengthens MCP and Runtime lifecycle bounds; fixes Windows notification activation; validates cross-architecture packages; and presents the approved bilingual four-section release notes.

The DSH plugin marketplace remains a default-off preview whose trusted third-party code runs with current-user permissions. Ask remains read-only, Execute keeps approval controls, and context compaction may use the selected model without deleting GoodBuddy chat history.

Release note: GoodBuddy 0.10.0 重点完善多 Runtime 工作流,统一 OpenCode、Continue 与 DeepSeek Harness 的能力、MCP、插件和上下文管理,并提升长对话、多会话与任务通知的连贯性。
This commit is contained in:
mesalogo
2026-08-17 12:57:12 +08:00
parent f5ee9b2198
commit cbbe30896e
50 changed files with 4077 additions and 605 deletions
+74 -23
View File
@@ -527,14 +527,39 @@ function targetHarnessPaths(options) {
function targetRuntimePackageNames(options) {
const target = targetHarnessPaths(options)
return [target.koffiPackage]
return [target.koffiPackage, target.canvasPackage]
}
function lockedTargetRuntimePackage(packageName) {
const expectedVersion =
packageJson.optionalDependencies?.[packageName]
function lockedTargetRuntimePackage(
packageName,
packageMetadata = packageJson,
lockMetadata = packageLock
) {
let expectedVersion =
packageMetadata.optionalDependencies?.[packageName]
if (
typeof expectedVersion !== 'string' &&
packageName.startsWith('@napi-rs/canvas-')
) {
const canvasPackageName = '@napi-rs/canvas'
const canvasVersion =
packageMetadata.dependencies?.[canvasPackageName]
const canvasLockEntry =
lockMetadata.packages?.[`node_modules/${canvasPackageName}`]
if (
typeof canvasVersion !== 'string' ||
canvasLockEntry?.version !== canvasVersion ||
canvasLockEntry.optionalDependencies?.[packageName] !==
canvasVersion
) {
throw new Error(
`目标 Runtime 依赖未完整锁定:${packageName}`
)
}
expectedVersion = canvasVersion
}
const lockEntry =
packageLock.packages?.[`node_modules/${packageName}`]
lockMetadata.packages?.[`node_modules/${packageName}`]
if (
typeof expectedVersion !== 'string' ||
lockEntry?.version !== expectedVersion ||
@@ -548,7 +573,6 @@ function lockedTargetRuntimePackage(packageName) {
return {
name: packageName,
version: expectedVersion,
resolved: lockEntry.resolved,
integrity: lockEntry.integrity
}
}
@@ -596,9 +620,13 @@ function verifyArchiveIntegrity(filePath, expectedIntegrity) {
}
}
function installedPackageMatches(packageName, expectedVersion) {
function installedPackageMatches(
packageName,
expectedVersion,
runtimeRoot = root
) {
const manifestPath = join(
root,
runtimeRoot,
'node_modules',
...packageName.split('/'),
'package.json'
@@ -618,14 +646,29 @@ function installedPackageMatches(packageName, expectedVersion) {
return true
}
async function stageTargetRuntimeDependencies(options) {
async function stageTargetRuntimeDependencies(
options,
dependencies = {}
) {
const runtimeRoot = dependencies.root ?? root
const runtimePackageJson =
dependencies.packageJson ?? packageJson
const runtimePackageLock =
dependencies.packageLock ?? packageLock
const missing = targetRuntimePackageNames(options)
.map(lockedTargetRuntimePackage)
.map((packageName) =>
lockedTargetRuntimePackage(
packageName,
runtimePackageJson,
runtimePackageLock
)
)
.filter(
(dependency) =>
!installedPackageMatches(
dependency.name,
dependency.version
dependency.version,
runtimeRoot
)
)
if (missing.length === 0) {
@@ -644,14 +687,27 @@ async function stageTargetRuntimeDependencies(options) {
}
try {
const npm = npmInvocation()
const npm = dependencies.npmInvocation?.() ?? npmInvocation()
const captureCommand =
dependencies.runCapture ?? runCapture
const extractArchive =
dependencies.extractArchive ??
((archivePath, destination) =>
run('tar', [
'-xzf',
archivePath,
'-C',
destination,
'--strip-components',
'1'
]))
for (const [index, dependency] of missing.entries()) {
const archiveDirectory = join(
stagingRoot,
`package-${index}`
)
mkdirSync(archiveDirectory, { recursive: true })
const output = await runCapture(npm.command, [
const output = await captureCommand(npm.command, [
...npm.prefixArgs,
'pack',
`${dependency.name}@${dependency.version}`,
@@ -671,7 +727,7 @@ async function stageTargetRuntimeDependencies(options) {
verifyArchiveIntegrity(archivePath, dependency.integrity)
const destination = join(
root,
runtimeRoot,
'node_modules',
...dependency.name.split('/')
)
@@ -682,18 +738,12 @@ async function stageTargetRuntimeDependencies(options) {
}
mkdirSync(destination, { recursive: true })
stagedDirectories.push(destination)
await run('tar', [
'-xzf',
archivePath,
'-C',
destination,
'--strip-components',
'1'
])
await extractArchive(archivePath, destination)
if (
!installedPackageMatches(
dependency.name,
dependency.version
dependency.version,
runtimeRoot
)
) {
throw new Error(
@@ -1619,6 +1669,7 @@ module.exports = {
parseArguments,
parsePackedPackageMetadata,
platformDefinitions,
lockedTargetRuntimePackage,
replaceOutput,
stageTargetRuntimeDependencies,
targetRuntimePackageNames,