openagent-framework 0.2.7__tar.gz → 0.2.8__tar.gz

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 (79) hide show
  1. {openagent_framework-0.2.7 → openagent_framework-0.2.8}/PKG-INFO +1 -1
  2. openagent_framework-0.2.8/openagent/channels/__init__.py +22 -0
  3. {openagent_framework-0.2.7 → openagent_framework-0.2.8}/openagent/channels/base.py +80 -0
  4. openagent_framework-0.2.8/openagent/channels/commands.py +172 -0
  5. openagent_framework-0.2.8/openagent/channels/discord.py +421 -0
  6. openagent_framework-0.2.8/openagent/channels/queue.py +171 -0
  7. openagent_framework-0.2.8/openagent/channels/telegram.py +380 -0
  8. {openagent_framework-0.2.7 → openagent_framework-0.2.8}/openagent/channels/whatsapp.py +121 -44
  9. {openagent_framework-0.2.7 → openagent_framework-0.2.8}/openagent/server.py +16 -1
  10. {openagent_framework-0.2.7 → openagent_framework-0.2.8}/openagent_framework.egg-info/PKG-INFO +1 -1
  11. {openagent_framework-0.2.7 → openagent_framework-0.2.8}/openagent_framework.egg-info/SOURCES.txt +2 -0
  12. {openagent_framework-0.2.7 → openagent_framework-0.2.8}/pyproject.toml +1 -1
  13. openagent_framework-0.2.7/openagent/channels/__init__.py +0 -3
  14. openagent_framework-0.2.7/openagent/channels/discord.py +0 -151
  15. openagent_framework-0.2.7/openagent/channels/telegram.py +0 -225
  16. {openagent_framework-0.2.7 → openagent_framework-0.2.8}/docs/README.md +0 -0
  17. {openagent_framework-0.2.7 → openagent_framework-0.2.8}/openagent/__init__.py +0 -0
  18. {openagent_framework-0.2.7 → openagent_framework-0.2.8}/openagent/agent.py +0 -0
  19. {openagent_framework-0.2.7 → openagent_framework-0.2.8}/openagent/bootstrap.py +0 -0
  20. {openagent_framework-0.2.7 → openagent_framework-0.2.8}/openagent/channels/senders.py +0 -0
  21. {openagent_framework-0.2.7 → openagent_framework-0.2.8}/openagent/cli.py +0 -0
  22. {openagent_framework-0.2.7 → openagent_framework-0.2.8}/openagent/config.py +0 -0
  23. {openagent_framework-0.2.7 → openagent_framework-0.2.8}/openagent/mcp/__init__.py +0 -0
  24. {openagent_framework-0.2.7 → openagent_framework-0.2.8}/openagent/mcp/client.py +0 -0
  25. {openagent_framework-0.2.7 → openagent_framework-0.2.8}/openagent/mcp/oauth.py +0 -0
  26. {openagent_framework-0.2.7 → openagent_framework-0.2.8}/openagent/mcps/chrome-devtools/.gitignore +0 -0
  27. {openagent_framework-0.2.7 → openagent_framework-0.2.8}/openagent/mcps/chrome-devtools/package.json +0 -0
  28. {openagent_framework-0.2.7 → openagent_framework-0.2.8}/openagent/mcps/computer-control/.gitignore +0 -0
  29. {openagent_framework-0.2.7 → openagent_framework-0.2.8}/openagent/mcps/computer-control/package.json +0 -0
  30. {openagent_framework-0.2.7 → openagent_framework-0.2.8}/openagent/mcps/computer-control/src/index.ts +0 -0
  31. {openagent_framework-0.2.7 → openagent_framework-0.2.8}/openagent/mcps/computer-control/src/main.ts +0 -0
  32. {openagent_framework-0.2.7 → openagent_framework-0.2.8}/openagent/mcps/computer-control/src/tools/computer.ts +0 -0
  33. {openagent_framework-0.2.7 → openagent_framework-0.2.8}/openagent/mcps/computer-control/src/tools/index.ts +0 -0
  34. {openagent_framework-0.2.7 → openagent_framework-0.2.8}/openagent/mcps/computer-control/src/utils/response.ts +0 -0
  35. {openagent_framework-0.2.7 → openagent_framework-0.2.8}/openagent/mcps/computer-control/src/xdotoolStringToKeys.ts +0 -0
  36. {openagent_framework-0.2.7 → openagent_framework-0.2.8}/openagent/mcps/computer-control/tsconfig.json +0 -0
  37. {openagent_framework-0.2.7 → openagent_framework-0.2.8}/openagent/mcps/editor/.gitignore +0 -0
  38. {openagent_framework-0.2.7 → openagent_framework-0.2.8}/openagent/mcps/editor/package.json +0 -0
  39. {openagent_framework-0.2.7 → openagent_framework-0.2.8}/openagent/mcps/editor/src/index.ts +0 -0
  40. {openagent_framework-0.2.7 → openagent_framework-0.2.8}/openagent/mcps/editor/tsconfig.json +0 -0
  41. {openagent_framework-0.2.7 → openagent_framework-0.2.8}/openagent/mcps/messaging/.gitignore +0 -0
  42. {openagent_framework-0.2.7 → openagent_framework-0.2.8}/openagent/mcps/messaging/index.ts +0 -0
  43. {openagent_framework-0.2.7 → openagent_framework-0.2.8}/openagent/mcps/messaging/package.json +0 -0
  44. {openagent_framework-0.2.7 → openagent_framework-0.2.8}/openagent/mcps/messaging/tsconfig.json +0 -0
  45. {openagent_framework-0.2.7 → openagent_framework-0.2.8}/openagent/mcps/shell/.gitignore +0 -0
  46. {openagent_framework-0.2.7 → openagent_framework-0.2.8}/openagent/mcps/shell/package.json +0 -0
  47. {openagent_framework-0.2.7 → openagent_framework-0.2.8}/openagent/mcps/shell/src/index.ts +0 -0
  48. {openagent_framework-0.2.7 → openagent_framework-0.2.8}/openagent/mcps/shell/tsconfig.json +0 -0
  49. {openagent_framework-0.2.7 → openagent_framework-0.2.8}/openagent/mcps/web-search/.gitignore +0 -0
  50. {openagent_framework-0.2.7 → openagent_framework-0.2.8}/openagent/mcps/web-search/package.json +0 -0
  51. {openagent_framework-0.2.7 → openagent_framework-0.2.8}/openagent/mcps/web-search/src/browser-pool.ts +0 -0
  52. {openagent_framework-0.2.7 → openagent_framework-0.2.8}/openagent/mcps/web-search/src/content-extractor.ts +0 -0
  53. {openagent_framework-0.2.7 → openagent_framework-0.2.8}/openagent/mcps/web-search/src/enhanced-content-extractor.ts +0 -0
  54. {openagent_framework-0.2.7 → openagent_framework-0.2.8}/openagent/mcps/web-search/src/index.ts +0 -0
  55. {openagent_framework-0.2.7 → openagent_framework-0.2.8}/openagent/mcps/web-search/src/rate-limiter.ts +0 -0
  56. {openagent_framework-0.2.7 → openagent_framework-0.2.8}/openagent/mcps/web-search/src/search-engine.ts +0 -0
  57. {openagent_framework-0.2.7 → openagent_framework-0.2.8}/openagent/mcps/web-search/src/types.ts +0 -0
  58. {openagent_framework-0.2.7 → openagent_framework-0.2.8}/openagent/mcps/web-search/src/utils.ts +0 -0
  59. {openagent_framework-0.2.7 → openagent_framework-0.2.8}/openagent/mcps/web-search/tsconfig.json +0 -0
  60. {openagent_framework-0.2.7 → openagent_framework-0.2.8}/openagent/memory/__init__.py +0 -0
  61. {openagent_framework-0.2.7 → openagent_framework-0.2.8}/openagent/memory/db.py +0 -0
  62. {openagent_framework-0.2.7 → openagent_framework-0.2.8}/openagent/memory/manager.py +0 -0
  63. {openagent_framework-0.2.7 → openagent_framework-0.2.8}/openagent/models/__init__.py +0 -0
  64. {openagent_framework-0.2.7 → openagent_framework-0.2.8}/openagent/models/base.py +0 -0
  65. {openagent_framework-0.2.7 → openagent_framework-0.2.8}/openagent/models/claude_api.py +0 -0
  66. {openagent_framework-0.2.7 → openagent_framework-0.2.8}/openagent/models/claude_cli.py +0 -0
  67. {openagent_framework-0.2.7 → openagent_framework-0.2.8}/openagent/models/zhipu.py +0 -0
  68. {openagent_framework-0.2.7 → openagent_framework-0.2.8}/openagent/prompts.py +0 -0
  69. {openagent_framework-0.2.7 → openagent_framework-0.2.8}/openagent/scheduler.py +0 -0
  70. {openagent_framework-0.2.7 → openagent_framework-0.2.8}/openagent/service.py +0 -0
  71. {openagent_framework-0.2.7 → openagent_framework-0.2.8}/openagent/services/__init__.py +0 -0
  72. {openagent_framework-0.2.7 → openagent_framework-0.2.8}/openagent/services/base.py +0 -0
  73. {openagent_framework-0.2.7 → openagent_framework-0.2.8}/openagent/services/manager.py +0 -0
  74. {openagent_framework-0.2.7 → openagent_framework-0.2.8}/openagent/services/syncthing.py +0 -0
  75. {openagent_framework-0.2.7 → openagent_framework-0.2.8}/openagent_framework.egg-info/dependency_links.txt +0 -0
  76. {openagent_framework-0.2.7 → openagent_framework-0.2.8}/openagent_framework.egg-info/entry_points.txt +0 -0
  77. {openagent_framework-0.2.7 → openagent_framework-0.2.8}/openagent_framework.egg-info/requires.txt +0 -0
  78. {openagent_framework-0.2.7 → openagent_framework-0.2.8}/openagent_framework.egg-info/top_level.txt +0 -0
  79. {openagent_framework-0.2.7 → openagent_framework-0.2.8}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: openagent-framework
