morpheus-cli 0.3.3 → 0.3.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 +1010 -999
- package/bin/morpheus.js +48 -48
- package/dist/channels/telegram.js +34 -29
- package/dist/cli/commands/start.js +41 -3
- package/dist/runtime/lifecycle.js +13 -0
- package/dist/runtime/memory/backfill-embeddings.js +12 -12
- package/dist/runtime/memory/sati/index.js +5 -5
- package/dist/runtime/memory/sati/repository.js +186 -186
- package/dist/runtime/memory/sati/system-prompts.js +52 -52
- package/dist/runtime/memory/session-embedding-worker.js +32 -32
- package/dist/runtime/memory/sqlite.js +151 -151
- package/dist/runtime/oracle.js +116 -116
- package/dist/runtime/tools/analytics-tools.js +12 -12
- package/dist/ui/index.html +13 -2
- package/dist/ui/manifest.webmanifest +1 -0
- package/dist/ui/pwa-192x192.png +0 -0
- package/dist/ui/pwa-512x512.png +0 -0
- package/dist/ui/pwa-maskable-192x192.png +0 -0
- package/dist/ui/pwa-maskable-512x512.png +0 -0
- package/dist/ui/registerSW.js +1 -0
- package/dist/ui/sw.js +1 -0
- package/dist/ui/vite.svg +31 -31
- package/dist/ui/workbox-26f462e7.js +1 -0
- package/package.json +84 -84
- package/dist/http/__tests__/status_api.test.js +0 -55
- package/dist/http/__tests__/status_with_server_api.test.js +0 -60
- package/dist/runtime/__tests__/agent.test.js +0 -95
- package/dist/runtime/__tests__/agent_memory_limit.test.js +0 -61
- package/dist/runtime/__tests__/agent_persistence.test.js +0 -154
- package/dist/runtime/__tests__/manual_santi_verify.js +0 -55
- package/dist/runtime/agent.js +0 -172
- package/dist/runtime/audio-agent.js +0 -55
- package/dist/runtime/santi/contracts.js +0 -1
- package/dist/runtime/santi/middleware.js +0 -61
- package/dist/runtime/santi/santi.js +0 -109
- package/dist/runtime/santi/store.js +0 -158
- package/dist/runtime/tools/__tests__/factory.test.js +0 -42
package/package.json
CHANGED
|
@@ -1,84 +1,84 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "morpheus-cli",
|
|
3
|
-
"version": "0.3.
|
|
4
|
-
"description": "Morpheus is a local AI agent for developers, running as a CLI daemon that connects to LLMs, local tools, and MCPs, enabling interaction via Terminal, Telegram, and Discord. Inspired by the character Morpheus from *The Matrix*, the project acts as an intelligent orchestrator, bridging the gap between the developer and complex systems.",
|
|
5
|
-
"bin": {
|
|
6
|
-
"morpheus": "./bin/morpheus.js"
|
|
7
|
-
},
|
|
8
|
-
"files": [
|
|
9
|
-
"dist",
|
|
10
|
-
"bin",
|
|
11
|
-
"README.md",
|
|
12
|
-
"LICENSE"
|
|
13
|
-
],
|
|
14
|
-
"type": "module",
|
|
15
|
-
"scripts": {
|
|
16
|
-
"build": "tsc && npm run build:ui",
|
|
17
|
-
"build:ui": "npm install --prefix src/ui && npm run build --prefix src/ui",
|
|
18
|
-
"prepublishOnly": "npm run build",
|
|
19
|
-
"start": "node bin/morpheus.js",
|
|
20
|
-
"dev:ui": "npm run dev --prefix src/ui",
|
|
21
|
-
"dev:cli": "npx tsx watch src/cli/index.ts -- start",
|
|
22
|
-
"test": "vitest",
|
|
23
|
-
"backfill": "tsx src/runtime/memory/backfill-embeddings.ts"
|
|
24
|
-
},
|
|
25
|
-
"keywords": [],
|
|
26
|
-
"author": "Marcos Nunes",
|
|
27
|
-
"license": "ISC",
|
|
28
|
-
"dependencies": {
|
|
29
|
-
"@google/genai": "^1.39.0",
|
|
30
|
-
"@inquirer/prompts": "^8.2.0",
|
|
31
|
-
"@langchain/anthropic": "^1.3.12",
|
|
32
|
-
"@langchain/core": "^1.1.18",
|
|
33
|
-
"@langchain/google-genai": "^2.1.13",
|
|
34
|
-
"@langchain/mcp-adapters": "^1.1.2",
|
|
35
|
-
"@langchain/ollama": "^1.2.1",
|
|
36
|
-
"@langchain/openai": "^1.2.3",
|
|
37
|
-
"@openrouter/sdk": "^0.8.0",
|
|
38
|
-
"@types/better-sqlite3": "^7.6.13",
|
|
39
|
-
"@xenova/transformers": "^2.17.2",
|
|
40
|
-
"better-sqlite3": "^12.6.2",
|
|
41
|
-
"body-parser": "^2.2.2",
|
|
42
|
-
"chalk": "^5.6.2",
|
|
43
|
-
"commander": "^14.0.2",
|
|
44
|
-
"cors": "^2.8.6",
|
|
45
|
-
"express": "^5.2.1",
|
|
46
|
-
"figlet": "^1.10.0",
|
|
47
|
-
"fs-extra": "^11.3.3",
|
|
48
|
-
"js-yaml": "^4.1.1",
|
|
49
|
-
"langchain": "^1.2.16",
|
|
50
|
-
"open": "^11.0.0",
|
|
51
|
-
"ora": "^9.1.0",
|
|
52
|
-
"sqlite-vec": "^0.1.7-alpha.2",
|
|
53
|
-
"telegraf": "^4.16.3",
|
|
54
|
-
"winston": "^3.19.0",
|
|
55
|
-
"winston-daily-rotate-file": "^5.0.0",
|
|
56
|
-
"zod": "^4.3.6"
|
|
57
|
-
},
|
|
58
|
-
"devDependencies": {
|
|
59
|
-
"@types/body-parser": "^1.19.6",
|
|
60
|
-
"@types/cors": "^2.8.19",
|
|
61
|
-
"@types/express": "^5.0.6",
|
|
62
|
-
"@types/figlet": "^1.7.0",
|
|
63
|
-
"@types/fs-extra": "^11.0.4",
|
|
64
|
-
"@types/js-yaml": "^4.0.9",
|
|
65
|
-
"@types/node": "^25.1.0",
|
|
66
|
-
"@types/supertest": "^6.0.3",
|
|
67
|
-
"concurrently": "^9.2.1",
|
|
68
|
-
"supertest": "^7.2.2",
|
|
69
|
-
"tsx": "^4.21.0",
|
|
70
|
-
"typescript": "^5.9.3",
|
|
71
|
-
"vitest": "^4.0.18"
|
|
72
|
-
},
|
|
73
|
-
"repository": {
|
|
74
|
-
"type": "git",
|
|
75
|
-
"url": "git+https://github.com/marcosnunesmbs/morpheus.git"
|
|
76
|
-
},
|
|
77
|
-
"bugs": {
|
|
78
|
-
"url": "https://github.com/marcosnunesmbs/morpheus/issues"
|
|
79
|
-
},
|
|
80
|
-
"homepage": "https://morpheusproject.xyz",
|
|
81
|
-
"optionalDependencies": {
|
|
82
|
-
"sqlite-vec-linux-x64": "^0.1.7-alpha.2"
|
|
83
|
-
}
|
|
84
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "morpheus-cli",
|
|
3
|
+
"version": "0.3.6",
|
|
4
|
+
"description": "Morpheus is a local AI agent for developers, running as a CLI daemon that connects to LLMs, local tools, and MCPs, enabling interaction via Terminal, Telegram, and Discord. Inspired by the character Morpheus from *The Matrix*, the project acts as an intelligent orchestrator, bridging the gap between the developer and complex systems.",
|
|
5
|
+
"bin": {
|
|
6
|
+
"morpheus": "./bin/morpheus.js"
|
|
7
|
+
},
|
|
8
|
+
"files": [
|
|
9
|
+
"dist",
|
|
10
|
+
"bin",
|
|
11
|
+
"README.md",
|
|
12
|
+
"LICENSE"
|
|
13
|
+
],
|
|
14
|
+
"type": "module",
|
|
15
|
+
"scripts": {
|
|
16
|
+
"build": "tsc && npm run build:ui",
|
|
17
|
+
"build:ui": "npm install --prefix src/ui && npm run build --prefix src/ui",
|
|
18
|
+
"prepublishOnly": "npm run build",
|
|
19
|
+
"start": "node bin/morpheus.js",
|
|
20
|
+
"dev:ui": "npm run dev --prefix src/ui",
|
|
21
|
+
"dev:cli": "npx tsx watch src/cli/index.ts -- start",
|
|
22
|
+
"test": "vitest",
|
|
23
|
+
"backfill": "tsx src/runtime/memory/backfill-embeddings.ts"
|
|
24
|
+
},
|
|
25
|
+
"keywords": [],
|
|
26
|
+
"author": "Marcos Nunes",
|
|
27
|
+
"license": "ISC",
|
|
28
|
+
"dependencies": {
|
|
29
|
+
"@google/genai": "^1.39.0",
|
|
30
|
+
"@inquirer/prompts": "^8.2.0",
|
|
31
|
+
"@langchain/anthropic": "^1.3.12",
|
|
32
|
+
"@langchain/core": "^1.1.18",
|
|
33
|
+
"@langchain/google-genai": "^2.1.13",
|
|
34
|
+
"@langchain/mcp-adapters": "^1.1.2",
|
|
35
|
+
"@langchain/ollama": "^1.2.1",
|
|
36
|
+
"@langchain/openai": "^1.2.3",
|
|
37
|
+
"@openrouter/sdk": "^0.8.0",
|
|
38
|
+
"@types/better-sqlite3": "^7.6.13",
|
|
39
|
+
"@xenova/transformers": "^2.17.2",
|
|
40
|
+
"better-sqlite3": "^12.6.2",
|
|
41
|
+
"body-parser": "^2.2.2",
|
|
42
|
+
"chalk": "^5.6.2",
|
|
43
|
+
"commander": "^14.0.2",
|
|
44
|
+
"cors": "^2.8.6",
|
|
45
|
+
"express": "^5.2.1",
|
|
46
|
+
"figlet": "^1.10.0",
|
|
47
|
+
"fs-extra": "^11.3.3",
|
|
48
|
+
"js-yaml": "^4.1.1",
|
|
49
|
+
"langchain": "^1.2.16",
|
|
50
|
+
"open": "^11.0.0",
|
|
51
|
+
"ora": "^9.1.0",
|
|
52
|
+
"sqlite-vec": "^0.1.7-alpha.2",
|
|
53
|
+
"telegraf": "^4.16.3",
|
|
54
|
+
"winston": "^3.19.0",
|
|
55
|
+
"winston-daily-rotate-file": "^5.0.0",
|
|
56
|
+
"zod": "^4.3.6"
|
|
57
|
+
},
|
|
58
|
+
"devDependencies": {
|
|
59
|
+
"@types/body-parser": "^1.19.6",
|
|
60
|
+
"@types/cors": "^2.8.19",
|
|
61
|
+
"@types/express": "^5.0.6",
|
|
62
|
+
"@types/figlet": "^1.7.0",
|
|
63
|
+
"@types/fs-extra": "^11.0.4",
|
|
64
|
+
"@types/js-yaml": "^4.0.9",
|
|
65
|
+
"@types/node": "^25.1.0",
|
|
66
|
+
"@types/supertest": "^6.0.3",
|
|
67
|
+
"concurrently": "^9.2.1",
|
|
68
|
+
"supertest": "^7.2.2",
|
|
69
|
+
"tsx": "^4.21.0",
|
|
70
|
+
"typescript": "^5.9.3",
|
|
71
|
+
"vitest": "^4.0.18"
|
|
72
|
+
},
|
|
73
|
+
"repository": {
|
|
74
|
+
"type": "git",
|
|
75
|
+
"url": "git+https://github.com/marcosnunesmbs/morpheus.git"
|
|
76
|
+
},
|
|
77
|
+
"bugs": {
|
|
78
|
+
"url": "https://github.com/marcosnunesmbs/morpheus/issues"
|
|
79
|
+
},
|
|
80
|
+
"homepage": "https://morpheusproject.xyz",
|
|
81
|
+
"optionalDependencies": {
|
|
82
|
+
"sqlite-vec-linux-x64": "^0.1.7-alpha.2"
|
|
83
|
+
}
|
|
84
|
+
}
|
|
@@ -1,55 +0,0 @@
|
|
|
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
|
-
});
|
|
@@ -1,60 +0,0 @@
|
|
|
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
|
-
});
|
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
2
|
-
import { Agent } from '../agent.js';
|
|
3
|
-
import { ProviderFactory } from '../providers/factory.js';
|
|
4
|
-
import { ToolsFactory } from '../tools/factory.js';
|
|
5
|
-
import { DEFAULT_CONFIG } from '../../types/config.js';
|
|
6
|
-
import { AIMessage } from '@langchain/core/messages';
|
|
7
|
-
import * as fs from 'fs-extra';
|
|
8
|
-
import * as path from 'path';
|
|
9
|
-
import { homedir } from 'os';
|
|
10
|
-
vi.mock('../providers/factory.js');
|
|
11
|
-
vi.mock('../tools/factory.js');
|
|
12
|
-
describe('Agent', () => {
|
|
13
|
-
let agent;
|
|
14
|
-
const mockProvider = {
|
|
15
|
-
invoke: vi.fn(),
|
|
16
|
-
};
|
|
17
|
-
beforeEach(async () => {
|
|
18
|
-
vi.resetAllMocks();
|
|
19
|
-
// Clean up any existing test database
|
|
20
|
-
const defaultDbPath = path.join(homedir(), ".morpheus", "memory", "short-memory.db");
|
|
21
|
-
if (fs.existsSync(defaultDbPath)) {
|
|
22
|
-
try {
|
|
23
|
-
const Database = (await import("better-sqlite3")).default;
|
|
24
|
-
const db = new Database(defaultDbPath);
|
|
25
|
-
db.exec("DELETE FROM messages");
|
|
26
|
-
db.close();
|
|
27
|
-
}
|
|
28
|
-
catch (err) {
|
|
29
|
-
// Ignore errors if database doesn't exist or is corrupted
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
mockProvider.invoke.mockResolvedValue({ messages: [new AIMessage('Hello world')] });
|
|
33
|
-
vi.mocked(ProviderFactory.create).mockResolvedValue(mockProvider);
|
|
34
|
-
vi.mocked(ToolsFactory.create).mockResolvedValue([]);
|
|
35
|
-
agent = new Agent(DEFAULT_CONFIG);
|
|
36
|
-
});
|
|
37
|
-
afterEach(async () => {
|
|
38
|
-
// Clean up after each test
|
|
39
|
-
if (agent) {
|
|
40
|
-
try {
|
|
41
|
-
await agent.clearMemory();
|
|
42
|
-
}
|
|
43
|
-
catch (err) {
|
|
44
|
-
// Ignore cleanup errors
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
});
|
|
48
|
-
it('should initialize successfully', async () => {
|
|
49
|
-
await agent.initialize();
|
|
50
|
-
expect(ToolsFactory.create).toHaveBeenCalled();
|
|
51
|
-
expect(ProviderFactory.create).toHaveBeenCalledWith(DEFAULT_CONFIG.llm, []);
|
|
52
|
-
});
|
|
53
|
-
it('should chat successfully', async () => {
|
|
54
|
-
await agent.initialize();
|
|
55
|
-
const response = await agent.chat('Hi');
|
|
56
|
-
expect(response).toBe('Hello world');
|
|
57
|
-
expect(mockProvider.invoke).toHaveBeenCalled();
|
|
58
|
-
});
|
|
59
|
-
it('should throw if not initialized', async () => {
|
|
60
|
-
await expect(agent.chat('Hi')).rejects.toThrow('initialize() first');
|
|
61
|
-
});
|
|
62
|
-
it('should maintain history', async () => {
|
|
63
|
-
await agent.initialize();
|
|
64
|
-
// Clear any residual history from previous tests
|
|
65
|
-
await agent.clearMemory();
|
|
66
|
-
// First turn
|
|
67
|
-
await agent.chat('Hi');
|
|
68
|
-
const history1 = await agent.getHistory();
|
|
69
|
-
expect(history1).toHaveLength(2);
|
|
70
|
-
expect(history1[0].content).toBe('Hi'); // User
|
|
71
|
-
expect(history1[1].content).toBe('Hello world'); // AI
|
|
72
|
-
// Second turn
|
|
73
|
-
// Update mock return value for next call
|
|
74
|
-
mockProvider.invoke.mockResolvedValue({ messages: [new AIMessage('I am fine')] });
|
|
75
|
-
await agent.chat('How are you?');
|
|
76
|
-
const history2 = await agent.getHistory();
|
|
77
|
-
expect(history2).toHaveLength(4);
|
|
78
|
-
expect(history2[2].content).toBe('How are you?');
|
|
79
|
-
expect(history2[3].content).toBe('I am fine');
|
|
80
|
-
});
|
|
81
|
-
describe('Configuration Validation', () => {
|
|
82
|
-
it('should throw if llm provider is missing', async () => {
|
|
83
|
-
const invalidConfig = JSON.parse(JSON.stringify(DEFAULT_CONFIG));
|
|
84
|
-
delete invalidConfig.llm.provider; // Invalid
|
|
85
|
-
const badAgent = new Agent(invalidConfig);
|
|
86
|
-
await expect(badAgent.initialize()).rejects.toThrow('LLM provider not specified');
|
|
87
|
-
});
|
|
88
|
-
it('should propagate ProviderError during initialization', async () => {
|
|
89
|
-
const mockError = new Error("Mock Factory Error");
|
|
90
|
-
vi.mocked(ProviderFactory.create).mockImplementation(() => { throw mockError; });
|
|
91
|
-
// ProviderError constructs message as: "Provider {provider} failed: {originalError.message}"
|
|
92
|
-
await expect(agent.initialize()).rejects.toThrow('Provider openai failed: Mock Factory Error');
|
|
93
|
-
});
|
|
94
|
-
});
|
|
95
|
-
});
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
2
|
-
import { Agent } from '../agent.js';
|
|
3
|
-
import { ProviderFactory } from '../providers/factory.js';
|
|
4
|
-
import { DEFAULT_CONFIG } from '../../types/config.js';
|
|
5
|
-
import { AIMessage } from '@langchain/core/messages';
|
|
6
|
-
import * as fs from 'fs-extra';
|
|
7
|
-
import * as path from 'path';
|
|
8
|
-
import { homedir } from 'os';
|
|
9
|
-
vi.mock('../providers/factory.js');
|
|
10
|
-
describe('Agent Memory Limit', () => {
|
|
11
|
-
let agent;
|
|
12
|
-
const mockProvider = {
|
|
13
|
-
invoke: vi.fn(),
|
|
14
|
-
};
|
|
15
|
-
const dbPath = path.join(homedir(), ".morpheus", "memory", "short-memory.db");
|
|
16
|
-
beforeEach(async () => {
|
|
17
|
-
vi.resetAllMocks();
|
|
18
|
-
// Clean up DB
|
|
19
|
-
if (fs.existsSync(dbPath)) {
|
|
20
|
-
try {
|
|
21
|
-
const Database = (await import("better-sqlite3")).default;
|
|
22
|
-
const db = new Database(dbPath);
|
|
23
|
-
db.exec("DELETE FROM messages");
|
|
24
|
-
db.close();
|
|
25
|
-
}
|
|
26
|
-
catch (err) { }
|
|
27
|
-
}
|
|
28
|
-
mockProvider.invoke.mockResolvedValue({
|
|
29
|
-
messages: [new AIMessage('Response')]
|
|
30
|
-
});
|
|
31
|
-
vi.mocked(ProviderFactory.create).mockResolvedValue(mockProvider);
|
|
32
|
-
});
|
|
33
|
-
afterEach(async () => {
|
|
34
|
-
if (agent) {
|
|
35
|
-
try {
|
|
36
|
-
await agent.clearMemory();
|
|
37
|
-
}
|
|
38
|
-
catch (err) { }
|
|
39
|
-
}
|
|
40
|
-
});
|
|
41
|
-
it('should respect configured memory limit', async () => {
|
|
42
|
-
const limitedConfig = JSON.parse(JSON.stringify(DEFAULT_CONFIG));
|
|
43
|
-
limitedConfig.memory.limit = 2; // Only last 2 messages (1 exchange)
|
|
44
|
-
agent = new Agent(limitedConfig);
|
|
45
|
-
await agent.initialize();
|
|
46
|
-
// Turn 1
|
|
47
|
-
await agent.chat('Msg 1');
|
|
48
|
-
// Turn 2
|
|
49
|
-
await agent.chat('Msg 2');
|
|
50
|
-
// Turn 3
|
|
51
|
-
await agent.chat('Msg 3');
|
|
52
|
-
// DB should have 6 messages (3 User + 3 AI)
|
|
53
|
-
// getHistory() should return only 2 (User Msg 3 + AI Response)
|
|
54
|
-
// Wait, SQLiteChatMessageHistory limit might be total messages? Or pairs?
|
|
55
|
-
// LangChain's limit usually means "last N messages".
|
|
56
|
-
const history = await agent.getHistory();
|
|
57
|
-
// Assuming limit=2 means 2 messages.
|
|
58
|
-
expect(history.length).toBeLessThanOrEqual(2);
|
|
59
|
-
expect(history[history.length - 1].content).toBe('Response');
|
|
60
|
-
});
|
|
61
|
-
});
|
|
@@ -1,154 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
|
|
2
|
-
import { Agent } from "../agent.js";
|
|
3
|
-
import { ProviderFactory } from "../providers/factory.js";
|
|
4
|
-
import { DEFAULT_CONFIG } from "../../types/config.js";
|
|
5
|
-
import { AIMessage } from "@langchain/core/messages";
|
|
6
|
-
import * as fs from "fs-extra";
|
|
7
|
-
import * as path from "path";
|
|
8
|
-
import { tmpdir } from "os";
|
|
9
|
-
import { ToolsFactory } from "../tools/factory.js";
|
|
10
|
-
vi.mock("../providers/factory.js");
|
|
11
|
-
vi.mock("../tools/factory.js");
|
|
12
|
-
describe("Agent Persistence Integration", () => {
|
|
13
|
-
let agent;
|
|
14
|
-
let testDbPath;
|
|
15
|
-
let tempDir;
|
|
16
|
-
const mockProvider = {
|
|
17
|
-
invoke: vi.fn(),
|
|
18
|
-
};
|
|
19
|
-
beforeEach(async () => {
|
|
20
|
-
vi.resetAllMocks();
|
|
21
|
-
// Create a unique temporary test database path for each test to avoid interference
|
|
22
|
-
tempDir = path.join(tmpdir(), "morpheus-test-agent", Date.now().toString() + Math.random().toString(36).substring(7));
|
|
23
|
-
testDbPath = path.join(tempDir, "short-memory.db");
|
|
24
|
-
mockProvider.invoke.mockImplementation(async ({ messages }) => {
|
|
25
|
-
return {
|
|
26
|
-
messages: [...messages, new AIMessage("Test response")]
|
|
27
|
-
};
|
|
28
|
-
});
|
|
29
|
-
vi.mocked(ProviderFactory.create).mockResolvedValue(mockProvider);
|
|
30
|
-
vi.mocked(ToolsFactory.create).mockResolvedValue([]);
|
|
31
|
-
agent = new Agent(DEFAULT_CONFIG, { databasePath: testDbPath });
|
|
32
|
-
});
|
|
33
|
-
afterEach(async () => {
|
|
34
|
-
// Clean up temporary test directory
|
|
35
|
-
if (fs.existsSync(tempDir)) {
|
|
36
|
-
try {
|
|
37
|
-
fs.removeSync(tempDir);
|
|
38
|
-
}
|
|
39
|
-
catch (err) {
|
|
40
|
-
// Ignore removal errors if file is locked
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
});
|
|
44
|
-
describe("Database File Creation", () => {
|
|
45
|
-
it("should create database file on initialization", async () => {
|
|
46
|
-
await agent.initialize();
|
|
47
|
-
expect(fs.existsSync(testDbPath)).toBe(true);
|
|
48
|
-
});
|
|
49
|
-
});
|
|
50
|
-
describe("Message Persistence", () => {
|
|
51
|
-
it("should persist messages to database", async () => {
|
|
52
|
-
await agent.initialize();
|
|
53
|
-
// Send a message
|
|
54
|
-
await agent.chat("Hello, Agent!");
|
|
55
|
-
// Verify history contains the message
|
|
56
|
-
const history = await agent.getHistory();
|
|
57
|
-
expect(history).toHaveLength(2); // User message + AI response
|
|
58
|
-
expect(history[0].content).toBe("Hello, Agent!");
|
|
59
|
-
expect(history[1].content).toBe("Test response");
|
|
60
|
-
});
|
|
61
|
-
it("should restore conversation history on restart", async () => {
|
|
62
|
-
// First session: send a message
|
|
63
|
-
await agent.initialize();
|
|
64
|
-
await agent.chat("Remember this message");
|
|
65
|
-
const firstHistory = await agent.getHistory();
|
|
66
|
-
expect(firstHistory).toHaveLength(2);
|
|
67
|
-
// Simulate restart: create new agent instance with SAME database path
|
|
68
|
-
const agent2 = new Agent(DEFAULT_CONFIG, { databasePath: testDbPath });
|
|
69
|
-
await agent2.initialize();
|
|
70
|
-
// Verify history was restored
|
|
71
|
-
const restoredHistory = await agent2.getHistory();
|
|
72
|
-
expect(restoredHistory.length).toBeGreaterThanOrEqual(2);
|
|
73
|
-
// Check that the old messages are present
|
|
74
|
-
const contents = restoredHistory.map(m => m.content);
|
|
75
|
-
expect(contents).toContain("Remember this message");
|
|
76
|
-
expect(contents).toContain("Test response");
|
|
77
|
-
});
|
|
78
|
-
it("should accumulate messages across multiple conversations", async () => {
|
|
79
|
-
await agent.initialize();
|
|
80
|
-
// First conversation
|
|
81
|
-
await agent.chat("First message");
|
|
82
|
-
// Second conversation
|
|
83
|
-
mockProvider.invoke.mockImplementation(async ({ messages }) => {
|
|
84
|
-
return {
|
|
85
|
-
messages: [...messages, new AIMessage("Second response")]
|
|
86
|
-
};
|
|
87
|
-
});
|
|
88
|
-
await agent.chat("Second message");
|
|
89
|
-
// Verify all messages are persisted
|
|
90
|
-
const history = await agent.getHistory();
|
|
91
|
-
expect(history).toHaveLength(4);
|
|
92
|
-
expect(history[0].content).toBe("First message");
|
|
93
|
-
expect(history[1].content).toBe("Test response");
|
|
94
|
-
expect(history[2].content).toBe("Second message");
|
|
95
|
-
expect(history[3].content).toBe("Second response");
|
|
96
|
-
});
|
|
97
|
-
});
|
|
98
|
-
describe("Memory Clearing", () => {
|
|
99
|
-
it("should clear all persisted messages", async () => {
|
|
100
|
-
await agent.initialize();
|
|
101
|
-
// Add some messages
|
|
102
|
-
await agent.chat("Message 1");
|
|
103
|
-
await agent.chat("Message 2");
|
|
104
|
-
// Verify messages exist
|
|
105
|
-
let history = await agent.getHistory();
|
|
106
|
-
expect(history.length).toBeGreaterThan(0);
|
|
107
|
-
// Clear memory
|
|
108
|
-
await agent.clearMemory();
|
|
109
|
-
// Verify messages are cleared
|
|
110
|
-
history = await agent.getHistory();
|
|
111
|
-
expect(history).toEqual([]);
|
|
112
|
-
});
|
|
113
|
-
it("should start fresh after clearing memory", async () => {
|
|
114
|
-
await agent.initialize();
|
|
115
|
-
// Add and clear messages
|
|
116
|
-
await agent.chat("Old message");
|
|
117
|
-
await agent.clearMemory();
|
|
118
|
-
// Add new message
|
|
119
|
-
mockProvider.invoke.mockImplementation(async ({ messages }) => {
|
|
120
|
-
return {
|
|
121
|
-
messages: [...messages, new AIMessage("New response")]
|
|
122
|
-
};
|
|
123
|
-
});
|
|
124
|
-
await agent.chat("New message");
|
|
125
|
-
// Verify only new messages exist
|
|
126
|
-
const history = await agent.getHistory();
|
|
127
|
-
expect(history).toHaveLength(2);
|
|
128
|
-
expect(history[0].content).toBe("New message");
|
|
129
|
-
expect(history[1].content).toBe("New response");
|
|
130
|
-
});
|
|
131
|
-
});
|
|
132
|
-
describe("Context Preservation", () => {
|
|
133
|
-
it("should include history in conversation context", async () => {
|
|
134
|
-
await agent.initialize();
|
|
135
|
-
// First message
|
|
136
|
-
await agent.chat("My name is Alice");
|
|
137
|
-
// Second message (should have context)
|
|
138
|
-
mockProvider.invoke.mockImplementation(async ({ messages }) => {
|
|
139
|
-
return {
|
|
140
|
-
messages: [...messages, new AIMessage("Hello Alice!")]
|
|
141
|
-
};
|
|
142
|
-
});
|
|
143
|
-
await agent.chat("What is my name?");
|
|
144
|
-
// Verify the provider was called with full history
|
|
145
|
-
const lastCall = mockProvider.invoke.mock.calls[1];
|
|
146
|
-
const messagesPassedToLLM = lastCall[0].messages;
|
|
147
|
-
// Should include: system message, previous user message, previous AI response, new user message
|
|
148
|
-
expect(messagesPassedToLLM.length).toBeGreaterThanOrEqual(4);
|
|
149
|
-
// Check that context includes the original message
|
|
150
|
-
const contents = messagesPassedToLLM.map((m) => m.content);
|
|
151
|
-
expect(contents).toContain("My name is Alice");
|
|
152
|
-
});
|
|
153
|
-
});
|
|
154
|
-
});
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
import { Santi } from "../santi/santi.js";
|
|
2
|
-
import { ConfigManager } from "../../config/manager.js";
|
|
3
|
-
import * as path from "path";
|
|
4
|
-
import { homedir } from "os";
|
|
5
|
-
async function main() {
|
|
6
|
-
console.log("Starting Santi Manual Verification...");
|
|
7
|
-
// 1. Check DB isolation
|
|
8
|
-
const santiDbPath = path.join(homedir(), ".morpheus", "memory", "santi-memory.db");
|
|
9
|
-
const shortDbPath = path.join(homedir(), ".morpheus", "memory", "short-memory.db");
|
|
10
|
-
console.log(`Checking DB paths: \n- ${santiDbPath}\n- ${shortDbPath}`);
|
|
11
|
-
// 2. Initialize Santi
|
|
12
|
-
const santi = new Santi();
|
|
13
|
-
console.log("Santi initialized.");
|
|
14
|
-
// 3. Test Recovery (should be empty or have data if prev run)
|
|
15
|
-
let memories = await santi.recover("test");
|
|
16
|
-
console.log(`Initial recovery "test": ${memories.length} items`);
|
|
17
|
-
// 4. Manually add memory via internal store (reflection hack for test)
|
|
18
|
-
console.log("Injecting test memory...");
|
|
19
|
-
// @ts-ignore
|
|
20
|
-
santi.store.addMemory({
|
|
21
|
-
category: 'preference',
|
|
22
|
-
importance: 'high',
|
|
23
|
-
summary: 'The user loves manual testing scripts.',
|
|
24
|
-
hash: 'manual-test-hash',
|
|
25
|
-
source: 'manual_test'
|
|
26
|
-
});
|
|
27
|
-
// 5. Test Recovery again
|
|
28
|
-
memories = await santi.recover("loves manual testing");
|
|
29
|
-
console.log(`Recovery after injection "loves manual testing": ${memories.length} items`);
|
|
30
|
-
if (memories.length > 0 && memories[0].summary.includes("loves manual testing")) {
|
|
31
|
-
console.log("✅ Recovery SUCCESS");
|
|
32
|
-
}
|
|
33
|
-
else {
|
|
34
|
-
console.error("❌ Recovery FAILED");
|
|
35
|
-
}
|
|
36
|
-
// 6. Test Evaluate (Mocking messages)
|
|
37
|
-
// This requires LLM config to be present.
|
|
38
|
-
// We skip this in manual test if no config, but logging will show warning.
|
|
39
|
-
if (ConfigManager.getInstance().get().llm) {
|
|
40
|
-
console.log("Testing Evaluate (dry run with LLM)...");
|
|
41
|
-
// This will assume valid LLM config
|
|
42
|
-
/*
|
|
43
|
-
await santi.evaluate([
|
|
44
|
-
new HumanMessage("My name is Morpheus Tester."),
|
|
45
|
-
new AIMessage("Nice to meet you.")
|
|
46
|
-
]);
|
|
47
|
-
*/
|
|
48
|
-
console.log("Evaluate test skipped to avoid api cost, but code is in place.");
|
|
49
|
-
}
|
|
50
|
-
else {
|
|
51
|
-
console.log("Skipping Evaluate test (no LLM config).");
|
|
52
|
-
}
|
|
53
|
-
console.log("Verification Complete.");
|
|
54
|
-
}
|
|
55
|
-
main().catch(console.error);
|