refactor(desktop): split kernel_commands/pipeline_commands into modules, add SaaS client libs and gateway modules

Split monolithic kernel_commands.rs (2185 lines) and pipeline_commands.rs (1391 lines)
into focused sub-modules under kernel_commands/ and pipeline_commands/ directories.
Add gateway module (commands, config, io, runtime), health_check, and 15 new
TypeScript client libraries for SaaS relay, auth, admin, telemetry, and kernel
sub-systems (a2a, agent, chat, hands, skills, triggers).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
iven
2026-03-31 11:12:47 +08:00
parent d0ae7d2770
commit f79560a911
71 changed files with 8521 additions and 5997 deletions

View File

@@ -0,0 +1,153 @@
/**
* SaaS Session Persistence
*
* Handles loading/saving SaaS auth session data.
* Token is stored in secure storage (OS keyring), not plain localStorage.
* Auth state is carried by HttpOnly cookies when possible (same-origin).
*/
import type { SaaSAccountInfo } from './saas-types';
import { createLogger } from './logger';
const logger = createLogger('saas-session');
// === Storage Keys ===
const SAAS_TOKEN_SECURE_KEY = 'zclaw-saas-token'; // OS keyring key
const SAASTOKEN_KEY = 'zclaw-saas-token'; // legacy localStorage — only used for cleanup
const SAASURL_KEY = 'zclaw-saas-url';
const SAASACCOUNT_KEY = 'zclaw-saas-account';
const SAASMODE_KEY = 'zclaw-connection-mode';
// === Session Interface ===
export interface SaaSSession {
token: string | null; // null when using cookie-based auth (page reload)
account: SaaSAccountInfo | null;
saasUrl: string;
}
// === Session Functions ===
/**
* Load a persisted SaaS session.
* Token is stored in secure storage (OS keyring), not plain localStorage.
* Returns null if no URL is stored (never logged in).
*
* NOTE: Token loading is async due to secure storage access.
* For synchronous checks, use loadSaaSSessionSync() (URL + account only).
*/
export async function loadSaaSSession(): Promise<SaaSSession | null> {
try {
const saasUrl = localStorage.getItem(SAASURL_KEY);
if (!saasUrl) {
return null;
}
// Clean up any legacy plaintext token from localStorage
const legacyToken = localStorage.getItem(SAASTOKEN_KEY);
if (legacyToken) {
localStorage.removeItem(SAASTOKEN_KEY);
}
// Load token from secure storage
let token: string | null = null;
try {
const { secureStorage } = await import('./secure-storage');
token = await secureStorage.get(SAAS_TOKEN_SECURE_KEY);
} catch (e) {
logger.debug('Secure storage unavailable for token load', { error: e });
// Secure storage unavailable — token stays null (cookie auth will be attempted)
}
const accountRaw = localStorage.getItem(SAASACCOUNT_KEY);
const account: SaaSAccountInfo | null = accountRaw
? (JSON.parse(accountRaw) as SaaSAccountInfo)
: null;
return { token, account, saasUrl };
} catch (e) {
logger.debug('Corrupted session data, clearing', { error: e });
// Corrupted data - clear all
clearSaaSSession();
return null;
}
}
/**
* Synchronous version — returns URL + account only (no token).
* Used during store initialization where async is not available.
*/
export function loadSaaSSessionSync(): { saasUrl: string; account: SaaSAccountInfo | null } | null {
try {
const saasUrl = localStorage.getItem(SAASURL_KEY);
if (!saasUrl) return null;
// Clean up legacy plaintext token
const legacyToken = localStorage.getItem(SAASTOKEN_KEY);
if (legacyToken) {
localStorage.removeItem(SAASTOKEN_KEY);
}
const accountRaw = localStorage.getItem(SAASACCOUNT_KEY);
const account: SaaSAccountInfo | null = accountRaw
? (JSON.parse(accountRaw) as SaaSAccountInfo)
: null;
return { saasUrl, account };
} catch (e) {
logger.debug('Failed to load sync session', { error: e });
return null;
}
}
/**
* Persist SaaS session.
* Token goes to secure storage (OS keyring), metadata to localStorage.
*/
export async function saveSaaSSession(session: SaaSSession): Promise<void> {
// Store token in secure storage (OS keyring), not plain localStorage
if (session.token) {
try {
const { secureStorage } = await import('./secure-storage');
await secureStorage.set(SAAS_TOKEN_SECURE_KEY, session.token);
} catch (e) {
logger.debug('Secure storage unavailable for token save', { error: e });
// Secure storage unavailable — token only in memory
}
}
localStorage.setItem(SAASURL_KEY, session.saasUrl);
if (session.account) {
localStorage.setItem(SAASACCOUNT_KEY, JSON.stringify(session.account));
}
}
/**
* Clear the persisted SaaS session from all storage.
*/
export async function clearSaaSSession(): Promise<void> {
// Remove from secure storage
try {
const { secureStorage } = await import('./secure-storage');
await secureStorage.set(SAAS_TOKEN_SECURE_KEY, '');
} catch (e) { logger.debug('Failed to clear secure storage token', { error: e }); }
localStorage.removeItem(SAASTOKEN_KEY);
localStorage.removeItem(SAASURL_KEY);
localStorage.removeItem(SAASACCOUNT_KEY);
}
/**
* Persist the connection mode to localStorage.
*/
export function saveConnectionMode(mode: string): void {
localStorage.setItem(SAASMODE_KEY, mode);
}
/**
* Load the connection mode from localStorage.
* Returns null if not set.
*/
export function loadConnectionMode(): string | null {
return localStorage.getItem(SAASMODE_KEY);
}