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
agents/__init__.py ADDED
@@ -0,0 +1,31 @@
1
+ # Assistant Agent System
2
+ """
3
+ HyperClaw Agents - Background monitoring, planning, transcription, and learning.
4
+ """
5
+
6
+ try:
7
+ from .background_monitor import agent_start, agent_stop, agent_status, agent_add_watch
8
+ except ImportError:
9
+ agent_start = agent_stop = agent_status = agent_add_watch = None
10
+
11
+ try:
12
+ from .planner import plan_create, plan_add_task, plan_status, plan_next, plan_update, plan_list, plan_delete
13
+ except ImportError:
14
+ plan_create = plan_add_task = plan_status = plan_next = plan_update = plan_list = plan_delete = None
15
+
16
+ try:
17
+ from .transcriber import audio_transcribe, video_transcribe, audio_record, audio_record_transcribe, transcripts_list, transcript_get
18
+ except ImportError:
19
+ audio_transcribe = video_transcribe = audio_record = audio_record_transcribe = transcripts_list = transcript_get = None
20
+
21
+ try:
22
+ from .learning import learning_log, learning_stats, learning_patterns, learning_reflect, learning_advice, learning_feedback
23
+ except ImportError:
24
+ learning_log = learning_stats = learning_patterns = learning_reflect = learning_advice = learning_feedback = None
25
+
26
+ __all__ = [
27
+ "agent_start", "agent_stop", "agent_status", "agent_add_watch",
28
+ "plan_create", "plan_add_task", "plan_status", "plan_next", "plan_update", "plan_list", "plan_delete",
29
+ "audio_transcribe", "video_transcribe", "audio_record", "audio_record_transcribe", "transcripts_list", "transcript_get",
30
+ "learning_log", "learning_stats", "learning_patterns", "learning_reflect", "learning_advice", "learning_feedback",
31
+ ]
@@ -0,0 +1,453 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Assistant Background Agents — Autonomous monitoring and alerting.
4
+ Runs continuously, watches email/markets/news, alerts the user.
5
+ """
6
+
7
+ import os
8
+ import sys
9
+ import json
10
+ import time
11
+ import asyncio
12
+ import threading
13
+ from pathlib import Path
14
+ from datetime import datetime, timedelta
15
+ from typing import Dict, List, Optional, Callable
16
+ import httpx
17
+
18
+ # Use user's ~/.hyperclaw directory, not package location
19
+ HYPERCLAW_ROOT = Path(os.environ.get("HYPERCLAW_ROOT", Path.home() / ".hyperclaw"))
20
+ AGENTS_STATE_FILE = HYPERCLAW_ROOT / "agents" / "state.json"
21
+ ALERTS_LOG = HYPERCLAW_ROOT / "logs" / "alerts.log"
22
+
23
+ # Ensure directories exist
24
+ AGENTS_STATE_FILE.parent.mkdir(parents=True, exist_ok=True)
25
+ ALERTS_LOG.parent.mkdir(parents=True, exist_ok=True)
26
+
27
+
28
+ def load_env():
29
+ """Load environment from .env file."""
30
+ env_file = HYPERCLAW_ROOT / ".env"
31
+ if env_file.exists():
32
+ for line in env_file.read_text().splitlines():
33
+ if "=" in line and not line.startswith("#"):
34
+ key, val = line.split("=", 1)
35
+ os.environ.setdefault(key.strip(), val.strip().strip('"'))
36
+
37
+
38
+ load_env()
39
+
40
+
41
+ class BackgroundAgent:
42
+ """Base class for background monitoring agents."""
43
+
44
+ def __init__(self, name: str, interval: int = 300):
45
+ self.name = name
46
+ self.interval = interval # seconds
47
+ self.running = False
48
+ self.last_run = None
49
+ self.last_result = None
50
+ self.error_count = 0
51
+
52
+ async def check(self) -> Optional[Dict]:
53
+ """Override this method to implement monitoring logic.
54
+ Return dict with 'alert' key if attention needed."""
55
+ raise NotImplementedError
56
+
57
+ async def run_once(self) -> Optional[Dict]:
58
+ """Run a single check."""
59
+ try:
60
+ result = await self.check()
61
+ self.last_run = datetime.now()
62
+ self.last_result = result
63
+ self.error_count = 0
64
+ return result
65
+ except Exception as e:
66
+ self.error_count += 1
67
+ return {"error": str(e)}
68
+
69
+ def to_dict(self) -> Dict:
70
+ return {
71
+ "name": self.name,
72
+ "interval": self.interval,
73
+ "running": self.running,
74
+ "last_run": self.last_run.isoformat() if self.last_run else None,
75
+ "error_count": self.error_count
76
+ }
77
+
78
+
79
+ class EmailMonitor(BackgroundAgent):
80
+ """Monitor email for important messages."""
81
+
82
+ def __init__(self, keywords: List[str] = None, senders: List[str] = None):
83
+ super().__init__("email_monitor", interval=300) # 5 min
84
+ self.keywords = keywords or ["urgent", "asap", "important", "deadline"]
85
+ self.senders = senders or []
86
+ self.seen_ids = set()
87
+
88
+ async def check(self) -> Optional[Dict]:
89
+ # Use AppleScript to check Mail.app
90
+ import subprocess
91
+ script = '''
92
+ tell application "Mail"
93
+ set output to ""
94
+ set unreadMsgs to (messages of inbox whose read status is false)
95
+ repeat with m in unreadMsgs
96
+ set output to output & (id of m) & "|" & (sender of m) & "|" & (subject of m) & "\\n"
97
+ end repeat
98
+ return output
99
+ end tell
100
+ '''
101
+ try:
102
+ result = subprocess.run(["osascript", "-e", script],
103
+ capture_output=True, text=True, timeout=30)
104
+
105
+ alerts = []
106
+ for line in result.stdout.strip().split('\n'):
107
+ if not line or '|' not in line:
108
+ continue
109
+ parts = line.split('|', 2)
110
+ if len(parts) < 3:
111
+ continue
112
+ msg_id, sender, subject = parts
113
+
114
+ if msg_id in self.seen_ids:
115
+ continue
116
+ self.seen_ids.add(msg_id)
117
+
118
+ # Check for important emails
119
+ is_important = False
120
+ reason = ""
121
+
122
+ # Check senders
123
+ for s in self.senders:
124
+ if s.lower() in sender.lower():
125
+ is_important = True
126
+ reason = f"From {s}"
127
+ break
128
+
129
+ # Check keywords
130
+ if not is_important:
131
+ for kw in self.keywords:
132
+ if kw.lower() in subject.lower():
133
+ is_important = True
134
+ reason = f"Contains '{kw}'"
135
+ break
136
+
137
+ if is_important:
138
+ alerts.append({
139
+ "type": "email",
140
+ "sender": sender,
141
+ "subject": subject,
142
+ "reason": reason
143
+ })
144
+
145
+ if alerts:
146
+ return {"alert": True, "emails": alerts}
147
+ return {"alert": False, "checked": len(self.seen_ids)}
148
+
149
+ except Exception as e:
150
+ return {"error": str(e)}
151
+
152
+
153
+ class MarketMonitor(BackgroundAgent):
154
+ """Monitor market prices and alert on significant moves."""
155
+
156
+ def __init__(self, symbols: List[str] = None, threshold: float = 0.05):
157
+ super().__init__("market_monitor", interval=60) # 1 min
158
+ self.symbols = symbols or ["BTC-USD", "ETH-USD", "SPY", "AAPL"]
159
+ self.threshold = threshold # 5% move
160
+ self.last_prices = {}
161
+
162
+ async def check(self) -> Optional[Dict]:
163
+ alerts = []
164
+
165
+ async with httpx.AsyncClient(timeout=10) as client:
166
+ for symbol in self.symbols:
167
+ try:
168
+ url = f"https://query1.finance.yahoo.com/v8/finance/chart/{symbol}"
169
+ resp = await client.get(url)
170
+ data = resp.json()
171
+
172
+ price = data["chart"]["result"][0]["meta"]["regularMarketPrice"]
173
+ prev_close = data["chart"]["result"][0]["meta"].get("previousClose", price)
174
+
175
+ change_pct = (price - prev_close) / prev_close if prev_close else 0
176
+
177
+ # Check against last known price for sudden moves
178
+ if symbol in self.last_prices:
179
+ last = self.last_prices[symbol]
180
+ move = abs((price - last) / last)
181
+ if move >= self.threshold:
182
+ direction = "up" if price > last else "down"
183
+ alerts.append({
184
+ "type": "market",
185
+ "symbol": symbol,
186
+ "price": price,
187
+ "move": f"{move*100:.1f}% {direction}",
188
+ "prev": last
189
+ })
190
+
191
+ self.last_prices[symbol] = price
192
+
193
+ except Exception as e:
194
+ continue
195
+
196
+ if alerts:
197
+ return {"alert": True, "moves": alerts}
198
+ return {"alert": False, "prices": self.last_prices}
199
+
200
+
201
+ class NewsMonitor(BackgroundAgent):
202
+ """Monitor news for relevant topics."""
203
+
204
+ def __init__(self, topics: List[str] = None):
205
+ super().__init__("news_monitor", interval=900) # 15 min
206
+ self.topics = topics or ["Organization", "AI hospitality", "hotel technology"]
207
+ self.seen_titles = set()
208
+
209
+ async def check(self) -> Optional[Dict]:
210
+ alerts = []
211
+
212
+ async with httpx.AsyncClient(timeout=15) as client:
213
+ for topic in self.topics:
214
+ try:
215
+ url = f"https://news.google.com/rss/search?q={topic.replace(' ', '+')}"
216
+ resp = await client.get(url)
217
+
218
+ import re
219
+ titles = re.findall(r'<title>(.+?)</title>', resp.text)[1:6]
220
+
221
+ for title in titles:
222
+ if title not in self.seen_titles:
223
+ self.seen_titles.add(title)
224
+ alerts.append({
225
+ "type": "news",
226
+ "topic": topic,
227
+ "title": title
228
+ })
229
+ except:
230
+ continue
231
+
232
+ if alerts:
233
+ return {"alert": True, "news": alerts[:5]} # Max 5 alerts
234
+ return {"alert": False, "topics_checked": len(self.topics)}
235
+
236
+
237
+ class AgentManager:
238
+ """Manages all background agents."""
239
+
240
+ def __init__(self):
241
+ self.agents: Dict[str, BackgroundAgent] = {}
242
+ self.running = False
243
+ self.alert_callback: Optional[Callable] = None
244
+ self._task = None
245
+
246
+ def register(self, agent: BackgroundAgent):
247
+ """Register an agent."""
248
+ self.agents[agent.name] = agent
249
+
250
+ def set_alert_callback(self, callback: Callable):
251
+ """Set callback for alerts (e.g., send Telegram)."""
252
+ self.alert_callback = callback
253
+
254
+ async def _run_loop(self):
255
+ """Main monitoring loop."""
256
+ while self.running:
257
+ for name, agent in self.agents.items():
258
+ if not agent.running:
259
+ continue
260
+
261
+ # Check if it's time to run
262
+ if agent.last_run:
263
+ elapsed = (datetime.now() - agent.last_run).total_seconds()
264
+ if elapsed < agent.interval:
265
+ continue
266
+
267
+ # Run check
268
+ result = await agent.run_once()
269
+
270
+ # Handle alerts
271
+ if result and result.get("alert"):
272
+ await self._handle_alert(agent, result)
273
+
274
+ await asyncio.sleep(10) # Check every 10 seconds
275
+
276
+ async def _handle_alert(self, agent: BackgroundAgent, result: Dict):
277
+ """Handle an alert from an agent."""
278
+ timestamp = datetime.now().isoformat()
279
+ alert_msg = f"[{timestamp}] {agent.name}: {json.dumps(result)}"
280
+
281
+ # Log alert
282
+ with open(ALERTS_LOG, "a") as f:
283
+ f.write(alert_msg + "\n")
284
+
285
+ # Call callback (e.g., Telegram)
286
+ if self.alert_callback:
287
+ try:
288
+ await self.alert_callback(agent.name, result)
289
+ except:
290
+ pass
291
+
292
+ def start(self):
293
+ """Start all agents."""
294
+ self.running = True
295
+ for agent in self.agents.values():
296
+ agent.running = True
297
+
298
+ # Run in background
299
+ loop = asyncio.new_event_loop()
300
+ self._task = threading.Thread(target=lambda: loop.run_until_complete(self._run_loop()))
301
+ self._task.daemon = True
302
+ self._task.start()
303
+
304
+ def stop(self):
305
+ """Stop all agents."""
306
+ self.running = False
307
+ for agent in self.agents.values():
308
+ agent.running = False
309
+
310
+ def status(self) -> Dict:
311
+ """Get status of all agents."""
312
+ return {
313
+ "running": self.running,
314
+ "agents": {name: agent.to_dict() for name, agent in self.agents.items()}
315
+ }
316
+
317
+ def save_state(self):
318
+ """Save agent state to disk."""
319
+ state = {
320
+ "running": self.running,
321
+ "agents": {}
322
+ }
323
+ for name, agent in self.agents.items():
324
+ state["agents"][name] = {
325
+ "running": agent.running,
326
+ "interval": agent.interval,
327
+ "last_run": agent.last_run.isoformat() if agent.last_run else None
328
+ }
329
+ AGENTS_STATE_FILE.write_text(json.dumps(state, indent=2))
330
+
331
+ def load_state(self):
332
+ """Load agent state from disk."""
333
+ if AGENTS_STATE_FILE.exists():
334
+ try:
335
+ state = json.loads(AGENTS_STATE_FILE.read_text())
336
+ for name, data in state.get("agents", {}).items():
337
+ if name in self.agents:
338
+ self.agents[name].running = data.get("running", False)
339
+ self.agents[name].interval = data.get("interval", 300)
340
+ except:
341
+ pass
342
+
343
+
344
+ # Owner-only alert sender (team of agents, rc5): one fixed format, the owner's
345
+ # chat(s) only, no links, no markdown, link previews off. See
346
+ # hyperclaw/team/watchers.py.
347
+ async def send_telegram_alert(agent_name: str, result: Dict):
348
+ """Send a watcher alert to the OWNER only, in the one fixed format."""
349
+ from hyperclaw.team.watchers import owner_alert_callback
350
+ await owner_alert_callback(agent_name, result)
351
+
352
+
353
+ # Global manager instance
354
+ manager = AgentManager()
355
+
356
+ # Register default agents
357
+ manager.register(EmailMonitor(
358
+ keywords=["urgent", "asap", "important", "investor", "funding", "deal"],
359
+ senders=["andreessen", "sequoia", "investor", "ceo"]
360
+ ))
361
+ manager.register(MarketMonitor(
362
+ symbols=["BTC-USD", "ETH-USD", "SPY", "NVDA"],
363
+ threshold=0.03 # 3% moves
364
+ ))
365
+ manager.register(NewsMonitor(
366
+ topics=["Organization", "AI hospitality", "hotel technology", "AI news"]
367
+ ))
368
+ manager.set_alert_callback(send_telegram_alert)
369
+
370
+
371
+ # CLI functions for TUI integration
372
+ def agent_start(name: str = None) -> str:
373
+ """Start an agent or all agents."""
374
+ if name:
375
+ if name in manager.agents:
376
+ manager.agents[name].running = True
377
+ if not manager.running:
378
+ manager.start()
379
+ return f"Started {name}"
380
+ return f"Unknown agent: {name}"
381
+ else:
382
+ manager.start()
383
+ return f"Started all agents: {list(manager.agents.keys())}"
384
+
385
+
386
+ def agent_stop(name: str = None) -> str:
387
+ """Stop an agent or all agents."""
388
+ if name:
389
+ if name in manager.agents:
390
+ manager.agents[name].running = False
391
+ return f"Stopped {name}"
392
+ return f"Unknown agent: {name}"
393
+ else:
394
+ manager.stop()
395
+ return "Stopped all agents"
396
+
397
+
398
+ def agent_status() -> Dict:
399
+ """Get agent status."""
400
+ return manager.status()
401
+
402
+
403
+ def agent_add_watch(agent_type: str, target: str) -> str:
404
+ """Add a watch target to an agent."""
405
+ if agent_type == "email" and "email_monitor" in manager.agents:
406
+ agent = manager.agents["email_monitor"]
407
+ if "@" in target:
408
+ agent.senders.append(target)
409
+ else:
410
+ agent.keywords.append(target)
411
+ return f"Added '{target}' to email monitor"
412
+
413
+ elif agent_type == "market" and "market_monitor" in manager.agents:
414
+ agent = manager.agents["market_monitor"]
415
+ agent.symbols.append(target.upper())
416
+ return f"Added {target.upper()} to market monitor"
417
+
418
+ elif agent_type == "news" and "news_monitor" in manager.agents:
419
+ agent = manager.agents["news_monitor"]
420
+ agent.topics.append(target)
421
+ return f"Added '{target}' to news monitor"
422
+
423
+ return f"Unknown agent type: {agent_type}"
424
+
425
+
426
+ if __name__ == "__main__":
427
+ import sys
428
+
429
+ if len(sys.argv) < 2:
430
+ print("Usage: background_monitor.py <start|stop|status>")
431
+ sys.exit(1)
432
+
433
+ cmd = sys.argv[1]
434
+
435
+ if cmd == "start":
436
+ print(agent_start())
437
+ # Keep running
438
+ try:
439
+ while True:
440
+ time.sleep(60)
441
+ manager.save_state()
442
+ except KeyboardInterrupt:
443
+ manager.stop()
444
+ print("\nStopped")
445
+
446
+ elif cmd == "stop":
447
+ print(agent_stop())
448
+
449
+ elif cmd == "status":
450
+ print(json.dumps(agent_status(), indent=2))
451
+
452
+ else:
453
+ print(f"Unknown command: {cmd}")