Files
zclaw_openfang/desktop/src/lib/autonomy-manager.ts
iven ecd7f2e928 fix(desktop): console.log 清理 — 替换为结构化 logger
将 desktop/src 中 23 处 console.log 替换为 createLogger() 结构化日志:
- 生产构建自动静默 debug/info 级别
- 保留 console.error 用于关键错误可见性
- 新增 dompurify 依赖修复 XSS 防护引入缺失

涉及文件: App.tsx, offlineStore.ts, autonomy-manager.ts,
gateway-auth.ts, llm-service.ts, request-helper.ts,
security-index.ts, skill-discovery.ts, use-onboarding.ts 等 16 个文件
2026-03-30 16:22:16 +08:00

554 lines
14 KiB
TypeScript

/**
* Autonomy Manager - Tiered authorization system for L4 self-evolution
*
* Provides granular control over what actions the Agent can take autonomously:
* - Supervised: All actions require user confirmation
* - Assisted: Low-risk actions execute automatically
* - Autonomous: Agent decides when to act and notify
*
* Security boundaries:
* - High-risk operations ALWAYS require confirmation
* - All autonomous actions are logged for audit
* - One-click rollback to any historical state
*
* Reference: ZCLAW_AGENT_INTELLIGENCE_EVOLUTION.md §6.4.3
*/
import { generateRandomString } from './crypto-utils';
import { createLogger } from './logger';
const log = createLogger('AutonomyManager');
// === Types ===
export type AutonomyLevel = 'supervised' | 'assisted' | 'autonomous';
export type RiskLevel = 'low' | 'medium' | 'high';
export type ActionType =
| 'memory_save'
| 'memory_delete'
| 'identity_update'
| 'identity_rollback'
| 'skill_install'
| 'skill_uninstall'
| 'config_change'
| 'workflow_trigger'
| 'hand_trigger'
| 'llm_call'
| 'reflection_run'
| 'compaction_run';
export interface AutonomyConfig {
level: AutonomyLevel;
allowedActions: {
memoryAutoSave: boolean;
identityAutoUpdate: boolean;
skillAutoInstall: boolean;
selfModification: boolean;
autoCompaction: boolean;
autoReflection: boolean;
};
approvalThreshold: {
importanceMax: number; // Auto-approve if importance <= this (default: 5)
riskMax: RiskLevel; // Auto-approve if risk <= this (default: 'low')
};
notifyOnAction: boolean; // Notify user after autonomous action
auditLogEnabled: boolean; // Log all autonomous actions
}
export interface AutonomyDecision {
action: ActionType;
allowed: boolean;
requiresApproval: boolean;
reason: string;
riskLevel: RiskLevel;
importance: number;
timestamp: string;
}
export interface AuditLogEntry {
id: string;
action: ActionType;
decision: AutonomyDecision;
context: Record<string, unknown>;
outcome: 'success' | 'failed' | 'rolled_back';
rolledBackAt?: string;
timestamp: string;
}
// === Risk Mapping ===
const ACTION_RISK_MAP: Record<ActionType, RiskLevel> = {
memory_save: 'low',
memory_delete: 'high',
identity_update: 'high',
identity_rollback: 'medium',
skill_install: 'medium',
skill_uninstall: 'medium',
config_change: 'medium',
workflow_trigger: 'low',
hand_trigger: 'medium',
llm_call: 'low',
reflection_run: 'low',
compaction_run: 'low',
};
const RISK_ORDER: Record<RiskLevel, number> = {
low: 1,
medium: 2,
high: 3,
};
// === Default Configs ===
export const DEFAULT_AUTONOMY_CONFIGS: Record<AutonomyLevel, AutonomyConfig> = {
supervised: {
level: 'supervised',
allowedActions: {
memoryAutoSave: false,
identityAutoUpdate: false,
skillAutoInstall: false,
selfModification: false,
autoCompaction: false,
autoReflection: false,
},
approvalThreshold: {
importanceMax: 0,
riskMax: 'low',
},
notifyOnAction: true,
auditLogEnabled: true,
},
assisted: {
level: 'assisted',
allowedActions: {
memoryAutoSave: true,
identityAutoUpdate: false,
skillAutoInstall: false,
selfModification: false,
autoCompaction: true,
autoReflection: true,
},
approvalThreshold: {
importanceMax: 5,
riskMax: 'low',
},
notifyOnAction: true,
auditLogEnabled: true,
},
autonomous: {
level: 'autonomous',
allowedActions: {
memoryAutoSave: true,
identityAutoUpdate: true,
skillAutoInstall: true,
selfModification: false, // Always require approval for self-modification
autoCompaction: true,
autoReflection: true,
},
approvalThreshold: {
importanceMax: 7,
riskMax: 'medium',
},
notifyOnAction: false, // Only notify on high-impact actions
auditLogEnabled: true,
},
};
// === Storage ===
const AUTONOMY_CONFIG_KEY = 'zclaw-autonomy-config';
const AUDIT_LOG_KEY = 'zclaw-autonomy-audit-log';
// === Autonomy Manager ===
export class AutonomyManager {
private config: AutonomyConfig;
private auditLog: AuditLogEntry[] = [];
private pendingApprovals: Map<string, AutonomyDecision> = new Map();
constructor(config?: Partial<AutonomyConfig>) {
this.config = this.loadConfig();
if (config) {
this.config = { ...this.config, ...config };
}
this.loadAuditLog();
}
// === Decision Making ===
/**
* Evaluate whether an action can be executed autonomously.
*/
evaluate(
action: ActionType,
context?: {
importance?: number;
riskOverride?: RiskLevel;
details?: Record<string, unknown>;
}
): AutonomyDecision {
const importance = context?.importance ?? 5;
const baseRisk = ACTION_RISK_MAP[action];
const riskLevel = context?.riskOverride ?? baseRisk;
// High-risk actions ALWAYS require approval
const isHighRisk = riskLevel === 'high';
const isSelfModification = action === 'identity_update';
const isDeletion = action === 'memory_delete';
let allowed = false;
let requiresApproval = true;
let reason = '';
// Determine if action is allowed based on config
if (isHighRisk || isDeletion) {
// Always require approval for high-risk and deletion
allowed = false;
requiresApproval = true;
reason = `高风险操作 [${action}] 始终需要用户确认`;
} else if (isSelfModification && !this.config.allowedActions.selfModification) {
// Self-modification requires explicit permission
allowed = false;
requiresApproval = true;
reason = `身份修改 [${action}] 需要显式授权`;
} else {
// Check against thresholds
const importanceOk = importance <= this.config.approvalThreshold.importanceMax;
const riskOk = RISK_ORDER[riskLevel] <= RISK_ORDER[this.config.approvalThreshold.riskMax];
const actionAllowed = this.isActionAllowed(action);
if (actionAllowed && importanceOk && riskOk) {
allowed = true;
requiresApproval = false;
reason = `自动批准: 重要性=${importance}, 风险=${riskLevel}`;
} else if (actionAllowed) {
allowed = false;
requiresApproval = true;
reason = `需要审批: 重要性=${importance}(阈值${this.config.approvalThreshold.importanceMax}), 风险=${riskLevel}(阈值${this.config.approvalThreshold.riskMax})`;
} else {
allowed = false;
requiresApproval = true;
reason = `操作 [${action}] 未在允许列表中`;
}
}
const decision: AutonomyDecision = {
action,
allowed,
requiresApproval,
reason,
riskLevel,
importance,
timestamp: new Date().toISOString(),
};
// Log the decision
if (this.config.auditLogEnabled) {
this.logDecision(decision, context?.details ?? {});
}
return decision;
}
/**
* Check if an action type is allowed by current config.
*/
private isActionAllowed(action: ActionType): boolean {
const actionMap: Record<ActionType, keyof AutonomyConfig['allowedActions'] | null> = {
memory_save: 'memoryAutoSave',
memory_delete: null, // Never auto-allowed
identity_update: 'identityAutoUpdate',
identity_rollback: null,
skill_install: 'skillAutoInstall',
skill_uninstall: 'skillAutoInstall',
config_change: null,
workflow_trigger: 'autoCompaction',
hand_trigger: null,
llm_call: 'autoReflection',
reflection_run: 'autoReflection',
compaction_run: 'autoCompaction',
};
const configKey = actionMap[action];
if (!configKey) return false;
return this.config.allowedActions[configKey] ?? false;
}
// === Approval Workflow ===
/**
* Request approval for an action.
* Returns approval ID that can be used to approve/reject.
*/
requestApproval(decision: AutonomyDecision): string {
const approvalId = `approval_${Date.now()}_${generateRandomString(6)}`;
this.pendingApprovals.set(approvalId, decision);
// Store in localStorage for persistence
this.persistPendingApprovals();
log.debug(`Approval requested: ${approvalId} for ${decision.action}`);
return approvalId;
}
/**
* Approve a pending action.
*/
approve(approvalId: string): boolean {
const decision = this.pendingApprovals.get(approvalId);
if (!decision) {
log.warn(`Approval not found: ${approvalId}`);
return false;
}
// Update decision
decision.allowed = true;
decision.requiresApproval = false;
decision.reason = `用户已批准 [${approvalId}]`;
// Remove from pending
this.pendingApprovals.delete(approvalId);
this.persistPendingApprovals();
// Update audit log
this.updateAuditLogOutcome(approvalId, 'success');
log.info('Approved: ${approvalId}');
return true;
}
/**
* Reject a pending action.
*/
reject(approvalId: string): boolean {
const decision = this.pendingApprovals.get(approvalId);
if (!decision) {
log.warn(`Approval not found: ${approvalId}`);
return false;
}
// Remove from pending
this.pendingApprovals.delete(approvalId);
this.persistPendingApprovals();
// Update audit log
this.updateAuditLogOutcome(approvalId, 'failed');
log.info('Rejected: ${approvalId}');
return true;
}
/**
* Get all pending approvals.
*/
getPendingApprovals(): Array<{ id: string; decision: AutonomyDecision }> {
return Array.from(this.pendingApprovals.entries()).map(([id, decision]) => ({
id,
decision,
}));
}
// === Audit Log ===
private logDecision(decision: AutonomyDecision, context: Record<string, unknown>): void {
const entry: AuditLogEntry = {
id: `audit_${Date.now()}_${generateRandomString(6)}`,
action: decision.action,
decision,
context,
outcome: decision.allowed ? 'success' : 'failed',
timestamp: decision.timestamp,
};
this.auditLog.push(entry);
// Keep last 100 entries
if (this.auditLog.length > 100) {
this.auditLog = this.auditLog.slice(-100);
}
this.saveAuditLog();
}
private updateAuditLogOutcome(approvalId: string, outcome: 'success' | 'failed' | 'rolled_back'): void {
// Find the most recent entry for this action and update outcome
const entry = this.auditLog.find(e => e.decision.reason.includes(approvalId));
if (entry) {
entry.outcome = outcome;
this.saveAuditLog();
}
}
/**
* Rollback an action by its audit log ID.
*/
rollback(auditId: string): boolean {
const entry = this.auditLog.find(e => e.id === auditId);
if (!entry) {
log.warn('Audit entry not found: ${auditId}');
return false;
}
if (entry.outcome === 'rolled_back') {
log.warn('Already rolled back: ${auditId}');
return false;
}
// Mark as rolled back
entry.outcome = 'rolled_back';
entry.rolledBackAt = new Date().toISOString();
this.saveAuditLog();
log.info(`Rolled back: ${auditId}`);
return true;
}
/**
* Get audit log entries.
*/
getAuditLog(limit: number = 50): AuditLogEntry[] {
return this.auditLog.slice(-limit);
}
/**
* Clear audit log.
*/
clearAuditLog(): void {
this.auditLog = [];
this.saveAuditLog();
}
// === Config Management ===
getConfig(): AutonomyConfig {
return { ...this.config };
}
updateConfig(updates: Partial<AutonomyConfig>): void {
this.config = { ...this.config, ...updates };
this.saveConfig();
}
setLevel(level: AutonomyLevel): void {
this.config = { ...DEFAULT_AUTONOMY_CONFIGS[level], level };
this.saveConfig();
log.info(`Level changed to: ${level}`);
}
// === Persistence ===
private loadConfig(): AutonomyConfig {
try {
const raw = localStorage.getItem(AUTONOMY_CONFIG_KEY);
if (raw) {
const parsed = JSON.parse(raw);
return { ...DEFAULT_AUTONOMY_CONFIGS.assisted, ...parsed };
}
} catch {
// Ignore
}
return DEFAULT_AUTONOMY_CONFIGS.assisted;
}
private saveConfig(): void {
try {
localStorage.setItem(AUTONOMY_CONFIG_KEY, JSON.stringify(this.config));
} catch {
// Ignore
}
}
private loadAuditLog(): void {
try {
const raw = localStorage.getItem(AUDIT_LOG_KEY);
if (raw) {
this.auditLog = JSON.parse(raw);
}
} catch {
this.auditLog = [];
}
}
private saveAuditLog(): void {
try {
localStorage.setItem(AUDIT_LOG_KEY, JSON.stringify(this.auditLog.slice(-100)));
} catch {
// Ignore
}
}
private persistPendingApprovals(): void {
try {
const pending = Array.from(this.pendingApprovals.entries());
localStorage.setItem('zclaw-pending-approvals', JSON.stringify(pending));
} catch {
// Ignore
}
}
}
// === Singleton ===
let _instance: AutonomyManager | null = null;
export function getAutonomyManager(config?: Partial<AutonomyConfig>): AutonomyManager {
if (!_instance) {
_instance = new AutonomyManager(config);
}
return _instance;
}
export function resetAutonomyManager(): void {
_instance = null;
}
// === Helper Functions ===
/**
* Quick check if an action can proceed autonomously.
*/
export function canAutoExecute(
action: ActionType,
importance: number = 5
): { canProceed: boolean; decision: AutonomyDecision } {
const manager = getAutonomyManager();
const decision = manager.evaluate(action, { importance });
return {
canProceed: decision.allowed && !decision.requiresApproval,
decision,
};
}
/**
* Execute an action with autonomy check.
* Returns the decision and whether the action was executed.
*/
export async function executeWithAutonomy<T>(
action: ActionType,
importance: number,
executor: () => Promise<T>,
onApprovalNeeded?: (decision: AutonomyDecision, approvalId: string) => void
): Promise<{ executed: boolean; result?: T; decision: AutonomyDecision; approvalId?: string }> {
const manager = getAutonomyManager();
const decision = manager.evaluate(action, { importance });
if (decision.allowed && !decision.requiresApproval) {
// Execute immediately
try {
const result = await executor();
return { executed: true, result, decision };
} catch (error) {
log.error(`Action ${action} failed:`, error);
return { executed: false, decision };
}
}
// Need approval
const approvalId = manager.requestApproval(decision);
onApprovalNeeded?.(decision, approvalId);
return { executed: false, decision, approvalId };
}