mirror of
https://gitee.com/mateos/mateclaw.git
synced 2026-09-13 03:13:41 +08:00
83 lines
2.8 KiB
TypeScript
83 lines
2.8 KiB
TypeScript
import { dialog, BrowserWindow } from 'electron'
|
|
|
|
// ─── Local tool approval ─────────────────────────────────────────────────────
|
|
// High-risk local operations (file write/edit, shell execution) prompt the user
|
|
// with a native dialog showing the full operation context before they run. The
|
|
// user may tick "don't ask again this session" to temporarily allow matching
|
|
// operations — same path for file ops, same command prefix for shell — until the
|
|
// app restarts (the cache is in-memory only).
|
|
|
|
// Cache of approvals the user chose to remember this session.
|
|
const sessionAllow = new Set<string>()
|
|
|
|
export type ApprovalKind = 'write_file' | 'edit_file' | 'execute_shell'
|
|
|
|
// The cache key scopes "remember": file ops by exact path, shell by command
|
|
// prefix (first word + first 40 chars) so re-running the same kind of command
|
|
// doesn't re-prompt, but a different command still does.
|
|
function cacheKey(kind: ApprovalKind, subject: string): string {
|
|
if (kind === 'execute_shell') {
|
|
const head = subject.trim().split(/\s+/)[0] || ''
|
|
return `shell:${head}:${subject.trim().slice(0, 40)}`
|
|
}
|
|
return `${kind}:${subject}`
|
|
}
|
|
|
|
interface ApprovalRequest {
|
|
kind: ApprovalKind
|
|
// The path (file ops) or command (shell) this approval is scoped to.
|
|
subject: string
|
|
// Human-readable detail shown in the dialog body.
|
|
detail: string
|
|
}
|
|
|
|
export interface ApprovalResult {
|
|
approved: boolean
|
|
}
|
|
|
|
function titleFor(kind: ApprovalKind): string {
|
|
switch (kind) {
|
|
case 'write_file':
|
|
return '允许写入本地文件?'
|
|
case 'edit_file':
|
|
return '允许修改本地文件?'
|
|
case 'execute_shell':
|
|
return '允许执行本地命令?'
|
|
}
|
|
}
|
|
|
|
export async function requestApproval(req: ApprovalRequest): Promise<ApprovalResult> {
|
|
const key = cacheKey(req.kind, req.subject)
|
|
if (sessionAllow.has(key)) return { approved: true }
|
|
|
|
const parent = BrowserWindow.getFocusedWindow() ?? BrowserWindow.getAllWindows()[0]
|
|
const options = {
|
|
type: 'warning' as const,
|
|
title: titleFor(req.kind),
|
|
message: titleFor(req.kind),
|
|
detail: `${req.detail}\n\n该操作由远程 Agent 发起,将在你的本机执行。`,
|
|
buttons: ['拒绝', '允许'],
|
|
defaultId: 0,
|
|
cancelId: 0,
|
|
checkboxLabel: '本次会话不再询问相同操作',
|
|
checkboxChecked: false,
|
|
noLink: true,
|
|
}
|
|
|
|
const result = parent
|
|
? await dialog.showMessageBox(parent, options)
|
|
: await dialog.showMessageBox(options)
|
|
|
|
const approved = result.response === 1
|
|
if (approved && result.checkboxChecked) {
|
|
sessionAllow.add(key)
|
|
}
|
|
return { approved }
|
|
}
|
|
|
|
// Drop all remembered approvals — called when the desktop disconnects/logs out so
|
|
// a new session starts from a clean slate.
|
|
export function clearApprovalCache(): void {
|
|
sessionAllow.clear()
|
|
}
|