apexbot 1.0.8 → 1.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/README.md +32 -6
- package/dist/agent/agentManager.js +82 -28
- package/dist/agent/orchestrator.js +7 -6
- package/dist/agent/toolExecutor.js +67 -26
- package/dist/channels/telegram.js +10 -0
- package/dist/cli/chat.js +41 -19
- package/dist/cli/index.js +37 -0
- package/dist/cli/onboard.js +124 -0
- package/dist/core/debug.js +23 -0
- package/dist/gateway/dashboard.js +2 -0
- package/dist/gateway/index.js +13 -0
- package/dist/skills/index.js +31 -21
- package/dist/skills/memory.js +467 -0
- package/dist/tools/loader.js +3 -1
- package/package.json +7 -4
package/README.md
CHANGED
|
@@ -39,20 +39,32 @@
|
|
|
39
39
|
|
|
40
40
|
### 🧠 Brain System
|
|
41
41
|
- Personality profiles (SOUL files)
|
|
42
|
-
- Long-term memory with vector store
|
|
42
|
+
- **Long-term memory** with LanceDB vector store
|
|
43
|
+
- **Auto-recall** - relevant memories injected into context
|
|
44
|
+
- **Auto-capture** - preferences and decisions saved automatically
|
|
43
45
|
- Context-aware responses
|
|
44
46
|
- Lobster Engine for reasoning
|
|
45
47
|
|
|
48
|
+
### 🔄 Model Fallback Chain
|
|
49
|
+
- **Automatic failover** between AI providers
|
|
50
|
+
- Tries: Ollama → Google → Anthropic
|
|
51
|
+
- No manual intervention needed
|
|
52
|
+
|
|
46
53
|
### 🛠️ 30+ Skills
|
|
47
54
|
|
|
48
55
|
| Category | Skills |
|
|
49
56
|
|----------|--------|
|
|
50
57
|
| **Communication** | Telegram, WhatsApp, Discord, Slack, Signal, iMessage, Matrix, Twitter, Gmail |
|
|
51
|
-
| **Productivity** | Obsidian, Notion, Trello, GitHub, Calendar, Reminders, Apple Notes |
|
|
58
|
+
| **Productivity** | Obsidian, Notion, Trello, GitHub, Calendar, Reminders, Apple Notes, **Memory** |
|
|
52
59
|
| **Media** | Spotify, Sonos, Voice/TTS, Image Gen, GIF Search |
|
|
53
60
|
| **Smart Home** | Home Assistant, Philips Hue |
|
|
54
61
|
| **Automation** | Browser, Cron, 1Password, Weather, Search, System |
|
|
55
62
|
|
|
63
|
+
### ⚡ Moltbot-Style Enhancements
|
|
64
|
+
- **Rate limiting** via `@grammyjs/transformer-throttler`
|
|
65
|
+
- **Typing indicators** - shows "typing..." while processing
|
|
66
|
+
- **Memory tools** - `memory_recall`, `memory_store`, `memory_forget`
|
|
67
|
+
|
|
56
68
|
### ⌨️ Claude Code Style CLI
|
|
57
69
|
|
|
58
70
|
**Input Prefixes:**
|
|
@@ -165,12 +177,23 @@ Config file: `~/.apexbot/config.json`
|
|
|
165
177
|
|
|
166
178
|
| Provider | Cost | Notes |
|
|
167
179
|
|----------|------|-------|
|
|
168
|
-
| **Ollama** | Free | Local, recommended |
|
|
169
|
-
| Google Gemini | Free tier | Cloud API |
|
|
170
|
-
| Anthropic Claude | Paid | Cloud API |
|
|
180
|
+
| **Ollama** | Free | Local, recommended, default |
|
|
181
|
+
| Google Gemini | Free tier | Cloud API, fallback #1 |
|
|
182
|
+
| Anthropic Claude | Paid | Cloud API, fallback #2 |
|
|
171
183
|
| OpenAI GPT | Paid | Cloud API |
|
|
172
184
|
| Kimi | Free tier | Cloud API |
|
|
173
185
|
|
|
186
|
+
### Memory Plugin (Optional)
|
|
187
|
+
|
|
188
|
+
```env
|
|
189
|
+
# Required for memory_recall/store/forget
|
|
190
|
+
OPENAI_API_KEY=sk-...
|
|
191
|
+
|
|
192
|
+
# Optional
|
|
193
|
+
MEMORY_EMBEDDING_MODEL=text-embedding-3-small
|
|
194
|
+
MEMORY_DB_PATH=~/.apexbot/memory/lancedb
|
|
195
|
+
```
|
|
196
|
+
|
|
174
197
|
---
|
|
175
198
|
|
|
176
199
|
## 🔧 CLI Commands
|
|
@@ -253,6 +276,9 @@ MIT License. See [LICENSE](LICENSE).
|
|
|
253
276
|
|
|
254
277
|
## 🙏 Acknowledgments
|
|
255
278
|
|
|
256
|
-
-
|
|
279
|
+
- **Ported features from** [Clawdbot/Moltbot](https://clawd.bot)
|
|
280
|
+
- Model fallback chain
|
|
281
|
+
- Long-term memory with LanceDB
|
|
282
|
+
- Rate limiting & typing indicators
|
|
257
283
|
- Powered by [Ollama](https://ollama.com)
|
|
258
284
|
- Built with TypeScript, grammy, discord.js
|
|
@@ -12,10 +12,18 @@ const generative_ai_1 = require("@google/generative-ai");
|
|
|
12
12
|
const tools_1 = require("../tools");
|
|
13
13
|
const brain_1 = require("../brain");
|
|
14
14
|
const toolExecutor_1 = require("./toolExecutor");
|
|
15
|
+
const debug_1 = require("../core/debug");
|
|
15
16
|
class AgentManager {
|
|
16
17
|
config = null;
|
|
17
18
|
googleClient = null;
|
|
18
19
|
brain = null;
|
|
20
|
+
// Moltbot-style model fallback chain
|
|
21
|
+
fallbackModels = [
|
|
22
|
+
{ provider: 'ollama', model: 'qwen2.5:14b' },
|
|
23
|
+
{ provider: 'google', model: 'gemini-2.0-flash' },
|
|
24
|
+
{ provider: 'anthropic', model: 'claude-sonnet-4-20250514' },
|
|
25
|
+
];
|
|
26
|
+
fallbackEnabled = true;
|
|
19
27
|
defaultSystemPrompt = `You are ApexBot, an autonomous AI assistant like Claude Code. You can execute real actions on the user's computer.
|
|
20
28
|
|
|
21
29
|
## CORE PRINCIPLES
|
|
@@ -58,7 +66,7 @@ You are running locally on the user's machine. No data leaves their computer. Yo
|
|
|
58
66
|
*/
|
|
59
67
|
setBrain(brain) {
|
|
60
68
|
this.brain = brain;
|
|
61
|
-
|
|
69
|
+
(0, debug_1.debug)('Agent', 'Brain connected');
|
|
62
70
|
}
|
|
63
71
|
configure(config) {
|
|
64
72
|
this.config = {
|
|
@@ -84,7 +92,7 @@ You are running locally on the user's machine. No data leaves their computer. Yo
|
|
|
84
92
|
if (config.provider === 'kimi') {
|
|
85
93
|
// nothing to initialize here; processWithKimi will use fetch + config.apiUrl
|
|
86
94
|
}
|
|
87
|
-
|
|
95
|
+
(0, debug_1.debug)('Agent', `Configured with ${config.provider} (${config.model || 'default model'})`);
|
|
88
96
|
}
|
|
89
97
|
async process(session, message) {
|
|
90
98
|
if (!this.config) {
|
|
@@ -92,7 +100,7 @@ You are running locally on the user's machine. No data leaves their computer. Yo
|
|
|
92
100
|
return { text: 'Agent not configured. Please set up an AI provider.' };
|
|
93
101
|
}
|
|
94
102
|
const userText = message.text || '';
|
|
95
|
-
|
|
103
|
+
(0, debug_1.debug)('Agent', `Processing message: ${userText.slice(0, 50)}...`);
|
|
96
104
|
// Handle slash commands
|
|
97
105
|
if (userText.startsWith('/')) {
|
|
98
106
|
return this.handleCommand(session, userText);
|
|
@@ -101,30 +109,44 @@ You are running locally on the user's machine. No data leaves their computer. Yo
|
|
|
101
109
|
const history = this.buildHistory(session, userText);
|
|
102
110
|
try {
|
|
103
111
|
let response;
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
case 'google':
|
|
109
|
-
response = await this.processWithGemini(history);
|
|
110
|
-
break;
|
|
111
|
-
case 'anthropic':
|
|
112
|
-
response = await this.processWithClaude(history);
|
|
113
|
-
break;
|
|
114
|
-
case 'openai':
|
|
115
|
-
response = await this.processWithOpenAI(history);
|
|
116
|
-
break;
|
|
117
|
-
case 'kimi':
|
|
118
|
-
response = await this.processWithKimi(history);
|
|
119
|
-
break;
|
|
120
|
-
default:
|
|
121
|
-
response = { text: 'Unknown AI provider' };
|
|
112
|
+
let lastError = null;
|
|
113
|
+
// Try primary provider first
|
|
114
|
+
try {
|
|
115
|
+
response = await this.processWithProvider(this.config.provider, this.config.model || '', history);
|
|
122
116
|
}
|
|
117
|
+
catch (primaryError) {
|
|
118
|
+
console.warn(`[Agent] Primary provider ${this.config.provider} failed: ${primaryError.message}`);
|
|
119
|
+
lastError = primaryError;
|
|
120
|
+
// Try fallback providers (Moltbot-style)
|
|
121
|
+
if (this.fallbackEnabled && this.fallbackModels.length > 0) {
|
|
122
|
+
for (const fallback of this.fallbackModels) {
|
|
123
|
+
// Skip if same as primary
|
|
124
|
+
if (fallback.provider === this.config.provider)
|
|
125
|
+
continue;
|
|
126
|
+
try {
|
|
127
|
+
(0, debug_1.debug)('Agent', `Trying fallback: ${fallback.provider}/${fallback.model}`);
|
|
128
|
+
response = await this.processWithProvider(fallback.provider, fallback.model, history);
|
|
129
|
+
(0, debug_1.debug)('Agent', `Fallback ${fallback.provider} succeeded!`);
|
|
130
|
+
lastError = null;
|
|
131
|
+
break;
|
|
132
|
+
}
|
|
133
|
+
catch (fallbackError) {
|
|
134
|
+
console.warn(`[Agent] Fallback ${fallback.provider} failed: ${fallbackError.message}`);
|
|
135
|
+
lastError = fallbackError;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
// If all providers failed
|
|
140
|
+
if (lastError) {
|
|
141
|
+
throw lastError;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
response = response;
|
|
123
145
|
// Check for tool calls in response
|
|
124
146
|
if (this.config.enableTools) {
|
|
125
147
|
const toolCalls = (0, toolExecutor_1.parseToolCalls)(response.text);
|
|
126
148
|
if (toolCalls.length > 0) {
|
|
127
|
-
|
|
149
|
+
(0, debug_1.debug)('Agent', `Found ${toolCalls.length} tool calls:`, toolCalls.map(t => t.name));
|
|
128
150
|
// Build tool context
|
|
129
151
|
const context = (0, toolExecutor_1.buildToolContext)(session.id, message.userId || message.from || 'unknown', message.channel, { workspaceDir: process.cwd() });
|
|
130
152
|
// Execute tools
|
|
@@ -132,9 +154,9 @@ You are running locally on the user's machine. No data leaves their computer. Yo
|
|
|
132
154
|
response.toolCalls = toolResults;
|
|
133
155
|
// Log results
|
|
134
156
|
for (const { call, result } of toolResults) {
|
|
135
|
-
|
|
157
|
+
(0, debug_1.debug)('Agent', `Tool ${call.name}: ${result.success ? 'SUCCESS' : 'FAILED'}`);
|
|
136
158
|
if (!result.success)
|
|
137
|
-
|
|
159
|
+
(0, debug_1.debug)('Agent', `Error: ${result.error}`);
|
|
138
160
|
}
|
|
139
161
|
// Format results and continue conversation
|
|
140
162
|
const resultsText = (0, toolExecutor_1.formatToolResults)(toolResults);
|
|
@@ -172,7 +194,7 @@ You are running locally on the user's machine. No data leaves their computer. Yo
|
|
|
172
194
|
}
|
|
173
195
|
}
|
|
174
196
|
}
|
|
175
|
-
|
|
197
|
+
(0, debug_1.debug)('Agent', `Generated response: ${response.text.slice(0, 50)}...`);
|
|
176
198
|
// Save to session (initialize messages array if needed)
|
|
177
199
|
if (!session.messages)
|
|
178
200
|
session.messages = [];
|
|
@@ -247,6 +269,38 @@ You are running locally on the user's machine. No data leaves their computer. Yo
|
|
|
247
269
|
}
|
|
248
270
|
return parts.join('\n') || 'Done.';
|
|
249
271
|
}
|
|
272
|
+
/**
|
|
273
|
+
* Route to the correct provider-specific method (Moltbot-style)
|
|
274
|
+
*/
|
|
275
|
+
async processWithProvider(provider, model, history) {
|
|
276
|
+
// Temporarily override model if specified
|
|
277
|
+
const originalModel = this.config?.model;
|
|
278
|
+
if (model && this.config) {
|
|
279
|
+
this.config.model = model;
|
|
280
|
+
}
|
|
281
|
+
try {
|
|
282
|
+
switch (provider) {
|
|
283
|
+
case 'ollama':
|
|
284
|
+
return await this.processWithOllama(history);
|
|
285
|
+
case 'google':
|
|
286
|
+
return await this.processWithGemini(history);
|
|
287
|
+
case 'anthropic':
|
|
288
|
+
return await this.processWithClaude(history);
|
|
289
|
+
case 'openai':
|
|
290
|
+
return await this.processWithOpenAI(history);
|
|
291
|
+
case 'kimi':
|
|
292
|
+
return await this.processWithKimi(history);
|
|
293
|
+
default:
|
|
294
|
+
throw new Error(`Unknown provider: ${provider}`);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
finally {
|
|
298
|
+
// Restore original model
|
|
299
|
+
if (originalModel && this.config) {
|
|
300
|
+
this.config.model = originalModel;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
}
|
|
250
304
|
async processWithKimi(history) {
|
|
251
305
|
// Generic Kimi 2.5 integration wrapper. This implementation is intentionally
|
|
252
306
|
// generic: it will POST to a user-provided `apiUrl` (in config or env) and
|
|
@@ -298,7 +352,7 @@ You are running locally on the user's machine. No data leaves their computer. Yo
|
|
|
298
352
|
const apiUrl = cfg.apiUrl || 'http://localhost:11434';
|
|
299
353
|
const model = cfg.model || 'llama3.2';
|
|
300
354
|
const temperature = cfg.temperature ?? 0.7;
|
|
301
|
-
|
|
355
|
+
(0, debug_1.debug)('Agent', `Calling Ollama at ${apiUrl} with model ${model}...`);
|
|
302
356
|
// Build messages array for Ollama chat API
|
|
303
357
|
const messages = history.map(m => ({
|
|
304
358
|
role: m.role,
|
|
@@ -327,7 +381,7 @@ You are running locally on the user's machine. No data leaves their computer. Yo
|
|
|
327
381
|
}
|
|
328
382
|
const data = await res.json();
|
|
329
383
|
const text = data.message?.content || '';
|
|
330
|
-
|
|
384
|
+
(0, debug_1.debug)('Agent', `Ollama response received: ${text.slice(0, 50)}...`);
|
|
331
385
|
return {
|
|
332
386
|
text: String(text).trim() || 'No response from model',
|
|
333
387
|
usage: {
|
|
@@ -379,7 +433,7 @@ You are running locally on the user's machine. No data leaves their computer. Yo
|
|
|
379
433
|
try {
|
|
380
434
|
// Build full system prompt from brain files
|
|
381
435
|
systemPrompt = await this.brain.buildSystemPrompt(this.defaultSystemPrompt);
|
|
382
|
-
|
|
436
|
+
(0, debug_1.debug)('Agent', 'Using brain context for system prompt');
|
|
383
437
|
}
|
|
384
438
|
catch (e) {
|
|
385
439
|
console.warn('[Agent] Failed to load brain context, using default');
|
|
@@ -22,6 +22,7 @@ const allowlist_1 = require("../safety/allowlist");
|
|
|
22
22
|
const unifiedInbox_1 = require("../channels/unifiedInbox");
|
|
23
23
|
const lobster_1 = require("../lobster");
|
|
24
24
|
const agentManager_1 = require("./agentManager");
|
|
25
|
+
const debug_1 = require("../core/debug");
|
|
25
26
|
const tools_1 = require("../tools");
|
|
26
27
|
/**
|
|
27
28
|
* Agent Orchestrator - Main control loop
|
|
@@ -94,7 +95,7 @@ class AgentOrchestrator extends events_1.EventEmitter {
|
|
|
94
95
|
// ─────────────────────────────────────────
|
|
95
96
|
// PHASE 1: INGEST
|
|
96
97
|
// ─────────────────────────────────────────
|
|
97
|
-
|
|
98
|
+
(0, debug_1.debugPhase)(1, 'INGEST', { message, sessionId });
|
|
98
99
|
this.emit('phase:ingest', { message, sessionId });
|
|
99
100
|
// Get or create session
|
|
100
101
|
const session = await this.sessionStore.getOrCreate(sessionId, userId, channel);
|
|
@@ -107,7 +108,7 @@ class AgentOrchestrator extends events_1.EventEmitter {
|
|
|
107
108
|
// ─────────────────────────────────────────
|
|
108
109
|
// PHASE 2: CONTEXT
|
|
109
110
|
// ─────────────────────────────────────────
|
|
110
|
-
|
|
111
|
+
(0, debug_1.debugPhase)(2, 'CONTEXT', { sessionId });
|
|
111
112
|
this.emit('phase:context', { sessionId });
|
|
112
113
|
// Get brain context
|
|
113
114
|
const brainContext = await this.brain.buildSystemPrompt();
|
|
@@ -118,7 +119,7 @@ class AgentOrchestrator extends events_1.EventEmitter {
|
|
|
118
119
|
if (this.config.enableRAG) {
|
|
119
120
|
const searchResults = await this.vectorStore.search(message, this.config.ragTopK, this.config.ragThreshold);
|
|
120
121
|
memories = searchResults.map(r => r.entry.content);
|
|
121
|
-
|
|
122
|
+
(0, debug_1.debug)('Orchestrator', `Found ${memories.length} relevant memories`);
|
|
122
123
|
}
|
|
123
124
|
const context = {
|
|
124
125
|
sessionId,
|
|
@@ -134,7 +135,7 @@ class AgentOrchestrator extends events_1.EventEmitter {
|
|
|
134
135
|
// ─────────────────────────────────────────
|
|
135
136
|
// PHASE 3: INFER
|
|
136
137
|
// ─────────────────────────────────────────
|
|
137
|
-
|
|
138
|
+
(0, debug_1.debugPhase)(3, 'INFER', null);
|
|
138
139
|
this.emit('phase:infer', { context });
|
|
139
140
|
// Build enhanced prompt with memories
|
|
140
141
|
let enhancedMessage = message;
|
|
@@ -172,7 +173,7 @@ class AgentOrchestrator extends events_1.EventEmitter {
|
|
|
172
173
|
// ─────────────────────────────────────────
|
|
173
174
|
// PHASE 4: EXECUTE (handled in agent)
|
|
174
175
|
// ─────────────────────────────────────────
|
|
175
|
-
|
|
176
|
+
(0, debug_1.debugPhase)(4, 'EXECUTE', { toolsExecuted: result.toolsExecuted });
|
|
176
177
|
this.emit('phase:execute', { toolsExecuted: result.toolsExecuted });
|
|
177
178
|
// Check for workflow triggers
|
|
178
179
|
const workflowMatch = message.match(/^\/(run|workflow)\s+(\w+)/i);
|
|
@@ -190,7 +191,7 @@ class AgentOrchestrator extends events_1.EventEmitter {
|
|
|
190
191
|
// ─────────────────────────────────────────
|
|
191
192
|
// PHASE 5: RESPOND
|
|
192
193
|
// ─────────────────────────────────────────
|
|
193
|
-
|
|
194
|
+
(0, debug_1.debugPhase)(5, 'RESPOND', null);
|
|
194
195
|
this.emit('phase:respond', { response: result.response });
|
|
195
196
|
// Store assistant response in session
|
|
196
197
|
await this.sessionStore.appendMessage(sessionId, {
|
|
@@ -94,6 +94,46 @@ function parseToolCalls(text) {
|
|
|
94
94
|
}
|
|
95
95
|
}
|
|
96
96
|
}
|
|
97
|
+
// Pattern 4: Raw JSON tool calls (no code block) - common with Ollama
|
|
98
|
+
// Matches: {"tool": "name", "args": {...}}
|
|
99
|
+
if (calls.length === 0) {
|
|
100
|
+
const rawJsonRegex = /\{"(?:tool|function|name)"\s*:\s*"([^"]+)"[^}]*\}/g;
|
|
101
|
+
let rawMatch;
|
|
102
|
+
while ((rawMatch = rawJsonRegex.exec(text)) !== null) {
|
|
103
|
+
try {
|
|
104
|
+
// Try to parse the full JSON object
|
|
105
|
+
const startIdx = rawMatch.index;
|
|
106
|
+
let braceCount = 0;
|
|
107
|
+
let endIdx = startIdx;
|
|
108
|
+
for (let i = startIdx; i < text.length; i++) {
|
|
109
|
+
if (text[i] === '{')
|
|
110
|
+
braceCount++;
|
|
111
|
+
if (text[i] === '}')
|
|
112
|
+
braceCount--;
|
|
113
|
+
if (braceCount === 0) {
|
|
114
|
+
endIdx = i + 1;
|
|
115
|
+
break;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
const jsonStr = text.slice(startIdx, endIdx);
|
|
119
|
+
const parsed = JSON.parse(jsonStr);
|
|
120
|
+
if (parsed.tool || parsed.function || parsed.name) {
|
|
121
|
+
const toolName = parsed.tool || parsed.function || parsed.name;
|
|
122
|
+
// Only add if it's a registered tool
|
|
123
|
+
if (tools_1.toolRegistry.has(toolName)) {
|
|
124
|
+
calls.push({
|
|
125
|
+
name: toolName,
|
|
126
|
+
arguments: parsed.args || parsed.arguments || parsed.parameters || {},
|
|
127
|
+
});
|
|
128
|
+
console.log(`[ToolExecutor] Parsed raw JSON tool call: ${toolName}`);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
catch (e) {
|
|
133
|
+
// Failed to parse, try next match
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
97
137
|
if (calls.length > 0) {
|
|
98
138
|
console.log(`[ToolExecutor] Found ${calls.length} tool call(s): ${calls.map(c => c.name).join(', ')}`);
|
|
99
139
|
}
|
|
@@ -140,47 +180,48 @@ function getToolsSystemPrompt() {
|
|
|
140
180
|
return '';
|
|
141
181
|
}
|
|
142
182
|
const toolDescriptions = tools.map(t => {
|
|
143
|
-
const params = t.parameters.map(p => `
|
|
144
|
-
return `- **${t.name}**: ${t.description}`;
|
|
183
|
+
const params = t.parameters.map(p => ` - ${p.name} (${p.type}${p.required ? ', required' : ''}): ${p.description}`).join('\n');
|
|
184
|
+
return `- **${t.name}**: ${t.description}\n Parameters:\n${params}`;
|
|
145
185
|
}).join('\n');
|
|
186
|
+
const toolNames = tools.map(t => t.name).join(', ');
|
|
146
187
|
return `
|
|
147
|
-
## TOOLS -
|
|
188
|
+
## TOOLS - CRITICAL INSTRUCTIONS
|
|
148
189
|
|
|
149
|
-
You have
|
|
190
|
+
You have access to specific tools listed below. Use them to perform real actions.
|
|
150
191
|
|
|
151
|
-
|
|
152
|
-
|
|
192
|
+
**⚠️ CRITICAL RULE:**
|
|
193
|
+
You can ONLY use tools from this exact list: [${toolNames}]
|
|
194
|
+
DO NOT invent, guess, or fabricate tool names. If a tool doesn't exist in the list, say "I don't have a tool for that" instead of making one up.
|
|
153
195
|
|
|
196
|
+
**FORMAT (exact JSON required):**
|
|
154
197
|
\`\`\`json
|
|
155
|
-
{"tool": "
|
|
198
|
+
{"tool": "exact_tool_name_from_list", "args": {"param": "value"}}
|
|
156
199
|
\`\`\`
|
|
157
200
|
|
|
158
|
-
**WHEN TO USE TOOLS (do it automatically
|
|
159
|
-
-
|
|
160
|
-
-
|
|
161
|
-
-
|
|
162
|
-
-
|
|
163
|
-
-
|
|
164
|
-
-
|
|
165
|
-
-
|
|
166
|
-
-
|
|
167
|
-
-
|
|
168
|
-
-
|
|
169
|
-
- "my notes" / obsidian → use \`obsidian_*\` tools
|
|
201
|
+
**WHEN TO USE TOOLS (do it automatically):**
|
|
202
|
+
- Weather request → use \`weather\`
|
|
203
|
+
- Time/date → use \`datetime\`
|
|
204
|
+
- Math → use \`math\`
|
|
205
|
+
- System info → use \`system_info\`
|
|
206
|
+
- Set reminder → use \`reminder_set\`
|
|
207
|
+
- Spotify controls → use \`spotify_*\` (only if listed)
|
|
208
|
+
- Notes/Obsidian → use \`obsidian_*\` (only if listed)
|
|
209
|
+
- Web search → use \`search\` or \`web_search\`
|
|
210
|
+
- Shell commands → use \`shell\`
|
|
211
|
+
- Read/write files → use \`read_file\`, \`write_file\`
|
|
170
212
|
|
|
171
|
-
**AVAILABLE TOOLS:**
|
|
213
|
+
**AVAILABLE TOOLS (ONLY these exist):**
|
|
172
214
|
${toolDescriptions}
|
|
173
215
|
|
|
174
216
|
**RULES:**
|
|
175
|
-
1.
|
|
176
|
-
2.
|
|
177
|
-
3.
|
|
178
|
-
4.
|
|
179
|
-
5.
|
|
217
|
+
1. ✅ USE tools from the above list automatically
|
|
218
|
+
2. ❌ NEVER invent tool names that aren't listed
|
|
219
|
+
3. ❌ NEVER show code examples - execute tools directly
|
|
220
|
+
4. If tool doesn't exist, say "I don't have that capability"
|
|
221
|
+
5. After execution, explain results in natural language
|
|
180
222
|
|
|
181
223
|
**EXAMPLE:**
|
|
182
224
|
User: "What's the weather in Tokyo?"
|
|
183
|
-
Your response should include:
|
|
184
225
|
\`\`\`json
|
|
185
226
|
{"tool": "weather", "args": {"location": "Tokyo"}}
|
|
186
227
|
\`\`\`
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.TelegramChannel = void 0;
|
|
7
7
|
const grammy_1 = require("grammy");
|
|
8
|
+
const transformer_throttler_1 = require("@grammyjs/transformer-throttler");
|
|
8
9
|
const eventBus_1 = require("../core/eventBus");
|
|
9
10
|
class TelegramChannel {
|
|
10
11
|
name = 'telegram';
|
|
@@ -19,6 +20,8 @@ class TelegramChannel {
|
|
|
19
20
|
...config,
|
|
20
21
|
};
|
|
21
22
|
this.bot = new grammy_1.Bot(config.botToken);
|
|
23
|
+
// Apply rate limiting (Moltbot-style)
|
|
24
|
+
this.bot.api.config.use((0, transformer_throttler_1.apiThrottler)());
|
|
22
25
|
this.setupHandlers();
|
|
23
26
|
}
|
|
24
27
|
setupHandlers() {
|
|
@@ -33,6 +36,13 @@ class TelegramChannel {
|
|
|
33
36
|
const chatId = ctx.chat.id;
|
|
34
37
|
const isGroup = ctx.chat.type === 'group' || ctx.chat.type === 'supergroup';
|
|
35
38
|
const text = ctx.message.text;
|
|
39
|
+
// Send typing indicator immediately (Moltbot-style)
|
|
40
|
+
try {
|
|
41
|
+
await ctx.replyWithChatAction('typing');
|
|
42
|
+
}
|
|
43
|
+
catch (e) {
|
|
44
|
+
// Ignore typing errors
|
|
45
|
+
}
|
|
36
46
|
// Skip slash commands - they are handled by bot.command()
|
|
37
47
|
if (text.startsWith('/')) {
|
|
38
48
|
return;
|
package/dist/cli/chat.js
CHANGED
|
@@ -81,16 +81,13 @@ const FOX_THINKING = `${colors.orange}
|
|
|
81
81
|
${colors.orange} / - \\${colors.reset}
|
|
82
82
|
`;
|
|
83
83
|
const BANNER = `
|
|
84
|
-
${colors.
|
|
85
|
-
${colors.
|
|
86
|
-
${colors.
|
|
87
|
-
${colors.
|
|
88
|
-
${colors.rust}
|
|
89
|
-
|
|
90
|
-
${colors.
|
|
91
|
-
${colors.rust}╠═══════════════════════════════════════════════════════════╣${colors.reset}
|
|
92
|
-
${colors.rust}║${colors.reset} ${colors.gold}🦊 Free • Private • Local-First AI${colors.reset} ${colors.gray}v2026.1${colors.reset} ${colors.rust}║${colors.reset}
|
|
93
|
-
${colors.rust}╚═══════════════════════════════════════════════════════════╝${colors.reset}
|
|
84
|
+
${colors.orange}${colors.bold} _ ____ _______ ______ ___ _____${colors.reset}
|
|
85
|
+
${colors.orange}${colors.bold} / \\ | _ \\| ____\\ \\/ / __ ) / _ \\_ _|${colors.reset}
|
|
86
|
+
${colors.amber}${colors.bold} / _ \\ | |_) | _| \\ /| _ \\| | | || |${colors.reset}
|
|
87
|
+
${colors.amber}${colors.bold} / ___ \\| __/| |___ / \\| |_) | |_| || |${colors.reset}
|
|
88
|
+
${colors.rust}${colors.bold} /_/ \\_\\_| |_____/_/\\_\\____/ \\___/ |_|${colors.reset}
|
|
89
|
+
|
|
90
|
+
${colors.gold}🦊 Free • Private • Local-First AI${colors.reset} ${colors.gray}v2026.1${colors.reset}
|
|
94
91
|
`;
|
|
95
92
|
const SPINNER_FRAMES = ['🦊', '🔥', '✨', '💫', '⭐', '🌟', '✨', '🔥'];
|
|
96
93
|
/**
|
|
@@ -270,6 +267,27 @@ ${colors.orange}${colors.bold}Input Prefixes${colors.reset} ${co
|
|
|
270
267
|
case 'tools':
|
|
271
268
|
this.emit('command:tools');
|
|
272
269
|
break;
|
|
270
|
+
case 'model':
|
|
271
|
+
if (args[0]) {
|
|
272
|
+
this.emit('command:model', args[0]);
|
|
273
|
+
console.log(`${colors.green}✓ Model changed to: ${colors.gold}${args[0]}${colors.reset}`);
|
|
274
|
+
}
|
|
275
|
+
else {
|
|
276
|
+
this.emit('command:model:show');
|
|
277
|
+
}
|
|
278
|
+
break;
|
|
279
|
+
case 'provider':
|
|
280
|
+
if (args[0]) {
|
|
281
|
+
this.emit('command:provider', args[0]);
|
|
282
|
+
console.log(`${colors.green}✓ Provider changed to: ${colors.gold}${args[0]}${colors.reset}`);
|
|
283
|
+
}
|
|
284
|
+
else {
|
|
285
|
+
console.log(`${colors.amber}Usage: /provider <ollama|google|anthropic|openai|kimi>${colors.reset}`);
|
|
286
|
+
}
|
|
287
|
+
break;
|
|
288
|
+
case 'config':
|
|
289
|
+
this.emit('command:config');
|
|
290
|
+
break;
|
|
273
291
|
case 'exit':
|
|
274
292
|
case 'quit':
|
|
275
293
|
case 'bye':
|
|
@@ -285,15 +303,19 @@ ${colors.orange}${colors.bold}Input Prefixes${colors.reset} ${co
|
|
|
285
303
|
${colors.orange}${colors.bold}🦊 ApexBot Commands${colors.reset}
|
|
286
304
|
${colors.gray}${'─'.repeat(40)}${colors.reset}
|
|
287
305
|
|
|
288
|
-
${colors.orange}/help${colors.reset}
|
|
289
|
-
${colors.orange}/clear${colors.reset}
|
|
290
|
-
${colors.orange}/reset${colors.reset}
|
|
291
|
-
${colors.orange}/history${colors.reset}
|
|
292
|
-
${colors.orange}/status${colors.reset}
|
|
293
|
-
${colors.orange}/skills${colors.reset}
|
|
294
|
-
${colors.orange}/tools${colors.reset}
|
|
295
|
-
${colors.orange}/
|
|
296
|
-
${colors.orange}/
|
|
306
|
+
${colors.orange}/help${colors.reset} Show this help
|
|
307
|
+
${colors.orange}/clear${colors.reset} Clear screen
|
|
308
|
+
${colors.orange}/reset${colors.reset} Reset conversation
|
|
309
|
+
${colors.orange}/history${colors.reset} Show recent messages
|
|
310
|
+
${colors.orange}/status${colors.reset} Show bot status
|
|
311
|
+
${colors.orange}/skills${colors.reset} List enabled skills
|
|
312
|
+
${colors.orange}/tools${colors.reset} List available tools
|
|
313
|
+
${colors.orange}/model${colors.reset} Show/change model
|
|
314
|
+
${colors.orange}/model${colors.reset} ${colors.gray}<name>${colors.reset} Change to specified model
|
|
315
|
+
${colors.orange}/provider${colors.reset} Change AI provider
|
|
316
|
+
${colors.orange}/config${colors.reset} Show configuration
|
|
317
|
+
${colors.orange}/fox${colors.reset} Show fox mascot 🦊
|
|
318
|
+
${colors.orange}/exit${colors.reset} Exit ApexBot
|
|
297
319
|
|
|
298
320
|
${colors.gray}Shortcuts: Ctrl+C (exit), Ctrl+L (clear), ↑↓ (history)${colors.reset}
|
|
299
321
|
`);
|
package/dist/cli/index.js
CHANGED
|
@@ -1105,6 +1105,43 @@ program
|
|
|
1105
1105
|
const status = await orchestrator.getStatus();
|
|
1106
1106
|
console.log(` Files: ${status.brain?.files?.join(', ') || 'none'}`);
|
|
1107
1107
|
});
|
|
1108
|
+
// Model/provider/config commands
|
|
1109
|
+
chat.on('command:model', (model) => {
|
|
1110
|
+
const cfg = loadConfig();
|
|
1111
|
+
cfg.agent = cfg.agent || {};
|
|
1112
|
+
cfg.agent.model = model;
|
|
1113
|
+
saveConfig(cfg);
|
|
1114
|
+
console.log(chalk.green(` Model configured: ${model}`));
|
|
1115
|
+
});
|
|
1116
|
+
chat.on('command:model:show', () => {
|
|
1117
|
+
const cfg = loadConfig();
|
|
1118
|
+
const currentModel = cfg.agent?.model || 'llama3.2:latest';
|
|
1119
|
+
const provider = cfg.agent?.provider || 'ollama';
|
|
1120
|
+
console.log(`\n ${chalk.yellow('Current Model:')} ${chalk.cyan(currentModel)}`);
|
|
1121
|
+
console.log(` ${chalk.yellow('Provider:')} ${chalk.cyan(provider)}`);
|
|
1122
|
+
console.log(`\n ${chalk.gray('Usage: /model <name> (e.g. /model gemma2:9b)')}`);
|
|
1123
|
+
});
|
|
1124
|
+
chat.on('command:provider', (provider) => {
|
|
1125
|
+
const validProviders = ['ollama', 'google', 'anthropic', 'openai', 'kimi'];
|
|
1126
|
+
if (!validProviders.includes(provider.toLowerCase())) {
|
|
1127
|
+
console.log(chalk.red(` Invalid provider. Use: ${validProviders.join(', ')}`));
|
|
1128
|
+
return;
|
|
1129
|
+
}
|
|
1130
|
+
const cfg = loadConfig();
|
|
1131
|
+
cfg.agent = cfg.agent || {};
|
|
1132
|
+
cfg.agent.provider = provider.toLowerCase();
|
|
1133
|
+
saveConfig(cfg);
|
|
1134
|
+
console.log(chalk.green(` Provider configured: ${provider}`));
|
|
1135
|
+
});
|
|
1136
|
+
chat.on('command:config', () => {
|
|
1137
|
+
const cfg = loadConfig();
|
|
1138
|
+
console.log(`\n ${chalk.yellow('Current Configuration:')}`);
|
|
1139
|
+
console.log(` ${chalk.gray('─'.repeat(30))}`);
|
|
1140
|
+
console.log(` Provider: ${chalk.cyan(cfg.agent?.provider || 'ollama')}`);
|
|
1141
|
+
console.log(` Model: ${chalk.cyan(cfg.agent?.model || 'llama3.2:latest')}`);
|
|
1142
|
+
console.log(` API URL: ${chalk.cyan(cfg.agent?.apiUrl || 'http://localhost:11434')}`);
|
|
1143
|
+
console.log(`\n ${chalk.gray(`Config file: ${CONFIG_FILE}`)}`);
|
|
1144
|
+
});
|
|
1108
1145
|
// Start chat
|
|
1109
1146
|
await chat.start();
|
|
1110
1147
|
});
|