@decido/kernel-bridge 1.0.0 → 4.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +31 -0
- package/dist/index.js +2593 -0
- package/dist/index.mjs +2518 -0
- package/package.json +13 -7
- package/.turbo/turbo-build.log +0 -13
- package/.turbo/turbo-lint.log +0 -30
- package/src/ai/components/PeerNetworkPanel.tsx +0 -219
- package/src/ai/components/TokenWalletPanel.tsx +0 -172
- package/src/ai/hooks/usePeerMesh.ts +0 -79
- package/src/ai/hooks/useTokenWallet.ts +0 -35
- package/src/ai/index.ts +0 -96
- package/src/ai/services/EmbeddingService.ts +0 -119
- package/src/ai/services/InferenceRouter.ts +0 -347
- package/src/ai/services/LocalAgentResponder.ts +0 -199
- package/src/ai/services/MLXBridge.ts +0 -278
- package/src/ai/services/OllamaService.ts +0 -326
- package/src/ai/services/PeerMesh.ts +0 -373
- package/src/ai/services/TokenWallet.ts +0 -237
- package/src/ai/services/providers/AnthropicProvider.ts +0 -229
- package/src/ai/services/providers/GeminiProvider.ts +0 -121
- package/src/ai/services/providers/LLMProvider.ts +0 -72
- package/src/ai/services/providers/OllamaProvider.ts +0 -84
- package/src/ai/services/providers/OpenAIProvider.ts +0 -178
- package/src/crypto.ts +0 -54
- package/src/index.ts +0 -4
- package/src/kernel.ts +0 -376
- package/src/rehydration.ts +0 -52
- package/tsconfig.json +0 -18
package/src/kernel.ts
DELETED
|
@@ -1,376 +0,0 @@
|
|
|
1
|
-
import { io, Socket } from 'socket.io-client';
|
|
2
|
-
import { inferenceRouter, TaskType, InferenceBackend } from './ai/services/InferenceRouter';
|
|
3
|
-
import { createSafeEvent } from '@decido/sdk';
|
|
4
|
-
|
|
5
|
-
// Tauri modules are loaded dynamically to avoid breaking browser-only hosts
|
|
6
|
-
// (e.g. theme-playground) where @tauri-apps/* is stubbed out by Vite.
|
|
7
|
-
async function getTauriCore() { return import('@tauri-apps/api/core'); }
|
|
8
|
-
async function getTauriEvent() { return import('@tauri-apps/api/event'); }
|
|
9
|
-
|
|
10
|
-
export interface LogEntry {
|
|
11
|
-
id: string;
|
|
12
|
-
timestamp: number;
|
|
13
|
-
type: 'ipc_out' | 'ipc_in' | 'info' | 'error';
|
|
14
|
-
channel: string;
|
|
15
|
-
payload?: any;
|
|
16
|
-
duration?: number;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
// -----------------------------------------------------
|
|
20
|
-
// TRANSPORT ADAPTERS (Tauri vs WebSocket)
|
|
21
|
-
// -----------------------------------------------------
|
|
22
|
-
export interface KernelTransport {
|
|
23
|
-
name: string;
|
|
24
|
-
isConnectedToCloud: boolean;
|
|
25
|
-
connectToSwarm(tenantId: string, url: string, token?: string): void;
|
|
26
|
-
execute<T>(tenantId: string, cmd: string, args?: Record<string, unknown>): Promise<T>;
|
|
27
|
-
notify(title: string, body: string): Promise<void>;
|
|
28
|
-
vibrate(pattern: any): Promise<void>;
|
|
29
|
-
getEventHistory(tenantId: string, limit: number): Promise<any[]>;
|
|
30
|
-
listenEvents(onEvent: (payload: { type: string, channel: string, payload: any }) => void): () => void;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
class TauriTransport implements KernelTransport {
|
|
34
|
-
name = "Anillo 0/1 (Tauri IPC)";
|
|
35
|
-
isConnectedToCloud = false;
|
|
36
|
-
private unlistenFns: (() => void)[] = [];
|
|
37
|
-
|
|
38
|
-
connectToSwarm(tenantId: string, url: string, token?: string) {
|
|
39
|
-
// En Anillo 0, Tauri (Rust) se encarga de la conexión WebSocket en background
|
|
40
|
-
// Podríamos enviar un invoke('connect_swarm') aquí si fuera necesario
|
|
41
|
-
console.log(`[Kernel/Tauri] Conexión nativa delegada a Core-Rust para tenant: ${tenantId}`);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
async execute<T>(tenantId: string, cmd: string, args?: Record<string, unknown>): Promise<T> {
|
|
45
|
-
const { invoke } = await getTauriCore();
|
|
46
|
-
return invoke<T>(cmd, { tenantId, ...args });
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
async notify(title: string, body: string): Promise<void> {
|
|
50
|
-
try {
|
|
51
|
-
const { sendNotification } = await import('@tauri-apps/plugin-notification');
|
|
52
|
-
await sendNotification({ title, body });
|
|
53
|
-
} catch (e) {
|
|
54
|
-
console.log('[Kernel/Tauri] Notification plugin not available.');
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
async vibrate(pattern: any): Promise<void> {
|
|
59
|
-
try {
|
|
60
|
-
const { vibrate: nativeVibrate } = await import('@tauri-apps/plugin-haptics');
|
|
61
|
-
await nativeVibrate(pattern);
|
|
62
|
-
} catch (e) {
|
|
63
|
-
console.log('[Kernel/Tauri] Haptics not available on this platform.');
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
async getEventHistory(tenantId: string, limit: number): Promise<any[]> {
|
|
68
|
-
return this.execute<any[]>(tenantId, 'get_stream_history', { limit });
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
listenEvents(onEvent: (payload: { type: string, channel: string, payload: any }) => void): () => void {
|
|
72
|
-
let unlistenFn: (() => void) | null = null;
|
|
73
|
-
getTauriEvent().then(({ listen }) => {
|
|
74
|
-
listen('orchestrator-event', (event: any) => {
|
|
75
|
-
onEvent({ type: 'ipc_in', channel: 'orchestrator-event', payload: event.payload });
|
|
76
|
-
}).then(fn => {
|
|
77
|
-
unlistenFn = fn;
|
|
78
|
-
this.unlistenFns.push(fn);
|
|
79
|
-
});
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
return () => {
|
|
83
|
-
if (unlistenFn) {
|
|
84
|
-
unlistenFn();
|
|
85
|
-
this.unlistenFns = this.unlistenFns.filter(f => f !== unlistenFn);
|
|
86
|
-
}
|
|
87
|
-
};
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
class WebSocketTransport implements KernelTransport {
|
|
92
|
-
name = "Satélite Web (WebSocket)";
|
|
93
|
-
isConnectedToCloud = false;
|
|
94
|
-
private sockets: Record<string, Socket> = {};
|
|
95
|
-
private eventListeners: Set<(payload: { type: string, channel: string, payload: any }) => void> = new Set();
|
|
96
|
-
|
|
97
|
-
connectToSwarm(tenantId: string, url: string, token?: string) {
|
|
98
|
-
if (this.sockets[tenantId]) return;
|
|
99
|
-
const socket = io(url, { auth: token ? { token } : undefined });
|
|
100
|
-
|
|
101
|
-
socket.on('connect', () => {
|
|
102
|
-
this.isConnectedToCloud = true;
|
|
103
|
-
console.log(`✅ [Kernel/WS] Satélite conectado al Enjambre del Tenant: ${tenantId}`);
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
socket.on('orchestrator-event', (eventPayload) => {
|
|
107
|
-
this.eventListeners.forEach(cb => cb({ type: 'ipc_in', channel: 'orchestrator-event', payload: { tenantId, ...eventPayload } }));
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
socket.on('rpc_broadcast', (eventPayload) => {
|
|
111
|
-
console.log(`[Kernel] 🌐 Broadcast Recibido [${tenantId}]:`, eventPayload);
|
|
112
|
-
this.eventListeners.forEach(cb => cb({ type: 'ipc_in', channel: 'rpc_broadcast', payload: { tenantId, ...eventPayload } }));
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
this.sockets[tenantId] = socket;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
async execute<T>(tenantId: string, cmd: string, args?: Record<string, unknown>): Promise<T> {
|
|
119
|
-
const socket = this.sockets[tenantId];
|
|
120
|
-
if (socket) {
|
|
121
|
-
return new Promise((resolve, reject) => {
|
|
122
|
-
socket.emit('rpc_call', { cmd, args }, (response: any) => {
|
|
123
|
-
if (response.error) reject(new Error(response.error));
|
|
124
|
-
else resolve(response.data as T);
|
|
125
|
-
});
|
|
126
|
-
});
|
|
127
|
-
}
|
|
128
|
-
console.warn(`[Kernel Mock] Executing ${cmd} for tenant ${tenantId}`, args);
|
|
129
|
-
return {} as T;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
async notify(title: string, body: string): Promise<void> {
|
|
133
|
-
console.log(`[Notification Mock] ${title}: ${body}`);
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
async vibrate(pattern: any): Promise<void> {
|
|
137
|
-
if (typeof navigator !== 'undefined' && navigator.vibrate) {
|
|
138
|
-
navigator.vibrate(pattern === 'heavy' ? 200 : 50);
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
async getEventHistory(tenantId: string, limit: number): Promise<any[]> {
|
|
143
|
-
console.warn('[Kernel Mock] Cannot fetch stream history in Web Sandbox.');
|
|
144
|
-
return [];
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
listenEvents(onEvent: (payload: { type: string, channel: string, payload: any }) => void): () => void {
|
|
148
|
-
this.eventListeners.add(onEvent);
|
|
149
|
-
return () => this.eventListeners.delete(onEvent);
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
// -----------------------------------------------------
|
|
154
|
-
// HIGH LEVEL KERNEL API
|
|
155
|
-
// -----------------------------------------------------
|
|
156
|
-
class DecidoKernel {
|
|
157
|
-
private logs: LogEntry[] = [];
|
|
158
|
-
private logListeners: Set<(logs: LogEntry[]) => void> = new Set();
|
|
159
|
-
private universalListeners: Set<(event: unknown) => void> = new Set();
|
|
160
|
-
|
|
161
|
-
private transport: KernelTransport;
|
|
162
|
-
private rateLimits: Map<string, { calls: number, resetAt: number }> = new Map();
|
|
163
|
-
|
|
164
|
-
private checkRateLimit(cmd: string): boolean {
|
|
165
|
-
const now = Date.now();
|
|
166
|
-
const limitWin = 1000; // 1 second window
|
|
167
|
-
const maxCalls = 50; // 50 calls per second per command
|
|
168
|
-
|
|
169
|
-
let tracker = this.rateLimits.get(cmd);
|
|
170
|
-
if (!tracker || tracker.resetAt < now) {
|
|
171
|
-
tracker = { calls: 0, resetAt: now + limitWin };
|
|
172
|
-
this.rateLimits.set(cmd, tracker);
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
tracker.calls++;
|
|
176
|
-
if (tracker.calls > maxCalls) {
|
|
177
|
-
console.warn(`[Kernel Throttle] 🚨 Command ${cmd} blocked (Rate limit: >50 calls/sec). Possible plugin infinite loop.`);
|
|
178
|
-
return false;
|
|
179
|
-
}
|
|
180
|
-
return true;
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
constructor() {
|
|
184
|
-
// Factory detect
|
|
185
|
-
const isTauri = typeof window !== 'undefined' && '__TAURI_INTERNALS__' in window;
|
|
186
|
-
this.transport = isTauri ? new TauriTransport() : new WebSocketTransport();
|
|
187
|
-
|
|
188
|
-
console.log(`[Kernel] Entorno Detectado. Operando en Modo ${this.transport.name}.`);
|
|
189
|
-
|
|
190
|
-
// Global Event Hook
|
|
191
|
-
this.transport.listenEvents((event) => {
|
|
192
|
-
if (event.type === 'ipc_in') {
|
|
193
|
-
this.addLog({ type: 'ipc_in', channel: event.channel, payload: event.payload });
|
|
194
|
-
this.notifyUniversalListeners(event.payload.payload || event.payload);
|
|
195
|
-
}
|
|
196
|
-
});
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
public get isConnectedToCloud() {
|
|
200
|
-
return this.transport.isConnectedToCloud;
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
public connectToSwarm(tenantId: string, url: string, token?: string) {
|
|
204
|
-
this.transport.connectToSwarm(tenantId, url, token);
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
// Validador Zod y Notificador Universal
|
|
208
|
-
private notifyUniversalListeners(rawPayload: unknown) {
|
|
209
|
-
const parsed = createSafeEvent(rawPayload);
|
|
210
|
-
|
|
211
|
-
if (!parsed.success) {
|
|
212
|
-
console.error('[Kernel] 🚨 Inbound Mesh Event Failed Contract Validation:', parsed.error.format());
|
|
213
|
-
this.universalListeners.forEach(cb => cb({
|
|
214
|
-
type: 'system_alert',
|
|
215
|
-
source: 'kernel-bridge',
|
|
216
|
-
payload: {
|
|
217
|
-
level: 'error',
|
|
218
|
-
title: 'Contract Violation',
|
|
219
|
-
message: 'A received event did not match the strict schema. Ignored.'
|
|
220
|
-
}
|
|
221
|
-
}));
|
|
222
|
-
return;
|
|
223
|
-
}
|
|
224
|
-
this.universalListeners.forEach(cb => cb(parsed.data));
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
injectEvent(payload: unknown) {
|
|
228
|
-
this.notifyUniversalListeners(payload);
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
private addLog(entry: Omit<LogEntry, 'id' | 'timestamp'>) {
|
|
232
|
-
const log: LogEntry = {
|
|
233
|
-
id: typeof crypto !== 'undefined' && crypto.randomUUID ? crypto.randomUUID() : Math.random().toString(36).substring(2, 9),
|
|
234
|
-
timestamp: Date.now(),
|
|
235
|
-
...entry
|
|
236
|
-
};
|
|
237
|
-
this.logs = [log, ...this.logs].slice(0, 500);
|
|
238
|
-
this.logListeners.forEach(listener => listener(this.logs));
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
onLogsChange(callback: (logs: LogEntry[]) => void): () => void {
|
|
242
|
-
this.logListeners.add(callback);
|
|
243
|
-
callback(this.logs);
|
|
244
|
-
return () => this.logListeners.delete(callback);
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
async execute<T>(tenantIdOrCmd: string, cmdOrArgs?: string | Record<string, unknown>, argsOpts?: Record<string, unknown>): Promise<T> {
|
|
248
|
-
let tenantId = 'system';
|
|
249
|
-
let cmd = tenantIdOrCmd;
|
|
250
|
-
let args = cmdOrArgs as Record<string, unknown> | undefined;
|
|
251
|
-
|
|
252
|
-
if (typeof cmdOrArgs === 'string') {
|
|
253
|
-
tenantId = tenantIdOrCmd;
|
|
254
|
-
cmd = cmdOrArgs;
|
|
255
|
-
args = argsOpts;
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
if (!this.checkRateLimit(cmd)) {
|
|
259
|
-
throw new Error(`[Kernel Throttle] Request dropped for ${cmd} (Rate limit exceeded)`);
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
const start = performance.now();
|
|
263
|
-
try {
|
|
264
|
-
const result = await this.transport.execute<T>(tenantId, cmd, args);
|
|
265
|
-
this.addLog({ type: 'ipc_out', channel: cmd, payload: { tenantId, ...args }, duration: performance.now() - start });
|
|
266
|
-
return result;
|
|
267
|
-
} catch (error) {
|
|
268
|
-
this.addLog({ type: 'error', channel: cmd, payload: { tenantId, args, error }, duration: performance.now() - start });
|
|
269
|
-
throw error;
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
async notify(title: string, body: string): Promise<void> {
|
|
274
|
-
return this.transport.notify(title, body);
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
async vibrate(pattern: 'light' | 'medium' | 'heavy' | 'selection' | 'success' | 'warning' | 'error' = 'medium'): Promise<void> {
|
|
278
|
-
return this.transport.vibrate(pattern);
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
async captureScreen(tenantId: string = 'system'): Promise<string> {
|
|
282
|
-
return this.execute<string>(tenantId, 'capture_screen');
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
async getMachineId(tenantId: string = 'system'): Promise<string> {
|
|
286
|
-
return this.execute<string>(tenantId, 'get_machine_id');
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
async initiateSingularity(tenantId: string = 'system'): Promise<any[]> {
|
|
290
|
-
return this.execute<any[]>(tenantId, 'initiate_singularity');
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
async broadcastMessage(tenantId: string, message: string): Promise<number> {
|
|
294
|
-
return this.execute<number>(tenantId, 'broadcast_message', { message });
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
async fetchMemories(tenantId: string = 'system', query: string, limit: number = 20): Promise<any[]> {
|
|
298
|
-
return this.execute<any[]>(tenantId, 'fetch_memories', { query, limit });
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
async storeMemory(tenantId: string = 'system', text: string, metadata: Record<string, unknown> = {}): Promise<void> {
|
|
302
|
-
return this.execute<void>(tenantId, 'store_memory', { text, metadata });
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
async deleteMemory(tenantId: string = 'system', id: string): Promise<void> {
|
|
306
|
-
return this.execute<void>(tenantId, 'delete_memory', { id });
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
async reasonWithMLX(base64Image: string, prompt: string, previousError?: string): Promise<any> {
|
|
310
|
-
let finalPrompt = prompt;
|
|
311
|
-
if (previousError) {
|
|
312
|
-
finalPrompt += `\n\nWARNING: Your previous response failed to parse as JSON with the following error: ${previousError}. Fix the JSON syntax and return ONLY a valid JSON object.`;
|
|
313
|
-
}
|
|
314
|
-
try {
|
|
315
|
-
const response = await fetch('http://127.0.0.1:8080/v1/chat/completions', {
|
|
316
|
-
method: 'POST',
|
|
317
|
-
headers: { 'Content-Type': 'application/json' },
|
|
318
|
-
body: JSON.stringify({
|
|
319
|
-
model: 'llava',
|
|
320
|
-
messages: [{
|
|
321
|
-
role: 'user',
|
|
322
|
-
content: [
|
|
323
|
-
{ type: 'text', text: finalPrompt },
|
|
324
|
-
{ type: 'image_url', image_url: { url: base64Image } }
|
|
325
|
-
]
|
|
326
|
-
}],
|
|
327
|
-
max_tokens: 150, temperature: 0.1
|
|
328
|
-
})
|
|
329
|
-
});
|
|
330
|
-
const data = await response.json();
|
|
331
|
-
let content = data.choices[0].message.content.trim();
|
|
332
|
-
if (content.startsWith('```json')) content = content.replace(/```json\n?/, '').replace(/```$/, '').trim();
|
|
333
|
-
else if (content.startsWith('```')) content = content.replace(/```\n?/, '').replace(/```$/, '').trim();
|
|
334
|
-
return JSON.parse(content);
|
|
335
|
-
} catch (e: any) {
|
|
336
|
-
console.error('[MLX Bridge] Reasoning failed:', e);
|
|
337
|
-
throw e;
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
async executeAction(tenantId: string = 'system', action: any): Promise<any> {
|
|
342
|
-
return this.execute(tenantId, 'execute_action', { action });
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
async runAutonomousStep(tenantId: string = 'system', objective: string): Promise<void> {
|
|
346
|
-
const image = await this.captureScreen(tenantId);
|
|
347
|
-
let decision = null, retries = 0, lastError = null;
|
|
348
|
-
const instruction = `You are an AI OS agent. Based on the screen image, what action should be taken to achieve: "${objective}"? Return ONLY a strict JSON object of the action, no markdown. E.g. {"type": "mouseClick", "x": 100, "y": 200, "button": "left", "count": 1}`;
|
|
349
|
-
while (retries <= 2 && !decision) {
|
|
350
|
-
try {
|
|
351
|
-
decision = await this.reasonWithMLX(image, instruction, lastError || undefined);
|
|
352
|
-
} catch (error: any) {
|
|
353
|
-
lastError = error.message || String(error);
|
|
354
|
-
retries++;
|
|
355
|
-
}
|
|
356
|
-
}
|
|
357
|
-
if (!decision) return;
|
|
358
|
-
await this.executeAction(tenantId, decision);
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
async askAI(prompt: string, taskType: TaskType = 'reasoning' as any, forceProvider?: InferenceBackend): Promise<string> {
|
|
362
|
-
const result = await inferenceRouter.route(prompt, { preferredBackend: forceProvider || 'auto' });
|
|
363
|
-
return result.text;
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
onEvent(callback: (eventData: any) => void): () => void {
|
|
367
|
-
this.universalListeners.add(callback);
|
|
368
|
-
return () => this.universalListeners.delete(callback);
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
async getEventHistory(tenantId: string, limit: number = 50): Promise<any[]> {
|
|
372
|
-
return this.transport.getEventHistory(tenantId, limit);
|
|
373
|
-
}
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
export const kernel = new DecidoKernel();
|
package/src/rehydration.ts
DELETED
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
import { kernel } from './kernel';
|
|
2
|
-
import { decryptEvent } from './crypto';
|
|
3
|
-
// Import store if needed, or rely on kernel dispatch.
|
|
4
|
-
// We will broadcast locally to avoid tight coupling if we don't have direct access to the shell store here.
|
|
5
|
-
|
|
6
|
-
export const StateRehydrationManager = {
|
|
7
|
-
async rehydrate(swarmKey: string) {
|
|
8
|
-
console.log("[Rehydration] 🔄 Iniciando recuperación de estado...");
|
|
9
|
-
|
|
10
|
-
try {
|
|
11
|
-
// Pedimos los últimos 100 eventos del stream persistente
|
|
12
|
-
const history = await kernel.execute('get_stream_history', { limit: 100 });
|
|
13
|
-
|
|
14
|
-
if (!Array.isArray(history)) {
|
|
15
|
-
console.warn("[Rehydration] El historial devuelto no es un array válido.");
|
|
16
|
-
return;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
// Procesamos cronológicamente (de más viejo a más nuevo)
|
|
20
|
-
const events = history.reverse();
|
|
21
|
-
|
|
22
|
-
for (const rawEvent of events) {
|
|
23
|
-
let event = rawEvent;
|
|
24
|
-
|
|
25
|
-
// Si el evento viene cifrado (formato Secure Stream), lo desciframos
|
|
26
|
-
if (typeof rawEvent === 'string') {
|
|
27
|
-
try {
|
|
28
|
-
event = await decryptEvent(rawEvent, swarmKey);
|
|
29
|
-
} catch (e) {
|
|
30
|
-
console.warn("[Rehydration] No se pudo descifrar un evento, saltando...");
|
|
31
|
-
continue;
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
// Distribuimos el evento a los sistemas interesados
|
|
36
|
-
this.replayEvent(event);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
console.log(`[Rehydration] ✅ Memoria restaurada: ${events.length} eventos procesados.`);
|
|
40
|
-
} catch (error) {
|
|
41
|
-
console.error("[Rehydration] Fallo crítico en la rehidratación:", error);
|
|
42
|
-
}
|
|
43
|
-
},
|
|
44
|
-
|
|
45
|
-
replayEvent(event: any) {
|
|
46
|
-
try {
|
|
47
|
-
kernel.injectEvent({ ...event, is_rehydration: true });
|
|
48
|
-
} catch (e) {
|
|
49
|
-
console.warn("[Rehydration] Error inyectando evento de historial", e);
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
};
|
package/tsconfig.json
DELETED