feat: add secure multi-runtime controls

Make agent backends configurable and governable with encrypted credentials, explicit context sharing, and tool approval boundaries.

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
This commit is contained in:
lofyer
2026-07-30 10:52:35 +08:00
co-authored by factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
parent 1b6178b841
commit 698a15ad14
27 changed files with 2465 additions and 91 deletions
+84
View File
@@ -0,0 +1,84 @@
import { describe, expect, it, vi } from 'vitest'
import type {
AgentEvent,
AgentRequest,
AgentRuntimeStatus
} from '../../shared/contracts'
import type { AgentRuntime } from './runtime'
import { AgentRuntimeController } from './runtime-controller'
class TestRuntime implements AgentRuntime {
readonly dispose = vi.fn(async () => {})
readonly started: Promise<void>
private release?: () => void
private markStarted!: () => void
constructor(
private readonly delayed = false,
readonly requiresToolApproval = false
) {
this.started = new Promise((resolve) => {
this.markStarted = resolve
})
}
getStatus(): Promise<AgentRuntimeStatus> {
return Promise.resolve({
id: 'demo',
label: 'Test',
available: true,
detail: 'Test runtime'
})
}
async *run(
request: AgentRequest
): AsyncGenerator<AgentEvent, void, void> {
this.markStarted()
if (this.delayed) {
await new Promise<void>((resolve) => {
this.release = resolve
})
}
yield {
requestId: request.requestId,
type: 'text',
delta: 'old runtime event'
}
}
finish(): void {
this.release?.()
}
}
describe('AgentRuntimeController', () => {
it('suppresses retired runtime events and disposes it after requests exit', async () => {
const previous = new TestRuntime(true, true)
const next = new TestRuntime()
const controller = new AgentRuntimeController(previous)
const authorize = vi.fn(async () => {})
const approvedStream = controller.run(
{
requestId: '1c608898-ecb7-4081-8174-2b6a52f53b08',
conversationId: 'conversation-2',
prompt: 'test'
},
new AbortController().signal,
authorize
)
const pendingEvent = approvedStream.next()
await previous.started
expect(authorize).toHaveBeenCalledWith(true)
const replacement = controller.replace(next)
previous.finish()
await expect(pendingEvent).resolves.toMatchObject({ done: true })
await replacement
expect(previous.dispose).toHaveBeenCalledOnce()
await expect(controller.getStatus()).resolves.toMatchObject({
label: 'Test'
})
})
})