@meechi-ai/core 1.0.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 (116) hide show
  1. package/LICENSE +624 -0
  2. package/README.md +59 -0
  3. package/dist/components/CalendarView.d.ts +3 -0
  4. package/dist/components/CalendarView.js +72 -0
  5. package/dist/components/ChatInterface.d.ts +6 -0
  6. package/dist/components/ChatInterface.js +105 -0
  7. package/dist/components/FileExplorer.d.ts +9 -0
  8. package/dist/components/FileExplorer.js +757 -0
  9. package/dist/components/Icon.d.ts +9 -0
  10. package/dist/components/Icon.js +44 -0
  11. package/dist/components/SourceEditor.d.ts +13 -0
  12. package/dist/components/SourceEditor.js +50 -0
  13. package/dist/components/ThemeProvider.d.ts +5 -0
  14. package/dist/components/ThemeProvider.js +105 -0
  15. package/dist/components/ThemeSwitcher.d.ts +1 -0
  16. package/dist/components/ThemeSwitcher.js +16 -0
  17. package/dist/components/voice/VoiceInputArea.d.ts +14 -0
  18. package/dist/components/voice/VoiceInputArea.js +190 -0
  19. package/dist/components/voice/VoiceOverlay.d.ts +7 -0
  20. package/dist/components/voice/VoiceOverlay.js +71 -0
  21. package/dist/hooks/useMeechi.d.ts +16 -0
  22. package/dist/hooks/useMeechi.js +461 -0
  23. package/dist/hooks/useSync.d.ts +8 -0
  24. package/dist/hooks/useSync.js +87 -0
  25. package/dist/index.d.ts +14 -0
  26. package/dist/index.js +22 -0
  27. package/dist/lib/ai/embeddings.d.ts +15 -0
  28. package/dist/lib/ai/embeddings.js +128 -0
  29. package/dist/lib/ai/gpu-lock.d.ts +19 -0
  30. package/dist/lib/ai/gpu-lock.js +43 -0
  31. package/dist/lib/ai/llm.worker.d.ts +1 -0
  32. package/dist/lib/ai/llm.worker.js +7 -0
  33. package/dist/lib/ai/local-llm.d.ts +30 -0
  34. package/dist/lib/ai/local-llm.js +211 -0
  35. package/dist/lib/ai/manager.d.ts +20 -0
  36. package/dist/lib/ai/manager.js +51 -0
  37. package/dist/lib/ai/parsing.d.ts +12 -0
  38. package/dist/lib/ai/parsing.js +56 -0
  39. package/dist/lib/ai/prompts.d.ts +2 -0
  40. package/dist/lib/ai/prompts.js +2 -0
  41. package/dist/lib/ai/providers/gemini.d.ts +6 -0
  42. package/dist/lib/ai/providers/gemini.js +88 -0
  43. package/dist/lib/ai/providers/groq.d.ts +6 -0
  44. package/dist/lib/ai/providers/groq.js +42 -0
  45. package/dist/lib/ai/registry.d.ts +29 -0
  46. package/dist/lib/ai/registry.js +52 -0
  47. package/dist/lib/ai/tools.d.ts +2 -0
  48. package/dist/lib/ai/tools.js +106 -0
  49. package/dist/lib/ai/types.d.ts +22 -0
  50. package/dist/lib/ai/types.js +1 -0
  51. package/dist/lib/ai/worker.d.ts +1 -0
  52. package/dist/lib/ai/worker.js +60 -0
  53. package/dist/lib/audio/input.d.ts +13 -0
  54. package/dist/lib/audio/input.js +121 -0
  55. package/dist/lib/audio/stt.d.ts +13 -0
  56. package/dist/lib/audio/stt.js +119 -0
  57. package/dist/lib/audio/tts.d.ts +12 -0
  58. package/dist/lib/audio/tts.js +128 -0
  59. package/dist/lib/audio/vad.d.ts +18 -0
  60. package/dist/lib/audio/vad.js +117 -0
  61. package/dist/lib/colors.d.ts +16 -0
  62. package/dist/lib/colors.js +67 -0
  63. package/dist/lib/extensions.d.ts +35 -0
  64. package/dist/lib/extensions.js +24 -0
  65. package/dist/lib/hooks/use-voice-loop.d.ts +13 -0
  66. package/dist/lib/hooks/use-voice-loop.js +313 -0
  67. package/dist/lib/mcp/McpClient.d.ts +19 -0
  68. package/dist/lib/mcp/McpClient.js +42 -0
  69. package/dist/lib/mcp/McpRegistry.d.ts +47 -0
  70. package/dist/lib/mcp/McpRegistry.js +117 -0
  71. package/dist/lib/mcp/native/GroqVoiceNative.d.ts +21 -0
  72. package/dist/lib/mcp/native/GroqVoiceNative.js +29 -0
  73. package/dist/lib/mcp/native/LocalSyncNative.d.ts +19 -0
  74. package/dist/lib/mcp/native/LocalSyncNative.js +26 -0
  75. package/dist/lib/mcp/native/LocalVoiceNative.d.ts +19 -0
  76. package/dist/lib/mcp/native/LocalVoiceNative.js +27 -0
  77. package/dist/lib/mcp/native/MeechiNativeCore.d.ts +25 -0
  78. package/dist/lib/mcp/native/MeechiNativeCore.js +209 -0
  79. package/dist/lib/mcp/native/index.d.ts +10 -0
  80. package/dist/lib/mcp/native/index.js +10 -0
  81. package/dist/lib/mcp/types.d.ts +35 -0
  82. package/dist/lib/mcp/types.js +1 -0
  83. package/dist/lib/pdf.d.ts +10 -0
  84. package/dist/lib/pdf.js +142 -0
  85. package/dist/lib/settings.d.ts +48 -0
  86. package/dist/lib/settings.js +87 -0
  87. package/dist/lib/storage/db.d.ts +57 -0
  88. package/dist/lib/storage/db.js +45 -0
  89. package/dist/lib/storage/local.d.ts +28 -0
  90. package/dist/lib/storage/local.js +534 -0
  91. package/dist/lib/storage/migrate.d.ts +3 -0
  92. package/dist/lib/storage/migrate.js +122 -0
  93. package/dist/lib/storage/types.d.ts +66 -0
  94. package/dist/lib/storage/types.js +1 -0
  95. package/dist/lib/sync/client-drive.d.ts +9 -0
  96. package/dist/lib/sync/client-drive.js +69 -0
  97. package/dist/lib/sync/engine.d.ts +18 -0
  98. package/dist/lib/sync/engine.js +517 -0
  99. package/dist/lib/sync/google-drive.d.ts +52 -0
  100. package/dist/lib/sync/google-drive.js +183 -0
  101. package/dist/lib/sync/merge.d.ts +1 -0
  102. package/dist/lib/sync/merge.js +68 -0
  103. package/dist/lib/yjs/YjsProvider.d.ts +11 -0
  104. package/dist/lib/yjs/YjsProvider.js +33 -0
  105. package/dist/lib/yjs/graph.d.ts +11 -0
  106. package/dist/lib/yjs/graph.js +7 -0
  107. package/dist/lib/yjs/hooks.d.ts +7 -0
  108. package/dist/lib/yjs/hooks.js +37 -0
  109. package/dist/lib/yjs/store.d.ts +4 -0
  110. package/dist/lib/yjs/store.js +19 -0
  111. package/dist/lib/yjs/syncGraph.d.ts +1 -0
  112. package/dist/lib/yjs/syncGraph.js +38 -0
  113. package/dist/providers/theme-provider.d.ts +3 -0
  114. package/dist/providers/theme-provider.js +18 -0
  115. package/dist/tsconfig.lib.tsbuildinfo +1 -0
  116. package/package.json +69 -0
