morpheus-cli 0.2.4 → 0.2.6
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 +203 -0
- package/dist/channels/telegram.js +301 -2
- package/dist/cli/commands/doctor.js +62 -0
- package/dist/cli/commands/init.js +66 -11
- package/dist/cli/commands/restart.js +167 -0
- package/dist/cli/index.js +2 -0
- package/dist/config/manager.js +84 -5
- package/dist/config/mcp-manager.js +140 -0
- package/dist/config/precedence.js +138 -0
- package/dist/config/schemas.js +3 -1
- package/dist/http/__tests__/status_api.test.js +55 -0
- package/dist/http/__tests__/status_with_server_api.test.js +60 -0
- package/dist/http/api.js +85 -0
- package/dist/http/middleware/auth.js +7 -5
- package/dist/http/server.js +21 -0
- package/dist/runtime/__tests__/manual_start_verify.js +1 -0
- package/dist/runtime/oracle.js +3 -3
- package/dist/runtime/providers/factory.js +14 -4
- package/dist/runtime/tools/config-tools.js +25 -31
- package/dist/types/config.js +1 -0
- package/dist/ui/assets/index-BiXkm8Yr.css +1 -0
- package/dist/ui/assets/index-BrbyUtJ5.js +96 -0
- package/dist/ui/index.html +16 -14
- package/package.json +1 -1
- package/dist/ui/assets/index-3USYAgWN.css +0 -1
- package/dist/ui/assets/index-Pd9zlYEP.js +0 -58
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Functions to resolve configuration values with precedence:
|
|
3
|
+
* 1. Provider-specific environment variable (e.g., OPENAI_API_KEY)
|
|
4
|
+
* 2. Generic environment variable (e.g., MORPHEUS_LLM_API_KEY)
|
|
5
|
+
* 3. Configuration file value
|
|
6
|
+
* 4. Default value
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Resolve an API key with provider-specific precedence
|
|
10
|
+
* @param provider The current provider
|
|
11
|
+
* @param genericEnvVar The generic environment variable name
|
|
12
|
+
* @param configFileValue The value from the config file
|
|
13
|
+
* @returns The resolved API key value
|
|
14
|
+
*/
|
|
15
|
+
export function resolveApiKey(provider, genericEnvVar, configFileValue) {
|
|
16
|
+
// Map provider to its specific environment variable
|
|
17
|
+
const providerSpecificVars = {
|
|
18
|
+
'openai': 'OPENAI_API_KEY',
|
|
19
|
+
'anthropic': 'ANTHROPIC_API_KEY',
|
|
20
|
+
'openrouter': 'OPENROUTER_API_KEY',
|
|
21
|
+
'ollama': '', // Ollama typically doesn't need an API key
|
|
22
|
+
'gemini': 'GOOGLE_API_KEY'
|
|
23
|
+
};
|
|
24
|
+
const providerSpecificVar = providerSpecificVars[provider];
|
|
25
|
+
// Check provider-specific variable first, then generic, then config file
|
|
26
|
+
if (providerSpecificVar && process.env[providerSpecificVar]) {
|
|
27
|
+
return process.env[providerSpecificVar];
|
|
28
|
+
}
|
|
29
|
+
if (process.env[genericEnvVar]) {
|
|
30
|
+
return process.env[genericEnvVar];
|
|
31
|
+
}
|
|
32
|
+
return configFileValue;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Resolve a model name with provider-specific precedence
|
|
36
|
+
* @param provider The current provider
|
|
37
|
+
* @param genericEnvVar The generic environment variable name
|
|
38
|
+
* @param configFileValue The value from the config file
|
|
39
|
+
* @returns The resolved model name value
|
|
40
|
+
*/
|
|
41
|
+
export function resolveModel(provider, genericEnvVar, configFileValue) {
|
|
42
|
+
// For now, we don't have provider-specific model variables, but we could add them later
|
|
43
|
+
// Check generic variable first, then config file
|
|
44
|
+
if (process.env[genericEnvVar]) {
|
|
45
|
+
return process.env[genericEnvVar];
|
|
46
|
+
}
|
|
47
|
+
return configFileValue;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Resolve a numeric configuration value
|
|
51
|
+
* @param genericEnvVar The generic environment variable name
|
|
52
|
+
* @param configFileValue The value from the config file
|
|
53
|
+
* @param defaultValue The default value to use if none is found
|
|
54
|
+
* @returns The resolved numeric value
|
|
55
|
+
*/
|
|
56
|
+
export function resolveNumeric(genericEnvVar, configFileValue, defaultValue) {
|
|
57
|
+
if (process.env[genericEnvVar] !== undefined && process.env[genericEnvVar] !== '') {
|
|
58
|
+
const envValue = Number(process.env[genericEnvVar]);
|
|
59
|
+
if (!isNaN(envValue)) {
|
|
60
|
+
return envValue;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
if (configFileValue !== undefined) {
|
|
64
|
+
return configFileValue;
|
|
65
|
+
}
|
|
66
|
+
return defaultValue;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Resolve a string configuration value
|
|
70
|
+
* @param genericEnvVar The generic environment variable name
|
|
71
|
+
* @param configFileValue The value from the config file
|
|
72
|
+
* @param defaultValue The default value to use if none is found
|
|
73
|
+
* @returns The resolved string value
|
|
74
|
+
*/
|
|
75
|
+
export function resolveString(genericEnvVar, configFileValue, defaultValue) {
|
|
76
|
+
if (process.env[genericEnvVar]) {
|
|
77
|
+
return process.env[genericEnvVar];
|
|
78
|
+
}
|
|
79
|
+
if (configFileValue !== undefined) {
|
|
80
|
+
return configFileValue;
|
|
81
|
+
}
|
|
82
|
+
return defaultValue;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Resolve a boolean configuration value
|
|
86
|
+
* @param genericEnvVar The generic environment variable name
|
|
87
|
+
* @param configFileValue The value from the config file
|
|
88
|
+
* @param defaultValue The default value to use if none is found
|
|
89
|
+
* @returns The resolved boolean value
|
|
90
|
+
*/
|
|
91
|
+
export function resolveBoolean(genericEnvVar, configFileValue, defaultValue) {
|
|
92
|
+
if (process.env[genericEnvVar] !== undefined) {
|
|
93
|
+
const envValue = process.env[genericEnvVar]?.toLowerCase();
|
|
94
|
+
if (envValue === 'true' || envValue === '1') {
|
|
95
|
+
return true;
|
|
96
|
+
}
|
|
97
|
+
else if (envValue === 'false' || envValue === '0') {
|
|
98
|
+
return false;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
if (configFileValue !== undefined) {
|
|
102
|
+
return configFileValue;
|
|
103
|
+
}
|
|
104
|
+
return defaultValue;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Resolve an array of string configuration value
|
|
108
|
+
* @param genericEnvVar The generic environment variable name
|
|
109
|
+
* @param configFileValue The value from the config file
|
|
110
|
+
* @param defaultValue The default value to use if none is found
|
|
111
|
+
* @returns The resolved array of strings value
|
|
112
|
+
*/
|
|
113
|
+
export function resolveStringArray(genericEnvVar, configFileValue, defaultValue) {
|
|
114
|
+
if (process.env[genericEnvVar]) {
|
|
115
|
+
// Split the environment variable by commas and trim whitespace
|
|
116
|
+
return process.env[genericEnvVar].split(',').map(item => item.trim()).filter(item => item.length > 0);
|
|
117
|
+
}
|
|
118
|
+
if (configFileValue !== undefined) {
|
|
119
|
+
return configFileValue;
|
|
120
|
+
}
|
|
121
|
+
return defaultValue;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Resolve a provider configuration value
|
|
125
|
+
* @param genericEnvVar The generic environment variable name
|
|
126
|
+
* @param configFileValue The value from the config file
|
|
127
|
+
* @param defaultValue The default value to use if none is found
|
|
128
|
+
* @returns The resolved provider value
|
|
129
|
+
*/
|
|
130
|
+
export function resolveProvider(genericEnvVar, configFileValue, defaultValue) {
|
|
131
|
+
if (process.env[genericEnvVar]) {
|
|
132
|
+
return process.env[genericEnvVar];
|
|
133
|
+
}
|
|
134
|
+
if (configFileValue !== undefined) {
|
|
135
|
+
return configFileValue;
|
|
136
|
+
}
|
|
137
|
+
return defaultValue;
|
|
138
|
+
}
|
package/dist/config/schemas.js
CHANGED
|
@@ -2,17 +2,19 @@ import { z } from 'zod';
|
|
|
2
2
|
import { DEFAULT_CONFIG } from '../types/config.js';
|
|
3
3
|
export const AudioConfigSchema = z.object({
|
|
4
4
|
provider: z.enum(['google']).default(DEFAULT_CONFIG.audio.provider),
|
|
5
|
+
model: z.string().min(1).default(DEFAULT_CONFIG.audio.model),
|
|
5
6
|
enabled: z.boolean().default(DEFAULT_CONFIG.audio.enabled),
|
|
6
7
|
apiKey: z.string().optional(),
|
|
7
8
|
maxDurationSeconds: z.number().default(DEFAULT_CONFIG.audio.maxDurationSeconds),
|
|
8
9
|
supportedMimeTypes: z.array(z.string()).default(DEFAULT_CONFIG.audio.supportedMimeTypes),
|
|
9
10
|
});
|
|
10
11
|
export const LLMConfigSchema = z.object({
|
|
11
|
-
provider: z.enum(['openai', 'anthropic', 'ollama', 'gemini']).default(DEFAULT_CONFIG.llm.provider),
|
|
12
|
+
provider: z.enum(['openai', 'anthropic', 'openrouter', 'ollama', 'gemini']).default(DEFAULT_CONFIG.llm.provider),
|
|
12
13
|
model: z.string().min(1).default(DEFAULT_CONFIG.llm.model),
|
|
13
14
|
temperature: z.number().min(0).max(1).default(DEFAULT_CONFIG.llm.temperature),
|
|
14
15
|
max_tokens: z.number().int().positive().optional(),
|
|
15
16
|
api_key: z.string().optional(),
|
|
17
|
+
base_url: z.string().optional(),
|
|
16
18
|
context_window: z.number().int().positive().optional(),
|
|
17
19
|
});
|
|
18
20
|
export const SatiConfigSchema = LLMConfigSchema.extend({
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import request from 'supertest';
|
|
3
|
+
import express from 'express';
|
|
4
|
+
import bodyParser from 'body-parser';
|
|
5
|
+
import { createApiRouter } from '../api.js';
|
|
6
|
+
import { ConfigManager } from '../../config/manager.js';
|
|
7
|
+
import fs from 'fs-extra';
|
|
8
|
+
// Mock dependencies
|
|
9
|
+
vi.mock('../../config/manager.js');
|
|
10
|
+
vi.mock('fs-extra');
|
|
11
|
+
describe('Status API', () => {
|
|
12
|
+
let app;
|
|
13
|
+
let mockConfigManager;
|
|
14
|
+
beforeEach(() => {
|
|
15
|
+
// Reset mocks
|
|
16
|
+
vi.clearAllMocks();
|
|
17
|
+
// Mock ConfigManager instance
|
|
18
|
+
mockConfigManager = {
|
|
19
|
+
get: vi.fn(),
|
|
20
|
+
};
|
|
21
|
+
ConfigManager.getInstance.mockReturnValue(mockConfigManager);
|
|
22
|
+
// Setup App
|
|
23
|
+
app = express();
|
|
24
|
+
app.use(bodyParser.json());
|
|
25
|
+
// Create router without server instance to test fallback
|
|
26
|
+
app.use('/api', createApiRouter());
|
|
27
|
+
});
|
|
28
|
+
afterEach(() => {
|
|
29
|
+
vi.restoreAllMocks();
|
|
30
|
+
});
|
|
31
|
+
describe('GET /api/status', () => {
|
|
32
|
+
it('should return status information including server port', async () => {
|
|
33
|
+
const mockConfig = {
|
|
34
|
+
agent: { name: 'TestAgent' },
|
|
35
|
+
llm: { provider: 'openai', model: 'gpt-4' },
|
|
36
|
+
ui: { port: 3333 }
|
|
37
|
+
};
|
|
38
|
+
mockConfigManager.get.mockReturnValue(mockConfig);
|
|
39
|
+
// Mock fs.readJson to return a version
|
|
40
|
+
fs.readJson.mockResolvedValue({ version: '1.0.0' });
|
|
41
|
+
const res = await request(app).get('/api/status');
|
|
42
|
+
expect(res.status).toBe(200);
|
|
43
|
+
expect(res.body).toHaveProperty('status');
|
|
44
|
+
expect(res.body).toHaveProperty('uptimeSeconds');
|
|
45
|
+
expect(res.body).toHaveProperty('pid');
|
|
46
|
+
expect(res.body).toHaveProperty('projectVersion');
|
|
47
|
+
expect(res.body).toHaveProperty('nodeVersion');
|
|
48
|
+
expect(res.body).toHaveProperty('agentName');
|
|
49
|
+
expect(res.body).toHaveProperty('llmProvider');
|
|
50
|
+
expect(res.body).toHaveProperty('llmModel');
|
|
51
|
+
expect(res.body).toHaveProperty('serverPort');
|
|
52
|
+
expect(res.body.serverPort).toBe(3333);
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
});
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import request from 'supertest';
|
|
3
|
+
import express from 'express';
|
|
4
|
+
import bodyParser from 'body-parser';
|
|
5
|
+
import { createApiRouter } from '../api.js';
|
|
6
|
+
import { ConfigManager } from '../../config/manager.js';
|
|
7
|
+
import fs from 'fs-extra';
|
|
8
|
+
// Mock dependencies
|
|
9
|
+
vi.mock('../../config/manager.js');
|
|
10
|
+
vi.mock('fs-extra');
|
|
11
|
+
describe('Status API with Server Instance', () => {
|
|
12
|
+
let app;
|
|
13
|
+
let mockConfigManager;
|
|
14
|
+
let mockServerInstance;
|
|
15
|
+
beforeEach(() => {
|
|
16
|
+
// Reset mocks
|
|
17
|
+
vi.clearAllMocks();
|
|
18
|
+
// Mock ConfigManager instance
|
|
19
|
+
mockConfigManager = {
|
|
20
|
+
get: vi.fn(),
|
|
21
|
+
};
|
|
22
|
+
ConfigManager.getInstance.mockReturnValue(mockConfigManager);
|
|
23
|
+
// Mock server instance with getPort method
|
|
24
|
+
mockServerInstance = {
|
|
25
|
+
getPort: vi.fn().mockReturnValue(4567),
|
|
26
|
+
};
|
|
27
|
+
// Setup App
|
|
28
|
+
app = express();
|
|
29
|
+
app.use(bodyParser.json());
|
|
30
|
+
// Create router with server instance
|
|
31
|
+
app.use('/api', createApiRouter(mockServerInstance));
|
|
32
|
+
});
|
|
33
|
+
afterEach(() => {
|
|
34
|
+
vi.restoreAllMocks();
|
|
35
|
+
});
|
|
36
|
+
describe('GET /api/status', () => {
|
|
37
|
+
it('should return status information with server instance port', async () => {
|
|
38
|
+
const mockConfig = {
|
|
39
|
+
agent: { name: 'TestAgent' },
|
|
40
|
+
llm: { provider: 'openai', model: 'gpt-4' },
|
|
41
|
+
ui: { port: 3333 } // This should be overridden by server instance
|
|
42
|
+
};
|
|
43
|
+
mockConfigManager.get.mockReturnValue(mockConfig);
|
|
44
|
+
// Mock fs.readJson to return a version
|
|
45
|
+
fs.readJson.mockResolvedValue({ version: '1.0.0' });
|
|
46
|
+
const res = await request(app).get('/api/status');
|
|
47
|
+
expect(res.status).toBe(200);
|
|
48
|
+
expect(res.body).toHaveProperty('status');
|
|
49
|
+
expect(res.body).toHaveProperty('uptimeSeconds');
|
|
50
|
+
expect(res.body).toHaveProperty('pid');
|
|
51
|
+
expect(res.body).toHaveProperty('projectVersion');
|
|
52
|
+
expect(res.body).toHaveProperty('nodeVersion');
|
|
53
|
+
expect(res.body).toHaveProperty('agentName');
|
|
54
|
+
expect(res.body).toHaveProperty('llmProvider');
|
|
55
|
+
expect(res.body).toHaveProperty('llmModel');
|
|
56
|
+
expect(res.body).toHaveProperty('serverPort');
|
|
57
|
+
expect(res.body.serverPort).toBe(4567); // Should come from server instance
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
});
|
package/dist/http/api.js
CHANGED
|
@@ -6,6 +6,10 @@ import fs from 'fs-extra';
|
|
|
6
6
|
import path from 'path';
|
|
7
7
|
import { SQLiteChatMessageHistory } from '../runtime/memory/sqlite.js';
|
|
8
8
|
import { SatiRepository } from '../runtime/memory/sati/repository.js';
|
|
9
|
+
import { spawn } from 'child_process';
|
|
10
|
+
import { z } from 'zod';
|
|
11
|
+
import { MCPManager } from '../config/mcp-manager.js';
|
|
12
|
+
import { MCPServerConfigSchema } from '../config/schemas.js';
|
|
9
13
|
async function readLastLines(filePath, n) {
|
|
10
14
|
try {
|
|
11
15
|
const content = await fs.readFile(filePath, 'utf8');
|
|
@@ -38,6 +42,29 @@ export function createApiRouter() {
|
|
|
38
42
|
llmModel: config.llm.model
|
|
39
43
|
});
|
|
40
44
|
});
|
|
45
|
+
router.post('/restart', async (req, res) => {
|
|
46
|
+
try {
|
|
47
|
+
// Send response immediately before restarting
|
|
48
|
+
res.json({
|
|
49
|
+
success: true,
|
|
50
|
+
message: 'Restart initiated. Process will shut down and restart shortly.'
|
|
51
|
+
});
|
|
52
|
+
// Delay the actual restart to allow response to be sent
|
|
53
|
+
setTimeout(() => {
|
|
54
|
+
// Execute the restart command using the CLI
|
|
55
|
+
const restartProcess = spawn(process.execPath, [process.argv[1], 'restart'], {
|
|
56
|
+
detached: true,
|
|
57
|
+
stdio: 'ignore'
|
|
58
|
+
});
|
|
59
|
+
restartProcess.unref();
|
|
60
|
+
// Exit the current process
|
|
61
|
+
process.exit(0);
|
|
62
|
+
}, 100);
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
res.status(500).json({ error: error.message });
|
|
66
|
+
}
|
|
67
|
+
});
|
|
41
68
|
router.get('/config', (req, res) => {
|
|
42
69
|
res.json(configManager.get());
|
|
43
70
|
});
|
|
@@ -224,6 +251,64 @@ export function createApiRouter() {
|
|
|
224
251
|
res.status(500).json({ error: error.message });
|
|
225
252
|
}
|
|
226
253
|
});
|
|
254
|
+
const MCPUpsertSchema = z.object({
|
|
255
|
+
name: z.string().min(1),
|
|
256
|
+
config: MCPServerConfigSchema,
|
|
257
|
+
});
|
|
258
|
+
const MCPToggleSchema = z.object({
|
|
259
|
+
enabled: z.boolean(),
|
|
260
|
+
});
|
|
261
|
+
router.get('/mcp/servers', async (_req, res) => {
|
|
262
|
+
try {
|
|
263
|
+
const servers = await MCPManager.listServers();
|
|
264
|
+
res.json({ servers });
|
|
265
|
+
}
|
|
266
|
+
catch (error) {
|
|
267
|
+
res.status(500).json({ error: 'Failed to load MCP servers.', details: String(error) });
|
|
268
|
+
}
|
|
269
|
+
});
|
|
270
|
+
router.post('/mcp/servers', async (req, res) => {
|
|
271
|
+
try {
|
|
272
|
+
const body = MCPUpsertSchema.parse(req.body);
|
|
273
|
+
await MCPManager.addServer(body.name, body.config);
|
|
274
|
+
res.status(201).json({ ok: true });
|
|
275
|
+
}
|
|
276
|
+
catch (error) {
|
|
277
|
+
const status = error instanceof z.ZodError ? 400 : 500;
|
|
278
|
+
res.status(status).json({ error: 'Failed to create MCP server.', details: error });
|
|
279
|
+
}
|
|
280
|
+
});
|
|
281
|
+
router.put('/mcp/servers/:name', async (req, res) => {
|
|
282
|
+
try {
|
|
283
|
+
const body = MCPUpsertSchema.parse({ name: req.params.name, config: req.body?.config ?? req.body });
|
|
284
|
+
await MCPManager.updateServer(body.name, body.config);
|
|
285
|
+
res.json({ ok: true });
|
|
286
|
+
}
|
|
287
|
+
catch (error) {
|
|
288
|
+
const status = error instanceof z.ZodError ? 400 : 500;
|
|
289
|
+
res.status(status).json({ error: 'Failed to update MCP server.', details: error });
|
|
290
|
+
}
|
|
291
|
+
});
|
|
292
|
+
router.delete('/mcp/servers/:name', async (req, res) => {
|
|
293
|
+
try {
|
|
294
|
+
await MCPManager.deleteServer(req.params.name);
|
|
295
|
+
res.json({ ok: true });
|
|
296
|
+
}
|
|
297
|
+
catch (error) {
|
|
298
|
+
res.status(500).json({ error: 'Failed to delete MCP server.', details: String(error) });
|
|
299
|
+
}
|
|
300
|
+
});
|
|
301
|
+
router.patch('/mcp/servers/:name/toggle', async (req, res) => {
|
|
302
|
+
try {
|
|
303
|
+
const body = MCPToggleSchema.parse(req.body);
|
|
304
|
+
await MCPManager.setServerEnabled(req.params.name, body.enabled);
|
|
305
|
+
res.json({ ok: true });
|
|
306
|
+
}
|
|
307
|
+
catch (error) {
|
|
308
|
+
const status = error instanceof z.ZodError ? 400 : 500;
|
|
309
|
+
res.status(status).json({ error: 'Failed to toggle MCP server.', details: error });
|
|
310
|
+
}
|
|
311
|
+
});
|
|
227
312
|
// Keep PUT for backward compatibility if needed, or remove.
|
|
228
313
|
// Tasks says Implement POST. I'll remove PUT to avoid confusion or redirect it.
|
|
229
314
|
router.put('/config', async (req, res) => {
|
|
@@ -2,13 +2,15 @@ import { AUTH_HEADER } from '../../types/auth.js';
|
|
|
2
2
|
import { DisplayManager } from '../../runtime/display.js';
|
|
3
3
|
/**
|
|
4
4
|
* Middleware to protect API routes with a password from THE_ARCHITECT_PASS env var.
|
|
5
|
-
* If the env var is not set,
|
|
5
|
+
* If the env var is not set, uses default password 'iamthearchitect'.
|
|
6
6
|
*/
|
|
7
7
|
export const authMiddleware = (req, res, next) => {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
8
|
+
// Use environment variable if set, otherwise use default password
|
|
9
|
+
const architectPass = process.env.THE_ARCHITECT_PASS || 'iamthearchitect';
|
|
10
|
+
// If password is not configured (using default), log a warning
|
|
11
|
+
if (!process.env.THE_ARCHITECT_PASS) {
|
|
12
|
+
const display = DisplayManager.getInstance();
|
|
13
|
+
display.log('Using default password for dashboard access. For security, set THE_ARCHITECT_PASS environment variable.', { source: 'http', level: 'warning' });
|
|
12
14
|
}
|
|
13
15
|
const providedPass = req.headers[AUTH_HEADER];
|
|
14
16
|
if (providedPass === architectPass) {
|
package/dist/http/server.js
CHANGED
|
@@ -20,8 +20,29 @@ export class HttpServer {
|
|
|
20
20
|
setupMiddleware() {
|
|
21
21
|
this.app.use(cors());
|
|
22
22
|
this.app.use(bodyParser.json());
|
|
23
|
+
// Adicionar cabeçalhos para evitar indexação por motores de busca
|
|
24
|
+
this.app.use((req, res, next) => {
|
|
25
|
+
res.setHeader('X-Robots-Tag', 'noindex, nofollow');
|
|
26
|
+
next();
|
|
27
|
+
});
|
|
23
28
|
}
|
|
24
29
|
setupRoutes() {
|
|
30
|
+
// Rota de health check pública (sem autenticação)
|
|
31
|
+
this.app.get('/health', (req, res) => {
|
|
32
|
+
res.status(200).json({
|
|
33
|
+
status: 'healthy',
|
|
34
|
+
timestamp: new Date().toISOString(),
|
|
35
|
+
uptime: process.uptime()
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
// Rota de health check para o Docker (padrão)
|
|
39
|
+
this.app.get('/api/health', (req, res) => {
|
|
40
|
+
res.status(200).json({
|
|
41
|
+
status: 'healthy',
|
|
42
|
+
timestamp: new Date().toISOString(),
|
|
43
|
+
uptime: process.uptime()
|
|
44
|
+
});
|
|
45
|
+
});
|
|
25
46
|
this.app.use('/api', authMiddleware, createApiRouter());
|
|
26
47
|
// Serve static frontend from compiled output
|
|
27
48
|
const uiPath = path.resolve(__dirname, '../ui');
|
package/dist/runtime/oracle.js
CHANGED
|
@@ -49,7 +49,7 @@ export class Oracle {
|
|
|
49
49
|
throw new ProviderError(this.config.llm.provider || 'unknown', err, "Oracle initialization failed");
|
|
50
50
|
}
|
|
51
51
|
}
|
|
52
|
-
async chat(message, extraUsage) {
|
|
52
|
+
async chat(message, extraUsage, isTelephonist) {
|
|
53
53
|
if (!this.provider) {
|
|
54
54
|
throw new Error("Oracle not initialized. Call initialize() first.");
|
|
55
55
|
}
|
|
@@ -61,8 +61,8 @@ export class Oracle {
|
|
|
61
61
|
const userMessage = new HumanMessage(message);
|
|
62
62
|
// Inject provider/model metadata for persistence
|
|
63
63
|
userMessage.provider_metadata = {
|
|
64
|
-
provider: this.config.llm.provider,
|
|
65
|
-
model: this.config.llm.model
|
|
64
|
+
provider: isTelephonist ? this.config.audio?.provider : this.config.llm.provider,
|
|
65
|
+
model: isTelephonist ? this.config.audio?.model : this.config.llm.model
|
|
66
66
|
};
|
|
67
67
|
// Attach extra usage (e.g. from Audio) to the user message to be persisted
|
|
68
68
|
if (extraUsage) {
|
|
@@ -38,14 +38,24 @@ export class ProviderFactory {
|
|
|
38
38
|
model = new ChatOpenAI({
|
|
39
39
|
modelName: config.model,
|
|
40
40
|
temperature: config.temperature,
|
|
41
|
-
apiKey: config.api_key, //
|
|
41
|
+
apiKey: process.env.OPENAI_API_KEY || config.api_key, // Check env var first, then config
|
|
42
42
|
});
|
|
43
43
|
break;
|
|
44
44
|
case 'anthropic':
|
|
45
45
|
model = new ChatAnthropic({
|
|
46
46
|
modelName: config.model,
|
|
47
47
|
temperature: config.temperature,
|
|
48
|
-
apiKey: config.api_key,
|
|
48
|
+
apiKey: process.env.ANTHROPIC_API_KEY || config.api_key, // Check env var first, then config
|
|
49
|
+
});
|
|
50
|
+
break;
|
|
51
|
+
case 'openrouter':
|
|
52
|
+
model = new ChatOpenAI({
|
|
53
|
+
modelName: config.model,
|
|
54
|
+
temperature: config.temperature,
|
|
55
|
+
apiKey: process.env.OPENROUTER_API_KEY || config.api_key, // Check env var first, then config
|
|
56
|
+
configuration: {
|
|
57
|
+
baseURL: config.base_url || 'https://openrouter.ai/api/v1'
|
|
58
|
+
}
|
|
49
59
|
});
|
|
50
60
|
break;
|
|
51
61
|
case 'ollama':
|
|
@@ -60,7 +70,7 @@ export class ProviderFactory {
|
|
|
60
70
|
model = new ChatGoogleGenerativeAI({
|
|
61
71
|
model: config.model,
|
|
62
72
|
temperature: config.temperature,
|
|
63
|
-
apiKey: config.api_key
|
|
73
|
+
apiKey: process.env.GOOGLE_API_KEY || config.api_key // Check env var first, then config
|
|
64
74
|
});
|
|
65
75
|
break;
|
|
66
76
|
default:
|
|
@@ -99,7 +109,7 @@ export class ProviderFactory {
|
|
|
99
109
|
suggestion = `Model '${config.model}' may not be available. Check provider docs.`;
|
|
100
110
|
}
|
|
101
111
|
else if (msg.includes("unsupported provider")) {
|
|
102
|
-
suggestion = "Edit your config file to use a supported provider (openai, anthropic, ollama, gemini).";
|
|
112
|
+
suggestion = "Edit your config file to use a supported provider (openai, anthropic, openrouter, ollama, gemini).";
|
|
103
113
|
}
|
|
104
114
|
throw new ProviderError(config.provider, error, suggestion);
|
|
105
115
|
}
|
|
@@ -5,66 +5,60 @@ import { ConfigManager } from "../../config/manager.js";
|
|
|
5
5
|
export const ConfigQueryTool = tool(async ({ key }) => {
|
|
6
6
|
try {
|
|
7
7
|
const configManager = ConfigManager.getInstance();
|
|
8
|
-
// Load config if not already loaded
|
|
9
8
|
await configManager.load();
|
|
10
9
|
const config = configManager.get();
|
|
11
10
|
if (key) {
|
|
12
|
-
//
|
|
13
|
-
const value =
|
|
11
|
+
// Suporta busca por chave aninhada, ex: 'llm.model' ou 'channels.telegram.enabled'
|
|
12
|
+
const value = key.split('.').reduce((obj, k) => (obj ? obj[k] : undefined), config);
|
|
14
13
|
return JSON.stringify({ [key]: value });
|
|
15
14
|
}
|
|
16
15
|
else {
|
|
17
|
-
// Return all configuration values
|
|
18
16
|
return JSON.stringify(config);
|
|
19
17
|
}
|
|
20
18
|
}
|
|
21
19
|
catch (error) {
|
|
22
|
-
console.
|
|
20
|
+
// Nunca usar console.log, mas manter para debug local
|
|
23
21
|
return JSON.stringify({ error: "Failed to query configuration" });
|
|
24
22
|
}
|
|
25
23
|
}, {
|
|
26
|
-
name: "
|
|
27
|
-
description: "Queries current configuration values. Accepts an optional 'key' parameter to get a specific configuration value, or no parameter to get all configuration values.",
|
|
24
|
+
name: "morpheus_config_query",
|
|
25
|
+
description: "Queries current configuration values. Accepts an optional 'key' parameter (dot notation supported, e.g. 'llm.model') to get a specific configuration value, or no parameter to get all configuration values.",
|
|
28
26
|
schema: z.object({
|
|
29
27
|
key: z.string().optional(),
|
|
30
28
|
}),
|
|
31
29
|
});
|
|
32
|
-
// Tool for updating configuration values
|
|
30
|
+
// Tool for updating configuration values (suporta objetos aninhados via dot notation)
|
|
31
|
+
function setNestedValue(obj, path, value) {
|
|
32
|
+
const keys = path.split('.');
|
|
33
|
+
let curr = obj;
|
|
34
|
+
for (let i = 0; i < keys.length - 1; i++) {
|
|
35
|
+
if (!curr[keys[i]] || typeof curr[keys[i]] !== 'object') {
|
|
36
|
+
curr[keys[i]] = {};
|
|
37
|
+
}
|
|
38
|
+
curr = curr[keys[i]];
|
|
39
|
+
}
|
|
40
|
+
curr[keys[keys.length - 1]] = value;
|
|
41
|
+
}
|
|
33
42
|
export const ConfigUpdateTool = tool(async ({ updates }) => {
|
|
34
43
|
try {
|
|
35
44
|
const configManager = ConfigManager.getInstance();
|
|
36
|
-
// Load current config
|
|
37
45
|
await configManager.load();
|
|
38
46
|
const currentConfig = configManager.get();
|
|
39
|
-
//
|
|
40
|
-
const newConfig = { ...currentConfig
|
|
41
|
-
|
|
47
|
+
// Suporta updates com dot notation para campos aninhados
|
|
48
|
+
const newConfig = { ...currentConfig };
|
|
49
|
+
for (const key in updates) {
|
|
50
|
+
setNestedValue(newConfig, key, updates[key]);
|
|
51
|
+
}
|
|
42
52
|
await configManager.save(newConfig);
|
|
43
53
|
return JSON.stringify({ success: true, message: "Configuration updated successfully" });
|
|
44
54
|
}
|
|
45
55
|
catch (error) {
|
|
46
|
-
console.error("Error in ConfigUpdateTool:", error);
|
|
47
56
|
return JSON.stringify({ error: `Failed to update configuration: ${error.message}` });
|
|
48
57
|
}
|
|
49
58
|
}, {
|
|
50
|
-
name: "
|
|
51
|
-
description: "Updates configuration values with validation. Accepts an 'updates' object containing key-value pairs to update.",
|
|
59
|
+
name: "morpheus_config_update",
|
|
60
|
+
description: "Updates configuration values with validation. Accepts an 'updates' object containing key-value pairs to update. Supports dot notation for nested fields (e.g. 'llm.model').",
|
|
52
61
|
schema: z.object({
|
|
53
|
-
updates: z.object({
|
|
54
|
-
// Define common config properties that might be updated
|
|
55
|
-
// Using optional fields to allow flexible updates
|
|
56
|
-
"llm.provider": z.string().optional(),
|
|
57
|
-
"llm.model": z.string().optional(),
|
|
58
|
-
"llm.temperature": z.number().optional(),
|
|
59
|
-
"llm.api_key": z.string().optional(),
|
|
60
|
-
"ui.enabled": z.boolean().optional(),
|
|
61
|
-
"ui.port": z.number().optional(),
|
|
62
|
-
"logging.enabled": z.boolean().optional(),
|
|
63
|
-
"logging.level": z.enum(['debug', 'info', 'warn', 'error']).optional(),
|
|
64
|
-
"audio.enabled": z.boolean().optional(),
|
|
65
|
-
"audio.provider": z.string().optional(),
|
|
66
|
-
"memory.limit": z.number().optional(),
|
|
67
|
-
// Add more specific fields as needed, or use a catch-all for other properties
|
|
68
|
-
}).passthrough(), // Allow additional properties not explicitly defined
|
|
62
|
+
updates: z.object({}).passthrough(),
|
|
69
63
|
}),
|
|
70
64
|
});
|
package/dist/types/config.js
CHANGED