hyperagents 1.2.3rc5.post3__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 (361) hide show
  1. agents/__init__.py +31 -0
  2. agents/background_monitor.py +453 -0
  3. agents/learning.py +460 -0
  4. agents/planner.py +421 -0
  5. agents/transcriber.py +341 -0
  6. civilization/__init__.py +51 -0
  7. civilization/api/__init__.py +7 -0
  8. civilization/api/civilization_api.py +361 -0
  9. civilization/graph/__init__.py +19 -0
  10. civilization/graph/knowledge_linker.py +348 -0
  11. civilization/graph/org_graph.py +348 -0
  12. civilization/graph/process_graph.py +386 -0
  13. civilization/ingestion/__init__.py +20 -0
  14. civilization/ingestion/chunker.py +273 -0
  15. civilization/ingestion/document_ingestor.py +358 -0
  16. civilization/ingestion/embedder.py +262 -0
  17. civilization/ingestion/extractor.py +224 -0
  18. civilization/interview/__init__.py +19 -0
  19. civilization/interview/gap_detector.py +380 -0
  20. civilization/interview/interviewer.py +323 -0
  21. civilization/interview/questioner.py +363 -0
  22. civilization/retrieval/__init__.py +17 -0
  23. civilization/retrieval/civilization_rag.py +316 -0
  24. civilization/retrieval/context_injector.py +342 -0
  25. civilization/retrieval/relevance_ranker.py +287 -0
  26. civilization/schema.py +299 -0
  27. civilization/store.py +177 -0
  28. civilization/sync/__init__.py +16 -0
  29. civilization/sync/confluence_sync.py +317 -0
  30. civilization/sync/gdrive_sync.py +262 -0
  31. civilization/sync/notion_sync.py +237 -0
  32. civilization/versioning/__init__.py +19 -0
  33. civilization/versioning/diff_engine.py +351 -0
  34. civilization/versioning/staleness_detector.py +303 -0
  35. civilization/versioning/version_manager.py +293 -0
  36. cli/__init__.py +1 -0
  37. cli/doctor.py +334 -0
  38. cli/hyperclaw.py +1357 -0
  39. cli/onboarding.py +469 -0
  40. core/__init__.py +1 -0
  41. core/connectors/__init__.py +119 -0
  42. core/connectors/infrastructure.py +772 -0
  43. core/connectors/messaging.py +586 -0
  44. core/connectors/productivity.py +764 -0
  45. core/connectors/smarthome.py +528 -0
  46. core/connectors/social.py +730 -0
  47. core/hyperrouter/__init__.py +23 -0
  48. core/hyperrouter/bandit.py +218 -0
  49. core/hyperrouter/fast_loop.py +68 -0
  50. core/hyperrouter/slow_loop.py +143 -0
  51. core/hyperstate/__init__.py +24 -0
  52. core/hyperstate/certifier.py +154 -0
  53. core/hyperstate/schema.py +160 -0
  54. core/hyperstate/state_manager.py +103 -0
  55. core/hyperstate/store.py +163 -0
  56. core/providers/__init__.py +121 -0
  57. core/providers/embeddings.py +389 -0
  58. core/providers/image.py +534 -0
  59. core/providers/llm.py +1935 -0
  60. core/providers/search.py +608 -0
  61. core/providers/voice.py +474 -0
  62. hyperagents-1.2.3rc5.post3.dist-info/METADATA +469 -0
  63. hyperagents-1.2.3rc5.post3.dist-info/RECORD +361 -0
  64. hyperagents-1.2.3rc5.post3.dist-info/WHEEL +5 -0
  65. hyperagents-1.2.3rc5.post3.dist-info/entry_points.txt +6 -0
  66. hyperagents-1.2.3rc5.post3.dist-info/licenses/LICENSE +21 -0
  67. hyperagents-1.2.3rc5.post3.dist-info/top_level.txt +13 -0
  68. hyperclaw/__init__.py +67 -0
  69. hyperclaw/__main__.py +220 -0
  70. hyperclaw/agent.py +775 -0
  71. hyperclaw/agent_coordinator.py +577 -0
  72. hyperclaw/anthropic_params.py +225 -0
  73. hyperclaw/api_utils.py +85 -0
  74. hyperclaw/approval_telegram.py +209 -0
  75. hyperclaw/approval_terminal.py +80 -0
  76. hyperclaw/attachments.py +177 -0
  77. hyperclaw/branding.py +55 -0
  78. hyperclaw/brief/__init__.py +11 -0
  79. hyperclaw/brief/cli.py +204 -0
  80. hyperclaw/brief/collect.py +146 -0
  81. hyperclaw/brief/compose.py +333 -0
  82. hyperclaw/brief/diff.py +110 -0
  83. hyperclaw/brief/errors.py +99 -0
  84. hyperclaw/brief/google.py +269 -0
  85. hyperclaw/brief/google_auth.py +294 -0
  86. hyperclaw/brief/links.py +124 -0
  87. hyperclaw/brief/service.py +424 -0
  88. hyperclaw/brief/store.py +205 -0
  89. hyperclaw/brief/tasks.py +200 -0
  90. hyperclaw/chat_scope.py +115 -0
  91. hyperclaw/cli_tui.py +497 -0
  92. hyperclaw/client_guard.py +717 -0
  93. hyperclaw/connection_check.py +113 -0
  94. hyperclaw/cost_guard.py +1728 -0
  95. hyperclaw/default_config/agents.yaml +534 -0
  96. hyperclaw/default_config/hyperclaw.yaml +27 -0
  97. hyperclaw/default_config/models.yaml +161 -0
  98. hyperclaw/deployment.py +39 -0
  99. hyperclaw/digest/__init__.py +9 -0
  100. hyperclaw/digest/cli.py +117 -0
  101. hyperclaw/digest/errors.py +100 -0
  102. hyperclaw/digest/gitlab.py +187 -0
  103. hyperclaw/digest/http.py +113 -0
  104. hyperclaw/digest/redact.py +32 -0
  105. hyperclaw/digest/service.py +271 -0
  106. hyperclaw/digest/slack.py +223 -0
  107. hyperclaw/digest/state.py +124 -0
  108. hyperclaw/digest/summarize.py +198 -0
  109. hyperclaw/digest/tokens.py +143 -0
  110. hyperclaw/distribution.py +46 -0
  111. hyperclaw/documents.py +351 -0
  112. hyperclaw/feeds.py +339 -0
  113. hyperclaw/google_workspace/__init__.py +5 -0
  114. hyperclaw/google_workspace/api.py +151 -0
  115. hyperclaw/google_workspace/auth.py +397 -0
  116. hyperclaw/google_workspace/calendar.py +157 -0
  117. hyperclaw/google_workspace/cli.py +72 -0
  118. hyperclaw/google_workspace/drive.py +236 -0
  119. hyperclaw/google_workspace/errors.py +58 -0
  120. hyperclaw/google_workspace/gmail.py +295 -0
  121. hyperclaw/google_workspace/meet.py +122 -0
  122. hyperclaw/google_workspace/tools.py +319 -0
  123. hyperclaw/hyperspeed.py +57 -0
  124. hyperclaw/integrations_layer.py +1427 -0
  125. hyperclaw/live_state.py +188 -0
  126. hyperclaw/managed_config.py +245 -0
  127. hyperclaw/media_hub.py +179 -0
  128. hyperclaw/memory_manager.py +584 -0
  129. hyperclaw/model_catalog.py +551 -0
  130. hyperclaw/model_policy.py +211 -0
  131. hyperclaw/model_router.py +595 -0
  132. hyperclaw/model_selector.py +72 -0
  133. hyperclaw/naming.py +284 -0
  134. hyperclaw/notices.py +65 -0
  135. hyperclaw/oai_stream.py +106 -0
  136. hyperclaw/orchestrator.py +591 -0
  137. hyperclaw/outbox.py +82 -0
  138. hyperclaw/persona.py +367 -0
  139. hyperclaw/prometheus.py +219 -0
  140. hyperclaw/prompt_assembly.py +157 -0
  141. hyperclaw/provider_setup.py +146 -0
  142. hyperclaw/providers.py +340 -0
  143. hyperclaw/qualification.py +341 -0
  144. hyperclaw/qualification_keys/hyper-d292fd127cef5a3e.pub +1 -0
  145. hyperclaw/qualification_records/anthropic--claude-sonnet-5-5.json +14 -0
  146. hyperclaw/reasoning_display.py +35 -0
  147. hyperclaw/scheduler.py +454 -0
  148. hyperclaw/server.py +847 -0
  149. hyperclaw/setup.py +627 -0
  150. hyperclaw/skills.py +717 -0
  151. hyperclaw/solomon.py +442 -0
  152. hyperclaw/swarm.py +533 -0
  153. hyperclaw/taint.py +832 -0
  154. hyperclaw/team/__init__.py +28 -0
  155. hyperclaw/team/dispatcher.py +431 -0
  156. hyperclaw/team/genesis.py +106 -0
  157. hyperclaw/team/lead.py +110 -0
  158. hyperclaw/team/model.py +135 -0
  159. hyperclaw/team/morning.py +238 -0
  160. hyperclaw/team/repos.py +66 -0
  161. hyperclaw/team/roster.py +264 -0
  162. hyperclaw/team/specialist.py +235 -0
  163. hyperclaw/team/status.py +163 -0
  164. hyperclaw/team/watchers.py +112 -0
  165. hyperclaw/teamsetup/__init__.py +17 -0
  166. hyperclaw/teamsetup/checks.py +208 -0
  167. hyperclaw/teamsetup/item.py +391 -0
  168. hyperclaw/teamsetup/oauth_loopback.py +84 -0
  169. hyperclaw/teamsetup/onboard.py +550 -0
  170. hyperclaw/teamsetup/profile.py +82 -0
  171. hyperclaw/teamsetup/secrets.py +313 -0
  172. hyperclaw/teamsetup/selfcheck.py +295 -0
  173. hyperclaw/teamsetup/service.py +495 -0
  174. hyperclaw/teamsetup/slack_oauth.py +442 -0
  175. hyperclaw/teamsetup/tty.py +62 -0
  176. hyperclaw/telegram_bot.py +1106 -0
  177. hyperclaw/telegram_intake.py +126 -0
  178. hyperclaw/telegram_pairing.py +360 -0
  179. hyperclaw/terminal_provider.py +323 -0
  180. hyperclaw/tools/__init__.py +1 -0
  181. hyperclaw/tools/approval.py +949 -0
  182. hyperclaw/tools/args.py +212 -0
  183. hyperclaw/tools/catalog.py +244 -0
  184. hyperclaw/tools/classify.py +266 -0
  185. hyperclaw/tools/effects.py +724 -0
  186. hyperclaw/tools/egress.py +210 -0
  187. hyperclaw/tools/envelope.py +188 -0
  188. hyperclaw/tools/guard.py +367 -0
  189. hyperclaw/tools/ledger.py +339 -0
  190. hyperclaw/tools/receipts.py +70 -0
  191. hyperclaw/tools/registry.py +318 -0
  192. hyperclaw/tools/service_guard.py +151 -0
  193. hyperclaw/tools/surfaces.py +54 -0
  194. hyperclaw/tools/workspace.py +87 -0
  195. hyperclaw/trading/__init__.py +77 -0
  196. hyperclaw/truth_guard.py +791 -0
  197. hyperclaw/tts.py +124 -0
  198. hyperclaw/tui.py +4255 -0
  199. hyperclaw/tui_bridge.py +938 -0
  200. hyperclaw/unattended.py +60 -0
  201. hyperclaw/updates/__init__.py +54 -0
  202. hyperclaw/updates/check.py +131 -0
  203. hyperclaw/updates/index.py +178 -0
  204. hyperclaw/updates/install.py +110 -0
  205. hyperclaw/updates/managed.py +161 -0
  206. hyperclaw/updates/manifest.py +138 -0
  207. hyperclaw/updates/notices.py +86 -0
  208. hyperclaw/updates/paths.py +53 -0
  209. hyperclaw/updates/release.py +146 -0
  210. hyperclaw/updates/upgrade.py +253 -0
  211. hyperclaw/updates/versioning.py +104 -0
  212. hyperclaw/userconfig.py +119 -0
  213. hyperclaw/xai.py +83 -0
  214. integrations/__init__.py +31 -0
  215. integrations/automation/__init__.py +1 -0
  216. integrations/automation/make.py +87 -0
  217. integrations/automation/zapier.py +87 -0
  218. integrations/base.py +476 -0
  219. integrations/communication/__init__.py +1 -0
  220. integrations/communication/twilio.py +279 -0
  221. integrations/communication/zoom.py +290 -0
  222. integrations/data/__init__.py +1 -0
  223. integrations/data/google_analytics.py +135 -0
  224. integrations/data/postgres.py +182 -0
  225. integrations/data/supabase_connector.py +202 -0
  226. integrations/data/weather_feeds.py +450 -0
  227. integrations/developer/__init__.py +9 -0
  228. integrations/developer/github.py +406 -0
  229. integrations/developer/gitlab.py +298 -0
  230. integrations/enterprise/__init__.py +13 -0
  231. integrations/enterprise/confluence.py +266 -0
  232. integrations/enterprise/hubspot.py +293 -0
  233. integrations/enterprise/jira.py +314 -0
  234. integrations/enterprise/salesforce.py +286 -0
  235. integrations/finance/__init__.py +1 -0
  236. integrations/finance/polymarket_client.py +247 -0
  237. integrations/finance/quickbooks.py +281 -0
  238. integrations/finance/stripe.py +261 -0
  239. integrations/gateway.py +238 -0
  240. integrations/messaging/__init__.py +32 -0
  241. integrations/messaging/discord.py +159 -0
  242. integrations/messaging/email.py +401 -0
  243. integrations/messaging/imessage.py +443 -0
  244. integrations/messaging/signal.py +175 -0
  245. integrations/messaging/slack.py +217 -0
  246. integrations/messaging/sms.py +175 -0
  247. integrations/messaging/teams.py +208 -0
  248. integrations/messaging/telegram.py +260 -0
  249. integrations/messaging/whatsapp.py +203 -0
  250. integrations/productivity/__init__.py +17 -0
  251. integrations/productivity/airtable.py +217 -0
  252. integrations/productivity/asana.py +248 -0
  253. integrations/productivity/google/__init__.py +19 -0
  254. integrations/productivity/google/base.py +89 -0
  255. integrations/productivity/google/calendar.py +272 -0
  256. integrations/productivity/google/docs.py +202 -0
  257. integrations/productivity/google/drive.py +268 -0
  258. integrations/productivity/google/gmail.py +279 -0
  259. integrations/productivity/google/meet.py +218 -0
  260. integrations/productivity/google/sheets.py +222 -0
  261. integrations/productivity/google/tasks.py +199 -0
  262. integrations/productivity/linear.py +279 -0
  263. integrations/productivity/microsoft/__init__.py +13 -0
  264. integrations/productivity/microsoft/base.py +93 -0
  265. integrations/productivity/microsoft/calendar.py +207 -0
  266. integrations/productivity/microsoft/onedrive.py +222 -0
  267. integrations/productivity/microsoft/outlook.py +241 -0
  268. integrations/productivity/microsoft/sharepoint.py +211 -0
  269. integrations/productivity/notion.py +243 -0
  270. integrations/productivity/todoist.py +250 -0
  271. integrations/productivity/trello.py +244 -0
  272. integrations/storage/__init__.py +1 -0
  273. integrations/storage/box.py +279 -0
  274. integrations/storage/dropbox.py +215 -0
  275. integrations/telegram_inbound.py +179 -0
  276. integrations/telegram_poller.py +151 -0
  277. memory/__init__.py +1 -0
  278. memory/causal_graph.py +125 -0
  279. memory/impact_tracker.py +88 -0
  280. memory/vector_memory.py +436 -0
  281. models/__init__.py +12 -0
  282. models/chatjimmy_client.py +210 -0
  283. models/claude_client.py +235 -0
  284. models/claude_code_subagent.py +192 -0
  285. models/openai_client.py +120 -0
  286. models/router.py +162 -0
  287. recursive/discovery_loop.py +118 -0
  288. recursive/optimization_loop.py +135 -0
  289. recursive/validation_loop.py +38 -0
  290. security/__init__.py +1 -0
  291. security/audit_logger.py +106 -0
  292. security/filesystem_guard.py +100 -0
  293. security/hypershield.py +99 -0
  294. security/network_guard.py +97 -0
  295. security/policy_engine.py +198 -0
  296. swarm/__init__.py +1 -0
  297. swarm/agents/__init__.py +0 -0
  298. swarm/agents/base.py +230 -0
  299. swarm/agents/business/__init__.py +0 -0
  300. swarm/agents/business/counsel.py +31 -0
  301. swarm/agents/business/herald.py +32 -0
  302. swarm/agents/business/ledger.py +26 -0
  303. swarm/agents/business/nexus.py +30 -0
  304. swarm/agents/business/ops.py +30 -0
  305. swarm/agents/business/pipeline.py +28 -0
  306. swarm/agents/business/revenue.py +30 -0
  307. swarm/agents/business/sovereign.py +31 -0
  308. swarm/agents/business/strategos.py +27 -0
  309. swarm/agents/business/talent.py +27 -0
  310. swarm/agents/business/venture.py +32 -0
  311. swarm/agents/comms/__init__.py +1 -0
  312. swarm/agents/comms/cipher.py +32 -0
  313. swarm/agents/comms/echo_comms.py +31 -0
  314. swarm/agents/comms/envoy.py +31 -0
  315. swarm/agents/comms/herald_scheduler.py +33 -0
  316. swarm/agents/comms/pulse.py +29 -0
  317. swarm/agents/creative/__init__.py +0 -0
  318. swarm/agents/creative/author.py +27 -0
  319. swarm/agents/creative/lens.py +25 -0
  320. swarm/agents/executive/__init__.py +1 -0
  321. swarm/agents/intelligence/__init__.py +1 -0
  322. swarm/agents/intelligence/arbiter.py +30 -0
  323. swarm/agents/intelligence/sentinel.py +30 -0
  324. swarm/agents/media/__init__.py +0 -0
  325. swarm/agents/personal/__init__.py +0 -0
  326. swarm/agents/personal/atlas.py +27 -0
  327. swarm/agents/personal/hearth.py +26 -0
  328. swarm/agents/personal/midas.py +27 -0
  329. swarm/agents/personal/navigator.py +27 -0
  330. swarm/agents/personal/nourish.py +31 -0
  331. swarm/agents/personal/vitals.py +33 -0
  332. swarm/agents/recursive/__init__.py +0 -0
  333. swarm/agents/recursive/alchemist.py +73 -0
  334. swarm/agents/recursive/calibrator.py +45 -0
  335. swarm/agents/recursive/scout.py +97 -0
  336. swarm/agents/scientific/__init__.py +0 -0
  337. swarm/agents/scientific/cosmos.py +36 -0
  338. swarm/agents/scientific/gaia.py +26 -0
  339. swarm/agents/scientific/medicus.py +33 -0
  340. swarm/agents/scientific/oracle_agent.py +39 -0
  341. swarm/agents/scientific/scribe.py +39 -0
  342. swarm/agents/talent/__init__.py +0 -0
  343. swarm/agents/talent/deal.py +28 -0
  344. swarm/agents/talent/roster.py +29 -0
  345. swarm/agents/talent/scout.py +29 -0
  346. swarm/agents/talent/stage.py +27 -0
  347. swarm/agents/tech/__init__.py +0 -0
  348. swarm/agents/tech/aegis.py +28 -0
  349. swarm/agents/tech/bridge.py +28 -0
  350. swarm/agents/tech/forge.py +28 -0
  351. swarm/agents/trading/__init__.py +1 -0
  352. swarm/agents/trading/global_prediction_engine.py +611 -0
  353. swarm/agents/trading/polymarket_trader.py +165 -0
  354. swarm/agents/trading/prediction_strategist.py +218 -0
  355. swarm/autogen_bridge.py +68 -0
  356. swarm/bid_protocol.py +196 -0
  357. swarm/dispatcher.py +527 -0
  358. swarm/nexus.py +181 -0
  359. swarm/registry.py +112 -0
  360. telegram_listener.py +403 -0
  361. ui/tui/app.py +214 -0
