squidclaw 0.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/LICENSE +21 -0
- package/README.md +149 -0
- package/bin/squidclaw.js +512 -0
- package/lib/ai/gateway.js +283 -0
- package/lib/ai/prompt-builder.js +149 -0
- package/lib/api/server.js +235 -0
- package/lib/behavior/engine.js +187 -0
- package/lib/channels/hub-media.js +128 -0
- package/lib/channels/hub.js +89 -0
- package/lib/channels/whatsapp/manager.js +319 -0
- package/lib/channels/whatsapp/media.js +228 -0
- package/lib/cli/agent-cmd.js +182 -0
- package/lib/cli/brain-cmd.js +49 -0
- package/lib/cli/broadcast-cmd.js +28 -0
- package/lib/cli/channels-cmd.js +157 -0
- package/lib/cli/config-cmd.js +26 -0
- package/lib/cli/conversations-cmd.js +27 -0
- package/lib/cli/engine-cmd.js +115 -0
- package/lib/cli/handoff-cmd.js +26 -0
- package/lib/cli/hours-cmd.js +38 -0
- package/lib/cli/key-cmd.js +62 -0
- package/lib/cli/knowledge-cmd.js +59 -0
- package/lib/cli/memory-cmd.js +50 -0
- package/lib/cli/platform-cmd.js +51 -0
- package/lib/cli/setup.js +226 -0
- package/lib/cli/stats-cmd.js +66 -0
- package/lib/cli/tui.js +308 -0
- package/lib/cli/update-cmd.js +25 -0
- package/lib/cli/webhook-cmd.js +40 -0
- package/lib/core/agent-manager.js +83 -0
- package/lib/core/agent.js +162 -0
- package/lib/core/config.js +172 -0
- package/lib/core/logger.js +43 -0
- package/lib/engine.js +117 -0
- package/lib/features/heartbeat.js +71 -0
- package/lib/storage/interface.js +56 -0
- package/lib/storage/sqlite.js +409 -0
- package/package.json +48 -0
- package/templates/BEHAVIOR.md +42 -0
- package/templates/IDENTITY.md +7 -0
- package/templates/RULES.md +9 -0
- package/templates/SOUL.md +19 -0
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 🦑 Squidclaw Config System
|
|
3
|
+
* Handles loading, saving, and validating configuration
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs';
|
|
7
|
+
import { join } from 'path';
|
|
8
|
+
import { homedir } from 'os';
|
|
9
|
+
|
|
10
|
+
const SQUIDCLAW_HOME = process.env.SQUIDCLAW_HOME || join(homedir(), '.squidclaw');
|
|
11
|
+
const CONFIG_FILE = join(SQUIDCLAW_HOME, 'config.json');
|
|
12
|
+
|
|
13
|
+
const DEFAULT_CONFIG = {
|
|
14
|
+
engine: {
|
|
15
|
+
port: 9500,
|
|
16
|
+
bind: '127.0.0.1',
|
|
17
|
+
mode: 'standalone', // standalone | platform
|
|
18
|
+
},
|
|
19
|
+
ai: {
|
|
20
|
+
defaultProvider: null,
|
|
21
|
+
defaultModel: null,
|
|
22
|
+
fallbackChain: [],
|
|
23
|
+
providers: {
|
|
24
|
+
anthropic: { key: null, models: ['claude-opus-4', 'claude-sonnet-4', 'claude-haiku-3.5'] },
|
|
25
|
+
openai: { key: null, models: ['gpt-4o', 'gpt-4o-mini', 'o3', 'o4-mini'] },
|
|
26
|
+
google: { key: null, models: ['gemini-2.5-pro', 'gemini-2.5-flash', 'gemini-2.0-flash'] },
|
|
27
|
+
groq: { key: null, models: ['llama-4-scout', 'llama-4-maverick', 'llama-3.3-70b'] },
|
|
28
|
+
together: { key: null, models: ['llama-4-scout', 'llama-4-maverick'] },
|
|
29
|
+
cerebras: { key: null, models: ['llama-3.3-70b'] },
|
|
30
|
+
openrouter: { key: null, models: [] },
|
|
31
|
+
mistral: { key: null, models: ['mistral-large', 'mistral-medium', 'mistral-small'] },
|
|
32
|
+
ollama: { key: 'local', baseUrl: 'http://localhost:11434', models: [] },
|
|
33
|
+
lmstudio: { key: 'local', baseUrl: 'http://localhost:1234', models: [] },
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
channels: {
|
|
37
|
+
whatsapp: { enabled: false },
|
|
38
|
+
telegram: { enabled: false },
|
|
39
|
+
},
|
|
40
|
+
storage: {
|
|
41
|
+
type: 'sqlite', // sqlite | supabase
|
|
42
|
+
sqlite: { path: join(SQUIDCLAW_HOME, 'squidclaw.db') },
|
|
43
|
+
supabase: { url: null, key: null },
|
|
44
|
+
},
|
|
45
|
+
platform: {
|
|
46
|
+
enabled: false,
|
|
47
|
+
platformKey: null, // default API key for all agents
|
|
48
|
+
allowUserKeys: true,
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
// Model name → full API model ID mapping
|
|
53
|
+
export const MODEL_MAP = {
|
|
54
|
+
// Anthropic
|
|
55
|
+
'claude-opus-4': 'claude-opus-4-20250514',
|
|
56
|
+
'claude-sonnet-4': 'claude-sonnet-4-20250514',
|
|
57
|
+
'claude-haiku-3.5': 'claude-3-5-haiku-20241022',
|
|
58
|
+
// OpenAI
|
|
59
|
+
'gpt-4o': 'gpt-4o',
|
|
60
|
+
'gpt-4o-mini': 'gpt-4o-mini',
|
|
61
|
+
'o3': 'o3',
|
|
62
|
+
'o4-mini': 'o4-mini',
|
|
63
|
+
// Google
|
|
64
|
+
'gemini-2.5-pro': 'gemini-2.5-pro-latest',
|
|
65
|
+
'gemini-2.5-flash': 'gemini-2.5-flash-preview-05-20',
|
|
66
|
+
'gemini-2.0-flash': 'gemini-2.0-flash',
|
|
67
|
+
// Groq
|
|
68
|
+
'groq/llama-4-scout': 'meta-llama/llama-4-scout-17b-16e-instruct',
|
|
69
|
+
'groq/llama-4-maverick': 'meta-llama/llama-4-maverick-17b-128e-instruct',
|
|
70
|
+
'groq/llama-3.3-70b': 'llama-3.3-70b-versatile',
|
|
71
|
+
// Together
|
|
72
|
+
'together/llama-4-scout': 'meta-llama/Llama-4-Scout-17B-16E-Instruct',
|
|
73
|
+
'together/llama-4-maverick': 'meta-llama/Llama-4-Maverick-17B-128E-Instruct',
|
|
74
|
+
// Cerebras
|
|
75
|
+
'cerebras/llama-3.3-70b': 'llama-3.3-70b',
|
|
76
|
+
// Mistral
|
|
77
|
+
'mistral-large': 'mistral-large-latest',
|
|
78
|
+
'mistral-medium': 'mistral-medium-latest',
|
|
79
|
+
'mistral-small': 'mistral-small-latest',
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
// Pricing per 1M tokens (input/output)
|
|
83
|
+
export const MODEL_PRICING = {
|
|
84
|
+
'claude-opus-4': { input: 15, output: 75 },
|
|
85
|
+
'claude-sonnet-4': { input: 3, output: 15 },
|
|
86
|
+
'claude-haiku-3.5': { input: 0.8, output: 4 },
|
|
87
|
+
'gpt-4o': { input: 2.5, output: 10 },
|
|
88
|
+
'gpt-4o-mini': { input: 0.15, output: 0.6 },
|
|
89
|
+
'o3': { input: 10, output: 40 },
|
|
90
|
+
'o4-mini': { input: 1.1, output: 4.4 },
|
|
91
|
+
'gemini-2.5-pro': { input: 1.25, output: 10 },
|
|
92
|
+
'gemini-2.5-flash': { input: 0.15, output: 0.6 },
|
|
93
|
+
'gemini-2.0-flash': { input: 0.1, output: 0.4 },
|
|
94
|
+
'groq/llama-4-scout': { input: 0, output: 0 },
|
|
95
|
+
'groq/llama-4-maverick': { input: 0, output: 0 },
|
|
96
|
+
'groq/llama-3.3-70b': { input: 0, output: 0 },
|
|
97
|
+
'together/llama-4-scout': { input: 0, output: 0 },
|
|
98
|
+
'cerebras/llama-3.3-70b': { input: 0, output: 0 },
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
export function getHome() {
|
|
102
|
+
return SQUIDCLAW_HOME;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export function ensureHome() {
|
|
106
|
+
const dirs = [
|
|
107
|
+
SQUIDCLAW_HOME,
|
|
108
|
+
join(SQUIDCLAW_HOME, 'agents'),
|
|
109
|
+
join(SQUIDCLAW_HOME, 'channels'),
|
|
110
|
+
join(SQUIDCLAW_HOME, 'channels', 'whatsapp'),
|
|
111
|
+
join(SQUIDCLAW_HOME, 'knowledge'),
|
|
112
|
+
join(SQUIDCLAW_HOME, 'logs'),
|
|
113
|
+
];
|
|
114
|
+
for (const dir of dirs) {
|
|
115
|
+
mkdirSync(dir, { recursive: true });
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export function loadConfig() {
|
|
120
|
+
ensureHome();
|
|
121
|
+
if (!existsSync(CONFIG_FILE)) {
|
|
122
|
+
return { ...DEFAULT_CONFIG };
|
|
123
|
+
}
|
|
124
|
+
try {
|
|
125
|
+
const raw = readFileSync(CONFIG_FILE, 'utf8');
|
|
126
|
+
const loaded = JSON.parse(raw);
|
|
127
|
+
return deepMerge(DEFAULT_CONFIG, loaded);
|
|
128
|
+
} catch {
|
|
129
|
+
return { ...DEFAULT_CONFIG };
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export function saveConfig(config) {
|
|
134
|
+
ensureHome();
|
|
135
|
+
writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export function getConfig(key) {
|
|
139
|
+
const config = loadConfig();
|
|
140
|
+
if (!key) return config;
|
|
141
|
+
return key.split('.').reduce((obj, k) => obj?.[k], config);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export function setConfigValue(key, value) {
|
|
145
|
+
const config = loadConfig();
|
|
146
|
+
const keys = key.split('.');
|
|
147
|
+
let obj = config;
|
|
148
|
+
for (let i = 0; i < keys.length - 1; i++) {
|
|
149
|
+
if (!obj[keys[i]]) obj[keys[i]] = {};
|
|
150
|
+
obj = obj[keys[i]];
|
|
151
|
+
}
|
|
152
|
+
// Try to parse JSON values
|
|
153
|
+
try {
|
|
154
|
+
obj[keys[keys.length - 1]] = JSON.parse(value);
|
|
155
|
+
} catch {
|
|
156
|
+
obj[keys[keys.length - 1]] = value;
|
|
157
|
+
}
|
|
158
|
+
saveConfig(config);
|
|
159
|
+
return config;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function deepMerge(target, source) {
|
|
163
|
+
const result = { ...target };
|
|
164
|
+
for (const key of Object.keys(source)) {
|
|
165
|
+
if (source[key] && typeof source[key] === 'object' && !Array.isArray(source[key])) {
|
|
166
|
+
result[key] = deepMerge(result[key] || {}, source[key]);
|
|
167
|
+
} else {
|
|
168
|
+
result[key] = source[key];
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
return result;
|
|
172
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 🦑 Squidclaw Logger
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { createWriteStream, mkdirSync } from 'fs';
|
|
6
|
+
import { join } from 'path';
|
|
7
|
+
import { getHome } from './config.js';
|
|
8
|
+
|
|
9
|
+
const LEVELS = { error: 0, warn: 1, info: 2, debug: 3, trace: 4 };
|
|
10
|
+
let currentLevel = LEVELS.info;
|
|
11
|
+
let logStream = null;
|
|
12
|
+
|
|
13
|
+
export function initLogger(level = 'info') {
|
|
14
|
+
currentLevel = LEVELS[level] ?? LEVELS.info;
|
|
15
|
+
const logDir = join(getHome(), 'logs');
|
|
16
|
+
mkdirSync(logDir, { recursive: true });
|
|
17
|
+
const logFile = join(logDir, 'squidclaw.log');
|
|
18
|
+
logStream = createWriteStream(logFile, { flags: 'a' });
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function log(level, component, message, data) {
|
|
22
|
+
if (LEVELS[level] > currentLevel) return;
|
|
23
|
+
const entry = {
|
|
24
|
+
ts: new Date().toISOString(),
|
|
25
|
+
level,
|
|
26
|
+
component,
|
|
27
|
+
msg: message,
|
|
28
|
+
...(data ? { data } : {}),
|
|
29
|
+
};
|
|
30
|
+
const line = JSON.stringify(entry);
|
|
31
|
+
if (logStream) logStream.write(line + '\n');
|
|
32
|
+
if (level === 'error') console.error(`[${component}] ${message}`);
|
|
33
|
+
else if (level === 'warn') console.warn(`[${component}] ${message}`);
|
|
34
|
+
else if (currentLevel >= LEVELS.debug) console.log(`[${component}] ${message}`);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export const logger = {
|
|
38
|
+
error: (component, msg, data) => log('error', component, msg, data),
|
|
39
|
+
warn: (component, msg, data) => log('warn', component, msg, data),
|
|
40
|
+
info: (component, msg, data) => log('info', component, msg, data),
|
|
41
|
+
debug: (component, msg, data) => log('debug', component, msg, data),
|
|
42
|
+
trace: (component, msg, data) => log('trace', component, msg, data),
|
|
43
|
+
};
|
package/lib/engine.js
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 🦑 Squidclaw Engine
|
|
3
|
+
* Main entry point — starts all subsystems
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { loadConfig, getHome, ensureHome } from './core/config.js';
|
|
7
|
+
import { initLogger, logger } from './core/logger.js';
|
|
8
|
+
import { AgentManager } from './core/agent-manager.js';
|
|
9
|
+
import { AIGateway } from './ai/gateway.js';
|
|
10
|
+
import { SQLiteStorage } from './storage/sqlite.js';
|
|
11
|
+
import { WhatsAppManager } from './channels/whatsapp/manager.js';
|
|
12
|
+
import { ChannelHub } from './channels/hub.js';
|
|
13
|
+
import { HeartbeatSystem } from './features/heartbeat.js';
|
|
14
|
+
import { createAPIServer } from './api/server.js';
|
|
15
|
+
import { addMediaSupport } from './channels/hub-media.js';
|
|
16
|
+
|
|
17
|
+
export class SquidclawEngine {
|
|
18
|
+
constructor(options = {}) {
|
|
19
|
+
this.config = loadConfig();
|
|
20
|
+
this.home = getHome();
|
|
21
|
+
this.port = options.port || this.config.engine?.port || 9500;
|
|
22
|
+
this.storage = null;
|
|
23
|
+
this.aiGateway = null;
|
|
24
|
+
this.agentManager = null;
|
|
25
|
+
this.whatsappManager = null;
|
|
26
|
+
this.channelHub = null;
|
|
27
|
+
this.heartbeat = null;
|
|
28
|
+
this.server = null;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async start() {
|
|
32
|
+
ensureHome();
|
|
33
|
+
initLogger(this.config.engine?.logLevel || 'info');
|
|
34
|
+
|
|
35
|
+
console.log(`
|
|
36
|
+
🦑 Squidclaw Engine v0.1.0
|
|
37
|
+
──────────────────────────`);
|
|
38
|
+
|
|
39
|
+
// 1. Storage
|
|
40
|
+
const dbPath = this.config.storage?.sqlite?.path || `${this.home}/squidclaw.db`;
|
|
41
|
+
this.storage = new SQLiteStorage(dbPath);
|
|
42
|
+
console.log(` 💾 Storage: SQLite (${dbPath})`);
|
|
43
|
+
|
|
44
|
+
// 2. AI Gateway
|
|
45
|
+
this.aiGateway = new AIGateway(this.config);
|
|
46
|
+
const providers = Object.entries(this.config.ai?.providers || {})
|
|
47
|
+
.filter(([_, v]) => v.key)
|
|
48
|
+
.map(([k]) => k);
|
|
49
|
+
console.log(` 🧠 AI: ${providers.join(', ') || 'none configured'}`);
|
|
50
|
+
if (this.config.ai?.defaultModel) {
|
|
51
|
+
console.log(` 🎯 Default model: ${this.config.ai.defaultModel}`);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// 3. Agent Manager
|
|
55
|
+
this.agentManager = new AgentManager(this.storage, this.aiGateway);
|
|
56
|
+
await this.agentManager.loadAll();
|
|
57
|
+
const agents = this.agentManager.getAll();
|
|
58
|
+
console.log(` 👥 Agents: ${agents.length} loaded`);
|
|
59
|
+
|
|
60
|
+
// 4. WhatsApp Manager
|
|
61
|
+
this.whatsappManager = new WhatsAppManager(this.config, this.agentManager, this.home);
|
|
62
|
+
|
|
63
|
+
// Start WhatsApp sessions for all agents that have them configured
|
|
64
|
+
for (const agent of agents) {
|
|
65
|
+
if (agent.status === 'active' && agent.whatsappNumber) {
|
|
66
|
+
try {
|
|
67
|
+
await this.whatsappManager.startSession(agent.id);
|
|
68
|
+
} catch (err) {
|
|
69
|
+
logger.warn('engine', `Failed to start WhatsApp for "${agent.name}": ${err.message}`);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
const waStatuses = this.whatsappManager.getStatuses();
|
|
74
|
+
const connected = Object.values(waStatuses).filter(s => s.connected).length;
|
|
75
|
+
console.log(` 📱 WhatsApp: ${connected}/${agents.length} connected`);
|
|
76
|
+
|
|
77
|
+
// 5. Channel Hub
|
|
78
|
+
this.channelHub = new ChannelHub(this.agentManager, this.whatsappManager, this.storage);
|
|
79
|
+
addMediaSupport(this.channelHub, this.config, this.home);
|
|
80
|
+
|
|
81
|
+
// 6. Heartbeat
|
|
82
|
+
this.heartbeat = new HeartbeatSystem(this.agentManager, this.whatsappManager, this.storage);
|
|
83
|
+
this.heartbeat.start();
|
|
84
|
+
console.log(` 💓 Heartbeat: active`);
|
|
85
|
+
|
|
86
|
+
// 7. API Server
|
|
87
|
+
const app = createAPIServer(this);
|
|
88
|
+
this.server = app.listen(this.port, this.config.engine?.bind || '127.0.0.1', () => {
|
|
89
|
+
console.log(` 🌐 API: http://${this.config.engine?.bind || '127.0.0.1'}:${this.port}`);
|
|
90
|
+
console.log(` ──────────────────────────`);
|
|
91
|
+
console.log(` ✅ Engine running!\n`);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
// Graceful shutdown
|
|
95
|
+
process.on('SIGINT', () => this.stop());
|
|
96
|
+
process.on('SIGTERM', () => this.stop());
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async stop() {
|
|
100
|
+
console.log('\n 🦑 Shutting down...');
|
|
101
|
+
this.heartbeat?.stop();
|
|
102
|
+
await this.whatsappManager?.stopAll();
|
|
103
|
+
this.storage?.close();
|
|
104
|
+
this.server?.close();
|
|
105
|
+
console.log(' 👋 Bye!\n');
|
|
106
|
+
process.exit(0);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Auto-start if run directly
|
|
111
|
+
if (process.argv[1]?.endsWith('engine.js')) {
|
|
112
|
+
const engine = new SquidclawEngine();
|
|
113
|
+
engine.start().catch(err => {
|
|
114
|
+
console.error('Fatal:', err.message);
|
|
115
|
+
process.exit(1);
|
|
116
|
+
});
|
|
117
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 🦑 Heartbeat System
|
|
3
|
+
* Periodic agent tasks — proactive messages, follow-ups, scheduled sends
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { Cron } from 'croner';
|
|
7
|
+
import { logger } from '../core/logger.js';
|
|
8
|
+
|
|
9
|
+
export class HeartbeatSystem {
|
|
10
|
+
constructor(agentManager, whatsappManager, storage) {
|
|
11
|
+
this.agentManager = agentManager;
|
|
12
|
+
this.whatsappManager = whatsappManager;
|
|
13
|
+
this.storage = storage;
|
|
14
|
+
this.jobs = [];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
start() {
|
|
18
|
+
// Check scheduled messages every minute
|
|
19
|
+
this.jobs.push(new Cron('* * * * *', async () => {
|
|
20
|
+
await this._processScheduledMessages();
|
|
21
|
+
}));
|
|
22
|
+
|
|
23
|
+
// Health check all agents every 5 minutes
|
|
24
|
+
this.jobs.push(new Cron('*/5 * * * *', async () => {
|
|
25
|
+
await this._healthCheck();
|
|
26
|
+
}));
|
|
27
|
+
|
|
28
|
+
logger.info('heartbeat', 'Heartbeat system started');
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
stop() {
|
|
32
|
+
for (const job of this.jobs) {
|
|
33
|
+
job.stop();
|
|
34
|
+
}
|
|
35
|
+
this.jobs = [];
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async _processScheduledMessages() {
|
|
39
|
+
try {
|
|
40
|
+
const due = await this.storage.getDueMessages();
|
|
41
|
+
for (const msg of due) {
|
|
42
|
+
try {
|
|
43
|
+
await this.whatsappManager.sendMessage(msg.agent_id, msg.contact_id, msg.message);
|
|
44
|
+
await this.storage.markMessageSent(msg.id);
|
|
45
|
+
logger.info('heartbeat', `Sent scheduled message ${msg.id} to ${msg.contact_id}`);
|
|
46
|
+
} catch (err) {
|
|
47
|
+
logger.error('heartbeat', `Failed to send scheduled message ${msg.id}: ${err.message}`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
} catch (err) {
|
|
51
|
+
logger.error('heartbeat', `Scheduled message check failed: ${err.message}`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async _healthCheck() {
|
|
56
|
+
const statuses = this.whatsappManager.getStatuses();
|
|
57
|
+
const agents = this.agentManager.getAll();
|
|
58
|
+
|
|
59
|
+
for (const agent of agents) {
|
|
60
|
+
const waStatus = statuses[agent.id];
|
|
61
|
+
if (agent.status === 'active' && agent.whatsappNumber && (!waStatus || !waStatus.connected)) {
|
|
62
|
+
logger.warn('heartbeat', `Agent "${agent.name}" WhatsApp disconnected — attempting reconnect`);
|
|
63
|
+
try {
|
|
64
|
+
await this.whatsappManager.startSession(agent.id);
|
|
65
|
+
} catch (err) {
|
|
66
|
+
logger.error('heartbeat', `Reconnect failed for "${agent.name}": ${err.message}`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 🦑 Storage Interface
|
|
3
|
+
* Abstract interface for all storage operations
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export class StorageInterface {
|
|
7
|
+
// ── Agents ──
|
|
8
|
+
async createAgent(agent) { throw new Error('Not implemented'); }
|
|
9
|
+
async getAgent(id) { throw new Error('Not implemented'); }
|
|
10
|
+
async listAgents() { throw new Error('Not implemented'); }
|
|
11
|
+
async updateAgent(id, updates) { throw new Error('Not implemented'); }
|
|
12
|
+
async deleteAgent(id) { throw new Error('Not implemented'); }
|
|
13
|
+
|
|
14
|
+
// ── Conversations ──
|
|
15
|
+
async saveMessage(agentId, contactId, role, content, metadata) { throw new Error('Not implemented'); }
|
|
16
|
+
async getConversation(agentId, contactId, limit) { throw new Error('Not implemented'); }
|
|
17
|
+
async searchConversations(query, agentId) { throw new Error('Not implemented'); }
|
|
18
|
+
|
|
19
|
+
// ── Memory ──
|
|
20
|
+
async saveMemory(agentId, key, value, type) { throw new Error('Not implemented'); }
|
|
21
|
+
async getMemories(agentId, type) { throw new Error('Not implemented'); }
|
|
22
|
+
async deleteMemory(agentId, key) { throw new Error('Not implemented'); }
|
|
23
|
+
async clearMemories(agentId) { throw new Error('Not implemented'); }
|
|
24
|
+
|
|
25
|
+
// ── Knowledge / RAG ──
|
|
26
|
+
async saveDocument(agentId, doc) { throw new Error('Not implemented'); }
|
|
27
|
+
async saveChunks(docId, chunks) { throw new Error('Not implemented'); }
|
|
28
|
+
async searchKnowledge(agentId, embedding, limit) { throw new Error('Not implemented'); }
|
|
29
|
+
async listDocuments(agentId) { throw new Error('Not implemented'); }
|
|
30
|
+
async deleteDocument(docId) { throw new Error('Not implemented'); }
|
|
31
|
+
|
|
32
|
+
// ── Usage ──
|
|
33
|
+
async trackUsage(agentId, model, inputTokens, outputTokens, costUsd) { throw new Error('Not implemented'); }
|
|
34
|
+
async getUsage(agentId, period) { throw new Error('Not implemented'); }
|
|
35
|
+
async getUsageAll(period) { throw new Error('Not implemented'); }
|
|
36
|
+
|
|
37
|
+
// ── Handoffs ──
|
|
38
|
+
async createHandoff(agentId, contactId, reason) { throw new Error('Not implemented'); }
|
|
39
|
+
async getActiveHandoffs() { throw new Error('Not implemented'); }
|
|
40
|
+
async resolveHandoff(id) { throw new Error('Not implemented'); }
|
|
41
|
+
|
|
42
|
+
// ── Webhooks ──
|
|
43
|
+
async addWebhook(url, events) { throw new Error('Not implemented'); }
|
|
44
|
+
async getWebhooks() { throw new Error('Not implemented'); }
|
|
45
|
+
async removeWebhook(id) { throw new Error('Not implemented'); }
|
|
46
|
+
|
|
47
|
+
// ── Contacts ──
|
|
48
|
+
async saveContact(agentId, contactId, name, metadata) { throw new Error('Not implemented'); }
|
|
49
|
+
async getContact(agentId, contactId) { throw new Error('Not implemented'); }
|
|
50
|
+
async listContacts(agentId) { throw new Error('Not implemented'); }
|
|
51
|
+
|
|
52
|
+
// ── Scheduled Messages ──
|
|
53
|
+
async scheduleMessage(agentId, contactId, message, sendAt) { throw new Error('Not implemented'); }
|
|
54
|
+
async getDueMessages() { throw new Error('Not implemented'); }
|
|
55
|
+
async markMessageSent(id) { throw new Error('Not implemented'); }
|
|
56
|
+
}
|