nova-terminal-assistant 0.1.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.
Potentially problematic release.
This version of nova-terminal-assistant might be problematic. Click here for more details.
- package/README.md +358 -0
- package/bin/nova +38 -0
- package/bin/nova.js +12 -0
- package/package.json +67 -0
- package/src/cli/commands/SmartCompletion.ts +458 -0
- package/src/cli/index.ts +5 -0
- package/src/cli/startup/IFlowRepl.ts +212 -0
- package/src/cli/startup/InkBasedRepl.ts +1056 -0
- package/src/cli/startup/InteractiveRepl.ts +2833 -0
- package/src/cli/startup/NovaApp.ts +1861 -0
- package/src/cli/startup/index.ts +4 -0
- package/src/cli/startup/parseArgs.ts +293 -0
- package/src/cli/test-modules.ts +27 -0
- package/src/cli/ui/IFlowDropdown.ts +425 -0
- package/src/cli/ui/ModernReplUI.ts +276 -0
- package/src/cli/ui/SimpleSelector2.ts +215 -0
- package/src/cli/ui/components/ConfirmDialog.ts +176 -0
- package/src/cli/ui/components/ErrorPanel.ts +364 -0
- package/src/cli/ui/components/InkAppRunner.tsx +67 -0
- package/src/cli/ui/components/InkComponents.tsx +613 -0
- package/src/cli/ui/components/NovaInkApp.tsx +312 -0
- package/src/cli/ui/components/ProgressBar.ts +177 -0
- package/src/cli/ui/components/ProgressIndicator.ts +298 -0
- package/src/cli/ui/components/QuickActions.ts +396 -0
- package/src/cli/ui/components/SimpleErrorPanel.ts +231 -0
- package/src/cli/ui/components/StatusBar.ts +194 -0
- package/src/cli/ui/components/ThinkingBlockRenderer.ts +401 -0
- package/src/cli/ui/components/index.ts +27 -0
- package/src/cli/ui/ink-prototype.tsx +347 -0
- package/src/cli/utils/CliUI.ts +336 -0
- package/src/cli/utils/CompletionHelper.ts +388 -0
- package/src/cli/utils/EnhancedCompleter.test.ts +226 -0
- package/src/cli/utils/EnhancedCompleter.ts +513 -0
- package/src/cli/utils/ErrorEnhancer.ts +429 -0
- package/src/cli/utils/OutputFormatter.ts +193 -0
- package/src/cli/utils/index.ts +9 -0
- package/src/core/agents/AgentOrchestrator.ts +515 -0
- package/src/core/agents/index.ts +17 -0
- package/src/core/audit/AuditLogger.ts +509 -0
- package/src/core/audit/index.ts +11 -0
- package/src/core/auth/AuthManager.d.ts.map +1 -0
- package/src/core/auth/AuthManager.ts +138 -0
- package/src/core/auth/index.d.ts.map +1 -0
- package/src/core/auth/index.ts +2 -0
- package/src/core/config/ConfigManager.d.ts.map +1 -0
- package/src/core/config/ConfigManager.test.ts +183 -0
- package/src/core/config/ConfigManager.ts +1219 -0
- package/src/core/config/index.d.ts.map +1 -0
- package/src/core/config/index.ts +1 -0
- package/src/core/context/ContextBuilder.d.ts.map +1 -0
- package/src/core/context/ContextBuilder.ts +171 -0
- package/src/core/context/ContextCompressor.d.ts.map +1 -0
- package/src/core/context/ContextCompressor.ts +642 -0
- package/src/core/context/LayeredMemoryManager.ts +657 -0
- package/src/core/context/MemoryDiscovery.d.ts.map +1 -0
- package/src/core/context/MemoryDiscovery.ts +175 -0
- package/src/core/context/defaultSystemPrompt.d.ts.map +1 -0
- package/src/core/context/defaultSystemPrompt.ts +35 -0
- package/src/core/context/index.d.ts.map +1 -0
- package/src/core/context/index.ts +22 -0
- package/src/core/extensions/SkillGenerator.ts +421 -0
- package/src/core/extensions/SkillInstaller.d.ts.map +1 -0
- package/src/core/extensions/SkillInstaller.ts +257 -0
- package/src/core/extensions/SkillRegistry.d.ts.map +1 -0
- package/src/core/extensions/SkillRegistry.ts +361 -0
- package/src/core/extensions/SkillValidator.ts +525 -0
- package/src/core/extensions/index.ts +15 -0
- package/src/core/index.d.ts.map +1 -0
- package/src/core/index.ts +42 -0
- package/src/core/mcp/McpManager.d.ts.map +1 -0
- package/src/core/mcp/McpManager.ts +632 -0
- package/src/core/mcp/index.d.ts.map +1 -0
- package/src/core/mcp/index.ts +2 -0
- package/src/core/model/ModelClient.d.ts.map +1 -0
- package/src/core/model/ModelClient.ts +217 -0
- package/src/core/model/ModelConnectionTester.ts +363 -0
- package/src/core/model/ModelValidator.ts +348 -0
- package/src/core/model/index.d.ts.map +1 -0
- package/src/core/model/index.ts +6 -0
- package/src/core/model/providers/AnthropicProvider.d.ts.map +1 -0
- package/src/core/model/providers/AnthropicProvider.ts +279 -0
- package/src/core/model/providers/CodingPlanProvider.d.ts.map +1 -0
- package/src/core/model/providers/CodingPlanProvider.ts +210 -0
- package/src/core/model/providers/OllamaCloudProvider.d.ts.map +1 -0
- package/src/core/model/providers/OllamaCloudProvider.ts +405 -0
- package/src/core/model/providers/OllamaManager.d.ts.map +1 -0
- package/src/core/model/providers/OllamaManager.ts +201 -0
- package/src/core/model/providers/OllamaProvider.d.ts.map +1 -0
- package/src/core/model/providers/OllamaProvider.ts +73 -0
- package/src/core/model/providers/OpenAICompatibleProvider.d.ts.map +1 -0
- package/src/core/model/providers/OpenAICompatibleProvider.ts +327 -0
- package/src/core/model/providers/OpenAIProvider.d.ts.map +1 -0
- package/src/core/model/providers/OpenAIProvider.ts +29 -0
- package/src/core/model/providers/index.d.ts.map +1 -0
- package/src/core/model/providers/index.ts +12 -0
- package/src/core/model/types.d.ts.map +1 -0
- package/src/core/model/types.ts +77 -0
- package/src/core/security/ApprovalManager.d.ts.map +1 -0
- package/src/core/security/ApprovalManager.ts +174 -0
- package/src/core/security/FileFilter.d.ts.map +1 -0
- package/src/core/security/FileFilter.ts +141 -0
- package/src/core/security/HookExecutor.d.ts.map +1 -0
- package/src/core/security/HookExecutor.ts +178 -0
- package/src/core/security/SandboxExecutor.ts +447 -0
- package/src/core/security/index.d.ts.map +1 -0
- package/src/core/security/index.ts +8 -0
- package/src/core/session/AgentLoop.d.ts.map +1 -0
- package/src/core/session/AgentLoop.ts +501 -0
- package/src/core/session/SessionManager.d.ts.map +1 -0
- package/src/core/session/SessionManager.test.ts +183 -0
- package/src/core/session/SessionManager.ts +460 -0
- package/src/core/session/index.d.ts.map +1 -0
- package/src/core/session/index.ts +3 -0
- package/src/core/telemetry/Telemetry.d.ts.map +1 -0
- package/src/core/telemetry/Telemetry.ts +90 -0
- package/src/core/telemetry/TelemetryService.ts +531 -0
- package/src/core/telemetry/index.d.ts.map +1 -0
- package/src/core/telemetry/index.ts +12 -0
- package/src/core/testing/AutoFixer.ts +385 -0
- package/src/core/testing/ErrorAnalyzer.ts +499 -0
- package/src/core/testing/TestRunner.ts +265 -0
- package/src/core/testing/agent-cli-tests.ts +538 -0
- package/src/core/testing/index.ts +11 -0
- package/src/core/tools/ToolRegistry.d.ts.map +1 -0
- package/src/core/tools/ToolRegistry.test.ts +206 -0
- package/src/core/tools/ToolRegistry.ts +260 -0
- package/src/core/tools/impl/EditFileTool.d.ts.map +1 -0
- package/src/core/tools/impl/EditFileTool.ts +97 -0
- package/src/core/tools/impl/ListDirectoryTool.d.ts.map +1 -0
- package/src/core/tools/impl/ListDirectoryTool.ts +142 -0
- package/src/core/tools/impl/MemoryTool.d.ts.map +1 -0
- package/src/core/tools/impl/MemoryTool.ts +102 -0
- package/src/core/tools/impl/ReadFileTool.d.ts.map +1 -0
- package/src/core/tools/impl/ReadFileTool.ts +58 -0
- package/src/core/tools/impl/SearchContentTool.d.ts.map +1 -0
- package/src/core/tools/impl/SearchContentTool.ts +94 -0
- package/src/core/tools/impl/SearchFileTool.d.ts.map +1 -0
- package/src/core/tools/impl/SearchFileTool.ts +61 -0
- package/src/core/tools/impl/ShellTool.d.ts.map +1 -0
- package/src/core/tools/impl/ShellTool.ts +118 -0
- package/src/core/tools/impl/TaskTool.d.ts.map +1 -0
- package/src/core/tools/impl/TaskTool.ts +207 -0
- package/src/core/tools/impl/TodoTool.d.ts.map +1 -0
- package/src/core/tools/impl/TodoTool.ts +122 -0
- package/src/core/tools/impl/WebFetchTool.d.ts.map +1 -0
- package/src/core/tools/impl/WebFetchTool.ts +103 -0
- package/src/core/tools/impl/WebSearchTool.d.ts.map +1 -0
- package/src/core/tools/impl/WebSearchTool.ts +89 -0
- package/src/core/tools/impl/WriteFileTool.d.ts.map +1 -0
- package/src/core/tools/impl/WriteFileTool.ts +49 -0
- package/src/core/tools/impl/index.d.ts.map +1 -0
- package/src/core/tools/impl/index.ts +16 -0
- package/src/core/tools/index.d.ts.map +1 -0
- package/src/core/tools/index.ts +7 -0
- package/src/core/tools/schemas/execution.d.ts.map +1 -0
- package/src/core/tools/schemas/execution.ts +42 -0
- package/src/core/tools/schemas/file.d.ts.map +1 -0
- package/src/core/tools/schemas/file.ts +119 -0
- package/src/core/tools/schemas/index.d.ts.map +1 -0
- package/src/core/tools/schemas/index.ts +11 -0
- package/src/core/tools/schemas/memory.d.ts.map +1 -0
- package/src/core/tools/schemas/memory.ts +52 -0
- package/src/core/tools/schemas/orchestration.d.ts.map +1 -0
- package/src/core/tools/schemas/orchestration.ts +44 -0
- package/src/core/tools/schemas/search.d.ts.map +1 -0
- package/src/core/tools/schemas/search.ts +112 -0
- package/src/core/tools/schemas/todo.d.ts.map +1 -0
- package/src/core/tools/schemas/todo.ts +32 -0
- package/src/core/tools/schemas/web.d.ts.map +1 -0
- package/src/core/tools/schemas/web.ts +86 -0
- package/src/core/types/config.d.ts.map +1 -0
- package/src/core/types/config.ts +200 -0
- package/src/core/types/errors.d.ts.map +1 -0
- package/src/core/types/errors.ts +204 -0
- package/src/core/types/index.d.ts.map +1 -0
- package/src/core/types/index.ts +8 -0
- package/src/core/types/session.d.ts.map +1 -0
- package/src/core/types/session.ts +216 -0
- package/src/core/types/tools.d.ts.map +1 -0
- package/src/core/types/tools.ts +157 -0
- package/src/core/utils/CheckpointManager.d.ts.map +1 -0
- package/src/core/utils/CheckpointManager.ts +327 -0
- package/src/core/utils/Logger.d.ts.map +1 -0
- package/src/core/utils/Logger.ts +98 -0
- package/src/core/utils/RetryManager.ts +471 -0
- package/src/core/utils/TokenCounter.d.ts.map +1 -0
- package/src/core/utils/TokenCounter.ts +414 -0
- package/src/core/utils/VectorMemoryStore.ts +440 -0
- package/src/core/utils/helpers.d.ts.map +1 -0
- package/src/core/utils/helpers.ts +89 -0
- package/src/core/utils/index.d.ts.map +1 -0
- package/src/core/utils/index.ts +19 -0
|
@@ -0,0 +1,440 @@
|
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// VectorMemoryStore - Vector-based semantic memory storage
|
|
3
|
+
// ============================================================================
|
|
4
|
+
|
|
5
|
+
import * as fs from 'node:fs';
|
|
6
|
+
import * as path from 'node:path';
|
|
7
|
+
import { createLogger } from './Logger.js';
|
|
8
|
+
import { generateId } from './helpers.js';
|
|
9
|
+
|
|
10
|
+
const logger = createLogger('VectorMemoryStore');
|
|
11
|
+
|
|
12
|
+
// ============================================================================
|
|
13
|
+
// Types
|
|
14
|
+
// ============================================================================
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Memory entry with vector embedding
|
|
18
|
+
*/
|
|
19
|
+
export interface VectorMemoryEntry {
|
|
20
|
+
id: string;
|
|
21
|
+
content: string;
|
|
22
|
+
embedding: number[];
|
|
23
|
+
metadata: {
|
|
24
|
+
session: string;
|
|
25
|
+
timestamp: Date;
|
|
26
|
+
type: 'decision' | 'code' | 'error' | 'insight' | 'context' | 'preference';
|
|
27
|
+
importance: number; // 0-1 score
|
|
28
|
+
tags: string[];
|
|
29
|
+
source?: string; // File or URL source
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Search result with similarity score
|
|
35
|
+
*/
|
|
36
|
+
export interface MemoryMatch {
|
|
37
|
+
entry: VectorMemoryEntry;
|
|
38
|
+
score: number; // Cosine similarity 0-1
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Memory store configuration
|
|
43
|
+
*/
|
|
44
|
+
export interface VectorMemoryConfig {
|
|
45
|
+
/** Storage directory for persistence */
|
|
46
|
+
storageDir: string;
|
|
47
|
+
/** Maximum entries to keep */
|
|
48
|
+
maxEntries?: number;
|
|
49
|
+
/** Minimum similarity score for search results */
|
|
50
|
+
minSimilarity?: number;
|
|
51
|
+
/** Embedding dimension (default 384 for small models) */
|
|
52
|
+
embeddingDimension?: number;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// ============================================================================
|
|
56
|
+
// Embedding Functions
|
|
57
|
+
// ============================================================================
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Simple embedding function using character n-grams
|
|
61
|
+
* This is a lightweight alternative to neural embeddings
|
|
62
|
+
*/
|
|
63
|
+
function simpleEmbedding(text: string, dimension: number = 384): number[] {
|
|
64
|
+
const embedding = new Array(dimension).fill(0);
|
|
65
|
+
|
|
66
|
+
// Normalize text
|
|
67
|
+
const normalized = text.toLowerCase().replace(/[^a-z0-9\u4e00-\u9fff\s]/g, ' ');
|
|
68
|
+
const words = normalized.split(/\s+/).filter(w => w.length > 0);
|
|
69
|
+
|
|
70
|
+
// Hash-based embedding
|
|
71
|
+
for (const word of words) {
|
|
72
|
+
// Simple hash function
|
|
73
|
+
let hash = 0;
|
|
74
|
+
for (let i = 0; i < word.length; i++) {
|
|
75
|
+
const char = word.charCodeAt(i);
|
|
76
|
+
hash = ((hash << 5) - hash) + char;
|
|
77
|
+
hash = hash & hash; // Convert to 32-bit integer
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Map hash to embedding dimensions
|
|
81
|
+
const pos = Math.abs(hash) % dimension;
|
|
82
|
+
const sign = hash > 0 ? 1 : -1;
|
|
83
|
+
embedding[pos] += sign * (1 / words.length);
|
|
84
|
+
|
|
85
|
+
// Add n-gram features
|
|
86
|
+
for (let i = 0; i < word.length - 1; i++) {
|
|
87
|
+
const bigram = word.slice(i, i + 2);
|
|
88
|
+
let bigramHash = 0;
|
|
89
|
+
for (let j = 0; j < bigram.length; j++) {
|
|
90
|
+
bigramHash = ((bigramHash << 5) - bigramHash) + bigram.charCodeAt(j);
|
|
91
|
+
bigramHash = bigramHash & bigramHash;
|
|
92
|
+
}
|
|
93
|
+
const bigramPos = Math.abs(bigramHash) % dimension;
|
|
94
|
+
embedding[bigramPos] += 0.1 / words.length;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Normalize embedding
|
|
99
|
+
const norm = Math.sqrt(embedding.reduce((sum, v) => sum + v * v, 0));
|
|
100
|
+
if (norm > 0) {
|
|
101
|
+
for (let i = 0; i < embedding.length; i++) {
|
|
102
|
+
embedding[i] /= norm;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return embedding;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Calculate cosine similarity between two vectors
|
|
111
|
+
*/
|
|
112
|
+
function cosineSimilarity(a: number[], b: number[]): number {
|
|
113
|
+
if (a.length !== b.length) return 0;
|
|
114
|
+
|
|
115
|
+
let dotProduct = 0;
|
|
116
|
+
let normA = 0;
|
|
117
|
+
let normB = 0;
|
|
118
|
+
|
|
119
|
+
for (let i = 0; i < a.length; i++) {
|
|
120
|
+
const aVal = a[i] ?? 0;
|
|
121
|
+
const bVal = b[i] ?? 0;
|
|
122
|
+
dotProduct += aVal * bVal;
|
|
123
|
+
normA += aVal * aVal;
|
|
124
|
+
normB += bVal * bVal;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const denom = Math.sqrt(normA) * Math.sqrt(normB);
|
|
128
|
+
return denom > 0 ? dotProduct / denom : 0;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// ============================================================================
|
|
132
|
+
// VectorMemoryStore
|
|
133
|
+
// ============================================================================
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Vector-based memory store for semantic search
|
|
137
|
+
*/
|
|
138
|
+
export class VectorMemoryStore {
|
|
139
|
+
private entries: VectorMemoryEntry[] = [];
|
|
140
|
+
private config: Required<VectorMemoryConfig>;
|
|
141
|
+
private storageFile: string;
|
|
142
|
+
private isDirty = false;
|
|
143
|
+
|
|
144
|
+
constructor(config: VectorMemoryConfig) {
|
|
145
|
+
this.config = {
|
|
146
|
+
maxEntries: config.maxEntries ?? 1000,
|
|
147
|
+
minSimilarity: config.minSimilarity ?? 0.5,
|
|
148
|
+
embeddingDimension: config.embeddingDimension ?? 384,
|
|
149
|
+
...config,
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
this.storageFile = path.join(this.config.storageDir, 'vector-memory.json');
|
|
153
|
+
this.load();
|
|
154
|
+
|
|
155
|
+
// Auto-save on process exit
|
|
156
|
+
process.on('beforeExit', () => this.save());
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// -----------------------------------------------------------------------
|
|
160
|
+
// Core Operations
|
|
161
|
+
// -----------------------------------------------------------------------
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Add a memory entry
|
|
165
|
+
*/
|
|
166
|
+
async addMemory(
|
|
167
|
+
content: string,
|
|
168
|
+
metadata: VectorMemoryEntry['metadata']
|
|
169
|
+
): Promise<VectorMemoryEntry> {
|
|
170
|
+
// Generate embedding
|
|
171
|
+
const embedding = simpleEmbedding(content, this.config.embeddingDimension);
|
|
172
|
+
|
|
173
|
+
const entry: VectorMemoryEntry = {
|
|
174
|
+
id: generateId(),
|
|
175
|
+
content,
|
|
176
|
+
embedding,
|
|
177
|
+
metadata: {
|
|
178
|
+
...metadata,
|
|
179
|
+
timestamp: metadata.timestamp ?? new Date(),
|
|
180
|
+
},
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
this.entries.push(entry);
|
|
184
|
+
this.isDirty = true;
|
|
185
|
+
|
|
186
|
+
// Prune if over limit
|
|
187
|
+
if (this.entries.length > this.config.maxEntries) {
|
|
188
|
+
this.prune();
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
logger.debug(`Added memory entry: ${entry.id}`, { type: metadata.type });
|
|
192
|
+
return entry;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Search for similar memories
|
|
197
|
+
*/
|
|
198
|
+
async searchSimilar(
|
|
199
|
+
query: string,
|
|
200
|
+
options: {
|
|
201
|
+
topK?: number;
|
|
202
|
+
minSimilarity?: number;
|
|
203
|
+
type?: VectorMemoryEntry['metadata']['type'];
|
|
204
|
+
tags?: string[];
|
|
205
|
+
} = {}
|
|
206
|
+
): Promise<MemoryMatch[]> {
|
|
207
|
+
const { topK = 5, minSimilarity, type, tags } = options;
|
|
208
|
+
const threshold = minSimilarity ?? this.config.minSimilarity;
|
|
209
|
+
|
|
210
|
+
// Generate query embedding
|
|
211
|
+
const queryEmbedding = simpleEmbedding(query, this.config.embeddingDimension);
|
|
212
|
+
|
|
213
|
+
// Calculate similarities
|
|
214
|
+
const matches: MemoryMatch[] = [];
|
|
215
|
+
|
|
216
|
+
for (const entry of this.entries) {
|
|
217
|
+
// Filter by type
|
|
218
|
+
if (type && entry.metadata.type !== type) continue;
|
|
219
|
+
|
|
220
|
+
// Filter by tags
|
|
221
|
+
if (tags && tags.length > 0) {
|
|
222
|
+
const hasTag = tags.some(t => entry.metadata.tags.includes(t));
|
|
223
|
+
if (!hasTag) continue;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const score = cosineSimilarity(queryEmbedding, entry.embedding);
|
|
227
|
+
|
|
228
|
+
if (score >= threshold) {
|
|
229
|
+
matches.push({ entry, score });
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Sort by score and return top K
|
|
234
|
+
matches.sort((a, b) => b.score - a.score);
|
|
235
|
+
return matches.slice(0, topK);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Get entry by ID
|
|
240
|
+
*/
|
|
241
|
+
getEntry(id: string): VectorMemoryEntry | undefined {
|
|
242
|
+
return this.entries.find(e => e.id === id);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Delete entry by ID
|
|
247
|
+
*/
|
|
248
|
+
deleteEntry(id: string): boolean {
|
|
249
|
+
const index = this.entries.findIndex(e => e.id === id);
|
|
250
|
+
if (index >= 0) {
|
|
251
|
+
this.entries.splice(index, 1);
|
|
252
|
+
this.isDirty = true;
|
|
253
|
+
return true;
|
|
254
|
+
}
|
|
255
|
+
return false;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Get all entries of a specific type
|
|
260
|
+
*/
|
|
261
|
+
getEntriesByType(type: VectorMemoryEntry['metadata']['type']): VectorMemoryEntry[] {
|
|
262
|
+
return this.entries.filter(e => e.metadata.type === type);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Get entries by tags
|
|
267
|
+
*/
|
|
268
|
+
getEntriesByTags(tags: string[]): VectorMemoryEntry[] {
|
|
269
|
+
return this.entries.filter(e =>
|
|
270
|
+
tags.some(t => e.metadata.tags.includes(t))
|
|
271
|
+
);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Get entries for a session
|
|
276
|
+
*/
|
|
277
|
+
getEntriesBySession(sessionId: string): VectorMemoryEntry[] {
|
|
278
|
+
return this.entries.filter(e => e.metadata.session === sessionId);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// -----------------------------------------------------------------------
|
|
282
|
+
// Persistence
|
|
283
|
+
// -----------------------------------------------------------------------
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Save to disk
|
|
287
|
+
*/
|
|
288
|
+
save(): void {
|
|
289
|
+
if (!this.isDirty) return;
|
|
290
|
+
|
|
291
|
+
try {
|
|
292
|
+
// Ensure directory exists
|
|
293
|
+
const dir = path.dirname(this.storageFile);
|
|
294
|
+
if (!fs.existsSync(dir)) {
|
|
295
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// Serialize entries
|
|
299
|
+
const data = {
|
|
300
|
+
version: 1,
|
|
301
|
+
entries: this.entries.map(e => ({
|
|
302
|
+
...e,
|
|
303
|
+
metadata: {
|
|
304
|
+
...e.metadata,
|
|
305
|
+
timestamp: e.metadata.timestamp.toISOString(),
|
|
306
|
+
},
|
|
307
|
+
})),
|
|
308
|
+
};
|
|
309
|
+
|
|
310
|
+
fs.writeFileSync(this.storageFile, JSON.stringify(data, null, 2), 'utf-8');
|
|
311
|
+
this.isDirty = false;
|
|
312
|
+
|
|
313
|
+
logger.debug(`Saved ${this.entries.length} memory entries`);
|
|
314
|
+
} catch (error) {
|
|
315
|
+
logger.error('Failed to save memory store', { error });
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Load from disk
|
|
321
|
+
*/
|
|
322
|
+
load(): void {
|
|
323
|
+
try {
|
|
324
|
+
if (!fs.existsSync(this.storageFile)) {
|
|
325
|
+
logger.debug('No existing memory store found, starting fresh');
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
const data = JSON.parse(fs.readFileSync(this.storageFile, 'utf-8'));
|
|
330
|
+
|
|
331
|
+
if (data.version === 1 && Array.isArray(data.entries)) {
|
|
332
|
+
this.entries = data.entries.map((e: any) => ({
|
|
333
|
+
...e,
|
|
334
|
+
metadata: {
|
|
335
|
+
...e.metadata,
|
|
336
|
+
timestamp: new Date(e.metadata.timestamp),
|
|
337
|
+
},
|
|
338
|
+
}));
|
|
339
|
+
|
|
340
|
+
logger.debug(`Loaded ${this.entries.length} memory entries`);
|
|
341
|
+
}
|
|
342
|
+
} catch (error) {
|
|
343
|
+
logger.error('Failed to load memory store', { error });
|
|
344
|
+
this.entries = [];
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// -----------------------------------------------------------------------
|
|
349
|
+
// Maintenance
|
|
350
|
+
// -----------------------------------------------------------------------
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* Prune entries based on importance and age
|
|
354
|
+
*/
|
|
355
|
+
private prune(): void {
|
|
356
|
+
if (this.entries.length <= this.config.maxEntries) return;
|
|
357
|
+
|
|
358
|
+
// Calculate age scores (newer is better)
|
|
359
|
+
const now = Date.now();
|
|
360
|
+
const maxAge = 30 * 24 * 60 * 60 * 1000; // 30 days
|
|
361
|
+
|
|
362
|
+
const scoredEntries = this.entries.map(entry => {
|
|
363
|
+
const age = now - entry.metadata.timestamp.getTime();
|
|
364
|
+
const ageScore = Math.max(0, 1 - age / maxAge);
|
|
365
|
+
|
|
366
|
+
// Combined score: importance * 0.7 + ageScore * 0.3
|
|
367
|
+
const score = entry.metadata.importance * 0.7 + ageScore * 0.3;
|
|
368
|
+
|
|
369
|
+
return { entry, score };
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
// Sort by score and keep top entries
|
|
373
|
+
scoredEntries.sort((a, b) => b.score - a.score);
|
|
374
|
+
this.entries = scoredEntries
|
|
375
|
+
.slice(0, this.config.maxEntries)
|
|
376
|
+
.map(s => s.entry);
|
|
377
|
+
|
|
378
|
+
this.isDirty = true;
|
|
379
|
+
logger.debug(`Pruned memory to ${this.entries.length} entries`);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
/**
|
|
383
|
+
* Get statistics
|
|
384
|
+
*/
|
|
385
|
+
getStats(): {
|
|
386
|
+
totalEntries: number;
|
|
387
|
+
byType: Record<string, number>;
|
|
388
|
+
oldestEntry: Date | null;
|
|
389
|
+
newestEntry: Date | null;
|
|
390
|
+
avgImportance: number;
|
|
391
|
+
} {
|
|
392
|
+
const byType: Record<string, number> = {};
|
|
393
|
+
let oldest: Date | null = null;
|
|
394
|
+
let newest: Date | null = null;
|
|
395
|
+
let totalImportance = 0;
|
|
396
|
+
|
|
397
|
+
for (const entry of this.entries) {
|
|
398
|
+
byType[entry.metadata.type] = (byType[entry.metadata.type] ?? 0) + 1;
|
|
399
|
+
|
|
400
|
+
if (!oldest || entry.metadata.timestamp < oldest) {
|
|
401
|
+
oldest = entry.metadata.timestamp;
|
|
402
|
+
}
|
|
403
|
+
if (!newest || entry.metadata.timestamp > newest) {
|
|
404
|
+
newest = entry.metadata.timestamp;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
totalImportance += entry.metadata.importance;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
return {
|
|
411
|
+
totalEntries: this.entries.length,
|
|
412
|
+
byType,
|
|
413
|
+
oldestEntry: oldest,
|
|
414
|
+
newestEntry: newest,
|
|
415
|
+
avgImportance: this.entries.length > 0 ? totalImportance / this.entries.length : 0,
|
|
416
|
+
};
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
/**
|
|
420
|
+
* Clear all entries
|
|
421
|
+
*/
|
|
422
|
+
clear(): void {
|
|
423
|
+
this.entries = [];
|
|
424
|
+
this.isDirty = true;
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
// ============================================================================
|
|
429
|
+
// Factory function
|
|
430
|
+
// ============================================================================
|
|
431
|
+
|
|
432
|
+
/**
|
|
433
|
+
* Create a vector memory store with default configuration
|
|
434
|
+
*/
|
|
435
|
+
export function createVectorMemoryStore(
|
|
436
|
+
storageDir?: string
|
|
437
|
+
): VectorMemoryStore {
|
|
438
|
+
const defaultDir = storageDir ?? path.join(process.env.HOME ?? '~', '.nova', 'memory');
|
|
439
|
+
return new VectorMemoryStore({ storageDir: defaultDir });
|
|
440
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["helpers.ts"],"names":[],"mappings":"AAMA,2BAA2B;AAC3B,wBAAgB,UAAU,CAAC,MAAM,SAAK,GAAG,MAAM,CAG9C;AAED,qCAAqC;AACrC,wBAAgB,KAAK,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAE/C;AAED,gDAAgD;AAChD,wBAAsB,KAAK,CAAC,CAAC,EAC3B,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EACpB,OAAO,GAAE;IACP,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,OAAO,CAAC;CACpC,GACL,OAAO,CAAC,CAAC,CAAC,CAiBZ;AAED,wCAAwC;AACxC,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,SAAQ,GAAG,MAAM,CAGhF;AAED,4CAA4C;AAC5C,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CASjD;AAED,+CAA+C;AAC/C,wBAAgB,cAAc,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAKjD;AAED,2BAA2B;AAC3B,wBAAgB,SAAS,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,CAEtC;AAED,0BAA0B;AAC1B,wBAAgB,QAAQ,CAAC,CAAC,SAAS,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,EAAE,EAAE,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,GAAG,CAAC,GAAG,IAAI,EAAE,UAAU,CAAC,CAAC,CAAC,KAAK,IAAI,CAMvH;AAED,uCAAuC;AACvC,wBAAgB,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAErE"}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// Utility Helpers
|
|
3
|
+
// ============================================================================
|
|
4
|
+
|
|
5
|
+
import { randomBytes } from 'node:crypto';
|
|
6
|
+
|
|
7
|
+
/** Generate a random ID */
|
|
8
|
+
export function generateId(prefix = ''): string {
|
|
9
|
+
const bytes = randomBytes(8).toString('hex');
|
|
10
|
+
return prefix ? `${prefix}_${bytes}` : bytes;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/** Sleep for a specified duration */
|
|
14
|
+
export function sleep(ms: number): Promise<void> {
|
|
15
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** Retry a function with exponential backoff */
|
|
19
|
+
export async function retry<T>(
|
|
20
|
+
fn: () => Promise<T>,
|
|
21
|
+
options: {
|
|
22
|
+
maxAttempts?: number;
|
|
23
|
+
baseDelay?: number;
|
|
24
|
+
maxDelay?: number;
|
|
25
|
+
shouldRetry?: (error: Error) => boolean;
|
|
26
|
+
} = {}
|
|
27
|
+
): Promise<T> {
|
|
28
|
+
const { maxAttempts = 3, baseDelay = 1000, maxDelay = 30000, shouldRetry } = options;
|
|
29
|
+
let lastError: Error | undefined;
|
|
30
|
+
|
|
31
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
32
|
+
try {
|
|
33
|
+
return await fn();
|
|
34
|
+
} catch (err) {
|
|
35
|
+
lastError = err as Error;
|
|
36
|
+
if (attempt === maxAttempts) break;
|
|
37
|
+
if (shouldRetry && !shouldRetry(lastError)) break;
|
|
38
|
+
const delay = Math.min(baseDelay * Math.pow(2, attempt - 1), maxDelay);
|
|
39
|
+
await sleep(delay);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
throw lastError;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Truncate text to a maximum length */
|
|
47
|
+
export function truncate(text: string, maxLength: number, suffix = '...'): string {
|
|
48
|
+
if (text.length <= maxLength) return text;
|
|
49
|
+
return text.slice(0, maxLength - suffix.length) + suffix;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Format bytes as human-readable string */
|
|
53
|
+
export function formatBytes(bytes: number): string {
|
|
54
|
+
const units = ['B', 'KB', 'MB', 'GB', 'TB'];
|
|
55
|
+
let size = bytes;
|
|
56
|
+
let unitIndex = 0;
|
|
57
|
+
while (size >= 1024 && unitIndex < units.length - 1) {
|
|
58
|
+
size /= 1024;
|
|
59
|
+
unitIndex++;
|
|
60
|
+
}
|
|
61
|
+
return `${size.toFixed(unitIndex === 0 ? 0 : 1)} ${units[unitIndex]}`;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Format duration as human-readable string */
|
|
65
|
+
export function formatDuration(ms: number): string {
|
|
66
|
+
if (ms < 1000) return `${ms}ms`;
|
|
67
|
+
if (ms < 60000) return `${(ms / 1000).toFixed(1)}s`;
|
|
68
|
+
if (ms < 3600000) return `${Math.floor(ms / 60000)}m ${Math.floor((ms % 60000) / 1000)}s`;
|
|
69
|
+
return `${Math.floor(ms / 3600000)}h ${Math.floor((ms % 3600000) / 60000)}m`;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** Deep clone an object */
|
|
73
|
+
export function deepClone<T>(obj: T): T {
|
|
74
|
+
return JSON.parse(JSON.stringify(obj));
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** Debounce a function */
|
|
78
|
+
export function debounce<T extends (...args: unknown[]) => void>(fn: T, delay: number): (...args: Parameters<T>) => void {
|
|
79
|
+
let timer: ReturnType<typeof setTimeout>;
|
|
80
|
+
return (...args: Parameters<T>) => {
|
|
81
|
+
clearTimeout(timer);
|
|
82
|
+
timer = setTimeout(() => fn(...args), delay);
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** Ensure a value is within a range */
|
|
87
|
+
export function clamp(value: number, min: number, max: number): number {
|
|
88
|
+
return Math.max(min, Math.min(max, value));
|
|
89
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AACnD,YAAY,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,WAAW,EAAE,cAAc,EAAE,SAAS,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,cAAc,CAAC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export { Logger, createLogger } from './Logger.js';
|
|
2
|
+
export type { LogEntry } from './Logger.js';
|
|
3
|
+
export { CheckpointManager } from './CheckpointManager.js';
|
|
4
|
+
export type { Checkpoint, CheckpointFile, CheckpointStats } from './CheckpointManager.js';
|
|
5
|
+
export { generateId, sleep, retry, truncate, formatBytes, formatDuration, deepClone, debounce, clamp } from './helpers.js';
|
|
6
|
+
export { TokenCounter, tokenCounter, countTokens, countMessagesTokens } from './TokenCounter.js';
|
|
7
|
+
export {
|
|
8
|
+
withRetry,
|
|
9
|
+
createRetryWrapper,
|
|
10
|
+
RateLimiter,
|
|
11
|
+
withRateLimit,
|
|
12
|
+
ConcurrencyLimiter,
|
|
13
|
+
createResilientFunction,
|
|
14
|
+
createDefaultApiRateLimiter,
|
|
15
|
+
createDefaultConcurrencyLimiter,
|
|
16
|
+
} from './RetryManager.js';
|
|
17
|
+
export type { RetryConfig, RateLimiterConfig } from './RetryManager.js';
|
|
18
|
+
export { VectorMemoryStore, createVectorMemoryStore } from './VectorMemoryStore.js';
|
|
19
|
+
export type { VectorMemoryEntry, MemoryMatch, VectorMemoryConfig } from './VectorMemoryStore.js';
|