feat: add persistent desktop assistant workspace

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
This commit is contained in:
lofyer
2026-07-31 22:33:03 +08:00
co-authored by factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
parent 698a15ad14
commit 6ef1795b81
101 changed files with 31866 additions and 1176 deletions
+47 -7
View File
@@ -4,7 +4,7 @@ import type {
AgentRequest,
AgentRuntimeStatus
} from '../../shared/contracts'
import type { AgentRuntime } from './runtime'
import type { AgentRuntime, RuntimeAuthorizer } from './runtime'
import { AgentRuntimeController } from './runtime-controller'
class TestRuntime implements AgentRuntime {
@@ -15,7 +15,8 @@ class TestRuntime implements AgentRuntime {
constructor(
private readonly delayed = false,
readonly requiresToolApproval = false
readonly requiresToolApproval = false,
private readonly invokeToolAuthorization = false
) {
this.started = new Promise((resolve) => {
this.markStarted = resolve
@@ -24,7 +25,7 @@ class TestRuntime implements AgentRuntime {
getStatus(): Promise<AgentRuntimeStatus> {
return Promise.resolve({
id: 'demo',
id: 'model',
label: 'Test',
available: true,
detail: 'Test runtime'
@@ -32,9 +33,21 @@ class TestRuntime implements AgentRuntime {
}
async *run(
request: AgentRequest
request: AgentRequest,
_signal: AbortSignal,
authorize?: RuntimeAuthorizer
): AsyncGenerator<AgentEvent, void, void> {
this.markStarted()
if (this.invokeToolAuthorization && authorize) {
const decision = await authorize({
scopeKey: 'test:tool',
title: 'Test tool',
description: 'Test tool request'
})
if (decision === 'deny') {
throw new Error('tool denied')
}
}
if (this.delayed) {
await new Promise<void>((resolve) => {
this.release = resolve
@@ -57,19 +70,24 @@ describe('AgentRuntimeController', () => {
const previous = new TestRuntime(true, true)
const next = new TestRuntime()
const controller = new AgentRuntimeController(previous)
const authorize = vi.fn(async () => {})
const authorize = vi.fn(async () => 'once' as const)
const approvedStream = controller.run(
{
requestId: '1c608898-ecb7-4081-8174-2b6a52f53b08',
conversationId: 'conversation-2',
prompt: 'test'
prompt: 'test',
workMode: 'execute'
},
new AbortController().signal,
authorize
)
const pendingEvent = approvedStream.next()
await previous.started
expect(authorize).toHaveBeenCalledWith(true)
expect(authorize).toHaveBeenCalledWith(
expect.objectContaining({
scopeKey: 'runtime:whole-run'
})
)
const replacement = controller.replace(next)
previous.finish()
@@ -81,4 +99,26 @@ describe('AgentRuntimeController', () => {
label: 'Test'
})
})
it.each(['ask', 'plan'] as const)(
'denies tool authorization in %s mode without prompting the user',
async (workMode) => {
const runtime = new TestRuntime(false, false, true)
const controller = new AgentRuntimeController(runtime)
const authorize = vi.fn(async () => 'once' as const)
const stream = controller.run(
{
requestId: '1c608898-ecb7-4081-8174-2b6a52f53b09',
conversationId: 'conversation-3',
prompt: 'test',
workMode
},
new AbortController().signal,
authorize
)
await expect(stream.next()).rejects.toThrow('tool denied')
expect(authorize).not.toHaveBeenCalled()
}
)
})