morpheus-cli 0.4.15 → 0.5.1
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 +293 -1115
- package/dist/channels/telegram.js +379 -74
- package/dist/cli/commands/doctor.js +34 -0
- package/dist/cli/commands/init.js +128 -0
- package/dist/cli/commands/restart.js +32 -14
- package/dist/cli/commands/start.js +28 -12
- package/dist/config/manager.js +82 -0
- package/dist/config/mcp-manager.js +19 -1
- package/dist/config/schemas.js +9 -0
- package/dist/devkit/tools/network.js +1 -1
- package/dist/http/api.js +399 -10
- package/dist/runtime/apoc.js +25 -17
- package/dist/runtime/memory/sati/repository.js +30 -2
- package/dist/runtime/memory/sati/service.js +46 -15
- package/dist/runtime/memory/sati/system-prompts.js +71 -29
- package/dist/runtime/memory/session-embedding-worker.js +3 -3
- package/dist/runtime/memory/sqlite.js +24 -0
- package/dist/runtime/memory/trinity-db.js +203 -0
- package/dist/runtime/neo.js +124 -0
- package/dist/runtime/oracle.js +252 -205
- package/dist/runtime/providers/factory.js +1 -12
- package/dist/runtime/session-embedding-scheduler.js +1 -1
- package/dist/runtime/tasks/context.js +53 -0
- package/dist/runtime/tasks/dispatcher.js +91 -0
- package/dist/runtime/tasks/notifier.js +68 -0
- package/dist/runtime/tasks/repository.js +370 -0
- package/dist/runtime/tasks/types.js +1 -0
- package/dist/runtime/tasks/worker.js +99 -0
- package/dist/runtime/tools/__tests__/tools.test.js +1 -3
- package/dist/runtime/tools/apoc-tool.js +61 -8
- package/dist/runtime/tools/delegation-guard.js +29 -0
- package/dist/runtime/tools/factory.js +1 -1
- package/dist/runtime/tools/index.js +2 -3
- package/dist/runtime/tools/morpheus-tools.js +742 -0
- package/dist/runtime/tools/neo-tool.js +109 -0
- 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 +10 -19
- package/dist/types/config.js +10 -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/ui/assets/index-LemKVRjC.js +0 -112
- package/dist/ui/assets/index-TCQ7VNYO.css +0 -1
|
@@ -208,6 +208,134 @@ export const initCommand = new Command('init')
|
|
|
208
208
|
if (satiApiKey) {
|
|
209
209
|
await configManager.set('sati.api_key', satiApiKey);
|
|
210
210
|
}
|
|
211
|
+
// Neo (MCP + Internal Tools Agent) Configuration
|
|
212
|
+
display.log(chalk.blue('\nNeo (MCP + Internal Tools Agent) Configuration'));
|
|
213
|
+
const configureNeo = await select({
|
|
214
|
+
message: 'Configure Neo separately?',
|
|
215
|
+
choices: [
|
|
216
|
+
{ name: 'No (Use Oracle provider/model defaults)', value: 'no' },
|
|
217
|
+
{ name: 'Yes', value: 'yes' },
|
|
218
|
+
],
|
|
219
|
+
default: currentConfig.neo ? 'yes' : 'no',
|
|
220
|
+
});
|
|
221
|
+
let neoProvider = provider;
|
|
222
|
+
let neoModel = model;
|
|
223
|
+
let neoTemperature = currentConfig.neo?.temperature ?? 0.2;
|
|
224
|
+
let neoContextWindow = currentConfig.neo?.context_window ?? Number(contextWindow);
|
|
225
|
+
let neoMaxTokens = currentConfig.neo?.max_tokens;
|
|
226
|
+
let neoApiKey = currentConfig.neo?.api_key || apiKey || currentConfig.llm.api_key;
|
|
227
|
+
let neoBaseUrl = provider === 'openrouter'
|
|
228
|
+
? (currentConfig.neo?.base_url || currentConfig.llm.base_url || 'https://openrouter.ai/api/v1')
|
|
229
|
+
: undefined;
|
|
230
|
+
if (configureNeo === 'yes') {
|
|
231
|
+
neoProvider = await select({
|
|
232
|
+
message: 'Select Neo LLM Provider:',
|
|
233
|
+
choices: [
|
|
234
|
+
{ name: 'OpenAI', value: 'openai' },
|
|
235
|
+
{ name: 'Anthropic', value: 'anthropic' },
|
|
236
|
+
{ name: 'OpenRouter', value: 'openrouter' },
|
|
237
|
+
{ name: 'Ollama', value: 'ollama' },
|
|
238
|
+
{ name: 'Google Gemini', value: 'gemini' },
|
|
239
|
+
],
|
|
240
|
+
default: currentConfig.neo?.provider || provider,
|
|
241
|
+
});
|
|
242
|
+
let defaultNeoModel = 'gpt-3.5-turbo';
|
|
243
|
+
switch (neoProvider) {
|
|
244
|
+
case 'openai':
|
|
245
|
+
defaultNeoModel = 'gpt-4o';
|
|
246
|
+
break;
|
|
247
|
+
case 'anthropic':
|
|
248
|
+
defaultNeoModel = 'claude-3-5-sonnet-20240620';
|
|
249
|
+
break;
|
|
250
|
+
case 'openrouter':
|
|
251
|
+
defaultNeoModel = 'openrouter/auto';
|
|
252
|
+
break;
|
|
253
|
+
case 'ollama':
|
|
254
|
+
defaultNeoModel = 'llama3';
|
|
255
|
+
break;
|
|
256
|
+
case 'gemini':
|
|
257
|
+
defaultNeoModel = 'gemini-pro';
|
|
258
|
+
break;
|
|
259
|
+
}
|
|
260
|
+
if (neoProvider === currentConfig.neo?.provider) {
|
|
261
|
+
defaultNeoModel = currentConfig.neo?.model || defaultNeoModel;
|
|
262
|
+
}
|
|
263
|
+
neoModel = await input({
|
|
264
|
+
message: 'Enter Neo Model Name:',
|
|
265
|
+
default: defaultNeoModel,
|
|
266
|
+
});
|
|
267
|
+
const neoTemperatureInput = await input({
|
|
268
|
+
message: 'Neo Temperature (0-1):',
|
|
269
|
+
default: (currentConfig.neo?.temperature ?? 0.2).toString(),
|
|
270
|
+
validate: (val) => {
|
|
271
|
+
const n = Number(val);
|
|
272
|
+
return (!isNaN(n) && n >= 0 && n <= 1) || 'Must be a number between 0 and 1';
|
|
273
|
+
},
|
|
274
|
+
});
|
|
275
|
+
neoTemperature = Number(neoTemperatureInput);
|
|
276
|
+
const neoContextWindowInput = await input({
|
|
277
|
+
message: 'Neo Context Window (messages):',
|
|
278
|
+
default: (currentConfig.neo?.context_window ?? Number(contextWindow)).toString(),
|
|
279
|
+
validate: (val) => (!isNaN(Number(val)) && Number(val) > 0) || 'Must be a positive number',
|
|
280
|
+
});
|
|
281
|
+
neoContextWindow = Number(neoContextWindowInput);
|
|
282
|
+
const neoMaxTokensInput = await input({
|
|
283
|
+
message: 'Neo Max Tokens (optional, leave empty for model default):',
|
|
284
|
+
default: currentConfig.neo?.max_tokens?.toString() || '',
|
|
285
|
+
validate: (val) => {
|
|
286
|
+
if (val.trim() === '')
|
|
287
|
+
return true;
|
|
288
|
+
return (!isNaN(Number(val)) && Number(val) > 0) || 'Must be a positive number';
|
|
289
|
+
},
|
|
290
|
+
});
|
|
291
|
+
neoMaxTokens = neoMaxTokensInput.trim() === '' ? undefined : Number(neoMaxTokensInput);
|
|
292
|
+
if (neoProvider !== 'ollama') {
|
|
293
|
+
const hasExistingNeoKey = !!currentConfig.neo?.api_key || !!currentConfig.llm?.api_key;
|
|
294
|
+
let neoKeyMsg = hasExistingNeoKey
|
|
295
|
+
? 'Enter Neo API Key (leave empty to preserve existing, or if using env vars):'
|
|
296
|
+
: 'Enter Neo API Key (leave empty if using env vars):';
|
|
297
|
+
if (neoProvider === 'openai') {
|
|
298
|
+
neoKeyMsg = `${neoKeyMsg} (Env vars: MORPHEUS_NEO_API_KEY / OPENAI_API_KEY)`;
|
|
299
|
+
}
|
|
300
|
+
else if (neoProvider === 'anthropic') {
|
|
301
|
+
neoKeyMsg = `${neoKeyMsg} (Env vars: MORPHEUS_NEO_API_KEY / ANTHROPIC_API_KEY)`;
|
|
302
|
+
}
|
|
303
|
+
else if (neoProvider === 'gemini') {
|
|
304
|
+
neoKeyMsg = `${neoKeyMsg} (Env vars: MORPHEUS_NEO_API_KEY / GOOGLE_API_KEY)`;
|
|
305
|
+
}
|
|
306
|
+
else if (neoProvider === 'openrouter') {
|
|
307
|
+
neoKeyMsg = `${neoKeyMsg} (Env vars: MORPHEUS_NEO_API_KEY / OPENROUTER_API_KEY)`;
|
|
308
|
+
}
|
|
309
|
+
const neoKeyInput = await password({ message: neoKeyMsg });
|
|
310
|
+
if (neoKeyInput) {
|
|
311
|
+
neoApiKey = neoKeyInput;
|
|
312
|
+
}
|
|
313
|
+
else {
|
|
314
|
+
neoApiKey = currentConfig.neo?.api_key || currentConfig.llm?.api_key;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
if (neoProvider === 'openrouter') {
|
|
318
|
+
neoBaseUrl = await input({
|
|
319
|
+
message: 'Enter Neo OpenRouter Base URL:',
|
|
320
|
+
default: currentConfig.neo?.base_url || currentConfig.llm.base_url || 'https://openrouter.ai/api/v1',
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
else {
|
|
324
|
+
neoBaseUrl = undefined;
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
await configManager.save({
|
|
328
|
+
...configManager.get(),
|
|
329
|
+
neo: {
|
|
330
|
+
provider: neoProvider,
|
|
331
|
+
model: neoModel,
|
|
332
|
+
temperature: neoTemperature,
|
|
333
|
+
context_window: neoContextWindow,
|
|
334
|
+
max_tokens: neoMaxTokens,
|
|
335
|
+
api_key: neoApiKey,
|
|
336
|
+
base_url: neoBaseUrl,
|
|
337
|
+
},
|
|
338
|
+
});
|
|
211
339
|
// Audio Configuration
|
|
212
340
|
const audioEnabled = await confirm({
|
|
213
341
|
message: 'Enable Audio Transcription? (Requires Gemini)',
|
|
@@ -12,6 +12,10 @@ import { Oracle } from '../../runtime/oracle.js';
|
|
|
12
12
|
import { ProviderError } from '../../runtime/errors.js';
|
|
13
13
|
import { HttpServer } from '../../http/server.js';
|
|
14
14
|
import { getVersion } from '../utils/version.js';
|
|
15
|
+
import { TaskWorker } from '../../runtime/tasks/worker.js';
|
|
16
|
+
import { TaskNotifier } from '../../runtime/tasks/notifier.js';
|
|
17
|
+
import { TaskDispatcher } from '../../runtime/tasks/dispatcher.js';
|
|
18
|
+
import { WebhookDispatcher } from '../../runtime/webhooks/dispatcher.js';
|
|
15
19
|
export const restartCommand = new Command('restart')
|
|
16
20
|
.description('Restart the Morpheus agent')
|
|
17
21
|
.option('--ui', 'Enable web UI', true)
|
|
@@ -62,10 +66,10 @@ export const restartCommand = new Command('restart')
|
|
|
62
66
|
const config = await configManager.load();
|
|
63
67
|
// Initialize persistent logging
|
|
64
68
|
await display.initialize(config.logging);
|
|
65
|
-
display.log(chalk.green(`Morpheus Agent (${config.agent.name}) starting...`));
|
|
66
|
-
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' });
|
|
67
71
|
if (options.ui) {
|
|
68
|
-
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' });
|
|
69
73
|
}
|
|
70
74
|
// Initialize Oracle
|
|
71
75
|
const oracle = new Oracle(config);
|
|
@@ -78,17 +82,17 @@ export const restartCommand = new Command('restart')
|
|
|
78
82
|
catch (err) {
|
|
79
83
|
display.stopSpinner();
|
|
80
84
|
if (err instanceof ProviderError) {
|
|
81
|
-
display.log(chalk.red(`\nProvider Error (${err.provider}):`));
|
|
82
|
-
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' });
|
|
83
87
|
if (err.suggestion) {
|
|
84
|
-
display.log(chalk.yellow(`Tip: ${err.suggestion}`));
|
|
88
|
+
display.log(chalk.yellow(`Tip: ${err.suggestion}`), { source: 'Oracle' });
|
|
85
89
|
}
|
|
86
90
|
}
|
|
87
91
|
else {
|
|
88
|
-
display.log(chalk.red('\nOracle initialization failed:'));
|
|
89
|
-
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' });
|
|
90
94
|
if (err.message.includes('API Key')) {
|
|
91
|
-
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' });
|
|
92
96
|
}
|
|
93
97
|
}
|
|
94
98
|
await clearPid();
|
|
@@ -96,6 +100,9 @@ export const restartCommand = new Command('restart')
|
|
|
96
100
|
}
|
|
97
101
|
const adapters = [];
|
|
98
102
|
let httpServer;
|
|
103
|
+
const taskWorker = new TaskWorker();
|
|
104
|
+
const taskNotifier = new TaskNotifier();
|
|
105
|
+
const asyncTasksEnabled = config.runtime?.async_tasks?.enabled !== false;
|
|
99
106
|
// Initialize Web UI
|
|
100
107
|
if (options.ui && config.ui.enabled) {
|
|
101
108
|
try {
|
|
@@ -105,7 +112,7 @@ export const restartCommand = new Command('restart')
|
|
|
105
112
|
httpServer.start(port);
|
|
106
113
|
}
|
|
107
114
|
catch (e) {
|
|
108
|
-
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' });
|
|
109
116
|
}
|
|
110
117
|
}
|
|
111
118
|
// Initialize Telegram
|
|
@@ -114,26 +121,36 @@ export const restartCommand = new Command('restart')
|
|
|
114
121
|
const telegram = new TelegramAdapter(oracle);
|
|
115
122
|
try {
|
|
116
123
|
await telegram.connect(config.channels.telegram.token, config.channels.telegram.allowedUsers || []);
|
|
124
|
+
WebhookDispatcher.setTelegramAdapter(telegram);
|
|
125
|
+
TaskDispatcher.setTelegramAdapter(telegram);
|
|
117
126
|
adapters.push(telegram);
|
|
118
127
|
}
|
|
119
128
|
catch (e) {
|
|
120
|
-
display.log(chalk.red('Failed to initialize Telegram adapter. Continuing...'));
|
|
129
|
+
display.log(chalk.red('Failed to initialize Telegram adapter. Continuing...'), { source: 'Zaion' });
|
|
121
130
|
}
|
|
122
131
|
}
|
|
123
132
|
else {
|
|
124
|
-
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' });
|
|
125
134
|
}
|
|
126
135
|
}
|
|
136
|
+
if (asyncTasksEnabled) {
|
|
137
|
+
taskWorker.start();
|
|
138
|
+
taskNotifier.start();
|
|
139
|
+
}
|
|
127
140
|
// Handle graceful shutdown
|
|
128
141
|
const shutdown = async (signal) => {
|
|
129
142
|
display.stopSpinner();
|
|
130
|
-
display.log(`\n${signal} received. Shutting down
|
|
143
|
+
display.log(`\n${signal} received. Shutting down...`, { source: 'Zaion' });
|
|
131
144
|
if (httpServer) {
|
|
132
145
|
httpServer.stop();
|
|
133
146
|
}
|
|
134
147
|
for (const adapter of adapters) {
|
|
135
148
|
await adapter.disconnect();
|
|
136
149
|
}
|
|
150
|
+
if (asyncTasksEnabled) {
|
|
151
|
+
taskWorker.stop();
|
|
152
|
+
taskNotifier.stop();
|
|
153
|
+
}
|
|
137
154
|
await clearPid();
|
|
138
155
|
process.exit(0);
|
|
139
156
|
};
|
|
@@ -160,7 +177,8 @@ export const restartCommand = new Command('restart')
|
|
|
160
177
|
}
|
|
161
178
|
catch (error) {
|
|
162
179
|
display.stopSpinner();
|
|
163
|
-
|
|
180
|
+
display.log(chalk.red('Failed to restart Morpheus:'), { source: 'Zaion' });
|
|
181
|
+
display.log(chalk.white(error.message), { source: 'Zaion' });
|
|
164
182
|
await clearPid();
|
|
165
183
|
process.exit(1);
|
|
166
184
|
}
|
|
@@ -15,6 +15,9 @@ import { ProviderError } from '../../runtime/errors.js';
|
|
|
15
15
|
import { HttpServer } from '../../http/server.js';
|
|
16
16
|
import { getVersion } from '../utils/version.js';
|
|
17
17
|
import { startSessionEmbeddingScheduler } from '../../runtime/session-embedding-scheduler.js';
|
|
18
|
+
import { TaskWorker } from '../../runtime/tasks/worker.js';
|
|
19
|
+
import { TaskNotifier } from '../../runtime/tasks/notifier.js';
|
|
20
|
+
import { TaskDispatcher } from '../../runtime/tasks/dispatcher.js';
|
|
18
21
|
export const startCommand = new Command('start')
|
|
19
22
|
.description('Start the Morpheus agent')
|
|
20
23
|
.option('--ui', 'Enable web UI', true)
|
|
@@ -88,7 +91,7 @@ export const startCommand = new Command('start')
|
|
|
88
91
|
display.log(chalk.green(`Morpheus Agent (${config.agent.name}) starting...`));
|
|
89
92
|
display.log(chalk.gray(`PID: ${process.pid}`));
|
|
90
93
|
if (options.ui) {
|
|
91
|
-
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' });
|
|
92
95
|
}
|
|
93
96
|
// Initialize Oracle
|
|
94
97
|
const oracle = new Oracle(config);
|
|
@@ -101,17 +104,17 @@ export const startCommand = new Command('start')
|
|
|
101
104
|
catch (err) {
|
|
102
105
|
display.stopSpinner();
|
|
103
106
|
if (err instanceof ProviderError) {
|
|
104
|
-
display.log(chalk.red(`\nProvider Error (${err.provider}):`));
|
|
105
|
-
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' });
|
|
106
109
|
if (err.suggestion) {
|
|
107
|
-
display.log(chalk.yellow(`Tip: ${err.suggestion}`));
|
|
110
|
+
display.log(chalk.yellow(`Tip: ${err.suggestion}`), { source: 'Oracle' });
|
|
108
111
|
}
|
|
109
112
|
}
|
|
110
113
|
else {
|
|
111
|
-
display.log(chalk.red('\nOracle initialization failed:'));
|
|
112
|
-
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' });
|
|
113
116
|
if (err.message.includes('API Key')) {
|
|
114
|
-
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' });
|
|
115
118
|
}
|
|
116
119
|
}
|
|
117
120
|
await clearPid();
|
|
@@ -119,6 +122,9 @@ export const startCommand = new Command('start')
|
|
|
119
122
|
}
|
|
120
123
|
const adapters = [];
|
|
121
124
|
let httpServer;
|
|
125
|
+
const taskWorker = new TaskWorker();
|
|
126
|
+
const taskNotifier = new TaskNotifier();
|
|
127
|
+
const asyncTasksEnabled = config.runtime?.async_tasks?.enabled !== false;
|
|
122
128
|
// Initialize Web UI
|
|
123
129
|
if (options.ui && config.ui.enabled) {
|
|
124
130
|
try {
|
|
@@ -128,7 +134,7 @@ export const startCommand = new Command('start')
|
|
|
128
134
|
httpServer.start(port);
|
|
129
135
|
}
|
|
130
136
|
catch (e) {
|
|
131
|
-
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' });
|
|
132
138
|
}
|
|
133
139
|
}
|
|
134
140
|
// Initialize Telegram
|
|
@@ -139,28 +145,37 @@ export const startCommand = new Command('start')
|
|
|
139
145
|
await telegram.connect(config.channels.telegram.token, config.channels.telegram.allowedUsers || []);
|
|
140
146
|
// Wire Telegram adapter to webhook dispatcher for proactive notifications
|
|
141
147
|
WebhookDispatcher.setTelegramAdapter(telegram);
|
|
148
|
+
TaskDispatcher.setTelegramAdapter(telegram);
|
|
142
149
|
adapters.push(telegram);
|
|
143
150
|
}
|
|
144
151
|
catch (e) {
|
|
145
|
-
display.log(chalk.red('Failed to initialize Telegram adapter. Continuing...'));
|
|
152
|
+
display.log(chalk.red('Failed to initialize Telegram adapter. Continuing...'), { source: 'Zaion' });
|
|
146
153
|
}
|
|
147
154
|
}
|
|
148
155
|
else {
|
|
149
|
-
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' });
|
|
150
157
|
}
|
|
151
158
|
}
|
|
152
159
|
// Start Background Services
|
|
153
160
|
startSessionEmbeddingScheduler();
|
|
161
|
+
if (asyncTasksEnabled) {
|
|
162
|
+
taskWorker.start();
|
|
163
|
+
taskNotifier.start();
|
|
164
|
+
}
|
|
154
165
|
// Handle graceful shutdown
|
|
155
166
|
const shutdown = async (signal) => {
|
|
156
167
|
display.stopSpinner();
|
|
157
|
-
display.log(`\n${signal} received. Shutting down
|
|
168
|
+
display.log(`\n${signal} received. Shutting down...`, { source: 'Zaion' });
|
|
158
169
|
if (httpServer) {
|
|
159
170
|
httpServer.stop();
|
|
160
171
|
}
|
|
161
172
|
for (const adapter of adapters) {
|
|
162
173
|
await adapter.disconnect();
|
|
163
174
|
}
|
|
175
|
+
if (asyncTasksEnabled) {
|
|
176
|
+
taskWorker.stop();
|
|
177
|
+
taskNotifier.stop();
|
|
178
|
+
}
|
|
164
179
|
await clearPid();
|
|
165
180
|
process.exit(0);
|
|
166
181
|
};
|
|
@@ -187,7 +202,8 @@ export const startCommand = new Command('start')
|
|
|
187
202
|
}
|
|
188
203
|
catch (error) {
|
|
189
204
|
display.stopSpinner();
|
|
190
|
-
|
|
205
|
+
display.log(chalk.red('Failed to start Morpheus:'), { source: 'Zaion' });
|
|
206
|
+
display.log(chalk.white(error.message), { source: 'Zaion' });
|
|
191
207
|
await clearPid();
|
|
192
208
|
process.exit(1);
|
|
193
209
|
}
|
package/dist/config/manager.js
CHANGED
|
@@ -85,6 +85,68 @@ export class ConfigManager {
|
|
|
85
85
|
timeout_ms: config.apoc.timeout_ms !== undefined ? resolveNumeric('MORPHEUS_APOC_TIMEOUT_MS', config.apoc.timeout_ms, 30_000) : 30_000
|
|
86
86
|
};
|
|
87
87
|
}
|
|
88
|
+
// Apply precedence to Neo config
|
|
89
|
+
const neoEnvVars = [
|
|
90
|
+
'MORPHEUS_NEO_PROVIDER',
|
|
91
|
+
'MORPHEUS_NEO_MODEL',
|
|
92
|
+
'MORPHEUS_NEO_TEMPERATURE',
|
|
93
|
+
'MORPHEUS_NEO_MAX_TOKENS',
|
|
94
|
+
'MORPHEUS_NEO_API_KEY',
|
|
95
|
+
'MORPHEUS_NEO_BASE_URL',
|
|
96
|
+
'MORPHEUS_NEO_CONTEXT_WINDOW',
|
|
97
|
+
];
|
|
98
|
+
const hasNeoEnvOverrides = neoEnvVars.some((envVar) => process.env[envVar] !== undefined);
|
|
99
|
+
const resolveOptionalNumeric = (envVar, configValue, fallbackValue) => {
|
|
100
|
+
if (fallbackValue !== undefined) {
|
|
101
|
+
return resolveNumeric(envVar, configValue, fallbackValue);
|
|
102
|
+
}
|
|
103
|
+
if (process.env[envVar] !== undefined && process.env[envVar] !== '') {
|
|
104
|
+
const parsed = Number(process.env[envVar]);
|
|
105
|
+
if (!Number.isNaN(parsed)) {
|
|
106
|
+
return parsed;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return configValue;
|
|
110
|
+
};
|
|
111
|
+
let neoConfig;
|
|
112
|
+
if (config.neo || hasNeoEnvOverrides) {
|
|
113
|
+
const neoProvider = resolveProvider('MORPHEUS_NEO_PROVIDER', config.neo?.provider, llmConfig.provider);
|
|
114
|
+
const neoBaseUrl = resolveString('MORPHEUS_NEO_BASE_URL', config.neo?.base_url, llmConfig.base_url || '');
|
|
115
|
+
const neoMaxTokensFallback = config.neo?.max_tokens ?? llmConfig.max_tokens;
|
|
116
|
+
const neoContextWindowFallback = config.neo?.context_window ?? llmConfig.context_window;
|
|
117
|
+
neoConfig = {
|
|
118
|
+
provider: neoProvider,
|
|
119
|
+
model: resolveModel(neoProvider, 'MORPHEUS_NEO_MODEL', config.neo?.model || llmConfig.model),
|
|
120
|
+
temperature: resolveNumeric('MORPHEUS_NEO_TEMPERATURE', config.neo?.temperature, llmConfig.temperature),
|
|
121
|
+
max_tokens: resolveOptionalNumeric('MORPHEUS_NEO_MAX_TOKENS', config.neo?.max_tokens, neoMaxTokensFallback),
|
|
122
|
+
api_key: resolveApiKey(neoProvider, 'MORPHEUS_NEO_API_KEY', config.neo?.api_key || llmConfig.api_key),
|
|
123
|
+
base_url: neoBaseUrl || undefined,
|
|
124
|
+
context_window: resolveOptionalNumeric('MORPHEUS_NEO_CONTEXT_WINDOW', config.neo?.context_window, neoContextWindowFallback),
|
|
125
|
+
};
|
|
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
|
+
}
|
|
88
150
|
// Apply precedence to audio config
|
|
89
151
|
const audioProvider = resolveString('MORPHEUS_AUDIO_PROVIDER', config.audio.provider, DEFAULT_CONFIG.audio.provider);
|
|
90
152
|
// AudioProvider uses 'google' but resolveApiKey expects LLMProvider which uses 'gemini'
|
|
@@ -128,7 +190,9 @@ export class ConfigManager {
|
|
|
128
190
|
agent: agentConfig,
|
|
129
191
|
llm: llmConfig,
|
|
130
192
|
sati: satiConfig,
|
|
193
|
+
neo: neoConfig,
|
|
131
194
|
apoc: apocConfig,
|
|
195
|
+
trinity: trinityConfig,
|
|
132
196
|
audio: audioConfig,
|
|
133
197
|
channels: channelsConfig,
|
|
134
198
|
ui: uiConfig,
|
|
@@ -185,4 +249,22 @@ export class ConfigManager {
|
|
|
185
249
|
timeout_ms: 30_000
|
|
186
250
|
};
|
|
187
251
|
}
|
|
252
|
+
getNeoConfig() {
|
|
253
|
+
if (this.config.neo) {
|
|
254
|
+
return {
|
|
255
|
+
...this.config.neo
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
// Fallback to main LLM config
|
|
259
|
+
return {
|
|
260
|
+
...this.config.llm,
|
|
261
|
+
};
|
|
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
|
+
}
|
|
188
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
|
@@ -26,6 +26,8 @@ export const ApocConfigSchema = LLMConfigSchema.extend({
|
|
|
26
26
|
working_dir: z.string().optional(),
|
|
27
27
|
timeout_ms: z.number().int().positive().optional(),
|
|
28
28
|
});
|
|
29
|
+
export const NeoConfigSchema = LLMConfigSchema;
|
|
30
|
+
export const TrinityConfigSchema = LLMConfigSchema;
|
|
29
31
|
export const WebhookConfigSchema = z.object({
|
|
30
32
|
telegram_notify_all: z.boolean().optional(),
|
|
31
33
|
}).optional();
|
|
@@ -37,12 +39,19 @@ export const ConfigSchema = z.object({
|
|
|
37
39
|
}).default(DEFAULT_CONFIG.agent),
|
|
38
40
|
llm: LLMConfigSchema.default(DEFAULT_CONFIG.llm),
|
|
39
41
|
sati: SatiConfigSchema.optional(),
|
|
42
|
+
neo: NeoConfigSchema.optional(),
|
|
40
43
|
apoc: ApocConfigSchema.optional(),
|
|
44
|
+
trinity: TrinityConfigSchema.optional(),
|
|
41
45
|
webhooks: WebhookConfigSchema,
|
|
42
46
|
audio: AudioConfigSchema.default(DEFAULT_CONFIG.audio),
|
|
43
47
|
memory: z.object({
|
|
44
48
|
limit: z.number().int().positive().optional(),
|
|
45
49
|
}).default(DEFAULT_CONFIG.memory),
|
|
50
|
+
runtime: z.object({
|
|
51
|
+
async_tasks: z.object({
|
|
52
|
+
enabled: z.boolean().default(DEFAULT_CONFIG.runtime?.async_tasks.enabled ?? true),
|
|
53
|
+
}).default(DEFAULT_CONFIG.runtime?.async_tasks ?? { enabled: true }),
|
|
54
|
+
}).optional(),
|
|
46
55
|
channels: z.object({
|
|
47
56
|
telegram: z.object({
|
|
48
57
|
enabled: z.boolean().default(false),
|
|
@@ -64,7 +64,7 @@ export function createNetworkTools(ctx) {
|
|
|
64
64
|
});
|
|
65
65
|
}, {
|
|
66
66
|
name: 'ping',
|
|
67
|
-
description: '
|
|
67
|
+
description: 'Preferred connectivity check tool. Verify if a host is reachable on a given port (TCP connect check). Use this instead of shell ping for routine reachability checks.',
|
|
68
68
|
schema: z.object({
|
|
69
69
|
host: z.string().describe('Hostname or IP'),
|
|
70
70
|
port: z.number().int().optional().describe('Port to check, default 80'),
|