hermes-agent/apps/desktop/src/store/approval-mode.ts

101 lines
2.7 KiB
TypeScript

import { atom } from 'nanostores'
export type ApprovalMode = 'manual' | 'off' | 'smart'
export type ApprovalModeRequester = (
method: string,
params?: Record<string, unknown>
) => Promise<unknown>
const APPROVAL_MODES = new Set<ApprovalMode>(['manual', 'smart', 'off'])
const revisions = new Map<string, number>()
const confirmedModes = new Map<string, ApprovalMode>()
export const $approvalModes = atom<Record<string, ApprovalMode>>({})
function profileKey(profile: string): string {
return profile.trim() || 'default'
}
function nextRevision(profile: string): number {
const revision = (revisions.get(profile) ?? 0) + 1
revisions.set(profile, revision)
return revision
}
function normalizeApprovalMode(value: unknown): ApprovalMode {
const normalized = String(value ?? '')
.trim()
.toLowerCase() as ApprovalMode
return APPROVAL_MODES.has(normalized) ? normalized : 'manual'
}
export function approvalModeForProfile(profile: string): ApprovalMode {
return $approvalModes.get()[profileKey(profile)] ?? 'smart'
}
function cacheApprovalMode(profile: string, mode: ApprovalMode): void {
const key = profileKey(profile)
$approvalModes.set({ ...$approvalModes.get(), [key]: mode })
}
export function reconcileApprovalModeForProfile(profile: string, value: unknown): ApprovalMode {
const key = profileKey(profile)
const mode = normalizeApprovalMode(value)
nextRevision(key)
confirmedModes.set(key, mode)
cacheApprovalMode(key, mode)
return mode
}
export async function syncApprovalModeForProfile(
requestGateway: ApprovalModeRequester,
profile: string
): Promise<ApprovalMode> {
const key = profileKey(profile)
const revision = nextRevision(key)
const result = (await requestGateway('config.get', { key: 'approvals.mode' })) as { value?: string }
const mode = normalizeApprovalMode(result?.value)
if (revisions.get(key) === revision) {
confirmedModes.set(key, mode)
cacheApprovalMode(key, mode)
}
return mode
}
export async function setApprovalModeForProfile(
requestGateway: ApprovalModeRequester,
profile: string,
mode: ApprovalMode
): Promise<ApprovalMode> {
const key = profileKey(profile)
const revision = nextRevision(key)
cacheApprovalMode(key, mode)
try {
const result = (await requestGateway('config.set', {
key: 'approvals.mode',
value: mode
})) as { value?: string }
const authoritative = normalizeApprovalMode(result?.value)
if (revisions.get(key) === revision) {
confirmedModes.set(key, authoritative)
cacheApprovalMode(key, authoritative)
}
return authoritative
} catch (error) {
if (revisions.get(key) === revision) {
cacheApprovalMode(key, confirmedModes.get(key) ?? 'smart')
}
throw error
}
}