morpheus-cli 0.8.9 → 0.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/channels/discord.js +133 -6
- package/dist/channels/telegram.js +23 -17
- package/dist/http/api.js +2 -3
- package/dist/runtime/__tests__/keymaker.test.js +5 -2
- package/dist/runtime/apoc.js +7 -7
- package/dist/{devkit/registry.js → runtime/devkit-instrument.js} +5 -29
- package/dist/runtime/keymaker.js +6 -5
- package/dist/runtime/memory/sati/service.js +1 -1
- package/dist/runtime/memory/sqlite.js +31 -124
- package/dist/runtime/neo.js +1 -1
- package/dist/runtime/oracle.js +30 -52
- package/dist/runtime/smiths/delegator.js +2 -2
- package/dist/runtime/trinity.js +1 -1
- package/dist/ui/assets/{AuditDashboard-5sA8Sd8S.js → AuditDashboard-nVV9KKFp.js} +1 -1
- package/dist/ui/assets/Chat-ChsmnZzq.js +41 -0
- package/dist/ui/assets/{Chronos-BAjeLobF.js → Chronos-kgO7IkEj.js} +1 -1
- package/dist/ui/assets/{ConfirmationModal-fvgnOWTY.js → ConfirmationModal-D1BYPXJ4.js} +1 -1
- package/dist/ui/assets/{Dashboard-Ca5mSefz.js → Dashboard-DWB5NwQn.js} +1 -1
- package/dist/ui/assets/{DeleteConfirmationModal-A8EmnHoa.js → DeleteConfirmationModal-CgIMbyB7.js} +1 -1
- package/dist/ui/assets/{Logs-CYu7se7R.js → Logs-DGdRnEFi.js} +1 -1
- package/dist/ui/assets/{MCPManager-DsDA_ZVT.js → MCPManager-BDjWMRRX.js} +1 -1
- package/dist/ui/assets/{ModelPricing-DnSm_Nh-.js → ModelPricing-DAk1sS7D.js} +1 -1
- package/dist/ui/assets/{Notifications-CiljQzvM.js → Notifications-DMEq6EZR.js} +1 -1
- package/dist/ui/assets/{SatiMemories-rnO2b0LG.js → SatiMemories-BxicQE35.js} +1 -1
- package/dist/ui/assets/{SessionAudit-Dfvhge3Z.js → SessionAudit-CKJQf9LU.js} +1 -1
- package/dist/ui/assets/{Settings-OQlHAJoy.js → Settings-CulMd4Qr.js} +1 -1
- package/dist/ui/assets/{Skills-Crsybug0.js → Skills-DPoqYa8Y.js} +1 -1
- package/dist/ui/assets/{Smiths-wm90jRDT.js → Smiths-Clamjlph.js} +1 -1
- package/dist/ui/assets/{Tasks-C5FMu_Yu.js → Tasks-BfTkhB1J.js} +1 -1
- package/dist/ui/assets/{TrinityDatabases-BzYfecKI.js → TrinityDatabases-BmM1S9aQ.js} +1 -1
- package/dist/ui/assets/{UsageStats-CBo2vW2n.js → UsageStats-aAu2DFlb.js} +1 -1
- package/dist/ui/assets/{WebhookManager-0tDFkfHd.js → WebhookManager-DdnRSWl9.js} +1 -1
- package/dist/ui/assets/{audit-B-F8XPLi.js → audit-CqszEkOd.js} +1 -1
- package/dist/ui/assets/{chronos-BvMxfBQH.js → chronos-CPwFWid9.js} +1 -1
- package/dist/ui/assets/{config-DteVgNGR.js → config-D0DePxKu.js} +1 -1
- package/dist/ui/assets/{index-Cwqr-n0Y.js → index-BxVeRyTh.js} +2 -2
- package/dist/ui/assets/index-OLhpm8I7.css +1 -0
- package/dist/ui/assets/{mcp-DxzodOdH.js → mcp-Gjc3IZpO.js} +1 -1
- package/dist/ui/assets/{skills--hAyQnmG.js → skills-B5DnmnHW.js} +1 -1
- package/dist/ui/assets/{stats-Cibaisqd.js → stats-BAse7jj0.js} +1 -1
- package/dist/ui/index.html +2 -2
- package/dist/ui/sw.js +1 -1
- package/package.json +6 -4
- package/dist/devkit/adapters/shell.js +0 -80
- package/dist/devkit/index.js +0 -11
- package/dist/devkit/tools/browser.js +0 -825
- package/dist/devkit/tools/filesystem.js +0 -235
- package/dist/devkit/tools/git.js +0 -226
- package/dist/devkit/tools/network.js +0 -165
- package/dist/devkit/tools/packages.js +0 -73
- package/dist/devkit/tools/processes.js +0 -130
- package/dist/devkit/tools/shell.js +0 -106
- package/dist/devkit/tools/system.js +0 -132
- package/dist/devkit/types.js +0 -1
- package/dist/devkit/utils.js +0 -45
- package/dist/ui/assets/Chat-CjxeAQmd.js +0 -41
- package/dist/ui/assets/index-DcfyUdLI.css +0 -1
package/dist/channels/discord.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Client, GatewayIntentBits, Partials, Events, ChannelType, REST, Routes, SlashCommandBuilder, } from 'discord.js';
|
|
1
|
+
import { Client, GatewayIntentBits, Partials, Events, ChannelType, REST, Routes, SlashCommandBuilder, ActionRowBuilder, ButtonBuilder, ButtonStyle, ComponentType, } from 'discord.js';
|
|
2
2
|
import chalk from 'chalk';
|
|
3
3
|
import fs from 'fs-extra';
|
|
4
4
|
import path from 'path';
|
|
@@ -26,6 +26,15 @@ const SLASH_COMMANDS = [
|
|
|
26
26
|
.setName('newsession')
|
|
27
27
|
.setDescription('Archive current session and start a new one')
|
|
28
28
|
.setDMPermission(true),
|
|
29
|
+
new SlashCommandBuilder()
|
|
30
|
+
.setName('sessions')
|
|
31
|
+
.setDescription('List all sessions and switch between them')
|
|
32
|
+
.setDMPermission(true),
|
|
33
|
+
new SlashCommandBuilder()
|
|
34
|
+
.setName('session_switch')
|
|
35
|
+
.setDescription('Switch to a specific session')
|
|
36
|
+
.addStringOption(opt => opt.setName('id').setDescription('Session ID to switch to').setRequired(true))
|
|
37
|
+
.setDMPermission(true),
|
|
29
38
|
new SlashCommandBuilder()
|
|
30
39
|
.setName('chronos')
|
|
31
40
|
.setDescription('Schedule a prompt for the Oracle')
|
|
@@ -103,6 +112,8 @@ export class DiscordAdapter {
|
|
|
103
112
|
display = DisplayManager.getInstance();
|
|
104
113
|
config = ConfigManager.getInstance();
|
|
105
114
|
history = new SQLiteChatMessageHistory({ sessionId: '' });
|
|
115
|
+
/** Per-channel session tracking — which session this Discord adapter is currently using */
|
|
116
|
+
currentSessionId = null;
|
|
106
117
|
telephonist = null;
|
|
107
118
|
telephonistProvider = null;
|
|
108
119
|
telephonistModel = null;
|
|
@@ -178,8 +189,8 @@ export class DiscordAdapter {
|
|
|
178
189
|
return;
|
|
179
190
|
this.display.log(`${message.author.tag}: ${text}`, { source: 'Discord' });
|
|
180
191
|
try {
|
|
181
|
-
const sessionId = await this.history.getCurrentSessionOrCreate();
|
|
182
|
-
|
|
192
|
+
const sessionId = this.currentSessionId ?? await this.history.getCurrentSessionOrCreate();
|
|
193
|
+
this.currentSessionId = sessionId;
|
|
183
194
|
const response = await this.oracle.chat(text, undefined, false, {
|
|
184
195
|
origin_channel: 'discord',
|
|
185
196
|
session_id: sessionId,
|
|
@@ -292,8 +303,8 @@ export class DiscordAdapter {
|
|
|
292
303
|
// Show transcription
|
|
293
304
|
await channel.send(`🎤 "${text}"`);
|
|
294
305
|
// Process with Oracle
|
|
295
|
-
const sessionId = await this.history.getCurrentSessionOrCreate();
|
|
296
|
-
|
|
306
|
+
const sessionId = this.currentSessionId ?? await this.history.getCurrentSessionOrCreate();
|
|
307
|
+
this.currentSessionId = sessionId;
|
|
297
308
|
const response = await this.oracle.chat(text, usage, true, {
|
|
298
309
|
origin_channel: 'discord',
|
|
299
310
|
session_id: sessionId,
|
|
@@ -355,6 +366,12 @@ export class DiscordAdapter {
|
|
|
355
366
|
case 'newsession':
|
|
356
367
|
await this.cmdNewSession(interaction);
|
|
357
368
|
break;
|
|
369
|
+
case 'sessions':
|
|
370
|
+
await this.cmdSessions(interaction);
|
|
371
|
+
break;
|
|
372
|
+
case 'session_switch':
|
|
373
|
+
await this.cmdSessionSwitch(interaction);
|
|
374
|
+
break;
|
|
358
375
|
case 'chronos':
|
|
359
376
|
await this.cmdChronos(interaction);
|
|
360
377
|
break;
|
|
@@ -407,6 +424,8 @@ export class DiscordAdapter {
|
|
|
407
424
|
'`/status` — Check Morpheus status',
|
|
408
425
|
'`/stats` — Token usage statistics',
|
|
409
426
|
'`/newsession` — Start a new session',
|
|
427
|
+
'`/sessions` — List all sessions (switch, archive, delete)',
|
|
428
|
+
'`/session_switch id:` — Switch to a specific session',
|
|
410
429
|
'',
|
|
411
430
|
'**Chronos (Scheduler)**',
|
|
412
431
|
'`/chronos prompt: time:` — Schedule a job for the Oracle',
|
|
@@ -474,14 +493,122 @@ export class DiscordAdapter {
|
|
|
474
493
|
async cmdNewSession(interaction) {
|
|
475
494
|
try {
|
|
476
495
|
const history = new SQLiteChatMessageHistory({ sessionId: '' });
|
|
477
|
-
await history.createNewSession();
|
|
496
|
+
const newSessionId = await history.createNewSession();
|
|
478
497
|
history.close();
|
|
498
|
+
// Track the new session as the current one for this Discord channel
|
|
499
|
+
this.currentSessionId = newSessionId;
|
|
479
500
|
await interaction.reply({ content: '✅ New session started.' });
|
|
480
501
|
}
|
|
481
502
|
catch (err) {
|
|
482
503
|
await interaction.reply({ content: `Error: ${err.message}` });
|
|
483
504
|
}
|
|
484
505
|
}
|
|
506
|
+
async cmdSessions(interaction) {
|
|
507
|
+
try {
|
|
508
|
+
const history = new SQLiteChatMessageHistory({ sessionId: '' });
|
|
509
|
+
const sessions = (await history.listSessions()).filter((s) => !s.id.startsWith('chronos-job-') && !s.id.startsWith('sati-evaluation'));
|
|
510
|
+
history.close();
|
|
511
|
+
if (sessions.length === 0) {
|
|
512
|
+
await interaction.reply({ content: 'No sessions found.' });
|
|
513
|
+
return;
|
|
514
|
+
}
|
|
515
|
+
const lines = ['**Sessions:**\n'];
|
|
516
|
+
const rows = [];
|
|
517
|
+
for (const session of sessions) {
|
|
518
|
+
const title = session.title || 'Untitled Session';
|
|
519
|
+
const isCurrent = session.id === this.currentSessionId;
|
|
520
|
+
const icon = isCurrent ? '🟢' : '⚪';
|
|
521
|
+
const started = new Date(session.started_at).toLocaleString();
|
|
522
|
+
lines.push(`${icon} **${title}**`);
|
|
523
|
+
lines.push(` ID: \`${session.id}\``);
|
|
524
|
+
lines.push(` Started: ${started}\n`);
|
|
525
|
+
// Discord allows max 5 buttons per row, max 5 rows per message
|
|
526
|
+
if (rows.length < 5) {
|
|
527
|
+
const btns = [];
|
|
528
|
+
if (!isCurrent) {
|
|
529
|
+
btns.push(new ButtonBuilder()
|
|
530
|
+
.setCustomId(`session_switch_${session.id}`)
|
|
531
|
+
.setLabel('Switch')
|
|
532
|
+
.setStyle(ButtonStyle.Primary)
|
|
533
|
+
.setEmoji('➡️'));
|
|
534
|
+
}
|
|
535
|
+
btns.push(new ButtonBuilder()
|
|
536
|
+
.setCustomId(`session_archive_${session.id}`)
|
|
537
|
+
.setLabel('Archive')
|
|
538
|
+
.setStyle(ButtonStyle.Secondary)
|
|
539
|
+
.setEmoji('📂'));
|
|
540
|
+
btns.push(new ButtonBuilder()
|
|
541
|
+
.setCustomId(`session_delete_${session.id}`)
|
|
542
|
+
.setLabel('Delete')
|
|
543
|
+
.setStyle(ButtonStyle.Danger)
|
|
544
|
+
.setEmoji('🗑️'));
|
|
545
|
+
rows.push(new ActionRowBuilder().addComponents(...btns));
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
const content = lines.join('\n').slice(0, 2000);
|
|
549
|
+
const reply = await interaction.reply({ content, components: rows, fetchReply: true });
|
|
550
|
+
// Collect button interactions for 60 seconds
|
|
551
|
+
const collector = reply.createMessageComponentCollector({
|
|
552
|
+
componentType: ComponentType.Button,
|
|
553
|
+
time: 60_000,
|
|
554
|
+
});
|
|
555
|
+
collector.on('collect', async (btn) => {
|
|
556
|
+
try {
|
|
557
|
+
if (btn.customId.startsWith('session_switch_')) {
|
|
558
|
+
const sid = btn.customId.replace('session_switch_', '');
|
|
559
|
+
const h = new SQLiteChatMessageHistory({ sessionId: '' });
|
|
560
|
+
await h.switchSession(sid);
|
|
561
|
+
h.close();
|
|
562
|
+
this.currentSessionId = sid;
|
|
563
|
+
await btn.reply({ content: `✅ Switched to session \`${sid}\`.`, ephemeral: true });
|
|
564
|
+
}
|
|
565
|
+
else if (btn.customId.startsWith('session_archive_')) {
|
|
566
|
+
const sid = btn.customId.replace('session_archive_', '');
|
|
567
|
+
const h = new SQLiteChatMessageHistory({ sessionId: '' });
|
|
568
|
+
await h.archiveSession(sid);
|
|
569
|
+
h.close();
|
|
570
|
+
if (this.currentSessionId === sid)
|
|
571
|
+
this.currentSessionId = null;
|
|
572
|
+
await btn.reply({ content: `📂 Session \`${sid}\` archived.`, ephemeral: true });
|
|
573
|
+
}
|
|
574
|
+
else if (btn.customId.startsWith('session_delete_')) {
|
|
575
|
+
const sid = btn.customId.replace('session_delete_', '');
|
|
576
|
+
const h = new SQLiteChatMessageHistory({ sessionId: '' });
|
|
577
|
+
await h.deleteSession(sid);
|
|
578
|
+
h.close();
|
|
579
|
+
if (this.currentSessionId === sid)
|
|
580
|
+
this.currentSessionId = null;
|
|
581
|
+
await btn.reply({ content: `🗑️ Session \`${sid}\` deleted.`, ephemeral: true });
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
catch (err) {
|
|
585
|
+
await btn.reply({ content: `Error: ${err.message}`, ephemeral: true }).catch(() => { });
|
|
586
|
+
}
|
|
587
|
+
});
|
|
588
|
+
collector.on('end', async () => {
|
|
589
|
+
try {
|
|
590
|
+
await interaction.editReply({ components: [] });
|
|
591
|
+
}
|
|
592
|
+
catch { /* message may have been deleted */ }
|
|
593
|
+
});
|
|
594
|
+
}
|
|
595
|
+
catch (err) {
|
|
596
|
+
await interaction.reply({ content: `Error: ${err.message}` });
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
async cmdSessionSwitch(interaction) {
|
|
600
|
+
const sessionId = interaction.options.getString('id', true);
|
|
601
|
+
try {
|
|
602
|
+
const history = new SQLiteChatMessageHistory({ sessionId: '' });
|
|
603
|
+
await history.switchSession(sessionId);
|
|
604
|
+
history.close();
|
|
605
|
+
this.currentSessionId = sessionId;
|
|
606
|
+
await interaction.reply({ content: `✅ Switched to session \`${sessionId}\`.` });
|
|
607
|
+
}
|
|
608
|
+
catch (err) {
|
|
609
|
+
await interaction.reply({ content: `Error: ${err.message}` });
|
|
610
|
+
}
|
|
611
|
+
}
|
|
485
612
|
async cmdChronos(interaction) {
|
|
486
613
|
const prompt = interaction.options.getString('prompt', true);
|
|
487
614
|
const timeExpr = interaction.options.getString('time', true);
|
|
@@ -144,6 +144,8 @@ export class TelegramAdapter {
|
|
|
144
144
|
telephonistProvider = null;
|
|
145
145
|
telephonistModel = null;
|
|
146
146
|
history = new SQLiteChatMessageHistory({ sessionId: '' });
|
|
147
|
+
/** Per-channel session tracking — which session this Telegram adapter is currently using */
|
|
148
|
+
currentSessionId = null;
|
|
147
149
|
RATE_LIMIT_MS = 3000; // minimum ms between requests per user
|
|
148
150
|
rateLimiter = new Map(); // userId -> last request timestamp
|
|
149
151
|
// Pending Chronos create confirmations (userId -> job data + expiry)
|
|
@@ -230,8 +232,8 @@ export class TelegramAdapter {
|
|
|
230
232
|
try {
|
|
231
233
|
// Send "typing" status
|
|
232
234
|
await ctx.sendChatAction('typing');
|
|
233
|
-
const sessionId = await this.history.getCurrentSessionOrCreate();
|
|
234
|
-
|
|
235
|
+
const sessionId = this.currentSessionId ?? await this.history.getCurrentSessionOrCreate();
|
|
236
|
+
this.currentSessionId = sessionId;
|
|
235
237
|
// Process with Agent
|
|
236
238
|
const response = await this.oracle.chat(text, undefined, false, {
|
|
237
239
|
origin_channel: 'telegram',
|
|
@@ -317,9 +319,9 @@ export class TelegramAdapter {
|
|
|
317
319
|
this.display.log(`Transcription success for @${user}: "${text}"`, { source: 'Telephonist', level: 'success' });
|
|
318
320
|
// Audit: record telephonist execution
|
|
319
321
|
try {
|
|
320
|
-
const
|
|
322
|
+
const auditSessionId = this.currentSessionId ?? await this.history.getCurrentSessionOrCreate();
|
|
321
323
|
AuditRepository.getInstance().insert({
|
|
322
|
-
session_id:
|
|
324
|
+
session_id: auditSessionId,
|
|
323
325
|
event_type: 'telephonist',
|
|
324
326
|
agent: 'telephonist',
|
|
325
327
|
provider: config.audio.provider,
|
|
@@ -345,8 +347,8 @@ export class TelegramAdapter {
|
|
|
345
347
|
// So I should treat 'text' as if it was a text message.
|
|
346
348
|
await ctx.reply(`🎤 Transcription: "${text}"`);
|
|
347
349
|
await ctx.sendChatAction('typing');
|
|
348
|
-
const sessionId = await this.history.getCurrentSessionOrCreate();
|
|
349
|
-
|
|
350
|
+
const sessionId = this.currentSessionId ?? await this.history.getCurrentSessionOrCreate();
|
|
351
|
+
this.currentSessionId = sessionId;
|
|
350
352
|
// Process with Agent
|
|
351
353
|
const response = await this.oracle.chat(text, usage, true, {
|
|
352
354
|
origin_channel: 'telegram',
|
|
@@ -380,9 +382,9 @@ export class TelegramAdapter {
|
|
|
380
382
|
const detail = error?.cause?.message || error?.response?.data?.error?.message || error.message;
|
|
381
383
|
this.display.log(`Audio processing error for @${user}: ${detail}`, { source: 'Telephonist', level: 'error' });
|
|
382
384
|
try {
|
|
383
|
-
const
|
|
385
|
+
const auditSessionId = this.currentSessionId ?? 'default';
|
|
384
386
|
AuditRepository.getInstance().insert({
|
|
385
|
-
session_id:
|
|
387
|
+
session_id: auditSessionId,
|
|
386
388
|
event_type: 'telephonist',
|
|
387
389
|
agent: 'telephonist',
|
|
388
390
|
provider: this.config.get().audio.provider,
|
|
@@ -428,12 +430,14 @@ export class TelegramAdapter {
|
|
|
428
430
|
return;
|
|
429
431
|
}
|
|
430
432
|
try {
|
|
431
|
-
//
|
|
433
|
+
// Validate session exists and is usable
|
|
432
434
|
const history = new SQLiteChatMessageHistory({ sessionId: "" });
|
|
433
|
-
// Alternar para a nova sessão
|
|
434
435
|
await history.switchSession(sessionId);
|
|
436
|
+
history.close();
|
|
437
|
+
// Track this session as the current one for this Telegram channel
|
|
438
|
+
this.currentSessionId = sessionId;
|
|
435
439
|
await ctx.answerCbQuery();
|
|
436
|
-
//
|
|
440
|
+
// Remove the previous message and send confirmation
|
|
437
441
|
if (ctx.updateType === 'callback_query') {
|
|
438
442
|
ctx.deleteMessage().catch(() => { });
|
|
439
443
|
}
|
|
@@ -1154,7 +1158,10 @@ export class TelegramAdapter {
|
|
|
1154
1158
|
async handleApproveNewSessionCommand(ctx, user) {
|
|
1155
1159
|
try {
|
|
1156
1160
|
const history = new SQLiteChatMessageHistory({ sessionId: "" });
|
|
1157
|
-
await history.createNewSession();
|
|
1161
|
+
const newSessionId = await history.createNewSession();
|
|
1162
|
+
history.close();
|
|
1163
|
+
// Track the new session as the current one for this Telegram channel
|
|
1164
|
+
this.currentSessionId = newSessionId;
|
|
1158
1165
|
}
|
|
1159
1166
|
catch (e) {
|
|
1160
1167
|
await ctx.reply(`Error creating new session: ${e.message}`);
|
|
@@ -1167,21 +1174,20 @@ export class TelegramAdapter {
|
|
|
1167
1174
|
// callback_data limit and they are not user-managed sessions.
|
|
1168
1175
|
const sessions = (await history.listSessions()).filter((s) => !s.id.startsWith('chronos-job-') && !s.id.startsWith('sati-evaluation'));
|
|
1169
1176
|
if (sessions.length === 0) {
|
|
1170
|
-
await ctx.reply('No
|
|
1177
|
+
await ctx.reply('No sessions found\\.', { parse_mode: 'MarkdownV2' });
|
|
1171
1178
|
return;
|
|
1172
1179
|
}
|
|
1173
1180
|
let response = '*Sessions:*\n\n';
|
|
1174
1181
|
const keyboard = [];
|
|
1175
1182
|
for (const session of sessions) {
|
|
1176
1183
|
const title = session.title || 'Untitled Session';
|
|
1177
|
-
const
|
|
1184
|
+
const isCurrent = session.id === this.currentSessionId;
|
|
1185
|
+
const statusEmoji = isCurrent ? '🟢' : '⚪';
|
|
1178
1186
|
response += `${statusEmoji} *${escMdRaw(title)}*\n`;
|
|
1179
1187
|
response += `\\- ID: \`${escMdRaw(session.id)}\`\n`;
|
|
1180
|
-
response += `\\- Status: ${escMdRaw(session.status)}\n`;
|
|
1181
1188
|
response += `\\- Started: ${escMdRaw(new Date(session.started_at).toLocaleString())}\n\n`;
|
|
1182
|
-
// Adicionar botão inline para alternar para esta sessão
|
|
1183
1189
|
const sessionButtons = [];
|
|
1184
|
-
if (
|
|
1190
|
+
if (!isCurrent) {
|
|
1185
1191
|
sessionButtons.push({
|
|
1186
1192
|
text: `➡️ Switch`,
|
|
1187
1193
|
callback_data: `switch_session_${session.id}`
|
package/dist/http/api.js
CHANGED
|
@@ -61,8 +61,7 @@ export function createApiRouter(oracle, chronosWorker) {
|
|
|
61
61
|
});
|
|
62
62
|
router.post('/sessions', async (req, res) => {
|
|
63
63
|
try {
|
|
64
|
-
await history.createNewSession();
|
|
65
|
-
const newSessionId = await history.getCurrentSessionOrCreate(); // Should be the new one
|
|
64
|
+
const newSessionId = await history.createNewSession();
|
|
66
65
|
res.json({ success: true, id: newSessionId, message: 'New session started' });
|
|
67
66
|
}
|
|
68
67
|
catch (err) {
|
|
@@ -248,7 +247,7 @@ export function createApiRouter(oracle, chronosWorker) {
|
|
|
248
247
|
}
|
|
249
248
|
try {
|
|
250
249
|
const { message, sessionId } = parsed.data;
|
|
251
|
-
|
|
250
|
+
// Session is passed via taskContext — no need to mutate global Oracle state.
|
|
252
251
|
const response = await oracle.chat(message, undefined, false, {
|
|
253
252
|
origin_channel: 'ui',
|
|
254
253
|
session_id: sessionId,
|
|
@@ -32,12 +32,15 @@ vi.mock('../display.js', () => ({
|
|
|
32
32
|
})),
|
|
33
33
|
},
|
|
34
34
|
}));
|
|
35
|
-
vi.mock('
|
|
35
|
+
vi.mock('morpheus-devkit', () => ({
|
|
36
36
|
buildDevKit: vi.fn(() => [
|
|
37
37
|
{ name: 'fs_read', description: 'Read file' },
|
|
38
38
|
{ name: 'shell_exec', description: 'Execute shell command' },
|
|
39
39
|
]),
|
|
40
40
|
}));
|
|
41
|
+
vi.mock('../devkit-instrument.js', () => ({
|
|
42
|
+
instrumentDevKitTools: vi.fn((tools) => tools),
|
|
43
|
+
}));
|
|
41
44
|
vi.mock('../tools/factory.js', () => ({
|
|
42
45
|
Construtor: {
|
|
43
46
|
create: vi.fn(() => Promise.resolve([
|
|
@@ -97,7 +100,7 @@ describe('Keymaker', () => {
|
|
|
97
100
|
describe('initialize()', () => {
|
|
98
101
|
it('should initialize agent with all tools', async () => {
|
|
99
102
|
const { ProviderFactory } = await import('../providers/factory.js');
|
|
100
|
-
const { buildDevKit } = await import('
|
|
103
|
+
const { buildDevKit } = await import('morpheus-devkit');
|
|
101
104
|
const { Construtor } = await import('../tools/factory.js');
|
|
102
105
|
const keymaker = new Keymaker('test-skill', '# Instructions');
|
|
103
106
|
await keymaker.initialize();
|
package/dist/runtime/apoc.js
CHANGED
|
@@ -3,7 +3,8 @@ import { ConfigManager } from "../config/manager.js";
|
|
|
3
3
|
import { ProviderFactory } from "./providers/factory.js";
|
|
4
4
|
import { ProviderError } from "./errors.js";
|
|
5
5
|
import { DisplayManager } from "./display.js";
|
|
6
|
-
import { buildDevKit } from "
|
|
6
|
+
import { buildDevKit } from "morpheus-devkit";
|
|
7
|
+
import { instrumentDevKitTools } from "./devkit-instrument.js";
|
|
7
8
|
import { extractRawUsage, persistAgentMessage, buildAgentResult, emitToolAuditEvents } from "./subagent-utils.js";
|
|
8
9
|
import { buildDelegationTool } from "./tools/delegation-utils.js";
|
|
9
10
|
/**
|
|
@@ -47,9 +48,9 @@ export class Apoc {
|
|
|
47
48
|
const devkit = ConfigManager.getInstance().getDevKitConfig();
|
|
48
49
|
const timeout_ms = devkit.timeout_ms || this.config.apoc?.timeout_ms || 30_000;
|
|
49
50
|
const personality = this.config.apoc?.personality || 'pragmatic_dev';
|
|
50
|
-
// Import
|
|
51
|
-
await import("
|
|
52
|
-
const
|
|
51
|
+
// Import morpheus-devkit to trigger side-effect tool registration
|
|
52
|
+
await import("morpheus-devkit");
|
|
53
|
+
const rawTools = buildDevKit({
|
|
53
54
|
working_dir: devkit.sandbox_dir || process.cwd(),
|
|
54
55
|
allowed_commands: devkit.allowed_shell_commands || [],
|
|
55
56
|
timeout_ms,
|
|
@@ -59,9 +60,8 @@ export class Apoc {
|
|
|
59
60
|
enable_shell: devkit.enable_shell,
|
|
60
61
|
enable_git: devkit.enable_git,
|
|
61
62
|
enable_network: devkit.enable_network,
|
|
62
|
-
getSessionId: () => Apoc.currentSessionId,
|
|
63
|
-
getAgent: () => 'apoc',
|
|
64
63
|
});
|
|
64
|
+
const tools = instrumentDevKitTools(rawTools, () => Apoc.currentSessionId, () => 'apoc');
|
|
65
65
|
this.display.log(`Apoc initialized with ${tools.length} DevKit tools (sandbox_dir: ${devkit.sandbox_dir}, personality: ${personality})`, { source: "Apoc" });
|
|
66
66
|
try {
|
|
67
67
|
this.agent = await ProviderFactory.createBare(apocConfig, tools);
|
|
@@ -259,7 +259,7 @@ ${context ? `CONTEXT FROM ORACLE:\n${context}` : ""}
|
|
|
259
259
|
try {
|
|
260
260
|
const inputCount = messages.length;
|
|
261
261
|
const startMs = Date.now();
|
|
262
|
-
const response = await this.agent.invoke({ messages }, { recursionLimit:
|
|
262
|
+
const response = await this.agent.invoke({ messages }, { recursionLimit: 50 });
|
|
263
263
|
const durationMs = Date.now() - startMs;
|
|
264
264
|
const apocConfig = this.config.apoc || this.config.llm;
|
|
265
265
|
const lastMessage = response.messages[response.messages.length - 1];
|
|
@@ -1,15 +1,4 @@
|
|
|
1
|
-
import { AuditRepository } from '
|
|
2
|
-
const factories = [];
|
|
3
|
-
export function registerToolFactory(factory, category = 'system') {
|
|
4
|
-
factories.push({ category, factory });
|
|
5
|
-
}
|
|
6
|
-
/** Categories that can be toggled off via DevKit config */
|
|
7
|
-
const TOGGLEABLE_CATEGORIES = {
|
|
8
|
-
filesystem: 'enable_filesystem',
|
|
9
|
-
shell: 'enable_shell',
|
|
10
|
-
git: 'enable_git',
|
|
11
|
-
network: 'enable_network',
|
|
12
|
-
};
|
|
1
|
+
import { AuditRepository } from './audit/repository.js';
|
|
13
2
|
/**
|
|
14
3
|
* Wraps a StructuredTool to record audit events on each invocation.
|
|
15
4
|
* The `getSessionId` getter is called at invocation time so it reflects
|
|
@@ -51,22 +40,9 @@ function instrumentTool(tool, getSessionId, getAgent) {
|
|
|
51
40
|
return tool;
|
|
52
41
|
}
|
|
53
42
|
/**
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
* and returns tools with the context captured in closure.
|
|
57
|
-
* Disabled categories are filtered out based on context flags.
|
|
58
|
-
* All tools are wrapped with audit instrumentation.
|
|
43
|
+
* Wraps all DevKit tools with audit instrumentation.
|
|
44
|
+
* Call this after buildDevKit() to add Morpheus audit tracking.
|
|
59
45
|
*/
|
|
60
|
-
export function
|
|
61
|
-
|
|
62
|
-
const getAgent = ctx.getAgent ?? (() => 'apoc');
|
|
63
|
-
return factories
|
|
64
|
-
.filter(({ category }) => {
|
|
65
|
-
const ctxKey = TOGGLEABLE_CATEGORIES[category];
|
|
66
|
-
if (!ctxKey)
|
|
67
|
-
return true; // non-toggleable categories always load
|
|
68
|
-
return ctx[ctxKey] !== false;
|
|
69
|
-
})
|
|
70
|
-
.flatMap(({ factory }) => factory(ctx))
|
|
71
|
-
.map(tool => instrumentTool(tool, getSessionId, getAgent));
|
|
46
|
+
export function instrumentDevKitTools(tools, getSessionId, getAgent) {
|
|
47
|
+
return tools.map(tool => instrumentTool(tool, getSessionId, getAgent));
|
|
72
48
|
}
|
package/dist/runtime/keymaker.js
CHANGED
|
@@ -3,7 +3,8 @@ import { ConfigManager } from "../config/manager.js";
|
|
|
3
3
|
import { ProviderFactory } from "./providers/factory.js";
|
|
4
4
|
import { ProviderError } from "./errors.js";
|
|
5
5
|
import { DisplayManager } from "./display.js";
|
|
6
|
-
import { buildDevKit } from "
|
|
6
|
+
import { buildDevKit } from "morpheus-devkit";
|
|
7
|
+
import { instrumentDevKitTools } from "./devkit-instrument.js";
|
|
7
8
|
import { Construtor } from "./tools/factory.js";
|
|
8
9
|
import { morpheusTools } from "./tools/index.js";
|
|
9
10
|
import { SkillRegistry } from "./skills/registry.js";
|
|
@@ -36,8 +37,8 @@ export class Keymaker {
|
|
|
36
37
|
// Build DevKit tools (filesystem, shell, git, browser, network, etc.)
|
|
37
38
|
const devkit = ConfigManager.getInstance().getDevKitConfig();
|
|
38
39
|
const timeout_ms = devkit.timeout_ms || 30_000;
|
|
39
|
-
await import("
|
|
40
|
-
const devKitTools = buildDevKit({
|
|
40
|
+
await import("morpheus-devkit");
|
|
41
|
+
const devKitTools = instrumentDevKitTools(buildDevKit({
|
|
41
42
|
working_dir: devkit.sandbox_dir || process.cwd(),
|
|
42
43
|
allowed_commands: devkit.allowed_shell_commands || [],
|
|
43
44
|
timeout_ms,
|
|
@@ -47,7 +48,7 @@ export class Keymaker {
|
|
|
47
48
|
enable_shell: devkit.enable_shell,
|
|
48
49
|
enable_git: devkit.enable_git,
|
|
49
50
|
enable_network: devkit.enable_network,
|
|
50
|
-
});
|
|
51
|
+
}), () => undefined, () => 'keymaker');
|
|
51
52
|
// Load MCP tools from configured servers
|
|
52
53
|
const mcpTools = await Construtor.create();
|
|
53
54
|
// Combine all tools
|
|
@@ -116,7 +117,7 @@ CRITICAL — NEVER FABRICATE DATA:
|
|
|
116
117
|
origin_user_id: taskContext?.origin_user_id,
|
|
117
118
|
};
|
|
118
119
|
const startMs = Date.now();
|
|
119
|
-
const response = await TaskRequestContext.run(invokeContext, () => this.agent.invoke({ messages }, { recursionLimit:
|
|
120
|
+
const response = await TaskRequestContext.run(invokeContext, () => this.agent.invoke({ messages }, { recursionLimit: 50 }));
|
|
120
121
|
const durationMs = Date.now() - startMs;
|
|
121
122
|
const lastMessage = response.messages[response.messages.length - 1];
|
|
122
123
|
const content = typeof lastMessage.content === "string"
|
|
@@ -97,7 +97,7 @@ export class SatiService {
|
|
|
97
97
|
console.warn('[SatiService] Failed to persist input log:', e);
|
|
98
98
|
}
|
|
99
99
|
const satiStartMs = Date.now();
|
|
100
|
-
const response = await agent.invoke({ messages }, { recursionLimit:
|
|
100
|
+
const response = await agent.invoke({ messages }, { recursionLimit: 50 });
|
|
101
101
|
const satiDurationMs = Date.now() - satiStartMs;
|
|
102
102
|
const lastMessage = response.messages[response.messages.length - 1];
|
|
103
103
|
let content = lastMessage.content.toString();
|