morpheus-cli 0.9.12 → 0.9.20

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 (106) hide show
  1. package/README.md +49 -18
  2. package/dist/channels/discord.js +93 -6
  3. package/dist/channels/telegram.js +112 -12
  4. package/dist/cli/commands/restart.js +2 -2
  5. package/dist/cli/commands/start.js +17 -2
  6. package/dist/config/manager.js +20 -1
  7. package/dist/config/paths.js +4 -0
  8. package/dist/config/schemas.js +15 -0
  9. package/dist/http/api.js +7 -3
  10. package/dist/http/routers/agents.js +9 -0
  11. package/dist/http/routers/danger.js +4 -5
  12. package/dist/http/routers/link.js +4 -4
  13. package/dist/runtime/__tests__/telephonist-tts.test.js +84 -0
  14. package/dist/runtime/adapters/AuditRepositoryAdapter.js +6 -0
  15. package/dist/runtime/adapters/ChannelNotifierAdapter.js +9 -0
  16. package/dist/runtime/adapters/LangChainProviderAdapter.js +9 -0
  17. package/dist/runtime/adapters/SQLiteChatHistoryAdapter.js +15 -0
  18. package/dist/runtime/adapters/SQLiteTaskEnqueuerAdapter.js +6 -0
  19. package/dist/runtime/adapters/index.js +5 -0
  20. package/dist/runtime/audit/repository.js +6 -2
  21. package/dist/runtime/chronos/repository.js +7 -5
  22. package/dist/runtime/chronos/worker.js +27 -4
  23. package/dist/runtime/chronos/worker.test.js +3 -2
  24. package/dist/runtime/container.js +50 -0
  25. package/dist/runtime/hot-reload.js +6 -9
  26. package/dist/runtime/memory/backfill-embeddings.js +2 -3
  27. package/dist/runtime/memory/sati/repository.js +3 -3
  28. package/dist/runtime/memory/sqlite.js +40 -14
  29. package/dist/runtime/memory/trinity-db.js +2 -2
  30. package/dist/runtime/oracle.js +78 -43
  31. package/dist/runtime/ports/IChatHistory.js +1 -0
  32. package/dist/runtime/ports/ILLMProviderFactory.js +1 -0
  33. package/dist/runtime/ports/INotifier.js +1 -0
  34. package/dist/runtime/ports/ITaskEnqueuer.js +1 -0
  35. package/dist/runtime/ports/index.js +1 -0
  36. package/dist/runtime/providers/factory.js +8 -52
  37. package/dist/runtime/providers/strategies.js +66 -0
  38. package/dist/runtime/setup/repository.js +2 -2
  39. package/dist/runtime/subagents/ISubagent.js +1 -0
  40. package/dist/runtime/{apoc.js → subagents/apoc.js} +20 -7
  41. package/dist/runtime/{devkit-instrument.js → subagents/devkit-instrument.js} +1 -1
  42. package/dist/runtime/subagents/index.js +12 -0
  43. package/dist/runtime/{link.js → subagents/link/link.js} +24 -10
  44. package/dist/runtime/{link-repository.js → subagents/link/repository.js} +4 -4
  45. package/dist/runtime/{link-search.js → subagents/link/search.js} +3 -3
  46. package/dist/runtime/{link-worker.js → subagents/link/worker.js} +9 -9
  47. package/dist/runtime/{neo.js → subagents/neo.js} +24 -10
  48. package/dist/runtime/subagents/registry.js +134 -0
  49. package/dist/runtime/{trinity.js → subagents/trinity/trinity.js} +23 -9
  50. package/dist/runtime/{subagent-utils.js → subagents/utils.js} +2 -2
  51. package/dist/runtime/tasks/repository.js +2 -2
  52. package/dist/runtime/tasks/worker.js +6 -70
  53. package/dist/runtime/telephonist.js +160 -0
  54. package/dist/runtime/tools/chronos-tools.js +1 -0
  55. package/dist/runtime/tools/delegation-utils.js +5 -7
  56. package/dist/runtime/tools/morpheus-tools.js +9 -10
  57. package/dist/runtime/tools/smith-tool.js +5 -7
  58. package/dist/runtime/webhooks/dispatcher.js +4 -0
  59. package/dist/runtime/webhooks/repository.js +2 -2
  60. package/dist/types/config.js +6 -0
  61. package/dist/ui/assets/AuditDashboard-Cu33zb_7.js +1 -0
  62. package/dist/ui/assets/Chat-mt1j5V55.js +41 -0
  63. package/dist/ui/assets/{Chronos-D1yAb4M5.js → Chronos-Bq_h41cw.js} +1 -1
  64. package/dist/ui/assets/{ConfirmationModal-DxUHZgTy.js → ConfirmationModal-CxLP8iC6.js} +1 -1
  65. package/dist/ui/assets/{Dashboard-BzxmcHaS.js → Dashboard-D0LAlHtG.js} +1 -1
  66. package/dist/ui/assets/{DeleteConfirmationModal-CqNXT_YQ.js → DeleteConfirmationModal-kZ_c3sFk.js} +1 -1
  67. package/dist/ui/assets/{Documents-DLFZdmim.js → Documents-nlQNoUcq.js} +1 -1
  68. package/dist/ui/assets/{Logs-B1Bpy9dB.js → Logs-C1tlg574.js} +1 -1
  69. package/dist/ui/assets/{MCPManager-BbUDMh5Q.js → MCPManager-Do7isizG.js} +1 -1
  70. package/dist/ui/assets/ModelPricing-BeJ7oXBA.js +1 -0
  71. package/dist/ui/assets/{Notifications-8Cqj-mNp.js → Notifications-Cg5CMlY0.js} +1 -1
  72. package/dist/ui/assets/{SatiMemories-CdHUe6di.js → SatiMemories-D9l6s8Pc.js} +1 -1
  73. package/dist/ui/assets/SessionAudit-Da1ySlYg.js +9 -0
  74. package/dist/ui/assets/Settings-DpXwpEhO.js +49 -0
  75. package/dist/ui/assets/{Skills-0k7A2T5_.js → Skills-DaqCY8QH.js} +1 -1
  76. package/dist/ui/assets/Smiths-DA-x4KFT.js +1 -0
  77. package/dist/ui/assets/Switch-CJTE4ZQm.js +1 -0
  78. package/dist/ui/assets/Tasks-DU49M9U-.js +1 -0
  79. package/dist/ui/assets/{TrinityDatabases-CGna6IMX.js → TrinityDatabases-CoKzKTL-.js} +1 -1
  80. package/dist/ui/assets/{UsageStats-B7EzZlZe.js → UsageStats-cds352Pj.js} +1 -1
  81. package/dist/ui/assets/{WebhookManager-Bb7KiucS.js → WebhookManager-DdAdHQUk.js} +1 -1
  82. package/dist/ui/assets/agents-B1z_dlQC.js +1 -0
  83. package/dist/ui/assets/{audit-CJ2Ms81U.js → audit-BAhaGrKY.js} +1 -1
  84. package/dist/ui/assets/{chronos-Bm68OSy4.js → chronos-DGD_Md9M.js} +1 -1
  85. package/dist/ui/assets/config-BwTXe5M2.js +1 -0
  86. package/dist/ui/assets/{index-BxN2w9sY.js → index-BcX5O7kY.js} +2 -2
  87. package/dist/ui/assets/index-Cjli-AD7.css +1 -0
  88. package/dist/ui/assets/{mcp-BE_OVkBe.js → mcp-BlkruPaA.js} +1 -1
  89. package/dist/ui/assets/{skills-Dt0qU4gH.js → skills-CtCb-52u.js} +1 -1
  90. package/dist/ui/assets/{stats-Bmdps1LR.js → stats-BiPI2kaw.js} +1 -1
  91. package/dist/ui/assets/useCurrency-BCdG-pHx.js +1 -0
  92. package/dist/ui/index.html +2 -2
  93. package/dist/ui/sw.js +1 -1
  94. package/package.json +1 -1
  95. package/dist/ui/assets/AuditDashboard-CM1YN1uk.js +0 -1
  96. package/dist/ui/assets/Chat-D4y-g6Tw.js +0 -41
  97. package/dist/ui/assets/ModelPricing-DCl-2_eJ.js +0 -1
  98. package/dist/ui/assets/SessionAudit-CtVHK_IH.js +0 -9
  99. package/dist/ui/assets/Settings-Clge45Z0.js +0 -49
  100. package/dist/ui/assets/Smiths-gjgBMN1F.js +0 -1
  101. package/dist/ui/assets/Tasks-AQ3MrrMp.js +0 -1
  102. package/dist/ui/assets/config-C88yQ_CP.js +0 -1
  103. package/dist/ui/assets/index-C3Ff736M.css +0 -1
  104. /package/dist/runtime/{ISubagent.js → ports/IAuditEmitter.js} +0 -0
  105. /package/dist/runtime/{link-chunker.js → subagents/link/chunker.js} +0 -0
  106. /package/dist/runtime/{trinity-connector.js → subagents/trinity/connector.js} +0 -0
