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,140 @@
|
|
|
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.unifiedMemoryRecall = unifiedMemoryRecall;
|
|
11
|
+
exports.buildMemoryInjection = buildMemoryInjection;
|
|
12
|
+
// core/memoryRecall.ts — Sprint 21: Proactive Memory Surfacing
|
|
13
|
+
//
|
|
14
|
+
// Silently recalls relevant context from all memory layers before
|
|
15
|
+
// every response. Injected into the system prompt so Aiden feels
|
|
16
|
+
// like it knows the user — without the user having to re-explain.
|
|
17
|
+
const fs_1 = __importDefault(require("fs"));
|
|
18
|
+
const path_1 = __importDefault(require("path"));
|
|
19
|
+
const conversationMemory_1 = require("./conversationMemory");
|
|
20
|
+
const semanticMemory_1 = require("./semanticMemory");
|
|
21
|
+
const entityGraph_1 = require("./entityGraph");
|
|
22
|
+
const knowledgeBase_1 = require("./knowledgeBase");
|
|
23
|
+
// ── Durable memory confidence filtering ───────────────────────
|
|
24
|
+
const MEMORY_DIR = path_1.default.join(process.cwd(), 'workspace', 'memory');
|
|
25
|
+
const CONFIDENCE_FLOOR = 0.6;
|
|
26
|
+
function loadDurableMemoriesFiltered() {
|
|
27
|
+
try {
|
|
28
|
+
if (!fs_1.default.existsSync(MEMORY_DIR))
|
|
29
|
+
return [];
|
|
30
|
+
const files = fs_1.default.readdirSync(MEMORY_DIR).filter(f => f.endsWith('.md') && f !== 'MEMORY_INDEX.md');
|
|
31
|
+
const all = [];
|
|
32
|
+
for (const file of files) {
|
|
33
|
+
try {
|
|
34
|
+
const content = fs_1.default.readFileSync(path_1.default.join(MEMORY_DIR, file), 'utf-8');
|
|
35
|
+
const sourceMatch = content.match(/^source:\s*(.+)$/m);
|
|
36
|
+
const confidenceMatch = content.match(/^confidence:\s*([0-9.]+)$/m);
|
|
37
|
+
const source = sourceMatch ? sourceMatch[1].trim() : 'llm_inferred';
|
|
38
|
+
const confidence = confidenceMatch ? parseFloat(confidenceMatch[1]) : 0.5;
|
|
39
|
+
// Strip frontmatter, get body text
|
|
40
|
+
const body = content.replace(/^---[\s\S]*?---\s*/m, '').trim();
|
|
41
|
+
if (body)
|
|
42
|
+
all.push({ text: body, source, confidence });
|
|
43
|
+
}
|
|
44
|
+
catch { }
|
|
45
|
+
}
|
|
46
|
+
const passing = all.filter(m => m.confidence >= CONFIDENCE_FLOOR);
|
|
47
|
+
const skipped = all.filter(m => m.confidence < CONFIDENCE_FLOOR);
|
|
48
|
+
if (skipped.length > 0) {
|
|
49
|
+
console.log(`[Memory] Skipped ${skipped.length} low-confidence memories (confidence < ${CONFIDENCE_FLOOR})`);
|
|
50
|
+
}
|
|
51
|
+
return passing
|
|
52
|
+
.sort((a, b) => b.confidence - a.confidence)
|
|
53
|
+
.slice(0, 20);
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
return [];
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
// ── Main recall function ───────────────────────────────────────
|
|
60
|
+
async function unifiedMemoryRecall(query, topK = 5) {
|
|
61
|
+
const results = [];
|
|
62
|
+
const entities = [];
|
|
63
|
+
// 1. Semantic memory — hybrid BM25 + vector search
|
|
64
|
+
try {
|
|
65
|
+
const semantic = semanticMemory_1.semanticMemory.search(query, topK);
|
|
66
|
+
for (const item of semantic.slice(0, 3)) {
|
|
67
|
+
if (item.text)
|
|
68
|
+
results.push(item.text.slice(0, 200));
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
catch { }
|
|
72
|
+
// 2. Entity graph — extract named entities from query and expand
|
|
73
|
+
try {
|
|
74
|
+
const queryEntities = query.match(/[A-Z][a-z]+(?:\s+[A-Z][a-z]+)*/g) || [];
|
|
75
|
+
for (const entity of queryEntities.slice(0, 3)) {
|
|
76
|
+
const related = entityGraph_1.entityGraph.getRelated(entity, 2);
|
|
77
|
+
if (related.length > 0) {
|
|
78
|
+
// getRelated returns strings like "name (relation)"
|
|
79
|
+
entities.push(`${entity}: ${related.join(', ')}`);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
catch { }
|
|
84
|
+
// 3. Knowledge base — BM25 + vector hybrid search
|
|
85
|
+
try {
|
|
86
|
+
const kb = knowledgeBase_1.knowledgeBase.search(query, 2);
|
|
87
|
+
for (const chunk of kb) {
|
|
88
|
+
if (chunk.text)
|
|
89
|
+
results.push(chunk.text.slice(0, 200));
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
catch { }
|
|
93
|
+
// 4. Recent conversation — pull relevant exchanges
|
|
94
|
+
try {
|
|
95
|
+
const recent = conversationMemory_1.conversationMemory.getRecentHistory();
|
|
96
|
+
const queryWords = new Set(query.toLowerCase().split(/\s+/).filter(w => w.length > 4));
|
|
97
|
+
const relevant = recent.filter(ex => {
|
|
98
|
+
const combined = `${ex.userMessage} ${ex.aiReply}`.toLowerCase();
|
|
99
|
+
return [...queryWords].some(w => combined.includes(w));
|
|
100
|
+
});
|
|
101
|
+
for (const ex of relevant.slice(0, 2)) {
|
|
102
|
+
if (ex.userMessage) {
|
|
103
|
+
results.push(`Previously: ${ex.userMessage.slice(0, 150)}`);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
catch { }
|
|
108
|
+
// 5. Durable memory files — confidence-filtered .md facts
|
|
109
|
+
try {
|
|
110
|
+
const durable = loadDurableMemoriesFiltered();
|
|
111
|
+
const queryLower = query.toLowerCase();
|
|
112
|
+
const queryWords = queryLower.split(/\s+/).filter(w => w.length > 3);
|
|
113
|
+
for (const mem of durable) {
|
|
114
|
+
const textLower = mem.text.toLowerCase();
|
|
115
|
+
const relevant = queryWords.some(w => textLower.includes(w));
|
|
116
|
+
if (relevant && !results.includes(mem.text.slice(0, 200))) {
|
|
117
|
+
results.push(mem.text.slice(0, 200));
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
catch { }
|
|
122
|
+
return {
|
|
123
|
+
relevant: [...new Set(results)].slice(0, topK),
|
|
124
|
+
entities,
|
|
125
|
+
source: 'unified_recall',
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
// ── Prompt injection builder ───────────────────────────────────
|
|
129
|
+
function buildMemoryInjection(recalled) {
|
|
130
|
+
if (recalled.relevant.length === 0 && recalled.entities.length === 0)
|
|
131
|
+
return '';
|
|
132
|
+
const parts = [];
|
|
133
|
+
if (recalled.entities.length > 0) {
|
|
134
|
+
parts.push(`Known context:\n${recalled.entities.map(e => ` - ${e}`).join('\n')}`);
|
|
135
|
+
}
|
|
136
|
+
if (recalled.relevant.length > 0) {
|
|
137
|
+
parts.push(`Relevant memory:\n${recalled.relevant.map(r => ` - ${r}`).join('\n')}`);
|
|
138
|
+
}
|
|
139
|
+
return `\n\n[MEMORY CONTEXT — use naturally, do not mention these are memories]\n${parts.join('\n')}\n`;
|
|
140
|
+
}
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// ============================================================
|
|
3
|
+
// DevOS — Autonomous AI Execution System
|
|
4
|
+
// Copyright (c) 2026 Shiva Deore. All rights reserved.
|
|
5
|
+
// ============================================================
|
|
6
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
7
|
+
if (k2 === undefined) k2 = k;
|
|
8
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
9
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
10
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
11
|
+
}
|
|
12
|
+
Object.defineProperty(o, k2, desc);
|
|
13
|
+
}) : (function(o, m, k, k2) {
|
|
14
|
+
if (k2 === undefined) k2 = k;
|
|
15
|
+
o[k2] = m[k];
|
|
16
|
+
}));
|
|
17
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
18
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
19
|
+
}) : function(o, v) {
|
|
20
|
+
o["default"] = v;
|
|
21
|
+
});
|
|
22
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
23
|
+
var ownKeys = function(o) {
|
|
24
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
25
|
+
var ar = [];
|
|
26
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
27
|
+
return ar;
|
|
28
|
+
};
|
|
29
|
+
return ownKeys(o);
|
|
30
|
+
};
|
|
31
|
+
return function (mod) {
|
|
32
|
+
if (mod && mod.__esModule) return mod;
|
|
33
|
+
var result = {};
|
|
34
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
35
|
+
__setModuleDefault(result, mod);
|
|
36
|
+
return result;
|
|
37
|
+
};
|
|
38
|
+
})();
|
|
39
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
40
|
+
exports.memoryStrategy = exports.MemoryStrategy = void 0;
|
|
41
|
+
// core/memoryStrategy.ts — Goal-hashing memory layer for computer-use sessions.
|
|
42
|
+
//
|
|
43
|
+
// Responsibilities:
|
|
44
|
+
// • MD5-hash goals for fast lookup
|
|
45
|
+
// • Store success/failure outcomes with action history
|
|
46
|
+
// • Rank retrieved memories by keyword overlap + success rate
|
|
47
|
+
// • Clean up entries older than 30 days
|
|
48
|
+
// • Persist to workspace/computer-use-memory.json + cross-write to MemoryLayers
|
|
49
|
+
const fs = __importStar(require("fs"));
|
|
50
|
+
const path = __importStar(require("path"));
|
|
51
|
+
const crypto = __importStar(require("crypto"));
|
|
52
|
+
const memoryLayers_1 = require("../memory/memoryLayers");
|
|
53
|
+
// ── Constants ─────────────────────────────────────────────────
|
|
54
|
+
const STOPWORDS = new Set([
|
|
55
|
+
'a', 'an', 'the', 'and', 'or', 'but', 'in', 'on', 'at', 'to', 'for',
|
|
56
|
+
'of', 'with', 'by', 'from', 'is', 'are', 'was', 'were', 'be', 'been',
|
|
57
|
+
'has', 'have', 'had', 'do', 'does', 'did', 'will', 'would', 'could',
|
|
58
|
+
'should', 'may', 'might', 'shall', 'can', 'not', 'this', 'that', 'it',
|
|
59
|
+
'its', 'my', 'your', 'our', 'their', 'we', 'i', 'you', 'he', 'she',
|
|
60
|
+
'they', 'me', 'him', 'her', 'us', 'them',
|
|
61
|
+
]);
|
|
62
|
+
const RETENTION_DAYS = 30;
|
|
63
|
+
// ── MemoryStrategy ────────────────────────────────────────────
|
|
64
|
+
class MemoryStrategy {
|
|
65
|
+
constructor() {
|
|
66
|
+
this.filePath = path.join(process.cwd(), 'workspace', 'computer-use-memory.json');
|
|
67
|
+
this.memory = [];
|
|
68
|
+
this.ensureDir();
|
|
69
|
+
this.load();
|
|
70
|
+
this.cleanup();
|
|
71
|
+
}
|
|
72
|
+
// ── Persistence ───────────────────────────────────────────
|
|
73
|
+
ensureDir() {
|
|
74
|
+
const dir = path.dirname(this.filePath);
|
|
75
|
+
if (!fs.existsSync(dir)) {
|
|
76
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
load() {
|
|
80
|
+
try {
|
|
81
|
+
if (fs.existsSync(this.filePath)) {
|
|
82
|
+
const raw = fs.readFileSync(this.filePath, 'utf8');
|
|
83
|
+
this.memory = JSON.parse(raw);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
this.memory = [];
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
save() {
|
|
91
|
+
try {
|
|
92
|
+
fs.writeFileSync(this.filePath, JSON.stringify(this.memory, null, 2), 'utf8');
|
|
93
|
+
}
|
|
94
|
+
catch (err) {
|
|
95
|
+
console.warn(`[MemoryStrategy] save failed: ${err?.message}`);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
// ── Hashing + keywords ─────────────────────────────────────
|
|
99
|
+
hash(goal) {
|
|
100
|
+
return crypto.createHash('md5').update(goal.trim().toLowerCase()).digest('hex');
|
|
101
|
+
}
|
|
102
|
+
extractKeywords(goal) {
|
|
103
|
+
return goal
|
|
104
|
+
.toLowerCase()
|
|
105
|
+
.replace(/[^a-z0-9\s]/g, '')
|
|
106
|
+
.split(/\s+/)
|
|
107
|
+
.filter(w => w.length > 2 && !STOPWORDS.has(w));
|
|
108
|
+
}
|
|
109
|
+
// ── Store success ─────────────────────────────────────────
|
|
110
|
+
async storeSuccess(goal, actions) {
|
|
111
|
+
const goalHash = this.hash(goal);
|
|
112
|
+
const keywords = this.extractKeywords(goal);
|
|
113
|
+
const now = new Date().toISOString();
|
|
114
|
+
const existing = this.memory.find(e => e.goalHash === goalHash);
|
|
115
|
+
if (existing) {
|
|
116
|
+
existing.successCount++;
|
|
117
|
+
existing.successRate = existing.successCount / (existing.successCount + existing.failureCount);
|
|
118
|
+
existing.lastActions = actions;
|
|
119
|
+
existing.lastUsed = now;
|
|
120
|
+
existing.keywords = keywords; // refresh keywords
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
this.memory.push({
|
|
124
|
+
goalHash,
|
|
125
|
+
goal,
|
|
126
|
+
keywords,
|
|
127
|
+
successCount: 1,
|
|
128
|
+
failureCount: 0,
|
|
129
|
+
successRate: 1.0,
|
|
130
|
+
lastActions: actions,
|
|
131
|
+
lastUsed: now,
|
|
132
|
+
createdAt: now,
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
this.save();
|
|
136
|
+
await memoryLayers_1.memoryLayers.write(`ComputerUse memory stored (success): ${goal}`, ['computer_use', 'memory', 'success']);
|
|
137
|
+
}
|
|
138
|
+
// ── Store failure ──────────────────────────────────────────
|
|
139
|
+
async storeFailure(goal) {
|
|
140
|
+
const goalHash = this.hash(goal);
|
|
141
|
+
const existing = this.memory.find(e => e.goalHash === goalHash);
|
|
142
|
+
if (existing) {
|
|
143
|
+
existing.failureCount++;
|
|
144
|
+
existing.successRate = existing.successCount / (existing.successCount + existing.failureCount);
|
|
145
|
+
existing.lastUsed = new Date().toISOString();
|
|
146
|
+
this.save();
|
|
147
|
+
}
|
|
148
|
+
// If no existing entry, nothing to update — failure without prior success is not tracked
|
|
149
|
+
}
|
|
150
|
+
// ── Retrieve ──────────────────────────────────────────────
|
|
151
|
+
/**
|
|
152
|
+
* Retrieve best-matching memory entries for a goal.
|
|
153
|
+
* Scores by keyword overlap count, then breaks ties by successRate.
|
|
154
|
+
*/
|
|
155
|
+
retrieve(goal, limit = 3) {
|
|
156
|
+
const keywords = this.extractKeywords(goal);
|
|
157
|
+
if (keywords.length === 0)
|
|
158
|
+
return [];
|
|
159
|
+
const scored = this.memory
|
|
160
|
+
.map(entry => {
|
|
161
|
+
const overlap = entry.keywords.filter(k => keywords.includes(k)).length;
|
|
162
|
+
return { entry, score: overlap + entry.successRate * 0.1 };
|
|
163
|
+
})
|
|
164
|
+
.filter(s => s.score > 0)
|
|
165
|
+
.sort((a, b) => b.score - a.score);
|
|
166
|
+
return scored.slice(0, limit).map(s => s.entry);
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Return the last successful action sequence if successRate >= 0.5.
|
|
170
|
+
*/
|
|
171
|
+
retrieveActions(goal) {
|
|
172
|
+
const goalHash = this.hash(goal);
|
|
173
|
+
const entry = this.memory.find(e => e.goalHash === goalHash);
|
|
174
|
+
if (!entry || entry.successRate < 0.5)
|
|
175
|
+
return null;
|
|
176
|
+
return entry.lastActions.length > 0 ? entry.lastActions : null;
|
|
177
|
+
}
|
|
178
|
+
// ── Stats ──────────────────────────────────────────────────
|
|
179
|
+
stats() {
|
|
180
|
+
const total = this.memory.length;
|
|
181
|
+
const avgSuccessRate = total
|
|
182
|
+
? this.memory.reduce((acc, e) => acc + e.successRate, 0) / total
|
|
183
|
+
: 0;
|
|
184
|
+
const topGoals = [...this.memory]
|
|
185
|
+
.sort((a, b) => b.successRate - a.successRate || b.successCount - a.successCount)
|
|
186
|
+
.slice(0, 5)
|
|
187
|
+
.map(e => e.goal);
|
|
188
|
+
return { total, avgSuccessRate, topGoals };
|
|
189
|
+
}
|
|
190
|
+
// ── Cleanup ───────────────────────────────────────────────
|
|
191
|
+
cleanup() {
|
|
192
|
+
const cutoff = Date.now() - RETENTION_DAYS * 24 * 60 * 60 * 1000;
|
|
193
|
+
const before = this.memory.length;
|
|
194
|
+
this.memory = this.memory.filter(e => new Date(e.lastUsed).getTime() > cutoff);
|
|
195
|
+
if (this.memory.length < before) {
|
|
196
|
+
this.save();
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
exports.MemoryStrategy = MemoryStrategy;
|
|
201
|
+
exports.memoryStrategy = new MemoryStrategy();
|
|
@@ -0,0 +1,85 @@
|
|
|
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.validateMessageSequence = validateMessageSequence;
|
|
8
|
+
exports.validateToolSequence = validateToolSequence;
|
|
9
|
+
exports.sanitizeMessages = sanitizeMessages;
|
|
10
|
+
// ── validateMessageSequence ───────────────────────────────────────────────────
|
|
11
|
+
/**
|
|
12
|
+
* Merge consecutive messages of the same non-system role.
|
|
13
|
+
*
|
|
14
|
+
* Providers like Hermes and many OpenAI-compat endpoints reject sequences
|
|
15
|
+
* such as [user, user] or [assistant, assistant]. When two adjacent messages
|
|
16
|
+
* share the same role, their content is joined with a blank line separator.
|
|
17
|
+
*
|
|
18
|
+
* System messages always pass through unchanged and do NOT affect the
|
|
19
|
+
* alternation check for the messages that follow them.
|
|
20
|
+
*/
|
|
21
|
+
function validateMessageSequence(messages) {
|
|
22
|
+
const result = [];
|
|
23
|
+
for (const msg of messages) {
|
|
24
|
+
// System messages pass through without merging
|
|
25
|
+
if (msg.role === 'system') {
|
|
26
|
+
result.push({ ...msg });
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
const last = result[result.length - 1];
|
|
30
|
+
if (last && last.role === msg.role && last.role !== 'system') {
|
|
31
|
+
// Merge consecutive same-role messages
|
|
32
|
+
last.content = last.content + '\n\n' + msg.content;
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
result.push({ ...msg });
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return result;
|
|
39
|
+
}
|
|
40
|
+
// ── validateToolSequence ──────────────────────────────────────────────────────
|
|
41
|
+
/**
|
|
42
|
+
* Remove orphaned tool messages (role='tool') that lack a preceding
|
|
43
|
+
* assistant message with tool_calls.
|
|
44
|
+
*
|
|
45
|
+
* When a tool result exists without the assistant turn that requested it,
|
|
46
|
+
* providers return 400 errors. This validator strips those orphaned entries
|
|
47
|
+
* so the sequence is structurally valid.
|
|
48
|
+
*/
|
|
49
|
+
function validateToolSequence(messages) {
|
|
50
|
+
const result = [];
|
|
51
|
+
for (let i = 0; i < messages.length; i++) {
|
|
52
|
+
const msg = messages[i];
|
|
53
|
+
if (msg.role === 'tool') {
|
|
54
|
+
const prev = result[result.length - 1];
|
|
55
|
+
const hasToolCalls = prev?.role === 'assistant' && Array.isArray(prev.tool_calls) && prev.tool_calls.length > 0;
|
|
56
|
+
if (!hasToolCalls) {
|
|
57
|
+
console.log(`[Validator] Removed orphaned tool message at position ${i}`);
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
result.push(msg);
|
|
62
|
+
}
|
|
63
|
+
return result;
|
|
64
|
+
}
|
|
65
|
+
// ── sanitizeMessages ──────────────────────────────────────────────────────────
|
|
66
|
+
/**
|
|
67
|
+
* Sanitize a message array before passing it to any LLM provider.
|
|
68
|
+
*
|
|
69
|
+
* Applies tool-sequence validation first (removes orphaned tool results),
|
|
70
|
+
* then alternation enforcement (merges consecutive same-role messages).
|
|
71
|
+
* Logs a single line when the array is modified so problems are traceable.
|
|
72
|
+
*
|
|
73
|
+
* @param messages The raw message array to sanitize (not mutated).
|
|
74
|
+
* @returns A new array that is safe to send to any provider.
|
|
75
|
+
*/
|
|
76
|
+
function sanitizeMessages(messages) {
|
|
77
|
+
const before = messages.length;
|
|
78
|
+
let sanitized = validateToolSequence(messages);
|
|
79
|
+
sanitized = validateMessageSequence(sanitized);
|
|
80
|
+
const after = sanitized.length;
|
|
81
|
+
if (after !== before) {
|
|
82
|
+
console.log(`[Validator] Fixed message sequence: ${before} → ${after} messages`);
|
|
83
|
+
}
|
|
84
|
+
return sanitized;
|
|
85
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
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.discoverLocalModels = discoverLocalModels;
|
|
8
|
+
exports.getOllamaTimeout = getOllamaTimeout;
|
|
9
|
+
// ── Known model profiles — matched by name prefix ──────────────
|
|
10
|
+
const MODEL_PROFILES = {
|
|
11
|
+
'gemma4': { sizeGB: 9.6, tier: 'large', strengths: ['chat', 'reasoning'] },
|
|
12
|
+
'gemma3': { sizeGB: 5, tier: 'medium', strengths: ['chat'] },
|
|
13
|
+
'llama3.3': { sizeGB: 43, tier: 'xlarge', strengths: ['chat', 'reasoning'] },
|
|
14
|
+
'llama3.2': { sizeGB: 2, tier: 'small', strengths: ['fast', 'chat'] },
|
|
15
|
+
'llama3.1': { sizeGB: 4.7, tier: 'medium', strengths: ['chat'] },
|
|
16
|
+
'llama3': { sizeGB: 4.7, tier: 'medium', strengths: ['chat'] },
|
|
17
|
+
'mistral-nemo': { sizeGB: 7.1, tier: 'medium', strengths: ['chat', 'reasoning'] },
|
|
18
|
+
'mistral': { sizeGB: 4.4, tier: 'medium', strengths: ['chat', 'fast'] },
|
|
19
|
+
'qwen2.5-coder': { sizeGB: 9, tier: 'large', strengths: ['code'] },
|
|
20
|
+
'qwen2.5': { sizeGB: 4.7, tier: 'medium', strengths: ['chat', 'reasoning'] },
|
|
21
|
+
'deepseek-coder': { sizeGB: 9, tier: 'large', strengths: ['code'] },
|
|
22
|
+
'deepseek-r1': { sizeGB: 9, tier: 'large', strengths: ['reasoning'] },
|
|
23
|
+
'phi4': { sizeGB: 9, tier: 'large', strengths: ['reasoning', 'chat'] },
|
|
24
|
+
'phi3': { sizeGB: 2.2, tier: 'small', strengths: ['fast', 'chat'] },
|
|
25
|
+
'codellama': { sizeGB: 4.7, tier: 'medium', strengths: ['code'] },
|
|
26
|
+
'vicuna': { sizeGB: 4, tier: 'medium', strengths: ['chat'] },
|
|
27
|
+
};
|
|
28
|
+
// Skip these — they are embedding / vision models, not chat
|
|
29
|
+
const SKIP_PATTERNS = ['embed', 'nomic', 'mxbai', 'clip', 'whisper'];
|
|
30
|
+
// ── Ollama base URL ─────────────────────────────────────────────
|
|
31
|
+
// Prefer OLLAMA_HOST env var, fall back to 127.0.0.1 (NOT localhost —
|
|
32
|
+
// on Windows, localhost resolves to ::1 (IPv6) but Ollama only binds
|
|
33
|
+
// to 127.0.0.1 (IPv4), causing ECONNREFUSED when IPv6 is tried first).
|
|
34
|
+
const OLLAMA_BASE = (process.env.OLLAMA_HOST ?? 'http://127.0.0.1:11434').replace(/\/$/, '');
|
|
35
|
+
// ── Main discovery function ─────────────────────────────────────
|
|
36
|
+
async function discoverLocalModels(retries = 3, delayMs = 2000) {
|
|
37
|
+
let rawModels = [];
|
|
38
|
+
for (let i = 0; i < retries; i++) {
|
|
39
|
+
try {
|
|
40
|
+
const r = await fetch(`${OLLAMA_BASE}/api/tags`, {
|
|
41
|
+
signal: AbortSignal.timeout(3000),
|
|
42
|
+
});
|
|
43
|
+
if (r.ok) {
|
|
44
|
+
const data = await r.json();
|
|
45
|
+
const chat = (data.models || []).filter(m => !['embed', 'nomic', 'mxbai', 'clip'].some(skip => m.name.includes(skip)));
|
|
46
|
+
if (chat.length > 0) {
|
|
47
|
+
rawModels = data.models;
|
|
48
|
+
break;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
catch { }
|
|
53
|
+
if (i < retries - 1) {
|
|
54
|
+
console.log(`[ModelDiscovery] Ollama not ready, retrying in ${delayMs}ms... (${i + 1}/${retries})`);
|
|
55
|
+
await new Promise(r => setTimeout(r, delayMs));
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
try {
|
|
59
|
+
if (rawModels.length === 0)
|
|
60
|
+
return emptyDiscovery();
|
|
61
|
+
const { models } = { models: rawModels };
|
|
62
|
+
const chatModels = models.filter(m => !SKIP_PATTERNS.some(skip => m.name.toLowerCase().includes(skip)));
|
|
63
|
+
if (chatModels.length === 0)
|
|
64
|
+
return emptyDiscovery();
|
|
65
|
+
const scored = chatModels.map(m => ({
|
|
66
|
+
name: m.name,
|
|
67
|
+
profile: getProfile(m.name),
|
|
68
|
+
}));
|
|
69
|
+
const bySize = (a, b) => b.profile.sizeGB - a.profile.sizeGB;
|
|
70
|
+
const planners = scored.filter(m => m.profile.strengths.includes('reasoning') ||
|
|
71
|
+
m.profile.tier === 'large' ||
|
|
72
|
+
m.profile.tier === 'xlarge');
|
|
73
|
+
const coders = scored.filter(m => m.profile.strengths.includes('code'));
|
|
74
|
+
const fast = scored.filter(m => m.profile.strengths.includes('fast') || m.profile.tier === 'small');
|
|
75
|
+
const bestBySize = [...scored].sort(bySize)[0]?.name || null;
|
|
76
|
+
const bestPlanner = [...planners].sort(bySize)[0]?.name || bestBySize;
|
|
77
|
+
const bestCoder = [...coders].sort(bySize)[0]?.name || bestBySize;
|
|
78
|
+
const bestFast = [...fast].sort((a, b) => a.profile.sizeGB - b.profile.sizeGB)[0]?.name || scored[scored.length - 1]?.name || null;
|
|
79
|
+
return {
|
|
80
|
+
planner: bestPlanner,
|
|
81
|
+
responder: bestBySize,
|
|
82
|
+
coder: bestCoder,
|
|
83
|
+
fast: bestFast,
|
|
84
|
+
all: chatModels.map(m => m.name),
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
return emptyDiscovery();
|
|
89
|
+
}
|
|
90
|
+
} // end discoverLocalModels
|
|
91
|
+
// ── Helpers ─────────────────────────────────────────────────────
|
|
92
|
+
function getProfile(modelName) {
|
|
93
|
+
const key = Object.keys(MODEL_PROFILES).find(k => modelName.toLowerCase().startsWith(k));
|
|
94
|
+
return key ? MODEL_PROFILES[key] : { sizeGB: 4, tier: 'medium', strengths: ['chat'] };
|
|
95
|
+
}
|
|
96
|
+
function emptyDiscovery() {
|
|
97
|
+
return { planner: null, responder: null, coder: null, fast: null, all: [] };
|
|
98
|
+
}
|
|
99
|
+
// ── Timeout scaling by model size ───────────────────────────────
|
|
100
|
+
// gemma4:e4b / 70b models need up to 2 min on first token generation.
|
|
101
|
+
function getOllamaTimeout(modelName) {
|
|
102
|
+
const n = modelName.toLowerCase();
|
|
103
|
+
if (n.includes('70b') || n.includes('e4b') || n.includes('34b'))
|
|
104
|
+
return 120000;
|
|
105
|
+
if (n.includes('14b') || n.includes('12b') || n.includes('13b'))
|
|
106
|
+
return 60000;
|
|
107
|
+
return 30000; // 8b and smaller
|
|
108
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
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.modelRouter = exports.ModelRouter = void 0;
|
|
8
|
+
const child_process_1 = require("child_process");
|
|
9
|
+
const hardwareDetector_1 = require("./hardwareDetector");
|
|
10
|
+
const TASK_TYPES = ['chat', 'code', 'vision', 'reasoning', 'embedding'];
|
|
11
|
+
class ModelRouter {
|
|
12
|
+
constructor() {
|
|
13
|
+
this.hardware = (0, hardwareDetector_1.detectHardware)();
|
|
14
|
+
this.models = this.buildRegistry();
|
|
15
|
+
this.syncWithOllama();
|
|
16
|
+
}
|
|
17
|
+
buildRegistry() {
|
|
18
|
+
return [
|
|
19
|
+
// Chat
|
|
20
|
+
{ name: 'phi3:mini', ollamaTag: 'phi3:mini', vramGB: 2, type: 'chat', moeEnabled: false, installed: false, description: 'Lightweight fast chat, fits any GPU' },
|
|
21
|
+
{ name: 'llama3.2:3b', ollamaTag: 'llama3.2:3b', vramGB: 3, type: 'chat', moeEnabled: false, installed: false, description: 'Meta Llama 3.2 — fast and capable' },
|
|
22
|
+
{ name: 'mistral-nemo:12b', ollamaTag: 'mistral-nemo:12b', vramGB: 6, type: 'chat', moeEnabled: false, installed: false, description: 'Best chat quality at 6GB VRAM' },
|
|
23
|
+
// Code
|
|
24
|
+
{ name: 'qwen2.5-coder:1.5b', ollamaTag: 'qwen2.5-coder:1.5b', vramGB: 2, type: 'code', moeEnabled: false, installed: false, description: 'Fast coder for simple tasks' },
|
|
25
|
+
{ name: 'deepseek-coder:6.7b', ollamaTag: 'deepseek-coder:6.7b', vramGB: 5, type: 'code', moeEnabled: false, installed: false, description: 'Efficient coding model' },
|
|
26
|
+
{ name: 'qwen2.5-coder:7b', ollamaTag: 'qwen2.5-coder:7b', vramGB: 6, type: 'code', moeEnabled: false, installed: false, description: 'Best local coder at 6GB VRAM' },
|
|
27
|
+
// Vision
|
|
28
|
+
{ name: 'moondream', ollamaTag: 'moondream', vramGB: 2, type: 'vision', moeEnabled: false, installed: false, description: 'Tiny vision model, fast screenshots' },
|
|
29
|
+
{ name: 'llava:7b', ollamaTag: 'llava:7b', vramGB: 5, type: 'vision', moeEnabled: false, installed: false, description: 'Full quality vision + text' },
|
|
30
|
+
// Reasoning
|
|
31
|
+
{ name: 'phi3:medium', ollamaTag: 'phi3:medium', vramGB: 5, type: 'reasoning', moeEnabled: false, installed: false, description: 'Strong reasoning, fits 6GB' },
|
|
32
|
+
{ name: 'mistral:7b', ollamaTag: 'mistral:7b', vramGB: 6, type: 'reasoning', moeEnabled: false, installed: false, description: 'Balanced reasoning model' },
|
|
33
|
+
// Embedding
|
|
34
|
+
{ name: 'nomic-embed-text', ollamaTag: 'nomic-embed-text', vramGB: 1, type: 'embedding', moeEnabled: false, installed: false, description: 'Fast text embeddings' },
|
|
35
|
+
{ name: 'mxbai-embed-large', ollamaTag: 'mxbai-embed-large', vramGB: 2, type: 'embedding', moeEnabled: false, installed: false, description: 'Higher quality embeddings' },
|
|
36
|
+
];
|
|
37
|
+
}
|
|
38
|
+
syncWithOllama() {
|
|
39
|
+
try {
|
|
40
|
+
// stdio:'pipe' suppresses the Windows "not recognized" error from leaking to stderr
|
|
41
|
+
const output = (0, child_process_1.execSync)('ollama list', { timeout: 5000, stdio: 'pipe' }).toString();
|
|
42
|
+
const installedNames = output.split('\n')
|
|
43
|
+
.slice(1)
|
|
44
|
+
.map(l => l.trim().split(/\s+/)[0])
|
|
45
|
+
.filter(Boolean);
|
|
46
|
+
this.models = this.models.map(m => ({
|
|
47
|
+
...m,
|
|
48
|
+
installed: installedNames.some(inst => inst.toLowerCase().includes(m.name.split(':')[0].toLowerCase())),
|
|
49
|
+
}));
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
// Ollama not installed or not on PATH — log once and continue
|
|
53
|
+
console.log('[providers] Ollama not detected — skipping local model support');
|
|
54
|
+
this.models = this.models.map(m => ({ ...m, installed: false }));
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
recommendModel(taskType) {
|
|
58
|
+
const budget = this.hardware.vramGB;
|
|
59
|
+
const candidates = this.models.filter(m => m.type === taskType && m.vramGB <= budget);
|
|
60
|
+
if (!candidates.length)
|
|
61
|
+
return 'phi3:mini';
|
|
62
|
+
// Prefer installed first, then highest VRAM
|
|
63
|
+
const installed = candidates.filter(m => m.installed);
|
|
64
|
+
const pool = installed.length ? installed : candidates;
|
|
65
|
+
return pool.sort((a, b) => b.vramGB - a.vramGB)[0].name;
|
|
66
|
+
}
|
|
67
|
+
// Alias used by index.ts (`devos models recommend`)
|
|
68
|
+
route(task) {
|
|
69
|
+
return this.recommendModel(task);
|
|
70
|
+
}
|
|
71
|
+
assessInstalledModels() {
|
|
72
|
+
const budget = this.hardware.vramGB;
|
|
73
|
+
const minVRAM = budget * 0.6;
|
|
74
|
+
const goodModels = {};
|
|
75
|
+
const missingModels = {};
|
|
76
|
+
const upgradesAvailable = {};
|
|
77
|
+
let hasGaps = false;
|
|
78
|
+
for (const task of TASK_TYPES) {
|
|
79
|
+
const compatible = this.models.filter(m => m.type === task && m.vramGB <= budget);
|
|
80
|
+
const installed = compatible.filter(m => m.installed);
|
|
81
|
+
const goodEnough = installed
|
|
82
|
+
.filter(m => m.vramGB >= minVRAM)
|
|
83
|
+
.sort((a, b) => b.vramGB - a.vramGB)[0] ?? null;
|
|
84
|
+
const best = compatible.sort((a, b) => b.vramGB - a.vramGB)[0] ?? null;
|
|
85
|
+
goodModels[task] = goodEnough;
|
|
86
|
+
if (!goodEnough) {
|
|
87
|
+
missingModels[task] = best;
|
|
88
|
+
hasGaps = true;
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
missingModels[task] = null;
|
|
92
|
+
}
|
|
93
|
+
upgradesAvailable[task] =
|
|
94
|
+
(goodEnough && best && best.name !== goodEnough.name) ? best : null;
|
|
95
|
+
}
|
|
96
|
+
return { allGood: !hasGaps, goodModels, missingModels, hasGaps, upgradesAvailable };
|
|
97
|
+
}
|
|
98
|
+
listCompatible() {
|
|
99
|
+
return this.models
|
|
100
|
+
.filter(m => m.vramGB <= this.hardware.vramGB)
|
|
101
|
+
.sort((a, b) => b.vramGB - a.vramGB);
|
|
102
|
+
}
|
|
103
|
+
listInstalled() {
|
|
104
|
+
return this.models.filter(m => m.installed);
|
|
105
|
+
}
|
|
106
|
+
listAll() { return [...this.models]; }
|
|
107
|
+
// Alias used by existing callers (api/server.ts, index.ts)
|
|
108
|
+
listModels() { return this.listCompatible(); }
|
|
109
|
+
getHardwareInfo() { return { ...this.hardware }; }
|
|
110
|
+
// Alias used by existing callers (api/server.ts, index.ts)
|
|
111
|
+
getHardware() { return this.hardware; }
|
|
112
|
+
fits(modelName) {
|
|
113
|
+
const m = this.models.find(mod => mod.name === modelName);
|
|
114
|
+
return m ? m.vramGB <= this.hardware.vramGB : false;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
exports.ModelRouter = ModelRouter;
|
|
118
|
+
exports.modelRouter = new ModelRouter();
|