simplecontext-bot 1.2.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.
- simplecontext_bot/__init__.py +4 -0
- simplecontext_bot/bot.py +465 -0
- simplecontext_bot/cli.py +269 -0
- simplecontext_bot/config.py +107 -0
- simplecontext_bot/installer.py +311 -0
- simplecontext_bot/llm.py +78 -0
- simplecontext_bot/setup_wizard.py +266 -0
- simplecontext_bot-1.2.0.dist-info/METADATA +315 -0
- simplecontext_bot-1.2.0.dist-info/RECORD +12 -0
- simplecontext_bot-1.2.0.dist-info/WHEEL +5 -0
- simplecontext_bot-1.2.0.dist-info/entry_points.txt +2 -0
- simplecontext_bot-1.2.0.dist-info/top_level.txt +1 -0
simplecontext_bot/bot.py
ADDED
|
@@ -0,0 +1,465 @@
|
|
|
1
|
+
"""
|
|
2
|
+
bot.py — Telegram Bot v1.2
|
|
3
|
+
Dynamic plugin system:
|
|
4
|
+
- Auto-scan semua .py di plugins/ folder (termasuk plugin komunitas)
|
|
5
|
+
- Plugin bisa declare BOT_COMMANDS untuk auto-register command ke Telegram
|
|
6
|
+
- /plugins selalu akurat: baca dari sc._plugins, bukan registry hardcoded
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import sys
|
|
10
|
+
import logging
|
|
11
|
+
import importlib.util
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from . import config as cfg
|
|
14
|
+
from . import llm
|
|
15
|
+
|
|
16
|
+
logger = logging.getLogger(__name__)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _load_simplecontext():
|
|
20
|
+
install_dir = Path(cfg.get("install_dir"))
|
|
21
|
+
if str(install_dir) not in sys.path:
|
|
22
|
+
sys.path.insert(0, str(install_dir))
|
|
23
|
+
try:
|
|
24
|
+
from simplecontext import SimpleContext
|
|
25
|
+
return SimpleContext
|
|
26
|
+
except ImportError:
|
|
27
|
+
logger.error("SimpleContext not found. Run: simplecontext-bot setup")
|
|
28
|
+
return None
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
# ── Plugin Loader ─────────────────────────────────────────────────────────────
|
|
32
|
+
|
|
33
|
+
def _scan_plugin_files(plugins_dir: Path) -> list[Path]:
|
|
34
|
+
"""
|
|
35
|
+
Scan semua file .py di plugins_dir.
|
|
36
|
+
Tidak tergantung OFFICIAL_PLUGINS — plugin komunitas yang di-drop manual
|
|
37
|
+
juga terdeteksi otomatis.
|
|
38
|
+
"""
|
|
39
|
+
if not plugins_dir.exists():
|
|
40
|
+
return []
|
|
41
|
+
return [f for f in plugins_dir.glob("*.py") if not f.name.startswith("_")]
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _load_plugin_class(plugin_file: Path):
|
|
45
|
+
"""
|
|
46
|
+
Load satu file plugin, return (plugin_class, module) atau (None, None).
|
|
47
|
+
Cari class pertama yang mewarisi BasePlugin dengan atribut name.
|
|
48
|
+
"""
|
|
49
|
+
try:
|
|
50
|
+
from simplecontext.plugins.base import BasePlugin
|
|
51
|
+
except ImportError:
|
|
52
|
+
return None, None
|
|
53
|
+
|
|
54
|
+
try:
|
|
55
|
+
module_name = f"sc_plugin_{plugin_file.stem}"
|
|
56
|
+
spec = importlib.util.spec_from_file_location(module_name, plugin_file)
|
|
57
|
+
module = importlib.util.module_from_spec(spec)
|
|
58
|
+
spec.loader.exec_module(module)
|
|
59
|
+
|
|
60
|
+
for attr_name in dir(module):
|
|
61
|
+
attr = getattr(module, attr_name)
|
|
62
|
+
if (isinstance(attr, type)
|
|
63
|
+
and issubclass(attr, BasePlugin)
|
|
64
|
+
and attr is not BasePlugin
|
|
65
|
+
and getattr(attr, "name", "")):
|
|
66
|
+
return attr, module
|
|
67
|
+
|
|
68
|
+
logger.warning(f"Tidak ada BasePlugin class di {plugin_file.name}")
|
|
69
|
+
return None, None
|
|
70
|
+
|
|
71
|
+
except Exception as e:
|
|
72
|
+
logger.warning(f"Gagal load plugin '{plugin_file.name}': {e}")
|
|
73
|
+
return None, None
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _load_all_plugins(sc, install_dir: Path) -> dict:
|
|
77
|
+
"""
|
|
78
|
+
Auto-scan dan load semua plugin dari install_dir/plugins/.
|
|
79
|
+
Tidak bergantung config.json installed list — semua .py ter-load otomatis.
|
|
80
|
+
|
|
81
|
+
Return: dict { plugin_name: plugin_instance }
|
|
82
|
+
"""
|
|
83
|
+
plugins_dir = install_dir / "plugins"
|
|
84
|
+
plugin_files = _scan_plugin_files(plugins_dir)
|
|
85
|
+
plugin_configs = cfg.get("plugins.configs", {})
|
|
86
|
+
|
|
87
|
+
if not plugin_files:
|
|
88
|
+
return {}
|
|
89
|
+
|
|
90
|
+
if str(plugins_dir) not in sys.path:
|
|
91
|
+
sys.path.insert(0, str(plugins_dir))
|
|
92
|
+
|
|
93
|
+
loaded = {}
|
|
94
|
+
for plugin_file in sorted(plugin_files):
|
|
95
|
+
plugin_cls, _ = _load_plugin_class(plugin_file)
|
|
96
|
+
if not plugin_cls:
|
|
97
|
+
continue
|
|
98
|
+
|
|
99
|
+
plugin_name = plugin_cls.name
|
|
100
|
+
|
|
101
|
+
# Cari config: dari config.json (keyed by plugin_id atau plugin_name)
|
|
102
|
+
# Fallback ke empty dict — plugin tetap load dengan default config-nya
|
|
103
|
+
plugin_cfg = (
|
|
104
|
+
plugin_configs.get(plugin_name)
|
|
105
|
+
or plugin_configs.get(plugin_file.stem)
|
|
106
|
+
or {}
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
try:
|
|
110
|
+
sc.use(plugin_cls(config=plugin_cfg))
|
|
111
|
+
loaded[plugin_name] = sc._plugins.get(plugin_name)
|
|
112
|
+
logger.info(
|
|
113
|
+
f"✅ Plugin loaded: {plugin_name} v{plugin_cls.version}"
|
|
114
|
+
+ (f" (BOT_COMMANDS: {list(plugin_cls.BOT_COMMANDS.keys())})"
|
|
115
|
+
if getattr(plugin_cls, "BOT_COMMANDS", None) else "")
|
|
116
|
+
)
|
|
117
|
+
except Exception as e:
|
|
118
|
+
logger.warning(f"Gagal register plugin '{plugin_name}': {e}")
|
|
119
|
+
|
|
120
|
+
return loaded
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
# ── Dynamic Command Registration ─────────────────────────────────────────────
|
|
124
|
+
|
|
125
|
+
def _collect_app_commands(sc) -> dict:
|
|
126
|
+
"""
|
|
127
|
+
Kumpulkan semua app_commands dari plugin via loader.get_all_app_commands().
|
|
128
|
+
Menggunakan kontrak resmi BasePlugin v4 — bukan convention ad-hoc.
|
|
129
|
+
Return: { "command_name": {...cmd_info, "plugin": plugin_instance} }
|
|
130
|
+
"""
|
|
131
|
+
commands = sc._plugins.get_all_app_commands()
|
|
132
|
+
for cmd_name, cmd_info in commands.items():
|
|
133
|
+
plugin_name = cmd_info["plugin"].name
|
|
134
|
+
logger.info(f" ↳ App command: /{cmd_name} (dari {plugin_name})")
|
|
135
|
+
return commands
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def _make_dynamic_handler(sc, cmd_name: str):
|
|
139
|
+
"""
|
|
140
|
+
Buat async Telegram handler untuk satu app_command.
|
|
141
|
+
Eksekusi via sc._plugins.fire_app_command() — routing ditangani core,
|
|
142
|
+
bukan bot. Handler plugin cukup terima AppCommandContext.
|
|
143
|
+
"""
|
|
144
|
+
async def handler(update, ctx):
|
|
145
|
+
from simplecontext.plugins.base import AppCommandContext
|
|
146
|
+
|
|
147
|
+
tg_ctx = AppCommandContext.create(
|
|
148
|
+
command = cmd_name,
|
|
149
|
+
user_id = str(update.effective_user.id),
|
|
150
|
+
args = ctx.args or [],
|
|
151
|
+
platform = "telegram",
|
|
152
|
+
raw = update,
|
|
153
|
+
sc = sc,
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
await ctx.bot.send_chat_action(update.effective_chat.id, "typing")
|
|
157
|
+
try:
|
|
158
|
+
result = await sc._plugins.fire_app_command(tg_ctx)
|
|
159
|
+
if result:
|
|
160
|
+
try:
|
|
161
|
+
await update.message.reply_text(result, parse_mode="Markdown")
|
|
162
|
+
except Exception:
|
|
163
|
+
await update.message.reply_text(result)
|
|
164
|
+
else:
|
|
165
|
+
await update.message.reply_text(
|
|
166
|
+
f"⚠️ Command `/{cmd_name}` tidak menghasilkan response.",
|
|
167
|
+
parse_mode="Markdown"
|
|
168
|
+
)
|
|
169
|
+
except Exception as e:
|
|
170
|
+
logger.error(f"Error di app_command /{cmd_name}: {e}")
|
|
171
|
+
await update.message.reply_text(f"❌ Error: {e}")
|
|
172
|
+
|
|
173
|
+
handler.__name__ = f"plugin_cmd_{cmd_name}"
|
|
174
|
+
return handler
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
# ── Bot Runner ────────────────────────────────────────────────────────────────
|
|
178
|
+
|
|
179
|
+
def run():
|
|
180
|
+
token = cfg.get("telegram.token", "")
|
|
181
|
+
if not token:
|
|
182
|
+
print("❌ Telegram token not configured. Run: simplecontext-bot setup")
|
|
183
|
+
sys.exit(1)
|
|
184
|
+
|
|
185
|
+
try:
|
|
186
|
+
from telegram import Update, BotCommand
|
|
187
|
+
from telegram.ext import (
|
|
188
|
+
ApplicationBuilder, CommandHandler,
|
|
189
|
+
MessageHandler, filters,
|
|
190
|
+
)
|
|
191
|
+
except ImportError:
|
|
192
|
+
print("❌ python-telegram-bot not installed.")
|
|
193
|
+
print(" Run: pip install python-telegram-bot")
|
|
194
|
+
sys.exit(1)
|
|
195
|
+
|
|
196
|
+
SimpleContext = _load_simplecontext()
|
|
197
|
+
if not SimpleContext:
|
|
198
|
+
print("❌ SimpleContext engine not found. Run: simplecontext-bot setup")
|
|
199
|
+
sys.exit(1)
|
|
200
|
+
|
|
201
|
+
install_dir = Path(cfg.get("install_dir"))
|
|
202
|
+
|
|
203
|
+
sc = SimpleContext(
|
|
204
|
+
storage__backend = "sqlite",
|
|
205
|
+
storage__path = cfg.get("simplecontext.db_path"),
|
|
206
|
+
agents__folder = cfg.get("simplecontext.agents_dir"),
|
|
207
|
+
agents__hot_reload = cfg.get("simplecontext.hot_reload", True),
|
|
208
|
+
agents__default = cfg.get("simplecontext.default_agent", "general"),
|
|
209
|
+
plugins__enabled = False,
|
|
210
|
+
plugins__folder = str(install_dir / "plugins"),
|
|
211
|
+
memory__default_limit = cfg.get("bot.memory_limit", 20),
|
|
212
|
+
debug__retrieval = cfg.get("bot.debug", False),
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
# ── Load semua plugin (auto-scan, tidak tergantung registry) ──────────────
|
|
216
|
+
loaded_plugins = _load_all_plugins(sc, install_dir)
|
|
217
|
+
dynamic_commands = _collect_bot_commands(loaded_plugins)
|
|
218
|
+
|
|
219
|
+
# Inject app_info ke semua plugin — mereka bisa tahu platform & versi bot
|
|
220
|
+
sc._plugins.set_app_info({"platform": "telegram", "version": "1.2.0"})
|
|
221
|
+
|
|
222
|
+
agents = sc._registry.names()
|
|
223
|
+
logger.info(
|
|
224
|
+
f"✅ Bot ready — {len(agents)} agents, "
|
|
225
|
+
f"{len(loaded_plugins)} plugins, "
|
|
226
|
+
f"{len(dynamic_commands)} plugin commands"
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
# ── Helpers ───────────────────────────────────────────────────────────────
|
|
230
|
+
|
|
231
|
+
def _plugin_summary_lines() -> list[str]:
|
|
232
|
+
"""Buat daftar ringkasan plugin yang ter-load untuk ditampilkan di bot."""
|
|
233
|
+
lines = []
|
|
234
|
+
all_plugins = sc._plugins.all()
|
|
235
|
+
if not all_plugins:
|
|
236
|
+
return ["_No plugins active._"]
|
|
237
|
+
for p in all_plugins:
|
|
238
|
+
cmds = p.get_app_commands()
|
|
239
|
+
cmd_str = ""
|
|
240
|
+
if cmds:
|
|
241
|
+
cmd_str = " · commands: " + ", ".join(f"`/{c}`" for c in cmds)
|
|
242
|
+
lines.append(f" ✅ *{p.name}* v{p.version} — {p.description}{cmd_str}")
|
|
243
|
+
return lines
|
|
244
|
+
|
|
245
|
+
# ── Static Handlers ───────────────────────────────────────────────────────
|
|
246
|
+
|
|
247
|
+
async def cmd_start(update: Update, ctx):
|
|
248
|
+
user = update.effective_user
|
|
249
|
+
mem = sc.memory(user.id)
|
|
250
|
+
mem.remember("name", user.first_name)
|
|
251
|
+
if user.username:
|
|
252
|
+
mem.remember("username", f"@{user.username}")
|
|
253
|
+
|
|
254
|
+
agents_list = "\n".join(f" • `{a}`" for a in agents)
|
|
255
|
+
plugin_lines = _plugin_summary_lines()
|
|
256
|
+
plugin_block = ""
|
|
257
|
+
if loaded_plugins:
|
|
258
|
+
plugin_block = "\n\n🔌 *Active plugins:*\n" + "\n".join(plugin_lines)
|
|
259
|
+
|
|
260
|
+
await update.message.reply_text(
|
|
261
|
+
f"👋 Hello *{user.first_name}*\\!\n\n"
|
|
262
|
+
f"I'm powered by *SimpleContext* — an AI brain with memory\\.\n\n"
|
|
263
|
+
f"🤖 *Available agents:*\n{agents_list}"
|
|
264
|
+
f"{plugin_block}\n\n"
|
|
265
|
+
f"Use /plugins to see plugin details\\.",
|
|
266
|
+
parse_mode="MarkdownV2"
|
|
267
|
+
)
|
|
268
|
+
|
|
269
|
+
async def cmd_help(update: Update, ctx):
|
|
270
|
+
static_cmds = (
|
|
271
|
+
"/start — Welcome message\n"
|
|
272
|
+
"/agents — List all agents\n"
|
|
273
|
+
"/agent \\<name\\> — Switch agent\n"
|
|
274
|
+
"/agent auto — Back to auto\\-routing\n"
|
|
275
|
+
"/clear — Clear conversation history\n"
|
|
276
|
+
"/status — Current status\n"
|
|
277
|
+
"/memory — Your saved profile\n"
|
|
278
|
+
"/plugins — Plugin details & available commands\n"
|
|
279
|
+
)
|
|
280
|
+
dynamic_cmd_lines = ""
|
|
281
|
+
if dynamic_commands:
|
|
282
|
+
dynamic_cmd_lines = "\n*Plugin Commands:*\n"
|
|
283
|
+
for cmd_name, info in dynamic_commands.items():
|
|
284
|
+
usage = info.get("usage", f"/{cmd_name}")
|
|
285
|
+
desc = info.get("description", "")
|
|
286
|
+
dynamic_cmd_lines += f"/{cmd_name} — {desc}\n"
|
|
287
|
+
if usage != f"/{cmd_name}":
|
|
288
|
+
dynamic_cmd_lines += f" Usage: `{usage}`\n"
|
|
289
|
+
|
|
290
|
+
await update.message.reply_text(
|
|
291
|
+
"📖 *Commands:*\n\n" + static_cmds + dynamic_cmd_lines,
|
|
292
|
+
parse_mode="MarkdownV2"
|
|
293
|
+
)
|
|
294
|
+
|
|
295
|
+
async def cmd_agents(update: Update, ctx):
|
|
296
|
+
lines = ["🤖 *Available Agents:*\n"]
|
|
297
|
+
for name in agents:
|
|
298
|
+
agent = sc._registry.get(name)
|
|
299
|
+
desc = agent.description if agent else ""
|
|
300
|
+
lines.append(f"• *{name}* — {desc}")
|
|
301
|
+
lines.append("\n_Use /agent \\<name\\> to select one_")
|
|
302
|
+
await update.message.reply_text("\n".join(lines), parse_mode="Markdown")
|
|
303
|
+
|
|
304
|
+
async def cmd_agent(update: Update, ctx):
|
|
305
|
+
uid = update.effective_user.id
|
|
306
|
+
args = ctx.args
|
|
307
|
+
if not args:
|
|
308
|
+
current = sc.memory(uid).recall("preferred_agent", "auto")
|
|
309
|
+
await update.message.reply_text(
|
|
310
|
+
f"Current agent: `{current}`\nUse `/agent <n>` or `/agent auto`",
|
|
311
|
+
parse_mode="Markdown"
|
|
312
|
+
)
|
|
313
|
+
return
|
|
314
|
+
name = args[0].lower()
|
|
315
|
+
if name == "auto":
|
|
316
|
+
sc.router.clear_user_agent(uid)
|
|
317
|
+
await update.message.reply_text("✅ Back to *auto-routing*.", parse_mode="Markdown")
|
|
318
|
+
elif name in agents:
|
|
319
|
+
sc.router.set_user_agent(uid, name)
|
|
320
|
+
await update.message.reply_text(f"✅ Agent set to *{name}*.", parse_mode="Markdown")
|
|
321
|
+
else:
|
|
322
|
+
await update.message.reply_text(
|
|
323
|
+
f"❌ Agent `{name}` not found.\nAvailable: {', '.join(f'`{a}`' for a in agents)}",
|
|
324
|
+
parse_mode="Markdown"
|
|
325
|
+
)
|
|
326
|
+
|
|
327
|
+
async def cmd_clear(update: Update, ctx):
|
|
328
|
+
sc.memory(update.effective_user.id).clear()
|
|
329
|
+
await update.message.reply_text("🗑 Conversation cleared\\. Profile kept\\.", parse_mode="MarkdownV2")
|
|
330
|
+
|
|
331
|
+
async def cmd_status(update: Update, ctx):
|
|
332
|
+
uid = update.effective_user.id
|
|
333
|
+
mem = sc.memory(uid)
|
|
334
|
+
result = sc.router.route(uid, "")
|
|
335
|
+
all_p = sc._plugins.all()
|
|
336
|
+
plugin_str = ", ".join(p.name for p in all_p) if all_p else "None"
|
|
337
|
+
await update.message.reply_text(
|
|
338
|
+
f"📊 *Status*\n\n"
|
|
339
|
+
f"🤖 Agent: `{result.agent_id}`\n"
|
|
340
|
+
f"💬 Messages: `{mem.count()}`\n"
|
|
341
|
+
f"🧠 Agents: `{len(agents)}`\n"
|
|
342
|
+
f"🔌 Plugins: `{plugin_str}`",
|
|
343
|
+
parse_mode="Markdown"
|
|
344
|
+
)
|
|
345
|
+
|
|
346
|
+
async def cmd_memory(update: Update, ctx):
|
|
347
|
+
uid = update.effective_user.id
|
|
348
|
+
profile = sc.memory(uid).get_profile()
|
|
349
|
+
display = {k: v for k, v in profile.items()
|
|
350
|
+
if not k.startswith("_") and k != "preferred_agent"}
|
|
351
|
+
if not display:
|
|
352
|
+
await update.message.reply_text("📝 No profile data saved yet.")
|
|
353
|
+
return
|
|
354
|
+
lines = ["📝 *Your Profile:*\n"]
|
|
355
|
+
for k, v in display.items():
|
|
356
|
+
lines.append(f"• *{k}*: {v}")
|
|
357
|
+
await update.message.reply_text("\n".join(lines), parse_mode="Markdown")
|
|
358
|
+
|
|
359
|
+
async def cmd_plugins(update: Update, ctx):
|
|
360
|
+
"""
|
|
361
|
+
Tampilkan semua plugin aktif (baca dari sc._plugins, bukan registry hardcoded).
|
|
362
|
+
Plugin komunitas yang di-drop manual ke plugins/ juga muncul di sini.
|
|
363
|
+
"""
|
|
364
|
+
lines = ["🔌 *Active Plugins*\n"]
|
|
365
|
+
all_p = sc._plugins.all()
|
|
366
|
+
|
|
367
|
+
if not all_p:
|
|
368
|
+
lines.append("_No plugins loaded._\n")
|
|
369
|
+
lines.append("Drop plugin `.py` ke folder `~/.simplecontext-bot/plugins/` dan restart bot.")
|
|
370
|
+
else:
|
|
371
|
+
for p in all_p:
|
|
372
|
+
cmds = p.get_app_commands()
|
|
373
|
+
lines.append(f"*{p.name}* v{p.version}")
|
|
374
|
+
lines.append(f" {p.description}")
|
|
375
|
+
if cmds:
|
|
376
|
+
for cmd_name, cmd_info in cmds.items():
|
|
377
|
+
lines.append(f" • `/{cmd_name}` — {cmd_info.get('description', '')}")
|
|
378
|
+
lines.append(f" Usage: `{cmd_info.get('usage', '/' + cmd_name)}`")
|
|
379
|
+
lines.append("")
|
|
380
|
+
|
|
381
|
+
lines.append("📖 More plugins: [SimpleContext\\-Plugin](https://github.com/zacxyonly/SimpleContext-Plugin)")
|
|
382
|
+
await update.message.reply_text("\n".join(lines), parse_mode="Markdown")
|
|
383
|
+
|
|
384
|
+
async def handle_message(update: Update, ctx):
|
|
385
|
+
uid = update.effective_user.id
|
|
386
|
+
user = update.effective_user
|
|
387
|
+
text = update.message.text
|
|
388
|
+
|
|
389
|
+
mem = sc.memory(uid)
|
|
390
|
+
if not mem.recall("name"):
|
|
391
|
+
mem.remember("name", user.first_name)
|
|
392
|
+
if user.username:
|
|
393
|
+
mem.remember("username", f"@{user.username}")
|
|
394
|
+
|
|
395
|
+
await ctx.bot.send_chat_action(update.effective_chat.id, "typing")
|
|
396
|
+
|
|
397
|
+
result = sc.router.route(uid, text)
|
|
398
|
+
messages = sc.prepare_messages(uid, text, result)
|
|
399
|
+
logger.info(f"[{user.username or uid}] → agent={result.agent_id}")
|
|
400
|
+
|
|
401
|
+
reply = llm.call(messages, max_tokens=1024)
|
|
402
|
+
|
|
403
|
+
chain_rule = result.should_chain(text)
|
|
404
|
+
if chain_rule and not reply.startswith("❌"):
|
|
405
|
+
await ctx.bot.send_chat_action(update.effective_chat.id, "typing")
|
|
406
|
+
result2 = sc.router.chain(uid, text, reply, chain_rule,
|
|
407
|
+
from_agent_id=result.agent_id)
|
|
408
|
+
messages2 = sc.prepare_messages(uid, text, result2)
|
|
409
|
+
reply = llm.call(messages2, max_tokens=1024)
|
|
410
|
+
reply = sc.process_response(uid, text, reply, result2,
|
|
411
|
+
chain_from=result.agent_id)
|
|
412
|
+
else:
|
|
413
|
+
reply = sc.process_response(uid, text, reply, result)
|
|
414
|
+
|
|
415
|
+
try:
|
|
416
|
+
await update.message.reply_text(reply, parse_mode="Markdown")
|
|
417
|
+
except Exception:
|
|
418
|
+
await update.message.reply_text(reply)
|
|
419
|
+
|
|
420
|
+
# ── Build & Register Handlers ─────────────────────────────────────────────
|
|
421
|
+
|
|
422
|
+
app = ApplicationBuilder().token(token).build()
|
|
423
|
+
|
|
424
|
+
# Static commands
|
|
425
|
+
app.add_handler(CommandHandler("start", cmd_start))
|
|
426
|
+
app.add_handler(CommandHandler("help", cmd_help))
|
|
427
|
+
app.add_handler(CommandHandler("agents", cmd_agents))
|
|
428
|
+
app.add_handler(CommandHandler("agent", cmd_agent))
|
|
429
|
+
app.add_handler(CommandHandler("clear", cmd_clear))
|
|
430
|
+
app.add_handler(CommandHandler("status", cmd_status))
|
|
431
|
+
app.add_handler(CommandHandler("memory", cmd_memory))
|
|
432
|
+
app.add_handler(CommandHandler("plugins", cmd_plugins))
|
|
433
|
+
|
|
434
|
+
# Dynamic commands dari plugin (auto-register)
|
|
435
|
+
for cmd_name, cmd_info in dynamic_commands.items():
|
|
436
|
+
handler_fn = _make_dynamic_handler(sc, cmd_name)
|
|
437
|
+
app.add_handler(CommandHandler(cmd_name, handler_fn))
|
|
438
|
+
logger.info(f" ↳ Telegram handler registered: /{cmd_name}")
|
|
439
|
+
|
|
440
|
+
# Set bot commands list di Telegram (muncul di menu)
|
|
441
|
+
async def post_init(application):
|
|
442
|
+
static = [
|
|
443
|
+
BotCommand("start", "Welcome message"),
|
|
444
|
+
BotCommand("help", "Show all commands"),
|
|
445
|
+
BotCommand("agents", "List available agents"),
|
|
446
|
+
BotCommand("agent", "Switch agent"),
|
|
447
|
+
BotCommand("clear", "Clear conversation history"),
|
|
448
|
+
BotCommand("status", "Show current status"),
|
|
449
|
+
BotCommand("memory", "Show your saved profile"),
|
|
450
|
+
BotCommand("plugins", "List active plugins & their commands"),
|
|
451
|
+
]
|
|
452
|
+
plugin_cmds = [
|
|
453
|
+
BotCommand(cmd_name, info.get("description", "")[:256])
|
|
454
|
+
for cmd_name, info in dynamic_commands.items()
|
|
455
|
+
]
|
|
456
|
+
await application.bot.set_my_commands(static + plugin_cmds)
|
|
457
|
+
|
|
458
|
+
app.post_init = post_init
|
|
459
|
+
|
|
460
|
+
app.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, handle_message))
|
|
461
|
+
|
|
462
|
+
print(f"🚀 Bot is running — {len(loaded_plugins)} plugins, {len(dynamic_commands)} plugin commands")
|
|
463
|
+
if dynamic_commands:
|
|
464
|
+
print(f" Plugin commands: {', '.join('/' + c for c in dynamic_commands)}")
|
|
465
|
+
app.run_polling(drop_pending_updates=True)
|