merco 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.
Files changed (62) hide show
  1. cli/__init__.py +5 -0
  2. cli/commands.py +3 -0
  3. cli/main.py +539 -0
  4. cli/tui.py +10 -0
  5. merco/__init__.py +4 -0
  6. merco/core/__init__.py +21 -0
  7. merco/core/agent.py +554 -0
  8. merco/core/config.py +152 -0
  9. merco/core/context.py +63 -0
  10. merco/core/llm.py +200 -0
  11. merco/core/message.py +45 -0
  12. merco/core/pipeline.py +568 -0
  13. merco/core/self_healing.py +216 -0
  14. merco/core/session.py +46 -0
  15. merco/gateway/__init__.py +5 -0
  16. merco/gateway/base.py +38 -0
  17. merco/gateway/discord.py +24 -0
  18. merco/gateway/telegram.py +25 -0
  19. merco/hooks/__init__.py +5 -0
  20. merco/hooks/chat_hooks.py +22 -0
  21. merco/hooks/lifecycle.py +23 -0
  22. merco/hooks/registry.py +40 -0
  23. merco/hooks/tool_hooks.py +22 -0
  24. merco/memory/__init__.py +6 -0
  25. merco/memory/compressor.py +168 -0
  26. merco/memory/recall.py +38 -0
  27. merco/memory/search.py +35 -0
  28. merco/memory/store.py +69 -0
  29. merco/observability/__init__.py +6 -0
  30. merco/observability/audit.py +39 -0
  31. merco/observability/logger.py +34 -0
  32. merco/observability/metrics.py +57 -0
  33. merco/observability/tracing.py +67 -0
  34. merco/sandbox/__init__.py +5 -0
  35. merco/sandbox/isolation.py +36 -0
  36. merco/sandbox/permissions.py +46 -0
  37. merco/sandbox/security.py +34 -0
  38. merco/scheduler/__init__.py +5 -0
  39. merco/scheduler/cron.py +100 -0
  40. merco/scheduler/delivery.py +30 -0
  41. merco/scheduler/jobs.py +65 -0
  42. merco/skills/__init__.py +6 -0
  43. merco/skills/builtin/__init__.py +1 -0
  44. merco/skills/loader.py +69 -0
  45. merco/skills/registry.py +48 -0
  46. merco/tools/__init__.py +60 -0
  47. merco/tools/base.py +57 -0
  48. merco/tools/bash_tools.py +51 -0
  49. merco/tools/file_tools.py +137 -0
  50. merco/tools/mcp_tools.py +55 -0
  51. merco/tools/registry.py +78 -0
  52. merco/tools/skill_tools.py +73 -0
  53. merco/tools/task_tools.py +37 -0
  54. merco/tools/web_tools.py +67 -0
  55. merco/utils/__init__.py +1 -0
  56. merco/utils/helpers.py +40 -0
  57. merco-0.1.0.dist-info/METADATA +101 -0
  58. merco-0.1.0.dist-info/RECORD +62 -0
  59. merco-0.1.0.dist-info/WHEEL +4 -0
  60. merco-0.1.0.dist-info/entry_points.txt +3 -0
  61. web/__init__.py +5 -0
  62. web/app.py +27 -0