telegram_listener.py ADDED
@@ -0,0 +1,403 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ HyperClaw Telegram Listener
4
+ Full duplex Telegram integration — receives messages, images, voice, files
5
+ Processes commands, routes to agents, downloads attachments, syncs with HyperClaw
6
+ """
7
+
8
+ import os
9
+ import sys
10
+ import json
11
+ import asyncio
12
+ import logging
13
+ import aiohttp
14
+ import aiofiles
15
+ from pathlib import Path
16
+ from typing import Optional
17
+ from datetime import datetime
18
+ import anthropic
19
+ from hyperclaw.api_utils import extract_text
20
+ from hyperclaw.cost_guard import max_output
21
+
22
+
23
+ def _load_env():
24
+ """Populate os.environ from ~/.hyperclaw/.env so the listener works
25
+ under launchd, where no shell profile runs. Existing env vars win."""
26
+ env_file = Path.home() / ".hyperclaw" / ".env"
27
+ if env_file.exists():
28
+ for line in env_file.read_text().splitlines():
29
+ line = line.strip()
30
+ if line and not line.startswith("#") and "=" in line:
31
+ k, _, v = line.partition("=")
32
+ os.environ.setdefault(k.strip(), v.strip().strip('"'))
33
+
34
+
35
+ _load_env()
36
+
37
+ # ── Config ─────────────────────────────────────────────────────────────────────
38
+ BOT_TOKEN = os.environ.get("TELEGRAM_BOT_TOKEN", "")
39
+ # No explicit allowlist -> trust the owner's own chat (TELEGRAM_CHAT_ID),
40
+ # so a fresh install isn't locked out of its own bot.
41
+ _allowed = os.environ.get("TELEGRAM_ALLOWED_IDS", "") or os.environ.get("TELEGRAM_CHAT_ID", "0")
42
+ ALLOWED_CHAT_IDS = [int(x) for x in _allowed.split(",") if x.strip()]
43
+ ANTHROPIC_API_KEY = os.environ.get("ANTHROPIC_API_KEY", "")
44
+ BASE_URL = f"https://api.telegram.org/bot{BOT_TOKEN}"
45
+ FILE_URL = f"https://api.telegram.org/file/bot{BOT_TOKEN}"
46
+ DOWNLOAD_DIR = Path(str(Path.home() / ".hyperclaw/workspace/telegram_inbox"))
47
+ LOG_FILE = Path(str(Path.home() / ".hyperclaw/logs/telegram_listener.log"))
48
+
49
+ # ── Logging ────────────────────────────────────────────────────────────────────
50
+ LOG_FILE.parent.mkdir(parents=True, exist_ok=True)
51
+ DOWNLOAD_DIR.mkdir(parents=True, exist_ok=True)
52
+
53
+ logging.basicConfig(
54
+ level=logging.INFO,
55
+ format="%(asctime)s [%(levelname)s] %(message)s",
56
+ handlers=[
57
+ logging.FileHandler(LOG_FILE),
58
+ logging.StreamHandler(sys.stdout)
59
+ ]
60
+ )
61
+ log = logging.getLogger("hyperclaw.telegram")
62
+
63
+ # ── Anthropic client ───────────────────────────────────────────────────────────
64
+ from hyperclaw.cost_guard import guard_client # noqa: E402
65
+ client = guard_client(anthropic.Anthropic(api_key=ANTHROPIC_API_KEY), "anthropic")
66
+
67
+ # ── Message history for context ────────────────────────────────────────────────
68
+ # Per chat: one chat's conversation must never appear in another chat's
69
+ # context (spec §6 guest fence).
70
+ conversation_history: dict = {}
71
+
72
+ # ── Send message to Telegram ───────────────────────────────────────────────────
73
+ async def send_message(chat_id: int, text: str, session: aiohttp.ClientSession):
74
+ """Send text message to Telegram"""
75
+ # Split long messages
76
+ if len(text) > 4000:
77
+ chunks = [text[i:i+4000] for i in range(0, len(text), 4000)]
78
+ for chunk in chunks:
79
+ await send_message(chat_id, chunk, session)
80
+ await asyncio.sleep(0.5)
81
+ return
82
+
83
+ url = f"{BASE_URL}/sendMessage"
84
+ payload = {
85
+ "chat_id": chat_id,
86
+ "text": text,
87
+ "parse_mode": "Markdown",
88
+ "link_preview_options": {"is_disabled": True}, # link previews off
89
+ }
90
+ try:
91
+ async with session.post(url, json=payload) as resp:
92
+ result = await resp.json()
93
+ if not result.get("ok"):
94
+ # Try without markdown if parse error
95
+ payload["parse_mode"] = "HTML"
96
+ async with session.post(url, json=payload) as resp2:
97
+ pass
98
+ except Exception as e:
99
+ log.error(f"Failed to send message: {e}")
100
+
101
+ # ── Download file from Telegram ────────────────────────────────────────────────
102
+ async def download_file(file_id: str, session: aiohttp.ClientSession, suffix: str = "") -> Optional[Path]:
103
+ """Download a file from Telegram and save to disk"""
104
+ try:
105
+ # Get file path
106
+ url = f"{BASE_URL}/getFile"
107
+ async with session.get(url, params={"file_id": file_id}) as resp:
108
+ data = await resp.json()
109
+ if not data.get("ok"):
110
+ log.error(f"Failed to get file path: {data}")
111
+ return None
112
+ file_path = data["result"]["file_path"]
113
+
114
+ # Download file
115
+ download_url = f"{FILE_URL}/{file_path}"
116
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
117
+ filename = f"{timestamp}_{file_path.split('/')[-1]}"
118
+ if suffix and not filename.endswith(suffix):
119
+ filename += suffix
120
+
121
+ local_path = DOWNLOAD_DIR / filename
122
+
123
+ async with session.get(download_url) as resp:
124
+ async with aiofiles.open(local_path, 'wb') as f:
125
+ await f.write(await resp.read())
126
+
127
+ log.info(f"Downloaded: {local_path}")
128
+ return local_path
129
+
130
+ except Exception as e:
131
+ log.error(f"Failed to download file: {e}")
132
+ return None
133
+
134
+ # ── Process image with Claude Vision ──────────────────────────────────────────
135
+ async def process_image(image_path: Path, caption: str = "") -> str:
136
+ """Analyze image with Claude Vision"""
137
+ try:
138
+ import base64
139
+ with open(image_path, "rb") as f:
140
+ image_data = base64.standard_b64encode(f.read()).decode("utf-8")
141
+
142
+ # Determine media type
143
+ suffix = image_path.suffix.lower()
144
+ media_type_map = {
145
+ ".jpg": "image/jpeg",
146
+ ".jpeg": "image/jpeg",
147
+ ".png": "image/png",
148
+ ".gif": "image/gif",
149
+ ".webp": "image/webp"
150
+ }
151
+ media_type = media_type_map.get(suffix, "image/jpeg")
152
+
153
+ context = f"Caption: {caption}\n" if caption else ""
154
+
155
+ response = client.messages.create(
156
+ model="claude-opus-4-5",
157
+ max_tokens=max_output("listener", "anthropic", "claude-opus-4-5"),
158
+ system="""You are a helpful AI assistant.
159
+ You are analyzing an image sent by the user via Telegram.
160
+ Be precise and helpful. Note any UI elements, design patterns, layouts, or relevant details.
161
+ If it's a dashboard/UI screenshot, describe the layout, colors, panels, and design language in detail.
162
+ Address him as user.""",
163
+ messages=[
164
+ {
165
+ "role": "user",
166
+ "content": [
167
+ {
168
+ "type": "image",
169
+ "source": {
170
+ "type": "base64",
171
+ "media_type": media_type,
172
+ "data": image_data,
173
+ },
174
+ },
175
+ {
176
+ "type": "text",
177
+ "text": f"{context}Please analyze this image and describe what you see in detail."
178
+ }
179
+ ],
180
+ }
181
+ ],
182
+ )
183
+ return extract_text(response)
184
+ except Exception as e:
185
+ log.error(f"Image processing failed: {e}")
186
+ return f"Image saved to {image_path} — vision analysis failed: {e}"
187
+
188
+ # ── Process text message with Assistant ─────────────────────────────────────────────
189
+ async def process_text(text: str, chat_id: int) -> str:
190
+ """Process text message through Assistant intelligence"""
191
+ # No owner memory is loaded here (this listener has no owner/guest
192
+ # roles), and history is kept per chat.
193
+ history = conversation_history.setdefault(chat_id, [])
194
+ history.append({"role": "user", "content": text})
195
+
196
+ # Keep last 20 messages for context
197
+ if len(history) > 20:
198
+ del history[:-20]
199
+
200
+ system_prompt = """You are a helpful AI assistant.
201
+ You are responding via Telegram — keep responses concise but complete.
202
+ You are a J.A.R.V.I.S + Samantha (Her) hybrid — precise, proactive, with genuine intelligence.
203
+ Address him as user. No filler words. Get to the point.
204
+ You have full access to his systems, files, email, calendar, and agents.
205
+ Current date: 2026-03-28"""
206
+
207
+ response = client.messages.create(
208
+ model="claude-opus-4-5",
209
+ max_tokens=max_output("listener", "anthropic", "claude-opus-4-5"),
210
+ system=system_prompt,
211
+ messages=history
212
+ )
213
+
214
+ reply = extract_text(response)
215
+ history.append({"role": "assistant", "content": reply})
216
+ return reply
217
+
218
+ # ── Handle incoming update ─────────────────────────────────────────────────────
219
+ async def handle_update(update: dict, session: aiohttp.ClientSession):
220
+ """Process a single Telegram update"""
221
+ message = update.get("message") or update.get("edited_message")
222
+ if not message:
223
+ return
224
+
225
+ chat_id = message["chat"]["id"]
226
+
227
+ # Security check
228
+ if chat_id not in ALLOWED_CHAT_IDS:
229
+ log.warning(f"Rejected message from unauthorized chat_id: {chat_id}")
230
+ return
231
+
232
+ timestamp = datetime.now().strftime("%H:%M:%S")
233
+
234
+ # ── Photo ──────────────────────────────────────────────────────────────────
235
+ if "photo" in message:
236
+ photos = message["photo"]
237
+ best_photo = max(photos, key=lambda p: p.get("file_size", 0))
238
+ caption = message.get("caption", "")
239
+
240
+ log.info(f"Receiving image from the user (caption: {caption})")
241
+ await send_message(chat_id, f"⚡ Receiving image...", session)
242
+
243
+ image_path = await download_file(best_photo["file_id"], session, ".jpg")
244
+
245
+ if image_path:
246
+ analysis = await process_image(image_path, caption)
247
+ response = f"📸 *Image received & analyzed:*\n\n{analysis}\n\n_Saved: {image_path.name}_"
248
+ await send_message(chat_id, response, session)
249
+
250
+ # Log to inbox manifest
251
+ manifest_path = DOWNLOAD_DIR / "manifest.jsonl"
252
+ entry = {
253
+ "timestamp": datetime.now().isoformat(),
254
+ "type": "photo",
255
+ "path": str(image_path),
256
+ "caption": caption,
257
+ "analysis": analysis[:500]
258
+ }
259
+ with open(manifest_path, "a") as f:
260
+ f.write(json.dumps(entry) + "\n")
261
+ else:
262
+ await send_message(chat_id, "⚠️ Failed to download image.", session)
263
+
264
+ # ── Document/File ──────────────────────────────────────────────────────────
265
+ elif "document" in message:
266
+ doc = message["document"]
267
+ caption = message.get("caption", "")
268
+ filename = doc.get("file_name", "document")
269
+
270
+ log.info(f"Receiving document: {filename}")
271
+ await send_message(chat_id, f"⚡ Receiving file: `{filename}`...", session)
272
+
273
+ file_path = await download_file(doc["file_id"], session)
274
+ if file_path:
275
+ await send_message(chat_id, f"✅ File saved: `{file_path.name}`\nReady to process.", session)
276
+ else:
277
+ await send_message(chat_id, "⚠️ Failed to download file.", session)
278
+
279
+ # ── Voice message ──────────────────────────────────────────────────────────
280
+ elif "voice" in message:
281
+ voice = message["voice"]
282
+ log.info("Receiving voice message")
283
+ await send_message(chat_id, "🎙️ Voice message received — saving...", session)
284
+
285
+ voice_path = await download_file(voice["file_id"], session, ".ogg")
286
+ if voice_path:
287
+ await send_message(chat_id, f"✅ Voice saved: `{voice_path.name}`\nTranscription coming in next build.", session)
288
+
289
+ # ── Text message ──────────────────────────────────────────────────────────
290
+ elif "text" in message:
291
+ text = message["text"]
292
+ log.info(f"Message from the user: {text[:100]}")
293
+
294
+ # Special commands
295
+ if text.lower() in ["/start", "/status"]:
296
+ status_msg = """⚡ *Assistant — Online*
297
+
298
+ 🤖 56 agents standing by
299
+ 📊 ATLAS_TRADING live on Hyperliquid
300
+ 🧠 Memory server: Port 8765
301
+ 🌐 HyperClaw API: Port 8001
302
+ 📬 Gmail: Connected
303
+ 📅 Calendar: Connected
304
+
305
+ Full duplex Telegram sync: ✅ ACTIVE
306
+
307
+ Ready."""
308
+ await send_message(chat_id, status_msg, session)
309
+ return
310
+
311
+ # Process through Assistant
312
+ await send_message(chat_id, "⚡ Processing...", session)
313
+ reply = await process_text(text, chat_id)
314
+ await send_message(chat_id, reply, session)
315
+
316
+ # ── Sticker/other ─────────────────────────────────────────────────────────
317
+ else:
318
+ log.info(f"Unhandled message type: {list(message.keys())}")
319
+
320
+ # ── Long polling loop ──────────────────────────────────────────────────────────
321
+ async def poll():
322
+ """Main polling loop — checks for new messages every second"""
323
+ offset = None
324
+
325
+ log.info("⚡ Assistant Telegram Listener starting — full duplex active")
326
+
327
+ async with aiohttp.ClientSession() as session:
328
+ # Send startup notification
329
+ await send_message(
330
+ int(os.environ.get("TELEGRAM_CHAT_ID", "0")),
331
+ "⚡ *Assistant Telegram Listener Online*\n\nFull duplex sync active. I can now receive your messages, images, and files directly.",
332
+ session
333
+ )
334
+
335
+ while True:
336
+ try:
337
+ params = {"timeout": 30, "allowed_updates": ["message", "edited_message"]}
338
+ if offset is not None:
339
+ params["offset"] = offset
340
+
341
+ url = f"{BASE_URL}/getUpdates"
342
+ async with session.get(url, params=params, timeout=aiohttp.ClientTimeout(total=35)) as resp:
343
+ if resp.status != 200:
344
+ log.warning(f"Telegram API returned {resp.status}")
345
+ await asyncio.sleep(5)
346
+ continue
347
+
348
+ data = await resp.json()
349
+
350
+ if not data.get("ok"):
351
+ log.warning(f"Telegram API error: {data}")
352
+ await asyncio.sleep(5)
353
+ continue
354
+
355
+ updates = data.get("result", [])
356
+
357
+ for update in updates:
358
+ update_id = update["update_id"]
359
+ offset = update_id + 1
360
+
361
+ try:
362
+ await handle_update(update, session)
363
+ except Exception as e:
364
+ log.error(f"Error handling update {update_id}: {e}", exc_info=True)
365
+
366
+ except asyncio.TimeoutError:
367
+ log.debug("Poll timeout — continuing")
368
+ except aiohttp.ClientError as e:
369
+ log.warning(f"Network error: {e} — retrying in 5s")
370
+ await asyncio.sleep(5)
371
+ except Exception as e:
372
+ log.error(f"Unexpected error: {e}", exc_info=True)
373
+ await asyncio.sleep(5)
374
+
375
+ def _unattended_guard(what: str) -> None:
376
+ """Telegram/iMessage polling is unattended work: HYPERCLAW_UNATTENDED=1
377
+ only. Off -> one line and a clean exit 0 (no KeepAlive restart loop)."""
378
+ try:
379
+ from hyperclaw.unattended import exit_if_off
380
+ except ImportError: # running outside a HyperClaw install
381
+ if os.environ.get("HYPERCLAW_UNATTENDED", "") != "1":
382
+ print("Unattended mode is off: scheduled jobs and Telegram polling are not "
383
+ "started. To turn them on, set HYPERCLAW_UNATTENDED=1 (only the exact "
384
+ f"value 1 enables it). ({what})", file=sys.stderr)
385
+ raise SystemExit(0)
386
+ return
387
+ exit_if_off(None, what)
388
+
389
+
390
+ # ── Entry point ────────────────────────────────────────────────────────────────
391
+ def main():
392
+ _unattended_guard("telegram_listener")
393
+ if not BOT_TOKEN:
394
+ log.error("TELEGRAM_BOT_TOKEN is not set (env or ~/.hyperclaw/.env) — exiting")
395
+ sys.exit(1)
396
+ try:
397
+ asyncio.run(poll())
398
+ except KeyboardInterrupt:
399
+ log.info("Assistant Telegram Listener stopped")
400
+
401
+
402
+ if __name__ == "__main__":
403
+ main()
ui/tui/app.py ADDED
@@ -0,0 +1,214 @@
1
+ """
2
+ HyperClaw TUI — Textual terminal user interface.
3
+ Screens: Dashboard, HyperState, Knowledge, Impact, Research, Audit
4
+ """
5
+ from __future__ import annotations
6
+
7
+ from datetime import datetime
8
+
9
+
10
+ try:
11
+ from textual.app import App, ComposeResult
12
+ from textual.binding import Binding
13
+ from textual.containers import Container, Horizontal, Vertical, ScrollableContainer
14
+ from textual.screen import Screen
15
+ from textual.widgets import (
16
+ DataTable, Footer, Header, Label, Log, Markdown,
17
+ Static, TabbedContent, TabPane,
18
+ )
19
+
20
+ BANNER = """\
21
+ ██╗ ██╗██╗ ██╗██████╗ ███████╗██████╗ ██████╗██╗ █████╗ ██╗ ██╗
22
+ ██║ ██║╚██╗ ██╔╝██╔══██╗██╔════╝██╔══██╗██╔════╝██║ ██╔══██╗██║ ██║
23
+ ███████║ ╚████╔╝ ██████╔╝█████╗ ██████╔╝██║ ██║ ███████║██║ █╗ ██║
24
+ ██╔══██║ ╚██╔╝ ██╔═══╝ ██╔══╝ ██╔══██╗██║ ██║ ██╔══██║██║███╗██║
25
+ ██║ ██║ ██║ ██║ ███████╗██║ ██║╚██████╗███████╗██║ ██║╚███╔███╔╝
26
+ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚══════╝╚═╝ ╚═╝ ╚═════╝╚══════╝╚═╝ ╚═╝ ╚══╝╚══╝"""
27
+
28
+ AGENTS = [
29
+ ("ATLAS", "personal", "planning,scheduling,synthesis"),
30
+ ("MIDAS", "personal", "analysis,finance,planning"),
31
+ ("VITALS", "personal", "health,analysis,planning"),
32
+ ("NOURISH", "personal", "health,planning,research"),
33
+ ("NAVIGATOR", "personal", "planning,research,synthesis"),
34
+ ("HEARTH", "personal", "planning,scheduling,quick_lookup"),
35
+ ("STRATEGOS", "business", "analysis,research,planning,synthesis"),
36
+ ("HERALD", "business", "synthesis,planning"),
37
+ ("PIPELINE", "business", "research,synthesis,quick_lookup"),
38
+ ("LEDGER", "business", "analysis,finance,synthesis"),
39
+ ("COUNSEL", "business", "research,analysis,synthesis"),
40
+ ("TALENT", "business", "analysis,research,planning"),
41
+ ("MEDICUS", "scientific", "research,analysis,scientific,health"),
42
+ ("COSMOS", "scientific", "research,analysis,scientific,code"),
43
+ ("GAIA", "scientific", "research,analysis,scientific"),
44
+ ("ORACLE", "scientific", "analysis,code,scientific,finance"),
45
+ ("SCRIBE", "scientific", "research,synthesis,summarization"),
46
+ ("AUTHOR", "creative", "synthesis,research,planning"),
47
+ ("LENS", "creative", "research,quick_lookup,summarization"),
48
+ ("SCOUT", "recursive", "research,quick_lookup,summarization"),
49
+ ("ALCHEMIST", "recursive", "code,research,analysis"),
50
+ ("CALIBRATOR", "recursive", "analysis,routing"),
51
+ ("NEXUS", "recursive", "planning,routing,coordination"),
52
+ ]
53
+
54
+ class DashboardScreen(Screen):
55
+ """Live agent grid + cost tracker + message feed."""
56
+
57
+ def compose(self) -> ComposeResult:
58
+ yield Header()
59
+ with TabbedContent():
60
+ with TabPane("Agents", id="agents-tab"):
61
+ table = DataTable(id="agent-table")
62
+ table.add_columns("Agent", "Domain", "Task Types", "Status")
63
+ for agent_id, domain, task_types in AGENTS:
64
+ table.add_row(agent_id, domain, task_types[:40], "✓ Ready")
65
+ yield table
66
+ with TabPane("Messages", id="messages-tab"):
67
+ yield Log(id="message-log", highlight=True)
68
+ yield Footer()
69
+
70
+ def on_mount(self) -> None:
71
+ self.title = "⚡ HyperClaw — Dashboard"
72
+ log = self.query_one("#message-log", Log)
73
+ log.write_line(f"[{datetime.now().strftime('%H:%M:%S')}] HyperClaw swarm initialized")
74
+ log.write_line(f"[{datetime.now().strftime('%H:%M:%S')}] 23 agents registered and ready")
75
+ log.write_line(f"[{datetime.now().strftime('%H:%M:%S')}] Recursive Growth Engine: active")
76
+
77
+ class HyperStateScreen(Screen):
78
+ """Active HyperStates list and inspection."""
79
+
80
+ def compose(self) -> ComposeResult:
81
+ yield Header()
82
+ yield Label("HyperState Manager — Active states will appear here when tasks are running.", id="state-info")
83
+ yield Static("Use `hyperclaw state list` to view states in CLI mode.", classes="dim")
84
+ yield Footer()
85
+
86
+ def on_mount(self) -> None:
87
+ self.title = "⚡ HyperClaw — HyperState"
88
+
89
+ class KnowledgeScreen(Screen):
90
+ """HyperMemory stats."""
91
+
92
+ def compose(self) -> ComposeResult:
93
+ yield Header()
94
+ with ScrollableContainer():
95
+ yield Markdown("""
96
+ # HyperMemory Knowledge Graph
97
+
98
+ | Metric | Value |
99
+ |--------|-------|
100
+ | Knowledge Nodes | 0 (connect DB to populate) |
101
+ | Causal Edges | 0 |
102
+ | Certified Skills | 0 |
103
+ | Discoveries This Week | 0 |
104
+
105
+ ## Recent Causal Edges
106
+ No edges recorded yet. Run tasks to populate the knowledge graph.
107
+ """)
108
+ yield Footer()
109
+
110
+ def on_mount(self) -> None:
111
+ self.title = "⚡ HyperClaw — Knowledge"
112
+
113
+ class ImpactScreen(Screen):
114
+ """Impact records by domain."""
115
+
116
+ def compose(self) -> ComposeResult:
117
+ yield Header()
118
+ yield Markdown("""
119
+ # Impact Dashboard
120
+
121
+ No impact records yet. Records are created when NEXUS completes measurable tasks.
122
+
123
+ Run: `hyperclaw swarm run "your goal"` to start generating impact data.
124
+ """)
125
+ yield Footer()
126
+
127
+ def on_mount(self) -> None:
128
+ self.title = "⚡ HyperClaw — Impact"
129
+
130
+ class ResearchScreen(Screen):
131
+ """SCOUT discoveries, ALCHEMIST queue, certified skills."""
132
+
133
+ def compose(self) -> ComposeResult:
134
+ yield Header()
135
+ with ScrollableContainer():
136
+ yield Markdown("""
137
+ # Recursive Research
138
+
139
+ ## SCOUT Status
140
+ - Sources monitored: arXiv cs.AI, arXiv q-bio, arXiv astro-ph, GitHub trending, PubMed
141
+ - Last sweep: Never (run `hyperclaw research sweep` to trigger)
142
+
143
+ ## ALCHEMIST Queue
144
+ No pending discoveries.
145
+
146
+ ## Certified Skills
147
+ No skills certified yet.
148
+ """)
149
+ yield Footer()
150
+
151
+ def on_mount(self) -> None:
152
+ self.title = "⚡ HyperClaw — Research"
153
+
154
+ class AuditScreen(Screen):
155
+ """HyperShield audit log."""
156
+
157
+ def compose(self) -> ComposeResult:
158
+ yield Header()
159
+ yield Log(id="audit-log", highlight=True)
160
+ yield Footer()
161
+
162
+ def on_mount(self) -> None:
163
+ self.title = "⚡ HyperClaw — Audit"
164
+ log = self.query_one("#audit-log", Log)
165
+ log.write_line("HyperShield audit log — events will appear here during operation.")
166
+
167
+ class HyperClawTUI(App):
168
+ """HyperClaw Terminal User Interface."""
169
+
170
+ CSS = """
171
+ Screen { background: #0d1117; }
172
+ Header { background: #161b22; color: #58a6ff; }
173
+ Footer { background: #161b22; }
174
+ DataTable { height: 1fr; }
175
+ Log { height: 1fr; border: solid #30363d; }
176
+ Label#state-info { padding: 1 2; color: #58a6ff; }
177
+ .dim { color: #8b949e; padding: 0 2; }
178
+ """
179
+
180
+ BINDINGS = [
181
+ Binding("d", "switch_mode('dashboard')", "Dashboard"),
182
+ Binding("s", "switch_mode('hyperstate')", "HyperState"),
183
+ Binding("k", "switch_mode('knowledge')", "Knowledge"),
184
+ Binding("i", "switch_mode('impact')", "Impact"),
185
+ Binding("r", "switch_mode('research')", "Research"),
186
+ Binding("a", "switch_mode('audit')", "Audit"),
187
+ Binding("q", "quit", "Quit"),
188
+ Binding("?", "help", "Help"),
189
+ ]
190
+
191
+ MODES = {
192
+ "dashboard": DashboardScreen,
193
+ "hyperstate": HyperStateScreen,
194
+ "knowledge": KnowledgeScreen,
195
+ "impact": ImpactScreen,
196
+ "research": ResearchScreen,
197
+ "audit": AuditScreen,
198
+ }
199
+
200
+ def on_mount(self) -> None:
201
+ self.title = "⚡ HyperClaw"
202
+ self.switch_mode("dashboard")
203
+
204
+ except ImportError:
205
+ # Textual not available — provide a no-op stub
206
+ class HyperClawTUI: # type: ignore
207
+ """Textual not installed — TUI unavailable. Install with: pip install textual"""
208
+
209
+ def run(self) -> None:
210
+ print("TUI unavailable. Install textual: pip install 'hyperclaw[tui]'")
211
+
212
+ from hyperclaw import __version__ as _v
213
+ BANNER = f"HyperClaw v{_v}"
214
+ AGENTS = []