morpheus-cli 0.9.12 → 0.9.13
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 +1 -1
- package/dist/channels/telegram.js +3 -3
- package/dist/cli/commands/restart.js +2 -2
- package/dist/cli/commands/start.js +2 -2
- package/dist/http/api.js +5 -2
- package/dist/http/routers/agents.js +9 -0
- package/dist/http/routers/link.js +2 -2
- package/dist/runtime/chronos/repository.js +5 -3
- package/dist/runtime/chronos/worker.js +27 -4
- package/dist/runtime/chronos/worker.test.js +3 -2
- package/dist/runtime/hot-reload.js +3 -3
- package/dist/runtime/memory/sqlite.js +37 -11
- package/dist/runtime/oracle.js +78 -43
- package/dist/runtime/{apoc.js → subagents/apoc.js} +19 -6
- 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} +23 -9
- package/dist/runtime/{link-repository.js → subagents/link/repository.js} +2 -2
- package/dist/runtime/{link-search.js → subagents/link/search.js} +3 -3
- package/dist/runtime/{link-worker.js → subagents/link/worker.js} +6 -6
- package/dist/runtime/{neo.js → subagents/neo.js} +23 -9
- package/dist/runtime/subagents/registry.js +134 -0
- package/dist/runtime/{trinity.js → subagents/trinity/trinity.js} +22 -8
- package/dist/runtime/{subagent-utils.js → subagents/utils.js} +2 -2
- package/dist/runtime/tasks/worker.js +6 -70
- package/dist/runtime/tools/chronos-tools.js +1 -0
- package/dist/runtime/tools/morpheus-tools.js +3 -3
- package/dist/runtime/webhooks/dispatcher.js +4 -0
- package/dist/ui/assets/AuditDashboard-BVyKnpVm.js +1 -0
- package/dist/ui/assets/Chat-UVoDlqqM.js +41 -0
- package/dist/ui/assets/{Chronos-D1yAb4M5.js → Chronos-Dfs_pOsc.js} +1 -1
- package/dist/ui/assets/{ConfirmationModal-DxUHZgTy.js → ConfirmationModal-BBIjVef7.js} +1 -1
- package/dist/ui/assets/{Dashboard-BzxmcHaS.js → Dashboard-BdSQDB14.js} +1 -1
- package/dist/ui/assets/{DeleteConfirmationModal-CqNXT_YQ.js → DeleteConfirmationModal-Du85q5u2.js} +1 -1
- package/dist/ui/assets/{Documents-DLFZdmim.js → Documents-DguILrI8.js} +1 -1
- package/dist/ui/assets/{Logs-B1Bpy9dB.js → Logs-BDup2FET.js} +1 -1
- package/dist/ui/assets/{MCPManager-BbUDMh5Q.js → MCPManager-WBdh1rum.js} +1 -1
- package/dist/ui/assets/{ModelPricing-DCl-2_eJ.js → ModelPricing-BQPw0r6z.js} +1 -1
- package/dist/ui/assets/{Notifications-8Cqj-mNp.js → Notifications-BslO2Ect.js} +1 -1
- package/dist/ui/assets/{SatiMemories-CdHUe6di.js → SatiMemories-DzaLaZ6M.js} +1 -1
- package/dist/ui/assets/SessionAudit-CBDThjBi.js +9 -0
- package/dist/ui/assets/{Settings-Clge45Z0.js → Settings-JPTCA7C7.js} +1 -1
- package/dist/ui/assets/{Skills-0k7A2T5_.js → Skills-BnDg1HCb.js} +1 -1
- package/dist/ui/assets/{Smiths-gjgBMN1F.js → Smiths-DR6g_o3D.js} +1 -1
- package/dist/ui/assets/Tasks-BuoNCvI-.js +1 -0
- package/dist/ui/assets/{TrinityDatabases-CGna6IMX.js → TrinityDatabases-DYHJunk7.js} +1 -1
- package/dist/ui/assets/{UsageStats-B7EzZlZe.js → UsageStats-BpGXaHgW.js} +1 -1
- package/dist/ui/assets/{WebhookManager-Bb7KiucS.js → WebhookManager-D2muhYy9.js} +1 -1
- package/dist/ui/assets/agents-CgqJea9n.js +1 -0
- package/dist/ui/assets/{audit-CJ2Ms81U.js → audit-Dc3YW0-4.js} +1 -1
- package/dist/ui/assets/{chronos-Bm68OSy4.js → chronos-CZvGhZQB.js} +1 -1
- package/dist/ui/assets/{config-C88yQ_CP.js → config-pKL8Y4V9.js} +1 -1
- package/dist/ui/assets/{index-BxN2w9sY.js → index-Bta9YXEm.js} +2 -2
- package/dist/ui/assets/index-Cjli-AD7.css +1 -0
- package/dist/ui/assets/{mcp-BE_OVkBe.js → mcp-vIffcwd6.js} +1 -1
- package/dist/ui/assets/{skills-Dt0qU4gH.js → skills-wANsorUj.js} +1 -1
- package/dist/ui/assets/{stats-Bmdps1LR.js → stats-xnlA4NwX.js} +1 -1
- 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/SessionAudit-CtVHK_IH.js +0 -9
- package/dist/ui/assets/Tasks-AQ3MrrMp.js +0 -1
- package/dist/ui/assets/index-C3Ff736M.css +0 -1
- /package/dist/runtime/{ISubagent.js → subagents/ISubagent.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
package/README.md
CHANGED
|
@@ -9,7 +9,7 @@ It runs as a daemon and orchestrates LLMs, MCP tools, DevKit tools, memory, and
|
|
|
9
9
|
|
|
10
10
|
## Why Morpheus
|
|
11
11
|
- Local-first persistence (sessions, messages, usage, tasks).
|
|
12
|
-
- Multi-agent architecture (Oracle, Neo, Apoc, Sati, Trinity, Smith).
|
|
12
|
+
- Multi-agent architecture (Oracle, Neo, Apoc, Sati, Trinity, Link, Smith) with centralized SubagentRegistry.
|
|
13
13
|
- Async task execution with queue + worker + notifier.
|
|
14
14
|
- Chronos temporal scheduler for recurring and one-time Oracle executions.
|
|
15
15
|
- Smith remote agent system for DevKit execution on isolated machines via WebSocket.
|
|
@@ -634,7 +634,7 @@ export class TelegramAdapter {
|
|
|
634
634
|
await ctx.answerCbQuery('Testing connection…').catch(() => { });
|
|
635
635
|
try {
|
|
636
636
|
const { DatabaseRegistry } = await import('../runtime/memory/trinity-db.js');
|
|
637
|
-
const { testConnection } = await import('../runtime/trinity
|
|
637
|
+
const { testConnection } = await import('../runtime/subagents/trinity/connector.js');
|
|
638
638
|
const db = DatabaseRegistry.getInstance().getDatabase(id);
|
|
639
639
|
if (!db) {
|
|
640
640
|
await safeReply(ctx, '❌ Database not found.');
|
|
@@ -666,8 +666,8 @@ export class TelegramAdapter {
|
|
|
666
666
|
await ctx.answerCbQuery('Refreshing schema…').catch(() => { });
|
|
667
667
|
try {
|
|
668
668
|
const { DatabaseRegistry } = await import('../runtime/memory/trinity-db.js');
|
|
669
|
-
const { introspectSchema } = await import('../runtime/trinity
|
|
670
|
-
const { Trinity } = await import('../runtime/trinity.js');
|
|
669
|
+
const { introspectSchema } = await import('../runtime/subagents/trinity/connector.js');
|
|
670
|
+
const { Trinity } = await import('../runtime/subagents/trinity/trinity.js');
|
|
671
671
|
const registry = DatabaseRegistry.getInstance();
|
|
672
672
|
const db = registry.getDatabase(id);
|
|
673
673
|
if (!db) {
|
|
@@ -15,8 +15,8 @@ import { HttpServer } from '../../http/server.js';
|
|
|
15
15
|
import { getVersion } from '../utils/version.js';
|
|
16
16
|
import { TaskWorker } from '../../runtime/tasks/worker.js';
|
|
17
17
|
import { TaskNotifier } from '../../runtime/tasks/notifier.js';
|
|
18
|
-
import { Link } from '../../runtime/link.js';
|
|
19
|
-
import { LinkWorker } from '../../runtime/link
|
|
18
|
+
import { Link } from '../../runtime/subagents/link/link.js';
|
|
19
|
+
import { LinkWorker } from '../../runtime/subagents/link/worker.js';
|
|
20
20
|
export const restartCommand = new Command('restart')
|
|
21
21
|
.description('Restart the Morpheus agent')
|
|
22
22
|
.option('--ui', 'Enable web UI', true)
|
|
@@ -26,8 +26,8 @@ import { ChronosRepository } from '../../runtime/chronos/repository.js';
|
|
|
26
26
|
import { SkillRegistry } from '../../runtime/skills/index.js';
|
|
27
27
|
import { MCPToolCache } from '../../runtime/tools/cache.js';
|
|
28
28
|
import { SmithRegistry } from '../../runtime/smiths/registry.js';
|
|
29
|
-
import { Link } from '../../runtime/link.js';
|
|
30
|
-
import { LinkWorker } from '../../runtime/link
|
|
29
|
+
import { Link } from '../../runtime/subagents/link/link.js';
|
|
30
|
+
import { LinkWorker } from '../../runtime/subagents/link/worker.js';
|
|
31
31
|
// Load .env file explicitly in start command
|
|
32
32
|
const envPath = path.join(process.cwd(), '.env');
|
|
33
33
|
if (fs.existsSync(envPath)) {
|
package/dist/http/api.js
CHANGED
|
@@ -13,8 +13,8 @@ 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
15
|
import { DatabaseRegistry } from '../runtime/memory/trinity-db.js';
|
|
16
|
-
import { testConnection, introspectSchema } from '../runtime/trinity
|
|
17
|
-
import { Trinity } from '../runtime/trinity.js';
|
|
16
|
+
import { testConnection, introspectSchema } from '../runtime/subagents/trinity/connector.js';
|
|
17
|
+
import { Trinity } from '../runtime/subagents/trinity/trinity.js';
|
|
18
18
|
import { ChronosRepository } from '../runtime/chronos/repository.js';
|
|
19
19
|
import { ChronosWorker } from '../runtime/chronos/worker.js';
|
|
20
20
|
import { createChronosJobRouter, createChronosConfigRouter } from './routers/chronos.js';
|
|
@@ -22,6 +22,7 @@ import { createSkillsRouter } from './routers/skills.js';
|
|
|
22
22
|
import { createSmithsRouter } from './routers/smiths.js';
|
|
23
23
|
import { createDangerRouter } from './routers/danger.js';
|
|
24
24
|
import { createLinkRouter } from './routers/link.js';
|
|
25
|
+
import { createAgentsRouter } from './routers/agents.js';
|
|
25
26
|
import { getActiveEnvOverrides } from '../config/precedence.js';
|
|
26
27
|
import { hotReloadConfig, getRestartRequiredChanges } from '../runtime/hot-reload.js';
|
|
27
28
|
import { AuditRepository } from '../runtime/audit/repository.js';
|
|
@@ -55,6 +56,8 @@ export function createApiRouter(oracle, chronosWorker) {
|
|
|
55
56
|
router.use('/danger', createDangerRouter());
|
|
56
57
|
// Mount Link router (Documentation management)
|
|
57
58
|
router.use('/link', createLinkRouter());
|
|
59
|
+
// Mount Agents metadata router
|
|
60
|
+
router.use('/agents', createAgentsRouter());
|
|
58
61
|
// --- Session Management ---
|
|
59
62
|
router.get('/sessions', async (req, res) => {
|
|
60
63
|
try {
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { Router } from 'express';
|
|
2
|
+
import { SubagentRegistry } from '../../runtime/subagents/registry.js';
|
|
3
|
+
export function createAgentsRouter() {
|
|
4
|
+
const router = Router();
|
|
5
|
+
router.get('/metadata', (_req, res) => {
|
|
6
|
+
res.json({ agents: SubagentRegistry.getDisplayMetadata() });
|
|
7
|
+
});
|
|
8
|
+
return router;
|
|
9
|
+
}
|
|
@@ -3,8 +3,8 @@ import multer from 'multer';
|
|
|
3
3
|
import path from 'path';
|
|
4
4
|
import fs from 'fs-extra';
|
|
5
5
|
import { homedir } from 'os';
|
|
6
|
-
import { LinkRepository } from '../../runtime/link
|
|
7
|
-
import { LinkWorker } from '../../runtime/link
|
|
6
|
+
import { LinkRepository } from '../../runtime/subagents/link/repository.js';
|
|
7
|
+
import { LinkWorker } from '../../runtime/subagents/link/worker.js';
|
|
8
8
|
import { ConfigManager } from '../../config/manager.js';
|
|
9
9
|
const DOCS_PATH = path.join(homedir(), '.morpheus', 'docs');
|
|
10
10
|
// Configure multer for file uploads
|
|
@@ -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
|
});
|
|
@@ -7,9 +7,9 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import { ConfigManager } from '../config/manager.js';
|
|
9
9
|
import { DisplayManager } from './display.js';
|
|
10
|
-
import { Apoc } from './apoc.js';
|
|
11
|
-
import { Neo } from './neo.js';
|
|
12
|
-
import { Trinity } from './trinity.js';
|
|
10
|
+
import { Apoc } from './subagents/apoc.js';
|
|
11
|
+
import { Neo } from './subagents/neo.js';
|
|
12
|
+
import { Trinity } from './subagents/trinity/trinity.js';
|
|
13
13
|
let currentOracle = null;
|
|
14
14
|
/**
|
|
15
15
|
* Register the current Oracle instance for hot-reload.
|
|
@@ -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
|
|
@@ -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
|
}
|
package/dist/runtime/oracle.js
CHANGED
|
@@ -5,12 +5,9 @@ import { ProviderError } from "./errors.js";
|
|
|
5
5
|
import { DisplayManager } from "./display.js";
|
|
6
6
|
import { SQLiteChatMessageHistory } from "./memory/sqlite.js";
|
|
7
7
|
import { SatiMemoryMiddleware } from "./memory/sati/index.js";
|
|
8
|
-
import { Apoc } from "./
|
|
8
|
+
import { Apoc, Neo, Trinity, Link, SubagentRegistry, emitToolAuditEvents } from "./subagents/index.js";
|
|
9
9
|
import { TaskRequestContext } from "./tasks/context.js";
|
|
10
10
|
import { TaskRepository } from "./tasks/repository.js";
|
|
11
|
-
import { Neo } from "./neo.js";
|
|
12
|
-
import { Trinity } from "./trinity.js";
|
|
13
|
-
import { Link } from "./link.js";
|
|
14
11
|
import { SmithDelegateTool } from "./tools/smith-tool.js";
|
|
15
12
|
import { TaskQueryTool, chronosTools, timeVerifierTool } from "./tools/index.js";
|
|
16
13
|
import { Construtor } from "./tools/factory.js";
|
|
@@ -20,12 +17,9 @@ import { SmithRegistry } from "./smiths/registry.js";
|
|
|
20
17
|
import { AuditRepository } from "./audit/repository.js";
|
|
21
18
|
import { SetupRepository } from './setup/repository.js';
|
|
22
19
|
import { buildSetupTool } from './tools/setup-tool.js';
|
|
23
|
-
import {
|
|
20
|
+
import { SmithDelegator } from "./smiths/delegator.js";
|
|
24
21
|
import { PATHS } from "../config/paths.js";
|
|
25
22
|
import { writeFileSync } from "fs";
|
|
26
|
-
const ORACLE_DELEGATION_TOOLS = new Set([
|
|
27
|
-
'apoc_delegate', 'neo_delegate', 'trinity_delegate', 'smith_delegate', 'link_delegate',
|
|
28
|
-
]);
|
|
29
23
|
export class Oracle {
|
|
30
24
|
provider;
|
|
31
25
|
config;
|
|
@@ -40,6 +34,48 @@ export class Oracle {
|
|
|
40
34
|
this.config = config || ConfigManager.getInstance().get();
|
|
41
35
|
this.databasePath = overrides?.databasePath;
|
|
42
36
|
}
|
|
37
|
+
/**
|
|
38
|
+
* Registers Smith in the SubagentRegistry if Smiths are configured and enabled.
|
|
39
|
+
* Smith is special — it uses a standalone tool (SmithDelegateTool) and SmithDelegator
|
|
40
|
+
* rather than implementing ISubagent, so we create a minimal registration.
|
|
41
|
+
*/
|
|
42
|
+
registerSmithIfEnabled() {
|
|
43
|
+
const smithsConfig = ConfigManager.getInstance().getSmithsConfig();
|
|
44
|
+
if (!smithsConfig.enabled || smithsConfig.entries.length === 0)
|
|
45
|
+
return;
|
|
46
|
+
if (SubagentRegistry.get('smith'))
|
|
47
|
+
return; // already registered
|
|
48
|
+
const delegator = SmithDelegator.getInstance();
|
|
49
|
+
SubagentRegistry.register({
|
|
50
|
+
agentKey: 'smith', auditAgent: 'smith', label: 'Smith',
|
|
51
|
+
delegateToolName: 'smith_delegate', emoji: '🕶️', color: 'gray',
|
|
52
|
+
description: 'Remote DevKit execution',
|
|
53
|
+
colorClass: 'text-gray-500 dark:text-gray-400',
|
|
54
|
+
bgClass: 'bg-gray-50 dark:bg-zinc-900',
|
|
55
|
+
badgeClass: 'bg-gray-200 text-gray-700 dark:bg-gray-700/60 dark:text-gray-300',
|
|
56
|
+
instance: {
|
|
57
|
+
initialize: async () => { },
|
|
58
|
+
execute: async (task, context) => delegator.delegate('unknown', task, context),
|
|
59
|
+
reload: async () => { },
|
|
60
|
+
createDelegateTool: () => SmithDelegateTool,
|
|
61
|
+
},
|
|
62
|
+
hasDynamicDescription: false,
|
|
63
|
+
isMultiInstance: true,
|
|
64
|
+
executeTask: async (task) => {
|
|
65
|
+
let smithName = 'unknown';
|
|
66
|
+
if (task.context) {
|
|
67
|
+
try {
|
|
68
|
+
const parsed = JSON.parse(task.context);
|
|
69
|
+
smithName = parsed.smith_name || parsed.smith || 'unknown';
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
smithName = task.context;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return delegator.delegate(smithName, task.input, task.context ?? undefined);
|
|
76
|
+
},
|
|
77
|
+
});
|
|
78
|
+
}
|
|
43
79
|
buildDelegationFailureResponse() {
|
|
44
80
|
return "Task enqueue could not be confirmed in the database. No task was created. Please retry.";
|
|
45
81
|
}
|
|
@@ -115,13 +151,16 @@ export class Oracle {
|
|
|
115
151
|
return valid;
|
|
116
152
|
}
|
|
117
153
|
hasDelegationToolCall(messages) {
|
|
154
|
+
const delegationTools = SubagentRegistry.getDelegationToolNames();
|
|
155
|
+
// Also include smith_delegate which may not be in registry if smiths are disabled
|
|
156
|
+
delegationTools.add('smith_delegate');
|
|
118
157
|
for (const msg of messages) {
|
|
119
158
|
if (!(msg instanceof AIMessage))
|
|
120
159
|
continue;
|
|
121
160
|
const toolCalls = msg.tool_calls ?? [];
|
|
122
161
|
if (!Array.isArray(toolCalls))
|
|
123
162
|
continue;
|
|
124
|
-
if (toolCalls.some((tc) => tc?.name
|
|
163
|
+
if (toolCalls.some((tc) => delegationTools.has(tc?.name))) {
|
|
125
164
|
return true;
|
|
126
165
|
}
|
|
127
166
|
}
|
|
@@ -152,27 +191,30 @@ export class Oracle {
|
|
|
152
191
|
// Note: API Key validation is delegated to ProviderFactory or the Provider itself
|
|
153
192
|
// to allow for Environment Variable fallback supported by LangChain.
|
|
154
193
|
try {
|
|
155
|
-
//
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
//
|
|
194
|
+
// Ensure subagents are instantiated and self-registered before using the registry.
|
|
195
|
+
Apoc.getInstance();
|
|
196
|
+
Neo.getInstance();
|
|
197
|
+
Trinity.getInstance();
|
|
198
|
+
Link.getInstance();
|
|
199
|
+
// Register Smith in the registry if configured
|
|
200
|
+
this.registerSmithIfEnabled();
|
|
201
|
+
// Refresh dynamic tool catalogs so delegate descriptions contain runtime info.
|
|
202
|
+
await SubagentRegistry.refreshAllCatalogs();
|
|
161
203
|
// Initialize setup repository (creates table if needed)
|
|
162
204
|
SetupRepository.getInstance();
|
|
163
205
|
const coreTools = [
|
|
164
206
|
buildSetupTool(),
|
|
165
207
|
TaskQueryTool,
|
|
166
|
-
|
|
167
|
-
Apoc.getInstance().createDelegateTool(),
|
|
168
|
-
Trinity.getInstance().createDelegateTool(),
|
|
169
|
-
Link.getInstance().createDelegateTool(),
|
|
208
|
+
...SubagentRegistry.getDelegationTools(),
|
|
170
209
|
createLoadSkillTool(),
|
|
171
210
|
timeVerifierTool,
|
|
172
211
|
...chronosTools,
|
|
173
212
|
];
|
|
213
|
+
// Smith's tool is already included via SubagentRegistry.getDelegationTools()
|
|
214
|
+
// if registerSmithIfEnabled() registered it. Only add it if Smith is enabled
|
|
215
|
+
// but NOT yet in the registry (shouldn't happen, but defensive).
|
|
174
216
|
const smithsConfig = ConfigManager.getInstance().getSmithsConfig();
|
|
175
|
-
if (smithsConfig.enabled && smithsConfig.entries.length > 0) {
|
|
217
|
+
if (smithsConfig.enabled && smithsConfig.entries.length > 0 && !SubagentRegistry.get('smith')) {
|
|
176
218
|
coreTools.push(SmithDelegateTool);
|
|
177
219
|
}
|
|
178
220
|
this.provider = await ProviderFactory.create(this.config.llm, coreTools);
|
|
@@ -225,12 +267,14 @@ export class Oracle {
|
|
|
225
267
|
provider: isTelephonist ? this.config.audio?.provider : this.config.llm.provider,
|
|
226
268
|
model: isTelephonist ? this.config.audio?.model : this.config.llm.model
|
|
227
269
|
};
|
|
228
|
-
// Inject source metadata for automated origins (webhook, chronos)
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
270
|
+
// Inject source metadata for automated origins (webhook, chronos).
|
|
271
|
+
// Prefer explicit taskContext.source (set by Chronos even when origin_channel
|
|
272
|
+
// points to a specific notification channel like 'telegram').
|
|
273
|
+
const messageSource = taskContext?.source ?? (taskContext?.origin_channel === 'webhook' ? 'webhook'
|
|
274
|
+
: taskContext?.origin_channel === 'chronos' ? 'chronos'
|
|
275
|
+
: null);
|
|
276
|
+
if (messageSource) {
|
|
277
|
+
userMessage.source_metadata = { source: messageSource };
|
|
234
278
|
}
|
|
235
279
|
// Attach extra usage (e.g. from Audio) to the user message to be persisted
|
|
236
280
|
if (extraUsage) {
|
|
@@ -465,10 +509,7 @@ Use it to inform your response and tool selection (if needed), but do not assume
|
|
|
465
509
|
}
|
|
466
510
|
messages.push(...previousMessages);
|
|
467
511
|
messages.push(userMessage);
|
|
468
|
-
|
|
469
|
-
Neo.setSessionId(currentSessionId);
|
|
470
|
-
Trinity.setSessionId(currentSessionId);
|
|
471
|
-
Link.setSessionId(currentSessionId);
|
|
512
|
+
SubagentRegistry.setAllSessionIds(currentSessionId);
|
|
472
513
|
const invokeContext = {
|
|
473
514
|
origin_channel: taskContext?.origin_channel ?? "api",
|
|
474
515
|
session_id: taskContext?.session_id ?? currentSessionId ?? "default",
|
|
@@ -514,8 +555,10 @@ Use it to inform your response and tool selection (if needed), but do not assume
|
|
|
514
555
|
// Emit tool_call audit events for Oracle's independent tool calls.
|
|
515
556
|
// Delegation tools (apoc/neo/trinity/smith/skill/link) are already audited
|
|
516
557
|
// inside buildDelegationTool or the task system — skip them here.
|
|
558
|
+
const delegationToolNames = SubagentRegistry.getDelegationToolNames();
|
|
559
|
+
delegationToolNames.add('smith_delegate');
|
|
517
560
|
emitToolAuditEvents(newGeneratedMessages, currentSessionId ?? 'default', 'oracle', {
|
|
518
|
-
skipTools:
|
|
561
|
+
skipTools: delegationToolNames,
|
|
519
562
|
});
|
|
520
563
|
// Inject provider/model metadata and duration into all new AI messages
|
|
521
564
|
for (const msg of newGeneratedMessages) {
|
|
@@ -694,24 +737,16 @@ Use it to inform your response and tool selection (if needed), but do not assume
|
|
|
694
737
|
}
|
|
695
738
|
// Reload MCP tool cache from servers (slow path)
|
|
696
739
|
await Construtor.reload();
|
|
697
|
-
await
|
|
698
|
-
await Trinity.refreshDelegateCatalog().catch(() => { });
|
|
699
|
-
await Link.refreshDelegateCatalog().catch(() => { });
|
|
740
|
+
await SubagentRegistry.refreshAllCatalogs();
|
|
700
741
|
this.provider = await ProviderFactory.create(this.config.llm, [
|
|
701
742
|
buildSetupTool(),
|
|
702
743
|
TaskQueryTool,
|
|
703
|
-
|
|
704
|
-
Apoc.getInstance().createDelegateTool(),
|
|
705
|
-
Trinity.getInstance().createDelegateTool(),
|
|
706
|
-
Link.getInstance().createDelegateTool(),
|
|
744
|
+
...SubagentRegistry.getDelegationTools(),
|
|
707
745
|
createLoadSkillTool(),
|
|
708
746
|
timeVerifierTool,
|
|
709
747
|
...chronosTools,
|
|
710
748
|
]);
|
|
711
|
-
await
|
|
712
|
-
|
|
713
|
-
await Trinity.getInstance().reload();
|
|
714
|
-
await Link.getInstance().reload();
|
|
715
|
-
this.display.log(`Oracle and Neo tools reloaded`, { source: 'Oracle' });
|
|
749
|
+
await SubagentRegistry.reloadAll();
|
|
750
|
+
this.display.log(`Oracle and subagent tools reloaded`, { source: 'Oracle' });
|
|
716
751
|
}
|
|
717
752
|
}
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { HumanMessage, SystemMessage, AIMessage } from "@langchain/core/messages";
|
|
2
|
-
import { ConfigManager } from "
|
|
3
|
-
import { ProviderFactory } from "
|
|
4
|
-
import { ProviderError } from "
|
|
5
|
-
import { DisplayManager } from "
|
|
2
|
+
import { ConfigManager } from "../../config/manager.js";
|
|
3
|
+
import { ProviderFactory } from "../providers/factory.js";
|
|
4
|
+
import { ProviderError } from "../errors.js";
|
|
5
|
+
import { DisplayManager } from "../display.js";
|
|
6
6
|
import { buildDevKit } from "morpheus-devkit";
|
|
7
7
|
import { instrumentDevKitTools } from "./devkit-instrument.js";
|
|
8
|
-
import { extractRawUsage, persistAgentMessage, buildAgentResult, emitToolAuditEvents } from "./
|
|
9
|
-
import { buildDelegationTool } from "
|
|
8
|
+
import { extractRawUsage, persistAgentMessage, buildAgentResult, emitToolAuditEvents } from "./utils.js";
|
|
9
|
+
import { buildDelegationTool } from "../tools/delegation-utils.js";
|
|
10
|
+
import { SubagentRegistry } from "./registry.js";
|
|
10
11
|
/**
|
|
11
12
|
* Apoc is a subagent of Oracle specialized in devtools operations.
|
|
12
13
|
* It receives delegated tasks from Oracle and executes them using DevKit tools
|
|
@@ -36,6 +37,18 @@ export class Apoc {
|
|
|
36
37
|
static getInstance(config) {
|
|
37
38
|
if (!Apoc.instance) {
|
|
38
39
|
Apoc.instance = new Apoc(config);
|
|
40
|
+
SubagentRegistry.register({
|
|
41
|
+
agentKey: 'apoc', auditAgent: 'apoc', label: 'Apoc',
|
|
42
|
+
delegateToolName: 'apoc_delegate', emoji: '🧑🔬', color: 'amber',
|
|
43
|
+
description: 'Filesystem, shell & browser',
|
|
44
|
+
colorClass: 'text-amber-600 dark:text-amber-400',
|
|
45
|
+
bgClass: 'bg-amber-50 dark:bg-amber-900/10',
|
|
46
|
+
badgeClass: 'bg-amber-100 text-amber-700 dark:bg-amber-900/40 dark:text-amber-300',
|
|
47
|
+
instance: Apoc.instance,
|
|
48
|
+
hasDynamicDescription: false,
|
|
49
|
+
isMultiInstance: false,
|
|
50
|
+
setSessionId: (id) => Apoc.setSessionId(id),
|
|
51
|
+
});
|
|
39
52
|
}
|
|
40
53
|
return Apoc.instance;
|
|
41
54
|
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// Re-exports for convenient external access
|
|
2
|
+
export { Apoc } from './apoc.js';
|
|
3
|
+
export { Neo } from './neo.js';
|
|
4
|
+
export { Trinity } from './trinity/trinity.js';
|
|
5
|
+
export { Link } from './link/link.js';
|
|
6
|
+
export { SubagentRegistry, SYSTEM_AGENTS } from './registry.js';
|
|
7
|
+
export { extractRawUsage, persistAgentMessage, buildAgentResult, emitToolAuditEvents } from './utils.js';
|
|
8
|
+
export { LinkRepository } from './link/repository.js';
|
|
9
|
+
export { LinkWorker } from './link/worker.js';
|
|
10
|
+
export { LinkSearch } from './link/search.js';
|
|
11
|
+
export { instrumentDevKitTools } from './devkit-instrument.js';
|
|
12
|
+
export { testConnection, introspectSchema, executeQuery } from './trinity/connector.js';
|