morpheus-cli 0.9.12 → 0.9.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +49 -18
- package/dist/channels/discord.js +93 -6
- package/dist/channels/telegram.js +112 -12
- package/dist/cli/commands/restart.js +2 -2
- package/dist/cli/commands/start.js +17 -2
- package/dist/config/manager.js +20 -1
- package/dist/config/paths.js +4 -0
- package/dist/config/schemas.js +15 -0
- package/dist/http/api.js +7 -3
- package/dist/http/routers/agents.js +9 -0
- package/dist/http/routers/danger.js +4 -5
- package/dist/http/routers/link.js +4 -4
- package/dist/runtime/__tests__/telephonist-tts.test.js +84 -0
- package/dist/runtime/adapters/AuditRepositoryAdapter.js +6 -0
- package/dist/runtime/adapters/ChannelNotifierAdapter.js +9 -0
- package/dist/runtime/adapters/LangChainProviderAdapter.js +9 -0
- package/dist/runtime/adapters/SQLiteChatHistoryAdapter.js +15 -0
- package/dist/runtime/adapters/SQLiteTaskEnqueuerAdapter.js +6 -0
- package/dist/runtime/adapters/index.js +5 -0
- package/dist/runtime/audit/repository.js +6 -2
- package/dist/runtime/chronos/repository.js +7 -5
- package/dist/runtime/chronos/worker.js +27 -4
- package/dist/runtime/chronos/worker.test.js +3 -2
- package/dist/runtime/container.js +50 -0
- package/dist/runtime/hot-reload.js +6 -9
- package/dist/runtime/memory/backfill-embeddings.js +2 -3
- package/dist/runtime/memory/sati/repository.js +3 -3
- package/dist/runtime/memory/sqlite.js +40 -14
- package/dist/runtime/memory/trinity-db.js +2 -2
- package/dist/runtime/oracle.js +78 -43
- package/dist/runtime/ports/IChatHistory.js +1 -0
- package/dist/runtime/ports/ILLMProviderFactory.js +1 -0
- package/dist/runtime/ports/INotifier.js +1 -0
- package/dist/runtime/ports/ITaskEnqueuer.js +1 -0
- package/dist/runtime/ports/index.js +1 -0
- package/dist/runtime/providers/factory.js +8 -52
- package/dist/runtime/providers/strategies.js +66 -0
- package/dist/runtime/setup/repository.js +2 -2
- package/dist/runtime/subagents/ISubagent.js +1 -0
- package/dist/runtime/{apoc.js → subagents/apoc.js} +20 -7
- package/dist/runtime/{devkit-instrument.js → subagents/devkit-instrument.js} +1 -1
- package/dist/runtime/subagents/index.js +12 -0
- package/dist/runtime/{link.js → subagents/link/link.js} +24 -10
- package/dist/runtime/{link-repository.js → subagents/link/repository.js} +4 -4
- package/dist/runtime/{link-search.js → subagents/link/search.js} +3 -3
- package/dist/runtime/{link-worker.js → subagents/link/worker.js} +9 -9
- package/dist/runtime/{neo.js → subagents/neo.js} +24 -10
- package/dist/runtime/subagents/registry.js +134 -0
- package/dist/runtime/{trinity.js → subagents/trinity/trinity.js} +23 -9
- package/dist/runtime/{subagent-utils.js → subagents/utils.js} +2 -2
- package/dist/runtime/tasks/repository.js +2 -2
- package/dist/runtime/tasks/worker.js +6 -70
- package/dist/runtime/telephonist.js +160 -0
- package/dist/runtime/tools/chronos-tools.js +1 -0
- package/dist/runtime/tools/delegation-utils.js +5 -7
- package/dist/runtime/tools/morpheus-tools.js +9 -10
- package/dist/runtime/tools/smith-tool.js +5 -7
- package/dist/runtime/webhooks/dispatcher.js +4 -0
- package/dist/runtime/webhooks/repository.js +2 -2
- package/dist/types/config.js +6 -0
- package/dist/ui/assets/AuditDashboard-Cu33zb_7.js +1 -0
- package/dist/ui/assets/Chat-mt1j5V55.js +41 -0
- package/dist/ui/assets/{Chronos-D1yAb4M5.js → Chronos-Bq_h41cw.js} +1 -1
- package/dist/ui/assets/{ConfirmationModal-DxUHZgTy.js → ConfirmationModal-CxLP8iC6.js} +1 -1
- package/dist/ui/assets/{Dashboard-BzxmcHaS.js → Dashboard-D0LAlHtG.js} +1 -1
- package/dist/ui/assets/{DeleteConfirmationModal-CqNXT_YQ.js → DeleteConfirmationModal-kZ_c3sFk.js} +1 -1
- package/dist/ui/assets/{Documents-DLFZdmim.js → Documents-nlQNoUcq.js} +1 -1
- package/dist/ui/assets/{Logs-B1Bpy9dB.js → Logs-C1tlg574.js} +1 -1
- package/dist/ui/assets/{MCPManager-BbUDMh5Q.js → MCPManager-Do7isizG.js} +1 -1
- package/dist/ui/assets/ModelPricing-BeJ7oXBA.js +1 -0
- package/dist/ui/assets/{Notifications-8Cqj-mNp.js → Notifications-Cg5CMlY0.js} +1 -1
- package/dist/ui/assets/{SatiMemories-CdHUe6di.js → SatiMemories-D9l6s8Pc.js} +1 -1
- package/dist/ui/assets/SessionAudit-Da1ySlYg.js +9 -0
- package/dist/ui/assets/Settings-DpXwpEhO.js +49 -0
- package/dist/ui/assets/{Skills-0k7A2T5_.js → Skills-DaqCY8QH.js} +1 -1
- package/dist/ui/assets/Smiths-DA-x4KFT.js +1 -0
- package/dist/ui/assets/Switch-CJTE4ZQm.js +1 -0
- package/dist/ui/assets/Tasks-DU49M9U-.js +1 -0
- package/dist/ui/assets/{TrinityDatabases-CGna6IMX.js → TrinityDatabases-CoKzKTL-.js} +1 -1
- package/dist/ui/assets/{UsageStats-B7EzZlZe.js → UsageStats-cds352Pj.js} +1 -1
- package/dist/ui/assets/{WebhookManager-Bb7KiucS.js → WebhookManager-DdAdHQUk.js} +1 -1
- package/dist/ui/assets/agents-B1z_dlQC.js +1 -0
- package/dist/ui/assets/{audit-CJ2Ms81U.js → audit-BAhaGrKY.js} +1 -1
- package/dist/ui/assets/{chronos-Bm68OSy4.js → chronos-DGD_Md9M.js} +1 -1
- package/dist/ui/assets/config-BwTXe5M2.js +1 -0
- package/dist/ui/assets/{index-BxN2w9sY.js → index-BcX5O7kY.js} +2 -2
- package/dist/ui/assets/index-Cjli-AD7.css +1 -0
- package/dist/ui/assets/{mcp-BE_OVkBe.js → mcp-BlkruPaA.js} +1 -1
- package/dist/ui/assets/{skills-Dt0qU4gH.js → skills-CtCb-52u.js} +1 -1
- package/dist/ui/assets/{stats-Bmdps1LR.js → stats-BiPI2kaw.js} +1 -1
- package/dist/ui/assets/useCurrency-BCdG-pHx.js +1 -0
- package/dist/ui/index.html +2 -2
- package/dist/ui/sw.js +1 -1
- package/package.json +1 -1
- package/dist/ui/assets/AuditDashboard-CM1YN1uk.js +0 -1
- package/dist/ui/assets/Chat-D4y-g6Tw.js +0 -41
- package/dist/ui/assets/ModelPricing-DCl-2_eJ.js +0 -1
- package/dist/ui/assets/SessionAudit-CtVHK_IH.js +0 -9
- package/dist/ui/assets/Settings-Clge45Z0.js +0 -49
- package/dist/ui/assets/Smiths-gjgBMN1F.js +0 -1
- package/dist/ui/assets/Tasks-AQ3MrrMp.js +0 -1
- package/dist/ui/assets/config-C88yQ_CP.js +0 -1
- package/dist/ui/assets/index-C3Ff736M.css +0 -1
- /package/dist/runtime/{ISubagent.js → ports/IAuditEmitter.js} +0 -0
- /package/dist/runtime/{link-chunker.js → subagents/link/chunker.js} +0 -0
- /package/dist/runtime/{trinity-connector.js → subagents/trinity/connector.js} +0 -0
|
@@ -2,11 +2,11 @@ import { Router } from 'express';
|
|
|
2
2
|
import multer from 'multer';
|
|
3
3
|
import path from 'path';
|
|
4
4
|
import fs from 'fs-extra';
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import { LinkWorker } from '../../runtime/link-worker.js';
|
|
5
|
+
import { LinkRepository } from '../../runtime/subagents/link/repository.js';
|
|
6
|
+
import { LinkWorker } from '../../runtime/subagents/link/worker.js';
|
|
8
7
|
import { ConfigManager } from '../../config/manager.js';
|
|
9
|
-
|
|
8
|
+
import { PATHS } from '../../config/paths.js';
|
|
9
|
+
const DOCS_PATH = PATHS.docs;
|
|
10
10
|
// Configure multer for file uploads
|
|
11
11
|
const storage = multer.diskStorage({
|
|
12
12
|
destination: async (req, file, cb) => {
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
2
|
+
import { createTtsTelephonist, TTS_MAX_CHARS } from '../telephonist.js';
|
|
3
|
+
// ─── createTtsTelephonist factory ─────────────────────────────────────────────
|
|
4
|
+
describe('createTtsTelephonist', () => {
|
|
5
|
+
it('returns an instance with synthesize() for openai provider', () => {
|
|
6
|
+
const telephonist = createTtsTelephonist({
|
|
7
|
+
enabled: true,
|
|
8
|
+
provider: 'openai',
|
|
9
|
+
model: 'tts-1',
|
|
10
|
+
voice: 'alloy',
|
|
11
|
+
});
|
|
12
|
+
expect(telephonist).toBeDefined();
|
|
13
|
+
expect(typeof telephonist.synthesize).toBe('function');
|
|
14
|
+
});
|
|
15
|
+
it('returns an instance with synthesize() for google provider', () => {
|
|
16
|
+
const telephonist = createTtsTelephonist({
|
|
17
|
+
enabled: true,
|
|
18
|
+
provider: 'google',
|
|
19
|
+
model: 'gemini-2.5-flash',
|
|
20
|
+
voice: 'Kore',
|
|
21
|
+
});
|
|
22
|
+
expect(telephonist).toBeDefined();
|
|
23
|
+
expect(typeof telephonist.synthesize).toBe('function');
|
|
24
|
+
});
|
|
25
|
+
it('throws for unsupported provider', () => {
|
|
26
|
+
expect(() => createTtsTelephonist({
|
|
27
|
+
enabled: true,
|
|
28
|
+
provider: 'ollama',
|
|
29
|
+
model: 'some-model',
|
|
30
|
+
voice: 'default',
|
|
31
|
+
})).toThrow(/Unsupported TTS provider/);
|
|
32
|
+
});
|
|
33
|
+
it('does not expose transcribe() meaningfully (throws)', async () => {
|
|
34
|
+
const telephonist = createTtsTelephonist({
|
|
35
|
+
enabled: true,
|
|
36
|
+
provider: 'openai',
|
|
37
|
+
model: 'tts-1',
|
|
38
|
+
voice: 'alloy',
|
|
39
|
+
});
|
|
40
|
+
await expect(telephonist.transcribe('', '', '')).rejects.toThrow();
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
// ─── Text truncation ──────────────────────────────────────────────────────────
|
|
44
|
+
describe('TTS text truncation', () => {
|
|
45
|
+
it('TTS_MAX_CHARS constant is 4096', () => {
|
|
46
|
+
expect(TTS_MAX_CHARS).toBe(4096);
|
|
47
|
+
});
|
|
48
|
+
it('short text (under 4096 chars) passes through unchanged in OpenAI synthesize', async () => {
|
|
49
|
+
// We test the truncation logic indirectly by checking that the SDK call
|
|
50
|
+
// receives the correct (non-truncated) text. We mock the OpenAI client.
|
|
51
|
+
const mockCreate = vi.fn().mockResolvedValue({
|
|
52
|
+
arrayBuffer: async () => new ArrayBuffer(8),
|
|
53
|
+
});
|
|
54
|
+
vi.mock('openai', () => ({
|
|
55
|
+
default: vi.fn().mockImplementation(() => ({
|
|
56
|
+
audio: {
|
|
57
|
+
speech: { create: mockCreate },
|
|
58
|
+
},
|
|
59
|
+
})),
|
|
60
|
+
}));
|
|
61
|
+
vi.mock('fs-extra', () => ({
|
|
62
|
+
default: { writeFile: vi.fn().mockResolvedValue(undefined) },
|
|
63
|
+
}));
|
|
64
|
+
const telephonist = createTtsTelephonist({
|
|
65
|
+
enabled: true,
|
|
66
|
+
provider: 'openai',
|
|
67
|
+
model: 'tts-1',
|
|
68
|
+
voice: 'alloy',
|
|
69
|
+
});
|
|
70
|
+
const shortText = 'Hello world';
|
|
71
|
+
await telephonist.synthesize(shortText, 'fake-key').catch(() => { });
|
|
72
|
+
// Verify the mock was called with the short text (not truncated)
|
|
73
|
+
if (mockCreate.mock.calls.length > 0) {
|
|
74
|
+
expect(mockCreate.mock.calls[0][0].input).toBe(shortText);
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
it('long text (over 4096 chars) is truncated to 4096', () => {
|
|
78
|
+
// Test the truncation logic directly by re-creating what truncateForTts does
|
|
79
|
+
const longText = 'a'.repeat(5000);
|
|
80
|
+
const truncated = longText.slice(0, 4096);
|
|
81
|
+
expect(truncated.length).toBe(4096);
|
|
82
|
+
expect(longText.length).toBeGreaterThan(4096);
|
|
83
|
+
});
|
|
84
|
+
});
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { ChannelRegistry } from '../../channels/registry.js';
|
|
2
|
+
export class ChannelNotifierAdapter {
|
|
3
|
+
async sendToUser(channel, userId, text) {
|
|
4
|
+
await ChannelRegistry.sendToUser(channel, userId, text);
|
|
5
|
+
}
|
|
6
|
+
async broadcast(text) {
|
|
7
|
+
await ChannelRegistry.broadcast(text);
|
|
8
|
+
}
|
|
9
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { ProviderFactory } from '../providers/factory.js';
|
|
2
|
+
export class LangChainProviderAdapter {
|
|
3
|
+
async createBare(config, tools = []) {
|
|
4
|
+
return ProviderFactory.createBare(config, tools);
|
|
5
|
+
}
|
|
6
|
+
async create(config, tools = []) {
|
|
7
|
+
return ProviderFactory.createBare(config, tools);
|
|
8
|
+
}
|
|
9
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { SQLiteChatMessageHistory } from '../memory/sqlite.js';
|
|
2
|
+
export class SQLiteChatHistoryAdapter {
|
|
3
|
+
async getMessages(sessionId) {
|
|
4
|
+
const history = new SQLiteChatMessageHistory({ sessionId });
|
|
5
|
+
return history.getMessages();
|
|
6
|
+
}
|
|
7
|
+
async addMessage(sessionId, message) {
|
|
8
|
+
const history = new SQLiteChatMessageHistory({ sessionId });
|
|
9
|
+
await history.addMessage(message);
|
|
10
|
+
}
|
|
11
|
+
async clear(sessionId) {
|
|
12
|
+
const history = new SQLiteChatMessageHistory({ sessionId });
|
|
13
|
+
await history.clear();
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { ChannelNotifierAdapter } from './ChannelNotifierAdapter.js';
|
|
2
|
+
export { SQLiteTaskEnqueuerAdapter } from './SQLiteTaskEnqueuerAdapter.js';
|
|
3
|
+
export { SQLiteChatHistoryAdapter } from './SQLiteChatHistoryAdapter.js';
|
|
4
|
+
export { LangChainProviderAdapter } from './LangChainProviderAdapter.js';
|
|
5
|
+
export { AuditRepositoryAdapter } from './AuditRepositoryAdapter.js';
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import Database from 'better-sqlite3';
|
|
2
2
|
import fs from 'fs-extra';
|
|
3
3
|
import path from 'path';
|
|
4
|
-
import { homedir } from 'os';
|
|
5
4
|
import { randomUUID } from 'crypto';
|
|
6
5
|
import { DisplayManager } from '../display.js';
|
|
6
|
+
import { PATHS } from '../../config/paths.js';
|
|
7
7
|
export class AuditRepository {
|
|
8
8
|
static instance = null;
|
|
9
9
|
db;
|
|
10
10
|
constructor() {
|
|
11
|
-
const dbPath =
|
|
11
|
+
const dbPath = PATHS.shortMemoryDb;
|
|
12
12
|
fs.ensureDirSync(path.dirname(dbPath));
|
|
13
13
|
this.db = new Database(dbPath, { timeout: 5000 });
|
|
14
14
|
this.db.pragma('journal_mode = WAL');
|
|
@@ -59,6 +59,10 @@ export class AuditRepository {
|
|
|
59
59
|
DisplayManager.getInstance().log(`AuditRepository.insert failed: ${err?.message ?? String(err)}`, { source: 'Audit', level: 'error' });
|
|
60
60
|
}
|
|
61
61
|
}
|
|
62
|
+
countBySession(sessionId) {
|
|
63
|
+
const row = this.db.prepare(`SELECT COUNT(*) as n FROM audit_events WHERE session_id = ?`).get(sessionId);
|
|
64
|
+
return row?.n ?? 0;
|
|
65
|
+
}
|
|
62
66
|
getBySession(sessionId, opts) {
|
|
63
67
|
const limit = opts?.limit ?? 500;
|
|
64
68
|
const offset = opts?.offset ?? 0;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import Database from 'better-sqlite3';
|
|
2
2
|
import fs from 'fs-extra';
|
|
3
3
|
import path from 'path';
|
|
4
|
-
import { homedir } from 'os';
|
|
5
4
|
import { randomUUID } from 'crypto';
|
|
6
5
|
import { ConfigManager } from '../../config/manager.js';
|
|
6
|
+
import { PATHS } from '../../config/paths.js';
|
|
7
7
|
export class ChronosError extends Error {
|
|
8
8
|
constructor(message) {
|
|
9
9
|
super(message);
|
|
@@ -14,7 +14,7 @@ export class ChronosRepository {
|
|
|
14
14
|
static instance = null;
|
|
15
15
|
db;
|
|
16
16
|
constructor() {
|
|
17
|
-
const dbPath =
|
|
17
|
+
const dbPath = PATHS.shortMemoryDb;
|
|
18
18
|
fs.ensureDirSync(path.dirname(dbPath));
|
|
19
19
|
this.db = new Database(dbPath, { timeout: 5000 });
|
|
20
20
|
this.db.pragma('journal_mode = WAL');
|
|
@@ -62,6 +62,7 @@ export class ChronosRepository {
|
|
|
62
62
|
addJobCol(`ALTER TABLE chronos_jobs ADD COLUMN updated_at INTEGER NOT NULL DEFAULT 0`, 'updated_at');
|
|
63
63
|
addJobCol(`ALTER TABLE chronos_jobs ADD COLUMN created_by TEXT NOT NULL DEFAULT 'api'`, 'created_by');
|
|
64
64
|
addJobCol(`ALTER TABLE chronos_jobs ADD COLUMN notify_channels TEXT NOT NULL DEFAULT '[]'`, 'notify_channels');
|
|
65
|
+
addJobCol(`ALTER TABLE chronos_jobs ADD COLUMN origin_session_id TEXT`, 'origin_session_id');
|
|
65
66
|
const execInfo = this.db.pragma('table_info(chronos_executions)');
|
|
66
67
|
const execCols = new Set(execInfo.map((c) => c.name));
|
|
67
68
|
const addExecCol = (sql, col) => {
|
|
@@ -107,6 +108,7 @@ export class ChronosRepository {
|
|
|
107
108
|
updated_at: row.updated_at,
|
|
108
109
|
created_by: row.created_by,
|
|
109
110
|
notify_channels,
|
|
111
|
+
origin_session_id: row.origin_session_id ?? null,
|
|
110
112
|
};
|
|
111
113
|
}
|
|
112
114
|
deserializeExecution(row) {
|
|
@@ -132,9 +134,9 @@ export class ChronosRepository {
|
|
|
132
134
|
this.db.prepare(`
|
|
133
135
|
INSERT INTO chronos_jobs (
|
|
134
136
|
id, prompt, schedule_type, schedule_expression, cron_normalized,
|
|
135
|
-
timezone, next_run_at, last_run_at, enabled, created_at, updated_at, created_by, notify_channels
|
|
136
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, NULL, 1, ?, ?, ?, ?)
|
|
137
|
-
`).run(id, input.prompt, input.schedule_type, input.schedule_expression, input.cron_normalized ?? null, input.timezone, input.next_run_at, now, now, input.created_by, JSON.stringify(input.notify_channels ?? []));
|
|
137
|
+
timezone, next_run_at, last_run_at, enabled, created_at, updated_at, created_by, notify_channels, origin_session_id
|
|
138
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, NULL, 1, ?, ?, ?, ?, ?)
|
|
139
|
+
`).run(id, input.prompt, input.schedule_type, input.schedule_expression, input.cron_normalized ?? null, input.timezone, input.next_run_at, now, now, input.created_by, JSON.stringify(input.notify_channels ?? []), input.origin_session_id ?? null);
|
|
138
140
|
return this.getJob(id);
|
|
139
141
|
}
|
|
140
142
|
getJob(id) {
|
|
@@ -4,6 +4,7 @@ import { DisplayManager } from '../display.js';
|
|
|
4
4
|
import { parseNextRun } from './parser.js';
|
|
5
5
|
import { ChannelRegistry } from '../../channels/registry.js';
|
|
6
6
|
import { AuditRepository } from '../audit/repository.js';
|
|
7
|
+
import { SQLiteChatMessageHistory } from '../memory/sqlite.js';
|
|
7
8
|
export class ChronosWorker {
|
|
8
9
|
repo;
|
|
9
10
|
oracle;
|
|
@@ -74,9 +75,27 @@ export class ChronosWorker {
|
|
|
74
75
|
const display = DisplayManager.getInstance();
|
|
75
76
|
const execId = randomUUID();
|
|
76
77
|
display.log(`Job ${job.id} triggered — "${job.prompt.slice(0, 60)}"`, { source: 'Chronos' });
|
|
77
|
-
//
|
|
78
|
-
//
|
|
79
|
-
const
|
|
78
|
+
// Resolve session: prefer the session where the job was originally created,
|
|
79
|
+
// fall back to Oracle's current session, then to most recent active session.
|
|
80
|
+
const tmpHistory = new SQLiteChatMessageHistory({ sessionId: 'tmp' });
|
|
81
|
+
let activeSessionId;
|
|
82
|
+
try {
|
|
83
|
+
if (job.origin_session_id && tmpHistory.isSessionUsable(job.origin_session_id)) {
|
|
84
|
+
activeSessionId = job.origin_session_id;
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
const oracleSession = this.oracle.getCurrentSessionId();
|
|
88
|
+
if (oracleSession && tmpHistory.isSessionUsable(oracleSession)) {
|
|
89
|
+
activeSessionId = oracleSession;
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
activeSessionId = tmpHistory.getMostRecentSession() ?? 'default';
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
finally {
|
|
97
|
+
tmpHistory.close();
|
|
98
|
+
}
|
|
80
99
|
this.repo.insertExecution({
|
|
81
100
|
id: execId,
|
|
82
101
|
job_id: job.id,
|
|
@@ -96,7 +115,7 @@ export class ChronosWorker {
|
|
|
96
115
|
const taskOriginChannel = job.notify_channels.length === 1
|
|
97
116
|
? job.notify_channels[0]
|
|
98
117
|
: 'chronos';
|
|
99
|
-
const taskContext = { origin_channel: taskOriginChannel, session_id: activeSessionId };
|
|
118
|
+
const taskContext = { origin_channel: taskOriginChannel, session_id: activeSessionId, source: 'chronos' };
|
|
100
119
|
// Hard-block Chronos management tools during execution.
|
|
101
120
|
ChronosWorker.isExecuting = true;
|
|
102
121
|
const chronosStartMs = Date.now();
|
|
@@ -154,6 +173,10 @@ export class ChronosWorker {
|
|
|
154
173
|
}
|
|
155
174
|
else {
|
|
156
175
|
for (const ch of job.notify_channels) {
|
|
176
|
+
// 'ui' has no adapter — it works by polling session messages from the DB.
|
|
177
|
+
// The response is already persisted via oracle.chat(), so skip silently.
|
|
178
|
+
if (ch === 'ui')
|
|
179
|
+
continue;
|
|
157
180
|
const adapter = ChannelRegistry.get(ch);
|
|
158
181
|
if (adapter) {
|
|
159
182
|
await adapter.sendMessage(text).catch((err) => {
|
|
@@ -30,6 +30,7 @@ function makeJob(overrides = {}) {
|
|
|
30
30
|
updated_at: Date.now() - 5000,
|
|
31
31
|
created_by: 'ui',
|
|
32
32
|
notify_channels: [],
|
|
33
|
+
origin_session_id: null,
|
|
33
34
|
...overrides,
|
|
34
35
|
};
|
|
35
36
|
}
|
|
@@ -91,7 +92,7 @@ describe('ChronosWorker.tick()', () => {
|
|
|
91
92
|
await worker.tick();
|
|
92
93
|
// Wait for fire-and-forget
|
|
93
94
|
await new Promise((r) => setTimeout(r, 50));
|
|
94
|
-
expect(oracle.chat).toHaveBeenCalledWith(expect.stringContaining(job.prompt));
|
|
95
|
+
expect(oracle.chat).toHaveBeenCalledWith(expect.stringContaining(job.prompt), undefined, false, expect.objectContaining({ source: 'chronos' }));
|
|
95
96
|
expect(repo.disableJob).toHaveBeenCalledWith(job.id);
|
|
96
97
|
expect(repo.updateJob).not.toHaveBeenCalled();
|
|
97
98
|
});
|
|
@@ -102,7 +103,7 @@ describe('ChronosWorker.tick()', () => {
|
|
|
102
103
|
const worker = new ChronosWorker(repo, oracle);
|
|
103
104
|
await worker.tick();
|
|
104
105
|
await new Promise((r) => setTimeout(r, 50));
|
|
105
|
-
expect(oracle.chat).toHaveBeenCalledWith(expect.stringContaining(job.prompt));
|
|
106
|
+
expect(oracle.chat).toHaveBeenCalledWith(expect.stringContaining(job.prompt), undefined, false, expect.objectContaining({ source: 'chronos' }));
|
|
106
107
|
expect(repo.disableJob).not.toHaveBeenCalled();
|
|
107
108
|
expect(repo.updateJob).toHaveBeenCalledWith(job.id, expect.objectContaining({ next_run_at: expect.any(Number) }));
|
|
108
109
|
});
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ServiceContainer — Composition Root
|
|
3
|
+
*
|
|
4
|
+
* Simple typed registry for application services (ports).
|
|
5
|
+
* Configured once at startup in start.ts. No DI framework magic —
|
|
6
|
+
* just a Map with type-safe get/register.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* // Registration (start.ts):
|
|
10
|
+
* ServiceContainer.register('notifier', new ChannelNotifierAdapter());
|
|
11
|
+
*
|
|
12
|
+
* // Consumption:
|
|
13
|
+
* const notifier = ServiceContainer.get<INotifier>('notifier');
|
|
14
|
+
*/
|
|
15
|
+
export class ServiceContainer {
|
|
16
|
+
static services = new Map();
|
|
17
|
+
/** Register a service under a given key. Overwrites if already registered. */
|
|
18
|
+
static register(key, service) {
|
|
19
|
+
ServiceContainer.services.set(key, service);
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Retrieve a registered service.
|
|
23
|
+
* @throws if the key is not registered.
|
|
24
|
+
*/
|
|
25
|
+
static get(key) {
|
|
26
|
+
const service = ServiceContainer.services.get(key);
|
|
27
|
+
if (service === undefined) {
|
|
28
|
+
throw new Error(`ServiceContainer: "${key}" is not registered. Did you call register() in start.ts?`);
|
|
29
|
+
}
|
|
30
|
+
return service;
|
|
31
|
+
}
|
|
32
|
+
/** Returns true if a service is registered under the given key. */
|
|
33
|
+
static has(key) {
|
|
34
|
+
return ServiceContainer.services.has(key);
|
|
35
|
+
}
|
|
36
|
+
/** Remove all registered services (useful in tests). */
|
|
37
|
+
static reset() {
|
|
38
|
+
ServiceContainer.services.clear();
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Well-known service keys — use these constants to avoid typo bugs.
|
|
43
|
+
*/
|
|
44
|
+
export const SERVICE_KEYS = {
|
|
45
|
+
notifier: 'notifier',
|
|
46
|
+
taskEnqueuer: 'taskEnqueuer',
|
|
47
|
+
chatHistory: 'chatHistory',
|
|
48
|
+
providerFactory: 'providerFactory',
|
|
49
|
+
auditEmitter: 'auditEmitter',
|
|
50
|
+
};
|
|
@@ -7,9 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import { ConfigManager } from '../config/manager.js';
|
|
9
9
|
import { DisplayManager } from './display.js';
|
|
10
|
-
import {
|
|
11
|
-
import { Neo } from './neo.js';
|
|
12
|
-
import { Trinity } from './trinity.js';
|
|
10
|
+
import { SubagentRegistry } from './subagents/registry.js';
|
|
13
11
|
let currentOracle = null;
|
|
14
12
|
/**
|
|
15
13
|
* Register the current Oracle instance for hot-reload.
|
|
@@ -44,12 +42,11 @@ export async function hotReloadConfig() {
|
|
|
44
42
|
reinitialized.push('Oracle');
|
|
45
43
|
display.log('Oracle reinitialized with new config', { source: 'HotReload', level: 'info' });
|
|
46
44
|
}
|
|
47
|
-
// 3.
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
display.log('Subagent singletons reset (will reinitialize on next use)', { source: 'HotReload', level: 'info' });
|
|
45
|
+
// 3. Reload all registered subagents via SubagentRegistry
|
|
46
|
+
await SubagentRegistry.reloadAll();
|
|
47
|
+
const agentNames = SubagentRegistry.getAll().map(r => r.label);
|
|
48
|
+
reinitialized.push(...agentNames);
|
|
49
|
+
display.log(`Subagents reloaded: ${agentNames.join(', ')}`, { source: 'HotReload', level: 'info' });
|
|
53
50
|
return {
|
|
54
51
|
success: true,
|
|
55
52
|
reinitialized,
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import Database from 'better-sqlite3';
|
|
2
2
|
import { EmbeddingService } from './embedding.service.js';
|
|
3
|
-
import path from 'path';
|
|
4
|
-
import { homedir } from 'os';
|
|
5
3
|
import loadVecExtension from './sqlite-vec.js';
|
|
6
|
-
|
|
4
|
+
import { PATHS } from '../../config/paths.js';
|
|
5
|
+
const db = new Database(PATHS.satiDb);
|
|
7
6
|
db.pragma('journal_mode = WAL');
|
|
8
7
|
// 🔥 ISSO AQUI É O QUE ESTÁ FALTANDO
|
|
9
8
|
loadVecExtension(db);
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import Database from 'better-sqlite3';
|
|
2
|
-
import path from 'path';
|
|
3
|
-
import { homedir } from 'os';
|
|
4
2
|
import fs from 'fs-extra';
|
|
3
|
+
import path from 'path';
|
|
5
4
|
import { randomUUID } from 'crypto';
|
|
6
5
|
import loadVecExtension from '../sqlite-vec.js';
|
|
7
6
|
import { DisplayManager } from '../../display.js';
|
|
8
7
|
import { ConfigManager } from '../../../config/manager.js';
|
|
8
|
+
import { PATHS } from '../../../config/paths.js';
|
|
9
9
|
const EMBEDDING_DIM = 384;
|
|
10
10
|
export class SatiRepository {
|
|
11
11
|
db = null;
|
|
@@ -14,7 +14,7 @@ export class SatiRepository {
|
|
|
14
14
|
display = DisplayManager.getInstance();
|
|
15
15
|
constructor(dbPath) {
|
|
16
16
|
this.dbPath =
|
|
17
|
-
dbPath ||
|
|
17
|
+
dbPath || PATHS.satiDb;
|
|
18
18
|
}
|
|
19
19
|
static getInstance(dbPath) {
|
|
20
20
|
if (!SatiRepository.instance) {
|
|
@@ -3,7 +3,7 @@ import { HumanMessage, AIMessage, SystemMessage, ToolMessage } from "@langchain/
|
|
|
3
3
|
import Database from "better-sqlite3";
|
|
4
4
|
import fs from "fs-extra";
|
|
5
5
|
import * as path from "path";
|
|
6
|
-
import {
|
|
6
|
+
import { PATHS } from "../../config/paths.js";
|
|
7
7
|
import { randomUUID } from 'crypto';
|
|
8
8
|
import { DisplayManager } from "../display.js";
|
|
9
9
|
export class SQLiteChatMessageHistory extends BaseListChatMessageHistory {
|
|
@@ -22,7 +22,7 @@ export class SQLiteChatMessageHistory extends BaseListChatMessageHistory {
|
|
|
22
22
|
this.sessionId = fields.sessionId && fields.sessionId !== '' ? fields.sessionId : '';
|
|
23
23
|
this.limit = fields.limit ? fields.limit : 20;
|
|
24
24
|
// Default path: ~/.morpheus/memory/short-memory.db
|
|
25
|
-
const dbPath = fields.databasePath ||
|
|
25
|
+
const dbPath = fields.databasePath || PATHS.shortMemoryDb;
|
|
26
26
|
// Ensure the directory exists
|
|
27
27
|
this.ensureDirectory(dbPath);
|
|
28
28
|
// Initialize database with retry logic for locked databases
|
|
@@ -847,6 +847,10 @@ export class SQLiteChatMessageHistory extends BaseListChatMessageHistory {
|
|
|
847
847
|
embedding_status = 'pending'
|
|
848
848
|
WHERE id = ?
|
|
849
849
|
`).run(now, now, sessionId);
|
|
850
|
+
// Remove channel/user bindings so channels don't route to an archived session
|
|
851
|
+
this.db.prepare(`
|
|
852
|
+
DELETE FROM user_channel_sessions WHERE session_id = ?
|
|
853
|
+
`).run(sessionId);
|
|
850
854
|
// Exportar mensagens → texto
|
|
851
855
|
const messages = this.db.prepare(`
|
|
852
856
|
SELECT type, content
|
|
@@ -870,7 +874,7 @@ export class SQLiteChatMessageHistory extends BaseListChatMessageHistory {
|
|
|
870
874
|
const sessionText = tx(); // Executar a transação
|
|
871
875
|
// Criar chunks no banco Sati — conexão aberta localmente e fechada ao fim
|
|
872
876
|
if (sessionText) {
|
|
873
|
-
const dbSatiPath =
|
|
877
|
+
const dbSatiPath = PATHS.satiDb;
|
|
874
878
|
this.ensureDirectory(dbSatiPath);
|
|
875
879
|
const dbSati = new Database(dbSatiPath, { timeout: 5000 });
|
|
876
880
|
dbSati.pragma('journal_mode = WAL');
|
|
@@ -936,6 +940,10 @@ export class SQLiteChatMessageHistory extends BaseListChatMessageHistory {
|
|
|
936
940
|
deleted_at = ?
|
|
937
941
|
WHERE id = ?
|
|
938
942
|
`).run(now, sessionId);
|
|
943
|
+
// Remove channel/user bindings so channels don't route to a deleted session
|
|
944
|
+
this.db.prepare(`
|
|
945
|
+
DELETE FROM user_channel_sessions WHERE session_id = ?
|
|
946
|
+
`).run(sessionId);
|
|
939
947
|
});
|
|
940
948
|
tx(); // Executar a transação
|
|
941
949
|
}
|
|
@@ -977,6 +985,14 @@ export class SQLiteChatMessageHistory extends BaseListChatMessageHistory {
|
|
|
977
985
|
this.db.prepare("INSERT INTO sessions (id, started_at, status) VALUES (?, ?, 'active')").run(sessionId, Date.now());
|
|
978
986
|
}
|
|
979
987
|
}
|
|
988
|
+
/**
|
|
989
|
+
* Checks whether a session exists and is usable (active or paused).
|
|
990
|
+
* Returns false for deleted, archived, or non-existent sessions.
|
|
991
|
+
*/
|
|
992
|
+
isSessionUsable(sessionId) {
|
|
993
|
+
const row = this.db.prepare("SELECT status FROM sessions WHERE id = ?").get(sessionId);
|
|
994
|
+
return !!row && (row.status === 'active' || row.status === 'paused');
|
|
995
|
+
}
|
|
980
996
|
/**
|
|
981
997
|
* Validates that the target session exists and is usable (not archived/deleted).
|
|
982
998
|
* No longer swaps active↔paused — sessions are independently usable from any channel.
|
|
@@ -1015,10 +1031,11 @@ export class SQLiteChatMessageHistory extends BaseListChatMessageHistory {
|
|
|
1015
1031
|
*/
|
|
1016
1032
|
async listSessions() {
|
|
1017
1033
|
const sessions = this.db.prepare(`
|
|
1018
|
-
SELECT id, title, status, started_at
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1034
|
+
SELECT s.id, s.title, s.status, s.started_at,
|
|
1035
|
+
(SELECT MAX(m.created_at) FROM messages m WHERE m.session_id = s.id) AS last_message_at
|
|
1036
|
+
FROM sessions s
|
|
1037
|
+
WHERE s.status IN ('active', 'paused')
|
|
1038
|
+
ORDER BY COALESCE(last_message_at, s.started_at) DESC
|
|
1022
1039
|
`).all();
|
|
1023
1040
|
return sessions;
|
|
1024
1041
|
}
|
|
@@ -1028,8 +1045,11 @@ export class SQLiteChatMessageHistory extends BaseListChatMessageHistory {
|
|
|
1028
1045
|
*/
|
|
1029
1046
|
async getUserChannelSession(channel, userId) {
|
|
1030
1047
|
const result = this.db.prepare(`
|
|
1031
|
-
SELECT session_id
|
|
1032
|
-
|
|
1048
|
+
SELECT ucs.session_id
|
|
1049
|
+
FROM user_channel_sessions ucs
|
|
1050
|
+
JOIN sessions s ON s.id = ucs.session_id
|
|
1051
|
+
WHERE ucs.channel = ? AND ucs.user_id = ?
|
|
1052
|
+
AND s.status IN ('active', 'paused')
|
|
1033
1053
|
`).get(channel, userId);
|
|
1034
1054
|
return result ? result.session_id : null;
|
|
1035
1055
|
}
|
|
@@ -1052,8 +1072,11 @@ export class SQLiteChatMessageHistory extends BaseListChatMessageHistory {
|
|
|
1052
1072
|
*/
|
|
1053
1073
|
async listUserChannelSessions(channel) {
|
|
1054
1074
|
const rows = this.db.prepare(`
|
|
1055
|
-
SELECT user_id, session_id
|
|
1056
|
-
|
|
1075
|
+
SELECT ucs.user_id, ucs.session_id
|
|
1076
|
+
FROM user_channel_sessions ucs
|
|
1077
|
+
JOIN sessions s ON s.id = ucs.session_id
|
|
1078
|
+
WHERE ucs.channel = ?
|
|
1079
|
+
AND s.status IN ('active', 'paused')
|
|
1057
1080
|
`).all(channel);
|
|
1058
1081
|
return rows.map(row => ({ userId: row.user_id, sessionId: row.session_id }));
|
|
1059
1082
|
}
|
|
@@ -1075,9 +1098,12 @@ export class SQLiteChatMessageHistory extends BaseListChatMessageHistory {
|
|
|
1075
1098
|
return null;
|
|
1076
1099
|
const placeholders = channels.map(() => '?').join(', ');
|
|
1077
1100
|
const result = this.db.prepare(`
|
|
1078
|
-
SELECT session_id
|
|
1079
|
-
|
|
1080
|
-
|
|
1101
|
+
SELECT ucs.session_id
|
|
1102
|
+
FROM user_channel_sessions ucs
|
|
1103
|
+
JOIN sessions s ON s.id = ucs.session_id
|
|
1104
|
+
WHERE ucs.channel IN (${placeholders})
|
|
1105
|
+
AND s.status IN ('active', 'paused')
|
|
1106
|
+
ORDER BY ucs.updated_at DESC LIMIT 1
|
|
1081
1107
|
`).get(...channels);
|
|
1082
1108
|
return result ? result.session_id : null;
|
|
1083
1109
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import Database from 'better-sqlite3';
|
|
2
2
|
import fs from 'fs-extra';
|
|
3
3
|
import path from 'path';
|
|
4
|
-
import { homedir } from 'os';
|
|
5
4
|
import { encrypt, decrypt, canEncrypt } from '../trinity-crypto.js';
|
|
5
|
+
import { PATHS } from '../../config/paths.js';
|
|
6
6
|
function safeDecrypt(value) {
|
|
7
7
|
if (!value)
|
|
8
8
|
return null;
|
|
@@ -39,7 +39,7 @@ export class DatabaseRegistry {
|
|
|
39
39
|
static instance = null;
|
|
40
40
|
db;
|
|
41
41
|
constructor() {
|
|
42
|
-
const dbPath =
|
|
42
|
+
const dbPath = PATHS.trinityDb;
|
|
43
43
|
fs.ensureDirSync(path.dirname(dbPath));
|
|
44
44
|
this.db = new Database(dbPath, { timeout: 5000 });
|
|
45
45
|
this.db.pragma('journal_mode = WAL');
|