morpheus-cli 0.9.11 → 0.9.13

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 (68) hide show
  1. package/README.md +1 -1
  2. package/dist/channels/telegram.js +234 -144
  3. package/dist/cli/commands/restart.js +2 -2
  4. package/dist/cli/commands/start.js +2 -2
  5. package/dist/http/api.js +5 -2
  6. package/dist/http/routers/agents.js +9 -0
  7. package/dist/http/routers/link.js +2 -2
  8. package/dist/runtime/chronos/repository.js +5 -3
  9. package/dist/runtime/chronos/worker.js +27 -4
  10. package/dist/runtime/chronos/worker.test.js +3 -2
  11. package/dist/runtime/hot-reload.js +3 -3
  12. package/dist/runtime/memory/sqlite.js +37 -11
  13. package/dist/runtime/oracle.js +78 -43
  14. package/dist/runtime/{apoc.js → subagents/apoc.js} +19 -6
  15. package/dist/runtime/{devkit-instrument.js → subagents/devkit-instrument.js} +1 -1
  16. package/dist/runtime/subagents/index.js +12 -0
  17. package/dist/runtime/{link.js → subagents/link/link.js} +23 -9
  18. package/dist/runtime/{link-repository.js → subagents/link/repository.js} +2 -2
  19. package/dist/runtime/{link-search.js → subagents/link/search.js} +3 -3
  20. package/dist/runtime/{link-worker.js → subagents/link/worker.js} +6 -6
  21. package/dist/runtime/{neo.js → subagents/neo.js} +23 -9
  22. package/dist/runtime/subagents/registry.js +134 -0
  23. package/dist/runtime/{trinity.js → subagents/trinity/trinity.js} +22 -8
  24. package/dist/runtime/{subagent-utils.js → subagents/utils.js} +2 -2
  25. package/dist/runtime/tasks/worker.js +6 -70
  26. package/dist/runtime/tools/chronos-tools.js +1 -0
  27. package/dist/runtime/tools/morpheus-tools.js +3 -3
  28. package/dist/runtime/webhooks/dispatcher.js +4 -0
  29. package/dist/ui/assets/AuditDashboard-BVyKnpVm.js +1 -0
  30. package/dist/ui/assets/Chat-UVoDlqqM.js +41 -0
  31. package/dist/ui/assets/{Chronos-D1yAb4M5.js → Chronos-Dfs_pOsc.js} +1 -1
  32. package/dist/ui/assets/{ConfirmationModal-DxUHZgTy.js → ConfirmationModal-BBIjVef7.js} +1 -1
  33. package/dist/ui/assets/{Dashboard-BzxmcHaS.js → Dashboard-BdSQDB14.js} +1 -1
  34. package/dist/ui/assets/{DeleteConfirmationModal-CqNXT_YQ.js → DeleteConfirmationModal-Du85q5u2.js} +1 -1
  35. package/dist/ui/assets/{Documents-DLFZdmim.js → Documents-DguILrI8.js} +1 -1
  36. package/dist/ui/assets/{Logs-B1Bpy9dB.js → Logs-BDup2FET.js} +1 -1
  37. package/dist/ui/assets/{MCPManager-BbUDMh5Q.js → MCPManager-WBdh1rum.js} +1 -1
  38. package/dist/ui/assets/{ModelPricing-DCl-2_eJ.js → ModelPricing-BQPw0r6z.js} +1 -1
  39. package/dist/ui/assets/{Notifications-8Cqj-mNp.js → Notifications-BslO2Ect.js} +1 -1
  40. package/dist/ui/assets/{SatiMemories-CdHUe6di.js → SatiMemories-DzaLaZ6M.js} +1 -1
  41. package/dist/ui/assets/SessionAudit-CBDThjBi.js +9 -0
  42. package/dist/ui/assets/{Settings-Clge45Z0.js → Settings-JPTCA7C7.js} +1 -1
  43. package/dist/ui/assets/{Skills-0k7A2T5_.js → Skills-BnDg1HCb.js} +1 -1
  44. package/dist/ui/assets/{Smiths-gjgBMN1F.js → Smiths-DR6g_o3D.js} +1 -1
  45. package/dist/ui/assets/Tasks-BuoNCvI-.js +1 -0
  46. package/dist/ui/assets/{TrinityDatabases-CGna6IMX.js → TrinityDatabases-DYHJunk7.js} +1 -1
  47. package/dist/ui/assets/{UsageStats-B7EzZlZe.js → UsageStats-BpGXaHgW.js} +1 -1
  48. package/dist/ui/assets/{WebhookManager-Bb7KiucS.js → WebhookManager-D2muhYy9.js} +1 -1
  49. package/dist/ui/assets/agents-CgqJea9n.js +1 -0
  50. package/dist/ui/assets/{audit-CJ2Ms81U.js → audit-Dc3YW0-4.js} +1 -1
  51. package/dist/ui/assets/{chronos-Bm68OSy4.js → chronos-CZvGhZQB.js} +1 -1
  52. package/dist/ui/assets/{config-C88yQ_CP.js → config-pKL8Y4V9.js} +1 -1
  53. package/dist/ui/assets/{index-BxN2w9sY.js → index-Bta9YXEm.js} +2 -2
  54. package/dist/ui/assets/index-Cjli-AD7.css +1 -0
  55. package/dist/ui/assets/{mcp-BE_OVkBe.js → mcp-vIffcwd6.js} +1 -1
  56. package/dist/ui/assets/{skills-Dt0qU4gH.js → skills-wANsorUj.js} +1 -1
  57. package/dist/ui/assets/{stats-Bmdps1LR.js → stats-xnlA4NwX.js} +1 -1
  58. package/dist/ui/index.html +2 -2
  59. package/dist/ui/sw.js +1 -1
  60. package/package.json +1 -1
  61. package/dist/ui/assets/AuditDashboard-CM1YN1uk.js +0 -1
  62. package/dist/ui/assets/Chat-D4y-g6Tw.js +0 -41
  63. package/dist/ui/assets/SessionAudit-CtVHK_IH.js +0 -9
  64. package/dist/ui/assets/Tasks-AQ3MrrMp.js +0 -1
  65. package/dist/ui/assets/index-C3Ff736M.css +0 -1
  66. /package/dist/runtime/{ISubagent.js → subagents/ISubagent.js} +0 -0
  67. /package/dist/runtime/{link-chunker.js → subagents/link/chunker.js} +0 -0
  68. /package/dist/runtime/{trinity-connector.js → subagents/trinity/connector.js} +0 -0
