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
@@ -15,6 +15,13 @@ function extension(
}
}
function blockEventLoop(durationMs: number): void {
const deadline = Date.now() + durationMs
while (Date.now() <= deadline) {
// Deliberately model finite synchronous CommonJS/plugin startup work.
}
}
describe('DeepSeek Harness extension loader', () => {
it('loads named Cordis plugin exports and keeps working extensions active', async () => {
const ctx = new Context()
@@ -157,4 +164,91 @@ describe('DeepSeek Harness extension loader', () => {
expect(apply).not.toHaveBeenCalled()
await ctx.fiber.dispose()
})
it('rejects a synchronous import that returns after its budget', async () => {
const ctx = new Context()
const apply = vi.fn()
const result = await loadControlledHarnessExtensions(
ctx,
[extension('slow-import')],
{
activationTimeoutMs: 10,
importModule: async () => {
blockEventLoop(25)
return { apply }
}
}
)
expect(result).toEqual({
loadedIds: [],
failedIds: ['slow-import'],
failures: [
{
id: 'slow-import',
message:
'DeepSeek Harness extension activation timed out'
}
]
})
expect(apply).not.toHaveBeenCalled()
await ctx.fiber.dispose()
})
it('rejects over-budget synchronous apply and loads the next extension', async () => {
const ctx = new Context()
const laterApply = vi.fn()
const result = await loadControlledHarnessExtensions(
ctx,
[extension('slow-apply'), extension('later')],
{
activationTimeoutMs: 10,
totalActivationTimeoutMs: 100,
importModule: async (url) =>
url.includes('slow-apply')
? {
apply() {
blockEventLoop(25)
}
}
: { apply: laterApply }
}
)
expect(result.loadedIds).toEqual(['later'])
expect(result.failedIds).toEqual(['slow-apply'])
expect(result.failures[0]?.message).toBe(
'DeepSeek Harness extension activation timed out'
)
expect(laterApply).toHaveBeenCalledOnce()
await ctx.fiber.dispose()
})
it('times out asynchronous activation and disposes its effects', async () => {
const ctx = new Context()
const cleanup = vi.fn()
const result = await loadControlledHarnessExtensions(
ctx,
[extension('async-slow')],
{
activationTimeoutMs: 10,
importModule: async () => ({
apply(pluginContext: Context) {
pluginContext.effect(() => cleanup)
return new Promise<void>((resolve) =>
setTimeout(resolve, 30)
)
}
})
}
)
expect(result.failedIds).toEqual(['async-slow'])
expect(result.failures[0]?.message).toContain('timed out')
expect(cleanup).toHaveBeenCalledOnce()
await ctx.fiber.dispose()
})
})