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.
Files changed (159) hide show
  1. package/LICENSE +661 -0
  2. package/README.md +465 -0
  3. package/config/devos.config.json +186 -0
  4. package/config/hardware.json +9 -0
  5. package/config/model-selection.json +7 -0
  6. package/config/setup-complete.json +20 -0
  7. package/dist/api/routes/computerUse.js +112 -0
  8. package/dist/api/server.js +6870 -0
  9. package/dist/bin/npx-init.js +71 -0
  10. package/dist/coordination/commandGate.js +115 -0
  11. package/dist/coordination/livePulse.js +127 -0
  12. package/dist/core/agentLoop.js +2718 -0
  13. package/dist/core/agentShield.js +231 -0
  14. package/dist/core/aidenIdentity.js +215 -0
  15. package/dist/core/aidenPersonality.js +166 -0
  16. package/dist/core/aidenSdk.js +374 -0
  17. package/dist/core/asyncTasks.js +82 -0
  18. package/dist/core/auditTrail.js +61 -0
  19. package/dist/core/auxiliaryClient.js +114 -0
  20. package/dist/core/bgLLM.js +108 -0
  21. package/dist/core/bm25.js +68 -0
  22. package/dist/core/callbackSystem.js +64 -0
  23. package/dist/core/channels/adapter.js +6 -0
  24. package/dist/core/channels/discord.js +173 -0
  25. package/dist/core/channels/email.js +253 -0
  26. package/dist/core/channels/imessage.js +164 -0
  27. package/dist/core/channels/manager.js +96 -0
  28. package/dist/core/channels/signal.js +140 -0
  29. package/dist/core/channels/slack.js +139 -0
  30. package/dist/core/channels/twilio.js +144 -0
  31. package/dist/core/channels/webhook.js +186 -0
  32. package/dist/core/channels/whatsapp.js +185 -0
  33. package/dist/core/clarifyBus.js +75 -0
  34. package/dist/core/codeInterpreter.js +82 -0
  35. package/dist/core/computerControl.js +439 -0
  36. package/dist/core/conversationMemory.js +334 -0
  37. package/dist/core/costTracker.js +221 -0
  38. package/dist/core/cronManager.js +217 -0
  39. package/dist/core/deepKB.js +77 -0
  40. package/dist/core/doctor.js +279 -0
  41. package/dist/core/dreamEngine.js +334 -0
  42. package/dist/core/entityGraph.js +169 -0
  43. package/dist/core/eventBus.js +16 -0
  44. package/dist/core/evolutionAnalyzer.js +153 -0
  45. package/dist/core/executionLoop.js +309 -0
  46. package/dist/core/executor.js +224 -0
  47. package/dist/core/failureAnalyzer.js +166 -0
  48. package/dist/core/fastPathExpansion.js +82 -0
  49. package/dist/core/faultEngine.js +106 -0
  50. package/dist/core/featureGates.js +70 -0
  51. package/dist/core/fileIngestion.js +113 -0
  52. package/dist/core/gateway.js +97 -0
  53. package/dist/core/goalTracker.js +75 -0
  54. package/dist/core/growthEngine.js +168 -0
  55. package/dist/core/hardwareDetector.js +98 -0
  56. package/dist/core/hooks.js +45 -0
  57. package/dist/core/httpKeepalive.js +46 -0
  58. package/dist/core/hybridSearch.js +101 -0
  59. package/dist/core/importers.js +164 -0
  60. package/dist/core/instinctSystem.js +223 -0
  61. package/dist/core/knowledgeBase.js +351 -0
  62. package/dist/core/learningMemory.js +121 -0
  63. package/dist/core/lessonsBrowser.js +125 -0
  64. package/dist/core/licenseManager.js +399 -0
  65. package/dist/core/logBuffer.js +85 -0
  66. package/dist/core/machineId.js +87 -0
  67. package/dist/core/mcpClient.js +442 -0
  68. package/dist/core/memoryDistiller.js +165 -0
  69. package/dist/core/memoryExtractor.js +212 -0
  70. package/dist/core/memoryIds.js +213 -0
  71. package/dist/core/memoryPreamble.js +113 -0
  72. package/dist/core/memoryQuery.js +136 -0
  73. package/dist/core/memoryRecall.js +140 -0
  74. package/dist/core/memoryStrategy.js +201 -0
  75. package/dist/core/messageValidator.js +85 -0
  76. package/dist/core/modelDiscovery.js +108 -0
  77. package/dist/core/modelRouter.js +118 -0
  78. package/dist/core/morningBriefing.js +203 -0
  79. package/dist/core/multiGoalValidator.js +51 -0
  80. package/dist/core/parallelExecutor.js +43 -0
  81. package/dist/core/passiveSkillObserver.js +204 -0
  82. package/dist/core/paths.js +57 -0
  83. package/dist/core/patternDetector.js +83 -0
  84. package/dist/core/planResponseRepair.js +64 -0
  85. package/dist/core/planTool.js +111 -0
  86. package/dist/core/playwrightBridge.js +356 -0
  87. package/dist/core/pluginSystem.js +121 -0
  88. package/dist/core/privateMode.js +85 -0
  89. package/dist/core/reactLoop.js +156 -0
  90. package/dist/core/recipeEngine.js +166 -0
  91. package/dist/core/responseCache.js +128 -0
  92. package/dist/core/runSandbox.js +132 -0
  93. package/dist/core/sandboxRunner.js +200 -0
  94. package/dist/core/scheduler.js +543 -0
  95. package/dist/core/secretScanner.js +49 -0
  96. package/dist/core/semanticMemory.js +223 -0
  97. package/dist/core/sessionMemory.js +259 -0
  98. package/dist/core/sessionRouter.js +91 -0
  99. package/dist/core/sessionSearch.js +163 -0
  100. package/dist/core/setupWizard.js +225 -0
  101. package/dist/core/skillImporter.js +303 -0
  102. package/dist/core/skillLibrary.js +144 -0
  103. package/dist/core/skillLoader.js +471 -0
  104. package/dist/core/skillTeacher.js +352 -0
  105. package/dist/core/skillValidator.js +210 -0
  106. package/dist/core/skillWriter.js +384 -0
  107. package/dist/core/slashAsTool.js +226 -0
  108. package/dist/core/spawnManager.js +197 -0
  109. package/dist/core/statusVerbs.js +43 -0
  110. package/dist/core/swarmManager.js +109 -0
  111. package/dist/core/taskQueue.js +119 -0
  112. package/dist/core/taskRecovery.js +128 -0
  113. package/dist/core/taskState.js +168 -0
  114. package/dist/core/telegramBot.js +152 -0
  115. package/dist/core/todoManager.js +70 -0
  116. package/dist/core/toolNameRepair.js +71 -0
  117. package/dist/core/toolRegistry.js +2730 -0
  118. package/dist/core/tools/calendarTool.js +98 -0
  119. package/dist/core/tools/companyFilingsTool.js +98 -0
  120. package/dist/core/tools/gmailTool.js +87 -0
  121. package/dist/core/tools/marketDataTool.js +135 -0
  122. package/dist/core/tools/socialResearchTool.js +121 -0
  123. package/dist/core/truthCheck.js +57 -0
  124. package/dist/core/updateChecker.js +74 -0
  125. package/dist/core/userCognitionProfile.js +238 -0
  126. package/dist/core/userProfile.js +341 -0
  127. package/dist/core/version.js +5 -0
  128. package/dist/core/visionAnalyze.js +161 -0
  129. package/dist/core/voice/audio.js +187 -0
  130. package/dist/core/voice/stt.js +226 -0
  131. package/dist/core/voice/tts.js +310 -0
  132. package/dist/core/voiceInput.js +118 -0
  133. package/dist/core/voiceOutput.js +130 -0
  134. package/dist/core/webSearch.js +326 -0
  135. package/dist/core/workflowTracker.js +72 -0
  136. package/dist/core/workspaceMemory.js +54 -0
  137. package/dist/core/youtubeTranscript.js +224 -0
  138. package/dist/integrations/computerUse/apiRegistry.js +113 -0
  139. package/dist/integrations/computerUse/screenAgent.js +203 -0
  140. package/dist/integrations/computerUse/visionLoop.js +296 -0
  141. package/dist/memory/memoryLayers.js +143 -0
  142. package/dist/providers/boa.js +93 -0
  143. package/dist/providers/cerebras.js +70 -0
  144. package/dist/providers/custom.js +89 -0
  145. package/dist/providers/gemini.js +82 -0
  146. package/dist/providers/groq.js +92 -0
  147. package/dist/providers/index.js +149 -0
  148. package/dist/providers/nvidia.js +70 -0
  149. package/dist/providers/ollama.js +99 -0
  150. package/dist/providers/openrouter.js +74 -0
  151. package/dist/providers/router.js +497 -0
  152. package/dist/providers/types.js +6 -0
  153. package/dist/security/browserVault.js +129 -0
  154. package/dist/security/dataGuard.js +89 -0
  155. package/dist/tools/eonetTool.js +72 -0
  156. package/dist/types/computerUse.js +2 -0
  157. package/dist/types/executor.js +2 -0
  158. package/dist-bundle/cli.js +357859 -0
  159. 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();