aiden-runtime 3.16.0
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/LICENSE +661 -0
- package/README.md +465 -0
- package/config/devos.config.json +186 -0
- package/config/hardware.json +9 -0
- package/config/model-selection.json +7 -0
- package/config/setup-complete.json +20 -0
- package/dist/api/routes/computerUse.js +112 -0
- package/dist/api/server.js +6870 -0
- package/dist/bin/npx-init.js +71 -0
- package/dist/coordination/commandGate.js +115 -0
- package/dist/coordination/livePulse.js +127 -0
- package/dist/core/agentLoop.js +2718 -0
- package/dist/core/agentShield.js +231 -0
- package/dist/core/aidenIdentity.js +215 -0
- package/dist/core/aidenPersonality.js +166 -0
- package/dist/core/aidenSdk.js +374 -0
- package/dist/core/asyncTasks.js +82 -0
- package/dist/core/auditTrail.js +61 -0
- package/dist/core/auxiliaryClient.js +114 -0
- package/dist/core/bgLLM.js +108 -0
- package/dist/core/bm25.js +68 -0
- package/dist/core/callbackSystem.js +64 -0
- package/dist/core/channels/adapter.js +6 -0
- package/dist/core/channels/discord.js +173 -0
- package/dist/core/channels/email.js +253 -0
- package/dist/core/channels/imessage.js +164 -0
- package/dist/core/channels/manager.js +96 -0
- package/dist/core/channels/signal.js +140 -0
- package/dist/core/channels/slack.js +139 -0
- package/dist/core/channels/twilio.js +144 -0
- package/dist/core/channels/webhook.js +186 -0
- package/dist/core/channels/whatsapp.js +185 -0
- package/dist/core/clarifyBus.js +75 -0
- package/dist/core/codeInterpreter.js +82 -0
- package/dist/core/computerControl.js +439 -0
- package/dist/core/conversationMemory.js +334 -0
- package/dist/core/costTracker.js +221 -0
- package/dist/core/cronManager.js +217 -0
- package/dist/core/deepKB.js +77 -0
- package/dist/core/doctor.js +279 -0
- package/dist/core/dreamEngine.js +334 -0
- package/dist/core/entityGraph.js +169 -0
- package/dist/core/eventBus.js +16 -0
- package/dist/core/evolutionAnalyzer.js +153 -0
- package/dist/core/executionLoop.js +309 -0
- package/dist/core/executor.js +224 -0
- package/dist/core/failureAnalyzer.js +166 -0
- package/dist/core/fastPathExpansion.js +82 -0
- package/dist/core/faultEngine.js +106 -0
- package/dist/core/featureGates.js +70 -0
- package/dist/core/fileIngestion.js +113 -0
- package/dist/core/gateway.js +97 -0
- package/dist/core/goalTracker.js +75 -0
- package/dist/core/growthEngine.js +168 -0
- package/dist/core/hardwareDetector.js +98 -0
- package/dist/core/hooks.js +45 -0
- package/dist/core/httpKeepalive.js +46 -0
- package/dist/core/hybridSearch.js +101 -0
- package/dist/core/importers.js +164 -0
- package/dist/core/instinctSystem.js +223 -0
- package/dist/core/knowledgeBase.js +351 -0
- package/dist/core/learningMemory.js +121 -0
- package/dist/core/lessonsBrowser.js +125 -0
- package/dist/core/licenseManager.js +399 -0
- package/dist/core/logBuffer.js +85 -0
- package/dist/core/machineId.js +87 -0
- package/dist/core/mcpClient.js +442 -0
- package/dist/core/memoryDistiller.js +165 -0
- package/dist/core/memoryExtractor.js +212 -0
- package/dist/core/memoryIds.js +213 -0
- package/dist/core/memoryPreamble.js +113 -0
- package/dist/core/memoryQuery.js +136 -0
- package/dist/core/memoryRecall.js +140 -0
- package/dist/core/memoryStrategy.js +201 -0
- package/dist/core/messageValidator.js +85 -0
- package/dist/core/modelDiscovery.js +108 -0
- package/dist/core/modelRouter.js +118 -0
- package/dist/core/morningBriefing.js +203 -0
- package/dist/core/multiGoalValidator.js +51 -0
- package/dist/core/parallelExecutor.js +43 -0
- package/dist/core/passiveSkillObserver.js +204 -0
- package/dist/core/paths.js +57 -0
- package/dist/core/patternDetector.js +83 -0
- package/dist/core/planResponseRepair.js +64 -0
- package/dist/core/planTool.js +111 -0
- package/dist/core/playwrightBridge.js +356 -0
- package/dist/core/pluginSystem.js +121 -0
- package/dist/core/privateMode.js +85 -0
- package/dist/core/reactLoop.js +156 -0
- package/dist/core/recipeEngine.js +166 -0
- package/dist/core/responseCache.js +128 -0
- package/dist/core/runSandbox.js +132 -0
- package/dist/core/sandboxRunner.js +200 -0
- package/dist/core/scheduler.js +543 -0
- package/dist/core/secretScanner.js +49 -0
- package/dist/core/semanticMemory.js +223 -0
- package/dist/core/sessionMemory.js +259 -0
- package/dist/core/sessionRouter.js +91 -0
- package/dist/core/sessionSearch.js +163 -0
- package/dist/core/setupWizard.js +225 -0
- package/dist/core/skillImporter.js +303 -0
- package/dist/core/skillLibrary.js +144 -0
- package/dist/core/skillLoader.js +471 -0
- package/dist/core/skillTeacher.js +352 -0
- package/dist/core/skillValidator.js +210 -0
- package/dist/core/skillWriter.js +384 -0
- package/dist/core/slashAsTool.js +226 -0
- package/dist/core/spawnManager.js +197 -0
- package/dist/core/statusVerbs.js +43 -0
- package/dist/core/swarmManager.js +109 -0
- package/dist/core/taskQueue.js +119 -0
- package/dist/core/taskRecovery.js +128 -0
- package/dist/core/taskState.js +168 -0
- package/dist/core/telegramBot.js +152 -0
- package/dist/core/todoManager.js +70 -0
- package/dist/core/toolNameRepair.js +71 -0
- package/dist/core/toolRegistry.js +2730 -0
- package/dist/core/tools/calendarTool.js +98 -0
- package/dist/core/tools/companyFilingsTool.js +98 -0
- package/dist/core/tools/gmailTool.js +87 -0
- package/dist/core/tools/marketDataTool.js +135 -0
- package/dist/core/tools/socialResearchTool.js +121 -0
- package/dist/core/truthCheck.js +57 -0
- package/dist/core/updateChecker.js +74 -0
- package/dist/core/userCognitionProfile.js +238 -0
- package/dist/core/userProfile.js +341 -0
- package/dist/core/version.js +5 -0
- package/dist/core/visionAnalyze.js +161 -0
- package/dist/core/voice/audio.js +187 -0
- package/dist/core/voice/stt.js +226 -0
- package/dist/core/voice/tts.js +310 -0
- package/dist/core/voiceInput.js +118 -0
- package/dist/core/voiceOutput.js +130 -0
- package/dist/core/webSearch.js +326 -0
- package/dist/core/workflowTracker.js +72 -0
- package/dist/core/workspaceMemory.js +54 -0
- package/dist/core/youtubeTranscript.js +224 -0
- package/dist/integrations/computerUse/apiRegistry.js +113 -0
- package/dist/integrations/computerUse/screenAgent.js +203 -0
- package/dist/integrations/computerUse/visionLoop.js +296 -0
- package/dist/memory/memoryLayers.js +143 -0
- package/dist/providers/boa.js +93 -0
- package/dist/providers/cerebras.js +70 -0
- package/dist/providers/custom.js +89 -0
- package/dist/providers/gemini.js +82 -0
- package/dist/providers/groq.js +92 -0
- package/dist/providers/index.js +149 -0
- package/dist/providers/nvidia.js +70 -0
- package/dist/providers/ollama.js +99 -0
- package/dist/providers/openrouter.js +74 -0
- package/dist/providers/router.js +497 -0
- package/dist/providers/types.js +6 -0
- package/dist/security/browserVault.js +129 -0
- package/dist/security/dataGuard.js +89 -0
- package/dist/tools/eonetTool.js +72 -0
- package/dist/types/computerUse.js +2 -0
- package/dist/types/executor.js +2 -0
- package/dist-bundle/cli.js +357859 -0
- package/package.json +256 -0
|
@@ -0,0 +1,497 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// ============================================================
|
|
3
|
+
// DevOS — Autonomous AI Execution System
|
|
4
|
+
// Copyright (c) 2026 Shiva Deore. All rights reserved.
|
|
5
|
+
// ============================================================
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.getLocalModels = getLocalModels;
|
|
8
|
+
exports.initLocalModels = initLocalModels;
|
|
9
|
+
exports.getOllamaModelForTask = getOllamaModelForTask;
|
|
10
|
+
exports.getNextAvailableAPI = getNextAvailableAPI;
|
|
11
|
+
exports.markRateLimited = markRateLimited;
|
|
12
|
+
exports.markHealthy = markHealthy;
|
|
13
|
+
exports.recordResponseTime = recordResponseTime;
|
|
14
|
+
exports.incrementUsage = incrementUsage;
|
|
15
|
+
exports.logProviderStatus = logProviderStatus;
|
|
16
|
+
exports.assessComplexity = assessComplexity;
|
|
17
|
+
exports.getModelForTask = getModelForTask;
|
|
18
|
+
exports.getSmartProvider = getSmartProvider;
|
|
19
|
+
exports.isInDegradedMode = isInDegradedMode;
|
|
20
|
+
exports.exitDegradedMode = exitDegradedMode;
|
|
21
|
+
exports.enterDegradedMode = enterDegradedMode;
|
|
22
|
+
exports.getProviderHealthState = getProviderHealthState;
|
|
23
|
+
// providers/router.ts — Smart multi-API routing engine
|
|
24
|
+
// Round-robin across available keys, auto-marks 429s, falls back to Ollama
|
|
25
|
+
const index_1 = require("./index");
|
|
26
|
+
const ollama_1 = require("./ollama");
|
|
27
|
+
const groq_1 = require("./groq");
|
|
28
|
+
const openrouter_1 = require("./openrouter");
|
|
29
|
+
const gemini_1 = require("./gemini");
|
|
30
|
+
const cerebras_1 = require("./cerebras");
|
|
31
|
+
const nvidia_1 = require("./nvidia");
|
|
32
|
+
const boa_1 = require("./boa");
|
|
33
|
+
const custom_1 = require("./custom");
|
|
34
|
+
const modelDiscovery_1 = require("../core/modelDiscovery");
|
|
35
|
+
// Per-provider rate-limit windows — tuned to actual reset characteristics.
|
|
36
|
+
// Previous flat 1-hour window was far too conservative for fast-reset APIs.
|
|
37
|
+
const RATE_LIMIT_WINDOWS = {
|
|
38
|
+
groq: 15 * 1000, // Groq free tier resets in ~10–15 s
|
|
39
|
+
gemini: 90 * 1000, // Gemini resets in ~60–90 s
|
|
40
|
+
openrouter: 30 * 1000, // OpenRouter rarely rate-limits; 30 s is safe
|
|
41
|
+
together: 30 * 1000,
|
|
42
|
+
mistral: 60 * 1000,
|
|
43
|
+
cohere: 60 * 1000,
|
|
44
|
+
deepseek: 60 * 1000,
|
|
45
|
+
openai: 60 * 1000,
|
|
46
|
+
anthropic: 60 * 1000,
|
|
47
|
+
cerebras: 30 * 1000,
|
|
48
|
+
nvidia: 60 * 1000,
|
|
49
|
+
cloudflare: 30 * 1000,
|
|
50
|
+
github: 30 * 1000,
|
|
51
|
+
boa: 30 * 1000,
|
|
52
|
+
custom: 30 * 1000, // user-defined endpoints — 30 s default
|
|
53
|
+
ollama: 0, // local — never rate-limited
|
|
54
|
+
};
|
|
55
|
+
const DEFAULT_RATE_LIMIT_MS = 60 * 1000; // 1 minute fallback
|
|
56
|
+
// In-memory response-time tracking (EWMA per provider)
|
|
57
|
+
// Separate from the config file so it resets on restart without persisting stale values.
|
|
58
|
+
const responseTimesMs = new Map();
|
|
59
|
+
// In-memory consecutive failure tracking for exponential backoff.
|
|
60
|
+
// Resets to 0 on markHealthy; increments on each markRateLimited call.
|
|
61
|
+
const consecutiveFailures = new Map();
|
|
62
|
+
// ── Local model discovery cache ───────────────────────────────
|
|
63
|
+
// Populated once at startup via initLocalModels(). Read-only after that.
|
|
64
|
+
let localModels = {
|
|
65
|
+
planner: null, responder: null, coder: null, fast: null, all: [],
|
|
66
|
+
};
|
|
67
|
+
function getLocalModels() { return localModels; }
|
|
68
|
+
async function initLocalModels() {
|
|
69
|
+
localModels = await (0, modelDiscovery_1.discoverLocalModels)();
|
|
70
|
+
if (localModels.all.length > 0) {
|
|
71
|
+
console.log('[ModelDiscovery] Found local models:');
|
|
72
|
+
console.log(' Planner: ', localModels.planner);
|
|
73
|
+
console.log(' Responder: ', localModels.responder);
|
|
74
|
+
console.log(' Coder: ', localModels.coder);
|
|
75
|
+
console.log(' Fast: ', localModels.fast);
|
|
76
|
+
// Persist discovered assignments to config so agentLoop can read them
|
|
77
|
+
const config = (0, index_1.loadConfig)();
|
|
78
|
+
config.ollama = {
|
|
79
|
+
...(config.ollama || { fallbackModels: [], baseUrl: 'http://localhost:11434' }),
|
|
80
|
+
model: localModels.responder || config.ollama?.model || 'gemma4:e4b',
|
|
81
|
+
plannerModel: localModels.planner || undefined,
|
|
82
|
+
coderModel: localModels.coder || undefined,
|
|
83
|
+
fastModel: localModels.fast || undefined,
|
|
84
|
+
};
|
|
85
|
+
(0, index_1.saveConfig)(config);
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
console.log('[ModelDiscovery] No local models found — cloud only');
|
|
89
|
+
}
|
|
90
|
+
return localModels;
|
|
91
|
+
}
|
|
92
|
+
// ── Per-task Ollama model selector ────────────────────────────
|
|
93
|
+
function getOllamaModelForTask(task) {
|
|
94
|
+
// Prefer user overrides from config, then discovered models, then safe default
|
|
95
|
+
const config = (0, index_1.loadConfig)();
|
|
96
|
+
switch (task) {
|
|
97
|
+
case 'planner':
|
|
98
|
+
return config.ollama?.plannerModel || localModels.planner || localModels.responder || 'llama3.2';
|
|
99
|
+
case 'executor':
|
|
100
|
+
return config.ollama?.fastModel || localModels.fast || localModels.responder || 'llama3.2';
|
|
101
|
+
case 'responder':
|
|
102
|
+
return config.ollama?.model || localModels.responder || 'llama3.2';
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
// ── Provider factory ──────────────────────────────────────────
|
|
106
|
+
function buildProvider(entry) {
|
|
107
|
+
const key = entry.key.startsWith('env:')
|
|
108
|
+
? process.env[entry.key.replace('env:', '')] || ''
|
|
109
|
+
: entry.key;
|
|
110
|
+
switch (entry.provider) {
|
|
111
|
+
case 'groq': return (0, groq_1.createGroqProvider)(key);
|
|
112
|
+
case 'openrouter': return (0, openrouter_1.createOpenRouterProvider)(key);
|
|
113
|
+
case 'gemini': return (0, gemini_1.createGeminiProvider)(key);
|
|
114
|
+
case 'cerebras': return (0, cerebras_1.createCerebrasProvider)(key);
|
|
115
|
+
case 'nvidia': return (0, nvidia_1.createNvidiaProvider)(key);
|
|
116
|
+
case 'boa': return (0, boa_1.createBOAProvider)(key);
|
|
117
|
+
case 'custom': return (0, custom_1.createCustomProvider)(entry.baseUrl || '', key, entry.name);
|
|
118
|
+
default: return ollama_1.ollamaProvider;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
// ── Convert a CustomProviderEntry to a transient APIEntry ─────
|
|
122
|
+
// Lets custom providers flow through the same scoring + routing
|
|
123
|
+
// machinery as built-in APIs without modifying that logic.
|
|
124
|
+
function customToAPIEntry(cp) {
|
|
125
|
+
return {
|
|
126
|
+
name: cp.id,
|
|
127
|
+
provider: 'custom',
|
|
128
|
+
key: cp.apiKey,
|
|
129
|
+
model: cp.model,
|
|
130
|
+
enabled: cp.enabled,
|
|
131
|
+
rateLimited: false,
|
|
132
|
+
usageCount: 0,
|
|
133
|
+
baseUrl: cp.baseUrl,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
// ── Merge custom providers into an APIEntry pool ──────────────
|
|
137
|
+
// Inserts enabled custom providers in tier order before Ollama.
|
|
138
|
+
// Skips providers whose baseUrl is empty.
|
|
139
|
+
function mergeCustomProviders(base) {
|
|
140
|
+
const config = (0, index_1.loadConfig)();
|
|
141
|
+
const customs = (config.customProviders || [])
|
|
142
|
+
.filter(cp => cp.enabled && cp.baseUrl.trim().length > 0);
|
|
143
|
+
// Tier-sort the combined pool: customs use their tier, base entries default to tier 99.
|
|
144
|
+
// JS Array.sort is stable (ES2019+/Node 11+) — insertion order preserved within same tier.
|
|
145
|
+
const ranked = [
|
|
146
|
+
...base.map(e => ({ entry: e, tier: 99 })),
|
|
147
|
+
...customs.map(cp => ({ entry: customToAPIEntry(cp), tier: cp.tier ?? 99 })),
|
|
148
|
+
];
|
|
149
|
+
ranked.sort((a, b) => a.tier - b.tier);
|
|
150
|
+
return ranked.map(r => r.entry);
|
|
151
|
+
}
|
|
152
|
+
// ── Auto-reset stale rate limits ──────────────────────────────
|
|
153
|
+
function autoResetExpiredLimits() {
|
|
154
|
+
const config = (0, index_1.loadConfig)();
|
|
155
|
+
let changed = false;
|
|
156
|
+
config.providers.apis = config.providers.apis.map(api => {
|
|
157
|
+
if (api.rateLimited && api.rateLimitedAt) {
|
|
158
|
+
// Use stored backoff window if available (set by exponential markRateLimited),
|
|
159
|
+
// otherwise fall back to the static per-provider window.
|
|
160
|
+
const window = api.rateLimitWindow
|
|
161
|
+
?? RATE_LIMIT_WINDOWS[api.provider]
|
|
162
|
+
?? DEFAULT_RATE_LIMIT_MS;
|
|
163
|
+
if (window === 0 || Date.now() - api.rateLimitedAt > window) {
|
|
164
|
+
changed = true;
|
|
165
|
+
const { rateLimitedAt, ...rest } = api;
|
|
166
|
+
return { ...rest, rateLimited: false, rateLimitWindow: undefined };
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
return api;
|
|
170
|
+
});
|
|
171
|
+
if (changed)
|
|
172
|
+
(0, index_1.saveConfig)(config);
|
|
173
|
+
return changed;
|
|
174
|
+
}
|
|
175
|
+
// ── Get next available API — scored by response time + failures ──
|
|
176
|
+
function getNextAvailableAPI() {
|
|
177
|
+
autoResetExpiredLimits();
|
|
178
|
+
const config = (0, index_1.loadConfig)();
|
|
179
|
+
const allApis = mergeCustomProviders(config.providers.apis);
|
|
180
|
+
const available = allApis.filter(api => {
|
|
181
|
+
if (!api.enabled || api.rateLimited)
|
|
182
|
+
return false;
|
|
183
|
+
// Custom providers use baseUrl instead of a key — allow empty key for local endpoints
|
|
184
|
+
if (api.provider === 'custom')
|
|
185
|
+
return (api.baseUrl || '').trim().length > 0;
|
|
186
|
+
// Resolve the actual key value — skip if env var is missing or empty
|
|
187
|
+
const resolvedKey = api.key.startsWith('env:')
|
|
188
|
+
? (process.env[api.key.replace('env:', '')] || '')
|
|
189
|
+
: api.key;
|
|
190
|
+
return resolvedKey.length > 0;
|
|
191
|
+
});
|
|
192
|
+
if (!available.length)
|
|
193
|
+
return null;
|
|
194
|
+
// Score: lower is better — blend usage count, response time, and failure history
|
|
195
|
+
const primary = config.primaryProvider;
|
|
196
|
+
const scored = available
|
|
197
|
+
.map(api => {
|
|
198
|
+
const avgMs = responseTimesMs.get(api.name) ?? 2000; // assume 2s if unknown
|
|
199
|
+
const usageScore = (api.usageCount || 0) * 0.1;
|
|
200
|
+
const timeScore = avgMs / 1000;
|
|
201
|
+
const primaryBoost = (primary && (api.name === primary || api.provider === primary)) ? -1000 : 0;
|
|
202
|
+
return { api, score: usageScore + timeScore + primaryBoost };
|
|
203
|
+
})
|
|
204
|
+
.sort((a, b) => a.score - b.score);
|
|
205
|
+
const entry = scored[0].api;
|
|
206
|
+
return { provider: buildProvider(entry), model: entry.model, entry };
|
|
207
|
+
}
|
|
208
|
+
// ── Mark an API as rate-limited (exponential backoff) ────────
|
|
209
|
+
function markRateLimited(apiName) {
|
|
210
|
+
const config = (0, index_1.loadConfig)();
|
|
211
|
+
const entry = config.providers.apis.find(a => a.name === apiName);
|
|
212
|
+
const base = entry ? (RATE_LIMIT_WINDOWS[entry.provider] ?? DEFAULT_RATE_LIMIT_MS) : DEFAULT_RATE_LIMIT_MS;
|
|
213
|
+
// Exponential backoff: base → 2× → 4× → 8× … capped at 5 min
|
|
214
|
+
const failures = (consecutiveFailures.get(apiName) ?? 0) + 1;
|
|
215
|
+
consecutiveFailures.set(apiName, failures);
|
|
216
|
+
const backoffMs = Math.min(300000, base * Math.pow(2, failures - 1));
|
|
217
|
+
config.providers.apis = config.providers.apis.map(api => api.name === apiName
|
|
218
|
+
? { ...api, rateLimited: true, rateLimitedAt: Date.now(), rateLimitWindow: backoffMs }
|
|
219
|
+
: api);
|
|
220
|
+
// Auto-unpin: if the pinned primary provider accumulates 3+ consecutive failures, clear the pin
|
|
221
|
+
// so the router can fall back to the next healthy provider automatically.
|
|
222
|
+
const AUTO_UNPIN_THRESHOLD = 3;
|
|
223
|
+
if (failures >= AUTO_UNPIN_THRESHOLD && config.primaryProvider) {
|
|
224
|
+
const pinnedName = config.primaryProvider;
|
|
225
|
+
const pinnedProvider = entry?.provider ?? '';
|
|
226
|
+
if (apiName === pinnedName || pinnedProvider === pinnedName) {
|
|
227
|
+
delete config.primaryProvider;
|
|
228
|
+
console.log(`[Router] Auto-unpinned "${pinnedName}" after ${failures} consecutive failures`);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
(0, index_1.saveConfig)(config);
|
|
232
|
+
console.log(`[Router] ${apiName} rate limited (failure #${failures}) — retry in ${Math.round(backoffMs / 1000)}s`);
|
|
233
|
+
}
|
|
234
|
+
// ── Mark an API as healthy ────────────────────────────────────
|
|
235
|
+
function markHealthy(apiName) {
|
|
236
|
+
if (apiName === 'ollama')
|
|
237
|
+
return;
|
|
238
|
+
const prev = consecutiveFailures.get(apiName) ?? 0;
|
|
239
|
+
if (prev === 0)
|
|
240
|
+
return; // already healthy, skip disk write
|
|
241
|
+
consecutiveFailures.set(apiName, 0);
|
|
242
|
+
const config = (0, index_1.loadConfig)();
|
|
243
|
+
config.providers.apis = config.providers.apis.map(api => api.name === apiName
|
|
244
|
+
? { ...api, rateLimited: false, rateLimitedAt: undefined }
|
|
245
|
+
: api);
|
|
246
|
+
(0, index_1.saveConfig)(config);
|
|
247
|
+
console.log(`[Router] ${apiName} marked healthy — backoff cleared`);
|
|
248
|
+
}
|
|
249
|
+
// ── Record response time (EWMA) ───────────────────────────────
|
|
250
|
+
// Call this after each successful LLM response to improve provider selection.
|
|
251
|
+
function recordResponseTime(providerName, ms) {
|
|
252
|
+
const prev = responseTimesMs.get(providerName);
|
|
253
|
+
// Exponential moving average — weight recent observations at 20%
|
|
254
|
+
responseTimesMs.set(providerName, prev ? prev * 0.8 + ms * 0.2 : ms);
|
|
255
|
+
}
|
|
256
|
+
// ── Increment usage count ─────────────────────────────────────
|
|
257
|
+
function incrementUsage(apiName) {
|
|
258
|
+
if (apiName === 'ollama')
|
|
259
|
+
return; // don't track Ollama usage
|
|
260
|
+
const config = (0, index_1.loadConfig)();
|
|
261
|
+
config.providers.apis = config.providers.apis.map(api => api.name === apiName ? { ...api, usageCount: (api.usageCount || 0) + 1 } : api);
|
|
262
|
+
(0, index_1.saveConfig)(config);
|
|
263
|
+
}
|
|
264
|
+
// ── Log which providers are active at startup ────────────────
|
|
265
|
+
function logProviderStatus() {
|
|
266
|
+
const config = (0, index_1.loadConfig)();
|
|
267
|
+
const apis = mergeCustomProviders(config.providers.apis);
|
|
268
|
+
const isDebug = (process.env.AIDEN_LOG_LEVEL || 'info') === 'debug';
|
|
269
|
+
if (config.primaryProvider) {
|
|
270
|
+
console.log('[Router] Primary provider: ' + config.primaryProvider + ' (user override)');
|
|
271
|
+
}
|
|
272
|
+
else {
|
|
273
|
+
console.log('[Router] Primary provider: (default ordering)');
|
|
274
|
+
}
|
|
275
|
+
let order = 1;
|
|
276
|
+
let active = 0;
|
|
277
|
+
const lines = [];
|
|
278
|
+
for (const api of apis) {
|
|
279
|
+
if (api.provider === 'custom') {
|
|
280
|
+
const hasUrl = (api.baseUrl || '').trim().length > 0;
|
|
281
|
+
const status = !api.enabled ? 'disabled' : !hasUrl ? 'SKIPPED (no url)' : '#' + (order++) + ' active';
|
|
282
|
+
if (status.includes('active'))
|
|
283
|
+
active++;
|
|
284
|
+
lines.push(' ' + api.name + ' (custom/' + api.model + ') - [' + (hasUrl ? 'url OK' : 'NO URL') + '] - ' + status);
|
|
285
|
+
continue;
|
|
286
|
+
}
|
|
287
|
+
const resolvedKey = api.key.startsWith('env:')
|
|
288
|
+
? (process.env[api.key.replace('env:', '')] || '')
|
|
289
|
+
: api.key;
|
|
290
|
+
const keyStatus = resolvedKey.length > 0 ? '[key OK]' : '[NO KEY]';
|
|
291
|
+
const status = !api.enabled ? 'disabled' : api.rateLimited ? 'rate-limited' : resolvedKey.length === 0 ? 'SKIPPED (no key)' : '#' + (order++) + ' active';
|
|
292
|
+
if (status.includes('active'))
|
|
293
|
+
active++;
|
|
294
|
+
lines.push(' ' + api.name + ' (' + api.provider + '/' + api.model + ') - ' + keyStatus + ' - ' + status);
|
|
295
|
+
}
|
|
296
|
+
lines.push(' ollama (' + OLLAMA_FALLBACK_MODEL + ') - local - #' + order + ' guaranteed fallback');
|
|
297
|
+
if (isDebug) {
|
|
298
|
+
console.log('[Router] Provider chain:');
|
|
299
|
+
lines.forEach(l => console.log(l));
|
|
300
|
+
}
|
|
301
|
+
else {
|
|
302
|
+
console.log('[Router] Provider chain: ' + active + ' active + Ollama fallback (AIDEN_LOG_LEVEL=debug for detail)');
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
// ── Complexity scorer ─────────────────────────────────────────
|
|
306
|
+
// Returns 0–1 where 0 = trivially simple (local Ollama) and
|
|
307
|
+
// 1 = highly complex (needs best cloud model).
|
|
308
|
+
function assessComplexity(message) {
|
|
309
|
+
let score = 0.3;
|
|
310
|
+
if (message.length > 500)
|
|
311
|
+
score += 0.15;
|
|
312
|
+
if (message.length > 1000)
|
|
313
|
+
score += 0.10;
|
|
314
|
+
const complexPatterns = [
|
|
315
|
+
/research|analyze|compare|explain in detail/i,
|
|
316
|
+
/plan|strategy|architecture|design/i,
|
|
317
|
+
/write.*code|build|create|implement/i,
|
|
318
|
+
/debug|fix.*error|troubleshoot/i,
|
|
319
|
+
/multi.*step|comprehensive|deep.research/i,
|
|
320
|
+
];
|
|
321
|
+
const simplePatterns = [
|
|
322
|
+
/^(hi|hello|hey|thanks|thank you|ok|yes|no|sure)\b/i,
|
|
323
|
+
/what time|what date|who are you|what can you do/i,
|
|
324
|
+
/^.{1,30}$/,
|
|
325
|
+
/^(good morning|good night|bye)\b/i,
|
|
326
|
+
];
|
|
327
|
+
if (complexPatterns.some(p => p.test(message)))
|
|
328
|
+
score += 0.30;
|
|
329
|
+
if (simplePatterns.some(p => p.test(message)))
|
|
330
|
+
score -= 0.30;
|
|
331
|
+
if (/open|launch|run|execute|deploy/i.test(message))
|
|
332
|
+
score += 0.10;
|
|
333
|
+
const qMarks = (message.match(/\?/g) || []).length;
|
|
334
|
+
if (qMarks > 2)
|
|
335
|
+
score += 0.15;
|
|
336
|
+
return Math.max(0, Math.min(1, score));
|
|
337
|
+
}
|
|
338
|
+
const OLLAMA_FALLBACK_MODEL = 'gemma4:e4b';
|
|
339
|
+
function resolveKey(api) {
|
|
340
|
+
return {
|
|
341
|
+
apiKey: api.key.startsWith('env:')
|
|
342
|
+
? (process.env[api.key.replace('env:', '')] || '')
|
|
343
|
+
: api.key,
|
|
344
|
+
model: api.model,
|
|
345
|
+
providerName: api.provider,
|
|
346
|
+
apiName: api.name,
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
const OLLAMA_RESULT = {
|
|
350
|
+
apiKey: '', model: OLLAMA_FALLBACK_MODEL, providerName: 'ollama', apiName: 'ollama',
|
|
351
|
+
};
|
|
352
|
+
function getModelForTask(task, message) {
|
|
353
|
+
// ── Complexity gate — responder only ─────────────────────────
|
|
354
|
+
// Ollama is used ONLY when all cloud providers are rate-limited (true fallback).
|
|
355
|
+
// Simple queries still get cloud speed (Groq is fast); Ollama is last resort.
|
|
356
|
+
if (task === 'responder' && message) {
|
|
357
|
+
const complexity = assessComplexity(message);
|
|
358
|
+
console.log(`[Router] Complexity: ${complexity.toFixed(2)} — "${message.substring(0, 40)}"`);
|
|
359
|
+
}
|
|
360
|
+
autoResetExpiredLimits();
|
|
361
|
+
const config = (0, index_1.loadConfig)();
|
|
362
|
+
const allApis = mergeCustomProviders(config.providers.apis);
|
|
363
|
+
const available = allApis.filter(a => {
|
|
364
|
+
if (!a.enabled || a.rateLimited)
|
|
365
|
+
return false;
|
|
366
|
+
if (a.provider === 'custom')
|
|
367
|
+
return (a.baseUrl || '').trim().length > 0;
|
|
368
|
+
const k = a.key.startsWith('env:') ? (process.env[a.key.replace('env:', '')] || '') : a.key;
|
|
369
|
+
return k.length > 0;
|
|
370
|
+
});
|
|
371
|
+
// Planner + Responder: walk ALL apis in config order (handles multiple slots per provider).
|
|
372
|
+
// Cerebras/nvidia excluded — 8B models cannot follow complex SOUL-based prompts.
|
|
373
|
+
const CHAT_EXCLUDED = new Set(['cerebras', 'nvidia']);
|
|
374
|
+
if (task === 'planner' || task === 'responder') {
|
|
375
|
+
let chatApis = available.filter(a => !CHAT_EXCLUDED.has(a.provider));
|
|
376
|
+
// Primary provider pinning — move primary to front of chain
|
|
377
|
+
const primaryPin = config.primaryProvider;
|
|
378
|
+
if (primaryPin && chatApis.length > 1) {
|
|
379
|
+
const idx = chatApis.findIndex(a => a.name === primaryPin || a.provider === primaryPin);
|
|
380
|
+
if (idx > 0)
|
|
381
|
+
chatApis = [chatApis[idx], ...chatApis.slice(0, idx), ...chatApis.slice(idx + 1)];
|
|
382
|
+
}
|
|
383
|
+
if (chatApis.length > 0) {
|
|
384
|
+
const chosen = chatApis[0];
|
|
385
|
+
const pinTag = primaryPin && (chosen.name === primaryPin || chosen.provider === primaryPin) ? ' [primary]' : '';
|
|
386
|
+
console.log(`[Router] ${task}: ${chosen.name} (${chosen.provider}/${chosen.model})${pinTag}`);
|
|
387
|
+
return resolveKey(chosen);
|
|
388
|
+
}
|
|
389
|
+
const model = getOllamaModelForTask(task === 'planner' ? 'planner' : 'responder');
|
|
390
|
+
console.log(`[Router] ${task}: all cloud providers rate-limited - using Ollama ${model}`);
|
|
391
|
+
return { apiKey: '', model, providerName: 'ollama', apiName: 'ollama' };
|
|
392
|
+
}
|
|
393
|
+
// Executor: fastest — cerebras > groq > nvidia → discovered fast model
|
|
394
|
+
if (task === 'executor') {
|
|
395
|
+
for (const p of ['cerebras', 'groq', 'nvidia', 'openai']) {
|
|
396
|
+
const api = available.find(a => a.provider === p);
|
|
397
|
+
if (api)
|
|
398
|
+
return resolveKey(api);
|
|
399
|
+
}
|
|
400
|
+
const model = getOllamaModelForTask('executor');
|
|
401
|
+
console.log(`[Router] Executor: all cloud providers unavailable - falling back to Ollama ${model}`);
|
|
402
|
+
return { apiKey: '', model, providerName: 'ollama', apiName: 'ollama' };
|
|
403
|
+
}
|
|
404
|
+
// Generic fallback — any available API, then gemma4:e4b
|
|
405
|
+
if (available.length > 0)
|
|
406
|
+
return resolveKey(available[0]);
|
|
407
|
+
return OLLAMA_RESULT;
|
|
408
|
+
}
|
|
409
|
+
// ── Main entry: get smart provider with full fallback chain ───
|
|
410
|
+
function getSmartProvider() {
|
|
411
|
+
const config = (0, index_1.loadConfig)();
|
|
412
|
+
const userName = config.user?.name || 'there';
|
|
413
|
+
// MANUAL MODE: use the explicitly selected active provider
|
|
414
|
+
if (config.routing?.mode === 'manual') {
|
|
415
|
+
if (config.model.active === 'ollama') {
|
|
416
|
+
return { provider: ollama_1.ollamaProvider, model: config.model.activeModel || OLLAMA_FALLBACK_MODEL, userName, apiName: 'ollama' };
|
|
417
|
+
}
|
|
418
|
+
const active = config.providers.apis.find(a => a.name === config.model.active);
|
|
419
|
+
if (active && active.enabled && !active.rateLimited) {
|
|
420
|
+
return { provider: buildProvider(active), model: active.model || config.model.activeModel, userName, apiName: active.name };
|
|
421
|
+
}
|
|
422
|
+
// Configured API is unavailable — fall through to auto
|
|
423
|
+
}
|
|
424
|
+
// AUTO MODE: round-robin across available APIs
|
|
425
|
+
const next = getNextAvailableAPI();
|
|
426
|
+
if (next) {
|
|
427
|
+
return { provider: next.provider, model: next.entry.model || 'llama-3.3-70b-versatile', userName, apiName: next.entry.name };
|
|
428
|
+
}
|
|
429
|
+
// FALLBACK: best discovered Ollama model
|
|
430
|
+
if (config.routing?.fallbackToOllama !== false) {
|
|
431
|
+
const model = getOllamaModelForTask('responder');
|
|
432
|
+
console.log(`[Router] All APIs unavailable — falling back to Ollama ${model}`);
|
|
433
|
+
return { provider: ollama_1.ollamaProvider, model, userName, apiName: 'ollama' };
|
|
434
|
+
}
|
|
435
|
+
// Last resort
|
|
436
|
+
const model = getOllamaModelForTask('responder');
|
|
437
|
+
return { provider: ollama_1.ollamaProvider, model, userName, apiName: 'ollama' };
|
|
438
|
+
}
|
|
439
|
+
let _degradedMode = false;
|
|
440
|
+
let _degradedTimer = null;
|
|
441
|
+
function isInDegradedMode() { return _degradedMode; }
|
|
442
|
+
function exitDegradedMode() {
|
|
443
|
+
_degradedMode = false;
|
|
444
|
+
if (_degradedTimer) {
|
|
445
|
+
clearTimeout(_degradedTimer);
|
|
446
|
+
_degradedTimer = null;
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
function enterDegradedMode(reason) {
|
|
450
|
+
console.log(`[Degraded] All providers unavailable: ${reason}`);
|
|
451
|
+
_degradedMode = true;
|
|
452
|
+
// Auto-retry after 60 s — silently check if any provider is back
|
|
453
|
+
if (!_degradedTimer) {
|
|
454
|
+
_degradedTimer = setTimeout(async () => {
|
|
455
|
+
_degradedTimer = null;
|
|
456
|
+
autoResetExpiredLimits();
|
|
457
|
+
const next = getNextAvailableAPI();
|
|
458
|
+
if (next) {
|
|
459
|
+
console.log(`[Degraded] Provider recovered: ${next.entry.name}`);
|
|
460
|
+
exitDegradedMode();
|
|
461
|
+
return;
|
|
462
|
+
}
|
|
463
|
+
// Check if Ollama came back
|
|
464
|
+
try {
|
|
465
|
+
const ollamaBase = (process.env.OLLAMA_HOST ?? 'http://127.0.0.1:11434').replace(/\/$/, '');
|
|
466
|
+
const r = await fetch(`${ollamaBase}/api/tags`, { signal: AbortSignal.timeout(3000) });
|
|
467
|
+
if (r.ok) {
|
|
468
|
+
console.log('[Degraded] Provider recovered: ollama');
|
|
469
|
+
exitDegradedMode();
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
catch { /* still down */ }
|
|
473
|
+
}, 60000);
|
|
474
|
+
}
|
|
475
|
+
return {
|
|
476
|
+
mode: 'degraded',
|
|
477
|
+
message: `I'm temporarily running in limited mode — my AI providers ` +
|
|
478
|
+
`are at capacity. I can still:\n` +
|
|
479
|
+
`• Search your files and memory\n` +
|
|
480
|
+
`• Run scheduled tasks\n` +
|
|
481
|
+
`• Execute shell commands and scripts\n` +
|
|
482
|
+
`• Open browsers and apps\n\n` +
|
|
483
|
+
`I'll automatically reconnect when providers are available. ` +
|
|
484
|
+
`This usually resolves in a few minutes.`,
|
|
485
|
+
availableTools: ['file_read', 'file_write', 'file_list',
|
|
486
|
+
'shell_exec', 'run_python', 'run_node', 'open_browser',
|
|
487
|
+
'system_info', 'notify'],
|
|
488
|
+
retryAfter: 60000,
|
|
489
|
+
};
|
|
490
|
+
}
|
|
491
|
+
// ── In-memory health snapshot (for /api/providers/state) ─────
|
|
492
|
+
function getProviderHealthState() {
|
|
493
|
+
return {
|
|
494
|
+
consecutiveFailures: Object.fromEntries(consecutiveFailures),
|
|
495
|
+
responseTimesMs: Object.fromEntries(responseTimesMs),
|
|
496
|
+
};
|
|
497
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// ============================================================
|
|
3
|
+
// DevOS — Autonomous AI Execution System
|
|
4
|
+
// Copyright (c) 2026 Shiva Deore. All rights reserved.
|
|
5
|
+
// ============================================================
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// ============================================================
|
|
3
|
+
// DevOS — Autonomous AI Execution System
|
|
4
|
+
// Copyright (c) 2026 Shiva Deore. All rights reserved.
|
|
5
|
+
// ============================================================
|
|
6
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
7
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
8
|
+
};
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.browserVault = void 0;
|
|
11
|
+
// security/browserVault.ts — Playwright-in-Docker with noVNC LiveView.
|
|
12
|
+
// Stub for sandbox environment; full implementation committed in Sprint 20.
|
|
13
|
+
const dockerode_1 = __importDefault(require("dockerode"));
|
|
14
|
+
const path_1 = __importDefault(require("path"));
|
|
15
|
+
const fs_1 = __importDefault(require("fs"));
|
|
16
|
+
// ── Constants ──────────────────────────────────────────────────
|
|
17
|
+
const PLAYWRIGHT_IMAGE = 'mcr.microsoft.com/playwright:v1.40.0-jammy';
|
|
18
|
+
const CONTAINER_VNC_WS_PORT = 6080;
|
|
19
|
+
const HOST_PORT_BASE = 6100;
|
|
20
|
+
const ENTRYPOINT_CMD = [
|
|
21
|
+
'sh', '-c',
|
|
22
|
+
[
|
|
23
|
+
'Xvfb :99 -screen 0 1280x900x24 &',
|
|
24
|
+
'export DISPLAY=:99',
|
|
25
|
+
'sleep 1',
|
|
26
|
+
'x11vnc -display :99 -nopw -forever -rfbport 5900 -quiet &',
|
|
27
|
+
`websockify --web /usr/share/novnc 0.0.0.0:${CONTAINER_VNC_WS_PORT} localhost:5900 &`,
|
|
28
|
+
'tail -f /dev/null',
|
|
29
|
+
].join(' && '),
|
|
30
|
+
];
|
|
31
|
+
// ── Persistence ────────────────────────────────────────────────
|
|
32
|
+
const WORKSPACE = path_1.default.join(process.cwd(), 'workspace');
|
|
33
|
+
const BVAULTS_FILE = path_1.default.join(WORKSPACE, 'browser-vaults.json');
|
|
34
|
+
function loadPersistedBVaults() {
|
|
35
|
+
try {
|
|
36
|
+
if (!fs_1.default.existsSync(BVAULTS_FILE))
|
|
37
|
+
return [];
|
|
38
|
+
return JSON.parse(fs_1.default.readFileSync(BVAULTS_FILE, 'utf-8'));
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
return [];
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
function savePersistedBVaults(vaults) {
|
|
45
|
+
fs_1.default.mkdirSync(WORKSPACE, { recursive: true });
|
|
46
|
+
fs_1.default.writeFileSync(BVAULTS_FILE, JSON.stringify(vaults, null, 2));
|
|
47
|
+
}
|
|
48
|
+
// ── BrowserVaultManager ───────────────────────────────────────
|
|
49
|
+
class BrowserVaultManager {
|
|
50
|
+
constructor() {
|
|
51
|
+
this.docker = new dockerode_1.default();
|
|
52
|
+
this.vaults = new Map();
|
|
53
|
+
this.nextPort = HOST_PORT_BASE;
|
|
54
|
+
for (const v of loadPersistedBVaults()) {
|
|
55
|
+
this.vaults.set(v.taskId, v);
|
|
56
|
+
if (v.hostPort >= this.nextPort)
|
|
57
|
+
this.nextPort = v.hostPort + 1;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
allocatePort() { return this.nextPort++; }
|
|
61
|
+
persist() {
|
|
62
|
+
savePersistedBVaults(Array.from(this.vaults.values()));
|
|
63
|
+
}
|
|
64
|
+
async createBrowserVault(taskId) {
|
|
65
|
+
const existing = this.vaults.get(taskId);
|
|
66
|
+
if (existing)
|
|
67
|
+
return existing;
|
|
68
|
+
const containerName = `devos-browser-${taskId}`;
|
|
69
|
+
const hostPort = this.allocatePort();
|
|
70
|
+
let container;
|
|
71
|
+
try {
|
|
72
|
+
container = await this.docker.createContainer({
|
|
73
|
+
name: containerName,
|
|
74
|
+
Image: PLAYWRIGHT_IMAGE,
|
|
75
|
+
Cmd: ENTRYPOINT_CMD,
|
|
76
|
+
Env: ['DISPLAY=:99'],
|
|
77
|
+
ExposedPorts: { [`${CONTAINER_VNC_WS_PORT}/tcp`]: {} },
|
|
78
|
+
HostConfig: {
|
|
79
|
+
PortBindings: {
|
|
80
|
+
[`${CONTAINER_VNC_WS_PORT}/tcp`]: [{ HostPort: String(hostPort) }],
|
|
81
|
+
},
|
|
82
|
+
Memory: 1024 * 1024 * 1024,
|
|
83
|
+
NanoCpus: 1000000000,
|
|
84
|
+
AutoRemove: true,
|
|
85
|
+
ShmSize: 256 * 1024 * 1024,
|
|
86
|
+
CapAdd: ['SYS_ADMIN'],
|
|
87
|
+
},
|
|
88
|
+
});
|
|
89
|
+
await container.start();
|
|
90
|
+
}
|
|
91
|
+
catch (err) {
|
|
92
|
+
throw new Error(`[BrowserVault] Failed to create container: ${err.message}`);
|
|
93
|
+
}
|
|
94
|
+
const vault = {
|
|
95
|
+
taskId, containerId: container.id, containerName, hostPort, createdAt: Date.now(),
|
|
96
|
+
};
|
|
97
|
+
this.vaults.set(taskId, vault);
|
|
98
|
+
this.persist();
|
|
99
|
+
return vault;
|
|
100
|
+
}
|
|
101
|
+
getLiveViewUrl(taskId) {
|
|
102
|
+
const vault = this.vaults.get(taskId);
|
|
103
|
+
if (!vault)
|
|
104
|
+
return null;
|
|
105
|
+
return `ws://localhost:${vault.hostPort}/websockify`;
|
|
106
|
+
}
|
|
107
|
+
isLiveViewAvailable(taskId) {
|
|
108
|
+
return this.vaults.has(taskId);
|
|
109
|
+
}
|
|
110
|
+
async destroyBrowserVault(taskId) {
|
|
111
|
+
const vault = this.vaults.get(taskId);
|
|
112
|
+
if (vault) {
|
|
113
|
+
try {
|
|
114
|
+
await this.docker.getContainer(vault.containerId).stop({ t: 5 });
|
|
115
|
+
}
|
|
116
|
+
catch { }
|
|
117
|
+
this.vaults.delete(taskId);
|
|
118
|
+
this.persist();
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
listBrowserVaults() {
|
|
122
|
+
return Array.from(this.vaults.values());
|
|
123
|
+
}
|
|
124
|
+
async destroyAll() {
|
|
125
|
+
const ids = Array.from(this.vaults.keys());
|
|
126
|
+
await Promise.all(ids.map(id => this.destroyBrowserVault(id)));
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
exports.browserVault = new BrowserVaultManager();
|