feat: add DeepSeek Harness runtime

This commit is contained in:
lofyer
2026-08-14 10:04:34 +08:00
parent fca9888f83
commit 8286e120a1
69 changed files with 13660 additions and 351 deletions
+15
View File
@@ -28,6 +28,13 @@ describe('bundled runtime paths', () => {
'cli',
'dist',
'cn.js'
),
deepseekHarness: join(
'workspace',
'app',
'out',
'main',
'deepseek-harness-host-bootstrap.js'
)
})
})
@@ -55,6 +62,14 @@ describe('bundled runtime paths', () => {
'continue',
'dist',
'cn.js'
),
deepseekHarness: join(
'installed',
'resources',
'app.asar.unpacked',
'out',
'main',
'deepseek-harness-host-bootstrap.js'
)
})
})
+17
View File
@@ -3,8 +3,12 @@ import { join } from 'node:path'
export type BundledRuntimePaths = {
opencode: string
continue: string
deepseekHarness: string
}
export const bundledContinueVersion = '1.5.47'
export const bundledDeepSeekHarnessVersion = '0.1.0-rc.6'
export function resolveBundledRuntimePaths(input: {
appPath: string
resourcesPath: string
@@ -29,6 +33,13 @@ export function resolveBundledRuntimePaths(input: {
'continue',
'dist',
'cn.js'
),
deepseekHarness: join(
input.resourcesPath,
'app.asar.unpacked',
'out',
'main',
'deepseek-harness-host-bootstrap.js'
)
}
}
@@ -48,6 +59,12 @@ export function resolveBundledRuntimePaths(input: {
'cli',
'dist',
'cn.js'
),
deepseekHarness: join(
input.appPath,
'out',
'main',
'deepseek-harness-host-bootstrap.js'
)
}
}
+32
View File
@@ -71,6 +71,38 @@ function settings(
}
describe('createAgentRuntime model compatibility', () => {
it('does not treat the default model profile as the platform DeepSeek source', () => {
const defaultProfile = {
id: '00000000-0000-4000-8000-000000000001',
name: 'Default DeepSeek',
baseUrl: 'https://api.deepseek.com',
modelName: 'deepseek-chat',
protocol: 'openai-chat-completions' as const,
authentication: 'api-key' as const,
imageGenerationQuality: 'auto' as const,
apiKey: 'default-deepseek-key'
}
expect(() =>
createAgentRuntime(
process.cwd(),
settings({
provider: 'deepseek-harness',
modelBaseUrl: defaultProfile.baseUrl,
modelName: defaultProfile.modelName,
modelProtocol: defaultProfile.protocol,
modelAuthentication: defaultProfile.authentication,
apiKey: defaultProfile.apiKey,
modelProfiles: [defaultProfile],
runtimeSandboxMode: 'auto'
}),
{ deepseekHarnessLauncher: vi.fn() }
)
).toThrow(
'DeepSeek Harness 需要 api.deepseek.com 的 OpenAI Chat Completions 模型连接'
)
})
it('creates an available direct runtime for a no-auth model', async () => {
const runtime = createAgentRuntime(process.cwd(), settings())
+44
View File
@@ -1,6 +1,10 @@
import { ModelAgentRuntime } from './model-runtime'
import { ContinueAgentRuntime } from './continue-runtime'
import { OpenCodeRuntime } from './opencode-runtime'
import {
DeepSeekHarnessRuntime,
type DeepSeekHarnessRuntimeOptions
} from './deepseek-harness-runtime'
import type { AgentRuntime } from './runtime'
import { UnconfiguredAgentRuntime } from './unconfigured-runtime'
import type {
@@ -9,6 +13,7 @@ import type {
} from '../runtime-settings-store'
import {
defaultRuntimeSettings,
isDeepSeekHarnessModelProfile,
isAgentRuntimeModelProtocol
} from '../../shared/contracts'
import type {
@@ -21,6 +26,7 @@ import { resolveRuntimeSandbox } from './runtime-sandbox'
import type { BrowserToolService } from '../browser/browser-model-tools'
import type { ModelToolProviderLike } from './model-tool-provider'
import type { KnowledgeMcpGateway } from './knowledge-mcp-gateway'
import { ModelToolProvider } from './model-tool-provider'
const noSubagentTools: ModelToolProviderLike = {
listTools: async () => [],
@@ -41,6 +47,7 @@ export type AgentCapabilityContext = {
continueHostCacheRoot?: string
bundledRuntimePaths?: BundledRuntimePaths
continueHostLauncher?: ContinueHostLauncher
deepseekHarnessLauncher?: DeepSeekHarnessRuntimeOptions['launch']
browserService?: BrowserToolService
knowledgeGateway?: KnowledgeMcpGateway
webSearchEnabled?: boolean
@@ -102,6 +109,43 @@ export function createAgentRuntime(
settings?.runtimeSandboxMode ??
defaultRuntimeSettings.runtimeSandboxMode
if (provider === 'deepseek-harness') {
const profile = settings?.deepseekHarnessModelProfile
if (!profile || !isDeepSeekHarnessModelProfile(profile)) {
throw new Error(
'DeepSeek Harness 需要 api.deepseek.com 的 OpenAI Chat Completions 模型连接'
)
}
if (!profile.apiKey) {
throw new Error('DeepSeek Harness 模型连接未配置 API Key')
}
if (!capabilities.deepseekHarnessLauncher) {
throw new Error('DeepSeek Harness 受控 Host 启动器不可用')
}
if (sandboxMode === 'off') {
throw new Error('DeepSeek Harness Execute 需要启用 Runtime 沙箱')
}
return new DeepSeekHarnessRuntime({
defaultWorkspace: workspace,
baseUrl: profile.baseUrl,
model: profile.modelName,
launch: capabilities.deepseekHarnessLauncher,
credentialRefs: {
GOODBUDDY_DEEPSEEK_API_KEY: profile.apiKey
},
requiredSandboxEnforcement:
sandboxMode === 'strict' ? 'full' : 'partial',
skillPackages: capabilities.skillPackages,
toolProvider: new ModelToolProvider(
workspace,
capabilities.mcpServers,
undefined,
capabilities.knowledgeGateway,
false
)
})
}
if (provider === 'continue') {
if (
settings?.continueModelProfile &&
@@ -0,0 +1,709 @@
import { mkdir, mkdtemp, realpath, rm } from 'node:fs/promises'
import { tmpdir } from 'node:os'
import { join, resolve } from 'node:path'
import { describe, expect, it, vi } from 'vitest'
import {
CallId,
type GenerateOptions,
type StreamChunk
} from '@deepseek-ai/dsh-llm'
import type { RuntimeEvent } from './runtime'
import {
ModelToolProvider,
type ModelToolCallContext
} from './model-tool-provider'
import type { ResolvedMcpServer } from '../capabilities/capability-service'
import {
createBoundedNdJsonStream,
startControlledDeepSeekHarnessHost,
type ControlledHarnessHost
} from '../deepseek-harness-host'
import {
DeepSeekHarnessRuntime,
type DeepSeekHarnessChild,
type DeepSeekHarnessLaunchOptions
} from './deepseek-harness-runtime'
import { GOODBUDDY_HARNESS_MAX_STEP_TOKENS } from './goodbuddy-harness-control-plane'
const MAX_FRAME_BYTES = 1024 * 1024
const CREDENTIAL_REF = 'GOODBUDDY_DEEPSEEK_API_KEY'
const SKILL_CALL_ID = 'e2e-skill-call'
const MCP_CALL_ID = 'e2e-mcp-call'
const ASK_MCP_CALL_ID = 'e2e-ask-mcp-call'
const MICRO_DELTA_COUNT = 30_000
function expectedSandbox() {
return process.platform === 'win32'
? { provider: 'windows-acl', enforcement: 'partial' as const }
: process.platform === 'darwin'
? { provider: 'seatbelt', enforcement: 'full' as const }
: { provider: 'local-linux', enforcement: 'full' as const }
}
function deferred<T>() {
let resolvePromise!: (value: T) => void
const promise = new Promise<T>((resolve) => {
resolvePromise = resolve
})
return { promise, resolve: resolvePromise }
}
function toolResultText(
options: GenerateOptions,
callId: string
): string | undefined {
for (const message of options.messages) {
for (const block of message.content) {
if (
block.type !== 'tool-result' ||
block.toolCallId !== callId
) {
continue
}
return block.content
.filter(
(
content
): content is Extract<
(typeof block.content)[number],
{ type: 'text' }
> => content.type === 'text'
)
.map((content) => content.text)
.join('\n')
}
}
return undefined
}
function latestUserText(options: GenerateOptions): string {
return options.messages
.filter(
(message) =>
message.role === 'user' &&
message.source.kind === 'user'
)
.flatMap((message) =>
message.content
.filter(
(
content
): content is Extract<
(typeof message.content)[number],
{ type: 'text' }
> => content.type === 'text'
)
.map((content) => content.text)
)
.at(-1) ?? ''
}
async function* toolCall(
callId: string,
name: string,
argumentsValue: Record<string, unknown>
): AsyncGenerator<StreamChunk> {
const id = CallId(callId)
const argumentsText = JSON.stringify(argumentsValue)
yield {
type: 'block-start',
index: 0,
blockType: 'tool-call'
}
yield {
type: 'tool-call-delta',
index: 0,
id,
name,
argumentsDelta: argumentsText
}
yield {
type: 'block-end',
index: 0,
block: {
type: 'tool-call',
id,
name,
arguments: argumentsText
}
}
yield {
type: 'usage',
usage: {
inputTokens: 10,
outputTokens: 5,
cacheReadTokens: 0,
cacheWriteTokens: 0
}
}
yield {
type: 'finish',
reason: { kind: 'tool-calls' }
}
}
async function* textResponse(
text: string
): AsyncGenerator<StreamChunk> {
yield {
type: 'block-start',
index: 0,
blockType: 'text'
}
yield {
type: 'text-delta',
index: 0,
text
}
yield {
type: 'block-end',
index: 0,
block: { type: 'text', text }
}
yield {
type: 'usage',
usage: {
inputTokens: 20,
outputTokens: 8,
cacheReadTokens: 0,
cacheWriteTokens: 0
}
}
yield {
type: 'finish',
reason: { kind: 'stop' }
}
}
async function* microDeltaResponse(): AsyncGenerator<StreamChunk> {
yield {
type: 'block-start',
index: 0,
blockType: 'reasoning'
}
for (let index = 0; index < MICRO_DELTA_COUNT; index += 1) {
yield {
type: 'reasoning-delta',
index: 0,
text: String(index % 10)
}
}
yield {
type: 'block-end',
index: 0,
block: {
type: 'reasoning',
text: Array.from(
{ length: MICRO_DELTA_COUNT },
(_value, index) => String(index % 10)
).join('')
}
}
yield {
type: 'usage',
usage: {
inputTokens: 20,
outputTokens: 8_000,
cacheReadTokens: 0,
cacheWriteTokens: 0
}
}
yield {
type: 'finish',
reason: { kind: 'stop' }
}
}
class FakeGameModel {
mcpToolName?: string
skillResult?: string
blueprint?: Record<string, unknown>
askToolResult?: string
executeToolNames: string[] = []
askToolNames: string[] = []
stream(options: GenerateOptions): AsyncIterable<StreamChunk> {
const prompt = latestUserText(options)
const toolNames = options.tools?.map((tool) => tool.name) ?? []
if (prompt.includes('ASK_BOUNDARY_PROBE')) {
this.askToolNames = toolNames
const result = toolResultText(options, ASK_MCP_CALL_ID)
if (!result) {
if (!this.mcpToolName) {
throw new Error('Fake model has no prior MCP tool identity')
}
return toolCall(ASK_MCP_CALL_ID, this.mcpToolName, {
theme: 'neon-ruins',
seed: 'ask-must-not-execute',
targetCount: 5
})
}
this.askToolResult = result
return textResponse('Ask mode MCP proxy unavailable as required.')
}
this.executeToolNames = toolNames
const skillResult = toolResultText(options, SKILL_CALL_ID)
if (!skillResult) {
return toolCall(SKILL_CALL_ID, 'skill', {
name: 'web-3d-game'
})
}
this.skillResult = skillResult
const blueprintResult = toolResultText(options, MCP_CALL_ID)
if (!blueprintResult) {
const mcpTool = options.tools?.find((tool) =>
tool.name.endsWith('_create_game_blueprint')
)
if (!mcpTool) {
throw new Error(
'Main-mediated 3D blueprint MCP tool was not exposed'
)
}
this.mcpToolName = mcpTool.name
return toolCall(MCP_CALL_ID, mcpTool.name, {
theme: 'neon-ruins',
seed: 'goodbuddy-0.9.0',
targetCount: 5
})
}
this.blueprint = JSON.parse(
blueprintResult
) as Record<string, unknown>
return textResponse(
'Loaded the Web 3D Game Skill and the approved Prism Relay blueprint.'
)
}
}
type HarnessModel = {
stream(options: GenerateOptions): AsyncIterable<StreamChunk>
}
async function collect(
stream: AsyncGenerator<RuntimeEvent, void, void>
): Promise<RuntimeEvent[]> {
const events: RuntimeEvent[] = []
for await (const event of stream) {
events.push(event)
}
return events
}
function createInProcessLaunch(
dshHome: string,
model: HarnessModel
): {
launch(
options: DeepSeekHarnessLaunchOptions
): Promise<DeepSeekHarnessChild>
hosts: ControlledHarnessHost[]
} {
const hosts: ControlledHarnessHost[] = []
return {
hosts,
async launch(options) {
const clientToHost =
new TransformStream<Uint8Array, Uint8Array>()
const hostToClient =
new TransformStream<Uint8Array, Uint8Array>()
const exited = deferred<{
exitCode: number | null
signal?: string | null
}>()
const host = await startControlledDeepSeekHarnessHost({
workspace: options.cwd,
dshHome,
baseUrl: options.baseUrl,
api: 'openai-completions',
provider: 'goodbuddy',
model: options.model,
harnessVersion: '0.1.0-rc.6',
sandbox: expectedSandbox(),
credentialRefs: options.credentialRefs,
skillPackages: options.skillPackages,
stream: createBoundedNdJsonStream(
hostToClient.writable,
clientToHost.readable,
MAX_FRAME_BYTES
)
})
hosts.push(host)
host.context.on(
'llm/stream',
(request) => model.stream(request),
{ global: true, prepend: true }
)
let terminated = false
return {
stdin: clientToHost.writable,
stdout: hostToClient.readable,
exited: exited.promise,
async terminate() {
if (terminated) {
return
}
terminated = true
await host.dispose().catch(() => undefined)
await Promise.allSettled([
clientToHost.writable.close(),
hostToClient.writable.close()
])
exited.resolve({ exitCode: 0 })
}
}
}
}
}
describe('DeepSeek Harness real ACP control-plane E2E', () => {
it(
'coalesces micro reasoning deltas without losing content and caps each model step',
async () => {
const root = await realpath(
await mkdtemp(join(tmpdir(), 'goodbuddy-harness-acp-deltas-'))
)
const workspace = join(root, 'workspace')
const dshHome = join(root, 'dsh-home')
await Promise.all([mkdir(workspace), mkdir(dshHome)])
let observedRequest: GenerateOptions | undefined
const inProcess = createInProcessLaunch(dshHome, {
stream(options) {
observedRequest = options
return microDeltaResponse()
}
})
const runtime = new DeepSeekHarnessRuntime({
defaultWorkspace: workspace,
baseUrl: 'https://api.deepseek.com',
model: 'deepseek-test',
launch: (options) => inProcess.launch(options),
credentialRefs: {
[CREDENTIAL_REF]: 'unused-in-memory-model-credential'
},
initializationTimeoutMs: 20_000,
promptTimeoutMs: 20_000,
shutdownTimeoutMs: 5_000
})
try {
const events = await collect(
runtime.run(
{
requestId: 'request-acp-deltas',
conversationId: 'acp-deltas',
prompt: 'Return the deterministic reasoning stream.',
workMode: 'execute'
},
new AbortController().signal
)
)
const reasoning = events.filter(
(
event
): event is Extract<
RuntimeEvent,
{ type: 'reasoning' }
> => event.type === 'reasoning'
)
expect(observedRequest?.maxTokens).toBe(
GOODBUDDY_HARNESS_MAX_STEP_TOKENS
)
expect(observedRequest?.system).toContain(
'act through the available tools'
)
expect(reasoning).toHaveLength(8)
expect(
reasoning.map((event) => event.delta).join('')
).toBe(
Array.from(
{ length: MICRO_DELTA_COUNT },
(_value, index) => String(index % 10)
).join('')
)
expect(events.at(-1)).toMatchObject({ type: 'done' })
} finally {
await runtime.dispose()
await Promise.allSettled(
inProcess.hosts.map((host) => host.dispose())
)
await rm(root, { recursive: true, force: true })
}
},
30_000
)
it(
'rejects the ACP prompt with a bounded model turn error',
async () => {
const root = await realpath(
await mkdtemp(join(tmpdir(), 'goodbuddy-harness-acp-error-'))
)
const workspace = join(root, 'workspace')
const dshHome = join(root, 'dsh-home')
await Promise.all([mkdir(workspace), mkdir(dshHome)])
const inProcess = createInProcessLaunch(dshHome, {
stream() {
throw new Error('synthetic model turn failed')
}
})
const runtime = new DeepSeekHarnessRuntime({
defaultWorkspace: workspace,
baseUrl: 'https://api.deepseek.com',
model: 'deepseek-test',
launch: (options) => inProcess.launch(options),
credentialRefs: {
[CREDENTIAL_REF]: 'unused-in-memory-model-credential'
},
initializationTimeoutMs: 20_000,
promptTimeoutMs: 2_000,
shutdownTimeoutMs: 5_000
})
try {
await expect(
collect(
runtime.run(
{
requestId: 'request-acp-error',
conversationId: 'acp-error',
prompt: 'Trigger the synthetic model failure.',
workMode: 'ask'
},
new AbortController().signal
)
)
).rejects.toThrow('synthetic model turn failed')
} finally {
await runtime.dispose()
await Promise.allSettled(
inProcess.hosts.map((host) => host.dispose())
)
await rm(root, { recursive: true, force: true })
}
},
30_000
)
it(
'loads a native Skill, calls an approved real MCP, forwards events, and removes MCP in Ask',
async () => {
const root = await realpath(
await mkdtemp(join(tmpdir(), 'goodbuddy-harness-acp-e2e-'))
)
const workspace = join(root, 'workspace')
const dshHome = join(root, 'dsh-home')
await Promise.all([
mkdir(workspace),
mkdir(dshHome)
])
const provider = new ModelToolProvider(workspace, [
{
id: 'fbf42200-4e60-48d0-b5f2-e816db38ac54',
name: 'Local 3D Game Blueprint',
description: 'Deterministic integration fixture',
enabled: true,
allowDynamicTools: false,
assignments: ['deepseek-harness'],
secretConfigured: false,
transport: 'stdio',
command: process.execPath,
args: [
resolve(
'tests',
'fixtures',
'web-3d-game-mcp.mjs'
)
]
} satisfies ResolvedMcpServer
])
const callTool = vi.spyOn(provider, 'callTool')
const listTools = vi.spyOn(provider, 'listTools')
const fakeModel = new FakeGameModel()
const inProcess = createInProcessLaunch(dshHome, fakeModel)
const runtime = new DeepSeekHarnessRuntime({
defaultWorkspace: workspace,
baseUrl: 'https://api.deepseek.com',
model: 'deepseek-test',
launch: (options) => inProcess.launch(options),
credentialRefs: {
[CREDENTIAL_REF]: 'unused-in-memory-model-credential'
},
skillPackages: [
{
id: 'web-3d-game',
directory: resolve(
'resources',
'skills',
'web-3d-game'
)
}
],
toolProvider: provider,
initializationTimeoutMs: 20_000,
promptTimeoutMs: 20_000,
shutdownTimeoutMs: 5_000
})
const authorize = vi.fn(
async (
request: Parameters<
NonNullable<
Parameters<DeepSeekHarnessRuntime['run']>[2]
>
>[0]
) =>
request.scopeKey.startsWith('model:mcp:')
? ('once' as const)
: ('deny' as const)
)
try {
const executeEvents = await collect(
runtime.run(
{
requestId: 'request-acp-execute',
conversationId: 'acp-e2e',
prompt:
'Use the Web 3D Game Skill and assigned blueprint MCP.',
workMode: 'execute'
},
new AbortController().signal,
authorize
)
)
expect(fakeModel.executeToolNames).toContain('skill')
expect(fakeModel.mcpToolName).toMatch(
/_create_game_blueprint$/u
)
expect(fakeModel.skillResult).toContain(
'window.__GOODBUDDY_GAME__'
)
expect(fakeModel.blueprint).toMatchObject({
title: 'Prism Relay',
objective: { targetCount: 5 },
acceptance: {
testSurface: 'window.__GOODBUDDY_GAME__'
}
})
expect(authorize).toHaveBeenCalledOnce()
expect(callTool).toHaveBeenCalledWith(
fakeModel.mcpToolName,
{
theme: 'neon-ruins',
seed: 'goodbuddy-0.9.0',
targetCount: 5
},
expect.any(AbortSignal),
{
conversationId: 'acp-e2e',
workMode: 'execute',
knowledgeCapabilityToken: undefined
} satisfies ModelToolCallContext
)
expect(executeEvents).toEqual(
expect.arrayContaining([
expect.objectContaining({
type: 'tool',
callId: SKILL_CALL_ID,
name: 'skill',
state: 'pending'
}),
expect.objectContaining({
type: 'tool',
callId: SKILL_CALL_ID,
state: 'completed'
}),
expect.objectContaining({
type: 'tool',
callId: MCP_CALL_ID,
name: fakeModel.mcpToolName,
state: 'pending'
}),
expect.objectContaining({
type: 'tool',
callId: MCP_CALL_ID,
state: 'completed'
}),
expect.objectContaining({
type: 'text',
delta: expect.stringContaining('Prism Relay')
}),
expect.objectContaining({
type: 'model-usage',
runtime: 'deepseek-harness'
}),
expect.objectContaining({
type: 'done',
sessionId: expect.any(String)
})
])
)
expect(
executeEvents.filter(
(event) =>
event.type === 'tool' &&
event.state === 'running'
)
).toHaveLength(0)
const callsBeforeAsk = callTool.mock.calls.length
const listsBeforeAsk = listTools.mock.calls.length
const approvalsBeforeAsk = authorize.mock.calls.length
const askEvents = await collect(
runtime.run(
{
requestId: 'request-acp-ask',
conversationId: 'acp-e2e',
prompt:
'ASK_BOUNDARY_PROBE: attempt the previous MCP tool.',
workMode: 'ask'
},
new AbortController().signal,
authorize
)
)
expect(fakeModel.askToolNames).not.toContain(
fakeModel.mcpToolName
)
expect(fakeModel.askToolResult).toContain('unknown tool')
expect(callTool).toHaveBeenCalledTimes(callsBeforeAsk)
expect(listTools).toHaveBeenCalledTimes(listsBeforeAsk)
expect(authorize).toHaveBeenCalledTimes(approvalsBeforeAsk)
expect(askEvents).toEqual(
expect.arrayContaining([
expect.objectContaining({
type: 'tool',
callId: ASK_MCP_CALL_ID,
name: fakeModel.mcpToolName,
state: 'pending'
}),
expect.objectContaining({
type: 'tool',
callId: ASK_MCP_CALL_ID,
state: 'failed'
}),
expect.objectContaining({
type: 'text',
delta: expect.stringContaining(
'MCP proxy unavailable'
)
}),
expect.objectContaining({ type: 'done' })
])
)
} finally {
await runtime.dispose()
await Promise.allSettled(
inProcess.hosts.map((host) => host.dispose())
)
await rm(root, { recursive: true, force: true })
}
},
60_000
)
})
@@ -0,0 +1,920 @@
import { describe, expect, it, vi } from 'vitest'
import { resolve } from 'node:path'
import type { RuntimeEvent } from './runtime'
import {
ModelToolProvider,
type ModelToolDefinition,
type ModelToolProviderLike
} from './model-tool-provider'
import type {
ResolvedMcpServer
} from '../capabilities/capability-service'
import {
DeepSeekHarnessRuntime,
harnessPromptError,
type DeepSeekHarnessAcpSdk,
type DeepSeekHarnessChild
} from './deepseek-harness-runtime'
import { RequestError } from '@agentclientprotocol/sdk'
type Permission = Parameters<
ReturnType<
ConstructorParameters<
DeepSeekHarnessAcpSdk['ClientSideConnection']
>[0]
>['requestPermission']
>[0]
function deferred<T>() {
let resolve!: (value: T) => void
let reject!: (error: unknown) => void
const promise = new Promise<T>((resolvePromise, rejectPromise) => {
resolve = resolvePromise
reject = rejectPromise
})
return { promise, resolve, reject }
}
function setup(
options: {
toolProvider?: ModelToolProviderLike
promptTimeoutMs?: number
maxEventCharacters?: number
maxRequestOutputCharacters?: number
} = {}
) {
const exit = deferred<{
exitCode: number | null
signal?: string | null
}>()
const stderr = new TransformStream<Uint8Array, Uint8Array>()
const child: DeepSeekHarnessChild = {
stdin: new WritableStream<Uint8Array>(),
stdout: new ReadableStream<Uint8Array>(),
stderr: stderr.readable,
exited: exit.promise,
terminate: vi.fn()
}
let permissionHandler:
| ((params: Permission) => Promise<unknown>)
| undefined
let updateHandler:
| ((context: {
sessionId: string
update: Record<string, unknown>
}) => Promise<void>)
| undefined
let extensionHandler:
| ((
method: string,
params: Record<string, unknown>
) => Promise<Record<string, unknown>>)
| undefined
const requests: Array<{
method: string
params: Record<string, unknown>
}> = []
const notifications: Array<{
method: string
params: Record<string, unknown>
}> = []
const promptGates: Array<ReturnType<typeof deferred<{ stopReason: string }>>> =
[]
let sessionIndex = 0
const connectionClosed = deferred<void>()
const connectionController = new AbortController()
const requestAgent = async (
method: string,
params: Record<string, unknown>
) => {
requests.push({ method, params })
if (method === 'initialize') {
return {
protocolVersion: 1,
agentCapabilities: {}
}
}
if (method === 'session/new') {
sessionIndex += 1
return { sessionId: `session-${sessionIndex}` }
}
if (method === 'session/prompt') {
const gate = deferred<{ stopReason: string }>()
promptGates.push(gate)
return gate.promise
}
throw new Error(`unexpected request: ${method}`)
}
const notifyAgent = async (
method: string,
params: Record<string, unknown>
) => {
notifications.push({ method, params })
}
const agent = {
initialize: vi.fn((params: Record<string, unknown>) =>
requestAgent('initialize', params)
),
newSession: vi.fn((params: Record<string, unknown>) =>
requestAgent('session/new', params)
),
prompt: vi.fn((params: Record<string, unknown>) =>
requestAgent('session/prompt', params)
),
cancel: vi.fn((params: Record<string, unknown>) =>
notifyAgent('session/cancel', params)
),
extMethod: vi.fn(
async (method: string, params: Record<string, unknown>) => {
requests.push({ method, params })
if (method === 'goodbuddy/handshake') {
return {
controlProtocolVersion: 1,
harnessVersion: '0.1.0-rc.6',
acpProtocolVersion: 1,
supports: {
cancellation: true,
sessionRelease: true,
oneShotApproval: true,
reasoningEvents: true,
toolEvents: true,
usageEvents: true,
credentialResolution: true
},
sandbox: {
provider: 'test',
enforcement: 'full'
}
}
}
if (method === 'goodbuddy/session/prepare') {
return { prepared: true }
}
if (method === 'goodbuddy/session/release') {
return { released: true }
}
if (method === 'goodbuddy/shutdown') {
return { shutdown: true }
}
throw new Error(`unexpected extension: ${method}`)
}
),
extNotification: vi.fn()
}
const connection = {
...agent,
signal: connectionController.signal,
closed: connectionClosed.promise
}
const ClientSideConnection = vi.fn(function (
this: unknown,
toClient: (
connectedAgent: typeof agent
) => {
requestPermission: typeof permissionHandler
sessionUpdate: typeof updateHandler
extMethod: (
method: string,
params: Record<string, unknown>
) => Promise<Record<string, unknown>>
extNotification: (
method: string,
params: Record<string, unknown>
) => Promise<void>
}
) {
const client = toClient(agent)
permissionHandler = client.requestPermission
updateHandler = client.sessionUpdate
extensionHandler = client.extMethod
agent.extNotification.mockImplementation(
async (
method: string,
params: Record<string, unknown>
) => client.extNotification(method, params)
)
return connection
})
const sdk = {
PROTOCOL_VERSION: 1,
ClientSideConnection,
ndJsonStream: vi.fn(() => ({ stream: true }))
} as unknown as DeepSeekHarnessAcpSdk
const launch = vi.fn(async () => child)
const runtime = new DeepSeekHarnessRuntime({
defaultWorkspace: 'C:\\workspace',
baseUrl: 'https://api.deepseek.com',
model: 'deepseek-test',
launch,
loadAcpSdk: async () => sdk,
initializationTimeoutMs: 100,
promptTimeoutMs: options.promptTimeoutMs ?? 100,
shutdownTimeoutMs: 10,
maxStderrBytes: 16,
maxEventCharacters: options.maxEventCharacters,
maxRequestOutputCharacters:
options.maxRequestOutputCharacters,
toolProvider: options.toolProvider
})
const emit = async (
sessionId: string,
update: Record<string, unknown>
): Promise<void> => {
await updateHandler?.({ sessionId, update })
}
return {
runtime,
child,
stderr,
exit,
sdk,
launch,
requests,
notifications,
promptGates,
agent,
permission: async (request: Permission) =>
permissionHandler?.(request),
extension: (
method: string,
params: Record<string, unknown>
) => extensionHandler?.(method, params),
notify: (
method: string,
params: Record<string, unknown>
) => agent.extNotification(method, params),
emit
}
}
async function collect(
stream: AsyncGenerator<RuntimeEvent, void, void>
): Promise<RuntimeEvent[]> {
const events: RuntimeEvent[] = []
for await (const event of stream) {
events.push(event)
}
return events
}
function request(
conversationId: string,
workMode: 'ask' | 'execute' = 'execute'
) {
return {
requestId: `request-${conversationId}`,
conversationId,
prompt: 'hello',
workMode
} as const
}
function permission(sessionId: string): Permission {
return {
sessionId,
toolCall: {
toolCallId: 'call-1',
title: 'Run tests',
name: 'shell',
kind: 'execute',
rawInput: { command: 'npm test' }
},
options: [
{
optionId: 'allow-once',
name: 'Allow once',
kind: 'allow_once'
},
{
optionId: 'allow-always',
name: 'Always allow',
kind: 'allow_always'
},
{
optionId: 'reject',
name: 'Reject',
kind: 'reject_once'
}
]
}
}
function mcpTool(
name = 'mcp_deadbeef_cafebabe_game_asset'
): ModelToolDefinition {
return {
name,
displayName: 'Local Game Assets / game_asset',
description: 'Returns a deterministic local game asset manifest.',
inputSchema: {
type: 'object',
properties: {
kind: { type: 'string' }
},
required: ['kind'],
additionalProperties: false
},
source: 'mcp',
serverName: 'Local Game Assets'
}
}
function toolProvider(
tools: ModelToolDefinition[] = [mcpTool()]
): ModelToolProviderLike {
return {
listTools: vi.fn(async () => tools),
getApproval: vi.fn((tool, _arguments, summary) => ({
scopeKey: `model:mcp:${tool.name}`,
title: `允许调用 MCP 工具「${tool.displayName}」?`,
description: '调用本地测试 MCP。',
toolName: tool.displayName,
argumentSummary: summary,
allowPermanent: false
})),
callTool: vi.fn(async () => ({
parts: [
{
type: 'text' as const,
text: '{"asset":"cube"}'
}
],
contextBytes: 16
})),
releaseConversation: vi.fn(async () => undefined),
dispose: vi.fn(async () => undefined)
}
}
describe('DeepSeekHarnessRuntime', () => {
it('surfaces bounded internal Harness details from ACP errors', () => {
expect(
harnessPromptError(
RequestError.internalError({
details: 'DeepSeek provider rejected the request'
})
)
).toEqual(
new Error('DeepSeek provider rejected the request')
)
expect(
harnessPromptError(
RequestError.internalError({ unrelated: 'hidden' })
)
).toBeInstanceOf(RequestError)
})
it('uses ACP stdio, maps conversations to sessions, and streams text', async () => {
const harness = setup()
const first = collect(
harness.runtime.run(
request('one'),
new AbortController().signal
)
)
await vi.waitFor(() =>
expect(harness.promptGates).toHaveLength(1)
)
await harness.emit('session-1', {
sessionUpdate: 'agent_message_chunk',
content: { type: 'text', text: 'hello ' }
})
await harness.emit('session-1', {
sessionUpdate: 'agent_message_chunk',
content: { type: 'text', text: 'world' }
})
harness.promptGates[0]!.resolve({ stopReason: 'end_turn' })
expect(await first).toEqual([
expect.objectContaining({ type: 'status' }),
expect.objectContaining({ type: 'text', delta: 'hello ' }),
expect.objectContaining({ type: 'text', delta: 'world' }),
expect.objectContaining({
type: 'done',
sessionId: 'session-1'
})
])
expect(harness.sdk.ndJsonStream).toHaveBeenCalledWith(
harness.child.stdin,
harness.child.stdout
)
expect(harness.launch).toHaveBeenCalledWith({
cwd: 'C:\\workspace',
signal: expect.any(AbortSignal),
baseUrl: 'https://api.deepseek.com',
model: 'deepseek-test',
credentialRefs: [],
requiredSandboxEnforcement: undefined,
skillPackages: []
})
expect(harness.requests).toContainEqual({
method: 'goodbuddy/session/prepare',
params: {
sessionId: 'session-1',
requestId: 'request-one',
mode: 'execute'
}
})
const second = collect(
harness.runtime.run(
request('one'),
new AbortController().signal
)
)
await vi.waitFor(() =>
expect(harness.promptGates).toHaveLength(2)
)
harness.promptGates[1]!.resolve({ stopReason: 'end_turn' })
await second
expect(
harness.requests.filter(({ method }) => method === 'session/new')
).toHaveLength(1)
await harness.runtime.dispose()
})
it('enforces the cumulative bridge limit against complete wire events', async () => {
const harness = setup({
maxEventCharacters: 1_000,
maxRequestOutputCharacters: 180
})
const running = collect(
harness.runtime.run(
request('output-limit'),
new AbortController().signal
)
)
await vi.waitFor(() =>
expect(harness.promptGates).toHaveLength(1)
)
await harness.notify('goodbuddy/session/event', {
sessionId: 'session-1',
requestId: 'request-output-limit',
type: 'reasoning',
delta: 'x'.repeat(40)
})
await harness.notify('goodbuddy/session/event', {
sessionId: 'session-1',
requestId: 'request-output-limit',
type: 'reasoning',
delta: 'y'.repeat(40)
})
harness.promptGates[0]!.resolve({ stopReason: 'end_turn' })
await expect(running).rejects.toThrow(
'请求累计输出超过安全限制'
)
await harness.runtime.dispose()
})
it('keeps independent conversation sessions distinct', async () => {
const harness = setup()
const first = collect(
harness.runtime.run(
request('one'),
new AbortController().signal
)
)
const second = collect(
harness.runtime.run(
request('two'),
new AbortController().signal
)
)
await vi.waitFor(() =>
expect(harness.promptGates).toHaveLength(2)
)
harness.promptGates[0]!.resolve({ stopReason: 'end_turn' })
harness.promptGates[1]!.resolve({ stopReason: 'end_turn' })
await Promise.all([first, second])
const prompts = harness.requests.filter(
({ method }) => method === 'session/prompt'
)
expect(prompts.map(({ params }) => params.sessionId).sort()).toEqual([
'session-1',
'session-2'
])
await harness.runtime.dispose()
})
it('fails Ask closed and never calls the authorizer', async () => {
const harness = setup()
const authorize = vi.fn().mockResolvedValue('once')
const running = collect(
harness.runtime.run(
request('ask', 'ask'),
new AbortController().signal,
authorize
)
)
await vi.waitFor(() =>
expect(harness.promptGates).toHaveLength(1)
)
await expect(
harness.permission(permission('session-1'))
).resolves.toEqual({
outcome: { outcome: 'selected', optionId: 'reject' }
})
expect(authorize).not.toHaveBeenCalled()
expect(harness.requests).toContainEqual({
method: 'goodbuddy/session/prepare',
params: {
sessionId: 'session-1',
requestId: 'request-ask',
mode: 'ask'
}
})
harness.promptGates[0]!.resolve({ stopReason: 'end_turn' })
await running
await harness.runtime.dispose()
})
it('authorizes Execute but can select only allow-once', async () => {
const harness = setup()
const authorize = vi.fn().mockResolvedValue('always')
const running = collect(
harness.runtime.run(
request('execute'),
new AbortController().signal,
authorize
)
)
await vi.waitFor(() =>
expect(harness.promptGates).toHaveLength(1)
)
await expect(
harness.permission(permission('session-1'))
).resolves.toEqual({
outcome: {
outcome: 'selected',
optionId: 'allow-once'
}
})
expect(authorize).toHaveBeenCalledWith({
scopeKey: 'deepseek-harness:shell',
title: 'Run tests',
description: 'DeepSeek Harness 请求一次性执行此工具',
toolName: 'shell',
argumentSummary: '{\n "command": "npm test"\n}',
allowPermanent: false
})
harness.promptGates[0]!.resolve({ stopReason: 'end_turn' })
await running
await harness.runtime.dispose()
})
it('lists only bounded MCP schemas without exposing server secrets', async () => {
const provider = toolProvider([
mcpTool(),
{
...mcpTool('workspace_read_text'),
source: 'builtin'
}
])
const harness = setup({ toolProvider: provider })
await harness.runtime.getStatus()
await expect(
harness.extension('goodbuddy/tools/list', {
sessionId: 'session-catalog'
})
).resolves.toEqual({
tools: [
{
name: mcpTool().name,
description: mcpTool().description,
inputSchema: mcpTool().inputSchema
}
]
})
expect(
JSON.stringify(
await harness.extension('goodbuddy/tools/list', {
sessionId: 'session-catalog'
})
)
).not.toContain('secret')
await harness.runtime.dispose()
})
it('rejects MCP calls in Ask mode without approval or execution', async () => {
const provider = toolProvider()
const harness = setup({ toolProvider: provider })
const authorize = vi.fn().mockResolvedValue('once')
const running = collect(
harness.runtime.run(
request('mcp-ask', 'ask'),
new AbortController().signal,
authorize
)
)
await vi.waitFor(() =>
expect(harness.promptGates).toHaveLength(1)
)
await expect(
harness.extension('goodbuddy/tools/call', {
sessionId: 'session-1',
name: mcpTool().name,
arguments: { kind: 'cube' }
})
).rejects.toThrow('需要 Execute 模式')
expect(authorize).not.toHaveBeenCalled()
expect(provider.callTool).not.toHaveBeenCalled()
harness.promptGates[0]!.resolve({ stopReason: 'end_turn' })
await running
await harness.runtime.dispose()
})
it('requires one-time approval before calling an assigned MCP tool', async () => {
const provider = toolProvider()
const harness = setup({ toolProvider: provider })
const authorize = vi.fn().mockResolvedValue('once')
const running = collect(
harness.runtime.run(
request('mcp-execute'),
new AbortController().signal,
authorize
)
)
await vi.waitFor(() =>
expect(harness.promptGates).toHaveLength(1)
)
await expect(
harness.extension('goodbuddy/tools/call', {
sessionId: 'session-1',
name: mcpTool().name,
arguments: { kind: 'cube' }
})
).resolves.toEqual({
content: [
{ type: 'text', text: '{"asset":"cube"}' }
]
})
expect(authorize).toHaveBeenCalledTimes(1)
expect(provider.callTool).toHaveBeenCalledWith(
mcpTool().name,
{ kind: 'cube' },
expect.any(AbortSignal),
expect.objectContaining({
conversationId: 'mcp-execute',
workMode: 'execute'
})
)
harness.promptGates[0]!.resolve({ stopReason: 'end_turn' })
await running
await harness.runtime.dispose()
})
it('lists and calls a real local stdio MCP through the Main proxy', async () => {
const provider = new ModelToolProvider(process.cwd(), [
{
id: 'fbf42200-4e60-48d0-b5f2-e816db38ac54',
name: 'Local 3D Game Blueprint',
description: 'Deterministic integration fixture',
enabled: true,
allowDynamicTools: false,
assignments: ['deepseek-harness'],
secretConfigured: false,
transport: 'stdio',
command: process.execPath,
args: [
resolve('tests', 'fixtures', 'web-3d-game-mcp.mjs')
]
} satisfies ResolvedMcpServer
])
const harness = setup({
toolProvider: provider,
promptTimeoutMs: 10_000
})
const authorize = vi.fn().mockResolvedValue('once')
const running = collect(
harness.runtime.run(
request('real-mcp'),
new AbortController().signal,
authorize
)
)
await vi.waitFor(() =>
expect(harness.promptGates).toHaveLength(1)
)
try {
const catalog = await harness.extension(
'goodbuddy/tools/list',
{ sessionId: 'session-1' }
)
const tool = (
catalog as {
tools: Array<{
name: string
description: string
inputSchema: Record<string, unknown>
}>
}
).tools.find((candidate) =>
candidate.name.endsWith('_create_game_blueprint')
)
expect(tool).toMatchObject({
description: expect.stringContaining(
'offline WebGL game design'
),
inputSchema: expect.objectContaining({ type: 'object' })
})
const result = await harness.extension(
'goodbuddy/tools/call',
{
sessionId: 'session-1',
name: tool!.name,
arguments: {
theme: 'neon-ruins',
seed: 'goodbuddy-0.9.0',
targetCount: 5
}
}
)
expect(result).toMatchObject({
content: [
{
type: 'text',
text: expect.stringContaining('"title":"Prism Relay"')
}
]
})
const blueprint = JSON.parse(
(
result as {
content: [{ type: 'text'; text: string }]
}
).content[0].text
) as Record<string, unknown>
expect(blueprint).toMatchObject({
acceptance: {
testSurface: 'window.__GOODBUDDY_GAME__'
}
})
expect(authorize).toHaveBeenCalledOnce()
} finally {
harness.promptGates[0]?.resolve({ stopReason: 'end_turn' })
await running.catch(() => undefined)
await harness.runtime.dispose()
}
})
it('does not execute an MCP tool when authorization is denied', async () => {
const provider = toolProvider()
const harness = setup({ toolProvider: provider })
const authorize = vi.fn().mockResolvedValue('deny')
const running = collect(
harness.runtime.run(
request('mcp-denied'),
new AbortController().signal,
authorize
)
)
await vi.waitFor(() =>
expect(harness.promptGates).toHaveLength(1)
)
await expect(
harness.extension('goodbuddy/tools/call', {
sessionId: 'session-1',
name: mcpTool().name,
arguments: { kind: 'cube' }
})
).rejects.toThrow('未获执行授权')
expect(provider.callTool).not.toHaveBeenCalled()
harness.promptGates[0]!.resolve({ stopReason: 'end_turn' })
await running
await harness.runtime.dispose()
})
it('validates MCP arguments before requesting authorization', async () => {
const provider = toolProvider()
const harness = setup({ toolProvider: provider })
const authorize = vi.fn().mockResolvedValue('once')
const running = collect(
harness.runtime.run(
request('mcp-invalid'),
new AbortController().signal,
authorize
)
)
await vi.waitFor(() =>
expect(harness.promptGates).toHaveLength(1)
)
await expect(
harness.extension('goodbuddy/tools/call', {
sessionId: 'session-1',
name: mcpTool().name,
arguments: {}
})
).rejects.toThrow('MCP 工具参数无效')
expect(authorize).not.toHaveBeenCalled()
expect(provider.callTool).not.toHaveBeenCalled()
harness.promptGates[0]!.resolve({ stopReason: 'end_turn' })
await running
await harness.runtime.dispose()
})
it('translates AbortSignal to session/cancel', async () => {
const harness = setup()
const controller = new AbortController()
const running = collect(
harness.runtime.run(request('abort'), controller.signal)
)
await vi.waitFor(() =>
expect(harness.promptGates).toHaveLength(1)
)
controller.abort(new Error('cancelled by user'))
harness.promptGates[0]!.resolve({ stopReason: 'cancelled' })
await expect(running).rejects.toThrow('cancelled by user')
expect(harness.notifications).toContainEqual({
method: 'session/cancel',
params: { sessionId: 'session-1' }
})
await harness.runtime.dispose()
})
it('fails on bounded stderr overflow without exposing stderr text', async () => {
const harness = setup()
const running = collect(
harness.runtime.run(
request('stderr'),
new AbortController().signal
)
)
await vi.waitFor(() =>
expect(harness.promptGates).toHaveLength(1)
)
const writer = harness.stderr.writable.getWriter()
await writer.write(
new TextEncoder().encode('private-secret-is-too-long')
)
await vi.waitFor(() =>
expect(harness.child.terminate).toHaveBeenCalled()
)
harness.promptGates[0]!.resolve({ stopReason: 'end_turn' })
await expect(running).rejects.toThrow('stderr 超过 16 字节')
await expect(running).rejects.not.toThrow('private-secret')
await harness.runtime.dispose()
})
it('reports process exit and fully disposes the connection and child', async () => {
const harness = setup()
await expect(harness.runtime.getStatus()).resolves.toMatchObject({
available: true
})
harness.exit.resolve({ exitCode: 9 })
await vi.waitFor(async () => {
const status = await harness.runtime.getStatus()
expect(status).toMatchObject({
available: false,
detail: 'DeepSeek Harness 进程意外退出(code 9'
})
})
await harness.runtime.dispose()
expect(harness.child.terminate).toHaveBeenCalled()
})
it('fails closed when the required bridge handshake is unavailable', async () => {
const harness = setup()
harness.agent.extMethod.mockRejectedValueOnce(
new Error('method not found')
)
await expect(harness.runtime.getStatus()).resolves.toMatchObject({
available: false,
detail: 'method not found'
})
expect(harness.child.terminate).toHaveBeenCalled()
})
it('times out a prompt, cancels it, and bounds disposal wait', async () => {
const harness = setup()
const running = collect(
harness.runtime.run(
request('timeout'),
new AbortController().signal
)
)
await expect(running).rejects.toThrow(
'DeepSeek Harness 请求超时'
)
expect(harness.notifications).toContainEqual({
method: 'session/cancel',
params: { sessionId: 'session-1' }
})
await expect(harness.runtime.dispose()).resolves.toBeUndefined()
})
})
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,147 @@
import { EventEmitter } from 'node:events'
import { mkdir, mkdtemp, realpath, writeFile } from 'node:fs/promises'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { PassThrough } from 'node:stream'
import { describe, expect, it, vi } from 'vitest'
import {
DEEPSEEK_HARNESS_CONTROL_PROTOCOL,
DEEPSEEK_HARNESS_CONTROL_VERSION,
DEEPSEEK_HARNESS_CREDENTIAL_REF,
createDeepSeekHarnessUtilityLauncher,
parseHarnessControlMessage
} from './deepseek-harness-utility-launcher'
class FakeUtility extends EventEmitter {
readonly messages: unknown[] = []
readonly stderr = new PassThrough()
readonly pid = 123
killed = false
postMessage(message: unknown): void {
this.messages.push(message)
}
kill(): boolean {
this.killed = true
return true
}
}
async function fixture() {
const root = await realpath(
await mkdtemp(join(tmpdir(), 'goodbuddy-harness-launcher-'))
)
const workspace = join(root, 'workspace')
const dshHome = join(root, 'home')
const hostPath = join(
root,
'deepseek-harness-host-bootstrap.js'
)
await Promise.all([
mkdir(workspace),
mkdir(dshHome),
writeFile(hostPath, '', 'utf8')
])
return {
dshHome,
hostPath,
launchOptions: {
cwd: workspace,
signal: new AbortController().signal,
baseUrl: 'https://api.deepseek.com',
model: 'deepseek-chat',
credentialRefs: [DEEPSEEK_HARNESS_CREDENTIAL_REF],
skillPackages: []
}
}
}
describe('DeepSeek Harness utility launcher', () => {
it('accepts only strict control messages and secret-free config', () => {
expect(
parseHarnessControlMessage({
protocol: DEEPSEEK_HARNESS_CONTROL_PROTOCOL,
version: DEEPSEEK_HARNESS_CONTROL_VERSION,
type: 'ready'
})
).toMatchObject({ type: 'ready' })
expect(
parseHarnessControlMessage({
protocol: DEEPSEEK_HARNESS_CONTROL_PROTOCOL,
version: DEEPSEEK_HARNESS_CONTROL_VERSION,
type: 'ready',
apiKey: 'must-not-pass'
})
).toBeUndefined()
})
it('waits for Host readiness and sends no credential value', async () => {
const { dshHome, hostPath, launchOptions } = await fixture()
const utility = new FakeUtility()
const fork = vi.fn(() => utility as never)
const launcher = createDeepSeekHarnessUtilityLauncher({
bundledHostPath: hostPath,
dshHome,
environment: { PATH: 'C:\\Tools' },
fork
})
const launching = launcher(launchOptions)
await vi.waitFor(() =>
expect(utility.messages).toHaveLength(1)
)
expect(JSON.stringify(utility.messages[0])).not.toContain(
'secret'
)
expect(utility.messages[0]).toMatchObject({
type: 'start',
config: {
baseUrl: 'https://api.deepseek.com',
credentialRefs: [DEEPSEEK_HARNESS_CREDENTIAL_REF]
}
})
utility.emit('message', {
protocol: DEEPSEEK_HARNESS_CONTROL_PROTOCOL,
version: DEEPSEEK_HARNESS_CONTROL_VERSION,
type: 'ready'
})
await expect(launching).resolves.toMatchObject({
stdin: expect.any(WritableStream),
stdout: expect.any(ReadableStream)
})
expect(fork).toHaveBeenCalledWith(
hostPath,
[],
expect.objectContaining({
cwd: launchOptions.cwd,
stdio: ['ignore', 'ignore', 'pipe']
})
)
})
it('fails closed on an invalid Host startup message', async () => {
const { dshHome, hostPath, launchOptions } = await fixture()
const utility = new FakeUtility()
const terminateProcess = vi.fn(() => {
utility.killed = true
})
const launcher = createDeepSeekHarnessUtilityLauncher({
bundledHostPath: hostPath,
dshHome,
environment: {},
fork: () => utility as never,
terminateProcess
})
const launching = launcher(launchOptions)
await vi.waitFor(() =>
expect(utility.messages).toHaveLength(1)
)
utility.emit('message', { type: 'ready' })
await expect(launching).rejects.toThrow('启动协议无效')
expect(terminateProcess).toHaveBeenCalledOnce()
})
})
@@ -0,0 +1,369 @@
import { Readable } from 'node:stream'
import { realpath, stat } from 'node:fs/promises'
import { isAbsolute } from 'node:path'
import type { UtilityProcess } from 'electron'
import { z } from 'zod'
import type {
DeepSeekHarnessChild,
DeepSeekHarnessLaunchOptions
} from './deepseek-harness-runtime'
import { createDeepSeekHarnessUtilityChild } from './deepseek-harness-utility-transport'
export const DEEPSEEK_HARNESS_CONTROL_PROTOCOL =
'goodbuddy.deepseek-harness.control'
export const DEEPSEEK_HARNESS_CONTROL_VERSION = 1
export const DEEPSEEK_HARNESS_HOST_VERSION = '0.1.0-rc.6'
export const DEEPSEEK_HARNESS_CREDENTIAL_REF =
'GOODBUDDY_DEEPSEEK_API_KEY'
const sandboxSchema = z
.object({
provider: z.string().min(1).max(64),
enforcement: z.enum(['full', 'partial'])
})
.strict()
const skillPackageSchema = z
.object({
id: z
.string()
.min(1)
.max(128)
.regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/u),
directory: z.string().min(1).max(32_768).refine(isAbsolute)
})
.strict()
export const controlledHarnessHostConfigSchema = z
.object({
workspace: z.string().min(1).max(32_768).refine(isAbsolute),
dshHome: z.string().min(1).max(32_768).refine(isAbsolute),
baseUrl: z
.url()
.max(2_048)
.refine((value) => {
const url = new URL(value)
return (
url.protocol === 'https:' &&
url.hostname.toLowerCase() === 'api.deepseek.com' &&
!url.username &&
!url.password
)
}),
api: z.literal('openai-completions'),
provider: z.literal('goodbuddy'),
model: z.string().min(1).max(128),
harnessVersion: z.literal(DEEPSEEK_HARNESS_HOST_VERSION),
sandbox: sandboxSchema,
credentialRefs: z
.tuple([z.literal(DEEPSEEK_HARNESS_CREDENTIAL_REF)])
.readonly(),
skillPackages: z.array(skillPackageSchema).max(64),
maxFrameBytes: z.literal(1024 * 1024)
})
.strict()
export type ControlledHarnessBootstrapConfig = z.infer<
typeof controlledHarnessHostConfigSchema
>
export type DeepSeekHarnessControlMessage =
| {
protocol: typeof DEEPSEEK_HARNESS_CONTROL_PROTOCOL
version: typeof DEEPSEEK_HARNESS_CONTROL_VERSION
type: 'start'
config: ControlledHarnessBootstrapConfig
}
| {
protocol: typeof DEEPSEEK_HARNESS_CONTROL_PROTOCOL
version: typeof DEEPSEEK_HARNESS_CONTROL_VERSION
type: 'ready'
}
| {
protocol: typeof DEEPSEEK_HARNESS_CONTROL_PROTOCOL
version: typeof DEEPSEEK_HARNESS_CONTROL_VERSION
type: 'fatal'
code: string
}
export function parseHarnessControlMessage(
value: unknown
): DeepSeekHarnessControlMessage | undefined {
if (
!value ||
typeof value !== 'object' ||
Array.isArray(value)
) {
return undefined
}
const record = value as Record<string, unknown>
if (
record.protocol !== DEEPSEEK_HARNESS_CONTROL_PROTOCOL ||
record.version !== DEEPSEEK_HARNESS_CONTROL_VERSION
) {
return undefined
}
if (record.type === 'ready' && Object.keys(record).length === 3) {
return record as DeepSeekHarnessControlMessage
}
if (
record.type === 'fatal' &&
Object.keys(record).length === 4 &&
typeof record.code === 'string' &&
/^[A-Z][A-Z0-9_]{0,63}$/u.test(record.code)
) {
return record as DeepSeekHarnessControlMessage
}
if (
record.type === 'start' &&
Object.keys(record).length === 4
) {
const parsed = controlledHarnessHostConfigSchema.safeParse(
record.config
)
return parsed.success
? ({
protocol: DEEPSEEK_HARNESS_CONTROL_PROTOCOL,
version: DEEPSEEK_HARNESS_CONTROL_VERSION,
type: 'start',
config: parsed.data
} satisfies DeepSeekHarnessControlMessage)
: undefined
}
return undefined
}
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
startupTimeoutMs?: number
}
function expectedSandbox(): ControlledHarnessBootstrapConfig['sandbox'] {
return process.platform === 'win32'
? { provider: 'windows-acl', enforcement: 'partial' }
: process.platform === 'darwin'
? { provider: 'seatbelt', enforcement: 'full' }
: { provider: 'local-linux', enforcement: 'full' }
}
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 [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、工作区或隔离目录类型无效'
)
}
const sandbox = expectedSandbox()
if (
options.requiredSandboxEnforcement === 'full' &&
sandbox.enforcement !== 'full'
) {
throw new Error(
'DeepSeek Harness 当前平台只能提供部分沙箱强制'
)
}
if (
options.baseUrl !== 'https://api.deepseek.com' &&
options.baseUrl !== 'https://api.deepseek.com/'
) {
throw new Error(
'DeepSeek Harness 仅允许 api.deepseek.com'
)
}
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 ?? 10_000
let timer: ReturnType<typeof setTimeout> | undefined
let onAbort: (() => void) | undefined
try {
await new Promise<void>((resolve, reject) => {
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 => {
cleanup()
terminate()
reject(error)
}
const onMessage = (message: unknown): void => {
const control = parseHarnessControlMessage(message)
if (!control) {
fail(new Error('DeepSeek Harness Host 启动协议无效'))
return
}
if (control.type === 'ready') {
cleanup()
resolve()
} 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,
harnessVersion: DEEPSEEK_HARNESS_HOST_VERSION,
sandbox,
credentialRefs: [DEEPSEEK_HARNESS_CREDENTIAL_REF],
skillPackages: canonicalSkillPackages,
maxFrameBytes: 1024 * 1024
})
utility.postMessage({
protocol: DEEPSEEK_HARNESS_CONTROL_PROTOCOL,
version: DEEPSEEK_HARNESS_CONTROL_VERSION,
type: 'start',
config
} satisfies DeepSeekHarnessControlMessage)
})
return createDeepSeekHarnessUtilityChild(utility, {
stderrToWeb: (stderr) =>
Readable.toWeb(stderr) as ReadableStream<Uint8Array>,
terminateProcess: terminate
})
} catch (error) {
terminate()
throw error
}
}
}
@@ -0,0 +1,365 @@
import { describe, expect, it, vi } from 'vitest'
import {
DEEPSEEK_HARNESS_BYTE_PROTOCOL,
DEEPSEEK_HARNESS_BYTE_PROTOCOL_VERSION,
DEEPSEEK_HARNESS_MAX_CHUNK_BYTES,
createDeepSeekHarnessHostTransport,
createDeepSeekHarnessUtilityChild,
type DeepSeekHarnessParentPortLike
} from './deepseek-harness-utility-transport'
type Listener = (value: unknown) => void
class LinkedPort {
peer?: LinkedPort
readonly sent: unknown[] = []
private readonly listeners = new Set<Listener>()
postMessage(message: unknown): void {
this.sent.push(message)
queueMicrotask(() => {
for (const listener of this.peer?.listeners ?? []) {
listener(message)
}
})
}
subscribe(listener: Listener): () => void {
this.listeners.add(listener)
return () => this.listeners.delete(listener)
}
}
class FakeUtility {
readonly port = new LinkedPort()
readonly stderr = 'node-stderr'
readonly kill = vi.fn(() => true)
private readonly listeners = {
message: new Set<(message: unknown) => void>(),
exit: new Set<(exitCode: number) => void>()
}
constructor(hostPort: LinkedPort) {
this.port.peer = hostPort
hostPort.peer = this.port
this.port.subscribe((message) => {
for (const listener of this.listeners.message) {
listener(message)
}
})
}
postMessage(message: unknown): void {
this.port.postMessage(message)
}
on(event: 'message', listener: (message: unknown) => void): void
on(event: 'exit', listener: (exitCode: number) => void): void
on(
event: keyof typeof this.listeners,
listener: ((message: unknown) => void) | ((exitCode: number) => void)
): void {
if (event === 'message') {
this.listeners.message.add(listener as (message: unknown) => void)
} else {
this.listeners.exit.add(listener as (exitCode: number) => void)
}
}
removeListener(event: 'message', listener: (message: unknown) => void): void
removeListener(event: 'exit', listener: (exitCode: number) => void): void
removeListener(
event: keyof typeof this.listeners,
listener: ((message: unknown) => void) | ((exitCode: number) => void)
): void {
if (event === 'message') {
this.listeners.message.delete(listener as (message: unknown) => void)
} else {
this.listeners.exit.delete(listener as (exitCode: number) => void)
}
}
emitMessage(message: unknown): void {
for (const listener of this.listeners.message) {
listener(message)
}
}
emitExit(exitCode: number): void {
for (const listener of this.listeners.exit) {
listener(exitCode)
}
}
}
function asParentPort(port: LinkedPort): DeepSeekHarnessParentPortLike {
const wrapped = new Map<Listener, () => void>()
return {
postMessage: (message) => port.postMessage(message),
on: (_event, listener) => {
const adapter: Listener = (data) => listener({ data })
wrapped.set(listener as Listener, port.subscribe(adapter))
},
removeListener: (_event, listener) => {
wrapped.get(listener as Listener)?.()
wrapped.delete(listener as Listener)
}
}
}
function setup() {
const hostPort = new LinkedPort()
const utility = new FakeUtility(hostPort)
const stderr = new ReadableStream<Uint8Array>()
const stderrToWeb = vi.fn(() => stderr)
const child = createDeepSeekHarnessUtilityChild(utility, { stderrToWeb })
const host = createDeepSeekHarnessHostTransport(asParentPort(hostPort))
return { child, host, hostPort, utility, stderr, stderrToWeb }
}
const tick = () => new Promise<void>((resolve) => queueMicrotask(resolve))
describe('DeepSeek Harness utility byte transport', () => {
it('ignores trusted control-plane messages that share the UtilityProcess port', async () => {
const { child, hostPort, utility } = setup()
await tick()
utility.kill.mockClear()
utility.emitMessage({
protocol: 'goodbuddy.deepseek-harness.control',
version: 1,
type: 'ready'
})
const reader = child.stdout.getReader()
const reading = reader.read()
hostPort.postMessage({
protocol: DEEPSEEK_HARNESS_BYTE_PROTOCOL,
version: DEEPSEEK_HARNESS_BYTE_PROTOCOL_VERSION,
type: 'data',
stream: 'stdout',
seq: 0,
bytes: Uint8Array.of(7)
})
await expect(reading).resolves.toEqual({
done: false,
value: Uint8Array.of(7)
})
expect(utility.kill).toHaveBeenCalledOnce()
})
it('fails closed for malformed control-plane lookalikes', async () => {
const { child, utility } = setup()
const reader = child.stdout.getReader()
utility.emitMessage({
protocol: 'goodbuddy.deepseek-harness.control',
version: 1,
type: 'ready',
unexpected: true
})
await expect(reader.read()).rejects.toThrow('PROTOCOL_VIOLATION')
expect(utility.kill).toHaveBeenCalledOnce()
})
it('transports bytes in both directions and adapts stderr and exit', async () => {
const { child, host, utility, stderr, stderrToWeb } = setup()
const childWriter = child.stdin.getWriter()
const hostInput = host.stdin.getReader()
const hostWriter = host.stdout.getWriter()
const childOutput = child.stdout.getReader()
await childWriter.write(Uint8Array.of(1, 2, 3))
await expect(hostInput.read()).resolves.toEqual({
done: false,
value: Uint8Array.of(1, 2, 3)
})
await hostWriter.write(Uint8Array.of(4, 5))
await expect(childOutput.read()).resolves.toEqual({
done: false,
value: Uint8Array.of(4, 5)
})
expect(stderrToWeb).toHaveBeenCalledWith('node-stderr')
expect(child.stderr).toBe(stderr)
utility.emitExit(7)
await expect(child.exited).resolves.toEqual({ exitCode: 7 })
})
it('splits chunks at 64 KiB and waits for ACK backpressure', async () => {
const { child, host, utility } = setup()
const writer = child.stdin.getWriter()
const bytes = new Uint8Array(DEEPSEEK_HARNESS_MAX_CHUNK_BYTES + 3)
bytes.fill(9)
let settled = false
const writing = writer.write(bytes).then(() => {
settled = true
})
await tick()
expect(settled).toBe(false)
expect(utility.port.sent).toHaveLength(1)
expect(utility.port.sent[0]).toMatchObject({
type: 'data',
seq: 0,
bytes: expect.objectContaining({
byteLength: DEEPSEEK_HARNESS_MAX_CHUNK_BYTES
})
})
const reader = host.stdin.getReader()
expect((await reader.read()).value).toHaveLength(
DEEPSEEK_HARNESS_MAX_CHUNK_BYTES
)
await tick()
expect(utility.port.sent).toHaveLength(2)
expect(utility.port.sent[1]).toMatchObject({
type: 'data',
seq: 1,
bytes: Uint8Array.of(9, 9, 9)
})
expect((await reader.read()).value).toEqual(Uint8Array.of(9, 9, 9))
await writing
expect(settled).toBe(true)
})
it('applies bounded receiver backpressure until the queued chunk is read', async () => {
const { child, host, utility } = setup()
const writer = child.stdin.getWriter()
await writer.write(Uint8Array.of(1))
let secondSettled = false
const second = writer.write(Uint8Array.of(2)).then(() => {
secondSettled = true
})
await tick()
expect(secondSettled).toBe(false)
expect(utility.port.sent).toHaveLength(2)
const reader = host.stdin.getReader()
await expect(reader.read()).resolves.toMatchObject({
value: Uint8Array.of(1)
})
await tick()
await second
expect(secondSettled).toBe(true)
})
it.each([
['unknown message', { surprise: true }],
[
'unknown type',
{
protocol: DEEPSEEK_HARNESS_BYTE_PROTOCOL,
version: DEEPSEEK_HARNESS_BYTE_PROTOCOL_VERSION,
type: 'wat'
}
],
[
'extra field',
{
protocol: DEEPSEEK_HARNESS_BYTE_PROTOCOL,
version: DEEPSEEK_HARNESS_BYTE_PROTOCOL_VERSION,
type: 'ack',
stream: 'stdin',
seq: 0,
extra: true
}
],
[
'oversized chunk',
{
protocol: DEEPSEEK_HARNESS_BYTE_PROTOCOL,
version: DEEPSEEK_HARNESS_BYTE_PROTOCOL_VERSION,
type: 'data',
stream: 'stdout',
seq: 0,
bytes: new Uint8Array(DEEPSEEK_HARNESS_MAX_CHUNK_BYTES + 1)
}
]
])('fails closed for %s without including payloads in errors', async (_, message) => {
const { child, utility } = setup()
const reader = child.stdout.getReader()
utility.emitMessage(message)
await expect(reader.read()).rejects.toThrow(
'DeepSeek Harness byte transport failed (PROTOCOL_VIOLATION)'
)
expect(utility.kill).toHaveBeenCalledTimes(1)
expect(String(await reader.closed.catch((error) => error))).not.toContain(
'surprise'
)
})
it('fails closed for duplicate and out-of-order sequence numbers', async () => {
const first = setup()
first.utility.emitMessage({
protocol: DEEPSEEK_HARNESS_BYTE_PROTOCOL,
version: DEEPSEEK_HARNESS_BYTE_PROTOCOL_VERSION,
type: 'data',
stream: 'stdout',
seq: 1,
bytes: Uint8Array.of(1)
})
await expect(first.child.stdout.getReader().read()).rejects.toThrow(
'PROTOCOL_VIOLATION'
)
expect(first.utility.kill).toHaveBeenCalledOnce()
const second = setup()
const reader = second.child.stdout.getReader()
second.utility.emitMessage({
protocol: DEEPSEEK_HARNESS_BYTE_PROTOCOL,
version: DEEPSEEK_HARNESS_BYTE_PROTOCOL_VERSION,
type: 'data',
stream: 'stdout',
seq: 0,
bytes: Uint8Array.of(1)
})
await reader.read()
second.utility.emitMessage({
protocol: DEEPSEEK_HARNESS_BYTE_PROTOCOL,
version: DEEPSEEK_HARNESS_BYTE_PROTOCOL_VERSION,
type: 'data',
stream: 'stdout',
seq: 0,
bytes: Uint8Array.of(1)
})
await expect(reader.read()).rejects.toThrow('PROTOCOL_VIOLATION')
expect(second.utility.kill).toHaveBeenCalledOnce()
})
it('propagates close and cancellation idempotently', async () => {
const { child, host, utility } = setup()
const writer = child.stdin.getWriter()
const reader = host.stdin.getReader()
const closing = writer.close()
await expect(reader.read()).resolves.toEqual({
done: true,
value: undefined
})
await closing
const childOutput = child.stdout.getReader()
await childOutput.cancel()
const hostWriter = host.stdout.getWriter()
await expect(hostWriter.write(Uint8Array.of(8))).rejects.toThrow(
'REMOTE_CANCELLED'
)
child.terminate()
child.terminate()
expect(utility.kill).toHaveBeenCalledTimes(1)
})
it('cancels a chunk waiting behind the bounded readable queue', async () => {
const { child, host } = setup()
const writer = child.stdin.getWriter()
await writer.write(Uint8Array.of(1))
const pendingWrite = writer.write(Uint8Array.of(2))
await tick()
await host.stdin.cancel()
await expect(pendingWrite).rejects.toThrow('REMOTE_CANCELLED')
})
})
@@ -0,0 +1,678 @@
import type { DeepSeekHarnessChild } from './deepseek-harness-runtime'
export const DEEPSEEK_HARNESS_BYTE_PROTOCOL =
'goodbuddy.deepseek-harness.byte-stream'
export const DEEPSEEK_HARNESS_BYTE_PROTOCOL_VERSION = 1
export const DEEPSEEK_HARNESS_MAX_CHUNK_BYTES = 64 * 1024
type StreamName = 'stdin' | 'stdout'
type ForwardType = 'data' | 'close' | 'abort'
type MessageBase = {
protocol: typeof DEEPSEEK_HARNESS_BYTE_PROTOCOL
version: typeof DEEPSEEK_HARNESS_BYTE_PROTOCOL_VERSION
stream: StreamName
seq: number
}
type ProtocolMessage =
| (MessageBase & {
type: 'data'
bytes: Uint8Array
})
| (MessageBase & { type: 'close' })
| (MessageBase & { type: 'abort' })
| (MessageBase & { type: 'ack' })
| (MessageBase & { type: 'cancel' })
| {
protocol: typeof DEEPSEEK_HARNESS_BYTE_PROTOCOL
version: typeof DEEPSEEK_HARNESS_BYTE_PROTOCOL_VERSION
type: 'fail'
}
type Deferred = {
readonly promise: Promise<void>
resolve(): void
reject(error: Error): void
}
type PendingSend = {
readonly seq: number
readonly deferred: Deferred
}
type SenderState = {
readonly stream: StreamName
nextSeq: number
pending?: PendingSend
finished: boolean
cancelled: boolean
controller?: WritableStreamDefaultController
}
type ReceiverState = {
readonly stream: StreamName
nextSeq: number
pendingBytes?: Uint8Array
finished: boolean
cancelled: boolean
controller?: ReadableStreamDefaultController<Uint8Array>
}
type MessagePortAdapter = {
postMessage(message: ProtocolMessage): void
subscribe(listener: (message: unknown) => void): () => void
}
type EndpointOptions = {
readonly senderStream: StreamName
readonly receiverStream: StreamName
readonly onFailure?: () => void
}
const CONTROL_PROTOCOL = 'goodbuddy.deepseek-harness.control'
const PROTOCOL_KEYS = ['protocol', 'version', 'type'] as const
const STREAM_KEYS = [...PROTOCOL_KEYS, 'stream', 'seq'] as const
const DATA_KEYS = [...STREAM_KEYS, 'bytes'] as const
const MAX_SEQUENCE = Number.MAX_SAFE_INTEGER
class ByteTransportError extends Error {
constructor(code: string) {
super(`DeepSeek Harness byte transport failed (${code})`)
this.name = 'ByteTransportError'
}
}
function deferred(): Deferred {
let resolvePromise: (() => void) | undefined
let rejectPromise: ((error: Error) => void) | undefined
const promise = new Promise<void>((resolve, reject) => {
resolvePromise = resolve
rejectPromise = reject
})
return {
promise,
resolve: () => resolvePromise?.(),
reject: (error) => rejectPromise?.(error)
}
}
function isRecord(value: unknown): value is Record<string, unknown> {
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
return false
}
const prototype = Object.getPrototypeOf(value)
return prototype === Object.prototype || prototype === null
}
function hasExactKeys(
value: Record<string, unknown>,
expected: readonly string[]
): boolean {
const keys = Object.keys(value)
return (
keys.length === expected.length &&
expected.every((key) => Object.prototype.hasOwnProperty.call(value, key))
)
}
function isSequence(value: unknown): value is number {
return (
typeof value === 'number' &&
Number.isSafeInteger(value) &&
value >= 0 &&
value <= MAX_SEQUENCE
)
}
function parseMessage(value: unknown): ProtocolMessage | undefined {
if (
!isRecord(value) ||
value.protocol !== DEEPSEEK_HARNESS_BYTE_PROTOCOL ||
value.version !== DEEPSEEK_HARNESS_BYTE_PROTOCOL_VERSION ||
typeof value.type !== 'string'
) {
return undefined
}
if (value.type === 'fail') {
return hasExactKeys(value, PROTOCOL_KEYS)
? (value as ProtocolMessage)
: undefined
}
if (
!['data', 'close', 'abort', 'ack', 'cancel'].includes(value.type) ||
(value.stream !== 'stdin' && value.stream !== 'stdout') ||
!isSequence(value.seq)
) {
return undefined
}
if (value.type === 'data') {
if (
!hasExactKeys(value, DATA_KEYS) ||
!(value.bytes instanceof Uint8Array) ||
value.bytes.byteLength === 0 ||
value.bytes.byteLength > DEEPSEEK_HARNESS_MAX_CHUNK_BYTES
) {
return undefined
}
return value as ProtocolMessage
}
return hasExactKeys(value, STREAM_KEYS)
? (value as ProtocolMessage)
: undefined
}
function isControlMessage(value: unknown): boolean {
if (
!isRecord(value) ||
value.protocol !== CONTROL_PROTOCOL ||
value.version !== 1 ||
typeof value.type !== 'string'
) {
return false
}
if (value.type === 'ready') {
return hasExactKeys(value, PROTOCOL_KEYS)
}
if (value.type === 'fatal') {
return (
hasExactKeys(value, [...PROTOCOL_KEYS, 'code']) &&
typeof value.code === 'string' &&
/^[A-Z][A-Z0-9_]{0,63}$/u.test(value.code)
)
}
return (
value.type === 'start' &&
hasExactKeys(value, [...PROTOCOL_KEYS, 'config']) &&
isRecord(value.config)
)
}
class ByteTransportEndpoint {
readonly writable: WritableStream<Uint8Array>
readonly readable: ReadableStream<Uint8Array>
private readonly sender: SenderState
private readonly receiver: ReceiverState
private readonly unsubscribe: () => void
private failed = false
private disposed = false
constructor(
private readonly port: MessagePortAdapter,
private readonly options: EndpointOptions
) {
this.sender = {
stream: options.senderStream,
nextSeq: 0,
finished: false,
cancelled: false
}
this.receiver = {
stream: options.receiverStream,
nextSeq: 0,
finished: false,
cancelled: false
}
this.writable = new WritableStream<Uint8Array>(
{
start: (controller) => {
this.sender.controller = controller
},
write: async (chunk) => {
if (!(chunk instanceof Uint8Array)) {
throw new ByteTransportError('INVALID_WRITE')
}
for (
let offset = 0;
offset < chunk.byteLength;
offset += DEEPSEEK_HARNESS_MAX_CHUNK_BYTES
) {
const bytes = chunk.slice(
offset,
offset + DEEPSEEK_HARNESS_MAX_CHUNK_BYTES
)
await this.sendForward('data', bytes)
}
},
close: () => this.sendForward('close'),
abort: () => this.sendForward('abort')
},
new CountQueuingStrategy({ highWaterMark: 1 })
)
this.readable = new ReadableStream<Uint8Array>(
{
start: (controller) => {
this.receiver.controller = controller
},
pull: () => {
this.flushReceiver()
},
cancel: () => {
this.cancelReceiver()
}
},
new CountQueuingStrategy({ highWaterMark: 1 })
)
this.unsubscribe = this.port.subscribe((message) => {
if (isControlMessage(message)) {
return
}
this.handleMessage(message)
})
}
dispose(code = 'CLOSED'): void {
if (this.disposed) {
return
}
this.disposed = true
this.unsubscribe()
const error = new ByteTransportError(code)
this.sender.pending?.deferred.reject(error)
this.sender.pending = undefined
try {
this.sender.controller?.error(error)
} catch {
// The stream may already be closed.
}
try {
this.receiver.controller?.error(error)
} catch {
// The stream may already be closed.
}
}
private fail(code: string, notifyPeer: boolean): void {
if (this.failed || this.disposed) {
return
}
this.failed = true
if (notifyPeer) {
try {
this.port.postMessage({
protocol: DEEPSEEK_HARNESS_BYTE_PROTOCOL,
version: DEEPSEEK_HARNESS_BYTE_PROTOCOL_VERSION,
type: 'fail'
})
} catch {
// The local endpoint still closes if peer notification fails.
}
}
this.dispose(code)
this.options.onFailure?.()
}
private post(message: ProtocolMessage): boolean {
if (this.failed || this.disposed) {
return false
}
try {
this.port.postMessage(message)
return true
} catch {
this.fail('CHANNEL_FAILURE', false)
return false
}
}
private async sendForward(
type: ForwardType,
bytes?: Uint8Array
): Promise<void> {
if (
this.failed ||
this.disposed ||
this.sender.finished ||
this.sender.cancelled
) {
throw new ByteTransportError(
this.sender.cancelled ? 'REMOTE_CANCELLED' : 'CLOSED'
)
}
if (this.sender.pending || this.sender.nextSeq > MAX_SEQUENCE) {
this.fail('LOCAL_STATE', true)
throw new ByteTransportError('LOCAL_STATE')
}
const waiting = deferred()
const seq = this.sender.nextSeq
this.sender.pending = { seq, deferred: waiting }
const message: ProtocolMessage =
type === 'data'
? {
protocol: DEEPSEEK_HARNESS_BYTE_PROTOCOL,
version: DEEPSEEK_HARNESS_BYTE_PROTOCOL_VERSION,
type,
stream: this.sender.stream,
seq,
bytes: bytes as Uint8Array
}
: {
protocol: DEEPSEEK_HARNESS_BYTE_PROTOCOL,
version: DEEPSEEK_HARNESS_BYTE_PROTOCOL_VERSION,
type,
stream: this.sender.stream,
seq
}
if (!this.post(message)) {
await waiting.promise
return
}
await waiting.promise
if (type !== 'data') {
this.sender.finished = true
}
}
private handleMessage(rawMessage: unknown): void {
const message = parseMessage(rawMessage)
if (!message) {
this.fail('PROTOCOL_VIOLATION', true)
return
}
if (message.type === 'fail') {
this.fail('REMOTE_FAILURE', false)
return
}
if (message.type === 'ack') {
this.handleAck(message)
return
}
if (message.type === 'cancel') {
this.handleCancel(message)
return
}
this.handleForward(message)
}
private handleAck(
message: MessageBase & { type: 'ack' }
): void {
const pending = this.sender.pending
if (
message.stream !== this.sender.stream ||
!pending ||
message.seq !== pending.seq
) {
this.fail('PROTOCOL_VIOLATION', true)
return
}
this.sender.pending = undefined
this.sender.nextSeq += 1
pending.deferred.resolve()
}
private handleCancel(
message: MessageBase & { type: 'cancel' }
): void {
const pending = this.sender.pending
if (
message.stream !== this.sender.stream ||
this.sender.finished ||
this.sender.cancelled ||
message.seq !== (pending?.seq ?? this.sender.nextSeq)
) {
this.fail('PROTOCOL_VIOLATION', true)
return
}
this.sender.cancelled = true
this.sender.pending = undefined
const error = new ByteTransportError('REMOTE_CANCELLED')
pending?.deferred.reject(error)
try {
this.sender.controller?.error(error)
} catch {
// The stream may already be closed.
}
}
private handleForward(
message: Extract<ProtocolMessage, { type: ForwardType }>
): void {
if (
message.stream !== this.receiver.stream ||
this.receiver.finished ||
this.receiver.cancelled ||
message.seq !== this.receiver.nextSeq
) {
this.fail('PROTOCOL_VIOLATION', true)
return
}
this.receiver.nextSeq += 1
if (message.type === 'data') {
if (this.receiver.pendingBytes) {
this.fail('PROTOCOL_VIOLATION', true)
return
}
this.receiver.pendingBytes = message.bytes.slice()
this.flushReceiver()
return
}
this.receiver.finished = true
if (message.type === 'close') {
try {
this.receiver.controller?.close()
} catch {
this.fail('LOCAL_STATE', true)
return
}
} else {
try {
this.receiver.controller?.error(
new ByteTransportError('REMOTE_ABORTED')
)
} catch {
// The stream may already have been cancelled.
}
}
this.sendAck(message.seq)
}
private flushReceiver(): void {
const controller = this.receiver.controller
const bytes = this.receiver.pendingBytes
if (
!controller ||
!bytes ||
this.receiver.cancelled ||
this.receiver.finished ||
(controller.desiredSize ?? 0) <= 0
) {
return
}
this.receiver.pendingBytes = undefined
controller.enqueue(bytes)
this.sendAck(this.receiver.nextSeq - 1)
}
private sendAck(seq: number): void {
this.post({
protocol: DEEPSEEK_HARNESS_BYTE_PROTOCOL,
version: DEEPSEEK_HARNESS_BYTE_PROTOCOL_VERSION,
type: 'ack',
stream: this.receiver.stream,
seq
})
}
private cancelReceiver(): void {
if (
this.receiver.cancelled ||
this.receiver.finished ||
this.failed ||
this.disposed
) {
return
}
this.receiver.cancelled = true
const cancelSeq = this.receiver.pendingBytes
? this.receiver.nextSeq - 1
: this.receiver.nextSeq
this.receiver.pendingBytes = undefined
this.post({
protocol: DEEPSEEK_HARNESS_BYTE_PROTOCOL,
version: DEEPSEEK_HARNESS_BYTE_PROTOCOL_VERSION,
type: 'cancel',
stream: this.receiver.stream,
seq: cancelSeq
})
}
}
export type DeepSeekHarnessUtilityProcessLike<Stderr = unknown> = {
postMessage(message: unknown): void
on(event: 'message', listener: (message: unknown) => void): unknown
on(event: 'exit', listener: (exitCode: number) => void): unknown
removeListener(
event: 'message',
listener: (message: unknown) => void
): unknown
removeListener(event: 'exit', listener: (exitCode: number) => void): unknown
kill(): boolean
readonly pid?: number
readonly stderr?: Stderr | null
}
export type DeepSeekHarnessUtilityChildOptions<Stderr> = {
stderrToWeb?: (stderr: Stderr) => ReadableStream<Uint8Array>
terminateProcess?: (
utilityProcess: DeepSeekHarnessUtilityProcessLike<Stderr>
) => void
}
/**
* Adapts an Electron UtilityProcess without importing Electron at runtime.
* Configure the utility process with piped stderr and inject Node's
* Readable.toWeb when stderr capture is required.
*/
export function createDeepSeekHarnessUtilityChild<Stderr = unknown>(
utilityProcess: DeepSeekHarnessUtilityProcessLike<Stderr>,
options: DeepSeekHarnessUtilityChildOptions<Stderr> = {}
): DeepSeekHarnessChild {
let killed = false
const killOnce = (): void => {
if (killed) {
return
}
killed = true
if (options.terminateProcess) {
options.terminateProcess(utilityProcess)
} else {
utilityProcess.kill()
}
}
const endpoint = new ByteTransportEndpoint(
{
postMessage: (message) => utilityProcess.postMessage(message),
subscribe: (listener) => {
const onMessage = (message: unknown): void => listener(message)
utilityProcess.on('message', onMessage)
return () => utilityProcess.removeListener('message', onMessage)
}
},
{
senderStream: 'stdin',
receiverStream: 'stdout',
onFailure: killOnce
}
)
let settleExit:
| ((result: { exitCode: number | null; signal?: string | null }) => void)
| undefined
const exited = new Promise<{
exitCode: number | null
signal?: string | null
}>((resolve) => {
settleExit = resolve
})
let exitedSettled = false
const onExit = (exitCode: number): void => {
if (exitedSettled) {
return
}
exitedSettled = true
killed = true
endpoint.dispose('PROCESS_EXITED')
settleExit?.({ exitCode })
}
utilityProcess.on('exit', onExit)
const stderr =
utilityProcess.stderr != null && options.stderrToWeb
? options.stderrToWeb(utilityProcess.stderr)
: undefined
return {
stdin: endpoint.writable,
stdout: endpoint.readable,
stderr,
exited,
terminate: () => {
endpoint.dispose('TERMINATED')
killOnce()
}
}
}
type ParentPortMessageEvent = {
readonly data: unknown
}
export type DeepSeekHarnessParentPortLike = {
postMessage(message: unknown): void
on(
event: 'message',
listener: (event: ParentPortMessageEvent) => void
): unknown
removeListener(
event: 'message',
listener: (event: ParentPortMessageEvent) => void
): unknown
}
export type DeepSeekHarnessHostTransport = {
readonly stdin: ReadableStream<Uint8Array>
readonly stdout: WritableStream<Uint8Array>
dispose(): void
}
/** Creates the host-side streams backed by process.parentPort-like messaging. */
export function createDeepSeekHarnessHostTransport(
parentPort: DeepSeekHarnessParentPortLike
): DeepSeekHarnessHostTransport {
const endpoint = new ByteTransportEndpoint(
{
postMessage: (message) => parentPort.postMessage(message),
subscribe: (listener) => {
const onMessage = (event: ParentPortMessageEvent): void =>
listener(event.data)
parentPort.on('message', onMessage)
return () => parentPort.removeListener('message', onMessage)
}
},
{
senderStream: 'stdout',
receiverStream: 'stdin'
}
)
return {
stdin: endpoint.readable,
stdout: endpoint.writable,
dispose: () => endpoint.dispose()
}
}
@@ -0,0 +1,333 @@
import { describe, expect, it, vi } from 'vitest'
import { Context } from '@deepseek-ai/cordis'
import type { Stream } from '@agentclientprotocol/sdk'
import { resolve } from 'node:path'
import {
GOODBUDDY_HANDSHAKE,
GOODBUDDY_PREPARE,
GoodBuddyCredentialProvider,
GoodBuddyHarnessControlPlane,
GoodBuddySandboxRetryLedger,
createBoundedAcpStream
} from './goodbuddy-harness-control-plane'
function execution(
callId: string,
name: string,
args: Record<string, unknown>
) {
return {
callId,
rootCallId: callId,
name,
arguments: args,
signal: new AbortController().signal,
token: Symbol('execution')
} as never
}
const sandboxDenied = {
isError: false,
value: {
sandbox: {
denied: true
}
},
content: []
} as const
function controlPlane() {
return new GoodBuddyHarnessControlPlane({} as Context, {
provider: 'goodbuddy',
model: 'deepseek-test',
workspace: resolve('workspace'),
harnessVersion: '0.1.0-rc.6',
sandbox: { provider: 'test', enforcement: 'full' },
credentialRefs: ['GOODBUDDY_API_KEY'],
skills: []
})
}
function stubAgentContext() {
const listeners = new Map<
string,
(...args: unknown[]) => unknown
>()
const extNotification = vi.fn(async () => undefined)
const handle = {
agent: {
session: {
id: 'session-output',
header: { id: 'session-output' },
events: []
},
cancel: vi.fn()
}
}
const ctx = {
on: vi.fn(
(
name: string,
listener: (...args: unknown[]) => unknown
) => {
listeners.set(name, listener)
return vi.fn()
}
)
} as unknown as Context
const subject = new GoodBuddyHarnessControlPlane(ctx, {
provider: 'goodbuddy',
model: 'deepseek-test',
workspace: resolve('workspace'),
harnessVersion: '0.1.0-rc.6',
sandbox: { provider: 'test', enforcement: 'full' },
credentialRefs: ['GOODBUDDY_API_KEY'],
skills: [],
maxEventCharacters: 10_000,
maxRequestCharacters: 180
})
const internals = subject as unknown as {
connection: {
extNotification: typeof extNotification
}
sessions: Map<
string,
{
handle: typeof handle
inflight: {
requestId: string
messageId: string
resolve: (reason: string) => void
reject: (error: unknown) => void
emittedCharacters: number
eventTail: Promise<void>
eventError?: unknown
}
}
>
observeSessions(): void
}
internals.connection = { extNotification }
internals.sessions.set('session-output', {
handle,
inflight: {
requestId: 'request-output',
messageId: 'message-output',
resolve: vi.fn(),
reject: vi.fn(),
emittedCharacters: 0,
eventTail: Promise.resolve()
}
})
internals.observeSessions()
return { listeners, extNotification, handle, internals }
}
describe('GoodBuddy Harness internal control plane', () => {
it('requires a versioned handshake before privileged extensions', async () => {
const subject = controlPlane()
await expect(
subject.extensionMethod(GOODBUDDY_PREPARE, {
sessionId: 'session',
requestId: 'request',
mode: 'execute'
})
).rejects.toThrow('GoodBuddy handshake is required')
await expect(
subject.extensionMethod(GOODBUDDY_HANDSHAKE, {
controlProtocolVersion: 9
})
).rejects.toThrow(
'incompatible GoodBuddy Harness control protocol'
)
await expect(
subject.extensionMethod(GOODBUDDY_HANDSHAKE, {
controlProtocolVersion: 1
})
).resolves.toMatchObject({
controlProtocolVersion: 1,
supports: {
cancellation: true,
sessionRelease: true,
oneShotApproval: true,
credentialResolution: true
},
sandbox: { enforcement: 'full' }
})
})
it('keeps credentials memory-only, allowlisted, and read-only', async () => {
const provider = new GoodBuddyCredentialProvider(
new Context(),
new Set(['GOODBUDDY_API_KEY'])
)
const resolver = vi
.fn()
.mockResolvedValue('secret-from-main')
provider.bind(resolver)
await expect(
provider.resolve('GOODBUDDY_API_KEY' as never)
).resolves.toEqual({
value: 'secret-from-main',
source: 'goodbuddy-main'
})
await expect(
provider.resolve('OTHER_KEY' as never)
).resolves.toBeUndefined()
expect(resolver).toHaveBeenCalledTimes(1)
await expect(
provider.set('GOODBUDDY_API_KEY' as never, 'x')
).rejects.toThrow('read-only')
})
it('fails closed on oversized inbound and outbound ACP frames', async () => {
const inbound = new TransformStream<
Record<string, unknown>,
Record<string, unknown>
>()
const outbound = new TransformStream<
Record<string, unknown>,
Record<string, unknown>
>()
const stream = createBoundedAcpStream(
({
readable: inbound.readable,
writable: outbound.writable
} as unknown as Stream),
16
)
const inputWriter = inbound.writable.getWriter()
const reader = stream.readable.getReader()
const read = reader.read()
await inputWriter.write({ value: 'too-long-for-frame' })
await expect(read).rejects.toThrow('input frame exceeds')
const writer = stream.writable.getWriter()
await expect(
writer.write({ value: 'too-long-for-frame' } as never)
).rejects.toThrow('output frame exceeds')
})
it('counts the complete emitted envelope against the request limit', async () => {
const { listeners, extNotification, handle, internals } =
stubAgentContext()
const sessionEvent = listeners.get('session/event')!
sessionEvent(
handle.agent.session,
{
type: 'assistant/chunk',
data: {
chunk: {
type: 'text-delta',
text: 'x'.repeat(80)
}
}
}
)
sessionEvent(
handle.agent.session,
{
type: 'assistant/chunk',
data: {
chunk: {
type: 'usage',
usage: {
inputTokens: 1,
outputTokens: 1,
cacheReadTokens: 0,
cacheWriteTokens: 0
}
}
}
}
)
await internals.sessions.get('session-output')!.inflight.eventTail
expect(extNotification).toHaveBeenCalledTimes(1)
expect(handle.agent.cancel).toHaveBeenCalledWith({
kind: 'user'
})
expect(
internals.sessions.get('session-output')!.inflight.eventError
).toEqual(
new Error(
'GoodBuddy Harness control request output exceeds safety limit'
)
)
expect(
internals.sessions.get('session-output')!.inflight.emittedCharacters
).toBeGreaterThan(180)
})
it('requires a matching real denial and consumes it once', () => {
const ledger = new GoodBuddySandboxRetryLedger()
const deniedArguments = {
command: 'type C:\\outside\\file.txt',
description: 'Read an outside file'
}
const retry = {
...deniedArguments,
sandbox_permissions: 'danger-full-access',
justification: 'The requested file is outside the workspace.'
}
expect(ledger.consumeRetry('pwsh', retry)).toBe(false)
ledger.record(
execution('denial-1', 'pwsh', deniedArguments),
sandboxDenied as never
)
expect(
ledger.consumeRetry('pwsh', {
...retry,
command: 'type C:\\different\\file.txt'
})
).toBe(false)
expect(ledger.consumeRetry('bash', retry)).toBe(false)
expect(ledger.consumeRetry('pwsh', retry)).toBe(true)
expect(ledger.consumeRetry('pwsh', retry)).toBe(false)
})
it('rejects non-denials, narrow escalation, and reordered ambiguity', () => {
const ledger = new GoodBuddySandboxRetryLedger()
const deniedArguments = {
description: 'Read an outside file',
command: 'cat /outside/file'
}
ledger.record(execution('success', 'bash', deniedArguments), {
isError: false,
value: {},
content: []
} as never)
expect(
ledger.consumeRetry('bash', {
command: 'cat /outside/file',
description: 'Read an outside file',
sandbox_permissions: 'danger-full-access',
justification: 'The requested file is outside the workspace.'
})
).toBe(false)
ledger.record(
execution('denial-2', 'bash', deniedArguments),
sandboxDenied as never
)
expect(
ledger.consumeRetry('bash', {
command: 'cat /outside/file',
description: 'Read an outside file',
sandbox_permissions: 'workspace-write',
justification: 'Retry in workspace-write.'
})
).toBe(false)
expect(
ledger.consumeRetry('bash', {
command: 'cat /outside/file',
description: 'Read an outside file',
sandbox_permissions: 'danger-full-access',
justification: 'The requested file is outside the workspace.'
})
).toBe(true)
})
})
File diff suppressed because it is too large Load Diff
@@ -1,6 +1,7 @@
import { describe, expect, it } from 'vitest'
import {
buildExplicitProfileRuntimeEnvironment,
buildControlledHarnessEnvironment,
buildRuntimeEnvironment
} from './process-environment'
@@ -89,4 +90,30 @@ describe('buildRuntimeEnvironment', () => {
NODE_TLS_REJECT_UNAUTHORIZED: '0'
})
})
it('builds a credential-free, telemetry-disabled Harness environment', () => {
expect(
buildControlledHarnessEnvironment('C:\\isolated-dsh', {
PATH: 'C:\\Tools',
TEMP: 'C:\\Temp',
OPENAI_API_KEY: 'must-not-leak',
DEEPSEEK_API_KEY: 'must-not-leak',
DSH_HOME: 'C:\\user-dsh',
NODE_OPTIONS: '--require malicious.js'
})
).toMatchObject({
PATH: 'C:\\Tools',
TEMP: 'C:\\Temp',
DSH_HOME: 'C:\\isolated-dsh',
DSH_TELEMETRY_DISABLED: '1',
DO_NOT_TRACK: '1',
OTEL_SDK_DISABLED: 'true'
})
expect(
buildControlledHarnessEnvironment('C:\\isolated-dsh', {
OPENAI_API_KEY: 'must-not-leak',
DEEPSEEK_API_KEY: 'must-not-leak'
})
).not.toHaveProperty('OPENAI_API_KEY')
})
})
+17
View File
@@ -92,3 +92,20 @@ export function buildExplicitProfileRuntimeEnvironment(
}
return environment
}
export function buildControlledHarnessEnvironment(
dshHome: string,
source: NodeJS.ProcessEnv = process.env
): NodeJS.ProcessEnv {
const environment = buildExplicitProfileRuntimeEnvironment(
{
DSH_HOME: dshHome,
DSH_TELEMETRY_DISABLED: '1',
...runtimePrivacyEnvironment
},
undefined,
source
)
delete environment.NODE_TLS_REJECT_UNAUTHORIZED
return environment
}
+53 -6
View File
@@ -36,7 +36,8 @@ describe('runtime discovery', () => {
expect(detection).toMatchObject({
available: true,
path: await realpath(process.execPath)
path: await realpath(process.execPath),
source: 'configured'
})
expect(detection.version).toMatch(/^\d+\.\d+\.\d+/u)
})
@@ -69,7 +70,8 @@ describe('runtime discovery', () => {
expect(detection).toMatchObject({
available: true,
path: await realpath(process.execPath)
path: await realpath(process.execPath),
source: 'automatic'
})
})
@@ -83,7 +85,8 @@ describe('runtime discovery', () => {
expect(detection).toMatchObject({
available: true,
path: await realpath(process.execPath)
path: await realpath(process.execPath),
source: 'configured'
})
expect(detection.detail).not.toContain('内置')
})
@@ -101,7 +104,8 @@ describe('runtime discovery', () => {
expect(detection).toMatchObject({
available: true,
path: await realpath(process.execPath)
path: await realpath(process.execPath),
source: 'bundled'
})
expect(detection.detail).toContain('内置')
})
@@ -115,15 +119,57 @@ describe('runtime discovery', () => {
binaryPath: '',
bundledPath: bundledScript,
bundledValidation: 'canonical-file',
bundledVersion: '1.5.47',
binaryNames: ['goodbuddy-runtime-that-does-not-exist'],
label: 'Script Runtime'
})
expect(detection).toMatchObject({
available: true,
path: await realpath(bundledScript)
path: await realpath(bundledScript),
version: '1.5.47',
source: 'bundled'
})
expect(detection.detail).toBe(
'内置 Script Runtime 1.5.47 已就绪'
)
})
it('accepts a controlled bundled harness when no custom host is configured', async () => {
const bundledScript = fileURLToPath(import.meta.url)
const detection = await detectRuntimeBinary({
binaryPath: '',
bundledPath: bundledScript,
bundledValidation: 'canonical-file',
bundledVersion: '0.1.0-rc.6',
binaryNames: [],
label: 'GoodBuddy DeepSeek Harness Host'
})
expect(detection).toMatchObject({
available: true,
path: await realpath(bundledScript),
version: '0.1.0-rc.6',
source: 'bundled'
})
expect(detection.detail).toContain('内置')
})
it('does not discover arbitrary DeepSeek Harness hosts from PATH', async () => {
process.env.PATH = dirname(process.execPath)
process.env.Path = dirname(process.execPath)
await expect(
detectRuntimeBinary({
binaryPath: '',
allowAutomaticDiscovery: false,
binaryNames: [basename(process.execPath)],
label: 'GoodBuddy DeepSeek Harness Host'
})
).resolves.toEqual({
available: false,
detail: expect.stringContaining('未自动检测到')
})
expect(detection.detail).toBe('内置 Script Runtime 已就绪')
})
it('returns both runtime detections without exposing PATH contents', async () => {
@@ -144,6 +190,7 @@ describe('runtime discovery', () => {
available: true,
path: await realpath(process.execPath)
})
expect(result.deepseekHarness.available).toBe(false)
expect(JSON.stringify(result)).not.toContain(privatePathValue)
})
})
+68 -34
View File
@@ -21,6 +21,8 @@ export type RuntimeBinaryDiscoveryInput = {
binaryPath: string
bundledPath?: string
bundledValidation?: 'execute' | 'canonical-file'
bundledVersion?: string
allowAutomaticDiscovery?: boolean
binaryNames: readonly string[]
label: string
}
@@ -246,13 +248,14 @@ function availableDetection(
label: string,
path: string,
version?: string,
bundled = false
source: 'bundled' | 'configured' | 'automatic' = 'automatic'
): RuntimeBinaryDetection {
return {
available: true,
path,
version,
detail: `${bundled ? '内置 ' : ''}${label}${
source,
detail: `${source === 'bundled' ? '内置 ' : ''}${label}${
version ? ` ${version}` : ''
} 已就绪`
}
@@ -264,6 +267,36 @@ export async function detectRuntimeBinary(
const configuredPath = input.binaryPath.trim()
let configuredPathProblem: 'relative' | 'invalid' | 'validation' | undefined
const detectBundled = async (): Promise<
RuntimeBinaryDetection | undefined
> => {
const bundledPath = input.bundledPath?.trim()
if (!bundledPath) {
return undefined
}
const canonicalPath = await canonicalFile(bundledPath)
if (!canonicalPath) {
return undefined
}
if (input.bundledValidation === 'canonical-file') {
return availableDetection(
input.label,
canonicalPath,
input.bundledVersion,
'bundled'
)
}
const validation = await validateVersion(canonicalPath)
return validation.valid
? availableDetection(
input.label,
canonicalPath,
validation.version,
'bundled'
)
: undefined
}
if (configuredPath) {
if (!isAbsolute(configuredPath)) {
configuredPathProblem = 'relative'
@@ -277,7 +310,8 @@ export async function detectRuntimeBinary(
return availableDetection(
input.label,
canonicalPath,
validation.version
validation.version,
'configured'
)
}
configuredPathProblem = 'validation'
@@ -285,47 +319,31 @@ export async function detectRuntimeBinary(
}
}
const bundledPath = input.bundledPath?.trim()
if (bundledPath) {
const canonicalPath = await canonicalFile(bundledPath)
if (canonicalPath) {
if (input.bundledValidation === 'canonical-file') {
return availableDetection(
input.label,
canonicalPath,
undefined,
true
)
const bundled = await detectBundled()
if (bundled) {
return bundled
}
let foundAutomaticCandidate = false
if (input.allowAutomaticDiscovery !== false) {
for (const candidate of automaticCandidates(input.binaryNames)) {
const canonicalPath = await canonicalFile(candidate)
if (!canonicalPath) {
continue
}
foundAutomaticCandidate = true
const validation = await validateVersion(canonicalPath)
if (validation.valid) {
return availableDetection(
input.label,
canonicalPath,
validation.version,
true
'automatic'
)
}
}
}
let foundAutomaticCandidate = false
for (const candidate of automaticCandidates(input.binaryNames)) {
const canonicalPath = await canonicalFile(candidate)
if (!canonicalPath) {
continue
}
foundAutomaticCandidate = true
const validation = await validateVersion(canonicalPath)
if (validation.valid) {
return availableDetection(
input.label,
canonicalPath,
validation.version
)
}
}
let detail: string
if (foundAutomaticCandidate || configuredPathProblem === 'validation') {
detail = `${input.label} 候选未通过 --version 安全验证`
@@ -349,9 +367,14 @@ export async function detectAgentRuntimes(input: {
bundledPaths?: {
opencode: string
continue: string
deepseekHarness: string
}
bundledVersions?: {
continue: string
deepseekHarness: string
}
}): Promise<AgentRuntimeDetection> {
const [opencode, continueRuntime] = await Promise.all([
const [opencode, continueRuntime, deepseekHarness] = await Promise.all([
detectRuntimeBinary({
binaryPath: input.opencodeBinaryPath,
bundledPath: input.bundledPaths?.opencode,
@@ -362,13 +385,24 @@ export async function detectAgentRuntimes(input: {
binaryPath: input.continueBinaryPath,
bundledPath: input.bundledPaths?.continue,
bundledValidation: 'canonical-file',
bundledVersion: input.bundledVersions?.continue,
binaryNames: ['cn'],
label: 'Continue CLI'
}),
detectRuntimeBinary({
binaryPath: '',
bundledPath: input.bundledPaths?.deepseekHarness,
bundledValidation: 'canonical-file',
bundledVersion: input.bundledVersions?.deepseekHarness,
allowAutomaticDiscovery: false,
binaryNames: [],
label: 'GoodBuddy DeepSeek Harness Host'
})
])
return {
opencode,
continue: continueRuntime
continue: continueRuntime,
deepseekHarness
}
}
+78 -1
View File
@@ -10,6 +10,7 @@ const defaultProfileId = '00000000-0000-4000-8000-000000000001'
const secondProfileId = '00000000-0000-4000-8000-000000000002'
const responsesProfileId = '00000000-0000-4000-8000-000000000003'
const imageProfileId = '00000000-0000-4000-8000-000000000004'
const deepseekProfileId = '00000000-0000-4000-8000-000000000005'
function settings(
overrides: Partial<ResolvedRuntimeSettings> = {}
@@ -62,6 +63,16 @@ function settings(
authentication: 'api-key',
imageGenerationQuality: 'auto',
apiKey: 'image-key'
},
{
id: deepseekProfileId,
name: 'DeepSeek',
baseUrl: 'https://api.deepseek.com',
modelName: 'deepseek-chat',
protocol: 'openai-chat-completions',
authentication: 'api-key',
imageGenerationQuality: 'auto',
apiKey: 'deepseek-key'
}
],
defaultModelProfileId: defaultProfileId,
@@ -154,13 +165,62 @@ describe('runtime selection', () => {
).toThrow('自动启动')
})
it('selects DeepSeek Harness only with an official compatible profile', () => {
const selected = applyRuntimeSelection(settings(), {
provider: 'deepseek-harness',
profileId: deepseekProfileId
})
expect(selected.target).toBe('deepseek-harness')
expect(selected.settings).toMatchObject({
provider: 'deepseek-harness',
deepseekHarnessModelProfile: { id: deepseekProfileId }
})
expect(() =>
applyRuntimeSelection(settings(), {
provider: 'deepseek-harness',
profileId: secondProfileId
})
).toThrow('api.deepseek.com')
})
it('keeps the controlled platform DeepSeek profile when selected without a profile ID', () => {
const base = settings()
const platformProfile = {
...base.modelProfiles[4]!,
id: 'goodbuddy-platform-deepseek',
name: '平台 DeepSeek',
modelName: 'deepseek-v4-flash'
}
const selected = applyRuntimeSelection(
settings({ deepseekHarnessModelProfile: platformProfile }),
{ provider: 'deepseek-harness' }
)
expect(selected.settings).toMatchObject({
provider: 'deepseek-harness',
deepseekHarnessModelProfile: {
id: 'goodbuddy-platform-deepseek',
modelName: 'deepseek-v4-flash'
}
})
})
it('resolves Agent Runtime backends from the global Runtime configuration', () => {
const base = settings()
const configured = settings({
opencodeModelProfile: base.modelProfiles[1],
continueModelProfile: base.modelProfiles[2]
continueModelProfile: base.modelProfiles[2],
deepseekHarnessModelProfile: base.modelProfiles[4]
})
expect(
resolveConfiguredAgentRuntimeSelection(configured, {
provider: 'deepseek-harness'
})
).toEqual({
provider: 'deepseek-harness',
profileId: deepseekProfileId
})
expect(
resolveConfiguredAgentRuntimeSelection(configured, {
provider: 'opencode',
@@ -189,6 +249,23 @@ describe('runtime selection', () => {
})
})
it('keeps the controlled platform DeepSeek source profile-free across configured selection repair', () => {
const base = settings()
const configured = settings({
deepseekHarnessModelProfile: {
...base.modelProfiles[4]!,
id: 'goodbuddy-platform-deepseek',
name: '平台 DeepSeek'
}
})
expect(
resolveConfiguredAgentRuntimeSelection(configured, {
provider: 'deepseek-harness'
})
).toEqual({ provider: 'deepseek-harness' })
})
it('routes legacy automatic settings through local OpenCode when the Server is blank', () => {
expect(getConfiguredRuntimeTarget(settings())).toBe('opencode')
expect(
+43 -5
View File
@@ -1,11 +1,18 @@
import { isAgentRuntimeModelProtocol } from '../../shared/contracts'
import {
isAgentRuntimeModelProtocol,
isDeepSeekHarnessModelProfile
} from '../../shared/contracts'
import type { AgentRuntimeSelection } from '../../shared/runtime-selection-contracts'
import type {
ResolvedModelProfile,
ResolvedRuntimeSettings
} from '../runtime-settings-store'
export type SelectedRuntimeTarget = 'model' | 'opencode' | 'continue'
export type SelectedRuntimeTarget =
| 'model'
| 'opencode'
| 'continue'
| 'deepseek-harness'
function requireProfile(
settings: ResolvedRuntimeSettings,
@@ -26,6 +33,9 @@ export function getConfiguredRuntimeTarget(
if (settings.provider === 'continue') {
return 'continue'
}
if (settings.provider === 'deepseek-harness') {
return 'deepseek-harness'
}
if (
settings.provider === 'opencode' ||
settings.provider === 'auto'
@@ -41,17 +51,24 @@ export function resolveConfiguredAgentRuntimeSelection(
): AgentRuntimeSelection {
if (
selection.provider !== 'opencode' &&
selection.provider !== 'continue'
selection.provider !== 'continue' &&
selection.provider !== 'deepseek-harness'
) {
return selection
}
const profile =
selection.provider === 'opencode'
? settings.opencodeModelProfile
: settings.continueModelProfile
: selection.provider === 'continue'
? settings.continueModelProfile
: settings.deepseekHarnessModelProfile
return {
provider: selection.provider,
...(profile ? { profileId: profile.id } : {})
...(profile && settings.modelProfiles.some(
(candidate) => candidate.id === profile.id
)
? { profileId: profile.id }
: {})
}
}
@@ -114,6 +131,27 @@ export function applyRuntimeSelection(
}
}
if (selection.provider === 'deepseek-harness') {
const selectedProfile =
profile ?? settings.deepseekHarnessModelProfile
if (
selectedProfile &&
!isDeepSeekHarnessModelProfile(selectedProfile)
) {
throw new Error(
'DeepSeek Harness 独立模型连接仅支持 api.deepseek.com 的 OpenAI Chat Completions 协议'
)
}
return {
target: 'deepseek-harness',
settings: {
...settings,
provider: 'deepseek-harness',
deepseekHarnessModelProfile: selectedProfile
}
}
}
if (
profile &&
!isAgentRuntimeModelProtocol(profile.protocol)
+1 -1
View File
@@ -32,7 +32,7 @@ export type RuntimeModelUsageEvent = {
requestId: string
type: 'model-usage'
callId: string
runtime: 'model' | 'continue' | 'opencode'
runtime: 'model' | 'continue' | 'opencode' | 'deepseek-harness'
provider: string
model: string
inputTokens: number
@@ -63,6 +63,13 @@ describe('bundled skills', () => {
expect(snapshot.skills.map((skill) => skill.id)).toContain(
'product-marketing'
)
expect(snapshot.skills).toContainEqual(
expect.objectContaining({
id: 'web-3d-game',
name: 'Web 3D Game',
assignments: expect.arrayContaining(['deepseek-harness'])
})
)
})
it('injects every enabled bundled skill with its resolved directory', async () => {
@@ -76,4 +83,19 @@ describe('bundled skills', () => {
expect(instructions).toContain(join(builtinSkillsRoot, skill.id))
}
})
it('exposes the 3D game Skill as a native Harness package', async () => {
const service = await createService()
await expect(
service.getRuntimeSkillContext('deepseek-harness')
).resolves.toMatchObject({
packages: expect.arrayContaining([
{
id: 'web-3d-game',
directory: join(builtinSkillsRoot, 'web-3d-game')
}
])
})
})
})
@@ -246,7 +246,12 @@ describe('CapabilityService', () => {
id: 'document-writing',
source: 'builtin',
enabled: true,
assignments: ['model', 'opencode', 'continue']
assignments: [
'model',
'opencode',
'continue',
'deepseek-harness'
]
}
]
})
@@ -625,9 +630,31 @@ describe('CapabilityService', () => {
})
})
it('rejects MCP assignments to Agent Runtimes', async () => {
it('allows Harness MCP assignment and rejects unsupported Agent Runtimes', async () => {
const { service } = await createService()
await expect(
service.saveMcpServer(undefined, {
name: 'Harness MCP',
description: '',
enabled: true,
allowDynamicTools: false,
assignments: ['deepseek-harness'],
secret: { action: 'keep' },
transport: 'stdio',
command: 'node',
args: ['server.js']
})
).resolves.toMatchObject({
mcpServers: [
expect.objectContaining({
assignments: ['deepseek-harness']
})
]
})
await expect(
service.getResolvedMcpServers('deepseek-harness')
).resolves.toHaveLength(1)
await expect(
service.saveMcpServer(undefined, {
name: 'Agent MCP',
@@ -640,7 +667,7 @@ describe('CapabilityService', () => {
command: 'node',
args: ['server.js']
})
).rejects.toThrow('只能分配给直连模型')
).rejects.toThrow('只能分配给直连模型或 DeepSeek Harness')
})
it('migrates legacy OpenCode MCP assignments to the direct model', async () => {
+13 -4
View File
@@ -269,7 +269,12 @@ function emptyStoredCapabilities(
function defaultSkillState(): z.infer<typeof skillStateSchema> {
return {
enabled: true,
assignments: ['model', 'opencode', 'continue']
assignments: [
'model',
'opencode',
'continue',
'deepseek-harness'
]
}
}
@@ -1606,10 +1611,14 @@ export class CapabilityService {
const value = mcpServerInputSchema.parse(input)
if (
value.assignments.some(
(assignment) => assignment !== 'model'
(assignment) =>
assignment !== 'model' &&
assignment !== 'deepseek-harness'
)
) {
throw new Error('当前版本的 MCP Server 只能分配给直连模型')
throw new Error(
'当前版本的 MCP Server 只能分配给直连模型或 DeepSeek Harness'
)
}
const state = await this.load()
const id = serverId ? mcpServerIdSchema.parse(serverId) : randomUUID()
@@ -1809,7 +1818,7 @@ export class CapabilityService {
async getResolvedMcpServers(
target: RuntimeTarget
): Promise<ResolvedMcpServer[]> {
if (target !== 'model') {
if (target !== 'model' && target !== 'deepseek-harness') {
return []
}
const state = await this.load()
+112
View File
@@ -0,0 +1,112 @@
import {
DEEPSEEK_HARNESS_CONTROL_PROTOCOL,
DEEPSEEK_HARNESS_CONTROL_VERSION,
parseHarnessControlMessage,
type DeepSeekHarnessControlMessage
} from './agent/deepseek-harness-utility-launcher'
import { createDeepSeekHarnessHostTransport } from './agent/deepseek-harness-utility-transport'
import {
createBoundedNdJsonStream,
ControlledHarnessHostStartupError,
installHarnessDiagnosticGuard,
startControlledDeepSeekHarnessHost,
type ControlledHarnessHost
} from './deepseek-harness-host'
const parentPort = process.parentPort
const restoreDiagnostics = installHarnessDiagnosticGuard()
// The Windows ACL sandbox launches its JavaScript runner through
// `process.execPath`. Inside an Electron UtilityProcess that path is Electron,
// so descendants must opt into Electron's supported Node execution mode.
if (process.platform === 'win32') {
process.env.ELECTRON_RUN_AS_NODE = '1'
}
let host: ControlledHarnessHost | undefined
let transport:
| ReturnType<typeof createDeepSeekHarnessHostTransport>
| undefined
let starting = false
let closed = false
function post(message: DeepSeekHarnessControlMessage): void {
if (!closed) {
parentPort.postMessage(message)
}
}
async function close(): Promise<void> {
if (closed) {
return
}
closed = true
await host?.dispose().catch(() => undefined)
transport?.dispose()
restoreDiagnostics()
}
function fatal(code: string): void {
post({
protocol: DEEPSEEK_HARNESS_CONTROL_PROTOCOL,
version: DEEPSEEK_HARNESS_CONTROL_VERSION,
type: 'fatal',
code
})
void close().finally(() => {
process.exitCode = 1
})
}
parentPort.on('message', (event) => {
const message = parseHarnessControlMessage(event.data)
if (!message) {
// Once the byte transport is installed, non-control messages belong to
// that transport's listener on the shared UtilityProcess port.
if (transport) {
return
}
fatal('INVALID_START')
return
}
if (message.type !== 'start') {
fatal('INVALID_START')
return
}
if (starting || host || closed) {
fatal('DUPLICATE_START')
return
}
starting = true
transport = createDeepSeekHarnessHostTransport(parentPort)
void startControlledDeepSeekHarnessHost({
...message.config,
stream: createBoundedNdJsonStream(
transport.stdout,
transport.stdin,
message.config.maxFrameBytes
)
})
.then((startedHost) => {
host = startedHost
starting = false
post({
protocol: DEEPSEEK_HARNESS_CONTROL_PROTOCOL,
version: DEEPSEEK_HARNESS_CONTROL_VERSION,
type: 'ready'
})
})
.catch((error: unknown) => {
transport?.dispose()
fatal(
error instanceof ControlledHarnessHostStartupError
? error.code
: 'HOST_START_FAILED'
)
})
})
process.once('disconnect', () => {
void close()
})
process.once('SIGTERM', () => {
void close()
})
+325
View File
@@ -0,0 +1,325 @@
import { describe, expect, it } from 'vitest'
import {
createBoundedNdJsonStream,
installHarnessDiagnosticGuard,
startControlledDeepSeekHarnessHost
} from './deepseek-harness-host'
import { vi } from 'vitest'
import { mkdir, mkdtemp, realpath, writeFile } from 'node:fs/promises'
import type {
Agent,
CreateAgentOptions
} from '@deepseek-ai/dsh-agent'
import { GOODBUDDY_HARNESS_MAX_STEP_TOKENS } from './agent/goodbuddy-harness-control-plane'
import { tmpdir } from 'node:os'
import { basename, join } from 'node:path'
const expectedSandbox =
process.platform === 'win32'
? { provider: 'windows-acl', enforcement: 'partial' as const }
: process.platform === 'darwin'
? { provider: 'seatbelt', enforcement: 'full' as const }
: { provider: 'local-linux', enforcement: 'full' as const }
async function readAllMessages(
readable: ReadableStream<unknown>
): Promise<unknown[]> {
const values: unknown[] = []
for await (const value of readable) {
values.push(value)
}
return values
}
describe('controlled DeepSeek Harness host', () => {
it('rejects unsupported endpoint protocols before Cordis starts', async () => {
await expect(
startControlledDeepSeekHarnessHost({
workspace: 'C:\\workspace',
baseUrl: 'file:///private/config',
api: 'openai-completions',
provider: 'goodbuddy',
model: 'deepseek-test',
harnessVersion: '0.1.0-rc.6',
sandbox: { provider: 'test', enforcement: 'full' },
credentialRefs: ['GOODBUDDY_API_KEY'],
dshHome: 'C:\\controlled-dsh-home',
skillPackages: []
})
).rejects.toThrow('trusted HTTPS DeepSeek endpoint')
})
it('suppresses console payloads instead of contaminating stdout', () => {
const restore = installHarnessDiagnosticGuard()
const originalWrite = process.stderr.write
const writes: string[] = []
process.stderr.write = ((value: string | Uint8Array) => {
writes.push(String(value))
return true
}) as typeof process.stderr.write
try {
console.log('prompt and secret must not reach protocol stdout')
expect(writes.join('')).toBe(
'DeepSeek Harness diagnostic suppressed\n'
)
expect(writes.join('')).not.toContain('secret')
} finally {
process.stderr.write = originalWrite
restore()
}
})
it('verifies the real local sandbox before advertising capabilities', async () => {
const root = await realpath(
await mkdtemp(join(tmpdir(), 'goodbuddy-harness-host-'))
)
const inbound = new TransformStream<
Record<string, unknown>,
Record<string, unknown>
>()
const outbound = new TransformStream<
Record<string, unknown>,
Record<string, unknown>
>()
const host = await startControlledDeepSeekHarnessHost({
workspace: root,
dshHome: root,
baseUrl: 'https://api.deepseek.com',
api: 'openai-completions',
provider: 'goodbuddy',
model: 'deepseek-test',
harnessVersion: '0.1.0-rc.6',
sandbox: expectedSandbox,
credentialRefs: ['GOODBUDDY_API_KEY'],
skillPackages: [],
stream: {
readable: inbound.readable,
writable: outbound.writable
} as never
})
await host.dispose()
})
it('canonicalizes workspace aliases before binding the host', async () => {
const root = await realpath(
await mkdtemp(join(tmpdir(), 'goodbuddy-harness-alias-'))
)
const alias = join(root, '..', basename(root))
const inbound = new TransformStream<
Record<string, unknown>,
Record<string, unknown>
>()
const outbound = new TransformStream<
Record<string, unknown>,
Record<string, unknown>
>()
const host = await startControlledDeepSeekHarnessHost({
workspace: alias,
dshHome: alias,
baseUrl: 'https://api.deepseek.com',
api: 'openai-completions',
provider: 'goodbuddy',
model: 'deepseek-test',
harnessVersion: '0.1.0-rc.6',
sandbox: expectedSandbox,
credentialRefs: ['GOODBUDDY_API_KEY'],
skillPackages: [],
stream: {
readable: inbound.readable,
writable: outbound.writable
} as never
})
await host.dispose()
})
it('loads only explicitly supplied Skill packages into a session scope', async () => {
const root = await realpath(
await mkdtemp(join(tmpdir(), 'goodbuddy-harness-skill-'))
)
const skillDirectory = join(root, 'web-3d-game')
await mkdir(skillDirectory)
await writeFile(
join(skillDirectory, 'SKILL.md'),
[
'---',
'name: web-3d-game',
'description: Build a playable browser 3D game.',
'---',
'',
'# Web 3D game',
'',
'Create and validate a playable project.'
].join('\n'),
'utf8'
)
const inbound = new TransformStream<
Record<string, unknown>,
Record<string, unknown>
>()
const outbound = new TransformStream<
Record<string, unknown>,
Record<string, unknown>
>()
const host = await startControlledDeepSeekHarnessHost({
workspace: root,
dshHome: root,
baseUrl: 'https://api.deepseek.com',
api: 'openai-completions',
provider: 'goodbuddy',
model: 'deepseek-test',
harnessVersion: '0.1.0-rc.6',
sandbox: expectedSandbox,
credentialRefs: ['GOODBUDDY_API_KEY'],
skillPackages: [
{ id: 'web-3d-game', directory: skillDirectory }
],
stream: {
readable: inbound.readable,
writable: outbound.writable
} as never
})
let createdContext: typeof host.context | undefined
let createdAgent: Agent | undefined
const create = vi
.spyOn(host.context.agents, 'create')
.mockImplementation(async (options: CreateAgentOptions) => {
const agentContext = host.context.extend({
isolate: ['skills', 'tools']
})
createdContext = agentContext
await options.setup?.(agentContext)
const agent = {
options: options.agentOptions ?? {},
session: {
id: options.sessionId,
header: { cwd: options.meta?.cwd ?? root },
events: [],
append: vi.fn()
},
ctx: agentContext,
cancel: vi.fn()
}
createdAgent = agent as never
return {
agent,
dispose: async () => {
await agentContext.fiber.dispose()
}
} as never
})
const api = (
host.controlPlane as unknown as {
createAgentApi(): {
newSession(params: {
cwd: string
mcpServers: never[]
}): Promise<{ sessionId: string }>
}
}
).createAgentApi()
const session = await api.newSession({
cwd: root,
mcpServers: []
})
expect(session.sessionId).toBeTruthy()
expect(
(
await createdContext!.skills.list({
cwd: root,
scope: createdAgent
})
).map((skill) => skill.name)
).toEqual(['web-3d-game'])
expect(
createdContext!.tools
.schemas(createdAgent)
.map((tool) => tool.name)
).toContain('skill')
const loadedSkill = await createdContext!.tools.execute({
callId: 'skill-call',
name: 'skill',
arguments: { name: 'web-3d-game' },
agent: createdAgent,
signal: new AbortController().signal
} as never)
expect(loadedSkill).toMatchObject({
isError: false,
value: {
name: 'web-3d-game',
content: expect.stringContaining(
'Create and validate a playable project.'
)
}
})
expect(create).toHaveBeenCalledTimes(1)
expect(create).toHaveBeenCalledWith(
expect.objectContaining({
agentOptions: {
provider: 'goodbuddy',
model: 'deepseek-test',
maxTokens: GOODBUDDY_HARNESS_MAX_STEP_TOKENS
}
})
)
const assembly = await createdContext!.systemPrompt.assemble({
agent: createdAgent,
scope: createdAgent
})
expect(
assembly.sections.find(
(section) =>
section.name === 'goodbuddy:controlled-execution'
)?.text
).toContain('create or update the requested workspace files promptly')
await host.dispose()
})
it('frames fragmented and coalesced ACP messages individually', async () => {
const inbound = new TransformStream<Uint8Array, Uint8Array>()
const outbound = new TransformStream<Uint8Array, Uint8Array>()
const stream = createBoundedNdJsonStream(
outbound.writable,
inbound.readable,
24
)
const reading = readAllMessages(stream.readable)
const writer = inbound.writable.getWriter()
const encoder = new TextEncoder()
await writer.write(encoder.encode('{"text":"你'))
await writer.write(
encoder.encode('好"}\n{"value":"1234567890"}\n')
)
await writer.close()
await expect(reading).resolves.toEqual([
{ text: '你好' },
{ value: '1234567890' }
])
})
it('rejects oversized ACP frames at EOF in both directions', async () => {
const inbound = new TransformStream<Uint8Array, Uint8Array>()
const outbound = new TransformStream<Uint8Array, Uint8Array>()
const stream = createBoundedNdJsonStream(
outbound.writable,
inbound.readable,
8
)
const reading = readAllMessages(stream.readable)
const inputWriter = inbound.writable.getWriter()
await inputWriter.write(
new TextEncoder().encode('{"value":"too large"}')
)
await inputWriter.close()
await expect(reading).rejects.toThrow('input frame exceeds')
const outputWriter = stream.writable.getWriter()
await expect(
outputWriter.write({ value: 'too large' } as never)
).rejects.toThrow('output frame exceeds')
})
})
+580
View File
@@ -0,0 +1,580 @@
import { Context, type Fiber } from '@deepseek-ai/cordis'
import { readFile, realpath, stat } from 'node:fs/promises'
import { isAbsolute, join } from 'node:path'
import { parse as parseYaml } from 'yaml'
import AgentRegistry from '@deepseek-ai/dsh-agent'
import AgentLoop from '@deepseek-ai/dsh-agent-loop'
import SandboxedBash from '@deepseek-ai/dsh-bash-sandbox'
import SandboxedPwsh from '@deepseek-ai/dsh-pwsh-sandbox'
import SandboxedFileSystem from '@deepseek-ai/dsh-fs-sandbox'
import LlmRuntime from '@deepseek-ai/dsh-llm'
import * as PiAiLlm from '@deepseek-ai/dsh-llm-pi-ai'
import ApprovalService from '@deepseek-ai/dsh-user-approval'
import LocalSandbox from '@deepseek-ai/dsh-sandbox-local'
import SandboxPolicy from '@deepseek-ai/dsh-sandbox-policy'
import SessionStore from '@deepseek-ai/dsh-session'
import SkillRegistry from '@deepseek-ai/dsh-skill'
import LocalSubprocess from '@deepseek-ai/dsh-subprocess-local'
import SystemPrompt from '@deepseek-ai/dsh-system-prompt'
import TokenMeter from '@deepseek-ai/dsh-token-meter'
import ToolRuntime from '@deepseek-ai/dsh-tools'
import * as ToolBash from '@deepseek-ai/dsh-tool-bash'
import * as ToolFs from '@deepseek-ai/dsh-tool-fs'
import * as ToolPwsh from '@deepseek-ai/dsh-tool-pwsh'
import * as ShellEnv from '@deepseek-ai/dsh-shell-env'
import {
GoodBuddyCredentialProvider,
GoodBuddyHarnessControlPlane,
createBoundedAcpStream,
type GoodBuddyHarnessControlConfig
} from './agent/goodbuddy-harness-control-plane'
import type { Stream } from '@agentclientprotocol/sdk'
import type { SandboxEnforcement } from '@deepseek-ai/dsh-sandbox'
const DEFAULT_MAX_FRAME_BYTES = 1024 * 1024
const MAX_DIAGNOSTIC_BYTES = 64 * 1024
export type ControlledHarnessHostConfig = Omit<
GoodBuddyHarnessControlConfig,
'stream' | 'skills'
> & {
workspace: string
baseUrl: string
api: 'openai-completions'
maxFrameBytes?: number
stream?: Stream
dshHome: string
skillPackages: readonly {
id: string
directory: string
}[]
}
export type ControlledHarnessHost = {
readonly context: Context
readonly controlPlane: GoodBuddyHarnessControlPlane
dispose(): Promise<void>
}
export type ControlledHarnessHostStartupCode =
| 'HOST_PLUGIN_GRAPH_FAILED'
| 'HOST_SANDBOX_CONFIGURATION_FAILED'
| 'HOST_SANDBOX_EXECUTION_FAILED'
| 'HOST_SANDBOX_PROBE_ABORTED'
| 'HOST_SANDBOX_PROBE_EXIT_FAILED'
| 'HOST_SANDBOX_PROBE_RUNNER_FAILED'
| 'HOST_SANDBOX_PROBE_TIMED_OUT'
| 'HOST_CONTROL_PLANE_FAILED'
export class ControlledHarnessHostStartupError extends Error {
constructor(
readonly code: ControlledHarnessHostStartupCode,
options?: ErrorOptions
) {
super(code, options)
this.name = 'ControlledHarnessHostStartupError'
}
}
async function verifySandboxExecution(
ctx: Context,
expected: GoodBuddyHarnessControlConfig['sandbox'],
workspace: string
): Promise<void> {
const result = await ctx.shell.run(
ctx.shell.resolve({
command:
process.platform === 'win32'
? 'Write-Output goodbuddy-sandbox-probe'
: 'printf goodbuddy-sandbox-probe',
workdir: workspace,
timeoutMs: 10_000,
stdoutMaxBytes: 1_024,
sandboxPolicy: {
mode: 'read-only',
workspaceRoot: workspace
}
})
)
if (
result.sandbox?.enforcement !== expected.enforcement
) {
throw new Error(
'Controlled Harness sandbox execution probe failed'
)
}
if (result.timedOut) {
throw new ControlledHarnessHostStartupError(
'HOST_SANDBOX_PROBE_TIMED_OUT'
)
}
if (result.aborted) {
throw new ControlledHarnessHostStartupError(
'HOST_SANDBOX_PROBE_ABORTED'
)
}
if (result.sandbox?.runnerFailed) {
throw new ControlledHarnessHostStartupError(
'HOST_SANDBOX_PROBE_RUNNER_FAILED'
)
}
if (result.exitCode !== 0) {
throw new ControlledHarnessHostStartupError(
'HOST_SANDBOX_PROBE_EXIT_FAILED'
)
}
}
type PluginSpec = {
plugin: Parameters<Context['plugin']>[0]
config?: unknown
}
function validateHostConfig(
config: ControlledHarnessHostConfig
): void {
const endpoint = URL.canParse(config.baseUrl)
? new URL(config.baseUrl)
: undefined
if (
config.api !== 'openai-completions' ||
!endpoint ||
endpoint.protocol !== 'https:' ||
endpoint.hostname.toLowerCase() !== 'api.deepseek.com' ||
endpoint.username ||
endpoint.password
) {
throw new Error(
'Controlled Harness requires the trusted HTTPS DeepSeek endpoint'
)
}
if (!config.credentialRefs.length) {
throw new Error(
'Controlled Harness requires a Main-side credential reference'
)
}
if (!isAbsolute(config.workspace) || !isAbsolute(config.dshHome)) {
throw new Error(
'Controlled Harness requires absolute workspace and home paths'
)
}
}
async function canonicalizeHostConfig(
config: ControlledHarnessHostConfig
): Promise<ControlledHarnessHostConfig> {
const [workspace, dshHome] = await Promise.all([
realpath(config.workspace),
realpath(config.dshHome)
])
const [workspaceMetadata, homeMetadata] = await Promise.all([
stat(workspace),
stat(dshHome)
])
if (!workspaceMetadata.isDirectory() || !homeMetadata.isDirectory()) {
throw new Error(
'Controlled Harness workspace and home must be directories'
)
}
const skillPackages = await Promise.all(
config.skillPackages.map(async (skill) => {
const directory = await realpath(skill.directory)
const metadata = await stat(directory)
if (!metadata.isDirectory()) {
throw new Error(
'Controlled Harness Skill path must be a directory'
)
}
return { ...skill, directory }
})
)
return { ...config, workspace, dshHome, skillPackages }
}
async function loadControlledSkills(
skillPackages: ControlledHarnessHostConfig['skillPackages']
): Promise<GoodBuddyHarnessControlConfig['skills']> {
return Promise.all(
skillPackages.map(async (skill) => {
const manifest = await readFile(
join(skill.directory, 'SKILL.md'),
'utf8'
)
if (Buffer.byteLength(manifest, 'utf8') > 2 * 1024 * 1024) {
throw new Error('Controlled Harness Skill is too large')
}
const match =
/^---\r?\n([\s\S]*?)\r?\n---\r?\n([\s\S]+)$/u.exec(
manifest
)
if (!match?.[1] || !match[2]?.trim()) {
throw new Error('Controlled Harness Skill manifest is invalid')
}
const metadata = parseYaml(match[1]) as Record<string, unknown>
const name =
typeof metadata.id === 'string'
? metadata.id
: metadata.name
const description = metadata.description
if (
name !== skill.id ||
typeof name !== 'string' ||
!/^[a-z0-9]+(?:-[a-z0-9]+)*$/u.test(name) ||
typeof description !== 'string'
) {
throw new Error('Controlled Harness Skill metadata is invalid')
}
return {
name,
description: description
.replace(/\s+/gu, ' ')
.trim()
.slice(0, 500),
content: match[2].trim(),
directory: skill.directory
}
})
)
}
function sandboxProviderName(): string {
return process.platform === 'win32'
? 'windows-acl'
: process.platform === 'darwin'
? 'seatbelt'
: 'local-linux'
}
function verifySandbox(
sandbox: {
confine(
argv: readonly string[],
policy: {
mode: 'read-only'
workspaceRoot: string
}
): {
enforcement: SandboxEnforcement
}
},
config: ControlledHarnessHostConfig
): GoodBuddyHarnessControlConfig['sandbox'] {
const expectedEnforcement: SandboxEnforcement =
process.platform === 'win32' ? 'partial' : 'full'
const probe = sandbox.confine(
process.platform === 'win32'
? ['cmd.exe', '/d', '/s', '/c', 'exit 0']
: ['/usr/bin/env', 'true'],
{
mode: 'read-only',
workspaceRoot: config.workspace
}
)
if (probe.enforcement !== expectedEnforcement) {
throw new Error(
'Controlled Harness sandbox enforcement probe returned an unexpected result'
)
}
if (config.sandbox.enforcement !== probe.enforcement) {
throw new Error(
'Controlled Harness sandbox capability does not match the verified provider'
)
}
return {
provider: sandboxProviderName(),
enforcement: probe.enforcement
}
}
/**
* Boots a fixed, programmatic Cordis graph. It never imports app-boot, a
* profile loader, settings-file, local credentials, persistence, telemetry,
* web, HMR, marketplace/plugin discovery, direct MCP clients, jobs,
* subagents, hooks, or workflow packages. The control plane registers only
* Main-selected Skill snapshots and Main-mediated MCP tool proxies.
*/
export async function startControlledDeepSeekHarnessHost(
input: ControlledHarnessHostConfig
): Promise<ControlledHarnessHost> {
validateHostConfig(input)
const config = await canonicalizeHostConfig(input)
const skills = await loadControlledSkills(config.skillPackages)
process.env.DSH_TELEMETRY_DISABLED = '1'
const ctx = new Context()
const specs: PluginSpec[] = [
{ plugin: LlmRuntime },
{ plugin: SessionStore },
{ plugin: SkillRegistry },
{
plugin: SystemPrompt,
config: {
persona: '',
includeHarnessIdentity: false,
includeRuntimeContext: true
}
},
{ plugin: ToolRuntime, config: { mode: 'native' } },
{ plugin: AgentRegistry },
{
plugin: GoodBuddyCredentialProvider,
config: new Set(config.credentialRefs)
},
{
plugin: PiAiLlm,
config: {
providers: {
[config.provider]: {
apiKeyEnv: config.credentialRefs[0],
api: config.api,
baseURL: config.baseUrl,
models: [{ id: config.model, input: ['text'] }]
}
}
}
},
{
plugin: SandboxPolicy,
config: {
mode: 'read-only',
workspaceRoot: config.workspace
}
},
{ plugin: ApprovalService, config: { policy: 'never' } },
{ plugin: LocalSubprocess },
{ plugin: LocalSandbox },
{ plugin: SandboxedFileSystem, config: { cwd: config.workspace } },
{ plugin: ShellEnv, config: { dshHome: config.dshHome } },
{
plugin:
process.platform === 'win32'
? SandboxedPwsh
: SandboxedBash,
config: { timeoutMs: 60_000 }
},
{ plugin: ToolFs },
{
plugin:
process.platform === 'win32' ? ToolPwsh : ToolBash,
config: { enableRunInBackground: false }
},
{ plugin: TokenMeter, config: {} },
{
plugin: AgentLoop,
config: { agents: [], maxParallelToolCalls: 10 }
}
]
const fibers: Fiber[] = []
let startupCode: ControlledHarnessHostStartupCode =
'HOST_PLUGIN_GRAPH_FAILED'
try {
for (const spec of specs) {
fibers.push(
ctx.plugin(
spec.plugin,
...(spec.config === undefined ? [] : [spec.config])
)
)
}
await Promise.all(fibers)
const credentialProvider = ctx.credentials
if (!(credentialProvider instanceof GoodBuddyCredentialProvider)) {
throw new Error(
'Controlled Harness credential provider failed to start'
)
}
startupCode = 'HOST_SANDBOX_CONFIGURATION_FAILED'
const verifiedSandbox = verifySandbox(ctx.sandbox, config)
startupCode = 'HOST_SANDBOX_EXECUTION_FAILED'
await verifySandboxExecution(
ctx,
verifiedSandbox,
config.workspace
)
startupCode = 'HOST_CONTROL_PLANE_FAILED'
const rawStream =
config.stream ??
createBoundedNdJsonStream(
stdoutStream(),
stdinStream(),
config.maxFrameBytes ?? DEFAULT_MAX_FRAME_BYTES
)
const controlPlane = new GoodBuddyHarnessControlPlane(ctx, {
...config,
skills,
sandbox: verifiedSandbox,
stream: createBoundedAcpStream(
rawStream,
config.maxFrameBytes ?? DEFAULT_MAX_FRAME_BYTES
)
})
controlPlane.bindCredentialProvider(credentialProvider)
controlPlane.start()
return {
context: ctx,
controlPlane,
async dispose() {
await controlPlane.dispose()
await ctx.fiber.dispose()
}
}
} catch (error) {
await ctx.fiber.dispose().catch(() => undefined)
if (error instanceof ControlledHarnessHostStartupError) {
throw error
}
throw new ControlledHarnessHostStartupError(startupCode, {
cause: error
})
}
}
export function createBoundedNdJsonStream(
output: WritableStream<Uint8Array>,
input: ReadableStream<Uint8Array>,
maxFrameBytes: number
): Stream {
const decoder = new TextDecoder('utf-8', { fatal: true })
const encoder = new TextEncoder()
return {
readable: new ReadableStream({
async start(controller) {
const reader = input.getReader()
let pending = ''
const emitCompleteFrames = (): void => {
let newline = pending.indexOf('\n')
while (newline >= 0) {
const line = pending.slice(0, newline).trim()
pending = pending.slice(newline + 1)
if (
line &&
Buffer.byteLength(line, 'utf8') > maxFrameBytes
) {
throw new Error('ACP input frame exceeds safety limit')
}
if (line) {
controller.enqueue(JSON.parse(line))
}
newline = pending.indexOf('\n')
}
if (Buffer.byteLength(pending, 'utf8') > maxFrameBytes) {
throw new Error('ACP input frame exceeds safety limit')
}
}
try {
while (true) {
const { value, done } = await reader.read()
if (done) {
pending += decoder.decode()
emitCompleteFrames()
break
}
pending += decoder.decode(value, { stream: true })
emitCompleteFrames()
}
const line = pending.trim()
if (line) {
if (Buffer.byteLength(line, 'utf8') > maxFrameBytes) {
throw new Error('ACP input frame exceeds safety limit')
}
controller.enqueue(JSON.parse(line))
}
controller.close()
} catch (error) {
controller.error(error)
} finally {
reader.releaseLock()
}
}
}),
writable: new WritableStream({
async write(message) {
const serialized = JSON.stringify(message)
if (
Buffer.byteLength(serialized, 'utf8') >
maxFrameBytes
) {
throw new Error('ACP output frame exceeds safety limit')
}
const bytes = encoder.encode(`${serialized}\n`)
const writer = output.getWriter()
try {
await writer.write(bytes)
} finally {
writer.releaseLock()
}
},
async close() {
const writer = output.getWriter()
try {
await writer.close()
} finally {
writer.releaseLock()
}
},
async abort(reason) {
await output.abort(reason)
}
})
}
}
function stdoutStream(): WritableStream<Uint8Array> {
return new WritableStream({
write(chunk) {
return new Promise<void>((resolve, reject) => {
process.stdout.write(chunk, (error) =>
error ? reject(error) : resolve()
)
})
}
})
}
function stdinStream(): ReadableStream<Uint8Array> {
return new ReadableStream({
start(controller) {
process.stdin.on('data', (chunk: Buffer) =>
controller.enqueue(new Uint8Array(chunk))
)
process.stdin.once('end', () => controller.close())
process.stdin.once('error', (error) =>
controller.error(error)
)
}
})
}
/**
* Keep diagnostics bounded and protocol-free. Call this in the utility entry
* before Cordis plugins start; no user content or secret is forwarded.
*/
export function installHarnessDiagnosticGuard(): () => void {
let bytes = 0
const original = {
log: console.log,
info: console.info,
warn: console.warn,
error: console.error,
debug: console.debug
}
const diagnostic = (): void => {
const line = 'DeepSeek Harness diagnostic suppressed\n'
const size = Buffer.byteLength(line)
if (bytes + size <= MAX_DIAGNOSTIC_BYTES) {
bytes += size
process.stderr.write(line)
}
}
console.log = diagnostic
console.info = diagnostic
console.warn = diagnostic
console.error = diagnostic
console.debug = diagnostic
return () => {
console.log = original.log
console.info = original.info
console.warn = original.warn
console.error = original.error
console.debug = original.debug
}
}
+58 -2
View File
@@ -10,8 +10,10 @@ import {
utilityProcess
} from 'electron'
import { homedir } from 'node:os'
import { mkdir } from 'node:fs/promises'
import { fileURLToPath } from 'node:url'
import { dirname, join } from 'node:path'
import spawn from 'cross-spawn'
import { ipcChannels } from '../shared/ipc-channels'
import {
createAgentRuntime,
@@ -75,6 +77,11 @@ import { DocumentOcrBroker } from './document-ocr-broker'
import { DocumentParsingService } from './document-parsing-service'
import { ReleaseNotesService } from './release-notes-service'
import { GoodBuddyConfigService } from './goodbuddy-config-service'
import {
createDeepSeekHarnessUtilityLauncher,
type DeepSeekHarnessFork
} from './agent/deepseek-harness-utility-launcher'
import { buildControlledHarnessEnvironment } from './agent/process-environment'
const shortcut = 'CommandOrControl+Shift+Space'
const mainModuleDirectory = dirname(fileURLToPath(import.meta.url))
@@ -209,6 +216,36 @@ const launchContinueHost: ContinueHostLauncher = (
return child
}
const forkDeepSeekHarness: DeepSeekHarnessFork = (
modulePath,
args,
options
) =>
utilityProcess.fork(modulePath, args, {
...options,
allowLoadingUnsignedLibraries: false,
disclaim: false
})
function terminateHarnessUtilityProcess(
child: ReturnType<DeepSeekHarnessFork>
): void {
if (process.platform === 'win32' && child.pid) {
const killer = spawn(
'taskkill.exe',
['/PID', String(child.pid), '/T', '/F'],
{
shell: false,
stdio: 'ignore',
windowsHide: true
}
)
killer.unref()
return
}
child.kill()
}
const launchWechatSidecar: WechatSidecarLauncher = () => {
const utilityChild = utilityProcess.fork(
join(mainModuleDirectory, 'wechat-sidecar.js'),
@@ -403,6 +440,24 @@ if (hasSingleInstanceLock) {
resourcesPath: process.resourcesPath,
packaged: app.isPackaged
})
const deepSeekHarnessHome = join(
app.getPath('userData'),
'deepseek-harness'
)
await mkdir(deepSeekHarnessHome, {
recursive: true,
mode: 0o700
})
const launchDeepSeekHarness =
createDeepSeekHarnessUtilityLauncher({
bundledHostPath: bundledRuntimePaths.deepseekHarness,
dshHome: deepSeekHarnessHome,
environment: buildControlledHarnessEnvironment(
deepSeekHarnessHome
),
fork: forkDeepSeekHarness,
terminateProcess: terminateHarnessUtilityProcess
})
knowledgeService = new KnowledgeService({
databasePath: join(app.getPath('userData'), 'knowledge.sqlite'),
managedRoot: join(app.getPath('userData'), 'knowledge'),
@@ -465,8 +520,8 @@ if (hasSingleInstanceLock) {
] =
await Promise.all([
capabilityService.getRuntimeSkillContext(target),
target === 'model'
? capabilityService.getResolvedMcpServers('model')
target === 'model' || target === 'deepseek-harness'
? capabilityService.getResolvedMcpServers(target)
: Promise.resolve([]),
target === 'model'
? capabilityService.getComputerCapabilityStatus(
@@ -487,6 +542,7 @@ if (hasSingleInstanceLock) {
),
bundledRuntimePaths,
continueHostLauncher: launchContinueHost,
deepseekHarnessLauncher: launchDeepSeekHarness,
browserService:
browserCapability?.enabled && browserCapability.supported
? browserService
+74 -1
View File
@@ -2935,6 +2935,78 @@ describe('registerIpcHandlers agent terminal state', () => {
}
)
it('keeps Ask fail-closed and auto-allows DeepSeek Harness Execute tools', async () => {
const receivedAuthorizers: unknown[] = []
const executeDecisions: string[] = []
const runtime = {
runtimeId: 'deepseek-harness',
capability: 'chat',
requiresToolApproval: false,
supportsToolExecution: true,
getStatus: vi.fn(),
dispose: vi.fn(),
async *run(
request: { requestId: string; workMode?: string },
_signal: AbortSignal,
authorize?: (request: {
scopeKey: string
title: string
description: string
}) => Promise<string>
) {
receivedAuthorizers.push(authorize)
if (request.workMode === 'execute') {
executeDecisions.push(
(await authorize?.({
scopeKey: 'deepseek-harness:write_file',
title: '写入文件',
description: '一次性沙箱升级'
})) ?? 'missing'
)
}
yield { requestId: request.requestId, type: 'done' }
}
}
const harness = createHarness(runtime)
harness.approvalBroker.request.mockResolvedValue('once')
for (const [index, workMode] of (
['ask', 'execute'] as const
).entries()) {
const requestId = `3f496642-f47d-4e0a-8944-a32c77b0d6e${index}`
harness.handler?.(trustedEvent(harness.webContents), {
requestId,
conversationId: `conversation-${index}`,
prompt: 'run the task',
workMode
})
await vi.waitFor(() =>
expect(
harness.assistantDatabase.updateTaskStatus
).toHaveBeenCalledWith(requestId, 'completed')
)
}
expect(receivedAuthorizers).toEqual([
expect.any(Function),
expect.any(Function)
])
expect(executeDecisions).toEqual(['once'])
await expect(
(
receivedAuthorizers[0] as (
request: Record<string, string>
) => Promise<string>
)({
scopeKey: 'deepseek-harness:write_file',
title: '写入文件',
description: 'must be denied'
})
).resolves.toBe('deny')
expect(harness.approvalBroker.request).not.toHaveBeenCalled()
await harness.dispose()
})
it.each(['model', 'opencode'] as const)(
'normalizes legacy interactive Plan requests to Ask for %s',
async (runtimeId) => {
@@ -3936,7 +4008,8 @@ describe('registerIpcHandlers agent terminal state', () => {
harness.getResolvedSettings.mockResolvedValue({
toolApproval: 'always',
subagentSmartRoutingEnabled: false,
continueModelProfile: { id: configuredProfileId }
continueModelProfile: { id: configuredProfileId },
modelProfiles: [{ id: configuredProfileId }]
})
vi.mocked(
harness.assistantDatabase.listProjects
+31 -6
View File
@@ -163,7 +163,11 @@ import {
import { resolveConfiguredAgentRuntimeSelection } from './agent/runtime-selection'
import { safeToolErrorDetail } from './agent/approval-summary'
import { ReasoningTagStreamParser } from './agent/reasoning-stream'
import type { BundledRuntimePaths } from './agent/bundled-runtimes'
import {
bundledContinueVersion,
bundledDeepSeekHarnessVersion,
type BundledRuntimePaths
} from './agent/bundled-runtimes'
import type { SelectedRuntimeResolver } from './agent/selected-runtime-manager'
import {
type MagicNotesCapabilityAccess,
@@ -1258,6 +1262,8 @@ export function registerIpcHandlers(
!agentRuntimeSelected
? (await settingsStore.getPolicySettings()).toolApproval
: undefined
const automaticHarnessRuntime =
requestRuntime.runtimeId === 'deepseek-harness'
const authorize: RuntimeAuthorizer = async (approvalRequest) => {
controller.signal.throwIfAborted()
if (schedule.workMode !== 'execute') {
@@ -1266,6 +1272,9 @@ export function registerIpcHandlers(
if (origin === 'delegation') {
return 'deny'
}
if (automaticHarnessRuntime) {
return 'once'
}
if (origin === 'channel') {
return channelToolPolicy === 'policy' ? 'deny' : 'once'
}
@@ -2456,16 +2465,26 @@ export function registerIpcHandlers(
throw error
}
}
const automaticHarnessRuntime =
selectedRuntime.runtimeId === 'deepseek-harness'
const executeToolPolicy =
request.workMode === 'execute' && !agentRuntimeSelected
? (await settingsStore.getPolicySettings()).toolApproval
: 'policy'
const authorize: RuntimeAuthorizer = async () => {
controller.signal.throwIfAborted()
return request.workMode === 'execute' &&
if (
request.workMode !== 'execute'
) {
return 'deny'
}
if (
automaticHarnessRuntime ||
executeToolPolicy !== 'policy'
? 'once'
: 'deny'
) {
return 'once'
}
return 'deny'
}
let smartRoute:
| ReturnType<typeof routeSubagent>
@@ -2771,7 +2790,11 @@ export function registerIpcHandlers(
return detectAgentRuntimes({
opencodeBinaryPath: settings.opencodeBinaryPath,
continueBinaryPath: settings.continueBinaryPath,
bundledPaths: bundledRuntimePaths
bundledPaths: bundledRuntimePaths,
bundledVersions: {
continue: bundledContinueVersion,
deepseekHarness: bundledDeepSeekHarnessVersion
}
})
}
)
@@ -2781,7 +2804,9 @@ export function registerIpcHandlers(
async (event, input: unknown): Promise<string | undefined> => {
assertTrustedSender(event, window)
const kind = runtimeFileSelectionKindSchema.parse(input)
const binary = kind.endsWith('Binary')
const binary =
kind === 'opencodeBinary' ||
kind === 'continueBinary'
const configRuntime =
kind === 'opencodeConfig'
? 'opencode'
+199 -5
View File
@@ -42,6 +42,7 @@ function settings(
continueBinaryPath: '',
continueConfigPath: '',
continueMode: 'chat',
deepseekHarnessModelSource: { kind: 'platform' },
runtimeSandboxMode: 'auto',
knowledgeEmbeddingEnabled: false,
knowledgeEmbeddingBaseUrl:
@@ -103,6 +104,199 @@ describe('RuntimeSettingsStore', () => {
})
})
it('migrates DeepSeek Harness to controlled platform mode and stores an official profile', async () => {
const { filePath, store } = await createStore()
await store.update(settings())
const versionFourteen = JSON.parse(
await readFile(filePath, 'utf8')
) as Record<string, unknown>
versionFourteen.version = 14
delete versionFourteen.deepseekHarnessModelSource
delete versionFourteen.deepseekHarnessBinaryPath
await writeFile(filePath, JSON.stringify(versionFourteen), 'utf8')
const migrated = new RuntimeSettingsStore(filePath, cipher, {})
await expect(migrated.getResolvedSettings()).resolves.toMatchObject({
deepseekHarnessModelProfile: undefined
})
const profileId = '00000000-0000-4000-8000-000000000044'
await migrated.update(
settings({
provider: 'deepseek-harness',
modelProfiles: [
{
id: profileId,
name: 'DeepSeek',
baseUrl: 'https://api.deepseek.com',
modelName: 'deepseek-chat',
protocol: 'openai-chat-completions',
authentication: 'api-key',
imageGenerationQuality: 'auto',
apiKey: { action: 'replace', value: 'deepseek-secret' }
}
],
defaultModelProfileId: profileId,
deepseekHarnessModelSource: { kind: 'profile', profileId }
})
)
await expect(migrated.getResolvedSettings()).resolves.toMatchObject({
provider: 'deepseek-harness',
deepseekHarnessModelProfile: {
id: profileId,
apiKey: 'deepseek-secret'
}
})
})
it('resolves a controlled platform DeepSeek profile without exposing its credential', async () => {
const apiKey = 'platform-deepseek-secret'
const { store } = await createStore({
GOODBUDDY_MODEL_API_KEY: apiKey,
GOODBUDDY_MODEL_BASE_URL: 'https://api.deepseek.com/',
GOODBUDDY_MODEL_NAME: 'deepseek-v4-flash'
})
await expect(store.getResolvedSettings()).resolves.toMatchObject({
modelProtocol: 'anthropic-messages',
deepseekHarnessModelProfile: {
id: 'goodbuddy-platform-deepseek',
name: '平台 DeepSeek',
baseUrl: 'https://api.deepseek.com/',
modelName: 'deepseek-v4-flash',
protocol: 'openai-chat-completions',
authentication: 'api-key',
supportsImageInput: false,
imageGenerationQuality: 'auto',
apiKey
}
})
const publicSettings = await store.getPublicSettings()
expect(JSON.stringify(publicSettings)).not.toContain(apiKey)
expect(publicSettings.modelProtocol).toBe('anthropic-messages')
})
it.each([
[
'a non-DeepSeek endpoint',
{
GOODBUDDY_MODEL_API_KEY: 'platform-key',
GOODBUDDY_MODEL_BASE_URL: 'https://deepseek.example',
GOODBUDDY_MODEL_NAME: 'deepseek-chat'
}
],
[
'an insecure DeepSeek endpoint',
{
GOODBUDDY_MODEL_API_KEY: 'platform-key',
GOODBUDDY_MODEL_BASE_URL: 'http://api.deepseek.com',
GOODBUDDY_MODEL_NAME: 'deepseek-chat'
}
],
[
'a DeepSeek endpoint path',
{
GOODBUDDY_MODEL_API_KEY: 'platform-key',
GOODBUDDY_MODEL_BASE_URL: 'https://api.deepseek.com/v1',
GOODBUDDY_MODEL_NAME: 'deepseek-chat'
}
],
[
'a missing API key',
{
GOODBUDDY_MODEL_BASE_URL: 'https://api.deepseek.com',
GOODBUDDY_MODEL_NAME: 'deepseek-chat'
}
]
])('does not resolve platform DeepSeek from %s', async (_, environment) => {
const { store } = await createStore(environment)
await expect(store.getResolvedSettings()).resolves.toMatchObject({
deepseekHarnessModelProfile: undefined
})
})
it('drops the legacy custom Harness Host path and ignores its environment override', async () => {
const { filePath, store } = await createStore()
await store.update(settings())
const versionFifteen = JSON.parse(
await readFile(filePath, 'utf8')
) as Record<string, unknown>
versionFifteen.version = 15
versionFifteen.deepseekHarnessBinaryPath =
'C:\\untrusted\\custom-harness.js'
await writeFile(filePath, JSON.stringify(versionFifteen), 'utf8')
const migrated = new RuntimeSettingsStore(filePath, cipher, {
GOODBUDDY_DEEPSEEK_HARNESS_BINARY:
'C:\\environment\\custom-harness.js'
})
const publicSettings = await migrated.getPublicSettings()
const resolvedSettings = await migrated.getResolvedSettings()
expect(publicSettings).not.toHaveProperty(
'deepseekHarnessBinaryPath'
)
expect(publicSettings.configured).not.toHaveProperty(
'deepseekHarnessBinaryPath'
)
expect(resolvedSettings).not.toHaveProperty(
'deepseekHarnessBinaryPath'
)
await migrated.update(settings())
const persisted = JSON.parse(
await readFile(filePath, 'utf8')
) as Record<string, unknown>
expect(persisted.version).toBe(16)
expect(persisted).not.toHaveProperty(
'deepseekHarnessBinaryPath'
)
})
it('rejects incompatible DeepSeek Harness model profiles', () => {
const profileId = '00000000-0000-4000-8000-000000000045'
expect(
runtimeSettingsInputSchema.safeParse(
settings({
modelProfiles: [
{
id: profileId,
name: 'Other compatible API',
baseUrl: 'https://other.example/v1',
modelName: 'deepseek-chat',
protocol: 'openai-chat-completions',
authentication: 'api-key',
imageGenerationQuality: 'auto',
apiKey: { action: 'keep' }
}
],
defaultModelProfileId: profileId,
deepseekHarnessModelSource: { kind: 'profile', profileId }
})
).success
).toBe(false)
expect(
runtimeSettingsInputSchema.safeParse(
settings({
modelProfiles: [
{
id: profileId,
name: 'DeepSeek without API key',
baseUrl: 'https://api.deepseek.com',
modelName: 'deepseek-chat',
protocol: 'openai-chat-completions',
authentication: 'none',
imageGenerationQuality: 'auto',
apiKey: { action: 'clear' }
}
],
defaultModelProfileId: profileId,
deepseekHarnessModelSource: { kind: 'profile', profileId }
})
).success
).toBe(false)
})
it('always enables bundled OpenCode when the Server address is blank', async () => {
const { filePath, store } = await createStore({
GOODBUDDY_OPENCODE_EMBEDDED: 'false'
@@ -331,7 +525,7 @@ describe('RuntimeSettingsStore', () => {
const persisted = JSON.parse(await readFile(filePath, 'utf8')) as {
version: number
}
expect(persisted.version).toBe(14)
expect(persisted.version).toBe(16)
})
it('migrates version 11 and removes the obsolete intranet toggle', async () => {
@@ -351,7 +545,7 @@ describe('RuntimeSettingsStore', () => {
version: number
intranetCompatibilityEnabled?: boolean
}
expect(persisted.version).toBe(14)
expect(persisted.version).toBe(16)
expect(persisted).not.toHaveProperty('intranetCompatibilityEnabled')
})
@@ -940,7 +1134,7 @@ describe('RuntimeSettingsStore', () => {
version: number
modelProfiles: Array<Record<string, unknown>>
}
expect(persisted.version).toBe(14)
expect(persisted.version).toBe(16)
expect(persisted.modelProfiles).toContainEqual(
expect.objectContaining({
id: imageId,
@@ -1186,7 +1380,7 @@ describe('RuntimeSettingsStore', () => {
unknown
>
expect(saved).toMatchObject({
version: 14,
version: 16,
provider: 'model',
continueBinaryPath: '',
continueMode: 'chat',
@@ -1465,7 +1659,7 @@ describe('RuntimeSettingsStore', () => {
version: number
modelProfiles: Array<Record<string, unknown>>
}
expect(persisted.version).toBe(14)
expect(persisted.version).toBe(16)
expect(persisted.modelProfiles[0]).not.toHaveProperty('credential')
})
+241 -98
View File
@@ -11,6 +11,7 @@ import {
defaultRuntimeSettings,
imageGenerationQualitySchema,
isAgentRuntimeModelProtocol,
isDeepSeekHarnessModelProfile,
modelAuthenticationSchema,
modelProtocolSchema,
runtimeModelSourceSchema,
@@ -164,7 +165,7 @@ const version13StoredSettingsSchema = version12StoredSettingsSchema
.max(20)
})
const storedSettingsSchema = version13StoredSettingsSchema
const version14StoredSettingsSchema = version13StoredSettingsSchema
.omit({ version: true })
.extend({
version: z.literal(14),
@@ -185,7 +186,24 @@ const storedSettingsSchema = version13StoredSettingsSchema
knowledgeRerankCredential: credentialSchema
})
const version15StoredSettingsSchema = version14StoredSettingsSchema
.omit({ version: true })
.extend({
version: z.literal(15),
deepseekHarnessModelSource: runtimeModelSourceSchema,
deepseekHarnessBinaryPath: runtimePathSchema.default('')
})
const storedSettingsSchema = version15StoredSettingsSchema
.omit({ version: true, deepseekHarnessBinaryPath: true })
.extend({
version: z.literal(16)
})
type StoredSettings = z.infer<typeof storedSettingsSchema>
type Version15StoredSettings = z.infer<
typeof version15StoredSettingsSchema
>
type Version10StoredSettings = z.infer<
typeof version10StoredSettingsSchema
>
@@ -198,6 +216,9 @@ type Version12StoredSettings = z.infer<
type Version13StoredSettings = z.infer<
typeof version13StoredSettingsSchema
>
type Version14StoredSettings = z.infer<
typeof version14StoredSettingsSchema
>
const version3StoredSettingsSchema = version4StoredSettingsSchema
.omit({ version: true, continueMode: true })
@@ -242,6 +263,7 @@ const embeddingCredentialPayloadSchema = z.object({
})
const rerankCredentialPayloadSchema = embeddingCredentialPayloadSchema
const platformDeepSeekProfileId = 'goodbuddy-platform-deepseek'
export type CredentialCipher = SettingsCredentialCipher
@@ -258,6 +280,7 @@ export type ResolvedRuntimeSettings = {
defaultModelProfileId: string
opencodeModelProfile?: ResolvedModelProfile
continueModelProfile?: ResolvedModelProfile
deepseekHarnessModelProfile?: ResolvedModelProfile
opencodeBaseUrl: string
opencodeEmbedded: boolean
opencodeBinaryPath: string
@@ -297,7 +320,7 @@ export type ResolvedModelProfile = {
}
const defaultSettings: StoredSettings = {
version: 14,
version: 16,
provider: defaultRuntimeSettings.provider,
modelProfiles: [
{
@@ -328,6 +351,7 @@ const defaultSettings: StoredSettings = {
continueBinaryPath: defaultRuntimeSettings.continueBinaryPath,
continueConfigPath: defaultRuntimeSettings.continueConfigPath,
continueMode: defaultRuntimeSettings.continueMode,
deepseekHarnessModelSource: { kind: 'platform' },
runtimeSandboxMode: defaultRuntimeSettings.runtimeSandboxMode,
subagentSmartRoutingEnabled:
defaultRuntimeSettings.subagentSmartRoutingEnabled,
@@ -411,7 +435,7 @@ function migrateVersion12(
function migrateVersion13(
settings: Version13StoredSettings
): StoredSettings {
return {
return migrateVersion14({
...settings,
version: 14,
knowledgeRerankEnabled:
@@ -421,6 +445,30 @@ function migrateVersion13(
knowledgeRerankModel:
defaultRuntimeSettings.knowledgeRerankModel,
knowledgeRerankCredential: undefined
})
}
function migrateVersion14(
settings: Version14StoredSettings
): StoredSettings {
return {
...settings,
version: 16,
deepseekHarnessModelSource: { kind: 'platform' }
}
}
function migrateVersion15(
settings: Version15StoredSettings
): StoredSettings {
const {
deepseekHarnessBinaryPath: _obsolete,
...current
} = settings
void _obsolete
return {
...current,
version: 16
}
}
@@ -483,6 +531,19 @@ function normalizeStoredSettings(settings: StoredSettings): StoredSettings {
? { kind: 'profile', profileId: fallbackProfileId }
: { kind: 'platform' }
}
const normalizeDeepSeekHarnessSource = (
source: RuntimeSettings['deepseekHarnessModelSource']
): NonNullable<RuntimeSettings['deepseekHarnessModelSource']> => {
if (!source || source.kind === 'platform') {
return { kind: 'platform' }
}
const profile = modelProfiles.find(
(candidate) => candidate.id === source.profileId
)
return profile && isDeepSeekHarnessModelProfile(profile)
? source
: { kind: 'platform' }
}
const opencodeBaseUrl = settings.opencodeBaseUrl.trim()
if (opencodeBaseUrl) {
const url = new URL(opencodeBaseUrl)
@@ -508,6 +569,9 @@ function normalizeStoredSettings(settings: StoredSettings): StoredSettings {
continueModelSource: normalizeSource(
settings.continueModelSource
),
deepseekHarnessModelSource: normalizeDeepSeekHarnessSource(
settings.deepseekHarnessModelSource
),
opencodeBaseUrl,
opencodeEmbedded: !opencodeBaseUrl
}
@@ -683,7 +747,7 @@ export class RuntimeSettingsStore {
const parsed: unknown = JSON.parse(contents)
assertSupportedSettingsVersion(
parsed,
14,
16,
(version) =>
`当前 GoodBuddy 不支持 Runtime 设置版本 ${version},请升级应用后重试`
)
@@ -691,110 +755,122 @@ export class RuntimeSettingsStore {
if (current.success) {
this.settings = current.data
} else {
const version13 =
version13StoredSettingsSchema.safeParse(parsed)
if (version13.success) {
this.settings = migrateVersion13(version13.data)
const version15 =
version15StoredSettingsSchema.safeParse(parsed)
if (version15.success) {
this.settings = migrateVersion15(version15.data)
} else {
const version12 =
version12StoredSettingsSchema.safeParse(parsed)
if (version12.success) {
this.settings = migrateVersion12(version12.data)
const version14 =
version14StoredSettingsSchema.safeParse(parsed)
if (version14.success) {
this.settings = migrateVersion14(version14.data)
} else {
const version11 =
version11StoredSettingsSchema.safeParse(parsed)
if (version11.success) {
this.settings = migrateVersion11(version11.data)
const version13 =
version13StoredSettingsSchema.safeParse(parsed)
if (version13.success) {
this.settings = migrateVersion13(version13.data)
} else {
const version10 =
version10StoredSettingsSchema.safeParse(parsed)
if (version10.success) {
this.settings = migrateVersion10(version10.data)
const version12 =
version12StoredSettingsSchema.safeParse(parsed)
if (version12.success) {
this.settings = migrateVersion12(version12.data)
} else {
const version9 =
version9StoredSettingsSchema.safeParse(parsed)
if (version9.success) {
this.settings = migrateVersion9(version9.data)
const version11 =
version11StoredSettingsSchema.safeParse(parsed)
if (version11.success) {
this.settings = migrateVersion11(version11.data)
} else {
const version8 =
version8StoredSettingsSchema.safeParse(parsed)
if (version8.success) {
this.settings = migrateVersion8(version8.data)
const version10 =
version10StoredSettingsSchema.safeParse(parsed)
if (version10.success) {
this.settings = migrateVersion10(version10.data)
} else {
const version7 =
version7StoredSettingsSchema.safeParse(parsed)
if (version7.success) {
this.settings = migrateVersion7(version7.data)
const version9 =
version9StoredSettingsSchema.safeParse(parsed)
if (version9.success) {
this.settings = migrateVersion9(version9.data)
} else {
const version6 =
version6StoredSettingsSchema.safeParse(parsed)
if (version6.success) {
this.settings = migrateVersion6(version6.data)
const version8 =
version8StoredSettingsSchema.safeParse(parsed)
if (version8.success) {
this.settings = migrateVersion8(version8.data)
} else {
const version5 =
version5StoredSettingsSchema.safeParse(parsed)
if (version5.success) {
this.settings = migrateVersion5(version5.data)
const version7 =
version7StoredSettingsSchema.safeParse(parsed)
if (version7.success) {
this.settings = migrateVersion7(version7.data)
} else {
const version4 =
version4StoredSettingsSchema.safeParse(parsed)
if (version4.success) {
this.settings = migrateVersion4(version4.data)
const version6 =
version6StoredSettingsSchema.safeParse(parsed)
if (version6.success) {
this.settings = migrateVersion6(version6.data)
} else {
const version3 =
version3StoredSettingsSchema.safeParse(parsed)
if (version3.success) {
this.settings = migrateVersion4({
...version3.data,
version: 4,
continueMode: 'chat'
})
const version5 =
version5StoredSettingsSchema.safeParse(parsed)
if (version5.success) {
this.settings = migrateVersion5(version5.data)
} else {
const version2 =
version2StoredSettingsSchema.safeParse(parsed)
if (version2.success) {
this.settings = migrateVersion4({
version: 4,
provider: version2.data.provider,
modelBaseUrl: version2.data.modelBaseUrl,
modelName: version2.data.modelName,
opencodeBaseUrl: version2.data.opencodeBaseUrl,
opencodeEmbedded: version2.data.opencodeEmbedded,
opencodeBinaryPath: '',
opencodeConfigPath: '',
continueBinaryPath: migrateContinueCommand(
version2.data.continueCommand
),
continueConfigPath: '',
continueMode: 'chat',
workspacePath: version2.data.workspacePath,
credential: version2.data.credential,
toolApproval: version2.data.toolApproval
})
const version4 =
version4StoredSettingsSchema.safeParse(parsed)
if (version4.success) {
this.settings = migrateVersion4(version4.data)
} else {
const legacy =
legacyStoredSettingsSchema.parse(parsed)
this.settings = migrateVersion4({
version: 4,
provider:
legacy.provider === 'bigtoken'
? 'model'
: legacy.provider,
modelBaseUrl: legacy.bigtokenBaseUrl,
modelName: legacy.bigtokenModel,
opencodeBaseUrl: legacy.opencodeBaseUrl,
opencodeEmbedded: legacy.opencodeEmbedded,
opencodeBinaryPath: '',
opencodeConfigPath: '',
continueBinaryPath: migrateContinueCommand(
legacy.continueCommand
),
continueConfigPath: '',
continueMode: 'chat',
workspacePath: legacy.workspacePath,
credential: legacy.credential,
toolApproval: legacy.toolApproval
})
const version3 =
version3StoredSettingsSchema.safeParse(parsed)
if (version3.success) {
this.settings = migrateVersion4({
...version3.data,
version: 4,
continueMode: 'chat'
})
} else {
const version2 =
version2StoredSettingsSchema.safeParse(parsed)
if (version2.success) {
this.settings = migrateVersion4({
version: 4,
provider: version2.data.provider,
modelBaseUrl: version2.data.modelBaseUrl,
modelName: version2.data.modelName,
opencodeBaseUrl: version2.data.opencodeBaseUrl,
opencodeEmbedded: version2.data.opencodeEmbedded,
opencodeBinaryPath: '',
opencodeConfigPath: '',
continueBinaryPath: migrateContinueCommand(
version2.data.continueCommand
),
continueConfigPath: '',
continueMode: 'chat',
workspacePath: version2.data.workspacePath,
credential: version2.data.credential,
toolApproval: version2.data.toolApproval
})
} else {
const legacy =
legacyStoredSettingsSchema.parse(parsed)
this.settings = migrateVersion4({
version: 4,
provider:
legacy.provider === 'bigtoken'
? 'model'
: legacy.provider,
modelBaseUrl: legacy.bigtokenBaseUrl,
modelName: legacy.bigtokenModel,
opencodeBaseUrl: legacy.opencodeBaseUrl,
opencodeEmbedded: legacy.opencodeEmbedded,
opencodeBinaryPath: '',
opencodeConfigPath: '',
continueBinaryPath: migrateContinueCommand(
legacy.continueCommand
),
continueConfigPath: '',
continueMode: 'chat',
workspacePath: legacy.workspacePath,
credential: legacy.credential,
toolApproval: legacy.toolApproval
})
}
}
}
}
}
@@ -965,6 +1041,43 @@ export class RuntimeSettingsStore {
)
}
private resolvePlatformDeepSeekProfile(): ResolvedModelProfile | undefined {
const apiKey = this.environment.GOODBUDDY_MODEL_API_KEY?.trim()
const baseUrl = this.environment.GOODBUDDY_MODEL_BASE_URL?.trim()
const modelName = this.environment.GOODBUDDY_MODEL_NAME?.trim()
if (!apiKey || !baseUrl || !modelName) {
return undefined
}
try {
const endpoint = new URL(baseUrl)
if (
endpoint.protocol !== 'https:' ||
endpoint.hostname.toLowerCase() !== 'api.deepseek.com' ||
endpoint.port ||
endpoint.pathname !== '/' ||
endpoint.search ||
endpoint.hash ||
endpoint.username ||
endpoint.password
) {
return undefined
}
} catch {
return undefined
}
return {
id: platformDeepSeekProfileId,
name: '平台 DeepSeek',
baseUrl,
modelName,
protocol: 'openai-chat-completions',
authentication: 'api-key',
supportsImageInput: false,
imageGenerationQuality: 'auto',
apiKey
}
}
private resolveEffectiveModelSettings(settings: StoredSettings): {
apiKey?: string
baseUrl: string
@@ -1235,6 +1348,8 @@ export class RuntimeSettingsStore {
? { kind: 'platform' }
: settings.opencodeModelSource,
continueModelSource: settings.continueModelSource,
deepseekHarnessModelSource:
settings.deepseekHarnessModelSource,
secureStorageAvailable: this.cipher.isAvailable(),
toolApproval: settings.toolApproval,
configured: {
@@ -1246,7 +1361,9 @@ export class RuntimeSettingsStore {
continueConfigPath: settings.continueConfigPath,
workspacePath: settings.workspacePath || homedir(),
opencodeModelSource: settings.opencodeModelSource,
continueModelSource: settings.continueModelSource
continueModelSource: settings.continueModelSource,
deepseekHarnessModelSource:
settings.deepseekHarnessModelSource
},
...(this.loadWarnings.length > 0
? { warnings: [...this.loadWarnings] }
@@ -1284,6 +1401,12 @@ export class RuntimeSettingsStore {
settings.continueModelSource.kind === 'profile'
? profilesById.get(settings.continueModelSource.profileId)
: undefined
const deepseekHarnessModelProfile =
settings.deepseekHarnessModelSource.kind === 'profile'
? profilesById.get(
settings.deepseekHarnessModelSource.profileId
)
: this.resolvePlatformDeepSeekProfile()
return {
provider: settings.provider,
modelBaseUrl: effective.baseUrl,
@@ -1297,6 +1420,7 @@ export class RuntimeSettingsStore {
defaultModelProfileId: settings.defaultModelProfileId,
opencodeModelProfile,
continueModelProfile,
deepseekHarnessModelProfile,
...agent,
subagentSmartRoutingEnabled:
settings.subagentSmartRoutingEnabled,
@@ -1589,15 +1713,34 @@ export class RuntimeSettingsStore {
: repairRuntimeSource(current.continueModelSource)
validateRuntimeSource(opencodeModelSource, 'OpenCode')
validateRuntimeSource(continueModelSource, 'Continue')
const requestedDeepSeekHarnessSource =
input.deepseekHarnessModelSource ??
current.deepseekHarnessModelSource
if (requestedDeepSeekHarnessSource.kind === 'profile') {
const profile = modelProfiles.find(
(candidate) =>
candidate.id === requestedDeepSeekHarnessSource.profileId
)
if (!profile) {
throw new Error('DeepSeek Harness 引用的模型连接不存在')
}
if (!isDeepSeekHarnessModelProfile(profile)) {
throw new Error(
'DeepSeek Harness 模型连接仅支持 api.deepseek.com 的 OpenAI Chat Completions 协议'
)
}
}
const next: StoredSettings = {
...current,
version: 14,
version: 16,
provider: input.provider,
modelProfiles,
defaultModelProfileId,
opencodeModelSource,
continueModelSource,
deepseekHarnessModelSource:
requestedDeepSeekHarnessSource,
opencodeBaseUrl,
opencodeEmbedded: !opencodeBaseUrl,
opencodeBinaryPath,