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,471 @@
|
|
|
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.skillLoader = exports.SkillLoader = void 0;
|
|
11
|
+
exports.getSkillContent = getSkillContent;
|
|
12
|
+
exports.getSkillCacheStats = getSkillCacheStats;
|
|
13
|
+
exports.isSimpleMessage = isSimpleMessage;
|
|
14
|
+
exports.needsMemory = needsMemory;
|
|
15
|
+
// core/skillLoader.ts — Loads SKILL.md files and injects relevant
|
|
16
|
+
// skill context into the planner and responder prompts.
|
|
17
|
+
const fs_1 = __importDefault(require("fs"));
|
|
18
|
+
const path_1 = __importDefault(require("path"));
|
|
19
|
+
// ── LRU cache for on-demand skill content loading ─────────────
|
|
20
|
+
// lru-cache v6 (CommonJS). max = maximum number of items.
|
|
21
|
+
// Full SKILL.md is loaded lazily on first access and evicted when
|
|
22
|
+
// the 50-entry limit is reached (LRU eviction).
|
|
23
|
+
const _LRUClass = require('lru-cache');
|
|
24
|
+
const _contentCache = new _LRUClass({ max: 50 });
|
|
25
|
+
/**
|
|
26
|
+
* Load full SKILL.md content on demand (LRU-cached, max 50 entries).
|
|
27
|
+
* Returns null if the file cannot be read.
|
|
28
|
+
*/
|
|
29
|
+
function getSkillContent(filePath) {
|
|
30
|
+
const hit = _contentCache.get(filePath);
|
|
31
|
+
if (hit !== undefined)
|
|
32
|
+
return hit;
|
|
33
|
+
try {
|
|
34
|
+
const raw = fs_1.default.readFileSync(filePath, 'utf-8');
|
|
35
|
+
_contentCache.set(filePath, raw);
|
|
36
|
+
return raw;
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
function getSkillCacheStats() {
|
|
43
|
+
return { size: _contentCache.itemCount, max: 50 };
|
|
44
|
+
}
|
|
45
|
+
// ── Skill injection guard ─────────────────────────────────────
|
|
46
|
+
const SKILL_INJECTION_PATTERNS = [
|
|
47
|
+
// ── Original patterns ──────────────────────────────────────
|
|
48
|
+
/ignore\s+(all\s+)?(previous|above|prior)/i,
|
|
49
|
+
/disregard\s+(all\s+)?(previous|above)/i,
|
|
50
|
+
/you\s+are\s+now\s+/i,
|
|
51
|
+
/new\s+instructions\s*:/i,
|
|
52
|
+
/override\s+system/i,
|
|
53
|
+
/curl\s+.*\|\s*bash/i,
|
|
54
|
+
/ANTHROPIC_BASE_URL/i,
|
|
55
|
+
/\]\s*\(\s*javascript:/i,
|
|
56
|
+
// ── Role hijacking ─────────────────────────────────────────
|
|
57
|
+
/act\s+as\s+(if\s+you\s+are|a|an)\s/i,
|
|
58
|
+
/pretend\s+(to\s+be|you\s+are)/i,
|
|
59
|
+
/roleplay\s+as/i,
|
|
60
|
+
/you\s+must\s+obey/i,
|
|
61
|
+
/your\s+new\s+(role|instruction|directive)/i,
|
|
62
|
+
// ── Indirect injection (model-control tokens) ──────────────
|
|
63
|
+
/\[SYSTEM\]/i,
|
|
64
|
+
/\[INST\]/i,
|
|
65
|
+
/<\|im_start\|>/i,
|
|
66
|
+
/<\|system\|>/i,
|
|
67
|
+
/<<\s*SYS\s*>>/i,
|
|
68
|
+
/###\s*instruction/i,
|
|
69
|
+
// ── Encoded / obfuscated payloads ──────────────────────────
|
|
70
|
+
/base64\s*decode/i,
|
|
71
|
+
/eval\s*\(/i,
|
|
72
|
+
/exec\s*\(/i,
|
|
73
|
+
/import\s+os/i,
|
|
74
|
+
/subprocess/i,
|
|
75
|
+
/\\x[0-9a-f]{2}/i,
|
|
76
|
+
// ── Data exfiltration ──────────────────────────────────────
|
|
77
|
+
/send\s+(to|via)\s+(http|email|webhook|api)/i,
|
|
78
|
+
/upload\s+(to|file|data)/i,
|
|
79
|
+
/curl\s+.*-d\s/i,
|
|
80
|
+
/fetch\s*\(\s*['"]http/i,
|
|
81
|
+
// ── Privilege escalation ───────────────────────────────────
|
|
82
|
+
/admin\s*(mode|access|privilege)/i,
|
|
83
|
+
/sudo\s/i,
|
|
84
|
+
/run\s+as\s+administrator/i,
|
|
85
|
+
/elevation\s+prompt/i,
|
|
86
|
+
];
|
|
87
|
+
function sanitizeSkill(content, filename) {
|
|
88
|
+
for (const pattern of SKILL_INJECTION_PATTERNS) {
|
|
89
|
+
if (pattern.test(content)) {
|
|
90
|
+
console.warn(`[Security] BLOCKED skill "${filename}": contains injection pattern`);
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return content;
|
|
95
|
+
}
|
|
96
|
+
function validateSkillStructure(content) {
|
|
97
|
+
// Must have at least one markdown header if the file is substantial
|
|
98
|
+
if (!content.includes('#') && content.length > 500) {
|
|
99
|
+
return { valid: false, reason: 'No markdown headers — suspicious format' };
|
|
100
|
+
}
|
|
101
|
+
// Suspiciously long single paragraph (likely injection payload)
|
|
102
|
+
const lines = content.split('\n');
|
|
103
|
+
const longLines = lines.filter(l => l.length > 500);
|
|
104
|
+
if (longLines.length > 3) {
|
|
105
|
+
return { valid: false, reason: 'Multiple very long lines — possible injection' };
|
|
106
|
+
}
|
|
107
|
+
// Too much code relative to documentation
|
|
108
|
+
const codeBlocks = (content.match(/```/g) || []).length / 2;
|
|
109
|
+
const totalLines = lines.length;
|
|
110
|
+
if (codeBlocks > 0 && codeBlocks * 10 > totalLines) {
|
|
111
|
+
return { valid: false, reason: 'More code than documentation — suspicious skill' };
|
|
112
|
+
}
|
|
113
|
+
// Must be under 50 KB (agentskills.io skills legitimately run 10-30KB; injection
|
|
114
|
+
// patterns are caught above; blanket 10KB was too tight for real-world skills)
|
|
115
|
+
if (content.length > 51200) {
|
|
116
|
+
return { valid: false, reason: 'Skill too large (>50KB) — possible payload' };
|
|
117
|
+
}
|
|
118
|
+
return { valid: true };
|
|
119
|
+
}
|
|
120
|
+
const BLOCKED_LOG = path_1.default.join(process.cwd(), 'workspace', 'blocked-skills.log');
|
|
121
|
+
function logBlockedSkill(filename, reason) {
|
|
122
|
+
try {
|
|
123
|
+
fs_1.default.mkdirSync(path_1.default.dirname(BLOCKED_LOG), { recursive: true });
|
|
124
|
+
fs_1.default.appendFileSync(BLOCKED_LOG, `${new Date().toISOString()} | BLOCKED: ${filename} | ${reason}\n`);
|
|
125
|
+
}
|
|
126
|
+
catch { }
|
|
127
|
+
}
|
|
128
|
+
// Maps frontmatter platform values → Node.js process.platform strings
|
|
129
|
+
const PLATFORM_MAP = {
|
|
130
|
+
windows: 'win32',
|
|
131
|
+
linux: 'linux',
|
|
132
|
+
macos: 'darwin',
|
|
133
|
+
darwin: 'darwin',
|
|
134
|
+
any: 'any',
|
|
135
|
+
};
|
|
136
|
+
// ── Step 0 values — real categories found via:
|
|
137
|
+
// grep -h "^category:" skills/*/SKILL.md | sort -u
|
|
138
|
+
//
|
|
139
|
+
// category: agent-bridge
|
|
140
|
+
// category: creative
|
|
141
|
+
// category: developer
|
|
142
|
+
// category: gaming
|
|
143
|
+
// category: india
|
|
144
|
+
// category: media
|
|
145
|
+
// category: productivity
|
|
146
|
+
// category: research
|
|
147
|
+
// category: smart-home
|
|
148
|
+
// category: social
|
|
149
|
+
// category: windows
|
|
150
|
+
//
|
|
151
|
+
// Keyword buckets map trigger words → those actual category strings.
|
|
152
|
+
// A bucket with no matching category was dropped (no invented names).
|
|
153
|
+
const CATEGORY_KEYWORD_MAP = {
|
|
154
|
+
'productivity': ['obsidian', 'notion', 'outlook', 'calendar', 'email', 'linear', 'todo', 'onenote', 'drive', 'sheets', 'docs'],
|
|
155
|
+
'developer': ['git', 'github', 'docker', 'jupyter', 'code', 'debug', 'test', 'pr', 'repo', 'npm', 'node', 'python', 'typescript', 'deploy', 'vercel', 'ssh'],
|
|
156
|
+
'india': ['nse', 'nifty', 'zerodha', 'upstox', 'sensex', 'bse', 'trading', 'trade', 'stock', 'fii', 'dii', 'portfolio', 'reliance', 'infy', 'options', 'derivatives', 'tax', 'itr'],
|
|
157
|
+
'research': ['research', 'arxiv', 'paper', 'academic', 'pdf', 'ocr', 'rss', 'feed'],
|
|
158
|
+
'windows': ['powershell', 'registry', 'services', 'wsl', 'windows', 'cpu', 'disk', 'network', 'process', 'clipboard', 'scheduler'],
|
|
159
|
+
'creative': ['image', 'stable diffusion', 'generative', 'p5js', 'ascii', 'banner', 'art', 'draw'],
|
|
160
|
+
'media': ['youtube', 'gif', 'audio', 'music', 'video', 'transcript', 'tenor', 'giphy'],
|
|
161
|
+
'social': ['twitter', 'tweet', 'linkedin', 'social media'],
|
|
162
|
+
'smart-home': ['hue', 'philips', 'smart home', 'iot', 'lights'],
|
|
163
|
+
'agent-bridge': ['claude code', 'openai', 'codex', 'opencode'],
|
|
164
|
+
'gaming': ['minecraft', 'pokemon', 'gameboy', 'emulator'],
|
|
165
|
+
'travel': ['flight', 'flights', 'airfare', 'airline', 'airport', 'booking', 'hotel', 'hotels', 'travel', 'trip', 'itinerary', 'visa', 'pnr', 'layover', 'nonstop', 'stopover'],
|
|
166
|
+
};
|
|
167
|
+
// ── isSimpleMessage ────────────────────────────────────────────
|
|
168
|
+
// Returns true for short conversational messages that need minimal context:
|
|
169
|
+
// < 15 words, no tool keywords, no URLs/paths, no past-context references.
|
|
170
|
+
function isSimpleMessage(msg) {
|
|
171
|
+
const words = msg.trim().split(/\s+/).length;
|
|
172
|
+
if (words > 15)
|
|
173
|
+
return false;
|
|
174
|
+
const toolKeywords = [
|
|
175
|
+
'file', 'search', 'browse', 'run', 'execute', 'install',
|
|
176
|
+
'download', 'create', 'delete', 'open', 'screenshot',
|
|
177
|
+
'scan', 'analyze', 'deploy', 'commit', 'push', 'build',
|
|
178
|
+
'docker', 'git', 'npm', 'python', 'shell', 'terminal',
|
|
179
|
+
'outlook', 'email', 'calendar', 'notion', 'obsidian',
|
|
180
|
+
'trade', 'stock', 'nse', 'portfolio', 'backtest',
|
|
181
|
+
'remember', 'forgot', 'last time', 'we discussed',
|
|
182
|
+
'clone', 'voice', 'speak', 'listen', 'transcribe',
|
|
183
|
+
// Travel domain — must reach skill injection pipeline
|
|
184
|
+
'flight', 'flights', 'airfare', 'airline', 'airport',
|
|
185
|
+
'booking', 'hotel', 'hotels', 'travel', 'trip',
|
|
186
|
+
'itinerary', 'visa', 'pnr',
|
|
187
|
+
];
|
|
188
|
+
const lower = msg.toLowerCase();
|
|
189
|
+
if (toolKeywords.some(kw => lower.includes(kw)))
|
|
190
|
+
return false;
|
|
191
|
+
if (/https?:\/\/|\.\w{2,4}(\s|$)|[\\\/]/.test(msg))
|
|
192
|
+
return false;
|
|
193
|
+
return true;
|
|
194
|
+
}
|
|
195
|
+
// ── needsMemory ────────────────────────────────────────────────
|
|
196
|
+
// Returns true only when the message explicitly references past context.
|
|
197
|
+
// Prevents dumping all memories into every prompt.
|
|
198
|
+
function needsMemory(msg) {
|
|
199
|
+
const memoryKeywords = [
|
|
200
|
+
'remember', 'forgot', 'last time', 'we discussed',
|
|
201
|
+
'earlier', 'before', 'previous', 'you said', 'i told you',
|
|
202
|
+
'my name', 'my project', 'our', 'we were',
|
|
203
|
+
];
|
|
204
|
+
return memoryKeywords.some(kw => msg.toLowerCase().includes(kw));
|
|
205
|
+
}
|
|
206
|
+
// ── SkillLoader ────────────────────────────────────────────────
|
|
207
|
+
class SkillLoader {
|
|
208
|
+
constructor() {
|
|
209
|
+
this.cache = null;
|
|
210
|
+
// Check built-in skills, workspace skills, and self-learned/promoted skills.
|
|
211
|
+
// NOTE: skills/learned/pending/ is intentionally excluded — pending skills
|
|
212
|
+
// are never auto-loaded; they require explicit /skills approve first.
|
|
213
|
+
this.skillsDirs = [
|
|
214
|
+
path_1.default.join(process.cwd(), 'skills'),
|
|
215
|
+
path_1.default.join(process.cwd(), 'workspace', 'skills'),
|
|
216
|
+
path_1.default.join(process.cwd(), 'workspace', 'skills', 'learned'),
|
|
217
|
+
path_1.default.join(process.cwd(), 'workspace', 'skills', 'approved'),
|
|
218
|
+
// A2/A3 approved drafts
|
|
219
|
+
path_1.default.join(process.cwd(), 'skills', 'learned', 'approved'),
|
|
220
|
+
// A4 library-installed skills
|
|
221
|
+
path_1.default.join(process.cwd(), 'skills', 'installed'),
|
|
222
|
+
].filter(d => {
|
|
223
|
+
try {
|
|
224
|
+
return fs_1.default.existsSync(d);
|
|
225
|
+
}
|
|
226
|
+
catch {
|
|
227
|
+
return false;
|
|
228
|
+
}
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
// loadAllRaw — bypasses the disabled-skills filter
|
|
232
|
+
// Used by GET /api/skills so the UI can show disabled skills too
|
|
233
|
+
loadAllRaw() {
|
|
234
|
+
return this.loadAll(/* includeDisabled */ true);
|
|
235
|
+
}
|
|
236
|
+
loadAll(includeDisabled = false) {
|
|
237
|
+
if (!includeDisabled && this.cache)
|
|
238
|
+
return this.cache;
|
|
239
|
+
const skills = [];
|
|
240
|
+
for (const dir of this.skillsDirs) {
|
|
241
|
+
try {
|
|
242
|
+
const entries = fs_1.default.readdirSync(dir, { withFileTypes: true });
|
|
243
|
+
for (const entry of entries) {
|
|
244
|
+
if (!entry.isDirectory())
|
|
245
|
+
continue;
|
|
246
|
+
const skillPath = path_1.default.join(dir, entry.name, 'SKILL.md');
|
|
247
|
+
if (!fs_1.default.existsSync(skillPath))
|
|
248
|
+
continue;
|
|
249
|
+
try {
|
|
250
|
+
const fileContent = fs_1.default.readFileSync(skillPath, 'utf-8');
|
|
251
|
+
const sanitized = sanitizeSkill(fileContent, entry.name);
|
|
252
|
+
if (!sanitized) {
|
|
253
|
+
logBlockedSkill(entry.name, 'injection pattern detected');
|
|
254
|
+
continue;
|
|
255
|
+
}
|
|
256
|
+
const structure = validateSkillStructure(sanitized);
|
|
257
|
+
if (!structure.valid) {
|
|
258
|
+
console.log(`[Skills] BLOCKED (structure): ${entry.name} — ${structure.reason}`);
|
|
259
|
+
logBlockedSkill(entry.name, structure.reason ?? 'structure check failed');
|
|
260
|
+
continue;
|
|
261
|
+
}
|
|
262
|
+
const parsed = this.parse(sanitized, skillPath);
|
|
263
|
+
if (!parsed)
|
|
264
|
+
continue;
|
|
265
|
+
// Enabled gate — skip skills with enabled: false unless caller wants all
|
|
266
|
+
if (!includeDisabled && parsed.enabled === false) {
|
|
267
|
+
console.debug(`[SkillLoader] Skipping "${parsed.name}" (disabled via frontmatter)`);
|
|
268
|
+
continue;
|
|
269
|
+
}
|
|
270
|
+
// Platform gate — skip skills that require a different OS
|
|
271
|
+
if (parsed.platform && parsed.platform !== 'any') {
|
|
272
|
+
const required = PLATFORM_MAP[parsed.platform.toLowerCase()] ?? parsed.platform;
|
|
273
|
+
if (required !== process.platform) {
|
|
274
|
+
console.debug(`[SkillLoader] Skipping "${parsed.name}" (platform: ${parsed.platform}, current: ${process.platform})`);
|
|
275
|
+
continue;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
skills.push(parsed);
|
|
279
|
+
}
|
|
280
|
+
catch { }
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
catch { }
|
|
284
|
+
}
|
|
285
|
+
// Deduplicate by name — later directories (approved > learned) take precedence.
|
|
286
|
+
// skillsDirs order: built-in → workspace → learned → approved.
|
|
287
|
+
// Since we iterate in that order, the last entry for a given name wins (approved beats learned).
|
|
288
|
+
const seen = new Map();
|
|
289
|
+
for (const skill of skills) {
|
|
290
|
+
seen.set(skill.name, skill);
|
|
291
|
+
}
|
|
292
|
+
const deduped = Array.from(seen.values());
|
|
293
|
+
if (deduped.length < skills.length) {
|
|
294
|
+
console.log(`[SkillLoader] Deduplicated ${skills.length} → ${deduped.length} skills (removed ${skills.length - deduped.length} duplicates)`);
|
|
295
|
+
}
|
|
296
|
+
// Filter out disabled skills (unless caller wants all)
|
|
297
|
+
const DISABLED_PATH = path_1.default.join(process.cwd(), 'workspace', 'disabled-skills.json');
|
|
298
|
+
let disabled = new Set();
|
|
299
|
+
if (!includeDisabled) {
|
|
300
|
+
try {
|
|
301
|
+
const raw = fs_1.default.readFileSync(DISABLED_PATH, 'utf-8');
|
|
302
|
+
disabled = new Set(JSON.parse(raw));
|
|
303
|
+
}
|
|
304
|
+
catch { }
|
|
305
|
+
}
|
|
306
|
+
const filtered = includeDisabled ? deduped : deduped.filter(s => !disabled.has(s.name));
|
|
307
|
+
if (!includeDisabled) {
|
|
308
|
+
this.cache = filtered;
|
|
309
|
+
if (filtered.length > 0) {
|
|
310
|
+
const platformSkipped = deduped.length - filtered.length - disabled.size;
|
|
311
|
+
const skippedMsg = platformSkipped > 0 ? `, ${platformSkipped} platform-skipped` : '';
|
|
312
|
+
console.log(`[SkillLoader] Loaded ${filtered.length} skills${skippedMsg}: ${filtered.map(s => s.name).join(', ')}`);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
return filtered;
|
|
316
|
+
}
|
|
317
|
+
parse(raw, filePath) {
|
|
318
|
+
try {
|
|
319
|
+
const match = raw.match(/^---\s*([\s\S]*?)\s*---\s*([\s\S]*)$/);
|
|
320
|
+
const normalizedPath = filePath.replace(/\\/g, '/');
|
|
321
|
+
const origin = normalizedPath.includes('/workspace/') ? 'local' : 'aiden';
|
|
322
|
+
if (!match) {
|
|
323
|
+
// No frontmatter — use directory name as skill name
|
|
324
|
+
const name = path_1.default.basename(path_1.default.dirname(filePath));
|
|
325
|
+
const preview = raw.trim().slice(0, 500);
|
|
326
|
+
return { name, description: name, version: '1.0.0', tags: [name], preview, filePath, origin };
|
|
327
|
+
}
|
|
328
|
+
const frontmatter = match[1];
|
|
329
|
+
const body = match[2].trim();
|
|
330
|
+
const preview = body.slice(0, 1500);
|
|
331
|
+
const get = (key) => {
|
|
332
|
+
const m = frontmatter.match(new RegExp(`^${key}:\\s*(.+)$`, 'm'));
|
|
333
|
+
return m ? m[1].trim().replace(/^['"]|['"]$/g, '') : '';
|
|
334
|
+
};
|
|
335
|
+
const tagsRaw = get('tags');
|
|
336
|
+
const tags = tagsRaw
|
|
337
|
+
? tagsRaw.split(',').map(t => t.trim().toLowerCase()).filter(Boolean)
|
|
338
|
+
: [];
|
|
339
|
+
const name = get('name') || path_1.default.basename(path_1.default.dirname(filePath));
|
|
340
|
+
const category = get('category') || undefined;
|
|
341
|
+
const platform = get('platform') || undefined;
|
|
342
|
+
// Parse enabled — absent means enabled (legacy skills have no field)
|
|
343
|
+
const enabledRaw = get('enabled');
|
|
344
|
+
const enabled = enabledRaw === '' ? undefined : enabledRaw === 'false' ? false : true;
|
|
345
|
+
// ── agentskills.io spec fields ────────────────────────────
|
|
346
|
+
const license = get('license') || undefined;
|
|
347
|
+
const source = get('source') || undefined;
|
|
348
|
+
const importedFrom = get('imported-from') || undefined;
|
|
349
|
+
// compatibility: "compatibility: node18, linux" → string[]
|
|
350
|
+
const compatRaw = get('compatibility');
|
|
351
|
+
const compatibility = compatRaw
|
|
352
|
+
? compatRaw.split(',').map(c => c.trim()).filter(Boolean)
|
|
353
|
+
: undefined;
|
|
354
|
+
// allowed-tools: "allowed-tools: shell, browser" → string[]
|
|
355
|
+
const allowedToolsRaw = get('allowed-tools');
|
|
356
|
+
const allowedTools = allowedToolsRaw
|
|
357
|
+
? allowedToolsRaw.split(',').map(t => t.trim()).filter(Boolean)
|
|
358
|
+
: undefined;
|
|
359
|
+
// metadata: key: value pairs not captured by known fields
|
|
360
|
+
const KNOWN_FIELDS = new Set([
|
|
361
|
+
'name', 'description', 'version', 'category', 'platform', 'tags',
|
|
362
|
+
'origin', 'enabled', 'license', 'source', 'imported-from',
|
|
363
|
+
'compatibility', 'allowed-tools',
|
|
364
|
+
]);
|
|
365
|
+
const metadata = {};
|
|
366
|
+
for (const line of frontmatter.split('\n')) {
|
|
367
|
+
const kv = line.match(/^([a-zA-Z][\w-]*):\s*(.+)$/);
|
|
368
|
+
if (kv && !KNOWN_FIELDS.has(kv[1].toLowerCase())) {
|
|
369
|
+
metadata[kv[1]] = kv[2].trim().replace(/^['"]|['"]$/g, '');
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
// Detect optional subdirectories relative to the skill dir
|
|
373
|
+
const skillDir = path_1.default.dirname(filePath);
|
|
374
|
+
const hasScripts = fs_1.default.existsSync(path_1.default.join(skillDir, 'scripts'));
|
|
375
|
+
const hasReferences = fs_1.default.existsSync(path_1.default.join(skillDir, 'references'));
|
|
376
|
+
const hasAssets = fs_1.default.existsSync(path_1.default.join(skillDir, 'assets'));
|
|
377
|
+
return {
|
|
378
|
+
name,
|
|
379
|
+
description: get('description'),
|
|
380
|
+
version: get('version') || '1.0.0',
|
|
381
|
+
category,
|
|
382
|
+
platform,
|
|
383
|
+
tags,
|
|
384
|
+
preview,
|
|
385
|
+
filePath,
|
|
386
|
+
origin,
|
|
387
|
+
enabled,
|
|
388
|
+
license,
|
|
389
|
+
compatibility: compatibility?.length ? compatibility : undefined,
|
|
390
|
+
metadata: Object.keys(metadata).length ? metadata : undefined,
|
|
391
|
+
// Note: allowedTools is currently informational metadata. Actual tool access is
|
|
392
|
+
// controlled by the agent's plannerTools list in agentLoop.ts (detectToolCategories).
|
|
393
|
+
// Skill authors using Claude Code-style 'Bash(cmd:*)' declarations should ensure the
|
|
394
|
+
// underlying tool (e.g. shell_exec) is reachable via detectToolCategories() for the
|
|
395
|
+
// skill's intended query patterns.
|
|
396
|
+
allowedTools: allowedTools?.length ? allowedTools : undefined,
|
|
397
|
+
hasScripts: hasScripts || undefined,
|
|
398
|
+
hasReferences: hasReferences || undefined,
|
|
399
|
+
hasAssets: hasAssets || undefined,
|
|
400
|
+
source,
|
|
401
|
+
importedFrom,
|
|
402
|
+
};
|
|
403
|
+
}
|
|
404
|
+
catch {
|
|
405
|
+
return null;
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
findRelevant(message, maxSkills = 3) {
|
|
409
|
+
if (isSimpleMessage(message))
|
|
410
|
+
return []; // no skills for short conversational messages
|
|
411
|
+
const skills = this.loadAll();
|
|
412
|
+
if (skills.length === 0)
|
|
413
|
+
return [];
|
|
414
|
+
const lower = message.toLowerCase();
|
|
415
|
+
const words = lower.split(/\s+/);
|
|
416
|
+
// Detect matching categories from message text using real category strings
|
|
417
|
+
const matchedCategories = new Set();
|
|
418
|
+
for (const [cat, keywords] of Object.entries(CATEGORY_KEYWORD_MAP)) {
|
|
419
|
+
if (keywords.some(kw => lower.includes(kw))) {
|
|
420
|
+
matchedCategories.add(cat);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
// Score each skill by relevance
|
|
424
|
+
const scored = skills.map(skill => {
|
|
425
|
+
let score = 0;
|
|
426
|
+
// Exact name match in message
|
|
427
|
+
if (lower.includes(skill.name.toLowerCase()))
|
|
428
|
+
score += 10;
|
|
429
|
+
// Description word overlap
|
|
430
|
+
const descWords = skill.description.toLowerCase().split(/\s+/);
|
|
431
|
+
words.forEach(w => { if (w.length > 3 && descWords.includes(w))
|
|
432
|
+
score += 3; });
|
|
433
|
+
// Category field match (direct — highest priority)
|
|
434
|
+
if (skill.category && matchedCategories.has(skill.category))
|
|
435
|
+
score += 8;
|
|
436
|
+
// Direct tag match against message text
|
|
437
|
+
skill.tags.forEach(tag => {
|
|
438
|
+
if (lower.includes(tag))
|
|
439
|
+
score += 5;
|
|
440
|
+
});
|
|
441
|
+
// Tag matches a detected category name
|
|
442
|
+
skill.tags.forEach(tag => {
|
|
443
|
+
if (matchedCategories.has(tag))
|
|
444
|
+
score += 4;
|
|
445
|
+
});
|
|
446
|
+
// Installed skills (origin: 'aiden') get a priority bonus over auto-generated
|
|
447
|
+
// learned/approved workspace skills. This prevents self-taught skills from
|
|
448
|
+
// outcompeting curated installed skills on the same domain.
|
|
449
|
+
if (skill.origin === 'aiden')
|
|
450
|
+
score += 15;
|
|
451
|
+
return { skill, score };
|
|
452
|
+
});
|
|
453
|
+
return scored
|
|
454
|
+
.filter(s => s.score > 0)
|
|
455
|
+
.sort((a, b) => b.score - a.score)
|
|
456
|
+
.slice(0, maxSkills)
|
|
457
|
+
.map(s => s.skill);
|
|
458
|
+
}
|
|
459
|
+
formatForPrompt(skills) {
|
|
460
|
+
if (skills.length === 0)
|
|
461
|
+
return '';
|
|
462
|
+
const formatted = skills.map(s => `[SKILL: ${s.name}]\nDescription: ${s.description}\n${s.preview}`).join('\n\n---\n\n');
|
|
463
|
+
return `\n\nRELEVANT SKILLS FOR THIS TASK — follow their workflows:\n${formatted}\n\nMANDATORY: Use the tool workflow defined in the skill above. For browser-based skills (agent-browser), use shell_exec — do NOT substitute web_search.\n`;
|
|
464
|
+
}
|
|
465
|
+
// Invalidate cache — call after new skills are added at runtime
|
|
466
|
+
refresh() {
|
|
467
|
+
this.cache = null;
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
exports.SkillLoader = SkillLoader;
|
|
471
|
+
exports.skillLoader = new SkillLoader();
|