@@ -5,12 +5,9 @@ import { ProviderError } from "./errors.js";
5
5
  import { DisplayManager } from "./display.js";
6
6
  import { SQLiteChatMessageHistory } from "./memory/sqlite.js";
7
7
  import { SatiMemoryMiddleware } from "./memory/sati/index.js";
8
- import { Apoc } from "./apoc.js";
8
+ import { Apoc, Neo, Trinity, Link, SubagentRegistry, emitToolAuditEvents } from "./subagents/index.js";
9
9
  import { TaskRequestContext } from "./tasks/context.js";
10
10
  import { TaskRepository } from "./tasks/repository.js";
11
- import { Neo } from "./neo.js";
12
- import { Trinity } from "./trinity.js";
13
- import { Link } from "./link.js";
14
11
  import { SmithDelegateTool } from "./tools/smith-tool.js";
15
12
  import { TaskQueryTool, chronosTools, timeVerifierTool } from "./tools/index.js";
16
13
  import { Construtor } from "./tools/factory.js";
@@ -20,12 +17,9 @@ import { SmithRegistry } from "./smiths/registry.js";
20
17
  import { AuditRepository } from "./audit/repository.js";
21
18
  import { SetupRepository } from './setup/repository.js';
22
19
  import { buildSetupTool } from './tools/setup-tool.js';
23
- import { emitToolAuditEvents } from "./subagent-utils.js";
20
+ import { SmithDelegator } from "./smiths/delegator.js";
24
21
  import { PATHS } from "../config/paths.js";
25
22
  import { writeFileSync } from "fs";
