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:
co-authored by
factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
parent
1b6178b841
commit
698a15ad14
+95
-10
@@ -2,17 +2,35 @@ import { app, BrowserWindow, ipcMain } from 'electron'
|
||||
import { z } from 'zod'
|
||||
import {
|
||||
agentRequestSchema,
|
||||
runtimeSettingsInputSchema,
|
||||
type AgentEvent,
|
||||
type AppInfo
|
||||
type AppInfo,
|
||||
type RuntimeSettings
|
||||
} from '../shared/contracts'
|
||||
import { ipcChannels } from '../shared/ipc-channels'
|
||||
import type { AgentRuntime } from './agent/runtime'
|
||||
import type { ContextManager } from './context-manager'
|
||||
import type { RuntimeSettingsStore } from './runtime-settings-store'
|
||||
import type { ToolApprovalBroker } from './tool-approval-broker'
|
||||
import { showWindow } from './window'
|
||||
|
||||
const requestIdSchema = z.string().uuid()
|
||||
const approvalResponseSchema = z
|
||||
.object({
|
||||
approvalId: z.string().uuid(),
|
||||
approved: z.boolean()
|
||||
})
|
||||
.strict()
|
||||
|
||||
function assertTrustedSender(event: Electron.IpcMainInvokeEvent, window: BrowserWindow): void {
|
||||
if (event.sender !== window.webContents) {
|
||||
function assertTrustedSender(
|
||||
event: Electron.IpcMainInvokeEvent,
|
||||
window: BrowserWindow
|
||||
): void {
|
||||
if (
|
||||
event.sender !== window.webContents ||
|
||||
event.senderFrame !== window.webContents.mainFrame ||
|
||||
event.senderFrame.url !== window.webContents.getURL()
|
||||
) {
|
||||
throw new Error('拒绝来自未知窗口的 IPC 请求')
|
||||
}
|
||||
}
|
||||
@@ -20,7 +38,12 @@ function assertTrustedSender(event: Electron.IpcMainInvokeEvent, window: Browser
|
||||
export function registerIpcHandlers(
|
||||
window: BrowserWindow,
|
||||
runtime: AgentRuntime,
|
||||
shortcut: string
|
||||
shortcut: string,
|
||||
settingsStore: RuntimeSettingsStore,
|
||||
contextManager: ContextManager,
|
||||
approvalBroker: ToolApprovalBroker,
|
||||
defaultWorkspace: string,
|
||||
onRuntimeSettingsChanged: () => Promise<void>
|
||||
): () => void {
|
||||
const activeRequests = new Map<string, AbortController>()
|
||||
const channels = Object.values(ipcChannels).filter(
|
||||
@@ -33,6 +56,13 @@ export function registerIpcHandlers(
|
||||
ipcMain.removeHandler(channel)
|
||||
}
|
||||
|
||||
const abortActiveRequests = (reason: string): void => {
|
||||
for (const controller of activeRequests.values()) {
|
||||
controller.abort(new Error(reason))
|
||||
}
|
||||
activeRequests.clear()
|
||||
}
|
||||
|
||||
ipcMain.handle(ipcChannels.appInfo, (event): AppInfo => {
|
||||
assertTrustedSender(event, window)
|
||||
return {
|
||||
@@ -61,7 +91,7 @@ export function registerIpcHandlers(
|
||||
|
||||
ipcMain.handle(ipcChannels.agentRun, (event, input: unknown) => {
|
||||
assertTrustedSender(event, window)
|
||||
const request = agentRequestSchema.parse(input)
|
||||
const request = contextManager.enrichRequest(agentRequestSchema.parse(input))
|
||||
if (activeRequests.has(request.requestId)) {
|
||||
throw new Error('请求正在执行')
|
||||
}
|
||||
@@ -73,7 +103,27 @@ export function registerIpcHandlers(
|
||||
try {
|
||||
for await (const agentEvent of runtime.run(
|
||||
request,
|
||||
controller.signal
|
||||
controller.signal,
|
||||
async (requiresToolApproval) => {
|
||||
if (!requiresToolApproval) {
|
||||
return
|
||||
}
|
||||
const settings = await settingsStore.getResolvedSettings()
|
||||
await approvalBroker.request(
|
||||
settings.toolApproval,
|
||||
request.requestId,
|
||||
defaultWorkspace,
|
||||
controller.signal,
|
||||
(approvalEvent) => {
|
||||
if (!window.isDestroyed()) {
|
||||
window.webContents.send(
|
||||
ipcChannels.agentEvent,
|
||||
approvalEvent
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
)) {
|
||||
if (!window.isDestroyed()) {
|
||||
window.webContents.send(ipcChannels.agentEvent, agentEvent)
|
||||
@@ -104,11 +154,46 @@ export function registerIpcHandlers(
|
||||
activeRequests.get(requestId)?.abort(new Error('用户取消了请求'))
|
||||
})
|
||||
|
||||
return () => {
|
||||
for (const controller of activeRequests.values()) {
|
||||
controller.abort(new Error('应用正在退出'))
|
||||
ipcMain.handle(ipcChannels.agentApprovalRespond, (event, input: unknown) => {
|
||||
assertTrustedSender(event, window)
|
||||
const response = approvalResponseSchema.parse(input)
|
||||
approvalBroker.respond(response.approvalId, response.approved)
|
||||
})
|
||||
|
||||
ipcMain.handle(
|
||||
ipcChannels.runtimeSettingsGet,
|
||||
(event): Promise<RuntimeSettings> => {
|
||||
assertTrustedSender(event, window)
|
||||
return settingsStore.getPublicSettings()
|
||||
}
|
||||
activeRequests.clear()
|
||||
)
|
||||
|
||||
ipcMain.handle(
|
||||
ipcChannels.runtimeSettingsUpdate,
|
||||
async (event, input: unknown): Promise<RuntimeSettings> => {
|
||||
assertTrustedSender(event, window)
|
||||
const settings = runtimeSettingsInputSchema.parse(input)
|
||||
const savedSettings = await settingsStore.update(settings)
|
||||
abortActiveRequests('运行时设置已更改')
|
||||
await onRuntimeSettingsChanged()
|
||||
return savedSettings
|
||||
}
|
||||
)
|
||||
|
||||
ipcMain.handle(ipcChannels.contextSelectFiles, (event) => {
|
||||
assertTrustedSender(event, window)
|
||||
return contextManager.selectFiles(window)
|
||||
})
|
||||
|
||||
ipcMain.handle(ipcChannels.contextRemove, (event, input: unknown) => {
|
||||
assertTrustedSender(event, window)
|
||||
contextManager.remove(requestIdSchema.parse(input))
|
||||
})
|
||||
|
||||
return () => {
|
||||
abortActiveRequests('应用正在退出')
|
||||
approvalBroker.clear()
|
||||
contextManager.clear()
|
||||
for (const channel of channels) {
|
||||
ipcMain.removeHandler(channel)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user