package/README.md CHANGED
@@ -9,7 +9,7 @@ It runs as a daemon and orchestrates LLMs, MCP tools, DevKit tools, memory, and
9
9
 
10
10
  ## Why Morpheus
11
11
  - Local-first persistence (sessions, messages, usage, tasks).
12
- - Multi-agent architecture (Oracle, Neo, Apoc, Sati, Trinity, Smith).
12
+ - Multi-agent architecture (Oracle, Neo, Apoc, Sati, Trinity, Link, Smith) with centralized SubagentRegistry.
13
13
  - Async task execution with queue + worker + notifier.
14
14
  - Chronos temporal scheduler for recurring and one-time Oracle executions.
15
15
  - Smith remote agent system for DevKit execution on isolated machines via WebSocket.
@@ -21,6 +21,36 @@ function escapeHtml(text) {
21
21
  .replace(/</g, '&lt;')
22
22
  .replace(/>/g, '&gt;');
23
23
  }
24
+ /** Telegram callback_data has a 64-byte limit. Truncate if needed. */
25
+ function safeCallbackData(data, prefix) {
26
+ const MAX_CALLBACK_DATA = 64;
27
+ const full = `${prefix}${data}`;
28
+ if (full.length > MAX_CALLBACK_DATA) {
29
+ // Truncate and add hash suffix to identify original
30
+ const hash = data.slice(-8);
31
+ return `${prefix}${data.slice(0, MAX_CALLBACK_DATA - prefix.length - 8)}${hash}`;
32
+ }
33
+ return full;
34
+ }
35
+ /** Safely reply to a message, handling Telegram API errors gracefully. */
36
+ async function safeReply(ctx, text, options) {
37
+ try {
38
+ await ctx.reply(text, options);
39
+ }
40
+ catch (error) {
41
+ // Common errors: message not found, chat not found, bot blocked, etc.
42
+ const msg = error?.description || error?.message || 'Unknown error';
43
+ if (msg.includes('message to reply not found') ||
44
+ msg.includes('chat not found') ||
45
+ msg.includes('bot was blocked') ||
46
+ msg.includes('user is deactivated')) {
47
+ // Silently ignore - user may have blocked bot or message expired
48
+ return;
49
+ }
50
+ // Log other errors but don't crash
51
+ console.error('[Telegram] Reply error:', msg);
52
+ }
53
+ }
24
54
  /** Strips HTML tags and unescapes entities for plain-text Telegram fallback. */
25
55
  function stripHtmlTags(html) {
26
56
  return html
@@ -405,19 +435,31 @@ export class TelegramAdapter {
405
435
  }
406
436
  });
