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,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();