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
|
@@ -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
|
}
|
|
@@ -156,6 +156,41 @@ export class ChronosRepository {
|
|
|
156
156
|
const rows = this.db.prepare(query).all(...params);
|
|
157
157
|
return rows.map((r) => this.deserializeJob(r));
|
|
158
158
|
}
|
|
159
|
+
countJobs(filters) {
|
|
160
|
+
const params = [];
|
|
161
|
+
let query = 'SELECT COUNT(*) as cnt FROM chronos_jobs WHERE 1=1';
|
|
162
|
+
if (filters?.enabled !== undefined) {
|
|
163
|
+
query += ' AND enabled = ?';
|
|
164
|
+
params.push(filters.enabled ? 1 : 0);
|
|
165
|
+
}
|
|
166
|
+
if (filters?.created_by) {
|
|
167
|
+
query += ' AND created_by = ?';
|
|
168
|
+
params.push(filters.created_by);
|
|
169
|
+
}
|
|
170
|
+
const row = this.db.prepare(query).get(...params);
|
|
171
|
+
return row.cnt;
|
|
172
|
+
}
|
|
173
|
+
listJobsPaginated(filters) {
|
|
174
|
+
const page = Math.max(1, filters?.page ?? 1);
|
|
175
|
+
const per_page = Math.min(100, Math.max(1, filters?.per_page ?? 20));
|
|
176
|
+
const offset = (page - 1) * per_page;
|
|
177
|
+
const total = this.countJobs(filters);
|
|
178
|
+
const total_pages = Math.ceil(total / per_page);
|
|
179
|
+
const params = [];
|
|
180
|
+
let query = 'SELECT * FROM chronos_jobs WHERE 1=1';
|
|
181
|
+
if (filters?.enabled !== undefined) {
|
|
182
|
+
query += ' AND enabled = ?';
|
|
183
|
+
params.push(filters.enabled ? 1 : 0);
|
|
184
|
+
}
|
|
185
|
+
if (filters?.created_by) {
|
|
186
|
+
query += ' AND created_by = ?';
|
|
187
|
+
params.push(filters.created_by);
|
|
188
|
+
}
|
|
189
|
+
query += ' ORDER BY created_at DESC LIMIT ? OFFSET ?';
|
|
190
|
+
params.push(per_page, offset);
|
|
191
|
+
const rows = this.db.prepare(query).all(...params);
|
|
192
|
+
return { data: rows.map((r) => this.deserializeJob(r)), total, page, per_page, total_pages };
|
|
193
|
+
}
|
|
159
194
|
updateJob(id, patch) {
|
|
160
195
|
const now = Date.now();
|
|
161
196
|
const sets = ['updated_at = ?'];
|
package/dist/runtime/keymaker.js
CHANGED
|
@@ -8,7 +8,7 @@ import { Construtor } from "./tools/factory.js";
|
|
|
8
8
|
import { morpheusTools } from "./tools/index.js";
|
|
9
9
|
import { SkillRegistry } from "./skills/registry.js";
|
|
10
10
|
import { TaskRequestContext } from "./tasks/context.js";
|
|
11
|
-
import {
|
|
11
|
+
import { extractRawUsage, persistAgentMessage, buildAgentResult, emitToolAuditEvents } from "./subagent-utils.js";
|
|
12
12
|
/**
|
|
13
13
|
* Keymaker is a specialized agent for executing skills.
|
|
14
14
|
* "The one who opens any door" - has access to ALL tools:
|
|
@@ -122,38 +122,14 @@ CRITICAL — NEVER FABRICATE DATA:
|
|
|
122
122
|
const content = typeof lastMessage.content === "string"
|
|
123
123
|
? lastMessage.content
|
|
124
124
|
: JSON.stringify(lastMessage.content);
|
|
125
|
-
// Persist message with token usage metadata (like Trinity/Neo/Apoc)
|
|
126
125
|
const keymakerConfig = this.config.keymaker || this.config.llm;
|
|
127
126
|
const targetSession = taskContext?.session_id ?? "keymaker";
|
|
128
|
-
const rawUsage = lastMessage
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
const history = new SQLiteChatMessageHistory({ sessionId: targetSession });
|
|
133
|
-
try {
|
|
134
|
-
const persisted = new AIMessage(content);
|
|
135
|
-
if (rawUsage)
|
|
136
|
-
persisted.usage_metadata = rawUsage;
|
|
137
|
-
persisted.provider_metadata = { provider: keymakerConfig.provider, model: keymakerConfig.model };
|
|
138
|
-
persisted.agent_metadata = { agent: 'keymaker' };
|
|
139
|
-
persisted.duration_ms = durationMs;
|
|
140
|
-
await history.addMessage(persisted);
|
|
141
|
-
}
|
|
142
|
-
finally {
|
|
143
|
-
history.close();
|
|
144
|
-
}
|
|
127
|
+
const rawUsage = extractRawUsage(lastMessage);
|
|
128
|
+
const stepCount = response.messages.filter((m) => m instanceof AIMessage).length;
|
|
129
|
+
await persistAgentMessage('keymaker', content, keymakerConfig, targetSession, rawUsage, durationMs);
|
|
130
|
+
emitToolAuditEvents(response.messages.slice(2), targetSession, 'keymaker');
|
|
145
131
|
this.display.log(`Keymaker completed skill "${this.skillName}" execution`, { source: "Keymaker" });
|
|
146
|
-
return
|
|
147
|
-
output: content,
|
|
148
|
-
usage: {
|
|
149
|
-
provider: keymakerConfig.provider,
|
|
150
|
-
model: keymakerConfig.model,
|
|
151
|
-
inputTokens: rawUsage?.input_tokens ?? 0,
|
|
152
|
-
outputTokens: rawUsage?.output_tokens ?? 0,
|
|
153
|
-
durationMs,
|
|
154
|
-
stepCount: response.messages.filter((m) => m instanceof AIMessage).length,
|
|
155
|
-
},
|
|
156
|
-
};
|
|
132
|
+
return buildAgentResult(content, keymakerConfig, rawUsage, durationMs, stepCount);
|
|
157
133
|
}
|
|
158
134
|
catch (err) {
|
|
159
135
|
this.display.log(`Keymaker execution error: ${err.message}`, { source: "Keymaker", level: "error" });
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { AIMessage } from "@langchain/core/messages";
|
|
2
2
|
import { SatiService } from "./service.js";
|
|
3
3
|
import { DisplayManager } from "../../display.js";
|
|
4
|
+
import { AuditRepository } from "../../audit/repository.js";
|
|
4
5
|
const display = DisplayManager.getInstance();
|
|
5
6
|
export class SatiMemoryMiddleware {
|
|
6
7
|
service;
|
|
@@ -14,12 +15,25 @@ export class SatiMemoryMiddleware {
|
|
|
14
15
|
}
|
|
15
16
|
return SatiMemoryMiddleware.instance;
|
|
16
17
|
}
|
|
17
|
-
async beforeAgent(currentMessage, history) {
|
|
18
|
+
async beforeAgent(currentMessage, history, sessionId) {
|
|
19
|
+
const startMs = Date.now();
|
|
18
20
|
try {
|
|
19
21
|
// Extract recent messages content strings for context
|
|
20
22
|
const recentText = history.slice(-10).map(m => m.content.toString());
|
|
21
23
|
display.log(`Searching memories for: "${currentMessage.substring(0, 50)}${currentMessage.length > 50 ? '...' : ''}"`, { source: 'Sati' });
|
|
22
24
|
const result = await this.service.recover(currentMessage, recentText);
|
|
25
|
+
const durationMs = Date.now() - startMs;
|
|
26
|
+
AuditRepository.getInstance().insert({
|
|
27
|
+
session_id: sessionId ?? 'sati-recovery',
|
|
28
|
+
event_type: 'memory_recovery',
|
|
29
|
+
agent: 'sati',
|
|
30
|
+
duration_ms: durationMs,
|
|
31
|
+
status: 'success',
|
|
32
|
+
metadata: {
|
|
33
|
+
memories_count: result.relevant_memories.length,
|
|
34
|
+
memories: result.relevant_memories.map(m => ({ category: m.category, importance: m.importance, summary: m.summary })),
|
|
35
|
+
},
|
|
36
|
+
});
|
|
23
37
|
if (result.relevant_memories.length === 0) {
|
|
24
38
|
display.log('No relevant memories found', { source: 'Sati' });
|
|
25
39
|
return null;
|
|
@@ -36,6 +50,13 @@ export class SatiMemoryMiddleware {
|
|
|
36
50
|
`);
|
|
37
51
|
}
|
|
38
52
|
catch (error) {
|
|
53
|
+
AuditRepository.getInstance().insert({
|
|
54
|
+
session_id: sessionId ?? 'sati-recovery',
|
|
55
|
+
event_type: 'memory_recovery',
|
|
56
|
+
agent: 'sati',
|
|
57
|
+
duration_ms: Date.now() - startMs,
|
|
58
|
+
status: 'error',
|
|
59
|
+
});
|
|
39
60
|
display.log(`Error in beforeAgent: ${error}`, { source: 'Sati' });
|
|
40
61
|
// Fail open: return null so execution continues without memory
|
|
41
62
|
return null;
|
|
@@ -5,6 +5,7 @@ import fs from 'fs-extra';
|
|
|
5
5
|
import { randomUUID } from 'crypto';
|
|
6
6
|
import loadVecExtension from '../sqlite-vec.js';
|
|
7
7
|
import { DisplayManager } from '../../display.js';
|
|
8
|
+
import { ConfigManager } from '../../../config/manager.js';
|
|
8
9
|
const EMBEDDING_DIM = 384;
|
|
9
10
|
export class SatiRepository {
|
|
10
11
|
db = null;
|
|
@@ -208,7 +209,7 @@ export class SatiRepository {
|
|
|
208
209
|
searchUnifiedVector(embedding, limit) {
|
|
209
210
|
if (!this.db)
|
|
210
211
|
return [];
|
|
211
|
-
const SIMILARITY_THRESHOLD = 0.
|
|
212
|
+
const SIMILARITY_THRESHOLD = ConfigManager.getInstance().getSatiConfig().similarity_threshold ?? 0.9;
|
|
212
213
|
const stmt = this.db.prepare(`
|
|
213
214
|
SELECT *
|
|
214
215
|
FROM (
|
|
@@ -412,6 +413,43 @@ export class SatiRepository {
|
|
|
412
413
|
const rows = this.db.prepare('SELECT * FROM long_term_memory WHERE archived = 0 ORDER BY created_at DESC').all();
|
|
413
414
|
return rows.map(this.mapRowToRecord);
|
|
414
415
|
}
|
|
416
|
+
buildMemoryFilterQuery(filters) {
|
|
417
|
+
const params = [];
|
|
418
|
+
let where = 'WHERE archived = 0';
|
|
419
|
+
if (filters?.category) {
|
|
420
|
+
where += ' AND category = ?';
|
|
421
|
+
params.push(filters.category);
|
|
422
|
+
}
|
|
423
|
+
if (filters?.importance) {
|
|
424
|
+
where += ' AND importance = ?';
|
|
425
|
+
params.push(filters.importance);
|
|
426
|
+
}
|
|
427
|
+
if (filters?.search) {
|
|
428
|
+
where += ' AND (summary LIKE ? OR details LIKE ? OR category LIKE ?)';
|
|
429
|
+
const pattern = `%${filters.search}%`;
|
|
430
|
+
params.push(pattern, pattern, pattern);
|
|
431
|
+
}
|
|
432
|
+
return { where, params };
|
|
433
|
+
}
|
|
434
|
+
countMemories(filters) {
|
|
435
|
+
if (!this.db)
|
|
436
|
+
this.initialize();
|
|
437
|
+
const { where, params } = this.buildMemoryFilterQuery(filters);
|
|
438
|
+
const row = this.db.prepare(`SELECT COUNT(*) as cnt FROM long_term_memory ${where}`).get(...params);
|
|
439
|
+
return row.cnt;
|
|
440
|
+
}
|
|
441
|
+
getMemoriesPaginated(filters) {
|
|
442
|
+
if (!this.db)
|
|
443
|
+
this.initialize();
|
|
444
|
+
const page = Math.max(1, filters?.page ?? 1);
|
|
445
|
+
const per_page = Math.min(100, Math.max(1, filters?.per_page ?? 20));
|
|
446
|
+
const offset = (page - 1) * per_page;
|
|
447
|
+
const total = this.countMemories(filters);
|
|
448
|
+
const total_pages = Math.ceil(total / per_page);
|
|
449
|
+
const { where, params } = this.buildMemoryFilterQuery(filters);
|
|
450
|
+
const rows = this.db.prepare(`SELECT * FROM long_term_memory ${where} ORDER BY created_at DESC LIMIT ? OFFSET ?`).all(...params, per_page, offset);
|
|
451
|
+
return { data: rows.map(this.mapRowToRecord), total, page, per_page, total_pages };
|
|
452
|
+
}
|
|
415
453
|
mapRowToRecord(row) {
|
|
416
454
|
return {
|
|
417
455
|
id: row.id,
|
|
@@ -204,6 +204,19 @@ export class SQLiteChatMessageHistory extends BaseListChatMessageHistory {
|
|
|
204
204
|
catch (error) {
|
|
205
205
|
console.warn(`[SQLite] Migration check failed: ${error}`);
|
|
206
206
|
}
|
|
207
|
+
// Migrate model_pricing table
|
|
208
|
+
try {
|
|
209
|
+
const pricingInfo = this.db.pragma('table_info(model_pricing)');
|
|
210
|
+
const pricingCols = new Set(pricingInfo.map(c => c.name));
|
|
211
|
+
if (!pricingCols.has('audio_cost_per_second')) {
|
|
212
|
+
this.db.exec('ALTER TABLE model_pricing ADD COLUMN audio_cost_per_second REAL');
|
|
213
|
+
// Seed Whisper pricing: $0.006/minute = $0.0001/second
|
|
214
|
+
this.db.prepare('INSERT OR IGNORE INTO model_pricing (provider, model, input_price_per_1m, output_price_per_1m, audio_cost_per_second) VALUES (?, ?, ?, ?, ?)').run('openai', 'whisper-1', 0, 0, 0.0001);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
catch (error) {
|
|
218
|
+
console.warn(`[SQLite] model_pricing migration failed: ${error}`);
|
|
219
|
+
}
|
|
207
220
|
}
|
|
208
221
|
/**
|
|
209
222
|
* Retrieves all messages for the current session from the database.
|
|
@@ -301,7 +314,7 @@ export class SQLiteChatMessageHistory extends BaseListChatMessageHistory {
|
|
|
301
314
|
}
|
|
302
315
|
try {
|
|
303
316
|
const placeholders = sessionIds.map(() => '?').join(', ');
|
|
304
|
-
const stmt = this.db.prepare(`SELECT id, session_id, type, content, created_at, input_tokens, output_tokens, total_tokens, cache_read_tokens, provider, model, agent, duration_ms
|
|
317
|
+
const stmt = this.db.prepare(`SELECT id, session_id, type, content, created_at, input_tokens, output_tokens, total_tokens, cache_read_tokens, provider, model, agent, duration_ms, audio_duration_seconds
|
|
305
318
|
FROM messages
|
|
306
319
|
WHERE session_id IN (${placeholders})
|
|
307
320
|
ORDER BY id DESC
|
|
@@ -648,11 +661,11 @@ export class SQLiteChatMessageHistory extends BaseListChatMessageHistory {
|
|
|
648
661
|
}
|
|
649
662
|
// --- Model Pricing CRUD ---
|
|
650
663
|
listModelPricing() {
|
|
651
|
-
const rows = this.db.prepare('SELECT provider, model, input_price_per_1m, output_price_per_1m FROM model_pricing ORDER BY provider, model').all();
|
|
664
|
+
const rows = this.db.prepare('SELECT provider, model, input_price_per_1m, output_price_per_1m, audio_cost_per_second FROM model_pricing ORDER BY provider, model').all();
|
|
652
665
|
return rows;
|
|
653
666
|
}
|
|
654
667
|
upsertModelPricing(entry) {
|
|
655
|
-
this.db.prepare('INSERT INTO model_pricing (provider, model, input_price_per_1m, output_price_per_1m) VALUES (?, ?, ?, ?) ON CONFLICT(provider, model) DO UPDATE SET input_price_per_1m = excluded.input_price_per_1m, output_price_per_1m = excluded.output_price_per_1m').run(entry.provider, entry.model, entry.input_price_per_1m, entry.output_price_per_1m);
|
|
668
|
+
this.db.prepare('INSERT INTO model_pricing (provider, model, input_price_per_1m, output_price_per_1m, audio_cost_per_second) VALUES (?, ?, ?, ?, ?) ON CONFLICT(provider, model) DO UPDATE SET input_price_per_1m = excluded.input_price_per_1m, output_price_per_1m = excluded.output_price_per_1m, audio_cost_per_second = excluded.audio_cost_per_second').run(entry.provider, entry.model, entry.input_price_per_1m, entry.output_price_per_1m, entry.audio_cost_per_second ?? null);
|
|
656
669
|
}
|
|
657
670
|
deleteModelPricing(provider, model) {
|
|
658
671
|
const result = this.db.prepare('DELETE FROM model_pricing WHERE provider = ? AND model = ?').run(provider, model);
|
package/dist/runtime/neo.js
CHANGED
|
@@ -5,12 +5,50 @@ import { ProviderError } from "./errors.js";
|
|
|
5
5
|
import { DisplayManager } from "./display.js";
|
|
6
6
|
import { Construtor } from "./tools/factory.js";
|
|
7
7
|
import { morpheusTools } from "./tools/index.js";
|
|
8
|
-
import { SQLiteChatMessageHistory } from "./memory/sqlite.js";
|
|
9
8
|
import { TaskRequestContext } from "./tasks/context.js";
|
|
10
|
-
import {
|
|
9
|
+
import { extractRawUsage, persistAgentMessage, buildAgentResult, emitToolAuditEvents } from "./subagent-utils.js";
|
|
10
|
+
import { buildDelegationTool } from "./tools/delegation-utils.js";
|
|
11
|
+
// Internal Morpheus tools get 'tool_call' event type; MCP tools get 'mcp_tool'
|
|
12
|
+
const MORPHEUS_TOOL_NAMES = new Set(morpheusTools.map((t) => t.name));
|
|
13
|
+
const NEO_BUILTIN_CAPABILITIES = `
|
|
14
|
+
Neo built-in capabilities (always available — no MCP required):
|
|
15
|
+
• Config: morpheus_config_query, morpheus_config_update — read/write Morpheus configuration (LLM, channels, UI, etc.)
|
|
16
|
+
• Diagnostics: diagnostic_check — full system health report (config, databases, LLM provider, logs)
|
|
17
|
+
• Analytics: message_count, token_usage, provider_model_usage — message counts and token/cost usage stats
|
|
18
|
+
• Tasks: task_query — look up task status by id or session
|
|
19
|
+
• MCP Management: mcp_list, mcp_manage — list/add/update/delete/enable/disable MCP servers; use action "reload" to reload tools across all agents after config changes
|
|
20
|
+
• Webhooks: webhook_list, webhook_manage — create/update/delete webhooks; create returns api_key`.trim();
|
|
21
|
+
const NEO_BASE_DESCRIPTION = `Delegate execution to Neo asynchronously.
|
|
22
|
+
|
|
23
|
+
This tool creates a background task and returns an acknowledgement with task id.
|
|
24
|
+
Use it for any request that requires Neo's built-in capabilities or a runtime MCP tool listed below.
|
|
25
|
+
Each delegated task must contain one atomic objective.
|
|
26
|
+
|
|
27
|
+
${NEO_BUILTIN_CAPABILITIES}`;
|
|
28
|
+
function normalizeDescription(text) {
|
|
29
|
+
if (!text)
|
|
30
|
+
return "No description";
|
|
31
|
+
return text.replace(/\s+/g, " ").trim();
|
|
32
|
+
}
|
|
33
|
+
function buildCatalogSection(mcpTools) {
|
|
34
|
+
if (mcpTools.length === 0) {
|
|
35
|
+
return "\n\nRuntime MCP tools: none currently loaded.";
|
|
36
|
+
}
|
|
37
|
+
const maxItems = 500;
|
|
38
|
+
const lines = mcpTools.slice(0, maxItems).map((t) => {
|
|
39
|
+
const desc = normalizeDescription(t.description).slice(0, 120);
|
|
40
|
+
return `- ${t.name}: ${desc}`;
|
|
41
|
+
});
|
|
42
|
+
const hidden = mcpTools.length - lines.length;
|
|
43
|
+
if (hidden > 0) {
|
|
44
|
+
lines.push(`- ... and ${hidden} more tools`);
|
|
45
|
+
}
|
|
46
|
+
return `\n\nRuntime MCP tools:\n${lines.join("\n")}`;
|
|
47
|
+
}
|
|
11
48
|
export class Neo {
|
|
12
49
|
static instance = null;
|
|
13
50
|
static currentSessionId = undefined;
|
|
51
|
+
static _delegateTool = null;
|
|
14
52
|
agent;
|
|
15
53
|
config;
|
|
16
54
|
display = DisplayManager.getInstance();
|
|
@@ -28,17 +66,25 @@ export class Neo {
|
|
|
28
66
|
}
|
|
29
67
|
static resetInstance() {
|
|
30
68
|
Neo.instance = null;
|
|
69
|
+
Neo._delegateTool = null;
|
|
31
70
|
}
|
|
32
71
|
static async refreshDelegateCatalog() {
|
|
33
72
|
const mcpTools = await Construtor.create(() => Neo.currentSessionId);
|
|
34
|
-
|
|
73
|
+
if (Neo._delegateTool) {
|
|
74
|
+
const full = `${NEO_BASE_DESCRIPTION}${buildCatalogSection(mcpTools)}`;
|
|
75
|
+
Neo._delegateTool.description = full;
|
|
76
|
+
}
|
|
35
77
|
}
|
|
36
78
|
async initialize() {
|
|
37
79
|
const neoConfig = this.config.neo || this.config.llm;
|
|
38
80
|
const personality = this.config.neo?.personality || 'analytical_engineer';
|
|
39
81
|
const mcpTools = await Construtor.create(() => Neo.currentSessionId);
|
|
40
82
|
const tools = [...mcpTools, ...morpheusTools];
|
|
41
|
-
|
|
83
|
+
// Update delegate tool description with current catalog
|
|
84
|
+
if (Neo._delegateTool) {
|
|
85
|
+
const full = `${NEO_BASE_DESCRIPTION}${buildCatalogSection(mcpTools)}`;
|
|
86
|
+
Neo._delegateTool.description = full;
|
|
87
|
+
}
|
|
42
88
|
this.display.log(`Neo initialized with ${tools.length} tools (personality: ${personality}).`, { source: "Neo" });
|
|
43
89
|
try {
|
|
44
90
|
this.agent = await ProviderFactory.create(neoConfig, tools);
|
|
@@ -89,6 +135,7 @@ ${context ? `Context:\n${context}` : ""}
|
|
|
89
135
|
origin_message_id: taskContext?.origin_message_id,
|
|
90
136
|
origin_user_id: taskContext?.origin_user_id,
|
|
91
137
|
};
|
|
138
|
+
const inputCount = messages.length;
|
|
92
139
|
const startMs = Date.now();
|
|
93
140
|
const response = await TaskRequestContext.run(invokeContext, () => this.agent.invoke({ messages }));
|
|
94
141
|
const durationMs = Date.now() - startMs;
|
|
@@ -96,44 +143,41 @@ ${context ? `Context:\n${context}` : ""}
|
|
|
96
143
|
const content = typeof lastMessage.content === "string"
|
|
97
144
|
? lastMessage.content
|
|
98
145
|
: JSON.stringify(lastMessage.content);
|
|
99
|
-
const rawUsage = lastMessage
|
|
100
|
-
?? lastMessage.response_metadata?.usage
|
|
101
|
-
?? lastMessage.response_metadata?.tokenUsage
|
|
102
|
-
?? lastMessage.usage;
|
|
103
|
-
const inputTokens = rawUsage?.input_tokens ?? 0;
|
|
104
|
-
const outputTokens = rawUsage?.output_tokens ?? 0;
|
|
146
|
+
const rawUsage = extractRawUsage(lastMessage);
|
|
105
147
|
const stepCount = response.messages.filter((m) => m instanceof AIMessage).length;
|
|
106
148
|
const targetSession = sessionId ?? Neo.currentSessionId ?? "neo";
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
persisted.provider_metadata = { provider: neoConfig.provider, model: neoConfig.model };
|
|
113
|
-
persisted.agent_metadata = { agent: 'neo' };
|
|
114
|
-
persisted.duration_ms = durationMs;
|
|
115
|
-
await history.addMessage(persisted);
|
|
116
|
-
}
|
|
117
|
-
finally {
|
|
118
|
-
history.close();
|
|
119
|
-
}
|
|
149
|
+
await persistAgentMessage('neo', content, neoConfig, targetSession, rawUsage, durationMs);
|
|
150
|
+
emitToolAuditEvents(response.messages.slice(inputCount), targetSession, 'neo', {
|
|
151
|
+
defaultEventType: 'mcp_tool',
|
|
152
|
+
internalToolNames: MORPHEUS_TOOL_NAMES,
|
|
153
|
+
});
|
|
120
154
|
this.display.log("Neo task completed.", { source: "Neo" });
|
|
121
|
-
return
|
|
122
|
-
output: content,
|
|
123
|
-
usage: {
|
|
124
|
-
provider: neoConfig.provider,
|
|
125
|
-
model: neoConfig.model,
|
|
126
|
-
inputTokens,
|
|
127
|
-
outputTokens,
|
|
128
|
-
durationMs,
|
|
129
|
-
stepCount,
|
|
130
|
-
},
|
|
131
|
-
};
|
|
155
|
+
return buildAgentResult(content, neoConfig, rawUsage, durationMs, stepCount);
|
|
132
156
|
}
|
|
133
157
|
catch (err) {
|
|
134
158
|
throw new ProviderError(neoConfig.provider, err, "Neo task execution failed");
|
|
135
159
|
}
|
|
136
160
|
}
|
|
161
|
+
createDelegateTool() {
|
|
162
|
+
if (!Neo._delegateTool) {
|
|
163
|
+
Neo._delegateTool = buildDelegationTool({
|
|
164
|
+
name: "neo_delegate",
|
|
165
|
+
description: NEO_BASE_DESCRIPTION,
|
|
166
|
+
agentKey: "neo",
|
|
167
|
+
agentLabel: "Neo",
|
|
168
|
+
auditAgent: "neo",
|
|
169
|
+
isSync: () => ConfigManager.getInstance().get().neo?.execution_mode === 'sync',
|
|
170
|
+
notifyText: '🥷 Neo is executing your request...',
|
|
171
|
+
executeSync: (task, context, sessionId, ctx) => Neo.getInstance().execute(task, context, sessionId, {
|
|
172
|
+
origin_channel: ctx?.origin_channel ?? "api",
|
|
173
|
+
session_id: sessionId,
|
|
174
|
+
origin_message_id: ctx?.origin_message_id,
|
|
175
|
+
origin_user_id: ctx?.origin_user_id,
|
|
176
|
+
}),
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
return Neo._delegateTool;
|
|
180
|
+
}
|
|
137
181
|
async reload() {
|
|
138
182
|
this.config = ConfigManager.getInstance().get();
|
|
139
183
|
this.agent = undefined;
|