Files
goodbuddy/src/main/agent/goodbuddy-harness-control-plane.ts
T
mesalogo 56ebd40bd8 feat: unify execution and add workspace file imports
Remove Ask/Execute modes and general product tool approvals so requests use the selected runtime, enabled capabilities, and execution account permissions. Migrate retired application metadata while preserving user content.

Retire the Results page while retaining conversation images and stored artifacts. Add local and remote workspace file imports, timestamps and sorting, improve image discovery and saving, and refine workspace navigation and supervision responsiveness. Include the accompanying web-search changes, tests, probes, and product documentation.

Remote imports require an updated Agent. Existing artifacts are not automatically copied into project directories. Validation evidence and outstanding failures remain documented in feature progress files; no full test suite or real-model calls were rerun for this commit.

Release note: Run requests without switching work modes, import files into a selected local or remote workspace folder, sort workspace files by date, and keep using conversation images after removal of the Results page.
2026-10-04 20:02:59 +08:00

1385 lines
38 KiB
TypeScript

import { randomUUID } from 'node:crypto'
import { isAbsolute } from 'node:path'
import {
AgentSideConnection,
PROTOCOL_VERSION,
RequestError,
type Agent,
type AgentSideConnection as AcpAgentConnection,
type ContentBlock as AcpContentBlock,
type Stream
} from '@agentclientprotocol/sdk'
import type { Context } from '@deepseek-ai/cordis'
import type { AgentHandle } from '@deepseek-ai/dsh-agent'
import {
AttachmentError,
isImageAdmissionError,
type ImageAttachmentRef,
type SaveImageAttachment
} from '@deepseek-ai/dsh-attachment'
import {
CredentialProvider,
type CredentialInfo,
type CredentialKey,
type CredentialRecord,
type CredentialRecordEntry,
type CredentialRecordInfo,
type CredentialRef,
type ResolvedCredential
} from '@deepseek-ai/dsh-credentials'
import {
createUserMessage,
errorChain,
type ContentBlock as HarnessContentBlock,
type TokenUsage
} from '@deepseek-ai/dsh-llm'
import {
SessionId,
type SessionEvent
} from '@deepseek-ai/dsh-session'
import type { ToolDefinition } from '@deepseek-ai/dsh-tools'
import * as ToolSkill from '@deepseek-ai/dsh-tool-skill'
import {
GOODBUDDY_CONTROL_PROTOCOL_VERSION,
GOODBUDDY_CREDENTIAL,
GOODBUDDY_EVENT,
GOODBUDDY_HANDSHAKE,
GOODBUDDY_NATIVE_SNAPSHOT,
GOODBUDDY_PREPARE,
GOODBUDDY_RELEASE,
GOODBUDDY_SHUTDOWN,
GOODBUDDY_TOOLS_CALL,
GOODBUDDY_TOOLS_LIST
} from './deepseek-harness-protocol'
import { GoodBuddyHarnessAttachmentStore } from './goodbuddy-harness-attachment-store'
export {
GOODBUDDY_CONTROL_PROTOCOL_VERSION,
GOODBUDDY_CREDENTIAL,
GOODBUDDY_EVENT,
GOODBUDDY_HANDSHAKE,
GOODBUDDY_NATIVE_SNAPSHOT,
GOODBUDDY_RELEASE,
GOODBUDDY_SHUTDOWN,
GOODBUDDY_TOOLS_CALL,
GOODBUDDY_TOOLS_LIST,
GOODBUDDY_PREPARE
} from './deepseek-harness-protocol'
const DEFAULT_MAX_EVENT_CHARACTERS = 64 * 1024
const DELTA_BATCH_CHARACTERS = 4 * 1024
const DELTA_BATCH_INTERVAL_MS = 100
const MAX_SUMMARY_CHARACTERS = 4_000
const MAX_MCP_PROXY_RESULT_BYTES = 256 * 1024
const MAX_NATIVE_SKILLS = 200
const MAX_NATIVE_TOOLS = 200
const NATIVE_SNAPSHOT_TIMEOUT_MS = 2_000
const GOODBUDDY_EXECUTION_GUIDANCE = [
'GoodBuddy execution guidance:',
'- Act through the available tools instead of writing a long implementation plan.',
'- Inspect only what is needed, then create or update the requested workspace files promptly.',
'- Work in small verifiable steps and use tool results as the source of truth.',
'- Keep reasoning concise. Do not narrate code that can be written and checked with tools.'
].join('\n')
export type GoodBuddyHarnessCapabilities = {
controlProtocolVersion: typeof GOODBUDDY_CONTROL_PROTOCOL_VERSION
harnessVersion: string
acpProtocolVersion: number
supports: {
cancellation: true
sessionRelease: true
reasoningEvents: boolean
toolEvents: boolean
usageEvents: boolean
credentialResolution: true
}
execution: {
mode: 'host'
}
}
export type GoodBuddyHarnessControlConfig = {
provider: string
model: string
supportsImageInput?: boolean
workspace: string
harnessVersion: string
execution: GoodBuddyHarnessCapabilities['execution']
credentialRefs: readonly string[]
skills: readonly {
name: string
description: string
content: string
directory: string
}[]
stream?: Stream
maxEventCharacters?: number
}
type Preparation = {
requestId: string
}
type OwnedSession = {
handle: AgentHandle
attachmentRefs: ImageAttachmentRef[]
preparation?: Preparation
proxyTools: Map<
string,
{
definition: ToolDefinition
dispose: () => void
}
>
inflight?: {
requestId: string
messageId: string
turn?: number
endReason?: string
turnError?: unknown
resolve: (reason: string) => void
reject: (error: unknown) => void
eventTail: Promise<void>
eventError?: unknown
pendingDelta?: {
type: 'text' | 'reasoning'
delta: string
}
pendingDeltaTimer?: ReturnType<typeof setTimeout>
}
}
type ProxyToolDefinition = {
name: string
description: string
inputSchema: Record<string, unknown>
}
function validateProxyToolResult(
value: unknown
): Record<string, unknown> {
if (
!value ||
typeof value !== 'object' ||
Array.isArray(value)
) {
throw new Error('GoodBuddy MCP tool result is invalid')
}
const serialized = JSON.stringify(value)
if (
Buffer.byteLength(serialized, 'utf8') >
MAX_MCP_PROXY_RESULT_BYTES
) {
throw new Error(
'GoodBuddy MCP tool result exceeds safety limit'
)
}
const content = (value as Record<string, unknown>).content
if (
!Array.isArray(content) ||
content.length > 100 ||
content.some(
(part) =>
!part ||
typeof part !== 'object' ||
Array.isArray(part) ||
(part as Record<string, unknown>).type !== 'text' ||
typeof (part as Record<string, unknown>).text !== 'string'
)
) {
throw new Error('GoodBuddy MCP tool result is invalid')
}
return value as Record<string, unknown>
}
function parseProxyToolCatalog(
value: unknown
): ProxyToolDefinition[] {
if (!Array.isArray(value) || value.length > 100) {
throw new Error('GoodBuddy MCP tool catalog is invalid')
}
const names = new Set<string>()
return value.map((candidate) => {
if (
!candidate ||
typeof candidate !== 'object' ||
Array.isArray(candidate)
) {
throw new Error('GoodBuddy MCP tool definition is invalid')
}
const tool = candidate as Record<string, unknown>
if (
typeof tool.name !== 'string' ||
!/^[a-zA-Z0-9_-]{1,64}$/u.test(tool.name) ||
names.has(tool.name) ||
typeof tool.description !== 'string' ||
tool.description.length > (tool.name === 'generate_image' ? imageToolDescriptionLimit : 1_000) ||
!tool.inputSchema ||
typeof tool.inputSchema !== 'object' ||
Array.isArray(tool.inputSchema)
) {
throw new Error('GoodBuddy MCP tool definition is invalid')
}
names.add(tool.name)
return {
name: tool.name,
description: tool.description,
inputSchema: tool.inputSchema as Record<string, unknown>
}
})
}
type CredentialResolver = (
ref: string
) => Promise<string | undefined>
/**
* Memory-only credential provider. It deliberately has no writable operation
* and can resolve only references registered by the trusted host.
*/
export class GoodBuddyCredentialProvider extends CredentialProvider {
private resolver?: CredentialResolver
constructor(
ctx: Context,
private readonly allowedRefs: ReadonlySet<string>
) {
super(ctx)
}
bind(resolver: CredentialResolver): void {
if (this.resolver) {
throw new Error('GoodBuddy credential resolver is already bound')
}
this.resolver = resolver
}
async resolve(
ref: CredentialRef
): Promise<ResolvedCredential | undefined> {
if (!this.allowedRefs.has(ref) || !this.resolver) {
return undefined
}
const value = await this.resolver(ref)
return value ? { value, source: 'goodbuddy-main' } : undefined
}
async describe(ref: CredentialRef): Promise<CredentialInfo> {
const available =
this.allowedRefs.has(ref) && this.resolver !== undefined
return {
configured: available,
source: available ? 'goodbuddy-main' : undefined,
writable: false
}
}
async set(ref: CredentialRef, value: string): Promise<void> {
void ref
void value
throw new Error('GoodBuddy Harness credentials are read-only')
}
async unset(ref: CredentialRef): Promise<void> {
void ref
throw new Error('GoodBuddy Harness credentials are read-only')
}
async readRecord(
key: CredentialKey
): Promise<CredentialRecord | undefined> {
void key
return undefined
}
async describeRecord(
key: CredentialKey
): Promise<CredentialRecordInfo> {
void key
return { configured: false, writable: false }
}
async listRecords(): Promise<readonly CredentialRecordEntry[]> {
return []
}
async modifyRecord(
key: CredentialKey,
mutate: (
current: CredentialRecord | undefined
) => Promise<CredentialRecord | undefined>
): Promise<CredentialRecord | undefined> {
void key
void mutate
throw new Error('GoodBuddy Harness credentials are read-only')
}
async deleteRecord(key: CredentialKey): Promise<void> {
void key
throw new Error('GoodBuddy Harness credentials are read-only')
}
}
export function createBoundedAcpStream(
stream: Stream,
maxFrameBytes: number
): Stream {
if (
!Number.isSafeInteger(maxFrameBytes) ||
maxFrameBytes < 1
) {
throw new TypeError('maxFrameBytes must be a positive integer')
}
return {
readable: stream.readable.pipeThrough(
new TransformStream({
transform(message, controller) {
if (
Buffer.byteLength(JSON.stringify(message), 'utf8') >
maxFrameBytes
) {
throw new Error(
`ACP input frame exceeds ${maxFrameBytes} bytes`
)
}
controller.enqueue(message)
}
})
),
writable: new WritableStream({
async write(message) {
if (
Buffer.byteLength(JSON.stringify(message), 'utf8') >
maxFrameBytes
) {
throw new Error(
`ACP output frame exceeds ${maxFrameBytes} bytes`
)
}
const writer = stream.writable.getWriter()
try {
await writer.write(message)
} finally {
writer.releaseLock()
}
},
async close() {
const writer = stream.writable.getWriter()
try {
await writer.close()
} finally {
writer.releaseLock()
}
},
async abort(reason) {
await stream.writable.abort(reason)
}
})
}
}
function requiredString(
params: Record<string, unknown>,
key: string,
maxLength = 512
): string {
const value = params[key]
if (
typeof value !== 'string' ||
value.length === 0 ||
value.length > maxLength
) {
throw RequestError.invalidParams(
undefined,
`${key} must be a non-empty bounded string`
)
}
return value
}
function boundedJson(value: unknown): string | undefined {
if (value === undefined) {
return undefined
}
try {
return (
typeof value === 'string'
? value
: JSON.stringify(value)
).slice(0, MAX_SUMMARY_CHARACTERS)
} catch {
return '[unserializable]'
}
}
function promptText(prompt: readonly AcpContentBlock[]): string {
const text: string[] = []
for (const block of prompt) {
switch (block.type) {
case 'text':
text.push(block.text)
break
case 'image':
case 'resource_link':
break
case 'audio':
case 'resource':
throw RequestError.invalidParams(
undefined,
`unsupported ACP prompt content: ${block.type}`
)
default:
block satisfies never
}
}
return text.join('')
}
function turnReason(event: SessionEvent): string | undefined {
if (event.type !== 'turn/end') {
return undefined
}
switch (event.data.reason.kind) {
case 'aborted':
case 'interrupted':
return 'cancelled'
case 'max-tokens':
return 'max_tokens'
default:
return 'end_turn'
}
}
export class GoodBuddyHarnessControlPlane {
private readonly sessions = new Map<string, OwnedSession>()
private readonly allowedCredentialRefs: ReadonlySet<string>
private readonly maxEventCharacters: number
private connection?: AcpAgentConnection
private credentialProvider?: GoodBuddyCredentialProvider
private handshaken = false
private observing = false
private closed = false
private disposing?: Promise<void>
constructor(
private readonly ctx: Context,
private readonly config: GoodBuddyHarnessControlConfig
) {
this.allowedCredentialRefs = new Set(config.credentialRefs)
this.maxEventCharacters =
config.maxEventCharacters ?? DEFAULT_MAX_EVENT_CHARACTERS
}
bindCredentialProvider(
provider: GoodBuddyCredentialProvider
): void {
if (this.connection) {
throw new Error(
'Credential provider must be bound before bridge start'
)
}
this.credentialProvider = provider
}
start(): AcpAgentConnection {
if (this.connection) {
throw new Error(
'GoodBuddy Harness control plane is already started'
)
}
if (!this.config.stream) {
throw new Error(
'GoodBuddy Harness control plane requires its internal Host transport'
)
}
const connection = new AgentSideConnection(
() => this.createAgentApi(),
this.config.stream
)
this.connection = connection
this.credentialProvider?.bind(async (ref) => {
if (!this.handshaken || !this.allowedCredentialRefs.has(ref)) {
return undefined
}
const response = await connection.extMethod(
GOODBUDDY_CREDENTIAL,
{ ref }
)
const value = response.value
return typeof value === 'string' && value.length > 0
? value
: undefined
})
void connection.closed.finally(() => this.dispose())
return connection
}
private capabilities(): GoodBuddyHarnessCapabilities {
return {
controlProtocolVersion: GOODBUDDY_CONTROL_PROTOCOL_VERSION,
harnessVersion: this.config.harnessVersion,
acpProtocolVersion: PROTOCOL_VERSION,
supports: {
cancellation: true,
sessionRelease: true,
reasoningEvents: true,
toolEvents: true,
usageEvents: true,
credentialResolution: true
},
execution: this.config.execution
}
}
private requireSession(sessionId: string): OwnedSession {
const session = this.sessions.get(sessionId)
if (!session) {
throw RequestError.invalidParams(
undefined,
`unknown session: ${sessionId}`
)
}
return session
}
private async sendEvent(
sessionId: string,
event: Record<string, unknown>
): Promise<void> {
const record = this.sessions.get(sessionId)
const inflight = record?.inflight
if (!inflight || !this.connection) {
return
}
const params = {
sessionId,
requestId: inflight.requestId,
...event
}
const text = JSON.stringify(params)
if (text.length > this.maxEventCharacters) {
throw new Error(
'GoodBuddy Harness control event exceeds safety limit'
)
}
await this.connection.extNotification(GOODBUDDY_EVENT, params)
}
private enqueueEvent(
sessionId: string,
event: Record<string, unknown>
): void {
const record = this.sessions.get(sessionId)
const inflight = record?.inflight
if (!record || !inflight) {
return
}
const previous = inflight.eventTail
const queued = previous.then(() =>
this.sendEvent(sessionId, event)
)
inflight.eventTail = queued.catch((error: unknown) => {
inflight.eventError ??= error
record.handle.agent.cancel({ kind: 'user' })
})
}
private flushPendingDelta(sessionId: string): void {
const inflight = this.sessions.get(sessionId)?.inflight
const pending = inflight?.pendingDelta
if (!inflight || !pending) {
return
}
if (inflight.pendingDeltaTimer) {
clearTimeout(inflight.pendingDeltaTimer)
inflight.pendingDeltaTimer = undefined
}
inflight.pendingDelta = undefined
this.enqueueEvent(sessionId, pending)
}
private maxDeltaBatchCharacters(
sessionId: string,
type: 'text' | 'reasoning'
): number {
const inflight = this.sessions.get(sessionId)?.inflight
if (!inflight) {
return 0
}
const envelopeLength = JSON.stringify({
sessionId,
requestId: inflight.requestId,
type,
delta: ''
}).length
return Math.min(
DELTA_BATCH_CHARACTERS,
this.maxEventCharacters - envelopeLength
)
}
private queueDelta(
sessionId: string,
type: 'text' | 'reasoning',
delta: string
): void {
const inflight = this.sessions.get(sessionId)?.inflight
if (!inflight || !delta) {
return
}
const batchLimit = this.maxDeltaBatchCharacters(
sessionId,
type
)
if (batchLimit < 1) {
this.failEventStream(
sessionId,
new Error(
'GoodBuddy Harness control event exceeds safety limit'
)
)
return
}
let remaining = delta
while (remaining) {
if (
inflight.pendingDelta &&
inflight.pendingDelta.type !== type
) {
this.flushPendingDelta(sessionId)
}
inflight.pendingDelta ??= { type, delta: '' }
const available =
batchLimit - inflight.pendingDelta.delta.length
if (available <= 0) {
this.flushPendingDelta(sessionId)
continue
}
inflight.pendingDelta.delta += remaining.slice(0, available)
remaining = remaining.slice(available)
if (inflight.pendingDelta.delta.length >= batchLimit) {
this.flushPendingDelta(sessionId)
}
}
if (
inflight.pendingDelta &&
!inflight.pendingDeltaTimer
) {
inflight.pendingDeltaTimer = setTimeout(() => {
this.flushPendingDelta(sessionId)
}, DELTA_BATCH_INTERVAL_MS)
}
}
private queueEvent(
sessionId: string,
event: Record<string, unknown>
): void {
this.flushPendingDelta(sessionId)
this.enqueueEvent(sessionId, event)
}
private failEventStream(sessionId: string, error: unknown): void {
const record = this.sessions.get(sessionId)
const inflight = record?.inflight
if (!record || !inflight) {
return
}
inflight.eventError ??= error
record.handle.agent.cancel({ kind: 'user' })
}
private observeSessions(): void {
if (this.observing) {
return
}
this.observing = true
this.ctx.on('agent/assistant-stream', ({ agent, frame }) => {
const record = this.sessions.get(agent.session.id)
if (
record?.handle.agent !== agent ||
!record.inflight ||
frame.type !== 'chunk'
) {
return
}
const chunk = frame.chunk
if (chunk.type === 'text-delta' || chunk.type === 'reasoning-delta') {
this.queueDelta(
agent.session.id,
chunk.type === 'text-delta' ? 'text' : 'reasoning',
chunk.text
)
} else if (chunk.type === 'usage') {
this.queueUsage(agent.session.id, chunk.usage)
}
})
this.ctx.on(
'session/event',
(session, event: SessionEvent) => {
const record = this.sessions.get(session.header.id)
if (!record || record.handle.agent.session !== session) {
return
}
const inflight = record.inflight
if (!inflight) {
return
}
if (event.type === 'tool/call') {
this.queueEvent(session.header.id, {
type: 'tool',
callId: event.data.callId,
name: event.data.name,
state: 'pending',
input: event.data.arguments.slice(
0,
MAX_SUMMARY_CHARACTERS
)
})
} else if (event.type === 'tool/result') {
const toolResult = event.data.message
this.queueEvent(session.header.id, {
type: 'tool',
callId: toolResult.toolCallId,
name: 'tool',
state:
event.data.error || toolResult.isError === true
? 'failed'
: 'completed',
output: boundedJson(event.data.message.content)
})
}
if (
event.type === 'turn/end' &&
inflight.turn === event.data.turn
) {
inflight.endReason = turnReason(event)
}
}
)
this.ctx.on(
'agent/inbox/claimed',
({ agent, message, turn }) => {
const record = this.sessions.get(agent.session.id)
if (
record?.handle.agent === agent &&
record.inflight?.messageId === message.id
) {
record.inflight.turn = turn
}
}
)
this.ctx.on(
'agent/error',
({ agent, error }) => {
const record = this.sessions.get(agent.session.id)
if (record?.handle.agent === agent) {
if (record.inflight) {
record.inflight.turnError = error
}
}
}
)
}
private queueUsage(sessionId: string, usage: TokenUsage): void {
this.flushPendingDelta(sessionId)
this.enqueueEvent(sessionId, {
type: 'model-usage',
callId: randomUUID(),
provider: this.config.provider,
model: this.config.model,
inputTokens: usage.inputTokens,
outputTokens: usage.outputTokens,
cacheReadTokens: usage.cacheReadTokens ?? 0,
cacheWriteTokens: usage.cacheWriteTokens ?? 0
})
}
private proxyToolDefinition(
sessionId: string,
tool: ProxyToolDefinition
): ToolDefinition {
return {
name: tool.name,
description: tool.description,
parameters: tool.inputSchema,
output: {
schema: {
type: 'object',
properties: {
content: {
type: 'array',
items: {
type: 'object',
additionalProperties: true
}
}
},
required: ['content'],
additionalProperties: false
},
render(_args, result) {
const content = (
result as {
content: Array<{
type?: unknown
text?: unknown
}>
}
).content
return content
.filter(
(
part
): part is {
type: 'text'
text: string
} =>
part.type === 'text' &&
typeof part.text === 'string'
)
.map((part) => ({
type: 'text' as const,
text: part.text
}))
}
},
execute: async (argumentsValue) => {
const result = await this.connection!.extMethod(
GOODBUDDY_TOOLS_CALL,
{
sessionId,
name: tool.name,
callId: randomUUID(),
arguments: argumentsValue
}
)
return validateProxyToolResult(result)
}
}
}
private async refreshProxyTools(
sessionId: string,
record: OwnedSession
): Promise<void> {
const response = await this.connection!.extMethod(
GOODBUDDY_TOOLS_LIST,
{ sessionId }
)
const tools = parseProxyToolCatalog(response.tools)
const nextNames = new Set(tools.map((tool) => tool.name))
for (const [name, registration] of record.proxyTools) {
if (!nextNames.has(name)) {
registration.dispose()
record.proxyTools.delete(name)
}
}
for (const tool of tools) {
if (!record.proxyTools.has(tool.name)) {
const definition = this.proxyToolDefinition(sessionId, tool)
const dispose =
record.handle.agent.ctx.tools.register(definition)
record.proxyTools.set(tool.name, {
definition,
dispose
})
}
}
}
private async nativeSnapshot(
workspace = this.config.workspace
): Promise<Record<string, unknown>> {
const controller = new AbortController()
const timer = setTimeout(
() =>
controller.abort(
new Error('DeepSeek Harness native inventory timed out')
),
NATIVE_SNAPSHOT_TIMEOUT_MS
)
try {
const skills = await Promise.race([
this.ctx.skills.list({
cwd: workspace,
signal: controller.signal
}),
new Promise<never>((_resolve, reject) => {
controller.signal.addEventListener(
'abort',
() => reject(controller.signal.reason),
{ once: true }
)
})
])
let toolsSupported = true
let tools: Array<{
id: string
name: string
description?: string
}> = []
try {
tools = this.ctx.tools
.schemas()
.slice(0, MAX_NATIVE_TOOLS)
.flatMap((tool) => {
const name = tool.name.trim().slice(0, 128)
if (!name) {
return []
}
const description = tool.description.trim().slice(0, 2_000)
return [
{
id: name,
name: name.slice(0, 200),
...(description ? { description } : {})
}
]
})
} catch {
toolsSupported = false
}
return {
tools,
toolsSupported,
skills: skills.slice(0, MAX_NATIVE_SKILLS).map((skill) => ({
id: skill.name.slice(0, 128),
name: skill.name.slice(0, 200),
description: skill.description.slice(0, 2_000),
source: skill.source.slice(0, 128),
provider: skill.provider.slice(0, 128)
}))
}
} finally {
clearTimeout(timer)
}
}
private attachmentStore(): GoodBuddyHarnessAttachmentStore {
const store = this.ctx.get('attachments')
if (!(store instanceof GoodBuddyHarnessAttachmentStore)) {
throw RequestError.internalError(
undefined,
'Harness image attachment service is unavailable'
)
}
return store
}
private releaseAttachments(refs: readonly ImageAttachmentRef[]): void {
if (refs.length === 0) {
return
}
const store = this.attachmentStore()
for (const ref of refs) {
store.releaseImage(ref)
}
}
private async storePromptImages(
prompt: readonly AcpContentBlock[]
): Promise<ImageAttachmentRef[]> {
const imageBlocks = prompt.filter(
(
block
): block is Extract<AcpContentBlock, { type: 'image' }> =>
block.type === 'image'
)
if (imageBlocks.length === 0) {
return []
}
if (!this.config.supportsImageInput) {
throw RequestError.invalidParams(
undefined,
'the selected model does not accept image input'
)
}
const store = this.attachmentStore()
if (imageBlocks.length > store.imageLimits.maxImagesPerMessage) {
throw RequestError.invalidParams(
undefined,
'too many images in one prompt'
)
}
const inputs: SaveImageAttachment[] = []
let totalBytes = 0
for (const block of imageBlocks) {
if (
block.uri != null ||
!store.imageLimits.mediaTypes.includes(
block.mimeType as SaveImageAttachment['mediaType']
) ||
block.data.length === 0 ||
block.data.length % 4 !== 0 ||
block.data.length >
Math.ceil(store.imageLimits.maxImageBytes / 3) * 4 ||
!/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/u.test(
block.data
)
) {
throw RequestError.invalidParams(
undefined,
'invalid inline image prompt content'
)
}
const data = Buffer.from(block.data, 'base64')
totalBytes += data.byteLength
if (
data.byteLength === 0 ||
data.byteLength > store.imageLimits.maxImageBytes ||
totalBytes > store.imageLimits.maxMessageImageBytes ||
data.toString('base64') !== block.data
) {
throw RequestError.invalidParams(
undefined,
'invalid inline image prompt content'
)
}
inputs.push({
data,
mediaType: block.mimeType as SaveImageAttachment['mediaType']
})
}
try {
return await store.saveImages(inputs)
} catch (error) {
if (error instanceof AttachmentError) {
if (isImageAdmissionError(error)) {
throw RequestError.invalidParams(
undefined,
'invalid inline image prompt content'
)
}
if (error.code === 'ATTACHMENT_WRITE_FAILED') {
throw RequestError.internalError(
undefined,
'Harness image attachment service is unavailable'
)
}
}
throw RequestError.internalError(
undefined,
'Harness image attachment storage failed'
)
}
}
private createAgentApi(): Agent {
this.observeSessions()
return {
initialize: async () => ({
protocolVersion: PROTOCOL_VERSION,
agentInfo: {
name: 'goodbuddy-deepseek-harness',
version: this.config.harnessVersion
},
agentCapabilities: {
promptCapabilities: {
image: this.config.supportsImageInput === true,
audio: false,
embeddedContext: false
},
sessionCapabilities: {
close: {}
}
},
authMethods: []
}),
authenticate: async () => undefined,
newSession: async (params) => {
if (this.closed) {
throw RequestError.internalError(
undefined,
'bridge is shutting down'
)
}
if (
!isAbsolute(params.cwd) ||
params.mcpServers.length > 0
) {
throw RequestError.invalidParams(
undefined,
'an absolute workspace and no MCP servers are required'
)
}
const sessionId = SessionId(randomUUID())
const handle = await this.ctx.agents.create({
sessionId,
meta: { cwd: params.cwd },
agentOptions: {
provider: this.config.provider,
model: this.config.model
},
setup: async (agentCtx) => {
agentCtx.systemPrompt.section({
name: 'goodbuddy:controlled-execution',
order: 50,
text: GOODBUDDY_EXECUTION_GUIDANCE
})
const skillRegistrations = agentCtx.inject(
['skills'],
(skillCtx) => {
for (const skill of this.config.skills) {
skillCtx.skills.register({
name: skill.name,
description: skill.description,
content: skill.content,
source: 'bundled',
resourceBase: {
kind: 'directory',
path: skill.directory
},
invocation: {
modelInvocable: true,
userInvocable: true
}
})
}
}
)
await agentCtx.plugin(ToolSkill)
await skillRegistrations
}
})
this.sessions.set(sessionId, {
handle,
attachmentRefs: [],
proxyTools: new Map()
})
return {
sessionId
}
},
prompt: async (params) => {
const record = this.requireSession(params.sessionId)
if (record.inflight) {
throw RequestError.invalidParams(
undefined,
'a prompt is already in flight'
)
}
const preparation = record.preparation
record.preparation = undefined
if (!preparation) {
throw RequestError.invalidParams(
undefined,
'a single-use goodbuddy/session/prepare is required'
)
}
await this.refreshProxyTools(params.sessionId, record)
const text = promptText(params.prompt)
if (!text.trim()) {
throw RequestError.invalidParams(
undefined,
'empty prompt'
)
}
const attachmentRefs = await this.storePromptImages(
params.prompt
)
let message: ReturnType<typeof createUserMessage>
try {
const content: HarnessContentBlock[] = [
{ type: 'text', text },
...attachmentRefs.map((attachment) => ({
type: 'image' as const,
attachment
}))
]
message = createUserMessage({
content,
source: { kind: 'user' }
})
} catch (error) {
this.releaseAttachments(attachmentRefs)
throw error
}
const stopReason = await new Promise<string>(
(resolve, reject) => {
record.inflight = {
requestId: preparation.requestId,
messageId: message.id,
resolve,
reject,
eventTail: Promise.resolve()
}
try {
record.handle.agent.followup(message)
} catch (error) {
record.inflight = undefined
this.releaseAttachments(attachmentRefs)
reject(error)
return
}
record.attachmentRefs.push(...attachmentRefs)
void record.handle.agent.whenIdle().then(() => {
const current = record.inflight
if (current?.messageId !== message.id) {
return
}
this.flushPendingDelta(params.sessionId)
void current.eventTail.then(() => {
if (record.inflight === current) {
record.inflight = undefined
}
if (current.eventError) {
current.reject(current.eventError)
return
}
if (current.turnError) {
const details = errorChain(
current.turnError
).slice(0, MAX_SUMMARY_CHARACTERS)
current.reject(
RequestError.internalError(
{ details },
`DeepSeek Harness turn failed: ${details}`
)
)
return
}
current.resolve(current.endReason ?? 'cancelled')
})
}, (error) => {
if (record.inflight?.messageId === message.id) {
record.inflight = undefined
}
reject(error)
})
}
)
return {
stopReason:
stopReason === 'cancelled'
? 'cancelled'
: stopReason === 'max_tokens'
? 'max_tokens'
: 'end_turn'
}
},
cancel: async ({ sessionId }) => {
const record = this.sessions.get(sessionId)
record?.handle.agent.cancel({ kind: 'user' })
if (record?.inflight) {
const inflight = record.inflight
if (inflight.pendingDeltaTimer) {
clearTimeout(inflight.pendingDeltaTimer)
}
record.inflight = undefined
inflight.resolve('cancelled')
}
},
closeSession: async ({ sessionId }) => {
await this.releaseSession(sessionId)
},
extMethod: (method, params) =>
this.extensionMethod(method, params),
extNotification: async () => {
throw RequestError.methodNotFound('extension notification')
}
}
}
async extensionMethod(
method: string,
params: Record<string, unknown>
): Promise<Record<string, unknown>> {
if (method === GOODBUDDY_HANDSHAKE) {
const version = params.controlProtocolVersion
if (version !== GOODBUDDY_CONTROL_PROTOCOL_VERSION) {
throw RequestError.invalidParams(
undefined,
'incompatible GoodBuddy Harness control protocol'
)
}
this.handshaken = true
return this.capabilities() as unknown as Record<
string,
unknown
>
}
if (!this.handshaken) {
throw RequestError.invalidParams(
undefined,
'GoodBuddy handshake is required'
)
}
if (method === GOODBUDDY_PREPARE) {
const sessionId = requiredString(params, 'sessionId')
const requestId = requiredString(params, 'requestId')
const record = this.requireSession(sessionId)
if (record.inflight || record.preparation) {
throw RequestError.invalidParams(
undefined,
'session is already prepared or running'
)
}
record.preparation = { requestId }
return { prepared: true }
}
if (method === GOODBUDDY_RELEASE) {
await this.releaseSession(
requiredString(params, 'sessionId')
)
return { released: true }
}
if (method === GOODBUDDY_NATIVE_SNAPSHOT) {
const workspace = params.workspace === undefined
? this.config.workspace
: requiredString(params, 'workspace')
if (!isAbsolute(workspace)) throw RequestError.invalidParams(undefined, 'workspace must be absolute')
return this.nativeSnapshot(workspace)
}
if (method === GOODBUDDY_SHUTDOWN) {
await this.dispose()
return { shutdown: true }
}
throw RequestError.methodNotFound(method)
}
async releaseSession(sessionId: string): Promise<void> {
const record = this.sessions.get(sessionId)
if (!record) {
return
}
this.sessions.delete(sessionId)
record.handle.agent.cancel({ kind: 'user' })
if (record.inflight) {
if (record.inflight.pendingDeltaTimer) {
clearTimeout(record.inflight.pendingDeltaTimer)
}
record.inflight.resolve('cancelled')
record.inflight = undefined
}
try {
await record.handle.dispose()
} finally {
this.releaseAttachments(record.attachmentRefs)
}
}
async dispose(): Promise<void> {
this.disposing ??= (async () => {
this.closed = true
const sessions = [...this.sessions.entries()]
this.sessions.clear()
for (const [, record] of sessions) {
record.handle.agent.cancel({ kind: 'disposed' })
if (record.inflight?.pendingDeltaTimer) {
clearTimeout(record.inflight.pendingDeltaTimer)
}
record.inflight?.resolve('cancelled')
}
await Promise.allSettled(
sessions.map(([, record]) => record.handle.dispose())
)
for (const [, record] of sessions) {
this.releaseAttachments(record.attachmentRefs)
}
})()
await this.disposing
}
}
import { imageToolDescriptionLimit } from '../../shared/image-generation-contracts'