morpheus-cli 0.8.6 → 0.8.8
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/http/api.js +49 -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/repository.js +37 -0
- package/dist/runtime/memory/sqlite.js +16 -3
- package/dist/runtime/neo.js +78 -34
- package/dist/runtime/oracle.js +31 -6
- package/dist/runtime/skills/tool.js +25 -0
- package/dist/runtime/smiths/delegator.js +21 -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/trinity.js +50 -34
- package/dist/runtime/webhooks/repository.js +31 -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-Bx-GtD9B.js → ConfirmationModal-fvgnOWTY.js} +1 -1
- package/dist/ui/assets/{Dashboard-DDyN_X-J.js → Dashboard-Ca5mSefz.js} +1 -1
- package/dist/ui/assets/{DeleteConfirmationModal-DIXbckY8.js → DeleteConfirmationModal-A8EmnHoa.js} +1 -1
- package/dist/ui/assets/{Logs-dzPLW45U.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-DNDe62-H.js → Settings-OQlHAJoy.js} +1 -1
- 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-doBLB7Lc.js → UsageStats-CBo2vW2n.js} +1 -1
- package/dist/ui/assets/{WebhookManager-D3A5pdjC.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-DX3Xb0XE.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-DfhJYN14.js → mcp-DxzodOdH.js} +1 -1
- package/dist/ui/assets/{skills-BPjq0qV7.js → skills--hAyQnmG.js} +1 -1
- package/dist/ui/assets/{stats-DHCRNkJp.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-CO15OnaY.js +0 -38
- package/dist/ui/assets/Chronos-CUZDQLh2.js +0 -1
- package/dist/ui/assets/MCPManager-CRHWR4S7.js +0 -1
- package/dist/ui/assets/ModelPricing-TRBesy0r.js +0 -1
- package/dist/ui/assets/Notifications-DMke7Dr7.js +0 -1
- package/dist/ui/assets/SatiMemories-CaLrgdZV.js +0 -1
- package/dist/ui/assets/SessionAudit-DedGO5XK.js +0 -9
- package/dist/ui/assets/Skills-KUhW7UXP.js +0 -7
- package/dist/ui/assets/Smiths-Btoqw4Ex.js +0 -1
- package/dist/ui/assets/Tasks-cwA25Hq2.js +0 -1
- package/dist/ui/assets/TrinityDatabases-CQhettEJ.js +0 -1
- package/dist/ui/assets/chronos-DlDM2UBT.js +0 -1
- package/dist/ui/assets/index-CQIUjucB.js +0 -10
- package/dist/ui/assets/index-DAh3q_hR.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/http/api.js
CHANGED
|
@@ -178,6 +178,7 @@ 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,
|
|
181
182
|
sati_memories_count: null,
|
|
182
183
|
};
|
|
183
184
|
});
|
|
@@ -211,7 +212,16 @@ export function createApiRouter(oracle, chronosWorker) {
|
|
|
211
212
|
sessionHistory.close();
|
|
212
213
|
}
|
|
213
214
|
});
|
|
214
|
-
// ---
|
|
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
|
+
});
|
|
215
225
|
router.get('/sessions/:id/audit', (req, res) => {
|
|
216
226
|
try {
|
|
217
227
|
const { id } = req.params;
|
|
@@ -259,14 +269,25 @@ export function createApiRouter(oracle, chronosWorker) {
|
|
|
259
269
|
const originChannel = req.query.origin_channel;
|
|
260
270
|
const sessionId = req.query.session_id;
|
|
261
271
|
const limit = req.query.limit;
|
|
272
|
+
const pageRaw = req.query.page;
|
|
273
|
+
const perPageRaw = req.query.per_page;
|
|
262
274
|
const parsedStatus = typeof status === 'string' ? TaskStatusSchema.safeParse(status) : null;
|
|
263
275
|
const parsedAgent = typeof agent === 'string' ? TaskAgentSchema.safeParse(agent) : null;
|
|
264
276
|
const parsedOrigin = typeof originChannel === 'string' ? OriginChannelSchema.safeParse(originChannel) : null;
|
|
265
|
-
const
|
|
277
|
+
const filters = {
|
|
266
278
|
status: parsedStatus?.success ? parsedStatus.data : undefined,
|
|
267
279
|
agent: parsedAgent?.success ? parsedAgent.data : undefined,
|
|
268
280
|
origin_channel: parsedOrigin?.success ? parsedOrigin.data : undefined,
|
|
269
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,
|
|
270
291
|
limit: typeof limit === 'string' ? Math.max(1, Math.min(500, Number(limit) || 200)) : 200,
|
|
271
292
|
});
|
|
272
293
|
res.json(tasks);
|
|
@@ -424,7 +445,8 @@ export function createApiRouter(oracle, chronosWorker) {
|
|
|
424
445
|
provider: z.string().min(1),
|
|
425
446
|
model: z.string().min(1),
|
|
426
447
|
input_price_per_1m: z.number().nonnegative(),
|
|
427
|
-
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(),
|
|
428
450
|
});
|
|
429
451
|
router.get('/model-pricing', (req, res) => {
|
|
430
452
|
try {
|
|
@@ -456,7 +478,8 @@ export function createApiRouter(oracle, chronosWorker) {
|
|
|
456
478
|
const { provider, model } = req.params;
|
|
457
479
|
const partial = z.object({
|
|
458
480
|
input_price_per_1m: z.number().nonnegative().optional(),
|
|
459
|
-
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(),
|
|
460
483
|
}).safeParse(req.body);
|
|
461
484
|
if (!partial.success) {
|
|
462
485
|
return res.status(400).json({ error: 'Invalid payload', details: partial.error.issues });
|
|
@@ -472,7 +495,10 @@ export function createApiRouter(oracle, chronosWorker) {
|
|
|
472
495
|
provider,
|
|
473
496
|
model,
|
|
474
497
|
input_price_per_1m: partial.data.input_price_per_1m ?? existing.input_price_per_1m,
|
|
475
|
-
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,
|
|
476
502
|
});
|
|
477
503
|
h.close();
|
|
478
504
|
res.json({ success: true });
|
|
@@ -701,15 +727,25 @@ export function createApiRouter(oracle, chronosWorker) {
|
|
|
701
727
|
router.get('/sati/memories', async (req, res) => {
|
|
702
728
|
try {
|
|
703
729
|
const repository = SatiRepository.getInstance();
|
|
704
|
-
const
|
|
705
|
-
|
|
706
|
-
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) => ({
|
|
707
736
|
...memory,
|
|
708
|
-
created_at: memory.created_at.toISOString(),
|
|
709
|
-
updated_at: memory.updated_at.toISOString(),
|
|
710
|
-
last_accessed_at: memory.last_accessed_at ? memory.last_accessed_at.toISOString() : null
|
|
711
|
-
})
|
|
712
|
-
|
|
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));
|
|
713
749
|
}
|
|
714
750
|
catch (error) {
|
|
715
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();
|
|
@@ -65,7 +65,24 @@ export class AuditRepository {
|
|
|
65
65
|
const rows = this.db.prepare(`
|
|
66
66
|
SELECT ae.*,
|
|
67
67
|
CASE
|
|
68
|
-
|
|
68
|
+
-- Telephonist: prefer audio_cost_per_second when set
|
|
69
|
+
WHEN ae.event_type = 'telephonist'
|
|
70
|
+
AND mp.audio_cost_per_second IS NOT NULL
|
|
71
|
+
AND mp.audio_cost_per_second > 0
|
|
72
|
+
THEN
|
|
73
|
+
COALESCE(CAST(json_extract(ae.metadata, '$.audio_duration_seconds') AS REAL), 0)
|
|
74
|
+
* mp.audio_cost_per_second
|
|
75
|
+
-- Telephonist with token-based pricing (e.g. Gemini/OpenRouter with real tokens)
|
|
76
|
+
WHEN ae.event_type = 'telephonist'
|
|
77
|
+
AND ae.provider IS NOT NULL AND ae.model IS NOT NULL
|
|
78
|
+
AND ae.input_tokens IS NOT NULL AND ae.input_tokens > 0
|
|
79
|
+
THEN (
|
|
80
|
+
COALESCE(ae.input_tokens, 0) / 1000000.0 * COALESCE(mp.input_price_per_1m, 0) +
|
|
81
|
+
COALESCE(ae.output_tokens, 0) / 1000000.0 * COALESCE(mp.output_price_per_1m, 0)
|
|
82
|
+
)
|
|
83
|
+
-- All other events: token-based
|
|
84
|
+
WHEN ae.event_type != 'telephonist'
|
|
85
|
+
AND ae.provider IS NOT NULL AND ae.model IS NOT NULL AND ae.input_tokens IS NOT NULL
|
|
69
86
|
THEN (
|
|
70
87
|
COALESCE(ae.input_tokens, 0) / 1000000.0 * COALESCE(mp.input_price_per_1m, 0) +
|
|
71
88
|
COALESCE(ae.output_tokens, 0) / 1000000.0 * COALESCE(mp.output_price_per_1m, 0)
|
|
@@ -84,11 +101,16 @@ export class AuditRepository {
|
|
|
84
101
|
const events = this.getBySession(sessionId, { limit: 10_000 });
|
|
85
102
|
const llmEvents = events.filter(e => e.event_type === 'llm_call');
|
|
86
103
|
const toolEvents = events.filter(e => e.event_type === 'tool_call' || e.event_type === 'mcp_tool');
|
|
87
|
-
const
|
|
104
|
+
const telephonistEvents = events.filter(e => e.event_type === 'telephonist');
|
|
105
|
+
const totalCostUsd = [...llmEvents, ...telephonistEvents].reduce((sum, e) => sum + (e.estimated_cost_usd ?? 0), 0);
|
|
88
106
|
const totalDurationMs = events.reduce((sum, e) => sum + (e.duration_ms ?? 0), 0);
|
|
89
|
-
|
|
107
|
+
const totalAudioSeconds = telephonistEvents.reduce((sum, e) => {
|
|
108
|
+
const meta = e.metadata ? JSON.parse(e.metadata) : null;
|
|
109
|
+
return sum + (meta?.audio_duration_seconds ?? 0);
|
|
110
|
+
}, 0);
|
|
111
|
+
// By agent (llm + telephonist)
|
|
90
112
|
const agentMap = new Map();
|
|
91
|
-
for (const e of llmEvents) {
|
|
113
|
+
for (const e of [...llmEvents, ...telephonistEvents]) {
|
|
92
114
|
const key = e.agent ?? 'unknown';
|
|
93
115
|
const existing = agentMap.get(key) ?? { llmCalls: 0, inputTokens: 0, outputTokens: 0, estimatedCostUsd: 0 };
|
|
94
116
|
agentMap.set(key, {
|
|
@@ -98,9 +120,9 @@ export class AuditRepository {
|
|
|
98
120
|
estimatedCostUsd: existing.estimatedCostUsd + (e.estimated_cost_usd ?? 0),
|
|
99
121
|
});
|
|
100
122
|
}
|
|
101
|
-
// By model
|
|
123
|
+
// By model (llm + telephonist)
|
|
102
124
|
const modelMap = new Map();
|
|
103
|
-
for (const e of llmEvents) {
|
|
125
|
+
for (const e of [...llmEvents, ...telephonistEvents]) {
|
|
104
126
|
if (!e.model)
|
|
105
127
|
continue;
|
|
106
128
|
const key = `${e.provider}/${e.model}`;
|
|
@@ -116,6 +138,7 @@ export class AuditRepository {
|
|
|
116
138
|
return {
|
|
117
139
|
totalCostUsd,
|
|
118
140
|
totalDurationMs,
|
|
141
|
+
totalAudioSeconds,
|
|
119
142
|
llmCallCount: llmEvents.length,
|
|
120
143
|
toolCallCount: toolEvents.length,
|
|
121
144
|
byAgent: Array.from(agentMap.entries()).map(([agent, s]) => ({ agent, ...s })),
|
|
@@ -129,4 +152,168 @@ export class AuditRepository {
|
|
|
129
152
|
})),
|
|
130
153
|
};
|
|
131
154
|
}
|
|
155
|
+
getGlobalSummary() {
|
|
156
|
+
// Reusable cost expression (telephonist + token-based)
|
|
157
|
+
const costExpr = `
|
|
158
|
+
CASE
|
|
159
|
+
WHEN ae.event_type = 'telephonist'
|
|
160
|
+
AND mp.audio_cost_per_second IS NOT NULL AND mp.audio_cost_per_second > 0
|
|
161
|
+
THEN COALESCE(CAST(json_extract(ae.metadata, '$.audio_duration_seconds') AS REAL), 0)
|
|
162
|
+
* mp.audio_cost_per_second
|
|
163
|
+
WHEN ae.event_type = 'telephonist'
|
|
164
|
+
AND ae.input_tokens IS NOT NULL AND ae.input_tokens > 0
|
|
165
|
+
THEN COALESCE(ae.input_tokens, 0) / 1000000.0 * COALESCE(mp.input_price_per_1m, 0)
|
|
166
|
+
+ COALESCE(ae.output_tokens, 0) / 1000000.0 * COALESCE(mp.output_price_per_1m, 0)
|
|
167
|
+
WHEN ae.event_type != 'telephonist'
|
|
168
|
+
AND ae.provider IS NOT NULL AND ae.input_tokens IS NOT NULL
|
|
169
|
+
THEN COALESCE(ae.input_tokens, 0) / 1000000.0 * COALESCE(mp.input_price_per_1m, 0)
|
|
170
|
+
+ COALESCE(ae.output_tokens, 0) / 1000000.0 * COALESCE(mp.output_price_per_1m, 0)
|
|
171
|
+
ELSE 0
|
|
172
|
+
END`;
|
|
173
|
+
// ── Sessions ─────────────────────────────────────────────────────────────
|
|
174
|
+
const sessionsRow = this.db.prepare(`
|
|
175
|
+
SELECT
|
|
176
|
+
COUNT(*) as total,
|
|
177
|
+
SUM(CASE WHEN status = 'active' THEN 1 ELSE 0 END) as active,
|
|
178
|
+
SUM(CASE WHEN status = 'paused' THEN 1 ELSE 0 END) as paused,
|
|
179
|
+
SUM(CASE WHEN status = 'archived' THEN 1 ELSE 0 END) as archived,
|
|
180
|
+
SUM(CASE WHEN status = 'deleted' THEN 1 ELSE 0 END) as deleted
|
|
181
|
+
FROM sessions
|
|
182
|
+
`).get();
|
|
183
|
+
const withAuditRow = this.db.prepare(`SELECT COUNT(DISTINCT session_id) as n FROM audit_events`).get();
|
|
184
|
+
// ── Global totals (single JOIN pass) ─────────────────────────────────────
|
|
185
|
+
const totalsRow = this.db.prepare(`
|
|
186
|
+
SELECT
|
|
187
|
+
COUNT(*) as totalEventCount,
|
|
188
|
+
SUM(CASE WHEN ae.event_type = 'llm_call' THEN 1 ELSE 0 END) as llmCallCount,
|
|
189
|
+
SUM(CASE WHEN ae.event_type = 'tool_call' THEN 1 ELSE 0 END) as toolCallCount,
|
|
190
|
+
SUM(CASE WHEN ae.event_type = 'mcp_tool' THEN 1 ELSE 0 END) as mcpToolCount,
|
|
191
|
+
SUM(CASE WHEN ae.event_type = 'skill_executed' THEN 1 ELSE 0 END) as skillCount,
|
|
192
|
+
SUM(CASE WHEN ae.event_type = 'memory_recovery' THEN 1 ELSE 0 END) as memoryRecoveryCount,
|
|
193
|
+
SUM(CASE WHEN ae.event_type = 'chronos_job' THEN 1 ELSE 0 END) as chronosJobCount,
|
|
194
|
+
SUM(CASE WHEN ae.event_type = 'task_created' THEN 1 ELSE 0 END) as taskCreatedCount,
|
|
195
|
+
SUM(CASE WHEN ae.event_type = 'task_completed' THEN 1 ELSE 0 END) as taskCompletedCount,
|
|
196
|
+
SUM(CASE WHEN ae.event_type = 'telephonist' THEN 1 ELSE 0 END) as telephonistCount,
|
|
197
|
+
COALESCE(SUM(ae.input_tokens), 0) as totalInputTokens,
|
|
198
|
+
COALESCE(SUM(ae.output_tokens), 0) as totalOutputTokens,
|
|
199
|
+
COALESCE(SUM(ae.duration_ms), 0) as totalDurationMs,
|
|
200
|
+
COALESCE(SUM(
|
|
201
|
+
CASE WHEN ae.event_type = 'telephonist'
|
|
202
|
+
THEN COALESCE(CAST(json_extract(ae.metadata,'$.audio_duration_seconds') AS REAL),0)
|
|
203
|
+
ELSE 0 END
|
|
204
|
+
), 0) as totalAudioSeconds,
|
|
205
|
+
COALESCE(SUM(${costExpr}), 0) as estimatedCostUsd
|
|
206
|
+
FROM audit_events ae
|
|
207
|
+
LEFT JOIN model_pricing mp ON mp.provider = ae.provider AND mp.model = ae.model
|
|
208
|
+
`).get();
|
|
209
|
+
// ── By agent ─────────────────────────────────────────────────────────────
|
|
210
|
+
const byAgentRows = this.db.prepare(`
|
|
211
|
+
SELECT
|
|
212
|
+
COALESCE(ae.agent, 'unknown') as agent,
|
|
213
|
+
SUM(CASE WHEN ae.event_type = 'llm_call' THEN 1 ELSE 0 END) as llmCalls,
|
|
214
|
+
SUM(CASE WHEN ae.event_type IN ('tool_call','mcp_tool') THEN 1 ELSE 0 END) as toolCalls,
|
|
215
|
+
COALESCE(SUM(ae.input_tokens), 0) as inputTokens,
|
|
216
|
+
COALESCE(SUM(ae.output_tokens), 0) as outputTokens,
|
|
217
|
+
COALESCE(SUM(ae.duration_ms), 0) as totalDurationMs,
|
|
218
|
+
COALESCE(SUM(${costExpr}), 0) as estimatedCostUsd
|
|
219
|
+
FROM audit_events ae
|
|
220
|
+
LEFT JOIN model_pricing mp ON mp.provider = ae.provider AND mp.model = ae.model
|
|
221
|
+
GROUP BY ae.agent
|
|
222
|
+
ORDER BY estimatedCostUsd DESC
|
|
223
|
+
`).all();
|
|
224
|
+
// ── By model ─────────────────────────────────────────────────────────────
|
|
225
|
+
const byModelRows = this.db.prepare(`
|
|
226
|
+
SELECT
|
|
227
|
+
ae.provider,
|
|
228
|
+
ae.model,
|
|
229
|
+
COUNT(*) as calls,
|
|
230
|
+
COALESCE(SUM(ae.input_tokens), 0) as inputTokens,
|
|
231
|
+
COALESCE(SUM(ae.output_tokens), 0) as outputTokens,
|
|
232
|
+
COALESCE(SUM(${costExpr}), 0) as estimatedCostUsd
|
|
233
|
+
FROM audit_events ae
|
|
234
|
+
LEFT JOIN model_pricing mp ON mp.provider = ae.provider AND mp.model = ae.model
|
|
235
|
+
WHERE ae.model IS NOT NULL
|
|
236
|
+
GROUP BY ae.provider, ae.model
|
|
237
|
+
ORDER BY estimatedCostUsd DESC
|
|
238
|
+
`).all();
|
|
239
|
+
// ── Top tools ─────────────────────────────────────────────────────────────
|
|
240
|
+
const topToolsRows = this.db.prepare(`
|
|
241
|
+
SELECT
|
|
242
|
+
ae.tool_name,
|
|
243
|
+
ae.agent,
|
|
244
|
+
ae.event_type,
|
|
245
|
+
COUNT(*) as count,
|
|
246
|
+
SUM(CASE WHEN ae.status = 'error' THEN 1 ELSE 0 END) as errorCount
|
|
247
|
+
FROM audit_events ae
|
|
248
|
+
WHERE ae.tool_name IS NOT NULL
|
|
249
|
+
AND ae.event_type IN ('tool_call','mcp_tool')
|
|
250
|
+
GROUP BY ae.tool_name, ae.agent, ae.event_type
|
|
251
|
+
ORDER BY count DESC
|
|
252
|
+
LIMIT 20
|
|
253
|
+
`).all();
|
|
254
|
+
// ── Recent sessions ──────────────────────────────────────────────────────
|
|
255
|
+
const recentRows = this.db.prepare(`
|
|
256
|
+
SELECT
|
|
257
|
+
ae.session_id,
|
|
258
|
+
s.title,
|
|
259
|
+
s.status,
|
|
260
|
+
s.started_at,
|
|
261
|
+
COUNT(ae.id) as event_count,
|
|
262
|
+
SUM(CASE WHEN ae.event_type = 'llm_call' THEN 1 ELSE 0 END) as llmCallCount,
|
|
263
|
+
COALESCE(SUM(ae.duration_ms), 0) as totalDurationMs,
|
|
264
|
+
COALESCE(SUM(${costExpr}), 0) as estimatedCostUsd
|
|
265
|
+
FROM audit_events ae
|
|
266
|
+
INNER JOIN sessions s ON ae.session_id = s.id
|
|
267
|
+
LEFT JOIN model_pricing mp ON mp.provider = ae.provider AND mp.model = ae.model
|
|
268
|
+
GROUP BY ae.session_id
|
|
269
|
+
ORDER BY MAX(ae.created_at) DESC
|
|
270
|
+
LIMIT 20
|
|
271
|
+
`).all();
|
|
272
|
+
// ── Daily activity (last 30 days) ─────────────────────────────────────────
|
|
273
|
+
const since = Date.now() - 30 * 24 * 60 * 60 * 1000;
|
|
274
|
+
const dailyRows = this.db.prepare(`
|
|
275
|
+
SELECT
|
|
276
|
+
date(ae.created_at / 1000, 'unixepoch') as date,
|
|
277
|
+
COUNT(*) as eventCount,
|
|
278
|
+
SUM(CASE WHEN ae.event_type = 'llm_call' THEN 1 ELSE 0 END) as llmCallCount,
|
|
279
|
+
COALESCE(SUM(${costExpr}), 0) as estimatedCostUsd
|
|
280
|
+
FROM audit_events ae
|
|
281
|
+
LEFT JOIN model_pricing mp ON mp.provider = ae.provider AND mp.model = ae.model
|
|
282
|
+
WHERE ae.created_at >= ?
|
|
283
|
+
GROUP BY date
|
|
284
|
+
ORDER BY date ASC
|
|
285
|
+
`).all(since);
|
|
286
|
+
return {
|
|
287
|
+
sessions: {
|
|
288
|
+
total: sessionsRow?.total ?? 0,
|
|
289
|
+
active: sessionsRow?.active ?? 0,
|
|
290
|
+
paused: sessionsRow?.paused ?? 0,
|
|
291
|
+
archived: sessionsRow?.archived ?? 0,
|
|
292
|
+
deleted: sessionsRow?.deleted ?? 0,
|
|
293
|
+
withAudit: withAuditRow?.n ?? 0,
|
|
294
|
+
},
|
|
295
|
+
totals: {
|
|
296
|
+
estimatedCostUsd: totalsRow?.estimatedCostUsd ?? 0,
|
|
297
|
+
totalDurationMs: totalsRow?.totalDurationMs ?? 0,
|
|
298
|
+
totalAudioSeconds: totalsRow?.totalAudioSeconds ?? 0,
|
|
299
|
+
totalInputTokens: totalsRow?.totalInputTokens ?? 0,
|
|
300
|
+
totalOutputTokens: totalsRow?.totalOutputTokens ?? 0,
|
|
301
|
+
totalEventCount: totalsRow?.totalEventCount ?? 0,
|
|
302
|
+
llmCallCount: totalsRow?.llmCallCount ?? 0,
|
|
303
|
+
toolCallCount: totalsRow?.toolCallCount ?? 0,
|
|
304
|
+
mcpToolCount: totalsRow?.mcpToolCount ?? 0,
|
|
305
|
+
skillCount: totalsRow?.skillCount ?? 0,
|
|
306
|
+
memoryRecoveryCount: totalsRow?.memoryRecoveryCount ?? 0,
|
|
307
|
+
chronosJobCount: totalsRow?.chronosJobCount ?? 0,
|
|
308
|
+
taskCreatedCount: totalsRow?.taskCreatedCount ?? 0,
|
|
309
|
+
taskCompletedCount: totalsRow?.taskCompletedCount ?? 0,
|
|
310
|
+
telephonistCount: totalsRow?.telephonistCount ?? 0,
|
|
311
|
+
},
|
|
312
|
+
byAgent: byAgentRows,
|
|
313
|
+
byModel: byModelRows,
|
|
314
|
+
topTools: topToolsRows,
|
|
315
|
+
recentSessions: recentRows,
|
|
316
|
+
dailyActivity: dailyRows,
|
|
317
|
+
};
|
|
318
|
+
}
|
|
132
319
|
}
|