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.
Files changed (209) hide show
  1. package/README.md +2 -2
  2. package/dist/cli/app.d.ts +3 -0
  3. package/dist/cli/app.js +100 -102
  4. package/dist/cli/commands/handlers.d.ts +8 -6
  5. package/dist/cli/commands/handlers.js +90 -32
  6. package/dist/cli/commands/useCommands.d.ts +20 -0
  7. package/dist/cli/commands/useCommands.js +189 -0
  8. package/dist/cli/commands/useRagCommands.d.ts +2 -5
  9. package/dist/cli/commands/useRagCommands.js +11 -18
  10. package/dist/cli/components/InitWizard.js +66 -27
  11. package/dist/cli/components/McpSetupWizard.js +23 -4
  12. package/dist/cli/components/SlotRow.d.ts +22 -0
  13. package/dist/cli/components/SlotRow.js +55 -0
  14. package/dist/cli/components/StatusBar.d.ts +14 -0
  15. package/dist/cli/components/StatusBar.js +156 -0
  16. package/dist/cli/contexts/DaemonStatusContext.d.ts +38 -0
  17. package/dist/cli/contexts/DaemonStatusContext.js +106 -0
  18. package/dist/cli/hooks/useStatusPolling.d.ts +34 -0
  19. package/dist/cli/hooks/useStatusPolling.js +121 -0
  20. package/dist/cli/store/app/selectors.d.ts +87 -0
  21. package/dist/cli/store/app/selectors.js +28 -0
  22. package/dist/cli/store/app/slice.d.ts +1013 -0
  23. package/dist/cli/store/app/slice.js +112 -0
  24. package/dist/cli/store/hooks.d.ts +22 -0
  25. package/dist/cli/store/hooks.js +17 -0
  26. package/dist/cli/store/store.d.ts +17 -0
  27. package/dist/cli/store/store.js +18 -0
  28. package/dist/cli/store/wizard/selectors.d.ts +115 -0
  29. package/dist/cli/store/wizard/selectors.js +36 -0
  30. package/dist/cli/store/wizard/slice.d.ts +523 -0
  31. package/dist/cli/store/wizard/slice.js +119 -0
  32. package/dist/cli/utils/error-handler.d.ts +55 -0
  33. package/dist/cli/utils/error-handler.js +92 -0
  34. package/dist/client/auto-start.d.ts +42 -0
  35. package/dist/client/auto-start.js +250 -0
  36. package/dist/client/connection.d.ts +48 -0
  37. package/dist/client/connection.js +200 -0
  38. package/dist/client/index.d.ts +93 -0
  39. package/dist/client/index.js +209 -0
  40. package/dist/client/types.d.ts +105 -0
  41. package/dist/client/types.js +7 -0
  42. package/dist/common/components/SlotRow.d.ts +22 -0
  43. package/dist/common/components/SlotRow.js +53 -0
  44. package/dist/common/components/StatusBar.js +82 -31
  45. package/dist/common/types.d.ts +12 -13
  46. package/dist/daemon/handlers.d.ts +15 -0
  47. package/dist/daemon/handlers.js +157 -0
  48. package/dist/daemon/index.d.ts +21 -0
  49. package/dist/daemon/index.js +123 -0
  50. package/dist/daemon/lib/chunker/bounded-channel.d.ts +51 -0
  51. package/dist/daemon/lib/chunker/bounded-channel.js +138 -0
  52. package/dist/daemon/lib/chunker/index.d.ts +135 -0
  53. package/dist/daemon/lib/chunker/index.js +1370 -0
  54. package/dist/daemon/lib/chunker/types.d.ts +77 -0
  55. package/dist/daemon/lib/chunker/types.js +50 -0
  56. package/dist/daemon/lib/config.d.ts +73 -0
  57. package/dist/daemon/lib/config.js +149 -0
  58. package/dist/daemon/lib/constants.d.ts +75 -0
  59. package/dist/daemon/lib/constants.js +114 -0
  60. package/dist/daemon/lib/gitignore.d.ts +57 -0
  61. package/dist/daemon/lib/gitignore.js +246 -0
  62. package/dist/daemon/lib/logger.d.ts +51 -0
  63. package/dist/daemon/lib/logger.js +167 -0
  64. package/dist/daemon/lib/manifest.d.ts +58 -0
  65. package/dist/daemon/lib/manifest.js +116 -0
  66. package/dist/daemon/lib/merkle/diff.d.ts +32 -0
  67. package/dist/daemon/lib/merkle/diff.js +107 -0
  68. package/dist/daemon/lib/merkle/hash.d.ts +40 -0
  69. package/dist/daemon/lib/merkle/hash.js +180 -0
  70. package/dist/daemon/lib/merkle/index.d.ts +71 -0
  71. package/dist/daemon/lib/merkle/index.js +309 -0
  72. package/dist/daemon/lib/merkle/node.d.ts +55 -0
  73. package/dist/daemon/lib/merkle/node.js +82 -0
  74. package/dist/daemon/lifecycle.d.ts +50 -0
  75. package/dist/daemon/lifecycle.js +142 -0
  76. package/dist/daemon/owner.d.ts +175 -0
  77. package/dist/daemon/owner.js +609 -0
  78. package/dist/daemon/protocol.d.ts +100 -0
  79. package/dist/daemon/protocol.js +163 -0
  80. package/dist/daemon/providers/api-utils.d.ts +130 -0
  81. package/dist/daemon/providers/api-utils.js +248 -0
  82. package/dist/daemon/providers/gemini.d.ts +39 -0
  83. package/dist/daemon/providers/gemini.js +205 -0
  84. package/dist/daemon/providers/index.d.ts +14 -0
  85. package/dist/daemon/providers/index.js +14 -0
  86. package/dist/daemon/providers/local-4b.d.ts +28 -0
  87. package/dist/daemon/providers/local-4b.js +51 -0
  88. package/dist/daemon/providers/local.d.ts +36 -0
  89. package/dist/daemon/providers/local.js +166 -0
  90. package/dist/daemon/providers/mistral.d.ts +35 -0
  91. package/dist/daemon/providers/mistral.js +160 -0
  92. package/dist/daemon/providers/mock.d.ts +35 -0
  93. package/dist/daemon/providers/mock.js +69 -0
  94. package/dist/daemon/providers/openai.d.ts +41 -0
  95. package/dist/daemon/providers/openai.js +190 -0
  96. package/dist/daemon/providers/types.d.ts +68 -0
  97. package/dist/daemon/providers/types.js +6 -0
  98. package/dist/daemon/providers/validate.d.ts +30 -0
  99. package/dist/daemon/providers/validate.js +162 -0
  100. package/dist/daemon/server.d.ts +79 -0
  101. package/dist/daemon/server.js +293 -0
  102. package/dist/daemon/services/index.d.ts +11 -0
  103. package/dist/daemon/services/index.js +16 -0
  104. package/dist/daemon/services/indexing.d.ts +117 -0
  105. package/dist/daemon/services/indexing.js +573 -0
  106. package/dist/daemon/services/search/filters.d.ts +21 -0
  107. package/dist/daemon/services/search/filters.js +106 -0
  108. package/dist/daemon/services/search/fts.d.ts +32 -0
  109. package/dist/daemon/services/search/fts.js +61 -0
  110. package/dist/daemon/services/search/hybrid.d.ts +17 -0
  111. package/dist/daemon/services/search/hybrid.js +58 -0
  112. package/dist/daemon/services/search/index.d.ts +108 -0
  113. package/dist/daemon/services/search/index.js +417 -0
  114. package/dist/daemon/services/search/types.d.ts +126 -0
  115. package/dist/daemon/services/search/types.js +4 -0
  116. package/dist/daemon/services/search/vector.d.ts +25 -0
  117. package/dist/daemon/services/search/vector.js +44 -0
  118. package/dist/daemon/services/storage/index.d.ts +110 -0
  119. package/dist/daemon/services/storage/index.js +378 -0
  120. package/dist/daemon/services/storage/schema.d.ts +24 -0
  121. package/dist/daemon/services/storage/schema.js +51 -0
  122. package/dist/daemon/services/storage/types.d.ts +105 -0
  123. package/dist/daemon/services/storage/types.js +71 -0
  124. package/dist/daemon/services/types.d.ts +192 -0
  125. package/dist/daemon/services/types.js +53 -0
  126. package/dist/daemon/services/watcher.d.ts +98 -0
  127. package/dist/daemon/services/watcher.js +386 -0
  128. package/dist/daemon/state.d.ts +119 -0
  129. package/dist/daemon/state.js +161 -0
  130. package/dist/mcp/index.d.ts +1 -1
  131. package/dist/mcp/index.js +44 -60
  132. package/dist/mcp/server.d.ts +10 -14
  133. package/dist/mcp/server.js +75 -74
  134. package/dist/mcp/services/lazy-loader.d.ts +23 -0
  135. package/dist/mcp/services/lazy-loader.js +34 -0
  136. package/dist/mcp/warmup.d.ts +3 -3
  137. package/dist/mcp/warmup.js +39 -40
  138. package/dist/mcp/watcher.d.ts +5 -7
  139. package/dist/mcp/watcher.js +73 -64
  140. package/dist/rag/config/index.d.ts +2 -0
  141. package/dist/rag/constants.d.ts +30 -0
  142. package/dist/rag/constants.js +38 -0
  143. package/dist/rag/embeddings/api-utils.d.ts +121 -0
  144. package/dist/rag/embeddings/api-utils.js +259 -0
  145. package/dist/rag/embeddings/gemini.d.ts +4 -12
  146. package/dist/rag/embeddings/gemini.js +22 -72
  147. package/dist/rag/embeddings/index.d.ts +5 -3
  148. package/dist/rag/embeddings/index.js +5 -2
  149. package/dist/rag/embeddings/local-4b.d.ts +2 -2
  150. package/dist/rag/embeddings/local-4b.js +1 -1
  151. package/dist/rag/embeddings/local.d.ts +10 -3
  152. package/dist/rag/embeddings/local.js +58 -12
  153. package/dist/rag/embeddings/mistral.d.ts +4 -12
  154. package/dist/rag/embeddings/mistral.js +22 -72
  155. package/dist/rag/embeddings/mock.d.ts +35 -0
  156. package/dist/rag/embeddings/mock.js +69 -0
  157. package/dist/rag/embeddings/openai.d.ts +11 -13
  158. package/dist/rag/embeddings/openai.js +47 -75
  159. package/dist/rag/embeddings/types.d.ts +27 -1
  160. package/dist/rag/embeddings/validate.d.ts +9 -1
  161. package/dist/rag/embeddings/validate.js +17 -4
  162. package/dist/rag/index.d.ts +2 -2
  163. package/dist/rag/index.js +1 -1
  164. package/dist/rag/indexer/bounded-channel.d.ts +51 -0
  165. package/dist/rag/indexer/bounded-channel.js +138 -0
  166. package/dist/rag/indexer/indexer.d.ts +4 -14
  167. package/dist/rag/indexer/indexer.js +246 -169
  168. package/dist/rag/indexer/types.d.ts +1 -0
  169. package/dist/rag/logger/index.d.ts +22 -0
  170. package/dist/rag/logger/index.js +78 -1
  171. package/dist/rag/manifest/index.js +1 -2
  172. package/dist/rag/search/index.js +1 -1
  173. package/dist/rag/storage/schema.d.ts +2 -4
  174. package/dist/rag/storage/schema.js +3 -5
  175. package/dist/store/app/selectors.d.ts +87 -0
  176. package/dist/store/app/selectors.js +28 -0
  177. package/dist/store/app/slice.d.ts +1013 -0
  178. package/dist/store/app/slice.js +112 -0
  179. package/dist/store/hooks.d.ts +22 -0
  180. package/dist/store/hooks.js +17 -0
  181. package/dist/store/index.d.ts +12 -0
  182. package/dist/store/index.js +18 -0
  183. package/dist/store/indexing/listeners.d.ts +25 -0
  184. package/dist/store/indexing/listeners.js +46 -0
  185. package/dist/store/indexing/selectors.d.ts +195 -0
  186. package/dist/store/indexing/selectors.js +69 -0
  187. package/dist/store/indexing/slice.d.ts +309 -0
  188. package/dist/store/indexing/slice.js +113 -0
  189. package/dist/store/slot-progress/listeners.d.ts +23 -0
  190. package/dist/store/slot-progress/listeners.js +33 -0
  191. package/dist/store/slot-progress/selectors.d.ts +67 -0
  192. package/dist/store/slot-progress/selectors.js +36 -0
  193. package/dist/store/slot-progress/slice.d.ts +246 -0
  194. package/dist/store/slot-progress/slice.js +70 -0
  195. package/dist/store/store.d.ts +17 -0
  196. package/dist/store/store.js +18 -0
  197. package/dist/store/warmup/selectors.d.ts +109 -0
  198. package/dist/store/warmup/selectors.js +44 -0
  199. package/dist/store/warmup/slice.d.ts +137 -0
  200. package/dist/store/warmup/slice.js +72 -0
  201. package/dist/store/watcher/selectors.d.ts +115 -0
  202. package/dist/store/watcher/selectors.js +52 -0
  203. package/dist/store/watcher/slice.d.ts +269 -0
  204. package/dist/store/watcher/slice.js +100 -0
  205. package/dist/store/wizard/selectors.d.ts +115 -0
  206. package/dist/store/wizard/selectors.js +36 -0
  207. package/dist/store/wizard/slice.d.ts +523 -0
  208. package/dist/store/wizard/slice.js +119 -0
  209. 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
+ }