feat: expand multi-runtime workflows for 0.10.0

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

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

Release note: GoodBuddy 0.10.0 重点完善多 Runtime 工作流,统一 OpenCode、Continue 与 DeepSeek Harness 的能力、MCP、插件和上下文管理,并提升长对话、多会话与任务通知的连贯性。
This commit is contained in:
mesalogo
2026-08-17 12:57:12 +08:00
parent f5ee9b2198
commit cbbe30896e
50 changed files with 4077 additions and 605 deletions
+129 -9
View File
@@ -2984,7 +2984,7 @@ describe('OpenCodeRuntime native customization', () => {
await runtime.dispose()
})
it('compacts an existing managed session through the v2 API', async () => {
it('compacts a managed session through the supported native API', async () => {
const setup = runClient([
{
id: 'idle',
@@ -2993,19 +2993,74 @@ describe('OpenCodeRuntime native customization', () => {
}
])
const context = vi.fn().mockResolvedValue({
data: { data: [] }
data: {
data: [
{
type: 'assistant',
model: {
providerID: 'anthropic',
id: 'claude-sonnet'
}
}
]
}
})
const compact = vi.fn().mockResolvedValue({
data: undefined,
const summarize = vi.fn().mockResolvedValue({
data: true,
error: undefined
})
Object.assign(setup.client, {
v2: {
session: { context, compact }
}
session: { context }
},
session: { ...setup.client.session, summarize }
})
const runtime = embeddedRuntime(setup.client)
await collectRun(runtime)
vi.mocked(setup.event.subscribe).mockResolvedValueOnce({
stream: (async function* () {
yield {
type: 'message.updated',
properties: {
sessionID: 'session-1',
info: {
id: 'compaction-message',
sessionID: 'session-1',
role: 'assistant',
time: {
created: 1,
completed: 2
},
parentID: 'compaction-parent',
modelID: 'claude-sonnet',
providerID: 'anthropic',
mode: 'compaction',
agent: 'build',
path: {
cwd: process.cwd(),
root: process.cwd()
},
cost: 0,
tokens: {
input: 100,
output: 20,
reasoning: 0,
cache: {
read: 30,
write: 4
},
total: 124
},
finish: 'stop'
}
}
}
yield {
type: 'session.idle',
properties: { sessionID: 'session-1' }
}
})()
} as never)
const signal = new AbortController().signal
await expect(
@@ -3025,19 +3080,84 @@ describe('OpenCodeRuntime native customization', () => {
strategy: 'native',
compacted: true,
detail: 'OpenCode 已完成原生上下文压缩'
}
},
usageEvents: [
{
requestId: '3f496642-f47d-4e0a-8944-a32c77b0d6ef',
type: 'model-usage',
callId: 'compaction-message',
runtime: 'opencode',
provider: 'anthropic',
model: 'claude-sonnet',
inputTokens: 100,
outputTokens: 20,
cacheReadTokens: 30,
cacheWriteTokens: 4,
reportedTotalTokens: 124
}
]
})
expect(context).toHaveBeenCalledWith(
{ sessionID: 'session-1' },
{ signal }
)
expect(compact).toHaveBeenCalledWith(
{ sessionID: 'session-1' },
expect(summarize).toHaveBeenCalledWith(
{
sessionID: 'session-1',
directory: process.cwd(),
providerID: 'anthropic',
modelID: 'claude-sonnet',
auto: false
},
{ signal }
)
await runtime.dispose()
})
it('reports when a managed session has no model to compact with', async () => {
const setup = runClient([
{
id: 'idle',
type: 'session.idle',
properties: { sessionID: 'session-1' }
}
])
const context = vi.fn().mockResolvedValue({
data: { data: [] }
})
const summarize = vi.fn()
Object.assign(setup.client, {
v2: {
session: { context }
},
session: { ...setup.client.session, summarize }
})
const runtime = embeddedRuntime(setup.client)
await collectRun(runtime)
await expect(
runtime.compactConversation(
{
requestId: '3f496642-f47d-4e0a-8944-a32c77b0d6ef',
conversationId: 'conversation-1',
runtimeSelection: { provider: 'opencode' },
history: [],
historyMessageIds: []
},
new AbortController().signal
)
).resolves.toEqual({
result: {
provider: 'opencode',
strategy: 'native',
compacted: false,
detail: '当前 OpenCode 会话尚无可用于压缩的模型记录'
}
})
expect(summarize).not.toHaveBeenCalled()
await runtime.dispose()
})
it('reports when no managed OpenCode session can be compacted', async () => {
const runtime = new OpenCodeRuntime(options())