3
- Version: 0.2.7
3
+ Version: 0.2.8
4
4
  Summary: Simplified LLM agent framework with MCP, memory, and multi-channel support
5
5
  License-Expression: MIT
6
6
  Requires-Python: >=3.11
@@ -0,0 +1,22 @@
1
+ from openagent.channels.base import (
2
+ Attachment,
3
+ BLOCKED_EXTENSIONS,
4
+ BaseChannel,
5
+ is_blocked_attachment,
6
+ parse_response_markers,
7
+ split_preserving_code_blocks,
8
+ )
9
+ from openagent.channels.commands import CommandDispatcher, CommandResult
10
+ from openagent.channels.queue import UserQueueManager
11
+
12
+ __all__ = [
13
+ "Attachment",
14
+ "BLOCKED_EXTENSIONS",
15
+ "BaseChannel",
16
+ "CommandDispatcher",
17
+ "CommandResult",
18
+ "UserQueueManager",
19
+ "is_blocked_attachment",
20
+ "parse_response_markers",
21
+ "split_preserving_code_blocks",
22
+ ]
@@ -74,6 +74,86 @@ def format_attachments_for_prompt(attachments: list[Attachment], caption: str =
74
74
  return prefix
75
75
 
76
76
 
77
+ # File extensions we refuse to download from any channel for basic safety.
78
+ # Shell scripts are NOT on this list — an agent is expected to deal with them
79
+ # legitimately. This is about Windows executables and obviously malicious
80
+ # droppers, not about blocking all code.
81
+ BLOCKED_EXTENSIONS: frozenset[str] = frozenset({
82
+ ".exe", ".bat", ".cmd", ".com", ".msi", ".scr", ".pif",
83
+ ".vbs", ".vbe", ".jse", ".ws", ".wsf", ".wsh", ".ps1", ".hta",
84
+ ".cpl", ".lnk", ".reg", ".jar",
85
+ })
86
+
87
+
88
+ def is_blocked_attachment(filename: str | None) -> bool:
89
+ """Return True if the filename has a blocked extension (case-insensitive)."""
90
+ if not filename:
91
+ return False
92
+ return Path(filename).suffix.lower() in BLOCKED_EXTENSIONS
93
+
94
+
95
+ def split_preserving_code_blocks(text: str, max_len: int) -> list[str]:
96
+ """Split *text* into chunks of ≤ ``max_len`` characters, preserving
97
+ fenced ``` code blocks so no chunk ends with a dangling fence.
98
+
99
+ Strategy:
100
+
101
+ 1. Walk the text in windows of at most ``max_len`` chars, cutting on a
102
+ newline when possible.
103
+ 2. For each chunk, count the unescaped ``` fences. If odd, close with a
104
+ trailing ``` and prepend ``` to the next chunk so the code style
105
+ carries over to the reader.
106
+
107
+ This loses the original language tag after a mid-block split — Discord
108
+ and Telegram render ``` (no lang) as a plain monospace block, which is
109
+ still the right thing for long output.
110
+ """
111
+ if max_len <= 0:
112
+ return [text] if text else []
113
+ if len(text) <= max_len:
114
+ return [text] if text.strip() else []
115
+
116
+ chunks: list[str] = []
117
+ carry_prefix = ""
118
+ i = 0
119
+ n = len(text)
120
+
121
+ while i < n:
122
+ budget = max_len - len(carry_prefix)
123
+ if budget <= 16:
124
+ # carry_prefix too large vs max_len; fall back to hard cut
125
+ budget = max(max_len // 2, 16)
126
+ end = min(i + budget, n)
127
+ if end < n:
128
+ # prefer newline cut, then space, else hard cut
129
+ nl = text.rfind("\n", i, end)
130
+ if nl > i + budget // 4:
131
+ end = nl
132
+ else:
133
+ sp = text.rfind(" ", i, end)
134
+ if sp > i + budget // 4:
135
+ end = sp
136
+
137
+ body = text[i:end]
138
+ chunk = carry_prefix + body
139
+
140
+ fence_total = chunk.count("```")
141
+ if fence_total % 2 == 1:
142
+ chunk = chunk + "\n```"
143
+ carry_prefix = "```\n"
144
+ else:
145
+ carry_prefix = ""
146
+
147
+ if chunk.strip():
148
+ chunks.append(chunk)
149
+
150
+ i = end
151
+ while i < n and text[i] in ("\n", " "):
152
+ i += 1
153
+
154
+ return chunks
155
+
156
+
77
157
  class BaseChannel(ABC):
78
158
  """Abstract base for messaging channels (Telegram, Discord, WhatsApp, etc.).
79
159
 
@@ -0,0 +1,172 @@
1
+ """Cross-channel slash command dispatcher.
2
+
3
+ All channels (Telegram, Discord, WhatsApp) share a single command
4
+ vocabulary via ``CommandDispatcher``. Commands are parsed with a leading
5
+ ``/`` (e.g. ``/new``, ``/stop``, ``/status``, ``/queue clear``) and resolve
6
+ to a :class:`CommandResult` the channel renders in its native way.
7
+
8
+ The registered commands are:
9
+
10
+ - ``/new`` — reset the user's session (fresh context)
11
+ - ``/reset`` — alias of /new
12
+ - ``/stop`` — cancel the currently running task for the user
13
+ - ``/status`` — busy/idle + queue depth + session id tail
14
+ - ``/queue`` — show queue state; ``/queue clear`` empties it
15
+ - ``/help`` — list of commands
16
+ - ``/usage`` — Claude Code usage via ``ccusage`` (claude-cli backend only)
17
+
18
+ Unknown commands return ``None`` so the channel can either fall through to
19
+ normal message handling or reject them.
20
+ """
21
+
22
+ from __future__ import annotations
23
+
24
+ import asyncio
25
+ import logging
26
+ import shutil
27
+ from dataclasses import dataclass
28
+ from typing import TYPE_CHECKING
29
+
30
+ if TYPE_CHECKING:
31
+ from openagent.agent import Agent
32
+ from openagent.channels.queue import UserQueueManager
33
+
34
+ logger = logging.getLogger(__name__)
35
+
36
+
37
+ HELP_TEXT = (
38
+ "Comandi disponibili:\n"
39
+ "• /new — nuova sessione (contesto fresco)\n"
40
+ "• /stop — ferma l'operazione in corso\n"
41
+ "• /status — stato (busy/idle + coda)\n"
42
+ "• /queue — messaggi in coda\n"
43
+ "• /queue clear — svuota la coda\n"
44
+ "• /usage — uso Claude Code (solo backend claude-cli)\n"
45
+ "• /help — questo messaggio"
46
+ )
47
+
48
+
49
+ @dataclass
50
+ class CommandResult:
51
+ """Result of a command. Rendered verbatim by the channel."""
52
+ text: str
53
+ is_error: bool = False
54
+
55
+
56
+ class CommandDispatcher:
57
+ """Parse and dispatch slash commands for a single channel instance."""
58
+
59
+ def __init__(self, agent: Agent, queue: UserQueueManager):
60
+ self.agent = agent
61
+ self.queue = queue
62
+
63
+ @staticmethod
64
+ def is_command(text: str | None) -> bool:
65
+ return bool(text) and text.lstrip().startswith("/")
66
+
67
+ @staticmethod
68
+ def parse(text: str) -> tuple[str, str]:
69
+ """Return (command, argument) stripped of the leading slash."""
70
+ body = text.lstrip()[1:]
71
+ parts = body.split(maxsplit=1)
72
+ cmd = parts[0].lower() if parts else ""
73
+ # Telegram-style @botname suffix: /new@mybot → /new
74
+ if "@" in cmd:
75
+ cmd = cmd.split("@", 1)[0]
76
+ arg = parts[1] if len(parts) > 1 else ""
77
+ return cmd, arg
78
+
79
+ async def dispatch(self, text: str, user_id: str) -> CommandResult | None:
80
+ """Run a command. Returns None if the text isn't a known command."""
81
+ if not self.is_command(text):
82
+ return None
83
+ cmd, arg = self.parse(text)
84
+ method = getattr(self, f"cmd_{cmd.replace('-', '_')}", None)
85
+ if method is None:
86
+ return None
87
+ try:
88
+ return await method(arg, user_id)
89
+ except Exception as e: # noqa: BLE001
90
+ logger.exception("Command /%s failed", cmd)
91
+ return CommandResult(f"Errore eseguendo /{cmd}: {e}", is_error=True)
92
+
93
+ # ── commands ───────────────────────────────────────────────────────
94
+
95
+ async def cmd_new(self, arg: str, user_id: str) -> CommandResult:
96
+ self.queue.reset_session(user_id)
97
+ return CommandResult("🆕 Nuova sessione avviata. Contesto precedente archiviato.")
98
+
99
+ async def cmd_reset(self, arg: str, user_id: str) -> CommandResult:
100
+ return await self.cmd_new(arg, user_id)
101
+
102
+ async def cmd_stop(self, arg: str, user_id: str) -> CommandResult:
103
+ stopped = self.queue.stop_current(user_id)
104
+ if stopped:
105
+ return CommandResult("⏹ Operazione in corso cancellata.")
106
+ return CommandResult("Nessuna operazione in corso.")
107
+
108
+ async def cmd_status(self, arg: str, user_id: str) -> CommandResult:
109
+ busy = self.queue.is_busy(user_id)
110
+ depth = self.queue.queue_depth(user_id)
111
+ sid = self.queue.get_session_id(user_id)
112
+ state = "🟢 busy" if busy else "⚪ idle"
113
+ lines = [
114
+ "Stato:",
115
+ f"• Agent: {self.agent.name}",
116
+ f"• Stato: {state}",
117
+ f"• In coda: {depth}",
118
+ f"• Sessione: …{sid[-8:]}",
119
+ ]
120
+ return CommandResult("\n".join(lines))
121
+
122
+ async def cmd_queue(self, arg: str, user_id: str) -> CommandResult:
123
+ if arg.strip().lower() in {"clear", "clean", "reset"}:
124
+ n = self.queue.clear_queue(user_id)
125
+ return CommandResult(f"🧹 Coda svuotata ({n} messaggi rimossi).")
126
+ depth = self.queue.queue_depth(user_id)
127
+ busy = self.queue.is_busy(user_id)
128
+ if depth == 0 and not busy:
129
+ return CommandResult("Nessun messaggio in coda.")
130
+ tail = " (operazione in corso)" if busy else ""
131
+ return CommandResult(f"📋 In coda: {depth} messaggi{tail}")
132
+
133
+ async def cmd_help(self, arg: str, user_id: str) -> CommandResult:
134
+ return CommandResult(HELP_TEXT)
135
+
136
+ async def cmd_usage(self, arg: str, user_id: str) -> CommandResult:
137
+ from openagent.models.claude_cli import ClaudeCLI
138
+ if not isinstance(self.agent.model, ClaudeCLI):
139
+ return CommandResult(
140
+ "ℹ️ /usage funziona solo con il backend claude-cli.",
141
+ is_error=True,
142
+ )
143
+ npx = shutil.which("npx")
144
+ if not npx:
145
+ return CommandResult(
146
+ "❌ npx non trovato sul PATH (serve Node.js per ccusage).",
147
+ is_error=True,
148
+ )
149
+ try:
150
+ proc = await asyncio.create_subprocess_exec(
151
+ npx, "-y", "ccusage@latest",
152
+ stdout=asyncio.subprocess.PIPE,
153
+ stderr=asyncio.subprocess.PIPE,
154
+ )
155
+ try:
156
+ stdout, stderr = await asyncio.wait_for(proc.communicate(), timeout=45)
157
+ except asyncio.TimeoutError:
158
+ proc.kill()
159
+ return CommandResult("❌ ccusage timeout (45s).", is_error=True)
160
+ except Exception as e: # noqa: BLE001
161
+ return CommandResult(f"❌ Impossibile lanciare ccusage: {e}", is_error=True)
162
+
163
+ output = stdout.decode(errors="replace").strip()
164
+ if not output:
165
+ output = stderr.decode(errors="replace").strip()
166
+ if not output:
167
+ return CommandResult("❌ ccusage non ha restituito output.", is_error=True)
168
+ # Keep it under 3900 chars to stay under Discord/Telegram limits when
169
+ # wrapped in a code fence.
170
+ if len(output) > 3800:
171
+ output = output[:3800] + "\n… (troncato)"
172
+ return CommandResult(f"```\n{output}\n```")