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,352 @@
|
|
|
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
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
40
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
41
|
+
};
|
|
42
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
43
|
+
exports.skillTeacher = exports.SkillTeacher = void 0;
|
|
44
|
+
// core/skillTeacher.ts — Self-learning skill generation.
|
|
45
|
+
// After every successful plan execution, records the tool sequence,
|
|
46
|
+
// generates a SKILL.md using the LLM, and promotes to "approved"
|
|
47
|
+
// after PROMOTE_THRESHOLD successes.
|
|
48
|
+
const fs_1 = __importDefault(require("fs"));
|
|
49
|
+
const path_1 = __importDefault(require("path"));
|
|
50
|
+
// ── Paths ──────────────────────────────────────────────────────
|
|
51
|
+
const LEARNED_DIR = path_1.default.join(process.cwd(), 'workspace', 'skills', 'learned');
|
|
52
|
+
const APPROVED_DIR = path_1.default.join(process.cwd(), 'workspace', 'skills', 'approved');
|
|
53
|
+
const BUNDLED_SKILLS_DIR = path_1.default.join(process.cwd(), 'skills');
|
|
54
|
+
const PROMOTE_THRESHOLD = 3; // successes needed to promote to approved/
|
|
55
|
+
const SESSION_SKILL_LIMIT = 2; // max NEW skills generated per process session
|
|
56
|
+
// ── Session-scoped new-skill counter (reset on process restart) ─
|
|
57
|
+
let _sessionSkillsCreated = 0;
|
|
58
|
+
// ── Skill name extractor ───────────────────────────────────────
|
|
59
|
+
// "research the top AI agents of 2025" → "research_ai_agents"
|
|
60
|
+
function extractSkillName(task, tools) {
|
|
61
|
+
// Use tool sequence to name the skill when pattern is recognisable
|
|
62
|
+
if (tools.includes('deep_research') && tools.includes('file_write'))
|
|
63
|
+
return 'research_and_save';
|
|
64
|
+
if (tools.includes('web_search') && tools.includes('file_write'))
|
|
65
|
+
return 'search_and_save';
|
|
66
|
+
if (tools.includes('get_stocks'))
|
|
67
|
+
return 'stock_research';
|
|
68
|
+
if (tools.includes('run_python'))
|
|
69
|
+
return 'python_execution';
|
|
70
|
+
if (tools.includes('run_node'))
|
|
71
|
+
return 'node_execution';
|
|
72
|
+
if (tools.includes('shell_exec') && tools.includes('file_write'))
|
|
73
|
+
return 'shell_and_save';
|
|
74
|
+
// Extract key nouns from task — first 3 meaningful words
|
|
75
|
+
const stopWords = new Set([
|
|
76
|
+
'the', 'a', 'an', 'and', 'or', 'to', 'in', 'on',
|
|
77
|
+
'for', 'of', 'with', 'my', 'your', 'about', 'from',
|
|
78
|
+
'save', 'get', 'find', 'make', 'show', 'tell',
|
|
79
|
+
]);
|
|
80
|
+
const words = task
|
|
81
|
+
.toLowerCase()
|
|
82
|
+
.replace(/[^a-z0-9\s]/g, ' ')
|
|
83
|
+
.split(/\s+/)
|
|
84
|
+
.filter(w => w.length > 2 && !stopWords.has(w))
|
|
85
|
+
.slice(0, 3);
|
|
86
|
+
return words.join('_') || 'general_task';
|
|
87
|
+
}
|
|
88
|
+
// ── SKILL.md generator ─────────────────────────────────────────
|
|
89
|
+
async function generateSkillContent(skillName, task, tools, duration, llmCaller, apiKey, model, provider) {
|
|
90
|
+
const prompt = `Generate a SKILL.md file for DevOS based on this successful task execution.
|
|
91
|
+
|
|
92
|
+
Task: "${task}"
|
|
93
|
+
Tools used in order: ${tools.join(' → ')}
|
|
94
|
+
Duration: ${Math.round(duration / 1000)}s
|
|
95
|
+
|
|
96
|
+
Write a SKILL.md with this EXACT format:
|
|
97
|
+
---
|
|
98
|
+
name: ${skillName}
|
|
99
|
+
description: [one line description of what this skill does]
|
|
100
|
+
version: 1.0.0
|
|
101
|
+
origin: local
|
|
102
|
+
confidence: low
|
|
103
|
+
tags: [comma separated tags relevant to this task]
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
# [Skill Title in Title Case]
|
|
107
|
+
|
|
108
|
+
[2-5 bullet points of key instructions for doing this type of task well]
|
|
109
|
+
[Include specific tips learned from this execution]
|
|
110
|
+
[Keep it concise — under 200 words total]
|
|
111
|
+
|
|
112
|
+
Output ONLY the SKILL.md content. No explanation.`;
|
|
113
|
+
try {
|
|
114
|
+
const content = await llmCaller(prompt, apiKey, model, provider);
|
|
115
|
+
// Validate it has valid frontmatter
|
|
116
|
+
if (content.includes('---') && content.includes('name:')) {
|
|
117
|
+
return content.trim();
|
|
118
|
+
}
|
|
119
|
+
// Fallback — minimal valid SKILL.md
|
|
120
|
+
return buildFallbackSkill(skillName, task, tools, duration);
|
|
121
|
+
}
|
|
122
|
+
catch {
|
|
123
|
+
return buildFallbackSkill(skillName, task, tools, duration);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
function buildFallbackSkill(skillName, task, tools, duration) {
|
|
127
|
+
const title = skillName.replace(/_/g, ' ').replace(/\b\w/g, c => c.toUpperCase());
|
|
128
|
+
return `---
|
|
129
|
+
name: ${skillName}
|
|
130
|
+
description: ${task.slice(0, 80)}
|
|
131
|
+
version: 1.0.0
|
|
132
|
+
origin: local
|
|
133
|
+
confidence: low
|
|
134
|
+
tags: ${tools.join(', ')}
|
|
135
|
+
---
|
|
136
|
+
|
|
137
|
+
# ${title}
|
|
138
|
+
|
|
139
|
+
When performing this type of task:
|
|
140
|
+
1. Use tools in this order: ${tools.join(' → ')}
|
|
141
|
+
2. Task completed in ~${Math.round(duration / 1000)}s
|
|
142
|
+
3. Verify each step output before proceeding to the next
|
|
143
|
+
`;
|
|
144
|
+
}
|
|
145
|
+
// ── SkillTeacher ───────────────────────────────────────────────
|
|
146
|
+
class SkillTeacher {
|
|
147
|
+
constructor() {
|
|
148
|
+
try {
|
|
149
|
+
fs_1.default.mkdirSync(LEARNED_DIR, { recursive: true });
|
|
150
|
+
}
|
|
151
|
+
catch { }
|
|
152
|
+
try {
|
|
153
|
+
fs_1.default.mkdirSync(APPROVED_DIR, { recursive: true });
|
|
154
|
+
}
|
|
155
|
+
catch { }
|
|
156
|
+
}
|
|
157
|
+
static getInstance() {
|
|
158
|
+
if (!SkillTeacher.instance) {
|
|
159
|
+
SkillTeacher.instance = new SkillTeacher();
|
|
160
|
+
}
|
|
161
|
+
return SkillTeacher.instance;
|
|
162
|
+
}
|
|
163
|
+
// ── Check if a matching skill already exists ──────────────
|
|
164
|
+
hasMatchingSkill(task, tools) {
|
|
165
|
+
const skillName = extractSkillName(task, tools);
|
|
166
|
+
const dirsToCheck = [
|
|
167
|
+
path_1.default.join(process.cwd(), 'skills'),
|
|
168
|
+
LEARNED_DIR,
|
|
169
|
+
APPROVED_DIR,
|
|
170
|
+
];
|
|
171
|
+
for (const dir of dirsToCheck) {
|
|
172
|
+
try {
|
|
173
|
+
if (fs_1.default.existsSync(dir) && fs_1.default.existsSync(path_1.default.join(dir, skillName)))
|
|
174
|
+
return true;
|
|
175
|
+
}
|
|
176
|
+
catch { }
|
|
177
|
+
}
|
|
178
|
+
return false;
|
|
179
|
+
}
|
|
180
|
+
// ── Record a successful task ───────────────────────────────
|
|
181
|
+
async recordSuccess(task, tools, duration, llmCaller, apiKey, model, provider) {
|
|
182
|
+
if (tools.length === 0)
|
|
183
|
+
return;
|
|
184
|
+
const skillName = extractSkillName(task, tools);
|
|
185
|
+
const metaPath = path_1.default.join(LEARNED_DIR, skillName, 'meta.json');
|
|
186
|
+
const skillPath = path_1.default.join(LEARNED_DIR, skillName, 'SKILL.md');
|
|
187
|
+
// ── If skill exists — update usage count ─────────────────
|
|
188
|
+
if (fs_1.default.existsSync(metaPath)) {
|
|
189
|
+
try {
|
|
190
|
+
const meta = JSON.parse(fs_1.default.readFileSync(metaPath, 'utf-8'));
|
|
191
|
+
meta.successCount++;
|
|
192
|
+
meta.lastUsed = Date.now();
|
|
193
|
+
meta.avgDuration = Math.round((meta.avgDuration + duration) / 2);
|
|
194
|
+
meta.confidence = Math.min(meta.successCount / PROMOTE_THRESHOLD, 1);
|
|
195
|
+
if (meta.successCount >= PROMOTE_THRESHOLD && !meta.promoted) {
|
|
196
|
+
this.promoteSkill(skillName);
|
|
197
|
+
meta.promoted = true;
|
|
198
|
+
console.log(`[SkillTeacher] Promoted "${skillName}" → approved/ (${meta.successCount} successes)`);
|
|
199
|
+
}
|
|
200
|
+
fs_1.default.writeFileSync(metaPath, JSON.stringify(meta, null, 2));
|
|
201
|
+
console.log(`[SkillTeacher] Updated "${skillName}" — ${meta.successCount} successes, confidence: ${(meta.confidence * 100).toFixed(0)}%`);
|
|
202
|
+
}
|
|
203
|
+
catch (e) {
|
|
204
|
+
console.warn(`[SkillTeacher] Meta update failed for "${skillName}": ${e.message}`);
|
|
205
|
+
}
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
// ── Quality gate — reject low-signal skill names ──────────
|
|
209
|
+
const isLowQuality = (skillName.length < 5 ||
|
|
210
|
+
skillName.split('_').length < 2 ||
|
|
211
|
+
task.split(/\s+/).length < 3);
|
|
212
|
+
if (isLowQuality) {
|
|
213
|
+
console.log(`[SkillTeacher] Rejected low-quality skill: "${skillName}"`);
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
// ── Session rate limit — max SESSION_SKILL_LIMIT new skills ─
|
|
217
|
+
if (_sessionSkillsCreated >= SESSION_SKILL_LIMIT) {
|
|
218
|
+
console.log(`[SkillTeacher] Session limit reached (${SESSION_SKILL_LIMIT}), skipping: "${skillName}"`);
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
// ── Deduplication — reject names already in bundled skills/, approved/, or learned/ ─
|
|
222
|
+
const dirsToDedup = [BUNDLED_SKILLS_DIR, APPROVED_DIR, LEARNED_DIR];
|
|
223
|
+
for (const dir of dirsToDedup) {
|
|
224
|
+
if (fs_1.default.existsSync(path_1.default.join(dir, skillName))) {
|
|
225
|
+
console.log(`[SkillTeacher] Skipping duplicate (exists in ${path_1.default.basename(dir)}/): "${skillName}"`);
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
// ── New skill — generate SKILL.md and write meta ──────────
|
|
230
|
+
console.log(`[SkillTeacher] Learning new skill: "${skillName}" from task: "${task.slice(0, 60)}"`);
|
|
231
|
+
try {
|
|
232
|
+
const content = await generateSkillContent(skillName, task, tools, duration, llmCaller, apiKey, model, provider);
|
|
233
|
+
// ── Size validation — reject suspiciously small or large content ──
|
|
234
|
+
const byteLen = Buffer.byteLength(content, 'utf-8');
|
|
235
|
+
if (byteLen < 200) {
|
|
236
|
+
console.warn(`[SkillTeacher] Rejected "${skillName}": content too small (${byteLen} bytes, min 200)`);
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
if (byteLen > 10240) {
|
|
240
|
+
console.warn(`[SkillTeacher] Rejected "${skillName}": content too large (${byteLen} bytes > 10KB)`);
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
// ── Structural validation — must have frontmatter + heading + body ──
|
|
244
|
+
const hasFrontmatter = (content.match(/---/g) || []).length >= 2;
|
|
245
|
+
const hasNameField = /^name:\s*\S/m.test(content);
|
|
246
|
+
const hasDescField = /^description:\s*\S/m.test(content);
|
|
247
|
+
const hasHeading = /^#\s+\S/m.test(content);
|
|
248
|
+
const hasBody = content.split('\n').filter(l => l.trim().length > 0).length >= 8;
|
|
249
|
+
if (!hasFrontmatter || !hasNameField || !hasDescField || !hasHeading || !hasBody) {
|
|
250
|
+
console.warn(`[SkillTeacher] Rejected "${skillName}": failed structural validation (frontmatter=${hasFrontmatter}, name=${hasNameField}, desc=${hasDescField}, heading=${hasHeading}, body=${hasBody})`);
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
fs_1.default.mkdirSync(path_1.default.join(LEARNED_DIR, skillName), { recursive: true });
|
|
254
|
+
fs_1.default.writeFileSync(skillPath, content, 'utf-8');
|
|
255
|
+
const meta = {
|
|
256
|
+
name: skillName,
|
|
257
|
+
taskPattern: task.slice(0, 100),
|
|
258
|
+
toolSequence: tools,
|
|
259
|
+
successCount: 1,
|
|
260
|
+
failCount: 0,
|
|
261
|
+
confidence: 1 / PROMOTE_THRESHOLD,
|
|
262
|
+
promoted: false,
|
|
263
|
+
createdAt: Date.now(),
|
|
264
|
+
lastUsed: Date.now(),
|
|
265
|
+
avgDuration: duration,
|
|
266
|
+
};
|
|
267
|
+
fs_1.default.writeFileSync(metaPath, JSON.stringify(meta, null, 2));
|
|
268
|
+
_sessionSkillsCreated++;
|
|
269
|
+
console.log(`[SkillTeacher] Saved new skill: "${skillName}" (session total: ${_sessionSkillsCreated}/${SESSION_SKILL_LIMIT})`);
|
|
270
|
+
// Invalidate skillLoader cache so new skill is picked up immediately
|
|
271
|
+
try {
|
|
272
|
+
const { skillLoader } = await Promise.resolve().then(() => __importStar(require('./skillLoader')));
|
|
273
|
+
skillLoader.refresh();
|
|
274
|
+
}
|
|
275
|
+
catch { }
|
|
276
|
+
}
|
|
277
|
+
catch (e) {
|
|
278
|
+
console.warn(`[SkillTeacher] Failed to generate skill "${skillName}": ${e.message}`);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
// ── Record a failed task ───────────────────────────────────
|
|
282
|
+
recordFailure(task, tools) {
|
|
283
|
+
if (tools.length === 0)
|
|
284
|
+
return;
|
|
285
|
+
const skillName = extractSkillName(task, tools);
|
|
286
|
+
const metaPath = path_1.default.join(LEARNED_DIR, skillName, 'meta.json');
|
|
287
|
+
if (!fs_1.default.existsSync(metaPath))
|
|
288
|
+
return;
|
|
289
|
+
try {
|
|
290
|
+
const meta = JSON.parse(fs_1.default.readFileSync(metaPath, 'utf-8'));
|
|
291
|
+
meta.failCount++;
|
|
292
|
+
fs_1.default.writeFileSync(metaPath, JSON.stringify(meta, null, 2));
|
|
293
|
+
}
|
|
294
|
+
catch { }
|
|
295
|
+
}
|
|
296
|
+
// ── Promote skill from learned/ to approved/ ───────────────
|
|
297
|
+
promoteSkill(skillName) {
|
|
298
|
+
const src = path_1.default.join(LEARNED_DIR, skillName);
|
|
299
|
+
const dest = path_1.default.join(APPROVED_DIR, skillName);
|
|
300
|
+
try {
|
|
301
|
+
fs_1.default.mkdirSync(dest, { recursive: true });
|
|
302
|
+
for (const file of fs_1.default.readdirSync(src)) {
|
|
303
|
+
fs_1.default.copyFileSync(path_1.default.join(src, file), path_1.default.join(dest, file));
|
|
304
|
+
}
|
|
305
|
+
// Invalidate cache after promotion
|
|
306
|
+
Promise.resolve().then(() => __importStar(require('./skillLoader'))).then(m => m.skillLoader.refresh()).catch(() => { });
|
|
307
|
+
}
|
|
308
|
+
catch (e) {
|
|
309
|
+
console.warn(`[SkillTeacher] Promotion failed for "${skillName}": ${e.message}`);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
// ── List helpers ───────────────────────────────────────────
|
|
313
|
+
readDir(dir) {
|
|
314
|
+
if (!fs_1.default.existsSync(dir))
|
|
315
|
+
return [];
|
|
316
|
+
return fs_1.default.readdirSync(dir)
|
|
317
|
+
.filter(d => {
|
|
318
|
+
try {
|
|
319
|
+
return fs_1.default.statSync(path_1.default.join(dir, d)).isDirectory();
|
|
320
|
+
}
|
|
321
|
+
catch {
|
|
322
|
+
return false;
|
|
323
|
+
}
|
|
324
|
+
})
|
|
325
|
+
.map(name => {
|
|
326
|
+
try {
|
|
327
|
+
const metaPath = path_1.default.join(dir, name, 'meta.json');
|
|
328
|
+
if (fs_1.default.existsSync(metaPath)) {
|
|
329
|
+
return JSON.parse(fs_1.default.readFileSync(metaPath, 'utf-8'));
|
|
330
|
+
}
|
|
331
|
+
return { name, successCount: 0, failCount: 0, confidence: 0 };
|
|
332
|
+
}
|
|
333
|
+
catch {
|
|
334
|
+
return { name, successCount: 0, failCount: 0, confidence: 0 };
|
|
335
|
+
}
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
listLearned() {
|
|
339
|
+
return this.readDir(LEARNED_DIR);
|
|
340
|
+
}
|
|
341
|
+
listApproved() {
|
|
342
|
+
return this.readDir(APPROVED_DIR);
|
|
343
|
+
}
|
|
344
|
+
getStats() {
|
|
345
|
+
const learned = this.listLearned();
|
|
346
|
+
const approved = this.listApproved();
|
|
347
|
+
const totalSuccesses = learned.reduce((s, m) => s + (m.successCount || 0), 0);
|
|
348
|
+
return { learned: learned.length, approved: approved.length, totalSuccesses };
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
exports.SkillTeacher = SkillTeacher;
|
|
352
|
+
exports.skillTeacher = SkillTeacher.getInstance();
|
|
@@ -0,0 +1,210 @@
|
|
|
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.validateSkillDir = validateSkillDir;
|
|
11
|
+
exports.validateSkillByName = validateSkillByName;
|
|
12
|
+
exports.validateAllSkills = validateAllSkills;
|
|
13
|
+
exports.summariseResults = summariseResults;
|
|
14
|
+
// core/skillValidator.ts — agentskills.io spec compliance validator.
|
|
15
|
+
//
|
|
16
|
+
// Validates a skill directory (or all loaded skills) against the
|
|
17
|
+
// Open Agent Skills specification at https://agentskills.io
|
|
18
|
+
//
|
|
19
|
+
// Spec summary:
|
|
20
|
+
// - SKILL.md with YAML frontmatter
|
|
21
|
+
// - Required: name (^[a-z0-9]+(-[a-z0-9]+)*$), description
|
|
22
|
+
// - Recommended: license (SPDX), version (semver)
|
|
23
|
+
// - Optional subdirs: scripts/, references/, assets/
|
|
24
|
+
// - Script extensions: .py .sh .js .ts .mjs only
|
|
25
|
+
// - allowed-tools: list of tool names skill may invoke
|
|
26
|
+
const fs_1 = __importDefault(require("fs"));
|
|
27
|
+
const path_1 = __importDefault(require("path"));
|
|
28
|
+
const skillLoader_1 = require("./skillLoader");
|
|
29
|
+
// ── Constants ─────────────────────────────────────────────────
|
|
30
|
+
const NAME_REGEX = /^[a-z0-9]+(-[a-z0-9]+)*$/;
|
|
31
|
+
const SEMVER_REGEX = /^\d+\.\d+\.\d+(-[\w.]+)?$/;
|
|
32
|
+
const SPDX_COMMON = new Set([
|
|
33
|
+
'Apache-2.0', 'MIT', 'GPL-2.0', 'GPL-3.0', 'LGPL-2.1', 'LGPL-3.0',
|
|
34
|
+
'BSD-2-Clause', 'BSD-3-Clause', 'ISC', 'MPL-2.0', 'AGPL-3.0', 'CC0-1.0',
|
|
35
|
+
'CC-BY-4.0', 'CC-BY-SA-4.0', 'Unlicense', 'WTFPL', 'Proprietary',
|
|
36
|
+
]);
|
|
37
|
+
const ALLOWED_SCRIPT_EXTS = new Set(['.py', '.sh', '.js', '.ts', '.mjs']);
|
|
38
|
+
// ── Core validator ─────────────────────────────────────────────
|
|
39
|
+
function validateSkillDir(skillDir) {
|
|
40
|
+
const skillId = path_1.default.basename(skillDir);
|
|
41
|
+
const skillPath = path_1.default.join(skillDir, 'SKILL.md');
|
|
42
|
+
const errors = [];
|
|
43
|
+
const warnings = [];
|
|
44
|
+
// ── E1: SKILL.md must exist ────────────────────────────────
|
|
45
|
+
if (!fs_1.default.existsSync(skillPath)) {
|
|
46
|
+
errors.push({ code: 'E_NO_SKILL_MD', message: 'SKILL.md not found in skill directory' });
|
|
47
|
+
return { skillId, skillPath, valid: false, errors, warnings, specScore: 0 };
|
|
48
|
+
}
|
|
49
|
+
const raw = (() => {
|
|
50
|
+
try {
|
|
51
|
+
return fs_1.default.readFileSync(skillPath, 'utf-8');
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
return '';
|
|
55
|
+
}
|
|
56
|
+
})();
|
|
57
|
+
if (!raw.trim()) {
|
|
58
|
+
errors.push({ code: 'E_EMPTY', message: 'SKILL.md is empty' });
|
|
59
|
+
return { skillId, skillPath, valid: false, errors, warnings, specScore: 0 };
|
|
60
|
+
}
|
|
61
|
+
// ── E2: Must have YAML frontmatter ─────────────────────────
|
|
62
|
+
const fmMatch = raw.match(/^---\s*\n([\s\S]*?)\n---\s*\n/);
|
|
63
|
+
if (!fmMatch) {
|
|
64
|
+
errors.push({ code: 'E_NO_FRONTMATTER', message: 'SKILL.md must begin with --- YAML frontmatter ---' });
|
|
65
|
+
}
|
|
66
|
+
const frontmatter = fmMatch ? fmMatch[1] : '';
|
|
67
|
+
const content = fmMatch ? raw.slice(fmMatch[0].length).trim() : raw.trim();
|
|
68
|
+
const get = (key) => {
|
|
69
|
+
const m = frontmatter.match(new RegExp(`^${key}:\\s*(.+)$`, 'm'));
|
|
70
|
+
return m ? m[1].trim().replace(/^['"]|['"]$/g, '') : '';
|
|
71
|
+
};
|
|
72
|
+
// ── E3: name — required, must match spec regex ─────────────
|
|
73
|
+
const name = get('name');
|
|
74
|
+
if (!name) {
|
|
75
|
+
errors.push({ code: 'E_NAME_MISSING', message: 'frontmatter "name:" is required', field: 'name' });
|
|
76
|
+
}
|
|
77
|
+
else if (!NAME_REGEX.test(name)) {
|
|
78
|
+
errors.push({
|
|
79
|
+
code: 'E_NAME_FORMAT',
|
|
80
|
+
message: `name "${name}" must match ^[a-z0-9]+(-[a-z0-9]+)*$ (lowercase, hyphens only, no underscores)`,
|
|
81
|
+
field: 'name',
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
else if (name !== skillId && !skillId.startsWith('generated') && !skillId.startsWith('learned')) {
|
|
85
|
+
warnings.push({
|
|
86
|
+
code: 'W_NAME_MISMATCH',
|
|
87
|
+
message: `name "${name}" does not match directory "${skillId}"`,
|
|
88
|
+
field: 'name',
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
// ── E4: description — required ─────────────────────────────
|
|
92
|
+
const description = get('description');
|
|
93
|
+
if (!description) {
|
|
94
|
+
errors.push({ code: 'E_DESC_MISSING', message: 'frontmatter "description:" is required', field: 'description' });
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
if (description.length < 10) {
|
|
98
|
+
warnings.push({ code: 'W_DESC_SHORT', message: `description too short (${description.length} chars, recommend ≥20)`, field: 'description' });
|
|
99
|
+
}
|
|
100
|
+
if (description.length > 300) {
|
|
101
|
+
warnings.push({ code: 'W_DESC_LONG', message: `description very long (${description.length} chars, recommend ≤200)`, field: 'description' });
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
// ── W1: version — recommended, should be semver ────────────
|
|
105
|
+
const version = get('version');
|
|
106
|
+
if (!version) {
|
|
107
|
+
warnings.push({ code: 'W_VERSION_MISSING', message: 'frontmatter "version:" is recommended (e.g. "1.0.0")', field: 'version' });
|
|
108
|
+
}
|
|
109
|
+
else if (!SEMVER_REGEX.test(version)) {
|
|
110
|
+
warnings.push({ code: 'W_VERSION_FORMAT', message: `version "${version}" does not look like semver (x.y.z)`, field: 'version' });
|
|
111
|
+
}
|
|
112
|
+
// ── W2: license — recommended ──────────────────────────────
|
|
113
|
+
const license = get('license');
|
|
114
|
+
if (!license) {
|
|
115
|
+
warnings.push({ code: 'W_LICENSE_MISSING', message: 'frontmatter "license:" is recommended (e.g. "Apache-2.0")', field: 'license' });
|
|
116
|
+
}
|
|
117
|
+
else if (!SPDX_COMMON.has(license)) {
|
|
118
|
+
warnings.push({ code: 'W_LICENSE_UNKNOWN', message: `license "${license}" is not a common SPDX identifier`, field: 'license' });
|
|
119
|
+
}
|
|
120
|
+
// ── E5: Body content must exist ────────────────────────────
|
|
121
|
+
if (!content) {
|
|
122
|
+
errors.push({ code: 'E_NO_BODY', message: 'SKILL.md has frontmatter but no body content' });
|
|
123
|
+
}
|
|
124
|
+
// ── W3: Body should have at least one markdown heading ─────
|
|
125
|
+
if (content && !content.includes('#')) {
|
|
126
|
+
warnings.push({ code: 'W_NO_HEADINGS', message: 'body content has no markdown headings — consider adding structure' });
|
|
127
|
+
}
|
|
128
|
+
// ── Scripts subdir checks ───────────────────────────────────
|
|
129
|
+
const scriptsDir = path_1.default.join(skillDir, 'scripts');
|
|
130
|
+
if (fs_1.default.existsSync(scriptsDir)) {
|
|
131
|
+
try {
|
|
132
|
+
const scriptFiles = fs_1.default.readdirSync(scriptsDir);
|
|
133
|
+
for (const f of scriptFiles) {
|
|
134
|
+
const ext = path_1.default.extname(f).toLowerCase();
|
|
135
|
+
if (!ALLOWED_SCRIPT_EXTS.has(ext) && ext !== '') {
|
|
136
|
+
errors.push({
|
|
137
|
+
code: 'E_SCRIPT_EXT',
|
|
138
|
+
message: `scripts/${f}: extension "${ext}" not allowed (permitted: .py .sh .js .ts .mjs)`,
|
|
139
|
+
field: 'scripts',
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
catch { }
|
|
145
|
+
}
|
|
146
|
+
// ── allowed-tools: validate list format ────────────────────
|
|
147
|
+
const allowedToolsRaw = get('allowed-tools');
|
|
148
|
+
if (allowedToolsRaw) {
|
|
149
|
+
const tools = allowedToolsRaw.split(',').map(t => t.trim()).filter(Boolean);
|
|
150
|
+
if (tools.length === 0) {
|
|
151
|
+
warnings.push({ code: 'W_ALLOWED_TOOLS_EMPTY', message: 'allowed-tools is set but empty', field: 'allowed-tools' });
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
// ── Score calculation ───────────────────────────────────────
|
|
155
|
+
// Start at 100, subtract per issue
|
|
156
|
+
let score = 100;
|
|
157
|
+
score -= errors.length * 15; // each error: -15
|
|
158
|
+
score -= warnings.length * 5; // each warning: -5
|
|
159
|
+
score = Math.max(0, Math.min(100, score));
|
|
160
|
+
const valid = errors.length === 0;
|
|
161
|
+
return { skillId, skillPath, valid, errors, warnings, specScore: score };
|
|
162
|
+
}
|
|
163
|
+
// ── Validate a single loaded skill by name ─────────────────────
|
|
164
|
+
function validateSkillByName(name) {
|
|
165
|
+
const skills = skillLoader_1.skillLoader.loadAllRaw();
|
|
166
|
+
const skill = skills.find(s => s.name === name);
|
|
167
|
+
if (!skill)
|
|
168
|
+
return null;
|
|
169
|
+
return validateSkillDir(path_1.default.dirname(skill.filePath));
|
|
170
|
+
}
|
|
171
|
+
// ── Validate all skills in a skills directory ──────────────────
|
|
172
|
+
function validateAllSkills(skillsDir) {
|
|
173
|
+
const dir = skillsDir ?? path_1.default.join(process.cwd(), 'skills');
|
|
174
|
+
if (!fs_1.default.existsSync(dir))
|
|
175
|
+
return [];
|
|
176
|
+
const results = [];
|
|
177
|
+
try {
|
|
178
|
+
const entries = fs_1.default.readdirSync(dir, { withFileTypes: true });
|
|
179
|
+
for (const entry of entries) {
|
|
180
|
+
if (!entry.isDirectory())
|
|
181
|
+
continue;
|
|
182
|
+
const skillMdPath = path_1.default.join(dir, entry.name, 'SKILL.md');
|
|
183
|
+
if (!fs_1.default.existsSync(skillMdPath))
|
|
184
|
+
continue;
|
|
185
|
+
results.push(validateSkillDir(path_1.default.join(dir, entry.name)));
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
catch { }
|
|
189
|
+
return results;
|
|
190
|
+
}
|
|
191
|
+
function summariseResults(results) {
|
|
192
|
+
const errorCounts = {};
|
|
193
|
+
const warnCounts = {};
|
|
194
|
+
let totalScore = 0;
|
|
195
|
+
for (const r of results) {
|
|
196
|
+
totalScore += r.specScore;
|
|
197
|
+
for (const e of r.errors)
|
|
198
|
+
errorCounts[e.code] = (errorCounts[e.code] ?? 0) + 1;
|
|
199
|
+
for (const w of r.warnings)
|
|
200
|
+
warnCounts[w.code] = (warnCounts[w.code] ?? 0) + 1;
|
|
201
|
+
}
|
|
202
|
+
return {
|
|
203
|
+
total: results.length,
|
|
204
|
+
valid: results.filter(r => r.valid).length,
|
|
205
|
+
invalid: results.filter(r => !r.valid).length,
|
|
206
|
+
avgScore: results.length ? Math.round(totalScore / results.length) : 0,
|
|
207
|
+
errorCounts,
|
|
208
|
+
warnCounts,
|
|
209
|
+
};
|
|
210
|
+
}
|