407
437
  this.bot.action('confirm_new_session', async (ctx) => {
408
- await this.handleApproveNewSessionCommand(ctx, ctx.from.username || ctx.from.first_name);
409
- if (ctx.updateType === 'callback_query') {
410
- ctx.answerCbQuery();
411
- ctx.deleteMessage().catch(() => { });
438
+ try {
439
+ await this.handleApproveNewSessionCommand(ctx, ctx.from.username || ctx.from.first_name);
440
+ if (ctx.updateType === 'callback_query') {
441
+ ctx.answerCbQuery().catch(() => { });
442
+ ctx.deleteMessage().catch(() => { });
443
+ }
444
+ await safeReply(ctx, "New session created.");
445
+ }
446
+ catch (error) {
447
+ console.error('[Telegram] confirm_new_session error:', error.message);
448
+ ctx.answerCbQuery(`Error: ${error.message}`).catch(() => { });
412
449
  }
413
- ctx.reply("New session created.");
414
450
  });
415
451
  this.bot.action('cancel_new_session', async (ctx) => {
416
- if (ctx.updateType === 'callback_query') {
417
- ctx.answerCbQuery();
418
- ctx.deleteMessage().catch(() => { });
452
+ try {
453
+ if (ctx.updateType === 'callback_query') {
454
+ ctx.answerCbQuery().catch(() => { });
455
+ ctx.deleteMessage().catch(() => { });
456
+ }
457
+ await safeReply(ctx, "New session cancelled.");
458
+ }
459
+ catch (error) {
460
+ console.error('[Telegram] cancel_new_session error:', error.message);
461
+ ctx.answerCbQuery(`Error: ${error.message}`).catch(() => { });
419
462
  }
420
- ctx.reply("New session cancelled.");
421
463
  });
422
464
  this.bot.action(/^switch_session_/, async (ctx) => {
423
465
  const callbackQuery = ctx.callbackQuery;
@@ -425,7 +467,7 @@ export class TelegramAdapter {
425
467
  const sessionId = typeof data === 'string' ? data.replace('switch_session_', '') : '';
426
468
  const userId = ctx.from.id.toString();
427
469
  if (!sessionId || sessionId === '') {
428
- await ctx.answerCbQuery('Invalid session ID');
470
+ await ctx.answerCbQuery('Invalid session ID').catch(() => { });
429
471
  return;
430
472
  }
431
473
  try {
@@ -436,34 +478,40 @@ export class TelegramAdapter {
436
478
  // Update user-channel session mapping
437
479
  await this.history.setUserChannelSession(this.channel, userId, sessionId);
438
480
  this.userSessions.set(userId, sessionId);
439
- await ctx.answerCbQuery();
481
+ await ctx.answerCbQuery().catch(() => { });
440
482
  // Remove the previous message and send confirmation
441
483
  if (ctx.updateType === 'callback_query') {
442
484
  ctx.deleteMessage().catch(() => { });
443
485
  }
444
- ctx.reply(`✅ Switched to session ID: ${sessionId}`);
486
+ await safeReply(ctx, `✅ Switched to session ID: ${sessionId}`);
445
487
  }
446
488
  catch (error) {
447
- await ctx.answerCbQuery(`Error switching session: ${error.message}`, { show_alert: true });
489
+ await ctx.answerCbQuery(`Error switching session: ${error.message}`, { show_alert: true }).catch(() => { });
448
490
  }
449
491
  });
450
492
  // --- Archive Flow ---
451
493
  this.bot.action(/^ask_archive_session_/, async (ctx) => {
452
- const data = ctx.callbackQuery.data;
453
- const sessionId = data.replace('ask_archive_session_', '');
454
- // Fetch session title for better UX (optional, but nice) - for now just use ID
455
- await ctx.reply(`⚠️ *ARCHIVE SESSION?*\n\nAre you sure you want to archive session \`${escMd(sessionId)}\`?\n\nIt will be moved to long\\-term memory \\(SATI\\) and removed from the active list\\. This action cannot be easily undone via Telegram\\.`, {
456
- parse_mode: 'MarkdownV2',
457
- reply_markup: {
458
- inline_keyboard: [
459
- [
460
- { text: '✅ Yes, Archive', callback_data: `confirm_archive_session_${sessionId}` },
461
- { text: ' Cancel', callback_data: 'cancel_session_action' }
494
+ try {
495
+ const data = ctx.callbackQuery.data;
496
+ const sessionId = data.replace('ask_archive_session_', '');
497
+ // Fetch session title for better UX (optional, but nice) - for now just use ID
498
+ await ctx.reply(`⚠️ *ARCHIVE SESSION?*\n\nAre you sure you want to archive session \`${escMd(sessionId)}\`?\n\nIt will be moved to long\\-term memory \\(SATI\\) and removed from the active list\\. This action cannot be easily undone via Telegram\\.`, {
499
+ parse_mode: 'MarkdownV2',
500
+ reply_markup: {
501
+ inline_keyboard: [
502
+ [
503
+ { text: ' Yes, Archive', callback_data: safeCallbackData(sessionId, 'confirm_archive_session_') },
504
+ { text: '❌ Cancel', callback_data: 'cancel_session_action' }
505
+ ]
462
506
  ]
463
- ]
464
- }
465
- });
466
- await ctx.answerCbQuery();
507
+ }
508
+ });
509
+ await ctx.answerCbQuery().catch(() => { });
510
+ }
511
+ catch (error) {
512
+ console.error('[Telegram] ask_archive_session error:', error.message);
513
+ ctx.answerCbQuery(`Error: ${error.message}`).catch(() => { });
514
+ }
467
515
  });
468
516
  this.bot.action(/^confirm_archive_session_/, async (ctx) => {
469
517
  const data = ctx.callbackQuery.data;
@@ -478,32 +526,38 @@ export class TelegramAdapter {
478
526
  await this.history.deleteUserChannelSession(this.channel, userId);
479
527
  this.userSessions.delete(userId);
480
528
  }
481
- await ctx.answerCbQuery('Session archived successfully');
529
+ await ctx.answerCbQuery('Session archived successfully').catch(() => { });
482
530
  if (ctx.updateType === 'callback_query') {
483
531
  ctx.deleteMessage().catch(() => { });
484
532
  }
485
- await ctx.reply(`✅ Session \`${escMd(sessionId)}\` has been archived and moved to long\\-term memory\\.`, { parse_mode: 'MarkdownV2' });
533
+ await safeReply(ctx, `✅ Session \`${escMd(sessionId)}\` has been archived and moved to long\\-term memory\\.`, { parse_mode: 'MarkdownV2' });
486
534
  }
487
535
  catch (error) {
488
- await ctx.answerCbQuery(`Error archiving: ${error.message}`, { show_alert: true });
536
+ await ctx.answerCbQuery(`Error archiving: ${error.message}`, { show_alert: true }).catch(() => { });
489
537
  }
490
538
  });
491
539
  // --- Delete Flow ---
492
540
  this.bot.action(/^ask_delete_session_/, async (ctx) => {
493
- const data = ctx.callbackQuery.data;
494
- const sessionId = data.replace('ask_delete_session_', '');
495
- await ctx.reply(`🚫 *DELETE SESSION?*\n\nAre you sure you want to PERMANENTLY DELETE session \`${escMd(sessionId)}\`?\n\nThis action is *IRREVERSIBLE*\\. All data will be lost\\.`, {
496
- parse_mode: 'MarkdownV2',
497
- reply_markup: {
498
- inline_keyboard: [
499
- [
500
- { text: '🗑️ Yes, DELETE PERMANENTLY', callback_data: `confirm_delete_session_${sessionId}` },
501
- { text: ' Cancel', callback_data: 'cancel_session_action' }
541
+ try {
542
+ const data = ctx.callbackQuery.data;
543
+ const sessionId = data.replace('ask_delete_session_', '');
544
+ await ctx.reply(`🚫 *DELETE SESSION?*\n\nAre you sure you want to PERMANENTLY DELETE session \`${escMd(sessionId)}\`?\n\nThis action is *IRREVERSIBLE*\\. All data will be lost\\.`, {
545
+ parse_mode: 'MarkdownV2',
546
+ reply_markup: {
547
+ inline_keyboard: [
548
+ [
549
+ { text: '🗑️ Yes, DELETE PERMANENTLY', callback_data: safeCallbackData(sessionId, 'confirm_delete_session_') },
550
+ { text: '❌ Cancel', callback_data: 'cancel_session_action' }
551
+ ]
502
552
  ]
503
- ]
504
- }
505
- });
506
- await ctx.answerCbQuery();
553
+ }
554
+ });
555
+ await ctx.answerCbQuery().catch(() => { });
556
+ }
557
+ catch (error) {
558
+ console.error('[Telegram] ask_delete_session error:', error.message);
559
+ ctx.answerCbQuery(`Error: ${error.message}`).catch(() => { });
560
+ }
507
561
  });
508
562
  this.bot.action(/^confirm_delete_session_/, async (ctx) => {
509
563
  const data = ctx.callbackQuery.data;
@@ -518,169 +572,205 @@ export class TelegramAdapter {
518
572
  await this.history.deleteUserChannelSession(this.channel, userId);
519
573
  this.userSessions.delete(userId);
520
574
  }
521
- await ctx.answerCbQuery('Session deleted successfully');
575
+ await ctx.answerCbQuery('Session deleted successfully').catch(() => { });
522
576
  if (ctx.updateType === 'callback_query') {
523
577
  ctx.deleteMessage().catch(() => { });
524
578
  }
525
- await ctx.reply(`🗑️ Session \`${escMd(sessionId)}\` has been permanently deleted\\.`, { parse_mode: 'MarkdownV2' });
579
+ await safeReply(ctx, `🗑️ Session \`${escMd(sessionId)}\` has been permanently deleted\\.`, { parse_mode: 'MarkdownV2' });
526
580
  }
527
581
  catch (error) {
528
- await ctx.answerCbQuery(`Error deleting: ${error.message}`, { show_alert: true });
582
+ await ctx.answerCbQuery(`Error deleting: ${error.message}`, { show_alert: true }).catch(() => { });
529
583
  }
530
584
  });
531
585
  // --- Cancel Action ---
532
586
  this.bot.action('cancel_session_action', async (ctx) => {
533
- await ctx.answerCbQuery('Action cancelled');
534
- if (ctx.updateType === 'callback_query') {
535
- ctx.deleteMessage().catch(() => { });
587
+ try {
588
+ await ctx.answerCbQuery('Action cancelled').catch(() => { });
589
+ if (ctx.updateType === 'callback_query') {
590
+ ctx.deleteMessage().catch(() => { });
591
+ }
592
+ await safeReply(ctx, 'Action cancelled.');
593
+ }
594
+ catch (error) {
595
+ console.error('[Telegram] cancel_session_action error:', error.message);
596
+ ctx.answerCbQuery(`Error: ${error.message}`).catch(() => { });
536
597
  }
537
- await ctx.reply('Action cancelled.');
538
598
  });
539
599
  this.bot.action(/^toggle_mcp_/, async (ctx) => {
540
600
  const data = ctx.callbackQuery.data;
541
601
  // format: toggle_mcp_enable_<name> or toggle_mcp_disable_<name>
542
602
  const match = data.match(/^toggle_mcp_(enable|disable)_(.+)$/);
543
603
  if (!match) {
544
- await ctx.answerCbQuery('Invalid action');
604
+ await ctx.answerCbQuery('Invalid action').catch(() => { });
545
605
  return;
546
606
  }
547
607
  const [, action, serverName] = match;
548
608
  const enable = action === 'enable';
549
609
  try {
550
610
  await MCPManager.setServerEnabled(serverName, enable);
551
- await ctx.answerCbQuery(`${enable ? '✅ Enabled' : '❌ Disabled'}: ${serverName}`);
611
+ await ctx.answerCbQuery(`${enable ? '✅ Enabled' : '❌ Disabled'}: ${serverName}`).catch(() => { });
552
612
  if (ctx.updateType === 'callback_query') {
553
613
  ctx.deleteMessage().catch(() => { });
554
614
  }
555
615
  const user = ctx.from?.username || ctx.from?.first_name || 'unknown';
556
616
  this.display.log(`MCP '${serverName}' ${enable ? 'enabled' : 'disabled'} by @${user}`, { source: 'Telegram', level: 'info' });
557
617
  await this.handleMcpListCommand(ctx, user);
558
- await ctx.reply(`⚠️ Use /mcpreload for the changes to take effect.`);
618
+ await safeReply(ctx, `⚠️ Use /mcpreload for the changes to take effect.`);
559
619
  }
560
620
  catch (error) {
561
- await ctx.answerCbQuery('Failed to update MCP');
562
- await ctx.reply(`❌ Failed to ${enable ? 'enable' : 'disable'} MCP '${serverName}': ${error.message}`);
621
+ await ctx.answerCbQuery('Failed to update MCP').catch(() => { });
622
+ await safeReply(ctx, `❌ Failed to ${enable ? 'enable' : 'disable'} MCP '${serverName}': ${error.message}`);
563
623
  }
564
624
  });
565
625
  // --- Trinity DB Test Connection ---
566
626
  this.bot.action(/^test_trinity_db_/, async (ctx) => {
567
- const data = ctx.callbackQuery.data;
568
- const id = parseInt(data.replace('test_trinity_db_', ''), 10);
569
- if (isNaN(id)) {
570
- await ctx.answerCbQuery('Invalid ID');
571
- return;
572
- }
573
- await ctx.answerCbQuery('Testing connection…');
574
627
  try {
575
- const { DatabaseRegistry } = await import('../runtime/memory/trinity-db.js');
576
- const { testConnection } = await import('../runtime/trinity-connector.js');
577
- const db = DatabaseRegistry.getInstance().getDatabase(id);
578
- if (!db) {
579
- await ctx.reply('❌ Database not found.');
628
+ const data = ctx.callbackQuery.data;
629
+ const id = parseInt(data.replace('test_trinity_db_', ''), 10);
630
+ if (isNaN(id)) {
631
+ await ctx.answerCbQuery('Invalid ID').catch(() => { });
580
632
  return;
581
633
  }
582
- const ok = await testConnection(db);
583
- await ctx.reply(ok
584
- ? `✅ <b>${escapeHtml(db.name)}</b>: connection successful.`
585
- : `❌ <b>${escapeHtml(db.name)}</b>: connection failed.`, { parse_mode: 'HTML' });
634
+ await ctx.answerCbQuery('Testing connection…').catch(() => { });
635
+ try {
636
+ const { DatabaseRegistry } = await import('../runtime/memory/trinity-db.js');
637
+ const { testConnection } = await import('../runtime/subagents/trinity/connector.js');
638
+ const db = DatabaseRegistry.getInstance().getDatabase(id);
639
+ if (!db) {
640
+ await safeReply(ctx, '❌ Database not found.');
641
+ return;
642
+ }
643
+ const ok = await testConnection(db);
644
+ await safeReply(ctx, ok
645
+ ? `✅ <b>${escapeHtml(db.name)}</b>: connection successful.`
646
+ : `❌ <b>${escapeHtml(db.name)}</b>: connection failed.`, { parse_mode: 'HTML' });
647
+ }
648
+ catch (e) {
649
+ await safeReply(ctx, `❌ Error testing connection: ${escapeHtml(e.message)}`, { parse_mode: 'HTML' });
650
+ }
586
651
  }
587
- catch (e) {
588
- await ctx.reply(`❌ Error testing connection: ${escapeHtml(e.message)}`, { parse_mode: 'HTML' });
652
+ catch (error) {
653
+ console.error('[Telegram] test_trinity_db error:', error.message);
654
+ ctx.answerCbQuery(`Error: ${error.message}`).catch(() => { });
589
655
  }
590
656
  });
591
657
  // --- Trinity DB Refresh Schema ---
592
658
  this.bot.action(/^refresh_trinity_db_schema_/, async (ctx) => {
593
- const data = ctx.callbackQuery.data;
594
- const id = parseInt(data.replace('refresh_trinity_db_schema_', ''), 10);
595
- if (isNaN(id)) {
596
- await ctx.answerCbQuery('Invalid ID');
597
- return;
598
- }
599
- await ctx.answerCbQuery('Refreshing schema…');
600
659
  try {
601
- const { DatabaseRegistry } = await import('../runtime/memory/trinity-db.js');
602
- const { introspectSchema } = await import('../runtime/trinity-connector.js');
603
- const { Trinity } = await import('../runtime/trinity.js');
604
- const registry = DatabaseRegistry.getInstance();
605
- const db = registry.getDatabase(id);
606
- if (!db) {
607
- await ctx.reply('❌ Database not found.');
660
+ const data = ctx.callbackQuery.data;
661
+ const id = parseInt(data.replace('refresh_trinity_db_schema_', ''), 10);
662
+ if (isNaN(id)) {
663
+ await ctx.answerCbQuery('Invalid ID').catch(() => { });
608
664
  return;
609
665
  }
610
- const schema = await introspectSchema(db);
611
- registry.updateSchema(id, JSON.stringify(schema, null, 2));
612
- await Trinity.refreshDelegateCatalog().catch(() => { });
613
- const tableNames = schema.databases
614
- ? schema.databases.flatMap((d) => d.tables.map((t) => `${d.name}.${t.name}`))
615
- : schema.tables.map((t) => t.name);
616
- const count = tableNames.length;
617
- await ctx.reply(`🔄 <b>${escapeHtml(db.name)}</b>: schema refreshed — ${count} ${count === 1 ? 'table' : 'tables'}.`, { parse_mode: 'HTML' });
666
+ await ctx.answerCbQuery('Refreshing schema…').catch(() => { });
667
+ try {
668
+ const { DatabaseRegistry } = await import('../runtime/memory/trinity-db.js');
669
+ const { introspectSchema } = await import('../runtime/subagents/trinity/connector.js');
670
+ const { Trinity } = await import('../runtime/subagents/trinity/trinity.js');
671
+ const registry = DatabaseRegistry.getInstance();
672
+ const db = registry.getDatabase(id);
673
+ if (!db) {
674
+ await safeReply(ctx, '❌ Database not found.');
675
+ return;
676
+ }
677
+ const schema = await introspectSchema(db);
678
+ registry.updateSchema(id, JSON.stringify(schema, null, 2));
679
+ await Trinity.refreshDelegateCatalog().catch(() => { });
680
+ const tableNames = schema.databases
681
+ ? schema.databases.flatMap((d) => d.tables.map((t) => `${d.name}.${t.name}`))
682
+ : schema.tables.map((t) => t.name);
683
+ const count = tableNames.length;
684
+ await safeReply(ctx, `🔄 <b>${escapeHtml(db.name)}</b>: schema refreshed — ${count} ${count === 1 ? 'table' : 'tables'}.`, { parse_mode: 'HTML' });
685
+ }
686
+ catch (e) {
687
+ await safeReply(ctx, `❌ Error refreshing schema: ${escapeHtml(e.message)}`, { parse_mode: 'HTML' });
688
+ }
618
689
  }
619
- catch (e) {
620
- await ctx.reply(`❌ Error refreshing schema: ${escapeHtml(e.message)}`, { parse_mode: 'HTML' });
690
+ catch (error) {
691
+ console.error('[Telegram] refresh_trinity_db_schema error:', error.message);
692
+ ctx.answerCbQuery(`Error: ${error.message}`).catch(() => { });
621
693
  }
622
694
  });
623
695
  // --- Trinity DB Delete Flow ---
624
696
  this.bot.action(/^ask_trinity_db_delete_/, async (ctx) => {
625
- const data = ctx.callbackQuery.data;
626
- const id = parseInt(data.replace('ask_trinity_db_delete_', ''), 10);
627
- if (isNaN(id)) {
628
- await ctx.answerCbQuery('Invalid ID');
629
- return;
630
- }
631
697
  try {
632
- const { DatabaseRegistry } = await import('../runtime/memory/trinity-db.js');
633
- const db = DatabaseRegistry.getInstance().getDatabase(id);
634
- if (!db) {
635
- await ctx.answerCbQuery('Database not found');
698
+ const data = ctx.callbackQuery.data;
699
+ const id = parseInt(data.replace('ask_trinity_db_delete_', ''), 10);
700
+ if (isNaN(id)) {
701
+ await ctx.answerCbQuery('Invalid ID').catch(() => { });
636
702
  return;
637
703
  }
638
- await ctx.answerCbQuery();
639
- 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.`, {
640
- parse_mode: 'HTML',
641
- reply_markup: {
642
- inline_keyboard: [
643
- [
644
- { text: '🗑️ Yes, delete', callback_data: `confirm_trinity_db_delete_${id}` },
645
- { text: 'Cancel', callback_data: 'cancel_trinity_db_delete' },
704
+ try {
705
+ const { DatabaseRegistry } = await import('../runtime/memory/trinity-db.js');
706
+ const db = DatabaseRegistry.getInstance().getDatabase(id);
707
+ if (!db) {
708
+ await ctx.answerCbQuery('Database not found').catch(() => { });
709
+ return;
710
+ }
711
+ await ctx.answerCbQuery().catch(() => { });
712
+ 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.`, {
713
+ parse_mode: 'HTML',
714
+ reply_markup: {
715
+ inline_keyboard: [
716
+ [
717
+ { text: '🗑️ Yes, delete', callback_data: `confirm_trinity_db_delete_${id}` },
718
+ { text: 'Cancel', callback_data: 'cancel_trinity_db_delete' },
719
+ ],
646
720
  ],
647
- ],
648
- },
649
- });
721
+ },
722
+ });
723
+ }
724
+ catch (e) {
725
+ await ctx.answerCbQuery(`Error: ${e.message}`, { show_alert: true }).catch(() => { });
726
+ }
650
727
  }
651
- catch (e) {
652
- await ctx.answerCbQuery(`Error: ${e.message}`, { show_alert: true });
728
+ catch (error) {
729
+ console.error('[Telegram] ask_trinity_db_delete error:', error.message);
730
+ ctx.answerCbQuery(`Error: ${error.message}`).catch(() => { });
653
731
  }
654
732
  });
655
733
  this.bot.action(/^confirm_trinity_db_delete_/, async (ctx) => {
656
- const data = ctx.callbackQuery.data;
657
- const id = parseInt(data.replace('confirm_trinity_db_delete_', ''), 10);
658
- if (isNaN(id)) {
659
- await ctx.answerCbQuery('Invalid ID');
660
- return;
734
+ try {
735
+ const data = ctx.callbackQuery.data;
736
+ const id = parseInt(data.replace('confirm_trinity_db_delete_', ''), 10);
737
+ if (isNaN(id)) {
738
+ await ctx.answerCbQuery('Invalid ID').catch(() => { });
739
+ return;
740
+ }
741
+ try {
742
+ const { DatabaseRegistry } = await import('../runtime/memory/trinity-db.js');
743
+ const registry = DatabaseRegistry.getInstance();
744
+ const db = registry.getDatabase(id);
745
+ const name = db?.name ?? `#${id}`;
746
+ const deleted = registry.deleteDatabase(id);
747
+ await ctx.answerCbQuery(deleted ? '🗑️ Deleted' : 'Not found').catch(() => { });
748
+ if (ctx.updateType === 'callback_query')
749
+ ctx.deleteMessage().catch(() => { });
750
+ const user = ctx.from?.username || ctx.from?.first_name || 'unknown';
751
+ this.display.log(`Trinity DB '${name}' deleted by @${user}`, { source: 'Telegram', level: 'info' });
752
+ await safeReply(ctx, deleted ? `🗑️ <b>${escapeHtml(name)}</b> removed from Trinity.` : `❌ Database #${id} not found.`, { parse_mode: 'HTML' });
753
+ }
754
+ catch (e) {
755
+ await ctx.answerCbQuery(`Error: ${e.message}`, { show_alert: true }).catch(() => { });
756
+ }
661
757
  }
758
+ catch (error) {
759
+ console.error('[Telegram] confirm_trinity_db_delete error:', error.message);
760
+ ctx.answerCbQuery(`Error: ${error.message}`).catch(() => { });
761
+ }
762
+ });
763
+ this.bot.action('cancel_trinity_db_delete', async (ctx) => {
662
764
  try {
663
- const { DatabaseRegistry } = await import('../runtime/memory/trinity-db.js');
664
- const registry = DatabaseRegistry.getInstance();
665
- const db = registry.getDatabase(id);
666
- const name = db?.name ?? `#${id}`;
667
- const deleted = registry.deleteDatabase(id);
668
- await ctx.answerCbQuery(deleted ? '🗑️ Deleted' : 'Not found');
765
+ await ctx.answerCbQuery('Cancelled').catch(() => { });
669
766
  if (ctx.updateType === 'callback_query')
670
767
  ctx.deleteMessage().catch(() => { });
671
- const user = ctx.from?.username || ctx.from?.first_name || 'unknown';
672
- this.display.log(`Trinity DB '${name}' deleted by @${user}`, { source: 'Telegram', level: 'info' });
673
- await ctx.reply(deleted ? `🗑️ <b>${escapeHtml(name)}</b> removed from Trinity.` : `❌ Database #${id} not found.`, { parse_mode: 'HTML' });
674
768
  }
675
- catch (e) {
676
- await ctx.answerCbQuery(`Error: ${e.message}`, { show_alert: true });
769
+ catch (error) {
770
+ console.error('[Telegram] cancel_trinity_db_delete error:', error.message);
771
+ ctx.answerCbQuery(`Error: ${error.message}`).catch(() => { });
677
772
  }
678
773
  });
679
- this.bot.action('cancel_trinity_db_delete', async (ctx) => {
680
- await ctx.answerCbQuery('Cancelled');
681
- if (ctx.updateType === 'callback_query')
682
- ctx.deleteMessage().catch(() => { });
683
- });
684
774
  this.bot.launch().catch((err) => {
685
775
  if (this.isConnected) {
686
776
  this.display.log(`Telegram bot error: ${err}`, { source: 'Telegram', level: 'error' });
@@ -1244,16 +1334,16 @@ export class TelegramAdapter {
1244
1334
  if (!isCurrent) {
1245
1335
  sessionButtons.push({
1246
1336
  text: `➡️ Switch`,
1247
- callback_data: `switch_session_${session.id}`
1337
+ callback_data: safeCallbackData(session.id, 'switch_session_')
1248
1338
  });
1249
1339
  }
1250
1340
  sessionButtons.push({
1251
1341
  text: `📂 Archive`,
1252
- callback_data: `ask_archive_session_${session.id}`
1342
+ callback_data: safeCallbackData(session.id, 'ask_archive_session_')
1253
1343
  });
1254
1344
  sessionButtons.push({
1255
1345
  text: `🗑️ Delete`,
1256
- callback_data: `ask_delete_session_${session.id}`
1346
+ callback_data: safeCallbackData(session.id, 'ask_delete_session_')
1257
1347
  });
1258
1348
  keyboard.push(sessionButtons);
1259
1349
  }
@@ -15,8 +15,8 @@ import { HttpServer } from '../../http/server.js';
15
15
  import { getVersion } from '../utils/version.js';
16
16
  import { TaskWorker } from '../../runtime/tasks/worker.js';
17
17
  import { TaskNotifier } from '../../runtime/tasks/notifier.js';
18
- import { Link } from '../../runtime/link.js';
19
- import { LinkWorker } from '../../runtime/link-worker.js';
18
+ import { Link } from '../../runtime/subagents/link/link.js';
19
+ import { LinkWorker } from '../../runtime/subagents/link/worker.js';
20
20
  export const restartCommand = new Command('restart')
21
21
  .description('Restart the Morpheus agent')
22
22
  .option('--ui', 'Enable web UI', true)
@@ -26,8 +26,8 @@ import { ChronosRepository } from '../../runtime/chronos/repository.js';
26
26
  import { SkillRegistry } from '../../runtime/skills/index.js';
27
27
  import { MCPToolCache } from '../../runtime/tools/cache.js';
28
28
  import { SmithRegistry } from '../../runtime/smiths/registry.js';
29
- import { Link } from '../../runtime/link.js';
30
- import { LinkWorker } from '../../runtime/link-worker.js';
29
+ import { Link } from '../../runtime/subagents/link/link.js';
30
+ import { LinkWorker } from '../../runtime/subagents/link/worker.js';
31
31
  // Load .env file explicitly in start command
32
32
  const envPath = path.join(process.cwd(), '.env');
33
33
  if (fs.existsSync(envPath)) {
package/dist/http/api.js CHANGED
@@ -13,8 +13,8 @@ import { MCPServerConfigSchema } from '../config/schemas.js';
13
13
  import { Construtor } from '../runtime/tools/factory.js';
14
14
  import { TaskRepository } from '../runtime/tasks/repository.js';
15
15
  import { DatabaseRegistry } from '../runtime/memory/trinity-db.js';
16
- import { testConnection, introspectSchema } from '../runtime/trinity-connector.js';
17
- import { Trinity } from '../runtime/trinity.js';
16
+ import { testConnection, introspectSchema } from '../runtime/subagents/trinity/connector.js';
17
+ import { Trinity } from '../runtime/subagents/trinity/trinity.js';
18
18
  import { ChronosRepository } from '../runtime/chronos/repository.js';
19
19
  import { ChronosWorker } from '../runtime/chronos/worker.js';
20
20
  import { createChronosJobRouter, createChronosConfigRouter } from './routers/chronos.js';
@@ -22,6 +22,7 @@ import { createSkillsRouter } from './routers/skills.js';
22
22
  import { createSmithsRouter } from './routers/smiths.js';
23
23
  import { createDangerRouter } from './routers/danger.js';
24
24
  import { createLinkRouter } from './routers/link.js';
25
+ import { createAgentsRouter } from './routers/agents.js';
25
26
  import { getActiveEnvOverrides } from '../config/precedence.js';
26
27
  import { hotReloadConfig, getRestartRequiredChanges } from '../runtime/hot-reload.js';
27
28
  import { AuditRepository } from '../runtime/audit/repository.js';
@@ -55,6 +56,8 @@ export function createApiRouter(oracle, chronosWorker) {
55
56
  router.use('/danger', createDangerRouter());
56
57
  // Mount Link router (Documentation management)
57
58
  router.use('/link', createLinkRouter());
59
+ // Mount Agents metadata router
60
+ router.use('/agents', createAgentsRouter());
58
61
  // --- Session Management ---
59
62
  router.get('/sessions', async (req, res) => {
60
63
  try {
@@ -0,0 +1,9 @@
1
+ import { Router } from 'express';
2
+ import { SubagentRegistry } from '../../runtime/subagents/registry.js';
3
+ export function createAgentsRouter() {
4
+ const router = Router();
5
+ router.get('/metadata', (_req, res) => {
6
+ res.json({ agents: SubagentRegistry.getDisplayMetadata() });
7
+ });
8
+ return router;
9
+ }
@@ -3,8 +3,8 @@ import multer from 'multer';
3
3
  import path from 'path';
4
4
  import fs from 'fs-extra';
5
5
  import { homedir } from 'os';
6
- import { LinkRepository } from '../../runtime/link-repository.js';
7
- import { LinkWorker } from '../../runtime/link-worker.js';
6
+ import { LinkRepository } from '../../runtime/subagents/link/repository.js';
7
+ import { LinkWorker } from '../../runtime/subagents/link/worker.js';
8
8
  import { ConfigManager } from '../../config/manager.js';
9
9
  const DOCS_PATH = path.join(homedir(), '.morpheus', 'docs');
10
10
  // Configure multer for file uploads