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
@@ -0,0 +1,308 @@
import { lookup as dnsLookup } from 'node:dns/promises'
import { request as httpsRequest } from 'node:https'
import { z } from 'zod'
import { isPublicAddress } from '../knowledge/url-importer'
const remoteTaskSchema = z
.object({
id: z.string().uuid(),
projectId: z.string().uuid().optional(),
title: z.string().trim().min(1).max(120),
prompt: z.string().trim().min(1).max(100_000),
workMode: z.enum(['ask', 'plan'])
})
.strict()
export type RemoteDelegationTask = z.infer<typeof remoteTaskSchema>
type RemoteResult = {
status: 'completed' | 'failed'
output?: string
error?: string
}
type ResolvedAddress = {
address: string
family: number
}
type RemoteTransport = (
url: URL,
address: ResolvedAddress,
token: string,
method: 'GET' | 'POST',
signal: AbortSignal,
body?: string
) => Promise<{ status: number; body: string }>
type RemoteDelegationOptions = {
endpoint: string
token: string
onTask: (task: RemoteDelegationTask) => Promise<RemoteResult>
lookup?: (hostname: string) => Promise<ResolvedAddress[]>
transport?: RemoteTransport
intervalMs?: number
outbox?: {
listPending: () => Array<{ taskId: string; result: RemoteResult }>
getStatus: (
taskId: string
) => 'pending' | 'delivered' | undefined
save: (taskId: string, result: RemoteResult) => void
markDelivered: (taskId: string) => void
}
}
function normalizeEndpoint(input: string): URL {
const url = new URL(input.trim())
if (
url.protocol !== 'https:' ||
url.username ||
url.password ||
url.search ||
url.hash ||
(url.pathname !== '' && url.pathname !== '/')
) {
throw new Error('远程委派地址必须是无凭据和路径的 HTTPS origin')
}
return url
}
async function defaultLookup(hostname: string): Promise<ResolvedAddress[]> {
return dnsLookup(hostname, { all: true, verbatim: true })
}
function defaultTransport(
url: URL,
address: ResolvedAddress,
token: string,
method: 'GET' | 'POST',
signal: AbortSignal,
body?: string
): Promise<{ status: number; body: string }> {
return new Promise((resolve, reject) => {
let settled = false
const fail = (error: Error): void => {
if (settled) {
return
}
settled = true
reject(error)
}
const request = httpsRequest(
url,
{
method,
headers: {
accept: 'application/json',
authorization: `Bearer ${token}`,
'content-type': 'application/json',
...(body
? { 'content-length': String(Buffer.byteLength(body)) }
: {})
},
lookup: (_hostname, _options, callback) => {
callback(null, address.address, address.family)
},
servername: url.hostname,
signal
},
(response) => {
const chunks: Buffer[] = []
let bytes = 0
response.on('data', (chunk: Buffer) => {
bytes += chunk.byteLength
if (bytes > 1024 * 1024) {
request.destroy(new Error('远程委派响应超过 1MB 限制'))
return
}
chunks.push(Buffer.from(chunk))
})
response.on('end', () => {
if (settled) {
return
}
settled = true
resolve({
status: response.statusCode ?? 0,
body: Buffer.concat(chunks).toString('utf8')
})
})
response.on('aborted', () => {
fail(new Error('远程委派响应意外中断'))
})
response.on('error', fail)
}
)
request.setTimeout(15_000, () => {
request.destroy(new Error('远程委派请求超时'))
})
request.on('error', fail)
request.end(body)
})
}
export class RemoteDelegationService {
private readonly endpoint: URL
private readonly lookup: NonNullable<RemoteDelegationOptions['lookup']>
private readonly transport: RemoteTransport
private readonly deliveredIds = new Set<string>()
private readonly pendingResults = new Map<string, RemoteResult>()
private interval?: NodeJS.Timeout
private activeRequest?: AbortController
private polling = false
constructor(private readonly options: RemoteDelegationOptions) {
this.endpoint = normalizeEndpoint(options.endpoint)
if (!options.token.trim() || options.token.length > 8_192) {
throw new Error('远程委派 Token 无效')
}
this.lookup = options.lookup ?? defaultLookup
this.transport = options.transport ?? defaultTransport
}
start(): void {
if (this.interval) {
return
}
this.interval = setInterval(
() => void this.pollOnce().catch(() => undefined),
this.options.intervalMs ?? 60_000
)
void this.pollOnce().catch(() => undefined)
}
stop(): void {
if (this.interval) {
clearInterval(this.interval)
this.interval = undefined
}
this.activeRequest?.abort()
}
async pollOnce(): Promise<void> {
if (this.polling) {
return
}
this.polling = true
const controller = new AbortController()
this.activeRequest = controller
try {
const address = await this.resolvePublicAddress()
const durablePending = this.options.outbox?.listPending()[0]
const memoryPending = this.pendingResults.entries().next().value
const pending = durablePending
? ([durablePending.taskId, durablePending.result] as const)
: memoryPending
if (pending) {
await this.deliverResult(
pending[0],
pending[1],
address,
controller.signal
)
this.markDelivered(pending[0])
}
const nextUrl = new URL('/goodbuddy/tasks/next', this.endpoint)
const response = await this.transport(
nextUrl,
address,
this.options.token,
'GET',
controller.signal
)
if (response.status === 204) {
return
}
if (response.status !== 200) {
throw new Error(`远程委派服务返回 HTTP ${response.status}`)
}
const task = remoteTaskSchema.parse(JSON.parse(response.body))
if (
this.deliveredIds.has(task.id) ||
this.options.outbox?.getStatus(task.id) === 'delivered'
) {
return
}
const existingResult =
this.options.outbox
?.listPending()
.find((item) => item.taskId === task.id)?.result ??
this.pendingResults.get(task.id)
let result: RemoteResult
if (existingResult) {
result = existingResult
} else {
try {
result = await this.options.onTask(task)
} catch (error) {
result = {
status: 'failed',
error: error instanceof Error ? error.message : '远程任务执行失败'
}
}
if (this.options.outbox) {
this.options.outbox.save(task.id, result)
} else {
this.pendingResults.set(task.id, result)
}
}
await this.deliverResult(task.id, result, address, controller.signal)
this.markDelivered(task.id)
} finally {
if (this.activeRequest === controller) {
this.activeRequest = undefined
}
this.polling = false
}
}
private async deliverResult(
taskId: string,
result: RemoteResult,
address: ResolvedAddress,
signal: AbortSignal
): Promise<void> {
const resultUrl = new URL(
`/goodbuddy/tasks/${encodeURIComponent(taskId)}/result`,
this.endpoint
)
const response = await this.transport(
resultUrl,
address,
this.options.token,
'POST',
signal,
JSON.stringify({
status: result.status,
output: result.output?.slice(0, 1_000_000),
error: result.error?.slice(0, 2_000)
})
)
if (response.status < 200 || response.status >= 300) {
throw new Error(`远程委派结果提交失败(HTTP ${response.status}`)
}
}
private markDelivered(taskId: string): void {
this.pendingResults.delete(taskId)
this.options.outbox?.markDelivered(taskId)
this.deliveredIds.add(taskId)
if (this.deliveredIds.size > 1_000) {
const oldest = this.deliveredIds.values().next().value
if (oldest) {
this.deliveredIds.delete(oldest)
}
}
}
private async resolvePublicAddress(): Promise<ResolvedAddress> {
const addresses = await this.lookup(this.endpoint.hostname)
const address = addresses.find((candidate) =>
isPublicAddress(candidate.address)
)
if (!address || addresses.some((candidate) => !isPublicAddress(candidate.address))) {
throw new Error('远程委派地址解析到私有或不安全网络')
}
return address
}
}