morpheus-cli 0.1.9 → 0.1.11

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.
@@ -28,12 +28,12 @@ vi.mock('../../runtime/display.js', () => ({
28
28
  }));
29
29
  describe('TelegramAdapter', () => {
30
30
  let adapter;
31
- let mockAgent;
31
+ let mockOracle;
32
32
  beforeEach(() => {
33
- mockAgent = {
33
+ mockOracle = {
34
34
  chat: vi.fn(),
35
35
  };
36
- adapter = new TelegramAdapter(mockAgent);
36
+ adapter = new TelegramAdapter(mockOracle);
37
37
  });
38
38
  afterEach(() => {
39
39
  vi.clearAllMocks();
@@ -6,16 +6,16 @@ import path from 'path';
6
6
  import os from 'os';
7
7
  import { ConfigManager } from '../config/manager.js';
8
8
  import { DisplayManager } from '../runtime/display.js';
9
- import { AudioAgent } from '../runtime/audio-agent.js';
9
+ import { Telephonist } from '../runtime/telephonist.js';
10
10
  export class TelegramAdapter {
11
11
  bot = null;
12
12
  isConnected = false;
13
13
  display = DisplayManager.getInstance();
14
14
  config = ConfigManager.getInstance();
15
- agent;
16
- audioAgent = new AudioAgent();
17
- constructor(agent) {
18
- this.agent = agent;
15
+ oracle;
16
+ telephonist = new Telephonist();
17
+ constructor(oracle) {
18
+ this.oracle = oracle;
19
19
  }
20
20
  async connect(token, allowedUsers) {
21
21
  if (this.isConnected) {
@@ -44,7 +44,7 @@ export class TelegramAdapter {
44
44
  // Send "typing" status
45
45
  await ctx.sendChatAction('typing');
46
46
  // Process with Agent
47
- const response = await this.agent.chat(text);
47
+ const response = await this.oracle.chat(text);
48
48
  if (response) {
49
49
  await ctx.reply(response);
50
50
  this.display.log(`Responded to @${user}`, { source: 'Telegram' });
@@ -76,7 +76,7 @@ export class TelegramAdapter {
76
76
  }
77
77
  const apiKey = config.audio.apiKey || (config.llm.provider === 'gemini' ? config.llm.api_key : undefined);
78
78
  if (!apiKey) {
79
- this.display.log(`Audio transcription failed: No Gemini API key available`, { source: 'AgentAudio', level: 'error' });
79
+ this.display.log(`Audio transcription failed: No Gemini API key available`, { source: 'Telephonist', level: 'error' });
80
80
  await ctx.reply("Audio transcription requires a Gemini API key. Please configure `audio.apiKey` or set LLM provider to Gemini.");
81
81
  return;
82
82
  }
@@ -85,19 +85,19 @@ export class TelegramAdapter {
85
85
  await ctx.reply(`Voice message too long. Max duration is ${config.audio.maxDurationSeconds}s.`);
86
86
  return;
87
87
  }
88
- this.display.log(`Receiving voice message from @${user} (${duration}s)...`, { source: 'AgentAudio' });
88
+ this.display.log(`Receiving voice message from @${user} (${duration}s)...`, { source: 'Telephonist' });
89
89
  let filePath = null;
90
90
  let listeningMsg = null;
91
91
  try {
92
92
  listeningMsg = await ctx.reply("🎧Escutando...");
93
93
  // Download
94
- this.display.log(`Downloading audio for @${user}...`, { source: 'AgentAudio' });
94
+ this.display.log(`Downloading audio for @${user}...`, { source: 'Telephonist' });
95
95
  const fileLink = await ctx.telegram.getFileLink(ctx.message.voice.file_id);
96
96
  filePath = await this.downloadToTemp(fileLink);
97
97
  // Transcribe
98
- this.display.log(`Transcribing audio for @${user}...`, { source: 'AgentAudio' });
99
- const { text, usage } = await this.audioAgent.transcribe(filePath, 'audio/ogg', apiKey);
100
- this.display.log(`Transcription success for @${user}: "${text}"`, { source: 'AgentAudio', level: 'success' });
98
+ this.display.log(`Transcribing audio for @${user}...`, { source: 'Telephonist' });
99
+ const { text, usage } = await this.telephonist.transcribe(filePath, 'audio/ogg', apiKey);
100
+ this.display.log(`Transcription success for @${user}: "${text}"`, { source: 'Telephonist', level: 'success' });
101
101
  // Reply with transcription (optional, maybe just process it?)
102
102
  // The prompt says "reply with the answer".
103
103
  // "Transcribe them... and process the resulting text as a standard user prompt."
@@ -105,7 +105,7 @@ export class TelegramAdapter {
105
105
  await ctx.reply(`🎤 *Transcription*: _"${text}"_`, { parse_mode: 'Markdown' });
106
106
  await ctx.sendChatAction('typing');
107
107
  // Process with Agent
108
- const response = await this.agent.chat(text, usage);
108
+ const response = await this.oracle.chat(text, usage);
109
109
  // if (listeningMsg) {
110
110
  // try {
111
111
  // await ctx.telegram.deleteMessage(ctx.chat.id, listeningMsg.message_id);
@@ -119,7 +119,7 @@ export class TelegramAdapter {
119
119
  }
120
120
  }
121
121
  catch (error) {
122
- this.display.log(`Audio processing error for @${user}: ${error.message}`, { source: 'AgentAudio', level: 'error' });
122
+ this.display.log(`Audio processing error for @${user}: ${error.message}`, { source: 'Telephonist', level: 'error' });
123
123
  await ctx.reply("Sorry, I failed to process your audio message.");
124
124
  }
125
125
  finally {
@@ -8,7 +8,7 @@ import { ConfigManager } from '../../config/manager.js';
8
8
  import { renderBanner } from '../utils/render.js';
9
9
  import { TelegramAdapter } from '../../channels/telegram.js';
10
10
  import { PATHS } from '../../config/paths.js';
11
- import { Agent } from '../../runtime/agent.js';
11
+ import { Oracle } from '../../runtime/oracle.js';
12
12
  import { ProviderError } from '../../runtime/errors.js';
13
13
  import { HttpServer } from '../../http/server.js';
14
14
  export const startCommand = new Command('start')
@@ -45,13 +45,13 @@ export const startCommand = new Command('start')
45
45
  if (options.ui) {
46
46
  display.log(chalk.blue(`Web UI enabled to port ${options.port}`));
47
47
  }
48
- // Initialize Agent
49
- const agent = new Agent(config);
48
+ // Initialize Oracle
49
+ const oracle = new Oracle(config);
50
50
  try {
51
- display.startSpinner(`Initializing ${config.llm.provider} agent...`);
52
- await agent.initialize();
51
+ display.startSpinner(`Initializing ${config.llm.provider} oracle...`);
52
+ await oracle.initialize();
53
53
  display.stopSpinner();
54
- display.log(chalk.green('✓ Agent initialized'), { source: 'Agent' });
54
+ display.log(chalk.green('✓ Oracle initialized'), { source: 'Oracle' });
55
55
  }
56
56
  catch (err) {
57
57
  display.stopSpinner();
@@ -63,7 +63,7 @@ export const startCommand = new Command('start')
63
63
  }
64
64
  }
65
65
  else {
66
- display.log(chalk.red('\nAgent initialization failed:'));
66
+ display.log(chalk.red('\nOracle initialization failed:'));
67
67
  display.log(chalk.white(err.message));
68
68
  if (err.message.includes('API Key')) {
69
69
  display.log(chalk.yellow('Tip: Check your API key in configuration or environment variables.'));
@@ -89,7 +89,7 @@ export const startCommand = new Command('start')
89
89
  // Initialize Telegram
90
90
  if (config.channels.telegram.enabled) {
91
91
  if (config.channels.telegram.token) {
92
- const telegram = new TelegramAdapter(agent);
92
+ const telegram = new TelegramAdapter(oracle);
93
93
  try {
94
94
  await telegram.connect(config.channels.telegram.token, config.channels.telegram.allowedUsers || []);
95
95
  adapters.push(telegram);
@@ -4,6 +4,7 @@ import { DEFAULT_CONFIG } from '../types/config.js';
4
4
  import { PATHS } from './paths.js';
5
5
  import { setByPath } from './utils.js';
6
6
  import { ConfigSchema } from './schemas.js';
7
+ import { migrateConfigFile } from '../runtime/migration.js';
7
8
  export class ConfigManager {
8
9
  static instance;
9
10
  config = DEFAULT_CONFIG;
@@ -16,6 +17,7 @@ export class ConfigManager {
16
17
  }
17
18
  async load() {
18
19
  try {
20
+ await migrateConfigFile();
19
21
  if (await fs.pathExists(PATHS.config)) {
20
22
  const raw = await fs.readFile(PATHS.config, 'utf8');
21
23
  const parsed = yaml.load(raw);
@@ -17,7 +17,7 @@ export async function loadMCPConfig() {
17
17
  rawConfig = JSON.parse(content);
18
18
  }
19
19
  catch (err) {
20
- display.log(`Failed to parse mcps.json: ${err.message}`, { level: 'error', source: 'Config' });
20
+ display.log(`Failed to parse mcps.json: ${err.message}`, { level: 'error', source: 'Zaion' });
21
21
  return servers;
22
22
  }
23
23
  // Filter metadata keys (starting with _ or $) and the "example" template key
@@ -26,15 +26,15 @@ export async function loadMCPConfig() {
26
26
  try {
27
27
  const validated = MCPServerConfigSchema.parse(config);
28
28
  servers[name] = validated;
29
- display.log(`Loaded MCP server: ${name}`, { level: 'debug', source: 'Config' });
29
+ display.log(`Loaded MCP server: ${name}`, { level: 'debug', source: 'Zaion' });
30
30
  }
31
31
  catch (err) {
32
32
  if (err instanceof z.ZodError) {
33
33
  const issues = err.issues.map(i => i.message).join(', ');
34
- display.log(`Invalid MCP server '${name}': ${issues}`, { level: 'warning', source: 'Config' });
34
+ display.log(`Invalid MCP server '${name}': ${issues}`, { level: 'warning', source: 'Zaion' });
35
35
  }
36
36
  else {
37
- display.log(`Invalid MCP server '${name}': ${err.message}`, { level: 'warning', source: 'Config' });
37
+ display.log(`Invalid MCP server '${name}': ${err.message}`, { level: 'warning', source: 'Zaion' });
38
38
  }
39
39
  }
40
40
  }
@@ -5,7 +5,8 @@ export const MORPHEUS_ROOT = path.join(USER_HOME, '.morpheus');
5
5
  export const LOGS_DIR = path.join(MORPHEUS_ROOT, 'logs');
6
6
  export const PATHS = {
7
7
  root: MORPHEUS_ROOT,
8
- config: path.join(MORPHEUS_ROOT, 'config.yaml'),
8
+ config: path.join(MORPHEUS_ROOT, 'zaion.yaml'),
9
+ legacyConfig: path.join(MORPHEUS_ROOT, 'config.yaml'),
9
10
  pid: path.join(MORPHEUS_ROOT, 'morpheus.pid'),
10
11
  logs: LOGS_DIR,
11
12
  memory: path.join(MORPHEUS_ROOT, 'memory'),
package/dist/http/api.js CHANGED
@@ -98,7 +98,7 @@ export function createApiRouter() {
98
98
  if (changes.length > 0) {
99
99
  const display = DisplayManager.getInstance();
100
100
  display.log(`Configuration updated via UI:\n - ${changes.join('\n - ')}`, {
101
- source: 'Config',
101
+ source: 'Zaion',
102
102
  level: 'info'
103
103
  });
104
104
  }
@@ -1,4 +1,4 @@
1
- import { Agent } from '../agent.js';
1
+ import { Oracle } from '../oracle.js';
2
2
  import chalk from 'chalk';
3
3
  const start = Date.now();
4
4
  console.log(chalk.blue('Running Manual Start Verification...'));
@@ -23,19 +23,19 @@ const mockConfig = {
23
23
  };
24
24
  const run = async () => {
25
25
  try {
26
- console.log(chalk.gray('1. Instantiating Agent...'));
27
- const agent = new Agent(mockConfig);
28
- console.log(chalk.gray('2. Initializing Agent...'));
29
- await agent.initialize();
26
+ console.log(chalk.gray('1. Instantiating Oracle...'));
27
+ const oracle = new Oracle(mockConfig);
28
+ console.log(chalk.gray('2. Initializing Oracle...'));
29
+ await oracle.initialize();
30
30
  const duration = (Date.now() - start) / 1000;
31
- console.log(chalk.green(`✓ Agent initialized successfully in ${duration}s`));
31
+ console.log(chalk.green(`✓ Oracle initialized successfully in ${duration}s`));
32
32
  if (duration > 5) {
33
33
  console.log(chalk.red(`✗ Startup took too long (> 5s)`));
34
34
  process.exit(1);
35
35
  }
36
36
  console.log(chalk.gray('3. Testing Initialization Check...'));
37
37
  try {
38
- await agent.chat('Hello');
38
+ await oracle.chat('Hello');
39
39
  // This might fail if using real network, but we just want to ensure it tries
40
40
  }
41
41
  catch (e) {
@@ -1,4 +1,4 @@
1
- import { Agent } from '../agent.js';
1
+ import { Oracle } from '../oracle.js';
2
2
  import { DEFAULT_CONFIG } from '../../types/config.js';
3
3
  // Verify environment requirements
4
4
  if (!process.env.OPENAI_API_KEY) {
@@ -15,12 +15,12 @@ const manualConfig = {
15
15
  }
16
16
  };
17
17
  async function run() {
18
- console.log("Initializing Agent...");
19
- const agent = new Agent(manualConfig);
20
- await agent.initialize();
18
+ console.log("Initializing Oracle...");
19
+ const oracle = new Oracle(manualConfig);
20
+ await oracle.initialize();
21
21
  console.log("Sending message: 'Hello, are you there?'");
22
22
  try {
23
- const response = await agent.chat("Hello, are you there?");
23
+ const response = await oracle.chat("Hello, are you there?");
24
24
  console.log("Response received:");
25
25
  console.log("---------------------------------------------------");
26
26
  console.log(response);
@@ -0,0 +1,92 @@
1
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
2
+ import { Oracle } from '../oracle.js';
3
+ import { ProviderFactory } from '../providers/factory.js';
4
+ import { Construtor } from '../tools/factory.js';
5
+ import { DEFAULT_CONFIG } from '../../types/config.js';
6
+ import { AIMessage } from '@langchain/core/messages';
7
+ import * as path from 'path';
8
+ import { tmpdir } from 'os';
9
+ vi.mock('../providers/factory.js');
10
+ vi.mock('../tools/factory.js');
11
+ describe('Oracle', () => {
12
+ let oracle;
13
+ let testDbPath;
14
+ const mockProvider = {
15
+ invoke: vi.fn(),
16
+ };
17
+ beforeEach(async () => {
18
+ vi.resetAllMocks();
19
+ // Use temp DB
20
+ testDbPath = path.join(tmpdir(), `test-oracle-${Date.now()}.db`);
21
+ mockProvider.invoke.mockImplementation(async ({ messages }) => {
22
+ return {
23
+ messages: [...messages, new AIMessage('Hello world')]
24
+ };
25
+ });
26
+ vi.mocked(ProviderFactory.create).mockResolvedValue(mockProvider);
27
+ vi.mocked(Construtor.create).mockResolvedValue([]);
28
+ oracle = new Oracle(DEFAULT_CONFIG, { databasePath: testDbPath });
29
+ });
30
+ afterEach(async () => {
31
+ // Clean up after each test
32
+ if (oracle) {
33
+ try {
34
+ await oracle.clearMemory();
35
+ }
36
+ catch (err) {
37
+ // Ignore cleanup errors
38
+ }
39
+ }
40
+ });
41
+ it('should initialize successfully', async () => {
42
+ await oracle.initialize();
43
+ expect(Construtor.create).toHaveBeenCalled();
44
+ expect(ProviderFactory.create).toHaveBeenCalledWith(DEFAULT_CONFIG.llm, []);
45
+ });
46
+ it('should chat successfully', async () => {
47
+ await oracle.initialize();
48
+ const response = await oracle.chat('Hi');
49
+ expect(response).toBe('Hello world');
50
+ expect(mockProvider.invoke).toHaveBeenCalled();
51
+ });
52
+ it('should throw if not initialized', async () => {
53
+ await expect(oracle.chat('Hi')).rejects.toThrow('initialize() first');
54
+ });
55
+ it('should maintain history', async () => {
56
+ await oracle.initialize();
57
+ // Clear any residual history from previous tests
58
+ await oracle.clearMemory();
59
+ // First turn
60
+ await oracle.chat('Hi');
61
+ const history1 = await oracle.getHistory();
62
+ expect(history1).toHaveLength(2);
63
+ expect(history1[0].content).toBe('Hi'); // User
64
+ expect(history1[1].content).toBe('Hello world'); // AI
65
+ // Second turn
66
+ // Update mock return value for next call
67
+ mockProvider.invoke.mockImplementation(async ({ messages }) => {
68
+ return {
69
+ messages: [...messages, new AIMessage('I am fine')]
70
+ };
71
+ });
72
+ await oracle.chat('How are you?');
73
+ const history2 = await oracle.getHistory();
74
+ expect(history2).toHaveLength(4);
75
+ expect(history2[2].content).toBe('How are you?');
76
+ expect(history2[3].content).toBe('I am fine');
77
+ });
78
+ describe('Configuration Validation', () => {
79
+ it('should throw if llm provider is missing', async () => {
80
+ const invalidConfig = JSON.parse(JSON.stringify(DEFAULT_CONFIG));
81
+ delete invalidConfig.llm.provider; // Invalid
82
+ const badOracle = new Oracle(invalidConfig);
83
+ await expect(badOracle.initialize()).rejects.toThrow('LLM provider not specified');
84
+ });
85
+ it('should propagate ProviderError during initialization', async () => {
86
+ const mockError = new Error("Mock Factory Error");
87
+ vi.mocked(ProviderFactory.create).mockImplementation(() => { throw mockError; });
88
+ // ProviderError constructs message as: "Provider {provider} failed: {originalError.message}"
89
+ await expect(oracle.initialize()).rejects.toThrow('Provider openai failed: Mock Factory Error');
90
+ });
91
+ });
92
+ });
@@ -0,0 +1,53 @@
1
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
2
+ import { Oracle } from '../oracle.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 path from 'path';
7
+ import { tmpdir } from 'os';
8
+ vi.mock('../providers/factory.js');
9
+ describe('Oracle Memory Limit', () => {
10
+ let oracle;
11
+ const mockProvider = {
12
+ invoke: vi.fn(),
13
+ };
14
+ let dbPath;
15
+ beforeEach(async () => {
16
+ vi.resetAllMocks();
17
+ dbPath = path.join(tmpdir(), `test-oracle-limit-${Date.now()}.db`);
18
+ mockProvider.invoke.mockImplementation(async ({ messages }) => {
19
+ return {
20
+ messages: [...messages, new AIMessage('Response')]
21
+ };
22
+ });
23
+ vi.mocked(ProviderFactory.create).mockResolvedValue(mockProvider);
24
+ });
25
+ afterEach(async () => {
26
+ if (oracle) {
27
+ try {
28
+ await oracle.clearMemory();
29
+ }
30
+ catch (err) { }
31
+ }
32
+ });
33
+ it('should respect configured memory limit', async () => {
34
+ const limitedConfig = JSON.parse(JSON.stringify(DEFAULT_CONFIG));
35
+ limitedConfig.memory.limit = 2; // Only last 2 messages (1 exchange)
36
+ oracle = new Oracle(limitedConfig, { databasePath: dbPath });
37
+ await oracle.initialize();
38
+ // Turn 1
39
+ await oracle.chat('Msg 1');
40
+ // Turn 2
41
+ await oracle.chat('Msg 2');
42
+ // Turn 3
43
+ await oracle.chat('Msg 3');
44
+ // DB should have 6 messages (3 User + 3 AI)
45
+ // getHistory() should return only 2 (User Msg 3 + AI Response)
46
+ // Wait, SQLiteChatMessageHistory limit might be total messages? Or pairs?
47
+ // LangChain's limit usually means "last N messages".
48
+ const history = await oracle.getHistory();
49
+ // Assuming limit=2 means 2 messages.
50
+ expect(history.length).toBeLessThanOrEqual(2);
51
+ expect(history[history.length - 1].content).toBe('Response');
52
+ });
53
+ });
@@ -0,0 +1,154 @@
1
+ import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
2
+ import { Oracle } from "../oracle.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 { Construtor } from "../tools/factory.js";
10
+ vi.mock("../providers/factory.js");
11
+ vi.mock("../tools/factory.js");
12
+ describe("Oracle Persistence Integration", () => {
13
+ let oracle;
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(Construtor.create).mockResolvedValue([]);
31
+ oracle = new Oracle(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 oracle.initialize();
47
+ expect(fs.existsSync(testDbPath)).toBe(true);
48
+ });
49
+ });
50
+ describe("Message Persistence", () => {
51
+ it("should persist messages to database", async () => {
52
+ await oracle.initialize();
53
+ // Send a message
54
+ await oracle.chat("Hello, Oracle!");
55
+ // Verify history contains the message
56
+ const history = await oracle.getHistory();
57
+ expect(history).toHaveLength(2); // User message + AI response
58
+ expect(history[0].content).toBe("Hello, Oracle!");
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 oracle.initialize();
64
+ await oracle.chat("Remember this message");
65
+ const firstHistory = await oracle.getHistory();
66
+ expect(firstHistory).toHaveLength(2);
67
+ // Simulate restart: create new oracle instance with SAME database path
68
+ const oracle2 = new Oracle(DEFAULT_CONFIG, { databasePath: testDbPath });
69
+ await oracle2.initialize();
70
+ // Verify history was restored
71
+ const restoredHistory = await oracle2.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 oracle.initialize();
80
+ // First conversation
81
+ await oracle.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 oracle.chat("Second message");
89
+ // Verify all messages are persisted
90
+ const history = await oracle.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 oracle.initialize();
101
+ // Add some messages
102
+ await oracle.chat("Message 1");
103
+ await oracle.chat("Message 2");
104
+ // Verify messages exist
105
+ let history = await oracle.getHistory();
106
+ expect(history.length).toBeGreaterThan(0);
107
+ // Clear memory
108
+ await oracle.clearMemory();
109
+ // Verify messages are cleared
110
+ history = await oracle.getHistory();
111
+ expect(history).toEqual([]);
112
+ });
113
+ it("should start fresh after clearing memory", async () => {
114
+ await oracle.initialize();
115
+ // Add and clear messages
116
+ await oracle.chat("Old message");
117
+ await oracle.clearMemory();
118
+ // Add new message
119
+ mockProvider.invoke.mockImplementation(async ({ messages }) => {
120
+ return {
121
+ messages: [...messages, new AIMessage("New response")]
122
+ };
123
+ });
124
+ await oracle.chat("New message");
125
+ // Verify only new messages exist
126
+ const history = await oracle.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 oracle.initialize();
135
+ // First message
136
+ await oracle.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 oracle.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
+ });
@@ -77,22 +77,22 @@ export class DisplayManager {
77
77
  if (options.source === 'Telegram') {
78
78
  color = chalk.green;
79
79
  }
80
- else if (options.source === 'Agent') {
80
+ else if (options.source === 'Oracle') {
81
81
  color = chalk.hex('#FFA500');
82
82
  }
83
- else if (options.source === 'AgentAudio') {
83
+ else if (options.source === 'Telephonist') {
84
84
  color = chalk.hex('#b902b9');
85
85
  }
86
- else if (options.source === 'ToolsFactory') {
86
+ else if (options.source === 'Construtor') {
87
87
  color = chalk.hex('#806d00');
88
88
  }
89
89
  else if (options.source === 'MCPServer') {
90
90
  color = chalk.hex('#be4b1d');
91
91
  }
92
- else if (options.source === 'ToolCall') {
92
+ else if (options.source === 'ConstructLoad') {
93
93
  color = chalk.hex('#e5ff00');
94
94
  }
95
- else if (options.source === 'Config') {
95
+ else if (options.source === 'Zaion') {
96
96
  color = chalk.hex('#00c3ff');
97
97
  }
98
98
  prefix = color(`[${options.source}] `);
@@ -0,0 +1,28 @@
1
+ import fs from 'fs-extra';
2
+ import path from 'path';
3
+ import { PATHS } from '../config/paths.js';
4
+ import { DisplayManager } from './display.js';
5
+ export async function migrateConfigFile() {
6
+ const display = DisplayManager.getInstance();
7
+ const legacyPath = PATHS.legacyConfig ?? path.join(PATHS.root, 'config.yaml');
8
+ const newPath = PATHS.config;
9
+ const legacyExists = await fs.pathExists(legacyPath);
10
+ const newExists = await fs.pathExists(newPath);
11
+ if (legacyExists && !newExists) {
12
+ try {
13
+ await fs.ensureDir(PATHS.root);
14
+ await fs.move(legacyPath, newPath, { overwrite: false });
15
+ display.log('Migrated config.yaml to zaion.yaml', { source: 'Zaion', level: 'info' });
16
+ }
17
+ catch (err) {
18
+ display.log(`Failed to migrate config.yaml to zaion.yaml: ${err.message}`, { source: 'Zaion', level: 'warning' });
19
+ }
20
+ return;
21
+ }
22
+ if (legacyExists && newExists) {
23
+ display.log('Both config.yaml and zaion.yaml exist. Using zaion.yaml and leaving config.yaml in place.', {
24
+ source: 'Zaion',
25
+ level: 'warning'
26
+ });
27
+ }
28
+ }