chore: prepare GoodBuddy 0.8.2
Cross-platform packages / Validate source (push) Waiting to run
Cross-platform packages / ${{ matrix.platform }} ${{ matrix.arch }} (arm64, macos, macos-15) (push) Blocked by required conditions
Cross-platform packages / ${{ matrix.platform }} ${{ matrix.arch }} (arm64, windows, windows-2025) (push) Blocked by required conditions
Cross-platform packages / ${{ matrix.platform }} ${{ matrix.arch }} (x64, linux, ubuntu-24.04) (push) Blocked by required conditions
Cross-platform packages / ${{ matrix.platform }} ${{ matrix.arch }} (x64, macos, macos-15-intel) (push) Blocked by required conditions
Cross-platform packages / ${{ matrix.platform }} ${{ matrix.arch }} (x64, windows, windows-2025) (push) Blocked by required conditions
Cross-platform packages / Publish GitHub Release (push) Blocked by required conditions
Cross-platform packages / ${{ matrix.platform }} ${{ matrix.arch }} (arm64, linux, ubuntu-24.04-arm) (push) Blocked by required conditions

This commit is contained in:
lofyer
2026-08-06 22:47:13 +08:00
parent 8d00e6371d
commit b8fc7bc86e
114 changed files with 22916 additions and 1560 deletions
@@ -0,0 +1,232 @@
import type {
AgentExecutionRequest,
AgentRuntime,
RuntimeEvent
} from './runtime'
import { SelectedRuntimeManager } from './selected-runtime-manager'
import { describe, expect, it, vi } from 'vitest'
function runtime() {
const releaseConversation = vi.fn(async () => undefined)
const dispose = vi.fn(async () => undefined)
const testConnection = vi.fn(async () => ({
id: 'model' as const,
label: 'model',
available: true,
supportsToolExecution: true,
detail: 'ready'
}))
const value: AgentRuntime = {
runtimeId: 'model',
requiresToolApproval: false,
supportsToolExecution: true,
capability: 'chat',
getStatus: vi.fn(async () => ({
id: 'model' as const,
label: 'model',
available: true,
supportsToolExecution: true,
detail: 'ready'
})),
testConnection,
async *run(
request: AgentExecutionRequest
): AsyncGenerator<RuntimeEvent, void, void> {
yield {
requestId: request.requestId,
type: 'done'
}
},
releaseConversation,
dispose
}
return { value, releaseConversation, dispose, testConnection }
}
describe('SelectedRuntimeManager', () => {
it('caches one controller per runtime and profile selection', async () => {
const first = runtime()
const second = runtime()
const create = vi
.fn()
.mockResolvedValueOnce(first.value)
.mockResolvedValueOnce(second.value)
const manager = new SelectedRuntimeManager(create)
const [left, right] = await Promise.all([
manager.getRuntime({
provider: 'model',
profileId: '00000000-0000-4000-8000-000000000001'
}),
manager.getRuntime({
provider: 'model',
profileId: '00000000-0000-4000-8000-000000000001'
})
])
expect(left).toBe(right)
expect(create).toHaveBeenCalledOnce()
await manager.getRuntime({ provider: 'continue' })
expect(create).toHaveBeenCalledTimes(2)
await manager.dispose()
})
it('retires cached runtimes when settings change', async () => {
const first = runtime()
const second = runtime()
const create = vi
.fn()
.mockResolvedValueOnce(first.value)
.mockResolvedValueOnce(second.value)
const manager = new SelectedRuntimeManager(create)
const selection = {
provider: 'model' as const,
profileId: '00000000-0000-4000-8000-000000000001'
}
await manager.getRuntime(selection)
await manager.reset()
expect(first.dispose).toHaveBeenCalledOnce()
await manager.getRuntime(selection)
expect(create).toHaveBeenCalledTimes(2)
await manager.dispose()
expect(second.dispose).toHaveBeenCalledOnce()
})
it('disposes a connection-test runtime without caching it', async () => {
const tested = runtime()
const cached = runtime()
const create = vi
.fn()
.mockResolvedValueOnce(tested.value)
.mockResolvedValueOnce(cached.value)
const manager = new SelectedRuntimeManager(create)
const selection = { provider: 'opencode' as const }
await expect(manager.testStatus(selection)).resolves.toMatchObject({
available: true
})
expect(tested.testConnection).toHaveBeenCalledOnce()
expect(tested.dispose).toHaveBeenCalledOnce()
await manager.getRuntime(selection)
expect(create).toHaveBeenCalledTimes(2)
await manager.dispose()
})
it('waits for a pending connection-test runtime during shutdown', async () => {
let finishCreate!: (value: AgentRuntime) => void
const pendingCreate = new Promise<AgentRuntime>((resolve) => {
finishCreate = resolve
})
const tested = runtime()
const manager = new SelectedRuntimeManager(
vi.fn(async () => pendingCreate)
)
const test = manager.testStatus({ provider: 'opencode' })
const disposal = manager.dispose()
finishCreate(tested.value)
await expect(test).rejects.toThrow('正在关闭')
await disposal
expect(tested.testConnection).not.toHaveBeenCalled()
expect(tested.dispose).toHaveBeenCalledOnce()
})
it('lets active work finish while settings changes retire its runtime', async () => {
let markStarted!: () => void
let finishRun!: () => void
const started = new Promise<void>((resolve) => {
markStarted = resolve
})
const finish = new Promise<void>((resolve) => {
finishRun = resolve
})
const active = runtime()
active.value.run = async function* (
request: AgentExecutionRequest
): AsyncGenerator<RuntimeEvent, void, void> {
markStarted()
await finish
yield {
requestId: request.requestId,
type: 'done'
}
}
const manager = new SelectedRuntimeManager(
vi.fn(async () => active.value)
)
const selection = {
provider: 'model' as const,
profileId: '00000000-0000-4000-8000-000000000001'
}
const controller = await manager.getRuntime(selection)
const stream = controller.run(
{
requestId: '00000000-0000-4000-8000-000000000011',
conversationId: 'conversation-one',
prompt: 'keep working',
workMode: 'ask'
},
new AbortController().signal
)
const firstEvent = stream.next()
await started
await manager.reset()
expect(active.dispose).not.toHaveBeenCalled()
await expect(
controller
.run(
{
requestId: '00000000-0000-4000-8000-000000000012',
conversationId: 'conversation-two',
prompt: 'new work',
workMode: 'ask'
},
new AbortController().signal
)
.next()
).rejects.toThrow('正在关闭')
finishRun()
await expect(firstEvent).resolves.toEqual(
expect.objectContaining({
value: expect.objectContaining({ type: 'done' }),
done: false
})
)
await stream.next()
await vi.waitFor(() =>
expect(active.dispose).toHaveBeenCalledOnce()
)
await manager.dispose()
})
it('releases a conversation from every selected runtime', async () => {
const first = runtime()
const second = runtime()
const create = vi
.fn()
.mockResolvedValueOnce(first.value)
.mockResolvedValueOnce(second.value)
const manager = new SelectedRuntimeManager(create)
await manager.getRuntime({
provider: 'model',
profileId: '00000000-0000-4000-8000-000000000001'
})
await manager.getRuntime({ provider: 'opencode' })
await manager.releaseConversation('conversation-one')
expect(first.releaseConversation).toHaveBeenCalledWith(
'conversation-one'
)
expect(second.releaseConversation).toHaveBeenCalledWith(
'conversation-one'
)
await manager.dispose()
})
})