Files
goodbuddy/src/main/agent/deepseek-harness-utility-launcher.ts
T
mesalogo cbbe30896e 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、插件和上下文管理,并提升长对话、多会话与任务通知的连贯性。
2026-08-17 12:57:12 +08:00

309 lines
9.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { Readable } from 'node:stream'
import { realpath, stat } from 'node:fs/promises'
import { isAbsolute } from 'node:path'
import type { UtilityProcess } from 'electron'
import { isDeepSeekHarnessCompatibleBaseUrl } from '../../shared/deepseek-harness-compatibility'
import type {
DeepSeekHarnessChild,
DeepSeekHarnessLaunchOptions
} from './deepseek-harness-runtime'
import { createDeepSeekHarnessUtilityChild } from './deepseek-harness-utility-transport'
import {
controlledHarnessHostConfigSchema,
DEEPSEEK_HARNESS_CONTROL_PROTOCOL,
DEEPSEEK_HARNESS_CONTROL_VERSION,
DEEPSEEK_HARNESS_CREDENTIAL_REF,
DEEPSEEK_HARNESS_HOST_VERSION,
DEEPSEEK_HARNESS_MAX_FRAME_BYTES,
deepSeekHarnessStartupBudget,
parseHarnessControlMessage,
type DeepSeekHarnessControlMessage as HarnessControlMessage
} from './deepseek-harness-control-protocol'
export {
controlledHarnessHostConfigSchema,
DEEPSEEK_HARNESS_CONTROL_PROTOCOL,
DEEPSEEK_HARNESS_CONTROL_VERSION,
DEEPSEEK_HARNESS_CREDENTIAL_REF,
DEEPSEEK_HARNESS_HOST_VERSION,
DEEPSEEK_HARNESS_MAX_FRAME_BYTES,
parseHarnessControlMessage
} from './deepseek-harness-control-protocol'
export type {
ControlledHarnessBootstrapConfig,
DeepSeekHarnessControlMessage
} from './deepseek-harness-control-protocol'
export type DeepSeekHarnessFork = (
modulePath: string,
args: string[],
options: {
cwd: string
env: NodeJS.ProcessEnv
serviceName: string
stdio: ['ignore', 'ignore', 'pipe']
}
) => UtilityProcess
export type DeepSeekHarnessUtilityLauncherOptions = {
bundledHostPath: string
dshHome: string
environment: NodeJS.ProcessEnv
fork: DeepSeekHarnessFork
terminateProcess?: (utility: UtilityProcess) => void
onExtensionStartupFailures?: (
extensionIds: readonly string[]
) => Promise<void>
/**
* Explicit hard Host-handshake deadline. Callers that also set the Runtime
* initialization timeout must leave enough additional time for startup
* failure persistence.
*/
startupTimeoutMs?: number
}
function hasControlCharacter(value: string): boolean {
for (const character of value) {
const codePoint = character.codePointAt(0)
if (
codePoint !== undefined &&
(codePoint <= 0x1f || codePoint === 0x7f)
) {
return true
}
}
return false
}
export function createDeepSeekHarnessUtilityLauncher(
launcherOptions: DeepSeekHarnessUtilityLauncherOptions
): (options: DeepSeekHarnessLaunchOptions) => Promise<DeepSeekHarnessChild> {
return async (options) => {
options.signal.throwIfAborted()
const hostPath = launcherOptions.bundledHostPath
if (!isAbsolute(hostPath)) {
throw new Error('DeepSeek Harness Host 路径必须为绝对路径')
}
if (!isAbsolute(options.cwd) || !isAbsolute(launcherOptions.dshHome)) {
throw new Error(
'DeepSeek Harness 工作区和隔离目录必须为绝对路径'
)
}
if (
options.model.length === 0 ||
options.model.length > 128 ||
hasControlCharacter(options.model)
) {
throw new Error('DeepSeek Harness 模型名称无效')
}
const canonicalSkillPackages = await Promise.all(
options.skillPackages.map(async (skill) => {
const directory = await realpath(skill.directory)
const metadata = await stat(directory)
if (!metadata.isDirectory()) {
throw new Error(
'DeepSeek Harness Skill 路径必须为目录'
)
}
return {
id: skill.id,
directory
}
})
)
const canonicalExtensionPackages = await Promise.all(
options.extensionPackages.map(async (extension) => {
const entrypoint = await realpath(extension.entrypoint)
const metadata = await stat(entrypoint)
if (!metadata.isFile()) {
throw new Error(
'DeepSeek Harness 插件入口必须为文件'
)
}
return {
...extension,
entrypoint
}
})
)
const [canonicalHostPath, canonicalWorkspace, canonicalDshHome] =
await Promise.all([
realpath(hostPath),
realpath(options.cwd),
realpath(launcherOptions.dshHome)
])
const [hostMetadata, workspaceMetadata, homeMetadata] =
await Promise.all([
stat(canonicalHostPath),
stat(canonicalWorkspace),
stat(canonicalDshHome)
])
if (
!hostMetadata.isFile() ||
!workspaceMetadata.isDirectory() ||
!homeMetadata.isDirectory()
) {
throw new Error(
'DeepSeek Harness Host、工作区或隔离目录类型无效'
)
}
if (!isDeepSeekHarnessCompatibleBaseUrl(options.baseUrl)) {
throw new Error(
'DeepSeek Harness 模型地址必须使用 HTTPS 或本机回环 HTTP,且不得包含凭据、查询参数或片段'
)
}
if (
options.credentialRefs.length !== 1 ||
options.credentialRefs[0] !==
DEEPSEEK_HARNESS_CREDENTIAL_REF
) {
throw new Error('DeepSeek Harness 凭据引用不受信任')
}
options.signal.throwIfAborted()
const utility = launcherOptions.fork(canonicalHostPath, [], {
cwd: canonicalWorkspace,
env: launcherOptions.environment,
serviceName: 'GoodBuddy DeepSeek Harness Host',
stdio: ['ignore', 'ignore', 'pipe']
})
let terminated = false
const terminate = (): void => {
if (terminated) {
return
}
terminated = true
if (launcherOptions.terminateProcess) {
launcherOptions.terminateProcess(utility)
} else {
utility.kill()
}
}
const startupTimeoutMs =
launcherOptions.startupTimeoutMs ??
deepSeekHarnessStartupBudget(
canonicalExtensionPackages.length
).hostTimeoutMs
let timer: ReturnType<typeof setTimeout> | undefined
let onAbort: (() => void) | undefined
try {
await new Promise<void>((resolve, reject) => {
let settled = false
const cleanup = (): void => {
if (timer) {
clearTimeout(timer)
}
if (onAbort) {
options.signal.removeEventListener('abort', onAbort)
}
utility.removeListener('message', onMessage)
utility.removeListener('exit', onExit)
}
const fail = (error: Error): void => {
if (settled) {
return
}
settled = true
cleanup()
terminate()
reject(error)
}
const succeed = (): void => {
if (settled) {
return
}
settled = true
cleanup()
resolve()
}
const onMessage = (message: unknown): void => {
const control = parseHarnessControlMessage(message)
if (!control) {
fail(new Error('DeepSeek Harness Host 启动协议无效'))
return
}
if (control.type === 'ready') {
utility.removeListener('message', onMessage)
if (timer) {
clearTimeout(timer)
timer = undefined
}
void (
control.failedExtensionIds.length > 0
? launcherOptions.onExtensionStartupFailures?.(
control.failedExtensionIds
) ?? Promise.resolve()
: Promise.resolve()
).then(succeed, (error: unknown) => {
fail(
error instanceof Error
? error
: new Error(
'DeepSeek Harness 插件失败状态保存失败'
)
)
})
} else if (control.type === 'fatal') {
fail(
new Error(
`DeepSeek Harness Host 启动失败(${control.code}`
)
)
}
}
const onExit = (exitCode: number): void => {
fail(
new Error(
`DeepSeek Harness Host 启动前退出(code ${exitCode}`
)
)
}
onAbort = () => {
fail(
options.signal.reason instanceof Error
? options.signal.reason
: new Error('DeepSeek Harness Host 启动已取消')
)
}
utility.on('message', onMessage)
utility.on('exit', onExit)
options.signal.addEventListener('abort', onAbort, {
once: true
})
timer = setTimeout(
() =>
fail(new Error('DeepSeek Harness Host 启动握手超时')),
startupTimeoutMs
)
const config = controlledHarnessHostConfigSchema.parse({
workspace: canonicalWorkspace,
dshHome: canonicalDshHome,
baseUrl: options.baseUrl,
api: 'openai-completions',
provider: 'goodbuddy',
model: options.model,
supportsImageInput: options.supportsImageInput,
harnessVersion: DEEPSEEK_HARNESS_HOST_VERSION,
credentialRefs: [DEEPSEEK_HARNESS_CREDENTIAL_REF],
skillPackages: canonicalSkillPackages,
extensionPackages: canonicalExtensionPackages,
maxFrameBytes: DEEPSEEK_HARNESS_MAX_FRAME_BYTES
})
utility.postMessage({
protocol: DEEPSEEK_HARNESS_CONTROL_PROTOCOL,
version: DEEPSEEK_HARNESS_CONTROL_VERSION,
type: 'start',
config
} satisfies HarnessControlMessage)
})
return createDeepSeekHarnessUtilityChild(utility, {
stderrToWeb: (stderr) =>
Readable.toWeb(stderr) as ReadableStream<Uint8Array>,
terminateProcess: terminate
})
} catch (error) {
terminate()
throw error
}
}
}