morpheus-cli 0.5.0 → 0.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +26 -7
- package/dist/channels/telegram.js +173 -0
- package/dist/cli/commands/restart.js +15 -14
- package/dist/cli/commands/start.js +17 -12
- package/dist/config/manager.js +31 -0
- package/dist/config/mcp-manager.js +19 -1
- package/dist/config/schemas.js +2 -0
- package/dist/http/api.js +222 -0
- package/dist/runtime/memory/session-embedding-worker.js +3 -3
- package/dist/runtime/memory/trinity-db.js +203 -0
- package/dist/runtime/neo.js +16 -26
- package/dist/runtime/oracle.js +16 -8
- package/dist/runtime/session-embedding-scheduler.js +1 -1
- package/dist/runtime/tasks/dispatcher.js +21 -0
- package/dist/runtime/tasks/repository.js +4 -0
- package/dist/runtime/tasks/worker.js +4 -1
- package/dist/runtime/tools/__tests__/tools.test.js +1 -3
- package/dist/runtime/tools/factory.js +1 -1
- package/dist/runtime/tools/index.js +1 -3
- package/dist/runtime/tools/morpheus-tools.js +742 -0
- package/dist/runtime/tools/neo-tool.js +19 -9
- package/dist/runtime/tools/trinity-tool.js +98 -0
- package/dist/runtime/trinity-connector.js +611 -0
- package/dist/runtime/trinity-crypto.js +52 -0
- package/dist/runtime/trinity.js +246 -0
- package/dist/runtime/webhooks/dispatcher.js +73 -2
- package/dist/runtime/webhooks/repository.js +7 -0
- package/dist/ui/assets/index-DP2V4kRd.js +112 -0
- package/dist/ui/assets/index-mglRG5Zw.css +1 -0
- package/dist/ui/index.html +2 -2
- package/dist/ui/sw.js +1 -1
- package/package.json +6 -1
- package/dist/runtime/tools/analytics-tools.js +0 -139
- package/dist/runtime/tools/config-tools.js +0 -64
- package/dist/runtime/tools/diagnostic-tools.js +0 -153
- package/dist/runtime/tools/task-query-tool.js +0 -76
- package/dist/ui/assets/index-20lLB1sM.js +0 -112
- package/dist/ui/assets/index-BJ56bRfs.css +0 -1
package/README.md
CHANGED
|
@@ -18,6 +18,7 @@ It runs as a daemon and orchestrates LLMs, MCP tools, DevKit tools, memory, and
|
|
|
18
18
|
- `Neo`: MCP and internal operational tools (config, diagnostics, analytics).
|
|
19
19
|
- `Apoc`: DevTools/browser execution (filesystem, shell, git, network, packages, processes, system, browser automation).
|
|
20
20
|
- `Sati`: long-term memory retrieval/evaluation.
|
|
21
|
+
- `Trinity`: database specialist. Executes queries, introspects schemas, and manages registered databases (PostgreSQL, MySQL, SQLite, MongoDB).
|
|
21
22
|
|
|
22
23
|
## Installation
|
|
23
24
|
|
|
@@ -68,10 +69,10 @@ morpheus session status
|
|
|
68
69
|
Morpheus uses asynchronous delegation by default:
|
|
69
70
|
|
|
70
71
|
1. Oracle receives user request.
|
|
71
|
-
2. If execution is needed, Oracle calls `neo_delegate` or `
|
|
72
|
+
2. If execution is needed, Oracle calls `neo_delegate`, `apoc_delegate`, or `trinity_delegate`.
|
|
72
73
|
3. Delegate tool creates a row in `tasks` table with origin metadata (`channel`, `session`, `message`, `user`).
|
|
73
74
|
4. Oracle immediately acknowledges task creation.
|
|
74
|
-
5. `TaskWorker` executes pending tasks.
|
|
75
|
+
5. `TaskWorker` executes pending tasks (routes `trinit` tasks to Trinity agent).
|
|
75
76
|
6. `TaskNotifier` sends completion/failure through `TaskDispatcher`.
|
|
76
77
|
|
|
77
78
|
Important behavior:
|
|
@@ -94,11 +95,13 @@ Task results are delivered proactively with metadata (task id, agent, status) an
|
|
|
94
95
|
The dashboard includes:
|
|
95
96
|
- Chat with session management
|
|
96
97
|
- Tasks page (stats, filters, details, retry)
|
|
97
|
-
- Agent settings (Oracle/Sati/Neo/Apoc)
|
|
98
|
-
- MCP manager
|
|
99
|
-
- Sati memories
|
|
98
|
+
- Agent settings (Oracle/Sati/Neo/Apoc/Trinity)
|
|
99
|
+
- MCP manager (add/edit/delete/toggle/reload)
|
|
100
|
+
- Sati memories (search, bulk delete)
|
|
100
101
|
- Usage stats and model pricing
|
|
102
|
+
- Trinity databases (register/test/refresh schema)
|
|
101
103
|
- Webhooks and notification inbox
|
|
104
|
+
- Logs viewer
|
|
102
105
|
|
|
103
106
|
Chat-specific rendering:
|
|
104
107
|
- AI messages rendered as markdown
|
|
@@ -140,6 +143,11 @@ apoc:
|
|
|
140
143
|
working_dir: /home/user/projects
|
|
141
144
|
timeout_ms: 30000
|
|
142
145
|
|
|
146
|
+
trinity:
|
|
147
|
+
provider: openai
|
|
148
|
+
model: gpt-4o-mini
|
|
149
|
+
temperature: 0.2
|
|
150
|
+
|
|
143
151
|
runtime:
|
|
144
152
|
async_tasks:
|
|
145
153
|
enabled: true
|
|
@@ -178,6 +186,9 @@ Provider-specific keys:
|
|
|
178
186
|
- `TELEGRAM_BOT_TOKEN`
|
|
179
187
|
- `THE_ARCHITECT_PASS`
|
|
180
188
|
|
|
189
|
+
Security:
|
|
190
|
+
- `MORPHEUS_SECRET` — AES-256-GCM key for encrypting Trinity database passwords (required when using Trinity)
|
|
191
|
+
|
|
181
192
|
Generic Morpheus overrides (selected):
|
|
182
193
|
|
|
183
194
|
| Variable | Target |
|
|
@@ -213,6 +224,10 @@ Generic Morpheus overrides (selected):
|
|
|
213
224
|
| `MORPHEUS_APOC_API_KEY` | `apoc.api_key` |
|
|
214
225
|
| `MORPHEUS_APOC_WORKING_DIR` | `apoc.working_dir` |
|
|
215
226
|
| `MORPHEUS_APOC_TIMEOUT_MS` | `apoc.timeout_ms` |
|
|
227
|
+
| `MORPHEUS_TRINITY_PROVIDER` | `trinity.provider` |
|
|
228
|
+
| `MORPHEUS_TRINITY_MODEL` | `trinity.model` |
|
|
229
|
+
| `MORPHEUS_TRINITY_TEMPERATURE` | `trinity.temperature` |
|
|
230
|
+
| `MORPHEUS_TRINITY_API_KEY` | `trinity.api_key` |
|
|
216
231
|
| `MORPHEUS_AUDIO_PROVIDER` | `audio.provider` |
|
|
217
232
|
| `MORPHEUS_AUDIO_MODEL` | `audio.model` |
|
|
218
233
|
| `MORPHEUS_AUDIO_ENABLED` | `audio.enabled` |
|
|
@@ -262,9 +277,10 @@ Authenticated endpoints (`x-architect-pass`):
|
|
|
262
277
|
- Sessions: `/api/sessions*`
|
|
263
278
|
- Chat: `POST /api/chat`
|
|
264
279
|
- Tasks: `GET /api/tasks`, `GET /api/tasks/stats`, `GET /api/tasks/:id`, `POST /api/tasks/:id/retry`
|
|
265
|
-
- Config: `/api/config`, `/api/config/sati`, `/api/config/neo`, `/api/config/apoc`
|
|
266
|
-
- MCP: `/api/mcp/*`
|
|
280
|
+
- Config: `/api/config`, `/api/config/sati`, `/api/config/neo`, `/api/config/apoc`, `/api/config/trinity`
|
|
281
|
+
- MCP: `/api/mcp/*` (servers CRUD + reload + status)
|
|
267
282
|
- Sati memories: `/api/sati/memories*`
|
|
283
|
+
- Trinity databases: `GET/POST/PUT/DELETE /api/trinity/databases`, `POST /api/trinity/databases/:id/test`, `POST /api/trinity/databases/:id/refresh-schema`
|
|
268
284
|
- Usage/model pricing/logs/restart
|
|
269
285
|
- Webhook management and webhook notifications
|
|
270
286
|
|
|
@@ -368,6 +384,9 @@ src/
|
|
|
368
384
|
apoc.ts
|
|
369
385
|
neo.ts
|
|
370
386
|
oracle.ts
|
|
387
|
+
trinity.ts
|
|
388
|
+
trinity-connector.ts # PostgreSQL/MySQL/SQLite/MongoDB drivers
|
|
389
|
+
trinity-crypto.ts # AES-256-GCM encryption for DB passwords
|
|
371
390
|
memory/
|
|
372
391
|
tasks/
|
|
373
392
|
tools/
|
|
@@ -150,6 +150,7 @@ export class TelegramAdapter {
|
|
|
150
150
|
/help \\- Show available commands
|
|
151
151
|
/zaion \\- Show system configurations
|
|
152
152
|
/sati qnt \\- Show specific memories
|
|
153
|
+
/trinity \\- List registered Trinity databases
|
|
153
154
|
/newsession \\- Archive current session and start fresh
|
|
154
155
|
/sessions \\- List all sessions with titles and switch between them
|
|
155
156
|
/restart \\- Restart the Morpheus agent
|
|
@@ -466,6 +467,125 @@ export class TelegramAdapter {
|
|
|
466
467
|
await ctx.reply(`❌ Failed to ${enable ? 'enable' : 'disable'} MCP '${serverName}': ${error.message}`);
|
|
467
468
|
}
|
|
468
469
|
});
|
|
470
|
+
// --- Trinity DB Test Connection ---
|
|
471
|
+
this.bot.action(/^test_trinity_db_/, async (ctx) => {
|
|
472
|
+
const data = ctx.callbackQuery.data;
|
|
473
|
+
const id = parseInt(data.replace('test_trinity_db_', ''), 10);
|
|
474
|
+
if (isNaN(id)) {
|
|
475
|
+
await ctx.answerCbQuery('Invalid ID');
|
|
476
|
+
return;
|
|
477
|
+
}
|
|
478
|
+
await ctx.answerCbQuery('Testing connection…');
|
|
479
|
+
try {
|
|
480
|
+
const { DatabaseRegistry } = await import('../runtime/memory/trinity-db.js');
|
|
481
|
+
const { testConnection } = await import('../runtime/trinity-connector.js');
|
|
482
|
+
const db = DatabaseRegistry.getInstance().getDatabase(id);
|
|
483
|
+
if (!db) {
|
|
484
|
+
await ctx.reply('❌ Database not found.');
|
|
485
|
+
return;
|
|
486
|
+
}
|
|
487
|
+
const ok = await testConnection(db);
|
|
488
|
+
await ctx.reply(ok
|
|
489
|
+
? `✅ <b>${escapeHtml(db.name)}</b>: connection successful.`
|
|
490
|
+
: `❌ <b>${escapeHtml(db.name)}</b>: connection failed.`, { parse_mode: 'HTML' });
|
|
491
|
+
}
|
|
492
|
+
catch (e) {
|
|
493
|
+
await ctx.reply(`❌ Error testing connection: ${escapeHtml(e.message)}`, { parse_mode: 'HTML' });
|
|
494
|
+
}
|
|
495
|
+
});
|
|
496
|
+
// --- Trinity DB Refresh Schema ---
|
|
497
|
+
this.bot.action(/^refresh_trinity_db_schema_/, async (ctx) => {
|
|
498
|
+
const data = ctx.callbackQuery.data;
|
|
499
|
+
const id = parseInt(data.replace('refresh_trinity_db_schema_', ''), 10);
|
|
500
|
+
if (isNaN(id)) {
|
|
501
|
+
await ctx.answerCbQuery('Invalid ID');
|
|
502
|
+
return;
|
|
503
|
+
}
|
|
504
|
+
await ctx.answerCbQuery('Refreshing schema…');
|
|
505
|
+
try {
|
|
506
|
+
const { DatabaseRegistry } = await import('../runtime/memory/trinity-db.js');
|
|
507
|
+
const { introspectSchema } = await import('../runtime/trinity-connector.js');
|
|
508
|
+
const { Trinity } = await import('../runtime/trinity.js');
|
|
509
|
+
const registry = DatabaseRegistry.getInstance();
|
|
510
|
+
const db = registry.getDatabase(id);
|
|
511
|
+
if (!db) {
|
|
512
|
+
await ctx.reply('❌ Database not found.');
|
|
513
|
+
return;
|
|
514
|
+
}
|
|
515
|
+
const schema = await introspectSchema(db);
|
|
516
|
+
registry.updateSchema(id, JSON.stringify(schema, null, 2));
|
|
517
|
+
await Trinity.refreshDelegateCatalog().catch(() => { });
|
|
518
|
+
const tableNames = schema.databases
|
|
519
|
+
? schema.databases.flatMap((d) => d.tables.map((t) => `${d.name}.${t.name}`))
|
|
520
|
+
: schema.tables.map((t) => t.name);
|
|
521
|
+
const count = tableNames.length;
|
|
522
|
+
await ctx.reply(`🔄 <b>${escapeHtml(db.name)}</b>: schema refreshed — ${count} ${count === 1 ? 'table' : 'tables'}.`, { parse_mode: 'HTML' });
|
|
523
|
+
}
|
|
524
|
+
catch (e) {
|
|
525
|
+
await ctx.reply(`❌ Error refreshing schema: ${escapeHtml(e.message)}`, { parse_mode: 'HTML' });
|
|
526
|
+
}
|
|
527
|
+
});
|
|
528
|
+
// --- Trinity DB Delete Flow ---
|
|
529
|
+
this.bot.action(/^ask_trinity_db_delete_/, async (ctx) => {
|
|
530
|
+
const data = ctx.callbackQuery.data;
|
|
531
|
+
const id = parseInt(data.replace('ask_trinity_db_delete_', ''), 10);
|
|
532
|
+
if (isNaN(id)) {
|
|
533
|
+
await ctx.answerCbQuery('Invalid ID');
|
|
534
|
+
return;
|
|
535
|
+
}
|
|
536
|
+
try {
|
|
537
|
+
const { DatabaseRegistry } = await import('../runtime/memory/trinity-db.js');
|
|
538
|
+
const db = DatabaseRegistry.getInstance().getDatabase(id);
|
|
539
|
+
if (!db) {
|
|
540
|
+
await ctx.answerCbQuery('Database not found');
|
|
541
|
+
return;
|
|
542
|
+
}
|
|
543
|
+
await ctx.answerCbQuery();
|
|
544
|
+
await ctx.reply(`⚠️ Delete <b>${escapeHtml(db.name)}</b> (${escapeHtml(db.type)}) from Trinity?\n\nThe actual database won't be affected — only this registration will be removed.`, {
|
|
545
|
+
parse_mode: 'HTML',
|
|
546
|
+
reply_markup: {
|
|
547
|
+
inline_keyboard: [
|
|
548
|
+
[
|
|
549
|
+
{ text: '🗑️ Yes, delete', callback_data: `confirm_trinity_db_delete_${id}` },
|
|
550
|
+
{ text: 'Cancel', callback_data: 'cancel_trinity_db_delete' },
|
|
551
|
+
],
|
|
552
|
+
],
|
|
553
|
+
},
|
|
554
|
+
});
|
|
555
|
+
}
|
|
556
|
+
catch (e) {
|
|
557
|
+
await ctx.answerCbQuery(`Error: ${e.message}`, { show_alert: true });
|
|
558
|
+
}
|
|
559
|
+
});
|
|
560
|
+
this.bot.action(/^confirm_trinity_db_delete_/, async (ctx) => {
|
|
561
|
+
const data = ctx.callbackQuery.data;
|
|
562
|
+
const id = parseInt(data.replace('confirm_trinity_db_delete_', ''), 10);
|
|
563
|
+
if (isNaN(id)) {
|
|
564
|
+
await ctx.answerCbQuery('Invalid ID');
|
|
565
|
+
return;
|
|
566
|
+
}
|
|
567
|
+
try {
|
|
568
|
+
const { DatabaseRegistry } = await import('../runtime/memory/trinity-db.js');
|
|
569
|
+
const registry = DatabaseRegistry.getInstance();
|
|
570
|
+
const db = registry.getDatabase(id);
|
|
571
|
+
const name = db?.name ?? `#${id}`;
|
|
572
|
+
const deleted = registry.deleteDatabase(id);
|
|
573
|
+
await ctx.answerCbQuery(deleted ? '🗑️ Deleted' : 'Not found');
|
|
574
|
+
if (ctx.updateType === 'callback_query')
|
|
575
|
+
ctx.deleteMessage().catch(() => { });
|
|
576
|
+
const user = ctx.from?.username || ctx.from?.first_name || 'unknown';
|
|
577
|
+
this.display.log(`Trinity DB '${name}' deleted by @${user}`, { source: 'Telegram', level: 'info' });
|
|
578
|
+
await ctx.reply(deleted ? `🗑️ <b>${escapeHtml(name)}</b> removed from Trinity.` : `❌ Database #${id} not found.`, { parse_mode: 'HTML' });
|
|
579
|
+
}
|
|
580
|
+
catch (e) {
|
|
581
|
+
await ctx.answerCbQuery(`Error: ${e.message}`, { show_alert: true });
|
|
582
|
+
}
|
|
583
|
+
});
|
|
584
|
+
this.bot.action('cancel_trinity_db_delete', async (ctx) => {
|
|
585
|
+
await ctx.answerCbQuery('Cancelled');
|
|
586
|
+
if (ctx.updateType === 'callback_query')
|
|
587
|
+
ctx.deleteMessage().catch(() => { });
|
|
588
|
+
});
|
|
469
589
|
this.bot.launch().catch((err) => {
|
|
470
590
|
if (this.isConnected) {
|
|
471
591
|
this.display.log(`Telegram bot error: ${err}`, { source: 'Telegram', level: 'error' });
|
|
@@ -596,6 +716,9 @@ export class TelegramAdapter {
|
|
|
596
716
|
case '/sati':
|
|
597
717
|
await this.handleSatiCommand(ctx, user, args);
|
|
598
718
|
break;
|
|
719
|
+
case '/trinity':
|
|
720
|
+
await this.handleTrinityCommand(ctx, user);
|
|
721
|
+
break;
|
|
599
722
|
case '/restart':
|
|
600
723
|
await this.handleRestartCommand(ctx, user);
|
|
601
724
|
break;
|
|
@@ -954,6 +1077,56 @@ How can I assist you today?`;
|
|
|
954
1077
|
response += `\\- Max Duration: ${escMd(config.audio.maxDurationSeconds)}s\n`;
|
|
955
1078
|
await ctx.reply(response, { parse_mode: 'MarkdownV2' });
|
|
956
1079
|
}
|
|
1080
|
+
async handleTrinityCommand(ctx, user) {
|
|
1081
|
+
try {
|
|
1082
|
+
const { DatabaseRegistry } = await import('../runtime/memory/trinity-db.js');
|
|
1083
|
+
const registry = DatabaseRegistry.getInstance();
|
|
1084
|
+
const databases = registry.listDatabases();
|
|
1085
|
+
if (databases.length === 0) {
|
|
1086
|
+
await ctx.reply('No databases registered in Trinity. Use the web UI to register databases.');
|
|
1087
|
+
return;
|
|
1088
|
+
}
|
|
1089
|
+
let html = `<b>Trinity Databases (${databases.length}):</b>\n\n`;
|
|
1090
|
+
const keyboard = [];
|
|
1091
|
+
for (const db of databases) {
|
|
1092
|
+
const schema = db.schema_json ? JSON.parse(db.schema_json) : null;
|
|
1093
|
+
const tables = schema?.tables?.map((t) => t.name).filter(Boolean) ?? [];
|
|
1094
|
+
const updatedAt = db.schema_updated_at
|
|
1095
|
+
? new Date(db.schema_updated_at).toLocaleDateString()
|
|
1096
|
+
: 'never';
|
|
1097
|
+
html += `🗄️ <b>${escapeHtml(db.name)}</b> (${escapeHtml(db.type)})\n`;
|
|
1098
|
+
if (db.host)
|
|
1099
|
+
html += ` Host: ${escapeHtml(db.host)}:${db.port}\n`;
|
|
1100
|
+
if (db.database_name && !db.host)
|
|
1101
|
+
html += ` File: ${escapeHtml(db.database_name)}\n`;
|
|
1102
|
+
if (tables.length > 0) {
|
|
1103
|
+
const tableList = tables.slice(0, 20).join(', ');
|
|
1104
|
+
const extra = tables.length > 20 ? ` (+${tables.length - 20} more)` : '';
|
|
1105
|
+
html += ` Tables: ${escapeHtml(tableList)}${escapeHtml(extra)}\n`;
|
|
1106
|
+
}
|
|
1107
|
+
else {
|
|
1108
|
+
html += ` Tables: (schema not loaded)\n`;
|
|
1109
|
+
}
|
|
1110
|
+
html += ` Schema updated: ${escapeHtml(updatedAt)}\n\n`;
|
|
1111
|
+
keyboard.push([
|
|
1112
|
+
{ text: `🔌 Test ${db.name}`, callback_data: `test_trinity_db_${db.id}` },
|
|
1113
|
+
{ text: `🔄 Schema`, callback_data: `refresh_trinity_db_schema_${db.id}` },
|
|
1114
|
+
{ text: `🗑️ Delete`, callback_data: `ask_trinity_db_delete_${db.id}` },
|
|
1115
|
+
]);
|
|
1116
|
+
}
|
|
1117
|
+
const chunks = splitHtmlChunks(html.trim());
|
|
1118
|
+
for (let i = 0; i < chunks.length; i++) {
|
|
1119
|
+
const isLast = i === chunks.length - 1;
|
|
1120
|
+
await ctx.reply(chunks[i], {
|
|
1121
|
+
parse_mode: 'HTML',
|
|
1122
|
+
...(isLast && keyboard.length > 0 ? { reply_markup: { inline_keyboard: keyboard } } : {}),
|
|
1123
|
+
});
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
1126
|
+
catch (e) {
|
|
1127
|
+
await ctx.reply(`Error listing Trinity databases: ${e.message}`);
|
|
1128
|
+
}
|
|
1129
|
+
}
|
|
957
1130
|
async handleSatiCommand(ctx, user, args) {
|
|
958
1131
|
let limit = null;
|
|
959
1132
|
if (args.length > 0) {
|
|
@@ -66,10 +66,10 @@ export const restartCommand = new Command('restart')
|
|
|
66
66
|
const config = await configManager.load();
|
|
67
67
|
// Initialize persistent logging
|
|
68
68
|
await display.initialize(config.logging);
|
|
69
|
-
display.log(chalk.green(`Morpheus Agent (${config.agent.name}) starting...`));
|
|
70
|
-
display.log(chalk.gray(`PID: ${process.pid}`));
|
|
69
|
+
display.log(chalk.green(`Morpheus Agent (${config.agent.name}) starting...`), { source: 'Zaion' });
|
|
70
|
+
display.log(chalk.gray(`PID: ${process.pid}`), { source: 'Zaion' });
|
|
71
71
|
if (options.ui) {
|
|
72
|
-
display.log(chalk.blue(`Web UI enabled to port ${options.port}`));
|
|
72
|
+
display.log(chalk.blue(`Web UI enabled to port ${options.port}`), { source: 'Zaion' });
|
|
73
73
|
}
|
|
74
74
|
// Initialize Oracle
|
|
75
75
|
const oracle = new Oracle(config);
|
|
@@ -82,17 +82,17 @@ export const restartCommand = new Command('restart')
|
|
|
82
82
|
catch (err) {
|
|
83
83
|
display.stopSpinner();
|
|
84
84
|
if (err instanceof ProviderError) {
|
|
85
|
-
display.log(chalk.red(`\nProvider Error (${err.provider}):`));
|
|
86
|
-
display.log(chalk.white(err.message));
|
|
85
|
+
display.log(chalk.red(`\nProvider Error (${err.provider}):`), { source: 'Oracle' });
|
|
86
|
+
display.log(chalk.white(err.message), { source: 'Oracle' });
|
|
87
87
|
if (err.suggestion) {
|
|
88
|
-
display.log(chalk.yellow(`Tip: ${err.suggestion}`));
|
|
88
|
+
display.log(chalk.yellow(`Tip: ${err.suggestion}`), { source: 'Oracle' });
|
|
89
89
|
}
|
|
90
90
|
}
|
|
91
91
|
else {
|
|
92
|
-
display.log(chalk.red('\nOracle initialization failed:'));
|
|
93
|
-
display.log(chalk.white(err.message));
|
|
92
|
+
display.log(chalk.red('\nOracle initialization failed:'), { source: 'Oracle' });
|
|
93
|
+
display.log(chalk.white(err.message), { source: 'Oracle' });
|
|
94
94
|
if (err.message.includes('API Key')) {
|
|
95
|
-
display.log(chalk.yellow('Tip: Check your API key in configuration or environment variables.'));
|
|
95
|
+
display.log(chalk.yellow('Tip: Check your API key in configuration or environment variables.'), { source: 'Oracle' });
|
|
96
96
|
}
|
|
97
97
|
}
|
|
98
98
|
await clearPid();
|
|
@@ -112,7 +112,7 @@ export const restartCommand = new Command('restart')
|
|
|
112
112
|
httpServer.start(port);
|
|
113
113
|
}
|
|
114
114
|
catch (e) {
|
|
115
|
-
display.log(chalk.red(`Failed to start Web UI: ${e.message}`));
|
|
115
|
+
display.log(chalk.red(`Failed to start Web UI: ${e.message}`), { source: 'Zaion' });
|
|
116
116
|
}
|
|
117
117
|
}
|
|
118
118
|
// Initialize Telegram
|
|
@@ -126,11 +126,11 @@ export const restartCommand = new Command('restart')
|
|
|
126
126
|
adapters.push(telegram);
|
|
127
127
|
}
|
|
128
128
|
catch (e) {
|
|
129
|
-
display.log(chalk.red('Failed to initialize Telegram adapter. Continuing...'));
|
|
129
|
+
display.log(chalk.red('Failed to initialize Telegram adapter. Continuing...'), { source: 'Zaion' });
|
|
130
130
|
}
|
|
131
131
|
}
|
|
132
132
|
else {
|
|
133
|
-
display.log(chalk.yellow('Telegram enabled but no token provided. Skipping.'));
|
|
133
|
+
display.log(chalk.yellow('Telegram enabled but no token provided. Skipping.'), { source: 'Zaion' });
|
|
134
134
|
}
|
|
135
135
|
}
|
|
136
136
|
if (asyncTasksEnabled) {
|
|
@@ -140,7 +140,7 @@ export const restartCommand = new Command('restart')
|
|
|
140
140
|
// Handle graceful shutdown
|
|
141
141
|
const shutdown = async (signal) => {
|
|
142
142
|
display.stopSpinner();
|
|
143
|
-
display.log(`\n${signal} received. Shutting down
|
|
143
|
+
display.log(`\n${signal} received. Shutting down...`, { source: 'Zaion' });
|
|
144
144
|
if (httpServer) {
|
|
145
145
|
httpServer.stop();
|
|
146
146
|
}
|
|
@@ -177,7 +177,8 @@ export const restartCommand = new Command('restart')
|
|
|
177
177
|
}
|
|
178
178
|
catch (error) {
|
|
179
179
|
display.stopSpinner();
|
|
180
|
-
|
|
180
|
+
display.log(chalk.red('Failed to restart Morpheus:'), { source: 'Zaion' });
|
|
181
|
+
display.log(chalk.white(error.message), { source: 'Zaion' });
|
|
181
182
|
await clearPid();
|
|
182
183
|
process.exit(1);
|
|
183
184
|
}
|
|
@@ -91,7 +91,7 @@ export const startCommand = new Command('start')
|
|
|
91
91
|
display.log(chalk.green(`Morpheus Agent (${config.agent.name}) starting...`));
|
|
92
92
|
display.log(chalk.gray(`PID: ${process.pid}`));
|
|
93
93
|
if (options.ui) {
|
|
94
|
-
display.log(chalk.blue(`Web UI enabled to port ${options.port}`));
|
|
94
|
+
display.log(chalk.blue(`Web UI enabled to port ${options.port}`), { source: 'Zaion' });
|
|
95
95
|
}
|
|
96
96
|
// Initialize Oracle
|
|
97
97
|
const oracle = new Oracle(config);
|
|
@@ -104,17 +104,17 @@ export const startCommand = new Command('start')
|
|
|
104
104
|
catch (err) {
|
|
105
105
|
display.stopSpinner();
|
|
106
106
|
if (err instanceof ProviderError) {
|
|
107
|
-
display.log(chalk.red(`\nProvider Error (${err.provider}):`));
|
|
108
|
-
display.log(chalk.white(err.message));
|
|
107
|
+
display.log(chalk.red(`\nProvider Error (${err.provider}):`), { source: 'Oracle' });
|
|
108
|
+
display.log(chalk.white(err.message), { source: 'Oracle' });
|
|
109
109
|
if (err.suggestion) {
|
|
110
|
-
display.log(chalk.yellow(`Tip: ${err.suggestion}`));
|
|
110
|
+
display.log(chalk.yellow(`Tip: ${err.suggestion}`), { source: 'Oracle' });
|
|
111
111
|
}
|
|
112
112
|
}
|
|
113
113
|
else {
|
|
114
|
-
display.log(chalk.red('\nOracle initialization failed:'));
|
|
115
|
-
display.log(chalk.white(err.message));
|
|
114
|
+
display.log(chalk.red('\nOracle initialization failed:'), { source: 'Oracle' });
|
|
115
|
+
display.log(chalk.white(err.message), { source: 'Oracle' });
|
|
116
116
|
if (err.message.includes('API Key')) {
|
|
117
|
-
display.log(chalk.yellow('Tip: Check your API key in configuration or environment variables.'));
|
|
117
|
+
display.log(chalk.yellow('Tip: Check your API key in configuration or environment variables.'), { source: 'Oracle' });
|
|
118
118
|
}
|
|
119
119
|
}
|
|
120
120
|
await clearPid();
|
|
@@ -134,7 +134,7 @@ export const startCommand = new Command('start')
|
|
|
134
134
|
httpServer.start(port);
|
|
135
135
|
}
|
|
136
136
|
catch (e) {
|
|
137
|
-
display.log(chalk.red(`Failed to start Web UI: ${e.message}`));
|
|
137
|
+
display.log(chalk.red(`Failed to start Web UI: ${e.message}`), { source: 'Zaion' });
|
|
138
138
|
}
|
|
139
139
|
}
|
|
140
140
|
// Initialize Telegram
|
|
@@ -149,11 +149,11 @@ export const startCommand = new Command('start')
|
|
|
149
149
|
adapters.push(telegram);
|
|
150
150
|
}
|
|
151
151
|
catch (e) {
|
|
152
|
-
display.log(chalk.red('Failed to initialize Telegram adapter. Continuing...'));
|
|
152
|
+
display.log(chalk.red('Failed to initialize Telegram adapter. Continuing...'), { source: 'Zaion' });
|
|
153
153
|
}
|
|
154
154
|
}
|
|
155
155
|
else {
|
|
156
|
-
display.log(chalk.yellow('Telegram enabled but no token provided. Skipping.'));
|
|
156
|
+
display.log(chalk.yellow('Telegram enabled but no token provided. Skipping.'), { source: 'Zaion' });
|
|
157
157
|
}
|
|
158
158
|
}
|
|
159
159
|
// Start Background Services
|
|
@@ -162,10 +162,14 @@ export const startCommand = new Command('start')
|
|
|
162
162
|
taskWorker.start();
|
|
163
163
|
taskNotifier.start();
|
|
164
164
|
}
|
|
165
|
+
// Recover webhook notifications stuck in 'pending' from previous runs
|
|
166
|
+
WebhookDispatcher.recoverStale().catch((err) => {
|
|
167
|
+
display.log(`Webhook recovery error: ${err.message}`, { source: 'Webhooks', level: 'error' });
|
|
168
|
+
});
|
|
165
169
|
// Handle graceful shutdown
|
|
166
170
|
const shutdown = async (signal) => {
|
|
167
171
|
display.stopSpinner();
|
|
168
|
-
display.log(`\n${signal} received. Shutting down
|
|
172
|
+
display.log(`\n${signal} received. Shutting down...`, { source: 'Zaion' });
|
|
169
173
|
if (httpServer) {
|
|
170
174
|
httpServer.stop();
|
|
171
175
|
}
|
|
@@ -202,7 +206,8 @@ export const startCommand = new Command('start')
|
|
|
202
206
|
}
|
|
203
207
|
catch (error) {
|
|
204
208
|
display.stopSpinner();
|
|
205
|
-
|
|
209
|
+
display.log(chalk.red('Failed to start Morpheus:'), { source: 'Zaion' });
|
|
210
|
+
display.log(chalk.white(error.message), { source: 'Zaion' });
|
|
206
211
|
await clearPid();
|
|
207
212
|
process.exit(1);
|
|
208
213
|
}
|
package/dist/config/manager.js
CHANGED
|
@@ -124,6 +124,29 @@ export class ConfigManager {
|
|
|
124
124
|
context_window: resolveOptionalNumeric('MORPHEUS_NEO_CONTEXT_WINDOW', config.neo?.context_window, neoContextWindowFallback),
|
|
125
125
|
};
|
|
126
126
|
}
|
|
127
|
+
// Apply precedence to Trinity config
|
|
128
|
+
const trinityEnvVars = [
|
|
129
|
+
'MORPHEUS_TRINITY_PROVIDER',
|
|
130
|
+
'MORPHEUS_TRINITY_MODEL',
|
|
131
|
+
'MORPHEUS_TRINITY_TEMPERATURE',
|
|
132
|
+
'MORPHEUS_TRINITY_API_KEY',
|
|
133
|
+
];
|
|
134
|
+
const hasTrinityEnvOverrides = trinityEnvVars.some((envVar) => process.env[envVar] !== undefined);
|
|
135
|
+
let trinityConfig;
|
|
136
|
+
if (config.trinity || hasTrinityEnvOverrides) {
|
|
137
|
+
const trinityProvider = resolveProvider('MORPHEUS_TRINITY_PROVIDER', config.trinity?.provider, llmConfig.provider);
|
|
138
|
+
const trinityMaxTokensFallback = config.trinity?.max_tokens ?? llmConfig.max_tokens;
|
|
139
|
+
const trinityContextWindowFallback = config.trinity?.context_window ?? llmConfig.context_window;
|
|
140
|
+
trinityConfig = {
|
|
141
|
+
provider: trinityProvider,
|
|
142
|
+
model: resolveModel(trinityProvider, 'MORPHEUS_TRINITY_MODEL', config.trinity?.model || llmConfig.model),
|
|
143
|
+
temperature: resolveNumeric('MORPHEUS_TRINITY_TEMPERATURE', config.trinity?.temperature, llmConfig.temperature),
|
|
144
|
+
max_tokens: resolveOptionalNumeric('MORPHEUS_TRINITY_MAX_TOKENS', config.trinity?.max_tokens, trinityMaxTokensFallback),
|
|
145
|
+
api_key: resolveApiKey(trinityProvider, 'MORPHEUS_TRINITY_API_KEY', config.trinity?.api_key || llmConfig.api_key),
|
|
146
|
+
base_url: config.trinity?.base_url || config.llm.base_url,
|
|
147
|
+
context_window: resolveOptionalNumeric('MORPHEUS_TRINITY_CONTEXT_WINDOW', config.trinity?.context_window, trinityContextWindowFallback),
|
|
148
|
+
};
|
|
149
|
+
}
|
|
127
150
|
// Apply precedence to audio config
|
|
128
151
|
const audioProvider = resolveString('MORPHEUS_AUDIO_PROVIDER', config.audio.provider, DEFAULT_CONFIG.audio.provider);
|
|
129
152
|
// AudioProvider uses 'google' but resolveApiKey expects LLMProvider which uses 'gemini'
|
|
@@ -169,6 +192,7 @@ export class ConfigManager {
|
|
|
169
192
|
sati: satiConfig,
|
|
170
193
|
neo: neoConfig,
|
|
171
194
|
apoc: apocConfig,
|
|
195
|
+
trinity: trinityConfig,
|
|
172
196
|
audio: audioConfig,
|
|
173
197
|
channels: channelsConfig,
|
|
174
198
|
ui: uiConfig,
|
|
@@ -236,4 +260,11 @@ export class ConfigManager {
|
|
|
236
260
|
...this.config.llm,
|
|
237
261
|
};
|
|
238
262
|
}
|
|
263
|
+
getTrinityConfig() {
|
|
264
|
+
if (this.config.trinity) {
|
|
265
|
+
return { ...this.config.trinity };
|
|
266
|
+
}
|
|
267
|
+
// Fallback to main LLM config
|
|
268
|
+
return { ...this.config.llm };
|
|
269
|
+
}
|
|
239
270
|
}
|
|
@@ -21,8 +21,11 @@ const readConfigFile = async () => {
|
|
|
21
21
|
};
|
|
22
22
|
const writeConfigFile = async (config) => {
|
|
23
23
|
const configPath = path.join(MORPHEUS_ROOT, MCP_FILE_NAME);
|
|
24
|
+
const tmpPath = configPath + '.tmp';
|
|
24
25
|
const serialized = JSON.stringify(config, null, 2) + '\n';
|
|
25
|
-
|
|
26
|
+
// Atomic write: write to temp file first, then rename — prevents partial writes from corrupting the live file
|
|
27
|
+
await fs.writeFile(tmpPath, serialized, 'utf-8');
|
|
28
|
+
await fs.rename(tmpPath, configPath);
|
|
26
29
|
};
|
|
27
30
|
const isMetadataKey = (key) => key.startsWith('_') || RESERVED_KEYS.has(key);
|
|
28
31
|
const normalizeName = (rawName) => rawName.replace(/^\$/, '');
|
|
@@ -44,6 +47,21 @@ const ensureValidName = (name) => {
|
|
|
44
47
|
}
|
|
45
48
|
};
|
|
46
49
|
export class MCPManager {
|
|
50
|
+
static reloadCallback = null;
|
|
51
|
+
/** Called by Oracle after initialization so MCPManager can trigger a full agent reload. */
|
|
52
|
+
static registerReloadCallback(fn) {
|
|
53
|
+
MCPManager.reloadCallback = fn;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Reloads MCP tools across all agents (Oracle provider, Neo catalog, Trinity catalog).
|
|
57
|
+
* Requires Oracle to have been initialized (and thus have registered its callback).
|
|
58
|
+
*/
|
|
59
|
+
static async reloadAgents() {
|
|
60
|
+
if (!MCPManager.reloadCallback) {
|
|
61
|
+
throw new Error('Reload callback not registered — Oracle must be initialized before calling reloadAgents().');
|
|
62
|
+
}
|
|
63
|
+
await MCPManager.reloadCallback();
|
|
64
|
+
}
|
|
47
65
|
static async listServers() {
|
|
48
66
|
const config = await readConfigFile();
|
|
49
67
|
const servers = [];
|
package/dist/config/schemas.js
CHANGED
|
@@ -27,6 +27,7 @@ export const ApocConfigSchema = LLMConfigSchema.extend({
|
|
|
27
27
|
timeout_ms: z.number().int().positive().optional(),
|
|
28
28
|
});
|
|
29
29
|
export const NeoConfigSchema = LLMConfigSchema;
|
|
30
|
+
export const TrinityConfigSchema = LLMConfigSchema;
|
|
30
31
|
export const WebhookConfigSchema = z.object({
|
|
31
32
|
telegram_notify_all: z.boolean().optional(),
|
|
32
33
|
}).optional();
|
|
@@ -40,6 +41,7 @@ export const ConfigSchema = z.object({
|
|
|
40
41
|
sati: SatiConfigSchema.optional(),
|
|
41
42
|
neo: NeoConfigSchema.optional(),
|
|
42
43
|
apoc: ApocConfigSchema.optional(),
|
|
44
|
+
trinity: TrinityConfigSchema.optional(),
|
|
43
45
|
webhooks: WebhookConfigSchema,
|
|
44
46
|
audio: AudioConfigSchema.default(DEFAULT_CONFIG.audio),
|
|
45
47
|
memory: z.object({
|