squidbot 0.1.0__py3-none-any.whl
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.
- squidbot/__init__.py +5 -0
- squidbot/agent.py +263 -0
- squidbot/channels.py +271 -0
- squidbot/character.py +83 -0
- squidbot/client.py +318 -0
- squidbot/config.py +148 -0
- squidbot/daemon.py +310 -0
- squidbot/lanes.py +41 -0
- squidbot/main.py +157 -0
- squidbot/memory_db.py +706 -0
- squidbot/playwright_check.py +233 -0
- squidbot/plugins/__init__.py +47 -0
- squidbot/plugins/base.py +96 -0
- squidbot/plugins/hooks.py +416 -0
- squidbot/plugins/loader.py +248 -0
- squidbot/plugins/web3_plugin.py +407 -0
- squidbot/scheduler.py +214 -0
- squidbot/server.py +487 -0
- squidbot/session.py +609 -0
- squidbot/skills.py +141 -0
- squidbot/skills_template/reminder/SKILL.md +13 -0
- squidbot/skills_template/search/SKILL.md +11 -0
- squidbot/skills_template/summarize/SKILL.md +14 -0
- squidbot/tools/__init__.py +100 -0
- squidbot/tools/base.py +42 -0
- squidbot/tools/browser.py +311 -0
- squidbot/tools/coding.py +599 -0
- squidbot/tools/cron.py +218 -0
- squidbot/tools/memory_tool.py +152 -0
- squidbot/tools/web_search.py +50 -0
- squidbot-0.1.0.dist-info/METADATA +542 -0
- squidbot-0.1.0.dist-info/RECORD +34 -0
- squidbot-0.1.0.dist-info/WHEEL +4 -0
- squidbot-0.1.0.dist-info/entry_points.txt +4 -0
squidbot/daemon.py
ADDED
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
SquidBot Daemon Manager
|
|
4
|
+
|
|
5
|
+
Process supervisor for running SquidBot server and managing clients.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import os
|
|
9
|
+
import signal
|
|
10
|
+
import subprocess
|
|
11
|
+
import sys
|
|
12
|
+
import time
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
|
|
15
|
+
# PID and log files
|
|
16
|
+
DATA_DIR = Path.home() / ".squidbot"
|
|
17
|
+
PID_FILE = DATA_DIR / "squidbot.pid"
|
|
18
|
+
LOG_FILE = DATA_DIR / "squidbot.log"
|
|
19
|
+
|
|
20
|
+
# Script directory
|
|
21
|
+
SCRIPT_DIR = Path(__file__).parent.absolute()
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def get_pid() -> int | None:
|
|
25
|
+
"""Get the PID of the running server."""
|
|
26
|
+
if not PID_FILE.exists():
|
|
27
|
+
return None
|
|
28
|
+
try:
|
|
29
|
+
pid = int(PID_FILE.read_text().strip())
|
|
30
|
+
# Check if process is actually running
|
|
31
|
+
os.kill(pid, 0)
|
|
32
|
+
return pid
|
|
33
|
+
except (ValueError, ProcessLookupError, PermissionError):
|
|
34
|
+
# PID file exists but process is not running
|
|
35
|
+
PID_FILE.unlink(missing_ok=True)
|
|
36
|
+
return None
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def is_running() -> bool:
|
|
40
|
+
"""Check if the server is running."""
|
|
41
|
+
return get_pid() is not None
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def find_squidbot_processes() -> list[tuple[int, str]]:
|
|
45
|
+
"""Find all SquidBot-related Python processes (server and client only)."""
|
|
46
|
+
processes = []
|
|
47
|
+
try:
|
|
48
|
+
# Use ps to find Python processes
|
|
49
|
+
result = subprocess.run(["ps", "aux"], capture_output=True, text=True)
|
|
50
|
+
|
|
51
|
+
for line in result.stdout.split("\n"):
|
|
52
|
+
# Only look for server.py and client.py, not daemon.py
|
|
53
|
+
if "python" in line.lower() and any(
|
|
54
|
+
script in line for script in ["server.py", "client.py"]
|
|
55
|
+
):
|
|
56
|
+
parts = line.split()
|
|
57
|
+
if len(parts) >= 2:
|
|
58
|
+
try:
|
|
59
|
+
pid = int(parts[1])
|
|
60
|
+
# Don't include ourselves
|
|
61
|
+
if pid != os.getpid():
|
|
62
|
+
processes.append((pid, line))
|
|
63
|
+
except ValueError:
|
|
64
|
+
pass
|
|
65
|
+
except Exception:
|
|
66
|
+
pass
|
|
67
|
+
|
|
68
|
+
return processes
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def show_env_info():
|
|
72
|
+
"""Display environment configuration to console."""
|
|
73
|
+
import os
|
|
74
|
+
|
|
75
|
+
from dotenv import load_dotenv
|
|
76
|
+
|
|
77
|
+
load_dotenv()
|
|
78
|
+
|
|
79
|
+
squid_port = int(os.environ.get("SQUID_PORT", "7777"))
|
|
80
|
+
openai_model = os.environ.get("OPENAI_MODEL", "gpt-4o")
|
|
81
|
+
heartbeat = int(os.environ.get("HEARTBEAT_INTERVAL_MINUTES", "30"))
|
|
82
|
+
telegram_token = os.environ.get("TELEGRAM_BOT_TOKEN", "")
|
|
83
|
+
openai_key = os.environ.get("OPENAI_API_KEY", "")
|
|
84
|
+
|
|
85
|
+
print("")
|
|
86
|
+
print("=" * 60)
|
|
87
|
+
print(" SquidBot Configuration")
|
|
88
|
+
print("=" * 60)
|
|
89
|
+
print(f" Home Directory : {DATA_DIR}")
|
|
90
|
+
print(f" Server Port : 127.0.0.1:{squid_port}")
|
|
91
|
+
print(f" Model : {openai_model}")
|
|
92
|
+
print(f" Heartbeat : {heartbeat} minutes")
|
|
93
|
+
print("-" * 60)
|
|
94
|
+
print(f" OPENAI_API_KEY : {'[SET]' if openai_key else '[NOT SET]'}")
|
|
95
|
+
print(f" Telegram Bot : {'[ENABLED]' if telegram_token else '[DISABLED]'}")
|
|
96
|
+
print("=" * 60)
|
|
97
|
+
print("")
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def start():
|
|
101
|
+
"""Start the server."""
|
|
102
|
+
if is_running():
|
|
103
|
+
print(f"SquidBot server is already running (PID: {get_pid()})")
|
|
104
|
+
return False
|
|
105
|
+
|
|
106
|
+
# Ensure data directory exists
|
|
107
|
+
DATA_DIR.mkdir(parents=True, exist_ok=True)
|
|
108
|
+
|
|
109
|
+
# Show environment info to console
|
|
110
|
+
show_env_info()
|
|
111
|
+
|
|
112
|
+
# Check Playwright before starting (so user sees errors immediately)
|
|
113
|
+
from .playwright_check import require_playwright_or_exit
|
|
114
|
+
|
|
115
|
+
require_playwright_or_exit()
|
|
116
|
+
|
|
117
|
+
print(f"Starting SquidBot server...")
|
|
118
|
+
print(f"Log file: {LOG_FILE}")
|
|
119
|
+
|
|
120
|
+
# Open log file
|
|
121
|
+
log_fd = open(LOG_FILE, "a")
|
|
122
|
+
|
|
123
|
+
# Start the process using module execution
|
|
124
|
+
# cwd must be parent of squidbot package (app directory)
|
|
125
|
+
process = subprocess.Popen(
|
|
126
|
+
[sys.executable, "-m", "squidbot.server"],
|
|
127
|
+
stdout=log_fd,
|
|
128
|
+
stderr=log_fd,
|
|
129
|
+
cwd=str(SCRIPT_DIR.parent),
|
|
130
|
+
start_new_session=True, # Detach from terminal
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
# Write PID file
|
|
134
|
+
PID_FILE.write_text(str(process.pid))
|
|
135
|
+
|
|
136
|
+
# Wait a moment to check if it started successfully
|
|
137
|
+
time.sleep(1)
|
|
138
|
+
|
|
139
|
+
if process.poll() is None:
|
|
140
|
+
print(f"SquidBot server started (PID: {process.pid})")
|
|
141
|
+
return True
|
|
142
|
+
else:
|
|
143
|
+
print("SquidBot failed to start. Check logs:")
|
|
144
|
+
print(f" tail -f {LOG_FILE}")
|
|
145
|
+
PID_FILE.unlink(missing_ok=True)
|
|
146
|
+
return False
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def stop():
|
|
150
|
+
"""Stop the server."""
|
|
151
|
+
pid = get_pid()
|
|
152
|
+
if pid is None:
|
|
153
|
+
print("SquidBot server is not running")
|
|
154
|
+
return False
|
|
155
|
+
|
|
156
|
+
print(f"Stopping SquidBot server (PID: {pid})...")
|
|
157
|
+
|
|
158
|
+
try:
|
|
159
|
+
# Send SIGTERM for graceful shutdown
|
|
160
|
+
os.kill(pid, signal.SIGTERM)
|
|
161
|
+
|
|
162
|
+
# Wait for process to terminate
|
|
163
|
+
for _ in range(10): # Wait up to 10 seconds
|
|
164
|
+
time.sleep(1)
|
|
165
|
+
try:
|
|
166
|
+
os.kill(pid, 0)
|
|
167
|
+
except ProcessLookupError:
|
|
168
|
+
print("SquidBot server stopped")
|
|
169
|
+
PID_FILE.unlink(missing_ok=True)
|
|
170
|
+
return True
|
|
171
|
+
|
|
172
|
+
# Force kill if still running
|
|
173
|
+
print("Process not responding, sending SIGKILL...")
|
|
174
|
+
os.kill(pid, signal.SIGKILL)
|
|
175
|
+
time.sleep(1)
|
|
176
|
+
PID_FILE.unlink(missing_ok=True)
|
|
177
|
+
print("SquidBot server forcefully terminated")
|
|
178
|
+
return True
|
|
179
|
+
|
|
180
|
+
except ProcessLookupError:
|
|
181
|
+
print("Process already terminated")
|
|
182
|
+
PID_FILE.unlink(missing_ok=True)
|
|
183
|
+
return True
|
|
184
|
+
except PermissionError:
|
|
185
|
+
print(f"Permission denied to stop process {pid}")
|
|
186
|
+
return False
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def stopall():
|
|
190
|
+
"""Stop server and kill all clients."""
|
|
191
|
+
print("Stopping all SquidBot processes...")
|
|
192
|
+
|
|
193
|
+
killed = 0
|
|
194
|
+
|
|
195
|
+
# First, stop the server gracefully
|
|
196
|
+
if is_running():
|
|
197
|
+
stop()
|
|
198
|
+
killed += 1
|
|
199
|
+
|
|
200
|
+
# Find and kill any remaining processes
|
|
201
|
+
processes = find_squidbot_processes()
|
|
202
|
+
|
|
203
|
+
for pid, cmdline in processes:
|
|
204
|
+
try:
|
|
205
|
+
print(f" Killing PID {pid}...")
|
|
206
|
+
os.kill(pid, signal.SIGTERM)
|
|
207
|
+
killed += 1
|
|
208
|
+
except (ProcessLookupError, PermissionError):
|
|
209
|
+
pass
|
|
210
|
+
|
|
211
|
+
# Wait a moment
|
|
212
|
+
if processes:
|
|
213
|
+
time.sleep(1)
|
|
214
|
+
|
|
215
|
+
# Force kill any remaining
|
|
216
|
+
for pid, cmdline in processes:
|
|
217
|
+
try:
|
|
218
|
+
os.kill(pid, 0) # Check if still running
|
|
219
|
+
print(f" Force killing PID {pid}...")
|
|
220
|
+
os.kill(pid, signal.SIGKILL)
|
|
221
|
+
except (ProcessLookupError, PermissionError):
|
|
222
|
+
pass
|
|
223
|
+
|
|
224
|
+
# Clean up PID file
|
|
225
|
+
PID_FILE.unlink(missing_ok=True)
|
|
226
|
+
|
|
227
|
+
print(f"Stopped {killed} process(es)")
|
|
228
|
+
return True
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
def restart():
|
|
232
|
+
"""Restart the server."""
|
|
233
|
+
if is_running():
|
|
234
|
+
stop()
|
|
235
|
+
time.sleep(1)
|
|
236
|
+
start()
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
def status():
|
|
240
|
+
"""Show server status."""
|
|
241
|
+
pid = get_pid()
|
|
242
|
+
if pid:
|
|
243
|
+
print(f"SquidBot server is running (PID: {pid})")
|
|
244
|
+
print(f"Log file: {LOG_FILE}")
|
|
245
|
+
|
|
246
|
+
# Show any client processes
|
|
247
|
+
processes = find_squidbot_processes()
|
|
248
|
+
clients = [(p, c) for p, c in processes if "client.py" in c]
|
|
249
|
+
if clients:
|
|
250
|
+
print(f"Active clients: {len(clients)}")
|
|
251
|
+
|
|
252
|
+
return True
|
|
253
|
+
else:
|
|
254
|
+
print("SquidBot server is not running")
|
|
255
|
+
return False
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
def logs(follow: bool = False, lines: int = 50):
|
|
259
|
+
"""Show server logs."""
|
|
260
|
+
if not LOG_FILE.exists():
|
|
261
|
+
print("No log file found")
|
|
262
|
+
return
|
|
263
|
+
|
|
264
|
+
if follow:
|
|
265
|
+
os.execvp("tail", ["tail", "-f", str(LOG_FILE)])
|
|
266
|
+
else:
|
|
267
|
+
os.execvp("tail", ["tail", "-n", str(lines), str(LOG_FILE)])
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
def main():
|
|
271
|
+
"""Main entry point."""
|
|
272
|
+
if len(sys.argv) < 2:
|
|
273
|
+
print("Usage: python daemon.py <command>")
|
|
274
|
+
print("")
|
|
275
|
+
print("Commands:")
|
|
276
|
+
print(" start Start the server")
|
|
277
|
+
print(" stop Stop the server")
|
|
278
|
+
print(" stopall Stop server and kill all clients")
|
|
279
|
+
print(" restart Restart the server")
|
|
280
|
+
print(" status Show server status")
|
|
281
|
+
print(" logs Show recent logs")
|
|
282
|
+
print(" logs -f Follow logs in real-time")
|
|
283
|
+
sys.exit(1)
|
|
284
|
+
|
|
285
|
+
command = sys.argv[1]
|
|
286
|
+
|
|
287
|
+
if command == "start":
|
|
288
|
+
success = start()
|
|
289
|
+
sys.exit(0 if success else 1)
|
|
290
|
+
elif command == "stop":
|
|
291
|
+
success = stop()
|
|
292
|
+
sys.exit(0 if success else 1)
|
|
293
|
+
elif command == "stopall":
|
|
294
|
+
success = stopall()
|
|
295
|
+
sys.exit(0 if success else 1)
|
|
296
|
+
elif command == "restart":
|
|
297
|
+
restart()
|
|
298
|
+
elif command == "status":
|
|
299
|
+
running = status()
|
|
300
|
+
sys.exit(0 if running else 1)
|
|
301
|
+
elif command == "logs":
|
|
302
|
+
follow = "-f" in sys.argv or "--follow" in sys.argv
|
|
303
|
+
logs(follow=follow)
|
|
304
|
+
else:
|
|
305
|
+
print(f"Unknown command: {command}")
|
|
306
|
+
sys.exit(1)
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
if __name__ == "__main__":
|
|
310
|
+
main()
|
squidbot/lanes.py
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Command Lanes - Execution context categorization.
|
|
3
|
+
|
|
4
|
+
Lanes categorize the context in which commands are executed,
|
|
5
|
+
enabling different behaviors for different execution paths.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from enum import Enum
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class CommandLane(str, Enum):
|
|
12
|
+
"""Command execution lane types."""
|
|
13
|
+
|
|
14
|
+
MAIN = "main" # Primary user interaction
|
|
15
|
+
CRON = "cron" # Scheduled/cron jobs
|
|
16
|
+
SUBAGENT = "subagent" # Sub-agent execution
|
|
17
|
+
NESTED = "nested" # Nested command execution
|
|
18
|
+
WEBHOOK = "webhook" # Webhook-triggered execution
|
|
19
|
+
PROACTIVE = "proactive" # Proactive/autonomous messages
|
|
20
|
+
|
|
21
|
+
def __str__(self) -> str:
|
|
22
|
+
return self.value
|
|
23
|
+
|
|
24
|
+
@property
|
|
25
|
+
def is_user_initiated(self) -> bool:
|
|
26
|
+
"""Whether this lane represents user-initiated actions."""
|
|
27
|
+
return self in (CommandLane.MAIN, CommandLane.NESTED)
|
|
28
|
+
|
|
29
|
+
@property
|
|
30
|
+
def is_automated(self) -> bool:
|
|
31
|
+
"""Whether this lane represents automated actions."""
|
|
32
|
+
return self in (CommandLane.CRON, CommandLane.WEBHOOK, CommandLane.PROACTIVE)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
# Convenience exports
|
|
36
|
+
LANE_MAIN = CommandLane.MAIN
|
|
37
|
+
LANE_CRON = CommandLane.CRON
|
|
38
|
+
LANE_SUBAGENT = CommandLane.SUBAGENT
|
|
39
|
+
LANE_NESTED = CommandLane.NESTED
|
|
40
|
+
LANE_WEBHOOK = CommandLane.WEBHOOK
|
|
41
|
+
LANE_PROACTIVE = CommandLane.PROACTIVE
|
squidbot/main.py
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
SquidBot - Autonomous AI Agent
|
|
4
|
+
|
|
5
|
+
A Telegram bot with:
|
|
6
|
+
- Telegram bot interface
|
|
7
|
+
- OpenAI LLM with tool calling
|
|
8
|
+
- Autonomous tool chaining loop
|
|
9
|
+
- Persistent memory
|
|
10
|
+
- Web search
|
|
11
|
+
- Browser automation (Playwright)
|
|
12
|
+
- Proactive messaging (cron/heartbeat)
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
import asyncio
|
|
16
|
+
import logging
|
|
17
|
+
|
|
18
|
+
from telegram import Update
|
|
19
|
+
from telegram.ext import (Application, CommandHandler, ContextTypes,
|
|
20
|
+
MessageHandler, filters)
|
|
21
|
+
|
|
22
|
+
from .agent import run_agent_with_history
|
|
23
|
+
from .config import TELEGRAM_BOT_TOKEN, validate_config
|
|
24
|
+
from .scheduler import Scheduler
|
|
25
|
+
|
|
26
|
+
# Setup logging
|
|
27
|
+
logging.basicConfig(
|
|
28
|
+
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", level=logging.INFO
|
|
29
|
+
)
|
|
30
|
+
logger = logging.getLogger(__name__)
|
|
31
|
+
|
|
32
|
+
# Session storage (in-memory for simplicity)
|
|
33
|
+
sessions: dict[int, list[dict]] = {}
|
|
34
|
+
|
|
35
|
+
# Scheduler instance (initialized later)
|
|
36
|
+
scheduler: Scheduler | None = None
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
async def start_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
|
40
|
+
"""Handle /start command."""
|
|
41
|
+
await update.message.reply_text(
|
|
42
|
+
"Hello! I'm your autonomous AI assistant.\n\n"
|
|
43
|
+
"I can:\n"
|
|
44
|
+
"- Remember things you tell me\n"
|
|
45
|
+
"- Search the web for information\n"
|
|
46
|
+
"- Browse websites\n"
|
|
47
|
+
"- Set reminders and scheduled tasks\n\n"
|
|
48
|
+
"Just send me a message!"
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
async def clear_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
|
53
|
+
"""Handle /clear command - clear session history."""
|
|
54
|
+
chat_id = update.effective_chat.id
|
|
55
|
+
sessions[chat_id] = []
|
|
56
|
+
await update.message.reply_text("Conversation history cleared.")
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
async def handle_message(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
|
60
|
+
"""Handle incoming messages."""
|
|
61
|
+
chat_id = update.effective_chat.id
|
|
62
|
+
user_message = update.message.text
|
|
63
|
+
|
|
64
|
+
# Update scheduler's chat_id for proactive messages
|
|
65
|
+
global scheduler
|
|
66
|
+
if scheduler:
|
|
67
|
+
scheduler.set_chat_id(chat_id)
|
|
68
|
+
|
|
69
|
+
# Get or create session history
|
|
70
|
+
if chat_id not in sessions:
|
|
71
|
+
sessions[chat_id] = []
|
|
72
|
+
|
|
73
|
+
# Send typing indicator
|
|
74
|
+
await context.bot.send_chat_action(chat_id=chat_id, action="typing")
|
|
75
|
+
|
|
76
|
+
try:
|
|
77
|
+
# Run the agent
|
|
78
|
+
response, updated_history = await run_agent_with_history(
|
|
79
|
+
user_message, sessions[chat_id]
|
|
80
|
+
)
|
|
81
|
+
sessions[chat_id] = updated_history
|
|
82
|
+
|
|
83
|
+
# Send response (split if too long)
|
|
84
|
+
max_length = 4096
|
|
85
|
+
if len(response) <= max_length:
|
|
86
|
+
await update.message.reply_text(response)
|
|
87
|
+
else:
|
|
88
|
+
# Split into chunks
|
|
89
|
+
for i in range(0, len(response), max_length):
|
|
90
|
+
chunk = response[i : i + max_length]
|
|
91
|
+
await update.message.reply_text(chunk)
|
|
92
|
+
|
|
93
|
+
# Reload scheduler jobs in case new ones were created
|
|
94
|
+
if scheduler:
|
|
95
|
+
scheduler.reload_jobs()
|
|
96
|
+
|
|
97
|
+
except Exception as e:
|
|
98
|
+
logger.exception("Error handling message")
|
|
99
|
+
await update.message.reply_text(f"Sorry, an error occurred: {str(e)}")
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
async def send_proactive_message(app: Application, chat_id: int, message: str):
|
|
103
|
+
"""Send a proactive message to a chat."""
|
|
104
|
+
try:
|
|
105
|
+
await app.bot.send_message(chat_id=chat_id, text=message)
|
|
106
|
+
except Exception as e:
|
|
107
|
+
logger.error(f"Failed to send proactive message: {e}")
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def main():
|
|
111
|
+
"""Main entry point."""
|
|
112
|
+
# Validate configuration
|
|
113
|
+
validate_config()
|
|
114
|
+
|
|
115
|
+
# Create application
|
|
116
|
+
app = Application.builder().token(TELEGRAM_BOT_TOKEN).build()
|
|
117
|
+
|
|
118
|
+
# Add handlers
|
|
119
|
+
app.add_handler(CommandHandler("start", start_command))
|
|
120
|
+
app.add_handler(CommandHandler("clear", clear_command))
|
|
121
|
+
app.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, handle_message))
|
|
122
|
+
|
|
123
|
+
# Setup scheduler
|
|
124
|
+
global scheduler
|
|
125
|
+
|
|
126
|
+
async def send_message(message: str):
|
|
127
|
+
"""Wrapper to send messages from scheduler."""
|
|
128
|
+
# Get the most recent chat_id
|
|
129
|
+
if scheduler and scheduler.chat_id:
|
|
130
|
+
await send_proactive_message(app, scheduler.chat_id, message)
|
|
131
|
+
|
|
132
|
+
async def run_agent_for_scheduler(prompt: str) -> str:
|
|
133
|
+
"""Run agent from scheduler context."""
|
|
134
|
+
response, _ = await run_agent_with_history(prompt, [])
|
|
135
|
+
return response
|
|
136
|
+
|
|
137
|
+
scheduler = Scheduler(send_message=send_message, run_agent=run_agent_for_scheduler)
|
|
138
|
+
|
|
139
|
+
# Start scheduler when bot starts
|
|
140
|
+
async def post_init(application: Application):
|
|
141
|
+
scheduler.start()
|
|
142
|
+
logger.info("Bot and scheduler started")
|
|
143
|
+
|
|
144
|
+
async def post_shutdown(application: Application):
|
|
145
|
+
scheduler.stop()
|
|
146
|
+
logger.info("Scheduler stopped")
|
|
147
|
+
|
|
148
|
+
app.post_init = post_init
|
|
149
|
+
app.post_shutdown = post_shutdown
|
|
150
|
+
|
|
151
|
+
# Run the bot
|
|
152
|
+
logger.info("Starting SquidBot...")
|
|
153
|
+
app.run_polling(allowed_updates=Update.ALL_TYPES)
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
if __name__ == "__main__":
|
|
157
|
+
main()
|