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,223 @@
|
|
|
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.semanticMemory = exports.SemanticMemory = void 0;
|
|
11
|
+
// core/semanticMemory.ts — Local semantic memory with TF-IDF inspired
|
|
12
|
+
// word-level embeddings. No external API required — pure JS math.
|
|
13
|
+
const fs_1 = __importDefault(require("fs"));
|
|
14
|
+
const path_1 = __importDefault(require("path"));
|
|
15
|
+
const bm25_1 = require("./bm25");
|
|
16
|
+
const MEMORY_PATH = path_1.default.join(process.cwd(), 'workspace', 'semantic.json');
|
|
17
|
+
// ── Temporal decay helpers ────────────────────────────────────
|
|
18
|
+
const HALF_LIFE_DAYS = 30;
|
|
19
|
+
const DECAY_LAMBDA = Math.LN2 / HALF_LIFE_DAYS;
|
|
20
|
+
function applyTemporalDecay(results) {
|
|
21
|
+
const now = Date.now();
|
|
22
|
+
return results.map(r => {
|
|
23
|
+
// Facts and entities are evergreen — never decay
|
|
24
|
+
if (r.item.metadata.type === 'fact' || r.item.metadata.type === 'entity') {
|
|
25
|
+
return r;
|
|
26
|
+
}
|
|
27
|
+
const ageInDays = (now - r.item.metadata.timestamp) / 86400000;
|
|
28
|
+
const multiplier = Math.exp(-DECAY_LAMBDA * ageInDays);
|
|
29
|
+
return { ...r, score: r.score * multiplier };
|
|
30
|
+
}).sort((a, b) => b.score - a.score);
|
|
31
|
+
}
|
|
32
|
+
// ── MMR diversity re-ranking helpers ──────────────────────────
|
|
33
|
+
function tokenize(text) {
|
|
34
|
+
return new Set(text.toLowerCase().split(/\W+/).filter(t => t.length > 2));
|
|
35
|
+
}
|
|
36
|
+
function jaccardSimilarity(a, b) {
|
|
37
|
+
const intersection = new Set([...a].filter(x => b.has(x)));
|
|
38
|
+
const union = new Set([...a, ...b]);
|
|
39
|
+
return union.size === 0 ? 0 : intersection.size / union.size;
|
|
40
|
+
}
|
|
41
|
+
function applyMMR(results, lambda = 0.7, maxResults = 10) {
|
|
42
|
+
if (results.length <= 1)
|
|
43
|
+
return results;
|
|
44
|
+
const selected = [];
|
|
45
|
+
const remaining = [...results];
|
|
46
|
+
// Always pick highest-scoring first
|
|
47
|
+
selected.push(remaining.shift());
|
|
48
|
+
while (selected.length < maxResults && remaining.length > 0) {
|
|
49
|
+
let bestIdx = 0;
|
|
50
|
+
let bestScore = -Infinity;
|
|
51
|
+
for (let i = 0; i < remaining.length; i++) {
|
|
52
|
+
const relevance = remaining[i].score;
|
|
53
|
+
const maxSim = Math.max(...selected.map(s => jaccardSimilarity(tokenize(remaining[i].item.text), tokenize(s.item.text))));
|
|
54
|
+
const mmrScore = lambda * relevance - (1 - lambda) * maxSim;
|
|
55
|
+
if (mmrScore > bestScore) {
|
|
56
|
+
bestScore = mmrScore;
|
|
57
|
+
bestIdx = i;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
selected.push(remaining.splice(bestIdx, 1)[0]);
|
|
61
|
+
}
|
|
62
|
+
return selected;
|
|
63
|
+
}
|
|
64
|
+
class SemanticMemory {
|
|
65
|
+
constructor() {
|
|
66
|
+
this.data = [];
|
|
67
|
+
this.bm25 = new bm25_1.BM25();
|
|
68
|
+
this.bm25IndexBuilt = false;
|
|
69
|
+
this.load();
|
|
70
|
+
}
|
|
71
|
+
// ── Persistence ───────────────────────────────────────────────
|
|
72
|
+
load() {
|
|
73
|
+
try {
|
|
74
|
+
if (fs_1.default.existsSync(MEMORY_PATH)) {
|
|
75
|
+
this.data = JSON.parse(fs_1.default.readFileSync(MEMORY_PATH, 'utf-8'));
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
catch { }
|
|
79
|
+
}
|
|
80
|
+
save() {
|
|
81
|
+
try {
|
|
82
|
+
fs_1.default.mkdirSync(path_1.default.dirname(MEMORY_PATH), { recursive: true });
|
|
83
|
+
fs_1.default.writeFileSync(MEMORY_PATH, JSON.stringify(this.data, null, 2));
|
|
84
|
+
}
|
|
85
|
+
catch { }
|
|
86
|
+
}
|
|
87
|
+
// ── Embedding ─────────────────────────────────────────────────
|
|
88
|
+
// TF-IDF inspired bag-of-words embedding into 128-dim space.
|
|
89
|
+
// Uses polynomial rolling hash — similar topics yield similar vectors.
|
|
90
|
+
embed(text) {
|
|
91
|
+
const dim = 128;
|
|
92
|
+
const vec = new Array(dim).fill(0);
|
|
93
|
+
// Normalize and tokenize
|
|
94
|
+
const words = text
|
|
95
|
+
.toLowerCase()
|
|
96
|
+
.replace(/[^a-z0-9\s]/g, ' ')
|
|
97
|
+
.split(/\s+/)
|
|
98
|
+
.filter(w => w.length > 2);
|
|
99
|
+
// Remove stop words
|
|
100
|
+
const stopWords = new Set([
|
|
101
|
+
'the', 'and', 'for', 'are', 'but', 'not', 'you', 'all', 'can',
|
|
102
|
+
'her', 'was', 'one', 'our', 'out', 'day', 'get', 'has', 'him',
|
|
103
|
+
'his', 'how', 'its', 'may', 'new', 'now', 'old', 'see', 'two',
|
|
104
|
+
'way', 'who', 'did', 'let', 'put', 'say', 'she', 'too', 'use',
|
|
105
|
+
]);
|
|
106
|
+
const meaningful = words.filter(w => !stopWords.has(w));
|
|
107
|
+
// Hash each word into vector space using polynomial rolling hash
|
|
108
|
+
for (let wi = 0; wi < meaningful.length; wi++) {
|
|
109
|
+
const word = meaningful[wi];
|
|
110
|
+
let h1 = 0, h2 = 0;
|
|
111
|
+
for (let i = 0; i < word.length; i++) {
|
|
112
|
+
const c = word.charCodeAt(i);
|
|
113
|
+
h1 = (h1 * 31 + c) % dim;
|
|
114
|
+
h2 = (h2 * 37 + c) % dim;
|
|
115
|
+
}
|
|
116
|
+
// Boost by word length — longer words carry more meaning
|
|
117
|
+
const weight = Math.log(word.length + 1);
|
|
118
|
+
vec[h1] += weight;
|
|
119
|
+
vec[h2] += weight * 0.5;
|
|
120
|
+
// Bigram context — captures phrase meaning
|
|
121
|
+
if (wi > 0) {
|
|
122
|
+
const prev = meaningful[wi - 1];
|
|
123
|
+
let bh = 0;
|
|
124
|
+
for (let i = 0; i < prev.length && i < 4; i++) {
|
|
125
|
+
bh = (bh * 41 + prev.charCodeAt(i)) % dim;
|
|
126
|
+
}
|
|
127
|
+
vec[(h1 + bh) % dim] += 0.3;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
// L2 normalize
|
|
131
|
+
const mag = Math.sqrt(vec.reduce((s, v) => s + v * v, 0)) + 1e-8;
|
|
132
|
+
return vec.map(v => v / mag);
|
|
133
|
+
}
|
|
134
|
+
cosine(a, b) {
|
|
135
|
+
let dot = 0;
|
|
136
|
+
for (let i = 0; i < a.length; i++)
|
|
137
|
+
dot += a[i] * b[i];
|
|
138
|
+
return Math.max(0, Math.min(1, dot)); // already L2-normalized
|
|
139
|
+
}
|
|
140
|
+
// ── Public API ────────────────────────────────────────────────
|
|
141
|
+
add(text, type = 'exchange', tags) {
|
|
142
|
+
if (!text.trim() || text.length < 10)
|
|
143
|
+
return;
|
|
144
|
+
// Don't store duplicates
|
|
145
|
+
const existing = this.data.find(d => d.text === text);
|
|
146
|
+
if (existing)
|
|
147
|
+
return;
|
|
148
|
+
const item = {
|
|
149
|
+
id: `mem_${Date.now()}_${Math.random().toString(36).slice(2, 6)}`,
|
|
150
|
+
text: text.slice(0, 500),
|
|
151
|
+
embedding: this.embed(text),
|
|
152
|
+
metadata: { type, timestamp: Date.now(), tags },
|
|
153
|
+
};
|
|
154
|
+
this.data.push(item);
|
|
155
|
+
this.bm25IndexBuilt = false; // Sprint 14: invalidate BM25 index on new item
|
|
156
|
+
// Keep max 500 items — remove oldest
|
|
157
|
+
if (this.data.length > 500) {
|
|
158
|
+
this.data = this.data
|
|
159
|
+
.sort((a, b) => b.metadata.timestamp - a.metadata.timestamp)
|
|
160
|
+
.slice(0, 500);
|
|
161
|
+
}
|
|
162
|
+
this.save();
|
|
163
|
+
}
|
|
164
|
+
// ── BM25 index builder ────────────────────────────────────────
|
|
165
|
+
buildBM25Index() {
|
|
166
|
+
const texts = this.data.map(d => d.text);
|
|
167
|
+
if (texts.length > 0) {
|
|
168
|
+
this.bm25.index(texts);
|
|
169
|
+
this.bm25IndexBuilt = true;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
// ── Vector-only search ────────────────────────────────────────
|
|
173
|
+
vectorSearch(query, topK) {
|
|
174
|
+
const qVec = this.embed(query);
|
|
175
|
+
return this.data
|
|
176
|
+
.map((item, index) => ({ index, score: this.cosine(qVec, item.embedding) }))
|
|
177
|
+
.filter(r => r.score > 0)
|
|
178
|
+
.sort((a, b) => b.score - a.score)
|
|
179
|
+
.slice(0, topK);
|
|
180
|
+
}
|
|
181
|
+
// ── Hybrid search: BM25 + vector with reciprocal rank fusion ──
|
|
182
|
+
search(query, topK = 5, minScore = 0.3) {
|
|
183
|
+
if (this.data.length === 0)
|
|
184
|
+
return [];
|
|
185
|
+
if (!this.bm25IndexBuilt)
|
|
186
|
+
this.buildBM25Index();
|
|
187
|
+
const fetch = topK * 2;
|
|
188
|
+
// BM25 reciprocal rank scores (weight 0.4)
|
|
189
|
+
const bm25Results = this.bm25.search(query, fetch);
|
|
190
|
+
const bm25Scores = new Map();
|
|
191
|
+
bm25Results.forEach((r, rank) => bm25Scores.set(r.index, 1 / (rank + 1)));
|
|
192
|
+
// Vector reciprocal rank scores (weight 0.6)
|
|
193
|
+
const vectorResults = this.vectorSearch(query, fetch);
|
|
194
|
+
const vectorScores = new Map();
|
|
195
|
+
vectorResults.forEach((r, rank) => vectorScores.set(r.index, 1 / (rank + 1)));
|
|
196
|
+
// Reciprocal rank fusion
|
|
197
|
+
const allIndices = new Set([...bm25Scores.keys(), ...vectorScores.keys()]);
|
|
198
|
+
const fused = Array.from(allIndices)
|
|
199
|
+
.map(idx => ({
|
|
200
|
+
item: this.data[idx],
|
|
201
|
+
score: (bm25Scores.get(idx) || 0) * 0.4 + (vectorScores.get(idx) || 0) * 0.6,
|
|
202
|
+
}))
|
|
203
|
+
.filter(r => r.item && r.score >= minScore)
|
|
204
|
+
.sort((a, b) => b.score - a.score);
|
|
205
|
+
// Feature 4: temporal decay — recent memories rank higher
|
|
206
|
+
const decayed = applyTemporalDecay(fused);
|
|
207
|
+
// Feature 5: MMR diversity — prevent duplicate daily-note results
|
|
208
|
+
const diverse = applyMMR(decayed, 0.7, topK);
|
|
209
|
+
return diverse.map(r => r.item);
|
|
210
|
+
}
|
|
211
|
+
searchText(query, topK = 3) {
|
|
212
|
+
return this.search(query, topK).map(item => item.text);
|
|
213
|
+
}
|
|
214
|
+
getStats() {
|
|
215
|
+
const byType = {};
|
|
216
|
+
this.data.forEach(d => {
|
|
217
|
+
byType[d.metadata.type] = (byType[d.metadata.type] || 0) + 1;
|
|
218
|
+
});
|
|
219
|
+
return { total: this.data.length, byType };
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
exports.SemanticMemory = SemanticMemory;
|
|
223
|
+
exports.semanticMemory = new SemanticMemory();
|
|
@@ -0,0 +1,259 @@
|
|
|
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.sessionMemory = void 0;
|
|
11
|
+
exports.saveSessionMetadata = saveSessionMetadata;
|
|
12
|
+
exports.loadSessionMetadata = loadSessionMetadata;
|
|
13
|
+
exports.getSessionLineage = getSessionLineage;
|
|
14
|
+
exports.createChildSession = createChildSession;
|
|
15
|
+
// core/sessionMemory.ts — Per-session markdown log.
|
|
16
|
+
// After every 5 exchanges AND on conversation end, a background
|
|
17
|
+
// agent writes/updates workspace/sessions/{sessionId}.md.
|
|
18
|
+
// Injects last session context at conversation start.
|
|
19
|
+
const fs_1 = __importDefault(require("fs"));
|
|
20
|
+
const path_1 = __importDefault(require("path"));
|
|
21
|
+
const auxiliaryClient_1 = require("./auxiliaryClient");
|
|
22
|
+
const SESSIONS_DIR = path_1.default.join(process.env.AIDEN_USER_DATA || process.cwd(), 'workspace', 'sessions');
|
|
23
|
+
function metaPath(sessionId) {
|
|
24
|
+
return path_1.default.join(SESSIONS_DIR, `${sessionId}.meta.json`);
|
|
25
|
+
}
|
|
26
|
+
function saveSessionMetadata(meta) {
|
|
27
|
+
try {
|
|
28
|
+
fs_1.default.mkdirSync(SESSIONS_DIR, { recursive: true });
|
|
29
|
+
fs_1.default.writeFileSync(metaPath(meta.id), JSON.stringify(meta, null, 2), 'utf-8');
|
|
30
|
+
}
|
|
31
|
+
catch (e) {
|
|
32
|
+
console.error('[SessionLineage] Save failed:', e.message);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
function loadSessionMetadata(sessionId) {
|
|
36
|
+
try {
|
|
37
|
+
const p = metaPath(sessionId);
|
|
38
|
+
if (!fs_1.default.existsSync(p))
|
|
39
|
+
return null;
|
|
40
|
+
return JSON.parse(fs_1.default.readFileSync(p, 'utf-8'));
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
function getSessionLineage(sessionId) {
|
|
47
|
+
const lineage = [];
|
|
48
|
+
let current = loadSessionMetadata(sessionId);
|
|
49
|
+
if (!current)
|
|
50
|
+
return lineage;
|
|
51
|
+
const visited = new Set();
|
|
52
|
+
// Walk back to root via parentId chain
|
|
53
|
+
while (current && !visited.has(current.id)) {
|
|
54
|
+
visited.add(current.id);
|
|
55
|
+
lineage.unshift(current);
|
|
56
|
+
if (!current.parentId)
|
|
57
|
+
break;
|
|
58
|
+
current = loadSessionMetadata(current.parentId);
|
|
59
|
+
}
|
|
60
|
+
// Walk forward from tail to include any children not yet visited
|
|
61
|
+
let tail = lineage[lineage.length - 1];
|
|
62
|
+
while (tail?.childId && !visited.has(tail.childId)) {
|
|
63
|
+
visited.add(tail.childId);
|
|
64
|
+
const child = loadSessionMetadata(tail.childId);
|
|
65
|
+
if (!child)
|
|
66
|
+
break;
|
|
67
|
+
lineage.push(child);
|
|
68
|
+
tail = child;
|
|
69
|
+
}
|
|
70
|
+
return lineage;
|
|
71
|
+
}
|
|
72
|
+
function createChildSession(parentSessionId, reason, messageCount, tokenCount) {
|
|
73
|
+
const childId = `session_${Date.now()}`;
|
|
74
|
+
const now = Date.now();
|
|
75
|
+
// Load or bootstrap parent metadata
|
|
76
|
+
const parentMeta = loadSessionMetadata(parentSessionId) ?? {
|
|
77
|
+
id: parentSessionId,
|
|
78
|
+
depth: 0,
|
|
79
|
+
originalSessionId: parentSessionId,
|
|
80
|
+
createdAt: now,
|
|
81
|
+
};
|
|
82
|
+
// Stamp compression info on parent
|
|
83
|
+
parentMeta.childId = childId;
|
|
84
|
+
parentMeta.compressedAt = now;
|
|
85
|
+
parentMeta.messageCountBeforeCompression = messageCount;
|
|
86
|
+
parentMeta.tokenCountBeforeCompression = tokenCount;
|
|
87
|
+
saveSessionMetadata(parentMeta);
|
|
88
|
+
// Create child metadata
|
|
89
|
+
const childMeta = {
|
|
90
|
+
id: childId,
|
|
91
|
+
parentId: parentSessionId,
|
|
92
|
+
depth: parentMeta.depth + 1,
|
|
93
|
+
compressionReason: reason,
|
|
94
|
+
originalSessionId: parentMeta.originalSessionId,
|
|
95
|
+
createdAt: now,
|
|
96
|
+
};
|
|
97
|
+
saveSessionMetadata(childMeta);
|
|
98
|
+
console.log(`[SessionLineage] Created child ${childId} (depth ${childMeta.depth}) from ${parentSessionId}`);
|
|
99
|
+
return childId;
|
|
100
|
+
}
|
|
101
|
+
// ── Section headers (must match exactly) ──────────────────────
|
|
102
|
+
const SECTION_HEADERS = [
|
|
103
|
+
'# Session Title',
|
|
104
|
+
'# Current State',
|
|
105
|
+
'# What the User Asked',
|
|
106
|
+
'# Files Touched',
|
|
107
|
+
'# Commands Run',
|
|
108
|
+
'# Errors & Fixes',
|
|
109
|
+
'# Learnings',
|
|
110
|
+
'# Results',
|
|
111
|
+
'# Worklog',
|
|
112
|
+
];
|
|
113
|
+
// ── Helpers ───────────────────────────────────────────────────
|
|
114
|
+
function sessionPath(sessionId) {
|
|
115
|
+
return path_1.default.join(SESSIONS_DIR, `${sessionId}.md`);
|
|
116
|
+
}
|
|
117
|
+
function buildPrompt(state) {
|
|
118
|
+
const msgs = state.exchanges.map(e => `[${e.role.toUpperCase()}] ${e.content.slice(0, 600)}`).join('\n\n');
|
|
119
|
+
const files = state.exchanges.flatMap(e => e.files ?? []).filter(Boolean);
|
|
120
|
+
const cmds = state.exchanges.flatMap(e => e.cmds ?? []).filter(Boolean);
|
|
121
|
+
const errors = state.exchanges.flatMap(e => e.errors ?? []).filter(Boolean);
|
|
122
|
+
return `You are a session memory writer for DevOS. Based on the conversation below, write a session summary in this exact format with these exact headers (never change the headers):
|
|
123
|
+
|
|
124
|
+
# Session Title
|
|
125
|
+
_A short 5-10 word title_
|
|
126
|
+
|
|
127
|
+
# Current State
|
|
128
|
+
_What's being worked on right now? Next steps?_
|
|
129
|
+
|
|
130
|
+
# What the User Asked
|
|
131
|
+
_The original goal. Design decisions made._
|
|
132
|
+
|
|
133
|
+
# Files Touched
|
|
134
|
+
_Important files and what was done to them._
|
|
135
|
+
|
|
136
|
+
# Commands Run
|
|
137
|
+
_Key commands and their purpose._
|
|
138
|
+
|
|
139
|
+
# Errors & Fixes
|
|
140
|
+
_What broke and how it was resolved._
|
|
141
|
+
|
|
142
|
+
# Learnings
|
|
143
|
+
_What worked, what didn't, what to avoid next time._
|
|
144
|
+
|
|
145
|
+
# Results
|
|
146
|
+
_Key outputs or answers produced._
|
|
147
|
+
|
|
148
|
+
# Worklog
|
|
149
|
+
_Terse step-by-step of what happened._
|
|
150
|
+
|
|
151
|
+
CONVERSATION:
|
|
152
|
+
${msgs}
|
|
153
|
+
|
|
154
|
+
FILES TOUCHED: ${files.join(', ') || 'none'}
|
|
155
|
+
COMMANDS RUN: ${cmds.join(', ') || 'none'}
|
|
156
|
+
ERRORS: ${errors.join(', ') || 'none'}
|
|
157
|
+
|
|
158
|
+
Write the session summary now. Use the exact headers shown above.`;
|
|
159
|
+
}
|
|
160
|
+
// ── SessionMemory ─────────────────────────────────────────────
|
|
161
|
+
class SessionMemory {
|
|
162
|
+
constructor() {
|
|
163
|
+
this.sessions = new Map();
|
|
164
|
+
try {
|
|
165
|
+
fs_1.default.mkdirSync(SESSIONS_DIR, { recursive: true });
|
|
166
|
+
}
|
|
167
|
+
catch { }
|
|
168
|
+
}
|
|
169
|
+
// ── Start / get session ───────────────────────────────────
|
|
170
|
+
getOrCreate(sessionId) {
|
|
171
|
+
if (!this.sessions.has(sessionId)) {
|
|
172
|
+
this.sessions.set(sessionId, {
|
|
173
|
+
sessionId,
|
|
174
|
+
startTs: Date.now(),
|
|
175
|
+
exchanges: [],
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
return this.sessions.get(sessionId);
|
|
179
|
+
}
|
|
180
|
+
// ── Add exchange ──────────────────────────────────────────
|
|
181
|
+
addExchange(sessionId, userMsg, aiMsg, files, cmds, errors) {
|
|
182
|
+
const state = this.getOrCreate(sessionId);
|
|
183
|
+
state.exchanges.push({ role: 'user', content: userMsg, ts: Date.now() });
|
|
184
|
+
state.exchanges.push({ role: 'assistant', content: aiMsg, ts: Date.now(), files, cmds, errors });
|
|
185
|
+
// Every 5 exchange pairs (10 messages), write in background
|
|
186
|
+
const pairs = Math.floor(state.exchanges.length / 2);
|
|
187
|
+
if (pairs > 0 && pairs % 5 === 0) {
|
|
188
|
+
setTimeout(() => { this.writeSession(sessionId).catch(() => { }); }, 100);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
// ── End session ───────────────────────────────────────────
|
|
192
|
+
endSession(sessionId) {
|
|
193
|
+
setTimeout(() => { this.writeSession(sessionId).catch(() => { }); }, 100);
|
|
194
|
+
}
|
|
195
|
+
// ── Write session file ────────────────────────────────────
|
|
196
|
+
async writeSession(sessionId) {
|
|
197
|
+
const state = this.sessions.get(sessionId);
|
|
198
|
+
if (!state || state.exchanges.length === 0)
|
|
199
|
+
return;
|
|
200
|
+
try {
|
|
201
|
+
const prompt = buildPrompt(state);
|
|
202
|
+
const content = await auxiliaryClient_1.auxiliaryClient.complete(prompt, { task: 'reflection', maxTokens: 500 });
|
|
203
|
+
if (!content || !content.includes('# Session Title'))
|
|
204
|
+
return;
|
|
205
|
+
const filePath = sessionPath(sessionId);
|
|
206
|
+
fs_1.default.writeFileSync(filePath, content, 'utf-8');
|
|
207
|
+
state.lastWrittenAt = Date.now();
|
|
208
|
+
console.log(`[SessionMemory] Written: ${filePath}`);
|
|
209
|
+
}
|
|
210
|
+
catch (e) {
|
|
211
|
+
console.error('[SessionMemory] Write failed:', e.message);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
// ── Get last session context (inject at conversation start) ─
|
|
215
|
+
getLastContext(sessionId) {
|
|
216
|
+
// Try exact session file first
|
|
217
|
+
const exact = sessionPath(sessionId);
|
|
218
|
+
if (fs_1.default.existsSync(exact)) {
|
|
219
|
+
try {
|
|
220
|
+
return fs_1.default.readFileSync(exact, 'utf-8').slice(0, 2000);
|
|
221
|
+
}
|
|
222
|
+
catch { }
|
|
223
|
+
}
|
|
224
|
+
// Fall back to most recent session file
|
|
225
|
+
try {
|
|
226
|
+
if (!fs_1.default.existsSync(SESSIONS_DIR))
|
|
227
|
+
return '';
|
|
228
|
+
const files = fs_1.default.readdirSync(SESSIONS_DIR)
|
|
229
|
+
.filter(f => f.endsWith('.md'))
|
|
230
|
+
.map(f => ({
|
|
231
|
+
name: f,
|
|
232
|
+
mtime: fs_1.default.statSync(path_1.default.join(SESSIONS_DIR, f)).mtimeMs,
|
|
233
|
+
}))
|
|
234
|
+
.sort((a, b) => b.mtime - a.mtime);
|
|
235
|
+
if (files.length === 0)
|
|
236
|
+
return '';
|
|
237
|
+
const latest = path_1.default.join(SESSIONS_DIR, files[0].name);
|
|
238
|
+
return fs_1.default.readFileSync(latest, 'utf-8').slice(0, 2000);
|
|
239
|
+
}
|
|
240
|
+
catch {
|
|
241
|
+
return '';
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
// ── List sessions ──────────────────────────────────────────
|
|
245
|
+
listSessions() {
|
|
246
|
+
try {
|
|
247
|
+
if (!fs_1.default.existsSync(SESSIONS_DIR))
|
|
248
|
+
return [];
|
|
249
|
+
return fs_1.default.readdirSync(SESSIONS_DIR)
|
|
250
|
+
.filter(f => f.endsWith('.md'))
|
|
251
|
+
.sort();
|
|
252
|
+
}
|
|
253
|
+
catch {
|
|
254
|
+
return [];
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
// ── Singleton ──────────────────────────────────────────────────
|
|
259
|
+
exports.sessionMemory = new SessionMemory();
|
|
@@ -0,0 +1,91 @@
|
|
|
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.sessionRouter = void 0;
|
|
11
|
+
// core/sessionRouter.ts — Cross-channel session tracking.
|
|
12
|
+
// Maintains a stable sessionId per user so conversation history
|
|
13
|
+
// is shared regardless of whether the user is on Telegram,
|
|
14
|
+
// the dashboard, or any future channel.
|
|
15
|
+
const fs_1 = __importDefault(require("fs"));
|
|
16
|
+
const path_1 = __importDefault(require("path"));
|
|
17
|
+
// ── SessionRouter class ────────────────────────────────────────
|
|
18
|
+
class SessionRouter {
|
|
19
|
+
constructor() {
|
|
20
|
+
this.sessions = new Map();
|
|
21
|
+
this.userToSession = new Map();
|
|
22
|
+
}
|
|
23
|
+
// ── Get or create session for a user ─────────────────────────
|
|
24
|
+
getSession(userId, channel) {
|
|
25
|
+
const existingId = this.userToSession.get(userId);
|
|
26
|
+
if (existingId && this.sessions.has(existingId)) {
|
|
27
|
+
const session = this.sessions.get(existingId);
|
|
28
|
+
session.lastChannel = channel;
|
|
29
|
+
session.lastActivity = Date.now();
|
|
30
|
+
if (!session.channels.includes(channel)) {
|
|
31
|
+
session.channels.push(channel);
|
|
32
|
+
console.log(`[Session] User ${userId} continued on ${channel} ` +
|
|
33
|
+
`(started on ${session.channels[0]})`);
|
|
34
|
+
}
|
|
35
|
+
return session;
|
|
36
|
+
}
|
|
37
|
+
// Create new session
|
|
38
|
+
const session = {
|
|
39
|
+
sessionId: `session_${Date.now()}`,
|
|
40
|
+
userId,
|
|
41
|
+
channels: [channel],
|
|
42
|
+
lastChannel: channel,
|
|
43
|
+
lastActivity: Date.now(),
|
|
44
|
+
messageCount: 0,
|
|
45
|
+
};
|
|
46
|
+
this.sessions.set(session.sessionId, session);
|
|
47
|
+
this.userToSession.set(userId, session.sessionId);
|
|
48
|
+
return session;
|
|
49
|
+
}
|
|
50
|
+
// ── Load persisted conversation history for a session ────────
|
|
51
|
+
// Reads from workspace/sessions/<sessionId>.json if it exists.
|
|
52
|
+
// The actual continuity comes from conversationMemory.setSession()
|
|
53
|
+
// receiving the stable sessionId — this is a foundation for
|
|
54
|
+
// future per-session file-based export / mobile sync.
|
|
55
|
+
getHistory(userId) {
|
|
56
|
+
const sessionId = this.userToSession.get(userId);
|
|
57
|
+
if (!sessionId)
|
|
58
|
+
return [];
|
|
59
|
+
const sessionFile = path_1.default.join(process.cwd(), 'workspace', 'sessions', `${sessionId}.json`);
|
|
60
|
+
if (fs_1.default.existsSync(sessionFile)) {
|
|
61
|
+
try {
|
|
62
|
+
const data = JSON.parse(fs_1.default.readFileSync(sessionFile, 'utf8'));
|
|
63
|
+
return data.messages || [];
|
|
64
|
+
}
|
|
65
|
+
catch { }
|
|
66
|
+
}
|
|
67
|
+
return [];
|
|
68
|
+
}
|
|
69
|
+
// ── Log a cross-channel continuation event ───────────────────
|
|
70
|
+
markContinuation(userId, fromChannel, toChannel) {
|
|
71
|
+
const session = this.getSession(userId, toChannel);
|
|
72
|
+
console.log(`[Session] Continuation: ${fromChannel} → ${toChannel} ` +
|
|
73
|
+
`(${session.messageCount} messages in history)`);
|
|
74
|
+
}
|
|
75
|
+
// ── Return channels for a given session ID ───────────────────
|
|
76
|
+
// Used by /api/sessions to enrich the session list response.
|
|
77
|
+
getSessionChannels(sessionId) {
|
|
78
|
+
return this.sessions.get(sessionId)?.channels ?? [];
|
|
79
|
+
}
|
|
80
|
+
// ── Expire sessions older than 24 h ──────────────────────────
|
|
81
|
+
cleanup() {
|
|
82
|
+
const cutoff = Date.now() - 24 * 60 * 60 * 1000;
|
|
83
|
+
for (const [id, session] of this.sessions) {
|
|
84
|
+
if (session.lastActivity < cutoff) {
|
|
85
|
+
this.sessions.delete(id);
|
|
86
|
+
this.userToSession.delete(session.userId);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
exports.sessionRouter = new SessionRouter();
|