morpheus-cli 0.9.12 → 0.9.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +49 -18
- package/dist/channels/discord.js +93 -6
- package/dist/channels/telegram.js +112 -12
- package/dist/cli/commands/restart.js +2 -2
- package/dist/cli/commands/start.js +17 -2
- package/dist/config/manager.js +20 -1
- package/dist/config/paths.js +4 -0
- package/dist/config/schemas.js +15 -0
- package/dist/http/api.js +7 -3
- package/dist/http/routers/agents.js +9 -0
- package/dist/http/routers/danger.js +4 -5
- package/dist/http/routers/link.js +4 -4
- package/dist/runtime/__tests__/telephonist-tts.test.js +84 -0
- package/dist/runtime/adapters/AuditRepositoryAdapter.js +6 -0
- package/dist/runtime/adapters/ChannelNotifierAdapter.js +9 -0
- package/dist/runtime/adapters/LangChainProviderAdapter.js +9 -0
- package/dist/runtime/adapters/SQLiteChatHistoryAdapter.js +15 -0
- package/dist/runtime/adapters/SQLiteTaskEnqueuerAdapter.js +6 -0
- package/dist/runtime/adapters/index.js +5 -0
- package/dist/runtime/audit/repository.js +6 -2
- package/dist/runtime/chronos/repository.js +7 -5
- package/dist/runtime/chronos/worker.js +27 -4
- package/dist/runtime/chronos/worker.test.js +3 -2
- package/dist/runtime/container.js +50 -0
- package/dist/runtime/hot-reload.js +6 -9
- package/dist/runtime/memory/backfill-embeddings.js +2 -3
- package/dist/runtime/memory/sati/repository.js +3 -3
- package/dist/runtime/memory/sqlite.js +40 -14
- package/dist/runtime/memory/trinity-db.js +2 -2
- package/dist/runtime/oracle.js +78 -43
- package/dist/runtime/ports/IChatHistory.js +1 -0
- package/dist/runtime/ports/ILLMProviderFactory.js +1 -0
- package/dist/runtime/ports/INotifier.js +1 -0
- package/dist/runtime/ports/ITaskEnqueuer.js +1 -0
- package/dist/runtime/ports/index.js +1 -0
- package/dist/runtime/providers/factory.js +8 -52
- package/dist/runtime/providers/strategies.js +66 -0
- package/dist/runtime/setup/repository.js +2 -2
- package/dist/runtime/subagents/ISubagent.js +1 -0
- package/dist/runtime/{apoc.js → subagents/apoc.js} +20 -7
- package/dist/runtime/{devkit-instrument.js → subagents/devkit-instrument.js} +1 -1
- package/dist/runtime/subagents/index.js +12 -0
- package/dist/runtime/{link.js → subagents/link/link.js} +24 -10
- package/dist/runtime/{link-repository.js → subagents/link/repository.js} +4 -4
- package/dist/runtime/{link-search.js → subagents/link/search.js} +3 -3
- package/dist/runtime/{link-worker.js → subagents/link/worker.js} +9 -9
- package/dist/runtime/{neo.js → subagents/neo.js} +24 -10
- package/dist/runtime/subagents/registry.js +134 -0
- package/dist/runtime/{trinity.js → subagents/trinity/trinity.js} +23 -9
- package/dist/runtime/{subagent-utils.js → subagents/utils.js} +2 -2
- package/dist/runtime/tasks/repository.js +2 -2
- package/dist/runtime/tasks/worker.js +6 -70
- package/dist/runtime/telephonist.js +160 -0
- package/dist/runtime/tools/chronos-tools.js +1 -0
- package/dist/runtime/tools/delegation-utils.js +5 -7
- package/dist/runtime/tools/morpheus-tools.js +9 -10
- package/dist/runtime/tools/smith-tool.js +5 -7
- package/dist/runtime/webhooks/dispatcher.js +4 -0
- package/dist/runtime/webhooks/repository.js +2 -2
- package/dist/types/config.js +6 -0
- package/dist/ui/assets/AuditDashboard-Cu33zb_7.js +1 -0
- package/dist/ui/assets/Chat-mt1j5V55.js +41 -0
- package/dist/ui/assets/{Chronos-D1yAb4M5.js → Chronos-Bq_h41cw.js} +1 -1
- package/dist/ui/assets/{ConfirmationModal-DxUHZgTy.js → ConfirmationModal-CxLP8iC6.js} +1 -1
- package/dist/ui/assets/{Dashboard-BzxmcHaS.js → Dashboard-D0LAlHtG.js} +1 -1
- package/dist/ui/assets/{DeleteConfirmationModal-CqNXT_YQ.js → DeleteConfirmationModal-kZ_c3sFk.js} +1 -1
- package/dist/ui/assets/{Documents-DLFZdmim.js → Documents-nlQNoUcq.js} +1 -1
- package/dist/ui/assets/{Logs-B1Bpy9dB.js → Logs-C1tlg574.js} +1 -1
- package/dist/ui/assets/{MCPManager-BbUDMh5Q.js → MCPManager-Do7isizG.js} +1 -1
- package/dist/ui/assets/ModelPricing-BeJ7oXBA.js +1 -0
- package/dist/ui/assets/{Notifications-8Cqj-mNp.js → Notifications-Cg5CMlY0.js} +1 -1
- package/dist/ui/assets/{SatiMemories-CdHUe6di.js → SatiMemories-D9l6s8Pc.js} +1 -1
- package/dist/ui/assets/SessionAudit-Da1ySlYg.js +9 -0
- package/dist/ui/assets/Settings-DpXwpEhO.js +49 -0
- package/dist/ui/assets/{Skills-0k7A2T5_.js → Skills-DaqCY8QH.js} +1 -1
- package/dist/ui/assets/Smiths-DA-x4KFT.js +1 -0
- package/dist/ui/assets/Switch-CJTE4ZQm.js +1 -0
- package/dist/ui/assets/Tasks-DU49M9U-.js +1 -0
- package/dist/ui/assets/{TrinityDatabases-CGna6IMX.js → TrinityDatabases-CoKzKTL-.js} +1 -1
- package/dist/ui/assets/{UsageStats-B7EzZlZe.js → UsageStats-cds352Pj.js} +1 -1
- package/dist/ui/assets/{WebhookManager-Bb7KiucS.js → WebhookManager-DdAdHQUk.js} +1 -1
- package/dist/ui/assets/agents-B1z_dlQC.js +1 -0
- package/dist/ui/assets/{audit-CJ2Ms81U.js → audit-BAhaGrKY.js} +1 -1
- package/dist/ui/assets/{chronos-Bm68OSy4.js → chronos-DGD_Md9M.js} +1 -1
- package/dist/ui/assets/config-BwTXe5M2.js +1 -0
- package/dist/ui/assets/{index-BxN2w9sY.js → index-BcX5O7kY.js} +2 -2
- package/dist/ui/assets/index-Cjli-AD7.css +1 -0
- package/dist/ui/assets/{mcp-BE_OVkBe.js → mcp-BlkruPaA.js} +1 -1
- package/dist/ui/assets/{skills-Dt0qU4gH.js → skills-CtCb-52u.js} +1 -1
- package/dist/ui/assets/{stats-Bmdps1LR.js → stats-BiPI2kaw.js} +1 -1
- package/dist/ui/assets/useCurrency-BCdG-pHx.js +1 -0
- package/dist/ui/index.html +2 -2
- package/dist/ui/sw.js +1 -1
- package/package.json +1 -1
- package/dist/ui/assets/AuditDashboard-CM1YN1uk.js +0 -1
- package/dist/ui/assets/Chat-D4y-g6Tw.js +0 -41
- package/dist/ui/assets/ModelPricing-DCl-2_eJ.js +0 -1
- package/dist/ui/assets/SessionAudit-CtVHK_IH.js +0 -9
- package/dist/ui/assets/Settings-Clge45Z0.js +0 -49
- package/dist/ui/assets/Smiths-gjgBMN1F.js +0 -1
- package/dist/ui/assets/Tasks-AQ3MrrMp.js +0 -1
- package/dist/ui/assets/config-C88yQ_CP.js +0 -1
- package/dist/ui/assets/index-C3Ff736M.css +0 -1
- /package/dist/runtime/{ISubagent.js → ports/IAuditEmitter.js} +0 -0
- /package/dist/runtime/{link-chunker.js → subagents/link/chunker.js} +0 -0
- /package/dist/runtime/{trinity-connector.js → subagents/trinity/connector.js} +0 -0
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { DisplayManager } from "../display.js";
|
|
2
|
+
/**
|
|
3
|
+
* System-level agents that are not subagents but need display metadata for audit/UI.
|
|
4
|
+
*/
|
|
5
|
+
export const SYSTEM_AGENTS = [
|
|
6
|
+
{
|
|
7
|
+
agentKey: 'oracle', auditAgent: 'oracle', label: 'Oracle',
|
|
8
|
+
delegateToolName: '', emoji: '🔮', color: 'blue',
|
|
9
|
+
description: 'Root orchestrator',
|
|
10
|
+
colorClass: 'text-blue-600 dark:text-blue-400',
|
|
11
|
+
bgClass: 'bg-blue-50 dark:bg-blue-900/10',
|
|
12
|
+
badgeClass: 'bg-blue-100 text-blue-700 dark:bg-blue-900/40 dark:text-blue-300',
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
agentKey: 'chronos', auditAgent: 'chronos', label: 'Chronos',
|
|
16
|
+
delegateToolName: '', emoji: '⏰', color: 'orange',
|
|
17
|
+
description: 'Temporal scheduler',
|
|
18
|
+
colorClass: 'text-orange-600 dark:text-orange-400',
|
|
19
|
+
bgClass: 'bg-orange-50 dark:bg-orange-900/10',
|
|
20
|
+
badgeClass: 'bg-orange-100 text-orange-700 dark:bg-orange-900/40 dark:text-orange-300',
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
agentKey: 'sati', auditAgent: 'sati', label: 'Sati',
|
|
24
|
+
delegateToolName: '', emoji: '🧠', color: 'emerald',
|
|
25
|
+
description: 'Long-term memory',
|
|
26
|
+
colorClass: 'text-emerald-600 dark:text-emerald-400',
|
|
27
|
+
bgClass: 'bg-emerald-50 dark:bg-emerald-900/10',
|
|
28
|
+
badgeClass: 'bg-emerald-100 text-emerald-700 dark:bg-emerald-900/40 dark:text-emerald-300',
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
agentKey: 'telephonist', auditAgent: 'telephonist', label: 'Telephonist',
|
|
32
|
+
delegateToolName: '', emoji: '📞', color: 'rose',
|
|
33
|
+
description: 'Audio transcription',
|
|
34
|
+
colorClass: 'text-rose-600 dark:text-rose-400',
|
|
35
|
+
bgClass: 'bg-rose-50 dark:bg-rose-900/10',
|
|
36
|
+
badgeClass: 'bg-rose-100 text-rose-700 dark:bg-rose-900/40 dark:text-rose-300',
|
|
37
|
+
},
|
|
38
|
+
];
|
|
39
|
+
/**
|
|
40
|
+
* Central registry for all subagents. Static singleton following ChannelRegistry pattern.
|
|
41
|
+
*/
|
|
42
|
+
export class SubagentRegistry {
|
|
43
|
+
static agents = new Map();
|
|
44
|
+
static display = DisplayManager.getInstance();
|
|
45
|
+
static register(reg) {
|
|
46
|
+
SubagentRegistry.agents.set(reg.agentKey, reg);
|
|
47
|
+
SubagentRegistry.display.log(`Subagent registered: ${reg.label} (${reg.agentKey})`, { source: 'SubagentRegistry', level: 'info' });
|
|
48
|
+
}
|
|
49
|
+
static get(agentKey) {
|
|
50
|
+
return SubagentRegistry.agents.get(agentKey);
|
|
51
|
+
}
|
|
52
|
+
static getAll() {
|
|
53
|
+
return [...SubagentRegistry.agents.values()];
|
|
54
|
+
}
|
|
55
|
+
static getByToolName(toolName) {
|
|
56
|
+
for (const reg of SubagentRegistry.agents.values()) {
|
|
57
|
+
if (reg.delegateToolName === toolName)
|
|
58
|
+
return reg;
|
|
59
|
+
}
|
|
60
|
+
return undefined;
|
|
61
|
+
}
|
|
62
|
+
/** Returns the set of all delegation tool names (replaces ORACLE_DELEGATION_TOOLS). */
|
|
63
|
+
static getDelegationToolNames() {
|
|
64
|
+
const names = new Set();
|
|
65
|
+
for (const reg of SubagentRegistry.agents.values()) {
|
|
66
|
+
if (reg.delegateToolName)
|
|
67
|
+
names.add(reg.delegateToolName);
|
|
68
|
+
}
|
|
69
|
+
return names;
|
|
70
|
+
}
|
|
71
|
+
/** Returns all delegation tools for Oracle's coreTools array. */
|
|
72
|
+
static getDelegationTools() {
|
|
73
|
+
return SubagentRegistry.getAll().map(reg => reg.instance.createDelegateTool());
|
|
74
|
+
}
|
|
75
|
+
/** Sets session ID on all registered subagents that support it. */
|
|
76
|
+
static setAllSessionIds(sessionId) {
|
|
77
|
+
for (const reg of SubagentRegistry.agents.values()) {
|
|
78
|
+
reg.setSessionId?.(sessionId);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
/** Refreshes dynamic descriptions on all subagents that support it. */
|
|
82
|
+
static async refreshAllCatalogs() {
|
|
83
|
+
const promises = [];
|
|
84
|
+
for (const reg of SubagentRegistry.agents.values()) {
|
|
85
|
+
if (reg.hasDynamicDescription && reg.refreshCatalog) {
|
|
86
|
+
promises.push(reg.refreshCatalog().catch(() => { }));
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
await Promise.all(promises);
|
|
90
|
+
}
|
|
91
|
+
/** Executes a task by routing to the correct subagent (replaces worker switch/case). */
|
|
92
|
+
static async executeTask(task) {
|
|
93
|
+
const reg = SubagentRegistry.agents.get(task.agent);
|
|
94
|
+
if (!reg) {
|
|
95
|
+
throw new Error(`Unknown task agent: ${task.agent}`);
|
|
96
|
+
}
|
|
97
|
+
if (reg.executeTask) {
|
|
98
|
+
return reg.executeTask(task);
|
|
99
|
+
}
|
|
100
|
+
return reg.instance.execute(task.input, task.context ?? undefined, task.session_id, {
|
|
101
|
+
origin_channel: task.origin_channel,
|
|
102
|
+
session_id: task.session_id,
|
|
103
|
+
origin_message_id: task.origin_message_id ?? undefined,
|
|
104
|
+
origin_user_id: task.origin_user_id ?? undefined,
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
/** Maps task agent key to audit agent name (e.g. 'trinit' -> 'trinity'). */
|
|
108
|
+
static resolveAuditAgent(taskAgent) {
|
|
109
|
+
const reg = SubagentRegistry.agents.get(taskAgent);
|
|
110
|
+
return (reg?.auditAgent ?? taskAgent);
|
|
111
|
+
}
|
|
112
|
+
/** Returns display metadata for all agents (subagents + system agents). */
|
|
113
|
+
static getDisplayMetadata() {
|
|
114
|
+
const subagents = SubagentRegistry.getAll().map(reg => ({
|
|
115
|
+
agentKey: reg.agentKey,
|
|
116
|
+
auditAgent: reg.auditAgent,
|
|
117
|
+
label: reg.label,
|
|
118
|
+
delegateToolName: reg.delegateToolName,
|
|
119
|
+
emoji: reg.emoji,
|
|
120
|
+
color: reg.color,
|
|
121
|
+
description: reg.description,
|
|
122
|
+
colorClass: reg.colorClass,
|
|
123
|
+
bgClass: reg.bgClass,
|
|
124
|
+
badgeClass: reg.badgeClass,
|
|
125
|
+
}));
|
|
126
|
+
return [...SYSTEM_AGENTS, ...subagents];
|
|
127
|
+
}
|
|
128
|
+
/** Reloads all registered subagents. */
|
|
129
|
+
static async reloadAll() {
|
|
130
|
+
for (const reg of SubagentRegistry.agents.values()) {
|
|
131
|
+
await reg.instance.reload();
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
import { HumanMessage, SystemMessage, AIMessage } from "@langchain/core/messages";
|
|
2
2
|
import { tool } from "@langchain/core/tools";
|
|
3
3
|
import { z } from "zod";
|
|
4
|
-
import { ConfigManager } from "
|
|
5
|
-
import {
|
|
6
|
-
import { ProviderError } from "
|
|
7
|
-
import { DisplayManager } from "
|
|
8
|
-
import { DatabaseRegistry } from "
|
|
9
|
-
import { testConnection, introspectSchema, executeQuery } from "./
|
|
10
|
-
import { extractRawUsage, persistAgentMessage, buildAgentResult, emitToolAuditEvents } from "
|
|
11
|
-
import { buildDelegationTool } from "
|
|
4
|
+
import { ConfigManager } from "../../../config/manager.js";
|
|
5
|
+
import { ServiceContainer, SERVICE_KEYS } from "../../container.js";
|
|
6
|
+
import { ProviderError } from "../../errors.js";
|
|
7
|
+
import { DisplayManager } from "../../display.js";
|
|
8
|
+
import { DatabaseRegistry } from "../../memory/trinity-db.js";
|
|
9
|
+
import { testConnection, introspectSchema, executeQuery } from "./connector.js";
|
|
10
|
+
import { extractRawUsage, persistAgentMessage, buildAgentResult, emitToolAuditEvents } from "../utils.js";
|
|
11
|
+
import { buildDelegationTool } from "../../tools/delegation-utils.js";
|
|
12
|
+
import { SubagentRegistry } from "../registry.js";
|
|
12
13
|
const TRINITY_BASE_DESCRIPTION = `Delegate a database task to Trinity, the specialized database subagent, asynchronously.
|
|
13
14
|
|
|
14
15
|
This tool enqueues a background task and returns an acknowledgement with task id.
|
|
@@ -52,6 +53,19 @@ export class Trinity {
|
|
|
52
53
|
static getInstance(config) {
|
|
53
54
|
if (!Trinity.instance) {
|
|
54
55
|
Trinity.instance = new Trinity(config);
|
|
56
|
+
SubagentRegistry.register({
|
|
57
|
+
agentKey: 'trinit', auditAgent: 'trinity', label: 'Trinity',
|
|
58
|
+
delegateToolName: 'trinity_delegate', emoji: '👩💻', color: 'teal',
|
|
59
|
+
description: 'Database specialist',
|
|
60
|
+
colorClass: 'text-teal-600 dark:text-teal-400',
|
|
61
|
+
bgClass: 'bg-teal-50 dark:bg-teal-900/10',
|
|
62
|
+
badgeClass: 'bg-teal-100 text-teal-700 dark:bg-teal-900/40 dark:text-teal-300',
|
|
63
|
+
instance: Trinity.instance,
|
|
64
|
+
hasDynamicDescription: true,
|
|
65
|
+
isMultiInstance: false,
|
|
66
|
+
setSessionId: (id) => Trinity.setSessionId(id),
|
|
67
|
+
refreshCatalog: () => Trinity.refreshDelegateCatalog(),
|
|
68
|
+
});
|
|
55
69
|
}
|
|
56
70
|
return Trinity.instance;
|
|
57
71
|
}
|
|
@@ -190,7 +204,7 @@ export class Trinity {
|
|
|
190
204
|
const tools = this.buildTrinityTools();
|
|
191
205
|
this.display.log(`Trinity initialized with ${tools.length} tools (personality: ${personality}).`, { source: 'Trinity' });
|
|
192
206
|
try {
|
|
193
|
-
this.agent = await
|
|
207
|
+
this.agent = await ServiceContainer.get(SERVICE_KEYS.providerFactory).createBare(trinityConfig, tools);
|
|
194
208
|
}
|
|
195
209
|
catch (err) {
|
|
196
210
|
throw new ProviderError(trinityConfig.provider, err, 'Trinity subagent initialization failed');
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { AIMessage, ToolMessage } from "@langchain/core/messages";
|
|
2
|
-
import { SQLiteChatMessageHistory } from "
|
|
3
|
-
import { AuditRepository } from "
|
|
2
|
+
import { SQLiteChatMessageHistory } from "../memory/sqlite.js";
|
|
3
|
+
import { AuditRepository } from "../audit/repository.js";
|
|
4
4
|
/** Extract token usage from a LangChain message using 4-fallback chain. */
|
|
5
5
|
export function extractRawUsage(lastMessage) {
|
|
6
6
|
return lastMessage.usage_metadata
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import Database from 'better-sqlite3';
|
|
2
2
|
import fs from 'fs-extra';
|
|
3
3
|
import path from 'path';
|
|
4
|
-
import { homedir } from 'os';
|
|
5
4
|
import { randomUUID } from 'crypto';
|
|
5
|
+
import { PATHS } from '../../config/paths.js';
|
|
6
6
|
export class TaskRepository {
|
|
7
7
|
static instance = null;
|
|
8
8
|
db;
|
|
9
9
|
constructor() {
|
|
10
|
-
const dbPath =
|
|
10
|
+
const dbPath = PATHS.shortMemoryDb;
|
|
11
11
|
fs.ensureDirSync(path.dirname(dbPath));
|
|
12
12
|
this.db = new Database(dbPath, { timeout: 5000 });
|
|
13
13
|
this.db.pragma('journal_mode = WAL');
|
|
@@ -1,10 +1,6 @@
|
|
|
1
1
|
import { randomUUID } from 'crypto';
|
|
2
2
|
import { DisplayManager } from '../display.js';
|
|
3
|
-
import {
|
|
4
|
-
import { Neo } from '../neo.js';
|
|
5
|
-
import { Trinity } from '../trinity.js';
|
|
6
|
-
import { Link } from '../link.js';
|
|
7
|
-
import { SmithDelegator } from '../smiths/delegator.js';
|
|
3
|
+
import { SubagentRegistry } from '../subagents/registry.js';
|
|
8
4
|
import { TaskRepository } from './repository.js';
|
|
9
5
|
import { AuditRepository } from '../audit/repository.js';
|
|
10
6
|
export class TaskWorker {
|
|
@@ -52,77 +48,17 @@ export class TaskWorker {
|
|
|
52
48
|
}
|
|
53
49
|
async executeTask(task) {
|
|
54
50
|
const audit = AuditRepository.getInstance();
|
|
51
|
+
const auditAgent = SubagentRegistry.resolveAuditAgent(task.agent);
|
|
55
52
|
audit.insert({
|
|
56
53
|
session_id: task.session_id,
|
|
57
54
|
task_id: task.id,
|
|
58
55
|
event_type: 'task_created',
|
|
59
|
-
agent:
|
|
56
|
+
agent: auditAgent,
|
|
60
57
|
status: 'success',
|
|
61
58
|
metadata: { agent: task.agent, input_preview: task.input.slice(0, 200) },
|
|
62
59
|
});
|
|
63
60
|
try {
|
|
64
|
-
|
|
65
|
-
switch (task.agent) {
|
|
66
|
-
case 'apoc': {
|
|
67
|
-
const apoc = Apoc.getInstance();
|
|
68
|
-
result = await apoc.execute(task.input, task.context ?? undefined, task.session_id, {
|
|
69
|
-
origin_channel: task.origin_channel,
|
|
70
|
-
session_id: task.session_id,
|
|
71
|
-
origin_message_id: task.origin_message_id ?? undefined,
|
|
72
|
-
origin_user_id: task.origin_user_id ?? undefined,
|
|
73
|
-
});
|
|
74
|
-
break;
|
|
75
|
-
}
|
|
76
|
-
case 'neo': {
|
|
77
|
-
const neo = Neo.getInstance();
|
|
78
|
-
result = await neo.execute(task.input, task.context ?? undefined, task.session_id, {
|
|
79
|
-
origin_channel: task.origin_channel,
|
|
80
|
-
session_id: task.session_id,
|
|
81
|
-
origin_message_id: task.origin_message_id ?? undefined,
|
|
82
|
-
origin_user_id: task.origin_user_id ?? undefined,
|
|
83
|
-
});
|
|
84
|
-
break;
|
|
85
|
-
}
|
|
86
|
-
case 'trinit': {
|
|
87
|
-
const trinity = Trinity.getInstance();
|
|
88
|
-
result = await trinity.execute(task.input, task.context ?? undefined, task.session_id, {
|
|
89
|
-
origin_channel: task.origin_channel,
|
|
90
|
-
session_id: task.session_id,
|
|
91
|
-
origin_message_id: task.origin_message_id ?? undefined,
|
|
92
|
-
origin_user_id: task.origin_user_id ?? undefined,
|
|
93
|
-
});
|
|
94
|
-
break;
|
|
95
|
-
}
|
|
96
|
-
case 'smith': {
|
|
97
|
-
// Parse smith name from context JSON
|
|
98
|
-
let smithName = 'unknown';
|
|
99
|
-
if (task.context) {
|
|
100
|
-
try {
|
|
101
|
-
const parsed = JSON.parse(task.context);
|
|
102
|
-
smithName = parsed.smith_name || parsed.smith || 'unknown';
|
|
103
|
-
}
|
|
104
|
-
catch {
|
|
105
|
-
smithName = task.context;
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
const delegator = SmithDelegator.getInstance();
|
|
109
|
-
result = await delegator.delegate(smithName, task.input, task.context ?? undefined);
|
|
110
|
-
break;
|
|
111
|
-
}
|
|
112
|
-
case 'link': {
|
|
113
|
-
const link = Link.getInstance();
|
|
114
|
-
result = await link.execute(task.input, task.context ?? undefined, task.session_id, {
|
|
115
|
-
origin_channel: task.origin_channel,
|
|
116
|
-
session_id: task.session_id,
|
|
117
|
-
origin_message_id: task.origin_message_id ?? undefined,
|
|
118
|
-
origin_user_id: task.origin_user_id ?? undefined,
|
|
119
|
-
});
|
|
120
|
-
break;
|
|
121
|
-
}
|
|
122
|
-
default: {
|
|
123
|
-
throw new Error(`Unknown task agent: ${task.agent}`);
|
|
124
|
-
}
|
|
125
|
-
}
|
|
61
|
+
const result = await SubagentRegistry.executeTask(task);
|
|
126
62
|
this.repository.markCompleted(task.id, result.output, result.usage ? {
|
|
127
63
|
provider: result.usage.provider,
|
|
128
64
|
model: result.usage.model,
|
|
@@ -131,7 +67,7 @@ export class TaskWorker {
|
|
|
131
67
|
durationMs: result.usage.durationMs,
|
|
132
68
|
stepCount: result.usage.stepCount,
|
|
133
69
|
} : undefined);
|
|
134
|
-
const agentName =
|
|
70
|
+
const agentName = auditAgent;
|
|
135
71
|
// Emit task_completed audit event
|
|
136
72
|
audit.insert({
|
|
137
73
|
session_id: task.session_id,
|
|
@@ -175,7 +111,7 @@ export class TaskWorker {
|
|
|
175
111
|
session_id: task.session_id,
|
|
176
112
|
task_id: task.id,
|
|
177
113
|
event_type: 'task_completed',
|
|
178
|
-
agent:
|
|
114
|
+
agent: auditAgent,
|
|
179
115
|
status: 'error',
|
|
180
116
|
metadata: { error: errorMessage },
|
|
181
117
|
});
|
|
@@ -2,6 +2,9 @@ import { GoogleGenAI } from '@google/genai';
|
|
|
2
2
|
import OpenAI from 'openai';
|
|
3
3
|
import { OpenRouter } from '@openrouter/sdk';
|
|
4
4
|
import fs from 'fs';
|
|
5
|
+
import fsExtra from 'fs-extra';
|
|
6
|
+
import path from 'path';
|
|
7
|
+
import os from 'os';
|
|
5
8
|
import { parseFile } from 'music-metadata';
|
|
6
9
|
/**
|
|
7
10
|
* Returns the actual audio duration in seconds by parsing the file header.
|
|
@@ -25,6 +28,7 @@ async function getAudioDurationSeconds(filePath) {
|
|
|
25
28
|
return 0;
|
|
26
29
|
}
|
|
27
30
|
}
|
|
31
|
+
export const TTS_MAX_CHARS = 4096;
|
|
28
32
|
class GeminiTelephonist {
|
|
29
33
|
model;
|
|
30
34
|
constructor(model) {
|
|
@@ -187,6 +191,162 @@ export function createTelephonist(config) {
|
|
|
187
191
|
throw new Error(`Unsupported audio provider: '${config.provider}'. Supported: google, openai, openrouter, ollama.`);
|
|
188
192
|
}
|
|
189
193
|
}
|
|
194
|
+
// ─── TTS Implementations ─────────────────────────────────────────────────────
|
|
195
|
+
function truncateForTts(text) {
|
|
196
|
+
if (text.length <= TTS_MAX_CHARS)
|
|
197
|
+
return text;
|
|
198
|
+
console.warn(`[Telephonist] TTS input truncated from ${text.length} to ${TTS_MAX_CHARS} chars.`);
|
|
199
|
+
return text.slice(0, TTS_MAX_CHARS);
|
|
200
|
+
}
|
|
201
|
+
function mimeTypeToExt(mimeType) {
|
|
202
|
+
if (mimeType.includes('ogg'))
|
|
203
|
+
return '.ogg';
|
|
204
|
+
if (mimeType.includes('mp3') || mimeType.includes('mpeg'))
|
|
205
|
+
return '.mp3';
|
|
206
|
+
if (mimeType.includes('wav'))
|
|
207
|
+
return '.wav';
|
|
208
|
+
if (mimeType.includes('aac'))
|
|
209
|
+
return '.aac';
|
|
210
|
+
return '.audio';
|
|
211
|
+
}
|
|
212
|
+
async function writeTempAudio(buffer, ext) {
|
|
213
|
+
const filePath = path.join(os.tmpdir(), `morpheus-tts-${Date.now()}${ext}`);
|
|
214
|
+
await fsExtra.writeFile(filePath, buffer);
|
|
215
|
+
return filePath;
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Wraps raw PCM data in a WAV container header.
|
|
219
|
+
* Gemini TTS returns audio/pcm at 24000Hz, 16-bit, mono.
|
|
220
|
+
*/
|
|
221
|
+
function pcmToWav(pcmBuffer, sampleRate = 24000, channels = 1, bitDepth = 16) {
|
|
222
|
+
const header = Buffer.alloc(44);
|
|
223
|
+
const dataSize = pcmBuffer.length;
|
|
224
|
+
const byteRate = sampleRate * channels * (bitDepth / 8);
|
|
225
|
+
const blockAlign = channels * (bitDepth / 8);
|
|
226
|
+
header.write('RIFF', 0);
|
|
227
|
+
header.writeUInt32LE(36 + dataSize, 4);
|
|
228
|
+
header.write('WAVE', 8);
|
|
229
|
+
header.write('fmt ', 12);
|
|
230
|
+
header.writeUInt32LE(16, 16); // PCM chunk size
|
|
231
|
+
header.writeUInt16LE(1, 20); // PCM format
|
|
232
|
+
header.writeUInt16LE(channels, 22);
|
|
233
|
+
header.writeUInt32LE(sampleRate, 24);
|
|
234
|
+
header.writeUInt32LE(byteRate, 28);
|
|
235
|
+
header.writeUInt16LE(blockAlign, 32);
|
|
236
|
+
header.writeUInt16LE(bitDepth, 34);
|
|
237
|
+
header.write('data', 36);
|
|
238
|
+
header.writeUInt32LE(dataSize, 40);
|
|
239
|
+
return Buffer.concat([header, pcmBuffer]);
|
|
240
|
+
}
|
|
241
|
+
class OpenAITtsTelephonist {
|
|
242
|
+
model;
|
|
243
|
+
defaultVoice;
|
|
244
|
+
constructor(model, defaultVoice) {
|
|
245
|
+
this.model = model;
|
|
246
|
+
this.defaultVoice = defaultVoice;
|
|
247
|
+
}
|
|
248
|
+
async transcribe() {
|
|
249
|
+
throw new Error('OpenAITtsTelephonist does not support transcription.');
|
|
250
|
+
}
|
|
251
|
+
async synthesize(text, apiKey, voice, stylePrompt) {
|
|
252
|
+
const client = new OpenAI({ apiKey });
|
|
253
|
+
const raw = stylePrompt ? `${stylePrompt}: ${text}` : text;
|
|
254
|
+
const input = truncateForTts(raw);
|
|
255
|
+
const response = await client.audio.speech.create({
|
|
256
|
+
model: this.model,
|
|
257
|
+
voice: (voice || this.defaultVoice),
|
|
258
|
+
input,
|
|
259
|
+
response_format: 'mp3',
|
|
260
|
+
});
|
|
261
|
+
const buffer = Buffer.from(await response.arrayBuffer());
|
|
262
|
+
const filePath = await writeTempAudio(buffer, '.mp3');
|
|
263
|
+
return {
|
|
264
|
+
filePath,
|
|
265
|
+
mimeType: 'audio/mpeg',
|
|
266
|
+
usage: {
|
|
267
|
+
input_tokens: 0,
|
|
268
|
+
output_tokens: 0,
|
|
269
|
+
total_tokens: 0,
|
|
270
|
+
audio_duration_seconds: 0,
|
|
271
|
+
},
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
class GeminiTtsTelephonist {
|
|
276
|
+
model;
|
|
277
|
+
defaultVoice;
|
|
278
|
+
constructor(model, defaultVoice) {
|
|
279
|
+
this.model = model;
|
|
280
|
+
this.defaultVoice = defaultVoice;
|
|
281
|
+
}
|
|
282
|
+
async transcribe() {
|
|
283
|
+
throw new Error('GeminiTtsTelephonist does not support transcription.');
|
|
284
|
+
}
|
|
285
|
+
async synthesize(text, apiKey, voice, stylePrompt) {
|
|
286
|
+
const ai = new GoogleGenAI({ apiKey });
|
|
287
|
+
const raw = stylePrompt ? `${stylePrompt}: ${text}` : text;
|
|
288
|
+
const input = truncateForTts(raw);
|
|
289
|
+
const response = await ai.models.generateContent({
|
|
290
|
+
model: this.model,
|
|
291
|
+
contents: [{ role: 'user', parts: [{ text: input }] }],
|
|
292
|
+
config: {
|
|
293
|
+
responseModalities: ['AUDIO'],
|
|
294
|
+
speechConfig: {
|
|
295
|
+
voiceConfig: {
|
|
296
|
+
prebuiltVoiceConfig: { voiceName: voice || this.defaultVoice },
|
|
297
|
+
},
|
|
298
|
+
},
|
|
299
|
+
},
|
|
300
|
+
});
|
|
301
|
+
const audioPart = response.candidates?.[0]?.content?.parts?.find((p) => p.inlineData?.mimeType?.startsWith('audio/'));
|
|
302
|
+
if (!audioPart?.inlineData?.data) {
|
|
303
|
+
throw new Error('Gemini TTS: no audio data in response');
|
|
304
|
+
}
|
|
305
|
+
const rawMimeType = audioPart.inlineData.mimeType ?? 'audio/pcm';
|
|
306
|
+
const rawBuffer = Buffer.from(audioPart.inlineData.data, 'base64');
|
|
307
|
+
let mimeType = rawMimeType;
|
|
308
|
+
let buffer;
|
|
309
|
+
// Gemini returns raw PCM — wrap it in a WAV container
|
|
310
|
+
if (rawMimeType.includes('pcm') || rawMimeType.includes('l16')) {
|
|
311
|
+
// Parse sample rate from mimeType params e.g. "audio/pcm;rate=24000"
|
|
312
|
+
const rateMatch = rawMimeType.match(/rate=(\d+)/i);
|
|
313
|
+
const sampleRate = rateMatch ? parseInt(rateMatch[1], 10) : 24000;
|
|
314
|
+
buffer = pcmToWav(rawBuffer, sampleRate);
|
|
315
|
+
mimeType = 'audio/wav';
|
|
316
|
+
}
|
|
317
|
+
else {
|
|
318
|
+
buffer = rawBuffer;
|
|
319
|
+
}
|
|
320
|
+
const ext = mimeTypeToExt(mimeType);
|
|
321
|
+
const filePath = await writeTempAudio(buffer, ext);
|
|
322
|
+
const usage = response.usageMetadata;
|
|
323
|
+
return {
|
|
324
|
+
filePath,
|
|
325
|
+
mimeType,
|
|
326
|
+
usage: {
|
|
327
|
+
input_tokens: usage?.promptTokenCount ?? 0,
|
|
328
|
+
output_tokens: usage?.candidatesTokenCount ?? 0,
|
|
329
|
+
total_tokens: usage?.totalTokenCount ?? 0,
|
|
330
|
+
audio_duration_seconds: 0,
|
|
331
|
+
},
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
/**
|
|
336
|
+
* Factory that creates an ITelephonist with TTS (synthesize) support.
|
|
337
|
+
* Supports providers: openai, google.
|
|
338
|
+
*/
|
|
339
|
+
export function createTtsTelephonist(config) {
|
|
340
|
+
switch (config.provider) {
|
|
341
|
+
case 'openai':
|
|
342
|
+
return new OpenAITtsTelephonist(config.model, config.voice);
|
|
343
|
+
case 'google':
|
|
344
|
+
return new GeminiTtsTelephonist(config.model, config.voice);
|
|
345
|
+
default:
|
|
346
|
+
throw new Error(`Unsupported TTS provider: '${config.provider}'. Supported: openai, google.`);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
// ─── Legacy export for backward compatibility ─────────────────────────────────
|
|
190
350
|
// Legacy export for backward compatibility
|
|
191
351
|
export class Telephonist {
|
|
192
352
|
delegate;
|
|
@@ -40,6 +40,7 @@ export const ChronosScheduleTool = tool(async ({ prompt, schedule_type, schedule
|
|
|
40
40
|
cron_normalized: parsed.cron_normalized,
|
|
41
41
|
created_by: 'oracle',
|
|
42
42
|
notify_channels: channels,
|
|
43
|
+
origin_session_id: TaskRequestContext.get()?.session_id,
|
|
43
44
|
});
|
|
44
45
|
return JSON.stringify({
|
|
45
46
|
success: true,
|
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
import { tool } from "@langchain/core/tools";
|
|
2
2
|
import { z } from "zod";
|
|
3
|
-
import { TaskRepository } from "../tasks/repository.js";
|
|
4
3
|
import { TaskRequestContext } from "../tasks/context.js";
|
|
5
4
|
import { compositeDelegationError, isLikelyCompositeDelegationTask } from "./delegation-guard.js";
|
|
6
5
|
import { DisplayManager } from "../display.js";
|
|
7
|
-
import {
|
|
8
|
-
import { AuditRepository } from "../audit/repository.js";
|
|
6
|
+
import { ServiceContainer, SERVICE_KEYS } from "../container.js";
|
|
9
7
|
/**
|
|
10
8
|
* Factory that builds a delegation StructuredTool for Apoc/Neo/Trinity.
|
|
11
9
|
* Handles: composite guard, sync branch (notify→execute→audit→increment),
|
|
@@ -33,7 +31,8 @@ export function buildDelegationTool(opts) {
|
|
|
33
31
|
const ctx = TaskRequestContext.get();
|
|
34
32
|
const sessionId = ctx?.session_id ?? "default";
|
|
35
33
|
if (ctx?.origin_channel && ctx.origin_user_id && ctx.origin_channel !== 'api' && ctx.origin_channel !== 'ui') {
|
|
36
|
-
|
|
34
|
+
ServiceContainer.get(SERVICE_KEYS.notifier)
|
|
35
|
+
.sendToUser(ctx.origin_channel, ctx.origin_user_id, notifyText)
|
|
37
36
|
.catch(() => { });
|
|
38
37
|
}
|
|
39
38
|
try {
|
|
@@ -41,7 +40,7 @@ export function buildDelegationTool(opts) {
|
|
|
41
40
|
TaskRequestContext.incrementSyncDelegation();
|
|
42
41
|
display.log(`${agentLabel} sync execution completed.`, { source, level: "info" });
|
|
43
42
|
if (result.usage) {
|
|
44
|
-
|
|
43
|
+
ServiceContainer.get(SERVICE_KEYS.auditEmitter).emit({
|
|
45
44
|
session_id: sessionId,
|
|
46
45
|
event_type: 'llm_call',
|
|
47
46
|
agent: auditAgent,
|
|
@@ -76,8 +75,7 @@ export function buildDelegationTool(opts) {
|
|
|
76
75
|
return "Delegation limit reached for this user turn. Split the request or wait for current tasks.";
|
|
77
76
|
}
|
|
78
77
|
const ctx = TaskRequestContext.get();
|
|
79
|
-
const
|
|
80
|
-
const created = repository.createTask({
|
|
78
|
+
const created = ServiceContainer.get(SERVICE_KEYS.taskEnqueuer).enqueue({
|
|
81
79
|
agent: agentKey,
|
|
82
80
|
input: task,
|
|
83
81
|
context: context ?? null,
|
|
@@ -2,14 +2,13 @@ import { tool } from "@langchain/core/tools";
|
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import { ConfigManager } from "../../config/manager.js";
|
|
4
4
|
import { promises as fsPromises } from "fs";
|
|
5
|
-
import path from "path";
|
|
6
|
-
import { homedir } from "os";
|
|
7
5
|
import Database from "better-sqlite3";
|
|
6
|
+
import { PATHS } from "../../config/paths.js";
|
|
8
7
|
import { TaskRepository } from "../tasks/repository.js";
|
|
9
8
|
import { TaskRequestContext } from "../tasks/context.js";
|
|
10
9
|
import { isEnvVarSet } from "../../config/precedence.js";
|
|
11
10
|
// ─── Shared ───────────────────────────────────────────────────────────────────
|
|
12
|
-
const shortMemoryDbPath =
|
|
11
|
+
const shortMemoryDbPath = PATHS.shortMemoryDb;
|
|
13
12
|
/**
|
|
14
13
|
* Map of config paths to their corresponding environment variable names.
|
|
15
14
|
* Used to check if a config field is being overridden by an env var.
|
|
@@ -145,7 +144,7 @@ export const DiagnosticTool = tool(async () => {
|
|
|
145
144
|
try {
|
|
146
145
|
const timestamp = new Date().toISOString();
|
|
147
146
|
const components = {};
|
|
148
|
-
const morpheusRoot =
|
|
147
|
+
const morpheusRoot = PATHS.root;
|
|
149
148
|
// Configuration
|
|
150
149
|
try {
|
|
151
150
|
const configManager = ConfigManager.getInstance();
|
|
@@ -198,7 +197,7 @@ export const DiagnosticTool = tool(async () => {
|
|
|
198
197
|
}
|
|
199
198
|
// Short-term memory DB
|
|
200
199
|
try {
|
|
201
|
-
const dbPath =
|
|
200
|
+
const dbPath = PATHS.shortMemoryDb;
|
|
202
201
|
await fsPromises.access(dbPath);
|
|
203
202
|
const stat = await fsPromises.stat(dbPath);
|
|
204
203
|
components.shortMemoryDb = {
|
|
@@ -216,7 +215,7 @@ export const DiagnosticTool = tool(async () => {
|
|
|
216
215
|
}
|
|
217
216
|
// Sati long-term memory DB
|
|
218
217
|
try {
|
|
219
|
-
const satiDbPath =
|
|
218
|
+
const satiDbPath = PATHS.satiDb;
|
|
220
219
|
await fsPromises.access(satiDbPath);
|
|
221
220
|
const stat = await fsPromises.stat(satiDbPath);
|
|
222
221
|
components.satiMemoryDb = {
|
|
@@ -266,7 +265,7 @@ export const DiagnosticTool = tool(async () => {
|
|
|
266
265
|
};
|
|
267
266
|
// Logs directory
|
|
268
267
|
try {
|
|
269
|
-
const logsDir =
|
|
268
|
+
const logsDir = PATHS.logs;
|
|
270
269
|
await fsPromises.access(logsDir);
|
|
271
270
|
components.logs = {
|
|
272
271
|
status: "healthy",
|
|
@@ -762,7 +761,7 @@ export const TrinityDbManageTool = tool(async ({ action, name, id, type, host, p
|
|
|
762
761
|
const db = registry.getDatabase(dbId);
|
|
763
762
|
if (!db)
|
|
764
763
|
return JSON.stringify({ error: `Database "${name}" not found` });
|
|
765
|
-
const { testConnection } = await import("../trinity
|
|
764
|
+
const { testConnection } = await import("../subagents/trinity/connector.js");
|
|
766
765
|
const ok = await testConnection(db);
|
|
767
766
|
return JSON.stringify({ status: ok ? "connected" : "failed", database: db.name });
|
|
768
767
|
}
|
|
@@ -771,10 +770,10 @@ export const TrinityDbManageTool = tool(async ({ action, name, id, type, host, p
|
|
|
771
770
|
const db = registry.getDatabase(dbId);
|
|
772
771
|
if (!db)
|
|
773
772
|
return JSON.stringify({ error: `Database "${name}" not found` });
|
|
774
|
-
const { introspectSchema } = await import("../trinity
|
|
773
|
+
const { introspectSchema } = await import("../subagents/trinity/connector.js");
|
|
775
774
|
const schema = await introspectSchema(db);
|
|
776
775
|
registry.updateSchema(dbId, JSON.stringify(schema));
|
|
777
|
-
const { Trinity } = await import("../trinity.js");
|
|
776
|
+
const { Trinity } = await import("../subagents/trinity/trinity.js");
|
|
778
777
|
await Trinity.refreshDelegateCatalog();
|
|
779
778
|
return JSON.stringify({ success: true, message: `Schema refreshed for "${db.name}"` });
|
|
780
779
|
}
|