Some checks failed
CI / Lint & TypeCheck (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled
CI / Build Frontend (push) Has been cancelled
CI / Rust Check (push) Has been cancelled
CI / Security Scan (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled
Batch 1 — User-facing fixes: - B1-1: Pipeline verified end-to-end (14 Rust commands, 8 frontend invoke, fully connected) - B1-2: MessageSearch restored to ChatArea with search button in DeerFlow header - B1-3: Viking cleanup — removed 5 orphan invokes (no Rust impl), added addWithMetadata + storeWithSummaries methods + summary generation UI - B1-4: api-fallbacks transparency — added _isFallback markers + console.warn to all 6 fallback functions Batch 2 — System health: - B2-1: Document drift calibration — TRUTH.md/README.md numbers verified and updated - B2-2: @reserved annotations on 15 SaaS handler functions with no frontend callers - B2-3: Scheduled Task Admin V2 — new service + page + route + sidebar navigation - B2-4: TRUTH.md Pipeline/Viking/ScheduledTask records corrected Batch 3 — Long-term quality: - B3-1: hand_run_status/hand_run_list verified as fully implemented (not stubs) - B3-2: Identity snapshot rollback UI added to RightPanel - B3-3: P2 code quality — 4 fixes (TODO comments, fire-and-forget notes, design notes, table name validation), 2 verified N/A, 1 upstream - B3-4: Config PATCH→PUT alignment (admin-v2 config.ts matched to SaaS backend)
292 lines
8.5 KiB
TypeScript
292 lines
8.5 KiB
TypeScript
/**
|
|
* API Fallbacks for ZCLAW Gateway
|
|
*
|
|
* Provides sensible default data when ZCLAW API endpoints return 404.
|
|
* This allows the UI to function gracefully even when backend features
|
|
* are not yet implemented.
|
|
*/
|
|
|
|
// === Types ===
|
|
|
|
export interface QuickConfigFallback {
|
|
agentName: string;
|
|
agentRole: string;
|
|
userName: string;
|
|
userRole: string;
|
|
agentNickname?: string;
|
|
scenarios?: string[];
|
|
workspaceDir?: string;
|
|
gatewayUrl?: string;
|
|
gatewayToken?: string;
|
|
skillsExtraDirs?: string[];
|
|
mcpServices?: Array<{ id: string; name: string; enabled: boolean }>;
|
|
theme: 'light' | 'dark';
|
|
autoStart?: boolean;
|
|
showToolCalls: boolean;
|
|
restrictFiles?: boolean;
|
|
autoSaveContext?: boolean;
|
|
fileWatching?: boolean;
|
|
privacyOptIn?: boolean;
|
|
}
|
|
|
|
export interface WorkspaceInfoFallback {
|
|
path: string;
|
|
resolvedPath: string;
|
|
exists: boolean;
|
|
fileCount: number;
|
|
totalSize: number;
|
|
}
|
|
|
|
export interface UsageStatsFallback {
|
|
totalSessions: number;
|
|
totalMessages: number;
|
|
totalTokens: number;
|
|
byModel: Record<string, { messages: number; inputTokens: number; outputTokens: number }>;
|
|
}
|
|
|
|
export interface PluginStatusFallback {
|
|
id: string;
|
|
name?: string;
|
|
status: 'active' | 'inactive' | 'error' | 'loading';
|
|
version?: string;
|
|
description?: string;
|
|
}
|
|
|
|
export interface ScheduledTaskFallback {
|
|
id: string;
|
|
name: string;
|
|
schedule: string;
|
|
status: 'active' | 'paused' | 'completed' | 'error';
|
|
lastRun?: string;
|
|
nextRun?: string;
|
|
description?: string;
|
|
}
|
|
|
|
export interface SecurityLayerFallback {
|
|
name: string;
|
|
enabled: boolean;
|
|
description?: string;
|
|
}
|
|
|
|
export interface SecurityStatusFallback {
|
|
_isFallback?: true;
|
|
layers: SecurityLayerFallback[];
|
|
enabledCount: number;
|
|
totalCount: number;
|
|
securityLevel: 'critical' | 'high' | 'medium' | 'low';
|
|
}
|
|
|
|
// Session type for usage calculation
|
|
interface SessionForStats {
|
|
id: string;
|
|
messageCount?: number;
|
|
metadata?: {
|
|
tokens?: { input?: number; output?: number };
|
|
model?: string;
|
|
};
|
|
}
|
|
|
|
// Skill type for plugin fallback
|
|
interface SkillForPlugins {
|
|
id: string;
|
|
name: string;
|
|
source: 'builtin' | 'extra';
|
|
enabled?: boolean;
|
|
description?: string;
|
|
}
|
|
|
|
// Trigger type for scheduled tasks
|
|
interface TriggerForTasks {
|
|
id: string;
|
|
type: string;
|
|
enabled: boolean;
|
|
}
|
|
|
|
// === Fallback Implementations ===
|
|
|
|
/**
|
|
* Default quick config when /api/config/quick returns 404.
|
|
* Uses sensible defaults for a new user experience.
|
|
*/
|
|
export function getQuickConfigFallback(): QuickConfigFallback & { _isFallback: true } {
|
|
console.warn('[fallback] 使用降级数据: getQuickConfigFallback');
|
|
return {
|
|
_isFallback: true as const,
|
|
agentName: '默认助手',
|
|
agentRole: 'AI 助手',
|
|
userName: '用户',
|
|
userRole: '用户',
|
|
agentNickname: 'ZCLAW',
|
|
scenarios: ['通用对话', '代码助手', '文档编写'],
|
|
theme: 'dark',
|
|
showToolCalls: true,
|
|
autoSaveContext: true,
|
|
fileWatching: true,
|
|
privacyOptIn: false,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Default workspace info when /api/workspace returns 404.
|
|
* Returns a placeholder indicating workspace is not configured.
|
|
*/
|
|
export function getWorkspaceInfoFallback(): WorkspaceInfoFallback & { _isFallback: true } {
|
|
console.warn('[fallback] 使用降级数据: getWorkspaceInfoFallback');
|
|
// Try to get a reasonable default path
|
|
const defaultPath = typeof window !== 'undefined'
|
|
? `${navigator.userAgent.includes('Windows') ? 'C:\\Users' : '/home'}/workspace`
|
|
: '/workspace';
|
|
|
|
return {
|
|
_isFallback: true as const,
|
|
path: defaultPath,
|
|
resolvedPath: defaultPath,
|
|
exists: false,
|
|
fileCount: 0,
|
|
totalSize: 0,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Calculate usage stats from session data when /api/stats/usage returns 404.
|
|
*/
|
|
export function getUsageStatsFallback(sessions: SessionForStats[] = []): UsageStatsFallback & { _isFallback: true } {
|
|
console.warn('[fallback] 使用降级数据: getUsageStatsFallback — 基于本地 session 数据计算');
|
|
const stats: UsageStatsFallback = {
|
|
totalSessions: sessions.length,
|
|
totalMessages: 0,
|
|
totalTokens: 0,
|
|
byModel: {},
|
|
};
|
|
|
|
for (const session of sessions) {
|
|
stats.totalMessages += session.messageCount || 0;
|
|
|
|
if (session.metadata?.tokens) {
|
|
const input = session.metadata.tokens.input || 0;
|
|
const output = session.metadata.tokens.output || 0;
|
|
stats.totalTokens += input + output;
|
|
|
|
if (session.metadata.model) {
|
|
const model = session.metadata.model;
|
|
if (!stats.byModel[model]) {
|
|
stats.byModel[model] = { messages: 0, inputTokens: 0, outputTokens: 0 };
|
|
}
|
|
stats.byModel[model].messages += session.messageCount || 0;
|
|
stats.byModel[model].inputTokens += input;
|
|
stats.byModel[model].outputTokens += output;
|
|
}
|
|
}
|
|
}
|
|
|
|
return { ...stats, _isFallback: true as const };
|
|
}
|
|
|
|
/**
|
|
* Convert skills to plugin status when /api/plugins/status returns 404.
|
|
* ZCLAW uses Skills instead of traditional plugins.
|
|
*/
|
|
export function getPluginStatusFallback(skills: SkillForPlugins[] = []): Array<PluginStatusFallback & { _isFallback?: true }> {
|
|
console.warn('[fallback] 使用降级数据: getPluginStatusFallback — 从 Skills 列表推断');
|
|
if (skills.length === 0) {
|
|
// No skills loaded — return empty rather than fabricating fake builtins
|
|
return [];
|
|
}
|
|
|
|
return skills.map((skill) => ({
|
|
id: skill.id,
|
|
name: skill.name,
|
|
status: skill.enabled !== false ? 'active' : 'inactive',
|
|
description: skill.description,
|
|
}));
|
|
}
|
|
|
|
/**
|
|
* Convert triggers to scheduled tasks when /api/scheduler/tasks returns 404.
|
|
*/
|
|
export function getScheduledTasksFallback(triggers: TriggerForTasks[] = []): Array<ScheduledTaskFallback & { _isFallback?: true }> {
|
|
console.warn('[fallback] 使用降级数据: getScheduledTasksFallback — 从 Triggers 列表推断');
|
|
return triggers
|
|
.filter((t) => t.enabled)
|
|
.map((trigger) => ({
|
|
id: trigger.id,
|
|
name: `Trigger: ${trigger.type}`,
|
|
schedule: 'event-based',
|
|
status: 'active' as const,
|
|
description: `Event trigger of type: ${trigger.type}`,
|
|
}));
|
|
}
|
|
|
|
/**
|
|
* Default security status when /api/security/status returns 404.
|
|
* Returns honest minimal response — only includes layers that correspond
|
|
* to real ZCLAW capabilities, no fabricated layers.
|
|
*/
|
|
export function getSecurityStatusFallback(): SecurityStatusFallback & { _isFallback: true } {
|
|
console.warn('[fallback] 使用降级数据: getSecurityStatusFallback — 返回静态安全层状态');
|
|
const layers: SecurityLayerFallback[] = [
|
|
{ name: 'device_auth', enabled: true, description: '设备认证' },
|
|
{ name: 'rbac', enabled: true, description: '角色权限控制' },
|
|
{ name: 'audit_log', enabled: true, description: '审计日志' },
|
|
{ name: 'approval_gate', enabled: true, description: '操作审批门' },
|
|
{ name: 'input_validation', enabled: true, description: '输入验证' },
|
|
{ name: 'secret_storage', enabled: true, description: '密钥安全存储 (OS keyring)' },
|
|
];
|
|
|
|
const enabledCount = layers.filter((l) => l.enabled).length;
|
|
const securityLevel = calculateSecurityLevel(enabledCount, layers.length);
|
|
|
|
return {
|
|
_isFallback: true as const,
|
|
layers,
|
|
enabledCount,
|
|
totalCount: layers.length,
|
|
securityLevel,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Calculate security level based on enabled layers ratio.
|
|
*/
|
|
function calculateSecurityLevel(enabledCount: number, totalCount: number): 'critical' | 'high' | 'medium' | 'low' {
|
|
if (totalCount === 0) return 'low';
|
|
const ratio = enabledCount / totalCount;
|
|
if (ratio >= 0.875) return 'critical'; // 14-16 layers
|
|
if (ratio >= 0.625) return 'high'; // 10-13 layers
|
|
if (ratio >= 0.375) return 'medium'; // 6-9 layers
|
|
return 'low'; // 0-5 layers
|
|
}
|
|
|
|
// === Error Detection Helpers ===
|
|
|
|
/**
|
|
* Check if an error is a 404 Not Found response.
|
|
*/
|
|
export function isNotFoundError(error: unknown): boolean {
|
|
if (error instanceof Error) {
|
|
const message = error.message.toLowerCase();
|
|
return message.includes('404') || message.includes('not found');
|
|
}
|
|
if (typeof error === 'object' && error !== null) {
|
|
const status = (error as { status?: number }).status;
|
|
return status === 404;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Check if an error is a network/connection error.
|
|
*/
|
|
export function isNetworkError(error: unknown): boolean {
|
|
if (error instanceof Error) {
|
|
const message = error.message.toLowerCase();
|
|
return (
|
|
message.includes('network') ||
|
|
message.includes('connection') ||
|
|
message.includes('timeout') ||
|
|
message.includes('abort')
|
|
);
|
|
}
|
|
return false;
|
|
}
|