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
@@ -0,0 +1,217 @@
1
+ """SlackConnector — Slack API integration."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import logging
6
+ from typing import AsyncIterator
7
+
8
+ import httpx
9
+
10
+ from integrations.base import (
11
+ BaseConnector,
12
+ ConnectorCapability,
13
+ ConnectorInfo,
14
+ InboundMessage,
15
+ OutboundMessage,
16
+ )
17
+
18
+ logger = logging.getLogger("hyperclaw.integrations.slack")
19
+
20
+
21
+ from hyperclaw.tools.egress import slack_payload # noqa: E402 (unfurls off)
22
+
23
+
24
+ class SlackConnector(BaseConnector):
25
+ """
26
+ Slack API connector using Bot tokens.
27
+
28
+ Required config:
29
+ - bot_token: Slack Bot User OAuth Token (xoxb-...)
30
+ """
31
+
32
+ BASE_URL = "https://slack.com/api"
33
+
34
+ def __init__(self, config: dict) -> None:
35
+ self.config = config
36
+ self.bot_token = config.get("bot_token", "")
37
+
38
+ if config.get("enabled", False) and not self.bot_token:
39
+ raise ValueError("SlackConnector requires bot_token")
40
+
41
+ self._client: httpx.AsyncClient | None = None
42
+
43
+ @property
44
+ def info(self) -> ConnectorInfo:
45
+ return ConnectorInfo(
46
+ connector_id="slack",
47
+ platform="slack",
48
+ category="messaging",
49
+ capabilities=frozenset(
50
+ [
51
+ ConnectorCapability.SEND_MESSAGE,
52
+ ConnectorCapability.RECEIVE_MESSAGE,
53
+ ConnectorCapability.FILE_UPLOAD,
54
+ ConnectorCapability.FILE_DOWNLOAD,
55
+ ConnectorCapability.WEBHOOKS,
56
+ ConnectorCapability.SEARCH,
57
+ ConnectorCapability.ACTION,
58
+ ConnectorCapability.NOTIFY,
59
+ ]
60
+ ),
61
+ auth_type="bearer",
62
+ rate_limit_per_minute=60,
63
+ supports_threads=True,
64
+ supports_attachments=True,
65
+ supports_reactions=True,
66
+ )
67
+
68
+ async def _get_client(self) -> httpx.AsyncClient:
69
+ if self._client is None:
70
+ self._client = httpx.AsyncClient(
71
+ timeout=30.0,
72
+ headers={
73
+ "Authorization": f"Bearer {self.bot_token}",
74
+ "Content-Type": "application/json",
75
+ },
76
+ )
77
+ return self._client
78
+
79
+ async def health(self) -> bool:
80
+ """Check if bot token is valid."""
81
+ try:
82
+ client = await self._get_client()
83
+ response = await client.post(f"{self.BASE_URL}/auth.test")
84
+ data = response.json()
85
+ return data.get("ok", False)
86
+ except Exception as e:
87
+ logger.error(f"Slack health check failed: {e}")
88
+ return False
89
+
90
+ async def _send_impl(self, message: OutboundMessage) -> str:
91
+ """Send a message via Slack."""
92
+ client = await self._get_client()
93
+
94
+ payload = {
95
+ "channel": message.recipient_id or message.channel_id,
96
+ "text": message.content,
97
+ }
98
+
99
+ if message.thread_id:
100
+ payload["thread_ts"] = message.thread_id
101
+
102
+ if message.format == "blocks":
103
+ # If blocks format, content should be JSON blocks
104
+ import json
105
+
106
+ payload["blocks"] = json.loads(message.content)
107
+ payload["text"] = "Message" # Fallback text
108
+
109
+ response = await client.post(f"{self.BASE_URL}/chat.postMessage", json=slack_payload(payload))
110
+ data = response.json()
111
+
112
+ if not data.get("ok"):
113
+ raise Exception(f"Slack send failed: {data.get('error')}")
114
+
115
+ return data.get("ts", "")
116
+
117
+ async def _receive_impl(self) -> AsyncIterator[InboundMessage]:
118
+ """
119
+ Receive messages. In production, use Socket Mode or Events API.
120
+ This implementation uses conversations.history for polling.
121
+ """
122
+ # Note: This is a simplified implementation.
123
+ # In production, use Slack Socket Mode or Events API with webhooks.
124
+ return
125
+ yield # Make this an async generator
126
+
127
+ async def post_blocks(
128
+ self,
129
+ channel: str,
130
+ blocks: list[dict],
131
+ text: str = "Message",
132
+ ) -> str:
133
+ """Post a message with Block Kit blocks."""
134
+ client = await self._get_client()
135
+
136
+ payload = {
137
+ "channel": channel,
138
+ "blocks": blocks,
139
+ "text": text,
140
+ }
141
+
142
+ response = await client.post(f"{self.BASE_URL}/chat.postMessage", json=slack_payload(payload))
143
+ data = response.json()
144
+
145
+ if not data.get("ok"):
146
+ raise Exception(f"Slack post_blocks failed: {data.get('error')}")
147
+
148
+ return data.get("ts", "")
149
+
150
+ async def add_reaction(
151
+ self,
152
+ channel: str,
153
+ timestamp: str,
154
+ emoji: str,
155
+ ) -> bool:
156
+ """Add a reaction to a message."""
157
+ client = await self._get_client()
158
+
159
+ payload = {
160
+ "channel": channel,
161
+ "timestamp": timestamp,
162
+ "name": emoji.strip(":"),
163
+ }
164
+
165
+ response = await client.post(f"{self.BASE_URL}/reactions.add", json=payload)
166
+ data = response.json()
167
+
168
+ return data.get("ok", False)
169
+
170
+ async def search_messages(self, query: str, count: int = 20) -> list[dict]:
171
+ """Search messages in Slack."""
172
+ client = await self._get_client()
173
+
174
+ params = {
175
+ "query": query,
176
+ "count": count,
177
+ }
178
+
179
+ response = await client.get(f"{self.BASE_URL}/search.messages", params=params)
180
+ data = response.json()
181
+
182
+ if not data.get("ok"):
183
+ raise Exception(f"Slack search failed: {data.get('error')}")
184
+
185
+ return data.get("messages", {}).get("matches", [])
186
+
187
+ async def _search_impl(self, query: str, **kwargs) -> list[dict]:
188
+ """Search implementation."""
189
+ return await self.search_messages(query, kwargs.get("count", 20))
190
+
191
+ async def _action_impl(self, action_name: str, params: dict) -> dict:
192
+ """Execute Slack-specific actions."""
193
+ client = await self._get_client()
194
+
195
+ if action_name == "list_channels":
196
+ response = await client.get(f"{self.BASE_URL}/conversations.list")
197
+ return response.json()
198
+
199
+ elif action_name == "get_user":
200
+ response = await client.get(
201
+ f"{self.BASE_URL}/users.info",
202
+ params={"user": params["user_id"]},
203
+ )
204
+ return response.json()
205
+
206
+ elif action_name == "update_message":
207
+ response = await client.post(
208
+ f"{self.BASE_URL}/chat.update",
209
+ json={
210
+ "channel": params["channel"],
211
+ "ts": params["ts"],
212
+ "text": params["text"],
213
+ },
214
+ )
215
+ return response.json()
216
+
217
+ raise ValueError(f"Unknown action: {action_name}")
@@ -0,0 +1,175 @@
1
+ """SMSConnector — SMS messaging via Twilio API."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import base64
6
+ import logging
7
+ from typing import AsyncIterator
8
+
9
+ import httpx
10
+
11
+ from integrations.base import (
12
+ BaseConnector,
13
+ ConnectorCapability,
14
+ ConnectorInfo,
15
+ InboundMessage,
16
+ OutboundMessage,
17
+ )
18
+
19
+ logger = logging.getLogger("hyperclaw.integrations.sms")
20
+
21
+
22
+ class SMSConnector(BaseConnector):
23
+ """
24
+ SMS connector via Twilio API.
25
+
26
+ Required config:
27
+ - account_sid: Twilio Account SID
28
+ - auth_token: Twilio Auth Token
29
+ - from_number: Twilio phone number to send from
30
+ """
31
+
32
+ BASE_URL = "https://api.twilio.com/2010-04-01"
33
+
34
+ def __init__(self, config: dict) -> None:
35
+ self.config = config
36
+ self.account_sid = config.get("account_sid", "")
37
+ self.auth_token = config.get("auth_token", "")
38
+ self.from_number = config.get("from_number", "")
39
+
40
+ if config.get("enabled", False):
41
+ if not self.account_sid:
42
+ raise ValueError("SMSConnector requires account_sid")
43
+ if not self.auth_token:
44
+ raise ValueError("SMSConnector requires auth_token")
45
+ if not self.from_number:
46
+ raise ValueError("SMSConnector requires from_number")
47
+
48
+ self._client: httpx.AsyncClient | None = None
49
+
50
+ @property
51
+ def info(self) -> ConnectorInfo:
52
+ return ConnectorInfo(
53
+ connector_id="sms",
54
+ platform="sms",
55
+ category="messaging",
56
+ capabilities=frozenset(
57
+ [
58
+ ConnectorCapability.SEND_MESSAGE,
59
+ ConnectorCapability.RECEIVE_MESSAGE,
60
+ ConnectorCapability.NOTIFY,
61
+ ]
62
+ ),
63
+ auth_type="api_key",
64
+ rate_limit_per_minute=100,
65
+ supports_threads=False,
66
+ supports_attachments=False,
67
+ supports_reactions=False,
68
+ )
69
+
70
+ def _get_auth_header(self) -> str:
71
+ """Generate Basic auth header."""
72
+ credentials = f"{self.account_sid}:{self.auth_token}"
73
+ encoded = base64.b64encode(credentials.encode()).decode()
74
+ return f"Basic {encoded}"
75
+
76
+ async def _get_client(self) -> httpx.AsyncClient:
77
+ if self._client is None:
78
+ self._client = httpx.AsyncClient(
79
+ timeout=30.0,
80
+ headers={
81
+ "Authorization": self._get_auth_header(),
82
+ "Content-Type": "application/x-www-form-urlencoded",
83
+ },
84
+ )
85
+ return self._client
86
+
87
+ async def health(self) -> bool:
88
+ """Check if Twilio credentials are valid."""
89
+ try:
90
+ client = await self._get_client()
91
+ response = await client.get(
92
+ f"{self.BASE_URL}/Accounts/{self.account_sid}.json",
93
+ )
94
+ return response.status_code == 200
95
+ except Exception as e:
96
+ logger.error(f"SMS health check failed: {e}")
97
+ return False
98
+
99
+ async def _send_impl(self, message: OutboundMessage) -> str:
100
+ """Send an SMS message."""
101
+ client = await self._get_client()
102
+
103
+ data = {
104
+ "To": message.recipient_id,
105
+ "From": self.from_number,
106
+ "Body": message.content,
107
+ }
108
+
109
+ response = await client.post(
110
+ f"{self.BASE_URL}/Accounts/{self.account_sid}/Messages.json",
111
+ data=data,
112
+ )
113
+
114
+ if response.status_code not in (200, 201):
115
+ raise Exception(f"SMS send failed: {response.text}")
116
+
117
+ result = response.json()
118
+ return result.get("sid", "")
119
+
120
+ async def _receive_impl(self) -> AsyncIterator[InboundMessage]:
121
+ """
122
+ Receive SMS messages. Twilio uses webhooks for incoming messages.
123
+ This fetches recent messages via API for polling mode.
124
+ """
125
+ client = await self._get_client()
126
+
127
+ response = await client.get(
128
+ f"{self.BASE_URL}/Accounts/{self.account_sid}/Messages.json",
129
+ params={"To": self.from_number, "PageSize": 20},
130
+ )
131
+
132
+ if response.status_code != 200:
133
+ return
134
+
135
+ data = response.json()
136
+
137
+ for msg in data.get("messages", []):
138
+ if msg.get("direction") != "inbound":
139
+ continue
140
+
141
+ yield InboundMessage(
142
+ message_id=msg.get("sid", ""),
143
+ platform="sms",
144
+ sender_id=msg.get("from", ""),
145
+ sender_name=msg.get("from", ""),
146
+ content=msg.get("body", ""),
147
+ raw=msg,
148
+ )
149
+
150
+ async def send_mms(
151
+ self,
152
+ to: str,
153
+ body: str,
154
+ media_url: str,
155
+ ) -> str:
156
+ """Send an MMS with media attachment."""
157
+ client = await self._get_client()
158
+
159
+ data = {
160
+ "To": to,
161
+ "From": self.from_number,
162
+ "Body": body,
163
+ "MediaUrl": media_url,
164
+ }
165
+
166
+ response = await client.post(
167
+ f"{self.BASE_URL}/Accounts/{self.account_sid}/Messages.json",
168
+ data=data,
169
+ )
170
+
171
+ if response.status_code not in (200, 201):
172
+ raise Exception(f"MMS send failed: {response.text}")
173
+
174
+ result = response.json()
175
+ return result.get("sid", "")
@@ -0,0 +1,208 @@
1
+ """TeamsConnector — Microsoft Teams integration."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import logging
6
+ from typing import AsyncIterator
7
+
8
+ import httpx
9
+
10
+ from integrations.base import (
11
+ BaseConnector,
12
+ ConnectorCapability,
13
+ ConnectorInfo,
14
+ InboundMessage,
15
+ OutboundMessage,
16
+ )
17
+
18
+ logger = logging.getLogger("hyperclaw.integrations.teams")
19
+
20
+
21
+ class TeamsConnector(BaseConnector):
22
+ """
23
+ Microsoft Teams connector.
24
+
25
+ Supports two modes:
26
+ 1. Webhook mode: Simple outbound only via webhook_url
27
+ 2. Bot mode: Full bi-directional via app_id + app_password
28
+
29
+ Required config (one of):
30
+ - webhook_url: Teams Incoming Webhook URL (simple mode)
31
+ - app_id + app_password: Bot Framework credentials (full mode)
32
+ """
33
+
34
+ BOT_FRAMEWORK_URL = "https://login.microsoftonline.com/botframework.com/oauth2/v2.0/token"
35
+
36
+ def __init__(self, config: dict) -> None:
37
+ self.config = config
38
+ self.webhook_url = config.get("webhook_url", "")
39
+ self.app_id = config.get("app_id", "")
40
+ self.app_password = config.get("app_password", "")
41
+
42
+ if config.get("enabled", False):
43
+ if not self.webhook_url and not (self.app_id and self.app_password):
44
+ raise ValueError(
45
+ "TeamsConnector requires webhook_url OR (app_id + app_password)"
46
+ )
47
+
48
+ self._client: httpx.AsyncClient | None = None
49
+ self._access_token: str | None = None
50
+
51
+ @property
52
+ def info(self) -> ConnectorInfo:
53
+ return ConnectorInfo(
54
+ connector_id="teams",
55
+ platform="teams",
56
+ category="messaging",
57
+ capabilities=frozenset(
58
+ [
59
+ ConnectorCapability.SEND_MESSAGE,
60
+ ConnectorCapability.RECEIVE_MESSAGE,
61
+ ConnectorCapability.FILE_UPLOAD,
62
+ ConnectorCapability.WEBHOOKS,
63
+ ConnectorCapability.ACTION,
64
+ ]
65
+ ),
66
+ auth_type="oauth2" if self.app_id else "webhook_token",
67
+ rate_limit_per_minute=60,
68
+ supports_threads=True,
69
+ supports_attachments=True,
70
+ supports_reactions=True,
71
+ )
72
+
73
+ async def _get_client(self) -> httpx.AsyncClient:
74
+ if self._client is None:
75
+ self._client = httpx.AsyncClient(timeout=30.0)
76
+ return self._client
77
+
78
+ async def health(self) -> bool:
79
+ """Check if connector is configured."""
80
+ # Webhook mode: just check URL is set
81
+ if self.webhook_url:
82
+ return bool(self.webhook_url)
83
+
84
+ # Bot mode: try to get access token
85
+ try:
86
+ await self._get_bot_token()
87
+ return True
88
+ except Exception as e:
89
+ logger.error(f"Teams health check failed: {e}")
90
+ return False
91
+
92
+ async def _get_bot_token(self) -> str:
93
+ """Get Bot Framework access token."""
94
+ if self._access_token:
95
+ return self._access_token
96
+
97
+ client = await self._get_client()
98
+
99
+ response = await client.post(
100
+ self.BOT_FRAMEWORK_URL,
101
+ data={
102
+ "grant_type": "client_credentials",
103
+ "client_id": self.app_id,
104
+ "client_secret": self.app_password,
105
+ "scope": "https://api.botframework.com/.default",
106
+ },
107
+ )
108
+
109
+ if response.status_code != 200:
110
+ raise Exception(f"Failed to get Teams bot token: {response.text}")
111
+
112
+ data = response.json()
113
+ self._access_token = data.get("access_token", "")
114
+ return self._access_token
115
+
116
+ async def _send_impl(self, message: OutboundMessage) -> str:
117
+ """Send a message via Teams."""
118
+ if self.webhook_url:
119
+ return await self._send_via_webhook(message)
120
+ else:
121
+ return await self._send_via_bot(message)
122
+
123
+ async def _send_via_webhook(self, message: OutboundMessage) -> str:
124
+ """Send via incoming webhook."""
125
+ client = await self._get_client()
126
+
127
+ payload = {"text": message.content}
128
+
129
+ response = await client.post(self.webhook_url, json=payload)
130
+
131
+ if response.status_code not in (200, 201):
132
+ raise Exception(f"Teams webhook send failed: {response.text}")
133
+
134
+ return "webhook_sent"
135
+
136
+ async def _send_via_bot(self, message: OutboundMessage) -> str:
137
+ """Send via Bot Framework."""
138
+ client = await self._get_client()
139
+ token = await self._get_bot_token()
140
+
141
+ # Bot Framework requires conversation reference
142
+ # This is a simplified implementation
143
+ service_url = self.config.get("service_url", "https://smba.trafficmanager.net/")
144
+ conversation_id = message.recipient_id
145
+
146
+ payload = {
147
+ "type": "message",
148
+ "text": message.content,
149
+ }
150
+
151
+ response = await client.post(
152
+ f"{service_url}v3/conversations/{conversation_id}/activities",
153
+ json=payload,
154
+ headers={"Authorization": f"Bearer {token}"},
155
+ )
156
+
157
+ if response.status_code not in (200, 201):
158
+ raise Exception(f"Teams bot send failed: {response.text}")
159
+
160
+ data = response.json()
161
+ return data.get("id", "")
162
+
163
+ async def _receive_impl(self) -> AsyncIterator[InboundMessage]:
164
+ """
165
+ Receive messages. Teams uses Bot Framework webhooks.
166
+ """
167
+ return
168
+ yield
169
+
170
+ async def send_adaptive_card(
171
+ self,
172
+ webhook_url: str | None,
173
+ card: dict,
174
+ ) -> str:
175
+ """Send an Adaptive Card."""
176
+ client = await self._get_client()
177
+
178
+ url = webhook_url or self.webhook_url
179
+ if not url:
180
+ raise ValueError("No webhook URL provided")
181
+
182
+ payload = {
183
+ "type": "message",
184
+ "attachments": [
185
+ {
186
+ "contentType": "application/vnd.microsoft.card.adaptive",
187
+ "content": card,
188
+ }
189
+ ],
190
+ }
191
+
192
+ response = await client.post(url, json=payload)
193
+
194
+ if response.status_code not in (200, 201):
195
+ raise Exception(f"Teams adaptive card send failed: {response.text}")
196
+
197
+ return "card_sent"
198
+
199
+ async def _action_impl(self, action_name: str, params: dict) -> dict:
200
+ """Execute Teams-specific actions."""
201
+ if action_name == "send_adaptive_card":
202
+ result = await self.send_adaptive_card(
203
+ params.get("webhook_url"),
204
+ params["card"],
205
+ )
206
+ return {"status": "sent", "id": result}
207
+
208
+ raise ValueError(f"Unknown action: {action_name}")