morpheus-cli 0.4.15 → 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 +293 -1115
- package/dist/channels/telegram.js +379 -74
- package/dist/cli/commands/doctor.js +34 -0
- package/dist/cli/commands/init.js +128 -0
- package/dist/cli/commands/restart.js +32 -14
- package/dist/cli/commands/start.js +28 -12
- package/dist/config/manager.js +82 -0
- package/dist/config/mcp-manager.js +19 -1
- package/dist/config/schemas.js +9 -0
- package/dist/devkit/tools/network.js +1 -1
- package/dist/http/api.js +399 -10
- package/dist/runtime/apoc.js +25 -17
- package/dist/runtime/memory/sati/repository.js +30 -2
- package/dist/runtime/memory/sati/service.js +46 -15
- package/dist/runtime/memory/sati/system-prompts.js +71 -29
- package/dist/runtime/memory/session-embedding-worker.js +3 -3
- package/dist/runtime/memory/sqlite.js +24 -0
- package/dist/runtime/memory/trinity-db.js +203 -0
- package/dist/runtime/neo.js +124 -0
- package/dist/runtime/oracle.js +252 -205
- package/dist/runtime/providers/factory.js +1 -12
- package/dist/runtime/session-embedding-scheduler.js +1 -1
- package/dist/runtime/tasks/context.js +53 -0
- package/dist/runtime/tasks/dispatcher.js +91 -0
- package/dist/runtime/tasks/notifier.js +68 -0
- package/dist/runtime/tasks/repository.js +370 -0
- package/dist/runtime/tasks/types.js +1 -0
- package/dist/runtime/tasks/worker.js +99 -0
- package/dist/runtime/tools/__tests__/tools.test.js +1 -3
- package/dist/runtime/tools/apoc-tool.js +61 -8
- package/dist/runtime/tools/delegation-guard.js +29 -0
- package/dist/runtime/tools/factory.js +1 -1
- package/dist/runtime/tools/index.js +2 -3
- package/dist/runtime/tools/morpheus-tools.js +742 -0
- package/dist/runtime/tools/neo-tool.js +109 -0
- 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/runtime/webhooks/dispatcher.js +10 -19
- package/dist/types/config.js +10 -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/ui/assets/index-LemKVRjC.js +0 -112
- package/dist/ui/assets/index-TCQ7VNYO.css +0 -1
package/dist/http/api.js
CHANGED
|
@@ -11,6 +11,10 @@ import { z } from 'zod';
|
|
|
11
11
|
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
|
+
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';
|
|
14
18
|
async function readLastLines(filePath, n) {
|
|
15
19
|
try {
|
|
16
20
|
const content = await fs.readFile(filePath, 'utf8');
|
|
@@ -25,6 +29,7 @@ export function createApiRouter(oracle) {
|
|
|
25
29
|
const router = Router();
|
|
26
30
|
const configManager = ConfigManager.getInstance();
|
|
27
31
|
const history = new SQLiteChatMessageHistory({ sessionId: 'api-reader' });
|
|
32
|
+
const taskRepository = TaskRepository.getInstance();
|
|
28
33
|
// --- Session Management ---
|
|
29
34
|
router.get('/sessions', async (req, res) => {
|
|
30
35
|
try {
|
|
@@ -83,18 +88,76 @@ export function createApiRouter(oracle) {
|
|
|
83
88
|
const { id } = req.params;
|
|
84
89
|
const sessionHistory = new SQLiteChatMessageHistory({ sessionId: id, limit: 100 });
|
|
85
90
|
try {
|
|
86
|
-
const
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
91
|
+
const relatedSessionIds = id.startsWith('sati-evaluation-')
|
|
92
|
+
? [id]
|
|
93
|
+
: [id, `sati-evaluation-${id}`];
|
|
94
|
+
const rows = await sessionHistory.getRawMessagesBySessionIds(relatedSessionIds, 200);
|
|
95
|
+
const normalizedMessages = rows.map((row) => {
|
|
96
|
+
let content = row.content;
|
|
97
|
+
let tool_calls;
|
|
98
|
+
let tool_name;
|
|
99
|
+
let tool_call_id;
|
|
100
|
+
if (row.type === 'ai') {
|
|
101
|
+
try {
|
|
102
|
+
const parsed = JSON.parse(row.content);
|
|
103
|
+
if (parsed && typeof parsed === 'object') {
|
|
104
|
+
if (Array.isArray(parsed.tool_calls)) {
|
|
105
|
+
tool_calls = parsed.tool_calls;
|
|
106
|
+
}
|
|
107
|
+
if (typeof parsed.text === 'string') {
|
|
108
|
+
content = parsed.text;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
// Keep raw content for legacy/plain-text messages.
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
if (row.type === 'tool') {
|
|
117
|
+
try {
|
|
118
|
+
const parsed = JSON.parse(row.content);
|
|
119
|
+
if (parsed && typeof parsed === 'object') {
|
|
120
|
+
if (parsed.content !== undefined) {
|
|
121
|
+
const parsedContent = parsed.content;
|
|
122
|
+
content =
|
|
123
|
+
typeof parsedContent === 'string'
|
|
124
|
+
? parsedContent
|
|
125
|
+
: JSON.stringify(parsedContent, null, 2);
|
|
126
|
+
}
|
|
127
|
+
if (typeof parsed.name === 'string') {
|
|
128
|
+
tool_name = parsed.name;
|
|
129
|
+
}
|
|
130
|
+
if (typeof parsed.tool_call_id === 'string') {
|
|
131
|
+
tool_call_id = parsed.tool_call_id;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
catch {
|
|
136
|
+
// Keep raw content for legacy/plain-text tool messages.
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
const usage_metadata = row.total_tokens != null
|
|
140
|
+
? {
|
|
141
|
+
input_tokens: row.input_tokens || 0,
|
|
142
|
+
output_tokens: row.output_tokens || 0,
|
|
143
|
+
total_tokens: row.total_tokens || 0,
|
|
144
|
+
input_token_details: row.cache_read_tokens
|
|
145
|
+
? { cache_read: row.cache_read_tokens }
|
|
146
|
+
: undefined,
|
|
147
|
+
}
|
|
148
|
+
: undefined;
|
|
90
149
|
return {
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
150
|
+
session_id: row.session_id,
|
|
151
|
+
created_at: row.created_at,
|
|
152
|
+
type: row.type,
|
|
153
|
+
content,
|
|
154
|
+
tool_calls,
|
|
155
|
+
tool_name,
|
|
156
|
+
tool_call_id,
|
|
157
|
+
usage_metadata,
|
|
95
158
|
};
|
|
96
159
|
});
|
|
97
|
-
//
|
|
160
|
+
// Convert DESC to ASC for UI rendering
|
|
98
161
|
res.json(normalizedMessages.reverse());
|
|
99
162
|
}
|
|
100
163
|
catch (err) {
|
|
@@ -117,13 +180,74 @@ export function createApiRouter(oracle) {
|
|
|
117
180
|
try {
|
|
118
181
|
const { message, sessionId } = parsed.data;
|
|
119
182
|
await oracle.setSessionId(sessionId);
|
|
120
|
-
const response = await oracle.chat(message
|
|
183
|
+
const response = await oracle.chat(message, undefined, false, {
|
|
184
|
+
origin_channel: 'ui',
|
|
185
|
+
session_id: sessionId,
|
|
186
|
+
});
|
|
121
187
|
res.json({ response });
|
|
122
188
|
}
|
|
123
189
|
catch (err) {
|
|
124
190
|
res.status(500).json({ error: err.message });
|
|
125
191
|
}
|
|
126
192
|
});
|
|
193
|
+
const TaskStatusSchema = z.enum(['pending', 'running', 'completed', 'failed', 'cancelled']);
|
|
194
|
+
const TaskAgentSchema = z.enum(['apoc', 'neo', 'trinit']);
|
|
195
|
+
const OriginChannelSchema = z.enum(['telegram', 'discord', 'ui', 'api', 'webhook', 'cli']);
|
|
196
|
+
router.get('/tasks', (req, res) => {
|
|
197
|
+
try {
|
|
198
|
+
const status = req.query.status;
|
|
199
|
+
const agent = req.query.agent;
|
|
200
|
+
const originChannel = req.query.origin_channel;
|
|
201
|
+
const sessionId = req.query.session_id;
|
|
202
|
+
const limit = req.query.limit;
|
|
203
|
+
const parsedStatus = typeof status === 'string' ? TaskStatusSchema.safeParse(status) : null;
|
|
204
|
+
const parsedAgent = typeof agent === 'string' ? TaskAgentSchema.safeParse(agent) : null;
|
|
205
|
+
const parsedOrigin = typeof originChannel === 'string' ? OriginChannelSchema.safeParse(originChannel) : null;
|
|
206
|
+
const tasks = taskRepository.listTasks({
|
|
207
|
+
status: parsedStatus?.success ? parsedStatus.data : undefined,
|
|
208
|
+
agent: parsedAgent?.success ? parsedAgent.data : undefined,
|
|
209
|
+
origin_channel: parsedOrigin?.success ? parsedOrigin.data : undefined,
|
|
210
|
+
session_id: typeof sessionId === 'string' ? sessionId : undefined,
|
|
211
|
+
limit: typeof limit === 'string' ? Math.max(1, Math.min(500, Number(limit) || 200)) : 200,
|
|
212
|
+
});
|
|
213
|
+
res.json(tasks);
|
|
214
|
+
}
|
|
215
|
+
catch (err) {
|
|
216
|
+
res.status(500).json({ error: err.message });
|
|
217
|
+
}
|
|
218
|
+
});
|
|
219
|
+
router.get('/tasks/stats', (req, res) => {
|
|
220
|
+
try {
|
|
221
|
+
res.json(taskRepository.getStats());
|
|
222
|
+
}
|
|
223
|
+
catch (err) {
|
|
224
|
+
res.status(500).json({ error: err.message });
|
|
225
|
+
}
|
|
226
|
+
});
|
|
227
|
+
router.get('/tasks/:id', (req, res) => {
|
|
228
|
+
try {
|
|
229
|
+
const task = taskRepository.getTaskById(req.params.id);
|
|
230
|
+
if (!task) {
|
|
231
|
+
return res.status(404).json({ error: 'Task not found' });
|
|
232
|
+
}
|
|
233
|
+
res.json(task);
|
|
234
|
+
}
|
|
235
|
+
catch (err) {
|
|
236
|
+
res.status(500).json({ error: err.message });
|
|
237
|
+
}
|
|
238
|
+
});
|
|
239
|
+
router.post('/tasks/:id/retry', (req, res) => {
|
|
240
|
+
try {
|
|
241
|
+
const ok = taskRepository.retryTask(req.params.id);
|
|
242
|
+
if (!ok) {
|
|
243
|
+
return res.status(404).json({ error: 'Failed task not found for retry' });
|
|
244
|
+
}
|
|
245
|
+
res.json({ success: true });
|
|
246
|
+
}
|
|
247
|
+
catch (err) {
|
|
248
|
+
res.status(500).json({ error: err.message });
|
|
249
|
+
}
|
|
250
|
+
});
|
|
127
251
|
// Legacy /session/reset (keep for backward compat or redirect to POST /sessions)
|
|
128
252
|
router.post('/session/reset', async (req, res) => {
|
|
129
253
|
try {
|
|
@@ -396,6 +520,52 @@ export function createApiRouter(oracle) {
|
|
|
396
520
|
res.status(500).json({ error: error.message });
|
|
397
521
|
}
|
|
398
522
|
});
|
|
523
|
+
// Neo config endpoints
|
|
524
|
+
router.get('/config/neo', (req, res) => {
|
|
525
|
+
try {
|
|
526
|
+
const neoConfig = configManager.getNeoConfig();
|
|
527
|
+
res.json(neoConfig);
|
|
528
|
+
}
|
|
529
|
+
catch (error) {
|
|
530
|
+
res.status(500).json({ error: error.message });
|
|
531
|
+
}
|
|
532
|
+
});
|
|
533
|
+
router.post('/config/neo', async (req, res) => {
|
|
534
|
+
try {
|
|
535
|
+
const config = configManager.get();
|
|
536
|
+
await configManager.save({ ...config, neo: req.body });
|
|
537
|
+
const display = DisplayManager.getInstance();
|
|
538
|
+
display.log('Neo configuration updated via UI', {
|
|
539
|
+
source: 'Zaion',
|
|
540
|
+
level: 'info'
|
|
541
|
+
});
|
|
542
|
+
res.json({ success: true });
|
|
543
|
+
}
|
|
544
|
+
catch (error) {
|
|
545
|
+
if (error.name === 'ZodError') {
|
|
546
|
+
res.status(400).json({ error: 'Validation failed', details: error.errors });
|
|
547
|
+
}
|
|
548
|
+
else {
|
|
549
|
+
res.status(500).json({ error: error.message });
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
});
|
|
553
|
+
router.delete('/config/neo', async (req, res) => {
|
|
554
|
+
try {
|
|
555
|
+
const config = configManager.get();
|
|
556
|
+
const { neo: _neo, ...restConfig } = config;
|
|
557
|
+
await configManager.save(restConfig);
|
|
558
|
+
const display = DisplayManager.getInstance();
|
|
559
|
+
display.log('Neo configuration removed via UI (falling back to Oracle config)', {
|
|
560
|
+
source: 'Zaion',
|
|
561
|
+
level: 'info'
|
|
562
|
+
});
|
|
563
|
+
res.json({ success: true });
|
|
564
|
+
}
|
|
565
|
+
catch (error) {
|
|
566
|
+
res.status(500).json({ error: error.message });
|
|
567
|
+
}
|
|
568
|
+
});
|
|
399
569
|
router.post('/config/apoc', async (req, res) => {
|
|
400
570
|
try {
|
|
401
571
|
const config = configManager.get();
|
|
@@ -578,6 +748,225 @@ export function createApiRouter(oracle) {
|
|
|
578
748
|
// Redirect to POST logic or just reuse
|
|
579
749
|
res.status(307).redirect(307, '/api/config');
|
|
580
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
|
+
});
|
|
581
970
|
router.get('/logs', async (req, res) => {
|
|
582
971
|
try {
|
|
583
972
|
await fs.ensureDir(PATHS.logs);
|
package/dist/runtime/apoc.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { HumanMessage, SystemMessage } from "@langchain/core/messages";
|
|
1
|
+
import { HumanMessage, SystemMessage, AIMessage } from "@langchain/core/messages";
|
|
2
2
|
import { ConfigManager } from "../config/manager.js";
|
|
3
3
|
import { ProviderFactory } from "./providers/factory.js";
|
|
4
4
|
import { ProviderError } from "./errors.js";
|
|
@@ -95,6 +95,11 @@ OPERATING RULES:
|
|
|
95
95
|
3. Report clearly what was done and what the result was.
|
|
96
96
|
4. If something fails, report the error and what you tried.
|
|
97
97
|
5. Stay focused on the delegated task only.
|
|
98
|
+
6. Respond in the language requested by the user. If not explicit, use the dominant language of the task/context.
|
|
99
|
+
7. For connectivity checks, prefer the dedicated network tool "ping" (TCP reachability) instead of shell "ping".
|
|
100
|
+
8. Only use shell ping when explicitly required by the user. If shell ping is needed, detect OS first:
|
|
101
|
+
- Windows: use "-n" (never use "-c")
|
|
102
|
+
- Linux/macOS: use "-c"
|
|
98
103
|
|
|
99
104
|
|
|
100
105
|
────────────────────────────────────────
|
|
@@ -216,27 +221,30 @@ ${context ? `CONTEXT FROM ORACLE:\n${context}` : ""}
|
|
|
216
221
|
const messages = [systemMessage, userMessage];
|
|
217
222
|
try {
|
|
218
223
|
const response = await this.agent.invoke({ messages });
|
|
219
|
-
// Persist
|
|
220
|
-
// Use
|
|
221
|
-
// otherwise fall back to 'apoc'.
|
|
224
|
+
// Persist one AI message per delegated task so usage can be parameterized later.
|
|
225
|
+
// Use task session id when provided.
|
|
222
226
|
const apocConfig = this.config.apoc || this.config.llm;
|
|
223
|
-
const newMessages = response.messages.slice(messages.length);
|
|
224
|
-
if (newMessages.length > 0) {
|
|
225
|
-
const targetSession = sessionId ?? Apoc.currentSessionId ?? 'apoc';
|
|
226
|
-
const history = new SQLiteChatMessageHistory({ sessionId: targetSession });
|
|
227
|
-
for (const msg of newMessages) {
|
|
228
|
-
msg.provider_metadata = {
|
|
229
|
-
provider: apocConfig.provider,
|
|
230
|
-
model: apocConfig.model,
|
|
231
|
-
};
|
|
232
|
-
}
|
|
233
|
-
await history.addMessages(newMessages);
|
|
234
|
-
history.close();
|
|
235
|
-
}
|
|
236
227
|
const lastMessage = response.messages[response.messages.length - 1];
|
|
237
228
|
const content = typeof lastMessage.content === "string"
|
|
238
229
|
? lastMessage.content
|
|
239
230
|
: JSON.stringify(lastMessage.content);
|
|
231
|
+
const targetSession = sessionId ?? Apoc.currentSessionId ?? "apoc";
|
|
232
|
+
const history = new SQLiteChatMessageHistory({ sessionId: targetSession });
|
|
233
|
+
try {
|
|
234
|
+
const persisted = new AIMessage(content);
|
|
235
|
+
persisted.usage_metadata = lastMessage.usage_metadata
|
|
236
|
+
?? lastMessage.response_metadata?.usage
|
|
237
|
+
?? lastMessage.response_metadata?.tokenUsage
|
|
238
|
+
?? lastMessage.usage;
|
|
239
|
+
persisted.provider_metadata = {
|
|
240
|
+
provider: apocConfig.provider,
|
|
241
|
+
model: apocConfig.model,
|
|
242
|
+
};
|
|
243
|
+
await history.addMessage(persisted);
|
|
244
|
+
}
|
|
245
|
+
finally {
|
|
246
|
+
history.close();
|
|
247
|
+
}
|
|
240
248
|
this.display.log("Apoc task completed.", { source: "Apoc" });
|
|
241
249
|
return content;
|
|
242
250
|
}
|
|
@@ -220,7 +220,7 @@ export class SatiRepository {
|
|
|
220
220
|
m.category as category,
|
|
221
221
|
m.importance as importance,
|
|
222
222
|
'long_term' as source_type,
|
|
223
|
-
(1 - vec_distance_cosine(v.embedding, ?)) *
|
|
223
|
+
(1 - vec_distance_cosine(v.embedding, ?)) * 0.8 as distance
|
|
224
224
|
FROM memory_vec v
|
|
225
225
|
JOIN memory_embedding_map map ON map.vec_rowid = v.rowid
|
|
226
226
|
JOIN long_term_memory m ON m.id = map.memory_id
|
|
@@ -236,7 +236,7 @@ export class SatiRepository {
|
|
|
236
236
|
'session' as category,
|
|
237
237
|
'medium' as importance,
|
|
238
238
|
'session_chunk' as source_type,
|
|
239
|
-
(1 - vec_distance_cosine(v.embedding, ?)) * 0.
|
|
239
|
+
(1 - vec_distance_cosine(v.embedding, ?)) * 0.2 as distance
|
|
240
240
|
FROM session_vec v
|
|
241
241
|
JOIN session_embedding_map map ON map.vec_rowid = v.rowid
|
|
242
242
|
JOIN session_chunks sc ON sc.id = map.session_chunk_id
|
|
@@ -431,6 +431,34 @@ export class SatiRepository {
|
|
|
431
431
|
archived: Boolean(row.archived)
|
|
432
432
|
};
|
|
433
433
|
}
|
|
434
|
+
update(id, data) {
|
|
435
|
+
if (!this.db)
|
|
436
|
+
this.initialize();
|
|
437
|
+
const existing = this.db.prepare('SELECT * FROM long_term_memory WHERE id = ?').get(id);
|
|
438
|
+
if (!existing)
|
|
439
|
+
return null;
|
|
440
|
+
const setClauses = ['updated_at = @updated_at', 'version = version + 1'];
|
|
441
|
+
const params = { id, updated_at: new Date().toISOString() };
|
|
442
|
+
if (data.importance !== undefined) {
|
|
443
|
+
setClauses.push('importance = @importance');
|
|
444
|
+
params.importance = data.importance;
|
|
445
|
+
}
|
|
446
|
+
if (data.category !== undefined) {
|
|
447
|
+
setClauses.push('category = @category');
|
|
448
|
+
params.category = data.category;
|
|
449
|
+
}
|
|
450
|
+
if (data.summary !== undefined) {
|
|
451
|
+
setClauses.push('summary = @summary');
|
|
452
|
+
params.summary = data.summary;
|
|
453
|
+
}
|
|
454
|
+
if (data.details !== undefined) {
|
|
455
|
+
setClauses.push('details = @details');
|
|
456
|
+
params.details = data.details;
|
|
457
|
+
}
|
|
458
|
+
this.db.prepare(`UPDATE long_term_memory SET ${setClauses.join(', ')} WHERE id = @id`).run(params);
|
|
459
|
+
const updated = this.db.prepare('SELECT * FROM long_term_memory WHERE id = ?').get(id);
|
|
460
|
+
return updated ? this.mapRowToRecord(updated) : null;
|
|
461
|
+
}
|
|
434
462
|
archiveMemory(id) {
|
|
435
463
|
if (!this.db)
|
|
436
464
|
this.initialize();
|