26
- const ORACLE_DELEGATION_TOOLS = new Set([
27
- 'apoc_delegate', 'neo_delegate', 'trinity_delegate', 'smith_delegate', 'link_delegate',
28
- ]);
29
23
  export class Oracle {
30
24
  provider;
31
25
  config;
@@ -40,6 +34,48 @@ export class Oracle {
40
34
  this.config = config || ConfigManager.getInstance().get();
41
35
  this.databasePath = overrides?.databasePath;
42
36
  }
37
+ /**
38
+ * Registers Smith in the SubagentRegistry if Smiths are configured and enabled.
39
+ * Smith is special — it uses a standalone tool (SmithDelegateTool) and SmithDelegator
40
+ * rather than implementing ISubagent, so we create a minimal registration.
41
+ */
42
+ registerSmithIfEnabled() {
43
+ const smithsConfig = ConfigManager.getInstance().getSmithsConfig();
44
+ if (!smithsConfig.enabled || smithsConfig.entries.length === 0)
45
+ return;
46
+ if (SubagentRegistry.get('smith'))
47
+ return; // already registered
48
+ const delegator = SmithDelegator.getInstance();
49
+ SubagentRegistry.register({
50
+ agentKey: 'smith', auditAgent: 'smith', label: 'Smith',
51
+ delegateToolName: 'smith_delegate', emoji: '🕶️', color: 'gray',
52
+ description: 'Remote DevKit execution',
53
+ colorClass: 'text-gray-500 dark:text-gray-400',
54
+ bgClass: 'bg-gray-50 dark:bg-zinc-900',
55
+ badgeClass: 'bg-gray-200 text-gray-700 dark:bg-gray-700/60 dark:text-gray-300',
56
+ instance: {
57
+ initialize: async () => { },
58
+ execute: async (task, context) => delegator.delegate('unknown', task, context),
59
+ reload: async () => { },
60
+ createDelegateTool: () => SmithDelegateTool,
61
+ },
62
+ hasDynamicDescription: false,
63
+ isMultiInstance: true,
64
+ executeTask: async (task) => {
65
+ let smithName = 'unknown';
66
+ if (task.context) {
67
+ try {
68
+ const parsed = JSON.parse(task.context);
69
+ smithName = parsed.smith_name || parsed.smith || 'unknown';
70
+ }
71
+ catch {
72
+ smithName = task.context;
73
+ }
74
+ }
75
+ return delegator.delegate(smithName, task.input, task.context ?? undefined);
76
+ },
77
+ });
78
+ }
43
79
  buildDelegationFailureResponse() {
44
80
  return "Task enqueue could not be confirmed in the database. No task was created. Please retry.";
45
81
  }
@@ -115,13 +151,16 @@ export class Oracle {
115
151
  return valid;
116
152
  }
117
153
  hasDelegationToolCall(messages) {
154
+ const delegationTools = SubagentRegistry.getDelegationToolNames();
155
+ // Also include smith_delegate which may not be in registry if smiths are disabled
156
+ delegationTools.add('smith_delegate');
118
157
  for (const msg of messages) {
119
158
  if (!(msg instanceof AIMessage))
120
159
  continue;
121
160
  const toolCalls = msg.tool_calls ?? [];
122
161
  if (!Array.isArray(toolCalls))
123
162
  continue;
124
- if (toolCalls.some((tc) => tc?.name === "apoc_delegate" || tc?.name === "neo_delegate" || tc?.name === "trinity_delegate" || tc?.name === "smith_delegate" || tc?.name === "link_delegate")) {
163
+ if (toolCalls.some((tc) => delegationTools.has(tc?.name))) {
125
164
  return true;
126
165
  }
127
166
  }
@@ -152,27 +191,30 @@ export class Oracle {
152
191
  // Note: API Key validation is delegated to ProviderFactory or the Provider itself
153
192
  // to allow for Environment Variable fallback supported by LangChain.
154
193
  try {
155
- // Refresh Neo and Trinity tool catalogs so delegate descriptions contain runtime info.
156
- // Fail-open: Oracle can still initialize even if catalog refresh fails.
157
- await Neo.refreshDelegateCatalog().catch(() => { });
158
- await Trinity.refreshDelegateCatalog().catch(() => { });
159
- await Link.refreshDelegateCatalog().catch(() => { });
160
- // Build tool list conditionally include SmithDelegateTool based on config
194
+ // Ensure subagents are instantiated and self-registered before using the registry.
195
+ Apoc.getInstance();
196
+ Neo.getInstance();
197
+ Trinity.getInstance();
198
+ Link.getInstance();
199
+ // Register Smith in the registry if configured
200
+ this.registerSmithIfEnabled();
201
+ // Refresh dynamic tool catalogs so delegate descriptions contain runtime info.
202
+ await SubagentRegistry.refreshAllCatalogs();
161
203
  // Initialize setup repository (creates table if needed)
162
204
  SetupRepository.getInstance();
163
205
  const coreTools = [
164
206
  buildSetupTool(),
165
207
  TaskQueryTool,
166
- Neo.getInstance().createDelegateTool(),
167
- Apoc.getInstance().createDelegateTool(),
168
- Trinity.getInstance().createDelegateTool(),
169
- Link.getInstance().createDelegateTool(),
208
+ ...SubagentRegistry.getDelegationTools(),
170
209
  createLoadSkillTool(),
171
210
  timeVerifierTool,
172
211
  ...chronosTools,
173
212
  ];
213
+ // Smith's tool is already included via SubagentRegistry.getDelegationTools()
214
+ // if registerSmithIfEnabled() registered it. Only add it if Smith is enabled
215
+ // but NOT yet in the registry (shouldn't happen, but defensive).
174
216
  const smithsConfig = ConfigManager.getInstance().getSmithsConfig();
175
- if (smithsConfig.enabled && smithsConfig.entries.length > 0) {
217
+ if (smithsConfig.enabled && smithsConfig.entries.length > 0 && !SubagentRegistry.get('smith')) {
176
218
  coreTools.push(SmithDelegateTool);
177
219
  }
178
220
  this.provider = await ProviderFactory.create(this.config.llm, coreTools);
@@ -225,12 +267,14 @@ export class Oracle {
225
267
  provider: isTelephonist ? this.config.audio?.provider : this.config.llm.provider,
226
268
  model: isTelephonist ? this.config.audio?.model : this.config.llm.model
227
269
  };
228
- // Inject source metadata for automated origins (webhook, chronos)
229
- if (taskContext?.origin_channel === 'webhook') {
230
- userMessage.source_metadata = { source: 'webhook' };
231
- }
232
- else if (taskContext?.origin_channel === 'chronos') {
233
- userMessage.source_metadata = { source: 'chronos' };
270
+ // Inject source metadata for automated origins (webhook, chronos).
271
+ // Prefer explicit taskContext.source (set by Chronos even when origin_channel
272
+ // points to a specific notification channel like 'telegram').
273
+ const messageSource = taskContext?.source ?? (taskContext?.origin_channel === 'webhook' ? 'webhook'
274
+ : taskContext?.origin_channel === 'chronos' ? 'chronos'
275
+ : null);
276
+ if (messageSource) {
277
+ userMessage.source_metadata = { source: messageSource };
234
278
  }
235
279
  // Attach extra usage (e.g. from Audio) to the user message to be persisted
236
280
  if (extraUsage) {
@@ -465,10 +509,7 @@ Use it to inform your response and tool selection (if needed), but do not assume
465
509
  }
466
510
  messages.push(...previousMessages);
467
511
  messages.push(userMessage);
468
- Apoc.setSessionId(currentSessionId);
469
- Neo.setSessionId(currentSessionId);
470
- Trinity.setSessionId(currentSessionId);
471
- Link.setSessionId(currentSessionId);
512
+ SubagentRegistry.setAllSessionIds(currentSessionId);
472
513
  const invokeContext = {
473
514
  origin_channel: taskContext?.origin_channel ?? "api",
474
515
  session_id: taskContext?.session_id ?? currentSessionId ?? "default",
@@ -514,8 +555,10 @@ Use it to inform your response and tool selection (if needed), but do not assume
514
555
  // Emit tool_call audit events for Oracle's independent tool calls.
515
556
  // Delegation tools (apoc/neo/trinity/smith/skill/link) are already audited
516
557
  // inside buildDelegationTool or the task system — skip them here.
558
+ const delegationToolNames = SubagentRegistry.getDelegationToolNames();
559
+ delegationToolNames.add('smith_delegate');
517
560
  emitToolAuditEvents(newGeneratedMessages, currentSessionId ?? 'default', 'oracle', {
518
- skipTools: ORACLE_DELEGATION_TOOLS,
561
+ skipTools: delegationToolNames,
519
562
  });
520
563
  // Inject provider/model metadata and duration into all new AI messages
521
564
  for (const msg of newGeneratedMessages) {
@@ -694,24 +737,16 @@ Use it to inform your response and tool selection (if needed), but do not assume
694
737
  }
695
738
  // Reload MCP tool cache from servers (slow path)
696
739
  await Construtor.reload();
697
- await Neo.refreshDelegateCatalog().catch(() => { });
698
- await Trinity.refreshDelegateCatalog().catch(() => { });
699
- await Link.refreshDelegateCatalog().catch(() => { });
740
+ await SubagentRegistry.refreshAllCatalogs();
700
741
  this.provider = await ProviderFactory.create(this.config.llm, [
701
742
  buildSetupTool(),
702
743
  TaskQueryTool,
703
- Neo.getInstance().createDelegateTool(),
704
- Apoc.getInstance().createDelegateTool(),
705
- Trinity.getInstance().createDelegateTool(),
706
- Link.getInstance().createDelegateTool(),
744
+ ...SubagentRegistry.getDelegationTools(),
707
745
  createLoadSkillTool(),
708
746
  timeVerifierTool,
709
747
  ...chronosTools,
710
748
  ]);
711
- await Neo.getInstance().reload();
712
- await Apoc.getInstance().reload();
713
- await Trinity.getInstance().reload();
714
- await Link.getInstance().reload();
715
- this.display.log(`Oracle and Neo tools reloaded`, { source: 'Oracle' });
749
+ await SubagentRegistry.reloadAll();
750
+ this.display.log(`Oracle and subagent tools reloaded`, { source: 'Oracle' });
716
751
  }
717
752
  }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -1,16 +1,13 @@
1
- import { ChatOpenAI } from "@langchain/openai";
2
- import { ChatAnthropic } from "@langchain/anthropic";
3
- import { ChatOllama } from "@langchain/ollama";
4
- import { ChatGoogleGenerativeAI } from "@langchain/google-genai";
5
1
  import { ProviderError } from "../errors.js";
6
2
  import { createAgent, createMiddleware } from "langchain";
7
3
  import { DisplayManager } from "../display.js";
8
- import { getUsableApiKey } from "../trinity-crypto.js";
9
4
  import { ConfigManager } from "../../config/manager.js";
10
5
  import { TaskRequestContext } from "../tasks/context.js";
11
6
  import { ChannelRegistry } from "../../channels/registry.js";
7
+ import { getStrategy, registerStrategy } from "./strategies.js";
12
8
  /** Channels that should NOT receive verbose tool notifications */
13
9
  const SILENT_CHANNELS = new Set(['api', 'ui']);
10
+ export { registerStrategy };
14
11
  export class ProviderFactory {
15
12
  static buildMonitoringMiddleware() {
16
13
  const display = DisplayManager.getInstance();
@@ -41,44 +38,11 @@ export class ProviderFactory {
41
38
  });
42
39
  }
43
40
  static buildModel(config) {
44
- const usableApiKey = getUsableApiKey(config.api_key);
45
- switch (config.provider) {
46
- case 'openai':
47
- return new ChatOpenAI({
48
- modelName: config.model,
49
- temperature: config.temperature,
50
- apiKey: process.env.OPENAI_API_KEY || usableApiKey,
51
- });
52
- case 'anthropic':
53
- return new ChatAnthropic({
54
- modelName: config.model,
55
- temperature: config.temperature,
56
- apiKey: process.env.ANTHROPIC_API_KEY || usableApiKey,
57
- });
58
- case 'openrouter':
59
- return new ChatOpenAI({
60
- modelName: config.model,
61
- temperature: config.temperature,
62
- apiKey: process.env.OPENROUTER_API_KEY || usableApiKey,
63
- configuration: {
64
- baseURL: config.base_url || 'https://openrouter.ai/api/v1'
65
- }
66
- });
67
- case 'ollama':
68
- return new ChatOllama({
69
- model: config.model,
70
- temperature: config.temperature,
71
- baseUrl: config.base_url || usableApiKey,
72
- });
73
- case 'gemini':
74
- return new ChatGoogleGenerativeAI({
75
- model: config.model,
76
- temperature: config.temperature,
77
- apiKey: process.env.GOOGLE_API_KEY || usableApiKey
78
- });
79
- default:
80
- throw new Error(`Unsupported provider: ${config.provider}`);
41
+ const strategy = getStrategy(config.provider);
42
+ if (!strategy) {
43
+ throw new Error(`Unsupported provider: ${config.provider}`);
81
44
  }
45
+ return strategy.build(config);
82
46
  }
83
47
  static handleProviderError(config, error) {
84
48
  let suggestion = "Check your configuration and API keys.";
@@ -114,14 +78,6 @@ export class ProviderFactory {
114
78
  ProviderFactory.handleProviderError(config, error);
115
79
  }
116
80
  }
117
- static async create(config, tools = []) {
118
- try {
119
- const model = ProviderFactory.buildModel(config);
120
- const middleware = ProviderFactory.buildMonitoringMiddleware();
121
- return createAgent({ model, tools, middleware: [middleware] });
122
- }
123
- catch (error) {
124
- ProviderFactory.handleProviderError(config, error);
125
- }
126
- }
81
+ /** Alias for createBare both methods are identical. */
82
+ static create = ProviderFactory.createBare;
127
83
  }
@@ -0,0 +1,66 @@
1
+ import { ChatOpenAI } from "@langchain/openai";
2
+ import { ChatAnthropic } from "@langchain/anthropic";
3
+ import { ChatOllama } from "@langchain/ollama";
4
+ import { ChatGoogleGenerativeAI } from "@langchain/google-genai";
5
+ import { getUsableApiKey } from "../trinity-crypto.js";
6
+ class OpenAIStrategy {
7
+ build(config) {
8
+ return new ChatOpenAI({
9
+ modelName: config.model,
10
+ temperature: config.temperature,
11
+ apiKey: process.env.OPENAI_API_KEY || getUsableApiKey(config.api_key),
12
+ });
13
+ }
14
+ }
15
+ class AnthropicStrategy {
16
+ build(config) {
17
+ return new ChatAnthropic({
18
+ modelName: config.model,
19
+ temperature: config.temperature,
20
+ apiKey: process.env.ANTHROPIC_API_KEY || getUsableApiKey(config.api_key),
21
+ });
22
+ }
23
+ }
24
+ class OpenRouterStrategy {
25
+ build(config) {
26
+ return new ChatOpenAI({
27
+ modelName: config.model,
28
+ temperature: config.temperature,
29
+ apiKey: process.env.OPENROUTER_API_KEY || getUsableApiKey(config.api_key),
30
+ configuration: {
31
+ baseURL: config.base_url || 'https://openrouter.ai/api/v1'
32
+ }
33
+ });
34
+ }
35
+ }
36
+ class OllamaStrategy {
37
+ build(config) {
38
+ return new ChatOllama({
39
+ model: config.model,
40
+ temperature: config.temperature,
41
+ baseUrl: config.base_url || getUsableApiKey(config.api_key),
42
+ });
43
+ }
44
+ }
45
+ class GeminiStrategy {
46
+ build(config) {
47
+ return new ChatGoogleGenerativeAI({
48
+ model: config.model,
49
+ temperature: config.temperature,
50
+ apiKey: process.env.GOOGLE_API_KEY || getUsableApiKey(config.api_key),
51
+ });
52
+ }
53
+ }
54
+ const strategies = new Map([
55
+ ['openai', new OpenAIStrategy()],
56
+ ['anthropic', new AnthropicStrategy()],
57
+ ['openrouter', new OpenRouterStrategy()],
58
+ ['ollama', new OllamaStrategy()],
59
+ ['gemini', new GeminiStrategy()],
60
+ ]);
61
+ export function registerStrategy(provider, strategy) {
62
+ strategies.set(provider, strategy);
63
+ }
64
+ export function getStrategy(provider) {
65
+ return strategies.get(provider);
66
+ }
@@ -1,13 +1,13 @@
1
1
  import Database from 'better-sqlite3';
2
2
  import fs from 'fs-extra';
3
3
  import path from 'path';
4
- import { homedir } from 'os';
5
4
  import { ConfigManager } from '../../config/manager.js';
5
+ import { PATHS } from '../../config/paths.js';
6
6
  export class SetupRepository {
7
7
  static instance = null;
8
8
  db;
9
9
  constructor(dbPath) {
10
- const resolvedPath = dbPath ?? path.join(homedir(), '.morpheus', 'memory', 'short-memory.db');
10
+ const resolvedPath = dbPath ?? PATHS.shortMemoryDb;
11
11
  fs.ensureDirSync(path.dirname(resolvedPath));
12
12
  this.db = new Database(resolvedPath, { timeout: 5000 });
13
13
  this.db.pragma('journal_mode = WAL');
@@ -0,0 +1 @@
1
+ export {};
@@ -1,12 +1,13 @@
1
1
  import { HumanMessage, SystemMessage, AIMessage } from "@langchain/core/messages";
2
- import { ConfigManager } from "../config/manager.js";
3
- import { ProviderFactory } from "./providers/factory.js";
4
- import { ProviderError } from "./errors.js";
5
- import { DisplayManager } from "./display.js";
2
+ import { ConfigManager } from "../../config/manager.js";
3
+ import { ServiceContainer, SERVICE_KEYS } from "../container.js";
4
+ import { ProviderError } from "../errors.js";
5
+ import { DisplayManager } from "../display.js";
6
6
  import { buildDevKit } from "morpheus-devkit";
7
7
  import { instrumentDevKitTools } from "./devkit-instrument.js";
8
- import { extractRawUsage, persistAgentMessage, buildAgentResult, emitToolAuditEvents } from "./subagent-utils.js";
9
- import { buildDelegationTool } from "./tools/delegation-utils.js";
8
+ import { extractRawUsage, persistAgentMessage, buildAgentResult, emitToolAuditEvents } from "./utils.js";
9
+ import { buildDelegationTool } from "../tools/delegation-utils.js";
10
+ import { SubagentRegistry } from "./registry.js";
10
11
  /**
11
12
  * Apoc is a subagent of Oracle specialized in devtools operations.
12
13
  * It receives delegated tasks from Oracle and executes them using DevKit tools
@@ -36,6 +37,18 @@ export class Apoc {
36
37
  static getInstance(config) {
37
38
  if (!Apoc.instance) {
38
39
  Apoc.instance = new Apoc(config);
40
+ SubagentRegistry.register({
41
+ agentKey: 'apoc', auditAgent: 'apoc', label: 'Apoc',
42
+ delegateToolName: 'apoc_delegate', emoji: '🧑‍🔬', color: 'amber',
43
+ description: 'Filesystem, shell & browser',
44
+ colorClass: 'text-amber-600 dark:text-amber-400',
45
+ bgClass: 'bg-amber-50 dark:bg-amber-900/10',
46
+ badgeClass: 'bg-amber-100 text-amber-700 dark:bg-amber-900/40 dark:text-amber-300',
47
+ instance: Apoc.instance,
48
+ hasDynamicDescription: false,
49
+ isMultiInstance: false,
50
+ setSessionId: (id) => Apoc.setSessionId(id),
51
+ });
39
52
  }
40
53
  return Apoc.instance;
41
54
  }
@@ -65,7 +78,7 @@ export class Apoc {
65
78
  const tools = instrumentDevKitTools(rawTools, () => Apoc.currentSessionId, () => 'apoc');
66
79
  this.display.log(`Apoc initialized with ${tools.length} DevKit tools (sandbox_dir: ${devkit.sandbox_dir}, personality: ${personality})`, { source: "Apoc" });
67
80
  try {
68
- this.agent = await ProviderFactory.createBare(apocConfig, tools);
81
+ this.agent = await ServiceContainer.get(SERVICE_KEYS.providerFactory).createBare(apocConfig, tools);
69
82
  }
70
83
  catch (err) {
71
84
  throw new ProviderError(apocConfig.provider, err, "Apoc subagent initialization failed");
@@ -1,4 +1,4 @@
1
- import { AuditRepository } from './audit/repository.js';
1
+ import { AuditRepository } from '../audit/repository.js';
2
2
  /**
3
3
  * Wraps a StructuredTool to record audit events on each invocation.
4
4
  * The `getSessionId` getter is called at invocation time so it reflects
@@ -0,0 +1,12 @@
1
+ // Re-exports for convenient external access
2
+ export { Apoc } from './apoc.js';
3
+ export { Neo } from './neo.js';
4
+ export { Trinity } from './trinity/trinity.js';
5
+ export { Link } from './link/link.js';
6
+ export { SubagentRegistry, SYSTEM_AGENTS } from './registry.js';
7
+ export { extractRawUsage, persistAgentMessage, buildAgentResult, emitToolAuditEvents } from './utils.js';
8
+ export { LinkRepository } from './link/repository.js';
9
+ export { LinkWorker } from './link/worker.js';
10
+ export { LinkSearch } from './link/search.js';
11
+ export { instrumentDevKitTools } from './devkit-instrument.js';
12
+ export { testConnection, introspectSchema, executeQuery } from './trinity/connector.js';
@@ -1,15 +1,16 @@
1
1
  import { HumanMessage, SystemMessage, AIMessage } from "@langchain/core/messages";
2
2
  import { z } from "zod";
3
3
  import { DynamicStructuredTool } from "@langchain/core/tools";
4
- import { ConfigManager } from '../config/manager.js';
5
- import { LinkRepository } from './link-repository.js';
6
- import { LinkSearch } from './link-search.js';
7
- import { ProviderFactory } from './providers/factory.js';
8
- import { ProviderError } from './errors.js';
9
- import { DisplayManager } from './display.js';
10
- import { TaskRequestContext } from './tasks/context.js';
11
- import { extractRawUsage, persistAgentMessage, buildAgentResult, emitToolAuditEvents } from './subagent-utils.js';
12
- import { buildDelegationTool } from './tools/delegation-utils.js';
4
+ import { ConfigManager } from '../../../config/manager.js';
5
+ import { LinkRepository } from './repository.js';
6
+ import { LinkSearch } from './search.js';
7
+ import { ServiceContainer, SERVICE_KEYS } from '../../container.js';
8
+ import { ProviderError } from '../../errors.js';
9
+ import { DisplayManager } from '../../display.js';
10
+ import { TaskRequestContext } from '../../tasks/context.js';
11
+ import { extractRawUsage, persistAgentMessage, buildAgentResult, emitToolAuditEvents } from '../utils.js';
12
+ import { buildDelegationTool } from '../../tools/delegation-utils.js';
13
+ import { SubagentRegistry } from '../registry.js';
13
14
  const LINK_BASE_DESCRIPTION = `Delegate to Link, the documentation specialist subagent.
14
15
 
15
16
  Link has access to indexed user documents (PDFs, Markdown, TXT, DOCX) stored in ~/.morpheus/docs.
@@ -58,6 +59,19 @@ export class Link {
58
59
  config = ConfigManager.getInstance().get();
59
60
  }
60
61
  Link.instance = new Link(config);
62
+ SubagentRegistry.register({
63
+ agentKey: 'link', auditAgent: 'link', label: 'Link',
64
+ delegateToolName: 'link_delegate', emoji: '🕵️‍♂️', color: 'indigo',
65
+ description: 'Document search & RAG',
66
+ colorClass: 'text-indigo-600 dark:text-indigo-400',
67
+ bgClass: 'bg-indigo-50 dark:bg-indigo-900/10',
68
+ badgeClass: 'bg-indigo-100 text-indigo-700 dark:bg-indigo-900/40 dark:text-indigo-300',
69
+ instance: Link.instance,
70
+ hasDynamicDescription: true,
71
+ isMultiInstance: false,
72
+ setSessionId: (id) => Link.setSessionId(id),
73
+ refreshCatalog: () => Link.refreshDelegateCatalog(),
74
+ });
61
75
  }
62
76
  return Link.instance;
63
77
  }
@@ -254,7 +268,7 @@ export class Link {
254
268
  }
255
269
  this.display.log(`Link initialized with personality: ${personality}.`, { source: 'Link' });
256
270
  try {
257
- this.agent = await ProviderFactory.create(linkConfig, tools);
271
+ this.agent = await ServiceContainer.get(SERVICE_KEYS.providerFactory).create(linkConfig, tools);
258
272
  }
259
273
  catch (err) {
260
274
  throw new ProviderError(linkConfig.provider, err, 'Link subagent initialization failed');
@@ -1,10 +1,10 @@
1
1
  import Database from 'better-sqlite3';
2
2
  import fs from 'fs-extra';
3
3
  import path from 'path';
4
- import { homedir } from 'os';
5
4
  import { randomUUID } from 'crypto';
6
- import loadVecExtension from './memory/sqlite-vec.js';
7
- import { DisplayManager } from './display.js';
5
+ import loadVecExtension from '../../memory/sqlite-vec.js';
6
+ import { DisplayManager } from '../../display.js';
7
+ import { PATHS } from '../../../config/paths.js';
8
8
  // ─── Repository ──────────────────────────────────────────────────────────────
9
9
  const EMBEDDING_DIM = 384;
10
10
  export class LinkRepository {
@@ -13,7 +13,7 @@ export class LinkRepository {
13
13
  dbPath;
14
14
  display = DisplayManager.getInstance();
15
15
  constructor(dbPath) {
16
- this.dbPath = dbPath || path.join(homedir(), '.morpheus', 'memory', 'link.db');
16
+ this.dbPath = dbPath || PATHS.linkDb;
17
17
  }
18
18
  static getInstance(dbPath) {
19
19
  if (!LinkRepository.instance) {
@@ -1,6 +1,6 @@
1
- import { LinkRepository } from './link-repository.js';
2
- import { ConfigManager } from '../config/manager.js';
3
- import { EmbeddingService } from './memory/embedding.service.js';
1
+ import { LinkRepository } from './repository.js';
2
+ import { ConfigManager } from '../../../config/manager.js';
3
+ import { EmbeddingService } from '../../memory/embedding.service.js';
4
4
  /**
5
5
  * LinkSearch - Hybrid search for Link documents
6
6
  *
@@ -1,13 +1,13 @@
1
- import { homedir } from 'os';
2
- import path from 'path';
3
1
  import fs from 'fs-extra';
4
2
  import fsSync from 'fs';
5
- import { LinkRepository } from './link-repository.js';
6
- import { LinkSearch } from './link-search.js';
7
- import { hashFile, processDocument, isSupportedFormat } from './link-chunker.js';
8
- import { EmbeddingService } from './memory/embedding.service.js';
9
- import { ConfigManager } from '../config/manager.js';
10
- import { DisplayManager } from './display.js';
3
+ import path from 'path';
4
+ import { LinkRepository } from './repository.js';
5
+ import { LinkSearch } from './search.js';
6
+ import { hashFile, processDocument, isSupportedFormat } from './chunker.js';
7
+ import { EmbeddingService } from '../../memory/embedding.service.js';
8
+ import { ConfigManager } from '../../../config/manager.js';
9
+ import { DisplayManager } from '../../display.js';
10
+ import { PATHS } from '../../../config/paths.js';
11
11
  /**
12
12
  * LinkWorker - Background worker for document indexing
13
13
  *
@@ -26,7 +26,7 @@ export class LinkWorker {
26
26
  constructor() {
27
27
  this.repository = LinkRepository.getInstance();
28
28
  this.search = LinkSearch.getInstance();
29
- this.docsPath = path.join(homedir(), '.morpheus', 'docs');
29
+ this.docsPath = PATHS.docs;
30
30
  }
31
31
  static getInstance() {
32
32
  if (!LinkWorker.instance) {
@@ -1,13 +1,14 @@
1
1
  import { HumanMessage, SystemMessage, AIMessage } from "@langchain/core/messages";
2
- import { ConfigManager } from "../config/manager.js";
3
- import { ProviderFactory } from "./providers/factory.js";
4
- import { ProviderError } from "./errors.js";
5
- import { DisplayManager } from "./display.js";
6
- import { Construtor } from "./tools/factory.js";
7
- import { morpheusTools } from "./tools/index.js";
8
- import { TaskRequestContext } from "./tasks/context.js";
9
- import { extractRawUsage, persistAgentMessage, buildAgentResult, emitToolAuditEvents } from "./subagent-utils.js";
10
- import { buildDelegationTool } from "./tools/delegation-utils.js";
2
+ import { ConfigManager } from "../../config/manager.js";
3
+ import { ServiceContainer, SERVICE_KEYS } from "../container.js";
4
+ import { ProviderError } from "../errors.js";
5
+ import { DisplayManager } from "../display.js";
6
+ import { Construtor } from "../tools/factory.js";
7
+ import { morpheusTools } from "../tools/index.js";
8
+ import { TaskRequestContext } from "../tasks/context.js";
9
+ import { extractRawUsage, persistAgentMessage, buildAgentResult, emitToolAuditEvents } from "./utils.js";
10
+ import { buildDelegationTool } from "../tools/delegation-utils.js";
11
+ import { SubagentRegistry } from "./registry.js";
11
12
  // Internal Morpheus tools get 'tool_call' event type; MCP tools get 'mcp_tool'
12
13
  const MORPHEUS_TOOL_NAMES = new Set(morpheusTools.map((t) => t.name));
13
14
  const NEO_BUILTIN_CAPABILITIES = `
@@ -61,6 +62,19 @@ export class Neo {
61
62
  static getInstance(config) {
62
63
  if (!Neo.instance) {
63
64
  Neo.instance = new Neo(config);
65
+ SubagentRegistry.register({
66
+ agentKey: 'neo', auditAgent: 'neo', label: 'Neo',
67
+ delegateToolName: 'neo_delegate', emoji: '🥷', color: 'violet',
68
+ description: 'MCP tool orchestration',
69
+ colorClass: 'text-violet-600 dark:text-violet-400',
70
+ bgClass: 'bg-violet-50 dark:bg-violet-900/10',
71
+ badgeClass: 'bg-purple-100 text-purple-700 dark:bg-purple-900/40 dark:text-purple-300',
72
+ instance: Neo.instance,
73
+ hasDynamicDescription: true,
74
+ isMultiInstance: false,
75
+ setSessionId: (id) => Neo.setSessionId(id),
76
+ refreshCatalog: () => Neo.refreshDelegateCatalog(),
77
+ });
64
78
  }
65
79
  return Neo.instance;
66
80
  }
@@ -87,7 +101,7 @@ export class Neo {
87
101
  }
88
102
  this.display.log(`Neo initialized with ${tools.length} tools (personality: ${personality}).`, { source: "Neo" });
89
103
  try {
90
- this.agent = await ProviderFactory.create(neoConfig, tools);
104
+ this.agent = await ServiceContainer.get(SERVICE_KEYS.providerFactory).create(neoConfig, tools);
91
105
  }
92
106
  catch (err) {
93
107
  throw new ProviderError(neoConfig.provider, err, "Neo subagent initialization failed");