morpheus-cli 0.8.5 → 0.8.7
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/telegram.js +43 -0
- package/dist/config/manager.js +3 -1
- package/dist/config/schemas.js +2 -0
- package/dist/http/api.js +70 -13
- package/dist/http/routers/chronos.js +12 -1
- package/dist/http/webhooks-router.js +11 -3
- package/dist/runtime/ISubagent.js +1 -0
- package/dist/runtime/apoc.js +49 -39
- package/dist/runtime/audit/repository.js +193 -6
- package/dist/runtime/chronos/repository.js +35 -0
- package/dist/runtime/keymaker.js +6 -30
- package/dist/runtime/memory/sati/index.js +22 -1
- package/dist/runtime/memory/sati/repository.js +39 -1
- package/dist/runtime/memory/sqlite.js +16 -3
- package/dist/runtime/neo.js +78 -34
- package/dist/runtime/oracle.js +68 -19
- package/dist/runtime/skills/tool.js +25 -0
- package/dist/runtime/subagent-utils.js +89 -0
- package/dist/runtime/tasks/repository.js +51 -0
- package/dist/runtime/tasks/worker.js +12 -2
- package/dist/runtime/telephonist.js +17 -9
- package/dist/runtime/tools/delegation-utils.js +120 -0
- package/dist/runtime/tools/index.js +0 -2
- package/dist/runtime/tools/time-verify-tools.js +15 -8
- package/dist/runtime/trinity.js +50 -34
- package/dist/runtime/webhooks/repository.js +31 -0
- package/dist/types/config.js +2 -0
- package/dist/types/pagination.js +1 -0
- package/dist/ui/assets/AuditDashboard-5sA8Sd8S.js +1 -0
- package/dist/ui/assets/Chat-CjxeAQmd.js +41 -0
- package/dist/ui/assets/Chronos-BAjeLobF.js +1 -0
- package/dist/ui/assets/{ConfirmationModal-MyIaIK_Z.js → ConfirmationModal-fvgnOWTY.js} +1 -1
- package/dist/ui/assets/{Dashboard-C52jjru9.js → Dashboard-Ca5mSefz.js} +1 -1
- package/dist/ui/assets/{DeleteConfirmationModal-B0nDocEK.js → DeleteConfirmationModal-A8EmnHoa.js} +1 -1
- package/dist/ui/assets/{Logs-fDrGC9Lq.js → Logs-CYu7se7R.js} +1 -1
- package/dist/ui/assets/MCPManager-DsDA_ZVT.js +1 -0
- package/dist/ui/assets/ModelPricing-DnSm_Nh-.js +1 -0
- package/dist/ui/assets/Notifications-CiljQzvM.js +1 -0
- package/dist/ui/assets/Pagination-JsiwxVNQ.js +1 -0
- package/dist/ui/assets/SatiMemories-rnO2b0LG.js +1 -0
- package/dist/ui/assets/SessionAudit-Dfvhge3Z.js +9 -0
- package/dist/ui/assets/{Settings-Cgd4dJdc.js → Settings-OQlHAJoy.js} +6 -4
- package/dist/ui/assets/Skills-Crsybug0.js +7 -0
- package/dist/ui/assets/Smiths-wm90jRDT.js +1 -0
- package/dist/ui/assets/Tasks-C5FMu_Yu.js +1 -0
- package/dist/ui/assets/TrinityDatabases-BzYfecKI.js +1 -0
- package/dist/ui/assets/{UsageStats-EEwfbJ6C.js → UsageStats-CBo2vW2n.js} +1 -1
- package/dist/ui/assets/{WebhookManager-CyVUcscY.js → WebhookManager-0tDFkfHd.js} +1 -1
- package/dist/ui/assets/audit-B-F8XPLi.js +1 -0
- package/dist/ui/assets/chronos-BvMxfBQH.js +1 -0
- package/dist/ui/assets/{config-cslLZS3q.js → config-DteVgNGR.js} +1 -1
- package/dist/ui/assets/index-Cwqr-n0Y.js +10 -0
- package/dist/ui/assets/index-DcfyUdLI.css +1 -0
- package/dist/ui/assets/{mcp-M0iDC0mj.js → mcp-DxzodOdH.js} +1 -1
- package/dist/ui/assets/{skills-BvaaqiOT.js → skills--hAyQnmG.js} +1 -1
- package/dist/ui/assets/{stats-DALk3GOj.js → stats-Cibaisqd.js} +1 -1
- package/dist/ui/assets/vendor-icons-BVuQI-6R.js +1 -0
- package/dist/ui/index.html +3 -3
- package/dist/ui/sw.js +1 -1
- package/package.json +2 -1
- package/dist/runtime/tools/apoc-tool.js +0 -157
- package/dist/runtime/tools/neo-tool.js +0 -172
- package/dist/runtime/tools/trinity-tool.js +0 -157
- package/dist/ui/assets/Chat-Cx2OgATp.js +0 -38
- package/dist/ui/assets/Chronos--mut48fM.js +0 -1
- package/dist/ui/assets/MCPManager-CtRQzwM8.js +0 -1
- package/dist/ui/assets/ModelPricing-d4EYrGko.js +0 -1
- package/dist/ui/assets/Notifications-Dkqug57C.js +0 -1
- package/dist/ui/assets/SatiMemories-DykYVHgi.js +0 -1
- package/dist/ui/assets/SessionAudit-Bk0-DpW0.js +0 -9
- package/dist/ui/assets/Skills-DSi313oC.js +0 -7
- package/dist/ui/assets/Smiths-DLys0BWT.js +0 -1
- package/dist/ui/assets/Tasks-B1MbPNUQ.js +0 -1
- package/dist/ui/assets/TrinityDatabases-B5SeHOLt.js +0 -1
- package/dist/ui/assets/chronos-BVRpP__j.js +0 -1
- package/dist/ui/assets/index-CpVvCthh.js +0 -10
- package/dist/ui/assets/index-QQyZIsmH.css +0 -1
- package/dist/ui/assets/vendor-icons-DLvvGkeN.js +0 -1
|
@@ -14,6 +14,7 @@ import { SQLiteChatMessageHistory } from '../runtime/memory/sqlite.js';
|
|
|
14
14
|
import { SatiRepository } from '../runtime/memory/sati/repository.js';
|
|
15
15
|
import { MCPManager } from '../config/mcp-manager.js';
|
|
16
16
|
import { Construtor } from '../runtime/tools/factory.js';
|
|
17
|
+
import { AuditRepository } from '../runtime/audit/repository.js';
|
|
17
18
|
function escapeHtml(text) {
|
|
18
19
|
return text
|
|
19
20
|
.replace(/&/g, '&')
|
|
@@ -310,8 +311,34 @@ export class TelegramAdapter {
|
|
|
310
311
|
filePath = await this.downloadToTemp(fileLink);
|
|
311
312
|
// Transcribe
|
|
312
313
|
this.display.log(`Transcribing audio for @${user}...`, { source: 'Telephonist' });
|
|
314
|
+
const transcribeStart = Date.now();
|
|
313
315
|
const { text, usage } = await this.telephonist.transcribe(filePath, 'audio/ogg', apiKey);
|
|
316
|
+
const transcribeDurationMs = Date.now() - transcribeStart;
|
|
314
317
|
this.display.log(`Transcription success for @${user}: "${text}"`, { source: 'Telephonist', level: 'success' });
|
|
318
|
+
// Audit: record telephonist execution
|
|
319
|
+
try {
|
|
320
|
+
const sessionId = await this.history.getCurrentSessionOrCreate();
|
|
321
|
+
AuditRepository.getInstance().insert({
|
|
322
|
+
session_id: sessionId,
|
|
323
|
+
event_type: 'telephonist',
|
|
324
|
+
agent: 'telephonist',
|
|
325
|
+
provider: config.audio.provider,
|
|
326
|
+
model: config.audio.model,
|
|
327
|
+
input_tokens: usage.input_tokens ?? null,
|
|
328
|
+
output_tokens: usage.output_tokens ?? null,
|
|
329
|
+
duration_ms: transcribeDurationMs,
|
|
330
|
+
status: 'success',
|
|
331
|
+
metadata: {
|
|
332
|
+
audio_duration_seconds: usage.audio_duration_seconds ?? duration,
|
|
333
|
+
text_preview: text.slice(0, 120),
|
|
334
|
+
text_length: text.length,
|
|
335
|
+
user,
|
|
336
|
+
},
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
catch {
|
|
340
|
+
// Audit failure never breaks the main flow
|
|
341
|
+
}
|
|
315
342
|
// Reply with transcription (optional, maybe just process it?)
|
|
316
343
|
// The prompt says "reply with the answer".
|
|
317
344
|
// "Transcribe them... and process the resulting text as a standard user prompt."
|
|
@@ -352,6 +379,22 @@ export class TelegramAdapter {
|
|
|
352
379
|
catch (error) {
|
|
353
380
|
const detail = error?.cause?.message || error?.response?.data?.error?.message || error.message;
|
|
354
381
|
this.display.log(`Audio processing error for @${user}: ${detail}`, { source: 'Telephonist', level: 'error' });
|
|
382
|
+
try {
|
|
383
|
+
const sessionId = await this.history.getCurrentSessionOrCreate();
|
|
384
|
+
AuditRepository.getInstance().insert({
|
|
385
|
+
session_id: sessionId,
|
|
386
|
+
event_type: 'telephonist',
|
|
387
|
+
agent: 'telephonist',
|
|
388
|
+
provider: this.config.get().audio.provider,
|
|
389
|
+
model: this.config.get().audio.model,
|
|
390
|
+
duration_ms: null,
|
|
391
|
+
status: 'error',
|
|
392
|
+
metadata: { error: detail, user },
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
catch {
|
|
396
|
+
// Audit failure never breaks the main flow
|
|
397
|
+
}
|
|
355
398
|
await ctx.reply("Sorry, I failed to process your audio message.");
|
|
356
399
|
}
|
|
357
400
|
finally {
|
package/dist/config/manager.js
CHANGED
|
@@ -151,7 +151,9 @@ export class ConfigManager {
|
|
|
151
151
|
base_url: config.sati.base_url || config.llm.base_url,
|
|
152
152
|
context_window: config.sati.context_window !== undefined ? resolveNumeric('MORPHEUS_SATI_CONTEXT_WINDOW', config.sati.context_window, config.sati.context_window) : llmConfig.context_window,
|
|
153
153
|
memory_limit: config.sati.memory_limit !== undefined ? resolveNumeric('MORPHEUS_SATI_MEMORY_LIMIT', config.sati.memory_limit, config.sati.memory_limit) : undefined,
|
|
154
|
-
enabled_archived_sessions: resolveBoolean('MORPHEUS_SATI_ENABLED_ARCHIVED_SESSIONS', config.sati.enabled_archived_sessions, true)
|
|
154
|
+
enabled_archived_sessions: resolveBoolean('MORPHEUS_SATI_ENABLED_ARCHIVED_SESSIONS', config.sati.enabled_archived_sessions, true),
|
|
155
|
+
similarity_threshold: resolveNumeric('MORPHEUS_SATI_SIMILARITY_THRESHOLD', config.sati.similarity_threshold, 0.9),
|
|
156
|
+
evaluation_interval: resolveNumeric('MORPHEUS_SATI_EVALUATION_INTERVAL', config.sati.evaluation_interval, 1),
|
|
155
157
|
};
|
|
156
158
|
}
|
|
157
159
|
// Apply precedence to Apoc config
|
package/dist/config/schemas.js
CHANGED
|
@@ -21,6 +21,8 @@ export const LLMConfigSchema = z.object({
|
|
|
21
21
|
export const SatiConfigSchema = LLMConfigSchema.extend({
|
|
22
22
|
memory_limit: z.number().int().positive().optional(),
|
|
23
23
|
enabled_archived_sessions: z.boolean().default(true),
|
|
24
|
+
similarity_threshold: z.number().min(0).max(1).default(0.9),
|
|
25
|
+
evaluation_interval: z.number().int().min(1).default(1),
|
|
24
26
|
});
|
|
25
27
|
export const ApocConfigSchema = LLMConfigSchema.extend({
|
|
26
28
|
working_dir: z.string().optional(),
|
package/dist/http/api.js
CHANGED
|
@@ -178,8 +178,30 @@ export function createApiRouter(oracle, chronosWorker) {
|
|
|
178
178
|
duration_ms: row.duration_ms ?? null,
|
|
179
179
|
provider: row.provider ?? null,
|
|
180
180
|
model: row.model ?? null,
|
|
181
|
+
audio_duration_seconds: row.audio_duration_seconds ?? null,
|
|
182
|
+
sati_memories_count: null,
|
|
181
183
|
};
|
|
182
184
|
});
|
|
185
|
+
// Enrich AI messages with Sati memory recovery counts from audit events
|
|
186
|
+
const recoveryEvents = AuditRepository.getInstance()
|
|
187
|
+
.getBySession(id, { limit: 10_000 })
|
|
188
|
+
.filter(e => e.event_type === 'memory_recovery')
|
|
189
|
+
.sort((a, b) => a.created_at - b.created_at);
|
|
190
|
+
for (let ri = 0; ri < recoveryEvents.length; ri++) {
|
|
191
|
+
const ev = recoveryEvents[ri];
|
|
192
|
+
const windowEnd = recoveryEvents[ri + 1]?.created_at ?? Infinity;
|
|
193
|
+
const count = ev.metadata ? (JSON.parse(ev.metadata).memories_count ?? 0) : 0;
|
|
194
|
+
if (count === 0)
|
|
195
|
+
continue;
|
|
196
|
+
// Assign to the LAST ai message within this recovery's window
|
|
197
|
+
for (let mi = normalizedMessages.length - 1; mi >= 0; mi--) {
|
|
198
|
+
const m = normalizedMessages[mi];
|
|
199
|
+
if (m.type === 'ai' && m.created_at > ev.created_at && m.created_at < windowEnd) {
|
|
200
|
+
m.sati_memories_count = count;
|
|
201
|
+
break;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
183
205
|
// Convert DESC to ASC for UI rendering
|
|
184
206
|
res.json(normalizedMessages.reverse());
|
|
185
207
|
}
|
|
@@ -190,7 +212,16 @@ export function createApiRouter(oracle, chronosWorker) {
|
|
|
190
212
|
sessionHistory.close();
|
|
191
213
|
}
|
|
192
214
|
});
|
|
193
|
-
// ---
|
|
215
|
+
// --- Audit ---
|
|
216
|
+
router.get('/audit/global', (_req, res) => {
|
|
217
|
+
try {
|
|
218
|
+
const summary = AuditRepository.getInstance().getGlobalSummary();
|
|
219
|
+
res.json(summary);
|
|
220
|
+
}
|
|
221
|
+
catch (err) {
|
|
222
|
+
res.status(500).json({ error: err.message });
|
|
223
|
+
}
|
|
224
|
+
});
|
|
194
225
|
router.get('/sessions/:id/audit', (req, res) => {
|
|
195
226
|
try {
|
|
196
227
|
const { id } = req.params;
|
|
@@ -238,14 +269,25 @@ export function createApiRouter(oracle, chronosWorker) {
|
|
|
238
269
|
const originChannel = req.query.origin_channel;
|
|
239
270
|
const sessionId = req.query.session_id;
|
|
240
271
|
const limit = req.query.limit;
|
|
272
|
+
const pageRaw = req.query.page;
|
|
273
|
+
const perPageRaw = req.query.per_page;
|
|
241
274
|
const parsedStatus = typeof status === 'string' ? TaskStatusSchema.safeParse(status) : null;
|
|
242
275
|
const parsedAgent = typeof agent === 'string' ? TaskAgentSchema.safeParse(agent) : null;
|
|
243
276
|
const parsedOrigin = typeof originChannel === 'string' ? OriginChannelSchema.safeParse(originChannel) : null;
|
|
244
|
-
const
|
|
277
|
+
const filters = {
|
|
245
278
|
status: parsedStatus?.success ? parsedStatus.data : undefined,
|
|
246
279
|
agent: parsedAgent?.success ? parsedAgent.data : undefined,
|
|
247
280
|
origin_channel: parsedOrigin?.success ? parsedOrigin.data : undefined,
|
|
248
281
|
session_id: typeof sessionId === 'string' ? sessionId : undefined,
|
|
282
|
+
};
|
|
283
|
+
if (pageRaw !== undefined || perPageRaw !== undefined) {
|
|
284
|
+
const page = Math.max(1, parseInt(String(pageRaw ?? '1'), 10) || 1);
|
|
285
|
+
const per_page = Math.min(100, Math.max(1, parseInt(String(perPageRaw ?? '20'), 10) || 20));
|
|
286
|
+
const result = taskRepository.listTasksPaginated({ ...filters, page, per_page });
|
|
287
|
+
return res.json(result);
|
|
288
|
+
}
|
|
289
|
+
const tasks = taskRepository.listTasks({
|
|
290
|
+
...filters,
|
|
249
291
|
limit: typeof limit === 'string' ? Math.max(1, Math.min(500, Number(limit) || 200)) : 200,
|
|
250
292
|
});
|
|
251
293
|
res.json(tasks);
|
|
@@ -403,7 +445,8 @@ export function createApiRouter(oracle, chronosWorker) {
|
|
|
403
445
|
provider: z.string().min(1),
|
|
404
446
|
model: z.string().min(1),
|
|
405
447
|
input_price_per_1m: z.number().nonnegative(),
|
|
406
|
-
output_price_per_1m: z.number().nonnegative()
|
|
448
|
+
output_price_per_1m: z.number().nonnegative(),
|
|
449
|
+
audio_cost_per_second: z.number().nonnegative().nullable().optional(),
|
|
407
450
|
});
|
|
408
451
|
router.get('/model-pricing', (req, res) => {
|
|
409
452
|
try {
|
|
@@ -435,7 +478,8 @@ export function createApiRouter(oracle, chronosWorker) {
|
|
|
435
478
|
const { provider, model } = req.params;
|
|
436
479
|
const partial = z.object({
|
|
437
480
|
input_price_per_1m: z.number().nonnegative().optional(),
|
|
438
|
-
output_price_per_1m: z.number().nonnegative().optional()
|
|
481
|
+
output_price_per_1m: z.number().nonnegative().optional(),
|
|
482
|
+
audio_cost_per_second: z.number().nonnegative().nullable().optional(),
|
|
439
483
|
}).safeParse(req.body);
|
|
440
484
|
if (!partial.success) {
|
|
441
485
|
return res.status(400).json({ error: 'Invalid payload', details: partial.error.issues });
|
|
@@ -451,7 +495,10 @@ export function createApiRouter(oracle, chronosWorker) {
|
|
|
451
495
|
provider,
|
|
452
496
|
model,
|
|
453
497
|
input_price_per_1m: partial.data.input_price_per_1m ?? existing.input_price_per_1m,
|
|
454
|
-
output_price_per_1m: partial.data.output_price_per_1m ?? existing.output_price_per_1m
|
|
498
|
+
output_price_per_1m: partial.data.output_price_per_1m ?? existing.output_price_per_1m,
|
|
499
|
+
audio_cost_per_second: 'audio_cost_per_second' in partial.data
|
|
500
|
+
? partial.data.audio_cost_per_second
|
|
501
|
+
: existing.audio_cost_per_second,
|
|
455
502
|
});
|
|
456
503
|
h.close();
|
|
457
504
|
res.json({ success: true });
|
|
@@ -680,15 +727,25 @@ export function createApiRouter(oracle, chronosWorker) {
|
|
|
680
727
|
router.get('/sati/memories', async (req, res) => {
|
|
681
728
|
try {
|
|
682
729
|
const repository = SatiRepository.getInstance();
|
|
683
|
-
const
|
|
684
|
-
|
|
685
|
-
const
|
|
730
|
+
const pageRaw = req.query.page;
|
|
731
|
+
const perPageRaw = req.query.per_page;
|
|
732
|
+
const category = typeof req.query.category === 'string' && req.query.category !== 'all' ? req.query.category : undefined;
|
|
733
|
+
const importance = typeof req.query.importance === 'string' && req.query.importance !== 'all' ? req.query.importance : undefined;
|
|
734
|
+
const search = typeof req.query.search === 'string' && req.query.search.trim() ? req.query.search.trim() : undefined;
|
|
735
|
+
const serializeMemory = (memory) => ({
|
|
686
736
|
...memory,
|
|
687
|
-
created_at: memory.created_at.toISOString(),
|
|
688
|
-
updated_at: memory.updated_at.toISOString(),
|
|
689
|
-
last_accessed_at: memory.last_accessed_at ? memory.last_accessed_at.toISOString() : null
|
|
690
|
-
})
|
|
691
|
-
|
|
737
|
+
created_at: memory.created_at instanceof Date ? memory.created_at.toISOString() : memory.created_at,
|
|
738
|
+
updated_at: memory.updated_at instanceof Date ? memory.updated_at.toISOString() : memory.updated_at,
|
|
739
|
+
last_accessed_at: memory.last_accessed_at instanceof Date ? memory.last_accessed_at.toISOString() : (memory.last_accessed_at ?? null),
|
|
740
|
+
});
|
|
741
|
+
if (pageRaw !== undefined || perPageRaw !== undefined) {
|
|
742
|
+
const page = Math.max(1, parseInt(String(pageRaw ?? '1'), 10) || 1);
|
|
743
|
+
const per_page = Math.min(100, Math.max(1, parseInt(String(perPageRaw ?? '20'), 10) || 20));
|
|
744
|
+
const result = repository.getMemoriesPaginated({ category, importance, search, page, per_page });
|
|
745
|
+
return res.json({ ...result, data: result.data.map(serializeMemory) });
|
|
746
|
+
}
|
|
747
|
+
const memories = repository.getAllMemories();
|
|
748
|
+
res.json(memories.map(serializeMemory));
|
|
692
749
|
}
|
|
693
750
|
catch (error) {
|
|
694
751
|
res.status(500).json({ error: error.message });
|
|
@@ -75,11 +75,22 @@ export function createChronosJobRouter(repo, _worker) {
|
|
|
75
75
|
res.status(400).json({ error: err.message });
|
|
76
76
|
}
|
|
77
77
|
});
|
|
78
|
-
// GET /api/chronos — list jobs
|
|
78
|
+
// GET /api/chronos — list jobs (paginated)
|
|
79
79
|
router.get('/', (req, res) => {
|
|
80
80
|
try {
|
|
81
81
|
const enabled = req.query.enabled;
|
|
82
82
|
const created_by = req.query.created_by;
|
|
83
|
+
const page = req.query.page ? Math.max(1, parseInt(String(req.query.page), 10) || 1) : undefined;
|
|
84
|
+
const per_page = req.query.per_page ? Math.min(100, Math.max(1, parseInt(String(req.query.per_page), 10) || 20)) : undefined;
|
|
85
|
+
if (page !== undefined || per_page !== undefined) {
|
|
86
|
+
const result = repo.listJobsPaginated({
|
|
87
|
+
enabled: enabled === 'true' ? true : enabled === 'false' ? false : undefined,
|
|
88
|
+
created_by,
|
|
89
|
+
page,
|
|
90
|
+
per_page,
|
|
91
|
+
});
|
|
92
|
+
return res.json(result);
|
|
93
|
+
}
|
|
83
94
|
const jobs = repo.listJobs({
|
|
84
95
|
enabled: enabled === 'true' ? true : enabled === 'false' ? false : undefined,
|
|
85
96
|
created_by,
|
|
@@ -74,11 +74,19 @@ export function createWebhooksRouter() {
|
|
|
74
74
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
75
75
|
router.get('/notifications', authMiddleware, (req, res) => {
|
|
76
76
|
try {
|
|
77
|
-
const { webhookId, unreadOnly } = req.query;
|
|
78
|
-
const
|
|
77
|
+
const { webhookId, unreadOnly, status, page: pageRaw, per_page: perPageRaw } = req.query;
|
|
78
|
+
const filters = {
|
|
79
79
|
webhookId: typeof webhookId === 'string' ? webhookId : undefined,
|
|
80
80
|
unreadOnly: unreadOnly === 'true',
|
|
81
|
-
|
|
81
|
+
status: typeof status === 'string' && status !== 'all' ? status : undefined,
|
|
82
|
+
};
|
|
83
|
+
if (pageRaw !== undefined || perPageRaw !== undefined) {
|
|
84
|
+
const page = Math.max(1, parseInt(String(pageRaw ?? '1'), 10) || 1);
|
|
85
|
+
const per_page = Math.min(100, Math.max(1, parseInt(String(perPageRaw ?? '20'), 10) || 20));
|
|
86
|
+
const result = repo.listNotificationsPaginated({ ...filters, page, per_page });
|
|
87
|
+
return res.json(result);
|
|
88
|
+
}
|
|
89
|
+
const notifications = repo.listNotifications(filters);
|
|
82
90
|
res.json(notifications);
|
|
83
91
|
}
|
|
84
92
|
catch (err) {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/runtime/apoc.js
CHANGED
|
@@ -4,7 +4,8 @@ import { ProviderFactory } from "./providers/factory.js";
|
|
|
4
4
|
import { ProviderError } from "./errors.js";
|
|
5
5
|
import { DisplayManager } from "./display.js";
|
|
6
6
|
import { buildDevKit } from "../devkit/index.js";
|
|
7
|
-
import {
|
|
7
|
+
import { extractRawUsage, persistAgentMessage, buildAgentResult, emitToolAuditEvents } from "./subagent-utils.js";
|
|
8
|
+
import { buildDelegationTool } from "./tools/delegation-utils.js";
|
|
8
9
|
/**
|
|
9
10
|
* Apoc is a subagent of Oracle specialized in devtools operations.
|
|
10
11
|
* It receives delegated tasks from Oracle and executes them using DevKit tools
|
|
@@ -17,6 +18,7 @@ import { SQLiteChatMessageHistory } from "./memory/sqlite.js";
|
|
|
17
18
|
export class Apoc {
|
|
18
19
|
static instance = null;
|
|
19
20
|
static currentSessionId = undefined;
|
|
21
|
+
static _delegateTool = null;
|
|
20
22
|
agent;
|
|
21
23
|
config;
|
|
22
24
|
display = DisplayManager.getInstance();
|
|
@@ -38,10 +40,10 @@ export class Apoc {
|
|
|
38
40
|
}
|
|
39
41
|
static resetInstance() {
|
|
40
42
|
Apoc.instance = null;
|
|
43
|
+
Apoc._delegateTool = null;
|
|
41
44
|
}
|
|
42
45
|
async initialize() {
|
|
43
46
|
const apocConfig = this.config.apoc || this.config.llm;
|
|
44
|
-
// console.log(`Apoc configuration: ${JSON.stringify(apocConfig)}`);
|
|
45
47
|
const devkit = ConfigManager.getInstance().getDevKitConfig();
|
|
46
48
|
const timeout_ms = devkit.timeout_ms || this.config.apoc?.timeout_ms || 30_000;
|
|
47
49
|
const personality = this.config.apoc?.personality || 'pragmatic_dev';
|
|
@@ -73,8 +75,9 @@ export class Apoc {
|
|
|
73
75
|
* @param task Natural language task description
|
|
74
76
|
* @param context Optional additional context from the ongoing conversation
|
|
75
77
|
* @param sessionId Session to attribute token usage to (defaults to 'apoc')
|
|
78
|
+
* @param taskContext Optional Oracle task context (unused by Apoc directly — kept for ISubagent compatibility)
|
|
76
79
|
*/
|
|
77
|
-
async execute(task, context, sessionId) {
|
|
80
|
+
async execute(task, context, sessionId, taskContext) {
|
|
78
81
|
if (!this.agent) {
|
|
79
82
|
await this.initialize();
|
|
80
83
|
}
|
|
@@ -194,7 +197,7 @@ If refinement is triggered:
|
|
|
194
197
|
|
|
195
198
|
2. Execute a second search cycle (Cycle 2).
|
|
196
199
|
3. Repeat selection, navigation, extraction, verification.
|
|
197
|
-
4. Choose the stronger cycle
|
|
200
|
+
4. Choose the stronger cycle's evidence.
|
|
198
201
|
5. Do NOT perform more than 2 cycles.
|
|
199
202
|
|
|
200
203
|
If Cycle 2 also fails:
|
|
@@ -239,10 +242,10 @@ LOW:
|
|
|
239
242
|
OUTPUT FORMAT (STRICT)
|
|
240
243
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
241
244
|
|
|
242
|
-
1. Direct Answer
|
|
243
|
-
2. Evidence Summary
|
|
244
|
-
3. Sources (URLs)
|
|
245
|
-
4. Confidence Level (HIGH / MEDIUM / LOW)
|
|
245
|
+
1. Direct Answer
|
|
246
|
+
2. Evidence Summary
|
|
247
|
+
3. Sources (URLs)
|
|
248
|
+
4. Confidence Level (HIGH / MEDIUM / LOW)
|
|
246
249
|
5. Completion Status (true / false)
|
|
247
250
|
|
|
248
251
|
No conversational filler.
|
|
@@ -254,6 +257,7 @@ ${context ? `CONTEXT FROM ORACLE:\n${context}` : ""}
|
|
|
254
257
|
const userMessage = new HumanMessage(task);
|
|
255
258
|
const messages = [systemMessage, userMessage];
|
|
256
259
|
try {
|
|
260
|
+
const inputCount = messages.length;
|
|
257
261
|
const startMs = Date.now();
|
|
258
262
|
const response = await this.agent.invoke({ messages });
|
|
259
263
|
const durationMs = Date.now() - startMs;
|
|
@@ -262,45 +266,51 @@ ${context ? `CONTEXT FROM ORACLE:\n${context}` : ""}
|
|
|
262
266
|
const content = typeof lastMessage.content === "string"
|
|
263
267
|
? lastMessage.content
|
|
264
268
|
: JSON.stringify(lastMessage.content);
|
|
265
|
-
|
|
266
|
-
const rawUsage = lastMessage.usage_metadata
|
|
267
|
-
?? lastMessage.response_metadata?.usage
|
|
268
|
-
?? lastMessage.response_metadata?.tokenUsage
|
|
269
|
-
?? lastMessage.usage;
|
|
270
|
-
const inputTokens = rawUsage?.input_tokens ?? 0;
|
|
271
|
-
const outputTokens = rawUsage?.output_tokens ?? 0;
|
|
269
|
+
const rawUsage = extractRawUsage(lastMessage);
|
|
272
270
|
const stepCount = response.messages.filter((m) => m instanceof AIMessage).length;
|
|
273
271
|
const targetSession = sessionId ?? Apoc.currentSessionId ?? "apoc";
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
const persisted = new AIMessage(content);
|
|
277
|
-
if (rawUsage)
|
|
278
|
-
persisted.usage_metadata = rawUsage;
|
|
279
|
-
persisted.provider_metadata = { provider: apocConfig.provider, model: apocConfig.model };
|
|
280
|
-
persisted.agent_metadata = { agent: 'apoc' };
|
|
281
|
-
persisted.duration_ms = durationMs;
|
|
282
|
-
await history.addMessage(persisted);
|
|
283
|
-
}
|
|
284
|
-
finally {
|
|
285
|
-
history.close();
|
|
286
|
-
}
|
|
272
|
+
await persistAgentMessage('apoc', content, apocConfig, targetSession, rawUsage, durationMs);
|
|
273
|
+
emitToolAuditEvents(response.messages.slice(inputCount), targetSession, 'apoc');
|
|
287
274
|
this.display.log("Apoc task completed.", { source: "Apoc" });
|
|
288
|
-
return
|
|
289
|
-
output: content,
|
|
290
|
-
usage: {
|
|
291
|
-
provider: apocConfig.provider,
|
|
292
|
-
model: apocConfig.model,
|
|
293
|
-
inputTokens,
|
|
294
|
-
outputTokens,
|
|
295
|
-
durationMs,
|
|
296
|
-
stepCount,
|
|
297
|
-
},
|
|
298
|
-
};
|
|
275
|
+
return buildAgentResult(content, apocConfig, rawUsage, durationMs, stepCount);
|
|
299
276
|
}
|
|
300
277
|
catch (err) {
|
|
301
278
|
throw new ProviderError(this.config.apoc?.provider || this.config.llm.provider, err, "Apoc task execution failed");
|
|
302
279
|
}
|
|
303
280
|
}
|
|
281
|
+
createDelegateTool() {
|
|
282
|
+
if (!Apoc._delegateTool) {
|
|
283
|
+
Apoc._delegateTool = buildDelegationTool({
|
|
284
|
+
name: "apoc_delegate",
|
|
285
|
+
description: `Delegate a devtools task to Apoc, the specialized development subagent.
|
|
286
|
+
|
|
287
|
+
This tool enqueues a background task and returns an acknowledgement with task id.
|
|
288
|
+
Do not expect final execution output in the same response.
|
|
289
|
+
Each task must contain a single atomic action with a clear expected result.
|
|
290
|
+
|
|
291
|
+
Use this tool when the user asks for ANY of the following:
|
|
292
|
+
- File operations: read, write, create, delete files or directories
|
|
293
|
+
- Shell commands: run scripts, execute commands, check output
|
|
294
|
+
- Git: status, log, diff, commit, push, pull, clone, branch
|
|
295
|
+
- Package management: npm install/update/audit, yarn, package.json inspection
|
|
296
|
+
- Process management: list processes, kill processes, check ports
|
|
297
|
+
- Network: ping hosts, curl URLs, DNS lookups
|
|
298
|
+
- System info: environment variables, OS info, disk space, memory
|
|
299
|
+
- Internet search: search DuckDuckGo and verify facts by reading at least 3 sources via browser_navigate before reporting results.
|
|
300
|
+
- Browser automation: navigate websites (JS/SPA), inspect DOM, click elements, fill forms. Apoc will ask for missing user input (e.g. credentials, form fields) before proceeding.
|
|
301
|
+
|
|
302
|
+
Provide a clear natural language task description. Optionally provide context
|
|
303
|
+
from the current conversation to help Apoc understand the broader goal.`,
|
|
304
|
+
agentKey: "apoc",
|
|
305
|
+
agentLabel: "Apoc",
|
|
306
|
+
auditAgent: "apoc",
|
|
307
|
+
isSync: () => ConfigManager.getInstance().get().apoc?.execution_mode === 'sync',
|
|
308
|
+
notifyText: '🧑🔬 Apoc is executing your request...',
|
|
309
|
+
executeSync: (task, context, sessionId) => Apoc.getInstance().execute(task, context, sessionId),
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
return Apoc._delegateTool;
|
|
313
|
+
}
|
|
304
314
|
/** Reload with updated config (called when settings change) */
|
|
305
315
|
async reload() {
|
|
306
316
|
this.config = ConfigManager.getInstance().get();
|