morpheus-cli 0.9.41 → 0.9.51
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/dist/channels/discord.js +1 -1
- package/dist/channels/telegram.js +1 -1
- package/dist/config/manager.js +8 -5
- package/dist/config/schemas.js +6 -3
- package/dist/runtime/__tests__/manual_start_verify.js +1 -1
- package/dist/runtime/__tests__/telephonist-tts.test.js +2 -2
- package/dist/runtime/hot-reload.js +5 -0
- package/dist/runtime/memory/sati/index.js +14 -2
- package/dist/runtime/memory/sati/repository.js +26 -28
- package/dist/runtime/memory/session-embedding-worker.js +16 -8
- package/dist/runtime/memory/sqlite.js +22 -6
- package/dist/runtime/migration.js +45 -0
- package/dist/runtime/oracle.js +3 -0
- package/dist/runtime/subagents/apoc.js +2 -2
- package/dist/runtime/subagents/link/link.js +2 -2
- package/dist/runtime/subagents/link/worker.js +2 -1
- package/dist/runtime/subagents/neo.js +5 -3
- package/dist/runtime/subagents/registry.js +8 -8
- package/dist/runtime/subagents/trinity/trinity.js +2 -2
- package/dist/runtime/subagents/utils.js +2 -0
- package/dist/runtime/telephonist.js +6 -6
- package/dist/runtime/tools/factory.js +50 -34
- package/dist/types/config.js +4 -3
- package/dist/ui/assets/{AuditDashboard-EvtKjy5H.js → AuditDashboard-DYsjEZ2h.js} +1 -1
- package/dist/ui/assets/{Chat-yptierPt.js → Chat-u6n5Fcmk.js} +4 -4
- package/dist/ui/assets/{Chronos-BA77MYbp.js → Chronos-Dgz6pUbe.js} +1 -1
- package/dist/ui/assets/{ConfirmationModal-NOZr-ipQ.js → ConfirmationModal-BCWFgigv.js} +1 -1
- package/dist/ui/assets/{Dashboard-ly1MJiB4.js → Dashboard-5ERDbl6k.js} +1 -1
- package/dist/ui/assets/{DeleteConfirmationModal-2HMraacH.js → DeleteConfirmationModal-O7K_CzNy.js} +1 -1
- package/dist/ui/assets/{Documents-C31fAm0Z.js → Documents-C6vFeWQC.js} +1 -1
- package/dist/ui/assets/{Logs-BiajoLAB.js → Logs-DZ820gmZ.js} +1 -1
- package/dist/ui/assets/{MCPManager-DS9jfiZT.js → MCPManager-BpscMusl.js} +1 -1
- package/dist/ui/assets/{ModelPresets-CxhKcalw.js → ModelPresets-B0OtJ3SG.js} +1 -1
- package/dist/ui/assets/{ModelPricing-CN8flHnP.js → ModelPricing-DSe7ibox.js} +1 -1
- package/dist/ui/assets/{Notifications-BfP1_CM3.js → Notifications-C4LoP0a6.js} +1 -1
- package/dist/ui/assets/{SatiMemories-Bk4_ubo7.js → SatiMemories-WJaz9vS2.js} +1 -1
- package/dist/ui/assets/{SessionAudit-D3E6QSQw.js → SessionAudit-BUFsqB5Q.js} +1 -1
- package/dist/ui/assets/{Settings-3VBK8muv.js → Settings-lHi1Jo5c.js} +5 -5
- package/dist/ui/assets/{Skills-Dp0_GwiW.js → Skills-jwgKeiMX.js} +1 -1
- package/dist/ui/assets/{Smiths-COTgI2R4.js → Smiths-DhytcrJX.js} +1 -1
- package/dist/ui/assets/{Tasks-COe4lIJ7.js → Tasks-Mn99HoyU.js} +1 -1
- package/dist/ui/assets/{TrinityDatabases-BEU4mmyW.js → TrinityDatabases-DDHblq8V.js} +1 -1
- package/dist/ui/assets/{UsageStats-BTmDeG2V.js → UsageStats-Czu8MFsZ.js} +1 -1
- package/dist/ui/assets/{WebhookManager-FQVyKyW-.js → WebhookManager-CKZOjGG3.js} +1 -1
- package/dist/ui/assets/{agents-B6e9N0QI.js → agents-fpKaFvxS.js} +1 -1
- package/dist/ui/assets/{audit-giQG2WRk.js → audit-BCFf71Ic.js} +1 -1
- package/dist/ui/assets/{chronos-sweaRcNj.js → chronos-_FyFfKFn.js} +1 -1
- package/dist/ui/assets/{config-CbUdj76n.js → config-sb-fswxE.js} +1 -1
- package/dist/ui/assets/{index-CRPT77Yo.css → index-Bn2nGQ5z.css} +1 -1
- package/dist/ui/assets/{index-yu2c4ry1.js → index-DZCpLD9L.js} +2 -2
- package/dist/ui/assets/{mcp-v64BBpUk.js → mcp-Dmcsh35T.js} +1 -1
- package/dist/ui/assets/{modelPresets-BaNh-gxn.js → modelPresets-CShvjJGA.js} +1 -1
- package/dist/ui/assets/{skills-ClRXBlVt.js → skills-CxeLa4Rc.js} +1 -1
- package/dist/ui/assets/{stats-nI-89hEX.js → stats-C7Z2ippX.js} +1 -1
- package/dist/ui/assets/{useCurrency-D5An8I2f.js → useCurrency-LdM_SjsH.js} +1 -1
- package/dist/ui/index.html +2 -2
- package/dist/ui/sw.js +1 -1
- package/package.json +1 -1
package/dist/channels/discord.js
CHANGED
|
@@ -331,7 +331,7 @@ export class DiscordAdapter {
|
|
|
331
331
|
this.display.startActivity('telephonist', 'Synthesizing TTS...');
|
|
332
332
|
const ttsApiKey = getUsableApiKey(ttsConfig.apiKey) ||
|
|
333
333
|
getUsableApiKey(config.audio.apiKey) ||
|
|
334
|
-
(config.llm.provider ===
|
|
334
|
+
(config.llm.provider === ttsConfig.provider
|
|
335
335
|
? getUsableApiKey(config.llm.api_key) : undefined);
|
|
336
336
|
const ttsResult = await this.ttsTelephonist.synthesize(response, ttsApiKey || '', ttsConfig.voice, ttsConfig.style_prompt);
|
|
337
337
|
ttsFilePath = ttsResult.filePath;
|
|
@@ -1921,7 +1921,7 @@ How can I assist you today?`;
|
|
|
1921
1921
|
this.display.startActivity('telephonist', 'Synthesizing TTS...');
|
|
1922
1922
|
const ttsApiKey = getUsableApiKey(ttsConfig.apiKey) ||
|
|
1923
1923
|
getUsableApiKey(config.audio.apiKey) ||
|
|
1924
|
-
(config.llm.provider ===
|
|
1924
|
+
(config.llm.provider === ttsConfig.provider
|
|
1925
1925
|
? getUsableApiKey(config.llm.api_key) : undefined);
|
|
1926
1926
|
const ttsResult = await this.ttsTelephonist.synthesize(response, ttsApiKey || '', ttsConfig.voice, ttsConfig.style_prompt);
|
|
1927
1927
|
ttsFilePath = ttsResult.filePath;
|
package/dist/config/manager.js
CHANGED
|
@@ -162,6 +162,7 @@ export class ConfigManager {
|
|
|
162
162
|
enabled_archived_sessions: resolveBoolean('MORPHEUS_SATI_ENABLED_ARCHIVED_SESSIONS', config.sati.enabled_archived_sessions, true),
|
|
163
163
|
similarity_threshold: resolveNumeric('MORPHEUS_SATI_SIMILARITY_THRESHOLD', config.sati.similarity_threshold, 0.9),
|
|
164
164
|
evaluation_interval: resolveNumeric('MORPHEUS_SATI_EVALUATION_INTERVAL', config.sati.evaluation_interval, 1),
|
|
165
|
+
chunk_size: resolveNumeric('MORPHEUS_SATI_CHUNK_SIZE', config.sati.chunk_size, 500),
|
|
165
166
|
};
|
|
166
167
|
}
|
|
167
168
|
// Apply precedence to Apoc config
|
|
@@ -282,13 +283,13 @@ export class ConfigManager {
|
|
|
282
283
|
}
|
|
283
284
|
// Apply precedence to audio config
|
|
284
285
|
const audioProvider = resolveString('MORPHEUS_AUDIO_PROVIDER', config.audio.provider, DEFAULT_CONFIG.audio.provider);
|
|
285
|
-
// AudioProvider uses '
|
|
286
|
-
const audioProviderForKey =
|
|
286
|
+
// AudioProvider uses 'gemini' which maps to LLMProvider 'gemini' (already compatible)
|
|
287
|
+
const audioProviderForKey = audioProvider;
|
|
287
288
|
// TTS config
|
|
288
289
|
const ttsDefaults = DEFAULT_CONFIG.audio.tts;
|
|
289
290
|
const ttsCfg = config.audio.tts;
|
|
290
291
|
const ttsProvider = resolveString('MORPHEUS_AUDIO_TTS_PROVIDER', ttsCfg?.provider, ttsDefaults.provider);
|
|
291
|
-
const ttsProviderForKey =
|
|
292
|
+
const ttsProviderForKey = ttsProvider;
|
|
292
293
|
const ttsConfig = {
|
|
293
294
|
enabled: resolveBoolean('MORPHEUS_AUDIO_TTS_ENABLED', ttsCfg?.enabled, ttsDefaults.enabled),
|
|
294
295
|
provider: ttsProvider,
|
|
@@ -449,14 +450,16 @@ export class ConfigManager {
|
|
|
449
450
|
getSatiConfig() {
|
|
450
451
|
if (this.config.sati) {
|
|
451
452
|
return {
|
|
452
|
-
memory_limit: 10,
|
|
453
|
+
memory_limit: 10,
|
|
454
|
+
chunk_size: 500,
|
|
453
455
|
...this.config.sati
|
|
454
456
|
};
|
|
455
457
|
}
|
|
456
458
|
// Fallback to main LLM config
|
|
457
459
|
return {
|
|
458
460
|
...this.config.llm,
|
|
459
|
-
memory_limit: 10
|
|
461
|
+
memory_limit: 10,
|
|
462
|
+
chunk_size: 500,
|
|
460
463
|
};
|
|
461
464
|
}
|
|
462
465
|
getApocConfig() {
|
package/dist/config/schemas.js
CHANGED
|
@@ -2,14 +2,14 @@ import { z } from 'zod';
|
|
|
2
2
|
import { DEFAULT_CONFIG } from '../types/config.js';
|
|
3
3
|
export const TtsConfigSchema = z.object({
|
|
4
4
|
enabled: z.boolean().default(false),
|
|
5
|
-
provider: z.enum(['openai', '
|
|
5
|
+
provider: z.enum(['openai', 'gemini']).default('gemini'),
|
|
6
6
|
model: z.string().min(1).default('gemini-2.5-flash-preview-tts'),
|
|
7
7
|
voice: z.string().min(1).default('Kore'),
|
|
8
8
|
apiKey: z.string().optional(),
|
|
9
9
|
style_prompt: z.string().optional(),
|
|
10
10
|
});
|
|
11
11
|
export const AudioConfigSchema = z.object({
|
|
12
|
-
provider: z.enum(['
|
|
12
|
+
provider: z.enum(['gemini', 'openai', 'openrouter', 'ollama']).default(DEFAULT_CONFIG.audio.provider),
|
|
13
13
|
model: z.string().min(1).default(DEFAULT_CONFIG.audio.model),
|
|
14
14
|
enabled: z.boolean().default(DEFAULT_CONFIG.audio.enabled),
|
|
15
15
|
apiKey: z.string().optional(),
|
|
@@ -30,8 +30,11 @@ export const LLMConfigSchema = z.object({
|
|
|
30
30
|
export const SatiConfigSchema = LLMConfigSchema.extend({
|
|
31
31
|
memory_limit: z.number().int().positive().optional(),
|
|
32
32
|
enabled_archived_sessions: z.boolean().default(true),
|
|
33
|
-
similarity_threshold: z.number().min(0).max(1).default(0.
|
|
33
|
+
similarity_threshold: z.number().min(0).max(1).default(0.7),
|
|
34
34
|
evaluation_interval: z.number().int().min(1).default(1),
|
|
35
|
+
chunk_size: z.number().int().positive().default(500),
|
|
36
|
+
session_chunk_limit: z.number().int().positive().optional(),
|
|
37
|
+
max_memory_tokens: z.number().int().positive().optional(),
|
|
35
38
|
});
|
|
36
39
|
export const ApocConfigSchema = LLMConfigSchema.extend({
|
|
37
40
|
working_dir: z.string().optional(),
|
|
@@ -12,7 +12,7 @@ const mockConfig = {
|
|
|
12
12
|
ui: { enabled: false, port: 3333 },
|
|
13
13
|
logging: { enabled: false, level: 'info', retention: '1d' },
|
|
14
14
|
audio: {
|
|
15
|
-
provider: '
|
|
15
|
+
provider: 'gemini',
|
|
16
16
|
model: 'gemini-2.5-flash-lite',
|
|
17
17
|
enabled: false,
|
|
18
18
|
maxDurationSeconds: 60,
|
|
@@ -12,10 +12,10 @@ describe('createTtsTelephonist', () => {
|
|
|
12
12
|
expect(telephonist).toBeDefined();
|
|
13
13
|
expect(typeof telephonist.synthesize).toBe('function');
|
|
14
14
|
});
|
|
15
|
-
it('returns an instance with synthesize() for
|
|
15
|
+
it('returns an instance with synthesize() for gemini provider', () => {
|
|
16
16
|
const telephonist = createTtsTelephonist({
|
|
17
17
|
enabled: true,
|
|
18
|
-
provider: '
|
|
18
|
+
provider: 'gemini',
|
|
19
19
|
model: 'gemini-2.5-flash',
|
|
20
20
|
voice: 'Kore',
|
|
21
21
|
});
|
|
@@ -9,6 +9,7 @@ import { ConfigManager } from '../config/manager.js';
|
|
|
9
9
|
import { DisplayManager } from './display.js';
|
|
10
10
|
import { SubagentRegistry } from './subagents/registry.js';
|
|
11
11
|
import { SkillRegistry } from './skills/index.js';
|
|
12
|
+
import { SQLiteChatMessageHistory } from './memory/sqlite.js';
|
|
12
13
|
let currentOracle = null;
|
|
13
14
|
/**
|
|
14
15
|
* Register the current Oracle instance for hot-reload.
|
|
@@ -51,6 +52,10 @@ export async function hotReloadConfig() {
|
|
|
51
52
|
const agentNames = SubagentRegistry.getAll().map(r => r.label);
|
|
52
53
|
reinitialized.push(...agentNames);
|
|
53
54
|
display.log(`Subagents reloaded: ${agentNames.join(', ')}`, { source: 'HotReload', level: 'info' });
|
|
55
|
+
// 4. Clear message cache to prevent stale tool_calls causing Gemini 400 errors
|
|
56
|
+
// This ensures the next chat() call re-fetches from DB with proper sanitization
|
|
57
|
+
SQLiteChatMessageHistory.clearCache();
|
|
58
|
+
display.log('Message cache cleared', { source: 'HotReload', level: 'info' });
|
|
54
59
|
return {
|
|
55
60
|
success: true,
|
|
56
61
|
reinitialized,
|
|
@@ -2,6 +2,7 @@ import { AIMessage } from "@langchain/core/messages";
|
|
|
2
2
|
import { SatiService } from "./service.js";
|
|
3
3
|
import { DisplayManager } from "../../display.js";
|
|
4
4
|
import { AuditRepository } from "../../audit/repository.js";
|
|
5
|
+
import { ConfigManager } from "../../../config/manager.js";
|
|
5
6
|
const display = DisplayManager.getInstance();
|
|
6
7
|
export class SatiMemoryMiddleware {
|
|
7
8
|
service;
|
|
@@ -38,10 +39,21 @@ export class SatiMemoryMiddleware {
|
|
|
38
39
|
display.log('No relevant memories found', { source: 'Sati' });
|
|
39
40
|
return null;
|
|
40
41
|
}
|
|
41
|
-
|
|
42
|
+
// Apply token budget: stop adding memories once we exceed the char limit.
|
|
43
|
+
const satiConfig = ConfigManager.getInstance().getSatiConfig();
|
|
44
|
+
const maxMemoryChars = (satiConfig.max_memory_tokens ?? 3000) * 4;
|
|
45
|
+
let usedChars = 0;
|
|
46
|
+
const budgetedMemories = result.relevant_memories.filter(m => {
|
|
47
|
+
const cost = m.summary.length + m.category.length + 20;
|
|
48
|
+
if (usedChars + cost > maxMemoryChars)
|
|
49
|
+
return false;
|
|
50
|
+
usedChars += cost;
|
|
51
|
+
return true;
|
|
52
|
+
});
|
|
53
|
+
const memoryContext = budgetedMemories
|
|
42
54
|
.map(m => `- [${m.category.toUpperCase()}] ${m.summary}`)
|
|
43
55
|
.join('\n');
|
|
44
|
-
display.log(`Retrieved ${result.relevant_memories.length} memories.`, { source: 'Sati' });
|
|
56
|
+
display.log(`Retrieved ${budgetedMemories.length}/${result.relevant_memories.length} memories (budget: ~${Math.round(usedChars / 4)} tokens).`, { source: 'Sati' });
|
|
45
57
|
return new AIMessage(`
|
|
46
58
|
### LONG-TERM MEMORY (SATI)
|
|
47
59
|
The following information was retrieved from previous sessions. Use it if relevant:
|
|
@@ -209,19 +209,24 @@ export class SatiRepository {
|
|
|
209
209
|
searchUnifiedVector(embedding, limit) {
|
|
210
210
|
if (!this.db)
|
|
211
211
|
return [];
|
|
212
|
-
const
|
|
212
|
+
const satiConfig = ConfigManager.getInstance().getSatiConfig();
|
|
213
|
+
const SIMILARITY_THRESHOLD = satiConfig.similarity_threshold ?? 0.7;
|
|
214
|
+
// Fetch a larger candidate pool so post-filtering still has enough results.
|
|
215
|
+
// weighted_score is used for ranking; cosine_similarity is used for threshold filtering.
|
|
216
|
+
const candidateLimit = limit * 10;
|
|
213
217
|
const stmt = this.db.prepare(`
|
|
214
218
|
SELECT *
|
|
215
219
|
FROM (
|
|
216
220
|
-- LONG TERM MEMORY
|
|
217
|
-
SELECT
|
|
221
|
+
SELECT
|
|
218
222
|
m.id as id,
|
|
219
223
|
m.summary as summary,
|
|
220
224
|
m.details as details,
|
|
221
225
|
m.category as category,
|
|
222
226
|
m.importance as importance,
|
|
223
227
|
'long_term' as source_type,
|
|
224
|
-
(1 - vec_distance_cosine(v.embedding, ?))
|
|
228
|
+
(1 - vec_distance_cosine(v.embedding, ?)) as cosine_similarity,
|
|
229
|
+
(1 - vec_distance_cosine(v.embedding, ?)) * 0.8 as weighted_score
|
|
225
230
|
FROM memory_vec v
|
|
226
231
|
JOIN memory_embedding_map map ON map.vec_rowid = v.rowid
|
|
227
232
|
JOIN long_term_memory m ON m.id = map.memory_id
|
|
@@ -230,42 +235,35 @@ export class SatiRepository {
|
|
|
230
235
|
UNION ALL
|
|
231
236
|
|
|
232
237
|
-- SESSION CHUNKS
|
|
233
|
-
SELECT
|
|
238
|
+
SELECT
|
|
234
239
|
sc.id as id,
|
|
235
240
|
sc.content as summary,
|
|
236
241
|
sc.content as details,
|
|
237
242
|
'session' as category,
|
|
238
243
|
'medium' as importance,
|
|
239
244
|
'session_chunk' as source_type,
|
|
240
|
-
(1 - vec_distance_cosine(v.embedding, ?))
|
|
245
|
+
(1 - vec_distance_cosine(v.embedding, ?)) as cosine_similarity,
|
|
246
|
+
(1 - vec_distance_cosine(v.embedding, ?)) * 0.2 as weighted_score
|
|
241
247
|
FROM session_vec v
|
|
242
248
|
JOIN session_embedding_map map ON map.vec_rowid = v.rowid
|
|
243
249
|
JOIN session_chunks sc ON sc.id = map.session_chunk_id
|
|
244
250
|
)
|
|
245
|
-
ORDER BY
|
|
251
|
+
ORDER BY weighted_score DESC
|
|
246
252
|
LIMIT ?
|
|
247
253
|
`);
|
|
248
|
-
const rows = stmt.all(new Float32Array(embedding), new Float32Array(embedding),
|
|
249
|
-
//
|
|
250
|
-
//
|
|
251
|
-
|
|
252
|
-
//
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
similarity: 1 - r.distance
|
|
262
|
-
}))
|
|
263
|
-
.sort((a, b) => b.similarity - a.similarity);
|
|
264
|
-
const filtered = ranked
|
|
265
|
-
.filter(r => r.similarity >= SIMILARITY_THRESHOLD)
|
|
266
|
-
.sort((a, b) => b.similarity - a.similarity);
|
|
267
|
-
this.display.log(`🧠 Unified vector search retornou ${filtered.length} resultados`, { source: 'Sati', level: 'debug' });
|
|
268
|
-
return filtered.map(r => ({
|
|
254
|
+
const rows = stmt.all(new Float32Array(embedding), new Float32Array(embedding), new Float32Array(embedding), new Float32Array(embedding), candidateLimit);
|
|
255
|
+
// Filter by raw cosine similarity (not the weighted score) so the threshold
|
|
256
|
+
// is independent of the long-term vs. session-chunk weighting.
|
|
257
|
+
const filtered = rows.filter(r => r.cosine_similarity >= SIMILARITY_THRESHOLD);
|
|
258
|
+
// Cap session chunks to avoid flooding from large archived sessions.
|
|
259
|
+
const chunkCap = satiConfig.session_chunk_limit ?? Math.ceil(limit * 0.3);
|
|
260
|
+
const longTerm = filtered.filter(r => r.source_type === 'long_term');
|
|
261
|
+
const chunks = filtered.filter(r => r.source_type === 'session_chunk');
|
|
262
|
+
const combined = [...longTerm, ...chunks.slice(0, chunkCap)]
|
|
263
|
+
.sort((a, b) => b.weighted_score - a.weighted_score)
|
|
264
|
+
.slice(0, limit);
|
|
265
|
+
this.display.log(`🧠 Unified vector search: ${filtered.length} acima do threshold (${longTerm.length} long-term, ${Math.min(chunks.length, chunkCap)} chunks) → ${combined.length} retornados`, { source: 'Sati', level: 'debug' });
|
|
266
|
+
return combined.map(r => ({
|
|
269
267
|
id: r.id,
|
|
270
268
|
summary: r.summary,
|
|
271
269
|
details: r.details,
|
|
@@ -374,7 +372,7 @@ export class SatiRepository {
|
|
|
374
372
|
this.display.log('🧵 Tentando fallback LIKE...', { source: 'Sati', level: 'debug' });
|
|
375
373
|
const likeStmt = this.db.prepare(`
|
|
376
374
|
SELECT * FROM long_term_memory
|
|
377
|
-
WHERE (summary LIKE ? OR details LIKE ?)
|
|
375
|
+
WHERE (summary LIKE ? OR details LIKE ?)
|
|
378
376
|
AND archived = 0
|
|
379
377
|
ORDER BY importance DESC, access_count DESC
|
|
380
378
|
LIMIT ?
|
|
@@ -8,6 +8,7 @@ const SHORT_DB_PATH = path.join(homedir(), '.morpheus', 'memory', 'short-memory.
|
|
|
8
8
|
const SATI_DB_PATH = path.join(homedir(), '.morpheus', 'memory', 'sati-memory.db');
|
|
9
9
|
const EMBEDDING_DIM = 384;
|
|
10
10
|
const BATCH_LIMIT = 5;
|
|
11
|
+
const PARALLEL_CHUNKS = 50;
|
|
11
12
|
export async function runSessionEmbeddingWorker() {
|
|
12
13
|
const display = DisplayManager.getInstance();
|
|
13
14
|
// display.log('🚀 Iniciando worker de embeddings de sessões...', { source: 'SessionEmbeddingWorker' });
|
|
@@ -62,15 +63,22 @@ export async function runSessionEmbeddingWorker() {
|
|
|
62
63
|
(session_chunk_id, vec_rowid)
|
|
63
64
|
VALUES (?, ?)
|
|
64
65
|
`);
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
const
|
|
68
|
-
|
|
69
|
-
|
|
66
|
+
display.log(` ↳ Processando ${chunks.length} chunks em paralelo (${PARALLEL_CHUNKS})...`, { source: 'SessionEmbeddingWorker' });
|
|
67
|
+
for (let i = 0; i < chunks.length; i += PARALLEL_CHUNKS) {
|
|
68
|
+
const batch = chunks.slice(i, i + PARALLEL_CHUNKS);
|
|
69
|
+
const batchEmbeddings = await Promise.all(batch.map(async (chunk) => {
|
|
70
|
+
const embedding = await embeddingService.generate(chunk.content);
|
|
71
|
+
return { chunkId: chunk.id, embedding };
|
|
72
|
+
}));
|
|
73
|
+
for (const { chunkId, embedding } of batchEmbeddings) {
|
|
74
|
+
if (!embedding || embedding.length !== EMBEDDING_DIM) {
|
|
75
|
+
throw new Error(`Embedding inválido. Esperado ${EMBEDDING_DIM}, recebido ${embedding?.length}`);
|
|
76
|
+
}
|
|
77
|
+
const result = insertVec.run(new Float32Array(embedding));
|
|
78
|
+
const vecRowId = result.lastInsertRowid;
|
|
79
|
+
insertMap.run(chunkId, vecRowId);
|
|
70
80
|
}
|
|
71
|
-
|
|
72
|
-
const vecRowId = result.lastInsertRowid;
|
|
73
|
-
insertMap.run(chunk.id, vecRowId);
|
|
81
|
+
display.log(` ↳ Batch ${Math.floor(i / PARALLEL_CHUNKS) + 1}/${Math.ceil(chunks.length / PARALLEL_CHUNKS)} concluído`, { source: 'SessionEmbeddingWorker' });
|
|
74
82
|
}
|
|
75
83
|
// ✅ finalizar sessão
|
|
76
84
|
shortDb.prepare(`
|
|
@@ -3,6 +3,7 @@ import { HumanMessage, AIMessage, SystemMessage, ToolMessage } from "@langchain/
|
|
|
3
3
|
import Database from "better-sqlite3";
|
|
4
4
|
import fs from "fs-extra";
|
|
5
5
|
import * as path from "path";
|
|
6
|
+
import { ConfigManager } from "../../config/manager.js";
|
|
6
7
|
import { PATHS } from "../../config/paths.js";
|
|
7
8
|
import { randomUUID } from 'crypto';
|
|
8
9
|
import { DisplayManager } from "../display.js";
|
|
@@ -39,6 +40,10 @@ export class SQLiteChatMessageHistory extends BaseListChatMessageHistory {
|
|
|
39
40
|
static invalidateCacheForSession(sessionId) {
|
|
40
41
|
SQLiteChatMessageHistory._cache.delete(sessionId);
|
|
41
42
|
}
|
|
43
|
+
/** Clear the entire message cache. Called during hot-reload to prevent stale tool_calls. */
|
|
44
|
+
static clearCache() {
|
|
45
|
+
SQLiteChatMessageHistory._cache.clear();
|
|
46
|
+
}
|
|
42
47
|
/** Prepend new messages (newest-first order) to an existing cache entry. */
|
|
43
48
|
static _appendToCache(sessionId, newMessages) {
|
|
44
49
|
if (!sessionId || newMessages.length === 0)
|
|
@@ -201,11 +206,11 @@ export class SQLiteChatMessageHistory extends BaseListChatMessageHistory {
|
|
|
201
206
|
('openai', 'gpt-3.5-turbo', 0.5, 1.5),
|
|
202
207
|
('openai', 'o1', 15.0, 60.0),
|
|
203
208
|
('openai', 'o1-mini', 3.0, 12.0),
|
|
204
|
-
('
|
|
205
|
-
('
|
|
206
|
-
('
|
|
207
|
-
('
|
|
208
|
-
('
|
|
209
|
+
('gemini', 'gemini-2.5-flash', 0.15, 0.6),
|
|
210
|
+
('gemini', 'gemini-2.5-flash-lite', 0.075, 0.3),
|
|
211
|
+
('gemini', 'gemini-2.0-flash', 0.1, 0.4),
|
|
212
|
+
('gemini', 'gemini-1.5-pro', 1.25, 5.0),
|
|
213
|
+
('gemini', 'gemini-1.5-flash', 0.075, 0.3);
|
|
209
214
|
|
|
210
215
|
CREATE TABLE IF NOT EXISTS model_presets (
|
|
211
216
|
id TEXT PRIMARY KEY,
|
|
@@ -281,6 +286,17 @@ export class SQLiteChatMessageHistory extends BaseListChatMessageHistory {
|
|
|
281
286
|
catch (error) {
|
|
282
287
|
console.warn(`[SQLite] model_pricing migration failed: ${error}`);
|
|
283
288
|
}
|
|
289
|
+
// Migration: rename 'google' provider to 'gemini' for consistency
|
|
290
|
+
// This ensures existing model_pricing records work with the unified 'gemini' provider
|
|
291
|
+
try {
|
|
292
|
+
const result = this.db.prepare("UPDATE model_pricing SET provider = 'gemini' WHERE provider = 'google'").run();
|
|
293
|
+
if (result.changes > 0) {
|
|
294
|
+
console.log(`[SQLite] Migrated ${result.changes} model_pricing records from 'google' to 'gemini'`);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
catch (error) {
|
|
298
|
+
console.warn(`[SQLite] google->gemini migration failed: ${error}`);
|
|
299
|
+
}
|
|
284
300
|
// Ensure model_presets table exists for databases created before this feature
|
|
285
301
|
try {
|
|
286
302
|
this.db.exec(`
|
|
@@ -1031,7 +1047,7 @@ export class SQLiteChatMessageHistory extends BaseListChatMessageHistory {
|
|
|
1031
1047
|
);
|
|
1032
1048
|
CREATE INDEX IF NOT EXISTS idx_session_chunks_session_id ON session_chunks(session_id);
|
|
1033
1049
|
`);
|
|
1034
|
-
const chunks = this.chunkText(sessionText);
|
|
1050
|
+
const chunks = this.chunkText(sessionText, ConfigManager.getInstance().getSatiConfig().chunk_size ?? 500);
|
|
1035
1051
|
const now = Date.now();
|
|
1036
1052
|
const insert = dbSati.prepare(`
|
|
1037
1053
|
INSERT INTO session_chunks (id, session_id, chunk_index, content, created_at)
|
|
@@ -29,6 +29,8 @@ export async function migrateConfigFile() {
|
|
|
29
29
|
await migrateContextWindow();
|
|
30
30
|
// Migrate santi -> sati
|
|
31
31
|
await migrateSantiToSati();
|
|
32
|
+
// Migrate google -> gemini in audio and tts providers
|
|
33
|
+
await migrateGoogleToGemini();
|
|
32
34
|
}
|
|
33
35
|
/**
|
|
34
36
|
* Migrates memory.limit to llm.context_window
|
|
@@ -118,3 +120,46 @@ async function migrateSantiToSati() {
|
|
|
118
120
|
});
|
|
119
121
|
}
|
|
120
122
|
}
|
|
123
|
+
/**
|
|
124
|
+
* Migrates audio.provider and audio.tts.provider from 'google' to 'gemini'
|
|
125
|
+
* Ensures consistency across the system (LLMs and audio use 'gemini' as provider)
|
|
126
|
+
*/
|
|
127
|
+
async function migrateGoogleToGemini() {
|
|
128
|
+
const display = DisplayManager.getInstance();
|
|
129
|
+
const configPath = PATHS.config;
|
|
130
|
+
try {
|
|
131
|
+
if (!await fs.pathExists(configPath)) {
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
const configContent = await fs.readFile(configPath, 'utf8');
|
|
135
|
+
const config = yaml.load(configContent);
|
|
136
|
+
// Check if migration is needed
|
|
137
|
+
const needsMigration = config?.audio?.provider === 'google' ||
|
|
138
|
+
config?.audio?.tts?.provider === 'google';
|
|
139
|
+
if (!needsMigration) {
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
// Create backup before migration
|
|
143
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
144
|
+
const backupPath = `${configPath}.backup-${timestamp}`;
|
|
145
|
+
await fs.copy(configPath, backupPath);
|
|
146
|
+
display.log(`Created config backup: ${backupPath}`, { source: 'Migration', level: 'info' });
|
|
147
|
+
// Perform migration
|
|
148
|
+
if (config?.audio?.provider === 'google') {
|
|
149
|
+
config.audio.provider = 'gemini';
|
|
150
|
+
}
|
|
151
|
+
if (config?.audio?.tts?.provider === 'google') {
|
|
152
|
+
config.audio.tts.provider = 'gemini';
|
|
153
|
+
}
|
|
154
|
+
// Write migrated config
|
|
155
|
+
const migratedYaml = yaml.dump(config);
|
|
156
|
+
await fs.writeFile(configPath, migratedYaml, 'utf8');
|
|
157
|
+
display.log('Migrated audio providers: google → gemini', { source: 'Migration', level: 'info' });
|
|
158
|
+
}
|
|
159
|
+
catch (error) {
|
|
160
|
+
display.log(`Config migration (google->gemini) failed: ${error.message}`, {
|
|
161
|
+
source: 'Migration',
|
|
162
|
+
level: 'warning'
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
}
|
package/dist/runtime/oracle.js
CHANGED
|
@@ -765,5 +765,8 @@ Use it to inform your response and tool selection (if needed), but do not assume
|
|
|
765
765
|
]);
|
|
766
766
|
await SubagentRegistry.reloadAll();
|
|
767
767
|
this.display.log(`Oracle and subagent tools reloaded`, { source: 'Oracle' });
|
|
768
|
+
// Clear message cache to prevent stale tool_calls from causing Gemini 400 errors
|
|
769
|
+
SQLiteChatMessageHistory.clearCache();
|
|
770
|
+
this.display.log(`Message cache cleared after tool reload`, { source: 'Oracle' });
|
|
768
771
|
}
|
|
769
772
|
}
|
|
@@ -73,8 +73,8 @@ Use this tool when the user asks for ANY of the following:
|
|
|
73
73
|
delegateToolName: 'apoc_delegate', emoji: '🧑🔬', color: 'amber',
|
|
74
74
|
description: 'Filesystem, shell & browser',
|
|
75
75
|
colorClass: 'text-amber-600 dark:text-amber-400',
|
|
76
|
-
bgClass: 'bg-amber-50 dark:bg-amber-900/
|
|
77
|
-
badgeClass: 'bg-amber-100 text-amber-700 dark:bg-amber-
|
|
76
|
+
bgClass: 'bg-amber-50 dark:bg-amber-900/30',
|
|
77
|
+
badgeClass: 'bg-amber-100 text-amber-700 dark:bg-amber-800 dark:text-amber-200',
|
|
78
78
|
instance: Apoc.instance,
|
|
79
79
|
hasDynamicDescription: true,
|
|
80
80
|
isMultiInstance: false,
|
|
@@ -64,8 +64,8 @@ export class Link {
|
|
|
64
64
|
delegateToolName: 'link_delegate', emoji: '🕵️♂️', color: 'indigo',
|
|
65
65
|
description: 'Document search & RAG',
|
|
66
66
|
colorClass: 'text-indigo-600 dark:text-indigo-400',
|
|
67
|
-
bgClass: 'bg-indigo-50 dark:bg-indigo-900/
|
|
68
|
-
badgeClass: 'bg-indigo-100 text-indigo-700 dark:bg-indigo-
|
|
67
|
+
bgClass: 'bg-indigo-50 dark:bg-indigo-900/30',
|
|
68
|
+
badgeClass: 'bg-indigo-100 text-indigo-700 dark:bg-indigo-800 dark:text-indigo-200',
|
|
69
69
|
instance: Link.instance,
|
|
70
70
|
hasDynamicDescription: true,
|
|
71
71
|
isMultiInstance: false,
|
|
@@ -294,13 +294,13 @@ export class LinkWorker {
|
|
|
294
294
|
}
|
|
295
295
|
/**
|
|
296
296
|
* Generate embeddings for chunks using Sati's EmbeddingService.
|
|
297
|
+
* Processes chunks in parallel batches for better performance.
|
|
297
298
|
*/
|
|
298
299
|
async generateEmbeddings(chunks) {
|
|
299
300
|
if (!this.embeddingService) {
|
|
300
301
|
this.embeddingService = await EmbeddingService.getInstance();
|
|
301
302
|
}
|
|
302
303
|
const embeddings = [];
|
|
303
|
-
// Process in batches to avoid memory issues
|
|
304
304
|
const batchSize = 50;
|
|
305
305
|
for (let i = 0; i < chunks.length; i += batchSize) {
|
|
306
306
|
const batch = chunks.slice(i, i + batchSize);
|
|
@@ -309,6 +309,7 @@ export class LinkWorker {
|
|
|
309
309
|
return { chunk_id: chunk.id, embedding };
|
|
310
310
|
}));
|
|
311
311
|
embeddings.push(...batchEmbeddings);
|
|
312
|
+
this.display.log(`Generated embeddings: batch ${Math.floor(i / batchSize) + 1}/${Math.ceil(chunks.length / batchSize)} (${embeddings.length}/${chunks.length})`, { source: 'Link', level: 'debug' });
|
|
312
313
|
}
|
|
313
314
|
// Store embeddings in database
|
|
314
315
|
this.repository.createEmbeddings(embeddings);
|
|
@@ -67,8 +67,8 @@ export class Neo {
|
|
|
67
67
|
delegateToolName: 'neo_delegate', emoji: '🥷', color: 'violet',
|
|
68
68
|
description: 'MCP tool orchestration',
|
|
69
69
|
colorClass: 'text-violet-600 dark:text-violet-400',
|
|
70
|
-
bgClass: 'bg-violet-50 dark:bg-violet-900/
|
|
71
|
-
badgeClass: 'bg-purple-100 text-purple-700 dark:bg-purple-
|
|
70
|
+
bgClass: 'bg-violet-50 dark:bg-violet-900/30',
|
|
71
|
+
badgeClass: 'bg-purple-100 text-purple-700 dark:bg-purple-800 dark:text-purple-200',
|
|
72
72
|
instance: Neo.instance,
|
|
73
73
|
hasDynamicDescription: true,
|
|
74
74
|
isMultiInstance: false,
|
|
@@ -161,8 +161,10 @@ ${context ? `Context:\n${context}` : ""}
|
|
|
161
161
|
const stepCount = response.messages.filter((m) => m instanceof AIMessage).length;
|
|
162
162
|
const targetSession = sessionId ?? Neo.currentSessionId ?? "neo";
|
|
163
163
|
await persistAgentMessage('neo', content, neoConfig, targetSession, rawUsage, durationMs);
|
|
164
|
+
// MCP tools are already audited inline by instrumentMcpTool (with timing + args/result).
|
|
165
|
+
// Only emit audit events here for internal Morpheus tools (not MCP tools).
|
|
164
166
|
emitToolAuditEvents(response.messages.slice(inputCount), targetSession, 'neo', {
|
|
165
|
-
defaultEventType:
|
|
167
|
+
defaultEventType: undefined, // skip MCP tools — audited by instrumentMcpTool
|
|
166
168
|
internalToolNames: MORPHEUS_TOOL_NAMES,
|
|
167
169
|
});
|
|
168
170
|
this.display.log("Neo task completed.", { source: "Neo" });
|
|
@@ -8,32 +8,32 @@ export const SYSTEM_AGENTS = [
|
|
|
8
8
|
delegateToolName: '', emoji: '🔮', color: 'blue',
|
|
9
9
|
description: 'Root orchestrator',
|
|
10
10
|
colorClass: 'text-blue-600 dark:text-blue-400',
|
|
11
|
-
bgClass: 'bg-blue-50 dark:bg-blue-900/
|
|
12
|
-
badgeClass: 'bg-blue-100 text-blue-700 dark:bg-blue-
|
|
11
|
+
bgClass: 'bg-blue-50 dark:bg-blue-900/30',
|
|
12
|
+
badgeClass: 'bg-blue-100 text-blue-700 dark:bg-blue-800 dark:text-blue-200',
|
|
13
13
|
},
|
|
14
14
|
{
|
|
15
15
|
agentKey: 'chronos', auditAgent: 'chronos', label: 'Chronos',
|
|
16
16
|
delegateToolName: '', emoji: '⏰', color: 'orange',
|
|
17
17
|
description: 'Temporal scheduler',
|
|
18
18
|
colorClass: 'text-orange-600 dark:text-orange-400',
|
|
19
|
-
bgClass: 'bg-orange-50 dark:bg-orange-900/
|
|
20
|
-
badgeClass: 'bg-orange-100 text-orange-700 dark:bg-orange-
|
|
19
|
+
bgClass: 'bg-orange-50 dark:bg-orange-900/30',
|
|
20
|
+
badgeClass: 'bg-orange-100 text-orange-700 dark:bg-orange-800 dark:text-orange-200',
|
|
21
21
|
},
|
|
22
22
|
{
|
|
23
23
|
agentKey: 'sati', auditAgent: 'sati', label: 'Sati',
|
|
24
24
|
delegateToolName: '', emoji: '🧠', color: 'emerald',
|
|
25
25
|
description: 'Long-term memory',
|
|
26
26
|
colorClass: 'text-emerald-600 dark:text-emerald-400',
|
|
27
|
-
bgClass: 'bg-emerald-50 dark:bg-emerald-900/
|
|
28
|
-
badgeClass: 'bg-emerald-100 text-emerald-700 dark:bg-emerald-
|
|
27
|
+
bgClass: 'bg-emerald-50 dark:bg-emerald-900/30',
|
|
28
|
+
badgeClass: 'bg-emerald-100 text-emerald-700 dark:bg-emerald-800 dark:text-emerald-200',
|
|
29
29
|
},
|
|
30
30
|
{
|
|
31
31
|
agentKey: 'telephonist', auditAgent: 'telephonist', label: 'Telephonist',
|
|
32
32
|
delegateToolName: '', emoji: '📞', color: 'rose',
|
|
33
33
|
description: 'Audio transcription',
|
|
34
34
|
colorClass: 'text-rose-600 dark:text-rose-400',
|
|
35
|
-
bgClass: 'bg-rose-50 dark:bg-rose-900/
|
|
36
|
-
badgeClass: 'bg-rose-100 text-rose-700 dark:bg-rose-
|
|
35
|
+
bgClass: 'bg-rose-50 dark:bg-rose-900/30',
|
|
36
|
+
badgeClass: 'bg-rose-100 text-rose-700 dark:bg-rose-800 dark:text-rose-200',
|
|
37
37
|
},
|
|
38
38
|
];
|
|
39
39
|
/**
|
|
@@ -58,8 +58,8 @@ export class Trinity {
|
|
|
58
58
|
delegateToolName: 'trinity_delegate', emoji: '👩💻', color: 'teal',
|
|
59
59
|
description: 'Database specialist',
|
|
60
60
|
colorClass: 'text-teal-600 dark:text-teal-400',
|
|
61
|
-
bgClass: 'bg-teal-50 dark:bg-teal-900/
|
|
62
|
-
badgeClass: 'bg-teal-100 text-teal-700 dark:bg-teal-
|
|
61
|
+
bgClass: 'bg-teal-50 dark:bg-teal-900/30',
|
|
62
|
+
badgeClass: 'bg-teal-100 text-teal-700 dark:bg-teal-800 dark:text-teal-200',
|
|
63
63
|
instance: Trinity.instance,
|
|
64
64
|
hasDynamicDescription: true,
|
|
65
65
|
isMultiInstance: false,
|
|
@@ -55,6 +55,8 @@ export function emitToolAuditEvents(messages, sessionId, agent, opts) {
|
|
|
55
55
|
const result = tc.id ? toolResults.get(tc.id) : undefined;
|
|
56
56
|
const isError = typeof result === 'string' && /^error:/i.test(result.trim());
|
|
57
57
|
const eventType = internalToolNames?.has(tc.name) ? 'tool_call' : defaultEventType;
|
|
58
|
+
if (!eventType)
|
|
59
|
+
continue; // no event type assigned — caller opted out for this tool
|
|
58
60
|
const meta = {};
|
|
59
61
|
if (tc.args && Object.keys(tc.args).length > 0)
|
|
60
62
|
meta.args = tc.args;
|
|
@@ -170,14 +170,14 @@ class OpenRouterTelephonist {
|
|
|
170
170
|
* based on the audio provider configuration.
|
|
171
171
|
*
|
|
172
172
|
* Supported providers:
|
|
173
|
-
* -
|
|
173
|
+
* - gemini: Google Gemini (native audio file upload)
|
|
174
174
|
* - openai: OpenAI Whisper API (/audio/transcriptions)
|
|
175
175
|
* - openrouter: OpenRouter SDK with input_audio (multimodal models)
|
|
176
176
|
* - ollama: Ollama local Whisper via OpenAI-compatible endpoint
|
|
177
177
|
*/
|
|
178
178
|
export function createTelephonist(config) {
|
|
179
179
|
switch (config.provider) {
|
|
180
|
-
case '
|
|
180
|
+
case 'gemini':
|
|
181
181
|
return new GeminiTelephonist(config.model);
|
|
182
182
|
case 'openai':
|
|
183
183
|
return new WhisperTelephonist(config.model);
|
|
@@ -188,7 +188,7 @@ export function createTelephonist(config) {
|
|
|
188
188
|
// Requires a Whisper model loaded: `ollama pull whisper`
|
|
189
189
|
return new WhisperTelephonist(config.model, (config.base_url || 'http://localhost:11434') + '/v1');
|
|
190
190
|
default:
|
|
191
|
-
throw new Error(`Unsupported audio provider: '${config.provider}'. Supported:
|
|
191
|
+
throw new Error(`Unsupported audio provider: '${config.provider}'. Supported: gemini, openai, openrouter, ollama.`);
|
|
192
192
|
}
|
|
193
193
|
}
|
|
194
194
|
// ─── TTS Implementations ─────────────────────────────────────────────────────
|
|
@@ -334,16 +334,16 @@ class GeminiTtsTelephonist {
|
|
|
334
334
|
}
|
|
335
335
|
/**
|
|
336
336
|
* Factory that creates an ITelephonist with TTS (synthesize) support.
|
|
337
|
-
* Supports providers: openai,
|
|
337
|
+
* Supports providers: openai, gemini.
|
|
338
338
|
*/
|
|
339
339
|
export function createTtsTelephonist(config) {
|
|
340
340
|
switch (config.provider) {
|
|
341
341
|
case 'openai':
|
|
342
342
|
return new OpenAITtsTelephonist(config.model, config.voice);
|
|
343
|
-
case '
|
|
343
|
+
case 'gemini':
|
|
344
344
|
return new GeminiTtsTelephonist(config.model, config.voice);
|
|
345
345
|
default:
|
|
346
|
-
throw new Error(`Unsupported TTS provider: '${config.provider}'. Supported: openai,
|
|
346
|
+
throw new Error(`Unsupported TTS provider: '${config.provider}'. Supported: openai, gemini.`);
|
|
347
347
|
}
|
|
348
348
|
}
|
|
349
349
|
// ─── Legacy export for backward compatibility ─────────────────────────────────
|