viberag 0.3.2 → 0.4.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/README.md +2 -2
- package/dist/cli/app.d.ts +3 -0
- package/dist/cli/app.js +100 -102
- package/dist/cli/commands/handlers.d.ts +8 -6
- package/dist/cli/commands/handlers.js +90 -32
- package/dist/cli/commands/useCommands.d.ts +20 -0
- package/dist/cli/commands/useCommands.js +189 -0
- package/dist/cli/commands/useRagCommands.d.ts +2 -5
- package/dist/cli/commands/useRagCommands.js +11 -18
- package/dist/cli/components/InitWizard.js +66 -27
- package/dist/cli/components/McpSetupWizard.js +23 -4
- package/dist/cli/components/SlotRow.d.ts +22 -0
- package/dist/cli/components/SlotRow.js +55 -0
- package/dist/cli/components/StatusBar.d.ts +14 -0
- package/dist/cli/components/StatusBar.js +156 -0
- package/dist/cli/contexts/DaemonStatusContext.d.ts +38 -0
- package/dist/cli/contexts/DaemonStatusContext.js +106 -0
- package/dist/cli/hooks/useStatusPolling.d.ts +34 -0
- package/dist/cli/hooks/useStatusPolling.js +121 -0
- package/dist/cli/store/app/selectors.d.ts +87 -0
- package/dist/cli/store/app/selectors.js +28 -0
- package/dist/cli/store/app/slice.d.ts +1013 -0
- package/dist/cli/store/app/slice.js +112 -0
- package/dist/cli/store/hooks.d.ts +22 -0
- package/dist/cli/store/hooks.js +17 -0
- package/dist/cli/store/store.d.ts +17 -0
- package/dist/cli/store/store.js +18 -0
- package/dist/cli/store/wizard/selectors.d.ts +115 -0
- package/dist/cli/store/wizard/selectors.js +36 -0
- package/dist/cli/store/wizard/slice.d.ts +523 -0
- package/dist/cli/store/wizard/slice.js +119 -0
- package/dist/cli/utils/error-handler.d.ts +55 -0
- package/dist/cli/utils/error-handler.js +92 -0
- package/dist/client/auto-start.d.ts +42 -0
- package/dist/client/auto-start.js +250 -0
- package/dist/client/connection.d.ts +48 -0
- package/dist/client/connection.js +200 -0
- package/dist/client/index.d.ts +93 -0
- package/dist/client/index.js +209 -0
- package/dist/client/types.d.ts +105 -0
- package/dist/client/types.js +7 -0
- package/dist/common/components/SlotRow.d.ts +22 -0
- package/dist/common/components/SlotRow.js +53 -0
- package/dist/common/components/StatusBar.js +82 -31
- package/dist/common/types.d.ts +12 -13
- package/dist/daemon/handlers.d.ts +15 -0
- package/dist/daemon/handlers.js +157 -0
- package/dist/daemon/index.d.ts +21 -0
- package/dist/daemon/index.js +123 -0
- package/dist/daemon/lib/chunker/bounded-channel.d.ts +51 -0
- package/dist/daemon/lib/chunker/bounded-channel.js +138 -0
- package/dist/daemon/lib/chunker/index.d.ts +135 -0
- package/dist/daemon/lib/chunker/index.js +1370 -0
- package/dist/daemon/lib/chunker/types.d.ts +77 -0
- package/dist/daemon/lib/chunker/types.js +50 -0
- package/dist/daemon/lib/config.d.ts +73 -0
- package/dist/daemon/lib/config.js +149 -0
- package/dist/daemon/lib/constants.d.ts +75 -0
- package/dist/daemon/lib/constants.js +114 -0
- package/dist/daemon/lib/gitignore.d.ts +57 -0
- package/dist/daemon/lib/gitignore.js +246 -0
- package/dist/daemon/lib/logger.d.ts +51 -0
- package/dist/daemon/lib/logger.js +167 -0
- package/dist/daemon/lib/manifest.d.ts +58 -0
- package/dist/daemon/lib/manifest.js +116 -0
- package/dist/daemon/lib/merkle/diff.d.ts +32 -0
- package/dist/daemon/lib/merkle/diff.js +107 -0
- package/dist/daemon/lib/merkle/hash.d.ts +40 -0
- package/dist/daemon/lib/merkle/hash.js +180 -0
- package/dist/daemon/lib/merkle/index.d.ts +71 -0
- package/dist/daemon/lib/merkle/index.js +309 -0
- package/dist/daemon/lib/merkle/node.d.ts +55 -0
- package/dist/daemon/lib/merkle/node.js +82 -0
- package/dist/daemon/lifecycle.d.ts +50 -0
- package/dist/daemon/lifecycle.js +142 -0
- package/dist/daemon/owner.d.ts +175 -0
- package/dist/daemon/owner.js +609 -0
- package/dist/daemon/protocol.d.ts +100 -0
- package/dist/daemon/protocol.js +163 -0
- package/dist/daemon/providers/api-utils.d.ts +130 -0
- package/dist/daemon/providers/api-utils.js +248 -0
- package/dist/daemon/providers/gemini.d.ts +39 -0
- package/dist/daemon/providers/gemini.js +205 -0
- package/dist/daemon/providers/index.d.ts +14 -0
- package/dist/daemon/providers/index.js +14 -0
- package/dist/daemon/providers/local-4b.d.ts +28 -0
- package/dist/daemon/providers/local-4b.js +51 -0
- package/dist/daemon/providers/local.d.ts +36 -0
- package/dist/daemon/providers/local.js +166 -0
- package/dist/daemon/providers/mistral.d.ts +35 -0
- package/dist/daemon/providers/mistral.js +160 -0
- package/dist/daemon/providers/mock.d.ts +35 -0
- package/dist/daemon/providers/mock.js +69 -0
- package/dist/daemon/providers/openai.d.ts +41 -0
- package/dist/daemon/providers/openai.js +190 -0
- package/dist/daemon/providers/types.d.ts +68 -0
- package/dist/daemon/providers/types.js +6 -0
- package/dist/daemon/providers/validate.d.ts +30 -0
- package/dist/daemon/providers/validate.js +162 -0
- package/dist/daemon/server.d.ts +79 -0
- package/dist/daemon/server.js +293 -0
- package/dist/daemon/services/index.d.ts +11 -0
- package/dist/daemon/services/index.js +16 -0
- package/dist/daemon/services/indexing.d.ts +117 -0
- package/dist/daemon/services/indexing.js +573 -0
- package/dist/daemon/services/search/filters.d.ts +21 -0
- package/dist/daemon/services/search/filters.js +106 -0
- package/dist/daemon/services/search/fts.d.ts +32 -0
- package/dist/daemon/services/search/fts.js +61 -0
- package/dist/daemon/services/search/hybrid.d.ts +17 -0
- package/dist/daemon/services/search/hybrid.js +58 -0
- package/dist/daemon/services/search/index.d.ts +108 -0
- package/dist/daemon/services/search/index.js +417 -0
- package/dist/daemon/services/search/types.d.ts +126 -0
- package/dist/daemon/services/search/types.js +4 -0
- package/dist/daemon/services/search/vector.d.ts +25 -0
- package/dist/daemon/services/search/vector.js +44 -0
- package/dist/daemon/services/storage/index.d.ts +110 -0
- package/dist/daemon/services/storage/index.js +378 -0
- package/dist/daemon/services/storage/schema.d.ts +24 -0
- package/dist/daemon/services/storage/schema.js +51 -0
- package/dist/daemon/services/storage/types.d.ts +105 -0
- package/dist/daemon/services/storage/types.js +71 -0
- package/dist/daemon/services/types.d.ts +192 -0
- package/dist/daemon/services/types.js +53 -0
- package/dist/daemon/services/watcher.d.ts +98 -0
- package/dist/daemon/services/watcher.js +386 -0
- package/dist/daemon/state.d.ts +119 -0
- package/dist/daemon/state.js +161 -0
- package/dist/mcp/index.d.ts +1 -1
- package/dist/mcp/index.js +44 -60
- package/dist/mcp/server.d.ts +10 -14
- package/dist/mcp/server.js +75 -74
- package/dist/mcp/services/lazy-loader.d.ts +23 -0
- package/dist/mcp/services/lazy-loader.js +34 -0
- package/dist/mcp/warmup.d.ts +3 -3
- package/dist/mcp/warmup.js +39 -40
- package/dist/mcp/watcher.d.ts +5 -7
- package/dist/mcp/watcher.js +73 -64
- package/dist/rag/config/index.d.ts +2 -0
- package/dist/rag/constants.d.ts +30 -0
- package/dist/rag/constants.js +38 -0
- package/dist/rag/embeddings/api-utils.d.ts +121 -0
- package/dist/rag/embeddings/api-utils.js +259 -0
- package/dist/rag/embeddings/gemini.d.ts +4 -12
- package/dist/rag/embeddings/gemini.js +22 -72
- package/dist/rag/embeddings/index.d.ts +5 -3
- package/dist/rag/embeddings/index.js +5 -2
- package/dist/rag/embeddings/local-4b.d.ts +2 -2
- package/dist/rag/embeddings/local-4b.js +1 -1
- package/dist/rag/embeddings/local.d.ts +10 -3
- package/dist/rag/embeddings/local.js +58 -12
- package/dist/rag/embeddings/mistral.d.ts +4 -12
- package/dist/rag/embeddings/mistral.js +22 -72
- package/dist/rag/embeddings/mock.d.ts +35 -0
- package/dist/rag/embeddings/mock.js +69 -0
- package/dist/rag/embeddings/openai.d.ts +11 -13
- package/dist/rag/embeddings/openai.js +47 -75
- package/dist/rag/embeddings/types.d.ts +27 -1
- package/dist/rag/embeddings/validate.d.ts +9 -1
- package/dist/rag/embeddings/validate.js +17 -4
- package/dist/rag/index.d.ts +2 -2
- package/dist/rag/index.js +1 -1
- package/dist/rag/indexer/bounded-channel.d.ts +51 -0
- package/dist/rag/indexer/bounded-channel.js +138 -0
- package/dist/rag/indexer/indexer.d.ts +4 -14
- package/dist/rag/indexer/indexer.js +246 -169
- package/dist/rag/indexer/types.d.ts +1 -0
- package/dist/rag/logger/index.d.ts +22 -0
- package/dist/rag/logger/index.js +78 -1
- package/dist/rag/manifest/index.js +1 -2
- package/dist/rag/search/index.js +1 -1
- package/dist/rag/storage/schema.d.ts +2 -4
- package/dist/rag/storage/schema.js +3 -5
- package/dist/store/app/selectors.d.ts +87 -0
- package/dist/store/app/selectors.js +28 -0
- package/dist/store/app/slice.d.ts +1013 -0
- package/dist/store/app/slice.js +112 -0
- package/dist/store/hooks.d.ts +22 -0
- package/dist/store/hooks.js +17 -0
- package/dist/store/index.d.ts +12 -0
- package/dist/store/index.js +18 -0
- package/dist/store/indexing/listeners.d.ts +25 -0
- package/dist/store/indexing/listeners.js +46 -0
- package/dist/store/indexing/selectors.d.ts +195 -0
- package/dist/store/indexing/selectors.js +69 -0
- package/dist/store/indexing/slice.d.ts +309 -0
- package/dist/store/indexing/slice.js +113 -0
- package/dist/store/slot-progress/listeners.d.ts +23 -0
- package/dist/store/slot-progress/listeners.js +33 -0
- package/dist/store/slot-progress/selectors.d.ts +67 -0
- package/dist/store/slot-progress/selectors.js +36 -0
- package/dist/store/slot-progress/slice.d.ts +246 -0
- package/dist/store/slot-progress/slice.js +70 -0
- package/dist/store/store.d.ts +17 -0
- package/dist/store/store.js +18 -0
- package/dist/store/warmup/selectors.d.ts +109 -0
- package/dist/store/warmup/selectors.js +44 -0
- package/dist/store/warmup/slice.d.ts +137 -0
- package/dist/store/warmup/slice.js +72 -0
- package/dist/store/watcher/selectors.d.ts +115 -0
- package/dist/store/watcher/selectors.js +52 -0
- package/dist/store/watcher/slice.d.ts +269 -0
- package/dist/store/watcher/slice.js +100 -0
- package/dist/store/wizard/selectors.d.ts +115 -0
- package/dist/store/wizard/selectors.js +36 -0
- package/dist/store/wizard/slice.d.ts +523 -0
- package/dist/store/wizard/slice.js +119 -0
- package/package.json +10 -2
|
@@ -0,0 +1,609 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Daemon Resource Owner
|
|
3
|
+
*
|
|
4
|
+
* Single owner of all mutable project state. The daemon owns:
|
|
5
|
+
* - LanceDB connection (via Storage)
|
|
6
|
+
* - FileWatcher (auto-indexing on file changes)
|
|
7
|
+
* - SearchEngine (singleton, shared across requests)
|
|
8
|
+
* - IndexingService (on-demand, exclusive access via mutex)
|
|
9
|
+
*
|
|
10
|
+
* Uses event-based services instead of Redux.
|
|
11
|
+
* CLI and MCP clients access these resources via IPC.
|
|
12
|
+
*/
|
|
13
|
+
import * as crypto from 'node:crypto';
|
|
14
|
+
import path from 'node:path';
|
|
15
|
+
import { loadConfig, configExists } from './lib/config.js';
|
|
16
|
+
import { loadManifest, manifestExists } from './lib/manifest.js';
|
|
17
|
+
import { createServiceLogger } from './lib/logger.js';
|
|
18
|
+
import { daemonState } from './state.js';
|
|
19
|
+
import { SearchEngine } from './services/search/index.js';
|
|
20
|
+
import { IndexingService } from './services/indexing.js';
|
|
21
|
+
import { FileWatcher } from './services/watcher.js';
|
|
22
|
+
import { Storage } from './services/storage/index.js';
|
|
23
|
+
// ============================================================================
|
|
24
|
+
// Daemon Owner
|
|
25
|
+
// ============================================================================
|
|
26
|
+
/**
|
|
27
|
+
* Owns and manages all daemon resources.
|
|
28
|
+
* Uses event-based services wired to daemon state.
|
|
29
|
+
*/
|
|
30
|
+
export class DaemonOwner {
|
|
31
|
+
constructor(projectRoot) {
|
|
32
|
+
Object.defineProperty(this, "projectRoot", {
|
|
33
|
+
enumerable: true,
|
|
34
|
+
configurable: true,
|
|
35
|
+
writable: true,
|
|
36
|
+
value: void 0
|
|
37
|
+
});
|
|
38
|
+
Object.defineProperty(this, "config", {
|
|
39
|
+
enumerable: true,
|
|
40
|
+
configurable: true,
|
|
41
|
+
writable: true,
|
|
42
|
+
value: null
|
|
43
|
+
});
|
|
44
|
+
Object.defineProperty(this, "logger", {
|
|
45
|
+
enumerable: true,
|
|
46
|
+
configurable: true,
|
|
47
|
+
writable: true,
|
|
48
|
+
value: null
|
|
49
|
+
});
|
|
50
|
+
Object.defineProperty(this, "watcher", {
|
|
51
|
+
enumerable: true,
|
|
52
|
+
configurable: true,
|
|
53
|
+
writable: true,
|
|
54
|
+
value: null
|
|
55
|
+
});
|
|
56
|
+
// Shared Storage instance (owned by DaemonOwner)
|
|
57
|
+
Object.defineProperty(this, "storage", {
|
|
58
|
+
enumerable: true,
|
|
59
|
+
configurable: true,
|
|
60
|
+
writable: true,
|
|
61
|
+
value: null
|
|
62
|
+
});
|
|
63
|
+
// SearchEngine singleton (lazy initialized)
|
|
64
|
+
Object.defineProperty(this, "searchEngine", {
|
|
65
|
+
enumerable: true,
|
|
66
|
+
configurable: true,
|
|
67
|
+
writable: true,
|
|
68
|
+
value: null
|
|
69
|
+
});
|
|
70
|
+
Object.defineProperty(this, "warmupPromise", {
|
|
71
|
+
enumerable: true,
|
|
72
|
+
configurable: true,
|
|
73
|
+
writable: true,
|
|
74
|
+
value: null
|
|
75
|
+
});
|
|
76
|
+
Object.defineProperty(this, "warmupStartTime", {
|
|
77
|
+
enumerable: true,
|
|
78
|
+
configurable: true,
|
|
79
|
+
writable: true,
|
|
80
|
+
value: null
|
|
81
|
+
});
|
|
82
|
+
this.projectRoot = projectRoot;
|
|
83
|
+
}
|
|
84
|
+
// ==========================================================================
|
|
85
|
+
// Lifecycle
|
|
86
|
+
// ==========================================================================
|
|
87
|
+
/**
|
|
88
|
+
* Initialize the daemon owner.
|
|
89
|
+
* Loads config, starts watcher, begins warmup.
|
|
90
|
+
*/
|
|
91
|
+
async initialize() {
|
|
92
|
+
// Check if project is initialized
|
|
93
|
+
if (!(await configExists(this.projectRoot))) {
|
|
94
|
+
throw new Error(`VibeRAG not initialized in ${this.projectRoot}. ` +
|
|
95
|
+
`Run 'npx viberag' and use /init command first.`);
|
|
96
|
+
}
|
|
97
|
+
// Load config
|
|
98
|
+
this.config = await loadConfig(this.projectRoot);
|
|
99
|
+
// Create service logger (writes to .viberag/logs/daemon/)
|
|
100
|
+
try {
|
|
101
|
+
this.logger = createServiceLogger(this.projectRoot, 'daemon');
|
|
102
|
+
}
|
|
103
|
+
catch {
|
|
104
|
+
// Ignore - debug logging is optional
|
|
105
|
+
}
|
|
106
|
+
this.log('info', `Daemon initializing for ${this.projectRoot}`);
|
|
107
|
+
// Create and connect shared Storage instance
|
|
108
|
+
this.storage = new Storage(this.projectRoot, this.config.embeddingDimensions);
|
|
109
|
+
await this.storage.connect();
|
|
110
|
+
this.log('info', 'Storage connected');
|
|
111
|
+
// Start watcher (if enabled)
|
|
112
|
+
if (this.config.watch?.enabled !== false) {
|
|
113
|
+
this.watcher = new FileWatcher(this.projectRoot);
|
|
114
|
+
this.wireWatcherEvents();
|
|
115
|
+
// Set index trigger callback
|
|
116
|
+
this.watcher.setIndexTrigger(async () => {
|
|
117
|
+
const stats = await this.index({ force: false });
|
|
118
|
+
return {
|
|
119
|
+
chunksAdded: stats.chunksAdded,
|
|
120
|
+
chunksDeleted: stats.chunksDeleted,
|
|
121
|
+
};
|
|
122
|
+
});
|
|
123
|
+
await this.watcher.start();
|
|
124
|
+
this.log('info', 'File watcher started');
|
|
125
|
+
}
|
|
126
|
+
// Start warmup in background (don't await)
|
|
127
|
+
this.startWarmup();
|
|
128
|
+
this.log('info', 'Daemon initialized');
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Shutdown the daemon owner.
|
|
132
|
+
* Stops watcher, closes search engine and storage.
|
|
133
|
+
*/
|
|
134
|
+
async shutdown() {
|
|
135
|
+
this.log('info', 'Daemon shutting down');
|
|
136
|
+
// Stop watcher
|
|
137
|
+
if (this.watcher) {
|
|
138
|
+
await this.watcher.stop();
|
|
139
|
+
this.watcher = null;
|
|
140
|
+
}
|
|
141
|
+
// Close search engine (no longer closes storage since it's external)
|
|
142
|
+
if (this.searchEngine) {
|
|
143
|
+
this.searchEngine.close();
|
|
144
|
+
this.searchEngine = null;
|
|
145
|
+
}
|
|
146
|
+
this.warmupPromise = null;
|
|
147
|
+
// Close shared storage
|
|
148
|
+
if (this.storage) {
|
|
149
|
+
this.storage.close();
|
|
150
|
+
this.storage = null;
|
|
151
|
+
}
|
|
152
|
+
// Reset state
|
|
153
|
+
daemonState.reset();
|
|
154
|
+
this.log('info', 'Daemon shutdown complete');
|
|
155
|
+
}
|
|
156
|
+
// ==========================================================================
|
|
157
|
+
// Event Wiring
|
|
158
|
+
// ==========================================================================
|
|
159
|
+
/**
|
|
160
|
+
* Wire watcher events to daemon state.
|
|
161
|
+
*/
|
|
162
|
+
wireWatcherEvents() {
|
|
163
|
+
if (!this.watcher)
|
|
164
|
+
return;
|
|
165
|
+
this.watcher.on('watcher-start', () => {
|
|
166
|
+
daemonState.updateNested('watcher', () => ({
|
|
167
|
+
watching: true,
|
|
168
|
+
}));
|
|
169
|
+
});
|
|
170
|
+
this.watcher.on('watcher-ready', ({ filesWatched }) => {
|
|
171
|
+
daemonState.updateNested('watcher', () => ({
|
|
172
|
+
watching: true,
|
|
173
|
+
filesWatched,
|
|
174
|
+
}));
|
|
175
|
+
});
|
|
176
|
+
this.watcher.on('watcher-debouncing', ({ pendingPaths }) => {
|
|
177
|
+
daemonState.updateNested('watcher', () => ({
|
|
178
|
+
pendingChanges: pendingPaths.length,
|
|
179
|
+
indexUpToDate: false,
|
|
180
|
+
}));
|
|
181
|
+
});
|
|
182
|
+
this.watcher.on('watcher-indexed', () => {
|
|
183
|
+
daemonState.updateNested('watcher', () => ({
|
|
184
|
+
lastIndexUpdate: new Date().toISOString(),
|
|
185
|
+
indexUpToDate: true,
|
|
186
|
+
pendingChanges: 0,
|
|
187
|
+
}));
|
|
188
|
+
});
|
|
189
|
+
this.watcher.on('watcher-stopped', () => {
|
|
190
|
+
daemonState.updateNested('watcher', () => ({
|
|
191
|
+
watching: false,
|
|
192
|
+
filesWatched: 0,
|
|
193
|
+
pendingChanges: 0,
|
|
194
|
+
}));
|
|
195
|
+
});
|
|
196
|
+
this.watcher.on('watcher-error', ({ error }) => {
|
|
197
|
+
this.log('error', `Watcher error: ${error}`);
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Wire indexing service events to daemon state.
|
|
202
|
+
*/
|
|
203
|
+
wireIndexingEvents(indexer) {
|
|
204
|
+
indexer.on('start', () => {
|
|
205
|
+
daemonState.updateNested('indexing', () => ({
|
|
206
|
+
status: 'initializing',
|
|
207
|
+
current: 0,
|
|
208
|
+
total: 0,
|
|
209
|
+
stage: '',
|
|
210
|
+
chunksProcessed: 0,
|
|
211
|
+
throttleMessage: null,
|
|
212
|
+
error: null,
|
|
213
|
+
}));
|
|
214
|
+
// Reset slots
|
|
215
|
+
daemonState.update(state => ({
|
|
216
|
+
slots: state.slots.map(() => ({
|
|
217
|
+
state: 'idle',
|
|
218
|
+
batchInfo: null,
|
|
219
|
+
retryInfo: null,
|
|
220
|
+
})),
|
|
221
|
+
failures: [],
|
|
222
|
+
}));
|
|
223
|
+
});
|
|
224
|
+
indexer.on('progress', ({ current, total, stage }) => {
|
|
225
|
+
let status = 'embedding';
|
|
226
|
+
if (stage.toLowerCase().includes('scan')) {
|
|
227
|
+
status = 'scanning';
|
|
228
|
+
}
|
|
229
|
+
else if (stage.toLowerCase().includes('chunk')) {
|
|
230
|
+
status = 'chunking';
|
|
231
|
+
}
|
|
232
|
+
daemonState.updateNested('indexing', () => ({
|
|
233
|
+
status,
|
|
234
|
+
current,
|
|
235
|
+
total,
|
|
236
|
+
stage,
|
|
237
|
+
}));
|
|
238
|
+
});
|
|
239
|
+
indexer.on('chunk-progress', ({ chunksProcessed }) => {
|
|
240
|
+
daemonState.updateNested('indexing', () => ({
|
|
241
|
+
chunksProcessed,
|
|
242
|
+
}));
|
|
243
|
+
});
|
|
244
|
+
indexer.on('throttle', ({ message }) => {
|
|
245
|
+
daemonState.updateNested('indexing', () => ({
|
|
246
|
+
throttleMessage: message,
|
|
247
|
+
}));
|
|
248
|
+
});
|
|
249
|
+
indexer.on('complete', () => {
|
|
250
|
+
daemonState.updateNested('indexing', () => ({
|
|
251
|
+
status: 'complete',
|
|
252
|
+
lastCompleted: new Date().toISOString(),
|
|
253
|
+
throttleMessage: null,
|
|
254
|
+
}));
|
|
255
|
+
// Reset to idle after a short delay
|
|
256
|
+
setTimeout(() => {
|
|
257
|
+
const state = daemonState.getSnapshot();
|
|
258
|
+
if (state.indexing.status === 'complete') {
|
|
259
|
+
daemonState.updateNested('indexing', () => ({
|
|
260
|
+
status: 'idle',
|
|
261
|
+
}));
|
|
262
|
+
}
|
|
263
|
+
}, 1000);
|
|
264
|
+
});
|
|
265
|
+
indexer.on('error', ({ error }) => {
|
|
266
|
+
daemonState.updateNested('indexing', () => ({
|
|
267
|
+
status: 'error',
|
|
268
|
+
error: error.message,
|
|
269
|
+
}));
|
|
270
|
+
});
|
|
271
|
+
// Slot events
|
|
272
|
+
indexer.on('slot-processing', ({ slot, batchInfo }) => {
|
|
273
|
+
daemonState.update(state => ({
|
|
274
|
+
slots: state.slots.map((s, i) => i === slot
|
|
275
|
+
? { state: 'processing', batchInfo, retryInfo: null }
|
|
276
|
+
: s),
|
|
277
|
+
}));
|
|
278
|
+
});
|
|
279
|
+
indexer.on('slot-rate-limited', ({ slot, retryInfo }) => {
|
|
280
|
+
daemonState.update(state => ({
|
|
281
|
+
slots: state.slots.map((s, i) => i === slot ? { ...s, state: 'rate-limited', retryInfo } : s),
|
|
282
|
+
}));
|
|
283
|
+
});
|
|
284
|
+
indexer.on('slot-idle', ({ slot }) => {
|
|
285
|
+
daemonState.update(state => ({
|
|
286
|
+
slots: state.slots.map((s, i) => i === slot
|
|
287
|
+
? { state: 'idle', batchInfo: null, retryInfo: null }
|
|
288
|
+
: s),
|
|
289
|
+
}));
|
|
290
|
+
});
|
|
291
|
+
indexer.on('slot-failure', ({ batchInfo, error }) => {
|
|
292
|
+
daemonState.update(state => ({
|
|
293
|
+
failures: [
|
|
294
|
+
...state.failures,
|
|
295
|
+
{ batchInfo, error, timestamp: new Date().toISOString() },
|
|
296
|
+
],
|
|
297
|
+
}));
|
|
298
|
+
});
|
|
299
|
+
indexer.on('slots-reset', () => {
|
|
300
|
+
daemonState.update(state => ({
|
|
301
|
+
slots: state.slots.map(() => ({
|
|
302
|
+
state: 'idle',
|
|
303
|
+
batchInfo: null,
|
|
304
|
+
retryInfo: null,
|
|
305
|
+
})),
|
|
306
|
+
failures: [],
|
|
307
|
+
}));
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
// ==========================================================================
|
|
311
|
+
// SearchEngine Management (WarmupManager pattern)
|
|
312
|
+
// ==========================================================================
|
|
313
|
+
/**
|
|
314
|
+
* Start warmup in background.
|
|
315
|
+
*/
|
|
316
|
+
startWarmup() {
|
|
317
|
+
if (this.warmupPromise || this.searchEngine) {
|
|
318
|
+
return; // Already started or ready
|
|
319
|
+
}
|
|
320
|
+
this.warmupPromise = this.doWarmup().catch(error => {
|
|
321
|
+
// Error captured in state, re-throw for chain
|
|
322
|
+
throw error;
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
/**
|
|
326
|
+
* Perform warmup - initialize SearchEngine with embedding provider.
|
|
327
|
+
*/
|
|
328
|
+
async doWarmup() {
|
|
329
|
+
this.warmupStartTime = Date.now();
|
|
330
|
+
try {
|
|
331
|
+
if (!this.config) {
|
|
332
|
+
throw new Error('Config not loaded');
|
|
333
|
+
}
|
|
334
|
+
// Update state
|
|
335
|
+
daemonState.updateNested('warmup', () => ({
|
|
336
|
+
status: 'initializing',
|
|
337
|
+
provider: this.config.embeddingProvider,
|
|
338
|
+
error: null,
|
|
339
|
+
startedAt: new Date().toISOString(),
|
|
340
|
+
}));
|
|
341
|
+
this.log('info', `Warming up ${this.config.embeddingProvider} provider`);
|
|
342
|
+
// Create SearchEngine with shared storage
|
|
343
|
+
const engine = new SearchEngine(this.projectRoot, {
|
|
344
|
+
logger: this.logger ?? undefined,
|
|
345
|
+
storage: this.storage ?? undefined,
|
|
346
|
+
});
|
|
347
|
+
// Timeout based on provider
|
|
348
|
+
const isLocal = this.config.embeddingProvider.startsWith('local');
|
|
349
|
+
const timeout = isLocal ? 180000 : 30000;
|
|
350
|
+
const initPromise = engine.warmup();
|
|
351
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
352
|
+
setTimeout(() => {
|
|
353
|
+
reject(new Error(`Warmup timeout after ${timeout}ms. ` +
|
|
354
|
+
`For local models, first run may take several minutes.`));
|
|
355
|
+
}, timeout);
|
|
356
|
+
});
|
|
357
|
+
await Promise.race([initPromise, timeoutPromise]);
|
|
358
|
+
const elapsed = Date.now() - this.warmupStartTime;
|
|
359
|
+
this.searchEngine = engine;
|
|
360
|
+
daemonState.updateNested('warmup', () => ({
|
|
361
|
+
status: 'ready',
|
|
362
|
+
readyAt: new Date().toISOString(),
|
|
363
|
+
}));
|
|
364
|
+
this.log('info', `Warmup complete (${elapsed}ms)`);
|
|
365
|
+
return engine;
|
|
366
|
+
}
|
|
367
|
+
catch (error) {
|
|
368
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
369
|
+
daemonState.updateNested('warmup', () => ({
|
|
370
|
+
status: 'failed',
|
|
371
|
+
error: message,
|
|
372
|
+
}));
|
|
373
|
+
this.log('error', `Warmup failed: ${message}`);
|
|
374
|
+
// Clear promise to allow retry
|
|
375
|
+
this.warmupPromise = null;
|
|
376
|
+
throw error;
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
/**
|
|
380
|
+
* Get the initialized SearchEngine.
|
|
381
|
+
*/
|
|
382
|
+
async getSearchEngine() {
|
|
383
|
+
if (this.searchEngine) {
|
|
384
|
+
return this.searchEngine;
|
|
385
|
+
}
|
|
386
|
+
if (this.warmupPromise) {
|
|
387
|
+
return this.warmupPromise;
|
|
388
|
+
}
|
|
389
|
+
// Start warmup and wait
|
|
390
|
+
this.warmupPromise = this.doWarmup();
|
|
391
|
+
return this.warmupPromise;
|
|
392
|
+
}
|
|
393
|
+
// ==========================================================================
|
|
394
|
+
// Operations
|
|
395
|
+
// ==========================================================================
|
|
396
|
+
/**
|
|
397
|
+
* Search the codebase.
|
|
398
|
+
*/
|
|
399
|
+
async search(query, options) {
|
|
400
|
+
const engine = await this.getSearchEngine();
|
|
401
|
+
return engine.search(query, options);
|
|
402
|
+
}
|
|
403
|
+
/**
|
|
404
|
+
* Index the codebase.
|
|
405
|
+
*/
|
|
406
|
+
async index(options) {
|
|
407
|
+
// Notify watcher that indexing is starting
|
|
408
|
+
this.watcher?.setIndexingState(true);
|
|
409
|
+
// Create IndexingService with shared storage
|
|
410
|
+
const indexer = new IndexingService(this.projectRoot, {
|
|
411
|
+
logger: this.logger ?? undefined,
|
|
412
|
+
storage: this.storage ?? undefined,
|
|
413
|
+
});
|
|
414
|
+
// Wire events to state
|
|
415
|
+
this.wireIndexingEvents(indexer);
|
|
416
|
+
try {
|
|
417
|
+
// Handle force reindex - sync config dimensions
|
|
418
|
+
if (options?.force && this.config) {
|
|
419
|
+
const { PROVIDER_CONFIGS, saveConfig } = await import('./lib/config.js');
|
|
420
|
+
const currentDimensions = PROVIDER_CONFIGS[this.config.embeddingProvider]?.dimensions;
|
|
421
|
+
if (currentDimensions &&
|
|
422
|
+
this.config.embeddingDimensions !== currentDimensions) {
|
|
423
|
+
const updatedConfig = {
|
|
424
|
+
...this.config,
|
|
425
|
+
embeddingDimensions: currentDimensions,
|
|
426
|
+
embeddingModel: PROVIDER_CONFIGS[this.config.embeddingProvider].model,
|
|
427
|
+
};
|
|
428
|
+
await saveConfig(this.projectRoot, updatedConfig);
|
|
429
|
+
this.config = updatedConfig;
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
const stats = await indexer.index({ force: options?.force ?? false });
|
|
433
|
+
daemonState.updateNested('indexing', () => ({
|
|
434
|
+
lastStats: stats,
|
|
435
|
+
}));
|
|
436
|
+
return stats;
|
|
437
|
+
}
|
|
438
|
+
finally {
|
|
439
|
+
indexer.close();
|
|
440
|
+
this.watcher?.setIndexingState(false);
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
/**
|
|
444
|
+
* Get daemon status.
|
|
445
|
+
* Enhanced to support polling-based state synchronization.
|
|
446
|
+
*/
|
|
447
|
+
async getStatus() {
|
|
448
|
+
const state = daemonState.getSnapshot();
|
|
449
|
+
const watcherStatus = this.watcher?.getStatus();
|
|
450
|
+
// Calculate warmup elapsed time
|
|
451
|
+
let warmupElapsedMs;
|
|
452
|
+
if (this.warmupStartTime) {
|
|
453
|
+
if (state.warmup.status === 'ready' || state.warmup.status === 'failed') {
|
|
454
|
+
warmupElapsedMs = state.warmup.readyAt
|
|
455
|
+
? new Date(state.warmup.readyAt).getTime() - this.warmupStartTime
|
|
456
|
+
: Date.now() - this.warmupStartTime;
|
|
457
|
+
}
|
|
458
|
+
else if (state.warmup.status === 'initializing') {
|
|
459
|
+
warmupElapsedMs = Date.now() - this.warmupStartTime;
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
const status = {
|
|
463
|
+
initialized: await configExists(this.projectRoot),
|
|
464
|
+
indexed: await manifestExists(this.projectRoot),
|
|
465
|
+
warmupStatus: state.warmup.status,
|
|
466
|
+
warmupElapsedMs,
|
|
467
|
+
watcherStatus: watcherStatus ?? {
|
|
468
|
+
watching: false,
|
|
469
|
+
filesWatched: 0,
|
|
470
|
+
pendingChanges: 0,
|
|
471
|
+
pendingPaths: [],
|
|
472
|
+
lastIndexUpdate: null,
|
|
473
|
+
indexUpToDate: false,
|
|
474
|
+
lastError: null,
|
|
475
|
+
},
|
|
476
|
+
// Map indexing status for backwards compatibility
|
|
477
|
+
indexing: {
|
|
478
|
+
status: this.mapIndexingStatus(state.indexing.status),
|
|
479
|
+
current: state.indexing.current,
|
|
480
|
+
total: state.indexing.total,
|
|
481
|
+
stage: state.indexing.stage,
|
|
482
|
+
chunksProcessed: state.indexing.chunksProcessed,
|
|
483
|
+
throttleMessage: state.indexing.throttleMessage,
|
|
484
|
+
error: state.indexing.error,
|
|
485
|
+
lastCompleted: state.indexing.lastCompleted,
|
|
486
|
+
lastStats: state.indexing.lastStats,
|
|
487
|
+
percent: state.indexing.total > 0
|
|
488
|
+
? Math.round((state.indexing.current / state.indexing.total) * 100)
|
|
489
|
+
: 0,
|
|
490
|
+
},
|
|
491
|
+
// Slot progress for concurrent embedding tracking
|
|
492
|
+
slots: state.slots.map(s => ({
|
|
493
|
+
state: s.state,
|
|
494
|
+
batchInfo: s.batchInfo,
|
|
495
|
+
retryInfo: s.retryInfo,
|
|
496
|
+
})),
|
|
497
|
+
// Failed batches after retries exhausted
|
|
498
|
+
failures: state.failures.map(f => ({
|
|
499
|
+
batchInfo: f.batchInfo,
|
|
500
|
+
error: f.error,
|
|
501
|
+
timestamp: f.timestamp,
|
|
502
|
+
})),
|
|
503
|
+
};
|
|
504
|
+
// Add config info if available
|
|
505
|
+
if (this.config) {
|
|
506
|
+
status.embeddingProvider = this.config.embeddingProvider;
|
|
507
|
+
status.embeddingModel = this.config.embeddingModel;
|
|
508
|
+
}
|
|
509
|
+
// Add manifest info if indexed
|
|
510
|
+
if (status.indexed) {
|
|
511
|
+
const manifest = await loadManifest(this.projectRoot);
|
|
512
|
+
status.version = manifest.version;
|
|
513
|
+
status.createdAt = manifest.createdAt;
|
|
514
|
+
status.updatedAt = manifest.updatedAt;
|
|
515
|
+
status.totalFiles = manifest.stats.totalFiles;
|
|
516
|
+
status.totalChunks = manifest.stats.totalChunks;
|
|
517
|
+
}
|
|
518
|
+
return status;
|
|
519
|
+
}
|
|
520
|
+
/**
|
|
521
|
+
* Map internal indexing status to client-facing status.
|
|
522
|
+
*/
|
|
523
|
+
mapIndexingStatus(status) {
|
|
524
|
+
switch (status) {
|
|
525
|
+
case 'idle':
|
|
526
|
+
return 'idle';
|
|
527
|
+
case 'initializing':
|
|
528
|
+
return 'initializing';
|
|
529
|
+
case 'scanning':
|
|
530
|
+
case 'chunking':
|
|
531
|
+
case 'embedding':
|
|
532
|
+
return 'indexing';
|
|
533
|
+
case 'complete':
|
|
534
|
+
return 'complete';
|
|
535
|
+
case 'error':
|
|
536
|
+
return 'error';
|
|
537
|
+
default:
|
|
538
|
+
return 'idle';
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
/**
|
|
542
|
+
* Get watcher status.
|
|
543
|
+
*/
|
|
544
|
+
getWatcherStatus() {
|
|
545
|
+
return (this.watcher?.getStatus() ?? {
|
|
546
|
+
watching: false,
|
|
547
|
+
filesWatched: 0,
|
|
548
|
+
pendingChanges: 0,
|
|
549
|
+
pendingPaths: [],
|
|
550
|
+
lastIndexUpdate: null,
|
|
551
|
+
indexUpToDate: false,
|
|
552
|
+
lastError: null,
|
|
553
|
+
});
|
|
554
|
+
}
|
|
555
|
+
/**
|
|
556
|
+
* Get the file watcher (for direct access if needed).
|
|
557
|
+
*/
|
|
558
|
+
getWatcher() {
|
|
559
|
+
return this.watcher;
|
|
560
|
+
}
|
|
561
|
+
/**
|
|
562
|
+
* Get the project root.
|
|
563
|
+
*/
|
|
564
|
+
getProjectRoot() {
|
|
565
|
+
return this.projectRoot;
|
|
566
|
+
}
|
|
567
|
+
// ==========================================================================
|
|
568
|
+
// Helpers
|
|
569
|
+
// ==========================================================================
|
|
570
|
+
/**
|
|
571
|
+
* Get the socket path for this project.
|
|
572
|
+
*/
|
|
573
|
+
getSocketPath() {
|
|
574
|
+
if (process.platform === 'win32') {
|
|
575
|
+
// Windows named pipe - hash project root for unique name
|
|
576
|
+
const hash = crypto
|
|
577
|
+
.createHash('md5')
|
|
578
|
+
.update(this.projectRoot)
|
|
579
|
+
.digest('hex')
|
|
580
|
+
.slice(0, 8);
|
|
581
|
+
return `\\\\.\\pipe\\viberag-${hash}`;
|
|
582
|
+
}
|
|
583
|
+
return path.join(this.projectRoot, '.viberag', 'daemon.sock');
|
|
584
|
+
}
|
|
585
|
+
/**
|
|
586
|
+
* Get the PID file path for this project.
|
|
587
|
+
*/
|
|
588
|
+
getPidPath() {
|
|
589
|
+
return path.join(this.projectRoot, '.viberag', 'daemon.pid');
|
|
590
|
+
}
|
|
591
|
+
/**
|
|
592
|
+
* Get the logger instance (for centralized error logging).
|
|
593
|
+
*/
|
|
594
|
+
getLogger() {
|
|
595
|
+
return this.logger;
|
|
596
|
+
}
|
|
597
|
+
/**
|
|
598
|
+
* Log a message.
|
|
599
|
+
*/
|
|
600
|
+
log(level, message) {
|
|
601
|
+
const prefix = '[Daemon]';
|
|
602
|
+
if (this.logger) {
|
|
603
|
+
this.logger[level](prefix, message);
|
|
604
|
+
}
|
|
605
|
+
if (level === 'error' || level === 'info') {
|
|
606
|
+
console.error(`${prefix} ${message}`);
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JSON-RPC 2.0 protocol types and helpers for daemon IPC.
|
|
3
|
+
*
|
|
4
|
+
* Protocol: Newline-delimited JSON over Unix socket.
|
|
5
|
+
* Each message is a complete JSON object followed by '\n'.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* JSON-RPC 2.0 request from client to daemon.
|
|
9
|
+
*/
|
|
10
|
+
export interface JsonRpcRequest {
|
|
11
|
+
jsonrpc: '2.0';
|
|
12
|
+
method: string;
|
|
13
|
+
params?: Record<string, unknown>;
|
|
14
|
+
id: number | string;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* JSON-RPC 2.0 response from daemon to client.
|
|
18
|
+
*/
|
|
19
|
+
export interface JsonRpcResponse {
|
|
20
|
+
jsonrpc: '2.0';
|
|
21
|
+
result?: unknown;
|
|
22
|
+
error?: JsonRpcError;
|
|
23
|
+
id: number | string | null;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* JSON-RPC 2.0 error object.
|
|
27
|
+
*/
|
|
28
|
+
export interface JsonRpcError {
|
|
29
|
+
code: number;
|
|
30
|
+
message: string;
|
|
31
|
+
data?: unknown;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Standard JSON-RPC 2.0 error codes and custom application codes.
|
|
35
|
+
*/
|
|
36
|
+
export declare const ErrorCodes: {
|
|
37
|
+
readonly PARSE_ERROR: -32700;
|
|
38
|
+
readonly INVALID_REQUEST: -32600;
|
|
39
|
+
readonly METHOD_NOT_FOUND: -32601;
|
|
40
|
+
readonly INVALID_PARAMS: -32602;
|
|
41
|
+
readonly INTERNAL_ERROR: -32603;
|
|
42
|
+
readonly NOT_INITIALIZED: -32001;
|
|
43
|
+
readonly INDEX_IN_PROGRESS: -32002;
|
|
44
|
+
readonly SHUTDOWN_IN_PROGRESS: -32003;
|
|
45
|
+
readonly CONNECTION_ERROR: -32004;
|
|
46
|
+
};
|
|
47
|
+
export type ErrorCode = (typeof ErrorCodes)[keyof typeof ErrorCodes];
|
|
48
|
+
/**
|
|
49
|
+
* Available daemon methods.
|
|
50
|
+
*/
|
|
51
|
+
export type DaemonMethod = 'search' | 'index' | 'indexAsync' | 'status' | 'watchStatus' | 'shutdown' | 'ping' | 'health';
|
|
52
|
+
/**
|
|
53
|
+
* Protocol version for client/daemon compatibility checking.
|
|
54
|
+
* Increment when making breaking changes to the protocol.
|
|
55
|
+
*/
|
|
56
|
+
export declare const PROTOCOL_VERSION = 1;
|
|
57
|
+
/**
|
|
58
|
+
* Parse a JSON-RPC request from a string.
|
|
59
|
+
* Returns the parsed request or throws an error.
|
|
60
|
+
*/
|
|
61
|
+
export declare function parseRequest(line: string): JsonRpcRequest;
|
|
62
|
+
/**
|
|
63
|
+
* Format a successful JSON-RPC response.
|
|
64
|
+
*/
|
|
65
|
+
export declare function formatResponse(id: number | string | null, result: unknown): string;
|
|
66
|
+
/**
|
|
67
|
+
* Format a JSON-RPC error response.
|
|
68
|
+
*/
|
|
69
|
+
export declare function formatError(id: number | string | null, code: ErrorCode, message: string, data?: unknown): string;
|
|
70
|
+
/**
|
|
71
|
+
* Error thrown when parsing JSON-RPC message fails.
|
|
72
|
+
*/
|
|
73
|
+
export declare class JsonRpcParseError extends Error {
|
|
74
|
+
constructor(message: string);
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Error class for JSON-RPC method errors.
|
|
78
|
+
* Use this in handlers to return structured errors.
|
|
79
|
+
*/
|
|
80
|
+
export declare class JsonRpcMethodError extends Error {
|
|
81
|
+
readonly code: ErrorCode;
|
|
82
|
+
readonly data?: unknown;
|
|
83
|
+
constructor(code: ErrorCode, message: string, data?: unknown);
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Buffer for accumulating partial messages.
|
|
87
|
+
* Messages are newline-delimited, so we need to buffer until we see '\n'.
|
|
88
|
+
*/
|
|
89
|
+
export declare class MessageBuffer {
|
|
90
|
+
private buffer;
|
|
91
|
+
/**
|
|
92
|
+
* Add data to buffer and extract complete messages.
|
|
93
|
+
* Returns array of complete message strings.
|
|
94
|
+
*/
|
|
95
|
+
append(data: string): string[];
|
|
96
|
+
/**
|
|
97
|
+
* Clear the buffer.
|
|
98
|
+
*/
|
|
99
|
+
clear(): void;
|
|
100
|
+
}
|