@@ -0,0 +1,313 @@
1
+ import { useState, useRef, useEffect, useCallback } from 'react';
2
+ import { AudioInputService } from '../audio/input';
3
+ import { VADService } from '../audio/vad';
4
+ import { TranscriberService } from '../audio/stt';
5
+ import { SynthesizerService } from '../audio/tts';
6
+ export function useVoiceLoop(sendMessage) {
7
+ const [state, setState] = useState('idle');
8
+ const stateRef = useRef('idle'); // Fix for stale closure in callbacks
9
+ const [transcript, setTranscript] = useState("");
10
+ const [vadProb, setVadProb] = useState(0);
11
+ // Sync ref with state
12
+ useEffect(() => {
13
+ stateRef.current = state;
14
+ }, [state]);
15
+ const audioInput = useRef(new AudioInputService());
16
+ const vad = useRef(new VADService());
17
+ const audioBuffer = useRef([]);
18
+ const audioChunksRef = useRef([]);
19
+ const isSpeechPrev = useRef(false);
20
+ // Audio Context for playback
21
+ const playbackCtx = useRef(null);
22
+ const playbackSource = useRef(null);
23
+ const chunksProcessedRef = useRef(0);
24
+ // STREAMING TTS STATE
25
+ const audioQueue = useRef([]);
26
+ const isPlayingRef = useRef(false);
27
+ const sentenceBuffer = useRef("");
28
+ const stopPlayback = useCallback(() => {
29
+ if (playbackSource.current) {
30
+ playbackSource.current.stop();
31
+ playbackSource.current = null;
32
+ }
33
+ // Clear Queue
34
+ audioQueue.current = [];
35
+ isPlayingRef.current = false;
36
+ sentenceBuffer.current = "";
37
+ setState('idle');
38
+ }, []);
39
+ const processTTSChunk = async (text) => {
40
+ if (!text.trim())
41
+ return;
42
+ // Strip Markdown for TTS (e.g. *italic*, **bold**, `code`)
43
+ // Also remove excessive punctuation runs
44
+ const cleanText = text
45
+ .replace(/[*_`~]/g, '') // Remove markdown symbols
46
+ .replace(/\[.*?\]/g, '') // Remove [instructions] if any leak
47
+ .replace(/\s+/g, ' ')
48
+ .trim();
49
+ if (!cleanText)
50
+ return;
51
+ try {
52
+ const rawAudio = await SynthesizerService.speak(cleanText);
53
+ if (rawAudio) {
54
+ audioQueue.current.push({ audio: rawAudio.audio, sampleRate: rawAudio.sampling_rate });
55
+ playNextInQueue();
56
+ }
57
+ }
58
+ catch (e) {
59
+ console.error("TTS Gen Error:", e);
60
+ }
61
+ };
62
+ const processAudio = async (text) => {
63
+ if (!text.trim())
64
+ return;
65
+ // PRE-FILTER: Ignore known hallucination strings from STT (Case-insensitive)
66
+ const lower = text.trim().toLowerCase();
67
+ if (lower === 'you' || // "You" is very common hallucination
68
+ /^\[.*\]$/.test(lower) || // [Music], [Silence]
69
+ /^\(.*\)$/.test(lower) || // (Music)
70
+ lower.includes('[music]') ||
71
+ lower.includes('[silence]') ||
72
+ // Common Whisper Hallucinations:
73
+ lower.includes('thank you for watching') ||
74
+ lower.includes('thanks for watching')) {
75
+ console.log("[VoiceLoop] Ignoring hallucination:", text);
76
+ setState('idle'); // Ensure we return to idle
77
+ return;
78
+ }
79
+ console.log("[VoiceLoop] Processing User Audio:", text);
80
+ setState('processing');
81
+ sentenceBuffer.current = "";
82
+ try {
83
+ // 1. Send to AI with STREAMING callback
84
+ const response = await sendMessage(text, (token) => {
85
+ // Check for interruption
86
+ if (stateRef.current === 'listening')
87
+ return; // Abort if user started talking again?
88
+ sentenceBuffer.current += token;
89
+ // Split by sentence endings (. ! ? \n)
90
+ // Regex lookbehind is not supported in all browsers, so use simple split
91
+ // Capture delimiters
92
+ // Note: This matches "Dr." as a split. Improved regex needed for "Dr." etc, but for now simple is ok.
93
+ // Heuristic: Ends with [.!?] AND (followed by space or newline) - logic applied to buffer
94
+ // Check if we have a full sentence in buffer
95
+ // Regex: Match anything ending in [.!?] followed by space or EOS
96
+ // We only split if we have enough content
97
+ // HALLUCINATION/NOISE FILTER
98
+ // If the *entire* buffer is just these common patterns, we suppress it.
99
+ // We only check this if probability is low, but better safe.
100
+ if (/^[\(\[\{]?(music|silence|sound|background|noise)[\)\]\}]?$/i.test(sentenceBuffer.current.trim())) {
101
+ console.log("[VoiceLoop] Filtered Hallucination:", sentenceBuffer.current);
102
+ sentenceBuffer.current = "";
103
+ return;
104
+ }
105
+ // FIRST CHUNK STRATEGY:
106
+ // To lower latency, we split the FIRST sentence on commas or small pauses (length)
107
+ const isFirstChunk = !isPlayingRef.current && audioQueue.current.length === 0;
108
+ const splitRegex = isFirstChunk
109
+ ? /([,;:.!?\n])\s+/ // Split on commas too for the first burst
110
+ : /([.!?\n])\s+/; // Standard sentence split
111
+ const minChars = isFirstChunk ? 30 : 100; // Allow smaller first chunk
112
+ if (splitRegex.test(sentenceBuffer.current) && sentenceBuffer.current.length > minChars) {
113
+ // Attempt to split
114
+ const match = sentenceBuffer.current.match(new RegExp(`^(.*?${splitRegex.source})`));
115
+ if (match) {
116
+ const chunk = match[0];
117
+ const remaining = sentenceBuffer.current.slice(chunk.length);
118
+ if (chunk.trim()) {
119
+ processTTSChunk(chunk.trim());
120
+ sentenceBuffer.current = remaining;
121
+ }
122
+ }
123
+ }
124
+ });
125
+ // 2. Flush remaining buffer
126
+ if (sentenceBuffer.current.trim()) {
127
+ await processTTSChunk(sentenceBuffer.current.trim());
128
+ }
129
+ console.log("[VoiceLoop] Generation Complete.");
130
+ // Wait for queue to drain?
131
+ // We need a loop to check if playing is done
132
+ const checkDone = setInterval(() => {
133
+ if (stateRef.current !== 'speaking' && stateRef.current !== 'processing') {
134
+ clearInterval(checkDone);
135
+ return;
136
+ }
137
+ if (!isPlayingRef.current && audioQueue.current.length === 0) {
138
+ clearInterval(checkDone);
139
+ setState('idle');
140
+ }
141
+ }, 200);
142
+ }
143
+ catch (e) {
144
+ console.error("Voice Loop Error:", e);
145
+ setState('idle');
146
+ }
147
+ };
148
+ const silenceChunksRef = useRef(0);
149
+ // 30 chunks * 512 samples / 16000 Hz ~= 1.0 second roughly.
150
+ // Let's use 1.2s to be safe for slow speakers.
151
+ // 1.2 / 0.032 = 37.5 ~ 40 chunks
152
+ const MAX_SILENCE_CHUNKS = 40;
153
+ // Track Playing State for UI
154
+ const [isPlaying, setIsPlaying] = useState(false);
155
+ // Sync Ref with State for internal logic
156
+ useEffect(() => {
157
+ setIsPlaying(isPlayingRef.current);
158
+ }, [isPlayingRef.current]); // This dependency might not trigger if ref mutates, we need to set state where we set ref.
159
+ // Helper to update playing state
160
+ const setPlayingState = (val) => {
161
+ isPlayingRef.current = val;
162
+ setIsPlaying(val);
163
+ };
164
+ // ... (stopPlayback updates)
165
+ // We need to update stopPlayback to use setPlayingState
166
+ // But since stopPlayback is memoized and we are inside the hook function body (recreated?), let's just update the internal logic below
167
+ // Process and Play next chunk in queue
168
+ const playNextInQueue = async () => {
169
+ if (isPlayingRef.current || audioQueue.current.length === 0) {
170
+ return;
171
+ }
172
+ const next = audioQueue.current.shift();
173
+ if (!next || !playbackCtx.current)
174
+ return;
175
+ setPlayingState(true);
176
+ setState('speaking');
177
+ try {
178
+ if (playbackCtx.current.state === 'suspended') {
179
+ await playbackCtx.current.resume();
180
+ }
181
+ const buffer = playbackCtx.current.createBuffer(1, next.audio.length, next.sampleRate);
182
+ buffer.copyToChannel(new Float32Array(next.audio), 0);
183
+ const source = playbackCtx.current.createBufferSource();
184
+ source.buffer = buffer;
185
+ source.connect(playbackCtx.current.destination);
186
+ source.start();
187
+ playbackSource.current = source;
188
+ source.onended = () => {
189
+ setPlayingState(false);
190
+ if (audioQueue.current.length > 0) {
191
+ playNextInQueue();
192
+ }
193
+ else {
194
+ // Queue empty.
195
+ }
196
+ };
197
+ }
198
+ catch (e) {
199
+ console.error("Playback Error:", e);
200
+ setPlayingState(false);
201
+ }
202
+ };
203
+ // Update stopPlayback
204
+ // We can't easily overwrite the previous useCallback in this 'replace' block without context.
205
+ // But we can just ensuring setPlayingState(false) is called where isPlayingRef.current = false was.
206
+ // I will replace start() logic mostly here.
207
+ const start = async () => {
208
+ if (!playbackCtx.current) {
209
+ playbackCtx.current = new AudioContext();
210
+ }
211
+ if (playbackCtx.current.state === 'suspended') {
212
+ await playbackCtx.current.resume();
213
+ }
214
+ await vad.current.init();
215
+ await audioInput.current.start(async (data) => {
216
+ // 1. VAD Check
217
+ let { isSpeech, probability } = await vad.current.process(data);
218
+ const maxAmp = Math.max(...data);
219
+ if (!isSpeech && maxAmp > 0.05) {
220
+ isSpeech = true;
221
+ probability = 0.8;
222
+ }
223
+ setVadProb(probability);
224
+ isSpeechPrev.current = isSpeech;
225
+ const currentState = stateRef.current;
226
+ if (isSpeech) {
227
+ // SPEECH DETECTED
228
+ silenceChunksRef.current = 0; // Reset silence counter
229
+ if (currentState === 'speaking') {
230
+ stopPlayback(); // Interruption
231
+ }
232
+ if (currentState !== 'listening' && currentState !== 'processing') {
233
+ setState('listening');
234
+ }
235
+ audioBuffer.current.push(data);
236
+ }
237
+ else if (currentState === 'listening') {
238
+ // SILENCE (DETECTED)
239
+ // We are in listening mode, but VAD says silence.
240
+ // We do NOT stop immediately. We accumulate silence.
241
+ silenceChunksRef.current++;
242
+ audioBuffer.current.push(data); // Keep recording the "silence" (sentence pause)
243
+ if (silenceChunksRef.current > MAX_SILENCE_CHUNKS) {
244
+ // TIMEOUT: Speech Actually Ended
245
+ silenceChunksRef.current = 0;
246
+ const length = audioBuffer.current.reduce((acc, chunk) => acc + chunk.length, 0);
247
+ // IGNORE SHORT CLICKS (Under 0.5s of effective content?)
248
+ // Since we included silence, the total length is huge now.
249
+ // We should check the length MINUS the silence tail?
250
+ // Or just rely on the fact that if we hit the timeout, it was probably speech + silence.
251
+ // But what if it was JUST silence (false trigger)?
252
+ // Check total length: 40 chunks * 512 = 20k samples.
253
+ // If we have < 30k total samples, it means we ONLY recorded the silence tail + tiny blip.
254
+ if (length < 24000) { // < 1.5s total (mostly silence)
255
+ console.log("[VoiceLoop] Speech too short (mostly silence), ignoring. Length:", length);
256
+ audioBuffer.current = [];
257
+ setState('idle');
258
+ return;
259
+ }
260
+ console.log(`[VoiceLoop] Speech Ended (Silence Timeout). Processing...`);
261
+ const fullAudio = new Float32Array(length);
262
+ let offset = 0;
263
+ for (const chunk of audioBuffer.current) {
264
+ fullAudio.set(chunk, offset);
265
+ offset += chunk.length;
266
+ }
267
+ audioBuffer.current = [];
268
+ stateRef.current = 'processing';
269
+ setState('processing');
270
+ console.log("[VoiceLoop] Transcribing...");
271
+ let text = "";
272
+ try {
273
+ text = await TranscriberService.transcribe(fullAudio);
274
+ console.log(`[VoiceLoop] Transcribed Text: "${text}"`);
275
+ setTranscript(text);
276
+ }
277
+ catch (err) {
278
+ console.error("[VoiceLoop] Transcription Error:", err);
279
+ setTranscript("");
280
+ }
281
+ if (text.trim()) {
282
+ await processAudio(text);
283
+ }
284
+ else {
285
+ stateRef.current = 'idle';
286
+ setState('idle');
287
+ }
288
+ }
289
+ }
290
+ });
291
+ };
292
+ const stop = () => {
293
+ audioInput.current.stop();
294
+ stopPlayback();
295
+ setState('idle');
296
+ };
297
+ // Kept for backward compatibility if manual playback is needed
298
+ const playResponse = async (text) => {
299
+ stopPlayback();
300
+ await processTTSChunk(text);
301
+ };
302
+ return {
303
+ start,
304
+ stop,
305
+ stopPlayback,
306
+ state,
307
+ vadProb,
308
+ transcript,
309
+ playResponse,
310
+ isPlaying,
311
+ getAnalyser: () => audioInput.current.getAnalyser()
312
+ };
313
+ }
@@ -0,0 +1,19 @@
1
+ /**
2
+ * McpClient - The MCP Host/Client for Meechi
3
+ *
4
+ * This class initializes the MCP registry with native (built-in) servers.
5
+ * It acts as the "host" that manages tool providers.
6
+ *
7
+ * Architecture:
8
+ * - Native servers: In-process TypeScript classes (MeechiNativeCore, etc.)
9
+ * - External servers: Real MCP protocol via SDK (coming in future releases)
10
+ */
11
+ export declare class McpClient {
12
+ constructor();
13
+ getTools(): Promise<any[]>;
14
+ executeTool(name: string, args: any): Promise<any>;
15
+ }
16
+ export declare const mcpClient: McpClient;
17
+ export { mcpRegistry } from './McpRegistry';
18
+ export * from './types';
19
+ export * from './native';
@@ -0,0 +1,42 @@
1
+ import { mcpRegistry } from './McpRegistry';
2
+ import { MeechiNativeCore, GroqVoiceNative, LocalVoiceNative, LocalSyncNative } from './native';
3
+ /**
4
+ * McpClient - The MCP Host/Client for Meechi
5
+ *
6
+ * This class initializes the MCP registry with native (built-in) servers.
7
+ * It acts as the "host" that manages tool providers.
8
+ *
9
+ * Architecture:
10
+ * - Native servers: In-process TypeScript classes (MeechiNativeCore, etc.)
11
+ * - External servers: Real MCP protocol via SDK (coming in future releases)
12
+ */
13
+ export class McpClient {
14
+ constructor() {
15
+ // 1. Mandatory Core System (permanent)
16
+ mcpRegistry.registerServer(new MeechiNativeCore());
17
+ // 2. Pluggable Native Servers (can be activated/deactivated)
18
+ mcpRegistry.registerServer(new GroqVoiceNative());
19
+ mcpRegistry.registerServer(new LocalVoiceNative());
20
+ mcpRegistry.registerServer(new LocalSyncNative());
21
+ }
22
+ async getTools() {
23
+ const tools = await mcpRegistry.getAllTools();
24
+ return tools.map(t => ({
25
+ type: 'function',
26
+ function: {
27
+ name: t.name,
28
+ description: t.description,
29
+ parameters: t.inputSchema
30
+ }
31
+ }));
32
+ }
33
+ async executeTool(name, args) {
34
+ return await mcpRegistry.executeTool(name, args);
35
+ }
36
+ }
37
+ // Global instance for the app
38
+ export const mcpClient = new McpClient();
39
+ // Re-exports
40
+ export { mcpRegistry } from './McpRegistry';
41
+ export * from './types';
42
+ export * from './native';
@@ -0,0 +1,47 @@
1
+ import { McpTool, McpResource } from './types';
2
+ /**
3
+ * Basic interface for ANY MCP Server (Internal or External)
4
+ */
5
+ export interface McpConnector {
6
+ id: string;
7
+ name: string;
8
+ description: string;
9
+ isPermanent: boolean;
10
+ isAgenticMemory?: boolean;
11
+ getTools(): Promise<McpTool[]>;
12
+ getResources?(): Promise<McpResource[]>;
13
+ executeTool(name: string, args: any): Promise<any>;
14
+ getSystemInstructions?(): Promise<string>;
15
+ }
16
+ export type MeechiTier = 'tier1' | 'tier2' | 'tier3';
17
+ /**
18
+ * The Central Registry that manages "Slotted" MCP Servers.
19
+ */
20
+ export declare class McpRegistry {
21
+ private servers;
22
+ private activeSlots;
23
+ private activeMemories;
24
+ private tier;
25
+ private TIER_CONSTRAINTS;
26
+ constructor(tier?: MeechiTier);
27
+ setTier(tier: MeechiTier): void;
28
+ getMaxSlots(): number;
29
+ registerServer(server: McpConnector): void;
30
+ activateSlot(serverId: string): boolean;
31
+ deactivateSlot(serverId: string): void;
32
+ getAllTools(): Promise<McpTool[]>;
33
+ executeTool(name: string, args: any): Promise<any>;
34
+ getCombinedInstructions(): Promise<string>;
35
+ /**
36
+ * Gets instructions for a SPECIFIC agent (e.g. for Mode switching)
37
+ */
38
+ getAgentInstructions(agentId: string): Promise<string | null>;
39
+ getMarketplace(): {
40
+ id: string;
41
+ name: string;
42
+ description: string;
43
+ isActive: boolean;
44
+ isPermanent: boolean;
45
+ }[];
46
+ }
47
+ export declare const mcpRegistry: McpRegistry;
@@ -0,0 +1,117 @@
1
+ /**
2
+ * The Central Registry that manages "Slotted" MCP Servers.
3
+ */
4
+ export class McpRegistry {
5
+ constructor(tier = 'tier1') {
6
+ this.servers = new Map();
7
+ this.activeSlots = new Set();
8
+ this.activeMemories = new Set();
9
+ this.tier = 'tier1';
10
+ this.TIER_CONSTRAINTS = {
11
+ tier1: { maxSlots: 2, maxMemories: 5 },
12
+ tier2: { maxSlots: 10, maxMemories: 5 },
13
+ tier3: { maxSlots: -1, maxMemories: 5 } // Unlimited slots, 5 agents
14
+ };
15
+ this.tier = tier;
16
+ }
17
+ setTier(tier) {
18
+ this.tier = tier;
19
+ }
20
+ getMaxSlots() {
21
+ return this.TIER_CONSTRAINTS[this.tier].maxSlots;
22
+ }
23
+ registerServer(server) {
24
+ this.servers.set(server.id, server);
25
+ if (server.isPermanent) {
26
+ this.activeSlots.add(server.id);
27
+ }
28
+ }
29
+ activateSlot(serverId) {
30
+ const server = this.servers.get(serverId);
31
+ if (!server)
32
+ return false;
33
+ if (this.activeSlots.has(serverId))
34
+ return true;
35
+ const constraints = this.TIER_CONSTRAINTS[this.tier];
36
+ // Agentic Memory Specific Logic (Uses interface flag, not hardcoded IDs)
37
+ const isAgentic = server.isAgenticMemory === true;
38
+ if (isAgentic) {
39
+ if (this.activeMemories.size >= constraints.maxMemories) {
40
+ // Return false or throw error for UI to catch
41
+ throw new Error(`Memory limit reached. You can have up to ${constraints.maxMemories} active memories. Please deactivate one first.`);
42
+ }
43
+ this.activeMemories.add(serverId);
44
+ this.activeSlots.add(serverId);
45
+ return true;
46
+ }
47
+ // Generic Marketplace Logic
48
+ const nonPermanentCount = Array.from(this.activeSlots).filter(id => {
49
+ const s = this.servers.get(id);
50
+ return !(s === null || s === void 0 ? void 0 : s.isPermanent) && !(s === null || s === void 0 ? void 0 : s.isAgenticMemory);
51
+ }).length;
52
+ if (constraints.maxSlots !== -1 && nonPermanentCount >= constraints.maxSlots) {
53
+ throw new Error(`Slot limit reached (${constraints.maxSlots}). Please upgrade your plan for more slots.`);
54
+ }
55
+ this.activeSlots.add(serverId);
56
+ return true;
57
+ }
58
+ deactivateSlot(serverId) {
59
+ const server = this.servers.get(serverId);
60
+ if (server === null || server === void 0 ? void 0 : server.isPermanent)
61
+ return;
62
+ this.activeSlots.delete(serverId);
63
+ this.activeMemories.delete(serverId);
64
+ }
65
+ async getAllTools() {
66
+ let allTools = [];
67
+ for (const serverId of this.activeSlots) {
68
+ const server = this.servers.get(serverId);
69
+ if (server) {
70
+ const tools = await server.getTools();
71
+ allTools = [...allTools, ...tools];
72
+ }
73
+ }
74
+ return allTools;
75
+ }
76
+ async executeTool(name, args) {
77
+ // Find which server has this tool
78
+ for (const serverId of this.activeSlots) {
79
+ const server = this.servers.get(serverId);
80
+ if (server) {
81
+ const tools = await server.getTools();
82
+ if (tools.find(t => t.name === name)) {
83
+ return await server.executeTool(name, args);
84
+ }
85
+ }
86
+ }
87
+ throw new Error(`Tool ${name} not found in any active MCP slots.`);
88
+ }
89
+ async getCombinedInstructions() {
90
+ let instructions = "";
91
+ for (const serverId of this.activeMemories) {
92
+ const server = this.servers.get(serverId);
93
+ if (server === null || server === void 0 ? void 0 : server.getSystemInstructions) {
94
+ const instructionsText = await server.getSystemInstructions();
95
+ instructions += `\n\n--- PERSONA: ${server.name} ---\n${instructionsText}\n`;
96
+ }
97
+ }
98
+ return instructions;
99
+ }
100
+ /**
101
+ * Gets instructions for a SPECIFIC agent (e.g. for Mode switching)
102
+ */
103
+ async getAgentInstructions(agentId) {
104
+ const server = this.servers.get(agentId);
105
+ return (server === null || server === void 0 ? void 0 : server.getSystemInstructions) ? await server.getSystemInstructions() : null;
106
+ }
107
+ getMarketplace() {
108
+ return Array.from(this.servers.values()).map(s => ({
109
+ id: s.id,
110
+ name: s.name,
111
+ description: s.description,
112
+ isActive: this.activeSlots.has(s.id),
113
+ isPermanent: s.isPermanent
114
+ }));
115
+ }
116
+ }
117
+ export const mcpRegistry = new McpRegistry();
@@ -0,0 +1,21 @@
1
+ import { McpTool } from '../types';
2
+ import { McpConnector } from '../McpRegistry';
3
+ /**
4
+ * NATIVE SERVER: GroqVoiceNative
5
+ *
6
+ * Cloud Voice capability for Groq.
7
+ * Enables high-speed voice interaction when a Groq key is available.
8
+ *
9
+ * This is a "capability" native - it exposes UI features rather than AI tools.
10
+ */
11
+ export declare class GroqVoiceNative implements McpConnector {
12
+ id: string;
13
+ name: string;
14
+ description: string;
15
+ isPermanent: boolean;
16
+ private apiKey?;
17
+ constructor(apiKey?: string);
18
+ getTools(): Promise<McpTool[]>;
19
+ executeTool(name: string, args: any): Promise<any>;
20
+ isVoiceReady(): boolean;
21
+ }
@@ -0,0 +1,29 @@
1
+ /**
2
+ * NATIVE SERVER: GroqVoiceNative
3
+ *
4
+ * Cloud Voice capability for Groq.
5
+ * Enables high-speed voice interaction when a Groq key is available.
6
+ *
7
+ * This is a "capability" native - it exposes UI features rather than AI tools.
8
+ */
9
+ export class GroqVoiceNative {
10
+ constructor(apiKey) {
11
+ this.id = "native-groq-voice";
12
+ this.name = "Native Cloud Voice (Groq)";
13
+ this.description = "High-speed cloud voice interaction via Groq. Requires a valid API key.";
14
+ this.isPermanent = false;
15
+ this.apiKey = apiKey;
16
+ }
17
+ async getTools() {
18
+ // Voice doesn't necessarily expose tools to the AI,
19
+ // it exposes capabilities to the UI.
20
+ return [];
21
+ }
22
+ async executeTool(name, args) {
23
+ throw new Error(`Tool ${name} not implemented in GroqVoiceNative`);
24
+ }
25
+ // Custom capability for the UI to check
26
+ isVoiceReady() {
27
+ return !!this.apiKey;
28
+ }
29
+ }
@@ -0,0 +1,19 @@
1
+ import { McpTool } from '../types';
2
+ import { McpConnector } from '../McpRegistry';
3
+ /**
4
+ * NATIVE SERVER: LocalSyncNative
5
+ *
6
+ * Local Folder Synchronization capability.
7
+ * Allows the browser-based IndexedDB to be mirrored to a local file system folder.
8
+ *
9
+ * This is a "capability" native - it exposes sync features rather than AI tools.
10
+ */
11
+ export declare class LocalSyncNative implements McpConnector {
12
+ id: string;
13
+ name: string;
14
+ description: string;
15
+ isPermanent: boolean;
16
+ getTools(): Promise<McpTool[]>;
17
+ executeTool(name: string, args: any): Promise<any>;
18
+ isSyncCapable(): boolean;
19
+ }
@@ -0,0 +1,26 @@
1
+ /**
2
+ * NATIVE SERVER: LocalSyncNative
3
+ *
4
+ * Local Folder Synchronization capability.
5
+ * Allows the browser-based IndexedDB to be mirrored to a local file system folder.
6
+ *
7
+ * This is a "capability" native - it exposes sync features rather than AI tools.
8
+ */
9
+ export class LocalSyncNative {
10
+ constructor() {
11
+ this.id = "native-local-sync";
12
+ this.name = "Native Local Folder Sync";
13
+ this.description = "Mirrors your database to a local folder for backup and cross-browser sync.";
14
+ this.isPermanent = false;
15
+ }
16
+ async getTools() {
17
+ return [];
18
+ }
19
+ async executeTool(name, args) {
20
+ throw new Error(`Tool ${name} not implemented in LocalSyncNative`);
21
+ }
22
+ // Capability check for Storage UI
23
+ isSyncCapable() {
24
+ return true;
25
+ }
26
+ }
@@ -0,0 +1,19 @@
1
+ import { McpTool } from '../types';
2
+ import { McpConnector } from '../McpRegistry';
3
+ /**
4
+ * NATIVE SERVER: LocalVoiceNative
5
+ *
6
+ * Experimental Local Voice capability.
7
+ * Uses client-side ONNX models (Whisper/Kokoro) for private, offline interaction.
8
+ *
9
+ * This is a "capability" native - it exposes UI features rather than AI tools.
10
+ */
11
+ export declare class LocalVoiceNative implements McpConnector {
12
+ id: string;
13
+ name: string;
14
+ description: string;
15
+ isPermanent: boolean;
16
+ getTools(): Promise<McpTool[]>;
17
+ executeTool(name: string, args: any): Promise<any>;
18
+ isVoiceCapable(): boolean;
19
+ }