morpheus-cli 0.9.20 → 0.9.23
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/dist/channels/discord.js +5 -0
- package/dist/channels/telegram.js +5 -0
- package/dist/http/api.js +3 -0
- package/dist/http/middleware/auth.js +2 -1
- package/dist/http/routers/display.js +38 -0
- package/dist/runtime/audit/repository.js +69 -1
- package/dist/runtime/chronos/worker.js +2 -0
- package/dist/runtime/display.js +49 -2
- package/dist/runtime/memory/sati/service.js +5 -0
- package/dist/runtime/oracle.js +5 -0
- package/dist/runtime/providers/factory.js +14 -2
- package/dist/runtime/smiths/delegator.js +3 -0
- package/dist/runtime/subagents/devkit-instrument.js +5 -0
- package/dist/runtime/subagents/link/link.js +120 -77
- package/dist/runtime/subagents/trinity/trinity.js +64 -34
- package/dist/runtime/tools/factory.js +6 -2
- package/dist/ui/assets/{AuditDashboard-Cu33zb_7.js → AuditDashboard-ClqEr7jg.js} +1 -1
- package/dist/ui/assets/{Chat-mt1j5V55.js → Chat-BwxZJphx.js} +7 -7
- package/dist/ui/assets/{Chronos-Bq_h41cw.js → Chronos-BafOMteb.js} +1 -1
- package/dist/ui/assets/{ConfirmationModal-CxLP8iC6.js → ConfirmationModal-DU0AwhXD.js} +1 -1
- package/dist/ui/assets/Dashboard-DvJb72Xe.js +4120 -0
- package/dist/ui/assets/{DeleteConfirmationModal-kZ_c3sFk.js → DeleteConfirmationModal-FSWLK6-I.js} +1 -1
- package/dist/ui/assets/{Documents-nlQNoUcq.js → Documents-D73CeGkW.js} +1 -1
- package/dist/ui/assets/{Logs-C1tlg574.js → Logs-BrFWnLIL.js} +1 -1
- package/dist/ui/assets/{MCPManager-Do7isizG.js → MCPManager-_L2Yo-uY.js} +1 -1
- package/dist/ui/assets/{ModelPricing-BeJ7oXBA.js → ModelPricing-CyXMdxJD.js} +1 -1
- package/dist/ui/assets/Notifications-BpHokTLS.js +1 -0
- package/dist/ui/assets/{Pagination-BHZKk42X.js → Pagination-D4ShqUKO.js} +1 -1
- package/dist/ui/assets/SatiMemories-CfSTgr9V.js +1 -0
- package/dist/ui/assets/{SelectInput-KVLsnfra.js → SelectInput-BPDcd3y7.js} +1 -1
- package/dist/ui/assets/{SessionAudit-Da1ySlYg.js → SessionAudit-pOWRgJtc.js} +1 -1
- package/dist/ui/assets/{Settings-DpXwpEhO.js → Settings-CPDXAk18.js} +1 -1
- package/dist/ui/assets/Skills-GIkCxMS3.js +7 -0
- package/dist/ui/assets/{Smiths-DA-x4KFT.js → Smiths-ZcHXcrMt.js} +1 -1
- package/dist/ui/assets/{Switch-CJTE4ZQm.js → Switch-C7TxLq0E.js} +1 -1
- package/dist/ui/assets/Tasks-DJ6R3d4f.js +1 -0
- package/dist/ui/assets/{TrinityDatabases-CoKzKTL-.js → TrinityDatabases-CnRAkDuu.js} +1 -1
- package/dist/ui/assets/{UsageStats-cds352Pj.js → UsageStats-Bl7bs4ay.js} +1 -1
- package/dist/ui/assets/{WebhookManager-DdAdHQUk.js → WebhookManager-CEjjk4tx.js} +2 -2
- package/dist/ui/assets/{agents-B1z_dlQC.js → agents-DO69pNM1.js} +1 -1
- package/dist/ui/assets/{audit-BAhaGrKY.js → audit-CP5fC4m8.js} +1 -1
- package/dist/ui/assets/{chronos-DGD_Md9M.js → chronos-DPhK718h.js} +1 -1
- package/dist/ui/assets/{config-BwTXe5M2.js → config-OLGQFNJL.js} +1 -1
- package/dist/ui/assets/{index-BcX5O7kY.js → index-B9ePr-vB.js} +3 -3
- package/dist/ui/assets/index-D_0tPLCk.css +1 -0
- package/dist/ui/assets/{mcp-BlkruPaA.js → mcp-BeBznKtK.js} +1 -1
- package/dist/ui/assets/{skills-CtCb-52u.js → skills-wEUxSGB3.js} +1 -1
- package/dist/ui/assets/{stats-BiPI2kaw.js → stats-KMbKDMJ-.js} +1 -1
- package/dist/ui/assets/{useCurrency-BCdG-pHx.js → useCurrency-Bgg-7MTE.js} +1 -1
- package/dist/ui/assets/vendor-icons-DE7PWdkN.js +1 -0
- package/dist/ui/assets/vendor-utils-BIYveU_1.js +39 -0
- package/dist/ui/index.html +4 -4
- package/dist/ui/sw.js +1 -1
- package/package.json +1 -1
- package/dist/ui/assets/Dashboard-D0LAlHtG.js +0 -1
- package/dist/ui/assets/Notifications-Cg5CMlY0.js +0 -1
- package/dist/ui/assets/SatiMemories-D9l6s8Pc.js +0 -1
- package/dist/ui/assets/Skills-DaqCY8QH.js +0 -7
- package/dist/ui/assets/Tasks-DU49M9U-.js +0 -1
- package/dist/ui/assets/index-Cjli-AD7.css +0 -1
- package/dist/ui/assets/vendor-icons-NHF9HNeN.js +0 -1
- package/dist/ui/assets/vendor-utils-D4NnWbOU.js +0 -39
package/dist/channels/discord.js
CHANGED
|
@@ -308,7 +308,9 @@ export class DiscordAdapter {
|
|
|
308
308
|
filePath = await this.downloadAudioToTemp(attachment.url, contentType);
|
|
309
309
|
// Transcribe
|
|
310
310
|
this.display.log(`Transcribing audio for ${message.author.tag}...`, { source: 'Telephonist' });
|
|
311
|
+
this.display.startActivity('telephonist', 'Transcribing audio...');
|
|
311
312
|
const { text, usage } = await this.telephonist.transcribe(filePath, contentType, apiKey);
|
|
313
|
+
this.display.endActivity('telephonist', true);
|
|
312
314
|
this.display.log(`Transcription for ${message.author.tag}: "${text}"`, { source: 'Telephonist', level: 'success' });
|
|
313
315
|
// Show transcription
|
|
314
316
|
await channel.send(`🎤 "${text}"`);
|
|
@@ -326,6 +328,7 @@ export class DiscordAdapter {
|
|
|
326
328
|
let ttsFilePath = null;
|
|
327
329
|
const ttsStart = Date.now();
|
|
328
330
|
try {
|
|
331
|
+
this.display.startActivity('telephonist', 'Synthesizing TTS...');
|
|
329
332
|
const ttsApiKey = getUsableApiKey(ttsConfig.apiKey) ||
|
|
330
333
|
getUsableApiKey(config.audio.apiKey) ||
|
|
331
334
|
(config.llm.provider === (ttsConfig.provider === 'google' ? 'gemini' : ttsConfig.provider)
|
|
@@ -333,6 +336,7 @@ export class DiscordAdapter {
|
|
|
333
336
|
const ttsResult = await this.ttsTelephonist.synthesize(response, ttsApiKey || '', ttsConfig.voice, ttsConfig.style_prompt);
|
|
334
337
|
ttsFilePath = ttsResult.filePath;
|
|
335
338
|
const ttsDurationMs = Date.now() - ttsStart;
|
|
339
|
+
this.display.endActivity('telephonist', true);
|
|
336
340
|
const attachment = new AttachmentBuilder(ttsFilePath, { name: 'response.ogg' });
|
|
337
341
|
await channel.send({ files: [attachment] });
|
|
338
342
|
this.display.log(`Responded to ${message.author.tag} (TTS audio)`, { source: 'Discord' });
|
|
@@ -362,6 +366,7 @@ export class DiscordAdapter {
|
|
|
362
366
|
}
|
|
363
367
|
}
|
|
364
368
|
catch (ttsError) {
|
|
369
|
+
this.display.endActivity('telephonist', false);
|
|
365
370
|
const ttsDetail = ttsError?.message || String(ttsError);
|
|
366
371
|
this.display.log(`TTS synthesis failed for ${message.author.tag}: ${ttsDetail} — falling back to text`, { source: 'Telephonist', level: 'warning' });
|
|
367
372
|
// Audit TTS failure
|
|
@@ -352,9 +352,11 @@ export class TelegramAdapter {
|
|
|
352
352
|
filePath = await this.downloadToTemp(fileLink);
|
|
353
353
|
// Transcribe
|
|
354
354
|
this.display.log(`Transcribing audio for @${user}...`, { source: 'Telephonist' });
|
|
355
|
+
this.display.startActivity('telephonist', 'Transcribing audio...');
|
|
355
356
|
const transcribeStart = Date.now();
|
|
356
357
|
const { text, usage } = await this.telephonist.transcribe(filePath, 'audio/ogg', apiKey);
|
|
357
358
|
const transcribeDurationMs = Date.now() - transcribeStart;
|
|
359
|
+
this.display.endActivity('telephonist', true);
|
|
358
360
|
this.display.log(`Transcription success for @${user}: "${text}"`, { source: 'Telephonist', level: 'success' });
|
|
359
361
|
// Audit: record telephonist execution
|
|
360
362
|
try {
|
|
@@ -408,6 +410,7 @@ export class TelegramAdapter {
|
|
|
408
410
|
let ttsFilePath = null;
|
|
409
411
|
const ttsStart = Date.now();
|
|
410
412
|
try {
|
|
413
|
+
this.display.startActivity('telephonist', 'Synthesizing TTS...');
|
|
411
414
|
const ttsApiKey = getUsableApiKey(ttsConfig.apiKey) ||
|
|
412
415
|
getUsableApiKey(config.audio.apiKey) ||
|
|
413
416
|
(config.llm.provider === (ttsConfig.provider === 'google' ? 'gemini' : ttsConfig.provider)
|
|
@@ -415,6 +418,7 @@ export class TelegramAdapter {
|
|
|
415
418
|
const ttsResult = await this.ttsTelephonist.synthesize(response, ttsApiKey || '', ttsConfig.voice, ttsConfig.style_prompt);
|
|
416
419
|
ttsFilePath = ttsResult.filePath;
|
|
417
420
|
const ttsDurationMs = Date.now() - ttsStart;
|
|
421
|
+
this.display.endActivity('telephonist', true);
|
|
418
422
|
// OGG/Opus → replyWithVoice; everything else (mp3, wav) → replyWithAudio
|
|
419
423
|
const isOgg = ttsResult.mimeType.includes('ogg') || ttsResult.mimeType.includes('opus');
|
|
420
424
|
if (isOgg) {
|
|
@@ -450,6 +454,7 @@ export class TelegramAdapter {
|
|
|
450
454
|
}
|
|
451
455
|
}
|
|
452
456
|
catch (ttsError) {
|
|
457
|
+
this.display.endActivity('telephonist', false);
|
|
453
458
|
const ttsDetail = ttsError?.message || String(ttsError);
|
|
454
459
|
this.display.log(`TTS synthesis failed for @${user}: ${ttsDetail} — falling back to text`, { source: 'Telephonist', level: 'warning' });
|
|
455
460
|
// Audit TTS failure
|
package/dist/http/api.js
CHANGED
|
@@ -23,6 +23,7 @@ import { createSmithsRouter } from './routers/smiths.js';
|
|
|
23
23
|
import { createDangerRouter } from './routers/danger.js';
|
|
24
24
|
import { createLinkRouter } from './routers/link.js';
|
|
25
25
|
import { createAgentsRouter } from './routers/agents.js';
|
|
26
|
+
import { createDisplayRouter } from './routers/display.js';
|
|
26
27
|
import { getActiveEnvOverrides } from '../config/precedence.js';
|
|
27
28
|
import { hotReloadConfig, getRestartRequiredChanges } from '../runtime/hot-reload.js';
|
|
28
29
|
import { AuditRepository } from '../runtime/audit/repository.js';
|
|
@@ -58,6 +59,8 @@ export function createApiRouter(oracle, chronosWorker) {
|
|
|
58
59
|
router.use('/link', createLinkRouter());
|
|
59
60
|
// Mount Agents metadata router
|
|
60
61
|
router.use('/agents', createAgentsRouter());
|
|
62
|
+
// Mount Display Stream router
|
|
63
|
+
router.use('/display', createDisplayRouter());
|
|
61
64
|
// --- Session Management ---
|
|
62
65
|
router.get('/sessions', async (req, res) => {
|
|
63
66
|
try {
|
|
@@ -12,7 +12,8 @@ export const authMiddleware = (req, res, next) => {
|
|
|
12
12
|
const display = DisplayManager.getInstance();
|
|
13
13
|
// display.log('Using default password for dashboard access. For security, set THE_ARCHITECT_PASS environment variable.', { source: 'http', level: 'warning' });
|
|
14
14
|
}
|
|
15
|
-
|
|
15
|
+
// Fallback to query param 'token' for EventSource and WebSockets which can't easily set headers
|
|
16
|
+
const providedPass = req.headers[AUTH_HEADER] || req.query.token;
|
|
16
17
|
if (providedPass === architectPass) {
|
|
17
18
|
return next();
|
|
18
19
|
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { Router } from 'express';
|
|
2
|
+
import { DisplayManager } from '../../runtime/display.js';
|
|
3
|
+
export function createDisplayRouter() {
|
|
4
|
+
const router = Router();
|
|
5
|
+
const display = DisplayManager.getInstance();
|
|
6
|
+
router.get('/stream', (req, res) => {
|
|
7
|
+
res.setHeader('Content-Type', 'text/event-stream');
|
|
8
|
+
res.setHeader('Cache-Control', 'no-cache');
|
|
9
|
+
res.setHeader('Connection', 'keep-alive');
|
|
10
|
+
// Flush headers to establish SSE instantly
|
|
11
|
+
res.flushHeaders?.();
|
|
12
|
+
// Send initial ping to confirm connection
|
|
13
|
+
res.write(`data: ${JSON.stringify({ type: 'connected', timestamp: Date.now() })}\n\n`);
|
|
14
|
+
const onActivityStart = (payload) => {
|
|
15
|
+
res.write(`data: ${JSON.stringify({ type: 'activity_start', ...payload })}\n\n`);
|
|
16
|
+
};
|
|
17
|
+
const onActivityEnd = (payload) => {
|
|
18
|
+
res.write(`data: ${JSON.stringify({ type: 'activity_end', ...payload })}\n\n`);
|
|
19
|
+
};
|
|
20
|
+
const onMessage = (payload) => {
|
|
21
|
+
res.write(`data: ${JSON.stringify({ type: 'message', ...payload })}\n\n`);
|
|
22
|
+
};
|
|
23
|
+
const onMessageSent = (payload) => {
|
|
24
|
+
res.write(`data: ${JSON.stringify({ type: 'message_sent', ...payload })}\n\n`);
|
|
25
|
+
};
|
|
26
|
+
display.on('activity_start', onActivityStart);
|
|
27
|
+
display.on('activity_end', onActivityEnd);
|
|
28
|
+
display.on('message', onMessage);
|
|
29
|
+
display.on('message_sent', onMessageSent);
|
|
30
|
+
req.on('close', () => {
|
|
31
|
+
display.off('activity_start', onActivityStart);
|
|
32
|
+
display.off('activity_end', onActivityEnd);
|
|
33
|
+
display.off('message', onMessage);
|
|
34
|
+
display.off('message_sent', onMessageSent);
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
return router;
|
|
38
|
+
}
|
|
@@ -46,19 +46,87 @@ export class AuditRepository {
|
|
|
46
46
|
`);
|
|
47
47
|
}
|
|
48
48
|
insert(event) {
|
|
49
|
+
const createdAt = Date.now();
|
|
50
|
+
const eventId = randomUUID();
|
|
49
51
|
try {
|
|
50
52
|
this.db.prepare(`
|
|
51
53
|
INSERT INTO audit_events
|
|
52
54
|
(id, session_id, task_id, event_type, agent, tool_name, provider, model,
|
|
53
55
|
input_tokens, output_tokens, duration_ms, status, metadata, created_at)
|
|
54
56
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
55
|
-
`).run(
|
|
57
|
+
`).run(eventId, event.session_id, event.task_id ?? null, event.event_type, event.agent ?? null, event.tool_name ?? null, event.provider ?? null, event.model ?? null, event.input_tokens ?? null, event.output_tokens ?? null, event.duration_ms ?? null, event.status ?? null, event.metadata ? JSON.stringify(event.metadata) : null, createdAt);
|
|
58
|
+
// Emit activity event for visualization when agent is working
|
|
59
|
+
this.emitActivityEvent(event, createdAt);
|
|
56
60
|
}
|
|
57
61
|
catch (err) {
|
|
58
62
|
// Non-critical — never let audit recording break the main flow
|
|
59
63
|
DisplayManager.getInstance().log(`AuditRepository.insert failed: ${err?.message ?? String(err)}`, { source: 'Audit', level: 'error' });
|
|
60
64
|
}
|
|
61
65
|
}
|
|
66
|
+
/**
|
|
67
|
+
* Emit activity events to DisplayManager for real-time visualization.
|
|
68
|
+
* This allows the frontend to show which agent is actively working.
|
|
69
|
+
*/
|
|
70
|
+
emitActivityEvent(event, timestamp) {
|
|
71
|
+
const display = DisplayManager.getInstance();
|
|
72
|
+
const agent = event.agent;
|
|
73
|
+
// Only emit for agent-specific events (not system events like task_created/completed)
|
|
74
|
+
const agentEventTypes = ['llm_call', 'tool_call', 'mcp_tool', 'telephonist', 'skill_loaded', 'chronos_job', 'memory_recovery', 'memory_persist'];
|
|
75
|
+
if (!agent || !agentEventTypes.includes(event.event_type)) {
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
// Build descriptive message based on event type
|
|
79
|
+
let message;
|
|
80
|
+
switch (event.event_type) {
|
|
81
|
+
case 'llm_call':
|
|
82
|
+
message = `LLM call (${event.model || event.provider || 'unknown'})`;
|
|
83
|
+
break;
|
|
84
|
+
case 'tool_call':
|
|
85
|
+
message = `Executing tool: ${event.tool_name || 'unknown'}`;
|
|
86
|
+
break;
|
|
87
|
+
case 'mcp_tool':
|
|
88
|
+
message = `MCP tool: ${event.tool_name || 'unknown'}`;
|
|
89
|
+
break;
|
|
90
|
+
case 'telephonist':
|
|
91
|
+
const op = event.metadata;
|
|
92
|
+
const operation = op?.operation;
|
|
93
|
+
message = operation === 'tts' ? 'Synthesizing TTS...' : 'Transcribing audio...';
|
|
94
|
+
break;
|
|
95
|
+
case 'skill_loaded':
|
|
96
|
+
message = `Loading skill: ${event.tool_name || 'unknown'}`;
|
|
97
|
+
break;
|
|
98
|
+
case 'chronos_job':
|
|
99
|
+
message = `Running scheduled job: ${event.tool_name || 'unknown'}`;
|
|
100
|
+
break;
|
|
101
|
+
case 'memory_recovery':
|
|
102
|
+
message = 'Recovering memories...';
|
|
103
|
+
break;
|
|
104
|
+
case 'memory_persist':
|
|
105
|
+
message = 'Persisting memories...';
|
|
106
|
+
break;
|
|
107
|
+
default:
|
|
108
|
+
message = `Working (${event.event_type})`;
|
|
109
|
+
}
|
|
110
|
+
// Emit activity_start with duration hint
|
|
111
|
+
// Frontend will use duration_ms to determine how long to keep agent active
|
|
112
|
+
display.emit('activity_start', {
|
|
113
|
+
agent: agent.toLowerCase(),
|
|
114
|
+
message,
|
|
115
|
+
timestamp,
|
|
116
|
+
duration_ms: event.duration_ms || 0,
|
|
117
|
+
event_type: event.event_type,
|
|
118
|
+
});
|
|
119
|
+
// Emit activity_end after duration (if duration_ms is provided)
|
|
120
|
+
if (event.duration_ms && event.duration_ms > 0) {
|
|
121
|
+
setTimeout(() => {
|
|
122
|
+
display.emit('activity_end', {
|
|
123
|
+
agent: agent.toLowerCase(),
|
|
124
|
+
timestamp: Date.now(),
|
|
125
|
+
duration_ms: event.duration_ms,
|
|
126
|
+
});
|
|
127
|
+
}, Math.min(event.duration_ms, 30000)); // Cap at 30s to avoid long-running timeouts
|
|
128
|
+
}
|
|
129
|
+
}
|
|
62
130
|
countBySession(sessionId) {
|
|
63
131
|
const row = this.db.prepare(`SELECT COUNT(*) as n FROM audit_events WHERE session_id = ?`).get(sessionId);
|
|
64
132
|
return row?.n ?? 0;
|
|
@@ -74,6 +74,7 @@ export class ChronosWorker {
|
|
|
74
74
|
async executeJob(job) {
|
|
75
75
|
const display = DisplayManager.getInstance();
|
|
76
76
|
const execId = randomUUID();
|
|
77
|
+
display.startActivity('chronos', 'Running scheduled job...');
|
|
77
78
|
display.log(`Job ${job.id} triggered — "${job.prompt.slice(0, 60)}"`, { source: 'Chronos' });
|
|
78
79
|
// Resolve session: prefer the session where the job was originally created,
|
|
79
80
|
// fall back to Oracle's current session, then to most recent active session.
|
|
@@ -150,6 +151,7 @@ export class ChronosWorker {
|
|
|
150
151
|
}
|
|
151
152
|
finally {
|
|
152
153
|
ChronosWorker.isExecuting = false;
|
|
154
|
+
display.endActivity('chronos', true);
|
|
153
155
|
if (job.schedule_type === 'once') {
|
|
154
156
|
this.repo.disableJob(job.id);
|
|
155
157
|
display.log(`Job ${job.id} auto-disabled (once-type)`, { source: 'Chronos' });
|
package/dist/runtime/display.js
CHANGED
|
@@ -2,12 +2,14 @@ import ora from 'ora';
|
|
|
2
2
|
import chalk from 'chalk';
|
|
3
3
|
import winston from 'winston';
|
|
4
4
|
import DailyRotateFile from 'winston-daily-rotate-file';
|
|
5
|
+
import { EventEmitter } from 'events';
|
|
5
6
|
import { LOGS_DIR } from '../config/paths.js';
|
|
6
|
-
export class DisplayManager {
|
|
7
|
+
export class DisplayManager extends EventEmitter {
|
|
7
8
|
static instance;
|
|
8
9
|
spinner;
|
|
9
10
|
logger;
|
|
10
11
|
constructor() {
|
|
12
|
+
super();
|
|
11
13
|
this.spinner = ora();
|
|
12
14
|
}
|
|
13
15
|
static getInstance() {
|
|
@@ -38,7 +40,9 @@ export class DisplayManager {
|
|
|
38
40
|
],
|
|
39
41
|
});
|
|
40
42
|
}
|
|
41
|
-
startSpinner(text) {
|
|
43
|
+
startSpinner(text, source) {
|
|
44
|
+
const defaultAgentKey = source ? source.toLowerCase() : 'oracle';
|
|
45
|
+
this.emit('activity_start', { agent: defaultAgentKey, message: text || 'processing...', timestamp: Date.now() });
|
|
42
46
|
if (this.spinner.isSpinning) {
|
|
43
47
|
if (text) {
|
|
44
48
|
this.spinner.text = text;
|
|
@@ -53,6 +57,7 @@ export class DisplayManager {
|
|
|
53
57
|
}
|
|
54
58
|
}
|
|
55
59
|
stopSpinner(success) {
|
|
60
|
+
this.emit('activity_end', { timestamp: Date.now(), success });
|
|
56
61
|
if (!this.spinner.isSpinning)
|
|
57
62
|
return;
|
|
58
63
|
if (success === true) {
|
|
@@ -65,6 +70,38 @@ export class DisplayManager {
|
|
|
65
70
|
this.spinner.stop();
|
|
66
71
|
}
|
|
67
72
|
}
|
|
73
|
+
/**
|
|
74
|
+
* Start an activity for an agent - emits activity_start event for visualization.
|
|
75
|
+
* Use this when an agent begins a task (e.g., before calling an API).
|
|
76
|
+
*/
|
|
77
|
+
startActivity(agent, message) {
|
|
78
|
+
this.emit('activity_start', {
|
|
79
|
+
agent: agent.toLowerCase(),
|
|
80
|
+
message,
|
|
81
|
+
timestamp: Date.now(),
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* End an activity for an agent - emits activity_end event for visualization.
|
|
86
|
+
* Use this when an agent finishes a task (e.g., after receiving API response).
|
|
87
|
+
*/
|
|
88
|
+
endActivity(agent, success) {
|
|
89
|
+
this.emit('activity_end', {
|
|
90
|
+
agent: agent.toLowerCase(),
|
|
91
|
+
timestamp: Date.now(),
|
|
92
|
+
success,
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Emit a message sent event - for visualizing outgoing messages (e.g., rocket launching).
|
|
97
|
+
* This is a transient event that appears briefly in the visualizer.
|
|
98
|
+
*/
|
|
99
|
+
emitMessageSent(agent = 'oracle') {
|
|
100
|
+
this.emit('message_sent', {
|
|
101
|
+
agent: agent.toLowerCase(),
|
|
102
|
+
timestamp: Date.now(),
|
|
103
|
+
});
|
|
104
|
+
}
|
|
68
105
|
log(message, options) {
|
|
69
106
|
const wasSpinning = this.spinner.isSpinning;
|
|
70
107
|
const previousText = this.spinner.text;
|
|
@@ -134,6 +171,16 @@ export class DisplayManager {
|
|
|
134
171
|
// But existing log() Implementation didn't handle 'debug' specifically before I added it to type.
|
|
135
172
|
// I'll leave console behavior as is (prints everything).
|
|
136
173
|
console.log(`${prefix}${formattedMessage}`);
|
|
174
|
+
// Emit purely for visualization (ignoring debug if needed to keep stream light)
|
|
175
|
+
if (options?.level !== 'debug') {
|
|
176
|
+
this.emit('message', {
|
|
177
|
+
message,
|
|
178
|
+
source: options?.source || 'system',
|
|
179
|
+
level: options?.level || 'info',
|
|
180
|
+
timestamp: Date.now(),
|
|
181
|
+
meta: options?.meta
|
|
182
|
+
});
|
|
183
|
+
}
|
|
137
184
|
if (this.logger) {
|
|
138
185
|
try {
|
|
139
186
|
const level = this.mapLevel(options?.level);
|
|
@@ -25,6 +25,7 @@ export class SatiService {
|
|
|
25
25
|
this.repository.initialize();
|
|
26
26
|
}
|
|
27
27
|
async recover(currentMessage, recentMessages) {
|
|
28
|
+
display.startActivity('sati', 'Recovering memories...');
|
|
28
29
|
const satiConfig = ConfigManager.getInstance().getSatiConfig();
|
|
29
30
|
const memoryLimit = satiConfig.memory_limit || 10;
|
|
30
31
|
const enabled_vector_search = satiConfig.enabled_archived_sessions ?? true;
|
|
@@ -43,6 +44,7 @@ export class SatiService {
|
|
|
43
44
|
console.warn('[Sati] Failed to generate embedding:', err);
|
|
44
45
|
}
|
|
45
46
|
const memories = this.repository.search(currentMessage, memoryLimit, queryEmbedding);
|
|
47
|
+
display.endActivity('sati', true);
|
|
46
48
|
return {
|
|
47
49
|
relevant_memories: memories.map(m => ({
|
|
48
50
|
summary: m.summary,
|
|
@@ -52,6 +54,7 @@ export class SatiService {
|
|
|
52
54
|
};
|
|
53
55
|
}
|
|
54
56
|
async evaluateAndPersist(conversation, userSessionId) {
|
|
57
|
+
display.startActivity('sati', 'Evaluating and persisting memories...');
|
|
55
58
|
try {
|
|
56
59
|
const satiConfig = ConfigManager.getInstance().getSatiConfig();
|
|
57
60
|
if (!satiConfig)
|
|
@@ -239,7 +242,9 @@ export class SatiService {
|
|
|
239
242
|
}
|
|
240
243
|
catch (error) {
|
|
241
244
|
console.error('[SatiService] Evaluation failed:', error);
|
|
245
|
+
display.endActivity('sati', false);
|
|
242
246
|
}
|
|
247
|
+
display.endActivity('sati', true);
|
|
243
248
|
}
|
|
244
249
|
generateHash(content) {
|
|
245
250
|
return createHash('sha256').update(content.trim().toLowerCase()).digest('hex');
|
package/dist/runtime/oracle.js
CHANGED
|
@@ -519,6 +519,8 @@ Use it to inform your response and tool selection (if needed), but do not assume
|
|
|
519
519
|
let contextDelegationAcks = [];
|
|
520
520
|
let syncDelegationCount = 0;
|
|
521
521
|
const oracleStartMs = Date.now();
|
|
522
|
+
const display = DisplayManager.getInstance();
|
|
523
|
+
display.startActivity('oracle', `LLM call (${this.config.llm.model})`);
|
|
522
524
|
const response = await TaskRequestContext.run(invokeContext, async () => {
|
|
523
525
|
const agentResponse = await this.provider.invoke({ messages }, { recursionLimit: 100 });
|
|
524
526
|
contextDelegationAcks = TaskRequestContext.getDelegationAcks();
|
|
@@ -526,6 +528,7 @@ Use it to inform your response and tool selection (if needed), but do not assume
|
|
|
526
528
|
return agentResponse;
|
|
527
529
|
});
|
|
528
530
|
const oracleDurationMs = Date.now() - oracleStartMs;
|
|
531
|
+
display.endActivity('oracle', true);
|
|
529
532
|
// Emit llm_call audit event for Oracle's own invocation
|
|
530
533
|
try {
|
|
531
534
|
const lastMsg = response.messages[response.messages.length - 1];
|
|
@@ -643,6 +646,8 @@ Use it to inform your response and tool selection (if needed), but do not assume
|
|
|
643
646
|
// Persist user message + all generated messages in a single transaction
|
|
644
647
|
await callHistory.addMessages([userMessage, ...newGeneratedMessages]);
|
|
645
648
|
}
|
|
649
|
+
// Emit message sent event for visualization (rocket animation)
|
|
650
|
+
this.display.emitMessageSent('oracle');
|
|
646
651
|
}
|
|
647
652
|
this.display.log('Response generated.', { source: 'Oracle' });
|
|
648
653
|
// Sati Middleware: skip memory evaluation for delegation-only acknowledgements.
|
|
@@ -14,23 +14,35 @@ export class ProviderFactory {
|
|
|
14
14
|
return createMiddleware({
|
|
15
15
|
name: "ToolMonitoringMiddleware",
|
|
16
16
|
wrapToolCall: (request, handler) => {
|
|
17
|
-
|
|
17
|
+
const toolName = request.toolCall.name;
|
|
18
|
+
// Determine which agent is running this tool based on context
|
|
19
|
+
// This is a heuristic - the actual agent should be passed in context
|
|
20
|
+
let agent = 'neo'; // Default to neo for MCP tools
|
|
21
|
+
const ctx = TaskRequestContext.get();
|
|
22
|
+
if (ctx?.session_id) {
|
|
23
|
+
// Try to determine agent from context - this is a simplified approach
|
|
24
|
+
// In practice, we'd need to pass the agent through the context
|
|
25
|
+
}
|
|
26
|
+
display.startActivity(agent, `Executing tool: ${toolName}`);
|
|
27
|
+
display.log(`Executing tool: ${toolName}`, { level: "warning", source: 'ConstructLoad' });
|
|
18
28
|
display.log(`Arguments: ${JSON.stringify(request.toolCall.args)}`, { level: "info", source: 'ConstructLoad' });
|
|
19
29
|
// Verbose mode: notify originating channel about which tool is running
|
|
20
30
|
const verboseEnabled = ConfigManager.getInstance().get().verbose_mode !== false;
|
|
21
31
|
if (verboseEnabled) {
|
|
22
32
|
const ctx = TaskRequestContext.get();
|
|
23
33
|
if (ctx?.origin_channel && ctx.origin_user_id && !SILENT_CHANNELS.has(ctx.origin_channel)) {
|
|
24
|
-
ChannelRegistry.sendToUser(ctx.origin_channel, ctx.origin_user_id, `🔧 executing: ${
|
|
34
|
+
ChannelRegistry.sendToUser(ctx.origin_channel, ctx.origin_user_id, `🔧 executing: ${toolName}`)
|
|
25
35
|
.catch(() => { });
|
|
26
36
|
}
|
|
27
37
|
}
|
|
28
38
|
try {
|
|
29
39
|
const result = handler(request);
|
|
40
|
+
display.endActivity(agent, true);
|
|
30
41
|
display.log(`Tool completed successfully. Result: ${JSON.stringify(result)}`, { level: "info", source: 'ConstructLoad' });
|
|
31
42
|
return result;
|
|
32
43
|
}
|
|
33
44
|
catch (e) {
|
|
45
|
+
display.endActivity(agent, false);
|
|
34
46
|
display.log(`Tool failed: ${e}`, { level: "error", source: 'ConstructLoad' });
|
|
35
47
|
throw e;
|
|
36
48
|
}
|
|
@@ -133,6 +133,7 @@ export class SmithDelegator {
|
|
|
133
133
|
level: 'info',
|
|
134
134
|
meta: { smith: smithName },
|
|
135
135
|
});
|
|
136
|
+
this.display.startActivity('smith', `Delegating to Smith '${smithName}'...`);
|
|
136
137
|
try {
|
|
137
138
|
// Build proxy tools for this Smith's capabilities
|
|
138
139
|
const proxyTools = this.buildProxyTools(smithName);
|
|
@@ -193,6 +194,7 @@ Respond in the same language as the task.`);
|
|
|
193
194
|
source: 'SmithDelegator',
|
|
194
195
|
level: 'info',
|
|
195
196
|
});
|
|
197
|
+
this.display.endActivity('smith', true);
|
|
196
198
|
return {
|
|
197
199
|
output: content,
|
|
198
200
|
usage: {
|
|
@@ -210,6 +212,7 @@ Respond in the same language as the task.`);
|
|
|
210
212
|
source: 'SmithDelegator',
|
|
211
213
|
level: 'error',
|
|
212
214
|
});
|
|
215
|
+
this.display.endActivity('smith', false);
|
|
213
216
|
return { output: `❌ Smith '${smithName}' delegation failed: ${err.message}` };
|
|
214
217
|
}
|
|
215
218
|
}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { AuditRepository } from '../audit/repository.js';
|
|
2
|
+
import { DisplayManager } from '../display.js';
|
|
3
|
+
const display = DisplayManager.getInstance();
|
|
2
4
|
/**
|
|
3
5
|
* Wraps a StructuredTool to record audit events on each invocation.
|
|
4
6
|
* The `getSessionId` getter is called at invocation time so it reflects
|
|
@@ -10,6 +12,7 @@ function instrumentTool(tool, getSessionId, getAgent) {
|
|
|
10
12
|
const startMs = Date.now();
|
|
11
13
|
const sessionId = getSessionId() ?? 'unknown';
|
|
12
14
|
const agent = getAgent();
|
|
15
|
+
display.startActivity(agent, `Executing tool: ${tool.name}`);
|
|
13
16
|
try {
|
|
14
17
|
const result = await original(input, runManager);
|
|
15
18
|
const durationMs = Date.now() - startMs;
|
|
@@ -21,9 +24,11 @@ function instrumentTool(tool, getSessionId, getAgent) {
|
|
|
21
24
|
duration_ms: durationMs,
|
|
22
25
|
status: 'success',
|
|
23
26
|
});
|
|
27
|
+
display.endActivity(agent, true);
|
|
24
28
|
return result;
|
|
25
29
|
}
|
|
26
30
|
catch (err) {
|
|
31
|
+
display.endActivity(agent, false);
|
|
27
32
|
const durationMs = Date.now() - startMs;
|
|
28
33
|
AuditRepository.getInstance().insert({
|
|
29
34
|
session_id: sessionId,
|