cli/__init__.py ADDED
@@ -0,0 +1,5 @@
1
+ """CLI 入口"""
2
+
3
+ from .main import app
4
+
5
+ __all__ = ["app"]
cli/commands.py ADDED
@@ -0,0 +1,3 @@
1
+ """CLI 命令定义"""
2
+
3
+ # 命令将在 main.py 中统一定义
cli/main.py ADDED
@@ -0,0 +1,539 @@
1
+ """CLI 主入口"""
2
+
3
+ import asyncio
4
+ import logging
5
+ import os
6
+ import readline
7
+ import signal
8
+ import typer
9
+ from rich.console import Console
10
+ from rich.markdown import Markdown
11
+ from rich.panel import Panel
12
+
13
+ console = Console()
14
+
15
+ app = typer.Typer(
16
+ name="merco",
17
+ help="Mercury Code — AI 驱动的自改进软件开发平台",
18
+ add_completion=False,
19
+ )
20
+
21
+
22
+ # ── 启动首页 Dashboard ──────────────────────────────────────────────
23
+
24
+ from abc import ABC, abstractmethod
25
+ import merco
26
+
27
+ class DashboardSection(ABC):
28
+ """首页展示区块基类。新增条目:继承 + 实现 render() + dashboard.use()"""
29
+ name: str = ""
30
+
31
+ @abstractmethod
32
+ def render(self, agent, **ctx) -> str | None:
33
+ """返回一行 Rich 标记文本,None 则跳过"""
34
+ ...
35
+
36
+
37
+ class WelcomeSection(DashboardSection):
38
+ name = "welcome"
39
+ def render(self, agent, **ctx) -> str:
40
+ return f"[bold green]Mercury Code v{merco.__version__}[/bold green]"
41
+
42
+
43
+ class ModelSection(DashboardSection):
44
+ name = "model"
45
+ def render(self, agent, **ctx) -> str:
46
+ return f"模型: {agent.config.model.provider}/{agent.config.model.model}"
47
+
48
+
49
+ class ToolsSection(DashboardSection):
50
+ name = "tools"
51
+
52
+ def __init__(self, max_display: int = 5):
53
+ self.max_display = max_display
54
+
55
+ def render(self, agent, **ctx) -> str:
56
+ tools = agent.tool_registry.list_tools() if agent.tool_registry else []
57
+ active = [t.name for t in tools if t.check()]
58
+ if not active:
59
+ return "工具: [dim]无[/dim]"
60
+ shown = active[:self.max_display]
61
+ line = f"工具: [bold]{', '.join(shown)}[/bold]"
62
+ if len(active) > self.max_display:
63
+ line += f" [dim]等 {len(active)} 个[/dim]"
64
+ return line
65
+
66
+
67
+ class SkillsSection(DashboardSection):
68
+ name = "skills"
69
+
70
+ def __init__(self, max_display: int = 3):
71
+ self.max_display = max_display
72
+
73
+ def render(self, agent, **ctx) -> str:
74
+ registry = getattr(agent, "skill_registry", None)
75
+ if not registry:
76
+ return "技能: [dim]无[/dim]"
77
+ skills = registry.list_skills()
78
+ if not skills:
79
+ return "技能: [dim]无[/dim]"
80
+ names = [s["name"] for s in skills[:self.max_display]]
81
+ line = f"技能: [bold]{', '.join(names)}[/bold]"
82
+ if len(skills) > self.max_display:
83
+ line += f" [dim]等 {len(skills)} 个[/dim]"
84
+ return line
85
+
86
+
87
+ class ConfigSection(DashboardSection):
88
+ name = "config"
89
+
90
+ def render(self, agent, **ctx) -> str:
91
+ return f"配置: [dim]{ctx.get('config_source', '默认值')}[/dim]"
92
+
93
+
94
+ class HintSection(DashboardSection):
95
+ name = "hint"
96
+
97
+ def render(self, agent, **ctx) -> str:
98
+ return "[dim]输入消息开始对话,/help 查看命令,/exit 退出[/dim]"
99
+
100
+
101
+ class Dashboard:
102
+ """首页渲染器。按 use() 顺序渲染各区块。"""
103
+ def __init__(self):
104
+ self._sections: list[DashboardSection] = []
105
+
106
+ def use(self, section: DashboardSection) -> "Dashboard":
107
+ self._sections.append(section)
108
+ return self
109
+
110
+ def render(self, agent, **ctx) -> str:
111
+ parts = []
112
+ for s in self._sections:
113
+ try:
114
+ line = s.render(agent, **ctx)
115
+ if line:
116
+ parts.append(line)
117
+ except Exception:
118
+ parts.append(f"[dim]({s.name}: 渲染失败)[/dim]")
119
+ return "\n".join(parts)
120
+
121
+
122
+ # ── 共享的 Agent 启动逻辑 ────────────────────────────────────────────────
123
+
124
+ def _setup_agent(config_path: str | None, model: str | None, api_key: str | None, debug: bool):
125
+ from merco.core.config import MercoConfig
126
+ from merco.core.agent import Agent
127
+ import merco
128
+ from merco.tools import discover_tools, tool_registry
129
+ discover_tools()
130
+
131
+ if debug:
132
+ logging.basicConfig(
133
+ level=logging.WARNING, # 全局默认 WARNING,不污染第三方库
134
+ format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
135
+ datefmt="%H:%M:%S",
136
+ )
137
+ logging.getLogger("merco").setLevel(logging.DEBUG)
138
+ console.print("[yellow]🔍 调试模式已开启[/yellow]")
139
+ else:
140
+ logging.basicConfig(level=logging.WARNING)
141
+
142
+ cfg = MercoConfig.load(config_path)
143
+ if model:
144
+ cfg.model.model = model
145
+ if api_key:
146
+ cfg.model.api_key = api_key
147
+
148
+ if not cfg.model.api_key:
149
+ env_key = os.environ.get("OPENAI_API_KEY") or os.environ.get("OPENROUTER_API_KEY")
150
+ if env_key:
151
+ cfg.model.api_key = env_key
152
+ else:
153
+ candidates = ["./merco.json", "~/.config/merco/config.json"]
154
+ searched = "\n".join(f" • {c}" for c in candidates)
155
+ console.print(Panel(
156
+ f"[red]未找到 API Key。请设置:\n"
157
+ f"1. 在 merco.json 中设置 model.api_key\n"
158
+ f"2. 或设置环境变量(OPENAI_API_KEY / OPENROUTER_API_KEY 等)\n"
159
+ f"3. 或使用 merco -k sk-... 启动\n\n"
160
+ f"[dim]已搜索配置文件:[/dim]\n{searched}[/red]",
161
+ title="配置错误",
162
+ ))
163
+ raise typer.Exit(1)
164
+
165
+ # tools auto-registered via discover_tools()
166
+
167
+ # ── 技能注册 ──
168
+ from merco.skills.registry import SkillRegistry
169
+ skill_registry = SkillRegistry()
170
+ if cfg.skills_paths:
171
+ skill_registry.load_from_paths(cfg.skills_paths)
172
+
173
+ # 注入 skill_registry 给 SkillViewTool(动态描述 + 可用性检查)
174
+ sv = tool_registry.get("skill_view")
175
+ if sv and hasattr(sv, "set_skill_registry"):
176
+ sv.set_skill_registry(skill_registry)
177
+
178
+ agent = Agent(config=cfg, tool_registry=tool_registry,
179
+ skill_registry=skill_registry)
180
+
181
+ # 显示加载的配置来源
182
+ config_source = "默认值"
183
+ for candidate in ["./merco.json", "./.merco/merco.json",
184
+ os.path.expanduser("~/.config/merco/config.json")]:
185
+ if os.path.exists(candidate):
186
+ config_source = candidate
187
+ break
188
+
189
+ dashboard = (Dashboard()
190
+ .use(WelcomeSection())
191
+ .use(ModelSection())
192
+ .use(ToolsSection(max_display=5))
193
+ .use(SkillsSection(max_display=3))
194
+ .use(ConfigSection())
195
+ .use(HintSection()))
196
+
197
+ console.print(Panel(
198
+ dashboard.render(agent, config_source=config_source),
199
+ title="🚀 Mercury Code",
200
+ ))
201
+ return agent
202
+
203
+
204
+ # ── 输入区 PromptDecorator ─────────────────────────────────────
205
+
206
+ class PromptDecorator(ABC):
207
+ """输入区装饰器基类。新增:继承 + 实现 render() + prompt_area.use()"""
208
+ name: str = ""
209
+
210
+ def render(self, agent) -> str | None:
211
+ """返回输入上方的展示文本,None 跳过"""
212
+ return None
213
+
214
+ def get_prompt(self) -> str:
215
+ """返回输入提示符"""
216
+ return "> "
217
+
218
+
219
+ class ContextBar(PromptDecorator):
220
+ """上下文用量进度条 — 半高薄款"""
221
+ name = "context_bar"
222
+ _W = 16
223
+
224
+ def render(self, agent) -> str:
225
+ stats = agent.get_context_stats()
226
+ thresh_p = int(stats["threshold"] * self._W)
227
+ filled_n = int(stats["ratio"] * self._W)
228
+ bar = "▐"
229
+ for i in range(self._W):
230
+ if i == thresh_p:
231
+ bar += "│"
232
+ elif i < filled_n:
233
+ bar += "█"
234
+ else:
235
+ bar += "░"
236
+ bar += "▌"
237
+
238
+ color = "dim"
239
+ if stats["ratio"] > stats["threshold"]:
240
+ color = "yellow"
241
+ if stats["ratio"] > 0.95:
242
+ color = "red"
243
+ est = "~" if stats["is_estimate"] else ""
244
+ cur = stats["current"]; mx = stats["max"]
245
+ def _f(n): return str(n) if n < 1024 else f"{n/1024:.1f}K"
246
+ return f" [{color}]{bar}[/{color}] {est}{_f(cur)}/{_f(mx)}"
247
+
248
+ def get_prompt(self) -> str:
249
+ return "▸ "
250
+
251
+
252
+ class PromptArea:
253
+ """输入区渲染器。按 use() 顺序渲染各装饰器。"""
254
+ def __init__(self):
255
+ self._decorators: list[PromptDecorator] = []
256
+
257
+ def use(self, d: PromptDecorator) -> "PromptArea":
258
+ self._decorators.append(d)
259
+ return self
260
+
261
+ def render(self, agent) -> tuple[str, str]:
262
+ pre_parts = []
263
+ prompt = "> "
264
+ for d in self._decorators:
265
+ try:
266
+ line = d.render(agent)
267
+ if line:
268
+ pre_parts.append(line)
269
+ prompt = d.get_prompt()
270
+ except Exception:
271
+ pre_parts.append(f"[dim]({d.name}: render failed)[/dim]")
272
+ return "\n".join(pre_parts), prompt
273
+
274
+ def _render_context_bar(stats: dict) -> str:
275
+ """渲染 token 用量进度条 — 阈值标记在中间"""
276
+ w = 10
277
+ thresh_p = int(stats["threshold"] * w)
278
+ filled_n = int(stats["ratio"] * w)
279
+ bar = "▕"
280
+ for i in range(w):
281
+ if i == thresh_p:
282
+ bar += "│"
283
+ elif i < filled_n:
284
+ bar += "█"
285
+ else:
286
+ bar += "░"
287
+ bar += "▏"
288
+
289
+ color = "dim"
290
+ if stats["ratio"] > stats["threshold"]:
291
+ color = "yellow"
292
+ if stats["ratio"] > 0.95:
293
+ color = "red"
294
+ est = "~" if stats["is_estimate"] else ""
295
+ tool_info = f"🔧 {stats['tool_count']}/{stats['max_tool_calls']}"
296
+ cur = stats["current"]; mx = stats["max"]
297
+ def _f(n): return str(n) if n < 1024 else f"{n/1024:.1f}K"
298
+ return f" [{color}]{bar}[/{color}] {est}{_f(cur)}/{_f(mx)} {tool_info}"
299
+
300
+
301
+ # ── REPL 交互循环 ────────────────────────────────────────────────────────
302
+
303
+ def run_repl(agent):
304
+ import termios
305
+
306
+ try:
307
+ old_tc = termios.tcgetattr(0)
308
+ except termios.error:
309
+ old_tc = None
310
+
311
+ if old_tc is not None:
312
+ new_tc = termios.tcgetattr(0)
313
+ new_tc[3] = new_tc[3] & ~termios.ECHOCTL
314
+ try:
315
+ termios.tcsetattr(0, termios.TCSADRAIN, new_tc)
316
+ except termios.error:
317
+ pass
318
+
319
+ _exit_hooks = []
320
+
321
+ def _on_exit(fn):
322
+ _exit_hooks.append(fn)
323
+
324
+ def _run_exit_hooks():
325
+ for hook in reversed(_exit_hooks):
326
+ try:
327
+ hook()
328
+ except Exception:
329
+ pass
330
+
331
+ if old_tc is not None:
332
+ _on_exit(lambda: termios.tcsetattr(0, termios.TCSADRAIN, old_tc))
333
+
334
+ async def repl():
335
+ loop = asyncio.get_running_loop()
336
+ current_task: asyncio.Task | None = None
337
+ exit_count = 0
338
+
339
+ def handle_interrupt():
340
+ nonlocal current_task, exit_count
341
+ if current_task and not current_task.done():
342
+ current_task.cancel()
343
+ else:
344
+ exit_count += 1
345
+ if exit_count == 1:
346
+ console.print("\n[yellow]再按 Ctrl+C 退出,或输入 /exit。[/yellow]")
347
+ else:
348
+ console.print("\n[dim]再见![/dim]")
349
+ try:
350
+ loop.remove_signal_handler(signal.SIGINT)
351
+ except Exception:
352
+ pass
353
+ _run_exit_hooks()
354
+ os._exit(0)
355
+
356
+ for sig in (signal.SIGINT, signal.SIGTERM):
357
+ loop.add_signal_handler(sig, handle_interrupt)
358
+
359
+ try:
360
+ while True:
361
+ try:
362
+ prompt_area = (PromptArea()
363
+ .use(ContextBar()))
364
+ pre_text, prompt = prompt_area.render(agent)
365
+ console.print(f"\n{pre_text}")
366
+ user_input = await asyncio.to_thread(input, prompt)
367
+ user_input = user_input.strip()
368
+ exit_count = 0 # 正常输入,重置计数
369
+
370
+ if not user_input:
371
+ continue
372
+
373
+ if user_input.startswith("/"):
374
+ if await handle_command(user_input, agent):
375
+ continue
376
+ else:
377
+ break
378
+
379
+ console.rule("[bold]Agent[/bold]", style="dim")
380
+ current_task = asyncio.current_task()
381
+ response = await agent.run(user_input)
382
+ current_task = None
383
+
384
+ console.print(Panel(Markdown(response), border_style="dim"))
385
+ console.rule(style="dim")
386
+
387
+ except asyncio.CancelledError:
388
+ console.rule(style="dim")
389
+ console.print("\n[dim]操作已取消。再按一次 Ctrl+C 退出。[/dim]")
390
+ current_task = None
391
+ except EOFError:
392
+ console.print("\n[dim]再见![/dim]")
393
+ break
394
+ except KeyboardInterrupt:
395
+ console.print("\n[dim]再见![/dim]")
396
+ break
397
+ except Exception as e:
398
+ current_task = None
399
+ console.print(f"[red]错误: {e}[/red]")
400
+ finally:
401
+ for sig in (signal.SIGINT, signal.SIGTERM):
402
+ try:
403
+ loop.remove_signal_handler(sig)
404
+ except (NotImplementedError, RuntimeError):
405
+ pass
406
+
407
+ try:
408
+ asyncio.run(repl())
409
+ except KeyboardInterrupt:
410
+ pass
411
+ finally:
412
+ _run_exit_hooks()
413
+
414
+
415
+ # ── 回调:无子命令时进入交互模式 ─────────────────────────────────────────
416
+
417
+ @app.callback(invoke_without_command=True)
418
+ def main_callback(
419
+ ctx: typer.Context,
420
+ config: str = typer.Option(None, "--config", "-c", help="配置文件路径"),
421
+ model: str = typer.Option(None, "--model", "-m", help="指定模型"),
422
+ api_key: str = typer.Option(None, "--api-key", "-k", help="API Key"),
423
+ debug: bool = typer.Option(False, "--debug", "-d", help="开启调试日志"),
424
+ ):
425
+ if ctx.invoked_subcommand is not None:
426
+ return
427
+ agent = _setup_agent(config, model, api_key, debug)
428
+ run_repl(agent)
429
+
430
+
431
+ # ── 子命令 ────────────────────────────────────────────────────────────────
432
+
433
+ @app.command("run")
434
+ def run_cmd(
435
+ config: str = typer.Option(None, "--config", "-c", help="配置文件路径"),
436
+ model: str = typer.Option(None, "--model", "-m", help="指定模型"),
437
+ api_key: str = typer.Option(None, "--api-key", "-k", help="API Key"),
438
+ debug: bool = typer.Option(False, "--debug", "-d", help="开启调试日志"),
439
+ ):
440
+ agent = _setup_agent(config, model, api_key, debug)
441
+ run_repl(agent)
442
+
443
+
444
+ @app.command("init")
445
+ def init_cmd(path: str = typer.Argument(".", help="项目路径")):
446
+ from pathlib import Path
447
+ from merco.core.config import MercoConfig
448
+
449
+ config_path = Path(path) / "merco.json"
450
+ if config_path.exists():
451
+ console.print(f"[yellow]配置已存在: {config_path}[/yellow]")
452
+ return
453
+
454
+ cfg = MercoConfig()
455
+ cfg.save(str(config_path))
456
+ console.print(f"[green]已创建配置: {config_path}[/green]")
457
+
458
+
459
+ @app.command("skills")
460
+ def skills_cmd(
461
+ list: bool = typer.Option(False, "--list", "-l", help="列出已加载技能"),
462
+ path: str = typer.Option(None, "--path", "-p", help="技能目录路径"),
463
+ ):
464
+ from merco.skills.loader import SkillLoader
465
+ from merco.skills.registry import SkillRegistry
466
+
467
+ if list:
468
+ registry = SkillRegistry()
469
+ if path:
470
+ registry.load_from_paths([path])
471
+ else:
472
+ registry.load_from_paths(["./.merco/skills", "~/.config/merco/skills"])
473
+
474
+ skills = registry.list_skills()
475
+ if skills:
476
+ console.print(f"[bold]已加载 {len(skills)} 个技能:[/bold]")
477
+ for skill in skills:
478
+ console.print(f" - {skill['name']}: {skill['description']}")
479
+ else:
480
+ console.print("未加载任何技能")
481
+
482
+
483
+ # ── 命令处理 ──────────────────────────────────────────────────────────────
484
+
485
+ async def handle_command(cmd: str, agent) -> bool:
486
+ parts = cmd.split(maxsplit=1)
487
+ command = parts[0].lower()
488
+
489
+ if command in ("/exit", "/quit", "/q"):
490
+ console.print("[dim]再见![/dim]")
491
+ return False
492
+
493
+ elif command == "/help":
494
+ console.print(Panel(
495
+ "[bold]可用命令[/bold]\n\n"
496
+ "/help - 显示此帮助\n"
497
+ "/exit - 退出\n"
498
+ "/new - 新会话\n"
499
+ "/model - 显示当前模型\n"
500
+ "/tools - 列出可用工具\n"
501
+ "/context - 上下文用量\n"
502
+ "/skills - 列出已加载技能",
503
+ title="帮助",
504
+ ))
505
+ return True
506
+
507
+ elif command == "/new":
508
+ agent.reset()
509
+ console.print("[dim]已开启新会话[/dim]")
510
+ return True
511
+
512
+ elif command == "/model":
513
+ console.print(f"当前模型: {agent.config.model.provider}/{agent.config.model.model}")
514
+ return True
515
+
516
+ elif command == "/context":
517
+ stats = agent.get_context_stats()
518
+ bar = ContextBar()
519
+ console.print(bar.render(agent))
520
+ console.print(f" 阈值: {int(stats['threshold']*100)}% | 模型推算: {'是' if stats['is_estimate'] else '否(API 实测)'}")
521
+ return True
522
+
523
+ elif command == "/tools":
524
+ tools = agent.tool_registry.list_tools() if agent.tool_registry else []
525
+ if tools:
526
+ console.print("[bold]可用工具:[/bold]")
527
+ for tool in tools:
528
+ console.print(f" - {tool.name}: {tool.description}")
529
+ else:
530
+ console.print("无可用工具")
531
+ return True
532
+
533
+ else:
534
+ console.print(f"[dim]未知命令: {command},输入 /help 查看帮助[/dim]")
535
+ return True
536
+
537
+
538
+ if __name__ == "__main__":
539
+ app()
cli/tui.py ADDED
@@ -0,0 +1,10 @@
1
+ """TUI 终端界面"""
2
+
3
+ import typer
4
+
5
+
6
+ def run_tui():
7
+ """启动 TUI 界面"""
8
+ # TODO: 集成 Textual 或 Rich 实现 TUI
9
+ print("TUI mode - coming soon")
10
+ print("For now, use CLI mode: merco run")
merco/__init__.py ADDED
@@ -0,0 +1,4 @@
1
+ """Mercury Code — lightweight AI coding assistant 驱动的自改进软件开发平台"""
2
+
3
+ __version__ = "0.1.0"
4
+ __author__ = "Mercury Code Contributors"
merco/core/__init__.py ADDED
@@ -0,0 +1,21 @@
1
+ """Mercury Code 核心引擎"""
2
+
3
+ from .agent import Agent, AgentLoop
4
+ from .config import MercoConfig, ModelConfig
5
+ from .session import Session, SessionStore
6
+ from .message import Message, MessageRole
7
+ from .context import ContextManager
8
+ from .llm import LLMClient
9
+
10
+ __all__ = [
11
+ "Agent",
12
+ "AgentLoop",
13
+ "MercoConfig",
14
+ "ModelConfig",
15
+ "Session",
16
+ "SessionStore",
17
+ "Message",
18
+ "MessageRole",
19
+ "ContextManager",
20
+ "LLMClient",
21
+ ]