neoagent 1.1.0 → 1.1.2
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/lib/manager.js +11 -1
- package/package.json +1 -1
- package/server/index.js +17 -268
- package/server/public/app.html +10 -0
- package/server/public/css/styles.css +64 -0
- package/server/public/js/app.js +53 -5
- package/server/routes/memory.js +1 -1
- package/server/routes/settings.js +78 -4
- package/server/routes/telnyx.js +35 -0
- package/server/services/ai/compaction.js +3 -2
- package/server/services/ai/engine.js +7 -1176
- package/server/services/ai/systemPrompt.js +113 -0
- package/server/services/ai/tools.js +1096 -0
- package/server/services/manager.js +172 -0
- package/server/services/messaging/base.js +12 -0
- package/server/services/messaging/discord.js +12 -18
- package/server/services/messaging/telegram.js +41 -47
- package/server/services/messaging/whatsapp.js +7 -8
- package/server/utils/logger.js +46 -0
- package/server/utils/whatsapp.js +46 -0
|
@@ -4,6 +4,7 @@ const path = require('path');
|
|
|
4
4
|
const router = express.Router();
|
|
5
5
|
const db = require('../db/database');
|
|
6
6
|
const { requireAuth } = require('../middleware/auth');
|
|
7
|
+
const { normalizeWhatsAppWhitelist } = require('../utils/whatsapp');
|
|
7
8
|
|
|
8
9
|
router.use(requireAuth);
|
|
9
10
|
|
|
@@ -55,6 +56,19 @@ router.get('/', (req, res) => {
|
|
|
55
56
|
router.put('/', (req, res) => {
|
|
56
57
|
const userId = req.session.userId;
|
|
57
58
|
const upsert = db.prepare('INSERT INTO user_settings (user_id, key, value) VALUES (?, ?, ?) ON CONFLICT(user_id, key) DO UPDATE SET value = excluded.value');
|
|
59
|
+
const normalizedBody = { ...req.body };
|
|
60
|
+
|
|
61
|
+
if ('platform_whitelist_whatsapp' in normalizedBody) {
|
|
62
|
+
let whitelist = normalizedBody.platform_whitelist_whatsapp;
|
|
63
|
+
if (typeof whitelist === 'string') {
|
|
64
|
+
try {
|
|
65
|
+
whitelist = JSON.parse(whitelist);
|
|
66
|
+
} catch {
|
|
67
|
+
whitelist = [];
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
normalizedBody.platform_whitelist_whatsapp = JSON.stringify(normalizeWhatsAppWhitelist(whitelist));
|
|
71
|
+
}
|
|
58
72
|
|
|
59
73
|
const tx = db.transaction((entries) => {
|
|
60
74
|
for (const [key, value] of entries) {
|
|
@@ -63,17 +77,66 @@ router.put('/', (req, res) => {
|
|
|
63
77
|
}
|
|
64
78
|
});
|
|
65
79
|
|
|
66
|
-
tx(Object.entries(
|
|
80
|
+
tx(Object.entries(normalizedBody));
|
|
67
81
|
|
|
68
82
|
// Apply headless toggle immediately without restarting
|
|
69
|
-
if ('headless_browser' in
|
|
83
|
+
if ('headless_browser' in normalizedBody) {
|
|
70
84
|
const bc = req.app.locals.browserController;
|
|
71
|
-
if (bc) bc.setHeadless(
|
|
85
|
+
if (bc) bc.setHeadless(normalizedBody.headless_browser).catch(() => { });
|
|
72
86
|
}
|
|
73
87
|
|
|
74
88
|
res.json({ success: true });
|
|
75
89
|
});
|
|
76
90
|
|
|
91
|
+
// Token usage summary for settings UI
|
|
92
|
+
router.get('/token-usage/summary', (req, res) => {
|
|
93
|
+
const userId = req.session.userId;
|
|
94
|
+
const totals = db.prepare(`
|
|
95
|
+
SELECT
|
|
96
|
+
COALESCE(SUM(total_tokens), 0) AS totalTokens,
|
|
97
|
+
COUNT(*) AS totalRuns,
|
|
98
|
+
COALESCE(AVG(CASE WHEN total_tokens > 0 THEN total_tokens END), 0) AS avgTokensPerRun
|
|
99
|
+
FROM agent_runs
|
|
100
|
+
WHERE user_id = ?
|
|
101
|
+
`).get(userId);
|
|
102
|
+
|
|
103
|
+
const recentRows = db.prepare(`
|
|
104
|
+
SELECT
|
|
105
|
+
date(created_at) AS day,
|
|
106
|
+
COALESCE(SUM(total_tokens), 0) AS tokens,
|
|
107
|
+
COUNT(*) AS runs
|
|
108
|
+
FROM agent_runs
|
|
109
|
+
WHERE user_id = ? AND created_at >= datetime('now', '-6 days')
|
|
110
|
+
GROUP BY date(created_at)
|
|
111
|
+
ORDER BY day ASC
|
|
112
|
+
`).all(userId);
|
|
113
|
+
|
|
114
|
+
const byDay = new Map(recentRows.map(r => [r.day, { tokens: Number(r.tokens || 0), runs: Number(r.runs || 0) }]));
|
|
115
|
+
const last7Days = [];
|
|
116
|
+
for (let offset = 6; offset >= 0; offset--) {
|
|
117
|
+
const day = db.prepare(`SELECT date('now', ?) AS day`).get(`-${offset} days`).day;
|
|
118
|
+
const dayRow = byDay.get(day) || { tokens: 0, runs: 0 };
|
|
119
|
+
last7Days.push({ date: day, tokens: dayRow.tokens, runs: dayRow.runs });
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const last7Totals = last7Days.reduce((acc, d) => {
|
|
123
|
+
acc.tokens += d.tokens;
|
|
124
|
+
acc.runs += d.runs;
|
|
125
|
+
return acc;
|
|
126
|
+
}, { tokens: 0, runs: 0 });
|
|
127
|
+
|
|
128
|
+
res.json({
|
|
129
|
+
totals: {
|
|
130
|
+
totalTokens: Number(totals?.totalTokens || 0),
|
|
131
|
+
totalRuns: Number(totals?.totalRuns || 0),
|
|
132
|
+
avgTokensPerRun: Math.round(Number(totals?.avgTokensPerRun || 0)),
|
|
133
|
+
last7DaysTokens: last7Totals.tokens,
|
|
134
|
+
last7DaysRuns: last7Totals.runs
|
|
135
|
+
},
|
|
136
|
+
last7Days
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
|
|
77
140
|
// Get single setting
|
|
78
141
|
router.get('/:key', (req, res) => {
|
|
79
142
|
const row = db.prepare('SELECT value FROM user_settings WHERE user_id = ? AND key = ?').get(req.session.userId, req.params.key);
|
|
@@ -90,7 +153,18 @@ router.get('/:key', (req, res) => {
|
|
|
90
153
|
|
|
91
154
|
// Set single setting
|
|
92
155
|
router.put('/:key', (req, res) => {
|
|
93
|
-
|
|
156
|
+
let value = req.body.value;
|
|
157
|
+
if (req.params.key === 'platform_whitelist_whatsapp') {
|
|
158
|
+
if (typeof value === 'string') {
|
|
159
|
+
try {
|
|
160
|
+
value = JSON.parse(value);
|
|
161
|
+
} catch {
|
|
162
|
+
value = [];
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
value = normalizeWhatsAppWhitelist(value);
|
|
166
|
+
}
|
|
167
|
+
const v = typeof value === 'string' ? value : JSON.stringify(value);
|
|
94
168
|
db.prepare('INSERT INTO user_settings (user_id, key, value) VALUES (?, ?, ?) ON CONFLICT(user_id, key) DO UPDATE SET value = excluded.value')
|
|
95
169
|
.run(req.session.userId, req.params.key, v);
|
|
96
170
|
res.json({ success: true });
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const crypto = require('crypto');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Sets up Telnyx voice webhook route
|
|
7
|
+
* @param {import('express').Application} app
|
|
8
|
+
*/
|
|
9
|
+
function setupTelnyxWebhook(app) {
|
|
10
|
+
const tokenMiddleware = (req, res, next) => {
|
|
11
|
+
const expected = process.env.TELNYX_WEBHOOK_TOKEN;
|
|
12
|
+
if (expected) {
|
|
13
|
+
const provided = req.query.token || '';
|
|
14
|
+
const a = Buffer.from(provided.padEnd(expected.length));
|
|
15
|
+
const b = Buffer.from(expected);
|
|
16
|
+
if (a.length !== b.length || !crypto.timingSafeEqual(a, b)) {
|
|
17
|
+
console.warn('[Telnyx webhook] Rejected request with invalid or missing token');
|
|
18
|
+
return res.status(403).send('Forbidden');
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
next();
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
app.post('/api/telnyx/webhook', tokenMiddleware, async (req, res) => {
|
|
25
|
+
res.status(200).send('OK');
|
|
26
|
+
const manager = app.locals.messagingManager;
|
|
27
|
+
if (manager) {
|
|
28
|
+
await manager.handleTelnyxWebhook(req.body).catch(err =>
|
|
29
|
+
console.error('[Telnyx webhook]', err.message)
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
module.exports = { setupTelnyxWebhook };
|
|
@@ -2,9 +2,10 @@ async function compact(messages, provider, model) {
|
|
|
2
2
|
const systemMsg = messages.find(m => m.role === 'system');
|
|
3
3
|
const nonSystem = messages.filter(m => m.role !== 'system');
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
// Only compact once history is clearly old enough to avoid touching recent context.
|
|
6
|
+
if (nonSystem.length < 12) return messages;
|
|
6
7
|
|
|
7
|
-
const keepRecent =
|
|
8
|
+
const keepRecent = 10;
|
|
8
9
|
const toCompact = nonSystem.slice(0, -keepRecent);
|
|
9
10
|
const recent = nonSystem.slice(-keepRecent);
|
|
10
11
|
|