apexbot 1.0.9 → 1.1.1
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/agent/agentManager.js +109 -18
- package/dist/agent/orchestrator.js +7 -6
- package/dist/agent/toolExecutor.js +90 -31
- package/dist/channels/channelManager.js +22 -0
- package/dist/cli/chat.js +96 -19
- package/dist/cli/index.js +2531 -113
- package/dist/cli/onboard.js +742 -2
- package/dist/core/debug.js +213 -0
- package/dist/cron/index.js +464 -0
- package/dist/gateway/dashboard.js +757 -786
- package/dist/gateway/index.js +375 -5
- package/dist/hooks/index.js +636 -0
- package/dist/index.js +1 -1
- package/dist/mcp/index.js +629 -0
- package/dist/pairing/index.js +274 -0
- package/dist/sessions/sessionManager.js +107 -1
- package/dist/skills/calendar.js +61 -1
- package/dist/skills/index.js +65 -0
- package/dist/skills/reminder.js +38 -3
- package/package.json +3 -3
|
@@ -6,15 +6,23 @@
|
|
|
6
6
|
*
|
|
7
7
|
* Enhanced with Moltbot-style brain system for personality and memory.
|
|
8
8
|
*/
|
|
9
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
10
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
11
|
+
};
|
|
9
12
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
13
|
exports.AgentManager = void 0;
|
|
11
14
|
const generative_ai_1 = require("@google/generative-ai");
|
|
15
|
+
const sdk_1 = __importDefault(require("@anthropic-ai/sdk"));
|
|
16
|
+
const openai_1 = __importDefault(require("openai"));
|
|
12
17
|
const tools_1 = require("../tools");
|
|
13
18
|
const brain_1 = require("../brain");
|
|
14
19
|
const toolExecutor_1 = require("./toolExecutor");
|
|
20
|
+
const debug_1 = require("../core/debug");
|
|
15
21
|
class AgentManager {
|
|
16
22
|
config = null;
|
|
17
23
|
googleClient = null;
|
|
24
|
+
anthropicClient = null;
|
|
25
|
+
openaiClient = null;
|
|
18
26
|
brain = null;
|
|
19
27
|
// Moltbot-style model fallback chain
|
|
20
28
|
fallbackModels = [
|
|
@@ -65,7 +73,7 @@ You are running locally on the user's machine. No data leaves their computer. Yo
|
|
|
65
73
|
*/
|
|
66
74
|
setBrain(brain) {
|
|
67
75
|
this.brain = brain;
|
|
68
|
-
|
|
76
|
+
(0, debug_1.debug)('Agent', 'Brain connected');
|
|
69
77
|
}
|
|
70
78
|
configure(config) {
|
|
71
79
|
this.config = {
|
|
@@ -87,11 +95,31 @@ You are running locally on the user's machine. No data leaves their computer. Yo
|
|
|
87
95
|
console.warn('[Agent] Google provider selected but no API key provided — Gemini will be unavailable until configured.');
|
|
88
96
|
}
|
|
89
97
|
}
|
|
98
|
+
// Initialize Anthropic Claude client
|
|
99
|
+
if (config.provider === 'anthropic') {
|
|
100
|
+
const claudeKey = config.apiKey || process.env.ANTHROPIC_API_KEY;
|
|
101
|
+
if (claudeKey) {
|
|
102
|
+
this.anthropicClient = new sdk_1.default({ apiKey: claudeKey });
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
console.warn('[Agent] Anthropic provider selected but no API key provided — Claude will be unavailable.');
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
// Initialize OpenAI client
|
|
109
|
+
if (config.provider === 'openai') {
|
|
110
|
+
const openaiKey = config.apiKey || process.env.OPENAI_API_KEY;
|
|
111
|
+
if (openaiKey) {
|
|
112
|
+
this.openaiClient = new openai_1.default({ apiKey: openaiKey });
|
|
113
|
+
}
|
|
114
|
+
else {
|
|
115
|
+
console.warn('[Agent] OpenAI provider selected but no API key provided — GPT will be unavailable.');
|
|
116
|
+
}
|
|
117
|
+
}
|
|
90
118
|
// Note: Kimi provider uses a generic HTTP endpoint (apiUrl) and apiKey
|
|
91
119
|
if (config.provider === 'kimi') {
|
|
92
120
|
// nothing to initialize here; processWithKimi will use fetch + config.apiUrl
|
|
93
121
|
}
|
|
94
|
-
|
|
122
|
+
(0, debug_1.debug)('Agent', `Configured with ${config.provider} (${config.model || 'default model'})`);
|
|
95
123
|
}
|
|
96
124
|
async process(session, message) {
|
|
97
125
|
if (!this.config) {
|
|
@@ -99,7 +127,7 @@ You are running locally on the user's machine. No data leaves their computer. Yo
|
|
|
99
127
|
return { text: 'Agent not configured. Please set up an AI provider.' };
|
|
100
128
|
}
|
|
101
129
|
const userText = message.text || '';
|
|
102
|
-
|
|
130
|
+
(0, debug_1.debug)('Agent', `Processing message: ${userText.slice(0, 50)}...`);
|
|
103
131
|
// Handle slash commands
|
|
104
132
|
if (userText.startsWith('/')) {
|
|
105
133
|
return this.handleCommand(session, userText);
|
|
@@ -123,9 +151,9 @@ You are running locally on the user's machine. No data leaves their computer. Yo
|
|
|
123
151
|
if (fallback.provider === this.config.provider)
|
|
124
152
|
continue;
|
|
125
153
|
try {
|
|
126
|
-
|
|
154
|
+
(0, debug_1.debug)('Agent', `Trying fallback: ${fallback.provider}/${fallback.model}`);
|
|
127
155
|
response = await this.processWithProvider(fallback.provider, fallback.model, history);
|
|
128
|
-
|
|
156
|
+
(0, debug_1.debug)('Agent', `Fallback ${fallback.provider} succeeded!`);
|
|
129
157
|
lastError = null;
|
|
130
158
|
break;
|
|
131
159
|
}
|
|
@@ -145,7 +173,7 @@ You are running locally on the user's machine. No data leaves their computer. Yo
|
|
|
145
173
|
if (this.config.enableTools) {
|
|
146
174
|
const toolCalls = (0, toolExecutor_1.parseToolCalls)(response.text);
|
|
147
175
|
if (toolCalls.length > 0) {
|
|
148
|
-
|
|
176
|
+
(0, debug_1.debug)('Agent', `Found ${toolCalls.length} tool calls:`, toolCalls.map(t => t.name));
|
|
149
177
|
// Build tool context
|
|
150
178
|
const context = (0, toolExecutor_1.buildToolContext)(session.id, message.userId || message.from || 'unknown', message.channel, { workspaceDir: process.cwd() });
|
|
151
179
|
// Execute tools
|
|
@@ -153,9 +181,9 @@ You are running locally on the user's machine. No data leaves their computer. Yo
|
|
|
153
181
|
response.toolCalls = toolResults;
|
|
154
182
|
// Log results
|
|
155
183
|
for (const { call, result } of toolResults) {
|
|
156
|
-
|
|
184
|
+
(0, debug_1.debug)('Agent', `Tool ${call.name}: ${result.success ? 'SUCCESS' : 'FAILED'}`);
|
|
157
185
|
if (!result.success)
|
|
158
|
-
|
|
186
|
+
(0, debug_1.debug)('Agent', `Error: ${result.error}`);
|
|
159
187
|
}
|
|
160
188
|
// Format results and continue conversation
|
|
161
189
|
const resultsText = (0, toolExecutor_1.formatToolResults)(toolResults);
|
|
@@ -193,7 +221,7 @@ You are running locally on the user's machine. No data leaves their computer. Yo
|
|
|
193
221
|
}
|
|
194
222
|
}
|
|
195
223
|
}
|
|
196
|
-
|
|
224
|
+
(0, debug_1.debug)('Agent', `Generated response: ${response.text.slice(0, 50)}...`);
|
|
197
225
|
// Save to session (initialize messages array if needed)
|
|
198
226
|
if (!session.messages)
|
|
199
227
|
session.messages = [];
|
|
@@ -351,7 +379,7 @@ You are running locally on the user's machine. No data leaves their computer. Yo
|
|
|
351
379
|
const apiUrl = cfg.apiUrl || 'http://localhost:11434';
|
|
352
380
|
const model = cfg.model || 'llama3.2';
|
|
353
381
|
const temperature = cfg.temperature ?? 0.7;
|
|
354
|
-
|
|
382
|
+
(0, debug_1.debug)('Agent', `Calling Ollama at ${apiUrl} with model ${model}...`);
|
|
355
383
|
// Build messages array for Ollama chat API
|
|
356
384
|
const messages = history.map(m => ({
|
|
357
385
|
role: m.role,
|
|
@@ -380,7 +408,7 @@ You are running locally on the user's machine. No data leaves their computer. Yo
|
|
|
380
408
|
}
|
|
381
409
|
const data = await res.json();
|
|
382
410
|
const text = data.message?.content || '';
|
|
383
|
-
|
|
411
|
+
(0, debug_1.debug)('Agent', `Ollama response received: ${text.slice(0, 50)}...`);
|
|
384
412
|
return {
|
|
385
413
|
text: String(text).trim() || 'No response from model',
|
|
386
414
|
usage: {
|
|
@@ -432,7 +460,7 @@ You are running locally on the user's machine. No data leaves their computer. Yo
|
|
|
432
460
|
try {
|
|
433
461
|
// Build full system prompt from brain files
|
|
434
462
|
systemPrompt = await this.brain.buildSystemPrompt(this.defaultSystemPrompt);
|
|
435
|
-
|
|
463
|
+
(0, debug_1.debug)('Agent', 'Using brain context for system prompt');
|
|
436
464
|
}
|
|
437
465
|
catch (e) {
|
|
438
466
|
console.warn('[Agent] Failed to load brain context, using default');
|
|
@@ -494,14 +522,77 @@ You are running locally on the user's machine. No data leaves their computer. Yo
|
|
|
494
522
|
};
|
|
495
523
|
}
|
|
496
524
|
async processWithClaude(history) {
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
525
|
+
if (!this.anthropicClient) {
|
|
526
|
+
return { text: 'Claude API key not configured. Set ANTHROPIC_API_KEY environment variable.' };
|
|
527
|
+
}
|
|
528
|
+
// Separate system prompt from messages
|
|
529
|
+
const systemPrompt = this.config?.systemPrompt || this.defaultSystemPrompt;
|
|
530
|
+
// Convert messages to Claude format
|
|
531
|
+
const messages = history
|
|
532
|
+
.filter(m => m.role !== 'system')
|
|
533
|
+
.map(m => ({
|
|
534
|
+
role: m.role === 'user' ? 'user' : 'assistant',
|
|
535
|
+
content: m.content,
|
|
536
|
+
}));
|
|
537
|
+
try {
|
|
538
|
+
const response = await this.anthropicClient.messages.create({
|
|
539
|
+
model: this.config?.model || 'claude-sonnet-4-20250514',
|
|
540
|
+
max_tokens: this.config?.maxTokens || 4096,
|
|
541
|
+
system: systemPrompt,
|
|
542
|
+
messages: messages,
|
|
543
|
+
});
|
|
544
|
+
// Extract text from response
|
|
545
|
+
const textContent = response.content.find(c => c.type === 'text');
|
|
546
|
+
const text = textContent && 'text' in textContent ? textContent.text : '';
|
|
547
|
+
return {
|
|
548
|
+
text,
|
|
549
|
+
usage: {
|
|
550
|
+
inputTokens: response.usage.input_tokens,
|
|
551
|
+
outputTokens: response.usage.output_tokens,
|
|
552
|
+
},
|
|
553
|
+
};
|
|
554
|
+
}
|
|
555
|
+
catch (error) {
|
|
556
|
+
console.error('[Agent] Claude API error:', error.message);
|
|
557
|
+
return { text: `Claude error: ${error.message}` };
|
|
558
|
+
}
|
|
500
559
|
}
|
|
501
560
|
async processWithOpenAI(history) {
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
561
|
+
if (!this.openaiClient) {
|
|
562
|
+
return { text: 'OpenAI API key not configured. Set OPENAI_API_KEY environment variable.' };
|
|
563
|
+
}
|
|
564
|
+
// Convert messages to OpenAI format
|
|
565
|
+
const systemPrompt = this.config?.systemPrompt || this.defaultSystemPrompt;
|
|
566
|
+
const messages = [
|
|
567
|
+
{ role: 'system', content: systemPrompt },
|
|
568
|
+
...history
|
|
569
|
+
.filter(m => m.role !== 'system')
|
|
570
|
+
.map(m => ({
|
|
571
|
+
role: m.role === 'user' ? 'user' : 'assistant',
|
|
572
|
+
content: m.content,
|
|
573
|
+
})),
|
|
574
|
+
];
|
|
575
|
+
try {
|
|
576
|
+
const response = await this.openaiClient.chat.completions.create({
|
|
577
|
+
model: this.config?.model || 'gpt-4o',
|
|
578
|
+
max_tokens: this.config?.maxTokens || 4096,
|
|
579
|
+
temperature: this.config?.temperature || 0.7,
|
|
580
|
+
messages: messages,
|
|
581
|
+
});
|
|
582
|
+
const choice = response.choices[0];
|
|
583
|
+
const text = choice?.message?.content || '';
|
|
584
|
+
return {
|
|
585
|
+
text,
|
|
586
|
+
usage: {
|
|
587
|
+
inputTokens: response.usage?.prompt_tokens || 0,
|
|
588
|
+
outputTokens: response.usage?.completion_tokens || 0,
|
|
589
|
+
},
|
|
590
|
+
};
|
|
591
|
+
}
|
|
592
|
+
catch (error) {
|
|
593
|
+
console.error('[Agent] OpenAI API error:', error.message);
|
|
594
|
+
return { text: `OpenAI error: ${error.message}` };
|
|
595
|
+
}
|
|
505
596
|
}
|
|
506
597
|
handleCommand(session, command) {
|
|
507
598
|
const [cmd, ...args] = command.slice(1).split(' ');
|
|
@@ -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, {
|
|
@@ -159,18 +159,76 @@ async function executeToolCalls(calls, context) {
|
|
|
159
159
|
}
|
|
160
160
|
/**
|
|
161
161
|
* Format tool results for inclusion in AI response
|
|
162
|
+
* Provides clean, readable formatting for different data types
|
|
162
163
|
*/
|
|
163
164
|
function formatToolResults(results) {
|
|
164
165
|
if (results.length === 0)
|
|
165
166
|
return '';
|
|
166
167
|
return results.map(({ call, result }) => {
|
|
167
|
-
const status = result.success ? '
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
168
|
+
const status = result.success ? '✅' : '❌';
|
|
169
|
+
if (!result.success) {
|
|
170
|
+
return `${status} [${call.name}] Error: ${result.error}`;
|
|
171
|
+
}
|
|
172
|
+
// Format data based on type
|
|
173
|
+
const data = formatToolData(result.data, call.name);
|
|
174
|
+
return `${status} [${call.name}]\n${data}`;
|
|
172
175
|
}).join('\n\n');
|
|
173
176
|
}
|
|
177
|
+
/**
|
|
178
|
+
* Format tool data in a human-readable way
|
|
179
|
+
*/
|
|
180
|
+
function formatToolData(data, toolName) {
|
|
181
|
+
if (data === null || data === undefined) {
|
|
182
|
+
return 'No data returned';
|
|
183
|
+
}
|
|
184
|
+
// Handle specific tool outputs
|
|
185
|
+
if (toolName === 'get_weather' && data.current) {
|
|
186
|
+
return `🌡️ ${data.location}: ${data.current.temp}°C, ${data.current.condition}\n` +
|
|
187
|
+
`💧 Humidity: ${data.current.humidity}%, 💨 Wind: ${data.current.wind} km/h`;
|
|
188
|
+
}
|
|
189
|
+
if (toolName === 'search_web' && Array.isArray(data.results)) {
|
|
190
|
+
return data.results.slice(0, 5).map((r, i) => `${i + 1}. **${r.title}**\n ${r.snippet || r.description || ''}\n ${r.url || ''}`).join('\n');
|
|
191
|
+
}
|
|
192
|
+
if (toolName === 'get_reminders' && Array.isArray(data)) {
|
|
193
|
+
if (data.length === 0)
|
|
194
|
+
return 'No active reminders';
|
|
195
|
+
return data.map((r) => `⏰ ${r.message} - ${new Date(r.triggerAt).toLocaleString()}`).join('\n');
|
|
196
|
+
}
|
|
197
|
+
if (toolName === 'get_calendar_events' && Array.isArray(data)) {
|
|
198
|
+
if (data.length === 0)
|
|
199
|
+
return 'No upcoming events';
|
|
200
|
+
return data.map((e) => `📅 ${e.title} - ${new Date(e.start).toLocaleString()}`).join('\n');
|
|
201
|
+
}
|
|
202
|
+
// Handle arrays
|
|
203
|
+
if (Array.isArray(data)) {
|
|
204
|
+
if (data.length === 0)
|
|
205
|
+
return 'Empty result';
|
|
206
|
+
if (data.length <= 5) {
|
|
207
|
+
return data.map((item) => typeof item === 'object' ? JSON.stringify(item, null, 2) : String(item)).join('\n');
|
|
208
|
+
}
|
|
209
|
+
return `${data.length} items:\n` + data.slice(0, 5).map((item) => typeof item === 'object' ? JSON.stringify(item) : String(item)).join('\n') + `\n... and ${data.length - 5} more`;
|
|
210
|
+
}
|
|
211
|
+
// Handle objects
|
|
212
|
+
if (typeof data === 'object') {
|
|
213
|
+
// Try to extract meaningful summary
|
|
214
|
+
if (data.message)
|
|
215
|
+
return String(data.message);
|
|
216
|
+
if (data.result)
|
|
217
|
+
return String(data.result);
|
|
218
|
+
if (data.output)
|
|
219
|
+
return String(data.output);
|
|
220
|
+
if (data.text)
|
|
221
|
+
return String(data.text);
|
|
222
|
+
// Compact JSON for small objects
|
|
223
|
+
const json = JSON.stringify(data, null, 2);
|
|
224
|
+
if (json.length < 500)
|
|
225
|
+
return json;
|
|
226
|
+
// Summary for large objects
|
|
227
|
+
const keys = Object.keys(data);
|
|
228
|
+
return `Object with ${keys.length} keys: ${keys.slice(0, 5).join(', ')}${keys.length > 5 ? '...' : ''}`;
|
|
229
|
+
}
|
|
230
|
+
return String(data);
|
|
231
|
+
}
|
|
174
232
|
/**
|
|
175
233
|
* Get the system prompt addition for tool usage
|
|
176
234
|
*/
|
|
@@ -180,47 +238,48 @@ function getToolsSystemPrompt() {
|
|
|
180
238
|
return '';
|
|
181
239
|
}
|
|
182
240
|
const toolDescriptions = tools.map(t => {
|
|
183
|
-
const params = t.parameters.map(p => `
|
|
184
|
-
return `- **${t.name}**: ${t.description}`;
|
|
241
|
+
const params = t.parameters.map(p => ` - ${p.name} (${p.type}${p.required ? ', required' : ''}): ${p.description}`).join('\n');
|
|
242
|
+
return `- **${t.name}**: ${t.description}\n Parameters:\n${params}`;
|
|
185
243
|
}).join('\n');
|
|
244
|
+
const toolNames = tools.map(t => t.name).join(', ');
|
|
186
245
|
return `
|
|
187
|
-
## TOOLS -
|
|
246
|
+
## TOOLS - CRITICAL INSTRUCTIONS
|
|
188
247
|
|
|
189
|
-
You have
|
|
248
|
+
You have access to specific tools listed below. Use them to perform real actions.
|
|
190
249
|
|
|
191
|
-
|
|
192
|
-
|
|
250
|
+
**⚠️ CRITICAL RULE:**
|
|
251
|
+
You can ONLY use tools from this exact list: [${toolNames}]
|
|
252
|
+
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.
|
|
193
253
|
|
|
254
|
+
**FORMAT (exact JSON required):**
|
|
194
255
|
\`\`\`json
|
|
195
|
-
{"tool": "
|
|
256
|
+
{"tool": "exact_tool_name_from_list", "args": {"param": "value"}}
|
|
196
257
|
\`\`\`
|
|
197
258
|
|
|
198
|
-
**WHEN TO USE TOOLS (do it automatically
|
|
199
|
-
-
|
|
200
|
-
-
|
|
201
|
-
-
|
|
202
|
-
-
|
|
203
|
-
-
|
|
204
|
-
-
|
|
205
|
-
-
|
|
206
|
-
-
|
|
207
|
-
-
|
|
208
|
-
-
|
|
209
|
-
- "my notes" / obsidian → use \`obsidian_*\` tools
|
|
259
|
+
**WHEN TO USE TOOLS (do it automatically):**
|
|
260
|
+
- Weather request → use \`weather\`
|
|
261
|
+
- Time/date → use \`datetime\`
|
|
262
|
+
- Math → use \`math\`
|
|
263
|
+
- System info → use \`system_info\`
|
|
264
|
+
- Set reminder → use \`reminder_set\`
|
|
265
|
+
- Spotify controls → use \`spotify_*\` (only if listed)
|
|
266
|
+
- Notes/Obsidian → use \`obsidian_*\` (only if listed)
|
|
267
|
+
- Web search → use \`search\` or \`web_search\`
|
|
268
|
+
- Shell commands → use \`shell\`
|
|
269
|
+
- Read/write files → use \`read_file\`, \`write_file\`
|
|
210
270
|
|
|
211
|
-
**AVAILABLE TOOLS:**
|
|
271
|
+
**AVAILABLE TOOLS (ONLY these exist):**
|
|
212
272
|
${toolDescriptions}
|
|
213
273
|
|
|
214
274
|
**RULES:**
|
|
215
|
-
1.
|
|
216
|
-
2.
|
|
217
|
-
3.
|
|
218
|
-
4.
|
|
219
|
-
5.
|
|
275
|
+
1. ✅ USE tools from the above list automatically
|
|
276
|
+
2. ❌ NEVER invent tool names that aren't listed
|
|
277
|
+
3. ❌ NEVER show code examples - execute tools directly
|
|
278
|
+
4. If tool doesn't exist, say "I don't have that capability"
|
|
279
|
+
5. After execution, explain results in natural language
|
|
220
280
|
|
|
221
281
|
**EXAMPLE:**
|
|
222
282
|
User: "What's the weather in Tokyo?"
|
|
223
|
-
Your response should include:
|
|
224
283
|
\`\`\`json
|
|
225
284
|
{"tool": "weather", "args": {"location": "Tokyo"}}
|
|
226
285
|
\`\`\`
|
|
@@ -4,11 +4,33 @@
|
|
|
4
4
|
*/
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.ChannelManager = void 0;
|
|
7
|
+
const eventBus_1 = require("../core/eventBus");
|
|
7
8
|
class ChannelManager {
|
|
8
9
|
channels = new Map();
|
|
9
10
|
gateway;
|
|
11
|
+
activeUsers = new Map(); // channelName -> lastActiveUserId
|
|
10
12
|
constructor(gateway) {
|
|
11
13
|
this.gateway = gateway;
|
|
14
|
+
this.setupBroadcastHandler();
|
|
15
|
+
}
|
|
16
|
+
setupBroadcastHandler() {
|
|
17
|
+
// Handle broadcast to all channels (for calendar notifications etc)
|
|
18
|
+
(0, eventBus_1.on)('channel:broadcast', async (msg) => {
|
|
19
|
+
console.log(`[Channels] Broadcasting to all: ${msg.text.slice(0, 50)}...`);
|
|
20
|
+
// Send to all channels with known active users
|
|
21
|
+
for (const [channelName, userId] of this.activeUsers) {
|
|
22
|
+
try {
|
|
23
|
+
await this.send(channelName, userId, msg.text);
|
|
24
|
+
}
|
|
25
|
+
catch (e) {
|
|
26
|
+
console.error(`[Channels] Broadcast to ${channelName}:${userId} failed:`, e);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
// Track active users for broadcasts
|
|
32
|
+
trackActiveUser(channelName, userId) {
|
|
33
|
+
this.activeUsers.set(channelName, userId);
|
|
12
34
|
}
|
|
13
35
|
register(channel) {
|
|
14
36
|
this.channels.set(channel.name, channel);
|
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
|
/**
|
|
@@ -104,9 +101,45 @@ class CLIChat extends events_1.EventEmitter {
|
|
|
104
101
|
currentSpinnerFrame = 0;
|
|
105
102
|
isThinking = false;
|
|
106
103
|
username;
|
|
104
|
+
commandHistory = [];
|
|
105
|
+
historyFile;
|
|
107
106
|
constructor(options = {}) {
|
|
108
107
|
super();
|
|
109
108
|
this.username = options.username || process.env.USER || 'You';
|
|
109
|
+
this.historyFile = path.join(process.env.HOME || process.env.USERPROFILE || '', '.apexbot', 'cli_history.json');
|
|
110
|
+
this.loadCommandHistory();
|
|
111
|
+
}
|
|
112
|
+
loadCommandHistory() {
|
|
113
|
+
try {
|
|
114
|
+
if (fs.existsSync(this.historyFile)) {
|
|
115
|
+
const data = JSON.parse(fs.readFileSync(this.historyFile, 'utf-8'));
|
|
116
|
+
this.commandHistory = data.commands || [];
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
// Ignore errors, start with empty history
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
saveCommandHistory() {
|
|
124
|
+
try {
|
|
125
|
+
const dir = path.dirname(this.historyFile);
|
|
126
|
+
if (!fs.existsSync(dir)) {
|
|
127
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
128
|
+
}
|
|
129
|
+
// Keep last 500 commands
|
|
130
|
+
const commands = this.commandHistory.slice(-500);
|
|
131
|
+
fs.writeFileSync(this.historyFile, JSON.stringify({ commands }, null, 2));
|
|
132
|
+
}
|
|
133
|
+
catch {
|
|
134
|
+
// Ignore errors
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
saveCommandToHistory(input) {
|
|
138
|
+
// Don't add duplicates of the last command
|
|
139
|
+
if (this.commandHistory[this.commandHistory.length - 1] !== input) {
|
|
140
|
+
this.commandHistory.push(input);
|
|
141
|
+
this.saveCommandHistory();
|
|
142
|
+
}
|
|
110
143
|
}
|
|
111
144
|
setMessageHandler(handler) {
|
|
112
145
|
this.messageHandler = handler;
|
|
@@ -128,6 +161,8 @@ class CLIChat extends events_1.EventEmitter {
|
|
|
128
161
|
this.rl?.prompt();
|
|
129
162
|
return;
|
|
130
163
|
}
|
|
164
|
+
// Save to persistent history
|
|
165
|
+
this.saveCommandToHistory(input);
|
|
131
166
|
// Handle prefixes like Claude Code
|
|
132
167
|
if (input.startsWith('/')) {
|
|
133
168
|
await this.handleCommand(input);
|
|
@@ -253,6 +288,9 @@ ${colors.orange}${colors.bold}Input Prefixes${colors.reset} ${co
|
|
|
253
288
|
case 'history':
|
|
254
289
|
this.showHistory(parseInt(args[0]) || 10);
|
|
255
290
|
break;
|
|
291
|
+
case 'commands':
|
|
292
|
+
this.showCommandHistory(parseInt(args[0]) || 20);
|
|
293
|
+
break;
|
|
256
294
|
case 'reset':
|
|
257
295
|
this.history = [];
|
|
258
296
|
console.log(`${colors.green}✓ Conversation reset${colors.reset}`);
|
|
@@ -270,6 +308,27 @@ ${colors.orange}${colors.bold}Input Prefixes${colors.reset} ${co
|
|
|
270
308
|
case 'tools':
|
|
271
309
|
this.emit('command:tools');
|
|
272
310
|
break;
|
|
311
|
+
case 'model':
|
|
312
|
+
if (args[0]) {
|
|
313
|
+
this.emit('command:model', args[0]);
|
|
314
|
+
console.log(`${colors.green}✓ Model changed to: ${colors.gold}${args[0]}${colors.reset}`);
|
|
315
|
+
}
|
|
316
|
+
else {
|
|
317
|
+
this.emit('command:model:show');
|
|
318
|
+
}
|
|
319
|
+
break;
|
|
320
|
+
case 'provider':
|
|
321
|
+
if (args[0]) {
|
|
322
|
+
this.emit('command:provider', args[0]);
|
|
323
|
+
console.log(`${colors.green}✓ Provider changed to: ${colors.gold}${args[0]}${colors.reset}`);
|
|
324
|
+
}
|
|
325
|
+
else {
|
|
326
|
+
console.log(`${colors.amber}Usage: /provider <ollama|google|anthropic|openai|kimi>${colors.reset}`);
|
|
327
|
+
}
|
|
328
|
+
break;
|
|
329
|
+
case 'config':
|
|
330
|
+
this.emit('command:config');
|
|
331
|
+
break;
|
|
273
332
|
case 'exit':
|
|
274
333
|
case 'quit':
|
|
275
334
|
case 'bye':
|
|
@@ -285,15 +344,20 @@ ${colors.orange}${colors.bold}Input Prefixes${colors.reset} ${co
|
|
|
285
344
|
${colors.orange}${colors.bold}🦊 ApexBot Commands${colors.reset}
|
|
286
345
|
${colors.gray}${'─'.repeat(40)}${colors.reset}
|
|
287
346
|
|
|
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}/
|
|
293
|
-
${colors.orange}/
|
|
294
|
-
${colors.orange}/
|
|
295
|
-
${colors.orange}/
|
|
296
|
-
${colors.orange}/
|
|
347
|
+
${colors.orange}/help${colors.reset} Show this help
|
|
348
|
+
${colors.orange}/clear${colors.reset} Clear screen
|
|
349
|
+
${colors.orange}/reset${colors.reset} Reset conversation
|
|
350
|
+
${colors.orange}/history${colors.reset} Show recent messages
|
|
351
|
+
${colors.orange}/commands${colors.reset} Show command history
|
|
352
|
+
${colors.orange}/status${colors.reset} Show bot status
|
|
353
|
+
${colors.orange}/skills${colors.reset} List enabled skills
|
|
354
|
+
${colors.orange}/tools${colors.reset} List available tools
|
|
355
|
+
${colors.orange}/model${colors.reset} Show/change model
|
|
356
|
+
${colors.orange}/model${colors.reset} ${colors.gray}<name>${colors.reset} Change to specified model
|
|
357
|
+
${colors.orange}/provider${colors.reset} Change AI provider
|
|
358
|
+
${colors.orange}/config${colors.reset} Show configuration
|
|
359
|
+
${colors.orange}/fox${colors.reset} Show fox mascot 🦊
|
|
360
|
+
${colors.orange}/exit${colors.reset} Exit ApexBot
|
|
297
361
|
|
|
298
362
|
${colors.gray}Shortcuts: Ctrl+C (exit), Ctrl+L (clear), ↑↓ (history)${colors.reset}
|
|
299
363
|
`);
|
|
@@ -315,6 +379,19 @@ ${colors.gray}Shortcuts: Ctrl+C (exit), Ctrl+L (clear), ↑↓ (history)${colors
|
|
|
315
379
|
}
|
|
316
380
|
console.log();
|
|
317
381
|
}
|
|
382
|
+
showCommandHistory(count) {
|
|
383
|
+
const recent = this.commandHistory.slice(-count);
|
|
384
|
+
if (recent.length === 0) {
|
|
385
|
+
console.log(`${colors.gray}No command history yet${colors.reset}`);
|
|
386
|
+
return;
|
|
387
|
+
}
|
|
388
|
+
console.log(`\n${colors.orange}${colors.bold}Recent Commands (${recent.length})${colors.reset}\n`);
|
|
389
|
+
recent.forEach((cmd, i) => {
|
|
390
|
+
const num = this.commandHistory.length - count + i + 1;
|
|
391
|
+
console.log(`${colors.gray}${String(num).padStart(4)}.${colors.reset} ${cmd}`);
|
|
392
|
+
});
|
|
393
|
+
console.log(`\n${colors.gray}Total: ${this.commandHistory.length} commands in history${colors.reset}\n`);
|
|
394
|
+
}
|
|
318
395
|
startSpinner(text) {
|
|
319
396
|
this.isThinking = true;
|
|
320
397
|
this.currentSpinnerFrame = 0;
|