OpenCode and Continue customization was previously planned but unavailable, and Runtime-native capabilities were not represented consistently across providers. GoodBuddy now provides secure Main-owned customization settings, truthful native inventories, OpenCode Agents and Commands, Continue Rules and Prompts, DSH Web Search/Fetch, MCP Prompt and Resource metadata, and manual compaction where supported. Native capabilities are presented in eleven accessible tabs with Tools separated from Commands, LSP, and Formatters. Tool source and Ask/Execute availability are explicit, external OpenCode remains connection-only, Continue reports unsupported static tool discovery instead of advertising unreachable Skills, and disposable inventory probes avoid retaining background runtimes. Ask remains read-only at the Runtime boundary, Execute keeps the existing authorization controls, and credentials remain confined to Main. Release note: 新增 OpenCode、Continue 与 DeepSeek Harness 的 Runtime 原生定制与真实能力清单;工具来源、Ask/Execute 可用性、上下文压缩和 MCP 元数据现在可清晰查看,同时继续保持 Main 进程凭据保护与现有权限边界。
234 lines
6.2 KiB
TypeScript
234 lines
6.2 KiB
TypeScript
import {
|
|
agentRuntimeSelectionKey,
|
|
type AgentRuntimeSelection
|
|
} from '../../shared/runtime-selection-contracts'
|
|
import type {
|
|
AgentRuntimeStatus,
|
|
RuntimeConversationCompactInput,
|
|
RuntimeNativeSnapshot
|
|
} from '../../shared/contracts'
|
|
import type {
|
|
AgentRuntime,
|
|
RuntimeConversationCompactOutcome
|
|
} from './runtime'
|
|
import { AgentRuntimeController } from './runtime-controller'
|
|
|
|
export type SelectedRuntimeResolver = {
|
|
getRuntime(
|
|
selection: AgentRuntimeSelection,
|
|
workspacePath?: string
|
|
): Promise<AgentRuntime>
|
|
getStatus(
|
|
selection: AgentRuntimeSelection
|
|
): Promise<AgentRuntimeStatus>
|
|
testStatus(
|
|
selection: AgentRuntimeSelection
|
|
): Promise<AgentRuntimeStatus>
|
|
getNativeSnapshot(
|
|
selection: AgentRuntimeSelection,
|
|
workspacePath?: string
|
|
): Promise<RuntimeNativeSnapshot>
|
|
compactConversation(
|
|
request: RuntimeConversationCompactInput,
|
|
workspacePath: string | undefined,
|
|
signal: AbortSignal
|
|
): Promise<RuntimeConversationCompactOutcome>
|
|
releaseConversation(conversationId: string): Promise<void>
|
|
reset?(): Promise<void>
|
|
}
|
|
|
|
export class SelectedRuntimeManager implements SelectedRuntimeResolver {
|
|
private readonly entries = new Map<
|
|
string,
|
|
Promise<AgentRuntimeController>
|
|
>()
|
|
private disposed = false
|
|
private readonly retiring = new Set<Promise<void>>()
|
|
private readonly tests = new Set<Promise<AgentRuntimeStatus>>()
|
|
private readonly snapshots = new Set<
|
|
Promise<RuntimeNativeSnapshot>
|
|
>()
|
|
|
|
constructor(
|
|
private readonly createRuntime: (
|
|
selection: AgentRuntimeSelection,
|
|
workspacePath?: string
|
|
) => Promise<AgentRuntime>
|
|
) {}
|
|
|
|
async getRuntime(
|
|
selection: AgentRuntimeSelection,
|
|
workspacePath?: string
|
|
): Promise<AgentRuntimeController> {
|
|
if (this.disposed) {
|
|
throw new Error('Agent Runtime 正在关闭')
|
|
}
|
|
const key = JSON.stringify([
|
|
agentRuntimeSelectionKey(selection),
|
|
workspacePath ?? ''
|
|
])
|
|
const existing = this.entries.get(key)
|
|
if (existing) {
|
|
return existing
|
|
}
|
|
const operation = this.createRuntime(selection, workspacePath).then(
|
|
async (runtime) => {
|
|
if (this.disposed || this.entries.get(key) !== operation) {
|
|
await runtime.dispose()
|
|
throw new Error('Runtime 设置已更改,请重新选择')
|
|
}
|
|
return new AgentRuntimeController(runtime)
|
|
}
|
|
)
|
|
this.entries.set(key, operation)
|
|
try {
|
|
return await operation
|
|
} catch (error) {
|
|
if (this.entries.get(key) === operation) {
|
|
this.entries.delete(key)
|
|
}
|
|
throw error
|
|
}
|
|
}
|
|
|
|
async getStatus(
|
|
selection: AgentRuntimeSelection
|
|
): Promise<AgentRuntimeStatus> {
|
|
return (await this.getRuntime(selection)).getStatus()
|
|
}
|
|
|
|
async testStatus(
|
|
selection: AgentRuntimeSelection
|
|
): Promise<AgentRuntimeStatus> {
|
|
if (this.disposed) {
|
|
throw new Error('Agent Runtime 正在关闭')
|
|
}
|
|
const operation = this.runConnectionTest(selection)
|
|
this.tests.add(operation)
|
|
try {
|
|
return await operation
|
|
} finally {
|
|
this.tests.delete(operation)
|
|
}
|
|
}
|
|
|
|
async getNativeSnapshot(
|
|
selection: AgentRuntimeSelection,
|
|
workspacePath?: string
|
|
): Promise<RuntimeNativeSnapshot> {
|
|
if (this.disposed) {
|
|
throw new Error('Agent Runtime 正在关闭')
|
|
}
|
|
const operation = this.runNativeSnapshot(
|
|
selection,
|
|
workspacePath
|
|
)
|
|
this.snapshots.add(operation)
|
|
try {
|
|
return await operation
|
|
} finally {
|
|
this.snapshots.delete(operation)
|
|
}
|
|
}
|
|
|
|
async compactConversation(
|
|
request: RuntimeConversationCompactInput,
|
|
workspacePath: string | undefined,
|
|
signal: AbortSignal
|
|
): Promise<RuntimeConversationCompactOutcome> {
|
|
const runtime = await this.getRuntime(
|
|
request.runtimeSelection,
|
|
workspacePath
|
|
)
|
|
return runtime.compactConversation(request, signal)
|
|
}
|
|
|
|
async releaseConversation(conversationId: string): Promise<void> {
|
|
const controllers = await Promise.allSettled([
|
|
...this.entries.values()
|
|
])
|
|
await Promise.allSettled(
|
|
controllers.flatMap((result) =>
|
|
result.status === 'fulfilled'
|
|
? [result.value.releaseConversation(conversationId)]
|
|
: []
|
|
)
|
|
)
|
|
}
|
|
|
|
async reset(): Promise<void> {
|
|
const entries = [...this.entries.values()]
|
|
this.entries.clear()
|
|
await Promise.allSettled(
|
|
entries.map((entry) => this.startRetiring(entry, false))
|
|
)
|
|
}
|
|
|
|
async dispose(): Promise<void> {
|
|
this.disposed = true
|
|
const entries = [...this.entries.values()]
|
|
this.entries.clear()
|
|
await Promise.allSettled(
|
|
entries.map((entry) => this.startRetiring(entry, true))
|
|
)
|
|
await Promise.allSettled([...this.tests])
|
|
await Promise.allSettled([...this.snapshots])
|
|
await Promise.allSettled([...this.retiring])
|
|
}
|
|
|
|
private async runConnectionTest(
|
|
selection: AgentRuntimeSelection
|
|
): Promise<AgentRuntimeStatus> {
|
|
const runtime = await this.createRuntime(selection)
|
|
try {
|
|
if (this.disposed) {
|
|
throw new Error('Agent Runtime 正在关闭')
|
|
}
|
|
return (
|
|
(await runtime.testConnection?.()) ??
|
|
(await runtime.getStatus())
|
|
)
|
|
} finally {
|
|
await runtime.dispose()
|
|
}
|
|
}
|
|
|
|
private async runNativeSnapshot(
|
|
selection: AgentRuntimeSelection,
|
|
workspacePath?: string
|
|
): Promise<RuntimeNativeSnapshot> {
|
|
const runtime = await this.createRuntime(selection, workspacePath)
|
|
try {
|
|
if (this.disposed) {
|
|
throw new Error('Agent Runtime 正在关闭')
|
|
}
|
|
if (!runtime.getNativeSnapshot) {
|
|
throw new Error('当前 Runtime 不支持原生能力清单')
|
|
}
|
|
return await runtime.getNativeSnapshot()
|
|
} finally {
|
|
await runtime.dispose()
|
|
}
|
|
}
|
|
|
|
private async startRetiring(
|
|
entry: Promise<AgentRuntimeController>,
|
|
waitForDisposal: boolean
|
|
): Promise<void> {
|
|
try {
|
|
const controller = await entry
|
|
const disposal = controller.dispose()
|
|
this.retiring.add(disposal)
|
|
void disposal.then(
|
|
() => this.retiring.delete(disposal),
|
|
() => this.retiring.delete(disposal)
|
|
)
|
|
if (waitForDisposal) {
|
|
await disposal
|
|
}
|
|
} catch {
|
|
return
|
|
}
|
|
}
|
|
}
|