morpheus-cli 0.5.0 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +26 -7
- package/dist/channels/telegram.js +173 -0
- package/dist/cli/commands/restart.js +15 -14
- package/dist/cli/commands/start.js +13 -12
- package/dist/config/manager.js +31 -0
- package/dist/config/mcp-manager.js +19 -1
- package/dist/config/schemas.js +2 -0
- package/dist/http/api.js +222 -0
- package/dist/runtime/memory/session-embedding-worker.js +3 -3
- package/dist/runtime/memory/trinity-db.js +203 -0
- package/dist/runtime/neo.js +16 -26
- package/dist/runtime/oracle.js +16 -8
- package/dist/runtime/session-embedding-scheduler.js +1 -1
- package/dist/runtime/tasks/dispatcher.js +21 -0
- package/dist/runtime/tasks/worker.js +4 -1
- package/dist/runtime/tools/__tests__/tools.test.js +1 -3
- package/dist/runtime/tools/factory.js +1 -1
- package/dist/runtime/tools/index.js +1 -3
- package/dist/runtime/tools/morpheus-tools.js +742 -0
- package/dist/runtime/tools/neo-tool.js +19 -9
- package/dist/runtime/tools/trinity-tool.js +98 -0
- package/dist/runtime/trinity-connector.js +611 -0
- package/dist/runtime/trinity-crypto.js +52 -0
- package/dist/runtime/trinity.js +246 -0
- package/dist/ui/assets/index-DP2V4kRd.js +112 -0
- package/dist/ui/assets/index-mglRG5Zw.css +1 -0
- package/dist/ui/index.html +2 -2
- package/dist/ui/sw.js +1 -1
- package/package.json +6 -1
- package/dist/runtime/tools/analytics-tools.js +0 -139
- package/dist/runtime/tools/config-tools.js +0 -64
- package/dist/runtime/tools/diagnostic-tools.js +0 -153
- package/dist/runtime/tools/task-query-tool.js +0 -76
- package/dist/ui/assets/index-20lLB1sM.js +0 -112
- package/dist/ui/assets/index-BJ56bRfs.css +0 -1
package/dist/http/api.js
CHANGED
|
@@ -12,6 +12,9 @@ import { MCPManager } from '../config/mcp-manager.js';
|
|
|
12
12
|
import { MCPServerConfigSchema } from '../config/schemas.js';
|
|
13
13
|
import { Construtor } from '../runtime/tools/factory.js';
|
|
14
14
|
import { TaskRepository } from '../runtime/tasks/repository.js';
|
|
15
|
+
import { DatabaseRegistry } from '../runtime/memory/trinity-db.js';
|
|
16
|
+
import { testConnection, introspectSchema } from '../runtime/trinity-connector.js';
|
|
17
|
+
import { Trinity } from '../runtime/trinity.js';
|
|
15
18
|
async function readLastLines(filePath, n) {
|
|
16
19
|
try {
|
|
17
20
|
const content = await fs.readFile(filePath, 'utf8');
|
|
@@ -745,6 +748,225 @@ export function createApiRouter(oracle) {
|
|
|
745
748
|
// Redirect to POST logic or just reuse
|
|
746
749
|
res.status(307).redirect(307, '/api/config');
|
|
747
750
|
});
|
|
751
|
+
// ─── Trinity Config ────────────────────────────────────────────────────────
|
|
752
|
+
router.get('/config/trinity', (req, res) => {
|
|
753
|
+
try {
|
|
754
|
+
const trinityConfig = configManager.getTrinityConfig();
|
|
755
|
+
res.json(trinityConfig);
|
|
756
|
+
}
|
|
757
|
+
catch (error) {
|
|
758
|
+
res.status(500).json({ error: error.message });
|
|
759
|
+
}
|
|
760
|
+
});
|
|
761
|
+
router.post('/config/trinity', async (req, res) => {
|
|
762
|
+
try {
|
|
763
|
+
const config = configManager.get();
|
|
764
|
+
await configManager.save({ ...config, trinity: req.body });
|
|
765
|
+
const display = DisplayManager.getInstance();
|
|
766
|
+
display.log('Trinity configuration updated via UI', { source: 'Zaion', level: 'info' });
|
|
767
|
+
res.json({ success: true });
|
|
768
|
+
}
|
|
769
|
+
catch (error) {
|
|
770
|
+
if (error.name === 'ZodError') {
|
|
771
|
+
res.status(400).json({ error: 'Validation failed', details: error.errors });
|
|
772
|
+
}
|
|
773
|
+
else {
|
|
774
|
+
res.status(500).json({ error: error.message });
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
});
|
|
778
|
+
router.delete('/config/trinity', async (req, res) => {
|
|
779
|
+
try {
|
|
780
|
+
const config = configManager.get();
|
|
781
|
+
const { trinity: _trinity, ...restConfig } = config;
|
|
782
|
+
await configManager.save(restConfig);
|
|
783
|
+
const display = DisplayManager.getInstance();
|
|
784
|
+
display.log('Trinity configuration removed via UI (falling back to Oracle config)', {
|
|
785
|
+
source: 'Zaion',
|
|
786
|
+
level: 'info',
|
|
787
|
+
});
|
|
788
|
+
res.json({ success: true });
|
|
789
|
+
}
|
|
790
|
+
catch (error) {
|
|
791
|
+
res.status(500).json({ error: error.message });
|
|
792
|
+
}
|
|
793
|
+
});
|
|
794
|
+
// ─── Trinity Databases CRUD ─────────────────────────────────────────────────
|
|
795
|
+
const DatabaseCreateSchema = z.object({
|
|
796
|
+
name: z.string().min(1).max(100),
|
|
797
|
+
type: z.enum(['postgresql', 'mysql', 'sqlite', 'mongodb']),
|
|
798
|
+
host: z.string().optional().nullable(),
|
|
799
|
+
port: z.number().int().positive().optional().nullable(),
|
|
800
|
+
database_name: z.string().optional().nullable(),
|
|
801
|
+
username: z.string().optional().nullable(),
|
|
802
|
+
password: z.string().optional().nullable(),
|
|
803
|
+
connection_string: z.string().optional().nullable(),
|
|
804
|
+
allow_read: z.boolean().optional(),
|
|
805
|
+
allow_insert: z.boolean().optional(),
|
|
806
|
+
allow_update: z.boolean().optional(),
|
|
807
|
+
allow_delete: z.boolean().optional(),
|
|
808
|
+
allow_ddl: z.boolean().optional(),
|
|
809
|
+
});
|
|
810
|
+
router.get('/trinity/databases', (req, res) => {
|
|
811
|
+
try {
|
|
812
|
+
const registry = DatabaseRegistry.getInstance();
|
|
813
|
+
const databases = registry.listDatabases().map((db) => ({
|
|
814
|
+
...db,
|
|
815
|
+
password: db.password ? '***' : null,
|
|
816
|
+
connection_string: db.connection_string ? '***' : null,
|
|
817
|
+
}));
|
|
818
|
+
res.json(databases);
|
|
819
|
+
}
|
|
820
|
+
catch (error) {
|
|
821
|
+
res.status(500).json({ error: error.message });
|
|
822
|
+
}
|
|
823
|
+
});
|
|
824
|
+
router.get('/trinity/databases/:id', (req, res) => {
|
|
825
|
+
try {
|
|
826
|
+
const id = parseInt(req.params.id);
|
|
827
|
+
if (isNaN(id))
|
|
828
|
+
return res.status(400).json({ error: 'Invalid id' });
|
|
829
|
+
const registry = DatabaseRegistry.getInstance();
|
|
830
|
+
const db = registry.getDatabase(id);
|
|
831
|
+
if (!db)
|
|
832
|
+
return res.status(404).json({ error: 'Database not found' });
|
|
833
|
+
res.json({
|
|
834
|
+
...db,
|
|
835
|
+
password: db.password ? '***' : null,
|
|
836
|
+
connection_string: db.connection_string ? '***' : null,
|
|
837
|
+
});
|
|
838
|
+
}
|
|
839
|
+
catch (error) {
|
|
840
|
+
res.status(500).json({ error: error.message });
|
|
841
|
+
}
|
|
842
|
+
});
|
|
843
|
+
router.post('/trinity/databases', async (req, res) => {
|
|
844
|
+
const parsed = DatabaseCreateSchema.safeParse(req.body);
|
|
845
|
+
if (!parsed.success) {
|
|
846
|
+
return res.status(400).json({ error: 'Invalid input', details: parsed.error.issues });
|
|
847
|
+
}
|
|
848
|
+
try {
|
|
849
|
+
const registry = DatabaseRegistry.getInstance();
|
|
850
|
+
const db = registry.createDatabase(parsed.data);
|
|
851
|
+
// Test connection
|
|
852
|
+
let connectionOk = false;
|
|
853
|
+
try {
|
|
854
|
+
connectionOk = await testConnection(db);
|
|
855
|
+
}
|
|
856
|
+
catch { /* ignore */ }
|
|
857
|
+
// Introspect schema if connection successful
|
|
858
|
+
if (connectionOk) {
|
|
859
|
+
try {
|
|
860
|
+
const schema = await introspectSchema(db);
|
|
861
|
+
registry.updateSchema(db.id, JSON.stringify(schema, null, 2));
|
|
862
|
+
await Trinity.refreshDelegateCatalog().catch(() => { });
|
|
863
|
+
}
|
|
864
|
+
catch { /* ignore schema errors */ }
|
|
865
|
+
}
|
|
866
|
+
const refreshed = registry.getDatabase(db.id);
|
|
867
|
+
res.status(201).json({
|
|
868
|
+
...refreshed,
|
|
869
|
+
password: refreshed.password ? '***' : null,
|
|
870
|
+
connection_string: refreshed.connection_string ? '***' : null,
|
|
871
|
+
connection_ok: connectionOk,
|
|
872
|
+
});
|
|
873
|
+
}
|
|
874
|
+
catch (error) {
|
|
875
|
+
res.status(500).json({ error: error.message });
|
|
876
|
+
}
|
|
877
|
+
});
|
|
878
|
+
router.put('/trinity/databases/:id', async (req, res) => {
|
|
879
|
+
const id = parseInt(req.params.id);
|
|
880
|
+
if (isNaN(id))
|
|
881
|
+
return res.status(400).json({ error: 'Invalid id' });
|
|
882
|
+
const parsed = DatabaseCreateSchema.partial().safeParse(req.body);
|
|
883
|
+
if (!parsed.success) {
|
|
884
|
+
return res.status(400).json({ error: 'Invalid input', details: parsed.error.issues });
|
|
885
|
+
}
|
|
886
|
+
try {
|
|
887
|
+
const registry = DatabaseRegistry.getInstance();
|
|
888
|
+
const updated = registry.updateDatabase(id, parsed.data);
|
|
889
|
+
if (!updated)
|
|
890
|
+
return res.status(404).json({ error: 'Database not found' });
|
|
891
|
+
// Re-test and re-introspect
|
|
892
|
+
let connectionOk = false;
|
|
893
|
+
try {
|
|
894
|
+
connectionOk = await testConnection(updated);
|
|
895
|
+
}
|
|
896
|
+
catch { /* ignore */ }
|
|
897
|
+
if (connectionOk) {
|
|
898
|
+
try {
|
|
899
|
+
const schema = await introspectSchema(updated);
|
|
900
|
+
registry.updateSchema(id, JSON.stringify(schema, null, 2));
|
|
901
|
+
await Trinity.refreshDelegateCatalog().catch(() => { });
|
|
902
|
+
}
|
|
903
|
+
catch { /* ignore */ }
|
|
904
|
+
}
|
|
905
|
+
const refreshed = registry.getDatabase(id);
|
|
906
|
+
res.json({
|
|
907
|
+
...refreshed,
|
|
908
|
+
password: refreshed.password ? '***' : null,
|
|
909
|
+
connection_string: refreshed.connection_string ? '***' : null,
|
|
910
|
+
connection_ok: connectionOk,
|
|
911
|
+
});
|
|
912
|
+
}
|
|
913
|
+
catch (error) {
|
|
914
|
+
res.status(500).json({ error: error.message });
|
|
915
|
+
}
|
|
916
|
+
});
|
|
917
|
+
router.delete('/trinity/databases/:id', (req, res) => {
|
|
918
|
+
const id = parseInt(req.params.id);
|
|
919
|
+
if (isNaN(id))
|
|
920
|
+
return res.status(400).json({ error: 'Invalid id' });
|
|
921
|
+
try {
|
|
922
|
+
const registry = DatabaseRegistry.getInstance();
|
|
923
|
+
const deleted = registry.deleteDatabase(id);
|
|
924
|
+
if (!deleted)
|
|
925
|
+
return res.status(404).json({ error: 'Database not found' });
|
|
926
|
+
Trinity.refreshDelegateCatalog().catch(() => { });
|
|
927
|
+
res.json({ success: true });
|
|
928
|
+
}
|
|
929
|
+
catch (error) {
|
|
930
|
+
res.status(500).json({ error: error.message });
|
|
931
|
+
}
|
|
932
|
+
});
|
|
933
|
+
router.post('/trinity/databases/:id/refresh-schema', async (req, res) => {
|
|
934
|
+
const id = parseInt(req.params.id);
|
|
935
|
+
if (isNaN(id))
|
|
936
|
+
return res.status(400).json({ error: 'Invalid id' });
|
|
937
|
+
try {
|
|
938
|
+
const registry = DatabaseRegistry.getInstance();
|
|
939
|
+
const db = registry.getDatabase(id);
|
|
940
|
+
if (!db)
|
|
941
|
+
return res.status(404).json({ error: 'Database not found' });
|
|
942
|
+
const schema = await introspectSchema(db);
|
|
943
|
+
registry.updateSchema(id, JSON.stringify(schema, null, 2));
|
|
944
|
+
await Trinity.refreshDelegateCatalog().catch(() => { });
|
|
945
|
+
const tableNames = schema.databases
|
|
946
|
+
? schema.databases.flatMap((d) => d.tables.map((t) => `${d.name}.${t.name}`))
|
|
947
|
+
: schema.tables.map((t) => t.name);
|
|
948
|
+
res.json({ success: true, tables: tableNames, databases: schema.databases?.length });
|
|
949
|
+
}
|
|
950
|
+
catch (error) {
|
|
951
|
+
res.status(500).json({ error: error.message });
|
|
952
|
+
}
|
|
953
|
+
});
|
|
954
|
+
router.post('/trinity/databases/:id/test', async (req, res) => {
|
|
955
|
+
const id = parseInt(req.params.id);
|
|
956
|
+
if (isNaN(id))
|
|
957
|
+
return res.status(400).json({ error: 'Invalid id' });
|
|
958
|
+
try {
|
|
959
|
+
const registry = DatabaseRegistry.getInstance();
|
|
960
|
+
const db = registry.getDatabase(id);
|
|
961
|
+
if (!db)
|
|
962
|
+
return res.status(404).json({ error: 'Database not found' });
|
|
963
|
+
const ok = await testConnection(db);
|
|
964
|
+
res.json({ success: ok, status: ok ? 'connected' : 'failed' });
|
|
965
|
+
}
|
|
966
|
+
catch (error) {
|
|
967
|
+
res.json({ success: false, status: 'error', error: error.message });
|
|
968
|
+
}
|
|
969
|
+
});
|
|
748
970
|
router.get('/logs', async (req, res) => {
|
|
749
971
|
try {
|
|
750
972
|
await fs.ensureDir(PATHS.logs);
|
|
@@ -10,7 +10,7 @@ const EMBEDDING_DIM = 384;
|
|
|
10
10
|
const BATCH_LIMIT = 5;
|
|
11
11
|
export async function runSessionEmbeddingWorker() {
|
|
12
12
|
const display = DisplayManager.getInstance();
|
|
13
|
-
display.log('🚀 Iniciando worker de embeddings de sessões...', { source: 'SessionEmbeddingWorker' });
|
|
13
|
+
// display.log('🚀 Iniciando worker de embeddings de sessões...', { source: 'SessionEmbeddingWorker' });
|
|
14
14
|
const shortDb = new Database(SHORT_DB_PATH);
|
|
15
15
|
const satiDb = new Database(SATI_DB_PATH);
|
|
16
16
|
shortDb.pragma('journal_mode = WAL');
|
|
@@ -27,7 +27,7 @@ export async function runSessionEmbeddingWorker() {
|
|
|
27
27
|
LIMIT ?
|
|
28
28
|
`).all(BATCH_LIMIT);
|
|
29
29
|
if (sessions.length === 0) {
|
|
30
|
-
display.log('✅ Nenhuma sessão pendente.', { level: 'debug', source: 'SessionEmbeddingWorker' });
|
|
30
|
+
// display.log('✅ Nenhuma sessão pendente.', { level: 'debug', source: 'SessionEmbeddingWorker' });
|
|
31
31
|
break;
|
|
32
32
|
}
|
|
33
33
|
for (const session of sessions) {
|
|
@@ -90,5 +90,5 @@ export async function runSessionEmbeddingWorker() {
|
|
|
90
90
|
}
|
|
91
91
|
}
|
|
92
92
|
}
|
|
93
|
-
display.log('🏁 Worker finalizado.', { source: 'SessionEmbeddingWorker' });
|
|
93
|
+
// display.log('🏁 Worker finalizado.', { source: 'SessionEmbeddingWorker' });
|
|
94
94
|
}
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import Database from 'better-sqlite3';
|
|
2
|
+
import fs from 'fs-extra';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { homedir } from 'os';
|
|
5
|
+
import { encrypt, decrypt, canEncrypt } from '../trinity-crypto.js';
|
|
6
|
+
function safeDecrypt(value) {
|
|
7
|
+
if (!value)
|
|
8
|
+
return null;
|
|
9
|
+
try {
|
|
10
|
+
return decrypt(value);
|
|
11
|
+
}
|
|
12
|
+
catch {
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
function rowToRecord(row) {
|
|
17
|
+
return {
|
|
18
|
+
id: row.id,
|
|
19
|
+
name: row.name,
|
|
20
|
+
type: row.type,
|
|
21
|
+
host: row.host,
|
|
22
|
+
port: row.port,
|
|
23
|
+
database_name: row.database_name,
|
|
24
|
+
username: row.username,
|
|
25
|
+
password: safeDecrypt(row.password_encrypted),
|
|
26
|
+
connection_string: safeDecrypt(row.connection_string_encrypted),
|
|
27
|
+
schema_json: row.schema_json,
|
|
28
|
+
schema_updated_at: row.schema_updated_at,
|
|
29
|
+
created_at: row.created_at,
|
|
30
|
+
updated_at: row.updated_at,
|
|
31
|
+
allow_read: row.allow_read === 1,
|
|
32
|
+
allow_insert: row.allow_insert === 1,
|
|
33
|
+
allow_update: row.allow_update === 1,
|
|
34
|
+
allow_delete: row.allow_delete === 1,
|
|
35
|
+
allow_ddl: row.allow_ddl === 1,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
export class DatabaseRegistry {
|
|
39
|
+
static instance = null;
|
|
40
|
+
db;
|
|
41
|
+
constructor() {
|
|
42
|
+
const dbPath = path.join(homedir(), '.morpheus', 'memory', 'trinity.db');
|
|
43
|
+
fs.ensureDirSync(path.dirname(dbPath));
|
|
44
|
+
this.db = new Database(dbPath, { timeout: 5000 });
|
|
45
|
+
this.db.pragma('journal_mode = WAL');
|
|
46
|
+
this.db.pragma('foreign_keys = ON');
|
|
47
|
+
this.ensureTable();
|
|
48
|
+
this.ensureMigrations();
|
|
49
|
+
}
|
|
50
|
+
static getInstance() {
|
|
51
|
+
if (!DatabaseRegistry.instance) {
|
|
52
|
+
DatabaseRegistry.instance = new DatabaseRegistry();
|
|
53
|
+
}
|
|
54
|
+
return DatabaseRegistry.instance;
|
|
55
|
+
}
|
|
56
|
+
ensureTable() {
|
|
57
|
+
this.db.exec(`
|
|
58
|
+
CREATE TABLE IF NOT EXISTS databases (
|
|
59
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
60
|
+
name TEXT NOT NULL UNIQUE,
|
|
61
|
+
type TEXT NOT NULL,
|
|
62
|
+
host TEXT,
|
|
63
|
+
port INTEGER,
|
|
64
|
+
database_name TEXT,
|
|
65
|
+
username TEXT,
|
|
66
|
+
password_encrypted TEXT,
|
|
67
|
+
connection_string_encrypted TEXT,
|
|
68
|
+
schema_json TEXT,
|
|
69
|
+
schema_updated_at INTEGER,
|
|
70
|
+
created_at INTEGER NOT NULL,
|
|
71
|
+
updated_at INTEGER NOT NULL,
|
|
72
|
+
allow_read INTEGER NOT NULL DEFAULT 1,
|
|
73
|
+
allow_insert INTEGER NOT NULL DEFAULT 0,
|
|
74
|
+
allow_update INTEGER NOT NULL DEFAULT 0,
|
|
75
|
+
allow_delete INTEGER NOT NULL DEFAULT 0,
|
|
76
|
+
allow_ddl INTEGER NOT NULL DEFAULT 0
|
|
77
|
+
)
|
|
78
|
+
`);
|
|
79
|
+
}
|
|
80
|
+
/** Add new columns to existing databases table (migration) */
|
|
81
|
+
ensureMigrations() {
|
|
82
|
+
const existingCols = this.db.pragma('table_info(databases)').map((c) => c.name);
|
|
83
|
+
const addIfMissing = (col, def) => {
|
|
84
|
+
if (!existingCols.includes(col)) {
|
|
85
|
+
this.db.exec(`ALTER TABLE databases ADD COLUMN ${col} ${def}`);
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
addIfMissing('allow_read', 'INTEGER NOT NULL DEFAULT 1');
|
|
89
|
+
addIfMissing('allow_insert', 'INTEGER NOT NULL DEFAULT 0');
|
|
90
|
+
addIfMissing('allow_update', 'INTEGER NOT NULL DEFAULT 0');
|
|
91
|
+
addIfMissing('allow_delete', 'INTEGER NOT NULL DEFAULT 0');
|
|
92
|
+
addIfMissing('allow_ddl', 'INTEGER NOT NULL DEFAULT 0');
|
|
93
|
+
}
|
|
94
|
+
listDatabases() {
|
|
95
|
+
const rows = this.db.prepare('SELECT * FROM databases ORDER BY name ASC').all();
|
|
96
|
+
return rows.map(rowToRecord);
|
|
97
|
+
}
|
|
98
|
+
getDatabase(id) {
|
|
99
|
+
const row = this.db.prepare('SELECT * FROM databases WHERE id = ?').get(id);
|
|
100
|
+
return row ? rowToRecord(row) : null;
|
|
101
|
+
}
|
|
102
|
+
getDatabaseByName(name) {
|
|
103
|
+
const row = this.db.prepare('SELECT * FROM databases WHERE name = ?').get(name);
|
|
104
|
+
return row ? rowToRecord(row) : null;
|
|
105
|
+
}
|
|
106
|
+
createDatabase(data) {
|
|
107
|
+
if ((data.password || data.connection_string) && !canEncrypt()) {
|
|
108
|
+
throw new Error('MORPHEUS_SECRET must be set to store database credentials. ' +
|
|
109
|
+
'Add MORPHEUS_SECRET to your environment before saving credentials.');
|
|
110
|
+
}
|
|
111
|
+
const now = Date.now();
|
|
112
|
+
const stmt = this.db.prepare(`
|
|
113
|
+
INSERT INTO databases (
|
|
114
|
+
name, type, host, port, database_name, username,
|
|
115
|
+
password_encrypted, connection_string_encrypted,
|
|
116
|
+
schema_json, schema_updated_at,
|
|
117
|
+
allow_read, allow_insert, allow_update, allow_delete, allow_ddl,
|
|
118
|
+
created_at, updated_at
|
|
119
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, NULL, NULL, ?, ?, ?, ?, ?, ?, ?)
|
|
120
|
+
`);
|
|
121
|
+
const result = stmt.run(data.name, data.type, data.host ?? null, data.port ?? null, data.database_name ?? null, data.username ?? null, data.password ? encrypt(data.password) : null, data.connection_string ? encrypt(data.connection_string) : null, data.allow_read !== false ? 1 : 0, data.allow_insert ? 1 : 0, data.allow_update ? 1 : 0, data.allow_delete ? 1 : 0, data.allow_ddl ? 1 : 0, now, now);
|
|
122
|
+
return this.getDatabase(result.lastInsertRowid);
|
|
123
|
+
}
|
|
124
|
+
updateDatabase(id, data) {
|
|
125
|
+
const existing = this.getDatabase(id);
|
|
126
|
+
if (!existing)
|
|
127
|
+
return null;
|
|
128
|
+
if ((data.password || data.connection_string) && !canEncrypt()) {
|
|
129
|
+
throw new Error('MORPHEUS_SECRET must be set to update database credentials.');
|
|
130
|
+
}
|
|
131
|
+
const now = Date.now();
|
|
132
|
+
// Build dynamic update
|
|
133
|
+
const fields = [];
|
|
134
|
+
const values = [];
|
|
135
|
+
if (data.name !== undefined) {
|
|
136
|
+
fields.push('name = ?');
|
|
137
|
+
values.push(data.name);
|
|
138
|
+
}
|
|
139
|
+
if (data.type !== undefined) {
|
|
140
|
+
fields.push('type = ?');
|
|
141
|
+
values.push(data.type);
|
|
142
|
+
}
|
|
143
|
+
if (data.host !== undefined) {
|
|
144
|
+
fields.push('host = ?');
|
|
145
|
+
values.push(data.host);
|
|
146
|
+
}
|
|
147
|
+
if (data.port !== undefined) {
|
|
148
|
+
fields.push('port = ?');
|
|
149
|
+
values.push(data.port);
|
|
150
|
+
}
|
|
151
|
+
if (data.database_name !== undefined) {
|
|
152
|
+
fields.push('database_name = ?');
|
|
153
|
+
values.push(data.database_name);
|
|
154
|
+
}
|
|
155
|
+
if (data.username !== undefined) {
|
|
156
|
+
fields.push('username = ?');
|
|
157
|
+
values.push(data.username);
|
|
158
|
+
}
|
|
159
|
+
if (data.password !== undefined) {
|
|
160
|
+
fields.push('password_encrypted = ?');
|
|
161
|
+
values.push(data.password ? encrypt(data.password) : null);
|
|
162
|
+
}
|
|
163
|
+
if (data.connection_string !== undefined) {
|
|
164
|
+
fields.push('connection_string_encrypted = ?');
|
|
165
|
+
values.push(data.connection_string ? encrypt(data.connection_string) : null);
|
|
166
|
+
}
|
|
167
|
+
if (data.allow_read !== undefined) {
|
|
168
|
+
fields.push('allow_read = ?');
|
|
169
|
+
values.push(data.allow_read ? 1 : 0);
|
|
170
|
+
}
|
|
171
|
+
if (data.allow_insert !== undefined) {
|
|
172
|
+
fields.push('allow_insert = ?');
|
|
173
|
+
values.push(data.allow_insert ? 1 : 0);
|
|
174
|
+
}
|
|
175
|
+
if (data.allow_update !== undefined) {
|
|
176
|
+
fields.push('allow_update = ?');
|
|
177
|
+
values.push(data.allow_update ? 1 : 0);
|
|
178
|
+
}
|
|
179
|
+
if (data.allow_delete !== undefined) {
|
|
180
|
+
fields.push('allow_delete = ?');
|
|
181
|
+
values.push(data.allow_delete ? 1 : 0);
|
|
182
|
+
}
|
|
183
|
+
if (data.allow_ddl !== undefined) {
|
|
184
|
+
fields.push('allow_ddl = ?');
|
|
185
|
+
values.push(data.allow_ddl ? 1 : 0);
|
|
186
|
+
}
|
|
187
|
+
if (fields.length === 0)
|
|
188
|
+
return existing;
|
|
189
|
+
fields.push('updated_at = ?');
|
|
190
|
+
values.push(now);
|
|
191
|
+
values.push(id);
|
|
192
|
+
this.db.prepare(`UPDATE databases SET ${fields.join(', ')} WHERE id = ?`).run(...values);
|
|
193
|
+
return this.getDatabase(id);
|
|
194
|
+
}
|
|
195
|
+
deleteDatabase(id) {
|
|
196
|
+
const result = this.db.prepare('DELETE FROM databases WHERE id = ?').run(id);
|
|
197
|
+
return result.changes > 0;
|
|
198
|
+
}
|
|
199
|
+
updateSchema(id, schemaJson) {
|
|
200
|
+
const now = Date.now();
|
|
201
|
+
this.db.prepare('UPDATE databases SET schema_json = ?, schema_updated_at = ?, updated_at = ? WHERE id = ?').run(schemaJson, now, now, id);
|
|
202
|
+
}
|
|
203
|
+
}
|
package/dist/runtime/neo.js
CHANGED
|
@@ -4,7 +4,7 @@ import { ProviderFactory } from "./providers/factory.js";
|
|
|
4
4
|
import { ProviderError } from "./errors.js";
|
|
5
5
|
import { DisplayManager } from "./display.js";
|
|
6
6
|
import { Construtor } from "./tools/factory.js";
|
|
7
|
-
import {
|
|
7
|
+
import { morpheusTools } from "./tools/index.js";
|
|
8
8
|
import { SQLiteChatMessageHistory } from "./memory/sqlite.js";
|
|
9
9
|
import { TaskRequestContext } from "./tasks/context.js";
|
|
10
10
|
import { updateNeoDelegateToolDescription } from "./tools/neo-tool.js";
|
|
@@ -31,30 +31,13 @@ export class Neo {
|
|
|
31
31
|
}
|
|
32
32
|
static async refreshDelegateCatalog() {
|
|
33
33
|
const mcpTools = await Construtor.create();
|
|
34
|
-
|
|
35
|
-
...mcpTools,
|
|
36
|
-
ConfigQueryTool,
|
|
37
|
-
ConfigUpdateTool,
|
|
38
|
-
DiagnosticTool,
|
|
39
|
-
MessageCountTool,
|
|
40
|
-
TokenUsageTool,
|
|
41
|
-
ProviderModelUsageTool
|
|
42
|
-
];
|
|
43
|
-
updateNeoDelegateToolDescription(catalogTools);
|
|
34
|
+
updateNeoDelegateToolDescription(mcpTools);
|
|
44
35
|
}
|
|
45
36
|
async initialize() {
|
|
46
37
|
const neoConfig = this.config.neo || this.config.llm;
|
|
47
38
|
const mcpTools = await Construtor.create();
|
|
48
|
-
const tools = [
|
|
49
|
-
|
|
50
|
-
ConfigQueryTool,
|
|
51
|
-
ConfigUpdateTool,
|
|
52
|
-
DiagnosticTool,
|
|
53
|
-
MessageCountTool,
|
|
54
|
-
TokenUsageTool,
|
|
55
|
-
ProviderModelUsageTool
|
|
56
|
-
];
|
|
57
|
-
updateNeoDelegateToolDescription(tools);
|
|
39
|
+
const tools = [...mcpTools, ...morpheusTools];
|
|
40
|
+
updateNeoDelegateToolDescription(mcpTools);
|
|
58
41
|
this.display.log(`Neo initialized with ${tools.length} tools.`, { source: "Neo" });
|
|
59
42
|
try {
|
|
60
43
|
this.agent = await ProviderFactory.create(neoConfig, tools);
|
|
@@ -78,13 +61,20 @@ You execute tasks using MCP and internal tools.
|
|
|
78
61
|
Focus on verifiable execution and return objective results.
|
|
79
62
|
|
|
80
63
|
Rules:
|
|
81
|
-
1. Use tools whenever task depends on external/system state.
|
|
82
|
-
2. Validate outputs before final answer.
|
|
83
|
-
3. If blocked, explain what is missing.
|
|
64
|
+
1. Use tools whenever the task depends on external/system state.
|
|
65
|
+
2. Validate outputs before giving a final answer.
|
|
66
|
+
3. If blocked, explain exactly what is missing — tool name, permission, or missing input.
|
|
84
67
|
4. Keep output concise and actionable.
|
|
85
68
|
5. Respond in the language requested by the user. If not explicit, use the dominant language of the task/context.
|
|
86
|
-
6. For connectivity checks, prefer
|
|
87
|
-
7. If
|
|
69
|
+
6. For connectivity checks, prefer dedicated network "ping" tool semantics and avoid forcing shell flags.
|
|
70
|
+
7. If shell ping is required, include OS-aware guidance: Windows uses "-n", Linux/macOS uses "-c".
|
|
71
|
+
|
|
72
|
+
CRITICAL — NEVER FABRICATE DATA:
|
|
73
|
+
- If none of your available tools can retrieve the requested information, respond EXACTLY with:
|
|
74
|
+
"I do not have the required tool to fetch this data. Cannot retrieve: [describe what was requested]. Available tools: [list your actual tool names]."
|
|
75
|
+
- NEVER generate fake records, fake IDs, fake names, fake statuses, or fake values of any kind.
|
|
76
|
+
- If a tool call fails or returns empty results, report the actual result — do not substitute invented data.
|
|
77
|
+
- An honest "I cannot retrieve this" is always correct. A fabricated answer is never acceptable.
|
|
88
78
|
|
|
89
79
|
${context ? `Context:\n${context}` : ""}
|
|
90
80
|
`);
|
package/dist/runtime/oracle.js
CHANGED
|
@@ -9,9 +9,12 @@ import { Apoc } from "./apoc.js";
|
|
|
9
9
|
import { TaskRequestContext } from "./tasks/context.js";
|
|
10
10
|
import { TaskRepository } from "./tasks/repository.js";
|
|
11
11
|
import { Neo } from "./neo.js";
|
|
12
|
+
import { Trinity } from "./trinity.js";
|
|
12
13
|
import { NeoDelegateTool } from "./tools/neo-tool.js";
|
|
13
14
|
import { ApocDelegateTool } from "./tools/apoc-tool.js";
|
|
14
|
-
import {
|
|
15
|
+
import { TrinityDelegateTool } from "./tools/trinity-tool.js";
|
|
16
|
+
import { TaskQueryTool } from "./tools/index.js";
|
|
17
|
+
import { MCPManager } from "../config/mcp-manager.js";
|
|
15
18
|
export class Oracle {
|
|
16
19
|
provider;
|
|
17
20
|
config;
|
|
@@ -41,9 +44,9 @@ export class Oracle {
|
|
|
41
44
|
const hasCreationClaim = /(as\s+tarefas?\s+foram\s+criadas|tarefa\s+criada|nova\s+tarefa\s+criada|deleguei|delegado|delegada|tasks?\s+created|task\s+created|queued\s+for|agendei|agendado|agendada|foi\s+agendad)/i.test(raw);
|
|
42
45
|
if (!hasCreationClaim)
|
|
43
46
|
return false;
|
|
44
|
-
const hasAgentMention = /\b(apoc|neo|trinit)\b/i.test(raw);
|
|
47
|
+
const hasAgentMention = /\b(apoc|neo|trinit|trinity)\b/i.test(raw);
|
|
45
48
|
const hasUuid = /\b[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}\b/.test(raw);
|
|
46
|
-
const hasAgentListLine = /(?:\*|-)?.{0,8}(apoc|neo|trinit)\s*[::]/i.test(raw);
|
|
49
|
+
const hasAgentListLine = /(?:\*|-)?.{0,8}(apoc|neo|trinit|trinity)\s*[::]/i.test(raw);
|
|
47
50
|
return hasCreationClaim && (hasAgentMention || hasUuid || hasAgentListLine);
|
|
48
51
|
}
|
|
49
52
|
buildDelegationAck(acks) {
|
|
@@ -66,7 +69,7 @@ export class Oracle {
|
|
|
66
69
|
}
|
|
67
70
|
extractDelegationAcksFromMessages(messages) {
|
|
68
71
|
const acks = [];
|
|
69
|
-
const regex = /Task\s+([0-9a-fA-F-]{36})\s+(?:queued|already queued)\s+for\s+(Apoc|Neo|apoc|neo)\s+execution/i;
|
|
72
|
+
const regex = /Task\s+([0-9a-fA-F-]{36})\s+(?:queued|already queued)\s+for\s+(Apoc|Neo|Trinity|apoc|neo|trinity)\s+execution/i;
|
|
70
73
|
for (const msg of messages) {
|
|
71
74
|
if (!(msg instanceof ToolMessage))
|
|
72
75
|
continue;
|
|
@@ -105,7 +108,7 @@ export class Oracle {
|
|
|
105
108
|
const toolCalls = msg.tool_calls ?? [];
|
|
106
109
|
if (!Array.isArray(toolCalls))
|
|
107
110
|
continue;
|
|
108
|
-
if (toolCalls.some((tc) => tc?.name === "apoc_delegate" || tc?.name === "neo_delegate")) {
|
|
111
|
+
if (toolCalls.some((tc) => tc?.name === "apoc_delegate" || tc?.name === "neo_delegate" || tc?.name === "trinity_delegate")) {
|
|
109
112
|
return true;
|
|
110
113
|
}
|
|
111
114
|
}
|
|
@@ -122,10 +125,11 @@ export class Oracle {
|
|
|
122
125
|
// Note: API Key validation is delegated to ProviderFactory or the Provider itself
|
|
123
126
|
// to allow for Environment Variable fallback supported by LangChain.
|
|
124
127
|
try {
|
|
125
|
-
// Refresh Neo tool
|
|
128
|
+
// Refresh Neo and Trinity tool catalogs so delegate descriptions contain runtime info.
|
|
126
129
|
// Fail-open: Oracle can still initialize even if catalog refresh fails.
|
|
127
130
|
await Neo.refreshDelegateCatalog().catch(() => { });
|
|
128
|
-
|
|
131
|
+
await Trinity.refreshDelegateCatalog().catch(() => { });
|
|
132
|
+
this.provider = await ProviderFactory.create(this.config.llm, [TaskQueryTool, NeoDelegateTool, ApocDelegateTool, TrinityDelegateTool]);
|
|
129
133
|
if (!this.provider) {
|
|
130
134
|
throw new Error("Provider factory returned undefined");
|
|
131
135
|
}
|
|
@@ -137,6 +141,8 @@ export class Oracle {
|
|
|
137
141
|
databasePath: this.databasePath,
|
|
138
142
|
limit: contextWindow,
|
|
139
143
|
});
|
|
144
|
+
// Register reload callback so MCPManager.reloadAgents() can trigger a full tool reload.
|
|
145
|
+
MCPManager.registerReloadCallback(() => this.reloadTools());
|
|
140
146
|
}
|
|
141
147
|
catch (err) {
|
|
142
148
|
if (err instanceof ProviderError)
|
|
@@ -250,6 +256,7 @@ Use it to inform your response and tool selection (if needed), but do not assume
|
|
|
250
256
|
: undefined;
|
|
251
257
|
Apoc.setSessionId(currentSessionId);
|
|
252
258
|
Neo.setSessionId(currentSessionId);
|
|
259
|
+
Trinity.setSessionId(currentSessionId);
|
|
253
260
|
const invokeContext = {
|
|
254
261
|
origin_channel: taskContext?.origin_channel ?? "api",
|
|
255
262
|
session_id: taskContext?.session_id ?? currentSessionId ?? "default",
|
|
@@ -440,7 +447,8 @@ Use it to inform your response and tool selection (if needed), but do not assume
|
|
|
440
447
|
throw new Error("Oracle not initialized. Call initialize() first.");
|
|
441
448
|
}
|
|
442
449
|
await Neo.refreshDelegateCatalog().catch(() => { });
|
|
443
|
-
|
|
450
|
+
await Trinity.refreshDelegateCatalog().catch(() => { });
|
|
451
|
+
this.provider = await ProviderFactory.create(this.config.llm, [TaskQueryTool, NeoDelegateTool, ApocDelegateTool, TrinityDelegateTool]);
|
|
444
452
|
await Neo.getInstance().reload();
|
|
445
453
|
this.display.log(`Oracle and Neo tools reloaded`, { source: 'Oracle' });
|
|
446
454
|
}
|
|
@@ -3,7 +3,7 @@ import { DisplayManager } from './display.js';
|
|
|
3
3
|
const INTERVAL_MS = 60_000 * 5; // 5 minutos
|
|
4
4
|
export function startSessionEmbeddingScheduler() {
|
|
5
5
|
const display = DisplayManager.getInstance();
|
|
6
|
-
display.log('
|
|
6
|
+
display.log('Scheduler embeddings started.', { source: 'SessionEmbeddingScheduler' });
|
|
7
7
|
// roda imediatamente na inicialização
|
|
8
8
|
runSessionEmbeddingWorker().catch(console.error);
|
|
9
9
|
let isRunning = false;
|
|
@@ -19,6 +19,27 @@ export class TaskDispatcher {
|
|
|
19
19
|
? (task.output && task.output.trim().length > 0 ? task.output : 'Task completed without output.')
|
|
20
20
|
: (task.error && task.error.trim().length > 0 ? task.error : 'Task failed with unknown error.');
|
|
21
21
|
repo.updateNotificationResult(task.origin_message_id, status, result);
|
|
22
|
+
// Send Telegram notification if the webhook has 'telegram' in notification_channels
|
|
23
|
+
const notification = repo.getNotificationById(task.origin_message_id);
|
|
24
|
+
if (notification) {
|
|
25
|
+
const webhook = repo.getWebhookById(notification.webhook_id);
|
|
26
|
+
if (webhook?.notification_channels.includes('telegram')) {
|
|
27
|
+
const adapter = TaskDispatcher.telegramAdapter;
|
|
28
|
+
if (adapter) {
|
|
29
|
+
try {
|
|
30
|
+
const icon = status === 'completed' ? '✅' : '❌';
|
|
31
|
+
const truncated = result.length > 3500 ? result.slice(0, 3500) + '…' : result;
|
|
32
|
+
await adapter.sendMessage(`${icon} Webhook: ${webhook.name}\n\n${truncated}`);
|
|
33
|
+
}
|
|
34
|
+
catch (err) {
|
|
35
|
+
TaskDispatcher.display.log(`Failed to send Telegram notification for webhook "${webhook.name}": ${err.message}`, { source: 'TaskDispatcher', level: 'error' });
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
TaskDispatcher.display.log(`Telegram notification skipped for webhook "${webhook.name}" — adapter not connected.`, { source: 'TaskDispatcher', level: 'warning' });
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
22
43
|
return;
|
|
23
44
|
}
|
|
24
45
|
if (task.origin_channel === 'ui') {
|
|
@@ -2,6 +2,7 @@ import { randomUUID } from 'crypto';
|
|
|
2
2
|
import { DisplayManager } from '../display.js';
|
|
3
3
|
import { Apoc } from '../apoc.js';
|
|
4
4
|
import { Neo } from '../neo.js';
|
|
5
|
+
import { Trinity } from '../trinity.js';
|
|
5
6
|
import { TaskRepository } from './repository.js';
|
|
6
7
|
export class TaskWorker {
|
|
7
8
|
workerId;
|
|
@@ -69,7 +70,9 @@ export class TaskWorker {
|
|
|
69
70
|
break;
|
|
70
71
|
}
|
|
71
72
|
case 'trinit': {
|
|
72
|
-
|
|
73
|
+
const trinity = Trinity.getInstance();
|
|
74
|
+
output = await trinity.execute(task.input, task.context ?? undefined, task.session_id);
|
|
75
|
+
break;
|
|
73
76
|
}
|
|
74
77
|
default: {
|
|
75
78
|
throw new Error(`Unknown task agent: ${task.agent}`);
|