squidclaw 1.5.0 → 2.1.0
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/engine.js +5 -0
- package/lib/features/allowlist-manager.js +105 -0
- package/lib/features/conversation-summary.js +15 -0
- package/lib/features/daily-briefing.js +48 -0
- package/lib/features/handoff.js +31 -0
- package/lib/features/scheduled-reports.js +47 -0
- package/lib/features/webhooks.js +55 -0
- package/lib/middleware/commands.js +151 -1
- package/lib/middleware/learning.js +33 -0
- package/lib/middleware/proactive.js +22 -0
- package/lib/tools/contacts.js +44 -0
- package/lib/tools/games.js +44 -0
- package/lib/tools/notes.js +44 -0
- package/lib/tools/router.js +161 -0
- package/package.json +1 -1
package/lib/engine.js
CHANGED
|
@@ -67,7 +67,11 @@ export class SquidclawEngine {
|
|
|
67
67
|
pipeline.use('skill-check', skillCheckMiddleware);
|
|
68
68
|
pipeline.use('typing', typingMiddleware); // wraps AI call with typing indicator
|
|
69
69
|
pipeline.use('ai-process', aiProcessMiddleware);
|
|
70
|
+
const { proactiveMiddleware } = await import('./middleware/proactive.js');
|
|
71
|
+
const { learningMiddleware } = await import('./middleware/learning.js');
|
|
70
72
|
pipeline.use('usage-alerts', usageAlertsMiddleware);
|
|
73
|
+
pipeline.use('proactive', proactiveMiddleware);
|
|
74
|
+
pipeline.use('learning', learningMiddleware);
|
|
71
75
|
pipeline.use('response-sender', responseSenderMiddleware);
|
|
72
76
|
|
|
73
77
|
return pipeline;
|
|
@@ -191,6 +195,7 @@ export class SquidclawEngine {
|
|
|
191
195
|
this.knowledgeBase = new KnowledgeBase(this.storage, this.config);
|
|
192
196
|
this.toolRouter = new ToolRouter(this.config, this.knowledgeBase);
|
|
193
197
|
|
|
198
|
+
this.toolRouter.setContext(this, this.aiGateway, this.config.ai?.defaultModel);
|
|
194
199
|
for (const agent of agents) {
|
|
195
200
|
addToolSupport(agent, this.toolRouter, this.knowledgeBase);
|
|
196
201
|
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 🦑 Allowlist Manager
|
|
3
|
+
* Manage who can talk to the agent via chat commands or AI tool
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { loadConfig, saveConfig, getHome } from '../core/config.js';
|
|
7
|
+
import { logger } from '../core/logger.js';
|
|
8
|
+
|
|
9
|
+
export class AllowlistManager {
|
|
10
|
+
constructor(engine) {
|
|
11
|
+
this.engine = engine;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Get current allowlist for a platform
|
|
16
|
+
*/
|
|
17
|
+
list(platform = 'telegram') {
|
|
18
|
+
const config = loadConfig();
|
|
19
|
+
const allowFrom = config.channels?.[platform]?.allowFrom;
|
|
20
|
+
if (!allowFrom || allowFrom === '*') return { mode: 'open', list: [] };
|
|
21
|
+
const list = Array.isArray(allowFrom) ? allowFrom : [String(allowFrom)];
|
|
22
|
+
return { mode: 'restricted', list };
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Add someone to the allowlist
|
|
27
|
+
*/
|
|
28
|
+
add(platform, id) {
|
|
29
|
+
const config = loadConfig();
|
|
30
|
+
if (!config.channels) config.channels = {};
|
|
31
|
+
if (!config.channels[platform]) config.channels[platform] = {};
|
|
32
|
+
|
|
33
|
+
let allowFrom = config.channels[platform].allowFrom;
|
|
34
|
+
|
|
35
|
+
if (!allowFrom || allowFrom === '*') {
|
|
36
|
+
// First time — also include current entries
|
|
37
|
+
config.channels[platform].allowFrom = [String(id)];
|
|
38
|
+
} else if (Array.isArray(allowFrom)) {
|
|
39
|
+
if (!allowFrom.includes(String(id))) {
|
|
40
|
+
allowFrom.push(String(id));
|
|
41
|
+
}
|
|
42
|
+
} else {
|
|
43
|
+
// Single value → convert to array
|
|
44
|
+
config.channels[platform].allowFrom = [String(allowFrom), String(id)];
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
saveConfig(config);
|
|
48
|
+
this.engine.config = config;
|
|
49
|
+
logger.info('allowlist', `Added ${id} to ${platform} allowlist`);
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Remove someone from the allowlist
|
|
55
|
+
*/
|
|
56
|
+
remove(platform, id) {
|
|
57
|
+
const config = loadConfig();
|
|
58
|
+
const allowFrom = config.channels?.[platform]?.allowFrom;
|
|
59
|
+
if (!allowFrom || allowFrom === '*') return false;
|
|
60
|
+
|
|
61
|
+
if (Array.isArray(allowFrom)) {
|
|
62
|
+
config.channels[platform].allowFrom = allowFrom.filter(a => a !== String(id));
|
|
63
|
+
if (config.channels[platform].allowFrom.length === 0) {
|
|
64
|
+
config.channels[platform].allowFrom = '*'; // No one left = open
|
|
65
|
+
}
|
|
66
|
+
} else if (String(allowFrom) === String(id)) {
|
|
67
|
+
config.channels[platform].allowFrom = '*';
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
saveConfig(config);
|
|
71
|
+
this.engine.config = config;
|
|
72
|
+
logger.info('allowlist', `Removed ${id} from ${platform} allowlist`);
|
|
73
|
+
return true;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Open to everyone
|
|
78
|
+
*/
|
|
79
|
+
openAll(platform) {
|
|
80
|
+
const config = loadConfig();
|
|
81
|
+
if (config.channels?.[platform]) {
|
|
82
|
+
config.channels[platform].allowFrom = '*';
|
|
83
|
+
}
|
|
84
|
+
saveConfig(config);
|
|
85
|
+
this.engine.config = config;
|
|
86
|
+
return true;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Lock down — only allowlist can message
|
|
91
|
+
*/
|
|
92
|
+
lockDown(platform) {
|
|
93
|
+
// Just make sure allowFrom is an array (not '*')
|
|
94
|
+
const config = loadConfig();
|
|
95
|
+
if (config.channels?.[platform]) {
|
|
96
|
+
const cur = config.channels[platform].allowFrom;
|
|
97
|
+
if (!cur || cur === '*') {
|
|
98
|
+
config.channels[platform].allowFrom = [];
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
saveConfig(config);
|
|
102
|
+
this.engine.config = config;
|
|
103
|
+
return true;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { logger } from '../core/logger.js';
|
|
2
|
+
|
|
3
|
+
export async function summarizeConversation(storage, aiGateway, agentId, contactId, model) {
|
|
4
|
+
const messages = await storage.getConversation(agentId, contactId, 100);
|
|
5
|
+
if (messages.length === 0) return 'No conversation to summarize.';
|
|
6
|
+
|
|
7
|
+
const chatText = messages.map(m => `${m.role}: ${m.content}`).join('\n');
|
|
8
|
+
|
|
9
|
+
const response = await aiGateway.chat([
|
|
10
|
+
{ role: 'system', content: 'Summarize this conversation concisely. Include key decisions, action items, and important information discussed. Be brief.' },
|
|
11
|
+
{ role: 'user', content: chatText },
|
|
12
|
+
], { model });
|
|
13
|
+
|
|
14
|
+
return response.content;
|
|
15
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { logger } from '../core/logger.js';
|
|
2
|
+
|
|
3
|
+
export async function generateBriefing(engine, agentId) {
|
|
4
|
+
const parts = [];
|
|
5
|
+
|
|
6
|
+
// Weather (default Riyadh)
|
|
7
|
+
try {
|
|
8
|
+
const { getWeather } = await import('../tools/weather.js');
|
|
9
|
+
parts.push(await getWeather('Riyadh'));
|
|
10
|
+
} catch {}
|
|
11
|
+
|
|
12
|
+
// Pending tasks
|
|
13
|
+
try {
|
|
14
|
+
const { TaskManager } = await import('../tools/tasks.js');
|
|
15
|
+
const tm = new TaskManager(engine.storage);
|
|
16
|
+
// Get all contacts' tasks for this agent
|
|
17
|
+
const tasks = engine.storage.db.prepare('SELECT * FROM tasks WHERE agent_id = ? AND done = 0 ORDER BY created_at LIMIT 10').all(agentId);
|
|
18
|
+
if (tasks.length > 0) {
|
|
19
|
+
parts.push('📋 *Pending Tasks:*\n' + tasks.map((t, i) => (i+1) + '. ' + t.task).join('\n'));
|
|
20
|
+
}
|
|
21
|
+
} catch {}
|
|
22
|
+
|
|
23
|
+
// Pending reminders
|
|
24
|
+
try {
|
|
25
|
+
const reminders = engine.storage.db.prepare("SELECT * FROM reminders WHERE agent_id = ? AND fired = 0 AND fire_at > datetime('now') ORDER BY fire_at LIMIT 5").all(agentId);
|
|
26
|
+
if (reminders.length > 0) {
|
|
27
|
+
parts.push('⏰ *Upcoming Reminders:*\n' + reminders.map(r => '• ' + r.fire_at.slice(11, 16) + ' — ' + r.message).join('\n'));
|
|
28
|
+
}
|
|
29
|
+
} catch {}
|
|
30
|
+
|
|
31
|
+
// Memories count
|
|
32
|
+
try {
|
|
33
|
+
const memCount = engine.storage.db.prepare('SELECT COUNT(*) as c FROM memories WHERE agent_id = ?').get(agentId);
|
|
34
|
+
parts.push('🧠 Memories: ' + (memCount?.c || 0) + ' facts stored');
|
|
35
|
+
} catch {}
|
|
36
|
+
|
|
37
|
+
// News
|
|
38
|
+
try {
|
|
39
|
+
const { BrowserTool } = await import('../tools/browser.js');
|
|
40
|
+
const b = new BrowserTool({});
|
|
41
|
+
const news = await b.search('Saudi Arabia news today', 3);
|
|
42
|
+
if (news.length > 0) {
|
|
43
|
+
parts.push('📰 *Top News:*\n' + news.map(n => '• ' + n.title).join('\n'));
|
|
44
|
+
}
|
|
45
|
+
} catch {}
|
|
46
|
+
|
|
47
|
+
return '☀️ *Good Morning! Daily Briefing*\n\n' + parts.join('\n\n');
|
|
48
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { logger } from '../core/logger.js';
|
|
2
|
+
|
|
3
|
+
export class HandoffManager {
|
|
4
|
+
constructor(storage) {
|
|
5
|
+
this.storage = storage;
|
|
6
|
+
this.activeHandoffs = new Map(); // contactId -> { to, startedAt }
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
start(agentId, contactId, to, reason) {
|
|
10
|
+
this.activeHandoffs.set(contactId, {
|
|
11
|
+
to, reason, startedAt: new Date(), agentId
|
|
12
|
+
});
|
|
13
|
+
logger.info('handoff', `${contactId} → ${to}: ${reason}`);
|
|
14
|
+
return true;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
isActive(contactId) {
|
|
18
|
+
return this.activeHandoffs.has(contactId);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
end(contactId) {
|
|
22
|
+
this.activeHandoffs.delete(contactId);
|
|
23
|
+
return true;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
getActive() {
|
|
27
|
+
return Array.from(this.activeHandoffs.entries()).map(([id, h]) => ({
|
|
28
|
+
contactId: id, ...h
|
|
29
|
+
}));
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { logger } from '../core/logger.js';
|
|
2
|
+
|
|
3
|
+
export class ScheduledReports {
|
|
4
|
+
constructor(engine) {
|
|
5
|
+
this.engine = engine;
|
|
6
|
+
this.timers = [];
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
startWeeklyReport(agentId, contactId, platform, metadata) {
|
|
10
|
+
// Every Sunday at 9 AM UTC
|
|
11
|
+
const check = () => {
|
|
12
|
+
const now = new Date();
|
|
13
|
+
if (now.getDay() === 0 && now.getHours() === 9 && now.getMinutes() === 0) {
|
|
14
|
+
this._sendWeeklyReport(agentId, contactId, platform, metadata);
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
const timer = setInterval(check, 60000);
|
|
18
|
+
this.timers.push(timer);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async _sendWeeklyReport(agentId, contactId, platform, metadata) {
|
|
22
|
+
try {
|
|
23
|
+
const usage = this.engine.storage.db.prepare(
|
|
24
|
+
"SELECT COUNT(*) as msgs, SUM(cost_usd) as cost, SUM(input_tokens + output_tokens) as tokens FROM usage WHERE agent_id = ? AND created_at >= date('now', '-7 days')"
|
|
25
|
+
).get(agentId);
|
|
26
|
+
|
|
27
|
+
const fmtT = (n) => n >= 1e6 ? (n/1e6).toFixed(1)+'M' : n >= 1e3 ? (n/1e3).toFixed(1)+'K' : String(n || 0);
|
|
28
|
+
|
|
29
|
+
const report = [
|
|
30
|
+
'📊 *Weekly Report*', '',
|
|
31
|
+
'💬 Messages: ' + (usage?.msgs || 0),
|
|
32
|
+
'🪙 Tokens: ' + fmtT(usage?.tokens),
|
|
33
|
+
'💰 Cost: $' + (usage?.cost || 0).toFixed(4),
|
|
34
|
+
].join('\n');
|
|
35
|
+
|
|
36
|
+
if (platform === 'telegram' && this.engine.telegramManager) {
|
|
37
|
+
await this.engine.telegramManager.sendMessage(agentId, contactId, report, metadata);
|
|
38
|
+
}
|
|
39
|
+
} catch (err) {
|
|
40
|
+
logger.error('reports', 'Weekly report failed: ' + err.message);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
stop() {
|
|
45
|
+
this.timers.forEach(t => clearInterval(t));
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { logger } from '../core/logger.js';
|
|
2
|
+
|
|
3
|
+
export class WebhookManager {
|
|
4
|
+
constructor(storage) {
|
|
5
|
+
this.storage = storage;
|
|
6
|
+
this._initDb();
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
_initDb() {
|
|
10
|
+
try {
|
|
11
|
+
this.storage.db.exec(`
|
|
12
|
+
CREATE TABLE IF NOT EXISTS webhooks (
|
|
13
|
+
id TEXT PRIMARY KEY,
|
|
14
|
+
agent_id TEXT NOT NULL,
|
|
15
|
+
name TEXT NOT NULL,
|
|
16
|
+
url TEXT NOT NULL,
|
|
17
|
+
event TEXT DEFAULT 'message',
|
|
18
|
+
active INTEGER DEFAULT 1,
|
|
19
|
+
created_at TEXT DEFAULT (datetime('now'))
|
|
20
|
+
)
|
|
21
|
+
`);
|
|
22
|
+
} catch {}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
add(agentId, name, url, event = 'message') {
|
|
26
|
+
const id = 'wh_' + Date.now().toString(36);
|
|
27
|
+
this.storage.db.prepare('INSERT INTO webhooks (id, agent_id, name, url, event) VALUES (?, ?, ?, ?, ?)').run(id, agentId, name, url, event);
|
|
28
|
+
return id;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async fire(agentId, event, data) {
|
|
32
|
+
const hooks = this.storage.db.prepare('SELECT * FROM webhooks WHERE agent_id = ? AND event = ? AND active = 1').all(agentId, event);
|
|
33
|
+
|
|
34
|
+
for (const hook of hooks) {
|
|
35
|
+
try {
|
|
36
|
+
await fetch(hook.url, {
|
|
37
|
+
method: 'POST',
|
|
38
|
+
headers: { 'Content-Type': 'application/json' },
|
|
39
|
+
body: JSON.stringify({ event, agentId, data, timestamp: new Date().toISOString() }),
|
|
40
|
+
});
|
|
41
|
+
logger.info('webhooks', `Fired ${hook.name} → ${hook.url}`);
|
|
42
|
+
} catch (err) {
|
|
43
|
+
logger.error('webhooks', `Failed ${hook.name}: ${err.message}`);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
list(agentId) {
|
|
49
|
+
return this.storage.db.prepare('SELECT * FROM webhooks WHERE agent_id = ?').all(agentId);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
remove(id) {
|
|
53
|
+
this.storage.db.prepare('DELETE FROM webhooks WHERE id = ?').run(id);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -16,7 +16,18 @@ export async function commandsMiddleware(ctx, next) {
|
|
|
16
16
|
'/memories — what I remember about you',
|
|
17
17
|
'/tasks — your todo list',
|
|
18
18
|
'/usage — spending report',
|
|
19
|
-
'/
|
|
19
|
+
'/allow <id> — allow someone to text me',
|
|
20
|
+
'/block <id> — block someone',
|
|
21
|
+
'/allowlist — show allowed users',
|
|
22
|
+
'/lockdown — restrict to allowlist only',
|
|
23
|
+
'/open — allow everyone',
|
|
24
|
+
'/notes — notes',
|
|
25
|
+
'/contacts — contact book',
|
|
26
|
+
'/briefing — daily briefing',
|
|
27
|
+
'/summary — summarize chat',
|
|
28
|
+
'/trivia — trivia game',
|
|
29
|
+
'/flip — coin flip',
|
|
30
|
+
'/config — manage settings',
|
|
20
31
|
'/exec <cmd> — run a shell command',
|
|
21
32
|
'/files — list sandbox files',
|
|
22
33
|
'/subagents — list background tasks',
|
|
@@ -121,6 +132,145 @@ export async function commandsMiddleware(ctx, next) {
|
|
|
121
132
|
return;
|
|
122
133
|
}
|
|
123
134
|
|
|
135
|
+
if (cmd === '/allow') {
|
|
136
|
+
const args = msg.slice(7).trim();
|
|
137
|
+
if (!args) { await ctx.reply('Usage: /allow <user_id or phone>'); return; }
|
|
138
|
+
try {
|
|
139
|
+
const { AllowlistManager } = await import('../features/allowlist-manager.js');
|
|
140
|
+
const am = new AllowlistManager(ctx.engine);
|
|
141
|
+
am.add(ctx.platform, args);
|
|
142
|
+
await ctx.reply('✅ Allowed *' + args + '* to message me');
|
|
143
|
+
} catch (err) { await ctx.reply('❌ ' + err.message); }
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (cmd === '/block') {
|
|
148
|
+
const args = msg.slice(7).trim();
|
|
149
|
+
if (!args) { await ctx.reply('Usage: /block <user_id or phone>'); return; }
|
|
150
|
+
try {
|
|
151
|
+
const { AllowlistManager } = await import('../features/allowlist-manager.js');
|
|
152
|
+
const am = new AllowlistManager(ctx.engine);
|
|
153
|
+
am.remove(ctx.platform, args);
|
|
154
|
+
await ctx.reply('🚫 Blocked *' + args + '* from messaging me');
|
|
155
|
+
} catch (err) { await ctx.reply('❌ ' + err.message); }
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (cmd === '/allowlist') {
|
|
160
|
+
try {
|
|
161
|
+
const { AllowlistManager } = await import('../features/allowlist-manager.js');
|
|
162
|
+
const am = new AllowlistManager(ctx.engine);
|
|
163
|
+
const { mode, list } = am.list(ctx.platform);
|
|
164
|
+
if (mode === 'open') {
|
|
165
|
+
await ctx.reply('🔓 *Open Mode* — anyone can message me\n\nUse /allow <id> to start restricting');
|
|
166
|
+
} else {
|
|
167
|
+
const lines = list.map(id => '• ' + id).join('\n');
|
|
168
|
+
await ctx.reply('🔒 *Restricted Mode*\n\nAllowed:\n' + lines + '\n\nUse /allow <id> or /block <id>');
|
|
169
|
+
}
|
|
170
|
+
} catch (err) { await ctx.reply('❌ ' + err.message); }
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (cmd === '/open') {
|
|
175
|
+
try {
|
|
176
|
+
const { AllowlistManager } = await import('../features/allowlist-manager.js');
|
|
177
|
+
const am = new AllowlistManager(ctx.engine);
|
|
178
|
+
am.openAll(ctx.platform);
|
|
179
|
+
await ctx.reply('🔓 Open mode — anyone can message me now');
|
|
180
|
+
} catch (err) { await ctx.reply('❌ ' + err.message); }
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (cmd === '/lockdown') {
|
|
185
|
+
try {
|
|
186
|
+
const { AllowlistManager } = await import('../features/allowlist-manager.js');
|
|
187
|
+
const am = new AllowlistManager(ctx.engine);
|
|
188
|
+
am.lockDown(ctx.platform);
|
|
189
|
+
await ctx.reply('🔒 Locked down — only allowlisted users can message me');
|
|
190
|
+
} catch (err) { await ctx.reply('❌ ' + err.message); }
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if (cmd === '/notes') {
|
|
195
|
+
try {
|
|
196
|
+
const { NotesManager } = await import('../tools/notes.js');
|
|
197
|
+
const nm = new NotesManager(ctx.storage);
|
|
198
|
+
const args = msg.slice(7).trim();
|
|
199
|
+
if (args) {
|
|
200
|
+
nm.add(ctx.agentId, ctx.contactId, args);
|
|
201
|
+
await ctx.reply('📝 Note saved!');
|
|
202
|
+
} else {
|
|
203
|
+
const notes = nm.list(ctx.agentId, ctx.contactId);
|
|
204
|
+
if (notes.length === 0) await ctx.reply('📝 No notes yet. Use /notes <text> to add one.');
|
|
205
|
+
else await ctx.reply('📝 *Your Notes*\n\n' + notes.map((n, i) => (i+1) + '. ' + n.content.slice(0, 80)).join('\n'));
|
|
206
|
+
}
|
|
207
|
+
} catch (err) { await ctx.reply('❌ ' + err.message); }
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
if (cmd === '/contacts') {
|
|
212
|
+
try {
|
|
213
|
+
const { ContactBook } = await import('../tools/contacts.js');
|
|
214
|
+
const cb = new ContactBook(ctx.storage);
|
|
215
|
+
const args = msg.slice(10).trim();
|
|
216
|
+
if (args) {
|
|
217
|
+
const found = cb.find(ctx.agentId, ctx.contactId, args);
|
|
218
|
+
if (found.length === 0) await ctx.reply('📇 No contacts found for: ' + args);
|
|
219
|
+
else await ctx.reply('📇 *Contacts*\n\n' + found.map(c => '• ' + c.name + (c.phone ? ' — ' + c.phone : '')).join('\n'));
|
|
220
|
+
} else {
|
|
221
|
+
const all = cb.list(ctx.agentId, ctx.contactId);
|
|
222
|
+
if (all.length === 0) await ctx.reply('📇 No contacts saved yet.');
|
|
223
|
+
else await ctx.reply('📇 *Contact Book*\n\n' + all.map(c => '• ' + c.name + (c.phone ? ' — ' + c.phone : '')).join('\n'));
|
|
224
|
+
}
|
|
225
|
+
} catch (err) { await ctx.reply('❌ ' + err.message); }
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if (cmd === '/briefing') {
|
|
230
|
+
try {
|
|
231
|
+
const { generateBriefing } = await import('../features/daily-briefing.js');
|
|
232
|
+
const briefing = await generateBriefing(ctx.engine, ctx.agentId);
|
|
233
|
+
await ctx.reply(briefing);
|
|
234
|
+
} catch (err) { await ctx.reply('❌ ' + err.message); }
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
if (cmd === '/summary') {
|
|
239
|
+
try {
|
|
240
|
+
const { summarizeConversation } = await import('../features/conversation-summary.js');
|
|
241
|
+
const summary = await summarizeConversation(ctx.storage, ctx.engine.aiGateway, ctx.agentId, ctx.contactId, ctx.agent?.model);
|
|
242
|
+
await ctx.reply('📋 *Conversation Summary*\n\n' + summary);
|
|
243
|
+
} catch (err) { await ctx.reply('❌ ' + err.message); }
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
if (cmd === '/flip') {
|
|
248
|
+
const { coinFlip } = await import('../tools/games.js');
|
|
249
|
+
await ctx.reply(coinFlip());
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
if (cmd === '/roll') {
|
|
254
|
+
const { rollDice } = await import('../tools/games.js');
|
|
255
|
+
const sides = parseInt(msg.slice(6)) || 6;
|
|
256
|
+
await ctx.reply('🎲 ' + rollDice(sides));
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
if (cmd === '/trivia') {
|
|
261
|
+
const { getTrivia } = await import('../tools/games.js');
|
|
262
|
+
const t = getTrivia();
|
|
263
|
+
await ctx.reply('🎯 *Trivia!*\n\n' + t.q);
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
if (cmd === '/riddle') {
|
|
268
|
+
const { getRiddle } = await import('../tools/games.js');
|
|
269
|
+
const r = getRiddle();
|
|
270
|
+
await ctx.reply('🧩 ' + r.q);
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
|
|
124
274
|
if (cmd === '/config') {
|
|
125
275
|
const args = msg.slice(8).trim();
|
|
126
276
|
if (!args || args === 'help') {
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Learning mode — track preferences and patterns
|
|
3
|
+
*/
|
|
4
|
+
import { logger } from '../core/logger.js';
|
|
5
|
+
|
|
6
|
+
export async function learningMiddleware(ctx, next) {
|
|
7
|
+
// Track interaction patterns
|
|
8
|
+
try {
|
|
9
|
+
const hour = new Date().getHours();
|
|
10
|
+
const key = 'active_hours_' + (hour < 10 ? '0' : '') + hour;
|
|
11
|
+
|
|
12
|
+
// Increment activity counter for this hour
|
|
13
|
+
const existing = ctx.storage.db.prepare(
|
|
14
|
+
'SELECT value FROM memories WHERE agent_id = ? AND key = ?'
|
|
15
|
+
).get(ctx.agentId, key);
|
|
16
|
+
|
|
17
|
+
const count = existing ? parseInt(existing.value) + 1 : 1;
|
|
18
|
+
await ctx.storage.saveMemory(ctx.agentId, key, String(count), 'pattern');
|
|
19
|
+
|
|
20
|
+
// Track language preference
|
|
21
|
+
const hasArabic = /[\u0600-\u06FF]/.test(ctx.message);
|
|
22
|
+
if (hasArabic) {
|
|
23
|
+
await ctx.storage.saveMemory(ctx.agentId, 'prefers_arabic', 'true', 'pattern');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Track message length preference
|
|
27
|
+
if (ctx.message.length < 20) {
|
|
28
|
+
await ctx.storage.saveMemory(ctx.agentId, 'prefers_short_messages', 'true', 'pattern');
|
|
29
|
+
}
|
|
30
|
+
} catch {}
|
|
31
|
+
|
|
32
|
+
await next();
|
|
33
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Proactive check-ins — notify about upcoming events
|
|
3
|
+
*/
|
|
4
|
+
export async function proactiveMiddleware(ctx, next) {
|
|
5
|
+
await next();
|
|
6
|
+
|
|
7
|
+
// After responding, check if there are upcoming reminders (< 1 hour)
|
|
8
|
+
if (!ctx.engine.reminders) return;
|
|
9
|
+
|
|
10
|
+
try {
|
|
11
|
+
const upcoming = ctx.engine.storage.db.prepare(
|
|
12
|
+
"SELECT * FROM reminders WHERE agent_id = ? AND contact_id = ? AND fired = 0 AND fire_at BETWEEN datetime('now') AND datetime('now', '+1 hour') LIMIT 1"
|
|
13
|
+
).get(ctx.agentId, ctx.contactId);
|
|
14
|
+
|
|
15
|
+
if (upcoming && ctx.response?.messages) {
|
|
16
|
+
const mins = Math.round((new Date(upcoming.fire_at + 'Z') - Date.now()) / 60000);
|
|
17
|
+
if (mins > 0 && mins <= 60) {
|
|
18
|
+
ctx.response.messages.push('⏰ Heads up — you have a reminder in ' + mins + ' minutes: ' + upcoming.message);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
} catch {}
|
|
22
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { logger } from '../core/logger.js';
|
|
2
|
+
|
|
3
|
+
export class ContactBook {
|
|
4
|
+
constructor(storage) {
|
|
5
|
+
this.storage = storage;
|
|
6
|
+
this._initDb();
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
_initDb() {
|
|
10
|
+
try {
|
|
11
|
+
this.storage.db.exec(`
|
|
12
|
+
CREATE TABLE IF NOT EXISTS contact_book (
|
|
13
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
14
|
+
agent_id TEXT NOT NULL,
|
|
15
|
+
owner_id TEXT NOT NULL,
|
|
16
|
+
name TEXT NOT NULL,
|
|
17
|
+
phone TEXT,
|
|
18
|
+
email TEXT,
|
|
19
|
+
notes TEXT DEFAULT '',
|
|
20
|
+
created_at TEXT DEFAULT (datetime('now'))
|
|
21
|
+
)
|
|
22
|
+
`);
|
|
23
|
+
} catch {}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
add(agentId, ownerId, name, phone, email, notes) {
|
|
27
|
+
this.storage.db.prepare('INSERT INTO contact_book (agent_id, owner_id, name, phone, email, notes) VALUES (?, ?, ?, ?, ?, ?)')
|
|
28
|
+
.run(agentId, ownerId, name, phone || null, email || null, notes || '');
|
|
29
|
+
return true;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
find(agentId, ownerId, query) {
|
|
33
|
+
return this.storage.db.prepare('SELECT * FROM contact_book WHERE agent_id = ? AND owner_id = ? AND (name LIKE ? OR phone LIKE ? OR email LIKE ?) LIMIT 10')
|
|
34
|
+
.all(agentId, ownerId, `%${query}%`, `%${query}%`, `%${query}%`);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
list(agentId, ownerId) {
|
|
38
|
+
return this.storage.db.prepare('SELECT * FROM contact_book WHERE agent_id = ? AND owner_id = ? ORDER BY name').all(agentId, ownerId);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
delete(agentId, id) {
|
|
42
|
+
this.storage.db.prepare('DELETE FROM contact_book WHERE id = ? AND agent_id = ?').run(id, agentId);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import crypto from 'crypto';
|
|
2
|
+
|
|
3
|
+
const TRIVIA = [
|
|
4
|
+
{ q: 'What is the capital of Saudi Arabia?', a: 'Riyadh' },
|
|
5
|
+
{ q: 'In what year was the iPhone first released?', a: '2007' },
|
|
6
|
+
{ q: 'What planet is known as the Red Planet?', a: 'Mars' },
|
|
7
|
+
{ q: 'How many legs does an octopus have?', a: '8' },
|
|
8
|
+
{ q: 'What is the largest ocean on Earth?', a: 'Pacific Ocean' },
|
|
9
|
+
{ q: 'Who painted the Mona Lisa?', a: 'Leonardo da Vinci' },
|
|
10
|
+
{ q: 'What is the chemical symbol for gold?', a: 'Au' },
|
|
11
|
+
{ q: 'What year did World War II end?', a: '1945' },
|
|
12
|
+
{ q: 'What is the tallest building in the world?', a: 'Burj Khalifa' },
|
|
13
|
+
{ q: 'How many continents are there?', a: '7' },
|
|
14
|
+
];
|
|
15
|
+
|
|
16
|
+
const RIDDLES = [
|
|
17
|
+
{ q: 'I have cities, but no houses. I have mountains, but no trees. What am I?', a: 'A map' },
|
|
18
|
+
{ q: 'What has hands but cannot clap?', a: 'A clock' },
|
|
19
|
+
{ q: 'I speak without a mouth and hear without ears. What am I?', a: 'An echo' },
|
|
20
|
+
{ q: 'The more you take, the more you leave behind. What am I?', a: 'Footsteps' },
|
|
21
|
+
{ q: 'What can you break, even if you never pick it up or touch it?', a: 'A promise' },
|
|
22
|
+
];
|
|
23
|
+
|
|
24
|
+
export function getTrivia() {
|
|
25
|
+
const i = crypto.randomInt(TRIVIA.length);
|
|
26
|
+
return TRIVIA[i];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function getRiddle() {
|
|
30
|
+
const i = crypto.randomInt(RIDDLES.length);
|
|
31
|
+
return RIDDLES[i];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function coinFlip() {
|
|
35
|
+
return crypto.randomInt(2) === 0 ? 'Heads! 🪙' : 'Tails! 🪙';
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function rollDice(sides = 6) {
|
|
39
|
+
return crypto.randomInt(sides) + 1;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function randomNumber(min = 1, max = 100) {
|
|
43
|
+
return crypto.randomInt(min, max + 1);
|
|
44
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { logger } from '../core/logger.js';
|
|
2
|
+
|
|
3
|
+
export class NotesManager {
|
|
4
|
+
constructor(storage) {
|
|
5
|
+
this.storage = storage;
|
|
6
|
+
this._initDb();
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
_initDb() {
|
|
10
|
+
try {
|
|
11
|
+
this.storage.db.exec(`
|
|
12
|
+
CREATE TABLE IF NOT EXISTS notes (
|
|
13
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
14
|
+
agent_id TEXT NOT NULL,
|
|
15
|
+
contact_id TEXT NOT NULL,
|
|
16
|
+
title TEXT,
|
|
17
|
+
content TEXT NOT NULL,
|
|
18
|
+
tags TEXT DEFAULT '',
|
|
19
|
+
created_at TEXT DEFAULT (datetime('now')),
|
|
20
|
+
updated_at TEXT DEFAULT (datetime('now'))
|
|
21
|
+
)
|
|
22
|
+
`);
|
|
23
|
+
} catch {}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
add(agentId, contactId, content, title = null) {
|
|
27
|
+
this.storage.db.prepare('INSERT INTO notes (agent_id, contact_id, title, content) VALUES (?, ?, ?, ?)').run(agentId, contactId, title, content);
|
|
28
|
+
return true;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
list(agentId, contactId, limit = 10) {
|
|
32
|
+
return this.storage.db.prepare('SELECT * FROM notes WHERE agent_id = ? AND contact_id = ? ORDER BY created_at DESC LIMIT ?').all(agentId, contactId, limit);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
search(agentId, contactId, query) {
|
|
36
|
+
return this.storage.db.prepare('SELECT * FROM notes WHERE agent_id = ? AND contact_id = ? AND (content LIKE ? OR title LIKE ?) ORDER BY created_at DESC LIMIT 10')
|
|
37
|
+
.all(agentId, contactId, `%${query}%`, `%${query}%`);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
delete(agentId, id) {
|
|
41
|
+
this.storage.db.prepare('DELETE FROM notes WHERE id = ? AND agent_id = ?').run(id, agentId);
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
}
|
package/lib/tools/router.js
CHANGED
|
@@ -59,6 +59,53 @@ export class ToolRouter {
|
|
|
59
59
|
'Send an email.');
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
+
tools.push('', '### Allow User',
|
|
63
|
+
'---TOOL:allow:user_id_or_phone---',
|
|
64
|
+
'Add someone to the allowlist so they can message you.',
|
|
65
|
+
'', '### Block User',
|
|
66
|
+
'---TOOL:block:user_id_or_phone---',
|
|
67
|
+
'Remove someone from the allowlist.',
|
|
68
|
+
'', '### Show Allowlist',
|
|
69
|
+
'---TOOL:allowlist:show---',
|
|
70
|
+
'Show who is currently allowed to message.');
|
|
71
|
+
|
|
72
|
+
tools.push('', '### Save Note',
|
|
73
|
+
'---TOOL:note:content of the note---',
|
|
74
|
+
'Save a personal note for the user.',
|
|
75
|
+
'', '### List Notes',
|
|
76
|
+
'---TOOL:notes_list:all---',
|
|
77
|
+
'Show user\'s saved notes.',
|
|
78
|
+
'', '### Search Notes',
|
|
79
|
+
'---TOOL:notes_search:query---',
|
|
80
|
+
'Search through user\'s notes.',
|
|
81
|
+
'', '### Save Contact',
|
|
82
|
+
'---TOOL:contact_add:Name|phone|email---',
|
|
83
|
+
'Save a contact to the user\'s contact book.',
|
|
84
|
+
'', '### Find Contact',
|
|
85
|
+
'---TOOL:contact_find:name or number---',
|
|
86
|
+
'Search the user\'s contacts.',
|
|
87
|
+
'', '### Summarize Chat',
|
|
88
|
+
'---TOOL:summarize:conversation---',
|
|
89
|
+
'Summarize our conversation so far.',
|
|
90
|
+
'', '### Daily Briefing',
|
|
91
|
+
'---TOOL:briefing:now---',
|
|
92
|
+
'Generate a morning briefing (weather, tasks, news, reminders).',
|
|
93
|
+
'', '### Trivia Game',
|
|
94
|
+
'---TOOL:trivia:start---',
|
|
95
|
+
'Ask a trivia question.',
|
|
96
|
+
'', '### Riddle',
|
|
97
|
+
'---TOOL:riddle:start---',
|
|
98
|
+
'Tell a riddle.',
|
|
99
|
+
'', '### Coin Flip',
|
|
100
|
+
'---TOOL:coinflip:flip---',
|
|
101
|
+
'Flip a coin.',
|
|
102
|
+
'', '### Roll Dice',
|
|
103
|
+
'---TOOL:dice:6---',
|
|
104
|
+
'Roll a dice. Number is sides (default 6).',
|
|
105
|
+
'', '### Handoff to Human',
|
|
106
|
+
'---TOOL:handoff:reason---',
|
|
107
|
+
'Transfer the conversation to a human agent. Use when you cannot help further.');
|
|
108
|
+
|
|
62
109
|
tools.push('', '### Run Command',
|
|
63
110
|
'---TOOL:exec:ls -la---',
|
|
64
111
|
'Execute a shell command. Output is returned. Sandboxed for safety.',
|
|
@@ -134,6 +181,12 @@ export class ToolRouter {
|
|
|
134
181
|
* Process AI response — extract and execute tool calls
|
|
135
182
|
* Returns: { toolUsed, toolResult, cleanResponse }
|
|
136
183
|
*/
|
|
184
|
+
setContext(engine, aiGateway, model) {
|
|
185
|
+
this._engine = engine;
|
|
186
|
+
this._aiGateway = aiGateway;
|
|
187
|
+
this._model = model;
|
|
188
|
+
}
|
|
189
|
+
|
|
137
190
|
async processResponse(response, agentId) {
|
|
138
191
|
const toolMatch = response.match(/---TOOL:(\w+):(.+?)---/);
|
|
139
192
|
if (!toolMatch) return { toolUsed: false, toolResult: null, cleanResponse: response };
|
|
@@ -168,6 +221,114 @@ export class ToolRouter {
|
|
|
168
221
|
}
|
|
169
222
|
break;
|
|
170
223
|
}
|
|
224
|
+
case 'allow': {
|
|
225
|
+
try {
|
|
226
|
+
const { AllowlistManager } = await import('../features/allowlist-manager.js');
|
|
227
|
+
const am = new AllowlistManager(this._engine);
|
|
228
|
+
const platform = this._currentPlatform || 'telegram';
|
|
229
|
+
am.add(platform, toolArg);
|
|
230
|
+
toolResult = 'Allowed ' + toolArg + ' to message you ✅';
|
|
231
|
+
} catch (err) { toolResult = 'Failed: ' + err.message; }
|
|
232
|
+
break;
|
|
233
|
+
}
|
|
234
|
+
case 'block': {
|
|
235
|
+
try {
|
|
236
|
+
const { AllowlistManager } = await import('../features/allowlist-manager.js');
|
|
237
|
+
const am = new AllowlistManager(this._engine);
|
|
238
|
+
const platform = this._currentPlatform || 'telegram';
|
|
239
|
+
am.remove(platform, toolArg);
|
|
240
|
+
toolResult = 'Blocked ' + toolArg + ' ✅';
|
|
241
|
+
} catch (err) { toolResult = 'Failed: ' + err.message; }
|
|
242
|
+
break;
|
|
243
|
+
}
|
|
244
|
+
case 'allowlist': {
|
|
245
|
+
try {
|
|
246
|
+
const { AllowlistManager } = await import('../features/allowlist-manager.js');
|
|
247
|
+
const am = new AllowlistManager(this._engine);
|
|
248
|
+
const platform = this._currentPlatform || 'telegram';
|
|
249
|
+
const { mode, list } = am.list(platform);
|
|
250
|
+
toolResult = mode === 'open' ? 'Open — anyone can message' : 'Restricted to: ' + list.join(', ');
|
|
251
|
+
} catch (err) { toolResult = 'Failed: ' + err.message; }
|
|
252
|
+
break;
|
|
253
|
+
}
|
|
254
|
+
case 'note': {
|
|
255
|
+
const { NotesManager } = await import('./notes.js');
|
|
256
|
+
const nm = new NotesManager(this.storage);
|
|
257
|
+
nm.add(agentId, this._currentContactId || 'unknown', toolArg);
|
|
258
|
+
toolResult = 'Note saved! 📝';
|
|
259
|
+
break;
|
|
260
|
+
}
|
|
261
|
+
case 'notes_list': {
|
|
262
|
+
const { NotesManager } = await import('./notes.js');
|
|
263
|
+
const nm = new NotesManager(this.storage);
|
|
264
|
+
const notes = nm.list(agentId, this._currentContactId || 'unknown');
|
|
265
|
+
if (notes.length === 0) toolResult = 'No notes yet!';
|
|
266
|
+
else toolResult = notes.map((n, i) => (i+1) + '. ' + (n.title || n.content.slice(0, 50))).join('\n');
|
|
267
|
+
break;
|
|
268
|
+
}
|
|
269
|
+
case 'notes_search': {
|
|
270
|
+
const { NotesManager } = await import('./notes.js');
|
|
271
|
+
const nm = new NotesManager(this.storage);
|
|
272
|
+
const found = nm.search(agentId, this._currentContactId || 'unknown', toolArg);
|
|
273
|
+
if (found.length === 0) toolResult = 'No notes found for: ' + toolArg;
|
|
274
|
+
else toolResult = found.map(n => '• ' + n.content.slice(0, 100)).join('\n');
|
|
275
|
+
break;
|
|
276
|
+
}
|
|
277
|
+
case 'contact_add': {
|
|
278
|
+
const { ContactBook } = await import('./contacts.js');
|
|
279
|
+
const cb = new ContactBook(this.storage);
|
|
280
|
+
const parts = toolArg.split('|').map(p => p.trim());
|
|
281
|
+
cb.add(agentId, this._currentContactId || 'unknown', parts[0], parts[1], parts[2], parts[3]);
|
|
282
|
+
toolResult = 'Contact saved: ' + parts[0] + ' 📇';
|
|
283
|
+
break;
|
|
284
|
+
}
|
|
285
|
+
case 'contact_find': {
|
|
286
|
+
const { ContactBook } = await import('./contacts.js');
|
|
287
|
+
const cb = new ContactBook(this.storage);
|
|
288
|
+
const found = cb.find(agentId, this._currentContactId || 'unknown', toolArg);
|
|
289
|
+
if (found.length === 0) toolResult = 'No contacts found for: ' + toolArg;
|
|
290
|
+
else toolResult = found.map(c => '📇 ' + c.name + (c.phone ? ' — ' + c.phone : '') + (c.email ? ' — ' + c.email : '')).join('\n');
|
|
291
|
+
break;
|
|
292
|
+
}
|
|
293
|
+
case 'summarize': {
|
|
294
|
+
const { summarizeConversation } = await import('../features/conversation-summary.js');
|
|
295
|
+
toolResult = await summarizeConversation(this.storage, this._aiGateway, agentId, this._currentContactId, this._model);
|
|
296
|
+
break;
|
|
297
|
+
}
|
|
298
|
+
case 'briefing': {
|
|
299
|
+
const { generateBriefing } = await import('../features/daily-briefing.js');
|
|
300
|
+
toolResult = await generateBriefing(this._engine, agentId);
|
|
301
|
+
break;
|
|
302
|
+
}
|
|
303
|
+
case 'trivia': {
|
|
304
|
+
const { getTrivia } = await import('./games.js');
|
|
305
|
+
const t = getTrivia();
|
|
306
|
+
toolResult = '🎯 *Trivia!*\n\n' + t.q + '\n\n(Answer: ||' + t.a + '||)';
|
|
307
|
+
break;
|
|
308
|
+
}
|
|
309
|
+
case 'riddle': {
|
|
310
|
+
const { getRiddle } = await import('./games.js');
|
|
311
|
+
const r = getRiddle();
|
|
312
|
+
toolResult = '🧩 *Riddle!*\n\n' + r.q + '\n\n(Answer: ||' + r.a + '||)';
|
|
313
|
+
break;
|
|
314
|
+
}
|
|
315
|
+
case 'coinflip':
|
|
316
|
+
case 'coin': {
|
|
317
|
+
const { coinFlip } = await import('./games.js');
|
|
318
|
+
toolResult = coinFlip();
|
|
319
|
+
break;
|
|
320
|
+
}
|
|
321
|
+
case 'dice':
|
|
322
|
+
case 'roll': {
|
|
323
|
+
const { rollDice } = await import('./games.js');
|
|
324
|
+
const sides = parseInt(toolArg) || 6;
|
|
325
|
+
toolResult = '🎲 Rolled a ' + rollDice(sides) + '! (d' + sides + ')';
|
|
326
|
+
break;
|
|
327
|
+
}
|
|
328
|
+
case 'handoff': {
|
|
329
|
+
toolResult = '🤝 Transferring to a human agent. Reason: ' + toolArg;
|
|
330
|
+
break;
|
|
331
|
+
}
|
|
171
332
|
case 'exec':
|
|
172
333
|
case 'shell':
|
|
173
334
|
case 'run': {
|